pull/4742/head
pushkine 5 years ago
parent c225731d06
commit eaf599f910

1
.gitignore vendored

@ -12,6 +12,7 @@ node_modules
/dev
/store
/easing
/interpolate
/motion
/transition
/animate

@ -0,0 +1 @@
export * from '../types/runtime/environment/index';

@ -0,0 +1,24 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
const is_browser = typeof window !== 'undefined';
const is_iframe = is_browser && window.self !== window.top;
const is_cors =
is_iframe &&
(() => {
try {
if (window.parent) void window.parent.document;
return false;
} catch (error) {
return true;
}
})();
const has_Symbol = typeof Symbol === 'function';
const globals = is_browser ? window : typeof globalThis !== 'undefined' ? globalThis : global;
exports.globals = globals;
exports.has_Symbol = has_Symbol;
exports.is_browser = is_browser;
exports.is_cors = is_cors;
exports.is_iframe = is_iframe;

@ -0,0 +1,16 @@
const is_browser = typeof window !== 'undefined';
const is_iframe = is_browser && window.self !== window.top;
const is_cors =
is_iframe &&
(() => {
try {
if (window.parent) void window.parent.document;
return false;
} catch (error) {
return true;
}
})();
const has_Symbol = typeof Symbol === 'function';
const globals = is_browser ? window : typeof globalThis !== 'undefined' ? globalThis : global;
export { globals, has_Symbol, is_browser, is_cors, is_iframe };

@ -0,0 +1,5 @@
{
"main": "./index",
"module": "./index.mjs",
"types": "./index.d.ts"
}

@ -18,9 +18,6 @@
"svelte",
"README.md"
],
"engines": {
"node": ">= 8"
},
"types": "types/runtime/index.d.ts",
"scripts": {
"test": "mocha --opts mocha.opts",
@ -64,16 +61,16 @@
"@rollup/plugin-typescript": "^4.1.1",
"@rollup/plugin-virtual": "^2.0.1",
"@types/mocha": "^7.0.2",
"@types/node": "^13.13.4",
"@typescript-eslint/eslint-plugin": "^2.30.0",
"@typescript-eslint/parser": "^2.30.0",
"acorn": "^7.1.1",
"@types/node": "^13.13.5",
"@typescript-eslint/eslint-plugin": "^2.31.0",
"@typescript-eslint/parser": "^2.31.0",
"acorn": "^7.2.0",
"agadoo": "^2.0.0",
"c8": "^7.1.1",
"c8": "^7.1.2",
"code-red": "0.1.1",
"codecov": "^3.6.5",
"css-tree": "1.0.0-alpha22",
"eslint": "^6.8.0",
"eslint": "^7.0.0",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-svelte3": "^2.7.3",
"estree-walker": "^2.0.1",
@ -83,13 +80,13 @@
"locate-character": "^2.0.5",
"magic-string": "^0.25.7",
"mocha": "^7.1.2",
"periscopic": "^2.0.2",
"puppeteer": "^3.0.2",
"rollup": "^2.7.6",
"periscopic": "^2.0.1",
"puppeteer": "^3.0.4",
"rollup": "^2.8.2",
"source-map": "^0.7.3",
"source-map-support": "^0.5.19",
"tiny-glob": "^0.2.6",
"tslib": "^1.11.1",
"tslib": "^1.11.2",
"typescript": "^3.8.3"
},
"nyc": {

@ -53,3 +53,5 @@ declare var __VERSION__: string;
* instead of relying on hacks
*/
declare var __TEST__: boolean;
declare var global: any;

@ -1,4 +1,5 @@
const now = (typeof process !== 'undefined' && process.hrtime)
const now =
typeof process !== 'undefined' && process.hrtime
? () => {
const t = process.hrtime();
return t[0] * 1e3 + t[1] / 1e6;
@ -14,10 +15,13 @@ interface Timing {
function collapse_timings(timings) {
const result = {};
timings.forEach(timing => {
result[timing.label] = Object.assign({
total: timing.end - timing.start
}, timing.children && collapse_timings(timing.children));
timings.forEach((timing) => {
result[timing.label] = Object.assign(
{
total: timing.end - timing.start,
},
timing.children && collapse_timings(timing.children)
);
});
return result;
}
@ -40,7 +44,7 @@ export default class Stats {
label,
start: now(),
end: null,
children: []
children: [],
};
this.current_children.push(timing);
@ -62,12 +66,13 @@ export default class Stats {
}
render() {
const timings = Object.assign({
total: now() - this.start_time
}, collapse_timings(this.timings));
const timings = {
total: now() - this.start_time,
...collapse_timings(this.timings),
};
return {
timings
timings,
};
}
}

@ -1,37 +1,37 @@
import { b, print, x } from 'code-red';
import {
AssignmentExpression,
ExpressionStatement,
Identifier,
ImportDeclaration,
Literal,
Node,
Program,
} from 'estree';
import { walk } from 'estree-walker';
import is_reference from 'is-reference';
import { getLocator } from 'locate-character';
import { test } from '../config';
import { Ast, CompileOptions, CssResult, Var, Warning } from '../interfaces';
import Stats from '../Stats';
import { globals, reserved, is_valid } from '../utils/names';
import { namespaces, valid_namespaces } from '../utils/namespaces';
import error from '../utils/error';
import fuzzymatch from '../utils/fuzzymatch';
import get_code_frame from '../utils/get_code_frame';
import { globals, is_valid, reserved } from '../utils/names';
import { namespaces } from '../utils/namespaces';
import create_module from './create_module';
import { create_scopes, extract_names, Scope, extract_identifiers } from './utils/scope';
import Stylesheet from './css/Stylesheet';
import { test } from '../config';
import Fragment from './nodes/Fragment';
import internal_exports from './internal_exports';
import { Ast, CompileOptions, Var, Warning, CssResult } from '../interfaces';
import error from '../utils/error';
import get_code_frame from '../utils/get_code_frame';
import flatten_reference from './utils/flatten_reference';
import is_used_as_reference from './utils/is_used_as_reference';
import is_reference from 'is-reference';
import Fragment from './nodes/Fragment';
import TemplateScope from './nodes/shared/TemplateScope';
import fuzzymatch from '../utils/fuzzymatch';
import get_object from './utils/get_object';
import Slot from './nodes/Slot';
import {
Node,
ImportDeclaration,
Identifier,
Program,
ExpressionStatement,
AssignmentExpression,
Literal,
} from 'estree';
import add_to_set from './utils/add_to_set';
import check_graph_for_cycles from './utils/check_graph_for_cycles';
import { print, x, b } from 'code-red';
import flatten_reference from './utils/flatten_reference';
import get_object from './utils/get_object';
import is_used_as_reference from './utils/is_used_as_reference';
import { is_reserved_keyword } from './utils/reserved_keywords';
import { create_scopes, extract_identifiers, extract_names, Scope } from './utils/scope';
interface ComponentOptions {
namespace?: string;
@ -1343,8 +1343,8 @@ function process_component_options(component: Component, nodes) {
if (typeof ns !== 'string') component.error(attribute, { code, message });
if (valid_namespaces.indexOf(ns) === -1) {
const match = fuzzymatch(ns, valid_namespaces);
if (!(ns in namespaces)) {
const match = fuzzymatch(ns, namespaces);
if (match) {
component.error(attribute, {
code: `invalid-namespace-property`,

@ -1,4 +1,3 @@
import { assign } from '../../runtime/internal/utils';
import Stats from '../Stats';
import parse from '../parse/index';
import render_dom from './render_dom/index';
@ -9,6 +8,7 @@ import fuzzymatch from '../utils/fuzzymatch';
import get_name_from_filename from './utils/get_name_from_filename';
const valid_options = [
'version',
'format',
'name',
'filename',
@ -26,13 +26,13 @@ const valid_options = [
'css',
'loopGuardTimeout',
'preserveComments',
'preserveWhitespace'
'preserveWhitespace',
];
function validate_options(options: CompileOptions, warnings: Warning[]) {
const { name, filename, loopGuardTimeout, dev } = options;
Object.keys(options).forEach(key => {
Object.keys(options).forEach((key) => {
if (!valid_options.includes(key)) {
const match = fuzzymatch(key, valid_options);
let message = `Unrecognized option '${key}'`;
@ -68,7 +68,7 @@ function validate_options(options: CompileOptions, warnings: Warning[]) {
}
export default function compile(source: string, options: CompileOptions = {}) {
options = assign({ generate: 'dom', dev: false }, options);
options = { generate: 'dom', dev: false, ...options };
const stats = new Stats();
const warnings = [];
@ -90,7 +90,8 @@ export default function compile(source: string, options: CompileOptions = {}) {
);
stats.stop('create component');
const result = options.generate === false
const result =
options.generate === false
? null
: options.generate === 'ssr'
? render_ssr(component, options)

@ -19,10 +19,14 @@ import { INode } from './interfaces';
const svg = /^(?:altGlyph|altGlyphDef|altGlyphItem|animate|animateColor|animateMotion|animateTransform|circle|clipPath|color-profile|cursor|defs|desc|discard|ellipse|feBlend|feColorMatrix|feComponentTransfer|feComposite|feConvolveMatrix|feDiffuseLighting|feDisplacementMap|feDistantLight|feDropShadow|feFlood|feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feImage|feMerge|feMergeNode|feMorphology|feOffset|fePointLight|feSpecularLighting|feSpotLight|feTile|feTurbulence|filter|font|font-face|font-face-format|font-face-name|font-face-src|font-face-uri|foreignObject|g|glyph|glyphRef|hatch|hatchpath|hkern|image|line|linearGradient|marker|mask|mesh|meshgradient|meshpatch|meshrow|metadata|missing-glyph|mpath|path|pattern|polygon|polyline|radialGradient|rect|set|solidcolor|stop|svg|switch|symbol|text|textPath|tref|tspan|unknown|use|view|vkern)$/;
const aria_attributes = 'activedescendant atomic autocomplete busy checked colindex controls current describedby details disabled dropeffect errormessage expanded flowto grabbed haspopup hidden invalid keyshortcuts label labelledby level live modal multiline multiselectable orientation owns placeholder posinset pressed readonly relevant required roledescription rowindex selected setsize sort valuemax valuemin valuenow valuetext'.split(' ');
const aria_attributes = 'activedescendant atomic autocomplete busy checked colindex controls current describedby details disabled dropeffect errormessage expanded flowto grabbed haspopup hidden invalid keyshortcuts label labelledby level live modal multiline multiselectable orientation owns placeholder posinset pressed readonly relevant required roledescription rowindex selected setsize sort valuemax valuemin valuenow valuetext'.split(
' '
);
const aria_attribute_set = new Set(aria_attributes);
const aria_roles = 'alert alertdialog application article banner button cell checkbox columnheader combobox command complementary composite contentinfo definition dialog directory document feed figure form grid gridcell group heading img input landmark link list listbox listitem log main marquee math menu menubar menuitem menuitemcheckbox menuitemradio navigation none note option presentation progressbar radio radiogroup range region roletype row rowgroup rowheader scrollbar search searchbox section sectionhead select separator slider spinbutton status structure switch tab table tablist tabpanel term textbox timer toolbar tooltip tree treegrid treeitem widget window'.split(' ');
const aria_roles = 'alert alertdialog application article banner button cell checkbox columnheader combobox command complementary composite contentinfo definition dialog directory document feed figure form grid gridcell group heading img input landmark link list listbox listitem log main marquee math menu menubar menuitem menuitemcheckbox menuitemradio navigation none note option presentation progressbar radio radiogroup range region roletype row rowgroup rowheader scrollbar search searchbox section sectionhead select separator slider spinbutton status structure switch tab table tablist tabpanel term textbox timer toolbar tooltip tree treegrid treeitem widget window'.split(
' '
);
const aria_role_set = new Set(aria_roles);
const a11y_required_attributes = {
@ -35,13 +39,10 @@ const a11y_required_attributes = {
// iframe-has-title
iframe: ['title'],
img: ['alt'],
object: ['title', 'aria-label', 'aria-labelledby']
object: ['title', 'aria-label', 'aria-labelledby'],
};
const a11y_distracting_elements = new Set([
'blink',
'marquee'
]);
const a11y_distracting_elements = new Set(['blink', 'marquee']);
const a11y_required_content = new Set([
// anchor-has-content
@ -53,35 +54,20 @@ const a11y_required_content = new Set([
'h3',
'h4',
'h5',
'h6'
'h6',
]);
const invisible_elements = new Set(['meta', 'html', 'script', 'style']);
const valid_modifiers = new Set([
'preventDefault',
'stopPropagation',
'capture',
'once',
'passive',
'self'
]);
const valid_modifiers = new Set(['preventDefault', 'stopPropagation', 'capture', 'once', 'passive', 'self']);
const passive_events = new Set([
'wheel',
'touchstart',
'touchmove',
'touchend',
'touchcancel'
]);
const passive_events = new Set(['wheel', 'touchstart', 'touchmove', 'touchend', 'touchcancel']);
function get_namespace(parent: Element, element: Element, explicit_namespace: string) {
const parent_element = parent.find_nearest(/^Element/);
if (!parent_element) {
return explicit_namespace || (svg.test(element.name)
? namespaces.svg
: null);
return explicit_namespace || (svg.test(element.name) ? namespaces.svg : null);
}
if (svg.test(element.name.toLowerCase())) return namespaces.svg;
@ -115,11 +101,11 @@ export default class Element extends Node {
if (this.name === 'textarea') {
if (info.children.length > 0) {
const value_attribute = info.attributes.find(node => node.name === 'value');
const value_attribute = info.attributes.find((node) => node.name === 'value');
if (value_attribute) {
component.error(value_attribute, {
code: `textarea-duplicate-value`,
message: `A <textarea> can have either a value attribute or (equivalently) child content, but not both`
message: `A <textarea> can have either a value attribute or (equivalently) child content, but not both`,
});
}
@ -128,7 +114,7 @@ export default class Element extends Node {
info.attributes.push({
type: 'Attribute',
name: 'value',
value: info.children
value: info.children,
});
info.children = [];
@ -139,19 +125,19 @@ export default class Element extends Node {
// Special case — treat these the same way:
// <option>{foo}</option>
// <option value={foo}>{foo}</option>
const value_attribute = info.attributes.find(attribute => attribute.name === 'value');
const value_attribute = info.attributes.find((attribute) => attribute.name === 'value');
if (!value_attribute) {
info.attributes.push({
type: 'Attribute',
name: 'value',
value: info.children,
synthetic: true
synthetic: true,
});
}
}
const has_let = info.attributes.some(node => node.type === 'Let');
const has_let = info.attributes.some((node) => node.type === 'Let');
if (has_let) {
scope = scope.child();
}
@ -160,7 +146,7 @@ export default class Element extends Node {
const order = ['Binding']; // everything else is -1
info.attributes.sort((a, b) => order.indexOf(a.type) - order.indexOf(b.type));
info.attributes.forEach(node => {
info.attributes.forEach((node) => {
switch (node.type) {
case 'Action':
this.actions.push(new Action(component, this, scope, node));
@ -191,14 +177,13 @@ export default class Element extends Node {
this.lets.push(l);
const dependencies = new Set([l.name.name]);
l.names.forEach(name => {
l.names.forEach((name) => {
scope.add(name, dependencies, this);
});
break;
}
case 'Transition':
{
case 'Transition': {
const transition = new Transition(component, this, scope, node);
if (node.intro) this.intro = transition;
if (node.outro) this.outro = transition;
@ -227,7 +212,7 @@ export default class Element extends Node {
// no-distracting-elements
this.component.warn(this, {
code: `a11y-distracting-elements`,
message: `A11y: Avoid <${this.name}> elements`
message: `A11y: Avoid <${this.name}> elements`,
});
}
@ -249,24 +234,24 @@ export default class Element extends Node {
if (!is_figure_parent) {
this.component.warn(this, {
code: `a11y-structure`,
message: `A11y: <figcaption> must be an immediate child of <figure>`
message: `A11y: <figcaption> must be an immediate child of <figure>`,
});
}
}
if (this.name === 'figure') {
const children = this.children.filter(node => {
const children = this.children.filter((node) => {
if (node.type === 'Comment') return false;
if (node.type === 'Text') return /\S/.test(node.data);
return true;
});
const index = children.findIndex(child => (child as Element).name === 'figcaption');
const index = children.findIndex((child) => (child as Element).name === 'figcaption');
if (index !== -1 && (index !== 0 && index !== children.length - 1)) {
if (index !== -1 && index !== 0 && index !== children.length - 1) {
this.component.warn(children[index], {
code: `a11y-structure`,
message: `A11y: <figcaption> must be first or last child of <figure>`
message: `A11y: <figcaption> must be first or last child of <figure>`,
});
}
}
@ -282,7 +267,7 @@ export default class Element extends Node {
const attribute_map = new Map();
this.attributes.forEach(attribute => {
this.attributes.forEach((attribute) => {
if (attribute.is_spread) return;
const name = attribute.name.toLowerCase();
@ -293,7 +278,7 @@ export default class Element extends Node {
// aria-unsupported-elements
component.warn(attribute, {
code: `a11y-aria-attributes`,
message: `A11y: <${this.name}> should not have aria-* attributes`
message: `A11y: <${this.name}> should not have aria-* attributes`,
});
}
@ -305,14 +290,14 @@ export default class Element extends Node {
component.warn(attribute, {
code: `a11y-unknown-aria-attribute`,
message
message,
});
}
if (name === 'aria-hidden' && /^h[1-6]$/.test(this.name)) {
component.warn(attribute, {
code: `a11y-hidden`,
message: `A11y: <${this.name}> element should not be hidden`
message: `A11y: <${this.name}> element should not be hidden`,
});
}
}
@ -323,7 +308,7 @@ export default class Element extends Node {
// aria-unsupported-elements
component.warn(attribute, {
code: `a11y-misplaced-role`,
message: `A11y: <${this.name}> should not have role attribute`
message: `A11y: <${this.name}> should not have role attribute`,
});
}
@ -337,7 +322,7 @@ export default class Element extends Node {
component.warn(attribute, {
code: `a11y-unknown-role`,
message
message,
});
}
}
@ -346,7 +331,7 @@ export default class Element extends Node {
if (name === 'accesskey') {
component.warn(attribute, {
code: `a11y-accesskey`,
message: `A11y: Avoid using accesskey`
message: `A11y: Avoid using accesskey`,
});
}
@ -354,7 +339,7 @@ export default class Element extends Node {
if (name === 'autofocus') {
component.warn(attribute, {
code: `a11y-autofocus`,
message: `A11y: Avoid using autofocus`
message: `A11y: Avoid using autofocus`,
});
}
@ -362,7 +347,7 @@ export default class Element extends Node {
if (name === 'scope' && this.name !== 'th') {
component.warn(attribute, {
code: `a11y-misplaced-scope`,
message: `A11y: The scope attribute should only be used with <th> elements`
message: `A11y: The scope attribute should only be used with <th> elements`,
});
}
@ -373,12 +358,11 @@ export default class Element extends Node {
if (!isNaN(value) && +value > 0) {
component.warn(attribute, {
code: `a11y-positive-tabindex`,
message: `A11y: avoid tabindex values above zero`
message: `A11y: avoid tabindex values above zero`,
});
}
}
if (/(^[0-9-.])|[\^$@%&#?!|()[\]{}^*+~;]/.test(name)) {
component.error(attribute, {
code: `illegal-attribute`,
@ -390,14 +374,14 @@ export default class Element extends Node {
if (!attribute.is_static) {
component.error(attribute, {
code: `invalid-slot-attribute`,
message: `slot attribute cannot have a dynamic value`
message: `slot attribute cannot have a dynamic value`,
});
}
if (component.slot_outlets.has(name)) {
component.error(attribute, {
code: `duplicate-slot-attribute`,
message: `Duplicate '${name}' slot`
message: `Duplicate '${name}' slot`,
});
component.slot_outlets.add(name);
@ -414,7 +398,7 @@ export default class Element extends Node {
if (name === 'is') {
component.warn(attribute, {
code: 'avoid-is',
message: `The 'is' attribute is not supported cross-browser and should be avoided`
message: `The 'is' attribute is not supported cross-browser and should be avoided`,
});
}
@ -431,21 +415,19 @@ export default class Element extends Node {
if (value === '' || value === '#') {
component.warn(attribute, {
code: `a11y-invalid-attribute`,
message: `A11y: '${value}' is not a valid ${attribute.name} attribute`
message: `A11y: '${value}' is not a valid ${attribute.name} attribute`,
});
}
} else {
component.warn(this, {
code: `a11y-missing-attribute`,
message: `A11y: <a> element should have an href attribute`
message: `A11y: <a> element should have an href attribute`,
});
}
}
else {
} else {
const required_attributes = a11y_required_attributes[this.name];
if (required_attributes) {
const has_attribute = required_attributes.some(name => attribute_map.has(name));
const has_attribute = required_attributes.some((name) => attribute_map.has(name));
if (!has_attribute) {
should_have_attribute(this, required_attributes);
@ -456,7 +438,7 @@ export default class Element extends Node {
const type = attribute_map.get('type');
if (type && type.get_static_value() === 'image') {
const required_attributes = ['alt', 'aria-label', 'aria-labelledby'];
const has_attribute = required_attributes.some(name => attribute_map.has(name));
const has_attribute = required_attributes.some((name) => attribute_map.has(name));
if (!has_attribute) {
should_have_attribute(this, required_attributes, 'input type="image"');
@ -470,16 +452,14 @@ export default class Element extends Node {
const { component } = this;
const check_type_attribute = () => {
const attribute = this.attributes.find(
(attribute: Attribute) => attribute.name === 'type'
);
const attribute = this.attributes.find((attribute: Attribute) => attribute.name === 'type');
if (!attribute) return null;
if (!attribute.is_static) {
component.error(attribute, {
code: `invalid-type`,
message: `'type' attribute cannot be dynamic if input uses two-way binding`
message: `'type' attribute cannot be dynamic if input uses two-way binding`,
});
}
@ -488,37 +468,31 @@ export default class Element extends Node {
if (value === true) {
component.error(attribute, {
code: `missing-type`,
message: `'type' attribute must be specified`
message: `'type' attribute must be specified`,
});
}
return value;
};
this.bindings.forEach(binding => {
this.bindings.forEach((binding) => {
const { name } = binding;
if (name === 'value') {
if (
this.name !== 'input' &&
this.name !== 'textarea' &&
this.name !== 'select'
) {
if (this.name !== 'input' && this.name !== 'textarea' && this.name !== 'select') {
component.error(binding, {
code: `invalid-binding`,
message: `'value' is not a valid binding on <${this.name}> elements`
message: `'value' is not a valid binding on <${this.name}> elements`,
});
}
if (this.name === 'select') {
const attribute = this.attributes.find(
(attribute: Attribute) => attribute.name === 'multiple'
);
const attribute = this.attributes.find((attribute: Attribute) => attribute.name === 'multiple');
if (attribute && !attribute.is_static) {
component.error(attribute, {
code: `dynamic-multiple-attribute`,
message: `'multiple' attribute cannot be dynamic if select uses two-way binding`
message: `'multiple' attribute cannot be dynamic if select uses two-way binding`,
});
}
} else {
@ -528,7 +502,7 @@ export default class Element extends Node {
if (this.name !== 'input') {
component.error(binding, {
code: `invalid-binding`,
message: `'${name}' is not a valid binding on <${this.name}> elements`
message: `'${name}' is not a valid binding on <${this.name}> elements`,
});
}
@ -543,7 +517,7 @@ export default class Element extends Node {
if (this.name !== 'input') {
component.error(binding, {
code: `invalid-binding`,
message: `'group' is not a valid binding on <${this.name}> elements`
message: `'group' is not a valid binding on <${this.name}> elements`,
});
}
@ -552,14 +526,14 @@ export default class Element extends Node {
if (type !== 'checkbox' && type !== 'radio') {
component.error(binding, {
code: `invalid-binding`,
message: `'group' binding can only be used with <input type="checkbox"> or <input type="radio">`
message: `'group' binding can only be used with <input type="checkbox"> or <input type="radio">`,
});
}
} else if (name === 'files') {
if (this.name !== 'input') {
component.error(binding, {
code: `invalid-binding`,
message: `'files' is not a valid binding on <${this.name}> elements`
message: `'files' is not a valid binding on <${this.name}> elements`,
});
}
@ -568,15 +542,14 @@ export default class Element extends Node {
if (type !== 'file') {
component.error(binding, {
code: `invalid-binding`,
message: `'files' binding can only be used with <input type="file">`
message: `'files' binding can only be used with <input type="file">`,
});
}
} else if (name === 'open') {
if (this.name !== 'details') {
component.error(binding, {
code: `invalid-binding`,
message: `'${name}' binding can only be used with <details>`
message: `'${name}' binding can only be used with <details>`,
});
}
} else if (
@ -594,59 +567,54 @@ export default class Element extends Node {
if (this.name !== 'audio' && this.name !== 'video') {
component.error(binding, {
code: `invalid-binding`,
message: `'${name}' binding can only be used with <audio> or <video>`
message: `'${name}' binding can only be used with <audio> or <video>`,
});
}
} else if (
name === 'videoHeight' ||
name === 'videoWidth'
) {
} else if (name === 'videoHeight' || name === 'videoWidth') {
if (this.name !== 'video') {
component.error(binding, {
code: `invalid-binding`,
message: `'${name}' binding can only be used with <video>`
message: `'${name}' binding can only be used with <video>`,
});
}
} else if (dimensions.test(name)) {
if (this.name === 'svg' && (name === 'offsetWidth' || name === 'offsetHeight')) {
component.error(binding, {
code: 'invalid-binding',
message: `'${binding.name}' is not a valid binding on <svg>. Use '${name.replace('offset', 'client')}' instead`
message: `'${binding.name}' is not a valid binding on <svg>. Use '${name.replace(
'offset',
'client'
)}' instead`,
});
} else if (svg.test(this.name)) {
component.error(binding, {
code: 'invalid-binding',
message: `'${binding.name}' is not a valid binding on SVG elements`
message: `'${binding.name}' is not a valid binding on SVG elements`,
});
} else if (is_void(this.name)) {
component.error(binding, {
code: 'invalid-binding',
message: `'${binding.name}' is not a valid binding on void elements like <${this.name}>. Use a wrapper element instead`
message: `'${binding.name}' is not a valid binding on void elements like <${this.name}>. Use a wrapper element instead`,
});
}
} else if (
name === 'textContent' ||
name === 'innerHTML'
) {
const contenteditable = this.attributes.find(
(attribute: Attribute) => attribute.name === 'contenteditable'
);
} else if (name === 'textContent' || name === 'innerHTML') {
const contenteditable = this.attributes.find((attribute: Attribute) => attribute.name === 'contenteditable');
if (!contenteditable) {
component.error(binding, {
code: `missing-contenteditable-attribute`,
message: `'contenteditable' attribute is required for textContent and innerHTML two-way bindings`
message: `'contenteditable' attribute is required for textContent and innerHTML two-way bindings`,
});
} else if (contenteditable && !contenteditable.is_static) {
component.error(contenteditable, {
code: `dynamic-contenteditable-attribute`,
message: `'contenteditable' attribute cannot be dynamic if element uses two-way binding`
message: `'contenteditable' attribute cannot be dynamic if element uses two-way binding`,
});
}
} else if (name !== 'this') {
component.error(binding, {
code: `invalid-binding`,
message: `'${binding.name}' is not a valid binding`
message: `'${binding.name}' is not a valid binding`,
});
}
});
@ -658,7 +626,7 @@ export default class Element extends Node {
if (this.children.length === 0) {
this.component.warn(this, {
code: `a11y-missing-content`,
message: `A11y: <${this.name}> element should have child content`
message: `A11y: <${this.name}> element should have child content`,
});
}
}
@ -666,19 +634,19 @@ export default class Element extends Node {
validate_event_handlers() {
const { component } = this;
this.handlers.forEach(handler => {
this.handlers.forEach((handler) => {
if (handler.modifiers.has('passive') && handler.modifiers.has('preventDefault')) {
component.error(handler, {
code: 'invalid-event-modifier',
message: `The 'passive' and 'preventDefault' modifiers cannot be used together`
message: `The 'passive' and 'preventDefault' modifiers cannot be used together`,
});
}
handler.modifiers.forEach(modifier => {
handler.modifiers.forEach((modifier) => {
if (!valid_modifiers.has(modifier)) {
component.error(handler, {
code: 'invalid-event-modifier',
message: `Valid event modifiers are ${list(Array.from(valid_modifiers))}`
message: `Valid event modifiers are ${list(Array.from(valid_modifiers))}`,
});
}
@ -687,25 +655,16 @@ export default class Element extends Node {
if (handler.can_make_passive) {
component.warn(handler, {
code: 'redundant-event-modifier',
message: `Touch event handlers that don't use the 'event' object are passive by default`
message: `Touch event handlers that don't use the 'event' object are passive by default`,
});
}
} else {
component.warn(handler, {
code: 'redundant-event-modifier',
message: `The passive modifier only works with wheel and touch events`
message: `The passive modifier only works with wheel and touch events`,
});
}
}
if (component.compile_options.legacy && (modifier === 'once' || modifier === 'passive')) {
// TODO this could be supported, but it would need a few changes to
// how event listeners work
component.error(handler, {
code: 'invalid-event-modifier',
message: `The '${modifier}' modifier cannot be used in legacy mode`
});
}
});
if (passive_events.has(handler.name) && handler.can_make_passive && !handler.modifiers.has('preventDefault')) {
@ -720,14 +679,14 @@ export default class Element extends Node {
}
add_css_class() {
if (this.attributes.some(attr => attr.is_spread)) {
if (this.attributes.some((attr) => attr.is_spread)) {
this.needs_manual_style_scoping = true;
return;
}
const { id } = this.component.stylesheet;
const class_attribute = this.attributes.find(a => a.name === 'class');
const class_attribute = this.attributes.find((a) => a.name === 'class');
if (class_attribute && !class_attribute.is_true) {
if (class_attribute.chunks.length === 1 && class_attribute.chunks[0].type === 'Text') {
@ -737,7 +696,7 @@ export default class Element extends Node {
new Text(this.component, this, this.scope, {
type: 'Text',
data: ` ${id}`,
synthetic: true
synthetic: true,
})
);
}
@ -746,26 +705,23 @@ export default class Element extends Node {
new Attribute(this.component, this, this.scope, {
type: 'Attribute',
name: 'class',
value: [{ type: 'Text', data: id, synthetic: true }]
value: [{ type: 'Text', data: id, synthetic: true }],
})
);
}
}
}
function should_have_attribute(
node,
attributes: string[],
name = node.name
) {
function should_have_attribute(node, attributes: string[], name = node.name) {
const article = /^[aeiou]/.test(attributes[0]) ? 'an' : 'a';
const sequence = attributes.length > 1 ?
attributes.slice(0, -1).join(', ') + ` or ${attributes[attributes.length - 1]}` :
attributes[0];
const sequence =
attributes.length > 1
? attributes.slice(0, -1).join(', ') + ` or ${attributes[attributes.length - 1]}`
: attributes[0];
node.component.warn(node, {
code: `a11y-missing-attribute`,
message: `A11y: <${name}> element should have ${article} ${sequence} attribute`
message: `A11y: <${name}> element should have ${article} ${sequence} attribute`,
});
}

@ -11,14 +11,17 @@ export interface BlockOptions {
renderer?: Renderer;
comment?: string;
key?: Identifier;
bindings?: Map<string, {
bindings?: Map<
string,
{
object: Identifier;
property: Identifier;
snippet: Node;
store: string;
tail: Node;
modifier: (node: Node) => Node;
}>;
}
>;
dependencies?: Set<string>;
}
@ -36,14 +39,17 @@ export default class Block {
dependencies: Set<string> = new Set();
bindings: Map<string, {
bindings: Map<
string,
{
object: Identifier;
property: Identifier;
snippet: Node;
store: string;
tail: Node;
modifier: (node: Node) => Node;
}>;
}
>;
chunks: {
declarations: Array<Node | Node[]>;
@ -157,7 +163,7 @@ export default class Block {
}
add_dependencies(dependencies: Set<string>) {
dependencies.forEach(dependency => {
dependencies.forEach((dependency) => {
this.dependencies.add(dependency);
});
@ -167,13 +173,7 @@ export default class Block {
}
}
add_element(
id: Identifier,
render_statement: Node,
claim_statement: Node,
parent_node: Node,
no_detach?: boolean
) {
add_element(id: Identifier, render_statement: Node, claim_statement: Node, parent_node: Node, no_detach?: boolean) {
this.add_variable(id);
this.chunks.create.push(b`${id} = ${render_statement};`);
@ -207,13 +207,14 @@ export default class Block {
add_variable(id: Identifier, init?: Node) {
if (this.variables.has(id.name)) {
throw new Error(
`Variable '${id.name}' already initialised with a different value`
);
throw new Error(`Variable '${id.name}' already initialised with a different value`);
}
this.variables.set(id.name, { id, init });
}
group_transition_out(fn) {
return this.has_outros ? b`@group_transition_out((#transition_out) => { ${fn(x`#transition_out`)} })` : fn(null);
}
alias(name: string) {
if (!this.aliases.has(name)) {
@ -263,11 +264,8 @@ export default class Block {
if (this.chunks.create.length === 0 && this.chunks.hydrate.length === 0) {
properties.create = noop;
} else {
const hydrate = this.chunks.hydrate.length > 0 && (
this.renderer.options.hydratable
? b`this.h();`
: this.chunks.hydrate
);
const hydrate =
this.chunks.hydrate.length > 0 && (this.renderer.options.hydratable ? b`this.h();` : this.chunks.hydrate);
properties.create = x`function #create() {
${this.chunks.create}
@ -393,23 +391,25 @@ export default class Block {
${this.chunks.declarations}
${Array.from(this.variables.values()).map(({ id, init }) => {
return init
? b`let ${id} = ${init}`
: b`let ${id}`;
return init ? b`let ${id} = ${init}` : b`let ${id}`;
})}
${this.chunks.init}
${dev
${
dev
? b`
const ${block} = ${return_value};
${version < 3.22 && b`@dispatch_dev$legacy("SvelteRegisterBlock", {
${
version < 3.22 &&
b`@dispatch_dev$legacy("SvelteRegisterBlock", {
block: ${block},
id: ${this.name || 'create_fragment'}.name,
type: "${this.type}",
source: "${this.comment ? this.comment.replace(/"/g, '\\"') : ''}",
ctx: #ctx
});`}
});`
}
return ${block};`
: b`
return ${return_value};`
@ -420,7 +420,8 @@ export default class Block {
}
has_content(): boolean {
return !!this.first ||
return (
!!this.first ||
this.event_listeners.length > 0 ||
this.chunks.intro.length > 0 ||
this.chunks.outro.length > 0 ||
@ -430,7 +431,8 @@ export default class Block {
this.chunks.mount.length > 0 ||
this.chunks.update.length > 0 ||
this.chunks.destroy.length > 0 ||
this.has_animation;
this.has_animation
);
}
render() {
@ -454,7 +456,7 @@ export default class Block {
if (this.event_listeners.length > 0) {
const dispose: Identifier = {
type: 'Identifier',
name: `#dispose${chunk}`
name: `#dispose${chunk}`,
};
this.add_variable(dispose);
@ -467,20 +469,16 @@ export default class Block {
`
);
this.chunks.destroy.push(
b`${dispose}();`
);
this.chunks.destroy.push(b`${dispose}();`);
} else {
this.chunks.mount.push(b`
if (#remount) @run_all(${dispose});
if (#remount) for(let #i=0;#i<${dispose}.length;#i++){ ${dispose}[#i];}
${dispose} = [
${this.event_listeners}
];
`);
this.chunks.destroy.push(
b`@run_all(${dispose});`
);
this.chunks.destroy.push(b`for(let #i=0;#i<${dispose}.length;#i++){ ${dispose}[#i];}`);
}
}
}

@ -8,10 +8,7 @@ import { invalidate } from './invalidate';
import Block from './Block';
import { ClassDeclaration, FunctionExpression, Node, Statement, ObjectExpression, Expression } from 'estree';
export default function dom(
component: Component,
options: CompileOptions
): { js: Node[]; css: CssResult } {
export default function dom(component: Component, options: CompileOptions): { js: Node[]; css: CssResult } {
const { name } = component;
const renderer = new Renderer(component, options);
@ -30,17 +27,14 @@ export default function dom(
}
const css = component.stylesheet.render(options.filename, !options.customElement);
const styles = component.stylesheet.has_styles && options.dev
const styles =
component.stylesheet.has_styles && options.dev
? `${css.code}\n/*# sourceMappingURL=${css.map.toUrl()} */`
: css.code;
const add_css = component.get_unique_name('add_css');
const should_add_css = (
!options.customElement &&
!!styles &&
options.css !== false
);
const should_add_css = !options.customElement && !!styles && options.css !== false;
if (should_add_css) {
body.push(b`
@ -57,12 +51,14 @@ export default function dom(
// TODO the deconflicted names of blocks are reversed... should set them here
const blocks = renderer.blocks.slice().reverse();
body.push(...blocks.map(block => {
body.push(
...blocks.map((block) => {
// TODO this is a horrible mess — renderer.blocks
// contains a mixture of Blocks and Nodes
if ((block as Block).render) return (block as Block).render();
return block;
}));
})
);
if (options.dev && !options.hydratable) {
block.chunks.claim.push(
@ -72,30 +68,44 @@ export default function dom(
const uses_props = component.var_lookup.has('$$props');
const uses_rest = component.var_lookup.has('$$restProps');
const $$props = uses_props || uses_rest ? `$$new_props` : `$$props`;
const props = component.vars.filter(variable => !variable.module && variable.export_name);
const writable_props = props.filter(variable => variable.writable);
const omit_props_names = component.get_unique_name('omit_props_names');
const compute_rest = x`@compute_rest_props($$props, ${omit_props_names.name})`;
const rest = uses_rest ? b`
const ${omit_props_names.name} = [${props.map(prop => `"${prop.export_name}"`).join(',')}];
let $$restProps = ${compute_rest};
` : null;
const set = (uses_props || uses_rest || writable_props.length > 0 || component.slots.size > 0)
? x`
${$$props} => {
${uses_props && renderer.invalidate('$$props', x`$$props = @assign(@assign({}, $$props), @exclude_internal_props($$new_props))`)}
${uses_rest && !uses_props && x`$$props = @assign(@assign({}, $$props), @exclude_internal_props($$new_props))`}
${uses_rest && renderer.invalidate('$$restProps', x`$$restProps = ${compute_rest}`)}
${writable_props.map(prop =>
b`if ('${prop.export_name}' in ${$$props}) ${renderer.invalidate(prop.name, x`${prop.name} = ${$$props}.${prop.export_name}`)};`
const uses_any = uses_props || uses_rest;
const $$props = uses_any ? `$$new_props` : `$$props`;
const props = component.vars.filter((variable) => !variable.module && variable.export_name);
const writable_props = props.filter((variable) => variable.writable);
const compute_rest = b`for (#k in $$props){ if (!#keys.has(#k) && $$props[#k][0] !== '$'){ $$restProps[#k] = $$props[#k];}}`;
const rest = uses_rest
? b`
let #k;
const #keys = new Set([${props.map((prop) => `"${prop.export_name}"`).join(',')}]);
let $$restProps = {};
${compute_rest};`
: null;
const set =
uses_any || writable_props.length || component.slots.size
? x`(${$$props}) => {
${
uses_any &&
b`
${!uses_rest && x`let #k;`}
for (#k in $$new_props) if ($$new_props[#k][0] !== '$') $$props[k] = $$new_props[k];
${uses_rest && compute_rest}
${renderer.invalidate(uses_props ? '$$props' : '$$restProps')}
`
}
${writable_props.map(
(prop) =>
b`if ('${prop.export_name}' in ${$$props}) ${renderer.invalidate(
prop.name,
x`${prop.name} = ${$$props}.${prop.export_name}`
)};`
)}
${component.slots.size > 0 &&
b`if ('$$scope' in ${$$props}) ${renderer.invalidate('$$scope', x`$$scope = ${$$props}.$$scope`)};`}
${
component.slots.size &&
b`if ('$$scope' in ${$$props}) ${renderer.invalidate('$$scope', x`$$scope = ${$$props}.$$scope`)};`
}
`
}`
: null;
const accessors = [];
@ -106,7 +116,7 @@ export default function dom(
let capture_state: Expression;
let props_inject: Node[] | Node;
props.forEach(prop => {
props.forEach((prop) => {
const variable = component.var_lookup.get(prop.name);
if (!variable.writable || component.component_options.accessors) {
@ -116,7 +126,7 @@ export default function dom(
key: { type: 'Identifier', name: prop.export_name },
value: x`function() {
return ${prop.hoistable ? prop.name : x`this.$$.ctx[${renderer.context_lookup.get(prop.name).index}]`}
}`
}`,
});
} else if (component.compile_options.dev) {
accessors.push({
@ -125,7 +135,7 @@ export default function dom(
key: { type: 'Identifier', name: prop.export_name },
value: x`function() {
throw new @_Error("<${component.tag}>: Props cannot be read directly from the component instance unless compiling with 'accessors: true' or '<svelte:options accessors/>'");
}`
}`,
});
}
@ -138,7 +148,7 @@ export default function dom(
value: x`function(${prop.name}) {
this.$set({ ${prop.export_name}: ${prop.name} });
@flush();
}`
}`,
});
} else if (component.compile_options.dev) {
accessors.push({
@ -147,7 +157,7 @@ export default function dom(
key: { type: 'Identifier', name: prop.export_name },
value: x`function(value) {
throw new @_Error("<${component.tag}>: Cannot set read-only property '${prop.export_name}'");
}`
}`,
});
}
} else if (component.compile_options.dev) {
@ -157,40 +167,42 @@ export default function dom(
key: { type: 'Identifier', name: prop.export_name },
value: x`function(value) {
throw new @_Error("<${component.tag}>: Props cannot be set directly on the component instance unless compiling with 'accessors: true' or '<svelte:options accessors/>'");
}`
}`,
});
}
});
if (component.compile_options.dev) {
// checking that expected ones were passed
const expected = props.filter(prop => prop.writable && !prop.initialised);
const expected = props.filter((prop) => prop.writable && !prop.initialised);
if (expected.length) {
dev_props_check = b`
const { ctx: #ctx } = this.$$;
const props = ${options.customElement ? x`this.attributes` : x`options.props || {}`};
${expected.map(prop => b`
${expected.map(
(prop) => b`
if (${renderer.reference(prop.name)} === undefined && !('${prop.export_name}' in props)) {
@_console.warn("<${component.tag}> was created without expected prop '${prop.export_name}'");
}`)}
}`
)}
`;
}
const capturable_vars = component.vars.filter(v => !v.internal && !v.global && !v.name.startsWith('$$'));
const capturable_vars = component.vars.filter((v) => !v.internal && !v.global && !v.name.startsWith('$$'));
if (capturable_vars.length > 0) {
capture_state = x`() => ({ ${capturable_vars.map(prop => p`${prop.name}`)} })`;
capture_state = x`() => ({ ${capturable_vars.map((prop) => p`${prop.name}`)} })`;
}
const injectable_vars = capturable_vars.filter(v => !v.module && v.writable && v.name[0] !== '$');
const injectable_vars = capturable_vars.filter((v) => !v.module && v.writable && v.name[0] !== '$');
if (uses_props || injectable_vars.length > 0) {
inject_state = x`
${$$props} => {
${uses_props && renderer.invalidate('$$props', x`$$props = @assign(@assign({}, $$props), $$new_props)`)}
${uses_props && renderer.invalidate('$$props', x`$$props = { ...$$props, ...$$new_props }`)}
${injectable_vars.map(
v => b`if ('${v.name}' in $$props) ${renderer.invalidate(v.name, x`${v.name} = ${$$props}.${v.name}`)};`
(v) => b`if ('${v.name}' in $$props) ${renderer.invalidate(v.name, x`${v.name} = ${$$props}.${v.name}`)};`
)}
}
`;
@ -242,16 +254,17 @@ export default function dom(
this.replace(invalidate(renderer, scope, node, names, execution_context === null));
}
}
},
});
component.rewrite_props(({ name, reassigned, export_name }) => {
const value = `$${name}`;
const i = renderer.context_lookup.get(`$${name}`).index;
const insert = (reassigned || export_name)
? b`${`$$subscribe_${name}`}()`
: b`@component_subscribe($$self, ${name}, #value => $$invalidate(${i}, ${value} = #value))`;
const insert =
reassigned || export_name
? b`${`$$subscribe_${name}`}();`
: b`$$self.$$.on_destroy.push(@subscribe(${name}, #value => $$invalidate(${i}, ${value} = #value)));`;
if (component.compile_options.dev) {
return b`@validate_store(${name}, '${name}'); ${insert}`;
@ -262,11 +275,8 @@ export default function dom(
}
const args = [x`$$self`];
const has_invalidate = props.length > 0 ||
component.has_reactive_assignments ||
component.slots.size > 0 ||
capture_state ||
inject_state;
const has_invalidate =
props.length > 0 || component.has_reactive_assignments || component.slots.size > 0 || capture_state || inject_state;
if (has_invalidate) {
args.push(x`$$props`, x`$$invalidate`);
} else if (component.compile_options.dev) {
@ -289,7 +299,7 @@ export default function dom(
${component.fully_hoisted}
`);
const filtered_props = props.filter(prop => {
const filtered_props = props.filter((prop) => {
const variable = component.var_lookup.get(prop.name);
if (variable.hoistable) return false;
@ -297,7 +307,7 @@ export default function dom(
return true;
});
const reactive_stores = component.vars.filter(variable => variable.name[0] === '$' && variable.name[1] !== '$');
const reactive_stores = component.vars.filter((variable) => variable.name[0] === '$' && variable.name[1] !== '$');
const instance_javascript = component.extract_javascript(component.ast.instance);
@ -312,7 +322,7 @@ export default function dom(
}
const initial_context = renderer.context.slice(0, i + 1);
const has_definition = (
const has_definition =
component.compile_options.dev ||
(instance_javascript && instance_javascript.length > 0) ||
filtered_props.length > 0 ||
@ -321,39 +331,40 @@ export default function dom(
initial_context.length > 0 ||
component.reactive_declarations.length > 0 ||
capture_state ||
inject_state
);
inject_state;
const definition = has_definition
? component.alias('instance')
: { type: 'Literal', value: null };
const definition = has_definition ? component.alias('instance') : { type: 'Literal', value: null };
const reactive_store_subscriptions = reactive_stores
.filter(store => {
.filter((store) => {
const variable = component.var_lookup.get(store.name.slice(1));
return !variable || variable.hoistable;
})
.map(({ name }) => b`
.map(
({ name }) => b`
${component.compile_options.dev && b`@validate_store(${name.slice(1)}, '${name.slice(1)}');`}
@component_subscribe($$self, ${name.slice(1)}, $$value => $$invalidate(${renderer.context_lookup.get(name).index}, ${name} = $$value));
`);
$$self.$$.on_destroy.push(@subscribe(${name.slice(1)}, #value => $$invalidate(${
renderer.context_lookup.get(name).index
}, ${name} = #value));
`
);
const resubscribable_reactive_store_unsubscribers = reactive_stores
.filter(store => {
.filter((store) => {
const variable = component.var_lookup.get(store.name.slice(1));
return variable && (variable.reassigned || variable.export_name);
})
.map(({ name }) => b`$$self.$$.on_destroy.push(() => ${`$$unsubscribe_${name.slice(1)}`}());`);
if (has_definition) {
const reactive_declarations: (Node | Node[]) = [];
const reactive_declarations: Node | Node[] = [];
const fixed_reactive_declarations = []; // not really 'reactive' but whatever
component.reactive_declarations.forEach(d => {
component.reactive_declarations.forEach((d) => {
const dependencies = Array.from(d.dependencies);
const uses_rest_or_props = !!dependencies.find(n => n === '$$props' || n === '$$restProps');
const uses_rest_or_props = !!dependencies.find((n) => n === '$$props' || n === '$$restProps');
const writable = dependencies.filter(n => {
const writable = dependencies.filter((n) => {
const variable = component.var_lookup.get(n);
return variable && (variable.export_name || variable.mutated || variable.reassigned);
});
@ -371,12 +382,12 @@ export default function dom(
}
});
const injected = Array.from(component.injected_reactive_declaration_vars).filter(name => {
const injected = Array.from(component.injected_reactive_declaration_vars).filter((name) => {
const variable = component.var_lookup.get(name);
return variable.injected && variable.name[0] !== '$';
});
const reactive_store_declarations = reactive_stores.map(variable => {
const reactive_store_declarations = reactive_stores.map((variable) => {
const $name = variable.name;
const name = $name.slice(1);
@ -395,19 +406,24 @@ export default function dom(
let unknown_props_check;
if (component.compile_options.dev && !(uses_props || uses_rest)) {
unknown_props_check = b`
const writable_props = [${writable_props.map(prop => x`'${prop.export_name}'`)}];
const writable_props = [${writable_props.map((prop) => x`'${prop.export_name}'`)}];
@_Object.keys($$props).forEach(key => {
if (!~writable_props.indexOf(key) && key.slice(0, 2) !== '$$') @_console.warn(\`<${component.tag}> was created with unknown prop '\${key}'\`);
if (!~writable_props.indexOf(key) && key.slice(0, 2) !== '$$') @_console.warn(\`<${
component.tag
}> was created with unknown prop '\${key}'\`);
});
`;
}
const return_value = {
type: 'ArrayExpression',
elements: initial_context.map(member => ({
elements: initial_context.map(
(member) =>
({
type: 'Identifier',
name: member.name
}) as Expression)
name: member.name,
} as Expression)
),
};
body.push(b`
@ -425,9 +441,14 @@ export default function dom(
${unknown_props_check}
${component.slots.size || component.compile_options.dev ? b`let { $$slots = {}, $$scope } = $$props;` : null}
${component.compile_options.dev && b`@validate_slots('${component.tag}', $$slots, [${[...component.slots.keys()].map(key => `'${key}'`).join(',')}]);`}
${
component.compile_options.dev &&
b`@validate_slots('${component.tag}', $$slots, [${[...component.slots.keys()]
.map((key) => `'${key}'`)
.join(',')}]);`
}
${renderer.binding_groups.length > 0 && b`const $$binding_groups = [${renderer.binding_groups.map(_ => x`[]`)}];`}
${renderer.binding_groups.length && b`const $$binding_groups = [${renderer.binding_groups.map((_) => x`[]`)}];`}
${component.partly_hoisted}
@ -437,19 +458,15 @@ export default function dom(
${inject_state && b`$$self.$inject_state = ${inject_state};`}
${injected.map(name => b`let ${name};`)}
${injected.map((name) => b`let ${name};`)}
${/* before reactive declarations */ props_inject}
${reactive_declarations.length > 0 && b`
$$self.$$.update = () => {
${reactive_declarations}
};
`}
${reactive_declarations.length && b`$$self.$$.update = () => {${reactive_declarations}};`}
${fixed_reactive_declarations}
${uses_props && b`$$props = @exclude_internal_props($$props);`}
${uses_props && b`let #k;for (#k in $$props) if ($$props[#k][0] === '$') delete $$props[#k];`}
return ${return_value};
}
@ -457,7 +474,9 @@ export default function dom(
}
const prop_indexes = x`{
${props.filter(v => v.export_name && !v.module).map(v => p`${v.export_name}: ${renderer.context_lookup.get(v.name).index}`)}
${props
.filter((v) => v.export_name && !v.module)
.map((v) => p`${v.export_name}: ${renderer.context_lookup.get(v.name).index}`)}
}` as ObjectExpression;
let dirty;
@ -474,9 +493,16 @@ export default function dom(
constructor(options) {
super();
${css.code && b`this.shadowRoot.innerHTML = \`<style>${css.code.replace(/\\/g, '\\\\')}${options.dev ? `\n/*# sourceMappingURL=${css.map.toUrl()} */` : ''}</style>\`;`}
${
css.code &&
b`this.shadowRoot.innerHTML = \`<style>${css.code.replace(/\\/g, '\\\\')}${
options.dev ? `\n/*# sourceMappingURL=${css.map.toUrl()} */` : ''
}</style>\`;`
}
@init(this, { target: this.shadowRoot }, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_indexes}, ${dirty});
@init(this, { target: this.shadowRoot }, ${definition}, ${
has_create_fragment ? 'create_fragment' : 'null'
}, ${not_equal}, ${prop_indexes}, ${dirty});
${dev_props_check}
@ -485,11 +511,14 @@ export default function dom(
@insert(options.target, this, options.anchor);
}
${(props.length > 0 || uses_props || uses_rest) && b`
${
(props.length > 0 || uses_props || uses_rest) &&
b`
if (options.props) {
this.$set(options.props);
@flush();
}`}
}`
}
}
}
}
@ -503,8 +532,8 @@ export default function dom(
computed: false,
key: { type: 'Identifier', name: 'observedAttributes' },
value: x`function() {
return [${props.map(prop => x`"${prop.export_name}"`)}];
}` as FunctionExpression
return [${props.map((prop) => x`"${prop.export_name}"`)}];
}` as FunctionExpression,
});
}
@ -520,7 +549,7 @@ export default function dom(
} else {
const superclass = {
type: 'Identifier',
name: options.dev ? '@SvelteComponentDev' : '@SvelteComponent'
name: options.dev ? '@SvelteComponentDev' : '@SvelteComponent',
};
const declaration = b`
@ -528,8 +557,14 @@ export default function dom(
constructor(options) {
super(${options.dev && `options`});
${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});
${options.dev && options.version < 3.22 && b`@dispatch_dev$legacy("SvelteRegisterComponent", { component: this, tagName: "${name.name}", options, id: create_fragment.name });`}
@init(this, options, ${definition}, ${
has_create_fragment ? 'create_fragment' : 'null'
}, ${not_equal}, ${prop_indexes}, ${dirty});
${
options.dev &&
options.version < 3.22 &&
b`@dispatch_dev$legacy("SvelteRegisterComponent", { component: this, tagName: "${name.name}", options, id: create_fragment.name });`
}
${dev_props_check}
}

@ -5,27 +5,32 @@ import { Node, Expression } from 'estree';
import Renderer from './Renderer';
import { Var } from '../../interfaces';
export function invalidate(renderer: Renderer, scope: Scope, node: Node, names: Set<string>, main_execution_context: boolean = false) {
export function invalidate(
renderer: Renderer,
scope: Scope,
node: Node,
names: Set<string>,
main_execution_context: boolean = false
) {
const { component } = renderer;
const [head, ...tail] = Array.from(names)
.filter(name => {
.filter((name) => {
const owner = scope.find_owner(name);
return !owner || owner === component.instance_scope;
})
.map(name => component.var_lookup.get(name))
.filter(variable => {
return variable && (
.map((name) => component.var_lookup.get(name))
.filter((variable) => {
return (
variable &&
!variable.hoistable &&
!variable.global &&
!variable.module &&
(
variable.referenced ||
(variable.referenced ||
variable.subscribable ||
variable.is_reactive_dependency ||
variable.export_name ||
variable.name[0] === '$'
)
variable.name[0] === '$')
);
}) as Var[];
@ -40,22 +45,26 @@ export function invalidate(renderer: Renderer, scope: Scope, node: Node, names:
if (head) {
component.has_reactive_assignments = true;
if (node.type === 'AssignmentExpression' && node.operator === '=' && nodes_match(node.left, node.right) && tail.length === 0) {
if (
!tail.length &&
node.type === 'AssignmentExpression' &&
node.operator === '=' &&
nodes_match(node.left, node.right)
) {
return get_invalidated(head, node);
} else {
const is_store_value = head.name[0] === '$' && head.name[1] !== '$';
const extra_args = tail.map(variable => get_invalidated(variable));
const extra_args = tail.map((variable) => get_invalidated(variable));
const pass_value = (
const pass_value =
extra_args.length > 0 ||
(node.type === 'AssignmentExpression' && node.left.type !== 'Identifier') ||
(node.type === 'UpdateExpression' && (!node.prefix || node.argument.type !== 'Identifier'))
);
(node.type === 'UpdateExpression' && (!node.prefix || node.argument.type !== 'Identifier'));
if (pass_value) {
extra_args.unshift({
type: 'Identifier',
name: head.name
name: head.name,
});
}

@ -33,7 +33,7 @@ class AwaitBlockBranch extends Wrapper {
this.block = block.child({
comment: create_debugging_comment(node, this.renderer.component),
name: this.renderer.component.get_unique_name(`create_${status}_block`),
type: status
type: status,
});
this.fragment = new FragmentWrapper(
@ -111,18 +111,10 @@ export default class AwaitBlockWrapper extends Wrapper {
let has_intros = false;
let has_outros = false;
['pending', 'then', 'catch'].forEach(status => {
['pending', 'then', 'catch'].forEach((status) => {
const child = this.node[status];
const branch = new AwaitBlockBranch(
status,
renderer,
block,
this,
child,
strip_whitespace,
next_sibling
);
const branch = new AwaitBlockBranch(status, renderer, block, this, child, strip_whitespace, next_sibling);
renderer.blocks.push(branch.block);
@ -138,7 +130,7 @@ export default class AwaitBlockWrapper extends Wrapper {
this[status] = branch;
});
['pending', 'then', 'catch'].forEach(status => {
['pending', 'then', 'catch'].forEach((status) => {
this[status].block.has_update_method = is_dynamic;
this[status].block.has_intro_method = has_intros;
this[status].block.has_outro_method = has_outros;
@ -149,11 +141,7 @@ export default class AwaitBlockWrapper extends Wrapper {
}
}
render(
block: Block,
parent_node: Identifier,
parent_nodes: Identifier
) {
render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes);
const update_mount_node = this.get_update_mount_node(anchor);
@ -222,9 +210,7 @@ export default class AwaitBlockWrapper extends Wrapper {
${promise} !== (${promise} = ${snippet}) &&
@handle_promise(${promise}, ${info})`;
block.chunks.update.push(
b`${info}.ctx = #ctx;`
);
block.chunks.update.push(b`${info}.ctx = #ctx;`);
if (this.pending.block.has_update_method) {
block.chunks.update.push(b`
@ -256,8 +242,7 @@ export default class AwaitBlockWrapper extends Wrapper {
if (this.pending.block.has_outro_method) {
block.chunks.outro.push(b`
for (let #i = 0; #i < 3; #i += 1) {
const block = ${info}.blocks[#i];
@transition_out(block);
@transition_out(${info}.blocks[#i]);
}
`);
}
@ -268,7 +253,7 @@ export default class AwaitBlockWrapper extends Wrapper {
${info} = null;
`);
[this.pending, this.then, this.catch].forEach(branch => {
[this.pending, this.then, this.catch].forEach((branch) => {
branch.render(branch.block, null, x`#nodes` as Identifier);
});
this.then.render_destructure(block, this.value, this.node.value, value_index);

@ -407,33 +407,38 @@ export default class EachBlockWrapper extends Wrapper {
const dynamic = this.block.has_update_method;
const destroy = this.node.has_animation
? this.block.has_outros
? `@fix_and_outro_and_destroy_block`
: `@fix_and_destroy_block`
: this.block.has_outros
? `@outro_and_destroy_block`
: `@destroy_block`;
// const destroy = this.node.has_animation
// ? this.block.has_outros
// ? `@fix_and_outro_and_destroy_block`
// : `@fix_and_destroy_block`
// : this.block.has_outros
// ? `@outro_and_destroy_block`
// : `@destroy_block`;
if (this.dependencies.size) {
this.updates.push(b`
this.updates.push(
b`
const ${this.vars.each_block_value} = ${snippet};
${this.renderer.options.dev && b`@validate_each_argument(${this.vars.each_block_value});`}
${this.block.has_outros && b`@group_outros();`}
${this.renderer.options.dev && b`@validate_each_argument(${this.vars.each_block_value});`}`,
this.block.group_transition_out(
(transition_out) => b`
${this.node.has_animation && b`for (let #i = 0; #i < ${view_length}; #i += 1){ ${iterations}[#i].r();}`}
${
this.renderer.options.dev &&
b`@validate_each_keys(#ctx, ${this.vars.each_block_value}, ${this.vars.get_each_context}, ${get_key});`
}
${iterations} = @update_keyed_each(${iterations}, #dirty, ${get_key}, ${dynamic ? 1 : 0}, #ctx, ${
${iterations} = @update_keyed_each(${iterations}, #dirty, #ctx, ${bit_state([
dynamic,
this.node.has_animation,
this.block.has_outros,
])}, ${get_key}, ${
this.vars.each_block_value
}, ${lookup}, ${update_mount_node}, ${destroy}, ${create_each_block}, ${update_anchor_node}, ${
}, ${lookup}, ${update_mount_node}, ${create_each_block}, ${update_anchor_node}, ${
this.vars.get_each_context
});
${this.node.has_animation && b`for (let #i = 0; #i < ${view_length}; #i += 1){ ${iterations}[#i].a();}`}
${this.block.has_outros && b`@check_outros();`}
`);
}, ${transition_out});
${this.node.has_animation && b`for (let #i = 0; #i < ${view_length}; #i += 1){ ${iterations}[#i].a();}`}`
)
);
}
if (this.block.has_outros) {
@ -533,33 +538,21 @@ export default class EachBlockWrapper extends Wrapper {
const start = this.block.has_update_method ? 0 : `#old_length`;
let remove_old_blocks;
if (this.block.has_outros) {
const out = block.get_unique_name('out');
block.chunks.init.push(b`
const ${out} = i => @transition_out(${iterations}[i], 1, 1, () => {
${iterations}[i] = null;
});
`);
remove_old_blocks = b`
@group_outros();
const remove_old_blocks = this.block.group_transition_out((transition_out) =>
transition_out
? b`
for (#i = ${data_length}; #i < ${view_length}; #i += 1) {
${out}(#i);
}
@check_outros();
`;
} else {
remove_old_blocks = b`
for (${this.block.has_update_method ? null : x`#i = ${data_length}`}; #i < ${
this.block.has_update_method ? view_length : '#old_length'
}; #i += 1) {
${transition_out}(${iterations}[#i], () => { ${iterations}[#i] = null; });
}`
: b`
for (${this.block.has_update_method ? null : x`#i = ${data_length}`};
#i < ${this.block.has_update_method ? view_length : '#old_length'};
#i += 1 ) {
${iterations}[#i].d(1);
}
${!fixed_length && b`${view_length} = ${data_length};`}
`;
}
`
);
// We declare `i` as block scoped here, as the `remove_old_blocks` code
// may rely on continuing where this iteration stopped.
@ -593,3 +586,4 @@ export default class EachBlockWrapper extends Wrapper {
block.chunks.destroy.push(b`@destroy_each(${iterations}, detaching);`);
}
}
const bit_state = (arr) => arr.reduce((state, bool, index) => (bool ? (state |= 1 << index) : state), 0);

@ -29,7 +29,7 @@ export default class AttributeWrapper {
select = select.parent;
if (select && select.select_binding_dependencies) {
select.select_binding_dependencies.forEach(prop => {
select.select_binding_dependencies.forEach((prop) => {
this.node.dependencies.forEach((dependency: string) => {
this.parent.renderer.component.indirect_dependencies.get(prop).add(dependency);
});
@ -42,13 +42,11 @@ export default class AttributeWrapper {
is_indirectly_bound_value() {
const element = this.parent;
const name = fix_attribute_casing(this.node.name);
return name === 'value' &&
return (
name === 'value' &&
(element.node.name === 'option' || // TODO check it's actually bound
(element.node.name === 'input' &&
element.node.bindings.some(
(binding) =>
/checked|group/.test(binding.name)
)));
(element.node.name === 'input' && element.node.bindings.some((binding) => /checked|group/.test(binding.name))))
);
}
render(block: Block) {
@ -59,9 +57,7 @@ export default class AttributeWrapper {
const is_indirectly_bound_value = this.is_indirectly_bound_value();
const property_name = is_indirectly_bound_value
? '__value'
: metadata && metadata.property_name;
const property_name = is_indirectly_bound_value ? '__value' : metadata && metadata.property_name;
// xlink is a special case... we could maybe extend this to generic
// namespaced attributes but I'm not sure that's applicable in
@ -72,34 +68,25 @@ export default class AttributeWrapper {
? '@xlink_attr'
: '@attr';
const is_legacy_input_type = element.renderer.component.compile_options.legacy && name === 'type' && this.parent.node.name === 'input';
const dependencies = this.node.get_dependencies();
const value = this.get_value(block);
const is_src = this.node.name === 'src'; // TODO retire this exception in favour of https://github.com/sveltejs/svelte/issues/3750
const is_select_value_attribute =
name === 'value' && element.node.name === 'select';
const is_select_value_attribute = name === 'value' && element.node.name === 'select';
const is_input_value = name === 'value' && element.node.name === 'input';
const should_cache = is_src || this.node.should_cache() || is_select_value_attribute; // TODO is this necessary?
const last = should_cache && block.get_unique_name(
`${element.var.name}_${name.replace(/[^a-zA-Z_$]/g, '_')}_value`
);
const last =
should_cache && block.get_unique_name(`${element.var.name}_${name.replace(/[^a-zA-Z_$]/g, '_')}_value`);
if (should_cache) block.add_variable(last);
let updater;
const init = should_cache ? x`${last} = ${value}` : value;
if (is_legacy_input_type) {
block.chunks.hydrate.push(
b`@set_input_type(${element.var}, ${init});`
);
updater = b`@set_input_type(${element.var}, ${should_cache ? last : value});`;
} else if (is_select_value_attribute) {
if (is_select_value_attribute) {
// annoying special case
const is_multiple_select = element.node.get_static_attribute_value('multiple');
const i = block.get_unique_name('i');
@ -127,21 +114,15 @@ export default class AttributeWrapper {
${updater}
`);
} else if (is_src) {
block.chunks.hydrate.push(
b`if (${element.var}.src !== ${init}) ${method}(${element.var}, "${name}", ${last});`
);
block.chunks.hydrate.push(b`if (${element.var}.src !== ${init}) ${method}(${element.var}, "${name}", ${last});`);
updater = b`${method}(${element.var}, "${name}", ${should_cache ? last : value});`;
} else if (property_name) {
block.chunks.hydrate.push(
b`${element.var}.${property_name} = ${init};`
);
block.chunks.hydrate.push(b`${element.var}.${property_name} = ${init};`);
updater = block.renderer.options.dev
? b`@prop_dev(${element.var}, "${property_name}", ${should_cache ? last : value});`
: b`${element.var}.${property_name} = ${should_cache ? last : value};`;
} else {
block.chunks.hydrate.push(
b`${method}(${element.var}, "${name}", ${init});`
);
block.chunks.hydrate.push(b`${method}(${element.var}, "${name}", ${init});`);
updater = b`${method}(${element.var}, "${name}", ${should_cache ? last : value});`;
}
@ -157,7 +138,7 @@ export default class AttributeWrapper {
if (is_input_value) {
const type = element.node.get_static_attribute_value('type');
if (type === null || type === "" || type === "text" || type === "email" || type === "password") {
if (type === null || type === '' || type === 'text' || type === 'email' || type === 'password') {
condition = x`${condition} && ${element.var}.${property_name} !== ${should_cache ? last : value}`;
}
}
@ -210,7 +191,8 @@ export default class AttributeWrapper {
: (this.node.chunks[0] as Expression).manipulate(block);
}
let value = this.node.name === 'class'
let value =
this.node.name === 'class'
? this.get_class_name_text(block)
: this.render_chunks(block).reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
@ -228,7 +210,7 @@ export default class AttributeWrapper {
if (scoped_css && rendered.length === 2) {
// we have a situation like class={possiblyUndefined}
rendered[0] = x`@null_to_empty(${rendered[0]})`;
rendered[0] = x`${rendered[0]} ?? ""`;
}
return rendered.reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
@ -250,11 +232,11 @@ export default class AttributeWrapper {
const value = this.node.chunks;
if (value.length === 0) return `=""`;
return `="${value.map(chunk => {
return chunk.type === 'Text'
? chunk.data.replace(/"/g, '\\"')
: `\${${chunk.manipulate()}}`;
}).join('')}"`;
return `="${value
.map((chunk) => {
return chunk.type === 'Text' ? chunk.data.replace(/"/g, '\\"') : `\${${chunk.manipulate()}}`;
})
.join('')}"`;
}
}
@ -270,16 +252,7 @@ const attribute_lookup = {
default: { applies_to: ['track'] },
defer: { applies_to: ['script'] },
disabled: {
applies_to: [
'button',
'fieldset',
'input',
'keygen',
'optgroup',
'option',
'select',
'textarea',
],
applies_to: ['button', 'fieldset', 'input', 'keygen', 'optgroup', 'option', 'select', 'textarea'],
},
formnovalidate: { property_name: 'formNoValidate', applies_to: ['button', 'input'] },
hidden: {},
@ -297,21 +270,11 @@ const attribute_lookup = {
reversed: { applies_to: ['ol'] },
selected: { applies_to: ['option'] },
value: {
applies_to: [
'button',
'option',
'input',
'li',
'meter',
'progress',
'param',
'select',
'textarea',
],
applies_to: ['button', 'option', 'input', 'li', 'meter', 'progress', 'param', 'select', 'textarea'],
},
};
Object.keys(attribute_lookup).forEach(name => {
Object.keys(attribute_lookup).forEach((name) => {
const metadata = attribute_lookup[name];
if (!metadata.property_name) metadata.property_name = name;
});
@ -342,5 +305,5 @@ const boolean_attribute = new Set([
'readonly',
'required',
'reversed',
'selected'
'selected',
]);

@ -27,11 +27,13 @@ export default class EventHandlerWrapper {
}
get_snippet(block) {
const snippet = this.node.expression ? this.node.expression.manipulate(block) : block.renderer.reference(this.node.handler_name);
const snippet = this.node.expression
? this.node.expression.manipulate(block)
: block.renderer.reference(this.node.handler_name);
if (this.node.reassigned) {
block.maintain_context = true;
return x`function () { if (@is_function(${snippet})) ${snippet}.apply(this, arguments); }`;
return x`function () { if ("function" === typeof (${snippet})) ${snippet}.apply(this, arguments); }`;
}
return snippet;
}
@ -45,11 +47,9 @@ export default class EventHandlerWrapper {
const args = [];
const opts = ['passive', 'once', 'capture'].filter(mod => this.node.modifiers.has(mod));
const opts = ['passive', 'once', 'capture'].filter((mod) => this.node.modifiers.has(mod));
if (opts.length) {
args.push((opts.length === 1 && opts[0] === 'capture')
? TRUE
: x`{ ${opts.map(opt => p`${opt}: true`)} }`);
args.push(opts.length === 1 && opts[0] === 'capture' ? TRUE : x`{ ${opts.map((opt) => p`${opt}: true`)} }`);
} else if (block.renderer.options.dev) {
args.push(FALSE);
}
@ -59,8 +59,6 @@ export default class EventHandlerWrapper {
args.push(this.node.modifiers.has('stopPropagation') ? TRUE : FALSE);
}
block.event_listeners.push(
x`@listen(${target}, "${this.node.name}", ${snippet}, ${args})`
);
block.event_listeners.push(x`@listen(${target}, "${this.node.name}", ${snippet}, ${args})`);
}
}

@ -372,7 +372,9 @@ export default class ElementWrapper extends Wrapper {
if (renderer.options.dev) {
const loc = renderer.locate(this.node.start);
block.chunks.hydrate.push(
b`@add_location(${this.var}, ${renderer.file_var}, ${loc.line - 1}, ${loc.column}, ${this.node.start});`
b`@add_location_dev$legacy(${this.var}, ${renderer.file_var}, ${loc.line - 1}, ${loc.column}, ${
this.node.start
});`
);
}
}
@ -387,7 +389,7 @@ export default class ElementWrapper extends Wrapper {
get_render_statement(block: Block) {
const { name, namespace } = this.node;
if (namespace === 'http://www.w3.org/2000/svg') {
if (namespace === namespaces.svg) {
return x`@svg_element("${name}")`;
}
@ -654,11 +656,10 @@ export default class ElementWrapper extends Wrapper {
});
block.chunks.init.push(b`
let ${levels} = [${initial_props}];
let ${data} = {};
for (let #i = 0; #i < ${levels}.length; #i += 1) {
${data} = @assign(${data}, ${levels}[#i]);
const ${levels} = [${initial_props}];
const ${data} = ${levels}[0]||{};
for (let #i = 1; #i < ${levels}.length; #i += 1) {
${data} = { ...${data}, ...${levels}[#i] };
}
`);
@ -669,15 +670,14 @@ export default class ElementWrapper extends Wrapper {
block.chunks.update.push(b`${fn}(${this.var}, @get_spread_update(${levels}, [${updates}]));`);
}
add_bidi_transition(block: Block, intro: Transition) {
const name = block.get_unique_name(`${this.var.name}_bidi_transition`);
const snippet = intro.expression ? intro.expression.manipulate(block) : x`{}`;
const name = block.get_unique_name(`${this.var.name}_transition`);
const snippet = intro.expression ? intro.expression.manipulate(block) : null;
block.add_variable(name);
const fn = this.renderer.reference(intro.name);
let intro_block = b`${name} = @run_bidirectional_transition(${this.var}, ${fn}, true, ${snippet});`;
let outro_block = b`${name} = @run_bidirectional_transition(${this.var}, ${fn}, false, ${snippet});`;
if (intro.is_local) {

@ -16,7 +16,6 @@ import Window from './Window';
import { INode } from '../../nodes/interfaces';
import Renderer from '../Renderer';
import Block from '../Block';
import { trim_start, trim_end } from '../../../utils/trim';
import { link } from '../../../utils/link';
import { Identifier } from 'estree';
@ -36,14 +35,17 @@ const wrappers = {
Slot,
Text,
Title,
Window
Window,
};
function trimmable_at(child: INode, next_sibling: Wrapper): boolean {
// Whitespace is trimmable if one of the following is true:
// The child and its sibling share a common nearest each block (not at an each block boundary)
// The next sibling's previous node is an each block
return (next_sibling.node.find_nearest(/EachBlock/) === child.find_nearest(/EachBlock/)) || next_sibling.node.prev.type === 'EachBlock';
return (
next_sibling.node.find_nearest(/EachBlock/) === child.find_nearest(/EachBlock/) ||
next_sibling.node.prev.type === 'EachBlock'
);
}
export default class FragmentWrapper {
@ -87,12 +89,14 @@ export default class FragmentWrapper {
// We want to remove trailing whitespace inside an element/component/block,
// *unless* there is no whitespace between this node and its next sibling
if (this.nodes.length === 0) {
const should_trim = (
next_sibling ? (next_sibling.node.type === 'Text' && /^\s/.test(next_sibling.node.data) && trimmable_at(child, next_sibling)) : !child.has_ancestor('EachBlock')
);
const should_trim = next_sibling
? next_sibling.node.type === 'Text' &&
/^\s/.test(next_sibling.node.data) &&
trimmable_at(child, next_sibling)
: !child.has_ancestor('EachBlock');
if (should_trim) {
data = trim_end(data);
data = data.trimRight();
if (!data) continue;
}
}
@ -108,7 +112,7 @@ export default class FragmentWrapper {
this.nodes.unshift(wrapper);
link(last_child, last_child = wrapper);
link(last_child, (last_child = wrapper));
} else {
const Wrapper = wrappers[child.type];
if (!Wrapper) continue;
@ -116,7 +120,7 @@ export default class FragmentWrapper {
const wrapper = new Wrapper(renderer, block, parent, child, strip_whitespace, last_child || next_sibling);
this.nodes.unshift(wrapper);
link(last_child, last_child = wrapper);
link(last_child, (last_child = wrapper));
}
}
@ -124,7 +128,7 @@ export default class FragmentWrapper {
const first = this.nodes[0] as Text;
if (first && first.node.type === 'Text') {
first.data = trim_start(first.data);
first.data = first.data.trimLeft();
if (!first.data) {
first.var = null;
this.nodes.shift();

@ -22,29 +22,22 @@ export default class HeadWrapper extends Wrapper {
this.can_use_innerhtml = false;
this.fragment = new FragmentWrapper(
renderer,
block,
node.children,
this,
strip_whitespace,
next_sibling
);
this.fragment = new FragmentWrapper(renderer, block, node.children, this, strip_whitespace, next_sibling);
}
render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) {
let nodes;
if (this.renderer.options.hydratable && this.fragment.nodes.length) {
nodes = block.get_unique_name('head_nodes');
block.chunks.claim.push(b`const ${nodes} = @query_selector_all('[data-svelte="${this.node.id}"]', @_document.head);`);
block.chunks.claim.push(
b`const ${nodes} = Array.from((@_document.head||document.body).querySelectorAll('[data-svelte="${this.node.id}"]'));`
);
}
this.fragment.render(block, x`@_document.head` as unknown as Identifier, nodes);
this.fragment.render(block, (x`@_document.head` as unknown) as Identifier, nodes);
if (nodes && this.renderer.options.hydratable) {
block.chunks.claim.push(
b`${nodes}.forEach(@detach);`
);
block.chunks.claim.push(b`${nodes}.forEach(@detach);`);
}
}
}

@ -12,9 +12,7 @@ import { is_head } from './shared/is_head';
import { Identifier, Node, UnaryExpression } from 'estree';
function is_else_if(node: ElseBlock) {
return (
node && node.children.length === 1 && node.children[0].type === 'IfBlock'
);
return node && node.children.length === 1 && node.children[0].type === 'IfBlock';
}
class IfBlockBranch extends Wrapper {
@ -37,7 +35,7 @@ class IfBlockBranch extends Wrapper {
) {
super(renderer, block, parent, node);
const { expression } = (node as IfBlock);
const { expression } = node as IfBlock;
const is_else = !expression;
if (expression) {
@ -51,12 +49,12 @@ class IfBlockBranch extends Wrapper {
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
should_cache = true;
}
}
},
});
if (should_cache) {
this.condition = block.get_unique_name(`show_if`);
this.snippet = (expression.manipulate(block) as Node);
this.snippet = expression.manipulate(block) as Node;
} else {
this.condition = expression.manipulate(block);
}
@ -64,10 +62,8 @@ class IfBlockBranch extends Wrapper {
this.block = block.child({
comment: create_debugging_comment(node, parent.renderer.component),
name: parent.renderer.component.get_unique_name(
is_else ? `create_else_block` : `create_if_block`
),
type: (node as IfBlock).expression ? 'if' : 'else'
name: parent.renderer.component.get_unique_name(is_else ? `create_else_block` : `create_if_block`),
type: (node as IfBlock).expression ? 'if' : 'else',
});
this.fragment = new FragmentWrapper(renderer, this.block, node.children, parent, strip_whitespace, next_sibling);
@ -104,14 +100,7 @@ export default class IfBlockWrapper extends Wrapper {
let has_outros = false;
const create_branches = (node: IfBlock) => {
const branch = new IfBlockBranch(
renderer,
block,
this,
node,
strip_whitespace,
next_sibling
);
const branch = new IfBlockBranch(renderer, block, this, node, strip_whitespace, next_sibling);
this.branches.push(branch);
@ -135,14 +124,7 @@ export default class IfBlockWrapper extends Wrapper {
if (is_else_if(node.else)) {
create_branches(node.else.children[0] as IfBlock);
} else if (node.else) {
const branch = new IfBlockBranch(
renderer,
block,
this,
node.else,
strip_whitespace,
next_sibling
);
const branch = new IfBlockBranch(renderer, block, this, node.else, strip_whitespace, next_sibling);
this.branches.push(branch);
@ -160,7 +142,7 @@ export default class IfBlockWrapper extends Wrapper {
create_branches(this.node);
blocks.forEach(block => {
blocks.forEach((block) => {
block.has_update_method = is_dynamic;
block.has_intro_method = has_intros;
block.has_outro_method = has_outros;
@ -169,11 +151,7 @@ export default class IfBlockWrapper extends Wrapper {
renderer.blocks.push(...blocks);
}
render(
block: Block,
parent_node: Identifier,
parent_nodes: Identifier
) {
render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
const name = this.var;
const needs_anchor = this.next ? !this.next.is_dom_node() : !parent_node || !this.parent.is_dom_node();
@ -181,7 +159,7 @@ export default class IfBlockWrapper extends Wrapper {
? block.get_unique_name(`${this.var.name}_anchor`)
: (this.next && this.next.var) || 'null';
const has_else = !(this.branches[this.branches.length - 1].condition);
const has_else = !this.branches[this.branches.length - 1].condition;
const if_exists_condition = has_else ? null : name;
const dynamic = this.branches[0].block.has_update_method; // can use [0] as proxy for all, since they necessarily have the same value
@ -194,7 +172,7 @@ export default class IfBlockWrapper extends Wrapper {
const detaching = parent_node && !is_head(parent_node) ? null : 'detaching';
if (this.node.else) {
this.branches.forEach(branch => {
this.branches.forEach((branch) => {
if (branch.snippet) block.add_variable(branch.condition);
});
@ -221,13 +199,9 @@ export default class IfBlockWrapper extends Wrapper {
if (parent_nodes && this.renderer.options.hydratable) {
if (if_exists_condition) {
block.chunks.claim.push(
b`if (${if_exists_condition}) ${name}.l(${parent_nodes});`
);
block.chunks.claim.push(b`if (${if_exists_condition}) ${name}.l(${parent_nodes});`);
} else {
block.chunks.claim.push(
b`${name}.l(${parent_nodes});`
);
block.chunks.claim.push(b`${name}.l(${parent_nodes});`);
}
}
@ -236,16 +210,11 @@ export default class IfBlockWrapper extends Wrapper {
}
if (needs_anchor) {
block.add_element(
anchor as Identifier,
x`@empty()`,
parent_nodes && x`@empty()`,
parent_node
);
block.add_element(anchor as Identifier, x`@empty()`, parent_nodes && x`@empty()`, parent_node);
}
this.branches.forEach(branch => {
branch.fragment.render(branch.block, null, x`#nodes` as unknown as Identifier);
this.branches.forEach((branch) => {
branch.fragment.render(branch.block, null, (x`#nodes` as unknown) as Identifier);
});
}
@ -266,23 +235,26 @@ export default class IfBlockWrapper extends Wrapper {
if (this.needs_update) {
block.chunks.init.push(b`
function ${select_block_type}(#ctx, #dirty) {
${this.branches.map(({ dependencies, condition, snippet, block }) => condition
${this.branches.map(({ dependencies, condition, snippet, block }) =>
condition
? b`
${snippet && (
dependencies.length > 0
${
snippet &&
(dependencies.length > 0
? b`if (${condition} == null || ${block.renderer.dirty(dependencies)}) ${condition} = !!${snippet}`
: b`if (${condition} == null) ${condition} = !!${snippet}`
)}
: b`if (${condition} == null) ${condition} = !!${snippet}`)
}
if (${condition}) return ${block.name};`
: b`return ${block.name};`)}
: b`return ${block.name};`
)}
}
`);
} else {
block.chunks.init.push(b`
function ${select_block_type}(#ctx, #dirty) {
${this.branches.map(({ condition, snippet, block }) => condition
? b`if (${snippet || condition}) return ${block.name};`
: b`return ${block.name};`)}
${this.branches.map(({ condition, snippet, block }) =>
condition ? b`if (${snippet || condition}) return ${block.name};` : b`return ${block.name};`
)}
}
`);
}
@ -296,13 +268,9 @@ export default class IfBlockWrapper extends Wrapper {
const anchor_node = parent_node ? 'null' : 'anchor';
if (if_exists_condition) {
block.chunks.mount.push(
b`if (${if_exists_condition}) ${name}.m(${initial_mount_node}, ${anchor_node});`
);
block.chunks.mount.push(b`if (${if_exists_condition}) ${name}.m(${initial_mount_node}, ${anchor_node});`);
} else {
block.chunks.mount.push(
b`${name}.m(${initial_mount_node}, ${anchor_node});`
);
block.chunks.mount.push(b`${name}.m(${initial_mount_node}, ${anchor_node});`);
}
if (this.needs_update) {
@ -371,42 +339,47 @@ export default class IfBlockWrapper extends Wrapper {
const if_blocks = block.get_unique_name(`if_blocks`);
const if_current_block_type_index = has_else
? nodes => nodes
: nodes => b`if (~${current_block_type_index}) { ${nodes} }`;
? (nodes) => nodes
: (nodes) => b`if (~${current_block_type_index}) { ${nodes} }`;
block.add_variable(current_block_type_index);
block.add_variable(name);
block.chunks.init.push(b`
const ${if_block_creators} = [
${this.branches.map(branch => branch.block.name)}
${this.branches.map((branch) => branch.block.name)}
];
const ${if_blocks} = [];
${this.needs_update
${
this.needs_update
? b`
function ${select_block_type}(#ctx, #dirty) {
${this.branches.map(({ dependencies, condition, snippet }, i) => condition
${this.branches.map(({ dependencies, condition, snippet }, i) =>
condition
? b`
${snippet && (
dependencies.length > 0
${
snippet &&
(dependencies.length > 0
? b`if (${block.renderer.dirty(dependencies)}) ${condition} = !!${snippet}`
: b`if (${condition} == -1) ${condition} = !!${snippet}`
)}
: b`if (${condition} == -1) ${condition} = !!${snippet}`)
}
if (${condition}) return ${i};`
: b`return ${i};`)}
: b`return ${i};`
)}
${!has_else && b`return -1;`}
}
`
: b`
function ${select_block_type}(#ctx, #dirty) {
${this.branches.map(({ condition, snippet }, i) => condition
? b`if (${snippet || condition}) return ${i};`
: b`return ${i};`)}
${this.branches.map(({ condition, snippet }, i) =>
condition ? b`if (${snippet || condition}) return ${i};` : b`return ${i};`
)}
${!has_else && b`return -1;`}
}
`}
`
}
`);
if (has_else) {
@ -434,13 +407,10 @@ export default class IfBlockWrapper extends Wrapper {
if (this.needs_update) {
const update_mount_node = this.get_update_mount_node(anchor);
const destroy_old_block = b`
@group_outros();
@transition_out(${if_blocks}[${previous_block_index}], 1, 1, () => {
${if_blocks}[${previous_block_index}] = null;
});
@check_outros();
`;
const destroy_old_block = block.group_transition_out(
(transition_out) =>
b`${transition_out}(${if_blocks}[${previous_block_index}], () => {${if_blocks}[${previous_block_index}] = null;})`
);
const create_new_block = b`
${name} = ${if_blocks}[${current_block_type_index}];
@ -455,7 +425,6 @@ export default class IfBlockWrapper extends Wrapper {
const change_block = has_else
? b`
${destroy_old_block}
${create_new_block}
`
: b`
@ -521,9 +490,7 @@ export default class IfBlockWrapper extends Wrapper {
const initial_mount_node = parent_node || '#target';
const anchor_node = parent_node ? 'null' : 'anchor';
block.chunks.mount.push(
b`if (${name}) ${name}.m(${initial_mount_node}, ${anchor_node});`
);
block.chunks.mount.push(b`if (${name}) ${name}.m(${initial_mount_node}, ${anchor_node});`);
if (branch.dependencies.length > 0) {
const update_mount_node = this.get_update_mount_node(anchor);
@ -546,7 +513,9 @@ export default class IfBlockWrapper extends Wrapper {
`;
if (branch.snippet) {
block.chunks.update.push(b`if (${block.renderer.dirty(branch.dependencies)}) ${branch.condition} = ${branch.snippet}`);
block.chunks.update.push(
b`if (${block.renderer.dirty(branch.dependencies)}) ${branch.condition} = ${branch.snippet}`
);
}
// no `p()` here — we don't want to update outroing nodes,
@ -556,11 +525,7 @@ export default class IfBlockWrapper extends Wrapper {
if (${branch.condition}) {
${enter}
} else if (${name}) {
@group_outros();
@transition_out(${name}, 1, 1, () => {
${name} = null;
});
@check_outros();
${block.group_transition_out((transition_out) => b`${transition_out}(${name},() => {${name} = null;})`)}
}
`);
} else {

@ -240,12 +240,10 @@ export default class InlineComponentWrapper extends Wrapper {
if (attr.is_spread) {
const value = attr.expression.manipulate(block);
initial_props.push(value);
let value_object = value;
if (attr.expression.node.type !== 'ObjectExpression') {
value_object = x`@get_spread_object(${value})`;
}
change_object = value_object;
change_object =
attr.expression.node.type !== 'ObjectExpression'
? x`(typeof ${value} === 'object' && ${value} !== null ? ${value} : {})`
: value;
} else {
const obj = x`{ ${name}: ${attr.get_value(block)} }`;
initial_props.push(obj);
@ -261,7 +259,7 @@ export default class InlineComponentWrapper extends Wrapper {
statements.push(b`
for (let #i = 0; #i < ${levels}.length; #i += 1) {
${props} = @assign(${props}, ${levels}[#i]);
${props} = Object.assign(${props}, ${levels}[#i]);
}
`);
@ -417,12 +415,11 @@ export default class InlineComponentWrapper extends Wrapper {
block.chunks.update.push(b`
if (${switch_value} !== (${switch_value} = ${snippet})) {
if (${name}) {
@group_outros();
const old_component = ${name};
@transition_out(old_component.$$.fragment, 1, 0, () => {
@destroy_component(old_component, 1);
});
@check_outros();
${block.group_transition_out(
(transition_out) =>
b`${transition_out}(old_component.$$.fragment, () => { @destroy_component(old_component, 1); }, 0)`
)}
}
if (${switch_value}) {

@ -2,12 +2,8 @@ import { b, x } from 'code-red';
import Block from '../../Block';
import Action from '../../../nodes/Action';
export default function add_actions(
block: Block,
target: string,
actions: Action[]
) {
actions.forEach(action => add_action(block, target, action));
export default function add_actions(block: Block, target: string, actions: Action[]) {
actions.forEach((action) => add_action(block, target, action));
}
export function add_action(block: Block, target: string, action: Action) {
@ -20,27 +16,21 @@ export function add_action(block: Block, target: string, action: Action) {
dependencies = expression.dynamic_dependencies();
}
const id = block.get_unique_name(
`${action.name.replace(/[^a-zA-Z0-9_$]/g, '_')}_action`
);
const id = block.get_unique_name(`${action.name.replace(/[^a-zA-Z0-9_$]/g, '_')}_action`);
block.add_variable(id);
const fn = block.renderer.reference(action.name);
block.event_listeners.push(
x`@action_destroyer(${id} = ${fn}.call(null, ${target}, ${snippet}))`
);
block.event_listeners.push(x`@action_destroyer(${id} = ${fn}.call(null, ${target}, ${snippet}))`);
if (dependencies && dependencies.length > 0) {
let condition = x`${id} && @is_function(${id}.update)`;
let condition = x`${id} && "function" === typeof ${id}.update`;
if (dependencies.length > 0) {
condition = x`${condition} && ${block.renderer.dirty(dependencies)}`;
}
block.chunks.update.push(
b`if (${condition}) ${id}.update.call(null, ${snippet});`
);
block.chunks.update.push(b`if (${condition}) ${id}.update.call(null, ${snippet});`);
}
}

@ -10,7 +10,7 @@ function get_prop_value(attribute) {
if (attribute.chunks.length === 0) return x`''`;
return attribute.chunks
.map(chunk => {
.map((chunk) => {
if (chunk.type === 'Text') return string_literal(chunk.data);
return chunk.node;
})
@ -21,35 +21,31 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend
const binding_props = [];
const binding_fns = [];
node.bindings.forEach(binding => {
node.bindings.forEach((binding) => {
renderer.has_bindings = true;
// TODO this probably won't work for contextual bindings
const snippet = binding.expression.node;
binding_props.push(p`${binding.name}: ${snippet}`);
binding_fns.push(p`${binding.name}: $$value => { ${snippet} = $$value; $$settled = false }`);
binding_fns.push(p`${binding.name}: (#value) => { ${snippet} = #value; $$settled = false }`);
});
const uses_spread = node.attributes.find(attr => attr.is_spread);
const uses_spread = node.attributes.find((attr) => attr.is_spread);
let props;
if (uses_spread) {
props = x`@_Object.assign(${
node.attributes
.map(attribute => {
if (attribute.is_spread) {
return attribute.expression.node;
} else {
return x`{ ${attribute.name}: ${get_prop_value(attribute)} }`;
}
props = x`{${node.attributes
.map((attribute) => {
if (attribute.is_spread) return x`...${attribute.expression.node}`;
else return x`${attribute.name}: ${get_prop_value(attribute)}`;
})
.concat(binding_props.map(p => x`{ ${p} }`))
})`;
.concat(binding_props.map((p) => x`${p}`))
.join()}}`;
} else {
props = x`{
${node.attributes.map(attribute => p`${attribute.name}: ${get_prop_value(attribute)}`)},
${node.attributes.map((attribute) => p`${attribute.name}: ${get_prop_value(attribute)}`)},
${binding_props}
}`;
}
@ -58,13 +54,12 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend
${binding_fns}
}`;
const expression = (
const expression =
node.name === 'svelte:self'
? renderer.name
: node.name === 'svelte:component'
? x`(${node.expression.node}) || @missing_component`
: node.name.split('.').reduce(((lhs, rhs) => x`${lhs}.${rhs}`) as any)
);
: node.name.split('.').reduce(((lhs, rhs) => x`${lhs}.${rhs}`) as any);
const slot_fns = [];
@ -75,20 +70,16 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend
renderer.push();
renderer.render(children, Object.assign({}, options, {
slot_scopes
}));
renderer.render(children, { ...options, ...slot_scopes });
slot_scopes.set('default', {
input: get_slot_scope(node.lets),
output: renderer.pop()
output: renderer.pop(),
});
slot_scopes.forEach(({ input, output }, name) => {
if (!is_empty_template_literal(output)) {
slot_fns.push(
p`${name}: (${input}) => ${output}`
);
slot_fns.push(p`${name}: (${input}) => ${output}`);
}
});
}
@ -97,13 +88,15 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend
${slot_fns}
}`;
renderer.add_expression(x`@validate_component(${expression}, "${node.name}").$$render($$result, ${props}, ${bindings}, ${slots})`);
renderer.add_expression(
x`@validate_component(${expression}, "${node.name}").$$render($$result, ${props}, ${bindings}, ${slots})`
);
}
function is_empty_template_literal(template_literal) {
return (
template_literal.expressions.length === 0 &&
template_literal.quasis.length === 1 &&
template_literal.quasis[0].value.raw === ""
template_literal.quasis[0].value.raw === ''
);
}

@ -9,7 +9,7 @@ export function get_class_attribute_value(attribute: Attribute): ESTreeExpressio
// handle special case — `class={possiblyUndefined}` with scoped CSS
if (attribute.chunks.length === 2 && (attribute.chunks[1] as Text).synthetic) {
const value = (attribute.chunks[0] as Expression).node;
return x`@escape(@null_to_empty(${value})) + "${(attribute.chunks[1] as Text).data}"`;
return x`@escape(${value} ?? "") + "${(attribute.chunks[1] as Text).data}"`;
}
return get_attribute_value(attribute);
@ -21,7 +21,7 @@ export function get_attribute_value(attribute: Attribute): ESTreeExpression {
return attribute.chunks
.map((chunk) => {
return chunk.type === 'Text'
? string_literal(chunk.data.replace(/"/g, '&quot;')) as ESTreeExpression
? (string_literal(chunk.data.replace(/"/g, '&quot;')) as ESTreeExpression)
: x`@escape(${chunk.node})`;
})
.reduce((lhs, rhs) => x`${lhs} + ${rhs}`);

@ -1,5 +1,4 @@
import { INode } from '../../../nodes/interfaces';
import { trim_end, trim_start } from '../../../../utils/trim';
import { link } from '../../../../utils/link';
// similar logic from `compile/render_dom/wrappers/Fragment`
@ -21,13 +20,11 @@ export default function remove_whitespace_children(children: INode[], next?: INo
if (nodes.length === 0) {
const should_trim = next
? next.type === 'Text' &&
/^\s/.test(next.data) &&
trimmable_at(child, next)
? next.type === 'Text' && /^\s/.test(next.data) && trimmable_at(child, next)
: !child.has_ancestor('EachBlock');
if (should_trim) {
data = trim_end(data);
data = data.trimRight();
if (!data) continue;
}
}
@ -39,16 +36,16 @@ export default function remove_whitespace_children(children: INode[], next?: INo
}
nodes.unshift(child);
link(last_child, last_child = child);
link(last_child, (last_child = child));
} else {
nodes.unshift(child);
link(last_child, last_child = child);
link(last_child, (last_child = child));
}
}
const first = nodes[0];
if (first && first.type === 'Text') {
first.data = trim_start(first.data);
first.data = first.data.trimLeft();
if (!first.data) {
first.var = null;
nodes.shift();
@ -67,7 +64,6 @@ function trimmable_at(child: INode, next_sibling: INode): boolean {
// The child and its sibling share a common nearest each block (not at an each block boundary)
// The next sibling's previous node is an each block
return (
next_sibling.find_nearest(/EachBlock/) ===
child.find_nearest(/EachBlock/) || next_sibling.prev.type === 'EachBlock'
next_sibling.find_nearest(/EachBlock/) === child.find_nearest(/EachBlock/) || next_sibling.prev.type === 'EachBlock'
);
}

@ -8,101 +8,91 @@ import Text from '../nodes/Text';
import { extract_names } from '../utils/scope';
import { LabeledStatement, Statement, ExpressionStatement, AssignmentExpression, Node } from 'estree';
export default function ssr(
component: Component,
options: CompileOptions
): {js: Node[]; css: CssResult} {
export default function ssr(component: Component, options: CompileOptions): { js: Node[]; css: CssResult } {
const renderer = new Renderer({
name: component.name
name: component.name,
});
const { name } = component;
// create $$render function
renderer.render(trim(component.fragment.children), Object.assign({
locate: component.locate
}, options));
renderer.render(trim(component.fragment.children), { locate: component.locate, ...options });
// TODO put this inside the Renderer class
const literal = renderer.pop();
// TODO concatenate CSS maps
const css = options.customElement ?
{ code: null, map: null } :
component.stylesheet.render(options.filename, true);
const css = options.customElement ? { code: null, map: null } : component.stylesheet.render(options.filename, true);
const uses_rest = component.var_lookup.has('$$restProps');
const props = component.vars.filter(variable => !variable.module && variable.export_name);
const rest = uses_rest ? b`let $$restProps = @compute_rest_props($$props, [${props.map(prop => `"${prop.export_name}"`).join(',')}]);` : null;
const props = component.vars.filter((variable) => !variable.module && variable.export_name);
const rest = uses_rest
? b`
let #k;
const #keys = new Set([${props.map((prop) => `"${prop.export_name}"`).join(',')}]);
let $$restProps = {};
for (#k in $$props){ if (!#keys.has(#k) && $$props[#k][0] !== '$'){ $$restProps[#k] = $$props[#k];}}`
: null;
const reactive_stores = component.vars.filter(variable => variable.name[0] === '$' && variable.name[1] !== '$');
const reactive_stores = component.vars.filter((variable) => variable.name[0] === '$' && variable.name[1] !== '$');
const reactive_store_values = reactive_stores
.map(({ name }) => {
const store_name = name.slice(1);
const store = component.var_lookup.get(store_name);
if (store && store.hoistable) return null;
const assignment = b`${name} = @get_store_value(${store_name});`;
const assignment = b`${store_name}.subscribe(#v=>{${name}=#v})();`;
return component.compile_options.dev
? b`@validate_store(${store_name}, '${store_name}'); ${assignment}`
: assignment;
})
.filter(Boolean);
component.rewrite_props(({ name }) => {
const value = `$${name}`;
let insert = b`${value} = @get_store_value(${name})`;
if (component.compile_options.dev) {
insert = b`@validate_store(${name}, '${name}'); ${insert}`;
}
return insert;
});
component.rewrite_props(
({ name }) =>
b`${
component.compile_options.dev && b`@validate_store(${name}, '${name}');`
}${name}.subscribe((#v)=>{$${name}=#v})()`
);
const instance_javascript = component.extract_javascript(component.ast.instance);
// TODO only do this for props with a default value
const parent_bindings = instance_javascript
? component.vars
.filter(variable => !variable.module && variable.export_name)
.map(prop => {
.filter((variable) => !variable.module && variable.export_name)
.map((prop) => {
return b`if ($$props.${prop.export_name} === void 0 && $$bindings.${prop.export_name} && ${prop.name} !== void 0) $$bindings.${prop.export_name}(${prop.name});`;
})
: [];
const reactive_declarations = component.reactive_declarations.map(d => {
const reactive_declarations = component.reactive_declarations.map((d) => {
const body: Statement = (d.node as LabeledStatement).body;
let statement = b`${body}`;
if (d.declaration) {
const declared = extract_names(d.declaration);
const injected = declared.filter(name => {
const injected = declared.filter((name) => {
return name[0] !== '$' && component.var_lookup.get(name).injected;
});
const self_dependencies = injected.filter(name => d.dependencies.has(name));
const self_dependencies = injected.filter((name) => d.dependencies.has(name));
if (injected.length) {
// in some cases we need to do `let foo; [expression]`, in
// others we can do `let [expression]`
const separate = (
self_dependencies.length > 0 ||
declared.length > injected.length
);
const separate = self_dependencies.length > 0 || declared.length > injected.length;
const { left, right } = (body as ExpressionStatement).expression as AssignmentExpression;
statement = separate
? b`
${injected.map(name => b`let ${name};`)}
${injected.map((name) => b`let ${name};`)}
${statement}`
: b`
let ${left} = ${right}`;
: b`let ${left} = ${right}`;
}
} else { // TODO do not add label if it's not referenced
} else {
// TODO do not add label if it's not referenced
statement = b`$: { ${statement} }`;
}
@ -138,24 +128,25 @@ export default function ssr(
...reactive_stores.map(({ name }) => {
const store_name = name.slice(1);
const store = component.var_lookup.get(store_name);
if (store && store.hoistable) {
return b`let ${name} = @get_store_value(${store_name});`;
}
return b`let ${name};`;
return b`let ${name};${store && store.hoistable && b`${store_name}.subscribe((#v)=>{${name}=#v})()`}`;
}),
instance_javascript,
...parent_bindings,
css.code && b`$$result.css.add(#css);`,
main
main,
].filter(Boolean);
const js = b`
${css.code ? b`
${
css.code
? b`
const #css = {
code: "${css.code}",
map: ${css.map ? string_literal(css.map.toString()) : 'null'}
};` : null}
};`
: null
}
${component.extract_javascript(component.ast.module)}

@ -5,13 +5,13 @@ import Block from '../render_dom/Block';
export default function get_slot_data(values: Map<string, Attribute>, block: Block = null) {
return {
type: 'ObjectExpression',
type: 'Expression',
properties: Array.from(values.values())
.filter(attribute => attribute.name !== 'name')
.map(attribute => {
.filter((attribute) => attribute.name !== 'name')
.map((attribute) => {
const value = get_value(block, attribute);
return p`${attribute.name}: ${value}`;
})
}),
};
}
@ -20,7 +20,7 @@ function get_value(block: Block, attribute: Attribute) {
if (attribute.chunks.length === 0) return x`""`;
let value = attribute.chunks
.map(chunk => chunk.type === 'Text' ? string_literal(chunk.data) : (block ? chunk.manipulate(block) : chunk.node))
.map((chunk) => (chunk.type === 'Text' ? string_literal(chunk.data) : block ? chunk.manipulate(block) : chunk.node))
.reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
if (attribute.chunks.length > 1 && attribute.chunks[0].type !== 'Text') {

@ -102,11 +102,14 @@ export interface Warning {
export type ModuleFormat = 'esm' | 'cjs';
export interface CompileOptions {
/* bundler */
filename?: string;
version?: number;
format?: ModuleFormat;
/* Component class name */
name?: string;
filename?: string;
generate?: string | false;
version?: number;
outputFilename?: string;
cssOutputFilename?: string;
@ -116,7 +119,6 @@ export interface CompileOptions {
accessors?: boolean;
immutable?: boolean;
hydratable?: boolean;
legacy?: boolean;
customElement?: boolean;
tag?: string;
css?: boolean;

@ -2,7 +2,6 @@ import read_context from '../read/context';
import read_expression from '../read/expression';
import { closing_tag_omitted } from '../utils/html';
import { whitespace } from '../../utils/patterns';
import { trim_start, trim_end } from '../../utils/trim';
import { to_string } from '../utils/node';
import { Parser } from '../index';
import { TemplateNode } from '../../interfaces';
@ -14,12 +13,12 @@ function trim_whitespace(block: TemplateNode, trim_before: boolean, trim_after:
const last_child = block.children[block.children.length - 1];
if (first_child.type === 'Text' && trim_before) {
first_child.data = trim_start(first_child.data);
first_child.data = first_child.data.trimLeft();
if (!first_child.data) block.children.shift();
}
if (last_child.type === 'Text' && trim_after) {
last_child.data = trim_end(last_child.data);
last_child.data = last_child.data.trimRight();
if (!last_child.data) block.children.pop();
}
@ -49,7 +48,12 @@ export default function mustache(parser: Parser) {
block = parser.current();
}
if (block.type === 'ElseBlock' || block.type === 'PendingBlock' || block.type === 'ThenBlock' || block.type === 'CatchBlock') {
if (
block.type === 'ElseBlock' ||
block.type === 'PendingBlock' ||
block.type === 'ThenBlock' ||
block.type === 'CatchBlock'
) {
block.end = start;
parser.stack.pop();
block = parser.current();
@ -66,7 +70,7 @@ export default function mustache(parser: Parser) {
} else {
parser.error({
code: `unexpected-block-close`,
message: `Unexpected block closing tag`
message: `Unexpected block closing tag`,
});
}
@ -98,7 +102,7 @@ export default function mustache(parser: Parser) {
if (parser.eat('if')) {
parser.error({
code: 'invalid-elseif',
message: `'elseif' should be 'else if'`
message: `'elseif' should be 'else if'`,
});
}
@ -110,9 +114,9 @@ export default function mustache(parser: Parser) {
if (block.type !== 'IfBlock') {
parser.error({
code: `invalid-elseif-placement`,
message: parser.stack.some(block => block.type === 'IfBlock')
message: parser.stack.some((block) => block.type === 'IfBlock')
? `Expected to close ${to_string(block)} before seeing {:else if ...} block`
: `Cannot have an {:else if ...} block outside an {#if ...} block`
: `Cannot have an {:else if ...} block outside an {#if ...} block`,
});
}
@ -148,9 +152,9 @@ export default function mustache(parser: Parser) {
if (block.type !== 'IfBlock' && block.type !== 'EachBlock') {
parser.error({
code: `invalid-else-placement`,
message: parser.stack.some(block => block.type === 'IfBlock' || block.type === 'EachBlock')
message: parser.stack.some((block) => block.type === 'IfBlock' || block.type === 'EachBlock')
? `Expected to close ${to_string(block)} before seeing {:else} block`
: `Cannot have an {:else} block outside an {#if ...} or {#each ...} block`
: `Cannot have an {:else} block outside an {#if ...} or {#each ...} block`,
});
}
@ -174,18 +178,18 @@ export default function mustache(parser: Parser) {
if (block.type !== 'PendingBlock') {
parser.error({
code: `invalid-then-placement`,
message: parser.stack.some(block => block.type === 'PendingBlock')
message: parser.stack.some((block) => block.type === 'PendingBlock')
? `Expected to close ${to_string(block)} before seeing {:then} block`
: `Cannot have an {:then} block outside an {#await ...} block`
: `Cannot have an {:then} block outside an {#await ...} block`,
});
}
} else {
if (block.type !== 'ThenBlock' && block.type !== 'PendingBlock') {
parser.error({
code: `invalid-catch-placement`,
message: parser.stack.some(block => block.type === 'ThenBlock' || block.type === 'PendingBlock')
message: parser.stack.some((block) => block.type === 'ThenBlock' || block.type === 'PendingBlock')
? `Expected to close ${to_string(block)} before seeing {:catch} block`
: `Cannot have an {:catch} block outside an {#await ...} block`
: `Cannot have an {:catch} block outside an {#await ...} block`,
});
}
}
@ -206,7 +210,7 @@ export default function mustache(parser: Parser) {
end: null,
type: is_then ? 'ThenBlock' : 'CatchBlock',
children: [],
skip: false
skip: false,
};
await_block[is_then ? 'then' : 'catch'] = new_block;
@ -224,7 +228,7 @@ export default function mustache(parser: Parser) {
} else {
parser.error({
code: `expected-block-type`,
message: `Expected if, each or await`
message: `Expected if, each or await`,
});
}
@ -232,8 +236,9 @@ export default function mustache(parser: Parser) {
const expression = read_expression(parser);
const block: TemplateNode = type === 'AwaitBlock' ?
{
const block: TemplateNode =
type === 'AwaitBlock'
? {
start,
end: null,
type,
@ -245,24 +250,24 @@ export default function mustache(parser: Parser) {
end: null,
type: 'PendingBlock',
children: [],
skip: true
skip: true,
},
then: {
start: null,
end: null,
type: 'ThenBlock',
children: [],
skip: true
skip: true,
},
catch: {
start: null,
end: null,
type: 'CatchBlock',
children: [],
skip: true
skip: true,
},
} :
{
}
: {
start,
end: null,
type,
@ -284,9 +289,10 @@ export default function mustache(parser: Parser) {
if (parser.eat(',')) {
parser.allow_whitespace();
block.index = parser.read_identifier();
if (!block.index) parser.error({
if (!block.index)
parser.error({
code: `expected-name`,
message: `Expected name`
message: `Expected name`,
});
parser.allow_whitespace();
@ -361,16 +367,17 @@ export default function mustache(parser: Parser) {
} else {
const expression = read_expression(parser);
identifiers = expression.type === 'SequenceExpression'
? expression.expressions
: [expression];
identifiers = expression.type === 'SequenceExpression' ? expression.expressions : [expression];
identifiers.forEach(node => {
identifiers.forEach((node) => {
if (node.type !== 'Identifier') {
parser.error({
parser.error(
{
code: 'invalid-debug-args',
message: '{@debug ...} arguments must be identifiers, not arbitrary expressions'
}, node.start);
message: '{@debug ...} arguments must be identifiers, not arbitrary expressions',
},
node.start
);
}
});
@ -382,7 +389,7 @@ export default function mustache(parser: Parser) {
start,
end: parser.index,
type: 'DebugTag',
identifiers
identifiers,
});
} else {
const expression = read_expression(parser);

@ -35,10 +35,7 @@ const windows_1252 = [
376,
];
const entity_pattern = new RegExp(
`&(#?(?:x[\\w\\d]+|\\d+|${Object.keys(entities).join('|')}))(?:;|\\b)`,
'g'
);
const entity_pattern = new RegExp(`&(#?(?:x[\\w\\d]+|\\d+|${Object.keys(entities).join('|')}))(?:;|\\b)`, 'g');
export function decode_character_references(html: string) {
return html.replace(entity_pattern, (match, entity) => {
@ -61,8 +58,6 @@ export function decode_character_references(html: string) {
});
}
const NUL = 0;
// some code points are verboten. If we were inserting HTML, the browser would replace the illegal
// code points with alternatives in some cases - since we're bypassing that mechanism, we need
// to replace them ourselves
@ -92,7 +87,7 @@ function validate_code(code: number) {
// UTF-16 surrogate halves
if (code <= 57343) {
return NUL;
return 0;
}
// rest of the basic multilingual plane
@ -110,7 +105,7 @@ function validate_code(code: number) {
return code;
}
return NUL;
return 0;
}
// based on http://developers.whatwg.org/syntax.html#syntax-tag-omission

@ -1,4 +1,4 @@
export default function fuzzymatch(name: string, names: string[]) {
export default function fuzzymatch(name: string, names: string[] | object) {
const set = new FuzzySet(names);
const matches = set.get(name);
@ -13,8 +13,7 @@ const GRAM_SIZE_UPPER = 3;
// return an edit distance from 0 to 1
function _distance(str1: string, str2: string) {
if (str1 === null && str2 === null)
throw 'Trying to compare two null values';
if (str1 === null && str2 === null) throw 'Trying to compare two null values';
if (str1 === null || str2 === null) return 0;
str1 = String(str1);
str2 = String(str2);
@ -96,15 +95,16 @@ class FuzzySet {
match_dict = {};
items = {};
constructor(arr: string[]) {
constructor(arr: string[] | object) {
// initialization
for (let i = GRAM_SIZE_LOWER; i < GRAM_SIZE_UPPER + 1; ++i) {
this.items[i] = [];
}
let k;
// add all the items to the set
for (let i = 0; i < arr.length; ++i) {
this.add(arr[i]);
for (k in arr) {
this.add(arr[k]);
}
}
@ -156,11 +156,7 @@ class FuzzySet {
let results = [];
// start with high gram size and if there are no results, go to lower gram sizes
for (
let gram_size = GRAM_SIZE_UPPER;
gram_size >= GRAM_SIZE_LOWER;
--gram_size
) {
for (let gram_size = GRAM_SIZE_UPPER; gram_size >= GRAM_SIZE_LOWER; --gram_size) {
results = this.__get(value, gram_size);
if (results) {
return results;
@ -204,10 +200,7 @@ class FuzzySet {
// build a results list of [score, str]
for (const match_index in matches) {
match_score = matches[match_index];
results.push([
match_score / (vector_normal * items[match_index][0]),
items[match_index][1],
]);
results.push([match_score / (vector_normal * items[match_index][0]), items[match_index][1]]);
}
results.sort(sort_descending);
@ -216,10 +209,7 @@ class FuzzySet {
const end_index = Math.min(50, results.length);
// truncate somewhat arbitrarily to 50
for (let i = 0; i < end_index; ++i) {
new_results.push([
_distance(results[i][1], normalized_value),
results[i][1],
]);
new_results.push([_distance(results[i][1], normalized_value), results[i][1]]);
}
results = new_results;
results.sort(sort_descending);

@ -1,4 +1,2 @@
export function link<T extends { next?: T; prev?: T }>(next: T, prev: T) {
prev.next = next;
if (next) next.prev = prev;
}
export const link = <T extends { next?: T; prev?: T }>(next: T, prev: T) =>
void ((prev.next = next) && (next.prev = prev));

@ -1,6 +1,2 @@
export default function list(items: string[], conjunction = 'or') {
if (items.length === 1) return items[0];
return `${items.slice(0, -1).join(', ')} ${conjunction} ${items[
items.length - 1
]}`;
}
export default (items: string[], conjunction = 'or') =>
items.length === 1 ? items[0] : items.slice(0, -1).join(', ') + `${conjunction} ${items[items.length - 1]}`;

@ -54,7 +54,7 @@ export const globals = new Set([
'undefined',
'URIError',
'URL',
'window'
'window',
]);
export const reserved = new Set([

@ -1,23 +1,14 @@
export const html = 'http://www.w3.org/1999/xhtml';
export const mathml = 'http://www.w3.org/1998/Math/MathML';
export const svg = 'http://www.w3.org/2000/svg';
export const xlink = 'http://www.w3.org/1999/xlink';
export const xml = 'http://www.w3.org/XML/1998/namespace';
export const xmlns = 'http://www.w3.org/2000/xmlns';
export const valid_namespaces = [
'html',
'mathml',
'svg',
'xlink',
'xml',
'xmlns',
html,
mathml,
svg,
xlink,
xml,
xmlns,
];
export const namespaces: Record<string, string> = { html, mathml, svg, xlink, xml, xmlns };
export enum namespaces {
svg = 'http://www.w3.org/2000/svg',
xmlns = 'http://www.w3.org/2000/xmlns',
xlink = 'http://www.w3.org/1999/xlink',
html = 'http://www.w3.org/1999/xhtml',
mathml = 'http://www.w3.org/1998/Math/MathML',
xml = 'http://www.w3.org/XML/1998/namespace',
'http://www.w3.org/2000/svg' = 'svg',
'http://www.w3.org/2000/xmlns' = 'xmlns',
'http://www.w3.org/1999/xlink' = 'xlink',
'http://www.w3.org/1999/xhtml' = 'html',
'http://www.w3.org/1998/Math/MathML' = 'mathml',
'http://www.w3.org/XML/1998/namespace' = 'xml',
}

@ -17,12 +17,8 @@ export function nodes_match(a, b) {
while (i--) {
const key = a_keys[i];
if (b_keys[i] !== key) return false;
if (key === 'start' || key === 'end') continue;
if (!nodes_match(a[key], b[key])) {
return false;
}
else if (key === 'start' || key === 'end') continue;
else if (!nodes_match(a[key], b[key])) return false;
}
return true;

@ -1,31 +1,31 @@
import { dev$assert } from 'svelte/internal';
export { identity as linear } from 'svelte/internal';
export const linear = (t: number) => t;
export const quadIn = (t: number) => t ** 2;
export const quadOut = (t: number) => 1.0 - (1.0 - t) ** 2;
export const quadInOut = (t: number) => 0.5 * (t >= 0.5 ? 2 - 2 * (1.0 - t) ** 2 : (2 * t) ** 2);
export const quadInOut = (t: number) => 0.5 * (t >= 0.5 ? 2.0 - 2 * (1.0 - t) ** 2 : (2 * t) ** 2);
export const cubicIn = (t: number) => t ** 3;
export const cubicOut = (t: number) => 1.0 - (1.0 - t) ** 3;
export const cubicInOut = (t: number) => 0.5 * (t >= 0.5 ? 2 - (2 * (1.0 - t)) ** 3 : (2 * t) ** 3);
export const cubicInOut = (t: number) => 0.5 * (t >= 0.5 ? 2.0 - (2 * (1.0 - t)) ** 3 : (2 * t) ** 3);
export const quartIn = (t: number) => t ** 4;
export const quartOut = (t: number) => 1.0 - (1.0 - t) ** 4;
export const quartInOut = (t: number) => 0.5 * (t >= 0.5 ? 2 - (2 * (1.0 - t)) ** 4 : (2 * t) ** 4);
export const quartInOut = (t: number) => 0.5 * (t >= 0.5 ? 2.0 - (2 * (1.0 - t)) ** 4 : (2 * t) ** 4);
export const easeIn = quartIn;
export const easeOut = quartOut;
export const easeInOut = quartInOut;
export const quintIn = (t: number) => t ** 5;
export const quintOut = (t: number) => 1.0 - (1.0 - t) ** 5;
export const quintInOut = (t: number) => 0.5 * (t >= 0.5 ? 2 - (2 * (1.0 - t)) ** 5 : (2 * t) ** 5);
export const quintInOut = (t: number) => 0.5 * (t >= 0.5 ? 2.0 - (2 * (1.0 - t)) ** 5 : (2 * t) ** 5);
export const backIn = (t: number) => t * t * (2.6 * t - 1.6);
export const backOut = (t: number) => 1 - (t = 1.0 - t) * t * (2.6 * t - 1.6);
export const backOut = (t: number) => 1.0 - (t = 1.0 - t) * t * (2.6 * t - 1.6);
export const backInOut = (t: number) =>
0.5 * (t >= 0.5 ? 2 - (t = 2 * (1 - t)) * t * (2.6 * t - 1.6) : (t = 2 * t) * t * (2.6 * t - 1.6));
0.5 * (t >= 0.5 ? 2 - (t = 2 * (1.0 - t)) * t * (2.6 * t - 1.6) : (t = 2 * t) * t * (2.6 * t - 1.6));
export const expoIn = (t: number) => (t ? Math.pow(2.0, 10.0 * (t - 1.0)) : t);
export const expoOut = (t: number) => (t ? 1.0 - Math.pow(2.0, -10.0 * t) : t);
export const expoInOut = (t: number) =>
!t || t === 1.0 ? t : t < 0.5 ? 0.5 * Math.pow(2.0, 20.0 * t - 10.0) : -0.5 * Math.pow(2.0, 10.0 - t * 20.0) + 1.0;
export const elasticIn = (t: number) => Math.sin((13.0 * t * Math.PI) / 2) * Math.pow(2.0, 10.0 * (t - 1.0));
export const elasticOut = (t: number) => Math.sin((-13.0 * (t + 1.0) * Math.PI) / 2) * Math.pow(2.0, -10.0 * t) + 1.0;
export const elasticIn = (t: number) => Math.sin((13.0 * t * Math.PI) / 2.0) * Math.pow(2.0, 10.0 * (t - 1.0));
export const elasticOut = (t: number) => Math.sin((-13.0 * (t + 1.0) * Math.PI) / 2.0) * Math.pow(2.0, -10.0 * t) + 1.0;
export const elasticInOut = (t: number) =>
t < 0.5
? 0.5 * Math.sin(((+13.0 * Math.PI) / 2) * 2.0 * t) * Math.pow(2.0, 10.0 * (2.0 * t - 1.0))
@ -43,9 +43,9 @@ export const bounceInOut = (t: number) =>
t < 0.5 ? 0.5 * (1.0 - bounceOut(1.0 - t * 2.0)) : 0.5 * bounceOut(t * 2.0 - 1.0) + 0.5;
export const sineIn = (t: number) => (1e-14 > Math.abs((t = Math.cos(t * Math.PI * 0.5))) ? 1.0 : 1.0 - t);
export const sineOut = (t: number) => Math.sin((t * Math.PI) / 2);
export const sineInOut = (t: number) => -0.5 * (Math.cos(Math.PI * t) - 1);
export const circIn = (t: number) => 1 - Math.sin(Math.acos(t));
export const circOut = (t: number) => Math.sin(Math.acos(1 - t));
export const sineInOut = (t: number) => -0.5 * (Math.cos(Math.PI * t) - 1.0);
export const circIn = (t: number) => 1.0 - Math.sin(Math.acos(t));
export const circOut = (t: number) => Math.sin(Math.acos(1.0 - t));
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)));
export const cubicBezier = (x1: number, y1: number, x2: number, y2: number) => {

@ -0,0 +1,14 @@
export const is_browser = typeof window !== 'undefined';
export const is_iframe = is_browser && window.self !== window.top;
export const is_cors =
is_iframe &&
(() => {
try {
if (window.parent) void window.parent.document;
return false;
} catch (error) {
return true;
}
})();
export const has_Symbol = typeof Symbol === 'function';
export const globals = is_browser ? window : typeof globalThis !== 'undefined' ? globalThis : global;

@ -1,49 +1,38 @@
import { add_render_callback, flush, schedule_update } from './scheduler';
import { current_component, set_current_component } from './lifecycle';
import { blank_object, is_function, run_all, noop } from './utils';
import { noop } from './utils';
import { children, detach } from './dom';
import { transition_in } from './transitions';
type binary = 0 | 1;
export interface Fragment {
key: string | null;
first: null;
/**
* create
* run once
* runs hydrate if exists
*/
c: () => void;
/**
* claim
* runs hydrate if exists
* */
l: (nodes: any) => void;
/* create */ c: () => void;
/* claim */ l: (nodes: any) => void;
/* hydrate */ h: () => void;
/* mount */ m: (target: HTMLElement, anchor: any, is_remount: boolean) => void;
/* mount */ m: (target: HTMLElement, anchor: any, is_remount: binary) => void;
/* update */ p: (ctx: any, dirty: any) => void;
/* measure */ r: () => void;
/* fix */ f: () => void;
/* animate */ a: () => void;
/* intro */ i: (local: 0 | 1) => void;
/* outro */ o: (local: 0 | 1) => void;
/* destroy */ d: (detaching: 0 | 1) => void;
/* intro */ i: (local: binary) => void;
/* outro */ o: (local: binary) => void;
/* destroy */ d: (detaching: binary) => void;
}
// eslint-disable-next-line @typescript-eslint/class-name-casing
interface T$$ {
export interface T$$ {
dirty: number[];
ctx: null | any;
bound: any;
update: () => void;
callbacks: any;
after_update: any[];
props: Record<string, 0 | string>;
fragment: null | false | Fragment;
not_equal: any;
before_update: any[];
context: Map<any, any>;
on_mount: any[];
on_destroy: any[];
before_update: any[];
after_update: any[];
}
export function bind({ $$: { props, bound, ctx } }, name: string, callback: (prop: any) => void) {
@ -52,22 +41,24 @@ export function bind({ $$: { props, bound, ctx } }, name: string, callback: (pro
bound[index] = callback;
callback(ctx[index]);
}
export function mount_component({ $$: { fragment, on_mount, on_destroy, after_update } }, target, anchor) {
if (fragment) fragment.m(target, anchor);
export function mount_component({ $$ }, target, anchor) {
if ($$.fragment) $$.fragment.m(target, anchor);
add_render_callback(() => {
for (let i = 0, res; i < on_mount.length; i++)
if (is_function((res = on_mount[i]())))
if (on_destroy) on_destroy.push(res);
let { on_mount, on_destroy, after_update } = $$,
i = 0,
res;
for (; i < on_mount.length; i++)
if ('function' === typeof (res = on_mount[i]()))
if ($$.on_destroy) on_destroy.push(res);
else res(); // component already destroyed
on_mount.length = 0;
for (let i = 0; i < after_update.length; i++) after_update[i]();
for (i = on_mount.length = 0; i < after_update.length; i++) after_update[i]();
});
}
export function destroy_component({ $$ }, detaching: 0 | 1) {
if ($$.fragment === null) return;
if (null === $$.fragment) return;
run_all($$.on_destroy);
for (let i = 0, { on_destroy } = $$; i < on_destroy.length; i++) on_destroy[i]();
if ($$.fragment) $$.fragment.d(detaching);
// TODO null out other refs, including component.$$
@ -93,21 +84,20 @@ export function init(
fragment: null,
ctx: null,
// state
/* state */
props,
update: noop,
not_equal,
bound: blank_object(),
bound: Object.create(null),
// lifecycle
/* lifecycle */
on_mount: [],
on_destroy: [],
before_update: [],
after_update: [],
context: new Map(parent_component ? parent_component.$$.context : []),
// everything else
callbacks: blank_object(),
/* everything else */
callbacks: Object.create(null),
dirty,
});
@ -131,7 +121,9 @@ export function init(
ready = true;
run_all($$.before_update);
for (let i = 0, { before_update } = $$; i < before_update.length; i++) {
before_update[i]();
}
// false when empty
$$.fragment = create_fragment ? create_fragment($$.ctx) : false;

@ -1,10 +1,10 @@
import { is_promise } from './utils';
import { check_outros, group_outros, transition_in, transition_out } from './transitions';
import { transition_in, group_transition_out } from './transitions';
import { flush } from './scheduler';
import { get_current_component, set_current_component } from './lifecycle';
export function handle_promise(promise, info) {
const token = info.token = {};
const token = (info.token = {});
function update(type, index, key?, value?) {
if (info.token !== token) return;
@ -26,11 +26,11 @@ export function handle_promise(promise, info) {
if (info.blocks) {
info.blocks.forEach((block, i) => {
if (i !== index && block) {
group_outros();
transition_out(block, 1, 1, () => {
group_transition_out((transition_out) => {
transition_out(block, () => {
info.blocks[i] = null;
});
check_outros();
});
}
});
} else {
@ -54,15 +54,18 @@ export function handle_promise(promise, info) {
if (is_promise(promise)) {
const current_component = get_current_component();
promise.then(value => {
promise.then(
(value) => {
set_current_component(current_component);
update(info.then, 1, info.value, value);
set_current_component(null);
}, error => {
},
(error) => {
set_current_component(current_component);
update(info.catch, 2, info.error, error);
set_current_component(null);
});
}
);
// if we previously had a then/catch block, destroy it
if (info.current !== info.pending) {

@ -1,9 +1,15 @@
import { custom_event, append, insert, detach, listen, attr } from './dom';
let inited
import { now } from './environment';
let inited;
export function add_location_dev$legacy(element, file, line, column, char) {
element.__svelte_meta = {
loc: { file, line, column, char },
};
}
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`)
inited = true;
throw new Error(`You must specify the version`);
}
document.dispatchEvent(custom_event(type, { version: __VERSION__, ...detail }));
}
@ -88,3 +94,16 @@ export function set_data_dev$legacy(text, data) {
dispatch_dev$legacy('SvelteDOMSetData', { node: text, data });
text.data = data;
}
export function loop_guard_dev$legacy(timeout) {
const start = now();
return () => {
if (now() - start > timeout) {
throw new Error(`Infinite loop detected`);
}
};
}
export function validate_store_dev$legacy(store, name) {
if (store != null && typeof store.subscribe !== 'function') {
throw new Error(`'${name}' is not a store with a 'subscribe' method`);
}
}

@ -0,0 +1,148 @@
import { SvelteComponent } from './Component';
const dev$context = {
block: null,
component: null,
};
abstract class Dev$ {
self;
parent_block = dev$context.block;
parent_component = dev$context.component;
constructor(self) {
this.self = self;
}
abstract add(type, payload): void;
abstract subscribers: {}[];
dispatch(type, payload: any) {
this.add(type, payload);
this.subscribers.forEach((subscriber) => {
if (type in subscriber) subscriber[type](payload);
});
}
subscribe(subscriber) {
this.subscribers.push(subscriber);
return () => {
const index = this.subscribers.indexOf(subscriber);
if (~index) this.subscribers.splice(index, 1);
};
}
}
class Dev$Component extends Dev$ {
self: SvelteComponent;
block;
add(type, payload) {}
}
class Dev$Block extends Dev$ {
self;
parent_block;
parent_component;
elements: Dev$Element[] = [];
}
class Dev$Element extends Dev$ {
component;
node;
parent;
children = [];
attributes: {};
eventListeners: {};
style: {};
class: {};
add(type, payload) {}
subscribers: Dev$ElementSubscriber[] = [];
dispatch(type, payload: any) {
this.add(type, payload);
this.subscribers.forEach((subscriber) => {
if (type in subscriber) subscriber[type](payload);
});
}
subscribe(subscriber) {
this.subscribers.push(subscriber);
return () => {
const index = this.subscribers.indexOf(subscriber);
if (~index) this.subscribers.splice(index, 1);
};
}
}
interface Dev$ElementSubscriber {
onMount(payload): void;
onUnmount(payload): void;
setAttribute(payload): void;
removeAttribute(payload): void;
addEventListener(payload): void;
removeEventListener(payload): void;
addClass(payload): void;
removeClass(payload): void;
addStyle(payload): void;
removeStyle(payload): void;
}
const dev$dispatch =
__DEV__ &&
!__TEST__ &&
typeof window !== 'undefined' &&
(function dev$create_hook() {
const subscribers = [];
const rootComponents = [];
const hook = {
version: __VERSION__,
has_subscribers: false,
subscribe(listener) {
this.has_subscribers = true;
subscribers.push(listener);
if (rootComponents.length) rootComponents.forEach((component) => listener.addRootComponent(component));
else listener.ping(`init-no-components`);
return () => {
const index = subscribers.indexOf(listener);
if (~index) {
subscribers.splice(index, 1);
this.has_subscribers = !!subscribers.length;
return true;
}
return false;
};
},
};
Object.defineProperty(window, `__SVELTE_DEVTOOLS_GLOBAL_HOOK__`, {
enumerable: false,
get() {
return hook;
},
});
return function update(target_type, event_type, ...values) {
if (!hook.has_subscribers) return;
subscribers.forEach((listener) => {
if (target_type in listener && event_type in listener[target_type]) {
listener[target_type][event_type](...values);
}
});
};
})();
export function dev$element(element: Element | Node | EventTarget, event: keyof ElementEventsMap, payload?: any) {
if (__DEV__) {
dev$dispatch(`element`, event, element, payload);
}
}
export function dev$block(event: keyof BlockEventsMap, payload) {}
export function dev$tracing(type, value: any) {}
export function dev$assert(truthy: any, else_throw: string) {
if (__DEV__ && !truthy) {
throw new Error(else_throw);
}
}
interface ElementEventsMap {
onMount;
onDestroy;
setAttribute;
removeAttribute;
addEventListener;
removeEventListener;
addClass;
removeClass;
}
interface BlockEventsMap {
'create';
'claim';
'claim.failed';
}

@ -1,214 +0,0 @@
import { custom_event, insert, detach, attr } from './dom';
import { SvelteComponent } from './Component';
import {
get_current_component,
beforeUpdate,
onMount,
afterUpdate,
onDestroy,
createEventDispatcher,
setContext,
getContext,
} from './lifecycle';
import { writable } from 'svelte/store';
export const [
beforeUpdate_dev,
onMount_dev,
afterUpdate_dev,
onDestroy_dev,
createEventDispatcher_dev,
setContext_dev,
getContext_dev,
] = [beforeUpdate, onMount, afterUpdate, onDestroy, createEventDispatcher, setContext, getContext].map(
(fn) => (a?, b?) => {
if (!get_current_component()) throw new Error(`${fn.name} cannot be called outside of component initialization`);
return fn(a, b);
}
);
const dev$hook =
__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 dev$block(event: keyof BlockEventsMap, payload) {}
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 dispatch_dev<T = any>(type: string, detail?: T) {
document.dispatchEvent(custom_event(type, { version: '__VERSION__', ...detail }));
}
export function insert_dev(target: Node, node: Node, anchor?: Node) {
dispatch_dev('SvelteDOMInsert', { target, node, anchor });
insert(target, node, anchor);
}
export function detach_dev(node: Node) {
dispatch_dev('SvelteDOMRemove', { node });
detach(node);
}
export function detach_between_dev(before: Node, after: Node) {
while (before.nextSibling && before.nextSibling !== after) {
detach_dev(before.nextSibling);
}
}
export function detach_before_dev(after: Node) {
while (after.previousSibling) {
detach_dev(after.previousSibling);
}
}
export function detach_after_dev(before: Node) {
while (before.nextSibling) {
detach_dev(before.nextSibling);
}
}
export function attr_dev(node: Element, attribute: string, value?: string) {
attr(node, attribute, value);
if (value == null) dispatch_dev('SvelteDOMRemoveAttribute', { node, attribute });
else dispatch_dev('SvelteDOMSetAttribute', { node, attribute, value });
}
export function prop_dev(node: Element, property: string, value?: any) {
node[property] = value;
dispatch_dev('SvelteDOMSetProperty', { node, property, value });
}
export function dataset_dev(node: HTMLElement, property: string, value?: any) {
node.dataset[property] = value;
dispatch_dev('SvelteDOMSetDataset', { node, property, value });
}
export function set_data_dev(text, data) {
data = '' + data;
if (text.data === data) return;
dispatch_dev('SvelteDOMSetData', { node: text, data });
text.data = data;
}
export function validate_each_argument(arg) {
if (typeof arg !== 'string' && !(arg && typeof arg === 'object' && 'length' in arg)) {
let msg = '{#each} only iterates over array-like objects.';
if (typeof Symbol === 'function' && arg && Symbol.iterator in arg) {
msg += ' You can use a spread to convert this iterable into an array.';
}
throw new Error(msg);
}
}
export function validate_slots(name, slot, keys) {
for (const slot_key of Object.keys(slot)) {
if (!~keys.indexOf(slot_key)) {
console.warn(`<${name}> received an unexpected slot "${slot_key}".`);
}
}
}
type Props = Record<string, any>;
export interface SvelteComponentDev {
$set(props?: Props): void;
$on<T = any>(event: string, callback: (event: CustomEvent<T>) => void): () => void;
$destroy(): void;
[accessor: string]: any;
}
export class SvelteComponentDev extends SvelteComponent {
constructor(options: {
target: Element;
anchor?: Element;
props?: Props;
hydrate?: boolean;
intro?: boolean;
$$inline?: boolean;
}) {
if (!options || (!options.target && !options.$$inline)) {
throw new Error(`'target' is a required option`);
}
super();
}
$destroy() {
super.$destroy();
this.$destroy = () => {
console.warn(`Component was already destroyed`); // eslint-disable-line no-console
};
}
$capture_state() {}
$inject_state() {}
}
export function loop_guard(timeout) {
const start = Date.now();
return () => {
if (Date.now() - start > timeout) {
throw new Error(`Infinite loop detected`);
}
};
}

@ -0,0 +1,56 @@
import { SvelteComponent } from './Component';
import { now, has_Symbol } from './environment';
import { dev$assert } from './dev.tools';
export const dev$is_array_like = (arg) =>
dev$assert(
typeof arg === 'string' || (typeof arg === 'object' && 'length' in arg),
`{#each} only iterates over Array-like Objects.${
has_Symbol && Symbol.iterator in arg
? ' Consider using a [...spread] to convert this iterable into an Array instead.'
: ''
}`
);
export const dev$known_slots = (name, slot, keys) =>
Object.keys(slot).forEach((key) => dev$assert(keys.includes(key), `<${name}> received an unexpected slot "${key}".`));
export const dev$is_valid_store = (store, name) =>
dev$assert(
typeof store === 'object' && (store === null || typeof store.subscribe === 'function'),
`Could not subscribe to $${name}. A valid store is an object with a .subscribe method, consider setting ${name} to null if this is expected.`
);
export function dev$loop_guard(loopGuardTimeout) {
const start = now();
return () => dev$assert(now() - start < loopGuardTimeout, `Infinite loop detected`);
}
type Props = Record<string, any>;
export interface SvelteComponentDev {
$set(props?: Props): void;
$on<T = any>(event: string, callback: (event: CustomEvent<T>) => void): () => void;
$destroy(): void;
[accessor: string]: any;
}
export class SvelteComponentDev extends SvelteComponent {
constructor(options: {
target: Element;
anchor?: Element;
props?: Props;
hydrate?: boolean;
intro?: boolean;
$$inline?: boolean;
}) {
if (!options || (!options.target && !options.$$inline)) throw new Error(`'target' is a required option`);
super();
}
$destroy() {
super.$destroy();
this.$destroy = () => {
console.warn(`Component was already destroyed`); // eslint-disable-line no-console
};
}
$capture_state() {}
$inject_state() {}
}

@ -1,18 +1,18 @@
import { has_prop } from './utils';
import { dispatch_dev, dev$dispatch, dev$element, dev$block } from './dev';
import { dev$element, dev$block } from './dev.tools';
import { is_client, is_cors } from './environment';
export function append(target: Node, node: Node) {
dev$element(node, `mount`, { target });
dev$element(node, `onMount`, { target });
target.appendChild(node);
}
export function insert(target: Node, node: Node, anchor?: Node) {
dev$element(node, `mount`, { target, anchor });
dev$element(node, `onMount`, { target, anchor });
target.insertBefore(node, anchor || null);
}
export function detach(node: Node) {
dev$element(node, `unmount`);
dev$element(node, `onDestroy`);
node.parentNode.removeChild(node);
}
@ -31,18 +31,10 @@ export function element_is<K extends keyof HTMLElementTagNameMap>(name: K, is: s
if (__DEV__) return dev$create(document.createElement<K>(name));
return document.createElement<K>(name, { is });
}
export function object_without_properties<T, K extends keyof T>(obj: T, exclude: K[]) {
const target = {} as Pick<T, Exclude<keyof T, K>>;
for (const k in obj) {
if (
has_prop(obj, k) &&
// @ts-ignore
exclude.indexOf(k) === -1
) {
// @ts-ignore
target[k] = obj[k];
}
}
export function object_without_properties<T, K extends string[]>(obj: T, excluded: K) {
const target = {} as Pick<T, Exclude<keyof T, keyof K>>;
let key;
for (key in obj) if (!~excluded.indexOf(key)) target[key] = obj[key];
return target;
}
@ -116,7 +108,7 @@ export function attr(node: Element, name: string, value?: any) {
}
export function set_attributes(node: HTMLElement, attributes: { [x: string]: any }) {
// @ts-ignore #3687
// @ts-ignore
const descriptors = Object.getOwnPropertyDescriptors(node.__proto__);
let name;
for (name in attributes) {
@ -264,26 +256,6 @@ export function select_multiple_value(select) {
return [].map.call(select.querySelectorAll(':checked'), (option) => option.__value);
}
// unfortunately this can't be a constant as that wouldn't be tree-shakeable
// so we cache the result instead
let crossorigin: boolean;
export function is_crossorigin() {
if (crossorigin === undefined) {
crossorigin = false;
try {
if (typeof window !== 'undefined' && window.parent) {
void window.parent.document;
}
} catch (error) {
crossorigin = true;
}
}
return crossorigin;
}
export function add_resize_listener(node: HTMLElement, fn: () => void) {
const computed_style = getComputedStyle(node);
const z_index = (parseInt(computed_style.zIndex) || 0) - 1;
@ -304,7 +276,7 @@ export function add_resize_listener(node: HTMLElement, fn: () => void) {
let unsubscribe: () => void;
if (is_crossorigin()) {
if (is_cors) {
iframe.src = `data:text/html,<script>onresize=function(){parent.postMessage(0,'*')}</script>`;
unsubscribe = listen(window, 'message', (event: MessageEvent) => {
if (event.source === iframe.contentWindow) fn();
@ -334,11 +306,6 @@ export function custom_event<T = any>(type: string, detail?: T) {
event.initCustomEvent(type, false, false, detail);
return event;
}
export function query_selector_all(selector: string, parent: HTMLElement = document.body) {
return Array.from(parent.querySelectorAll(selector));
}
export class HtmlTag {
e: HTMLElement;
n: ChildNode[];
@ -374,3 +341,18 @@ export class HtmlTag {
this.n.forEach(detach);
}
}
export const hasOwnProperty = Object.prototype.hasOwnProperty;
const nodeProto = Node.prototype;
export const insertBefore = nodeProto.insertBefore;
export const removeChild = nodeProto.removeChild;
export const replaceChild = nodeProto.replaceChild;
export const cloneNode = nodeProto.cloneNode;
const elementProto = Element.prototype;
export const setAttribute = elementProto.setAttribute;
export const setAttributeNS = elementProto.setAttributeNS;
export const removeAttribute = elementProto.removeAttribute;
const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
export const setClassName = getOwnPropertyDescriptor(elementProto, 'className').set;
export const getStyle = getOwnPropertyDescriptor(HTMLElement.prototype, 'style').get;
export const svg_getStyle = getOwnPropertyDescriptor(SVGElement.prototype, 'style').get;

@ -1,16 +1,30 @@
import { noop } from './utils';
export const resolved_promise = Promise.resolve();
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.self !== window.top;
export const is_cors =
is_iframe &&
(() => {
try {
if (window.parent) void window.parent.document;
return false;
} catch (error) {
return true;
}
})();
export const globals = ((is_client
? window
: typeof globalThis !== 'undefined'
? globalThis
: global) as unknown) as typeof globalThis;
export const has_Symbol = typeof Symbol === 'function';
export let now = is_client ? performance.now.bind(performance) : Date.now.bind(Date);
export let raf = is_client ? window.requestAnimationFrame : noop;
export let raf = is_client ? requestAnimationFrame : noop;
// used internally for testing
export function set_now(fn) {
now = fn;
}
export function set_raf(fn) {
raf = fn;
}
/* tests only */
export const set_now = (fn) => void (now = fn);
export const set_raf = (fn) => void (raf = fn);

@ -1,7 +0,0 @@
declare const global: any;
export const globals = (typeof window !== 'undefined'
? window
: typeof globalThis !== 'undefined'
? globalThis
: global) as unknown as typeof globalThis;

@ -2,10 +2,10 @@ export * from './animations';
export * from './await_block';
export * from './Component';
export * from './dev.legacy';
export * from './dev';
export * from './dev.utils';
export * from './dev.tools';
export * from './dom';
export * from './environment';
export * from './globals';
export * from './keyed_each';
export * from './lifecycle';
export * from './loop';

@ -1,27 +1,18 @@
import { transition_in, transition_out } from './transitions';
export function destroy_block(block, lookup) {
block.d(1);
lookup.delete(block.key);
}
export function outro_and_destroy_block(block, lookup) {
transition_out(block, 1, 1, () => {
lookup.delete(block.key);
});
}
export function fix_and_destroy_block(block, lookup) {
block.f();
destroy_block(block, lookup);
}
export function fix_and_outro_and_destroy_block(block, lookup) {
block.f();
outro_and_destroy_block(block, lookup);
}
export function update_keyed_each(old_blocks, dirty, get_key, dynamic, ctx, list, lookup, node, destroy, create_each_block, next, get_context) {
import { transition_in } from './transitions';
export function update_keyed_each(
old_blocks,
dirty,
ctx,
state,
get_key,
list,
lookup,
node,
create_each_block,
next,
get_context,
transition_out?
) {
let o = old_blocks.length;
let n = list.length;
@ -42,11 +33,11 @@ export function update_keyed_each(old_blocks, dirty, get_key, dynamic, ctx, list
if (!block) {
block = create_each_block(key, child_ctx);
block.c();
} else if (dynamic) {
} else if (state & 1) {
block.p(child_ctx, dirty);
}
new_lookup.set(key, new_blocks[i] = block);
new_lookup.set(key, (new_blocks[i] = block));
if (key in old_indexes) deltas.set(key, Math.abs(i - old_indexes[key]));
}
@ -54,13 +45,18 @@ export function update_keyed_each(old_blocks, dirty, get_key, dynamic, ctx, list
const will_move = new Set();
const did_move = new Set();
function insert(block) {
const insert = (block) => {
transition_in(block, 1);
block.m(node, next, lookup.has(block.key));
lookup.set(block.key, block);
next = block.first;
n--;
}
};
const destroy = (block) => {
if (state & 2) block.f();
if (state & 4) transition_out(block, () => lookup.delete(block.key));
else block.d(1), lookup.delete(block.key);
};
while (o && n) {
const new_block = new_blocks[n - 1];
@ -73,25 +69,17 @@ export function update_keyed_each(old_blocks, dirty, get_key, dynamic, ctx, list
next = new_block.first;
o--;
n--;
}
else if (!new_lookup.has(old_key)) {
} else if (!new_lookup.has(old_key)) {
// remove old block
destroy(old_block, lookup);
destroy(old_block);
o--;
}
else if (!lookup.has(new_key) || will_move.has(new_key)) {
} else if (!lookup.has(new_key) || will_move.has(new_key)) {
insert(new_block);
}
else if (did_move.has(old_key)) {
} else if (did_move.has(old_key)) {
o--;
} else if (deltas.get(new_key) > deltas.get(old_key)) {
did_move.add(new_key);
insert(new_block);
} else {
will_move.add(old_key);
o--;
@ -100,7 +88,7 @@ export function update_keyed_each(old_blocks, dirty, get_key, dynamic, ctx, list
while (o--) {
const old_block = old_blocks[o];
if (!new_lookup.has(old_block.key)) destroy(old_block, lookup);
if (!new_lookup.has(old_block.key)) destroy(old_block);
}
while (n) insert(new_blocks[n - 1]);

@ -1,12 +1,13 @@
import { custom_event } from './dom';
import { dev$assert, SvelteComponentDev } from './dev';
import { SvelteComponentDev } from './dev.utils';
import { SvelteComponent } from './Component';
import { dev$assert } from './dev.tools';
export let current_component: SvelteComponentDev | SvelteComponent | null;
export const set_current_component = (component) => (current_component = component);
const dev$guard = (name: string) =>
const dev$on_init_only = (name: string) =>
dev$assert(!!current_component, `${name} cannot be called outside of component initialization`);
export function get_current_component() {
@ -14,27 +15,27 @@ export function get_current_component() {
}
export function beforeUpdate(fn) {
dev$guard(`beforeUpdate`);
dev$on_init_only(`beforeUpdate`);
return current_component.$$.before_update.push(fn);
}
export function onMount(fn) {
dev$guard(`onMount`);
dev$on_init_only(`onMount`);
return current_component.$$.on_mount.push(fn);
}
export function afterUpdate(fn) {
dev$guard(`afterUpdate`);
dev$on_init_only(`afterUpdate`);
return current_component.$$.after_update.push(fn);
}
export function onDestroy(fn) {
dev$guard(`onDestroy`);
dev$on_init_only(`onDestroy`);
return current_component.$$.on_destroy.push(fn);
}
// todo : deprecate
export function createEventDispatcher() {
dev$guard(`createEventDispatcher`);
dev$on_init_only(`createEventDispatcher`);
const component = current_component;
return (type: string, detail?: any) => {
const callbacks = component.$$.callbacks[type];
@ -49,12 +50,12 @@ export function createEventDispatcher() {
}
export function setContext(key, context) {
dev$guard(`setContext`);
dev$on_init_only(`setContext`);
current_component.$$.context.set(key, context);
}
export function getContext(key) {
dev$guard(`getContext`);
dev$on_init_only(`getContext`);
return current_component.$$.context.get(key);
}

@ -17,7 +17,6 @@ const run = (t: number) => {
}
running_frame.length = 0;
if (next_frame_length) raf(run);
else console.log('ended loop');
};
type TimeoutTask = { timestamp: number; callback: (now: number) => void };

@ -1,73 +1,102 @@
import { set_current_component } from './lifecycle';
import { resolved_promise } from './environment';
const dirty_components = [];
import { T$$ } from './Component';
let update_scheduled = false;
export const schedule_update = (component) => {
dirty_components.push(component);
if (!update_scheduled) (update_scheduled = true), resolved_promise.then(flush);
};
export const tick = () =>
update_scheduled ? resolved_promise : ((update_scheduled = true), resolved_promise.then(flush));
let is_flushing = false;
// todo : remove binding_callbacks export
const dirty_components = [];
export const binding_callbacks = [];
const render_callbacks = [];
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 = [];
export const add_flush_callback = (fn) => void flush_callbacks.push(fn);
const measure_callbacks = [];
export const add_measure_callback = (fn) => void measure_callbacks.push(fn);
const flush_callbacks = [];
let flushing = false;
export function flush() {
if (flushing) return;
else flushing = true;
// todo : remove add_flush_callback
export const add_flush_callback = Array.prototype.push.bind(flush_callbacks);
export const add_measure_callback = Array.prototype.push.bind(measure_callbacks);
const seen_render_callbacks = new Set();
export const add_render_callback = (fn) => {
if (!seen_render_callbacks.has(fn)) {
seen_render_callbacks.add(fn);
render_callbacks.push(fn);
}
};
export const schedule_update = (component) => {
dirty_components.push(component);
if (!update_scheduled) {
update_scheduled = true;
resolved_promise.then(flush);
}
};
export const tick = () => {
if (!update_scheduled) {
update_scheduled = true;
return resolved_promise.then(flush);
}
return resolved_promise;
};
export const flush = () => {
if (is_flushing) return;
else is_flushing = true;
let i = 0,
j = 0,
$$,
$$: T$$,
dirty,
before_update,
dirty;
after_update;
// RUN LOGIC, CANCEL STYLES
do {
// update components + beforeUpdate
for (i = 0; i < dirty_components.length; i++) {
for (; i < dirty_components.length; i++) {
({ $$ } = set_current_component(dirty_components[i]));
if ($$.fragment === null) continue;
// todo : is this check still necessary ?
if (null === $$.fragment) continue;
/* run reactive statements */
$$.update();
for (j = 0, { before_update } = $$; j < before_update.length; j++) before_update[j]();
({ dirty } = $$);
$$.dirty = [-1];
if ($$.fragment) $$.fragment.p($$.ctx, dirty);
render_callbacks.push(...$$.after_update);
/* run beforeUpdate */
for (j = 0, { before_update } = $$; j < before_update.length; j++) {
before_update[j]();
}
/* update blocks */
({ dirty } = $$).dirty = [-1];
if (false !== $$.fragment) $$.fragment.p($$.ctx, dirty);
/* schedule afterUpdate */
for (j = 0, { after_update } = $$; j < after_update.length; j++) {
add_render_callback(after_update[j]);
}
}
dirty_components.length = 0;
// update bindings in reverse order
// update bindings [ in reverse order (#3145); is there a better way ? ]
i = binding_callbacks.length;
while (i--) binding_callbacks[i]();
binding_callbacks.length = 0;
binding_callbacks.length = i = 0;
// afterUpdate
for (i = 0; i < render_callbacks.length; i++) render_callbacks[i]();
render_callbacks.length = 0;
seen_callbacks.clear();
// run afterUpdates
// todo : remove every non afterUpdate callback from render_callbacks
for (; i < render_callbacks.length; i++) render_callbacks[i]();
render_callbacks.length = i = 0;
} while (dirty_components.length);
seen_render_callbacks.clear();
update_scheduled = false;
// STYLE MEASURE CHANGES
for (i = 0; i < measure_callbacks.length; i++) flush_callbacks.push(measure_callbacks[i]());
measure_callbacks.length = 0;
// measurement callbacks for animations
for (i = 0; i < measure_callbacks.length; i++) {
flush_callbacks.push(measure_callbacks[i]());
}
measure_callbacks.length = i = 0;
// APPLY STYLE CHANGES
for (i = 0; i < flush_callbacks.length; i++) flush_callbacks[i]();
// apply styles
// todo : remove every non style callback from flush_callbacks
for (; i < flush_callbacks.length; i++) flush_callbacks[i]();
flush_callbacks.length = 0;
flushing = false;
}
is_flushing = false;
};

@ -1,41 +1,16 @@
export function get_spread_update(levels, updates) {
export function get_spread_update(levels, updates: any[]) {
const update = {};
const to_null_out = {};
const accounted_for = { $$scope: 1 };
let i = levels.length;
while (i--) {
const o = levels[i];
const n = updates[i];
if (n) {
for (const key in o) {
if (!(key in n)) to_null_out[key] = 1;
}
for (const key in n) {
if (!accounted_for[key]) {
update[key] = n[key];
accounted_for[key] = 1;
}
}
let i = levels.length,
key,
n;
while (i--)
if ((n = updates[i])) {
for (key in levels[i]) if (!(key in n)) to_null_out[key] = 1;
for (key in n) if (!accounted_for[key]) (update[key] = n[key]), (accounted_for[key] = 1);
levels[i] = n;
} else {
for (const key in o) {
accounted_for[key] = 1;
}
}
}
for (const key in to_null_out) {
if (!(key in update)) update[key] = undefined;
}
} else for (key in levels[i]) accounted_for[key] = 1;
for (key in to_null_out) if (!(key in update)) update[key] = undefined;
return update;
}
export function get_spread_object(spread_props) {
return typeof spread_props === 'object' && spread_props !== null ? spread_props : {};
}

@ -1,5 +1,4 @@
import { set_current_component, current_component } from './lifecycle';
import { run_all, blank_object } from './utils';
import { boolean_attributes } from '../../compiler/compile/render_ssr/handlers/shared/boolean_attributes';
export const invalid_attribute_name_character = /[\s'">/=\u{FDD0}-\u{FDEF}\u{FFFE}\u{FFFF}\u{1FFFE}\u{1FFFF}\u{2FFFE}\u{2FFFF}\u{3FFFE}\u{3FFFF}\u{4FFFE}\u{4FFFF}\u{5FFFE}\u{5FFFF}\u{6FFFE}\u{6FFFF}\u{7FFFE}\u{7FFFF}\u{8FFFE}\u{8FFFF}\u{9FFFE}\u{9FFFF}\u{AFFFE}\u{AFFFF}\u{BFFFE}\u{BFFFF}\u{CFFFE}\u{CFFFF}\u{DFFFE}\u{DFFFF}\u{EFFFE}\u{EFFFF}\u{FFFFE}\u{FFFFF}\u{10FFFE}\u{10FFFF}]/u;
@ -17,13 +16,13 @@ export function spread(args, classes_to_add) {
}
let str = '';
Object.keys(attributes).forEach(name => {
Object.keys(attributes).forEach((name) => {
if (invalid_attribute_name_character.test(name)) return;
const value = attributes[name];
if (value === true) str += " " + name;
if (value === true) str += ' ' + name;
else if (boolean_attributes.has(name.toLowerCase())) {
if (value) str += " " + name;
if (value) str += ' ' + name;
} else if (value != null) {
str += ` ${name}="${String(value).replace(/"/g, '&#34;').replace(/'/g, '&#39;')}"`;
}
@ -37,11 +36,11 @@ export const escaped = {
"'": '&#39;',
'&': '&amp;',
'<': '&lt;',
'>': '&gt;'
'>': '&gt;',
};
export function escape(html) {
return String(html).replace(/["'&<>]/g, match => escaped[match]);
return String(html).replace(/["'&<>]/g, (match) => escaped[match]);
}
export function each(items, fn) {
@ -53,13 +52,15 @@ export function each(items, fn) {
}
export const missing_component = {
$$render: () => ''
$$render: () => '',
};
export function validate_component(component, name) {
if (!component || !component.$$render) {
if (name === 'svelte:component') name += ' this={...}';
throw new Error(`<${name}> is not a valid SSR component. You may need to review your build config to ensure that dependencies are compiled, rather than imported as pre-compiled modules`);
throw new Error(
`<${name}> is not a valid SSR component. You may need to review your build config to ensure that dependencies are compiled, rather than imported as pre-compiled modules`
);
}
return component;
@ -85,7 +86,7 @@ export function create_ssr_component(fn) {
on_mount: [],
before_update: [],
after_update: [],
callbacks: blank_object()
callbacks: Object.create(null),
};
set_current_component({ $$ });
@ -111,25 +112,29 @@ export function create_ssr_component(fn) {
const html = $$render(result, props, {}, options);
run_all(on_destroy);
for (let i = 0; i < on_destroy.length; i++) on_destroy[i]();
return {
html,
css: {
code: Array.from(result.css).map(css => css.code).join('\n'),
map: null // TODO
code: Array.from(result.css)
.map((css) => css.code)
.join('\n'),
map: null, // TODO
},
head: result.title + result.head
head: result.title + result.head,
};
},
$$render
$$render,
};
}
export function add_attribute(name, value, boolean) {
if (value == null || (boolean && !value)) return '';
return ` ${name}${value === true ? '' : `=${typeof value === 'string' ? JSON.stringify(escape(value)) : `"${value}"`}`}`;
return ` ${name}${
value === true ? '' : `=${typeof value === 'string' ? JSON.stringify(escape(value)) : `"${value}"`}`
}`;
}
export function add_classes(classes) {

@ -1,5 +1,5 @@
import { element } from './dom';
import { raf, now } from './environment';
import { raf } from './environment';
const enum SVELTE {
RULE = `__svelte_`,
STYLESHEET = `__svelte_stylesheet`,

@ -7,44 +7,54 @@ import { add_measure_callback } from './scheduler';
import { animate_css } from './style_manager';
type TransitionFn = (node: HTMLElement, params: any) => TransitionConfig;
type StopResetReverse = (reset_reverse?: 1 | -1) => StopResetReverse;
export type StopResetReverse = (reset_reverse?: 1 | -1) => StopResetReverse;
function startStopDispatcher(node: Element, is_intro: boolean) {
node.dispatchEvent(custom_event(`${is_intro ? 'intro' : 'outro'}start`));
return () => node.dispatchEvent(custom_event(`${is_intro ? 'intro' : 'outro'}end`));
}
const outroing = new Set();
let transition_group;
export const group_outros = () => void (transition_group = { p: transition_group, c: [], r: 0 });
export const check_outros = () => {
if (!transition_group.r) for (let i = 0; i < transition_group.c.length; i++) transition_group.c[i]();
transition_group = transition_group.p;
};
export const transition_in = (block: Fragment, local?: 0 | 1) => {
export const transition_in = (block: Fragment, local?) => {
if (!block || !block.i) return;
outroing.delete(block);
block.i(local);
};
export const transition_out = (block: Fragment, local?: 0 | 1, detach?: 0 | 1, callback?: () => void) => {
export const transition_out = (block: Fragment, local) => {
if (!block || !block.o || outroing.has(block)) return;
outroing.add(block);
block.o(local);
};
let transition_group;
const outroing = new Set();
const check_transition_group = (group, decrement = true) => {
if (decrement) group.r--;
if (!group.r) for (let i = 0; i < group.c.length; i++) group.c[i]();
};
export const group_transition_out = (fn) => {
const c = [];
const current_group = (transition_group = { p: transition_group, c, r: 0 });
fn((block, callback, detach = true) => {
if (!block || !block.o || outroing.has(block)) return;
outroing.add(block);
transition_group.c.push(() => {
if (!outroing.has(block)) return;
else outroing.delete(block);
if (!callback) return;
c.push(() => {
if (outroing.has(block)) {
outroing.delete(block);
if (detach) block.d(1);
// callback always ?
callback();
}
});
block.o(local);
block.o(1);
});
check_transition_group(current_group, false);
transition_group = transition_group.p;
};
// todo : deprecate
function startStopDispatcher(node: Element, is_intro: boolean) {
node.dispatchEvent(custom_event(`${is_intro ? 'intro' : 'outro'}start`));
return () => node.dispatchEvent(custom_event(`${is_intro ? 'intro' : 'outro'}end`));
}
/* todo: deprecate */
const swap = (fn, is_intro) => (is_intro ? (t) => fn(t, 1 - t) : (t) => fn(1 - t, t));
const swap = (fn, is_intro) =>
fn.length === 1 ? (is_intro ? fn : (t) => fn(1 - t)) : is_intro ? (t) => fn(t, 1 - t) : (t) => fn(1 - t, t);
const mirrored = (fn, is_intro, easing) => {
const run = swap(fn, is_intro);
@ -78,7 +88,7 @@ export const run_transition = (
let start_time = 0;
let end_time = 0;
const group = transition_group;
const current_group = transition_group;
if (!is_intro) transition_group.r++;
const start = ({ delay = 0, duration = 300, easing, tick, css, strategy = 'reverse' }: TransitionConfig) => {
@ -115,13 +125,7 @@ export const run_transition = (
}
}
if (!is_intro) {
if (!--group.r) {
for (let i = 0; i < group.c.length; i++) {
group.c[i]();
}
}
}
if (!is_intro) check_transition_group(current_group);
if (is_bidirectional) {
if (-1 === t) {

@ -1,52 +1,11 @@
export function noop() {}
export const is_promise = <T = any>(value: any): value is PromiseLike<T> =>
value && typeof value === 'object' && typeof value.then === 'function';
export const identity = (x) => x;
export const safe_not_equal = (a, b) =>
a != a ? b == b : a !== b || (a && typeof a === 'object') || typeof a === 'function';
export function assign<T, S>(tar: T, src: S): T & S {
// @ts-ignore
for (const k in src) tar[k] = src[k];
return tar as T & S;
}
export function is_promise<T = any>(value: any): value is PromiseLike<T> {
return value && typeof value === 'object' && typeof value.then === 'function';
}
export function add_location(element, file, line, column, char) {
element.__svelte_meta = {
loc: { file, line, column, char },
};
}
export function run(fn) {
return fn();
}
export function blank_object() {
return Object.create(null);
}
export function run_all(fns) {
fns.forEach(run);
}
export function is_function(thing: any): thing is Function {
return typeof thing === 'function';
}
export function safe_not_equal(a, b) {
return a != a ? b == b : a !== b || (a && typeof a === 'object') || typeof a === 'function';
}
export function not_equal(a, b) {
return a != a ? b == b : a !== b;
}
export function validate_store(store, name) {
if (store != null && typeof store.subscribe !== 'function') {
throw new Error(`'${name}' is not a store with a 'subscribe' method`);
}
}
export const not_equal = (a, b) => (a != a ? b == b : a !== b);
export function subscribe(store, subscriber, invalidator?) {
if (store == null) return noop;
@ -54,16 +13,6 @@ export function subscribe(store, subscriber, invalidator?) {
return unsub.unsubscribe ? () => unsub.unsubscribe() : unsub;
}
export function get_store_value(store) {
let value;
subscribe(store, (_) => (value = _))();
return value;
}
export function component_subscribe(component, store, callback) {
component.$$.on_destroy.push(subscribe(store, callback));
}
export function create_slot(definition, ctx, $$scope, fn) {
if (definition) {
const slot_ctx = get_slot_context(definition, ctx, $$scope, fn);
@ -72,45 +21,18 @@ export function create_slot(definition, ctx, $$scope, fn) {
}
export function get_slot_context(definition, ctx, $$scope, fn) {
return definition[1] && fn ? assign($$scope.ctx.slice(), definition[1](fn(ctx))) : $$scope.ctx;
return definition[1] && fn ? Object.assign($$scope.ctx.slice(), definition[1](fn(ctx))) : $$scope.ctx;
}
export function get_slot_changes(definition, $$scope, dirty, fn) {
if (definition[2] && fn) {
if (!definition[2] || !fn) return $$scope.dirty;
const lets = definition[2](fn(dirty));
if ($$scope.dirty === undefined) {
return lets;
}
if (typeof lets === 'object') {
const merged = [];
const len = Math.max($$scope.dirty.length, lets.length);
for (let i = 0; i < len; i += 1) {
merged[i] = $$scope.dirty[i] | lets[i];
}
if ($$scope.dirty === void 0) return lets;
else if (typeof lets === 'object') {
const merged = new Array(Math.max($$scope.dirty.length, lets.length));
for (let i = 0; i < merged.length; i += 1) merged[i] = $$scope.dirty[i] | lets[i];
return merged;
}
return $$scope.dirty | lets;
}
return $$scope.dirty;
}
export function exclude_internal_props(props) {
const result = {};
for (const k in props) if (k[0] !== '$') result[k] = props[k];
return result;
}
export function compute_rest_props(props, keys) {
const rest = {};
keys = new Set(keys);
let k: string;
for (k in props) if (!keys.has(k) && k[0] !== '$') rest[k] = props[k];
return rest;
} else return $$scope.dirty | lets;
}
export function once(fn) {
@ -121,18 +43,8 @@ export function once(fn) {
fn.call(this, ...args);
};
}
export function null_to_empty(value) {
return value == null ? '' : value;
}
export function set_store_value(store, ret, value = ret) {
store.set(value);
return ret;
}
export const has_prop = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
export const set_store_value = (store, ret, value) => (store.set(value || ret), ret);
export function action_destroyer(action_result) {
return action_result && is_function(action_result.destroy) ? action_result.destroy : noop;
return action_result && 'function' === typeof action_result.destroy ? action_result.destroy : noop;
}

@ -1,14 +1,14 @@
import { get_store_value, Writable, StartStopNotifier, Derived } from 'svelte/internal';
export { get_store_value as get };
export function readable<T>(value: T, start: StartStopNotifier<T>) {
import { Writable, StartStopNotifier, Derived } from 'svelte/internal';
export const get = (store) => (store.subscribe((v) => void (store = v))(), store);
export const readable = <T>(value: T, start: StartStopNotifier<T>) => {
const store = new Writable(value, start);
return { subscribe: store.subscribe.bind(store) };
}
export function writable<T>(value: T, start: StartStopNotifier<T>) {
};
export const writable = <T>(value: T, start: StartStopNotifier<T>) => {
const store = new Writable(value, start);
return { set: store.set.bind(store), update: store.update.bind(store), subscribe: store.subscribe.bind(store) };
}
export function derived(stores, deriver, initial_value?) {
};
export const derived = (stores, deriver, initial_value?) => {
const store = new Derived(stores, deriver, initial_value);
return { subscribe: store.subscribe.bind(store) };
}
};

@ -2,21 +2,21 @@ import { cubicOut, cubicInOut } from 'svelte/easing';
import { run_duration } from 'svelte/internal';
type EasingFunction = (t: number) => number;
interface BasicConfig {
interface AnyConfig {
delay?: number;
duration?: number;
easing?: EasingFunction;
strategy?: 'reverse' | 'mirror';
}
interface TimeableConfig extends Omit<BasicConfig, 'duration'> {
interface TimeableConfig extends Omit<AnyConfig, 'duration'> {
duration?: number | ((len: number) => number);
}
export interface TransitionConfig extends BasicConfig {
export interface TransitionConfig extends AnyConfig {
css?: (t: number, u?: number) => string;
tick?: (t: number, u?: number) => void;
}
interface BlurParams extends BasicConfig {
interface BlurParams extends AnyConfig {
amount: number;
opacity: number;
}
@ -37,12 +37,12 @@ export function blur(
};
}
export function fade(node: Element, { delay = 0, duration = 400, easing }: BasicConfig): TransitionConfig {
export function fade(node: Element, { delay = 0, duration = 400, easing }: AnyConfig): TransitionConfig {
const o = +getComputedStyle(node).opacity;
return { delay, duration, easing, css: (t) => `opacity: ${t * o};` };
}
interface FlyParams extends BasicConfig {
interface FlyParams extends AnyConfig {
x: number;
y: number;
opacity: number;
@ -64,7 +64,7 @@ export function fly(
};
}
export function slide(node: Element, { delay = 0, duration = 400, easing = cubicOut }: BasicConfig): TransitionConfig {
export function slide(node: Element, { delay = 0, duration = 400, easing = cubicOut }: AnyConfig): TransitionConfig {
const style = getComputedStyle(node);
const opacity = +style.opacity;
const height = parseFloat(style.height);
@ -91,7 +91,7 @@ export function slide(node: Element, { delay = 0, duration = 400, easing = cubic
};
}
interface ScaleParams extends BasicConfig {
interface ScaleParams extends AnyConfig {
start: number;
opacity: number;
}

@ -3,8 +3,8 @@
"include": [".", "../ambient.ts"],
"compilerOptions": {
"lib": ["es2015", "dom", "dom.iterable"],
"target": "es2015",
"lib": ["ESNext", "dom", "dom.iterable"],
"target": "ESNext",
"types": [],
"baseUrl": ".",

Loading…
Cancel
Save