Merge branch 'master' into gh-654

pull/952/head
Rich Harris 8 years ago committed by GitHub
commit 82fc0f2713
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,5 +1,6 @@
src/shared src/shared
shared.js shared.js
store.js
test/test.js test/test.js
test/setup.js test/setup.js
**/_actual.js **/_actual.js

@ -1,5 +1,19 @@
# Svelte changelog # Svelte changelog
## 1.43.1
* Fix parameterised transitions ([#962](https://github.com/sveltejs/svelte/issues/962))
* Prevent boolean attributes breaking estree-walker expectations ([#961](https://github.com/sveltejs/svelte/issues/961))
* Throw error on cyclical store computations ([#964](https://github.com/sveltejs/svelte/pull/964))
## 1.43.0
* Export `Store` class to manage global state ([#930](https://github.com/sveltejs/svelte/issues/930))
* Recognise `aria-current` ([#953](https://github.com/sveltejs/svelte/pull/953))
* Support SSR register options including `extensions` ([#939](https://github.com/sveltejs/svelte/issues/939))
* Friendlier error for illegal contexts ([#934](https://github.com/sveltejs/svelte/issues/934))
* Remove whitespace around `<:Window>` components ([#943](https://github.com/sveltejs/svelte/issues/943))
## 1.42.1 ## 1.42.1
* Correctly append items inside a slotted `each` block ([#932](https://github.com/sveltejs/svelte/pull/932)) * Correctly append items inside a slotted `each` block ([#932](https://github.com/sveltejs/svelte/pull/932))

@ -1,12 +1,13 @@
{ {
"name": "svelte", "name": "svelte",
"version": "1.42.1", "version": "1.43.1",
"description": "The magical disappearing UI framework", "description": "The magical disappearing UI framework",
"main": "compiler/svelte.js", "main": "compiler/svelte.js",
"files": [ "files": [
"compiler", "compiler",
"ssr", "ssr",
"shared.js", "shared.js",
"store.js",
"README.md" "README.md"
], ],
"scripts": { "scripts": {

@ -72,15 +72,8 @@ function removeIndentation(
// We need to tell estree-walker that it should always // We need to tell estree-walker that it should always
// look for an `else` block, otherwise it might get // look for an `else` block, otherwise it might get
// the wrong idea about the shape of each/if blocks // the wrong idea about the shape of each/if blocks
childKeys.EachBlock = [ childKeys.EachBlock = childKeys.IfBlock = ['children', 'else'];
'children', childKeys.Attribute = ['value'];
'else'
];
childKeys.IfBlock = [
'children',
'else'
];
export default class Generator { export default class Generator {
ast: Parsed; ast: Parsed;
@ -536,6 +529,9 @@ export default class Generator {
(param: Node) => (param: Node) =>
param.type === 'AssignmentPattern' ? param.left.name : param.name param.type === 'AssignmentPattern' ? param.left.name : param.name
); );
deps.forEach(dep => {
this.expectedProperties.add(dep);
});
dependencies.set(key, deps); dependencies.set(key, deps);
}); });
@ -762,6 +758,11 @@ export default class Generator {
}); });
this.skip(); this.skip();
} }
if (node.type === 'Transition' && node.expression) {
node.metadata = contextualise(node.expression, contextDependencies, indexes);
this.skip();
}
}, },
leave(node: Node, parent: Node) { leave(node: Node, parent: Node) {

@ -184,15 +184,23 @@ export default function dom(
const debugName = `<${generator.customElement ? generator.tag : name}>`; const debugName = `<${generator.customElement ? generator.tag : name}>`;
// generate initial state object // generate initial state object
const globals = Array.from(generator.expectedProperties).filter(prop => globalWhitelist.has(prop)); const expectedProperties = Array.from(generator.expectedProperties);
const globals = expectedProperties.filter(prop => globalWhitelist.has(prop));
const storeProps = options.store ? expectedProperties.filter(prop => prop[0] === '$') : [];
const initialState = []; const initialState = [];
if (globals.length > 0) { if (globals.length > 0) {
initialState.push(`{ ${globals.map(prop => `${prop} : ${prop}`).join(', ')} }`); 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) { if (templateProperties.data) {
initialState.push(`%data()`); initialState.push(`%data()`);
} else if (globals.length === 0) { } else if (globals.length === 0 && storeProps.length === 0) {
initialState.push('{}'); initialState.push('{}');
} }
@ -205,6 +213,7 @@ export default function dom(
@init(this, options); @init(this, options);
${generator.usesRefs && `this.refs = {};`} ${generator.usesRefs && `this.refs = {};`}
this._state = @assign(${initialState.join(', ')}); this._state = @assign(${initialState.join(', ')});
${storeProps.length > 0 && `this.store._add(this, [${storeProps.map(prop => `"${prop.slice(1)}"`)}]);`}
${generator.metaBindings} ${generator.metaBindings}
${computations.length && `this._recompute({ ${Array.from(computationDeps).map(dep => `${dep}: 1`).join(', ')} }, this._state);`} ${computations.length && `this._recompute({ ${Array.from(computationDeps).map(dep => `${dep}: 1`).join(', ')} }, this._state);`}
${options.dev && ${options.dev &&
@ -215,7 +224,11 @@ export default function dom(
${generator.bindingGroups.length && ${generator.bindingGroups.length &&
`this._bindingGroups = [${Array(generator.bindingGroups.length).fill('[]').join(', ')}];`} `this._bindingGroups = [${Array(generator.bindingGroups.length).fill('[]').join(', ')}];`}
${templateProperties.ondestroy && `this._handlers.destroy = [%ondestroy]`} ${(templateProperties.ondestroy || storeProps.length) && (
`this._handlers.destroy = [${
[templateProperties.ondestroy && `%ondestroy`, storeProps.length && `@removeFromStore`].filter(Boolean).join(', ')
}];`
)}
${generator.slots.size && `this._slotted = options.slots || {};`} ${generator.slots.size && `this._slotted = options.slots || {};`}

@ -195,13 +195,19 @@ export default function addBindings(
const usesContext = group.bindings.some(binding => binding.handler.usesContext); const usesContext = group.bindings.some(binding => binding.handler.usesContext);
const usesState = group.bindings.some(binding => binding.handler.usesState); const usesState = group.bindings.some(binding => binding.handler.usesState);
const usesStore = group.bindings.some(binding => binding.handler.usesStore);
const mutations = group.bindings.map(binding => binding.handler.mutation).filter(Boolean).join('\n'); const mutations = group.bindings.map(binding => binding.handler.mutation).filter(Boolean).join('\n');
const props = new Set(); const props = new Set();
const storeProps = new Set();
group.bindings.forEach(binding => { group.bindings.forEach(binding => {
binding.handler.props.forEach(prop => { binding.handler.props.forEach(prop => {
props.add(prop); props.add(prop);
}); });
binding.handler.storeProps.forEach(prop => {
storeProps.add(prop);
});
}); // TODO use stringifyProps here, once indenting is fixed }); // TODO use stringifyProps here, once indenting is fixed
// media bindings — awkward special case. The native timeupdate events // media bindings — awkward special case. The native timeupdate events
@ -222,9 +228,11 @@ export default function addBindings(
} }
${usesContext && `var context = ${node.var}._svelte;`} ${usesContext && `var context = ${node.var}._svelte;`}
${usesState && `var state = #component.get();`} ${usesState && `var state = #component.get();`}
${usesStore && `var $ = #component.store.get();`}
${needsLock && `${lock} = true;`} ${needsLock && `${lock} = true;`}
${mutations.length > 0 && mutations} ${mutations.length > 0 && mutations}
#component.set({ ${Array.from(props).join(', ')} }); ${props.size > 0 && `#component.set({ ${Array.from(props).join(', ')} });`}
${storeProps.size > 0 && `#component.store.set({ ${Array.from(storeProps).join(', ')} });`}
${needsLock && `${lock} = false;`} ${needsLock && `${lock} = false;`}
} }
`); `);
@ -307,6 +315,13 @@ function getEventHandler(
dependencies: string[], dependencies: string[],
value: string, value: string,
) { ) {
let storeDependencies = [];
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'
? getTailSnippet(attribute.value) ? getTailSnippet(attribute.value)
@ -318,8 +333,10 @@ function getEventHandler(
return { return {
usesContext: true, usesContext: true,
usesState: true, usesState: true,
usesStore: storeDependencies.length > 0,
mutation: `${list}[${index}]${tail} = ${value};`, mutation: `${list}[${index}]${tail} = ${value};`,
props: dependencies.map(prop => `${prop}: state.${prop}`) props: dependencies.map(prop => `${prop}: state.${prop}`),
storeProps: storeDependencies.map(prop => `${prop}: $.${prop}`)
}; };
} }
@ -336,16 +353,31 @@ function getEventHandler(
return { return {
usesContext: false, usesContext: false,
usesState: true, usesState: true,
usesStore: storeDependencies.length > 0,
mutation: `${snippet} = ${value}`, mutation: `${snippet} = ${value}`,
props: dependencies.map((prop: string) => `${prop}: state.${prop}`) props: dependencies.map((prop: string) => `${prop}: state.${prop}`),
storeProps: storeDependencies.map(prop => `${prop}: $.${prop}`)
}; };
} }
let props;
let storeProps;
if (generator.options.store && name[0] === '$') {
props = [];
storeProps = [`${name.slice(1)}: ${value}`];
} else {
props = [`${name}: ${value}`];
storeProps = [];
}
return { return {
usesContext: false, usesContext: false,
usesState: false, usesState: false,
usesStore: false,
mutation: null, mutation: null,
props: [`${name}: ${value}`] props,
storeProps
}; };
} }

@ -21,7 +21,7 @@ export default function addTransitions(
if (intro === outro) { if (intro === outro) {
const name = block.getUniqueName(`${node.var}_transition`); const name = block.getUniqueName(`${node.var}_transition`);
const snippet = intro.expression const snippet = intro.expression
? intro.expression.metadata.snippet ? intro.metadata.snippet
: '{}'; : '{}';
block.addVariable(name); block.addVariable(name);
@ -51,7 +51,7 @@ export default function addTransitions(
if (intro) { if (intro) {
block.addVariable(introName); block.addVariable(introName);
const snippet = intro.expression const snippet = intro.expression
? intro.expression.metadata.snippet ? intro.metadata.snippet
: '{}'; : '{}';
const fn = `%transitions-${intro.name}`; // TODO add built-in transitions? const fn = `%transitions-${intro.name}`; // TODO add built-in transitions?
@ -76,7 +76,7 @@ export default function addTransitions(
if (outro) { if (outro) {
block.addVariable(outroName); block.addVariable(outroName);
const snippet = outro.expression const snippet = outro.expression
? intro.expression.metadata.snippet ? outro.metadata.snippet
: '{}'; : '{}';
const fn = `%transitions-${outro.name}`; const fn = `%transitions-${outro.name}`;

@ -73,16 +73,22 @@ export default function ssr(
generator.stylesheet.render(options.filename, true); generator.stylesheet.render(options.filename, true);
// generate initial state object // generate initial state object
// TODO this doesn't work, because expectedProperties isn't populated const expectedProperties = Array.from(generator.expectedProperties);
const globals = Array.from(generator.expectedProperties).filter(prop => globalWhitelist.has(prop)); const globals = expectedProperties.filter(prop => globalWhitelist.has(prop));
const storeProps = options.store ? expectedProperties.filter(prop => prop[0] === '$') : [];
const initialState = []; const initialState = [];
if (globals.length > 0) { if (globals.length > 0) {
initialState.push(`{ ${globals.map(prop => `${prop} : ${prop}`).join(', ')} }`); initialState.push(`{ ${globals.map(prop => `${prop} : ${prop}`).join(', ')} }`);
} }
if (storeProps.length > 0) {
initialState.push(`options.store._init([${storeProps.map(prop => `"${prop.slice(1)}"`)}])`);
}
if (templateProperties.data) { if (templateProperties.data) {
initialState.push(`%data()`); initialState.push(`%data()`);
} else if (globals.length === 0) { } else if (globals.length === 0 && storeProps.length === 0) {
initialState.push('{}'); initialState.push('{}');
} }
@ -99,7 +105,7 @@ export default function ssr(
return ${templateProperties.data ? `%data()` : `{}`}; return ${templateProperties.data ? `%data()` : `{}`};
}; };
${name}.render = function(state, options) { ${name}.render = function(state, options = {}) {
state = Object.assign(${initialState.join(', ')}); state = Object.assign(${initialState.join(', ')});
${computations.map( ${computations.map(

@ -79,6 +79,11 @@ export default function visitComponent(
let open = `\${${expression}.render({${props}}`; let open = `\${${expression}.render({${props}}`;
const options = [];
if (generator.options.store) {
options.push(`store: options.store`);
}
if (node.children.length) { if (node.children.length) {
const appendTarget: AppendTarget = { const appendTarget: AppendTarget = {
slots: { default: '' }, slots: { default: '' },
@ -95,11 +100,15 @@ export default function visitComponent(
.map(name => `${name}: () => \`${appendTarget.slots[name]}\``) .map(name => `${name}: () => \`${appendTarget.slots[name]}\``)
.join(', '); .join(', ');
open += `, { slotted: { ${slotted} } }`; options.push(`slotted: { ${slotted} }`);
generator.appendTargets.pop(); generator.appendTargets.pop();
} }
if (options.length) {
open += `, { ${options.join(', ')} }`;
}
generator.append(open); generator.append(open);
generator.append(')}'); generator.append(')}');
} }

@ -4,7 +4,8 @@ import generate from './generators/dom/index';
import generateSSR from './generators/server-side-rendering/index'; import generateSSR from './generators/server-side-rendering/index';
import { assign } from './shared/index.js'; import { assign } from './shared/index.js';
import Stylesheet from './css/Stylesheet'; import Stylesheet from './css/Stylesheet';
import { Parsed, CompileOptions, Warning } from './interfaces'; import { Parsed, CompileOptions, Warning, PreprocessOptions, Preprocessor } from './interfaces';
import { SourceMap } from 'magic-string';
const version = '__VERSION__'; const version = '__VERSION__';
@ -34,9 +35,74 @@ function defaultOnerror(error: Error) {
throw error; throw error;
} }
function parseAttributeValue(value: string) {
return /^['"]/.test(value) ?
value.slice(1, -1) :
value;
}
function parseAttributes(str: string) {
const attrs = {};
str.split(/\s+/).filter(Boolean).forEach(attr => {
const [name, value] = attr.split('=');
attrs[name] = value ? parseAttributeValue(value) : true;
});
return attrs;
}
async function replaceTagContents(source, type: 'script' | 'style', preprocessor: Preprocessor) {
const exp = new RegExp(`<${type}([\\S\\s]*?)>([\\S\\s]*?)<\\/${type}>`, 'ig');
const match = exp.exec(source);
if (match) {
const attributes: Record<string, string | boolean> = parseAttributes(match[1]);
const content: string = match[2];
const processed: { code: string, map?: SourceMap | string } = await preprocessor({
content,
attributes
});
if (processed && processed.code) {
return (
source.slice(0, match.index) +
`<${type}>${processed.code}</${type}>` +
source.slice(match.index + match[0].length)
);
}
}
return source;
}
export async function preprocess(source: string, options: PreprocessOptions) {
const { markup, style, script } = options;
if (!!markup) {
const processed: { code: string, map?: SourceMap | string } = await markup({ content: source });
source = processed.code;
}
if (!!style) {
source = await replaceTagContents(source, 'style', style);
}
if (!!script) {
source = await replaceTagContents(source, 'script', script);
}
return {
// TODO return separated output, in future version where svelte.compile supports it:
// style: { code: styleCode, map: styleMap },
// script { code: scriptCode, map: scriptMap },
// markup { code: markupCode, map: markupMap },
toString() {
return source;
}
};
}
export function compile(source: string, _options: CompileOptions) { export function compile(source: string, _options: CompileOptions) {
const options = normalizeOptions(_options); const options = normalizeOptions(_options);
let parsed: Parsed; let parsed: Parsed;
try { try {
@ -53,7 +119,7 @@ export function compile(source: string, _options: CompileOptions) {
const compiler = options.generate === 'ssr' ? generateSSR : generate; const compiler = options.generate === 'ssr' ? generateSSR : generate;
return compiler(parsed, source, stylesheet, options); return compiler(parsed, source, stylesheet, options);
} };
export function create(source: string, _options: CompileOptions = {}) { export function create(source: string, _options: CompileOptions = {}) {
_options.format = 'eval'; _options.format = 'eval';
@ -65,7 +131,7 @@ export function create(source: string, _options: CompileOptions = {}) {
} }
try { try {
return (0,eval)(compiled.code); return (0, eval)(compiled.code);
} catch (err) { } catch (err) {
if (_options.onerror) { if (_options.onerror) {
_options.onerror(err); _options.onerror(err);

@ -1,3 +1,5 @@
import {SourceMap} from 'magic-string';
export interface Node { export interface Node {
start: number; start: number;
end: number; end: number;
@ -56,6 +58,7 @@ export interface CompileOptions {
legacy?: boolean; legacy?: boolean;
customElement?: CustomElementOptions | true; customElement?: CustomElementOptions | true;
css?: boolean; css?: boolean;
store?: boolean;
onerror?: (error: Error) => void; onerror?: (error: Error) => void;
onwarn?: (warning: Warning) => void; onwarn?: (warning: Warning) => void;
@ -78,3 +81,11 @@ export interface CustomElementOptions {
tag?: string; tag?: string;
props?: string[]; props?: string[];
} }
export interface PreprocessOptions {
markup?: (options: {content: string}) => { code: string, map?: SourceMap | string };
style?: Preprocessor;
script?: Preprocessor;
}
export type Preprocessor = (options: {content: string, attributes: Record<string, string | boolean>}) => { code: string, map?: SourceMap | string };

@ -2,16 +2,22 @@ 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';
const compileOptions = {};
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; const { extensions } = options;
if (extensions) { if (extensions) {
_deregister('.html'); _deregister('.html');
extensions.forEach(_register); extensions.forEach(_register);
} }
// TODO make this the default and remove in v2
if ('store' in options) compileOptions.store = options.store;
} }
function _deregister(extension) { function _deregister(extension) {
@ -20,13 +26,15 @@ function _deregister(extension) {
function _register(extension) { function _register(extension) {
require.extensions[extension] = function(module, filename) { require.extensions[extension] = function(module, filename) {
const {code} = compile(fs.readFileSync(filename, 'utf-8'), { const options = Object.assign({}, compileOptions, {
filename, filename,
name: capitalise(path.basename(filename) name: capitalise(path.basename(filename)
.replace(new RegExp(`${extension.replace('.', '\\.')}$`), '')), .replace(new RegExp(`${extension.replace('.', '\\.')}$`), '')),
generate: 'ssr', generate: 'ssr'
}); });
const {code} = compile(fs.readFileSync(filename, 'utf-8'), options);
return module._compile(code, filename); return module._compile(code, filename);
}; };
} }

@ -65,12 +65,13 @@ export function get(key) {
} }
export function init(component, options) { export function init(component, options) {
component.options = options;
component._observers = { pre: blankObject(), post: blankObject() }; component._observers = { pre: blankObject(), post: blankObject() };
component._handlers = blankObject(); component._handlers = blankObject();
component._root = options._root || component; component._root = options._root || component;
component._bind = options._bind; component._bind = options._bind;
component.options = options;
component.store = component._root.options.store;
} }
export function observe(key, callback, options) { export function observe(key, callback, options) {
@ -195,6 +196,10 @@ export var PENDING = {};
export var SUCCESS = {}; export var SUCCESS = {};
export var FAILURE = {}; export var FAILURE = {};
export function removeFromStore() {
this.store._remove(this);
}
export var proto = { export var proto = {
destroy: destroy, destroy: destroy,
get: get, get: get,

@ -5,7 +5,7 @@ import validateEventHandler from './validateEventHandler';
import { Validator } from '../index'; import { Validator } from '../index';
import { Node } from '../../interfaces'; import { Node } from '../../interfaces';
const ariaAttributes = 'activedescendant atomic autocomplete busy checked controls describedby disabled dropeffect expanded flowto grabbed haspopup hidden invalid label labelledby level live multiline multiselectable orientation owns posinset pressed readonly relevant required selected setsize sort valuemax valuemin valuenow valuetext'.split(' '); const ariaAttributes = 'activedescendant atomic autocomplete busy checked controls current describedby disabled dropeffect expanded flowto grabbed haspopup hidden invalid label labelledby level live multiline multiselectable orientation owns posinset pressed readonly relevant required selected setsize sort valuemax valuemin valuenow valuetext'.split(' ');
const ariaAttributeSet = new Set(ariaAttributes); const ariaAttributeSet = new Set(ariaAttributes);
const ariaRoles = 'alert alertdialog application article banner button checkbox columnheader combobox command complementary composite contentinfo definition dialog directory document form grid gridcell group heading img input landmark link list listbox listitem log main marquee math menu menubar menuitem menuitemcheckbox menuitemradio navigation note option presentation progressbar radio radiogroup range region roletype row rowgroup rowheader scrollbar search section sectionhead select separator slider spinbutton status structure tab tablist tabpanel textbox timer toolbar tooltip tree treegrid treeitem widget window'.split(' '); const ariaRoles = 'alert alertdialog application article banner button checkbox columnheader combobox command complementary composite contentinfo definition dialog directory document form grid gridcell group heading img input landmark link list listbox listitem log main marquee math menu menubar menuitem menuitemcheckbox menuitemradio navigation note option presentation progressbar radio radiogroup range region roletype row rowgroup rowheader scrollbar search section sectionhead select separator slider spinbutton status structure tab tablist tabpanel textbox timer toolbar tooltip tree treegrid treeitem widget window'.split(' ');

@ -1,6 +1,6 @@
import flattenReference from '../../utils/flattenReference'; import flattenReference from '../../utils/flattenReference';
import list from '../../utils/list'; import list from '../../utils/list';
import { Validator } from '../index'; import validate, { Validator } from '../index';
import validCalleeObjects from '../../utils/validCalleeObjects'; import validCalleeObjects from '../../utils/validCalleeObjects';
import { Node } from '../../interfaces'; import { Node } from '../../interfaces';
@ -28,6 +28,13 @@ export default function validateEventHandlerCallee(
return; return;
} }
if (name === 'store' && attribute.expression.callee.type === 'MemberExpression') {
if (!validator.options.store) {
validator.warn('compile with `store: true` in order to call store methods', attribute.expression.start);
}
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)
@ -35,6 +42,7 @@ export default function validateEventHandlerCallee(
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())
); );

@ -22,6 +22,7 @@ export class Validator {
readonly source: string; readonly source: string;
readonly filename: string; readonly filename: string;
options: CompileOptions;
onwarn: ({}) => void; onwarn: ({}) => void;
locator?: (pos: number) => Location; locator?: (pos: number) => Location;
@ -37,8 +38,8 @@ export class Validator {
constructor(parsed: Parsed, source: string, options: CompileOptions) { constructor(parsed: Parsed, source: string, options: CompileOptions) {
this.source = source; this.source = source;
this.filename = options.filename; this.filename = options.filename;
this.onwarn = options.onwarn; this.onwarn = options.onwarn;
this.options = options;
this.namespace = null; this.namespace = null;
this.defaultExport = null; this.defaultExport = null;
@ -78,7 +79,7 @@ export default function validate(
stylesheet: Stylesheet, stylesheet: Stylesheet,
options: CompileOptions options: CompileOptions
) { ) {
const { onwarn, onerror, name, filename } = options; const { onwarn, onerror, name, filename, store } = 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)) {
@ -99,6 +100,7 @@ export default function validate(
onwarn, onwarn,
name, name,
filename, filename,
store
}); });
if (parsed.js) { if (parsed.js) {

@ -0,0 +1,164 @@
import {
assign,
blankObject,
differs,
dispatchObservers,
get,
observe
} from './shared.js';
function Store(state) {
this._observers = { pre: blankObject(), post: blankObject() };
this._changeHandlers = [];
this._dependents = [];
this._computed = blankObject();
this._sortedComputedProperties = [];
this._state = assign({}, state);
}
assign(Store.prototype, {
_add: function(component, props) {
this._dependents.push({
component: component,
props: props
});
},
_init: function(props) {
var state = {};
for (var i = 0; i < props.length; i += 1) {
var prop = props[i];
state['$' + prop] = this._state[prop];
}
return state;
},
_remove: function(component) {
var i = this._dependents.length;
while (i--) {
if (this._dependents[i].component === component) {
this._dependents.splice(i, 1);
return;
}
}
},
_sortComputedProperties: function() {
var computed = this._computed;
var sorted = this._sortedComputedProperties = [];
var cycles;
var visited = blankObject();
function visit(key) {
if (cycles[key]) {
throw new Error('Cyclical dependency detected');
}
if (visited[key]) return;
visited[key] = true;
var c = computed[key];
if (c) {
cycles[key] = true;
c.deps.forEach(visit);
sorted.push(c);
}
}
for (var key in this._computed) {
cycles = blankObject();
visit(key);
}
},
compute: function(key, deps, fn) {
var store = this;
var value;
var c = {
deps: deps,
update: function(state, changed, dirty) {
var values = deps.map(function(dep) {
if (dep in changed) dirty = true;
return state[dep];
});
if (dirty) {
var newValue = fn.apply(null, values);
if (differs(newValue, value)) {
value = newValue;
changed[key] = true;
state[key] = value;
}
}
}
};
c.update(this._state, {}, true);
this._computed[key] = c;
this._sortComputedProperties();
},
get: get,
observe: observe,
onchange: function(callback) {
this._changeHandlers.push(callback);
return {
cancel: function() {
var index = this._changeHandlers.indexOf(callback);
if (~index) this._changeHandlers.splice(index, 1);
}
};
},
set: function(newState) {
var oldState = this._state,
changed = this._changed = {},
dirty = false;
for (var key in newState) {
if (this._computed[key]) throw new Error("'" + key + "' is a read-only property");
if (differs(newState[key], oldState[key])) changed[key] = dirty = true;
}
if (!dirty) return;
this._state = assign({}, oldState, newState);
for (var i = 0; i < this._sortedComputedProperties.length; i += 1) {
this._sortedComputedProperties[i].update(this._state, changed);
}
for (var i = 0; i < this._changeHandlers.length; i += 1) {
this._changeHandlers[i](this._state, changed);
}
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
var dependents = this._dependents.slice(); // guard against mutations
for (var i = 0; i < dependents.length; i += 1) {
var dependent = dependents[i];
var componentState = {};
dirty = false;
for (var j = 0; j < dependent.props.length; j += 1) {
var prop = dependent.props[j];
if (prop in changed) {
componentState['$' + prop] = this._state[prop];
dirty = true;
}
}
if (dirty) dependent.component.set(componentState);
}
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
}
});
export { Store };

@ -91,12 +91,13 @@ function get(key) {
} }
function init(component, options) { function init(component, options) {
component.options = options;
component._observers = { pre: blankObject(), post: blankObject() }; component._observers = { pre: blankObject(), post: blankObject() };
component._handlers = blankObject(); component._handlers = blankObject();
component._root = options._root || component; component._root = options._root || component;
component._bind = options._bind; component._bind = options._bind;
component.options = options;
component.store = component._root.options.store;
} }
function observe(key, callback, options) { function observe(key, callback, options) {

@ -67,12 +67,13 @@ function get(key) {
} }
function init(component, options) { function init(component, options) {
component.options = options;
component._observers = { pre: blankObject(), post: blankObject() }; component._observers = { pre: blankObject(), post: blankObject() };
component._handlers = blankObject(); component._handlers = blankObject();
component._root = options._root || component; component._root = options._root || component;
component._bind = options._bind; component._bind = options._bind;
component.options = options;
component.store = component._root.options.store;
} }
function observe(key, callback, options) { function observe(key, callback, options) {

@ -67,12 +67,13 @@ function get(key) {
} }
function init(component, options) { function init(component, options) {
component.options = options;
component._observers = { pre: blankObject(), post: blankObject() }; component._observers = { pre: blankObject(), post: blankObject() };
component._handlers = blankObject(); component._handlers = blankObject();
component._root = options._root || component; component._root = options._root || component;
component._bind = options._bind; component._bind = options._bind;
component.options = options;
component.store = component._root.options.store;
} }
function observe(key, callback, options) { function observe(key, callback, options) {

@ -87,12 +87,13 @@ function get(key) {
} }
function init(component, options) { function init(component, options) {
component.options = options;
component._observers = { pre: blankObject(), post: blankObject() }; component._observers = { pre: blankObject(), post: blankObject() };
component._handlers = blankObject(); component._handlers = blankObject();
component._root = options._root || component; component._root = options._root || component;
component._bind = options._bind; component._bind = options._bind;
component.options = options;
component.store = component._root.options.store;
} }
function observe(key, callback, options) { function observe(key, callback, options) {

@ -79,12 +79,13 @@ function get(key) {
} }
function init(component, options) { function init(component, options) {
component.options = options;
component._observers = { pre: blankObject(), post: blankObject() }; component._observers = { pre: blankObject(), post: blankObject() };
component._handlers = blankObject(); component._handlers = blankObject();
component._root = options._root || component; component._root = options._root || component;
component._bind = options._bind; component._bind = options._bind;
component.options = options;
component.store = component._root.options.store;
} }
function observe(key, callback, options) { function observe(key, callback, options) {

@ -83,12 +83,13 @@ function get(key) {
} }
function init(component, options) { function init(component, options) {
component.options = options;
component._observers = { pre: blankObject(), post: blankObject() }; component._observers = { pre: blankObject(), post: blankObject() };
component._handlers = blankObject(); component._handlers = blankObject();
component._root = options._root || component; component._root = options._root || component;
component._bind = options._bind; component._bind = options._bind;
component.options = options;
component.store = component._root.options.store;
} }
function observe(key, callback, options) { function observe(key, callback, options) {

@ -87,12 +87,13 @@ function get(key) {
} }
function init(component, options) { function init(component, options) {
component.options = options;
component._observers = { pre: blankObject(), post: blankObject() }; component._observers = { pre: blankObject(), post: blankObject() };
component._handlers = blankObject(); component._handlers = blankObject();
component._root = options._root || component; component._root = options._root || component;
component._bind = options._bind; component._bind = options._bind;
component.options = options;
component.store = component._root.options.store;
} }
function observe(key, callback, options) { function observe(key, callback, options) {

@ -99,12 +99,13 @@ function get(key) {
} }
function init(component, options) { function init(component, options) {
component.options = options;
component._observers = { pre: blankObject(), post: blankObject() }; component._observers = { pre: blankObject(), post: blankObject() };
component._handlers = blankObject(); component._handlers = blankObject();
component._root = options._root || component; component._root = options._root || component;
component._bind = options._bind; component._bind = options._bind;
component.options = options;
component.store = component._root.options.store;
} }
function observe(key, callback, options) { function observe(key, callback, options) {

@ -79,12 +79,13 @@ function get(key) {
} }
function init(component, options) { function init(component, options) {
component.options = options;
component._observers = { pre: blankObject(), post: blankObject() }; component._observers = { pre: blankObject(), post: blankObject() };
component._handlers = blankObject(); component._handlers = blankObject();
component._root = options._root || component; component._root = options._root || component;
component._bind = options._bind; component._bind = options._bind;
component.options = options;
component.store = component._root.options.store;
} }
function observe(key, callback, options) { function observe(key, callback, options) {

@ -83,12 +83,13 @@ function get(key) {
} }
function init(component, options) { function init(component, options) {
component.options = options;
component._observers = { pre: blankObject(), post: blankObject() }; component._observers = { pre: blankObject(), post: blankObject() };
component._handlers = blankObject(); component._handlers = blankObject();
component._root = options._root || component; component._root = options._root || component;
component._bind = options._bind; component._bind = options._bind;
component.options = options;
component.store = component._root.options.store;
} }
function observe(key, callback, options) { function observe(key, callback, options) {

@ -83,12 +83,13 @@ function get(key) {
} }
function init(component, options) { function init(component, options) {
component.options = options;
component._observers = { pre: blankObject(), post: blankObject() }; component._observers = { pre: blankObject(), post: blankObject() };
component._handlers = blankObject(); component._handlers = blankObject();
component._root = options._root || component; component._root = options._root || component;
component._bind = options._bind; component._bind = options._bind;
component.options = options;
component.store = component._root.options.store;
} }
function observe(key, callback, options) { function observe(key, callback, options) {

@ -83,12 +83,13 @@ function get(key) {
} }
function init(component, options) { function init(component, options) {
component.options = options;
component._observers = { pre: blankObject(), post: blankObject() }; component._observers = { pre: blankObject(), post: blankObject() };
component._handlers = blankObject(); component._handlers = blankObject();
component._root = options._root || component; component._root = options._root || component;
component._bind = options._bind; component._bind = options._bind;
component.options = options;
component.store = component._root.options.store;
} }
function observe(key, callback, options) { function observe(key, callback, options) {

@ -83,12 +83,13 @@ function get(key) {
} }
function init(component, options) { function init(component, options) {
component.options = options;
component._observers = { pre: blankObject(), post: blankObject() }; component._observers = { pre: blankObject(), post: blankObject() };
component._handlers = blankObject(); component._handlers = blankObject();
component._root = options._root || component; component._root = options._root || component;
component._bind = options._bind; component._bind = options._bind;
component.options = options;
component.store = component._root.options.store;
} }
function observe(key, callback, options) { function observe(key, callback, options) {

@ -83,12 +83,13 @@ function get(key) {
} }
function init(component, options) { function init(component, options) {
component.options = options;
component._observers = { pre: blankObject(), post: blankObject() }; component._observers = { pre: blankObject(), post: blankObject() };
component._handlers = blankObject(); component._handlers = blankObject();
component._root = options._root || component; component._root = options._root || component;
component._bind = options._bind; component._bind = options._bind;
component.options = options;
component.store = component._root.options.store;
} }
function observe(key, callback, options) { function observe(key, callback, options) {

@ -83,12 +83,13 @@ function get(key) {
} }
function init(component, options) { function init(component, options) {
component.options = options;
component._observers = { pre: blankObject(), post: blankObject() }; component._observers = { pre: blankObject(), post: blankObject() };
component._handlers = blankObject(); component._handlers = blankObject();
component._root = options._root || component; component._root = options._root || component;
component._bind = options._bind; component._bind = options._bind;
component.options = options;
component.store = component._root.options.store;
} }
function observe(key, callback, options) { function observe(key, callback, options) {

@ -87,12 +87,13 @@ function get(key) {
} }
function init(component, options) { function init(component, options) {
component.options = options;
component._observers = { pre: blankObject(), post: blankObject() }; component._observers = { pre: blankObject(), post: blankObject() };
component._handlers = blankObject(); component._handlers = blankObject();
component._root = options._root || component; component._root = options._root || component;
component._bind = options._bind; component._bind = options._bind;
component.options = options;
component.store = component._root.options.store;
} }
function observe(key, callback, options) { function observe(key, callback, options) {

@ -85,12 +85,13 @@ function get(key) {
} }
function init(component, options) { function init(component, options) {
component.options = options;
component._observers = { pre: blankObject(), post: blankObject() }; component._observers = { pre: blankObject(), post: blankObject() };
component._handlers = blankObject(); component._handlers = blankObject();
component._root = options._root || component; component._root = options._root || component;
component._bind = options._bind; component._bind = options._bind;
component.options = options;
component.store = component._root.options.store;
} }
function observe(key, callback, options) { function observe(key, callback, options) {

@ -102,12 +102,13 @@ function get(key) {
} }
function init(component, options) { function init(component, options) {
component.options = options;
component._observers = { pre: blankObject(), post: blankObject() }; component._observers = { pre: blankObject(), post: blankObject() };
component._handlers = blankObject(); component._handlers = blankObject();
component._root = options._root || component; component._root = options._root || component;
component._bind = options._bind; component._bind = options._bind;
component.options = options;
component.store = component._root.options.store;
} }
function observe(key, callback, options) { function observe(key, callback, options) {

@ -95,12 +95,13 @@ function get(key) {
} }
function init(component, options) { function init(component, options) {
component.options = options;
component._observers = { pre: blankObject(), post: blankObject() }; component._observers = { pre: blankObject(), post: blankObject() };
component._handlers = blankObject(); component._handlers = blankObject();
component._root = options._root || component; component._root = options._root || component;
component._bind = options._bind; component._bind = options._bind;
component.options = options;
component.store = component._root.options.store;
} }
function observe(key, callback, options) { function observe(key, callback, options) {

@ -81,12 +81,13 @@ function get(key) {
} }
function init(component, options) { function init(component, options) {
component.options = options;
component._observers = { pre: blankObject(), post: blankObject() }; component._observers = { pre: blankObject(), post: blankObject() };
component._handlers = blankObject(); component._handlers = blankObject();
component._root = options._root || component; component._root = options._root || component;
component._bind = options._bind; component._bind = options._bind;
component.options = options;
component.store = component._root.options.store;
} }
function observe(key, callback, options) { function observe(key, callback, options) {

@ -67,12 +67,13 @@ function get(key) {
} }
function init(component, options) { function init(component, options) {
component.options = options;
component._observers = { pre: blankObject(), post: blankObject() }; component._observers = { pre: blankObject(), post: blankObject() };
component._handlers = blankObject(); component._handlers = blankObject();
component._root = options._root || component; component._root = options._root || component;
component._bind = options._bind; component._bind = options._bind;
component.options = options;
component.store = component._root.options.store;
} }
function observe(key, callback, options) { function observe(key, callback, options) {

@ -24,7 +24,7 @@ function SvelteComponent(options) {
init(this, options); init(this, options);
this._state = assign({}, options.data); this._state = assign({}, options.data);
this._handlers.destroy = [ondestroy] this._handlers.destroy = [ondestroy];
var _oncreate = oncreate.bind(this); var _oncreate = oncreate.bind(this);

@ -67,12 +67,13 @@ function get(key) {
} }
function init(component, options) { function init(component, options) {
component.options = options;
component._observers = { pre: blankObject(), post: blankObject() }; component._observers = { pre: blankObject(), post: blankObject() };
component._handlers = blankObject(); component._handlers = blankObject();
component._root = options._root || component; component._root = options._root || component;
component._bind = options._bind; component._bind = options._bind;
component.options = options;
component.store = component._root.options.store;
} }
function observe(key, callback, options) { function observe(key, callback, options) {

@ -4,7 +4,7 @@ SvelteComponent.data = function() {
return {}; return {};
}; };
SvelteComponent.render = function(state, options) { SvelteComponent.render = function(state, options = {}) {
state = Object.assign({}, state); state = Object.assign({}, state);
return ``.trim(); return ``.trim();

@ -6,7 +6,7 @@ SvelteComponent.data = function() {
return {}; return {};
}; };
SvelteComponent.render = function(state, options) { SvelteComponent.render = function(state, options = {}) {
state = Object.assign({}, state); state = Object.assign({}, state);
return ``.trim(); return ``.trim();

@ -91,12 +91,13 @@ function get(key) {
} }
function init(component, options) { function init(component, options) {
component.options = options;
component._observers = { pre: blankObject(), post: blankObject() }; component._observers = { pre: blankObject(), post: blankObject() };
component._handlers = blankObject(); component._handlers = blankObject();
component._root = options._root || component; component._root = options._root || component;
component._bind = options._bind; component._bind = options._bind;
component.options = options;
component.store = component._root.options.store;
} }
function observe(key, callback, options) { function observe(key, callback, options) {

@ -87,12 +87,13 @@ function get(key) {
} }
function init(component, options) { function init(component, options) {
component.options = options;
component._observers = { pre: blankObject(), post: blankObject() }; component._observers = { pre: blankObject(), post: blankObject() };
component._handlers = blankObject(); component._handlers = blankObject();
component._root = options._root || component; component._root = options._root || component;
component._bind = options._bind; component._bind = options._bind;
component.options = options;
component.store = component._root.options.store;
} }
function observe(key, callback, options) { function observe(key, callback, options) {

@ -0,0 +1,148 @@
import assert from 'assert';
import {svelte} from '../helpers.js';
describe('preprocess', () => {
it('preprocesses entire component', () => {
const source = `
<h1>Hello __NAME__!</h1>
`;
const expected = `
<h1>Hello world!</h1>
`;
return svelte.preprocess(source, {
markup: ({ content }) => {
return {
code: content.replace('__NAME__', 'world')
};
}
}).then(processed => {
assert.equal(processed.toString(), expected);
});
});
it('preprocesses style', () => {
const source = `
<div class='brand-color'>$brand</div>
<style>
.brand-color {
color: $brand;
}
</style>
`;
const expected = `
<div class='brand-color'>$brand</div>
<style>
.brand-color {
color: purple;
}
</style>
`;
return svelte.preprocess(source, {
style: ({ content }) => {
return {
code: content.replace('$brand', 'purple')
};
}
}).then(processed => {
assert.equal(processed.toString(), expected);
});
});
it('preprocesses style asynchronously', () => {
const source = `
<div class='brand-color'>$brand</div>
<style>
.brand-color {
color: $brand;
}
</style>
`;
const expected = `
<div class='brand-color'>$brand</div>
<style>
.brand-color {
color: purple;
}
</style>
`;
return svelte.preprocess(source, {
style: ({ content }) => {
return Promise.resolve({
code: content.replace('$brand', 'purple')
});
}
}).then(processed => {
assert.equal(processed.toString(), expected);
});
});
it('preprocesses script', () => {
const source = `
<script>
console.log(__THE_ANSWER__);
</script>
`;
const expected = `
<script>
console.log(42);
</script>
`;
return svelte.preprocess(source, {
script: ({ content }) => {
return {
code: content.replace('__THE_ANSWER__', '42')
};
}
}).then(processed => {
assert.equal(processed.toString(), expected);
});
});
it('parses attributes', () => {
const source = `
<style type='text/scss' data-foo="bar" bool></style>
`;
return svelte.preprocess(source, {
style: ({ attributes }) => {
assert.deepEqual(attributes, {
type: 'text/scss',
'data-foo': 'bar',
bool: true
});
}
});
});
it('ignores null/undefined returned from preprocessor', () => {
const source = `
<script>
console.log('ignore me');
</script>
`;
const expected = `
<script>
console.log('ignore me');
</script>
`;
return svelte.preprocess(source, {
script: () => null
}).then(processed => {
assert.equal(processed.toString(), expected);
});
});
});

@ -63,6 +63,7 @@ describe("runtime", () => {
compileOptions.shared = shared; compileOptions.shared = shared;
compileOptions.hydratable = hydrate; compileOptions.hydratable = hydrate;
compileOptions.dev = config.dev; compileOptions.dev = config.dev;
compileOptions.store = !!config.store;
// check that no ES2015+ syntax slipped in // check that no ES2015+ syntax slipped in
if (!config.allowES2015) { if (!config.allowES2015) {
@ -88,7 +89,7 @@ describe("runtime", () => {
} }
} catch (err) { } catch (err) {
failed.add(dir); failed.add(dir);
showOutput(cwd, { shared, format: 'cjs' }, svelte); // eslint-disable-line no-console showOutput(cwd, { shared, format: 'cjs', store: !!compileOptions.store }, svelte); // eslint-disable-line no-console
throw err; throw err;
} }
} }
@ -130,12 +131,10 @@ describe("runtime", () => {
}; };
}; };
global.window = window;
try { try {
SvelteComponent = require(`./samples/${dir}/main.html`); SvelteComponent = require(`./samples/${dir}/main.html`);
} catch (err) { } catch (err) {
showOutput(cwd, { shared, format: 'cjs', hydratable: hydrate }, svelte); // eslint-disable-line no-console showOutput(cwd, { shared, format: 'cjs', hydratable: hydrate, store: !!compileOptions.store }, svelte); // eslint-disable-line no-console
throw err; throw err;
} }
@ -155,7 +154,8 @@ describe("runtime", () => {
const options = Object.assign({}, { const options = Object.assign({}, {
target, target,
hydrate, hydrate,
data: config.data data: config.data,
store: config.store
}, config.options || {}); }, config.options || {});
const component = new SvelteComponent(options); const component = new SvelteComponent(options);
@ -190,12 +190,12 @@ describe("runtime", () => {
config.error(assert, err); config.error(assert, err);
} else { } else {
failed.add(dir); failed.add(dir);
showOutput(cwd, { shared, format: 'cjs', hydratable: hydrate }, svelte); // eslint-disable-line no-console showOutput(cwd, { shared, format: 'cjs', hydratable: hydrate, store: !!compileOptions.store }, svelte); // eslint-disable-line no-console
throw err; throw err;
} }
}) })
.then(() => { .then(() => {
if (config.show) showOutput(cwd, { shared, format: 'cjs', hydratable: hydrate }, svelte); if (config.show) showOutput(cwd, { shared, format: 'cjs', hydratable: hydrate, store: !!compileOptions.store }, svelte);
}); });
}); });
} }

@ -10,19 +10,17 @@ export default {
selected: [ values[1] ] selected: [ values[1] ]
}, },
'skip-ssr': true, // values are rendered as [object Object]
html: ` html: `
<label> <label>
<input type="checkbox"> Alpha <input type="checkbox" value="[object Object]"> Alpha
</label> </label>
<label> <label>
<input type="checkbox"> Beta <input type="checkbox" value="[object Object]"> Beta
</label> </label>
<label> <label>
<input type="checkbox"> Gamma <input type="checkbox" value="[object Object]"> Gamma
</label> </label>
<p>Beta</p>`, <p>Beta</p>`,
@ -40,15 +38,15 @@ export default {
assert.htmlEqual( target.innerHTML, ` assert.htmlEqual( target.innerHTML, `
<label> <label>
<input type="checkbox"> Alpha <input type="checkbox" value="[object Object]"> Alpha
</label> </label>
<label> <label>
<input type="checkbox"> Beta <input type="checkbox" value="[object Object]"> Beta
</label> </label>
<label> <label>
<input type="checkbox"> Gamma <input type="checkbox" value="[object Object]"> Gamma
</label> </label>
<p>Alpha, Beta</p> <p>Alpha, Beta</p>
@ -61,15 +59,15 @@ export default {
assert.htmlEqual( target.innerHTML, ` assert.htmlEqual( target.innerHTML, `
<label> <label>
<input type="checkbox"> Alpha <input type="checkbox" value="[object Object]"> Alpha
</label> </label>
<label> <label>
<input type="checkbox"> Beta <input type="checkbox" value="[object Object]"> Beta
</label> </label>
<label> <label>
<input type="checkbox"> Gamma <input type="checkbox" value="[object Object]"> Gamma
</label> </label>
<p>Beta, Gamma</p> <p>Beta, Gamma</p>

@ -10,19 +10,17 @@ export default {
selected: [ values[1] ] selected: [ values[1] ]
}, },
'skip-ssr': true, // values are rendered as [object Object]
html: ` html: `
<label> <label>
<input type="checkbox"> Alpha <input type="checkbox" value="[object Object]"> Alpha
</label> </label>
<label> <label>
<input type="checkbox"> Beta <input type="checkbox" value="[object Object]"> Beta
</label> </label>
<label> <label>
<input type="checkbox"> Gamma <input type="checkbox" value="[object Object]"> Gamma
</label> </label>
<p>Beta</p>`, <p>Beta</p>`,
@ -40,15 +38,15 @@ export default {
assert.htmlEqual( target.innerHTML, ` assert.htmlEqual( target.innerHTML, `
<label> <label>
<input type="checkbox"> Alpha <input type="checkbox" value="[object Object]"> Alpha
</label> </label>
<label> <label>
<input type="checkbox"> Beta <input type="checkbox" value="[object Object]"> Beta
</label> </label>
<label> <label>
<input type="checkbox"> Gamma <input type="checkbox" value="[object Object]"> Gamma
</label> </label>
<p>Alpha, Beta</p> <p>Alpha, Beta</p>
@ -61,15 +59,15 @@ export default {
assert.htmlEqual( target.innerHTML, ` assert.htmlEqual( target.innerHTML, `
<label> <label>
<input type="checkbox"> Alpha <input type="checkbox" value="[object Object]"> Alpha
</label> </label>
<label> <label>
<input type="checkbox"> Beta <input type="checkbox" value="[object Object]"> Beta
</label> </label>
<label> <label>
<input type="checkbox"> Gamma <input type="checkbox" value="[object Object]"> Gamma
</label> </label>
<p>Beta, Gamma</p> <p>Beta, Gamma</p>

@ -10,19 +10,17 @@ export default {
selected: values[1] selected: values[1]
}, },
'skip-ssr': true, // values are rendered as [object Object]
html: ` html: `
<label> <label>
<input type="radio"> Alpha <input type="radio" value="[object Object]"> Alpha
</label> </label>
<label> <label>
<input type="radio"> Beta <input type="radio" value="[object Object]"> Beta
</label> </label>
<label> <label>
<input type="radio"> Gamma <input type="radio" value="[object Object]"> Gamma
</label> </label>
<p>Beta</p>`, <p>Beta</p>`,
@ -40,15 +38,15 @@ export default {
assert.htmlEqual( target.innerHTML, ` assert.htmlEqual( target.innerHTML, `
<label> <label>
<input type="radio"> Alpha <input type="radio" value="[object Object]"> Alpha
</label> </label>
<label> <label>
<input type="radio"> Beta <input type="radio" value="[object Object]"> Beta
</label> </label>
<label> <label>
<input type="radio"> Gamma <input type="radio" value="[object Object]"> Gamma
</label> </label>
<p>Alpha</p> <p>Alpha</p>
@ -65,15 +63,15 @@ export default {
assert.htmlEqual( target.innerHTML, ` assert.htmlEqual( target.innerHTML, `
<label> <label>
<input type="radio"> Alpha <input type="radio" value="[object Object]"> Alpha
</label> </label>
<label> <label>
<input type="radio"> Beta <input type="radio" value="[object Object]"> Beta
</label> </label>
<label> <label>
<input type="radio"> Gamma <input type="radio" value="[object Object]"> Gamma
</label> </label>
<p>Gamma</p> <p>Gamma</p>

@ -0,0 +1,3 @@
export default {
html: `<a href='/cool'>link</a>`
};

@ -0,0 +1,10 @@
<Link x href="/cool"/>
<script>
import Link from './Link.html';
export default {
components: {
Link
}
};
</script>

@ -1,6 +1,10 @@
export default { export default {
dev: true, dev: true,
data: {
a: 42
},
test ( assert, component ) { test ( assert, component ) {
try { try {
component.set({ foo: 1 }); component.set({ foo: 1 });

@ -1,10 +1,14 @@
export default { export default {
dev: true, dev: true,
data: {
a: 42
},
test ( assert, component ) { test ( assert, component ) {
const obj = { a: 1 }; const obj = { a: 1 };
component.set( obj ); component.set( obj );
component.set( obj ); // will fail if the object is not cloned component.set( obj ); // will fail if the object is not cloned
component.destroy(); component.destroy();
} }
} };

@ -0,0 +1,28 @@
import { Store } from '../../../../store.js';
const store = new Store({
name: 'world'
});
export default {
store,
html: `
<h1>Hello world!</h1>
<input>
`,
test(assert, component, target, window) {
const input = target.querySelector('input');
const event = new window.Event('input');
input.value = 'everybody';
input.dispatchEvent(event);
assert.equal(store.get('name'), 'everybody');
assert.htmlEqual(target.innerHTML, `
<h1>Hello everybody!</h1>
<input>
`);
}
};

@ -0,0 +1,10 @@
<h1>Hello {{$name}}!</h1>
<NameInput/>
<script>
import NameInput from './NameInput.html';
export default {
components: { NameInput }
};
</script>

@ -0,0 +1,15 @@
{{#if isVisible}}
<div class='{{todo.done ? "done": "pending"}}'>{{todo.description}}</div>
{{/if}}
<script>
export default {
computed: {
isVisible: ($filter, todo) => {
if ($filter === 'all') return true;
if ($filter === 'done') return todo.done;
if ($filter === 'pending') return !todo.done;
}
}
};
</script>

@ -0,0 +1,63 @@
import { Store } from '../../../../store.js';
class MyStore extends Store {
setFilter(filter) {
this.set({ filter });
}
toggleTodo(todo) {
todo.done = !todo.done;
this.set({ todos: this.get('todos') });
}
}
const todos = [
{
description: 'Buy some milk',
done: true,
},
{
description: 'Do the laundry',
done: true,
},
{
description: "Find life's true purpose",
done: false,
}
];
const store = new MyStore({
filter: 'all',
todos
});
export default {
store,
html: `
<div class='done'>Buy some milk</div>
<div class='done'>Do the laundry</div>
<div class='pending'>Find life's true purpose</div>
`,
test(assert, component, target) {
store.setFilter('pending');
assert.htmlEqual(target.innerHTML, `
<div class='pending'>Find life's true purpose</div>
`);
store.toggleTodo(todos[1]);
assert.htmlEqual(target.innerHTML, `
<div class='pending'>Do the laundry</div>
<div class='pending'>Find life's true purpose</div>
`);
store.setFilter('done');
assert.htmlEqual(target.innerHTML, `
<div class='done'>Buy some milk</div>
`);
}
};

@ -0,0 +1,11 @@
{{#each $todos as todo}}
<Todo :todo/>
{{/each}}
<script>
import Todo from './Todo.html';
export default {
components: { Todo }
};
</script>

@ -0,0 +1 @@
<input on:input='store.setName(this.value)'>

@ -0,0 +1,34 @@
import { Store } from '../../../../store.js';
class MyStore extends Store {
setName(name) {
this.set({ name });
}
}
const store = new MyStore({
name: 'world'
});
export default {
store,
html: `
<h1>Hello world!</h1>
<input>
`,
test(assert, component, target, window) {
const input = target.querySelector('input');
const event = new window.Event('input');
input.value = 'everybody';
input.dispatchEvent(event);
assert.equal(store.get('name'), 'everybody');
assert.htmlEqual(target.innerHTML, `
<h1>Hello everybody!</h1>
<input>
`);
}
};

@ -0,0 +1,10 @@
<h1>Hello {{$name}}!</h1>
<NameInput/>
<script>
import NameInput from './NameInput.html';
export default {
components: { NameInput }
};
</script>

@ -0,0 +1,17 @@
import { Store } from '../../../../store.js';
const store = new Store({
name: 'world'
});
export default {
store,
html: `<h1>Hello world!</h1>`,
test(assert, component, target) {
store.set({ name: 'everybody' });
assert.htmlEqual(target.innerHTML, `<h1>Hello everybody!</h1>`);
}
};

@ -0,0 +1 @@
<h1>Hello {{$name}}!</h1>

@ -0,0 +1,17 @@
export default {
test(assert, component, target, window, raf) {
component.set({ visible: true });
const div = target.querySelector('div');
assert.equal(div.foo, 0);
raf.tick(50);
assert.equal(div.foo, 100);
raf.tick(100);
assert.equal(div.foo, 200);
raf.tick(101);
component.destroy();
},
};

@ -0,0 +1,18 @@
{{#if visible}}
<div in:foo='{k: 200}'>fades in</div>
{{/if}}
<script>
export default {
transitions: {
foo: function(node, params) {
return {
duration: 100,
tick: t => {
node.foo = t * params.k;
}
};
}
}
};
</script>

@ -22,7 +22,8 @@ function tryToReadFile(file) {
describe("ssr", () => { describe("ssr", () => {
before(() => { before(() => {
require("../../ssr/register")({ require("../../ssr/register")({
extensions: ['.svelte', '.html'] extensions: ['.svelte', '.html'],
store: true
}); });
return setupHtmlEqual(); return setupHtmlEqual();
@ -98,9 +99,15 @@ describe("ssr", () => {
delete require.cache[resolved]; delete require.cache[resolved];
}); });
require("../../ssr/register")({
store: !!config.store
});
try { try {
const component = require(`../runtime/samples/${dir}/main.html`); const component = require(`../runtime/samples/${dir}/main.html`);
const html = component.render(config.data); const html = component.render(config.data, {
store: config.store
});
if (config.html) { if (config.html) {
assert.htmlEqual(html, config.html); assert.htmlEqual(html, config.html);

@ -0,0 +1,190 @@
import fs from 'fs';
import assert from 'assert';
import MagicString from 'magic-string';
import { parse } from 'acorn';
import { Store } from '../../store.js';
describe('store', () => {
it('is written in ES5', () => {
const source = fs.readFileSync('store.js', 'utf-8');
const ast = parse(source, {
sourceType: 'module'
});
const magicString = new MagicString(source);
ast.body.forEach(node => {
if (/^(Im|Ex)port/.test(node.type)) magicString.remove(node.start, node.end);
});
parse(magicString.toString(), {
ecmaVersion: 5
});
});
describe('get', () => {
it('gets a specific key', () => {
const store = new Store({
foo: 'bar'
});
assert.equal(store.get('foo'), 'bar');
});
it('gets the entire state object', () => {
const store = new Store({
foo: 'bar'
});
assert.deepEqual(store.get(), { foo: 'bar' });
});
});
describe('set', () => {
it('sets state', () => {
const store = new Store();
store.set({
foo: 'bar'
});
assert.equal(store.get('foo'), 'bar');
});
});
describe('observe', () => {
it('observes state', () => {
let newFoo;
let oldFoo;
const store = new Store({
foo: 'bar'
});
store.observe('foo', (n, o) => {
newFoo = n;
oldFoo = o;
});
assert.equal(newFoo, 'bar');
assert.equal(oldFoo, undefined);
store.set({
foo: 'baz'
});
assert.equal(newFoo, 'baz');
assert.equal(oldFoo, 'bar');
});
});
describe('onchange', () => {
it('fires a callback when state changes', () => {
const store = new Store();
let count = 0;
let args;
store.onchange((state, changed) => {
count += 1;
args = { state, changed };
});
store.set({ foo: 'bar' });
assert.equal(count, 1);
assert.deepEqual(args, {
state: { foo: 'bar' },
changed: { foo: true }
});
// this should be a noop
store.set({ foo: 'bar' });
assert.equal(count, 1);
// this shouldn't
store.set({ foo: 'baz' });
assert.equal(count, 2);
assert.deepEqual(args, {
state: { foo: 'baz' },
changed: { foo: true }
});
});
});
describe('computed', () => {
it('computes a property based on data', () => {
const store = new Store({
foo: 1
});
store.compute('bar', ['foo'], foo => foo * 2);
assert.equal(store.get('bar'), 2);
const values = [];
store.observe('bar', bar => {
values.push(bar);
});
store.set({ foo: 2 });
assert.deepEqual(values, [2, 4]);
});
it('computes a property based on another computed property', () => {
const store = new Store({
foo: 1
});
store.compute('bar', ['foo'], foo => foo * 2);
store.compute('baz', ['bar'], bar => bar * 2);
assert.equal(store.get('baz'), 4);
const values = [];
store.observe('baz', baz => {
values.push(baz);
});
store.set({ foo: 2 });
assert.deepEqual(values, [4, 8]);
});
it('prevents computed properties from being set', () => {
const store = new Store({
foo: 1
});
store.compute('bar', ['foo'], foo => foo * 2);
assert.throws(() => {
store.set({ bar: 'whatever' });
}, /'bar' is a read-only property/);
});
it('allows multiple dependents to depend on the same computed property', () => {
const store = new Store({
a: 1
});
store.compute('b', ['a'], a => a * 2);
store.compute('c', ['b'], b => b * 3);
store.compute('d', ['b'], b => b * 4);
assert.deepEqual(store.get(), { a: 1, b: 2, c: 6, d: 8 });
// bit cheeky, testing a private property, but whatever
assert.equal(store._sortedComputedProperties.length, 3);
});
it('prevents cyclical dependencies', () => {
const store = new Store();
assert.throws(() => {
store.compute('a', ['b'], b => b + 1);
store.compute('b', ['a'], a => a + 1);
}, /Cyclical dependency detected/);
});
});
});

@ -0,0 +1 @@
<button on:click='store.foo()'>foo</button>

@ -0,0 +1,8 @@
[{
"message": "compile with `store: true` in order to call store methods",
"loc": {
"line": 1,
"column": 18
},
"pos": 18
}]

@ -10,7 +10,7 @@
version "8.0.53" version "8.0.53"
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.53.tgz#396b35af826fa66aad472c8cb7b8d5e277f4e6d8" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.53.tgz#396b35af826fa66aad472c8cb7b8d5e277f4e6d8"
"@types/node@^7.0.18", "@types/node@^7.0.48": "@types/node@^7.0.18":
version "7.0.48" version "7.0.48"
resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.48.tgz#24bfdc0aa82e8f6dbd017159c58094a2e06d0abb" resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.48.tgz#24bfdc0aa82e8f6dbd017159c58094a2e06d0abb"
@ -64,8 +64,8 @@ ajv@^4.9.1:
json-stable-stringify "^1.0.1" json-stable-stringify "^1.0.1"
ajv@^5.1.0, ajv@^5.2.3, ajv@^5.3.0: ajv@^5.1.0, ajv@^5.2.3, ajv@^5.3.0:
version "5.4.0" version "5.5.1"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.4.0.tgz#32d1cf08dbc80c432f426f12e10b2511f6b46474" resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.1.tgz#b38bb8876d9e86bee994956a04e721e88b248eb2"
dependencies: dependencies:
co "^4.6.0" co "^4.6.0"
fast-deep-equal "^1.0.0" fast-deep-equal "^1.0.0"
@ -441,8 +441,8 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0:
supports-color "^4.0.0" supports-color "^4.0.0"
chardet@^0.4.0: chardet@^0.4.0:
version "0.4.0" version "0.4.2"
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.0.tgz#0bbe1355ac44d7a3ed4a925707c4ef70f8190f6c" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2"
chokidar@^1.7.0: chokidar@^1.7.0:
version "1.7.0" version "1.7.0"
@ -538,10 +538,8 @@ commander@2.9.0:
graceful-readlink ">= 1.0.0" graceful-readlink ">= 1.0.0"
commander@^2.9.0: commander@^2.9.0:
version "2.12.0" version "2.12.2"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.12.0.tgz#2f13615c39c687a77926aa68ef25c099db1e72fb" resolved "https://registry.yarnpkg.com/commander/-/commander-2.12.2.tgz#0f5946c427ed9ec0d91a46bb9def53e54650e555"
dependencies:
"@types/node" "^7.0.48"
commondir@^1.0.1: commondir@^1.0.1:
version "1.0.1" version "1.0.1"
@ -580,8 +578,8 @@ content-type-parser@^1.0.1:
resolved "https://registry.yarnpkg.com/content-type-parser/-/content-type-parser-1.0.2.tgz#caabe80623e63638b2502fd4c7f12ff4ce2352e7" resolved "https://registry.yarnpkg.com/content-type-parser/-/content-type-parser-1.0.2.tgz#caabe80623e63638b2502fd4c7f12ff4ce2352e7"
convert-source-map@^1.3.0: convert-source-map@^1.3.0:
version "1.5.0" version "1.5.1"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5"
core-js@^2.4.0: core-js@^2.4.0:
version "2.5.1" version "2.5.1"
@ -744,12 +742,11 @@ doctrine@1.5.0:
esutils "^2.0.2" esutils "^2.0.2"
isarray "^1.0.0" isarray "^1.0.0"
doctrine@^2.0.0: doctrine@^2.0.2:
version "2.0.0" version "2.0.2"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.0.0.tgz#c73d8d2909d22291e1a007a395804da8b665fe63" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.0.2.tgz#68f96ce8efc56cc42651f1faadb4f175273b0075"
dependencies: dependencies:
esutils "^2.0.2" esutils "^2.0.2"
isarray "^1.0.0"
dom-serializer@0: dom-serializer@0:
version "0.1.0" version "0.1.0"
@ -890,8 +887,8 @@ eslint-scope@^3.7.1:
estraverse "^4.1.1" estraverse "^4.1.1"
eslint@^4.3.0: eslint@^4.3.0:
version "4.11.0" version "4.12.1"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.11.0.tgz#39a8c82bc0a3783adf5a39fa27fdd9d36fac9a34" resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.12.1.tgz#5ec1973822b4a066b353770c3c6d69a2a188e880"
dependencies: dependencies:
ajv "^5.3.0" ajv "^5.3.0"
babel-code-frame "^6.22.0" babel-code-frame "^6.22.0"
@ -899,7 +896,7 @@ eslint@^4.3.0:
concat-stream "^1.6.0" concat-stream "^1.6.0"
cross-spawn "^5.1.0" cross-spawn "^5.1.0"
debug "^3.0.1" debug "^3.0.1"
doctrine "^2.0.0" doctrine "^2.0.2"
eslint-scope "^3.7.1" eslint-scope "^3.7.1"
espree "^3.5.2" espree "^3.5.2"
esquery "^1.0.0" esquery "^1.0.0"
@ -908,7 +905,7 @@ eslint@^4.3.0:
file-entry-cache "^2.0.0" file-entry-cache "^2.0.0"
functional-red-black-tree "^1.0.1" functional-red-black-tree "^1.0.1"
glob "^7.1.2" glob "^7.1.2"
globals "^9.17.0" globals "^11.0.1"
ignore "^3.3.3" ignore "^3.3.3"
imurmurhash "^0.1.4" imurmurhash "^0.1.4"
inquirer "^3.0.6" inquirer "^3.0.6"
@ -1030,10 +1027,14 @@ extract-zip@^1.0.3:
mkdirp "0.5.0" mkdirp "0.5.0"
yauzl "2.4.1" yauzl "2.4.1"
extsprintf@1.3.0, extsprintf@^1.2.0: extsprintf@1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
extsprintf@^1.2.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
fast-deep-equal@^1.0.0: fast-deep-equal@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff"
@ -1272,7 +1273,11 @@ glob@^7.0.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1, glob@^7.1.2:
once "^1.3.0" once "^1.3.0"
path-is-absolute "^1.0.0" path-is-absolute "^1.0.0"
globals@^9.17.0, globals@^9.18.0: globals@^11.0.1:
version "11.0.1"
resolved "https://registry.yarnpkg.com/globals/-/globals-11.0.1.tgz#12a87bb010e5154396acc535e1e43fc753b0e5e8"
globals@^9.18.0:
version "9.18.0" version "9.18.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
@ -1599,8 +1604,8 @@ is-path-in-cwd@^1.0.0:
is-path-inside "^1.0.0" is-path-inside "^1.0.0"
is-path-inside@^1.0.0: is-path-inside@^1.0.0:
version "1.0.0" version "1.0.1"
resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.0.tgz#fc06e5a1683fbda13de667aff717bbc10a48f37f" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036"
dependencies: dependencies:
path-is-inside "^1.0.1" path-is-inside "^1.0.1"
@ -1723,8 +1728,8 @@ jsbn@~0.1.0:
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
jsdom@^11.1.0: jsdom@^11.1.0:
version "11.4.0" version "11.5.1"
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.4.0.tgz#a3941a9699cbb0d61f8ab86f6f28f4ad5ea60d04" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.5.1.tgz#5df753b8d0bca20142ce21f4f6c039f99a992929"
dependencies: dependencies:
abab "^1.0.3" abab "^1.0.3"
acorn "^5.1.2" acorn "^5.1.2"
@ -1737,6 +1742,7 @@ jsdom@^11.1.0:
domexception "^1.0.0" domexception "^1.0.0"
escodegen "^1.9.0" escodegen "^1.9.0"
html-encoding-sniffer "^1.0.1" html-encoding-sniffer "^1.0.1"
left-pad "^1.2.0"
nwmatcher "^1.4.3" nwmatcher "^1.4.3"
parse5 "^3.0.2" parse5 "^3.0.2"
pn "^1.0.0" pn "^1.0.0"
@ -1839,6 +1845,10 @@ lcid@^1.0.0:
dependencies: dependencies:
invert-kv "^1.0.0" invert-kv "^1.0.0"
left-pad@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.2.0.tgz#d30a73c6b8201d8f7d8e7956ba9616087a68e0ee"
levn@^0.3.0, levn@~0.3.0: levn@^0.3.0, levn@~0.3.0:
version "0.3.0" version "0.3.0"
resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
@ -1866,8 +1876,8 @@ load-json-file@^2.0.0:
strip-bom "^3.0.0" strip-bom "^3.0.0"
locate-character@^2.0.0: locate-character@^2.0.0:
version "2.0.1" version "2.0.3"
resolved "https://registry.yarnpkg.com/locate-character/-/locate-character-2.0.1.tgz#48f9599f342daf26f73db32f45941eae37bae391" resolved "https://registry.yarnpkg.com/locate-character/-/locate-character-2.0.3.tgz#85a5aedae26b3536c3e97016af164cdaa3ae5ae1"
locate-path@^2.0.0: locate-path@^2.0.0:
version "2.0.0" version "2.0.0"
@ -3286,8 +3296,8 @@ typescript@^1.8.9:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-1.8.10.tgz#b475d6e0dff0bf50f296e5ca6ef9fbb5c7320f1e" resolved "https://registry.yarnpkg.com/typescript/-/typescript-1.8.10.tgz#b475d6e0dff0bf50f296e5ca6ef9fbb5c7320f1e"
typescript@^2.6.1: typescript@^2.6.1:
version "2.6.1" version "2.6.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.1.tgz#ef39cdea27abac0b500242d6726ab90e0c846631" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4"
uglify-js@^2.6: uglify-js@^2.6:
version "2.8.29" version "2.8.29"

Loading…
Cancel
Save