Merge branch 'master' of github.com:sveltejs/svelte

pull/1355/head v2.0.0
Rich Harris 7 years ago
commit d215279ef1

@ -76,14 +76,11 @@ The Svelte compiler optionally takes a second argument, an object of configurati
| | **Values** | **Description** | **Default** | | | **Values** | **Description** | **Default** |
|---|---|---|---| |---|---|---|---|
| `generate` | `'dom'`, `'ssr'` | Whether to generate JavaScript code intended for use on the client (`'dom'`), or for use in server-side rendering (`'ssr'`). | `'dom'` | | `generate` | `'dom'`, `'ssr'`, `false` | Whether to generate JavaScript code intended for use on the client (`'dom'`), or for use in server-side rendering (`'ssr'`). If `false`, component will be parsed and validated but no code will be emitted | `'dom'` |
| `dev` | `true`, `false` | Whether to enable run-time checks in the compiled component. These are helpful during development, but slow your component down. | `false` | | `dev` | `true`, `false` | Whether to enable run-time checks in the compiled component. These are helpful during development, but slow your component down. | `false` |
| `css` | `true`, `false` | Whether to include code to inject your component's styles into the DOM. | `true` | | `css` | `true`, `false` | Whether to include code to inject your component's styles into the DOM. | `true` |
| `store` | `true`, `false` | Whether to support store integration on the compiled component. | `false` |
| `hydratable` | `true`, `false` | Whether to support hydration on the compiled component. | `false` | | `hydratable` | `true`, `false` | Whether to support hydration on the compiled component. | `false` |
| `customElement` | `true`, `false`, `{ tag, props }` | Whether to compile this component to a custom element. If `tag`/`props` are passed, compiles to a custom element and overrides the values exported by the component. | `false` | | `customElement` | `true`, `false`, `{ tag, props }` | Whether to compile this component to a custom element. If `tag`/`props` are passed, compiles to a custom element and overrides the values exported by the component. | `false` |
| `cascade` | `true`, `false` | Whether to cascade all of the component's styles to child components. If `false`, only selectors wrapped in `:global(...)` and keyframe IDs beginning with `-global-` are cascaded. | `true` |
| `parser` | `v2` | Opt in to [v2 syntax](https://github.com/sveltejs/svelte-upgrade#svelte-v2-syntax-changes). | `undefined` |
| `bind` | `boolean` | If `false`, disallows `bind:` directives | `true` | | `bind` | `boolean` | If `false`, disallows `bind:` directives | `true` |
| | | | | | | |
| `shared` | `true`, `false`, `string` | Whether to import various helpers from a shared external library. When you have a project with multiple components, this reduces the overall size of your JavaScript bundle, at the expense of having immediately-usable component. You can pass a string of the module path to use, or `true` will import from `'svelte/shared.js'`. | `false` | | `shared` | `true`, `false`, `string` | Whether to import various helpers from a shared external library. When you have a project with multiple components, this reduces the overall size of your JavaScript bundle, at the expense of having immediately-usable component. You can pass a string of the module path to use, or `true` will import from `'svelte/shared.js'`. | `false` |
@ -94,7 +91,7 @@ The Svelte compiler optionally takes a second argument, an object of configurati
| `filename` | `string` | The filename to use in sourcemaps and compiler error and warning messages. | `'SvelteComponent.html'` | | `filename` | `string` | The filename to use in sourcemaps and compiler error and warning messages. | `'SvelteComponent.html'` |
| `amd`.`id` | `string` | The AMD module ID to use for the `'amd'` and `'umd'` output formats. | `undefined` | | `amd`.`id` | `string` | The AMD module ID to use for the `'amd'` and `'umd'` output formats. | `undefined` |
| `globals` | `object`, `function` | When outputting to the `'umd'`, `'iife'` or `'eval'` formats, an object or function mapping the names of imported dependencies to the names of global variables. | `{}` | | `globals` | `object`, `function` | When outputting to the `'umd'`, `'iife'` or `'eval'` formats, an object or function mapping the names of imported dependencies to the names of global variables. | `{}` |
| `preserveComments` | `boolean` | Include comments in rendering. Currently, only applies to SSR rendering | `false` | | `preserveComments` | `boolean` | Include comments in rendering. Currently, only applies to SSR rendering | `false` |
| | | | | | | |
| `onerror` | `function` | Specify a callback for when Svelte encounters an error while compiling the component. Passed two arguments: the error object, and another function that is Svelte's default onerror handling. | (exception is thrown) | | `onerror` | `function` | Specify a callback for when Svelte encounters an error while compiling the component. Passed two arguments: the error object, and another function that is Svelte's default onerror handling. | (exception is thrown) |
| `onwarn` | `function` | Specify a callback for when Svelte encounters a non-fatal warning while compiling the component. Passed two arguments: the warning object, and another function that is Svelte's default onwarn handling. | (warning is logged to console) | | `onwarn` | `function` | Specify a callback for when Svelte encounters a non-fatal warning while compiling the component. Passed two arguments: the warning object, and another function that is Svelte's default onwarn handling. | (warning is logged to console) |

@ -58,7 +58,7 @@
"glob": "^7.1.1", "glob": "^7.1.1",
"is-reference": "^1.1.0", "is-reference": "^1.1.0",
"jsdom": "^11.6.1", "jsdom": "^11.6.1",
"locate-character": "^2.0.0", "locate-character": "^2.0.5",
"magic-string": "^0.22.3", "magic-string": "^0.22.3",
"mocha": "^3.2.0", "mocha": "^3.2.0",
"nightmare": "^2.10.0", "nightmare": "^2.10.0",

@ -26,6 +26,8 @@ function collapseTimings(timings) {
} }
export default class Stats { export default class Stats {
onwarn: (warning: Warning) => void;
startTime: number; startTime: number;
currentTiming: Timing; currentTiming: Timing;
currentChildren: Timing[]; currentChildren: Timing[];
@ -33,11 +35,15 @@ export default class Stats {
stack: Timing[]; stack: Timing[];
warnings: Warning[]; warnings: Warning[];
constructor() { constructor({ onwarn }: {
onwarn: (warning: Warning) => void
}) {
this.startTime = now(); this.startTime = now();
this.stack = []; this.stack = [];
this.currentChildren = this.timings = []; this.currentChildren = this.timings = [];
this.onwarn = onwarn;
this.warnings = []; this.warnings = [];
} }
@ -99,4 +105,9 @@ export default class Stats {
hooks hooks
}; };
} }
warn(warning) {
this.warnings.push(warning);
this.onwarn(warning);
}
} }

@ -31,18 +31,18 @@ class Rule {
return this.selectors.some(s => s.used); return this.selectors.some(s => s.used);
} }
minify(code: MagicString, cascade: boolean, dev: boolean) { minify(code: MagicString, dev: boolean) {
let c = this.node.start; let c = this.node.start;
let started = false; let started = false;
this.selectors.forEach((selector, i) => { this.selectors.forEach((selector, i) => {
if (cascade || selector.used) { if (selector.used) {
const separator = started ? ',' : ''; const separator = started ? ',' : '';
if ((selector.node.start - c) > separator.length) { if ((selector.node.start - c) > separator.length) {
code.overwrite(c, selector.node.start, separator); code.overwrite(c, selector.node.start, separator);
} }
if (!cascade) selector.minify(code); selector.minify(code);
c = selector.node.end; c = selector.node.end;
started = true; started = true;
@ -66,39 +66,12 @@ class Rule {
code.remove(c, this.node.block.end - 1); code.remove(c, this.node.block.end - 1);
} }
transform(code: MagicString, id: string, keyframes: Map<string, string>, cascade: boolean) { transform(code: MagicString, id: string, keyframes: Map<string, string>) {
if (this.parent && this.parent.node.type === 'Atrule' && this.parent.node.name === 'keyframes') return true; if (this.parent && this.parent.node.type === 'Atrule' && this.parent.node.name === 'keyframes') return true;
const attr = `.${id}`; const attr = `.${id}`;
if (cascade) { this.selectors.forEach(selector => selector.transform(code, attr));
this.selectors.forEach(selector => {
// TODO disable cascading (without :global(...)) in v2
const { start, end, children } = selector.node;
const css = code.original;
const selectorString = css.slice(start, end);
const firstToken = children[0];
let transformed;
if (firstToken.type === 'TypeSelector') {
const insert = firstToken.end;
const head = firstToken.name === '*' ? '' : css.slice(start, insert);
const tail = css.slice(insert, end);
transformed = `${head}${attr}${tail},${attr} ${selectorString}`;
} else {
transformed = `${attr}${selectorString},${attr} ${selectorString}`;
}
code.overwrite(start, end, transformed);
});
} else {
this.selectors.forEach(selector => selector.transform(code, attr));
}
this.declarations.forEach(declaration => declaration.transform(code, keyframes)); this.declarations.forEach(declaration => declaration.transform(code, keyframes));
} }
@ -182,7 +155,7 @@ class Atrule {
return true; // TODO return true; // TODO
} }
minify(code: MagicString, cascade: boolean, dev: boolean) { minify(code: MagicString, dev: boolean) {
if (this.node.name === 'media') { if (this.node.name === 'media') {
const expressionChar = code.original[this.node.expression.start]; const expressionChar = code.original[this.node.expression.start];
let c = this.node.start + (expressionChar === '(' ? 6 : 7); let c = this.node.start + (expressionChar === '(' ? 6 : 7);
@ -215,9 +188,9 @@ class Atrule {
let c = this.node.block.start + 1; let c = this.node.block.start + 1;
this.children.forEach(child => { this.children.forEach(child => {
if (cascade || child.isUsed(dev)) { if (child.isUsed(dev)) {
code.remove(c, child.node.start); code.remove(c, child.node.start);
child.minify(code, cascade, dev); child.minify(code, dev);
c = child.node.end; c = child.node.end;
} }
}); });
@ -226,7 +199,7 @@ class Atrule {
} }
} }
transform(code: MagicString, id: string, keyframes: Map<string, string>, cascade: boolean) { transform(code: MagicString, id: string, keyframes: Map<string, string>) {
if (this.node.name === 'keyframes') { if (this.node.name === 'keyframes') {
this.node.expression.children.forEach(({ type, name, start, end }: Node) => { this.node.expression.children.forEach(({ type, name, start, end }: Node) => {
if (type === 'Identifier') { if (type === 'Identifier') {
@ -240,7 +213,7 @@ class Atrule {
} }
this.children.forEach(child => { this.children.forEach(child => {
child.transform(code, id, keyframes, cascade); child.transform(code, id, keyframes);
}) })
} }
@ -264,7 +237,6 @@ const keys = {};
export default class Stylesheet { export default class Stylesheet {
source: string; source: string;
parsed: Parsed; parsed: Parsed;
cascade: boolean;
filename: string; filename: string;
dev: boolean; dev: boolean;
@ -276,10 +248,9 @@ export default class Stylesheet {
nodesWithCssClass: Set<Node>; nodesWithCssClass: Set<Node>;
constructor(source: string, parsed: Parsed, filename: string, cascade: boolean, dev: boolean) { constructor(source: string, parsed: Parsed, filename: string, dev: boolean) {
this.source = source; this.source = source;
this.parsed = parsed; this.parsed = parsed;
this.cascade = cascade;
this.filename = filename; this.filename = filename;
this.dev = dev; this.dev = dev;
@ -356,11 +327,6 @@ export default class Stylesheet {
if (parent.type === 'Element') stack.unshift(<Element>parent); if (parent.type === 'Element') stack.unshift(<Element>parent);
} }
if (this.cascade) {
if (stack.length === 0) this.nodesWithCssClass.add(node);
return;
}
for (let i = 0; i < this.children.length; i += 1) { for (let i = 0; i < this.children.length; i += 1) {
const child = this.children[i]; const child = this.children[i];
child.apply(node, stack); child.apply(node, stack);
@ -389,15 +355,15 @@ export default class Stylesheet {
if (shouldTransformSelectors) { if (shouldTransformSelectors) {
this.children.forEach((child: (Atrule|Rule)) => { this.children.forEach((child: (Atrule|Rule)) => {
child.transform(code, this.id, this.keyframes, this.cascade); child.transform(code, this.id, this.keyframes);
}); });
} }
let c = 0; let c = 0;
this.children.forEach(child => { this.children.forEach(child => {
if (this.cascade || child.isUsed(this.dev)) { if (child.isUsed(this.dev)) {
code.remove(c, child.node.start); code.remove(c, child.node.start);
child.minify(code, this.cascade, this.dev); child.minify(code, this.dev);
c = child.node.end; c = child.node.end;
} }
}); });
@ -421,27 +387,27 @@ export default class Stylesheet {
} }
warnOnUnusedSelectors(onwarn: (warning: Warning) => void) { warnOnUnusedSelectors(onwarn: (warning: Warning) => void) {
if (this.cascade) return;
let locator; let locator;
const handler = (selector: Selector) => { const handler = (selector: Selector) => {
const pos = selector.node.start; const pos = selector.node.start;
if (!locator) locator = getLocator(this.source); if (!locator) locator = getLocator(this.source, { offsetLine: 1 });
const { line, column } = locator(pos); const start = locator(pos);
const end = locator(selector.node.end);
const frame = getCodeFrame(this.source, line, column); const frame = getCodeFrame(this.source, start.line - 1, start.column);
const message = `Unused CSS selector`; const message = `Unused CSS selector`;
onwarn({ onwarn({
code: `css-unused-selector`, code: `css-unused-selector`,
message, message,
frame, frame,
loc: { line: line + 1, column }, start,
end,
pos, pos,
filename: this.filename, filename: this.filename,
toString: () => `${message} (${line + 1}:${column})\n${frame}`, toString: () => `${message} (${start.line}:${start.column})\n${frame}`,
}); });
}; };

@ -5,7 +5,6 @@ import { getLocator } from 'locate-character';
import Stats from '../Stats'; import Stats from '../Stats';
import deindent from '../utils/deindent'; import deindent from '../utils/deindent';
import CodeBuilder from '../utils/CodeBuilder'; import CodeBuilder from '../utils/CodeBuilder';
import getCodeFrame from '../utils/getCodeFrame';
import flattenReference from '../utils/flattenReference'; import flattenReference from '../utils/flattenReference';
import reservedNames from '../utils/reservedNames'; import reservedNames from '../utils/reservedNames';
import namespaces from '../utils/namespaces'; import namespaces from '../utils/namespaces';
@ -84,7 +83,6 @@ export default class Generator {
source: string; source: string;
name: string; name: string;
options: CompileOptions; options: CompileOptions;
v2: boolean;
customElement: CustomElementOptions; customElement: CustomElementOptions;
tag: string; tag: string;
@ -95,6 +93,7 @@ export default class Generator {
helpers: Set<string>; helpers: Set<string>;
components: Set<string>; components: Set<string>;
events: Set<string>; events: Set<string>;
methods: Set<string>;
transitions: Set<string>; transitions: Set<string>;
actions: Set<string>; actions: Set<string>;
importedComponents: Map<string, string>; importedComponents: Map<string, string>;
@ -133,8 +132,6 @@ export default class Generator {
stats.start('compile'); stats.start('compile');
this.stats = stats; this.stats = stats;
this.v2 = options.parser === 'v2';
this.ast = clone(parsed); this.ast = clone(parsed);
this.parsed = parsed; this.parsed = parsed;
@ -145,6 +142,7 @@ export default class Generator {
this.helpers = new Set(); this.helpers = new Set();
this.components = new Set(); this.components = new Set();
this.events = new Set(); this.events = new Set();
this.methods = new Set();
this.transitions = new Set(); this.transitions = new Set();
this.actions = new Set(); this.actions = new Set();
this.importedComponents = new Map(); this.importedComponents = new Map();
@ -369,31 +367,13 @@ export default class Generator {
}) })
}; };
Object.getOwnPropertyNames(String.prototype).forEach(name => {
const descriptor = Object.getOwnPropertyDescriptor(String.prototype, name);
if (typeof descriptor.value === 'function') {
Object.defineProperty(css, name, {
value: (...args) => {
return css.code === null
? null
: css.code[name].call(css.code, ...args);
}
});
}
});
this.stats.stop('compile'); this.stats.stop('compile');
return { return {
ast: this.ast, ast: this.ast,
js, js,
css, css,
stats: this.stats.render(this), stats: this.stats.render(this)
// TODO deprecate
code: js.code,
map: js.map,
cssMap: css.map
}; };
} }
@ -441,6 +421,7 @@ export default class Generator {
code, code,
source, source,
computations, computations,
methods,
templateProperties, templateProperties,
imports imports
} = this; } = this;
@ -578,9 +559,7 @@ export default class Generator {
const key = getName(prop.key); const key = getName(prop.key);
const value = prop.value; const value = prop.value;
const deps = this.v2 const deps = value.params[0].properties.map(prop => prop.key.name);
? value.params[0].properties.map(prop => prop.key.name)
: value.params.map(param => param.type === 'AssignmentPattern' ? param.left.name : param.name);
deps.forEach(dep => { deps.forEach(dep => {
this.expectedProperties.add(dep); this.expectedProperties.add(dep);
@ -632,6 +611,10 @@ export default class Generator {
if (templateProperties.methods && dom) { if (templateProperties.methods && dom) {
addDeclaration('methods', templateProperties.methods.value); addDeclaration('methods', templateProperties.methods.value);
templateProperties.methods.value.properties.forEach(prop => {
this.methods.add(prop.key.name);
});
} }
if (templateProperties.namespace) { if (templateProperties.namespace) {
@ -639,12 +622,10 @@ export default class Generator {
this.namespace = namespaces[ns] || ns; this.namespace = namespaces[ns] || ns;
} }
if (templateProperties.onrender) templateProperties.oncreate = templateProperties.onrender; // remove after v2
if (templateProperties.oncreate && dom) { if (templateProperties.oncreate && dom) {
addDeclaration('oncreate', templateProperties.oncreate.value); addDeclaration('oncreate', templateProperties.oncreate.value);
} }
if (templateProperties.onteardown) templateProperties.ondestroy = templateProperties.onteardown; // remove after v2
if (templateProperties.ondestroy && dom) { if (templateProperties.ondestroy && dom) {
addDeclaration('ondestroy', templateProperties.ondestroy.value); addDeclaration('ondestroy', templateProperties.ondestroy.value);
} }
@ -802,7 +783,7 @@ export default class Generator {
node.generator = generator; node.generator = generator;
if (node.type === 'Element' && (node.name === ':Component' || node.name === ':Self' || node.name === 'svelte:component' || node.name === 'svelte:self' || generator.components.has(node.name))) { if (node.type === 'Element' && (node.name === 'svelte:component' || node.name === 'svelte:self' || generator.components.has(node.name))) {
node.type = 'Component'; node.type = 'Component';
Object.setPrototypeOf(node, nodes.Component.prototype); Object.setPrototypeOf(node, nodes.Component.prototype);
} else if (node.type === 'Element' && node.name === 'title' && parentIsHead(parent)) { // TODO do this in parse? } else if (node.type === 'Element' && node.name === 'title' && parentIsHead(parent)) { // TODO do this in parse?
@ -886,7 +867,7 @@ export default class Generator {
this.skip(); this.skip();
} }
if (node.type === 'Component' && (node.name === ':Component' || node.name === 'svelte:component')) { if (node.type === 'Component' && node.name === 'svelte:component') {
node.metadata = contextualise(node.expression, contextDependencies, indexes, false); node.metadata = contextualise(node.expression, contextDependencies, indexes, false);
} }

@ -47,7 +47,7 @@ export class DomGenerator extends Generator {
this.legacy = options.legacy; this.legacy = options.legacy;
this.needsEncapsulateHelper = false; this.needsEncapsulateHelper = false;
// initial values for e.g. window.innerWidth, if there's a <:Window> meta tag // initial values for e.g. window.innerWidth, if there's a <svelte:window> meta tag
this.metaBindings = []; this.metaBindings = [];
} }
} }
@ -89,7 +89,7 @@ export default function dom(
}); });
if (generator.readonly.has(key)) { if (generator.readonly.has(key)) {
// <:Window> bindings // <svelte:window> bindings
throw new Error( throw new Error(
`Cannot have a computed value '${key}' that clashes with a read-only property` `Cannot have a computed value '${key}' that clashes with a read-only property`
); );
@ -99,11 +99,7 @@ export default function dom(
const condition = `${deps.map(dep => `changed.${dep}`).join(' || ')}`; const condition = `${deps.map(dep => `changed.${dep}`).join(' || ')}`;
const call = generator.v2 const statement = `if (this._differs(state.${key}, (state.${key} = %computed-${key}(state)))) changed.${key} = true;`;
? `%computed-${key}(state)`
: `%computed-${key}(${deps.map(dep => `state.${dep}`).join(', ')})`;
const statement = `if (this._differs(state.${key}, (state.${key} = ${call}))) changed.${key} = true;`;
computationBuilder.addConditional(condition, statement); computationBuilder.addConditional(condition, statement);
}); });
@ -143,8 +139,8 @@ export default function dom(
? `@proto` ? `@proto`
: deindent` : deindent`
{ {
${['destroy', 'get', 'fire', 'observe', 'on', 'set', 'teardown', '_set', '_mount', '_unmount', '_differs'] ${['destroy', 'get', 'fire', 'on', 'set', '_set', '_mount', '_unmount', '_differs']
.map(n => `${n}: @${n === 'teardown' ? 'destroy' : n}`) .map(n => `${n}: @${n}`)
.join(',\n')} .join(',\n')}
}`; }`;
@ -153,7 +149,7 @@ export default function dom(
// generate initial state object // generate initial state object
const expectedProperties = Array.from(generator.expectedProperties); const expectedProperties = Array.from(generator.expectedProperties);
const globals = expectedProperties.filter(prop => globalWhitelist.has(prop)); const globals = expectedProperties.filter(prop => globalWhitelist.has(prop));
const storeProps = options.store || templateProperties.store ? expectedProperties.filter(prop => prop[0] === '$') : []; const storeProps = expectedProperties.filter(prop => prop[0] === '$');
const initialState = []; const initialState = [];
if (globals.length > 0) { if (globals.length > 0) {
@ -294,7 +290,7 @@ export default function dom(
${props.map(prop => deindent` ${props.map(prop => deindent`
get ${prop}() { get ${prop}() {
return this.get('${prop}'); return this.get().${prop};
} }
set ${prop}(value) { set ${prop}(value) {

@ -159,12 +159,8 @@ function getEventHandler(
dependencies: string[], dependencies: string[],
value: string, value: string,
) { ) {
let storeDependencies = []; const storeDependencies = dependencies.filter(prop => prop[0] === '$').map(prop => prop.slice(1));
dependencies = dependencies.filter(prop => prop[0] !== '$');
if (generator.options.store) {
storeDependencies = dependencies.filter(prop => prop[0] === '$').map(prop => prop.slice(1));
dependencies = dependencies.filter(prop => prop[0] !== '$');
}
if (block.contexts.has(name)) { if (block.contexts.has(name)) {
const tail = attribute.value.type === 'MemberExpression' const tail = attribute.value.type === 'MemberExpression'
@ -207,7 +203,7 @@ function getEventHandler(
let props; let props;
let storeProps; let storeProps;
if (generator.options.store && name[0] === '$') { if (name[0] === '$') {
props = []; props = [];
storeProps = [`${name.slice(1)}: ${value}`]; storeProps = [`${name.slice(1)}: ${value}`];
} else { } else {

@ -45,8 +45,8 @@ export default class Component extends Node {
this.var = block.getUniqueName( this.var = block.getUniqueName(
( (
(this.name === ':Self' || this.name === 'svelte:self') ? this.generator.name : this.name === 'svelte:self' ? this.generator.name :
(this.name === ':Component' || this.name === 'svelte:component') ? 'switch_instance' : this.name === 'svelte:component' ? 'switch_instance' :
this.name this.name
).toLowerCase() ).toLowerCase()
); );
@ -73,11 +73,11 @@ export default class Component extends Node {
const componentInitProperties = [`root: #component.root`]; const componentInitProperties = [`root: #component.root`];
if (this.children.length > 0) { if (this.children.length > 0) {
const slots = Array.from(this._slots).map(name => `${quoteIfNecessary(name, generator.legacy)}: @createFragment()`); const slots = Array.from(this._slots).map(name => `${quoteIfNecessary(name)}: @createFragment()`);
componentInitProperties.push(`slots: { ${slots.join(', ')} }`); componentInitProperties.push(`slots: { ${slots.join(', ')} }`);
this.children.forEach((child: Node) => { this.children.forEach((child: Node) => {
child.build(block, `${this.var}._slotted${generator.legacy ? `["default"]` : `.default`}`, 'nodes'); child.build(block, `${this.var}._slotted.default`, 'nodes');
}); });
} }
@ -139,7 +139,7 @@ export default class Component extends Node {
const condition = dependencies && dependencies.map(d => `changed.${d}`).join(' || '); const condition = dependencies && dependencies.map(d => `changed.${d}`).join(' || ');
changes.push(condition ? `${condition} && ${value}` : value); changes.push(condition ? `${condition} && ${value}` : value);
} else { } else {
const obj = `{ ${quoteIfNecessary(name, this.generator.legacy)}: ${value} }`; const obj = `{ ${quoteIfNecessary(name)}: ${value} }`;
initialProps.push(obj); initialProps.push(obj);
const condition = dependencies && dependencies.map(d => `changed.${d}`).join(' || '); const condition = dependencies && dependencies.map(d => `changed.${d}`).join(' || ');
@ -217,7 +217,7 @@ export default class Component extends Node {
${binding.dependencies ${binding.dependencies
.map((name: string) => { .map((name: string) => {
const isStoreProp = generator.options.store && name[0] === '$'; const isStoreProp = name[0] === '$';
const prop = isStoreProp ? name.slice(1) : name; const prop = isStoreProp ? name.slice(1) : name;
const newState = isStoreProp ? 'newStoreState' : 'newState'; const newState = isStoreProp ? 'newStoreState' : 'newState';
@ -230,7 +230,7 @@ export default class Component extends Node {
} }
else { else {
const isStoreProp = generator.options.store && key[0] === '$'; const isStoreProp = key[0] === '$';
const prop = isStoreProp ? key.slice(1) : key; const prop = isStoreProp ? key.slice(1) : key;
const newState = isStoreProp ? 'newStoreState' : 'newState'; const newState = isStoreProp ? 'newStoreState' : 'newState';
@ -293,7 +293,7 @@ export default class Component extends Node {
`; `;
} }
if (this.name === ':Component' || this.name === 'svelte:component') { if (this.name === 'svelte:component') {
const switch_value = block.getUniqueName('switch_value'); const switch_value = block.getUniqueName('switch_value');
const switch_props = block.getUniqueName('switch_props'); const switch_props = block.getUniqueName('switch_props');
@ -387,7 +387,7 @@ export default class Component extends Node {
block.builders.destroy.addLine(`if (${name}) ${name}.destroy(false);`); block.builders.destroy.addLine(`if (${name}) ${name}.destroy(false);`);
} else { } else {
const expression = (this.name === ':Self' || this.name === 'svelte:self') const expression = this.name === 'svelte:self'
? generator.name ? generator.name
: `%components-${this.name}`; : `%components-${this.name}`;
@ -440,7 +440,7 @@ export default class Component extends Node {
} }
remount(name: string) { remount(name: string) {
return `${this.var}._mount(${name}._slotted${this.generator.legacy ? `["default"]` : `.default`}, null);`; return `${this.var}._mount(${name}._slotted.default, null);`;
} }
} }

@ -455,6 +455,6 @@ export default class EachBlock extends Node {
remount(name: string) { remount(name: string) {
// TODO consider keyed blocks // TODO consider keyed blocks
return `for (var #i = 0; #i < ${this.iterations}.length; #i += 1) ${this.iterations}[#i].m(${name}._slotted${this.generator.legacy ? `["default"]` : `.default`}, null);`; return `for (var #i = 0; #i < ${this.iterations}.length; #i += 1) ${this.iterations}[#i].m(${name}._slotted.default, null);`;
} }
} }

@ -461,7 +461,7 @@ export default class Element extends Node {
if (attr.type === 'Attribute') { if (attr.type === 'Attribute') {
const { dynamic, value, dependencies } = mungeAttribute(attr, block); const { dynamic, value, dependencies } = mungeAttribute(attr, block);
const snippet = `{ ${quoteIfNecessary(attr.name, this.generator.legacy)}: ${value} }`; const snippet = `{ ${quoteIfNecessary(attr.name)}: ${value} }`;
initialProps.push(snippet); initialProps.push(snippet);
const condition = dependencies && dependencies.map(d => `changed.${d}`).join(' || '); const condition = dependencies && dependencies.map(d => `changed.${d}`).join(' || ');
@ -519,10 +519,19 @@ export default class Element extends Node {
if (!validCalleeObjects.has(flattened.name)) { if (!validCalleeObjects.has(flattened.name)) {
// allow event.stopPropagation(), this.select() etc // allow event.stopPropagation(), this.select() etc
// TODO verify that it's a valid callee (i.e. built-in or declared method) // TODO verify that it's a valid callee (i.e. built-in or declared method)
generator.code.prependRight( if (flattened.name[0] === '$' && !generator.methods.has(flattened.name)) {
attribute.expression.start, generator.code.overwrite(
`${block.alias('component')}.` attribute.expression.start,
); attribute.expression.start + 1,
`${block.alias('component')}.store.`
);
} else {
generator.code.prependRight(
attribute.expression.start,
`${block.alias('component')}.`
);
}
if (shouldHoist) eventHandlerUsesComponent = true; // this feels a bit hacky but it works! if (shouldHoist) eventHandlerUsesComponent = true; // this feels a bit hacky but it works!
} }
@ -580,16 +589,8 @@ export default class Element extends Node {
}); });
`); `);
if (generator.options.dev) {
block.builders.hydrate.addBlock(deindent`
if (${handlerName}.teardown) {
console.warn("Return 'destroy()' from custom event handlers. Returning 'teardown()' has been deprecated and will be unsupported in Svelte 2");
}
`);
}
block.builders.destroy.addLine(deindent` block.builders.destroy.addLine(deindent`
${handlerName}[${handlerName}.destroy ? 'destroy' : 'teardown'](); ${handlerName}.destroy();
`); `);
} else { } else {
const handler = deindent` const handler = deindent`
@ -789,7 +790,7 @@ export default class Element extends Node {
return `@appendNode(${this.var}, ${name}._slotted.${this.getStaticAttributeValue('slot')});`; return `@appendNode(${this.var}, ${name}._slotted.${this.getStaticAttributeValue('slot')});`;
} }
return `@appendNode(${this.var}, ${name}._slotted${this.generator.legacy ? `["default"]` : `.default`});`; return `@appendNode(${this.var}, ${name}._slotted.default);`;
} }
addCssClass() { addCssClass() {
@ -839,7 +840,7 @@ function getClaimStatement(
) { ) {
const attributes = node.attributes const attributes = node.attributes
.filter((attr: Node) => attr.type === 'Attribute') .filter((attr: Node) => attr.type === 'Attribute')
.map((attr: Node) => `${quoteProp(attr.name, generator.legacy)}: true`) .map((attr: Node) => `${quoteIfNecessary(attr.name)}: true`)
.join(', '); .join(', ');
const name = namespace ? node.name : node.name.toUpperCase(); const name = namespace ? node.name : node.name.toUpperCase();
@ -849,13 +850,6 @@ function getClaimStatement(
: `{}`}, ${namespace === namespaces.svg ? true : false})`; : `{}`}, ${namespace === namespaces.svg ? true : false})`;
} }
function quoteProp(name: string, legacy: boolean) {
const isLegacyPropName = legacy && reservedNames.has(name);
if (/[^a-zA-Z_$0-9]/.test(name) || isLegacyPropName) return `"${name}"`;
return name;
}
function stringifyAttributeValue(value: Node | true) { function stringifyAttributeValue(value: Node | true) {
if (value === true) return ''; if (value === true) return '';
if (value.length === 0) return `=""`; if (value.length === 0) return `=""`;

@ -28,6 +28,6 @@ export default class MustacheTag extends Tag {
} }
remount(name: string) { remount(name: string) {
return `@appendNode(${this.var}, ${name}._slotted${this.generator.legacy ? `["default"]` : `.default`});`; return `@appendNode(${this.var}, ${name}._slotted.default);`;
} }
} }

@ -91,6 +91,6 @@ export default class RawMustacheTag extends Tag {
} }
remount(name: string) { remount(name: string) {
return `@appendNode(${this.var}, ${name}._slotted${this.generator.legacy ? `["default"]` : `.default`});`; return `@appendNode(${this.var}, ${name}._slotted.default);`;
} }
} }

@ -37,7 +37,7 @@ export default class Slot extends Element {
generator.slots.add(slotName); generator.slots.add(slotName);
const content_name = block.getUniqueName(`slot_content_${slotName}`); const content_name = block.getUniqueName(`slot_content_${slotName}`);
const prop = !isValidIdentifier(slotName) || (generator.legacy && reservedNames.has(slotName)) ? `["${slotName}"]` : `.${slotName}`; const prop = !isValidIdentifier(slotName) ? `["${slotName}"]` : `.${slotName}`;
block.addVariable(content_name, `#component._slotted${prop}`); block.addVariable(content_name, `#component._slotted${prop}`);
const needsAnchorBefore = this.prev ? this.prev.type !== 'Element' : !parentNode; const needsAnchorBefore = this.prev ? this.prev.type !== 'Element' : !parentNode;

@ -60,6 +60,6 @@ export default class Text extends Node {
} }
remount(name: string) { remount(name: string) {
return `@appendNode(${this.var}, ${name}._slotted${this.generator.legacy ? `["default"]` : `.default`});`; return `@appendNode(${this.var}, ${name}._slotted.default);`;
} }
} }

@ -86,16 +86,8 @@ export default class Window extends Node {
}); });
`); `);
if (generator.options.dev) {
block.builders.hydrate.addBlock(deindent`
if (${handlerName}.teardown) {
console.warn("Return 'destroy()' from custom event handlers. Returning 'teardown()' has been deprecated and will be unsupported in Svelte 2");
}
`);
}
block.builders.destroy.addLine(deindent` block.builders.destroy.addLine(deindent`
${handlerName}[${handlerName}.destroy ? 'destroy' : 'teardown'](); ${handlerName}.destroy();
`); `);
} else { } else {
block.builders.init.addBlock(deindent` block.builders.init.addBlock(deindent`
@ -180,41 +172,23 @@ export default class Window extends Node {
}); });
// special case... might need to abstract this out if we add more special cases // special case... might need to abstract this out if we add more special cases
if (bindings.scrollX && bindings.scrollY) { if (bindings.scrollX || bindings.scrollY) {
const observerCallback = block.getUniqueName(`scrollobserver`);
block.builders.init.addBlock(deindent`
function ${observerCallback}() {
${lock} = true;
clearTimeout(${timeout});
var x = ${bindings.scrollX
? `#component.get("${bindings.scrollX}")`
: `window.pageXOffset`};
var y = ${bindings.scrollY
? `#component.get("${bindings.scrollY}")`
: `window.pageYOffset`};
window.scrollTo(x, y);
${timeout} = setTimeout(${clear}, 100);
}
`);
if (bindings.scrollX)
block.builders.init.addLine(
`#component.observe("${bindings.scrollX}", ${observerCallback});`
);
if (bindings.scrollY)
block.builders.init.addLine(
`#component.observe("${bindings.scrollY}", ${observerCallback});`
);
} else if (bindings.scrollX || bindings.scrollY) {
const isX = !!bindings.scrollX;
block.builders.init.addBlock(deindent` block.builders.init.addBlock(deindent`
#component.observe("${bindings.scrollX || bindings.scrollY}", function(${isX ? 'x' : 'y'}) { #component.on("state", ({ changed, current }) => {
${lock} = true; if (${
clearTimeout(${timeout}); [bindings.scrollX, bindings.scrollY].map(
window.scrollTo(${isX ? 'x, window.pageYOffset' : 'window.pageXOffset, y'}); binding => binding && `changed["${binding}"]`
${timeout} = setTimeout(${clear}, 100); ).filter(Boolean).join(' || ')
}) {
${lock} = true;
clearTimeout(${timeout});
window.scrollTo(${
bindings.scrollX ? `current["${bindings.scrollX}"]` : `window.pageXOffset`
}, ${
bindings.scrollY ? `current["${bindings.scrollY}"]` : `window.pageYOffset`
});
${timeout} = setTimeout(${clear}, 100);
}
}); });
`); `);
} }

@ -58,7 +58,7 @@ export default class Node {
if (child.type === 'Comment') return; if (child.type === 'Comment') return;
// special case — this is an easy way to remove whitespace surrounding // special case — this is an easy way to remove whitespace surrounding
// <:Window/>. lil hacky but it works // <svelte:window/>. lil hacky but it works
if (child.type === 'Window') { if (child.type === 'Window') {
windowComponent = child; windowComponent = child;
return; return;
@ -163,6 +163,6 @@ export default class Node {
} }
remount(name: string) { remount(name: string) {
return `${this.var}.m(${name}._slotted${this.generator.legacy ? `["default"]` : `.default`}, null);`; return `${this.var}.m(${name}._slotted.default, null);`;
} }
} }

@ -55,7 +55,7 @@ export default function mungeAttribute(attribute: Node, block: Block): MungedAtt
return { return {
spread: false, spread: false,
name: attribute.name, name: attribute.name,
value: isNaN(value.data) ? stringify(value.data) : value.data, value: stringify(value.data),
dynamic: false, dynamic: false,
dependencies: [] dependencies: []
}; };

@ -75,7 +75,7 @@ export default function ssr(
// generate initial state object // generate initial state object
const expectedProperties = Array.from(generator.expectedProperties); const expectedProperties = Array.from(generator.expectedProperties);
const globals = expectedProperties.filter(prop => globalWhitelist.has(prop)); const globals = expectedProperties.filter(prop => globalWhitelist.has(prop));
const storeProps = options.store || templateProperties.store ? expectedProperties.filter(prop => prop[0] === '$') : []; const storeProps = expectedProperties.filter(prop => prop[0] === '$');
const initialState = []; const initialState = [];
if (globals.length > 0) { if (globals.length > 0) {
@ -84,9 +84,7 @@ export default function ssr(
if (storeProps.length > 0) { if (storeProps.length > 0) {
const initialize = `_init([${storeProps.map(prop => `"${prop.slice(1)}"`)}])` const initialize = `_init([${storeProps.map(prop => `"${prop.slice(1)}"`)}])`
if (options.store || templateProperties.store) { initialState.push(`options.store.${initialize}`);
initialState.push(`options.store.${initialize}`);
}
} }
if (templateProperties.data) { if (templateProperties.data) {
@ -139,7 +137,7 @@ export default function ssr(
${computations.map( ${computations.map(
({ key, deps }) => ({ key, deps }) =>
`state.${key} = %computed-${key}(${generator.v2 ? 'state' : deps.map(dep => `state.${dep}`).join(', ')});` `state.${key} = %computed-${key}(state);`
)} )}
${generator.bindings.length && ${generator.bindings.length &&
@ -163,47 +161,6 @@ export default function ssr(
}; };
var warned = false; var warned = false;
${name}.renderCss = function() {
if (!warned) {
console.error('Component.renderCss(...) is deprecated and will be removed in v2 — use Component.render(...).css instead');
warned = true;
}
var components = [];
${generator.stylesheet.hasStyles &&
deindent`
components.push({
filename: ${name}.filename,
css: ${name}.css && ${name}.css.code,
map: ${name}.css && ${name}.css.map
});
`}
${templateProperties.components &&
deindent`
var seen = {};
function addComponent(component) {
var result = component.renderCss();
result.components.forEach(x => {
if (seen[x.filename]) return;
seen[x.filename] = true;
components.push(x);
});
}
${templateProperties.components.value.properties.map((prop: Node) => {
return `addComponent(%components-${getName(prop.key)});`;
})}
`}
return {
css: components.map(x => x.css).join('\\n'),
map: null,
components
};
};
${templateProperties.preload && `${name}.preload = %preload;`} ${templateProperties.preload && `${name}.preload = %preload;`}

@ -57,7 +57,7 @@ export default function visitComponent(
if (attribute.value.length === 1) { if (attribute.value.length === 1) {
const chunk = attribute.value[0]; const chunk = attribute.value[0];
if (chunk.type === 'Text') { if (chunk.type === 'Text') {
return isNaN(chunk.data) ? stringify(chunk.data) : chunk.data; return stringify(chunk.data);
} }
block.contextualise(chunk.expression); block.contextualise(chunk.expression);
@ -87,11 +87,11 @@ export default function visitComponent(
.concat(bindingProps) .concat(bindingProps)
.join(', ')} }`; .join(', ')} }`;
const isDynamicComponent = node.name === ':Component' || node.name === 'svelte:component'; const isDynamicComponent = node.name === 'svelte:component';
if (isDynamicComponent) block.contextualise(node.expression); if (isDynamicComponent) block.contextualise(node.expression);
const expression = ( const expression = (
(node.name === ':Self' || node.name === 'svelte:self') ? generator.name : node.name === 'svelte:self' ? generator.name :
isDynamicComponent ? `((${node.metadata.snippet}) || __missingComponent)` : isDynamicComponent ? `((${node.metadata.snippet}) || __missingComponent)` :
`%components-${node.name}` `%components-${node.name}`
); );
@ -103,9 +103,7 @@ export default function visitComponent(
let open = `\${${expression}._render(__result, ${props}`; let open = `\${${expression}._render(__result, ${props}`;
const options = []; const options = [];
if (generator.options.store) { options.push(`store: options.store`);
options.push(`store: options.store`);
}
if (node.children.length) { if (node.children.length) {
const appendTarget: AppendTarget = { const appendTarget: AppendTarget = {

@ -23,9 +23,9 @@ function normalizeOptions(options: CompileOptions): CompileOptions {
} }
function defaultOnwarn(warning: Warning) { function defaultOnwarn(warning: Warning) {
if (warning.loc) { if (warning.start) {
console.warn( console.warn(
`(${warning.loc.line}:${warning.loc.column}) ${warning.message}` `(${warning.start.line}:${warning.start.column}) ${warning.message}`
); // eslint-disable-line no-console ); // eslint-disable-line no-console
} else { } else {
console.warn(warning.message); // eslint-disable-line no-console console.warn(warning.message); // eslint-disable-line no-console
@ -106,11 +106,13 @@ export async function preprocess(source: string, options: PreprocessOptions) {
}; };
} }
export function compile(source: string, _options: CompileOptions) { function compile(source: string, _options: CompileOptions) {
const options = normalizeOptions(_options); const options = normalizeOptions(_options);
let parsed: Parsed; let parsed: Parsed;
const stats = new Stats(); const stats = new Stats({
onwarn: options.onwarn
});
try { try {
stats.start('parse'); stats.start('parse');
@ -122,37 +124,33 @@ export function compile(source: string, _options: CompileOptions) {
} }
stats.start('stylesheet'); stats.start('stylesheet');
const stylesheet = new Stylesheet(source, parsed, options.filename, options.cascade !== false, options.dev); const stylesheet = new Stylesheet(source, parsed, options.filename, options.dev);
stats.stop('stylesheet'); stats.stop('stylesheet');
stats.start('validate'); stats.start('validate');
// TODO remove this when we remove svelte.validate from public API — we validate(parsed, source, stylesheet, stats, options);
// can use the stats object instead
const onwarn = options.onwarn;
options.onwarn = warning => {
stats.warnings.push(warning);
onwarn(warning);
};
validate(parsed, source, stylesheet, options);
stats.stop('validate'); stats.stop('validate');
if (options.generate === false) {
return { ast: parsed, stats, js: null, css: null };
}
const compiler = options.generate === 'ssr' ? generateSSR : generate; const compiler = options.generate === 'ssr' ? generateSSR : generate;
return compiler(parsed, source, stylesheet, options, stats); return compiler(parsed, source, stylesheet, options, stats);
}; };
export function create(source: string, _options: CompileOptions = {}) { function create(source: string, _options: CompileOptions = {}) {
_options.format = 'eval'; _options.format = 'eval';
const compiled = compile(source, _options); const compiled = compile(source, _options);
if (!compiled || !compiled.code) { if (!compiled || !compiled.js.code) {
return; return;
} }
try { try {
return (0, eval)(compiled.code); return (new Function(`return ${compiled.js.code}`))();
} catch (err) { } catch (err) {
if (_options.onerror) { if (_options.onerror) {
_options.onerror(err); _options.onerror(err);
@ -163,4 +161,4 @@ export function create(source: string, _options: CompileOptions = {}) {
} }
} }
export { parse, validate, Stylesheet, version as VERSION }; export { parse, create, compile, version as VERSION };

@ -21,14 +21,13 @@ export interface Parser {
} }
export interface Parsed { export interface Parsed {
hash: number;
html: Node; html: Node;
css: Node; css: Node;
js: Node; js: Node;
} }
export interface Warning { export interface Warning {
loc?: { line: number; column: number; pos?: number }; start?: { line: number; column: number; pos?: number };
end?: { line: number; column: number; }; end?: { line: number; column: number; };
pos?: number; pos?: number;
code: string; code: string;
@ -44,7 +43,7 @@ export interface CompileOptions {
format?: ModuleFormat; format?: ModuleFormat;
name?: string; name?: string;
filename?: string; filename?: string;
generate?: string; generate?: string | false;
globals?: ((id: string) => string) | object; globals?: ((id: string) => string) | object;
amd?: { amd?: {
id?: string; id?: string;
@ -56,19 +55,15 @@ export interface CompileOptions {
dev?: boolean; dev?: boolean;
immutable?: boolean; immutable?: boolean;
shared?: boolean | string; shared?: boolean | string;
cascade?: boolean;
hydratable?: boolean; hydratable?: boolean;
legacy?: boolean; legacy?: boolean;
customElement?: CustomElementOptions | true; customElement?: CustomElementOptions | true;
css?: boolean; css?: boolean;
store?: boolean;
preserveComments?: boolean | false; preserveComments?: boolean | false;
onerror?: (error: Error) => void; onerror?: (error: Error) => void;
onwarn?: (warning: Warning) => void; onwarn?: (warning: Warning) => void;
parser?: 'v2';
} }
export interface GenerateOptions { export interface GenerateOptions {

@ -3,23 +3,19 @@ import { locate, Location } from 'locate-character';
import fragment from './state/fragment'; import fragment from './state/fragment';
import { whitespace } from '../utils/patterns'; import { whitespace } from '../utils/patterns';
import { trimStart, trimEnd } from '../utils/trim'; import { trimStart, trimEnd } from '../utils/trim';
import getCodeFrame from '../utils/getCodeFrame';
import reservedNames from '../utils/reservedNames'; import reservedNames from '../utils/reservedNames';
import fullCharCodeAt from '../utils/fullCharCodeAt'; import fullCharCodeAt from '../utils/fullCharCodeAt';
import hash from '../utils/hash';
import { Node, Parsed } from '../interfaces'; import { Node, Parsed } from '../interfaces';
import error from '../utils/error'; import error from '../utils/error';
interface ParserOptions { interface ParserOptions {
filename?: string; filename?: string;
bind?: boolean; bind?: boolean;
parser?: 'v2';
} }
type ParserState = (parser: Parser) => (ParserState | void); type ParserState = (parser: Parser) => (ParserState | void);
export class Parser { export class Parser {
readonly v2: boolean;
readonly template: string; readonly template: string;
readonly filename?: string; readonly filename?: string;
@ -34,8 +30,6 @@ export class Parser {
allowBindings: boolean; allowBindings: boolean;
constructor(template: string, options: ParserOptions) { constructor(template: string, options: ParserOptions) {
this.v2 = options.parser === 'v2';
if (typeof template !== 'string') { if (typeof template !== 'string') {
throw new TypeError('Template must be a string'); throw new TypeError('Template must be a string');
} }
@ -229,7 +223,6 @@ export default function parse(
): Parsed { ): Parsed {
const parser = new Parser(template, options); const parser = new Parser(template, options);
return { return {
hash: hash(parser.template),
html: parser.html, html: parser.html,
css: parser.css, css: parser.css,
js: parser.js, js: parser.js,

@ -165,10 +165,10 @@ export function readDirective(
// assume the mistake was wrapping the directive arguments. // assume the mistake was wrapping the directive arguments.
// this could yield false positives! but hopefully not too many // this could yield false positives! but hopefully not too many
let message = 'directive values should not be wrapped'; let message = 'directive values should not be wrapped';
const expressionEnd = parser.template.indexOf((parser.v2 ? '}' : '}}'), expressionStart); const expressionEnd = parser.template.indexOf('}', expressionStart);
if (expressionEnd !== -1) { if (expressionEnd !== -1) {
const value = parser.template.slice(expressionStart + (parser.v2 ? 1 : 2), expressionEnd); const value = parser.template.slice(expressionStart + 1, expressionEnd);
message += ` — use '${value}', not '${parser.v2 ? `{${value}}` : `{{${value}}}`}'`; message += ` — use '${value}', not '{${value}}'`;
} }
parser.error({ parser.error({
code: `invalid-directive-value`, code: `invalid-directive-value`,

@ -6,7 +6,7 @@ const literals = new Map([['true', true], ['false', false], ['null', null]]);
export default function readExpression(parser: Parser) { export default function readExpression(parser: Parser) {
const start = parser.index; const start = parser.index;
const name = parser.readUntil(parser.v2 ? /\s*}/ : /\s*}}/); const name = parser.readUntil(/\s*}/);
if (name && /^[a-z]+$/.test(name)) { if (name && /^[a-z]+$/.test(name)) {
const end = start + name.length; const end = start + name.length;

@ -8,7 +8,7 @@ export default function fragment(parser: Parser) {
return tag; return tag;
} }
if (parser.match(parser.v2 ? '{' : '{{')) { if (parser.match('{')) {
return mustache; return mustache;
} }

@ -32,7 +32,7 @@ function trimWhitespace(block: Node, trimBefore: boolean, trimAfter: boolean) {
export default function mustache(parser: Parser) { export default function mustache(parser: Parser) {
const start = parser.index; const start = parser.index;
parser.index += parser.v2 ? 1 : 2; parser.index += 1;
parser.allowWhitespace(); parser.allowWhitespace();
@ -64,7 +64,7 @@ export default function mustache(parser: Parser) {
parser.eat(expected, true); parser.eat(expected, true);
parser.allowWhitespace(); parser.allowWhitespace();
parser.eat(parser.v2 ? '}' : '}}', true); parser.eat('}', true);
while (block.elseif) { while (block.elseif) {
block.end = parser.index; block.end = parser.index;
@ -86,7 +86,7 @@ export default function mustache(parser: Parser) {
block.end = parser.index; block.end = parser.index;
parser.stack.pop(); parser.stack.pop();
} else if (parser.eat(parser.v2 ? ':elseif' : 'elseif')) { } else if (parser.eat(':elseif')) {
const block = parser.current(); const block = parser.current();
if (block.type !== 'IfBlock') if (block.type !== 'IfBlock')
parser.error({ parser.error({
@ -99,7 +99,7 @@ export default function mustache(parser: Parser) {
const expression = readExpression(parser); const expression = readExpression(parser);
parser.allowWhitespace(); parser.allowWhitespace();
parser.eat(parser.v2 ? '}' : '}}', true); parser.eat('}', true);
block.else = { block.else = {
start: parser.index, start: parser.index,
@ -118,7 +118,7 @@ export default function mustache(parser: Parser) {
}; };
parser.stack.push(block.else.children[0]); parser.stack.push(block.else.children[0]);
} else if (parser.eat(parser.v2 ? ':else' : 'else')) { } else if (parser.eat(':else')) {
const block = parser.current(); const block = parser.current();
if (block.type !== 'IfBlock' && block.type !== 'EachBlock') { if (block.type !== 'IfBlock' && block.type !== 'EachBlock') {
parser.error({ parser.error({
@ -128,7 +128,7 @@ export default function mustache(parser: Parser) {
} }
parser.allowWhitespace(); parser.allowWhitespace();
parser.eat(parser.v2 ? '}' : '}}', true); parser.eat('}', true);
block.else = { block.else = {
start: parser.index, start: parser.index,
@ -138,7 +138,7 @@ export default function mustache(parser: Parser) {
}; };
parser.stack.push(block.else); parser.stack.push(block.else);
} else if (parser.eat(parser.v2 ? ':then' : 'then')) { } else if (parser.eat(':then')) {
// TODO DRY out this and the next section // TODO DRY out this and the next section
const pendingBlock = parser.current(); const pendingBlock = parser.current();
if (pendingBlock.type === 'PendingBlock') { if (pendingBlock.type === 'PendingBlock') {
@ -150,7 +150,7 @@ export default function mustache(parser: Parser) {
awaitBlock.value = parser.readIdentifier(); awaitBlock.value = parser.readIdentifier();
parser.allowWhitespace(); parser.allowWhitespace();
parser.eat(parser.v2 ? '}' : '}}', true); parser.eat('}', true);
const thenBlock: Node = { const thenBlock: Node = {
start, start,
@ -162,7 +162,7 @@ export default function mustache(parser: Parser) {
awaitBlock.then = thenBlock; awaitBlock.then = thenBlock;
parser.stack.push(thenBlock); parser.stack.push(thenBlock);
} }
} else if (parser.eat(parser.v2 ? ':catch' : 'catch')) { } else if (parser.eat(':catch')) {
const thenBlock = parser.current(); const thenBlock = parser.current();
if (thenBlock.type === 'ThenBlock') { if (thenBlock.type === 'ThenBlock') {
thenBlock.end = start; thenBlock.end = start;
@ -173,7 +173,7 @@ export default function mustache(parser: Parser) {
awaitBlock.error = parser.readIdentifier(); awaitBlock.error = parser.readIdentifier();
parser.allowWhitespace(); parser.allowWhitespace();
parser.eat(parser.v2 ? '}' : '}}', true); parser.eat('}', true);
const catchBlock: Node = { const catchBlock: Node = {
start, start,
@ -336,7 +336,7 @@ export default function mustache(parser: Parser) {
parser.allowWhitespace(); parser.allowWhitespace();
} }
parser.eat(parser.v2 ? '}' : '}}', true); parser.eat('}', true);
parser.current().children.push(block); parser.current().children.push(block);
parser.stack.push(block); parser.stack.push(block);
@ -346,44 +346,12 @@ export default function mustache(parser: Parser) {
childBlock.start = parser.index; childBlock.start = parser.index;
parser.stack.push(childBlock); parser.stack.push(childBlock);
} }
} else if (parser.eat('yield')) { } else if (parser.eat('@html')) {
// {{yield}}
// TODO deprecate
parser.allowWhitespace();
if (parser.v2) {
const expressionEnd = parser.index;
parser.eat('}', true);
parser.current().children.push({
start,
end: parser.index,
type: 'MustacheTag',
expression: {
start: expressionEnd - 5,
end: expressionEnd,
type: 'Identifier',
name: 'yield'
}
});
} else {
parser.eat('}}', true);
parser.current().children.push({
start,
end: parser.index,
type: 'Element',
name: 'slot',
attributes: [],
children: []
});
}
} else if (parser.eat(parser.v2 ? '@html' : '{')) {
// {{{raw}}} mustache // {{{raw}}} mustache
const expression = readExpression(parser); const expression = readExpression(parser);
parser.allowWhitespace(); parser.allowWhitespace();
parser.eat(parser.v2 ? '}' : '}}}', true); parser.eat('}', true);
parser.current().children.push({ parser.current().children.push({
start, start,
@ -395,7 +363,7 @@ export default function mustache(parser: Parser) {
const expression = readExpression(parser); const expression = readExpression(parser);
parser.allowWhitespace(); parser.allowWhitespace();
parser.eat(parser.v2 ? '}' : '}}', true); parser.eat('}', true);
parser.current().children.push({ parser.current().children.push({
start, start,

@ -11,8 +11,6 @@ import { Node } from '../../interfaces';
const validTagName = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/; const validTagName = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/;
const metaTags = new Map([ const metaTags = new Map([
[':Window', 'Window'],
[':Head', 'Head'],
['svelte:window', 'Window'], ['svelte:window', 'Window'],
['svelte:head', 'Head'] ['svelte:head', 'Head']
]); ]);
@ -34,6 +32,9 @@ const specials = new Map([
], ],
]); ]);
const SELF = 'svelte:self';
const COMPONENT = 'svelte:component';
// based on http://developers.whatwg.org/syntax.html#syntax-tag-omission // based on http://developers.whatwg.org/syntax.html#syntax-tag-omission
const disallowedContents = new Map([ const disallowedContents = new Map([
['li', new Set(['li'])], ['li', new Set(['li'])],
@ -85,7 +86,7 @@ export default function tag(parser: Parser) {
if (metaTags.has(name)) { if (metaTags.has(name)) {
const slug = metaTags.get(name).toLowerCase(); const slug = metaTags.get(name).toLowerCase();
if (isClosingTag) { if (isClosingTag) {
if ((name === ':Window' || name === 'svelte:window') && parser.current().children.length) { if (name === 'svelte:window' && parser.current().children.length) {
parser.error({ parser.error({
code: `invalid-window-content`, code: `invalid-window-content`,
message: `<${name}> cannot have children` message: `<${name}> cannot have children`
@ -175,14 +176,6 @@ export default function tag(parser: Parser) {
} }
} }
if (name === ':Component') {
parser.eat('{', true);
element.expression = readExpression(parser);
parser.allowWhitespace();
parser.eat('}', true);
parser.allowWhitespace();
}
const uniqueNames = new Set(); const uniqueNames = new Set();
let attribute; let attribute;
@ -198,7 +191,7 @@ export default function tag(parser: Parser) {
parser.allowWhitespace(); parser.allowWhitespace();
} }
if (parser.v2 && name === 'svelte:component') { if (name === 'svelte:component') {
// TODO post v2, treat this just as any other attribute // TODO post v2, treat this just as any other attribute
const index = element.attributes.findIndex(attr => attr.name === 'this'); const index = element.attributes.findIndex(attr => attr.name === 'this');
if (!~index) { if (!~index) {
@ -264,21 +257,11 @@ export default function tag(parser: Parser) {
element.end = parser.index; element.end = parser.index;
} else if (name === 'style') { } else if (name === 'style') {
// special case // special case
if (parser.v2) { const start = parser.index;
const start = parser.index; const data = parser.readUntil(/<\/style>/);
const data = parser.readUntil(/<\/style>/); const end = parser.index;
const end = parser.index; element.children.push({ start, end, type: 'Text', data });
element.children.push({ start, end, type: 'Text', data }); parser.eat('</style>', true);
parser.eat('</style>', true);
} else {
element.children = readSequence(
parser,
() =>
parser.template.slice(parser.index, parser.index + 8) === '</style>'
);
parser.read(/<\/style>/);
element.end = parser.index;
}
} else { } else {
parser.stack.push(element); parser.stack.push(element);
} }
@ -287,10 +270,6 @@ export default function tag(parser: Parser) {
function readTagName(parser: Parser) { function readTagName(parser: Parser) {
const start = parser.index; const start = parser.index;
// TODO hoist these back to the top, post-v2
const SELF = parser.v2 ? 'svelte:self' : ':Self';
const COMPONENT = parser.v2 ? 'svelte:component' : ':Component';
if (parser.eat(SELF)) { if (parser.eat(SELF)) {
// check we're inside a block, otherwise this // check we're inside a block, otherwise this
// will cause infinite recursion // will cause infinite recursion
@ -334,14 +313,14 @@ function readTagName(parser: Parser) {
function readAttribute(parser: Parser, uniqueNames: Set<string>) { function readAttribute(parser: Parser, uniqueNames: Set<string>) {
const start = parser.index; const start = parser.index;
if (parser.eat(parser.v2 ? '{' : '{{')) { if (parser.eat('{')) {
parser.allowWhitespace(); parser.allowWhitespace();
if (parser.eat('...')) { if (parser.eat('...')) {
const expression = readExpression(parser); const expression = readExpression(parser);
parser.allowWhitespace(); parser.allowWhitespace();
parser.eat(parser.v2 ? '}' : '}}', true); parser.eat('}', true);
return { return {
start, start,
@ -350,13 +329,6 @@ function readAttribute(parser: Parser, uniqueNames: Set<string>) {
expression expression
}; };
} else { } else {
if (!parser.v2) {
parser.error({
code: `expected-spread`,
message: 'Expected spread operator (...)'
});
}
const valueStart = parser.index; const valueStart = parser.index;
const name = parser.readIdentifier(); const name = parser.readIdentifier();
@ -449,7 +421,7 @@ function readSequence(parser: Parser, done: () => boolean) {
}); });
return chunks; return chunks;
} else if (parser.eat(parser.v2 ? '{' : '{{')) { } else if (parser.eat('{')) {
if (currentChunk.data) { if (currentChunk.data) {
currentChunk.end = index; currentChunk.end = index;
chunks.push(currentChunk); chunks.push(currentChunk);
@ -457,7 +429,7 @@ function readSequence(parser: Parser, done: () => boolean) {
const expression = readExpression(parser); const expression = readExpression(parser);
parser.allowWhitespace(); parser.allowWhitespace();
parser.eat(parser.v2 ? '}' : '}}', true); parser.eat('}', true);
chunks.push({ chunks.push({
start: index, start: index,

@ -9,7 +9,7 @@ export default function text(parser: Parser) {
while ( while (
parser.index < parser.template.length && parser.index < parser.template.length &&
!parser.match('<') && !parser.match('<') &&
!parser.match(parser.v2 ? '{' : '{{') !parser.match('{')
) { ) {
data += parser.template[parser.index++]; data += parser.template[parser.index++];
} }

@ -2,29 +2,28 @@ import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import { compile } from '../index.ts'; import { compile } from '../index.ts';
let compileOptions = {}; let compileOptions = {
extensions: ['.html']
};
function capitalise(name) { function capitalise(name) {
return name[0].toUpperCase() + name.slice(1); return name[0].toUpperCase() + name.slice(1);
} }
export default function register(options) { export default function register(options) {
const { extensions } = options; if (options.extensions) {
compileOptions.extensions.forEach(deregisterExtension);
if (extensions) { options.extensions.forEach(registerExtension);
_deregister('.html');
extensions.forEach(_register);
} }
// TODO make this the default and remove in v2 compileOptions = options;
if (options) compileOptions = options;
} }
function _deregister(extension) { function deregisterExtension(extension) {
delete require.extensions[extension]; delete require.extensions[extension];
} }
function _register(extension) { function registerExtension(extension) {
require.extensions[extension] = function(module, filename) { require.extensions[extension] = function(module, filename) {
const name = path.basename(filename) const name = path.basename(filename)
.slice(0, -path.extname(filename).length) .slice(0, -path.extname(filename).length)
@ -37,10 +36,10 @@ function _register(extension) {
generate: 'ssr' generate: 'ssr'
}); });
const {code} = compile(fs.readFileSync(filename, 'utf-8'), options); const { js } = compile(fs.readFileSync(filename, 'utf-8'), options);
return module._compile(code, filename); return module._compile(js.code, filename);
}; };
} }
_register('.html'); registerExtension('.html');

@ -29,7 +29,7 @@
"plugin:import/warnings" "plugin:import/warnings"
], ],
"parserOptions": { "parserOptions": {
"ecmaVersion": 6, "ecmaVersion": 9,
"sourceType": "module" "sourceType": "module"
}, },
"settings": { "settings": {

@ -9,18 +9,13 @@ fs.readdirSync(__dirname).forEach(file => {
const source = fs.readFileSync(path.join(__dirname, file), 'utf-8'); const source = fs.readFileSync(path.join(__dirname, file), 'utf-8');
const ast = acorn.parse(source, { const ast = acorn.parse(source, {
ecmaVersion: 6, ecmaVersion: 9,
sourceType: 'module' sourceType: 'module'
}); });
ast.body.forEach(node => { ast.body.forEach(node => {
if (node.type !== 'ExportNamedDeclaration') return; if (node.type !== 'ExportNamedDeclaration') return;
// check no ES6+ slipped in
acorn.parse(source.slice(node.declaration.start, node.end), {
ecmaVersion: 5
});
const declaration = node.declaration; const declaration = node.declaration;
if (!declaration) return; if (!declaration) return;

@ -51,13 +51,8 @@ export function fire(eventName, data) {
} }
} }
export function getDev(key) { export function get() {
if (key) console.warn("`let x = component.get('x')` is deprecated. Use `let { x } = component.get()` instead"); return this._state;
return get.call(this, key);
}
export function get(key) {
return key ? this._state[key] : this._state;
} }
export function init(component, options) { export function init(component, options) {
@ -69,37 +64,7 @@ export function init(component, options) {
component.store = component.root.store || options.store; component.store = component.root.store || options.store;
} }
export function observe(key, callback, options) {
var fn = callback.bind(this);
if (!options || options.init !== false) {
fn(this.get()[key], undefined);
}
return this.on(options && options.defer ? 'update' : 'state', function(event) {
if (event.changed[key]) fn(event.current[key], event.previous && event.previous[key]);
});
}
export function observeDev(key, callback, options) {
console.warn("this.observe(key, (newValue, oldValue) => {...}) is deprecated. Use\n\n // runs before DOM updates\n this.on('state', ({ changed, current, previous }) => {...});\n\n // runs after DOM updates\n this.on('update', ...);\n\n...or add the observe method from the svelte-extras package");
var c = (key = '' + key).search(/[.[]/);
if (c > -1) {
var message =
'The first argument to component.observe(...) must be the name of a top-level property';
if (c > 0)
message += ", i.e. '" + key.slice(0, c) + "' rather than '" + key + "'";
throw new Error(message);
}
return observe.call(this, key, callback, options);
}
export function on(eventName, handler) { export function on(eventName, handler) {
if (eventName === 'teardown') return this.on('destroy', handler);
var handlers = this._handlers[eventName] || (this._handlers[eventName] = []); var handlers = this._handlers[eventName] || (this._handlers[eventName] = []);
handlers.push(handler); handlers.push(handler);
@ -111,17 +76,6 @@ export function on(eventName, handler) {
}; };
} }
export function onDev(eventName, handler) {
if (eventName === 'teardown') {
console.warn(
"Use component.on('destroy', ...) instead of component.on('teardown', ...) which has been deprecated and will be unsupported in Svelte 2"
);
return this.on('destroy', handler);
}
return on.call(this, eventName, handler);
}
export function run(fn) { export function run(fn) {
fn(); fn();
} }
@ -193,31 +147,27 @@ export function removeFromStore() {
} }
export var proto = { export var proto = {
destroy: destroy, destroy,
get: get, get,
fire: fire, fire,
observe: observe, on,
on: on, set,
set: set,
teardown: destroy,
_recompute: noop, _recompute: noop,
_set: _set, _set,
_mount: _mount, _mount,
_unmount: _unmount, _unmount,
_differs: _differs _differs
}; };
export var protoDev = { export var protoDev = {
destroy: destroyDev, destroy: destroyDev,
get: getDev, get,
fire: fire, fire,
observe: observeDev, on,
on: onDev,
set: setDev, set: setDev,
teardown: destroyDev,
_recompute: noop, _recompute: noop,
_set: _set, _set,
_mount: _mount, _mount,
_unmount: _unmount, _unmount,
_differs: _differs _differs
}; };

@ -3,14 +3,14 @@ import getCodeFrame from '../utils/getCodeFrame';
class CompileError extends Error { class CompileError extends Error {
code: string; code: string;
loc: { line: number, column: number }; start: { line: number, column: number };
end: { line: number, column: number }; end: { line: number, column: number };
pos: number; pos: number;
filename: string; filename: string;
frame: string; frame: string;
toString() { toString() {
return `${this.message} (${this.loc.line}:${this.loc.column})\n${this.frame}`; return `${this.message} (${this.start.line}:${this.start.column})\n${this.frame}`;
} }
} }
@ -25,16 +25,16 @@ export default function error(message: string, props: {
const error = new CompileError(message); const error = new CompileError(message);
error.name = props.name; error.name = props.name;
const start = locate(props.source, props.start); const start = locate(props.source, props.start, { offsetLine: 1 });
const end = locate(props.source, props.end || props.start); const end = locate(props.source, props.end || props.start, { offsetLine: 1 });
error.code = props.code; error.code = props.code;
error.loc = { line: start.line + 1, column: start.column }; error.start = start;
error.end = { line: end.line + 1, column: end.column }; error.end = end;
error.pos = props.start; error.pos = props.start;
error.filename = props.filename; error.filename = props.filename;
error.frame = getCodeFrame(props.source, start.line, start.column); error.frame = getCodeFrame(props.source, start.line - 1, start.column);
throw error; throw error;
} }

@ -1,7 +1,7 @@
import isValidIdentifier from './isValidIdentifier'; import isValidIdentifier from './isValidIdentifier';
import reservedNames from './reservedNames'; import reservedNames from './reservedNames';
export default function quoteIfNecessary(name: string, legacy?: boolean) { export default function quoteIfNecessary(name) {
if (!isValidIdentifier(name) || (legacy && reservedNames.has(name))) return `"${name}"`; if (!isValidIdentifier(name)) return `"${name}"`;
return name; return name;
} }

@ -31,8 +31,6 @@ export default function validateHtml(validator: Validator, html: Node) {
else if (node.type === 'Element') { else if (node.type === 'Element') {
const isComponent = const isComponent =
node.name === ':Self' ||
node.name === ':Component' ||
node.name === 'svelte:self' || node.name === 'svelte:self' ||
node.name === 'svelte:component' || node.name === 'svelte:component' ||
validator.components.has(node.name); validator.components.has(node.name);

@ -19,8 +19,7 @@ export default function validateElement(
} }
if (!isComponent && /^[A-Z]/.test(node.name[0])) { if (!isComponent && /^[A-Z]/.test(node.name[0])) {
// TODO upgrade to validator.error in v2 validator.error(node, {
validator.warn(node, {
code: `missing-component`, code: `missing-component`,
message: `${node.name} component is not defined` message: `${node.name} component is not defined`
}); });
@ -298,7 +297,7 @@ function checkSlotAttribute(validator: Validator, node: Node, attribute: Node, s
const parent = stack[i]; const parent = stack[i];
if (parent.type === 'Element') { if (parent.type === 'Element') {
// if we're inside a component or a custom element, gravy // if we're inside a component or a custom element, gravy
if (parent.name === ':Self' || parent.name === ':Component' || parent.name === 'svelte:self' || parent.name === 'svelte:component' || validator.components.has(parent.name)) return; if (parent.name === 'svelte:self' || parent.name === 'svelte:component' || validator.components.has(parent.name)) return;
if (/-/.test(parent.name)) return; if (/-/.test(parent.name)) return;
} }

@ -31,35 +31,33 @@ export default function validateEventHandlerCallee(
return; return;
} }
if (name === 'store' && attribute.expression.callee.type === 'MemberExpression') {
if (!validator.options.store) {
validator.warn(attribute.expression, {
code: `options-missing-store`,
message: 'compile with `store: true` in order to call store methods'
});
}
return;
}
if ( if (
(callee.type === 'Identifier' && validBuiltins.has(callee.name)) || (callee.type === 'Identifier' && validBuiltins.has(callee.name)) ||
validator.methods.has(callee.name) validator.methods.has(callee.name)
) ) {
return;
}
if (name[0] === '$') {
// assume it's a store method
return; return;
}
const validCallees = ['this.*', 'event.*', 'options.*', 'console.*'].concat( const validCallees = ['this.*', 'event.*', 'options.*', 'console.*'].concat(
validator.options.store ? 'store.*' : [],
Array.from(validBuiltins), Array.from(validBuiltins),
Array.from(validator.methods.keys()) Array.from(validator.methods.keys())
); );
let message = `'${validator.source.slice( let message = `'${validator.source.slice(callee.start, callee.end)}' is an invalid callee ` ;
callee.start,
callee.end
)}' is an invalid callee (should be one of ${list(validCallees)})`;
if (callee.type === 'Identifier' && validator.helpers.has(callee.name)) { if (name === 'store') {
message += `. '${callee.name}' exists on 'helpers', did you put it in the wrong place?`; message += `(did you mean '$${validator.source.slice(callee.start + 6, callee.end)}(...)'?)`;
} else {
message += `(should be one of ${list(validCallees)})`;
if (callee.type === 'Identifier' && validator.helpers.has(callee.name)) {
message += `. '${callee.name}' exists on 'helpers', did you put it in the wrong place?`;
}
} }
validator.warn(attribute.expression, { validator.warn(attribute.expression, {

@ -6,7 +6,7 @@ export default function validateHead(validator: Validator, node: Node, refs: Map
if (node.attributes.length) { if (node.attributes.length) {
validator.error(node.attributes[0], { validator.error(node.attributes[0], {
code: `invalid-attribute`, code: `invalid-attribute`,
message: `<:Head> should not have any attributes or directives` message: `<svelte:head> should not have any attributes or directives`
}); });
} }

@ -23,7 +23,7 @@ export default function validateWindow(validator: Validator, node: Node, refs: M
validator.error(attribute.value, { validator.error(attribute.value, {
code: `invalid-binding`, code: `invalid-binding`,
message: `Bindings on <:Window/> must be to top-level properties, e.g. '${parts[parts.length - 1]}' rather than '${parts.join('.')}'` message: `Bindings on <svelte:window> must be to top-level properties, e.g. '${parts[parts.length - 1]}' rather than '${parts.join('.')}'`
}); });
} }
@ -34,7 +34,7 @@ export default function validateWindow(validator: Validator, node: Node, refs: M
? 'innerHeight' ? 'innerHeight'
: fuzzymatch(attribute.name, validBindings); : fuzzymatch(attribute.name, validBindings);
const message = `'${attribute.name}' is not a valid binding on <:Window>`; const message = `'${attribute.name}' is not a valid binding on <svelte:window>`;
if (match) { if (match) {
validator.error(attribute, { validator.error(attribute, {

@ -5,15 +5,15 @@ import getCodeFrame from '../utils/getCodeFrame';
import Stats from '../Stats'; import Stats from '../Stats';
import error from '../utils/error'; import error from '../utils/error';
import Stylesheet from '../css/Stylesheet'; import Stylesheet from '../css/Stylesheet';
import Stats from '../Stats';
import { Node, Parsed, CompileOptions, Warning } from '../interfaces'; import { Node, Parsed, CompileOptions, Warning } from '../interfaces';
export class Validator { export class Validator {
readonly source: string; readonly source: string;
readonly filename: string; readonly filename: string;
readonly v2: boolean; readonly stats: Stats;
options: CompileOptions; options: CompileOptions;
onwarn: ({}) => void;
locator?: (pos: number) => Location; locator?: (pos: number) => Location;
namespace: string; namespace: string;
@ -34,12 +34,12 @@ export class Validator {
actions: Set<string>; actions: Set<string>;
}; };
constructor(parsed: Parsed, source: string, options: CompileOptions) { constructor(parsed: Parsed, source: string, stats: Stats, options: CompileOptions) {
this.source = source; this.source = source;
this.stats = stats;
this.filename = options.filename; this.filename = options.filename;
this.onwarn = options.onwarn;
this.options = options; this.options = options;
this.v2 = options.parser === 'v2';
this.namespace = null; this.namespace = null;
this.defaultExport = null; this.defaultExport = null;
@ -73,18 +73,18 @@ export class Validator {
} }
warn(pos: { start: number, end: number }, { code, message }: { code: string, message: string }) { warn(pos: { start: number, end: number }, { code, message }: { code: string, message: string }) {
if (!this.locator) this.locator = getLocator(this.source); if (!this.locator) this.locator = getLocator(this.source, { offsetLine: 1 });
const start = this.locator(pos.start); const start = this.locator(pos.start);
const end = this.locator(pos.end); const end = this.locator(pos.end);
const frame = getCodeFrame(this.source, start.line, start.column); const frame = getCodeFrame(this.source, start.line - 1, start.column);
this.onwarn({ this.stats.warn({
code, code,
message, message,
frame, frame,
loc: { line: start.line + 1, column: start.column }, start,
end: { line: end.line + 1, column: end.column }, end,
pos: pos.start, pos: pos.start,
filename: this.filename, filename: this.filename,
toString: () => `${message} (${start.line + 1}:${start.column})\n${frame}`, toString: () => `${message} (${start.line + 1}:${start.column})\n${frame}`,
@ -96,9 +96,10 @@ export default function validate(
parsed: Parsed, parsed: Parsed,
source: string, source: string,
stylesheet: Stylesheet, stylesheet: Stylesheet,
stats: Stats,
options: CompileOptions options: CompileOptions
) { ) {
const { onwarn, onerror, name, filename, store, dev, parser } = options; const { onerror, name, filename, dev, parser } = options;
try { try {
if (name && !/^[a-zA-Z_$][a-zA-Z_$0-9]*$/.test(name)) { if (name && !/^[a-zA-Z_$][a-zA-Z_$0-9]*$/.test(name)) {
@ -108,7 +109,7 @@ export default function validate(
if (name && /^[a-z]/.test(name)) { if (name && /^[a-z]/.test(name)) {
const message = `options.name should be capitalised`; const message = `options.name should be capitalised`;
onwarn({ stats.warn({
code: `options-lowercase-name`, code: `options-lowercase-name`,
message, message,
filename, filename,
@ -116,11 +117,9 @@ export default function validate(
}); });
} }
const validator = new Validator(parsed, source, { const validator = new Validator(parsed, source, stats, {
onwarn,
name, name,
filename, filename,
store,
dev, dev,
parser parser
}); });

@ -27,9 +27,9 @@ export default function components(validator: Validator, prop: Node) {
} }
if (!/^[A-Z]/.test(name)) { if (!/^[A-Z]/.test(name)) {
validator.warn(component, { validator.error(component, {
code: `component-lowercase`, code: `component-lowercase`,
message: `Component names should be capitalised` message: `Component names must be capitalised`
}); });
} }
}); });

@ -30,14 +30,14 @@ export default function computed(validator: Validator, prop: Node) {
if (!isValidIdentifier(name)) { if (!isValidIdentifier(name)) {
const suggestion = name.replace(/[^_$a-z0-9]/ig, '_').replace(/^\d/, '_$&'); const suggestion = name.replace(/[^_$a-z0-9]/ig, '_').replace(/^\d/, '_$&');
validator.error(computation, { validator.error(computation.key, {
code: `invalid-computed-name`, code: `invalid-computed-name`,
message: `Computed property name '${name}' is invalid — must be a valid identifier such as ${suggestion}` message: `Computed property name '${name}' is invalid — must be a valid identifier such as ${suggestion}`
}); });
} }
if (reservedNames.has(name)) { if (reservedNames.has(name)) {
validator.error(computation, { validator.error(computation.key, {
code: `invalid-computed-name`, code: `invalid-computed-name`,
message: `Computed property name '${name}' is invalid — cannot be a JavaScript reserved word` message: `Computed property name '${name}' is invalid — cannot be a JavaScript reserved word`
}); });
@ -75,36 +75,19 @@ export default function computed(validator: Validator, prop: Node) {
}); });
} }
if (validator.v2) { if (params.length > 1) {
if (params.length > 1) { validator.error(computation.value, {
validator.error(computation.value, { code: `invalid-computed-arguments`,
code: `invalid-computed-arguments`, message: `Computed properties must take a single argument`
message: `Computed properties must take a single argument` });
}); }
}
const param = params[0];
if (param.type !== 'ObjectPattern') {
// TODO in v2, allow the entire object to be passed in
validator.error(computation.value, {
code: `invalid-computed-argument`,
message: `Computed property argument must be a destructured object pattern`
});
}
} else {
params.forEach((param: Node) => {
const valid =
param.type === 'Identifier' ||
(param.type === 'AssignmentPattern' &&
param.left.type === 'Identifier');
if (!valid) { const param = params[0];
// TODO change this for v2 if (param.type !== 'ObjectPattern') {
validator.error(param, { // TODO post-v2, allow the entire object to be passed in
code: `invalid-computed-arguments`, validator.error(computation.value, {
message: `Computed properties cannot use destructuring in function parameters` code: `invalid-computed-argument`,
}); message: `Computed property argument must be a destructured object pattern`
}
}); });
} }
}); });

@ -4,13 +4,11 @@ import {
_differs, _differs,
_differsImmutable, _differsImmutable,
get, get,
observe,
on, on,
fire fire
} from './shared.js'; } from './shared.js';
function Store(state, options) { function Store(state, options) {
this._observers = { pre: blankObject(), post: blankObject() };
this._handlers = {}; this._handlers = {};
this._dependents = []; this._dependents = [];
@ -110,20 +108,8 @@ assign(Store.prototype, {
get: get, get: get,
// TODO remove this method
observe: observe,
on: on, on: on,
onchange: function(callback) {
// TODO remove this method
console.warn("store.onchange is deprecated in favour of store.on('state', event => {...})");
return this.on('state', function(event) {
callback(event.current, event.changed);
});
},
set: function(newState) { set: function(newState) {
var oldState = this._state, var oldState = this._state,
changed = this._changed = {}, changed = this._changed = {},

@ -4,7 +4,7 @@ import { svelte, deindent } from "../helpers.js";
describe("create", () => { describe("create", () => {
it("should return a component constructor", () => { it("should return a component constructor", () => {
const source = deindent` const source = deindent`
<div>{{prop}}</div> <div>{prop}</div>
`; `;
const component = svelte.create(source); const component = svelte.create(source);
@ -13,7 +13,7 @@ describe("create", () => {
it("should throw error when source is invalid ", done => { it("should throw error when source is invalid ", done => {
const source = deindent` const source = deindent`
<div>{{prop}</div> <div>{prop</div>
`; `;
const component = svelte.create(source, { const component = svelte.create(source, {
@ -27,7 +27,7 @@ describe("create", () => {
it("should return undefined when source is invalid ", () => { it("should return undefined when source is invalid ", () => {
const source = deindent` const source = deindent`
<div>{{prop}</div> <div>{prop</div>
`; `;
const component = svelte.create(source, { const component = svelte.create(source, {

@ -78,10 +78,10 @@ describe('css', () => {
); );
// check the code is valid // check the code is valid
checkCodeIsValid(dom.code); checkCodeIsValid(dom.js.code);
checkCodeIsValid(ssr.code); checkCodeIsValid(ssr.js.code);
assert.equal(dom.css.toString(), ssr.css.toString()); assert.equal(dom.css.code, ssr.css.code);
assert.deepEqual( assert.deepEqual(
domWarnings.map(normalizeWarning), domWarnings.map(normalizeWarning),
@ -89,13 +89,13 @@ describe('css', () => {
); );
assert.deepEqual(domWarnings.map(normalizeWarning), expectedWarnings); assert.deepEqual(domWarnings.map(normalizeWarning), expectedWarnings);
fs.writeFileSync(`test/css/samples/${dir}/_actual.css`, dom.css); fs.writeFileSync(`test/css/samples/${dir}/_actual.css`, dom.css.code);
const expected = { const expected = {
html: read(`test/css/samples/${dir}/expected.html`), html: read(`test/css/samples/${dir}/expected.html`),
css: read(`test/css/samples/${dir}/expected.css`) css: read(`test/css/samples/${dir}/expected.css`)
}; };
assert.equal(dom.css.replace(/svelte(-ref)?-[a-z0-9]+/g, (m, $1) => $1 ? m : 'svelte-xyz'), expected.css); assert.equal(dom.css.code.replace(/svelte(-ref)?-[a-z0-9]+/g, (m, $1) => $1 ? m : 'svelte-xyz'), expected.css);
// verify that the right elements have scoping selectors // verify that the right elements have scoping selectors
if (expected.html !== null) { if (expected.html !== null) {
@ -104,7 +104,7 @@ describe('css', () => {
// dom // dom
try { try {
const Component = eval( const Component = eval(
`(function () { ${dom.code}; return SvelteComponent; }())` `(function () { ${dom.js.code}; return SvelteComponent; }())`
); );
const target = window.document.querySelector('main'); const target = window.document.querySelector('main');
@ -120,14 +120,14 @@ describe('css', () => {
window.document.head.innerHTML = ''; // remove added styles window.document.head.innerHTML = ''; // remove added styles
} catch (err) { } catch (err) {
console.log(dom.code); console.log(dom.js.code);
throw err; throw err;
} }
// ssr // ssr
try { try {
const component = eval( const component = eval(
`(function () { ${ssr.code}; return SvelteComponent; }())` `(function () { ${ssr.js.code}; return SvelteComponent; }())`
); );
assert.equal( assert.equal(
@ -138,7 +138,7 @@ describe('css', () => {
normalizeHtml(window, expected.html) normalizeHtml(window, expected.html)
); );
} catch (err) { } catch (err) {
console.log(ssr.code); console.log(ssr.js.code);
throw err; throw err;
} }
} }

@ -1,3 +0,0 @@
export default {
cascade: false
};

@ -1,3 +0,0 @@
export default {
cascade: false
};

@ -1 +1 @@
div.svelte-xyz,.svelte-xyz div{color:red} div.svelte-xyz{color:red}

@ -1,3 +0,0 @@
export default {
cascade: false
};

@ -1,3 +0,0 @@
export default {
cascade: false
};

@ -1,3 +0,0 @@
export default {
cascade: false
};

@ -1,3 +0,0 @@
export default {
cascade: false
};

@ -1,3 +0,0 @@
export default {
cascade: false
};

@ -1 +0,0 @@
@keyframes svelte-xyz-why{0%{color:red}100%{color:blue}}.animated.svelte-xyz{animation:svelte-xyz-why 2s}.also-animated.svelte-xyz{animation:not-defined-here 2s}

@ -1,17 +0,0 @@
<div class='animated'>animated</div>
<div class='also-animated'>also animated</div>
<style>
@keyframes why {
0% { color: red; }
100% { color: blue; }
}
.animated {
animation: why 2s;
}
.also-animated {
animation: not-defined-here 2s;
}
</style>

@ -1,3 +0,0 @@
export default {
cascade: false
};

@ -1,3 +0,0 @@
export default {
cascade: false
};

@ -1,7 +0,0 @@
<div></div>
<style>
* {
color: red;
}
</style>

@ -1,3 +0,0 @@
export default {
cascade: false
};

@ -1 +0,0 @@
div.svelte-xyz{color:red}div.foo.svelte-xyz{color:blue}.foo.svelte-xyz{font-weight:bold}

@ -1,16 +0,0 @@
<div>red</div>
<div class='foo'>bold/blue</div>
<style>
div {
color: red;
}
div.foo {
color: blue;
}
.foo {
font-weight: bold;
}
</style>

@ -1,3 +0,0 @@
export default {
cascade: false
};

@ -1 +1 @@
div.svelte-xyz,.svelte-xyz div{--test:10} div.svelte-xyz{--test:10}

@ -1,13 +1,17 @@
export default { export default {
cascade: false,
warnings: [{ warnings: [{
filename: "SvelteComponent.html", filename: "SvelteComponent.html",
code: `css-unused-selector`, code: `css-unused-selector`,
message: "Unused CSS selector", message: "Unused CSS selector",
loc: { start: {
line: 4, line: 4,
column: 1 column: 1,
character: 31
},
end: {
line: 4,
column: 3,
character: 33
}, },
pos: 31, pos: 31,
frame: ` frame: `

@ -1,4 +1,3 @@
export default { export default {
cascade: false,
dev: true dev: true
}; };

@ -1 +1 @@
@keyframes svelte-xyz-why{0%{color:red}100%{color:blue}}.svelte-xyz.animated,.svelte-xyz .animated{animation:svelte-xyz-why 2s} @keyframes svelte-xyz-why{0%{color:red}100%{color:blue}}.animated.svelte-xyz{animation:svelte-xyz-why 2s}.also-animated.svelte-xyz{animation:not-defined-here 2s}

@ -1,4 +1,5 @@
<div class='animated'>animated</div> <div class='animated'>animated</div>
<div class='also-animated'>also animated</div>
<style> <style>
@keyframes why { @keyframes why {
@ -9,4 +10,8 @@
.animated { .animated {
animation: why 2s; animation: why 2s;
} }
.also-animated {
animation: not-defined-here 2s;
}
</style> </style>

@ -1 +1 @@
@media only screen and (min-width: 400px){div.svelte-xyz,.svelte-xyz div{color:red}} @media only screen and (min-width: 400px){div.svelte-xyz{color:red}}

@ -1 +1 @@
@media(min-width: 400px){.svelte-xyz.large-screen,.svelte-xyz .large-screen{display:block}} @media(min-width: 400px){.large-screen.svelte-xyz{display:block}}

@ -1,6 +1,4 @@
export default { export default {
cascade: false,
data: { data: {
dynamic: 'x' dynamic: 'x'
} }

@ -3,7 +3,7 @@
</span> </span>
<span class='foo'> <span class='foo'>
<span class='bar'>{{dynamic}}</span> <span class='bar'>{dynamic}</span>
</span> </span>
<style> <style>

@ -1,5 +1,4 @@
export default { export default {
cascade: false,
data: { data: {
dynamic: 'whatever' dynamic: 'whatever'
} }

@ -1,5 +1,5 @@
<div> <div>
<p data-foo='{{dynamic}}'>this is styled</p> <p data-foo='{dynamic}'>this is styled</p>
<p data-foo='baz'>this is unstyled</p> <p data-foo='baz'>this is unstyled</p>
</div> </div>

@ -1,4 +1,4 @@
<p class='{{unknown}}'>this is styled</p> <p class='{unknown}'>this is styled</p>
<p class='bar'>this is unstyled</p> <p class='bar'>this is unstyled</p>
<style> <style>

@ -1,6 +1,4 @@
export default { export default {
cascade: false,
data: { data: {
raw: '<p>raw</p>' raw: '<p>raw</p>'
} }

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save