pull/4742/head
pushkine 5 years ago
parent a26a75eba6
commit 0d9d3c53d1

1
.gitignore vendored

@ -9,6 +9,7 @@ node_modules
/compiler.*js /compiler.*js
/index.*js /index.*js
/internal /internal
/dev
/store /store
/easing /easing
/motion /motion

@ -56,41 +56,41 @@
}, },
"homepage": "https://github.com/sveltejs/svelte#README", "homepage": "https://github.com/sveltejs/svelte#README",
"devDependencies": { "devDependencies": {
"@rollup/plugin-commonjs": "^11.0.0", "@rollup/plugin-commonjs": "^11.1.0",
"@rollup/plugin-json": "^4.0.1", "@rollup/plugin-json": "^4.0.3",
"@rollup/plugin-node-resolve": "^6.0.0", "@rollup/plugin-node-resolve": "^7.1.3",
"@rollup/plugin-replace": "^2.3.0", "@rollup/plugin-replace": "^2.3.2",
"@rollup/plugin-sucrase": "^3.0.0", "@rollup/plugin-sucrase": "^3.0.0",
"@rollup/plugin-typescript": "^2.0.1", "@rollup/plugin-typescript": "^4.1.1",
"@rollup/plugin-virtual": "^2.0.0", "@rollup/plugin-virtual": "^2.0.1",
"@types/mocha": "^5.2.7", "@types/mocha": "^7.0.2",
"@types/node": "^8.10.53", "@types/node": "^13.13.4",
"@typescript-eslint/eslint-plugin": "^1.13.0", "@typescript-eslint/eslint-plugin": "^2.30.0",
"@typescript-eslint/parser": "^2.1.0", "@typescript-eslint/parser": "^2.30.0",
"acorn": "^7.1.0", "acorn": "^7.1.1",
"agadoo": "^1.1.0", "agadoo": "^2.0.0",
"c8": "^5.0.1", "c8": "^7.1.1",
"code-red": "0.1.1", "code-red": "0.1.1",
"codecov": "^3.5.0", "codecov": "^3.6.5",
"css-tree": "1.0.0-alpha22", "css-tree": "1.0.0-alpha39",
"eslint": "^6.3.0", "eslint": "^6.8.0",
"eslint-plugin-import": "^2.18.2", "eslint-plugin-import": "^2.20.2",
"eslint-plugin-svelte3": "^2.7.3", "eslint-plugin-svelte3": "^2.7.3",
"estree-walker": "^1.0.0", "estree-walker": "^2.0.1",
"is-reference": "^1.1.4", "is-reference": "^1.1.4",
"jsdom": "^15.1.1", "jsdom": "^16.2.2",
"kleur": "^3.0.3", "kleur": "^3.0.3",
"locate-character": "^2.0.5", "locate-character": "^2.0.5",
"magic-string": "^0.25.3", "magic-string": "^0.25.7",
"mocha": "^6.2.0", "mocha": "^7.1.2",
"periscopic": "^2.0.1", "periscopic": "^2.0.2",
"puppeteer": "^1.19.0", "puppeteer": "^3.0.2",
"rollup": "^1.27.14", "rollup": "^2.7.6",
"source-map": "^0.7.3", "source-map": "^0.7.3",
"source-map-support": "^0.5.13", "source-map-support": "^0.5.19",
"tiny-glob": "^0.2.6", "tiny-glob": "^0.2.6",
"tslib": "^1.10.0", "tslib": "^1.11.1",
"typescript": "^3.5.3" "typescript": "^3.8.3"
}, },
"nyc": { "nyc": {
"include": [ "include": [

@ -7,89 +7,121 @@ import sucrase from '@rollup/plugin-sucrase';
import typescript from '@rollup/plugin-typescript'; import typescript from '@rollup/plugin-typescript';
import pkg from './package.json'; import pkg from './package.json';
const esm = { format: 'esm' };
const cjs = { format: 'cjs' };
const is_publish = !!process.env.PUBLISH; const is_publish = !!process.env.PUBLISH;
const external = (id) => id.startsWith('svelte/');
const ts_plugin = is_publish const ts_plugin = is_publish
? typescript({ include: 'src/**', typescript: require('typescript') }) ? typescript({ include: 'src/**', typescript: require('typescript') })
: sucrase({ transforms: ['typescript'] }); : sucrase({ transforms: ['typescript'] });
const external = (id) => id.startsWith('svelte/'); // documented in src/ambient.ts
const version = replace({ __VERSION__: pkg.version }); const globals = (name) =>
replace({
fs.writeFileSync(`./compiler.d.ts`, `export { compile, parse, preprocess, VERSION } from './types/compiler/index';`); 'var __DEV__: boolean': 'var rollup_removes_this',
'__DEV__': name === 'dev',
const output = (obj) => 'var __TEST__: boolean': 'var rollup_removes_this',
Object.keys(obj).flatMap((name) => '__TEST__': name === 'test',
[esm, cjs].map(({ format }) => ({ 'var __VERSION__: string': 'var rollup_removes_this',
format, '__VERSION__': `"${pkg.version}"`,
file: `${name !== 'default' ? name + '/' : ''}${obj[name].file}.${format === 'esm' ? 'mjs' : 'js'}`, });
paths: (id) => external(id) && `${id.replace('svelte', obj[name].path)}`, /**
plugins: (defaults.plugins || []).concat([replace({ __DEV__: name === 'dev' })]), *
*/
function map_outputs({ output, plugins, ...rest }) {
return Object.keys(output).flatMap((name) =>
['esm', 'cjs'].map((format) => ({
external,
...rest,
output: {
format,
file: `${name !== 'default' ? name + '/' : ''}${output[name].file}.${format === 'esm' ? 'mjs' : 'js'}`,
paths: (id) => external(id) && `${id.replace('svelte', output[name].path)}`,
},
plugins: [globals(name), ...(plugins || [])],
})) }))
); );
}
function writeFileSync(...arr) { function writeFileSync(...arr) {
arr.filter(Boolean).forEach(({ dir, content }) => fs.writeFileSync(dir, content)); arr.filter(Boolean).forEach(({ dir, content }) => fs.writeFileSync(dir, content));
} }
fs.writeFileSync(`./compiler.d.ts`, `export { compile, parse, preprocess, version } from './types/compiler/index';`);
export default [ export default [
/* runtime main */ /* main exports */
{ ...map_outputs({
input: `src/runtime/index.ts`, input: `src/runtime/index.ts`,
output: output({ output: {
/**
* Production main runtime
* -> 'svelte/index.js'
*/
default: { file: 'index', path: '.' }, default: { file: 'index', path: '.' },
/**
* Development main runtime
* -> 'svelte/dev/index.js'
* redirected to by the compiler
*/
dev: { file: 'index', path: '.' }, dev: { file: 'index', path: '.' },
}), },
external,
plugins: [ts_plugin], plugins: [ts_plugin],
}, }),
/* svelte/[library] */ /* 'svelte/[library]' exports */
...fs ...fs
.readdirSync('src/runtime') .readdirSync('src/runtime')
.filter((dir) => fs.statSync(`src/runtime/${dir}`).isDirectory()) .filter((dir) => fs.statSync(`src/runtime/${dir}`).isDirectory())
.map((dir) => ({ .flatMap((library) =>
input: `src/runtime/${dir}/index.ts`, map_outputs({
output: output({ input: `src/runtime/${library}/index.ts`,
[dir]: { file: 'index', path: '..' }, output: {
dev: { file: dir, path: '.' }, /**
}), * Production runtime
external, * -> 'svelte/[library]/index.js'
plugins: [ */
version, [library]: { file: 'index', path: '..' },
ts_plugin, /**
{ * Development runtime
writeBundle(bundle) { * -> 'svelte/dev/[library].js'
writeFileSync( * Must be redirected to by user's bundler
dir === 'internal' && */
bundle['index.mjs'] && { dev: { file: library, path: '.' },
dir: `src/compiler/compile/internal_exports.ts`, },
content: ` plugins: [
// This file is automatically generated ts_plugin,
export default new Set(${JSON.stringify(bundle['index.mjs'].exports)});`, {
writeBundle(bundle) {
writeFileSync(
/* gives internal function names to the compiler */
library === 'internal' &&
bundle['index.mjs'] && {
dir: `src/compiler/compile/internal_exports.ts`,
content: `
// This file is automatically generated
export default new Set(${JSON.stringify(bundle['index.mjs'].exports)});`,
},
/* adds package.json to every svelte/[library] */
{
dir: `${library}/package.json`,
content: `{
"main": "./index",
"module": "./index.mjs",
"types": "./index.d.ts"
}`,
}, },
{ /* exports types */
dir: `${dir}/package.json`, {
content: `{ dir: `${library}/index.d.ts`,
"main": "./index", content: `export * from '../types/runtime/${library}/index';`,
"module": "./index.mjs", }
"types": "./index.d.ts" );
}`, },
},
{
dir: `${dir}/index.d.ts`,
content: `export * from '../types/runtime/${dir}/index';`,
}
);
}, },
}, ],
], })
})), ),
/* compiler.js */ /* compiler.js */
{ {
input: 'src/compiler/index.ts', input: 'src/compiler/index.ts',
plugins: [version, resolve(), commonjs({ include: ['node_modules/**'] }), json(), ts_plugin], plugins: [globals('compiler'), resolve(), commonjs({ include: ['node_modules/**'] }), json(), ts_plugin],
output: { output: {
file: 'compiler.js', file: 'compiler.js',
format: is_publish ? 'umd' : 'cjs', format: is_publish ? 'umd' : 'cjs',

@ -0,0 +1,55 @@
/**
* __DEV__
*
* Used in src/runtime
*
* Bundles dev runtime to svelte/dev/[library].[m]js
* the compiler rewrites its own 'svelte' imports to 'svelte/dev' automatically
*
*/
declare var __DEV__: boolean;
/**
* __VERSION__
*
* Svelte's version in package.json
* Used in src/compiler and src/runtime
*
*/
declare var __VERSION__: string;
/**
* __COMPILER_API_VERSION__
*
* Unique ID passed to the compiler
* Used to mitigate breaking changes with bundler plugins
*
* VERSIONS ( default is "3.0.0" )
*
* >3.22.0 : {
*
* The change :
* A different runtime is now used in dev mode,
* every import has to go through 'svelte/dev' instead of 'svelte'
*
* Requirement :
* In dev mode, bundler plugins must make sure that every 'svelte' import is replaced by 'svelte/dev'
*
* }
*
*/
//declare var __COMPILER_API_VERSION__: boolean;
/**
* Unique ID devtools must pass to the compiler
* Used to
*
* VERSIONS ( default is "^3.21.0" )
* - 0 (default) : compiler imports from prod runtime
* - 1 : in dev mode, compiler imports from dev runtime
*/
//declare var __DEVTOOLS_API_VERSION__: boolean;
/**
* TODO
* Bundle different runtime for tests
* instead of relying on hacks
*/
declare var __TEST__: boolean;

@ -4,12 +4,7 @@ import Stats from '../Stats';
import { globals, reserved, is_valid } from '../utils/names'; import { globals, reserved, is_valid } from '../utils/names';
import { namespaces, valid_namespaces } from '../utils/namespaces'; import { namespaces, valid_namespaces } from '../utils/namespaces';
import create_module from './create_module'; import create_module from './create_module';
import { import { create_scopes, extract_names, Scope, extract_identifiers } from './utils/scope';
create_scopes,
extract_names,
Scope,
extract_identifiers,
} from './utils/scope';
import Stylesheet from './css/Stylesheet'; import Stylesheet from './css/Stylesheet';
import { test } from '../config'; import { test } from '../config';
import Fragment from './nodes/Fragment'; import Fragment from './nodes/Fragment';
@ -24,7 +19,15 @@ import TemplateScope from './nodes/shared/TemplateScope';
import fuzzymatch from '../utils/fuzzymatch'; import fuzzymatch from '../utils/fuzzymatch';
import get_object from './utils/get_object'; import get_object from './utils/get_object';
import Slot from './nodes/Slot'; import Slot from './nodes/Slot';
import { Node, ImportDeclaration, Identifier, Program, ExpressionStatement, AssignmentExpression, Literal } from 'estree'; import {
Node,
ImportDeclaration,
Identifier,
Program,
ExpressionStatement,
AssignmentExpression,
Literal,
} from 'estree';
import add_to_set from './utils/add_to_set'; import add_to_set from './utils/add_to_set';
import check_graph_for_cycles from './utils/check_graph_for_cycles'; import check_graph_for_cycles from './utils/check_graph_for_cycles';
import { print, x, b } from 'code-red'; import { print, x, b } from 'code-red';
@ -66,8 +69,8 @@ export default class Component {
hoistable_nodes: Set<Node> = new Set(); hoistable_nodes: Set<Node> = new Set();
node_for_declaration: Map<string, Node> = new Map(); node_for_declaration: Map<string, Node> = new Map();
partly_hoisted: Array<(Node | Node[])> = []; partly_hoisted: Array<Node | Node[]> = [];
fully_hoisted: Array<(Node | Node[])> = []; fully_hoisted: Array<Node | Node[]> = [];
reactive_declarations: Array<{ reactive_declarations: Array<{
assignees: Set<string>; assignees: Set<string>;
dependencies: Set<string>; dependencies: Set<string>;
@ -116,43 +119,29 @@ export default class Component {
html: ast.html, html: ast.html,
css: ast.css, css: ast.css,
instance: ast.instance && JSON.parse(JSON.stringify(ast.instance)), instance: ast.instance && JSON.parse(JSON.stringify(ast.instance)),
module: ast.module module: ast.module,
}; };
this.file = this.file =
compile_options.filename && compile_options.filename &&
(typeof process !== 'undefined' (typeof process !== 'undefined'
? compile_options.filename ? compile_options.filename.replace(process.cwd(), '').replace(/^[/\\]/, '')
.replace(process.cwd(), '')
.replace(/^[/\\]/, '')
: compile_options.filename); : compile_options.filename);
this.locate = getLocator(this.source, { offsetLine: 1 }); this.locate = getLocator(this.source, { offsetLine: 1 });
// styles // styles
this.stylesheet = new Stylesheet( this.stylesheet = new Stylesheet(source, ast, compile_options.filename, compile_options.dev);
source,
ast,
compile_options.filename,
compile_options.dev
);
this.stylesheet.validate(this); this.stylesheet.validate(this);
this.component_options = process_component_options( this.component_options = process_component_options(this, this.ast.html.children);
this, this.namespace = namespaces[this.component_options.namespace] || this.component_options.namespace;
this.ast.html.children
);
this.namespace =
namespaces[this.component_options.namespace] ||
this.component_options.namespace;
if (compile_options.customElement) { if (compile_options.customElement) {
if ( if (this.component_options.tag === undefined && compile_options.tag === undefined) {
this.component_options.tag === undefined && const svelteOptions = ast.html.children.find((child) => child.name === 'svelte:options') || {
compile_options.tag === undefined start: 0,
) { end: 0,
const svelteOptions = ast.html.children.find( };
child => child.name === 'svelte:options'
) || { start: 0, end: 0 };
this.warn(svelteOptions, { this.warn(svelteOptions, {
code: 'custom-element-no-tag', code: 'custom-element-no-tag',
message: `No custom element 'tag' option was specified. To automatically register a custom element, specify a name with a hyphen in it, e.g. <svelte:options tag="my-thing"/>. To hide this warning, use <svelte:options tag={null}/>`, message: `No custom element 'tag' option was specified. To automatically register a custom element, specify a name with a hyphen in it, e.g. <svelte:options tag="my-thing"/>. To hide this warning, use <svelte:options tag={null}/>`,
@ -205,7 +194,7 @@ export default class Component {
const variable = this.var_lookup.get(subscribable_name); const variable = this.var_lookup.get(subscribable_name);
if (variable) { if (variable) {
variable.referenced = true; variable.referenced = true;
variable.subscribable = true; variable.subscribable = true;
} }
} else { } else {
@ -235,7 +224,10 @@ export default class Component {
const { compile_options, name } = this; const { compile_options, name } = this;
const { format = 'esm' } = compile_options; const { format = 'esm' } = compile_options;
const banner = `${this.file ? `${this.file} ` : ``}generated by Svelte v${'__VERSION__'}`; const banner = `${this.file ? `${this.file} ` : ``}generated by Svelte v${__VERSION__}`;
const legacy_dev_runtime = compile_options.dev && compile_options.version < 3.22;
const dev_runtime = compile_options.dev && compile_options.version >= 3.22;
const program: any = { type: 'Program', body: result.js }; const program: any = { type: 'Program', body: result.js };
@ -249,11 +241,11 @@ export default class Component {
} else { } else {
let name = node.name.slice(1); let name = node.name.slice(1);
if (compile_options.dev) { if (legacy_dev_runtime) {
if (internal_exports.has(`${name}_dev`)) { if (internal_exports.has(`${name}_dev$legacy`)) {
name += '_dev'; name += '_dev$legacy';
} else if (internal_exports.has(`${name}Dev`)) { } else if (internal_exports.has(`${name}Dev$legacy`)) {
name += 'Dev'; name += 'Dev$legacy';
} }
} }
@ -261,23 +253,19 @@ export default class Component {
this.helpers.set(name, alias); this.helpers.set(name, alias);
node.name = alias.name; node.name = alias.name;
} }
} } else if (node.name[0] !== '#' && !is_valid(node.name)) {
else if (node.name[0] !== '#' && !is_valid(node.name)) {
// this hack allows x`foo.${bar}` where bar could be invalid // this hack allows x`foo.${bar}` where bar could be invalid
const literal: Literal = { type: 'Literal', value: node.name }; const literal: Literal = { type: 'Literal', value: node.name };
if (parent.type === 'Property' && key === 'key') { if (parent.type === 'Property' && key === 'key') {
parent.key = literal; parent.key = literal;
} } else if (parent.type === 'MemberExpression' && key === 'property') {
else if (parent.type === 'MemberExpression' && key === 'property') {
parent.property = literal; parent.property = literal;
parent.computed = true; parent.computed = true;
} }
} }
} }
} },
}); });
const referenced_globals = Array.from( const referenced_globals = Array.from(
@ -297,33 +285,31 @@ export default class Component {
format, format,
name, name,
banner, banner,
compile_options.sveltePath, compile_options.sveltePath + (dev_runtime ? '/dev' : ''),
imported_helpers, imported_helpers,
referenced_globals, referenced_globals,
this.imports, this.imports,
this.vars this.vars
.filter(variable => variable.module && variable.export_name) .filter((variable) => variable.module && variable.export_name)
.map(variable => ({ .map((variable) => ({
name: variable.name, name: variable.name,
as: variable.export_name, as: variable.export_name,
})) }))
); );
css = compile_options.customElement css = compile_options.customElement ? { code: null, map: null } : result.css;
? { code: null, map: null }
: result.css;
js = print(program, { js = print(program, {
sourceMapSource: compile_options.filename sourceMapSource: compile_options.filename,
}); });
js.map.sources = [ js.map.sources = [
compile_options.filename ? get_relative_path(compile_options.outputFilename || '', compile_options.filename) : null compile_options.filename
? get_relative_path(compile_options.outputFilename || '', compile_options.filename)
: null,
]; ];
js.map.sourcesContent = [ js.map.sourcesContent = [this.source];
this.source
];
} }
return { return {
@ -332,8 +318,8 @@ export default class Component {
ast: this.original_ast, ast: this.original_ast,
warnings: this.warnings, warnings: this.warnings,
vars: this.vars vars: this.vars
.filter(v => !v.global && !v.internal) .filter((v) => !v.global && !v.internal)
.map(v => ({ .map((v) => ({
name: v.name, name: v.name,
export_name: v.export_name || null, export_name: v.export_name || null,
injected: v.injected || false, injected: v.injected || false,
@ -378,17 +364,13 @@ export default class Component {
return (name: string): Identifier => { return (name: string): Identifier => {
if (test) name = `${name}$`; if (test) name = `${name}$`;
let alias = name; let alias = name;
for ( for (let i = 1; this.used_names.has(alias) || local_used_names.has(alias); alias = `${name}_${i++}`);
let i = 1;
this.used_names.has(alias) || local_used_names.has(alias);
alias = `${name}_${i++}`
);
local_used_names.add(alias); local_used_names.add(alias);
this.globally_used_names.add(alias); this.globally_used_names.add(alias);
return { return {
type: 'Identifier', type: 'Identifier',
name: alias name: alias,
}; };
}; };
} }
@ -440,8 +422,7 @@ export default class Component {
end, end,
pos: pos.start, pos: pos.start,
filename: this.compile_options.filename, filename: this.compile_options.filename,
toString: () => toString: () => `${warning.message} (${start.line}:${start.column})\n${frame}`,
`${warning.message} (${start.line}:${start.column})\n${frame}`,
}); });
} }
@ -466,14 +447,17 @@ export default class Component {
} }
if (node.declaration) { if (node.declaration) {
if (node.declaration.type === 'VariableDeclaration') { if (node.declaration.type === 'VariableDeclaration') {
node.declaration.declarations.forEach(declarator => { node.declaration.declarations.forEach((declarator) => {
extract_names(declarator.id).forEach(name => { extract_names(declarator.id).forEach((name) => {
const variable = this.var_lookup.get(name); const variable = this.var_lookup.get(name);
variable.export_name = name; variable.export_name = name;
if (variable.writable && !(variable.referenced || variable.referenced_from_script || variable.subscribable)) { if (
variable.writable &&
!(variable.referenced || variable.referenced_from_script || variable.subscribable)
) {
this.warn(declarator, { this.warn(declarator, {
code: `unused-export-let`, code: `unused-export-let`,
message: `${this.name.name} has unused export property '${name}'. If it is for external reference only, please consider using \`export const ${name}\`` message: `${this.name.name} has unused export property '${name}'. If it is for external reference only, please consider using \`export const ${name}\``,
}); });
} }
}); });
@ -487,16 +471,19 @@ export default class Component {
return node.declaration; return node.declaration;
} else { } else {
node.specifiers.forEach(specifier => { node.specifiers.forEach((specifier) => {
const variable = this.var_lookup.get(specifier.local.name); const variable = this.var_lookup.get(specifier.local.name);
if (variable) { if (variable) {
variable.export_name = specifier.exported.name; variable.export_name = specifier.exported.name;
if (variable.writable && !(variable.referenced || variable.referenced_from_script || variable.subscribable)) { if (
variable.writable &&
!(variable.referenced || variable.referenced_from_script || variable.subscribable)
) {
this.warn(specifier, { this.warn(specifier, {
code: `unused-export-let`, code: `unused-export-let`,
message: `${this.name.name} has unused export property '${specifier.exported.name}'. If it is for external reference only, please consider using \`export const ${specifier.exported.name}\`` message: `${this.name.name} has unused export property '${specifier.exported.name}'. If it is for external reference only, please consider using \`export const ${specifier.exported.name}\``,
}); });
} }
} }
@ -510,13 +497,12 @@ export default class Component {
extract_javascript(script) { extract_javascript(script) {
if (!script) return null; if (!script) return null;
return script.content.body.filter(node => { return script.content.body.filter((node) => {
if (!node) return false; if (!node) return false;
if (this.hoistable_nodes.has(node)) return false; if (this.hoistable_nodes.has(node)) return false;
if (this.reactive_declaration_nodes.has(node)) return false; if (this.reactive_declaration_nodes.has(node)) return false;
if (node.type === 'ImportDeclaration') return false; if (node.type === 'ImportDeclaration') return false;
if (node.type === 'ExportDeclaration' && node.specifiers.length > 0) if (node.type === 'ExportDeclaration' && node.specifiers.length > 0) return false;
return false;
return true; return true;
}); });
} }
@ -554,7 +540,7 @@ export default class Component {
name, name,
module: true, module: true,
hoistable: true, hoistable: true,
writable writable,
}); });
}); });
@ -568,7 +554,7 @@ export default class Component {
this.add_var({ this.add_var({
name, name,
global: true, global: true,
hoistable: true hoistable: true,
}); });
} }
}); });
@ -598,7 +584,7 @@ export default class Component {
if (!script) return; if (!script) return;
// inject vars for reactive declarations // inject vars for reactive declarations
script.content.body.forEach(node => { script.content.body.forEach((node) => {
if (node.type !== 'LabeledStatement') return; if (node.type !== 'LabeledStatement') return;
if (node.body.type !== 'ExpressionStatement') return; if (node.body.type !== 'ExpressionStatement') return;
@ -606,16 +592,14 @@ export default class Component {
if (expression.type !== 'AssignmentExpression') return; if (expression.type !== 'AssignmentExpression') return;
if (expression.left.type === 'MemberExpression') return; if (expression.left.type === 'MemberExpression') return;
extract_names(expression.left).forEach(name => { extract_names(expression.left).forEach((name) => {
if (!this.var_lookup.has(name) && name[0] !== '$') { if (!this.var_lookup.has(name) && name[0] !== '$') {
this.injected_reactive_declaration_vars.add(name); this.injected_reactive_declaration_vars.add(name);
} }
}); });
}); });
const { scope: instance_scope, map, globals } = create_scopes( const { scope: instance_scope, map, globals } = create_scopes(script.content);
script.content
);
this.instance_scope = instance_scope; this.instance_scope = instance_scope;
this.instance_scope_map = map; this.instance_scope_map = map;
@ -632,7 +616,7 @@ export default class Component {
this.add_var({ this.add_var({
name, name,
initialised: instance_scope.initialised_declarations.has(name), initialised: instance_scope.initialised_declarations.has(name),
writable writable,
}); });
this.node_for_declaration.set(name, node); this.node_for_declaration.set(name, node);
@ -658,7 +642,7 @@ export default class Component {
if (name === '$' || name[1] === '$') { if (name === '$' || name[1] === '$') {
this.error(node as any, { this.error(node as any, {
code: 'illegal-global', code: 'illegal-global',
message: `${name} is an illegal variable name` message: `${name} is an illegal variable name`,
}); });
} }
@ -680,7 +664,7 @@ export default class Component {
this.add_var({ this.add_var({
name, name,
global: true, global: true,
hoistable: true hoistable: true,
}); });
} }
}); });
@ -744,7 +728,11 @@ export default class Component {
leave(node: Node) { leave(node: Node) {
// do it on leave, to prevent infinite loop // do it on leave, to prevent infinite loop
if (component.compile_options.dev && component.compile_options.loopGuardTimeout > 0) { if (component.compile_options.dev && component.compile_options.loopGuardTimeout > 0) {
const to_replace_for_loop_protect = component.loop_protect(node, scope, component.compile_options.loopGuardTimeout); const to_replace_for_loop_protect = component.loop_protect(
node,
scope,
component.compile_options.loopGuardTimeout
);
if (to_replace_for_loop_protect) { if (to_replace_for_loop_protect) {
this.replace(to_replace_for_loop_protect); this.replace(to_replace_for_loop_protect);
scope_updated = true; scope_updated = true;
@ -796,13 +784,9 @@ export default class Component {
const deep = assignee.type === 'MemberExpression'; const deep = assignee.type === 'MemberExpression';
names.forEach(name => { names.forEach((name) => {
const scope_owner = scope.find_owner(name); const scope_owner = scope.find_owner(name);
if ( if (scope_owner !== null ? scope_owner === instance_scope : module_scope && module_scope.has(name)) {
scope_owner !== null
? scope_owner === instance_scope
: module_scope && module_scope.has(name)
) {
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
variable[deep ? 'mutated' : 'reassigned'] = true; variable[deep ? 'mutated' : 'reassigned'] = true;
} }
@ -827,11 +811,7 @@ export default class Component {
} }
warn_on_undefined_store_value_references(node, parent, scope) { warn_on_undefined_store_value_references(node, parent, scope) {
if ( if (node.type === 'LabeledStatement' && node.label.name === '$' && parent.type !== 'Program') {
node.type === 'LabeledStatement' &&
node.label.name === '$' &&
parent.type !== 'Program'
) {
this.warn(node as any, { this.warn(node as any, {
code: 'non-top-level-reactive-declaration', code: 'non-top-level-reactive-declaration',
message: '$: has no effect outside of the top-level', message: '$: has no effect outside of the top-level',
@ -849,9 +829,7 @@ export default class Component {
} }
loop_protect(node, scope: Scope, timeout: number): Node | null { loop_protect(node, scope: Scope, timeout: number): Node | null {
if (node.type === 'WhileStatement' || if (node.type === 'WhileStatement' || node.type === 'ForStatement' || node.type === 'DoWhileStatement') {
node.type === 'ForStatement' ||
node.type === 'DoWhileStatement') {
const guard = this.get_unique_name('guard', scope); const guard = this.get_unique_name('guard', scope);
this.used_names.add(guard.name); this.used_names.add(guard.name);
@ -869,10 +847,7 @@ export default class Component {
return { return {
type: 'BlockStatement', type: 'BlockStatement',
body: [ body: [before[0], node],
before[0],
node,
],
}; };
} }
return null; return null;
@ -897,11 +872,11 @@ export default class Component {
if (node.type === 'VariableDeclaration') { if (node.type === 'VariableDeclaration') {
if (node.kind === 'var' || scope === instance_scope) { if (node.kind === 'var' || scope === instance_scope) {
node.declarations.forEach(declarator => { node.declarations.forEach((declarator) => {
if (declarator.id.type !== 'Identifier') { if (declarator.id.type !== 'Identifier') {
const inserts = []; const inserts = [];
extract_names(declarator.id).forEach(name => { extract_names(declarator.id).forEach((name) => {
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
if (variable.export_name) { if (variable.export_name) {
@ -928,29 +903,29 @@ export default class Component {
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
if (variable.export_name && variable.writable) { if (variable.export_name && variable.writable) {
const insert = variable.subscribable const insert = variable.subscribable ? get_insert(variable) : null;
? get_insert(variable)
: null;
parent[key].splice(index + 1, 0, insert); parent[key].splice(index + 1, 0, insert);
declarator.id = { declarator.id = {
type: 'ObjectPattern', type: 'ObjectPattern',
properties: [{ properties: [
type: 'Property', {
method: false, type: 'Property',
shorthand: false, method: false,
computed: false, shorthand: false,
kind: 'init', computed: false,
key: { type: 'Identifier', name: variable.export_name }, kind: 'init',
value: declarator.init key: { type: 'Identifier', name: variable.export_name },
? { value: declarator.init
type: 'AssignmentPattern', ? {
left: declarator.id, type: 'AssignmentPattern',
right: declarator.init left: declarator.id,
} right: declarator.init,
: declarator.id }
}] : declarator.id,
},
],
}; };
declarator.init = x`$$props`; declarator.init = x`$$props`;
@ -981,12 +956,7 @@ export default class Component {
// reference instance variables other than other // reference instance variables other than other
// hoistable functions. TODO others? // hoistable functions. TODO others?
const { const { hoistable_nodes, var_lookup, injected_reactive_declaration_vars, imports } = this;
hoistable_nodes,
var_lookup,
injected_reactive_declaration_vars,
imports,
} = this;
const top_level_function_declarations = new Map(); const top_level_function_declarations = new Map();
@ -996,7 +966,7 @@ export default class Component {
const node = body[i]; const node = body[i];
if (node.type === 'VariableDeclaration') { if (node.type === 'VariableDeclaration') {
const all_hoistable = node.declarations.every(d => { const all_hoistable = node.declarations.every((d) => {
if (!d.init) return false; if (!d.init) return false;
if (d.init.type !== 'Literal') return false; if (d.init.type !== 'Literal') return false;
@ -1011,18 +981,13 @@ export default class Component {
if (v.export_name) return false; if (v.export_name) return false;
if (this.var_lookup.get(name).reassigned) return false; if (this.var_lookup.get(name).reassigned) return false;
if ( if (this.vars.find((variable) => variable.name === name && variable.module)) return false;
this.vars.find(
variable => variable.name === name && variable.module
)
)
return false;
return true; return true;
}); });
if (all_hoistable) { if (all_hoistable) {
node.declarations.forEach(d => { node.declarations.forEach((d) => {
const variable = this.var_lookup.get((d.id as Identifier).name); const variable = this.var_lookup.get((d.id as Identifier).name);
variable.hoistable = true; variable.hoistable = true;
}); });
@ -1050,7 +1015,7 @@ export default class Component {
const checked = new Set(); const checked = new Set();
const walking = new Set(); const walking = new Set();
const is_hoistable = fn_declaration => { const is_hoistable = (fn_declaration) => {
if (fn_declaration.type === 'ExportNamedDeclaration') { if (fn_declaration.type === 'ExportNamedDeclaration') {
fn_declaration = fn_declaration.declaration; fn_declaration = fn_declaration.declaration;
} }
@ -1090,9 +1055,7 @@ export default class Component {
if (variable.hoistable) return; if (variable.hoistable) return;
if (top_level_function_declarations.has(name)) { if (top_level_function_declarations.has(name)) {
const other_declaration = top_level_function_declarations.get( const other_declaration = top_level_function_declarations.get(name);
name
);
if (walking.has(other_declaration)) { if (walking.has(other_declaration)) {
hoistable = false; hoistable = false;
@ -1152,7 +1115,7 @@ export default class Component {
const unsorted_reactive_declarations = []; const unsorted_reactive_declarations = [];
this.ast.instance.content.body.forEach(node => { this.ast.instance.content.body.forEach((node) => {
if (node.type === 'LabeledStatement' && node.label.name === '$') { if (node.type === 'LabeledStatement' && node.label.name === '$') {
this.reactive_declaration_nodes.add(node); this.reactive_declaration_nodes.add(node);
@ -1172,7 +1135,7 @@ export default class Component {
if (node.type === 'AssignmentExpression') { if (node.type === 'AssignmentExpression') {
const left = get_object(node.left); const left = get_object(node.left);
extract_identifiers(left).forEach(node => { extract_identifiers(left).forEach((node) => {
assignee_nodes.add(node); assignee_nodes.add(node);
assignees.add(node.name); assignees.add(node.name);
}); });
@ -1190,12 +1153,8 @@ export default class Component {
const owner = scope.find_owner(name); const owner = scope.find_owner(name);
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
if (variable) variable.is_reactive_dependency = true; if (variable) variable.is_reactive_dependency = true;
const is_writable_or_mutated = const is_writable_or_mutated = variable && (variable.writable || variable.mutated);
variable && (variable.writable || variable.mutated); if ((!owner || owner === component.instance_scope) && (name[0] === '$' || is_writable_or_mutated)) {
if (
(!owner || owner === component.instance_scope) &&
(name[0] === '$' || is_writable_or_mutated)
) {
dependencies.add(name); dependencies.add(name);
} }
} }
@ -1225,8 +1184,8 @@ export default class Component {
const lookup = new Map(); const lookup = new Map();
unsorted_reactive_declarations.forEach(declaration => { unsorted_reactive_declarations.forEach((declaration) => {
declaration.assignees.forEach(name => { declaration.assignees.forEach((name) => {
if (!lookup.has(name)) { if (!lookup.has(name)) {
lookup.set(name, []); lookup.set(name, []);
} }
@ -1237,34 +1196,35 @@ export default class Component {
}); });
}); });
const cycle = check_graph_for_cycles(unsorted_reactive_declarations.reduce((acc, declaration) => { const cycle = check_graph_for_cycles(
declaration.assignees.forEach(v => { unsorted_reactive_declarations.reduce((acc, declaration) => {
declaration.dependencies.forEach(w => { declaration.assignees.forEach((v) => {
if (!declaration.assignees.has(w)) { declaration.dependencies.forEach((w) => {
acc.push([v, w]); if (!declaration.assignees.has(w)) {
} acc.push([v, w]);
}
});
}); });
}); return acc;
return acc; }, [])
}, [])); );
if (cycle && cycle.length) { if (cycle && cycle.length) {
const declarationList = lookup.get(cycle[0]); const declarationList = lookup.get(cycle[0]);
const declaration = declarationList[0]; const declaration = declarationList[0];
this.error(declaration.node, { this.error(declaration.node, {
code: 'cyclical-reactive-declaration', code: 'cyclical-reactive-declaration',
message: `Cyclical dependency detected: ${cycle.join(' → ')}` message: `Cyclical dependency detected: ${cycle.join(' → ')}`,
}); });
} }
const add_declaration = declaration => { const add_declaration = (declaration) => {
if (this.reactive_declarations.includes(declaration)) return; if (this.reactive_declarations.includes(declaration)) return;
declaration.dependencies.forEach(name => { declaration.dependencies.forEach((name) => {
if (declaration.assignees.has(name)) return; if (declaration.assignees.has(name)) return;
const earlier_declarations = lookup.get(name); const earlier_declarations = lookup.get(name);
if (earlier_declarations) if (earlier_declarations) earlier_declarations.forEach(add_declaration);
earlier_declarations.forEach(add_declaration);
}); });
this.reactive_declarations.push(declaration); this.reactive_declarations.push(declaration);
@ -1275,10 +1235,10 @@ export default class Component {
warn_if_undefined(name: string, node, template_scope: TemplateScope) { warn_if_undefined(name: string, node, template_scope: TemplateScope) {
if (name[0] === '$') { if (name[0] === '$') {
if (name === '$' || name[1] === '$' && !is_reserved_keyword(name)) { if (name === '$' || (name[1] === '$' && !is_reserved_keyword(name))) {
this.error(node, { this.error(node, {
code: 'illegal-global', code: 'illegal-global',
message: `${name} is an illegal variable name` message: `${name} is an illegal variable name`,
}); });
} }
@ -1294,8 +1254,7 @@ export default class Component {
if (globals.has(name) && node.type !== 'InlineComponent') return; if (globals.has(name) && node.type !== 'InlineComponent') return;
let message = `'${name}' is not defined`; let message = `'${name}' is not defined`;
if (!this.ast.instance) if (!this.ast.instance) message += `. Consider adding a <script> block with 'export let ${name}' to declare a prop`;
message += `. Consider adding a <script> block with 'export let ${name}' to declare a prop`;
this.warn(node, { this.warn(node, {
code: 'missing-declaration', code: 'missing-declaration',
@ -1325,7 +1284,7 @@ function process_component_options(component: Component, nodes) {
preserveWhitespace: !!component.compile_options.preserveWhitespace, preserveWhitespace: !!component.compile_options.preserveWhitespace,
}; };
const node = nodes.find(node => node.name === 'svelte:options'); const node = nodes.find((node) => node.name === 'svelte:options');
function get_value(attribute, code, message) { function get_value(attribute, code, message) {
const { value } = attribute; const { value } = attribute;
@ -1347,7 +1306,7 @@ function process_component_options(component: Component, nodes) {
} }
if (node) { if (node) {
node.attributes.forEach(attribute => { node.attributes.forEach((attribute) => {
if (attribute.type === 'Attribute') { if (attribute.type === 'Attribute') {
const { name } = attribute; const { name } = attribute;
@ -1357,8 +1316,7 @@ function process_component_options(component: Component, nodes) {
const message = `'tag' must be a string literal`; const message = `'tag' must be a string literal`;
const tag = get_value(attribute, code, message); const tag = get_value(attribute, code, message);
if (typeof tag !== 'string' && tag !== null) if (typeof tag !== 'string' && tag !== null) component.error(attribute, { code, message });
component.error(attribute, { code, message });
if (tag && !/^[a-zA-Z][a-zA-Z0-9]*-[a-zA-Z0-9-]+$/.test(tag)) { if (tag && !/^[a-zA-Z][a-zA-Z0-9]*-[a-zA-Z0-9-]+$/.test(tag)) {
component.error(attribute, { component.error(attribute, {
@ -1370,7 +1328,7 @@ function process_component_options(component: Component, nodes) {
if (tag && !component.compile_options.customElement) { if (tag && !component.compile_options.customElement) {
component.warn(attribute, { component.warn(attribute, {
code: 'missing-custom-element-compile-options', code: 'missing-custom-element-compile-options',
message: `The 'tag' option is used when generating a custom element. Did you forget the 'customElement: true' compile option?` message: `The 'tag' option is used when generating a custom element. Did you forget the 'customElement: true' compile option?`,
}); });
} }
@ -1383,8 +1341,7 @@ function process_component_options(component: Component, nodes) {
const message = `The 'namespace' attribute must be a string literal representing a valid namespace`; const message = `The 'namespace' attribute must be a string literal representing a valid namespace`;
const ns = get_value(attribute, code, message); const ns = get_value(attribute, code, message);
if (typeof ns !== 'string') if (typeof ns !== 'string') component.error(attribute, { code, message });
component.error(attribute, { code, message });
if (valid_namespaces.indexOf(ns) === -1) { if (valid_namespaces.indexOf(ns) === -1) {
const match = fuzzymatch(ns, valid_namespaces); const match = fuzzymatch(ns, valid_namespaces);
@ -1412,8 +1369,7 @@ function process_component_options(component: Component, nodes) {
const message = `${name} attribute must be true or false`; const message = `${name} attribute must be true or false`;
const value = get_value(attribute, code, message); const value = get_value(attribute, code, message);
if (typeof value !== 'boolean') if (typeof value !== 'boolean') component.error(attribute, { code, message });
component.error(attribute, { code, message });
component_options[name] = value; component_options[name] = value;
break; break;

@ -23,48 +23,51 @@ export default function create_module(
) { ) {
const internal_path = `${sveltePath}/internal`; const internal_path = `${sveltePath}/internal`;
helpers.sort((a, b) => (a.name < b.name) ? -1 : 1); helpers.sort((a, b) => (a.name < b.name ? -1 : 1));
globals.sort((a, b) => (a.name < b.name) ? -1 : 1); globals.sort((a, b) => (a.name < b.name ? -1 : 1));
if (format === 'esm') { if (format === 'esm') {
return esm(program, name, banner, sveltePath, internal_path, helpers, globals, imports, module_exports); return esm(program, name, banner, sveltePath, internal_path, helpers, globals, imports, module_exports);
} }
if (format === 'cjs') return cjs(program, name, banner, sveltePath, internal_path, helpers, globals, imports, module_exports); if (format === 'cjs')
return cjs(program, name, banner, sveltePath, internal_path, helpers, globals, imports, module_exports);
throw new Error(`options.format is invalid (must be ${list(Object.keys(wrappers))})`); throw new Error(`options.format is invalid (must be ${list(Object.keys(wrappers))})`);
} }
function edit_source(source, sveltePath) { function edit_source(source, sveltePath) {
return source === 'svelte' || source.startsWith('svelte/') return source === 'svelte' || source.startsWith('svelte/') ? source.replace('svelte', sveltePath) : source;
? source.replace('svelte', sveltePath)
: source;
} }
function get_internal_globals( function get_internal_globals(
globals: Array<{ name: string; alias: Identifier }>, globals: Array<{ name: string; alias: Identifier }>,
helpers: Array<{ name: string; alias: Identifier }> helpers: Array<{ name: string; alias: Identifier }>
) { ) {
return globals.length > 0 && { return (
type: 'VariableDeclaration', globals.length > 0 && {
kind: 'const', type: 'VariableDeclaration',
declarations: [{ kind: 'const',
type: 'VariableDeclarator', declarations: [
id: { {
type: 'ObjectPattern', type: 'VariableDeclarator',
properties: globals.map(g => ({ id: {
type: 'Property', type: 'ObjectPattern',
method: false, properties: globals.map((g) => ({
shorthand: false, type: 'Property',
computed: false, method: false,
key: { type: 'Identifier', name: g.name }, shorthand: false,
value: g.alias, computed: false,
kind: 'init' key: { type: 'Identifier', name: g.name },
})) value: g.alias,
}, kind: 'init',
init: helpers.find(({ name }) => name === 'globals').alias })),
}] },
}; init: helpers.find(({ name }) => name === 'globals').alias,
},
],
}
);
} }
function esm( function esm(
@ -80,28 +83,28 @@ function esm(
) { ) {
const import_declaration = { const import_declaration = {
type: 'ImportDeclaration', type: 'ImportDeclaration',
specifiers: helpers.map(h => ({ specifiers: helpers.map((h) => ({
type: 'ImportSpecifier', type: 'ImportSpecifier',
local: h.alias, local: h.alias,
imported: { type: 'Identifier', name: h.name } imported: { type: 'Identifier', name: h.name },
})), })),
source: { type: 'Literal', value: internal_path } source: { type: 'Literal', value: internal_path },
}; };
const internal_globals = get_internal_globals(globals, helpers); const internal_globals = get_internal_globals(globals, helpers);
// edit user imports // edit user imports
imports.forEach(node => { imports.forEach((node) => {
node.source.value = edit_source(node.source.value, sveltePath); node.source.value = edit_source(node.source.value, sveltePath);
}); });
const exports = module_exports.length > 0 && { const exports = module_exports.length > 0 && {
type: 'ExportNamedDeclaration', type: 'ExportNamedDeclaration',
specifiers: module_exports.map(x => ({ specifiers: module_exports.map((x) => ({
type: 'Specifier', type: 'Specifier',
local: { type: 'Identifier', name: x.name }, local: { type: 'Identifier', name: x.name },
exported: { type: 'Identifier', name: x.as } exported: { type: 'Identifier', name: x.as },
})) })),
}; };
program.body = b` program.body = b`
@ -132,27 +135,29 @@ function cjs(
const internal_requires = { const internal_requires = {
type: 'VariableDeclaration', type: 'VariableDeclaration',
kind: 'const', kind: 'const',
declarations: [{ declarations: [
type: 'VariableDeclarator', {
id: { type: 'VariableDeclarator',
type: 'ObjectPattern', id: {
properties: helpers.map(h => ({ type: 'ObjectPattern',
type: 'Property', properties: helpers.map((h) => ({
method: false, type: 'Property',
shorthand: false, method: false,
computed: false, shorthand: false,
key: { type: 'Identifier', name: h.name }, computed: false,
value: h.alias, key: { type: 'Identifier', name: h.name },
kind: 'init' value: h.alias,
})) kind: 'init',
})),
},
init: x`require("${internal_path}")`,
}, },
init: x`require("${internal_path}")` ],
}]
}; };
const internal_globals = get_internal_globals(globals, helpers); const internal_globals = get_internal_globals(globals, helpers);
const user_requires = imports.map(node => { const user_requires = imports.map((node) => {
const init = x`require("${edit_source(node.source.value, sveltePath)}")`; const init = x`require("${edit_source(node.source.value, sveltePath)}")`;
if (node.specifiers.length === 0) { if (node.specifiers.length === 0) {
return b`${init};`; return b`${init};`;
@ -160,28 +165,33 @@ function cjs(
return { return {
type: 'VariableDeclaration', type: 'VariableDeclaration',
kind: 'const', kind: 'const',
declarations: [{ declarations: [
type: 'VariableDeclarator', {
id: node.specifiers[0].type === 'ImportNamespaceSpecifier' type: 'VariableDeclarator',
? { type: 'Identifier', name: node.specifiers[0].local.name } id:
: { node.specifiers[0].type === 'ImportNamespaceSpecifier'
type: 'ObjectPattern', ? { type: 'Identifier', name: node.specifiers[0].local.name }
properties: node.specifiers.map(s => ({ : {
type: 'Property', type: 'ObjectPattern',
method: false, properties: node.specifiers.map((s) => ({
shorthand: false, type: 'Property',
computed: false, method: false,
key: s.type === 'ImportSpecifier' ? s.imported : { type: 'Identifier', name: 'default' }, shorthand: false,
value: s.local, computed: false,
kind: 'init' key: s.type === 'ImportSpecifier' ? s.imported : { type: 'Identifier', name: 'default' },
})) value: s.local,
}, kind: 'init',
init })),
}] },
init,
},
],
}; };
}); });
const exports = module_exports.map(x => b`exports.${{ type: 'Identifier', name: x.as }} = ${{ type: 'Identifier', name: x.name }};`); const exports = module_exports.map(
(x) => b`exports.${{ type: 'Identifier', name: x.as }} = ${{ type: 'Identifier', name: x.name }};`
);
program.body = b` program.body = b`
/* ${banner} */ /* ${banner} */

@ -228,7 +228,7 @@ export default class Block {
} }
get_contents(key?: any) { get_contents(key?: any) {
const { dev } = this.renderer.options; const { dev,version } = this.renderer.options;
if (this.has_outros) { if (this.has_outros) {
this.add_variable({ type: 'Identifier', name: '#current' }); this.add_variable({ type: 'Identifier', name: '#current' });
@ -403,13 +403,13 @@ export default class Block {
${dev ${dev
? b` ? b`
const ${block} = ${return_value}; const ${block} = ${return_value};
@dispatch_dev("SvelteRegisterBlock", { ${version < 3.22 && b`@dispatch_dev$legacy("SvelteRegisterBlock", {
block: ${block}, block: ${block},
id: ${this.name || 'create_fragment'}.name, id: ${this.name || 'create_fragment'}.name,
type: "${this.type}", type: "${this.type}",
source: "${this.comment ? this.comment.replace(/"/g, '\\"') : ''}", source: "${this.comment ? this.comment.replace(/"/g, '\\"') : ''}",
ctx: #ctx ctx: #ctx
}); });`}
return ${block};` return ${block};`
: b` : b`
return ${return_value};` return ${return_value};`

@ -529,7 +529,7 @@ export default function dom(
super(${options.dev && `options`}); super(${options.dev && `options`});
${should_add_css && b`if (!@_document.getElementById("${component.stylesheet.id}-style")) ${add_css}();`} ${should_add_css && b`if (!@_document.getElementById("${component.stylesheet.id}-style")) ${add_css}();`}
@init(this, options, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_indexes}, ${dirty}); @init(this, options, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_indexes}, ${dirty});
${options.dev && b`@dispatch_dev("SvelteRegisterComponent", { component: this, tagName: "${name.name}", options, id: create_fragment.name });`} ${options.dev && options.version < 3.22 && b`@dispatch_dev$legacy("SvelteRegisterComponent", { component: this, tagName: "${name.name}", options, id: create_fragment.name });`}
${dev_props_check} ${dev_props_check}
} }

@ -886,10 +886,6 @@ function run_animation(element: ElementWrapper, block: Block) {
]; ];
} }
function run_transition(element: ElementWrapper, block: Block, transition: Transition, type: string) { function run_transition(element: ElementWrapper, block: Block, transition: Transition, type: string) {
// const run = b`
// 0 = @run_transition(1, `2`, ${
// type === 'intro' ? `true` : `false`
// }, ${params});
return [ return [
/* node_intro */ block.alias(`${element.var.name}_${type}`), /* node_intro */ block.alias(`${element.var.name}_${type}`),
/* node */ element.var, /* node */ element.var,

@ -1,6 +1,8 @@
import '../ambient';
export { default as compile } from './compile/index'; export { default as compile } from './compile/index';
export { default as parse } from './parse/index'; export { default as parse } from './parse/index';
export { default as preprocess } from './preprocess/index'; export { default as preprocess } from './preprocess/index';
export { walk } from 'estree-walker'; export { walk } from 'estree-walker';
export const VERSION = '__VERSION__'; export const version = __VERSION__;

@ -1,4 +1,4 @@
import { Node, Program } from "estree"; import { Node, Program } from 'estree';
import { SourceMap } from 'magic-string'; import { SourceMap } from 'magic-string';
interface BaseNode { interface BaseNode {
@ -24,14 +24,15 @@ export interface MustacheTag extends BaseNode {
expression: Node; expression: Node;
} }
export type DirectiveType = 'Action' export type DirectiveType =
| 'Animation' | 'Action'
| 'Binding' | 'Animation'
| 'Class' | 'Binding'
| 'EventHandler' | 'Class'
| 'Let' | 'EventHandler'
| 'Ref' | 'Let'
| 'Transition'; | 'Ref'
| 'Transition';
interface BaseDirective extends BaseNode { interface BaseDirective extends BaseNode {
type: DirectiveType; type: DirectiveType;
@ -40,7 +41,7 @@ interface BaseDirective extends BaseNode {
modifiers: string[]; modifiers: string[];
} }
export interface Transition extends BaseDirective{ export interface Transition extends BaseDirective {
type: 'Transition'; type: 'Transition';
intro: boolean; intro: boolean;
outro: boolean; outro: boolean;
@ -48,11 +49,7 @@ export interface Transition extends BaseDirective{
export type Directive = BaseDirective | Transition; export type Directive = BaseDirective | Transition;
export type TemplateNode = Text export type TemplateNode = Text | MustacheTag | BaseNode | Directive | Transition;
| MustacheTag
| BaseNode
| Directive
| Transition;
export interface Parser { export interface Parser {
readonly template: string; readonly template: string;
@ -109,6 +106,7 @@ export interface CompileOptions {
name?: string; name?: string;
filename?: string; filename?: string;
generate?: string | false; generate?: string | false;
version?: number;
outputFilename?: string; outputFilename?: string;
cssOutputFilename?: string; cssOutputFilename?: string;
@ -150,8 +148,8 @@ export interface Var {
module?: boolean; module?: boolean;
mutated?: boolean; mutated?: boolean;
reassigned?: boolean; reassigned?: boolean;
referenced?: boolean; // referenced from template scope referenced?: boolean; // referenced from template scope
referenced_from_script?: boolean; // referenced from script referenced_from_script?: boolean; // referenced from script
writable?: boolean; writable?: boolean;
// used internally, but not exposed // used internally, but not exposed

@ -47,12 +47,15 @@ export const circOut = (t: number) => Math.sin(Math.acos(1 - t));
export const circInOut = (t: number) => export const circInOut = (t: number) =>
0.5 * (t >= 0.5 ? 2.0 - Math.sin(Math.acos(1.0 - 2.0 * (1.0 - t))) : Math.sin(Math.acos(1.0 - 2.0 * t))); 0.5 * (t >= 0.5 ? 2.0 - Math.sin(Math.acos(1.0 - 2.0 * (1.0 - t))) : Math.sin(Math.acos(1.0 - 2.0 * t)));
export const cubicBezier = (x1: number, y1: number, x2: number, y2: number) => { export const cubicBezier = (x1: number, y1: number, x2: number, y2: number) => {
if (__DEV__)
if (0 > x1 || x1 < 1 || 0 > x2 || x2 > 1)
throw new Error(`CubicBezier x1 & x2 values must be { 0 < x < 1 }, got { x1 : ${x1}, x2: ${x2} }`);
const ax = 1.0 - (x2 = 3.0 * (x2 - x1) - (x1 = 3.0 * x1)) - x1, const ax = 1.0 - (x2 = 3.0 * (x2 - x1) - (x1 = 3.0 * x1)) - x1,
ay = 1.0 - (y2 = 3.0 * (y2 - y1) - (y1 = 3.0 * y1)) - y1; ay = 1.0 - (y2 = 3.0 * (y2 - y1) - (y1 = 3.0 * y1)) - y1;
let r = Number.NaN, let r = 0.0,
s = Number.NaN, s = 0.0,
d = Number.NaN, d = 0.0,
x = Number.NaN; x = 0.0;
return (t: number) => { return (t: number) => {
r = t; r = t;
for (let i = 0; 32 > i; i++) for (let i = 0; 32 > i; i++)

@ -1,4 +1,5 @@
import './ambient'; import './ambient';
import '../ambient';
export { export {
onMount, onMount,
@ -9,5 +10,5 @@ export {
getContext, getContext,
tick, tick,
createEventDispatcher, createEventDispatcher,
SvelteComponentDev as SvelteComponent SvelteComponentDev as SvelteComponent,
} from 'svelte/internal'; } from 'svelte/internal';

@ -157,9 +157,9 @@ export function init(
set_current_component(parent_component); set_current_component(parent_component);
} }
export let SvelteElement; export const SvelteElement =
if (typeof HTMLElement === 'function') { typeof HTMLElement === 'function' &&
SvelteElement = class extends HTMLElement { class extends HTMLElement {
$$: T$$; $$: T$$;
constructor() { constructor() {
super(); super();
@ -196,7 +196,6 @@ if (typeof HTMLElement === 'function') {
// overridden by instance, if it has props // overridden by instance, if it has props
$set() {} $set() {}
}; };
}
export class SvelteComponent { export class SvelteComponent {
$$: T$$; $$: T$$;

@ -0,0 +1,90 @@
import { custom_event, append, insert, detach, listen, attr } from './dom';
let inited
export function dispatch_dev$legacy<T = any>(type: string, detail?: T) {
if(!inited && `__SVELTE_DEVTOOLS_GLOBAL_HOOK__` in window) {
inited = true
throw new Error(`You must specify the version`)
}
document.dispatchEvent(custom_event(type, { version: __VERSION__, ...detail }));
}
export function append_dev$legacy(target: Node, node: Node) {
dispatch_dev$legacy('SvelteDOMInsert', { target, node });
append(target, node);
}
export function insert_dev$legacy(target: Node, node: Node, anchor?: Node) {
dispatch_dev$legacy('SvelteDOMInsert', { target, node, anchor });
insert(target, node, anchor);
}
export function detach_dev$legacy(node: Node) {
dispatch_dev$legacy('SvelteDOMRemove', { node });
detach(node);
}
export function detach_between_dev$legacy(before: Node, after: Node) {
while (before.nextSibling && before.nextSibling !== after) {
detach_dev$legacy(before.nextSibling);
}
}
export function detach_before_dev$legacy(after: Node) {
while (after.previousSibling) {
detach_dev$legacy(after.previousSibling);
}
}
export function detach_after_dev$legacy(before: Node) {
while (before.nextSibling) {
detach_dev$legacy(before.nextSibling);
}
}
export function listen_dev$legacy(
node: Node,
event: string,
handler: EventListenerOrEventListenerObject,
options?: boolean | AddEventListenerOptions | EventListenerOptions,
has_prevent_default?: boolean,
has_stop_propagation?: boolean
) {
const modifiers = options === true ? ['capture'] : options ? Array.from(Object.keys(options)) : [];
if (has_prevent_default) modifiers.push('preventDefault');
if (has_stop_propagation) modifiers.push('stopPropagation');
dispatch_dev$legacy('SvelteDOMAddEventListener', { node, event, handler, modifiers });
const dispose = listen(node, event, handler, options);
return () => {
dispatch_dev$legacy('SvelteDOMRemoveEventListener', { node, event, handler, modifiers });
dispose();
};
}
export function attr_dev$legacy(node: Element, attribute: string, value?: string) {
attr(node, attribute, value);
if (value == null) dispatch_dev$legacy('SvelteDOMRemoveAttribute', { node, attribute });
else dispatch_dev$legacy('SvelteDOMSetAttribute', { node, attribute, value });
}
export function prop_dev$legacy(node: Element, property: string, value?: any) {
node[property] = value;
dispatch_dev$legacy('SvelteDOMSetProperty', { node, property, value });
}
export function dataset_dev$legacy(node: HTMLElement, property: string, value?: any) {
node.dataset[property] = value;
dispatch_dev$legacy('SvelteDOMSetDataset', { node, property, value });
}
export function set_data_dev$legacy(text, data) {
data = '' + data;
if (text.data === data) return;
dispatch_dev$legacy('SvelteDOMSetData', { node: text, data });
text.data = data;
}

@ -1,4 +1,4 @@
import { custom_event, append, insert, detach, listen, attr } from './dom'; import { custom_event, insert, detach, attr } from './dom';
import { SvelteComponent } from './Component'; import { SvelteComponent } from './Component';
import { import {
get_current_component, get_current_component,
@ -10,7 +10,7 @@ import {
setContext, setContext,
getContext, getContext,
} from './lifecycle'; } from './lifecycle';
import { cubicBezier } from 'svelte/easing'; import { writable } from 'svelte/store';
export const [ export const [
beforeUpdate_dev, beforeUpdate_dev,
@ -26,17 +26,73 @@ export const [
return fn(a, b); return fn(a, b);
} }
); );
export function cubicBezier_dev(mX1, mY1, mX2, mY2) {
if (!(0 <= mX1 && mX1 <= 1 && 0 <= mX2 && mX2 <= 1)) throw new Error('CubicBezier x values must be { 0 < x < 1 }'); const dev$hook =
return cubicBezier(mX1, mY1, mX2, mY2); __DEV__ &&
!__TEST__ &&
typeof window !== 'undefined' &&
(() => {
const subscribers = [];
function subscribe(listener) {
subscribers.push(listener);
return () => subscribers.splice(subscribers.indexOf(listener), 1);
}
const components = [];
const hook = writable(components);
Object.defineProperty(window, `__SVELTE_DEVTOOLS_GLOBAL_HOOK__`, {
enumerable: false,
get() {
return {
version: __VERSION__,
components,
};
},
});
return function update(type, value) {
subscribers.forEach((listener) => {
listener(type, value);
});
};
})();
export function dev$dispatch(type: string, value: any) {
dev$hook.update((o) => {
return o;
});
}
export function dev$element(element: Element, event: keyof ElementEventsMap, payload?: any) {
if (__DEV__) {
}
} }
export function dispatch_dev<T = any>(type: string, detail?: T) { export function dev$block(event: keyof BlockEventsMap, payload) {}
document.dispatchEvent(custom_event(type, { version: '__VERSION__', ...detail })); export function dev$tracing(type, value: any) {}
export function dev$assert(truthy: boolean, else_throw: string) {
if (__DEV__ && !truthy) {
throw new Error(else_throw);
}
}
interface SvelteDevEvent extends CustomEvent {
__VERSION__: string;
}
interface ElementEventsMap {
mount;
unmount;
setAttribute;
removeAttribute;
addEventListener;
removeEventListener;
addClass;
removeClass;
} }
interface BlockEventsMap {
'create';
'claim';
'claim.failed';
}
interface SvelteDevErrorsMap {}
export interface SvelteDOM {}
export function append_dev(target: Node, node: Node) { export function dispatch_dev<T = any>(type: string, detail?: T) {
dispatch_dev('SvelteDOMInsert', { target, node }); document.dispatchEvent(custom_event(type, { version: '__VERSION__', ...detail }));
append(target, node);
} }
export function insert_dev(target: Node, node: Node, anchor?: Node) { export function insert_dev(target: Node, node: Node, anchor?: Node) {
@ -67,27 +123,6 @@ export function detach_after_dev(before: Node) {
} }
} }
export function listen_dev(
node: Node,
event: string,
handler: EventListenerOrEventListenerObject,
options?: boolean | AddEventListenerOptions | EventListenerOptions,
has_prevent_default?: boolean,
has_stop_propagation?: boolean
) {
const modifiers = options === true ? ['capture'] : options ? Array.from(Object.keys(options)) : [];
if (has_prevent_default) modifiers.push('preventDefault');
if (has_stop_propagation) modifiers.push('stopPropagation');
dispatch_dev('SvelteDOMAddEventListener', { node, event, handler, modifiers });
const dispose = listen(node, event, handler, options);
return () => {
dispatch_dev('SvelteDOMRemoveEventListener', { node, event, handler, modifiers });
dispose();
};
}
export function attr_dev(node: Element, attribute: string, value?: string) { export function attr_dev(node: Element, attribute: string, value?: string) {
attr(node, attribute, value); attr(node, attribute, value);

@ -1,14 +1,18 @@
import { has_prop } from './utils'; import { has_prop } from './utils';
import { dispatch_dev, dev$dispatch, dev$element, dev$block } from './dev';
export function append(target: Node, node: Node) { export function append(target: Node, node: Node) {
dev$element(node, `mount`, { target });
target.appendChild(node); target.appendChild(node);
} }
export function insert(target: Node, node: Node, anchor?: Node) { export function insert(target: Node, node: Node, anchor?: Node) {
dev$element(node, `mount`, { target, anchor });
target.insertBefore(node, anchor || null); target.insertBefore(node, anchor || null);
} }
export function detach(node: Node) { export function detach(node: Node) {
dev$element(node, `unmount`);
node.parentNode.removeChild(node); node.parentNode.removeChild(node);
} }
@ -17,12 +21,14 @@ export function destroy_each(iterations, detaching) {
if (iterations[i]) iterations[i].d(detaching); if (iterations[i]) iterations[i].d(detaching);
} }
} }
const dev$create = <K>(elem: K) => (dev$block(`create`, elem), elem);
export function element<K extends keyof HTMLElementTagNameMap>(name: K) { export function element<K extends keyof HTMLElementTagNameMap>(name: K) {
if (__DEV__) return dev$create(document.createElement<K>(name));
return document.createElement<K>(name); return document.createElement<K>(name);
} }
export function element_is<K extends keyof HTMLElementTagNameMap>(name: K, is: string) { export function element_is<K extends keyof HTMLElementTagNameMap>(name: K, is: string) {
if (__DEV__) return dev$create(document.createElement<K>(name));
return document.createElement<K>(name, { is }); return document.createElement<K>(name, { is });
} }
export function object_without_properties<T, K extends keyof T>(obj: T, exclude: K[]) { export function object_without_properties<T, K extends keyof T>(obj: T, exclude: K[]) {
@ -41,10 +47,12 @@ export function object_without_properties<T, K extends keyof T>(obj: T, exclude:
} }
export function svg_element<K extends keyof SVGElementTagNameMap>(name: K): SVGElement { export function svg_element<K extends keyof SVGElementTagNameMap>(name: K): SVGElement {
if (__DEV__) return dev$create(document.createElementNS<K>('http://www.w3.org/2000/svg', name));
return document.createElementNS<K>('http://www.w3.org/2000/svg', name); return document.createElementNS<K>('http://www.w3.org/2000/svg', name);
} }
export function text(data: string) { export function text(data: string) {
if (__DEV__) return dev$create(document.createTextNode(data));
return document.createTextNode(data); return document.createTextNode(data);
} }
@ -55,17 +63,25 @@ export function space() {
export function empty() { export function empty() {
return text(''); return text('');
} }
export function listen( export function listen(
node: EventTarget, node: EventTarget,
event: string, event: string,
handler: EventListenerOrEventListenerObject, handler: EventListenerOrEventListenerObject,
options?: boolean | AddEventListenerOptions | EventListenerOptions options?: boolean | AddEventListenerOptions | EventListenerOptions
) { ) {
if (__DEV__) {
const reference = { event, handler };
dev$element(node, `addEventListener`, reference);
node.addEventListener(event, handler, options);
return () => {
dev$element(node, `removeEventListener`, reference);
node.removeEventListener(event, handler, options);
};
}
node.addEventListener(event, handler, options); node.addEventListener(event, handler, options);
return () => node.removeEventListener(event, handler, options); return () => node.removeEventListener(event, handler, options);
} }
// todo inline at compile time
export function prevent_default(fn) { export function prevent_default(fn) {
return function (event) { return function (event) {
event.preventDefault(); event.preventDefault();
@ -73,15 +89,15 @@ export function prevent_default(fn) {
return fn.call(this, event); return fn.call(this, event);
}; };
} }
// todo inline at compile time
export function stop_propagation(fn) { export function stop_propagation(fn): EventListenerOrEventListenerObject {
return function (event) { return function (event) {
event.stopPropagation(); event.stopPropagation();
// @ts-ignore //@ts-ignore
return fn.call(this, event); return fn.call(this, event);
}; };
} }
// todo inline at compile time
export function self(fn) { export function self(fn) {
return function (event) { return function (event) {
// @ts-ignore // @ts-ignore
@ -89,48 +105,60 @@ export function self(fn) {
}; };
} }
export function attr(node: Element, attribute: string, value?: string) { export function attr(node: Element, name: string, value?: any) {
if (value == null) node.removeAttribute(attribute); if (value == null) {
else if (node.getAttribute(attribute) !== value) node.setAttribute(attribute, value); dev$element(node, `removeAttribute`, { name });
node.removeAttribute(name);
} else if (node.getAttribute(name) !== value) {
dev$element(node, `setAttribute`, { name, value });
node.setAttribute(name, value);
}
} }
export function set_attributes(node: Element & ElementCSSInlineStyle, attributes: { [x: string]: string }) { export function set_attributes(node: HTMLElement, attributes: { [x: string]: any }) {
// @ts-ignore // @ts-ignore #3687
const descriptors = Object.getOwnPropertyDescriptors(node.__proto__); const descriptors = Object.getOwnPropertyDescriptors(node.__proto__);
for (const key in attributes) { let name;
if (attributes[key] == null) { for (name in attributes) {
node.removeAttribute(key); if (attributes[name] == null) {
} else if (key === 'style') { dev$element(node, `removeAttribute`, { name });
node.style.cssText = attributes[key]; node.removeAttribute(name);
} else if (key === '__value' || (descriptors[key] && descriptors[key].set)) { } else if (name === 'style') {
node[key] = attributes[key]; dev$element(node, `setAttribute`, { name, value: attributes[name] });
node.style.cssText = attributes[name];
} else if (name === '__value' || (descriptors[name] && descriptors[name].set)) {
dev$element(node, `setAttribute`, { name, value: attributes[name] });
node[name] = attributes[name];
} else { } else {
attr(node, key, attributes[key]); attr(node, name, attributes[name]);
} }
} }
} }
export function set_svg_attributes(node: Element & ElementCSSInlineStyle, attributes: { [x: string]: string }) { export function set_svg_attributes(node: SVGElement, attributes: { [x: string]: any }) {
for (const key in attributes) { let name;
attr(node, key, attributes[key]); for (name in attributes) {
attr(node, name, attributes[name]);
} }
} }
export function set_custom_element_data(node, prop, value) { export function set_custom_element_data(node, name, value) {
if (prop in node) { if (name in node) {
node[prop] = value; dev$element(node, `setAttribute`, { name, value });
node[name] = value;
} else { } else {
attr(node, prop, value); attr(node, name, value);
} }
} }
export function xlink_attr(node, attribute, value) { export function xlink_attr(node: Element, name, value) {
node.setAttributeNS('http://www.w3.org/1999/xlink', attribute, value); dev$element(node, `setAttribute`, { name, value });
node.setAttributeNS('http://www.w3.org/1999/xlink', name, value);
} }
export function get_binding_group_value(group) { export function get_binding_group_value(group) {
const value = []; const value = [];
for (let i = 0; i < group.length; i += 1) { for (let i = 0, value = []; i < group.length; i += 1) {
if (group[i].checked) value.push(group[i].__value); if (group[i].checked) value.push(group[i].__value);
} }
return value; return value;
@ -150,35 +178,28 @@ export function time_ranges_to_array(ranges) {
export const children = (element: HTMLElement) => Array.from(element.childNodes); export const children = (element: HTMLElement) => Array.from(element.childNodes);
export function claim_element(nodes, name, attributes, svg) { export function claim_element(nodes, name, attributes, is_svg) {
for (let i = 0; i < nodes.length; i += 1) { for (let i = 0, j = 0, n, a; i < nodes.length; i += 1, j = 0)
const node = nodes[i]; if ((n = nodes[i]).nodeName !== name) continue;
if (node.nodeName === name) { else {
let j = 0; while (j < n.attributes.length) {
while (j < node.attributes.length) { if (attributes[(a = n.attributes[j]).name]) j++;
const attribute = node.attributes[j]; else n.removeAttribute(a.name);
if (attributes[attribute.name]) {
j++;
} else {
node.removeAttribute(attribute.name);
}
} }
dev$block(`claim`, n);
return nodes.splice(i, 1)[0]; return nodes.splice(i, 1)[0];
} }
} dev$block(`claim.failed`, name);
return is_svg ? svg_element(name) : element(name);
return svg ? svg_element(name) : element(name);
} }
export function claim_text(nodes, data) { export function claim_text(nodes, data) {
for (let i = 0; i < nodes.length; i += 1) { for (let i = 0, n; i < nodes.length; i += 1)
const node = nodes[i]; if ((n = nodes[i]).nodeType === 3) {
if (node.nodeType === 3) { dev$block(`claim`, n);
node.data = '' + data; return (n.data = '' + data), nodes.splice(i, 1)[0];
return nodes.splice(i, 1)[0];
} }
} dev$block(`claim.failed`, 'text');
return text(data); return text(data);
} }
@ -187,49 +208,56 @@ export function claim_space(nodes) {
} }
export function set_data(text, data) { export function set_data(text, data) {
data = '' + data; if (text.data !== (data = '' + data)) {
if (text.data !== data) text.data = data; text.data = data;
dev$element(text, `setAttribute`, { name: 'data', value: data });
}
} }
export function set_input_value(input, value) { export function set_input_value(input, value) {
if (value != null || input.value) { if (value != null || input.value) {
input.value = value; input.value = value;
dev$element(input, `setAttribute`, { name: 'value', value });
} }
} }
export function set_input_type(input, type) { export function set_input_type(input, type) {
try { try {
input.type = type; input.type = type;
} catch (e) { dev$element(input, `setAttribute`, { name: 'type', value: type });
// do nothing } catch (e) {}
}
} }
export function set_style(node, key, value, important) { export function set_style(node, property, value, is_important?) {
node.style.setProperty(key, value, important ? 'important' : ''); dev$element(node, `setAttribute`, { name: 'style', property, value });
node.style.setProperty(property, value, is_important ? 'important' : '');
} }
export function select_option(select, value) { export function select_option(select, value) {
for (let i = 0; i < select.options.length; i += 1) { for (let i = 0, o; i < select.options.length; i += 1) {
const option = select.options[i]; if ((o = select.options[i]).__value === value) {
dev$element(o, `setAttribute`, { name: 'selected', value: true });
if (option.__value === value) { o.selected = true;
option.selected = true;
return; return;
} }
} }
} }
export function select_options(select, value) { export function select_options(select, value) {
for (let i = 0; i < select.options.length; i += 1) { for (let i = 0, o; i < select.options.length; i += 1) {
const option = select.options[i]; if (__DEV__) {
option.selected = ~value.indexOf(option.__value); dev$element((o = select.options[i]), `setAttribute`, {
name: 'selected',
value: (o = select.options[i]).selected = ~value.indexOf(o.__value),
});
continue;
}
(o = select.options[i]).selected = ~value.indexOf(o.__value);
} }
} }
export function select_value(select) { export function select_value(select) {
const selected_option = select.querySelector(':checked') || select.options[0]; return (select = select.querySelector(':checked') || select.options[0]) && select.__value;
return selected_option && selected_option.__value;
} }
export function select_multiple_value(select) { export function select_multiple_value(select) {
@ -261,17 +289,18 @@ export function add_resize_listener(node: HTMLElement, fn: () => void) {
const z_index = (parseInt(computed_style.zIndex) || 0) - 1; const z_index = (parseInt(computed_style.zIndex) || 0) - 1;
if (computed_style.position === 'static') { if (computed_style.position === 'static') {
node.style.position = 'relative'; set_style(node, 'position', 'relative');
} }
const iframe = element('iframe'); const iframe = element('iframe');
iframe.setAttribute( attr(
iframe,
'style', 'style',
`display: block; position: absolute; top: 0; left: 0; width: 100%; height: 100%; ` + `display: block; position: absolute; top: 0; left: 0; width: 100%; height: 100%; ` +
`overflow: hidden; border: 0; opacity: 0; pointer-events: none; z-index: ${z_index};` `overflow: hidden; border: 0; opacity: 0; pointer-events: none; z-index: ${z_index};`
); );
iframe.setAttribute('aria-hidden', 'true'); attr(iframe, 'aria-hidden', 'true');
iframe.tabIndex = -1; attr(iframe, 'tabIndex', -1);
let unsubscribe: () => void; let unsubscribe: () => void;
@ -296,13 +325,14 @@ export function add_resize_listener(node: HTMLElement, fn: () => void) {
} }
export function toggle_class(element, name, toggle) { export function toggle_class(element, name, toggle) {
dev$element(element, toggle ? 'addClass' : 'removeClass', { name });
element.classList[toggle ? 'add' : 'remove'](name); element.classList[toggle ? 'add' : 'remove'](name);
} }
export function custom_event<T = any>(type: string, detail?: T) { export function custom_event<T = any>(type: string, detail?: T) {
const e: CustomEvent<T> = document.createEvent('CustomEvent'); const event: CustomEvent<T> = document.createEvent('CustomEvent');
e.initCustomEvent(type, false, false, detail); event.initCustomEvent(type, false, false, detail);
return e; return event;
} }
export function query_selector_all(selector: string, parent: HTMLElement = document.body) { export function query_selector_all(selector: string, parent: HTMLElement = document.body) {

@ -3,7 +3,7 @@ import { noop } from './utils';
export const is_client = typeof window !== 'undefined'; export const is_client = typeof window !== 'undefined';
export const is_iframe = is_client && window.location !== window.parent.location; export const is_iframe = is_client && window.location !== window.parent.location;
export let now = is_client ? window.performance.now : () => Date.now(); export let now = is_client ?()=> performance.now() :()=> Date.now();
export let raf = is_client ? window.requestAnimationFrame : noop; export let raf = is_client ? window.requestAnimationFrame : noop;

@ -13,3 +13,4 @@ export * from './transitions';
export * from './utils'; export * from './utils';
export * from './Component'; export * from './Component';
export * from './dev'; export * from './dev';
export * from './stores'

@ -1,33 +1,41 @@
import { custom_event } from './dom'; import { custom_event } from './dom';
import { dev$assert, SvelteComponentDev } from './dev';
import { SvelteComponent } from './Component';
export let current_component; export let current_component: SvelteComponentDev | SvelteComponent | null;
export const set_current_component = (component) => (current_component = component); export const set_current_component = (component) => (current_component = component);
const dev$guard = (name: string) =>
dev$assert(!!current_component, `${name} cannot be called outside of component initialization`);
export function get_current_component() { export function get_current_component() {
if (!current_component) throw new Error(`Function called outside component initialization`);
return current_component; return current_component;
} }
export function beforeUpdate(fn) { export function beforeUpdate(fn) {
get_current_component().$$.before_update.push(fn); dev$guard(`beforeUpdate`);
return current_component.$$.before_update.push(fn);
} }
export function onMount(fn) { export function onMount(fn) {
get_current_component().$$.on_mount.push(fn); dev$guard(`onMount`);
return current_component.$$.on_mount.push(fn);
} }
export function afterUpdate(fn) { export function afterUpdate(fn) {
get_current_component().$$.after_update.push(fn); dev$guard(`afterUpdate`);
return current_component.$$.after_update.push(fn);
} }
export function onDestroy(fn) { export function onDestroy(fn) {
get_current_component().$$.on_destroy.push(fn); dev$guard(`onDestroy`);
return current_component.$$.on_destroy.push(fn);
} }
export function createEventDispatcher() { export function createEventDispatcher() {
const component = get_current_component(); dev$guard(`createEventDispatcher`);
const component = current_component;
return (type: string, detail?: any) => { return (type: string, detail?: any) => {
const callbacks = component.$$.callbacks[type]; const callbacks = component.$$.callbacks[type];
if (!callbacks) return; if (!callbacks) return;
@ -41,11 +49,13 @@ export function createEventDispatcher() {
} }
export function setContext(key, context) { export function setContext(key, context) {
get_current_component().$$.context.set(key, context); dev$guard(`setContext`);
current_component.$$.context.set(key, context);
} }
export function getContext(key) { export function getContext(key) {
return get_current_component().$$.context.get(key); dev$guard(`getContext`);
return current_component.$$.context.get(key);
} }
// TODO figure out if we still want to support // TODO figure out if we still want to support

@ -1,88 +1,108 @@
import { raf, now } from './environment'; import { now, raf } from './environment';
import { calc_framerate, FRAME_RATE } from './style_manager';
import { noop } from './utils'; import { noop } from './utils';
type TaskCallback = (t: number) => boolean;
type TaskCanceller = () => void;
export interface Task { let next_frame: Array<TaskCallback> = [];
abort(): void; let running_frame: Array<TaskCallback> = [];
promise: Promise<void>; function run(t: number) {
[running_frame, next_frame] = [next_frame, running_frame];
let next_frame_tasks = 0;
for (let i = 0, j = running_frame.length, v; i < j; i++) {
if ((v = running_frame[i])(t)) next_frame[next_frame_tasks++] = v;
}
running_frame.length = 0;
if (next_frame_tasks) raf(run);
} }
type TaskCallback = (now: number) => boolean | void;
type TaskEntry = { c: TaskCallback; f: () => void };
const tasks = new Set<TaskEntry>();
function run_tasks(now: number) { type TimeoutTask = { t: number; c: (now: number) => void };
tasks.forEach((task) => { const timed_tasks: Array<TimeoutTask> = [];
if (task.c(now)) return; const pending_insert_timed: Array<TimeoutTask> = [];
tasks.delete(task); let running_timed = false;
task.f(); let pending_inserts = false;
}); function run_timed(now: number) {
if (tasks.size) raf(run_tasks); /* Runs every timed out task */
let last_index = timed_tasks.length - 1;
while (last_index >= 0 && now >= timed_tasks[last_index].t) {
timed_tasks[last_index--].c(now);
}
if (pending_inserts) {
for (
let i = 0, j = last_index, this_task: TimeoutTask, that_task: TimeoutTask;
i < pending_insert_timed.length;
i++
) {
if (now >= (this_task = pending_insert_timed[i]).t) {
this_task.c(now);
} else {
for (j = last_index; j > 0 && this_task.t > (that_task = timed_tasks[j]).t; j--) {
/* move each task up until this_task.timestamp > task.timestamp */
timed_tasks[j + 1] = that_task;
}
timed_tasks[j] = this_task;
last_index++;
}
}
pending_inserts = !!(pending_insert_timed.length = 0);
}
return (running_timed = !!(timed_tasks.length = last_index + 1));
} }
export function setAnimationTimeout(callback: () => void, timestamp: number): TaskCanceller {
/** const task: TimeoutTask = { c: callback, t: timestamp };
* For testing purposes only! if (running_timed) {
*/ pending_inserts = !!pending_insert_timed.push(task);
export function clear_loops() { } else {
tasks.clear(); timed_tasks.push(task);
if (1 === next_frame.push(run_timed)) raf(run);
}
return () => void (task.c = noop);
} }
/** /**
* Creates a new task that runs on each raf frame * Calls function every frame with a value going from 0 to 1
* until it returns a falsy value or is aborted
*/ */
export function loop(callback: TaskCallback): Task { export function useTween(
let task: TaskEntry; run: (now: number) => void,
if (!tasks.size) raf(run_tasks); stop: () => void,
return { end_time: number,
promise: new Promise((fulfill) => { duration = end_time - now()
tasks.add((task = { c: callback, f: fulfill })); ): TaskCanceller {
}), let running = true;
abort() { if (
tasks.delete(task); 1 ===
}, next_frame.push((t) => {
}; if (!running) return false;
} t = (end_time - t) / duration;
if (t >= 1) return run(1), stop(), (running = false);
function add(c: TaskCallback) { if (t >= 0) run(t);
const task = { c, f: noop }; return running;
if (!tasks.size) raf(run_tasks); })
tasks.add(task); )
return () => tasks.delete(task); raf(run);
return () => void (running = false);
} }
type TimeoutTask = { t: number; c: (now: number) => void };
// sorted descending
const timed_tasks: Array<TimeoutTask> = [];
/** /**
* basically * Calls function every frame with the amount of elapsed frames
* (fn, t) => setTimeout( () => raf(fn), t )
*/ */
export function setAnimationTimeout(callback: () => void, timestamp: number) { export function onEachFrame(
let i = timed_tasks.length; each_frame: (seconds_elapsed: number) => boolean,
let v; on_stop?,
const task = { c: callback, t: timestamp }; max_skipped_frames = 4
if (i) { ): TaskCanceller {
while (i > 0 && timestamp > (v = timed_tasks[i - 1]).t) timed_tasks[i--] = v; max_skipped_frames *= FRAME_RATE || calc_framerate();
timed_tasks[i] = task; let lastTime = now();
} else { let running = true;
timed_tasks.push(task); if (
add((now) => { 1 ===
let i = timed_tasks.length; next_frame.push((t: number) => {
while (i > 0 && now >= timed_tasks[--i].t) timed_tasks.pop().c(now); if (!running) return on_stop && on_stop(t), false;
return !!timed_tasks.length; if (t > lastTime + max_skipped_frames) t = lastTime + max_skipped_frames;
}); return each_frame((-lastTime + (lastTime = t)) / 1000);
} })
return () => { )
const index = timed_tasks.indexOf(task); raf(run);
if (~index) timed_tasks.splice(index, 1); return () => void (running = false);
};
} }
export const loopThen = (run: (now: number) => void, stop: () => void, duration: number, end_time: number) => // tests
add((t) => { export const clear_loops = () =>
t = (end_time - t) / duration; void (next_frame.length = running_frame.length = timed_tasks.length = pending_insert_timed.length = 0);
if (t >= 1) return void run(1), stop();
else if (t >= 0) run(t);
return true;
});

@ -7,8 +7,7 @@ const resolved_promise = Promise.resolve();
let update_scheduled = false; let update_scheduled = false;
export function schedule_update(component) { export function schedule_update(component) {
dirty_components.push(component); dirty_components.push(component);
if (update_scheduled) return; if (!update_scheduled) (update_scheduled = true), resolved_promise.then(flush);
(update_scheduled = true), resolved_promise.then(flush);
} }
export function tick() { export function tick() {
if (!update_scheduled) (update_scheduled = true), resolved_promise.then(flush); if (!update_scheduled) (update_scheduled = true), resolved_promise.then(flush);
@ -16,16 +15,17 @@ export function tick() {
} }
export const binding_callbacks = []; export const binding_callbacks = [];
const render_callbacks = []; const render_callbacks = [];
export const add_render_callback = (fn) => render_callbacks.push(fn); const seen_callbacks = new Set();
export const add_render_callback = (fn) =>
void (!seen_callbacks.has(fn) && (seen_callbacks.add(fn), render_callbacks.push(fn)));
const flush_callbacks = []; const flush_callbacks = [];
export const add_flush_callback = (fn) => flush_callbacks.push(fn); export const add_flush_callback = (fn) => void flush_callbacks.push(fn);
let flushing = false; let flushing = false;
const seen_callbacks = new Set();
export function flush() { export function flush() {
if (flushing) return; if (flushing) return;
flushing = true; else flushing = true;
for (; dirty_components.length; ) { for (; dirty_components.length; ) {
// update components + beforeUpdate // update components + beforeUpdate
@ -42,21 +42,17 @@ export function flush() {
dirty_components.length = 0; dirty_components.length = 0;
// update bindings in reverse order // update bindings in reverse order
for (let i = binding_callbacks.length - 1; i; i--) binding_callbacks[i](); for (let i = binding_callbacks.length - 1; i >= 0; i--) binding_callbacks[i]();
binding_callbacks.length = 0; binding_callbacks.length = 0;
// afterUpdate // afterUpdate
for (let i = 0, callback; i < render_callbacks.length; i++) { for (let i = 0; i < render_callbacks.length; i++) render_callbacks[i]();
if (seen_callbacks.has((callback = render_callbacks[i]))) continue;
seen_callbacks.add(callback), callback();
}
render_callbacks.length = 0; render_callbacks.length = 0;
seen_callbacks.clear();
} }
for (let i = 0; i < flush_callbacks.length; i++) flush_callbacks[i](); for (let i = 0; i < flush_callbacks.length; i++) flush_callbacks[i]();
flush_callbacks.length = 0; flush_callbacks.length = 0;
seen_callbacks.clear(); update_scheduled = flushing = false;
update_scheduled = false;
flushing = false;
} }

@ -0,0 +1,65 @@
import { safe_not_equal, noop } from './utils';
type Setter<T> = (value: T) => void;
type StopCallback = () => void;
export type StartStopNotifier<T> = (set: Setter<T>) => StopCallback | void;
type Subscriber<T> = (value: T) => void;
type Unsubscriber = () => void;
export class Store<T> {
static update_queue: Store<any>[] = [];
static is_flushing = false;
static flush(store: Store<any>) {
this.update_queue.push(store);
if (this.is_flushing) return;
this.is_flushing = true;
for (let i = 0, j = 0, subscribers, value; i < this.update_queue.length; i++) {
for (j = 0, { subscribers, value } = this.update_queue[i]; j < subscribers.length; j++) {
subscribers[j].r(value);
}
}
this.update_queue.length = +(this.is_flushing = false);
}
value: T;
has_subscribers = false;
subscribers = [];
constructor(initial: T) {
this.value = initial;
}
set(next_value: T) {
this.value = next_value;
if (!this.has_subscribers) return;
for (let i = 0; i < this.subscribers.length; i++) this.subscribers[i].i();
Store.flush(this);
}
subscribe(run: Subscriber<T>, invalidate = noop) {
const subscriber = { r: run, i: invalidate };
this.subscribers.push(subscriber);
run(this.value), (this.has_subscribers = true);
return this.unsubscribe.bind(this, subscriber) as Unsubscriber;
}
unsubscribe(subscriber) {
this.subscribers.splice(this.subscribers.indexOf(subscriber), 1);
this.has_subscribers = !!this.subscribers.length;
}
}
export class Writable<T> extends Store<T> {
start: StartStopNotifier<T>;
stop = noop;
constructor(initial: T, startStopNotifier: StartStopNotifier<T>) {
super(initial);
this.start = startStopNotifier || noop;
}
subscribe(run, invalidate) {
if (!super.has_subscribers) this.stop = this.start(this.set.bind(this)) || noop;
return super.subscribe(run, invalidate);
}
set(next_value: T) {
if (this.stop && safe_not_equal(super.value, next_value)) super.set(next_value);
}
update(fn) {
this.set(fn(this.value));
}
unsubscribe(subscriber) {
super.unsubscribe(subscriber);
if (!super.has_subscribers && this.stop) this.stop(), (this.stop = null);
}
}

@ -6,8 +6,8 @@ const enum SVELTE {
RULESET = `__svelte_rules`, RULESET = `__svelte_rules`,
} }
let FRAME_RATE; export let FRAME_RATE;
function calc_framerate() { export function calc_framerate() {
const f24 = 1000 / 24, const f24 = 1000 / 24,
f60 = 1000 / 60, f60 = 1000 / 60,
f144 = 1000 / 144; f144 = 1000 / 144;

@ -1,6 +1,6 @@
import { run_all } from './utils'; import { run_all } from './utils';
import { now } from './environment'; import { now } from './environment';
import { setAnimationTimeout, loopThen } from './loop'; import { setAnimationTimeout, useTween } from './loop';
import { animate_css } from './style_manager'; import { animate_css } from './style_manager';
import { custom_event } from './dom'; import { custom_event } from './dom';
import { TransitionConfig } from '../transition'; import { TransitionConfig } from '../transition';
@ -82,7 +82,7 @@ export function run_transition(
); );
dispatch_end = startStopDispatcher(node, is_intro); dispatch_end = startStopDispatcher(node, is_intro);
cancel_raf = tick cancel_raf = tick
? loopThen(runner(eased(tick, easing), is_intro), stop, duration, end_time) ? useTween(runner(eased(tick, easing), is_intro), stop, duration, end_time)
: setAnimationTimeout(stop, end_time); : setAnimationTimeout(stop, end_time);
} }
function stop(reset_reverse?: 1 | -1) { function stop(reset_reverse?: 1 | -1) {

@ -48,9 +48,9 @@ export function validate_store(store, name) {
} }
} }
export function subscribe(store, ...callbacks) { export function subscribe(store, subscriber, invalidator?) {
if (store == null) return noop; if (store == null) return noop;
const unsub = store.subscribe(...callbacks); const unsub = store.subscribe(subscriber, invalidator);
return unsub.unsubscribe ? () => unsub.unsubscribe() : unsub; return unsub.unsubscribe ? () => unsub.unsubscribe() : unsub;
} }
@ -136,5 +136,8 @@ export const has_prop = (obj, prop) => Object.prototype.hasOwnProperty.call(obj,
export function action_destroyer(action_result) { export function action_destroyer(action_result) {
return action_result && is_function(action_result.destroy) ? action_result.destroy : noop; return action_result && is_function(action_result.destroy) ? action_result.destroy : noop;
} }
export const minmax = (min: number, max: number) => (value: number) => (min > value ? min : value > max ? max : value);
export const minmax = (min: number, max: number) => (v: number) => (v > min ? (v > max ? max : v) : min); export const clamper = (min: number, max: number) =>
min !== -Infinity || max !== Infinity
? (fn: (value: number) => any) => (value: number) => (min > (value = fn(value)) ? min : value > max ? max : value)
: (fn: (value: number) => any) => fn;

@ -0,0 +1,133 @@
// import { Readable, writable } from 'svelte/store';
// import { assign, loop, now, Task } from 'svelte/internal';
// import { linear } from 'svelte/easing';
// import { is_date } from './utils';
// function get_interpolator(a, b) {
// if (a === b || a !== a) return () => a;
// const type = typeof a;
// if (type !== typeof b || Array.isArray(a) !== Array.isArray(b)) {
// throw new Error('Cannot interpolate values of different type');
// }
// if (Array.isArray(a)) {
// const arr = b.map((bi, i) => {
// return get_interpolator(a[i], bi);
// });
// return t => arr.map(fn => fn(t));
// }
// if (type === 'object') {
// if (!a || !b) throw new Error('Object cannot be null');
// if (is_date(a) && is_date(b)) {
// a = a.getTime();
// b = b.getTime();
// const delta = b - a;
// return t => new Date(a + t * delta);
// }
// const keys = Object.keys(b);
// const interpolators = {};
// keys.forEach(key => {
// interpolators[key] = get_interpolator(a[key], b[key]);
// });
// return t => {
// const result = {};
// keys.forEach(key => {
// result[key] = interpolators[key](t);
// });
// return result;
// };
// }
// if (type === 'number') {
// const delta = b - a;
// return t => a + t * delta;
// }
// throw new Error(`Cannot interpolate ${type} values`);
// }
// interface Options<T> {
// delay?: number;
// duration?: number | ((from: T, to: T) => number);
// easing?: (t: number) => number;
// interpolate?: (a: T, b: T) => (t: number) => T;
// }
// type Updater<T> = (target_value: T, value: T) => T;
// interface Tweened<T> extends Readable<T> {
// set(value: T, opts: Options<T>): Promise<void>;
// update(updater: Updater<T>, opts: Options<T>): Promise<void>;
// }
// export function tweened<T>(value?: T, defaults: Options<T> = {}): Tweened<T> {
// const store = writable(value);
// let task: Task;
// let target_value = value;
// function set(new_value: T, opts: Options<T>) {
// if (value == null) {
// store.set(value = new_value);
// return Promise.resolve();
// }
// target_value = new_value;
// let previous_task = task;
// let started = false;
// let {
// delay = 0,
// duration = 400,
// easing = linear,
// interpolate = get_interpolator
// } = assign(assign({}, defaults), opts);
// const start = now() + delay;
// let fn;
// task = loop(now => {
// if (now < start) return true;
// if (!started) {
// fn = interpolate(value, new_value);
// if (typeof duration === 'function') duration = duration(value, new_value);
// started = true;
// }
// if (previous_task) {
// previous_task.abort();
// previous_task = null;
// }
// const elapsed = now - start;
// if (elapsed > duration) {
// store.set(value = new_value);
// return false;
// }
// // @ts-ignore
// store.set(value = fn(easing(elapsed / duration)));
// return true;
// });
// return task.promise;
// }
// return {
// set,
// update: (fn, opts: Options<T>) => set(fn(target_value, value), opts),
// subscribe: store.subscribe
// };
// }

@ -1,2 +1,2 @@
export * from './spring'; export * from './spring';
export * from './tweened'; // export * from './_tweened';

@ -1,147 +1,81 @@
import { Readable, writable } from 'svelte/store'; import { Store, onEachFrame } from 'svelte/internal';
import { loop, now, Task } from 'svelte/internal';
import { is_date } from './utils'; function solve_spring(
prev_value: number,
interface TickContext<T> { prev_velocity: number,
inv_mass: number; target_value: number,
dt: number; { stiffness, mass, damping, soft }
opts: Spring<T>; ) {
settled: boolean; const delta = target_value - prev_value;
} if (soft || 1 <= damping / (2.0 * Math.sqrt(stiffness * mass))) {
const angular_frequency = -Math.sqrt(stiffness / mass);
function tick_spring<T>(ctx: TickContext<T>, last_value: T, current_value: T, target_value: T): T { return (t: number) =>
if (typeof current_value === 'number' || is_date(current_value)) { target_value - (delta + t * (-angular_frequency * delta - prev_velocity)) * Math.exp(t * angular_frequency);
// @ts-ignore
const delta = target_value - current_value;
// @ts-ignore
const velocity = (current_value - last_value) / (ctx.dt||1/60); // guard div by 0
const spring = ctx.opts.stiffness * delta;
const damper = ctx.opts.damping * velocity;
const acceleration = (spring - damper) * ctx.inv_mass;
const d = (velocity + acceleration) * ctx.dt;
if (Math.abs(d) < ctx.opts.precision && Math.abs(delta) < ctx.opts.precision) {
return target_value; // settled
} else {
ctx.settled = false; // signal loop to keep ticking
// @ts-ignore
return is_date(current_value) ?
new Date(current_value.getTime() + d) : current_value + d;
}
} else if (Array.isArray(current_value)) {
// @ts-ignore
return current_value.map((_, i) =>
tick_spring(ctx, last_value[i], current_value[i], target_value[i]));
} else if (typeof current_value === 'object') {
const next_value = {};
for (const k in current_value)
// @ts-ignore
next_value[k] = tick_spring(ctx, last_value[k], current_value[k], target_value[k]);
// @ts-ignore
return next_value;
} else { } else {
throw new Error(`Cannot spring ${typeof current_value} values`); const damping_frequency = Math.sqrt(4.0 * mass * stiffness - damping ** 2);
const leftover = (damping * delta - 2.0 * mass * prev_velocity) / damping_frequency;
const dfm = (0.5 * damping_frequency) / mass;
const dm = -(0.5 * damping) / mass;
let f = 0.0;
return (t: number) => target_value - (Math.cos((f = t * dfm)) * delta + Math.sin(f) * leftover) * Math.exp(t * dm);
} }
} }
class MotionStore<T> extends Store<T> {
interface SpringOpts { elapsed = 0.0;
stiffness?: number; running = false;
damping?: number; setTick: (prev_value: T, next_value: T) => (current_value: T, elapsed: number, dt: number) => boolean;
precision?: number; tick: (current_value: T, elapsed: number, dt: number) => boolean;
} constructor(value, startSetTick) {
super(value);
interface SpringUpdateOpts { this.setTick = startSetTick(super.set.bind(this));
hard?: any; }
soft?: string | number | boolean; set(next_value: T) {
this.elapsed = 0.0;
this.tick = this.setTick(this.value, next_value);
if (this.running) return;
else this.running = true;
onEachFrame((dt) => (this.running = this.tick(this.value, (this.elapsed += dt), dt)));
}
} }
export function spring(value, params) {
type Updater<T> = (target_value: T, value: T) => T; const { mass = 1.0, damping = 10.0, stiffness = 100.0, precision = 0.001, soft = false } = params || {};
return new MotionStore(
interface Spring<T> extends Readable<T>{ value,
set: (new_value: T, opts?: SpringUpdateOpts) => Promise<void>; parseStructure(value, (set) => {
update: (fn: Updater<T>, opts?: SpringUpdateOpts) => Promise<void>; let velocity = 0.0,
precision: number; calc;
damping: number; return (from_value, to_value) => {
stiffness: number; calc = solve_spring(from_value, velocity, to_value, { mass, damping, stiffness, soft });
return (current, elapsed, dt) =>
precision > Math.abs((velocity = (-current + (current = calc(elapsed))) / dt)) &&
precision > Math.abs(to_value - current)
? (set(to_value), !!(velocity = 0.0))
: (set(current), true);
};
})
);
} }
function parseStructure(obj, schema) {
export function spring<T=any>(value?: T, opts: SpringOpts = {}): Spring<T> { if (typeof obj === 'object' && obj !== null) {
const store = writable(value); const keys = Object.keys(obj);
const { stiffness = 0.15, damping = 0.8, precision = 0.01 } = opts; let i = 0,
k = '',
let last_time: number; setTickers = keys.map((key) => parseStructure(obj[key], schema)((next_value) => (obj[key] = next_value))),
let task: Task; tickers,
let current_token: object; pending = 0;
let last_value: T = value; const target = { ...obj };
let target_value: T = value; const isArray = Array.isArray(obj);
obj = isArray ? [...obj] : { ...obj };
let inv_mass = 1; return (set) => (_from_value, to_value) => {
let inv_mass_recovery_rate = 0; for (k in to_value) if (to_value[k] !== obj[k]) target[k] = to_value[k];
let cancel_task = false; tickers = setTickers.map((setTicker, i) => ((pending |= 1 << i), setTicker(obj[keys[i]], target[keys[i]])));
return (_current, elapsed, dt) => {
function set(new_value: T, opts: SpringUpdateOpts={}): Promise<void> { for (i = 0; i < tickers.length; i++)
target_value = new_value; if (pending & (1 << i) && !tickers[i](obj[keys[i]], elapsed, dt)) pending &= ~(1 << i);
const token = current_token = {}; set(isArray ? [...obj] : { ...obj });
return !!pending;
if (value == null || opts.hard || (spring.stiffness >= 1 && spring.damping >= 1)) { };
cancel_task = true; // cancel any running animation };
last_time = now();
last_value = new_value;
store.set(value = target_value);
return Promise.resolve();
} else if (opts.soft) {
const rate = opts.soft === true ? .5 : +opts.soft;
inv_mass_recovery_rate = 1 / (rate * 60);
inv_mass = 0; // infinite mass, unaffected by spring forces
}
if (!task) {
last_time = now();
cancel_task = false;
task = loop(now => {
if (cancel_task) {
cancel_task = false;
task = null;
return false;
}
inv_mass = Math.min(inv_mass + inv_mass_recovery_rate, 1);
const ctx: TickContext<T> = {
inv_mass,
opts: spring,
settled: true, // tick_spring may signal false
dt: (now - last_time) * 60 / 1000
};
const next_value = tick_spring(ctx, last_value, value, target_value);
last_time = now;
last_value = value;
store.set(value = next_value);
if (ctx.settled)
task = null;
return !ctx.settled;
});
}
return new Promise(fulfil => {
task.promise.then(() => {
if (token === current_token) fulfil();
});
});
} }
return schema;
const spring: Spring<T> = {
set,
update: (fn, opts: SpringUpdateOpts) => set(fn(target_value, value), opts),
subscribe: store.subscribe,
stiffness,
damping,
precision
};
return spring;
} }

@ -1,133 +0,0 @@
import { Readable, writable } from 'svelte/store';
import { assign, loop, now, Task } from 'svelte/internal';
import { linear } from 'svelte/easing';
import { is_date } from './utils';
function get_interpolator(a, b) {
if (a === b || a !== a) return () => a;
const type = typeof a;
if (type !== typeof b || Array.isArray(a) !== Array.isArray(b)) {
throw new Error('Cannot interpolate values of different type');
}
if (Array.isArray(a)) {
const arr = b.map((bi, i) => {
return get_interpolator(a[i], bi);
});
return t => arr.map(fn => fn(t));
}
if (type === 'object') {
if (!a || !b) throw new Error('Object cannot be null');
if (is_date(a) && is_date(b)) {
a = a.getTime();
b = b.getTime();
const delta = b - a;
return t => new Date(a + t * delta);
}
const keys = Object.keys(b);
const interpolators = {};
keys.forEach(key => {
interpolators[key] = get_interpolator(a[key], b[key]);
});
return t => {
const result = {};
keys.forEach(key => {
result[key] = interpolators[key](t);
});
return result;
};
}
if (type === 'number') {
const delta = b - a;
return t => a + t * delta;
}
throw new Error(`Cannot interpolate ${type} values`);
}
interface Options<T> {
delay?: number;
duration?: number | ((from: T, to: T) => number);
easing?: (t: number) => number;
interpolate?: (a: T, b: T) => (t: number) => T;
}
type Updater<T> = (target_value: T, value: T) => T;
interface Tweened<T> extends Readable<T> {
set(value: T, opts: Options<T>): Promise<void>;
update(updater: Updater<T>, opts: Options<T>): Promise<void>;
}
export function tweened<T>(value?: T, defaults: Options<T> = {}): Tweened<T> {
const store = writable(value);
let task: Task;
let target_value = value;
function set(new_value: T, opts: Options<T>) {
if (value == null) {
store.set(value = new_value);
return Promise.resolve();
}
target_value = new_value;
let previous_task = task;
let started = false;
let {
delay = 0,
duration = 400,
easing = linear,
interpolate = get_interpolator
} = assign(assign({}, defaults), opts);
const start = now() + delay;
let fn;
task = loop(now => {
if (now < start) return true;
if (!started) {
fn = interpolate(value, new_value);
if (typeof duration === 'function') duration = duration(value, new_value);
started = true;
}
if (previous_task) {
previous_task.abort();
previous_task = null;
}
const elapsed = now - start;
if (elapsed > duration) {
store.set(value = new_value);
return false;
}
// @ts-ignore
store.set(value = fn(easing(elapsed / duration)));
return true;
});
return task.promise;
}
return {
set,
update: (fn, opts: Options<T>) => set(fn(target_value, value), opts),
subscribe: store.subscribe
};
}

@ -1,204 +1,45 @@
import { run_all, subscribe, noop, safe_not_equal, is_function, get_store_value } from 'svelte/internal'; import { subscribe, noop, get_store_value, Writable, StartStopNotifier } from 'svelte/internal';
export { get_store_value as get };
/** Callback to inform of a value updates. */ export function readable<T>(value: T, start: StartStopNotifier<T>) {
type Subscriber<T> = (value: T) => void; const store = new Writable(value, start);
return { subscribe: store.subscribe.bind(store) };
/** Unsubscribes from value updates. */
type Unsubscriber = () => void;
/** Callback to update a value. */
type Updater<T> = (value: T) => T;
/** Cleanup logic callback. */
type Invalidator<T> = (value?: T) => void;
/** Start and stop notification callbacks. */
type StartStopNotifier<T> = (set: Subscriber<T>) => Unsubscriber | void;
/** Readable interface for subscribing. */
export interface Readable<T> {
/**
* Subscribe on value changes.
* @param run subscription callback
* @param invalidate cleanup callback
*/
subscribe(run: Subscriber<T>, invalidate?: Invalidator<T>): Unsubscriber;
}
/** Writable interface for both updating and subscribing. */
export interface Writable<T> extends Readable<T> {
/**
* Set value and inform subscribers.
* @param value to set
*/
set(value: T): void;
/**
* Update value using callback and inform subscribers.
* @param updater callback
*/
update(updater: Updater<T>): void;
}
/** Pair of subscriber and invalidator. */
type SubscribeInvalidateTuple<T> = [Subscriber<T>, Invalidator<T>];
const subscriber_queue = [];
/**
* Creates a `Readable` store that allows reading by subscription.
* @param value initial value
* @param {StartStopNotifier}start start and stop notifications for subscriptions
*/
export function readable<T>(value: T, start: StartStopNotifier<T>): Readable<T> {
return {
subscribe: writable(value, start).subscribe,
};
} }
export function writable<T>(value: T, start: StartStopNotifier<T>) {
/** const store = new Writable(value, start);
* Create a `Writable` store that allows both updating and reading by subscription. return { set: store.set.bind(store), update: store.update.bind(store), subscribe: store.subscribe.bind(store) };
* @param {*=}value initial value
* @param {StartStopNotifier=}start start and stop notifications for subscriptions
*/
export function writable<T>(value: T, start: StartStopNotifier<T> = noop): Writable<T> {
let stop: Unsubscriber;
const subscribers: Array<SubscribeInvalidateTuple<T>> = [];
function set(new_value: T): void {
if (safe_not_equal(value, new_value)) {
value = new_value;
if (stop) { // store is ready
const run_queue = !subscriber_queue.length;
for (let i = 0; i < subscribers.length; i += 1) {
const s = subscribers[i];
s[1]();
subscriber_queue.push(s, value);
}
if (run_queue) {
for (let i = 0; i < subscriber_queue.length; i += 2) {
subscriber_queue[i][0](subscriber_queue[i + 1]);
}
subscriber_queue.length = 0;
}
}
}
}
function update(fn: Updater<T>): void {
set(fn(value));
}
function subscribe(run: Subscriber<T>, invalidate: Invalidator<T> = noop): Unsubscriber {
const subscriber: SubscribeInvalidateTuple<T> = [run, invalidate];
subscribers.push(subscriber);
if (subscribers.length === 1) {
stop = start(set) || noop;
}
run(value);
return () => {
const index = subscribers.indexOf(subscriber);
if (index !== -1) {
subscribers.splice(index, 1);
}
if (subscribers.length === 0) {
stop();
stop = null;
}
};
}
return { set, update, subscribe };
} }
export function derived<T, S>(stores: S, deriver, initial_value?: T) {
/** One or more `Readable`s. */ let cleanup = noop;
type Stores = Readable<any> | [Readable<any>, ...Array<Readable<any>>]; const dispatcher =
deriver.length < 2
/** One or more values from `Readable` stores. */ ? (set, v) => void set(deriver(v))
type StoresValues<T> = T extends Readable<infer U> ? U : : (set, v) => void (cleanup(), typeof (cleanup = deriver(v, set)) !== 'function' && (cleanup = noop));
{ [K in keyof T]: T[K] extends Readable<infer U> ? U : never }; return readable(
initial_value,
/** Array.isArray(stores)
* Derived value store by synchronizing one or more readable stores and ? (set) => {
* applying an aggregation function over its input values. set = dispatcher.bind(null, set);
* let l = stores.length;
* @param stores - input stores let pending = 1 << l;
* @param fn - function callback that aggregates the values const values = new Array(l);
*/ const unsubs = stores.map((store, i) =>
export function derived<S extends Stores, T>( subscribe(
stores: S, store,
fn: (values: StoresValues<S>) => T // @ts-ignore
): Readable<T>; (v) => void ((values[i] = v), !(pending &= ~(1 << i)) && set(values)),
() => void (pending |= 1 << i)
/** )
* Derived value store by synchronizing one or more readable stores and );
* applying an aggregation function over its input values. // @ts-ignore
* if (!(pending &= ~(1 << l))) set(values);
* @param stores - input stores return () => {
* @param fn - function callback that aggregates the values while (l--) unsubs[l]();
* @param initial_value - when used asynchronously cleanup();
*/ };
export function derived<S extends Stores, T>( }
stores: S, : (set) => {
fn: (values: StoresValues<S>, set: (value: T) => void) => Unsubscriber | void, const unsub = subscribe(stores, dispatcher.bind(null, set));
initial_value?: T return () => void (unsub(), cleanup());
): Readable<T>; }
);
export function derived<T>(stores: Stores, fn: Function, initial_value?: T): Readable<T> {
const single = !Array.isArray(stores);
const stores_array: Array<Readable<any>> = single
? [stores as Readable<any>]
: stores as Array<Readable<any>>;
const auto = fn.length < 2;
return readable(initial_value, (set) => {
let inited = false;
const values = [];
let pending = 0;
let cleanup = noop;
const sync = () => {
if (pending) {
return;
}
cleanup();
const result = fn(single ? values[0] : values, set);
if (auto) {
set(result as T);
} else {
cleanup = is_function(result) ? result as Unsubscriber : noop;
}
};
const unsubscribers = stores_array.map((store, i) => subscribe(
store,
(value) => {
values[i] = value;
pending &= ~(1 << i);
if (inited) {
sync();
}
},
() => {
pending |= (1 << i);
}),
);
inited = true;
sync();
return function stop() {
run_all(unsubscribers);
cleanup();
};
});
} }
/**
* Get the current value from a store by subscribing and immediately unsubscribing.
* @param store readable
*/
export { get_store_value as get };

@ -1,6 +1,6 @@
{ {
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"include": ["."], "include": [".", "../ambient.ts"],
"compilerOptions": { "compilerOptions": {
"lib": ["es2015", "dom", "dom.iterable"], "lib": ["es2015", "dom", "dom.iterable"],

Loading…
Cancel
Save