first test passing

pull/1864/head
Rich Harris 7 years ago
parent 699328b165
commit 6e51bb9e4b

@ -0,0 +1,615 @@
function append(target, node) {
target.appendChild(node);
}
function insert(target, node, anchor) {
target.insertBefore(node, anchor);
}
function detachNode(node) {
node.parentNode.removeChild(node);
}
function detachBetween(before, after) {
while (before.nextSibling && before.nextSibling !== after) {
before.parentNode.removeChild(before.nextSibling);
}
}
function detachBefore(after) {
while (after.previousSibling) {
after.parentNode.removeChild(after.previousSibling);
}
}
function detachAfter(before) {
while (before.nextSibling) {
before.parentNode.removeChild(before.nextSibling);
}
}
function reinsertBetween(before, after, target) {
while (before.nextSibling && before.nextSibling !== after) {
target.appendChild(before.parentNode.removeChild(before.nextSibling));
}
}
function reinsertChildren(parent, target) {
while (parent.firstChild) target.appendChild(parent.firstChild);
}
function reinsertAfter(before, target) {
while (before.nextSibling) target.appendChild(before.nextSibling);
}
function reinsertBefore(after, target) {
var parent = after.parentNode;
while (parent.firstChild !== after) target.appendChild(parent.firstChild);
}
function destroyEach(iterations, detach) {
for (var i = 0; i < iterations.length; i += 1) {
if (iterations[i]) iterations[i].d(detach);
}
}
function createFragment() {
return document.createDocumentFragment();
}
function createElement(name) {
return document.createElement(name);
}
function createSvgElement(name) {
return document.createElementNS('http://www.w3.org/2000/svg', name);
}
function createText(data) {
return document.createTextNode(data);
}
function createComment() {
return document.createComment('');
}
function addListener(node, event, handler, options) {
node.addEventListener(event, handler, options);
}
function removeListener(node, event, handler, options) {
node.removeEventListener(event, handler, options);
}
function setAttribute(node, attribute, value) {
if (value == null) node.removeAttribute(attribute);
else node.setAttribute(attribute, value);
}
function setAttributes(node, attributes) {
for (var key in attributes) {
if (key === 'style') {
node.style.cssText = attributes[key];
} else if (key in node) {
node[key] = attributes[key];
} else {
setAttribute(node, key, attributes[key]);
}
}
}
function setCustomElementData(node, prop, value) {
if (prop in node) {
node[prop] = value;
} else if (value) {
setAttribute(node, prop, value);
} else {
node.removeAttribute(prop);
}
}
function setXlinkAttribute(node, attribute, value) {
node.setAttributeNS('http://www.w3.org/1999/xlink', attribute, value);
}
function getBindingGroupValue(group) {
var value = [];
for (var i = 0; i < group.length; i += 1) {
if (group[i].checked) value.push(group[i].__value);
}
return value;
}
function toNumber(value) {
return value === '' ? undefined : +value;
}
function timeRangesToArray(ranges) {
var array = [];
for (var i = 0; i < ranges.length; i += 1) {
array.push({ start: ranges.start(i), end: ranges.end(i) });
}
return array;
}
function children (element) {
return Array.from(element.childNodes);
}
function claimElement (nodes, name, attributes, svg) {
for (var i = 0; i < nodes.length; i += 1) {
var node = nodes[i];
if (node.nodeName === name) {
for (var j = 0; j < node.attributes.length; j += 1) {
var attribute = node.attributes[j];
if (!attributes[attribute.name]) node.removeAttribute(attribute.name);
}
return nodes.splice(i, 1)[0]; // TODO strip unwanted attributes
}
}
return svg ? createSvgElement(name) : createElement(name);
}
function claimText (nodes, data) {
for (var i = 0; i < nodes.length; i += 1) {
var node = nodes[i];
if (node.nodeType === 3) {
node.data = data;
return nodes.splice(i, 1)[0];
}
}
return createText(data);
}
function setData(text, data) {
text.data = '' + data;
}
function setInputType(input, type) {
try {
input.type = type;
} catch (e) {}
}
function setStyle(node, key, value) {
node.style.setProperty(key, value);
}
function selectOption(select, value) {
for (var i = 0; i < select.options.length; i += 1) {
var option = select.options[i];
if (option.__value === value) {
option.selected = true;
return;
}
}
}
function selectOptions(select, value) {
for (var i = 0; i < select.options.length; i += 1) {
var option = select.options[i];
option.selected = ~value.indexOf(option.__value);
}
}
function selectValue(select) {
var selectedOption = select.querySelector(':checked') || select.options[0];
return selectedOption && selectedOption.__value;
}
function selectMultipleValue(select) {
return [].map.call(select.querySelectorAll(':checked'), function(option) {
return option.__value;
});
}
function addResizeListener(element, fn) {
if (getComputedStyle(element).position === 'static') {
element.style.position = 'relative';
}
const object = document.createElement('object');
object.setAttribute('style', 'display: block; position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden; pointer-events: none; z-index: -1;');
object.type = 'text/html';
let win;
object.onload = () => {
win = object.contentDocument.defaultView;
win.addEventListener('resize', fn);
};
if (/Trident/.test(navigator.userAgent)) {
element.appendChild(object);
object.data = 'about:blank';
} else {
object.data = 'about:blank';
element.appendChild(object);
}
return {
cancel: () => {
win && win.removeEventListener && win.removeEventListener('resize', fn);
element.removeChild(object);
}
};
}
function toggleClass(element, name, toggle) {
element.classList.toggle(name, !!toggle);
}
let update_scheduled = false;
const dirty_components = [];
function schedule_update(component) {
dirty_components.push(component);
if (!update_scheduled) {
update_scheduled = true;
queueMicrotask(flush);
}
}
function flush() {
while (dirty_components.length) {
dirty_components.pop().__update();
}
update_scheduled = false;
}
function queueMicrotask(callback) {
Promise.resolve().then(callback);
}
function noop() {}
function assign(tar, src) {
for (var k in src) tar[k] = src[k];
return tar;
}
function assignTrue(tar, src) {
for (var k in src) tar[k] = 1;
return tar;
}
function isPromise(value) {
return value && typeof value.then === 'function';
}
function callAfter(fn, i) {
if (i === 0) fn();
return () => {
if (!--i) fn();
};
}
function addLoc(element, file, line, column, char) {
element.__svelte_meta = {
loc: { file, line, column, char }
};
}
function exclude(src, prop) {
const tar = {};
for (const k in src) k === prop || (tar[k] = src[k]);
return tar;
}
function run(fn) {
fn();
}
function linear(t) {
return t;
}
function generateRule({ a, b, delta, duration }, ease, fn) {
const step = 16.666 / duration;
let keyframes = '{\n';
for (let p = 0; p <= 1; p += step) {
const t = a + delta * ease(p);
keyframes += p * 100 + `%{${fn(t, 1 - t)}}\n`;
}
return keyframes + `100% {${fn(b, 1 - b)}}\n}`;
}
// https://github.com/darkskyapp/string-hash/blob/master/index.js
function hash(str) {
let hash = 5381;
let i = str.length;
while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i);
return hash >>> 0;
}
function wrapTransition(component, node, fn, params, intro) {
let obj = fn.call(component, node, params);
let duration;
let ease;
let cssText;
let initialised = false;
return {
t: intro ? 0 : 1,
running: false,
program: null,
pending: null,
run(b, callback) {
if (typeof obj === 'function') {
transitionManager.wait().then(() => {
obj = obj();
this._run(b, callback);
});
} else {
this._run(b, callback);
}
},
_run(b, callback) {
duration = obj.duration || 300;
ease = obj.easing || linear;
const program = {
start: window.performance.now() + (obj.delay || 0),
b,
callback: callback || noop
};
if (intro && !initialised) {
if (obj.css && obj.delay) {
cssText = node.style.cssText;
node.style.cssText += obj.css(0, 1);
}
if (obj.tick) obj.tick(0, 1);
initialised = true;
}
if (!b) {
program.group = outros.current;
outros.current.remaining += 1;
}
if (obj.delay) {
this.pending = program;
} else {
this.start(program);
}
if (!this.running) {
this.running = true;
transitionManager.add(this);
}
},
start(program) {
component.fire(`${program.b ? 'intro' : 'outro'}.start`, { node });
program.a = this.t;
program.delta = program.b - program.a;
program.duration = duration * Math.abs(program.b - program.a);
program.end = program.start + program.duration;
if (obj.css) {
if (obj.delay) node.style.cssText = cssText;
const rule = generateRule(program, ease, obj.css);
transitionManager.addRule(rule, program.name = '__svelte_' + hash(rule));
node.style.animation = (node.style.animation || '')
.split(', ')
.filter(anim => anim && (program.delta < 0 || !/__svelte/.test(anim)))
.concat(`${program.name} ${program.duration}ms linear 1 forwards`)
.join(', ');
}
this.program = program;
this.pending = null;
},
update(now) {
const program = this.program;
if (!program) return;
const p = now - program.start;
this.t = program.a + program.delta * ease(p / program.duration);
if (obj.tick) obj.tick(this.t, 1 - this.t);
},
done() {
const program = this.program;
this.t = program.b;
if (obj.tick) obj.tick(this.t, 1 - this.t);
component.fire(`${program.b ? 'intro' : 'outro'}.end`, { node });
if (!program.b && !program.invalidated) {
program.group.callbacks.push(() => {
program.callback();
if (obj.css) transitionManager.deleteRule(node, program.name);
});
if (--program.group.remaining === 0) {
program.group.callbacks.forEach(run);
}
} else {
if (obj.css) transitionManager.deleteRule(node, program.name);
}
this.running = !!this.pending;
},
abort(reset) {
if (this.program) {
if (reset && obj.tick) obj.tick(1, 0);
if (obj.css) transitionManager.deleteRule(node, this.program.name);
this.program = this.pending = null;
this.running = false;
}
},
invalidate() {
if (this.program) {
this.program.invalidated = true;
}
}
};
}
let outros = {};
function groupOutros() {
outros.current = {
remaining: 0,
callbacks: []
};
}
var transitionManager = {
running: false,
transitions: [],
bound: null,
stylesheet: null,
activeRules: {},
promise: null,
add(transition) {
this.transitions.push(transition);
if (!this.running) {
this.running = true;
requestAnimationFrame(this.bound || (this.bound = this.next.bind(this)));
}
},
addRule(rule, name) {
if (!this.stylesheet) {
const style = createElement('style');
document.head.appendChild(style);
transitionManager.stylesheet = style.sheet;
}
if (!this.activeRules[name]) {
this.activeRules[name] = true;
this.stylesheet.insertRule(`@keyframes ${name} ${rule}`, this.stylesheet.cssRules.length);
}
},
next() {
this.running = false;
const now = window.performance.now();
let i = this.transitions.length;
while (i--) {
const transition = this.transitions[i];
if (transition.program && now >= transition.program.end) {
transition.done();
}
if (transition.pending && now >= transition.pending.start) {
transition.start(transition.pending);
}
if (transition.running) {
transition.update(now);
this.running = true;
} else if (!transition.pending) {
this.transitions.splice(i, 1);
}
}
if (this.running) {
requestAnimationFrame(this.bound);
} else if (this.stylesheet) {
let i = this.stylesheet.cssRules.length;
while (i--) this.stylesheet.deleteRule(i);
this.activeRules = {};
}
},
deleteRule(node, name) {
node.style.animation = node.style.animation
.split(', ')
.filter(anim => anim && anim.indexOf(name) === -1)
.join(', ');
},
wait() {
if (!transitionManager.promise) {
transitionManager.promise = Promise.resolve();
transitionManager.promise.then(() => {
transitionManager.promise = null;
});
}
return transitionManager.promise;
}
};
class SvelteComponent {
constructor(options) {
this.__get_state = this.__init(
fn => this.__inject_props = fn
);
this.__dirty = null;
if (options.props) {
this.__inject_props(options.props);
}
if (options.target) {
this.__mount(options.target);
}
}
$on(eventName, callback) {
}
$destroy() {
this.__destroy(true);
}
__make_dirty() {
if (this.__dirty) return;
this.__dirty = {};
schedule_update(this);
}
__mount(target, anchor) {
this.__fragment = this.__create_fragment(this.__get_state());
this.__fragment.c();
this.__fragment.m(target, anchor);
}
__set(key, value) {
this.__inject_props({ [key]: value });
this.__make_dirty();
this.__dirty[key] = true;
}
__update() {
this.__fragment.p(this.__dirty, this.__get_state());
this.__dirty = null;
}
__destroy(detach) {
this.__fragment.d(detach);
}
}
export { append, insert, detachNode, detachBetween, detachBefore, detachAfter, reinsertBetween, reinsertChildren, reinsertAfter, reinsertBefore, destroyEach, createFragment, createElement, createSvgElement, createText, createComment, addListener, removeListener, setAttribute, setAttributes, setCustomElementData, setXlinkAttribute, getBindingGroupValue, toNumber, timeRangesToArray, children, claimElement, claimText, setData, setInputType, setStyle, selectOption, selectOptions, selectValue, selectMultipleValue, addResizeListener, toggleClass, schedule_update, flush, linear, generateRule, hash, wrapTransition, outros, groupOutros, transitionManager, noop, assign, assignTrue, isPromise, callAfter, addLoc, exclude, run, SvelteComponent };

@ -1 +1,2 @@
--bail
test/test.js

@ -20,7 +20,7 @@ export default [
json(),
typescript({
include: 'src/**',
exclude: 'src/shared/**',
exclude: 'src/internal/**',
typescript: require('typescript')
})
],
@ -40,7 +40,7 @@ export default [
commonjs(),
buble({
include: 'src/**',
exclude: 'src/shared/**',
exclude: 'src/internal/**',
target: {
node: 4
}
@ -79,11 +79,11 @@ export default [
experimentalCodeSplitting: true
},
/* shared.js */
/* internal.js */
{
input: 'src/shared/index.js',
input: 'src/internal/index.js',
output: {
file: 'shared.js',
file: 'internal.js',
format: 'es'
}
}

@ -10,7 +10,7 @@ import namespaces from '../utils/namespaces';
import { removeNode } from '../utils/removeNode';
import nodeToString from '../utils/nodeToString';
import wrapModule from './wrapModule';
import annotateWithScopes from '../utils/annotateWithScopes';
import { createScopes } from '../utils/annotateWithScopes';
import getName from '../utils/getName';
import Stylesheet from './css/Stylesheet';
import { test } from '../config';
@ -67,7 +67,7 @@ function getIndentExclusionRanges(node: Node) {
return ranges;
}
function removeIndentation(
function increaseIndentation(
code: MagicString,
start: number,
end: number,
@ -75,13 +75,16 @@ function removeIndentation(
ranges: Node[]
) {
const str = code.original.slice(start, end);
const pattern = new RegExp(`^${indentationLevel}`, 'gm');
let match;
const lines = str.split('\n');
while (match = pattern.exec(str)) {
// TODO bail if we're inside an exclusion range
code.remove(start + match.index, start + match.index + indentationLevel.length);
let c = start;
lines.forEach(line => {
if (line) {
code.prependRight(c, '\t\t\t'); // TODO detect indentation
}
c += line.length + 1;
});
}
// We need to tell estree-walker that it should always
@ -131,7 +134,7 @@ export default class Component {
actions: Set<string>;
};
declarations: Declaration[];
declarations: string[];
refCallees: Node[];
@ -342,85 +345,12 @@ export default class Component {
return sigil.slice(1) + name;
});
let importedHelpers;
if (options.shared) {
if (format !== 'es' && format !== 'cjs') {
throw new Error(`Components with shared helpers must be compiled with \`format: 'es'\` or \`format: 'cjs'\``);
}
importedHelpers = Array.from(helpers).sort().map(name => {
const importedHelpers = Array.from(helpers).concat('SvelteComponent').sort().map(name => {
const alias = this.alias(name);
return { name, alias };
});
} else {
let inlineHelpers = '';
const component = this;
importedHelpers = [];
helpers.forEach(name => {
const str = shared[name];
const code = new MagicString(str);
const expression = parseExpressionAt(str, 0);
let { scope } = annotateWithScopes(expression);
walk(expression, {
enter(node: Node, parent: Node) {
if (node._scope) scope = node._scope;
if (
node.type === 'Identifier' &&
isReference(node, parent) &&
!scope.has(node.name)
) {
if (node.name in shared) {
// this helper function depends on another one
const dependency = node.name;
helpers.add(dependency);
const alias = component.alias(dependency);
if (alias !== node.name) {
code.overwrite(node.start, node.end, alias);
}
}
}
},
leave(node: Node) {
if (node._scope) scope = scope.parent;
},
});
if (name === 'transitionManager' || name === 'outros') {
// special case
const global = name === 'outros'
? `_svelteOutros`
: `_svelteTransitionManager`;
inlineHelpers += `\n\nvar ${this.alias(name)} = window.${global} || (window.${global} = ${code});\n\n`;
} else if (name === 'escaped' || name === 'missingComponent' || name === 'invalidAttributeNameCharacter') {
// vars are an awkward special case... would be nice to avoid this
const alias = this.alias(name);
inlineHelpers += `\n\nconst ${alias} = ${code};`
} else {
const alias = this.alias(expression.id.name);
if (alias !== expression.id.name) {
code.overwrite(expression.id.start, expression.id.end, alias);
}
inlineHelpers += `\n\n${code}`;
}
});
result += inlineHelpers;
}
const sharedPath = options.shared === true
? 'svelte/shared.js'
: options.shared || '';
const sharedPath = options.shared || 'svelte/internal.js';
const module = wrapModule(result, format, name, options, banner, sharedPath, importedHelpers, this.imports, this.shorthandImports, this.source);
@ -571,306 +501,6 @@ export default class Component {
});
}
processDefaultExport(node, indentExclusionRanges) {
const { templateProperties, source, code } = this;
if (node.declaration.type !== 'ObjectExpression') {
this.error(node.declaration, {
code: `invalid-default-export`,
message: `Default export must be an object literal`
});
}
checkForComputedKeys(this, node.declaration.properties);
checkForDupes(this, node.declaration.properties);
const props = this.properties;
node.declaration.properties.forEach((prop: Node) => {
props.set(getName(prop.key), prop);
});
const validPropList = Object.keys(propValidators);
// ensure all exported props are valid
node.declaration.properties.forEach((prop: Node) => {
const name = getName(prop.key);
const propValidator = propValidators[name];
if (propValidator) {
propValidator(this, prop);
} else {
const match = fuzzymatch(name, validPropList);
if (match) {
this.error(prop, {
code: `unexpected-property`,
message: `Unexpected property '${name}' (did you mean '${match}'?)`
});
} else if (/FunctionExpression/.test(prop.value.type)) {
this.error(prop, {
code: `unexpected-property`,
message: `Unexpected property '${name}' (did you mean to include it in 'methods'?)`
});
} else {
this.error(prop, {
code: `unexpected-property`,
message: `Unexpected property '${name}'`
});
}
}
});
if (props.has('namespace')) {
const ns = nodeToString(props.get('namespace').value);
this.namespace = namespaces[ns] || ns;
}
node.declaration.properties.forEach((prop: Node) => {
templateProperties[getName(prop.key)] = prop;
});
['helpers', 'events', 'components', 'transitions', 'actions', 'animations'].forEach(key => {
if (templateProperties[key]) {
templateProperties[key].value.properties.forEach((prop: Node) => {
this[key].add(getName(prop.key));
});
}
});
const addArrowFunctionExpression = (type: string, name: string, node: Node) => {
const { body, params, async } = node;
const fnKeyword = async ? 'async function' : 'function';
const paramString = params.length ?
`[✂${params[0].start}-${params[params.length - 1].end}✂]` :
``;
const block = body.type === 'BlockStatement'
? deindent`
${fnKeyword} ${name}(${paramString}) [${body.start}-${body.end}]
`
: deindent`
${fnKeyword} ${name}(${paramString}) {
return [${body.start}-${body.end}];
}
`;
this.declarations.push({ type, name, block, node });
};
const addFunctionExpression = (type: string, name: string, node: Node) => {
const { async } = node;
const fnKeyword = async ? 'async function' : 'function';
let c = node.start;
while (this.source[c] !== '(') c += 1;
const block = deindent`
${fnKeyword} ${name}[${c}-${node.end}];
`;
this.declarations.push({ type, name, block, node });
};
const addValue = (type: string, name: string, node: Node) => {
const block = deindent`
var ${name} = [${node.start}-${node.end}];
`;
this.declarations.push({ type, name, block, node });
};
const addDeclaration = (
type: string,
key: string,
node: Node,
allowShorthandImport?: boolean,
disambiguator?: string,
conflicts?: Record<string, boolean>
) => {
const qualified = disambiguator ? `${disambiguator}-${key}` : key;
if (node.type === 'Identifier' && node.name === key) {
this.templateVars.set(qualified, key);
return;
}
let deconflicted = key;
if (conflicts) while (deconflicted in conflicts) deconflicted += '_'
let name = this.getUniqueName(deconflicted);
this.templateVars.set(qualified, name);
if (allowShorthandImport && node.type === 'Literal' && typeof node.value === 'string') {
this.shorthandImports.push({ name, source: node.value });
return;
}
// deindent
const indentationLevel = getIndentationLevel(source, node.start);
if (indentationLevel) {
removeIndentation(code, node.start, node.end, indentationLevel, indentExclusionRanges);
}
if (node.type === 'ArrowFunctionExpression') {
addArrowFunctionExpression(type, name, node);
} else if (node.type === 'FunctionExpression') {
addFunctionExpression(type, name, node);
} else {
addValue(type, name, node);
}
};
if (templateProperties.components) {
templateProperties.components.value.properties.forEach((property: Node) => {
addDeclaration('components', getName(property.key), property.value, true, 'components');
});
}
if (templateProperties.computed) {
const dependencies = new Map();
const fullStateComputations = [];
templateProperties.computed.value.properties.forEach((prop: Node) => {
const key = getName(prop.key);
const value = prop.value;
addDeclaration('computed', key, value, false, 'computed', {
state: true,
changed: true
});
const param = value.params[0];
const hasRestParam = (
param.properties &&
param.properties.some(prop => prop.type === 'RestElement')
);
if (param.type !== 'ObjectPattern' || hasRestParam) {
fullStateComputations.push({ key, deps: null, hasRestParam });
} else {
const deps = param.properties.map(prop => prop.key.name);
deps.forEach(dep => {
this.expectedProperties.add(dep);
});
dependencies.set(key, deps);
}
});
const visited = new Set();
const visit = (key: string) => {
if (!dependencies.has(key)) return; // not a computation
if (visited.has(key)) return;
visited.add(key);
const deps = dependencies.get(key);
deps.forEach(visit);
this.computations.push({ key, deps, hasRestParam: false });
const prop = templateProperties.computed.value.properties.find((prop: Node) => getName(prop.key) === key);
};
templateProperties.computed.value.properties.forEach((prop: Node) =>
visit(getName(prop.key))
);
if (fullStateComputations.length > 0) {
this.computations.push(...fullStateComputations);
}
}
if (templateProperties.data) {
addDeclaration('data', 'data', templateProperties.data.value);
}
if (templateProperties.events) {
templateProperties.events.value.properties.forEach((property: Node) => {
addDeclaration('events', getName(property.key), property.value, false, 'events');
});
}
if (templateProperties.helpers) {
templateProperties.helpers.value.properties.forEach((property: Node) => {
addDeclaration('helpers', getName(property.key), property.value, false, 'helpers');
});
}
if (templateProperties.methods) {
addDeclaration('methods', 'methods', templateProperties.methods.value);
templateProperties.methods.value.properties.forEach(property => {
this.methods.add(getName(property.key));
});
}
if (templateProperties.namespace) {
const ns = nodeToString(templateProperties.namespace.value);
this.namespace = namespaces[ns] || ns;
}
if (templateProperties.oncreate) {
addDeclaration('oncreate', 'oncreate', templateProperties.oncreate.value);
}
if (templateProperties.ondestroy) {
addDeclaration('ondestroy', 'ondestroy', templateProperties.ondestroy.value);
}
if (templateProperties.onstate) {
addDeclaration('onstate', 'onstate', templateProperties.onstate.value);
}
if (templateProperties.onupdate) {
addDeclaration('onupdate', 'onupdate', templateProperties.onupdate.value);
}
if (templateProperties.preload) {
addDeclaration('preload', 'preload', templateProperties.preload.value);
}
if (templateProperties.props) {
this.props = templateProperties.props.value.elements.map((element: Node) => nodeToString(element));
}
if (templateProperties.setup) {
addDeclaration('setup', 'setup', templateProperties.setup.value);
}
if (templateProperties.store) {
addDeclaration('store', 'store', templateProperties.store.value);
}
if (templateProperties.tag) {
this.tag = nodeToString(templateProperties.tag.value);
}
if (templateProperties.transitions) {
templateProperties.transitions.value.properties.forEach((property: Node) => {
addDeclaration('transitions', getName(property.key), property.value, false, 'transitions');
});
}
if (templateProperties.animations) {
templateProperties.animations.value.properties.forEach((property: Node) => {
addDeclaration('animations', getName(property.key), property.value, false, 'animations');
});
}
if (templateProperties.actions) {
templateProperties.actions.value.properties.forEach((property: Node) => {
addDeclaration('actions', getName(property.key), property.value, false, 'actions');
});
}
this.defaultExport = node;
}
walkJs() {
const { js } = this.ast;
if (!js) return;
@ -882,10 +512,11 @@ export default class Component {
const indentationLevel = getIndentationLevel(source, js.content.body[0].start);
const indentExclusionRanges = getIndentExclusionRanges(js.content);
const { scope, globals } = annotateWithScopes(js.content);
const { scope, globals } = createScopes(js.content);
scope.declarations.forEach(name => {
this.userVars.add(name);
this.declarations.push(name);
});
globals.forEach(name => {
@ -895,19 +526,15 @@ export default class Component {
const body = js.content.body.slice(); // slice, because we're going to be mutating the original
body.forEach(node => {
// check there are no named exports
if (node.type === 'ExportNamedDeclaration') {
this.error(node, {
code: `named-export`,
message: `A component can only have a default export`
});
}
if (node.type === 'ExportDefaultDeclaration') {
this.processDefaultExport(node, indentExclusionRanges);
this.error(node, {
code: `default-export`,
message: `A component cannot have a default export`
})
}
// imports need to be hoisted out of the IIFE
// TODO hoist other stuff where possible
else if (node.type === 'ImportDeclaration') {
removeNode(code, js.content, node);
imports.push(node);
@ -918,15 +545,6 @@ export default class Component {
}
});
if (indentationLevel) {
if (this.defaultExport) {
removeIndentation(code, js.content.start, this.defaultExport.start, indentationLevel, indentExclusionRanges);
removeIndentation(code, this.defaultExport.end, js.content.end, indentationLevel, indentExclusionRanges);
} else {
removeIndentation(code, js.content.start, js.content.end, indentationLevel, indentExclusionRanges);
}
}
let a = js.content.start;
while (/\s/.test(source[a])) a += 1;

@ -13,13 +13,6 @@ export default class Action extends Node {
component.used.actions.add(this.name);
if (!component.actions.has(this.name)) {
component.error(this, {
code: `missing-action`,
message: `Missing action '${this.name}'`
});
}
this.expression = info.expression
? new Expression(component, this, scope, info.expression)
: null;

@ -212,7 +212,7 @@ export default class Block {
return new Block(Object.assign({}, this, { key: null }, options, { parent: this }));
}
toString() {
getContents(localKey?: string) {
const { dev } = this.renderer.options;
if (this.hasIntroMethod || this.hasOutroMethod) {
@ -233,9 +233,7 @@ export default class Block {
const properties = new CodeBuilder();
let localKey;
if (this.key) {
localKey = this.getUniqueName('key');
if (localKey) {
properties.addBlock(`key: ${localKey},`);
}
@ -360,8 +358,6 @@ export default class Block {
}
return deindent`
${this.comment && `// ${escape(this.comment)}`}
function ${this.name}(#component${this.key ? `, ${localKey}` : ''}, ctx) {
${this.variables.size > 0 &&
`var ${Array.from(this.variables.keys())
.map(key => {
@ -375,6 +371,18 @@ export default class Block {
return {
${properties}
};
`.replace(/(#+)(\w*)/g, (match: string, sigil: string, name: string) => {
return sigil === '#' ? this.alias(name) : sigil.slice(1) + name;
});
}
toString() {
const localKey = this.key && this.getUniqueName('key');
return deindent`
${this.comment && `// ${escape(this.comment)}`}
function ${this.name}(#component${this.key ? `, ${localKey}` : ''}, ctx) {
${this.getContents(localKey)}
}
`.replace(/(#+)(\w*)/g, (match: string, sigil: string, name: string) => {
return sigil === '#' ? this.alias(name) : sigil.slice(1) + name;

@ -43,7 +43,7 @@ export default class Renderer {
// main block
this.block = new Block({
renderer: this,
name: '@create_main_fragment',
name: null,
key: null,
bindings: new Map(),
@ -64,8 +64,6 @@ export default class Renderer {
null
);
this.blocks.push(this.block);
this.blocks.forEach(block => {
if (typeof block !== 'string') {
block.assignVariableNames();

@ -30,53 +30,6 @@ export default function dom(
if (options.customElement) block.builders.create.addLine(`this.c = @noop;`);
const builder = new CodeBuilder();
const computationBuilder = new CodeBuilder();
const computationDeps = new Set();
if (computations.length) {
computations.forEach(({ key, deps, hasRestParam }) => {
if (renderer.readonly.has(key)) {
// <svelte:window> bindings
throw new Error(
`Cannot have a computed value '${key}' that clashes with a read-only property`
);
}
renderer.readonly.add(key);
if (deps) {
deps.forEach(dep => {
computationDeps.add(dep);
});
const condition = `${deps.map(dep => `changed.${dep}`).join(' || ')}`;
const statement = `if (this._differs(state.${key}, (state.${key} = %computed-${key}(state)))) changed.${key} = true;`;
computationBuilder.addConditional(condition, statement);
} else {
// computed property depends on entire state object —
// these must go at the end
computationBuilder.addLine(
`if (this._differs(state.${key}, (state.${key} = %computed-${key}(@exclude(state, "${key}"))))) changed.${key} = true;`
);
}
});
}
if (component.javascript) {
const componentDefinition = new CodeBuilder();
component.declarations.forEach(declaration => {
componentDefinition.addBlock(declaration.block);
});
const js = (
component.javascript[0] +
componentDefinition +
component.javascript[1]
);
builder.addBlock(js);
}
if (component.options.dev) {
builder.addLine(`const ${renderer.fileVar} = ${JSON.stringify(component.file)};`);
@ -106,139 +59,10 @@ export default function dom(
builder.addBlock(block.toString());
});
const sharedPath: string = options.shared === true
? 'svelte/shared.js'
: options.shared || '';
const proto = sharedPath
? `@proto`
: deindent`
{
${['destroy', 'get', 'fire', 'on', 'set', '_set', '_stage', '_mount', '_differs']
.map(n => `${n}: @${n}`)
.join(',\n')}
}`;
const debugName = `<${component.customElement ? component.tag : name}>`;
// generate initial state object
const expectedProperties = Array.from(component.expectedProperties);
const globals = expectedProperties.filter(prop => globalWhitelist.has(prop));
const storeProps = expectedProperties.filter(prop => prop[0] === '$');
const initialState = [];
if (globals.length > 0) {
initialState.push(`{ ${globals.map(prop => `${prop} : ${prop}`).join(', ')} }`);
}
if (storeProps.length > 0) {
initialState.push(`this.store._init([${storeProps.map(prop => `"${prop.slice(1)}"`)}])`);
}
if (templateProperties.data) {
initialState.push(`%data()`);
} else if (globals.length === 0 && storeProps.length === 0) {
initialState.push('{}');
}
initialState.push(`options.data`);
const hasInitHooks = !!(templateProperties.oncreate || templateProperties.onstate || templateProperties.onupdate);
const constructorBody = deindent`
${options.dev && deindent`
this._debugName = '${debugName}';
${!component.customElement && deindent`
if (!options || (!options.target && !options.root)) {
throw new Error("'target' is a required option");
}`}
${storeProps.length > 0 && !templateProperties.store && deindent`
if (!options.store) {
throw new Error("${debugName} references store properties, but no store was provided");
}`}
`}
@init(this, options);
${templateProperties.store && `this.store = %store();`}
${component.refs.size > 0 && `this.refs = {};`}
this._state = ${initialState.reduce((state, piece) => `@assign(${state}, ${piece})`)};
${storeProps.length > 0 && `this.store._add(this, [${storeProps.map(prop => `"${prop.slice(1)}"`)}]);`}
${renderer.metaBindings}
${computations.length && `this._recompute({ ${Array.from(computationDeps).map(dep => `${dep}: 1`).join(', ')} }, this._state);`}
${options.dev &&
Array.from(component.expectedProperties).map(prop => {
if (globalWhitelist.has(prop)) return;
if (computations.find(c => c.key === prop)) return;
const message = component.components.has(prop) ?
`${debugName} expected to find '${prop}' in \`data\`, but found it in \`components\` instead` :
`${debugName} was created without expected data property '${prop}'`;
const conditions = [`!('${prop}' in this._state)`];
if (component.customElement) conditions.push(`!('${prop}' in this.attributes)`);
return `if (${conditions.join(' && ')}) console.warn("${message}");`
})}
${renderer.bindingGroups.length &&
`this._bindingGroups = [${Array(renderer.bindingGroups.length).fill('[]').join(', ')}];`}
this._intro = ${component.options.skipIntroByDefault ? '!!options.intro' : 'true'};
${templateProperties.onstate && `this._handlers.state = [%onstate];`}
${templateProperties.onupdate && `this._handlers.update = [%onupdate];`}
${(templateProperties.ondestroy || storeProps.length) && (
`this._handlers.destroy = [${
[templateProperties.ondestroy && `%ondestroy`, storeProps.length && `@removeFromStore`].filter(Boolean).join(', ')
}];`
)}
${renderer.slots.size && `this._slotted = options.slots || {};`}
${component.customElement ?
deindent`
this.attachShadow({ mode: 'open' });
${css.code && `this.shadowRoot.innerHTML = \`<style>${escape(css.code, { onlyEscapeAtSymbol: true }).replace(/\\/g, '\\\\')}${options.dev ? `\n/*# sourceMappingURL=${css.map.toUrl()} */` : ''}</style>\`;`}
` :
(component.stylesheet.hasStyles && options.css !== false &&
`if (!document.getElementById("${component.stylesheet.id}-style")) @add_css();`)
}
${templateProperties.onstate && `%onstate.call(this, { changed: @assignTrue({}, this._state), current: this._state });`}
this._fragment = @create_main_fragment(this, this._state);
${hasInitHooks && deindent`
this.root._oncreate.push(() => {
${templateProperties.oncreate && `%oncreate.call(this);`}
this.fire("update", { changed: @assignTrue({}, this._state), current: this._state });
});
`}
${component.customElement ? deindent`
this._fragment.c();
this._fragment.${block.hasIntroMethod ? 'i' : 'm'}(this.shadowRoot, null);
if (options.target) this._mount(options.target, options.anchor);
` : deindent`
if (options.target) {
${component.options.hydratable
? deindent`
var nodes = @children(options.target);
options.hydrate ? this._fragment.l(nodes) : this._fragment.c();
nodes.forEach(@detachNode);` :
deindent`
${options.dev &&
`if (options.hydrate) throw new Error("options.hydrate only works if the component was compiled with the \`hydratable: true\` option");`}
this._fragment.c();`}
this._mount(options.target, options.anchor);
${(component.hasComponents || renderer.hasComplexBindings || hasInitHooks || renderer.hasIntroTransitions) &&
`@flush(this);`}
}
`}
${component.options.skipIntroByDefault && `this._intro = true;`}
`;
if (component.customElement) {
const props = component.props || Array.from(component.expectedProperties);
@ -247,7 +71,6 @@ export default function dom(
class ${name} extends HTMLElement {
constructor(options = {}) {
super();
${constructorBody}
}
static get observedAttributes() {
@ -282,57 +105,30 @@ export default function dom(
`}
}
@assign(${name}.prototype, ${proto});
${templateProperties.methods && `@assign(${name}.prototype, %methods);`}
@assign(${name}.prototype, {
_mount(target, anchor) {
target.insertBefore(this, anchor);
}
});
customElements.define("${component.tag}", ${name});
`);
} else {
builder.addBlock(deindent`
function ${name}(options) {
${constructorBody}
class ${name} extends @SvelteComponent {
__init() {
${component.javascript}
return () => ({ ${(component.declarations).join(', ')} });
}
@assign(${name}.prototype, ${proto});
${templateProperties.methods && `@assign(${name}.prototype, %methods);`}
__create_fragment(ctx) {
${block.getContents()}
}
}
`);
}
const immutable = templateProperties.immutable ? templateProperties.immutable.value.value : options.immutable;
builder.addBlock(deindent`
${options.dev && deindent`
${name}.prototype._checkReadOnly = function _checkReadOnly(newState) {
${Array.from(renderer.readonly).map(
prop =>
`if ('${prop}' in newState && !this._updatingReadonlyProperty) throw new Error("${debugName}: Cannot set read-only property '${prop}'");`
)}
};
`}
${computations.length ? deindent`
${name}.prototype._recompute = function _recompute(changed, state) {
${computationBuilder}
}
` : (!sharedPath && `${name}.prototype._recompute = @noop;`)}
${templateProperties.setup && `%setup(${name});`}
${templateProperties.preload && `${name}.preload = %preload;`}
${immutable && `${name}.prototype._differs = @_differsImmutable;`}
`);
let result = builder.toString();
return component.generate(result, options, {
banner: `/* ${component.file ? `${component.file} ` : ``}generated by Svelte v${"__VERSION__"} */`,
sharedPath,
name,
format,
});

@ -829,10 +829,10 @@ export default class ElementWrapper extends Wrapper {
);
block.addVariable(name);
const fn = `%actions-${action.name}`;
const fn = `ctx.${action.name}`;
block.builders.mount.addLine(
`${name} = ${fn}.call(#component, ${this.var}${snippet ? `, ${snippet}` : ''}) || {};`
`${name} = ${fn}.call(null, ${this.var}${snippet ? `, ${snippet}` : ''}) || {};`
);
if (dependencies && dependencies.size > 0) {
@ -842,12 +842,12 @@ export default class ElementWrapper extends Wrapper {
block.builders.update.addConditional(
conditional,
`${name}.update.call(#component, ${snippet});`
`${name}.update.call(null, ${snippet});`
);
}
block.builders.destroy.addLine(
`if (${name} && typeof ${name}.destroy === 'function') ${name}.destroy.call(#component);`
`if (${name} && typeof ${name}.destroy === 'function') ${name}.destroy();`
);
});
}
@ -860,7 +860,7 @@ export default class ElementWrapper extends Wrapper {
snippet = expression.snippet;
dependencies = expression.dependencies;
} else {
snippet = `ctx${quotePropIfNecessary(name)}`;
snippet = `${quotePropIfNecessary(name)}`;
dependencies = new Set([name]);
}
const updater = `@toggleClass(${this.var}, "${name}", ${snippet});`;

@ -50,29 +50,7 @@ export default function ssr(
let js = null;
if (component.javascript) {
const componentDefinition = new CodeBuilder();
// not all properties are relevant to SSR (e.g. lifecycle hooks)
const relevant = new Set([
'data',
'components',
'computed',
'helpers',
'preload',
'store'
]);
component.declarations.forEach(declaration => {
if (relevant.has(declaration.type)) {
componentDefinition.addBlock(declaration.block);
}
});
js = (
component.javascript[0] +
componentDefinition +
component.javascript[1]
);
// TODO
}
const debugName = `<${component.customElement ? component.tag : name}>`;

@ -0,0 +1,54 @@
import { schedule_update } from './scheduler';
export class SvelteComponent {
constructor(options) {
this.__get_state = this.__init(
fn => this.__inject_props = fn
);
this.__dirty = null;
if (options.props) {
this.__inject_props(options.props);
}
if (options.target) {
this.__mount(options.target);
}
}
$on(eventName, callback) {
}
$destroy() {
this.__destroy(true);
}
__make_dirty() {
if (this.__dirty) return;
this.__dirty = {};
schedule_update(this);
}
__mount(target, anchor) {
this.__fragment = this.__create_fragment(this.__get_state());
this.__fragment.c();
this.__fragment.m(target, anchor);
}
__set(key, value) {
this.__inject_props({ [key]: value });
this.__make_dirty();
this.__dirty[key] = true;
}
__update() {
this.__fragment.p(this.__dirty, this.__get_state());
this.__dirty = null;
}
__destroy(detach) {
this.__fragment.d(detach);
}
}

@ -0,0 +1,243 @@
export function append(target, node) {
target.appendChild(node);
}
export function insert(target, node, anchor) {
target.insertBefore(node, anchor);
}
export function detachNode(node) {
node.parentNode.removeChild(node);
}
export function detachBetween(before, after) {
while (before.nextSibling && before.nextSibling !== after) {
before.parentNode.removeChild(before.nextSibling);
}
}
export function detachBefore(after) {
while (after.previousSibling) {
after.parentNode.removeChild(after.previousSibling);
}
}
export function detachAfter(before) {
while (before.nextSibling) {
before.parentNode.removeChild(before.nextSibling);
}
}
export function reinsertBetween(before, after, target) {
while (before.nextSibling && before.nextSibling !== after) {
target.appendChild(before.parentNode.removeChild(before.nextSibling));
}
}
export function reinsertChildren(parent, target) {
while (parent.firstChild) target.appendChild(parent.firstChild);
}
export function reinsertAfter(before, target) {
while (before.nextSibling) target.appendChild(before.nextSibling);
}
export function reinsertBefore(after, target) {
var parent = after.parentNode;
while (parent.firstChild !== after) target.appendChild(parent.firstChild);
}
export function destroyEach(iterations, detach) {
for (var i = 0; i < iterations.length; i += 1) {
if (iterations[i]) iterations[i].d(detach);
}
}
export function createFragment() {
return document.createDocumentFragment();
}
export function createElement(name) {
return document.createElement(name);
}
export function createSvgElement(name) {
return document.createElementNS('http://www.w3.org/2000/svg', name);
}
export function createText(data) {
return document.createTextNode(data);
}
export function createComment() {
return document.createComment('');
}
export function addListener(node, event, handler, options) {
node.addEventListener(event, handler, options);
}
export function removeListener(node, event, handler, options) {
node.removeEventListener(event, handler, options);
}
export function setAttribute(node, attribute, value) {
if (value == null) node.removeAttribute(attribute);
else node.setAttribute(attribute, value);
}
export function setAttributes(node, attributes) {
for (var key in attributes) {
if (key === 'style') {
node.style.cssText = attributes[key];
} else if (key in node) {
node[key] = attributes[key];
} else {
setAttribute(node, key, attributes[key]);
}
}
}
export function setCustomElementData(node, prop, value) {
if (prop in node) {
node[prop] = value;
} else if (value) {
setAttribute(node, prop, value);
} else {
node.removeAttribute(prop);
}
}
export function setXlinkAttribute(node, attribute, value) {
node.setAttributeNS('http://www.w3.org/1999/xlink', attribute, value);
}
export function getBindingGroupValue(group) {
var value = [];
for (var i = 0; i < group.length; i += 1) {
if (group[i].checked) value.push(group[i].__value);
}
return value;
}
export function toNumber(value) {
return value === '' ? undefined : +value;
}
export function timeRangesToArray(ranges) {
var array = [];
for (var i = 0; i < ranges.length; i += 1) {
array.push({ start: ranges.start(i), end: ranges.end(i) });
}
return array;
}
export function children (element) {
return Array.from(element.childNodes);
}
export function claimElement (nodes, name, attributes, svg) {
for (var i = 0; i < nodes.length; i += 1) {
var node = nodes[i];
if (node.nodeName === name) {
for (var j = 0; j < node.attributes.length; j += 1) {
var attribute = node.attributes[j];
if (!attributes[attribute.name]) node.removeAttribute(attribute.name);
}
return nodes.splice(i, 1)[0]; // TODO strip unwanted attributes
}
}
return svg ? createSvgElement(name) : createElement(name);
}
export function claimText (nodes, data) {
for (var i = 0; i < nodes.length; i += 1) {
var node = nodes[i];
if (node.nodeType === 3) {
node.data = data;
return nodes.splice(i, 1)[0];
}
}
return createText(data);
}
export function setData(text, data) {
text.data = '' + data;
}
export function setInputType(input, type) {
try {
input.type = type;
} catch (e) {}
}
export function setStyle(node, key, value) {
node.style.setProperty(key, value);
}
export function selectOption(select, value) {
for (var i = 0; i < select.options.length; i += 1) {
var option = select.options[i];
if (option.__value === value) {
option.selected = true;
return;
}
}
}
export function selectOptions(select, value) {
for (var i = 0; i < select.options.length; i += 1) {
var option = select.options[i];
option.selected = ~value.indexOf(option.__value);
}
}
export function selectValue(select) {
var selectedOption = select.querySelector(':checked') || select.options[0];
return selectedOption && selectedOption.__value;
}
export function selectMultipleValue(select) {
return [].map.call(select.querySelectorAll(':checked'), function(option) {
return option.__value;
});
}
export function addResizeListener(element, fn) {
if (getComputedStyle(element).position === 'static') {
element.style.position = 'relative';
}
const object = document.createElement('object');
object.setAttribute('style', 'display: block; position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden; pointer-events: none; z-index: -1;');
object.type = 'text/html';
let win;
object.onload = () => {
win = object.contentDocument.defaultView;
win.addEventListener('resize', fn);
};
if (/Trident/.test(navigator.userAgent)) {
element.appendChild(object);
object.data = 'about:blank';
} else {
object.data = 'about:blank';
element.appendChild(object);
}
return {
cancel: () => {
win && win.removeEventListener && win.removeEventListener('resize', fn);
element.removeChild(object);
}
};
}
export function toggleClass(element, name, toggle) {
element.classList.toggle(name, !!toggle);
}

@ -0,0 +1,5 @@
export * from './dom.js';
export * from './scheduler.js';
export * from './transitions.js';
export * from './utils.js';
export * from './SvelteComponent.js';

@ -0,0 +1,23 @@
let update_scheduled = false;
const dirty_components = [];
export function schedule_update(component) {
dirty_components.push(component);
if (!update_scheduled) {
update_scheduled = true;
queueMicrotask(flush);
}
}
export function flush() {
while (dirty_components.length) {
dirty_components.pop().__update();
}
update_scheduled = false;
}
function queueMicrotask(callback) {
Promise.resolve().then(callback);
}

@ -0,0 +1,256 @@
import { createElement } from './dom.js';
import { noop, run } from './utils.js';
export function linear(t) {
return t;
}
export function generateRule({ a, b, delta, duration }, ease, fn) {
const step = 16.666 / duration;
let keyframes = '{\n';
for (let p = 0; p <= 1; p += step) {
const t = a + delta * ease(p);
keyframes += p * 100 + `%{${fn(t, 1 - t)}}\n`;
}
return keyframes + `100% {${fn(b, 1 - b)}}\n}`;
}
// https://github.com/darkskyapp/string-hash/blob/master/index.js
export function hash(str) {
let hash = 5381;
let i = str.length;
while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i);
return hash >>> 0;
}
export function wrapTransition(component, node, fn, params, intro) {
let obj = fn.call(component, node, params);
let duration;
let ease;
let cssText;
let initialised = false;
return {
t: intro ? 0 : 1,
running: false,
program: null,
pending: null,
run(b, callback) {
if (typeof obj === 'function') {
transitionManager.wait().then(() => {
obj = obj();
this._run(b, callback);
});
} else {
this._run(b, callback);
}
},
_run(b, callback) {
duration = obj.duration || 300;
ease = obj.easing || linear;
const program = {
start: window.performance.now() + (obj.delay || 0),
b,
callback: callback || noop
};
if (intro && !initialised) {
if (obj.css && obj.delay) {
cssText = node.style.cssText;
node.style.cssText += obj.css(0, 1);
}
if (obj.tick) obj.tick(0, 1);
initialised = true;
}
if (!b) {
program.group = outros.current;
outros.current.remaining += 1;
}
if (obj.delay) {
this.pending = program;
} else {
this.start(program);
}
if (!this.running) {
this.running = true;
transitionManager.add(this);
}
},
start(program) {
component.fire(`${program.b ? 'intro' : 'outro'}.start`, { node });
program.a = this.t;
program.delta = program.b - program.a;
program.duration = duration * Math.abs(program.b - program.a);
program.end = program.start + program.duration;
if (obj.css) {
if (obj.delay) node.style.cssText = cssText;
const rule = generateRule(program, ease, obj.css);
transitionManager.addRule(rule, program.name = '__svelte_' + hash(rule));
node.style.animation = (node.style.animation || '')
.split(', ')
.filter(anim => anim && (program.delta < 0 || !/__svelte/.test(anim)))
.concat(`${program.name} ${program.duration}ms linear 1 forwards`)
.join(', ');
}
this.program = program;
this.pending = null;
},
update(now) {
const program = this.program;
if (!program) return;
const p = now - program.start;
this.t = program.a + program.delta * ease(p / program.duration);
if (obj.tick) obj.tick(this.t, 1 - this.t);
},
done() {
const program = this.program;
this.t = program.b;
if (obj.tick) obj.tick(this.t, 1 - this.t);
component.fire(`${program.b ? 'intro' : 'outro'}.end`, { node });
if (!program.b && !program.invalidated) {
program.group.callbacks.push(() => {
program.callback();
if (obj.css) transitionManager.deleteRule(node, program.name);
});
if (--program.group.remaining === 0) {
program.group.callbacks.forEach(run);
}
} else {
if (obj.css) transitionManager.deleteRule(node, program.name);
}
this.running = !!this.pending;
},
abort(reset) {
if (this.program) {
if (reset && obj.tick) obj.tick(1, 0);
if (obj.css) transitionManager.deleteRule(node, this.program.name);
this.program = this.pending = null;
this.running = false;
}
},
invalidate() {
if (this.program) {
this.program.invalidated = true;
}
}
};
}
export let outros = {};
export function groupOutros() {
outros.current = {
remaining: 0,
callbacks: []
};
}
export var transitionManager = {
running: false,
transitions: [],
bound: null,
stylesheet: null,
activeRules: {},
promise: null,
add(transition) {
this.transitions.push(transition);
if (!this.running) {
this.running = true;
requestAnimationFrame(this.bound || (this.bound = this.next.bind(this)));
}
},
addRule(rule, name) {
if (!this.stylesheet) {
const style = createElement('style');
document.head.appendChild(style);
transitionManager.stylesheet = style.sheet;
}
if (!this.activeRules[name]) {
this.activeRules[name] = true;
this.stylesheet.insertRule(`@keyframes ${name} ${rule}`, this.stylesheet.cssRules.length);
}
},
next() {
this.running = false;
const now = window.performance.now();
let i = this.transitions.length;
while (i--) {
const transition = this.transitions[i];
if (transition.program && now >= transition.program.end) {
transition.done();
}
if (transition.pending && now >= transition.pending.start) {
transition.start(transition.pending);
}
if (transition.running) {
transition.update(now);
this.running = true;
} else if (!transition.pending) {
this.transitions.splice(i, 1);
}
}
if (this.running) {
requestAnimationFrame(this.bound);
} else if (this.stylesheet) {
let i = this.stylesheet.cssRules.length;
while (i--) this.stylesheet.deleteRule(i);
this.activeRules = {};
}
},
deleteRule(node, name) {
node.style.animation = node.style.animation
.split(', ')
.filter(anim => anim && anim.indexOf(name) === -1)
.join(', ');
},
wait() {
if (!transitionManager.promise) {
transitionManager.promise = Promise.resolve();
transitionManager.promise.then(() => {
transitionManager.promise = null;
});
}
return transitionManager.promise;
}
};

@ -0,0 +1,38 @@
export function noop() {}
export function assign(tar, src) {
for (var k in src) tar[k] = src[k];
return tar;
}
export function assignTrue(tar, src) {
for (var k in src) tar[k] = 1;
return tar;
}
export function isPromise(value) {
return value && typeof value.then === 'function';
}
export function callAfter(fn, i) {
if (i === 0) fn();
return () => {
if (!--i) fn();
};
}
export function addLoc(element, file, line, column, char) {
element.__svelte_meta = {
loc: { file, line, column, char }
};
}
export function exclude(src, prop) {
const tar = {};
for (const k in src) k === prop || (tar[k] = src[k]);
return tar;
}
export function run(fn) {
fn();
}

@ -384,20 +384,38 @@ function readAttribute(parser: Parser, uniqueNames: Set<string>) {
parser.allowWhitespace();
const directive = readDirective(parser, start, name);
if (directive) return directive;
let value = parser.eat('=') ? readAttributeValue(parser) : true;
const end = parser.index;
const colon_index = name.indexOf(':');
if (colon_index !== -1) {
const type = get_directive_type(name.slice(0, colon_index));
name = name.slice(colon_index + 1);
return {
start,
end: parser.index,
end,
type,
name,
expression: value[0].expression
};
}
return {
start,
end,
type: 'Attribute',
name,
value,
};
}
function get_directive_type(name) {
if (name === 'use') return 'Action';
throw new Error(`TODO directive ${name}`);
}
function readAttributeValue(parser: Parser) {
const quoteMark = parser.eat(`'`) ? `'` : parser.eat(`"`) ? `"` : null;

@ -13,6 +13,8 @@ export function createScopes(expression: Node) {
if (/Function/.test(node.type)) {
if (node.type === 'FunctionDeclaration') {
scope.declarations.add(node.id.name);
scope = new Scope(scope, false);
map.set(node, scope);
} else {
scope = new Scope(scope, false);
map.set(node, scope);
@ -30,9 +32,9 @@ export function createScopes(expression: Node) {
} else if (node.type === 'BlockStatement') {
scope = new Scope(scope, true);
map.set(node, scope);
} else if (/(Function|Class|Variable)Declaration/.test(node.type)) {
} else if (/(Class|Variable)Declaration/.test(node.type)) {
scope.addDeclaration(node);
} else if (isReference(node, parent)) {
} else if (node.type === 'Identifier' && isReference(node, parent)) {
if (!scope.has(node.name)) {
globals.add(node.name);
}
@ -49,49 +51,6 @@ export function createScopes(expression: Node) {
return { map, scope, globals };
}
// TODO remove this in favour of weakmap version
export default function annotateWithScopes(expression: Node) {
const globals = new Set();
let scope = new Scope(null, false);
walk(expression, {
enter(node: Node, parent: Node) {
if (/Function/.test(node.type)) {
if (node.type === 'FunctionDeclaration') {
scope.declarations.add(node.id.name);
} else {
node._scope = scope = new Scope(scope, false);
if (node.id) scope.declarations.add(node.id.name);
}
node.params.forEach((param: Node) => {
extractNames(param).forEach(name => {
scope.declarations.add(name);
});
});
} else if (/For(?:In|Of)Statement/.test(node.type)) {
node._scope = scope = new Scope(scope, true);
} else if (node.type === 'BlockStatement') {
node._scope = scope = new Scope(scope, true);
} else if (/(Function|Class|Variable)Declaration/.test(node.type)) {
scope.addDeclaration(node);
} else if (isReference(node, parent)) {
if (!scope.has(node.name)) {
globals.add(node.name);
}
}
},
leave(node: Node) {
if (node._scope) {
scope = scope.parent;
}
},
});
return { scope, globals };
}
export class Scope {
parent: Scope;
block: boolean;

@ -2,7 +2,7 @@
import { onmount } from 'svelte';
onmount(() => {
console.log( 'here' );
console.log('here');
});
</script>

@ -2,7 +2,7 @@
import { onmount } from 'svelte';
onmount(() => {
console.log( 'here' );
console.log('here');
});
</script>

@ -3,7 +3,7 @@ import chalk from 'chalk';
import * as path from "path";
import * as fs from "fs";
import * as acorn from "acorn";
import { transitionManager } from "../../shared.js";
import { transitionManager } from "../../internal.js";
import {
showOutput,
@ -25,7 +25,7 @@ function getName(filename) {
return base[0].toUpperCase() + base.slice(1);
}
describe("runtime", () => {
describe.only("runtime", () => {
before(() => {
svelte = loadSvelte(false);
svelte$ = loadSvelte(true);
@ -161,10 +161,10 @@ describe("runtime", () => {
if (config.test) {
return Promise.resolve(config.test(assert, component, target, window, raf)).then(() => {
component.destroy();
component.$destroy();
});
} else {
component.destroy();
component.$destroy();
assert.htmlEqual(target.innerHTML, "");
}
})
@ -195,11 +195,10 @@ describe("runtime", () => {
});
}
const shared = path.resolve("shared.js");
const internal = path.resolve("internal.js");
fs.readdirSync("test/runtime/samples").forEach(dir => {
runTest(dir, shared, false);
runTest(dir, shared, true);
runTest(dir, null, false);
runTest(dir, internal, false);
runTest(dir, internal, true);
});
it("fails if options.target is missing in dev mode", () => {

@ -1,2 +1,2 @@
<div>i got 99 problems</div>
<div>the answer is 42</div>
<div>i got undefined problems</div>
<div>the answer is undefined</div>
Loading…
Cancel
Save