Merge master into pr/3822

pull/3822/head
rixo 6 years ago
commit ae5d759da7

@ -1,5 +1,6 @@
**/_actual.js **/_actual.js
**/expected.js **/expected.js
_output
test/*/samples/*/output.js test/*/samples/*/output.js
node_modules node_modules

1
.gitignore vendored

@ -23,6 +23,7 @@ node_modules
/test/sourcemaps/samples/*/output.css.map /test/sourcemaps/samples/*/output.css.map
/yarn-error.log /yarn-error.log
_actual*.* _actual*.*
_output
/types /types
/site/cypress/screenshots/ /site/cypress/screenshots/

@ -1,10 +1,30 @@
# Svelte changelog # Svelte changelog
## Unreleased ## 3.16.5
* Better fix for cascading invalidations and fix some regressions ([#4098](https://github.com/sveltejs/svelte/issues/4098), [#4114](https://github.com/sveltejs/svelte/issues/4114), [#4120](https://github.com/sveltejs/svelte/issues/4120))
## 3.16.4
* Fix slots with props not propagating through to inner slots ([#4061](https://github.com/sveltejs/svelte/issues/4061))
* Fix noting autosubscribed stores as `referenced` in `vars` for tooling ([#4081](https://github.com/sveltejs/svelte/issues/4081))
* Fix cascading invalidations in certain situations ([#4094](https://github.com/sveltejs/svelte/issues/4094))
## 3.16.3
* Fix bitmask overflow when using slotted components ([#4077](https://github.com/sveltejs/svelte/issues/4077))
* Remove unnecessary `$$invalidate` calls from init block ([#4018](https://github.com/sveltejs/svelte/issues/4018))
## 3.16.2
* Handle slot updates when parent component has a bitmask overflow ([#4078](https://github.com/sveltejs/svelte/pull/4078))
## 3.16.1
* Fix unused export warning for props used as stores ([#4021](https://github.com/sveltejs/svelte/issues/4021)) * Fix unused export warning for props used as stores ([#4021](https://github.com/sveltejs/svelte/issues/4021))
* Fix `{:then}` without resolved value containing `{#each}` ([#4022](https://github.com/sveltejs/svelte/issues/4022)) * Fix `{:then}` without resolved value containing `{#each}` ([#4022](https://github.com/sveltejs/svelte/issues/4022))
* Fix incorrect code generated with `loopGuardTimeout` ([#4034](https://github.com/sveltejs/svelte/issues/4034)) * Fix incorrect code generated with `loopGuardTimeout` ([#4034](https://github.com/sveltejs/svelte/issues/4034))
* Fix handling of bitmask overflow and globals ([#4037](https://github.com/sveltejs/svelte/issues/4037))
* Fix `{:then}` containing `{#if}` ([#4044](https://github.com/sveltejs/svelte/issues/4044)) * Fix `{:then}` containing `{#if}` ([#4044](https://github.com/sveltejs/svelte/issues/4044))
* Fix bare `import`s in `format: 'cjs'` output mode ([#4055](https://github.com/sveltejs/svelte/issues/4050)) * Fix bare `import`s in `format: 'cjs'` output mode ([#4055](https://github.com/sveltejs/svelte/issues/4050))
* Warn when using a known global as a component name ([#4070](https://github.com/sveltejs/svelte/issues/4070)) * Warn when using a known global as a component name ([#4070](https://github.com/sveltejs/svelte/issues/4070))

@ -11,8 +11,8 @@
<img src="https://packagephobia.now.sh/badge?p=svelte" alt="install size"> <img src="https://packagephobia.now.sh/badge?p=svelte" alt="install size">
</a> </a>
<a href="https://travis-ci.org/sveltejs/svelte"> <a href="https://github.com/sveltejs/svelte/actions">
<img src="https://api.travis-ci.org/sveltejs/svelte.svg?branch=master" <img src="https://github.com/sveltejs/svelte/workflows/CI/badge.svg?branch=master"
alt="build status"> alt="build status">
</a> </a>

8
package-lock.json generated

@ -1,6 +1,6 @@
{ {
"name": "svelte", "name": "svelte",
"version": "3.15.0", "version": "3.16.5",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -500,9 +500,9 @@
"dev": true "dev": true
}, },
"code-red": { "code-red": {
"version": "0.0.26", "version": "0.0.27",
"resolved": "https://registry.npmjs.org/code-red/-/code-red-0.0.26.tgz", "resolved": "https://registry.npmjs.org/code-red/-/code-red-0.0.27.tgz",
"integrity": "sha512-W4t68vk3xJjmkbuAKfEtaj7E+K82BtV+A4VjBlxHA6gDoSLc+sTB643JdJMSk27vpp5iEqHFuGnHieQGy/GmUQ==", "integrity": "sha512-MSILIi8kkSGag76TW+PRfBP/dN++Ei+uTeiUfqW4Es5teFNrbsAWtyAbPwxKI1vxEsBx64loAfGxS1kVCo1d2g==",
"dev": true, "dev": true,
"requires": { "requires": {
"acorn": "^7.1.0", "acorn": "^7.1.0",

@ -1,6 +1,6 @@
{ {
"name": "svelte", "name": "svelte",
"version": "3.16.0", "version": "3.16.5",
"description": "Cybernetically enhanced web apps", "description": "Cybernetically enhanced web apps",
"module": "index.mjs", "module": "index.mjs",
"main": "index", "main": "index",
@ -64,7 +64,7 @@
"acorn": "^7.1.0", "acorn": "^7.1.0",
"agadoo": "^1.1.0", "agadoo": "^1.1.0",
"c8": "^5.0.1", "c8": "^5.0.1",
"code-red": "0.0.26", "code-red": "0.0.27",
"codecov": "^3.5.0", "codecov": "^3.5.0",
"css-tree": "1.0.0-alpha22", "css-tree": "1.0.0-alpha22",
"eslint": "^6.3.0", "eslint": "^6.3.0",

@ -29,13 +29,33 @@
font-weight: 700; font-weight: 700;
} }
div {
display: flex;
flex-direction: row;
padding: 0.2rem 3rem;
margin: 0 -3rem;
}
div.active {
background: rgba(0, 0, 0, 0.15) calc(100% - 3rem) 47% no-repeat
url(/icons/arrow-right.svg);
background-size: 1em 1em;
color: white;
}
div.active.loading {
background: rgba(0, 0, 0, 0.1) calc(100% - 3rem) 47% no-repeat
url(/icons/loading.svg);
background-size: 1em 1em;
color: white;
}
a { a {
display: flex; display: flex;
flex: 1 1 auto;
position: relative; position: relative;
color: var(--sidebar-text); color: var(--sidebar-text);
border-bottom: none; border-bottom: none;
padding: 0.2rem 3rem;
margin: 0 -3rem;
font-size: 1.6rem; font-size: 1.6rem;
align-items: center; align-items: center;
justify-content: start; justify-content: start;
@ -45,18 +65,11 @@
color: white; color: white;
} }
a.active { .repl-link {
background: rgba(0, 0, 0, 0.15) calc(100% - 3rem) 50% no-repeat flex: 0 1 auto;
url(/icons/arrow-right.svg); font-size: 1.2rem;
background-size: 1em 1em; font-weight: 700;
color: white; margin-right: 2.5rem;
}
a.active.loading {
background: rgba(0, 0, 0, 0.1) calc(100% - 3rem) 50% no-repeat
url(/icons/loading.svg);
background-size: 1em 1em;
color: white;
} }
.thumbnail { .thumbnail {
@ -72,27 +85,31 @@
<ul class="examples-toc"> <ul class="examples-toc">
{#each sections as section} {#each sections as section}
<li> <li>
<span class="section-title"> <span class="section-title">{section.title}</span>
{section.title}
</span>
{#each section.examples as example} {#each section.examples as example}
<a <div
href="examples#{example.slug}" class="row"
class="row" class:active={example.slug === active_section}
class:active="{example.slug === active_section}" class:loading={isLoading}>
class:loading="{isLoading}" <a
> href="examples#{example.slug}"
<img class="row"
class="thumbnail" class:active={example.slug === active_section}
alt="{example.title} thumbnail" class:loading={isLoading}>
src="examples/thumbnails/{example.slug}.jpg" <img
/> class="thumbnail"
alt="{example.title} thumbnail"
src="examples/thumbnails/{example.slug}.jpg" />
<span>{example.title}</span> <span>{example.title}</span>
</a> </a>
{/each} {#if example.slug === active_section}
</li> <a href="repl/{example.slug}" class="repl-link">REPL</a>
{/if}
</div>
{/each}
</li>
{/each} {/each}
</ul> </ul>

@ -85,7 +85,8 @@
<div style="grid-area: start; display: flex; flex-direction: column; min-width: 0" slot="how"> <div style="grid-area: start; display: flex; flex-direction: column; min-width: 0" slot="how">
<pre class="language-bash" style="margin: 0 0 1em 0; min-width: 0; min-height: 0"> <pre class="language-bash" style="margin: 0 0 1em 0; min-width: 0; min-height: 0">
npx degit sveltejs/template my-svelte-project npx degit <a href="https://github.com/sveltejs/template" style="user-select: initial;">sveltejs/template</a> my-svelte-project
<span class="token comment"># or download and extract <a href="https://github.com/sveltejs/template/archive/master.zip">this .zip file</a></span>
cd my-svelte-project cd my-svelte-project
npm install npm install

@ -187,7 +187,7 @@
<svelte:head> <svelte:head>
<title>{name} • REPL • Svelte</title> <title>{name} • REPL • Svelte</title>
<meta name="twitter:title" content="{name} | Svelte REPL"> <meta name="twitter:title" content="Svelte REPL">
<meta name="twitter:description" content="Cybernetically enhanced web apps"> <meta name="twitter:description" content="Cybernetically enhanced web apps">
<meta name="Description" content="Interactive Svelte playground"> <meta name="Description" content="Interactive Svelte playground">
</svelte:head> </svelte:head>

@ -203,7 +203,10 @@ export default class Component {
const subscribable_name = name.slice(1); const subscribable_name = name.slice(1);
const variable = this.var_lookup.get(subscribable_name); const variable = this.var_lookup.get(subscribable_name);
if (variable) variable.subscribable = true; if (variable) {
variable.referenced = true;
variable.subscribable = true;
}
} else { } else {
this.used_names.add(name); this.used_names.add(name);
} }

@ -33,7 +33,7 @@ function validate_options(options: CompileOptions, warnings: Warning[]) {
const { name, filename, loopGuardTimeout, dev } = options; const { name, filename, loopGuardTimeout, dev } = options;
Object.keys(options).forEach(key => { Object.keys(options).forEach(key => {
if (valid_options.indexOf(key) === -1) { if (!valid_options.includes(key)) {
const match = fuzzymatch(key, valid_options); const match = fuzzymatch(key, valid_options);
let message = `Unrecognized option '${key}'`; let message = `Unrecognized option '${key}'`;
if (match) message += ` (did you mean '${match}'?)`; if (match) message += ` (did you mean '${match}'?)`;

@ -15,6 +15,11 @@ interface ContextMember {
priority: number; priority: number;
} }
type BitMasks = Array<{
n: number;
names: string[];
}>;
export default class Renderer { export default class Renderer {
component: Component; // TODO Maybe Renderer shouldn't know about Component? component: Component; // TODO Maybe Renderer shouldn't know about Component?
options: CompileOptions; options: CompileOptions;
@ -81,8 +86,6 @@ export default class Renderer {
null null
); );
this.context_overflow = this.context.length > 31;
// TODO messy // TODO messy
this.blocks.forEach(block => { this.blocks.forEach(block => {
if (block instanceof Block) { if (block instanceof Block) {
@ -94,6 +97,8 @@ export default class Renderer {
this.fragment.render(this.block, null, x`#nodes` as Identifier); this.fragment.render(this.block, null, x`#nodes` as Identifier);
this.context_overflow = this.context.length > 31;
this.context.forEach(member => { this.context.forEach(member => {
const { variable } = member; const { variable } = member;
if (variable) { if (variable) {
@ -199,65 +204,53 @@ export default class Renderer {
? x`$$self.$$.dirty` ? x`$$self.$$.dirty`
: x`#dirty`) as Identifier | MemberExpression; : x`#dirty`) as Identifier | MemberExpression;
const get_bitmask = () => names.reduce((bitmask, name) => { const get_bitmask = () => {
const member = renderer.context_lookup.get(name); const bitmask: BitMasks = [];
names.forEach((name) => {
if (!member) return bitmask; const member = renderer.context_lookup.get(name);
if (member.index.value === -1) { if (!member) return;
throw new Error(`unset index`);
}
const value = member.index.value as number; if (member.index.value === -1) {
const i = (value / 31) | 0; throw new Error(`unset index`);
const n = 1 << (value % 31); }
if (!bitmask[i]) bitmask[i] = { n: 0, names: [] }; const value = member.index.value as number;
const i = (value / 31) | 0;
const n = 1 << (value % 31);
bitmask[i].n |= n; if (!bitmask[i]) bitmask[i] = { n: 0, names: [] };
bitmask[i].names.push(name);
bitmask[i].n |= n;
bitmask[i].names.push(name);
});
return bitmask; return bitmask;
}, Array((this.context.length / 31) | 0).fill(null)); };
let operator;
let left;
let right;
return { return {
get type() { // Using a ParenthesizedExpression allows us to create
// we make the type a getter, even though it's always // the expression lazily. TODO would be better if
// a BinaryExpression, because it gives us an opportunity // context was determined before rendering, so that
// to lazily create the node. TODO would be better if // this indirection was unnecessary
// context was determined before rendering, so that type: 'ParenthesizedExpression',
// this indirection was unnecessary get expression() {
const bitmask = get_bitmask(); const bitmask = get_bitmask();
if (!bitmask.length) {
return x`${dirty} & /*${names.join(', ')}*/ 0` as BinaryExpression;
}
if (renderer.context_overflow) { if (renderer.context_overflow) {
const expression = bitmask return bitmask
.map((b, i) => ({ b, i })) .map((b, i) => ({ b, i }))
.filter(({ b }) => b) .filter(({ b }) => b)
.map(({ b, i }) => x`${dirty}[${i}] & /*${b.names.join(', ')}*/ ${b.n}`) .map(({ b, i }) => x`${dirty}[${i}] & /*${b.names.join(', ')}*/ ${b.n}`)
.reduce((lhs, rhs) => x`${lhs} | ${rhs}`); .reduce((lhs, rhs) => x`${lhs} | ${rhs}`);
({ operator, left, right } = expression);
} else {
({ operator, left, right } = x`${dirty} & /*${names.join(', ')}*/ ${bitmask[0] ? bitmask[0].n : 0}` as BinaryExpression); // TODO the `: 0` case should never apply
} }
return 'BinaryExpression'; return x`${dirty} & /*${names.join(', ')}*/ ${bitmask[0].n}` as BinaryExpression;
},
get operator() {
return operator;
},
get left() {
return left;
},
get right() {
return right;
} }
} as Expression; } as any;
} }
reference(node: string | Identifier | MemberExpression) { reference(node: string | Identifier | MemberExpression) {

@ -3,7 +3,7 @@ import Component from '../Component';
import Renderer from './Renderer'; import Renderer from './Renderer';
import { CompileOptions } from '../../interfaces'; import { CompileOptions } from '../../interfaces';
import { walk } from 'estree-walker'; import { walk } from 'estree-walker';
import { extract_names } from '../utils/scope'; import { extract_names, Scope } from '../utils/scope';
import { invalidate } from './invalidate'; import { invalidate } from './invalidate';
import Block from './Block'; import Block from './Block';
import { ClassDeclaration, FunctionExpression, Node, Statement, ObjectExpression, Expression } from 'estree'; import { ClassDeclaration, FunctionExpression, Node, Statement, ObjectExpression, Expression } from 'estree';
@ -203,11 +203,18 @@ export default function dom(
if (component.ast.instance) { if (component.ast.instance) {
let scope = component.instance_scope; let scope = component.instance_scope;
const map = component.instance_scope_map; const map = component.instance_scope_map;
let execution_context: Node | null = null;
walk(component.ast.instance.content, { walk(component.ast.instance.content, {
enter: (node) => { enter(node) {
if (map.has(node)) { if (map.has(node)) {
scope = map.get(node); scope = map.get(node) as Scope;
if (!execution_context && !scope.block) {
execution_context = node;
}
} else if (!execution_context && node.type === 'LabeledStatement' && node.label.name === '$') {
execution_context = node;
} }
}, },
@ -216,6 +223,10 @@ export default function dom(
scope = scope.parent; scope = scope.parent;
} }
if (execution_context === node) {
execution_context = null;
}
if (node.type === 'AssignmentExpression' || node.type === 'UpdateExpression') { if (node.type === 'AssignmentExpression' || node.type === 'UpdateExpression') {
const assignee = node.type === 'AssignmentExpression' ? node.left : node.argument; const assignee = node.type === 'AssignmentExpression' ? node.left : node.argument;
@ -225,7 +236,7 @@ export default function dom(
// onto the initial function call // onto the initial function call
const names = new Set(extract_names(assignee)); const names = new Set(extract_names(assignee));
this.replace(invalidate(renderer, scope, node, names)); this.replace(invalidate(renderer, scope, node, names, execution_context === null));
} }
} }
}); });

@ -1,42 +1,50 @@
import { nodes_match } from '../../utils/nodes_match'; import { nodes_match } from '../../utils/nodes_match';
import { Scope } from '../utils/scope'; import { Scope } from '../utils/scope';
import { x } from 'code-red'; import { x } from 'code-red';
import { Node } from 'estree'; import { Node, Expression } from 'estree';
import Renderer from './Renderer'; import Renderer from './Renderer';
import { Var } from '../../interfaces';
export function invalidate(renderer: Renderer, scope: Scope, node: Node, names: Set<string>) { export function invalidate(renderer: Renderer, scope: Scope, node: Node, names: Set<string>, main_execution_context: boolean = false) {
const { component } = renderer; const { component } = renderer;
const [head, ...tail] = Array.from(names).filter(name => { const [head, ...tail] = Array.from(names)
const owner = scope.find_owner(name); .filter(name => {
if (owner && owner !== component.instance_scope) return false; const owner = scope.find_owner(name);
return !owner || owner === component.instance_scope;
})
.map(name => component.var_lookup.get(name))
.filter(variable => {
return variable && (
!variable.hoistable &&
!variable.global &&
!variable.module &&
(
variable.referenced ||
variable.subscribable ||
variable.is_reactive_dependency ||
variable.export_name ||
variable.name[0] === '$'
)
);
}) as Var[];
const variable = component.var_lookup.get(name); function get_invalidated(variable: Var, node?: Expression) {
if (main_execution_context && !variable.subscribable && variable.name[0] !== '$') {
return node || x`${variable.name}`;
}
return variable && ( return renderer.invalidate(variable.name);
!variable.hoistable && }
!variable.global &&
!variable.module &&
(
variable.referenced ||
variable.subscribable ||
variable.is_reactive_dependency ||
variable.export_name ||
variable.name[0] === '$'
)
);
});
if (head) { if (head) {
component.has_reactive_assignments = true; component.has_reactive_assignments = true;
if (node.type === 'AssignmentExpression' && node.operator === '=' && nodes_match(node.left, node.right) && tail.length === 0) { if (node.type === 'AssignmentExpression' && node.operator === '=' && nodes_match(node.left, node.right) && tail.length === 0) {
return renderer.invalidate(head); return get_invalidated(head, node);
} else { } else {
const is_store_value = head[0] === '$'; const is_store_value = head.name[0] === '$';
const variable = component.var_lookup.get(head); const extra_args = tail.map(variable => get_invalidated(variable));
const extra_args = tail.map(name => renderer.invalidate(name));
const pass_value = ( const pass_value = (
extra_args.length > 0 || extra_args.length > 0 ||
@ -47,16 +55,18 @@ export function invalidate(renderer: Renderer, scope: Scope, node: Node, names:
if (pass_value) { if (pass_value) {
extra_args.unshift({ extra_args.unshift({
type: 'Identifier', type: 'Identifier',
name: head name: head.name
}); });
} }
let invalidate = is_store_value let invalidate = is_store_value
? x`@set_store_value(${head.slice(1)}, ${node}, ${extra_args})` ? x`@set_store_value(${head.name.slice(1)}, ${node}, ${extra_args})`
: x`$$invalidate(${renderer.context_lookup.get(head).index}, ${node}, ${extra_args})`; : !main_execution_context
? x`$$invalidate(${renderer.context_lookup.get(head.name).index}, ${node}, ${extra_args})`
: node;
if (variable.subscribable && variable.reassigned) { if (head.subscribable && head.reassigned) {
const subscribe = `$$subscribe_${head}`; const subscribe = `$$subscribe_${head.name}`;
invalidate = x`${subscribe}(${invalidate})}`; invalidate = x`${subscribe}(${invalidate})}`;
} }

@ -182,11 +182,9 @@ export default class InlineComponentWrapper extends Wrapper {
}); });
}); });
const non_let_dependencies = Array.from(fragment_dependencies).filter(name => !this.node.scope.is_let(name));
const dynamic_attributes = this.node.attributes.filter(a => a.get_dependencies().length > 0); const dynamic_attributes = this.node.attributes.filter(a => a.get_dependencies().length > 0);
if (!uses_spread && (dynamic_attributes.length > 0 || this.node.bindings.length > 0 || non_let_dependencies.length > 0)) { if (!uses_spread && (dynamic_attributes.length > 0 || this.node.bindings.length > 0 || fragment_dependencies.size > 0)) {
updates.push(b`const ${name_changes} = {};`); updates.push(b`const ${name_changes} = {};`);
} }
@ -266,9 +264,9 @@ export default class InlineComponentWrapper extends Wrapper {
} }
} }
if (non_let_dependencies.length > 0) { if (fragment_dependencies.size > 0) {
updates.push(b` updates.push(b`
if (${renderer.dirty(non_let_dependencies)}) { if (${renderer.dirty(Array.from(fragment_dependencies))}) {
${name_changes}.$$scope = { dirty: #dirty, ctx: #ctx }; ${name_changes}.$$scope = { dirty: #dirty, ctx: #ctx };
}`); }`);
} }

@ -2,6 +2,7 @@ import Let from '../../../nodes/Let';
import { x, p } from 'code-red'; import { x, p } from 'code-red';
import Block from '../../Block'; import Block from '../../Block';
import TemplateScope from '../../../nodes/shared/TemplateScope'; import TemplateScope from '../../../nodes/shared/TemplateScope';
import { BinaryExpression } from 'estree';
export function get_slot_definition(block: Block, scope: TemplateScope, lets: Let[]) { export function get_slot_definition(block: Block, scope: TemplateScope, lets: Let[]) {
if (lets.length === 0) return { block, scope }; if (lets.length === 0) return { block, scope };
@ -28,21 +29,48 @@ export function get_slot_definition(block: Block, scope: TemplateScope, lets: Le
properties: Array.from(names).map(name => p`${block.renderer.context_lookup.get(name).index}: ${name}`) properties: Array.from(names).map(name => p`${block.renderer.context_lookup.get(name).index}: ${name}`)
}; };
const changes = Array.from(names) const { context_lookup } = block.renderer;
.map(name => {
const { context_lookup } = block.renderer;
const literal = { // i am well aware that this code is gross
type: 'Literal', // TODO make it less gross
get value() { const changes = {
type: 'ParenthesizedExpression',
get expression() {
if (block.renderer.context_overflow) {
const grouped = [];
Array.from(names).forEach(name => {
const i = context_lookup.get(name).index.value as number; const i = context_lookup.get(name).index.value as number;
return 1 << i; const g = Math.floor(i / 31);
if (!grouped[g]) grouped[g] = [];
grouped[g].push({ name, n: i % 31 });
});
const elements = [];
for (let g = 0; g < grouped.length; g += 1) {
elements[g] = grouped[g]
? grouped[g]
.map(({ name, n }) => x`${name} ? ${1 << n} : 0`)
.reduce((lhs, rhs) => x`${lhs} | ${rhs}`)
: x`0`;
} }
};
return x`${name} ? ${literal} : 0`; return {
}) type: 'ArrayExpression',
.reduce((lhs, rhs) => x`${lhs} | ${rhs}`); elements
};
}
return Array.from(names)
.map(name => {
const i = context_lookup.get(name).index.value as number;
return x`${name} ? ${1 << i} : 0`;
})
.reduce((lhs, rhs) => x`${lhs} | ${rhs}`) as BinaryExpression;
}
};
return { return {
block, block,

@ -73,8 +73,9 @@ function update($$) {
if ($$.fragment !== null) { if ($$.fragment !== null) {
$$.update(); $$.update();
run_all($$.before_update); run_all($$.before_update);
$$.fragment && $$.fragment.p($$.ctx, $$.dirty); const dirty = $$.dirty;
$$.dirty = [-1]; $$.dirty = [-1];
$$.fragment && $$.fragment.p($$.ctx, dirty);
$$.after_update.forEach(add_render_callback); $$.after_update.forEach(add_render_callback);
} }

@ -77,9 +77,23 @@ export function get_slot_context(definition, ctx, $$scope, fn) {
} }
export function get_slot_changes(definition, $$scope, dirty, fn) { export function get_slot_changes(definition, $$scope, dirty, fn) {
return definition[2] && fn if (definition[2] && fn) {
? $$scope.dirty | definition[2](fn(dirty)) const lets = definition[2](fn(dirty));
: $$scope.dirty;
if (typeof $$scope.dirty === '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];
}
return merged;
}
return $$scope.dirty | lets;
}
return $$scope.dirty;
} }
export function exclude_internal_props(props) { export function exclude_internal_props(props) {

@ -1,6 +1,7 @@
import * as jsdom from 'jsdom'; import * as jsdom from 'jsdom';
import * as assert from 'assert'; import * as assert from 'assert';
import * as glob from 'tiny-glob/sync.js'; import * as glob from 'tiny-glob/sync.js';
import * as path from 'path';
import * as fs from 'fs'; import * as fs from 'fs';
import * as colors from 'kleur'; import * as colors from 'kleur';
@ -237,3 +238,16 @@ export function useFakeTimers() {
} }
}; };
} }
export function mkdirp(dir) {
const parent = path.dirname(dir);
if (parent === dir) return;
mkdirp(parent);
try {
fs.mkdirSync(dir);
} catch (err) {
// do nothing
}
}

@ -43,7 +43,7 @@ function instance($$self, $$props, $$invalidate) {
let $foo; let $foo;
const foo = writable(0); const foo = writable(0);
component_subscribe($$self, foo, value => $$invalidate(0, $foo = value)); component_subscribe($$self, foo, value => $$invalidate(0, $foo = value));
return [$foo]; return [$foo, foo];
} }
class Component extends SvelteComponent { class Component extends SvelteComponent {

@ -0,0 +1,75 @@
/* generated by Svelte vX.Y.Z */
import {
SvelteComponent,
append,
detach,
element,
init,
insert,
noop,
safe_not_equal,
set_data,
text
} from "svelte/internal";
function create_fragment(ctx) {
let p;
let t0;
let t1;
return {
c() {
p = element("p");
t0 = text("x: ");
t1 = text(/*x*/ ctx[0]);
},
m(target, anchor) {
insert(target, p, anchor);
append(p, t0);
append(p, t1);
},
p(ctx, [dirty]) {
if (dirty & /*x*/ 1) set_data(t1, /*x*/ ctx[0]);
},
i: noop,
o: noop,
d(detaching) {
if (detaching) detach(p);
}
};
}
function instance($$self, $$props, $$invalidate) {
let x = 0;
let y = 1;
x += 1;
{
x += 2;
}
setTimeout(
function foo() {
$$invalidate(0, x += 10);
$$invalidate(1, y += 20);
},
1000
);
$$self.$$.update = () => {
if ($$self.$$.dirty & /*x, y*/ 3) {
$: $$invalidate(0, x += y);
}
};
return [x];
}
class Component extends SvelteComponent {
constructor(options) {
super();
init(this, options, instance, create_fragment, safe_not_equal, {});
}
}
export default Component;

@ -0,0 +1,19 @@
<script>
let x = 0;
let y = 1;
x += 1;
{
x += 2;
}
setTimeout(function foo() {
x += 10;
y += 20;
}, 1000);
$: x += y;
</script>
<p>x: {x}</p>

@ -3,6 +3,7 @@ import * as path from "path";
import * as fs from "fs"; import * as fs from "fs";
import { rollup } from 'rollup'; import { rollup } from 'rollup';
import * as virtual from 'rollup-plugin-virtual'; import * as virtual from 'rollup-plugin-virtual';
import * as glob from 'tiny-glob/sync.js';
import { clear_loops, flush, set_now, set_raf } from "../../internal"; import { clear_loops, flush, set_now, set_raf } from "../../internal";
import { import {
@ -10,7 +11,8 @@ import {
loadConfig, loadConfig,
loadSvelte, loadSvelte,
env, env,
setupHtmlEqual setupHtmlEqual,
mkdirp
} from "../helpers.js"; } from "../helpers.js";
let svelte$; let svelte$;
@ -90,6 +92,33 @@ describe("runtime", () => {
const window = env(); const window = env();
glob('**/*.svelte', { cwd }).forEach(file => {
if (file[0] === '_') return;
const dir = `${cwd}/_output/${hydrate ? 'hydratable' : 'normal'}`;
const out = `${dir}/${file.replace(/\.svelte$/, '.js')}`;
if (fs.existsSync(out)) {
fs.unlinkSync(out);
}
mkdirp(dir);
try {
const { js } = compile(
fs.readFileSync(`${cwd}/${file}`, 'utf-8'),
{
...compileOptions,
filename: file
}
);
fs.writeFileSync(out, js.code);
} catch (err) {
// do nothing
}
});
return Promise.resolve() return Promise.resolve()
.then(() => { .then(() => {
// hack to support transition tests // hack to support transition tests
@ -195,7 +224,7 @@ describe("runtime", () => {
} else { } else {
throw err; throw err;
} }
}).catch(err => { }).catch(err => {
failed.add(dir); failed.add(dir);
showOutput(cwd, compileOptions, compile); // eslint-disable-line no-console showOutput(cwd, compileOptions, compile); // eslint-disable-line no-console
throw err; throw err;

@ -0,0 +1,3 @@
export default {
error: `potato is not defined`,
};

@ -0,0 +1,35 @@
<script>
export let x1;
export let x2;
export let x3;
export let x4;
export let x5;
export let x6;
export let x7;
export let x8;
export let x9;
export let x10;
export let x11;
export let x12;
export let x13;
export let x14;
export let x15;
export let x16;
export let x17;
export let x18;
export let x19;
export let x20;
export let x21;
export let x22;
export let x23;
export let x24;
export let x25;
export let x26;
export let x27;
export let x28;
export let x29;
export let x30;
export let x31;
export let x32;
</script>
<p {...potato}>{x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x10 + x11 + x12 + x13 + x14 + x15 + x16 + x17 + x18 + x19 + x20 + x21 + x22 + x23 + x24 + x25 + x26 + x27 + x28 + x29 + x30 + x31 + x32}</p>

@ -0,0 +1,3 @@
export default {
error: `A is not defined`,
};

@ -0,0 +1,4 @@
<script>
let x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22, x23, x24, x25, x26, x27, x28, x29, x30, x31;
</script>
<A>foo</A>

@ -0,0 +1,73 @@
<script>
export let d1 = 'd1';
export let d2 = 'd2';
export let d3 = 'd3';
export let d4 = 'd4';
export let d5 = 'd5';
export let d6 = 'd6';
export let d7 = 'd7';
export let d8 = 'd8';
export let d9 = 'd9';
export let d10 = 'd10';
export let d11 = 'd11';
export let d12 = 'd12';
export let d13 = 'd13';
export let d14 = 'd14';
export let d15 = 'd15';
export let d16 = 'd16';
export let d17 = 'd17';
export let d18 = 'd18';
export let d19 = 'd19';
export let d20 = 'd20';
export let d21 = 'd21';
export let d22 = 'd22';
export let d23 = 'd23';
export let d24 = 'd24';
export let d25 = 'd25';
export let d26 = 'd26';
export let d27 = 'd27';
export let d28 = 'd28';
export let d29 = 'd29';
export let d30 = 'd30';
export let d31 = 'd31';
export let d32 = 'd32';
export let d33 = 'd33';
$: dummy = d32 + ':' + d33;
</script>
<p>{d1}</p>
<p>{d2}</p>
<p>{d3}</p>
<p>{d4}</p>
<p>{d5}</p>
<p>{d6}</p>
<p>{d7}</p>
<p>{d8}</p>
<p>{d9}</p>
<p>{d10}</p>
<p>{d11}</p>
<p>{d12}</p>
<p>{d13}</p>
<p>{d14}</p>
<p>{d15}</p>
<p>{d16}</p>
<p>{d17}</p>
<p>{d18}</p>
<p>{d19}</p>
<p>{d20}</p>
<p>{d21}</p>
<p>{d22}</p>
<p>{d23}</p>
<p>{d24}</p>
<p>{d25}</p>
<p>{d26}</p>
<p>{d27}</p>
<p>{d28}</p>
<p>{d29}</p>
<p>{d30}</p>
<p>{d31}</p>
<p>{d32}</p>
<p>{d33}</p>
<slot dummy={dummy}></slot>

@ -0,0 +1,96 @@
export default {
html: `
<p>d1</p>
<p>d2</p>
<p>d3</p>
<p>d4</p>
<p>d5</p>
<p>d6</p>
<p>d7</p>
<p>d8</p>
<p>d9</p>
<p>d10</p>
<p>d11</p>
<p>d12</p>
<p>d13</p>
<p>d14</p>
<p>d15</p>
<p>d16</p>
<p>d17</p>
<p>d18</p>
<p>d19</p>
<p>d20</p>
<p>d21</p>
<p>d22</p>
<p>d23</p>
<p>d24</p>
<p>d25</p>
<p>d26</p>
<p>d27</p>
<p>d28</p>
<p>d29</p>
<p>d30</p>
<p>d31</p>
<p>2</p>
<p>1</p>
<p>0:1</p>
<p>2:1</p>
<p>0</p>
<p>1</p>
<p>2</p>
`,
test({ assert, component, target }) {
component.reads = {};
component._0 = 'a';
component._1 = 'b';
component._2 = 'c';
assert.htmlEqual(target.innerHTML, `
<p>d1</p>
<p>d2</p>
<p>d3</p>
<p>d4</p>
<p>d5</p>
<p>d6</p>
<p>d7</p>
<p>d8</p>
<p>d9</p>
<p>d10</p>
<p>d11</p>
<p>d12</p>
<p>d13</p>
<p>d14</p>
<p>d15</p>
<p>d16</p>
<p>d17</p>
<p>d18</p>
<p>d19</p>
<p>d20</p>
<p>d21</p>
<p>d22</p>
<p>d23</p>
<p>d24</p>
<p>d25</p>
<p>d26</p>
<p>d27</p>
<p>d28</p>
<p>d29</p>
<p>d30</p>
<p>d31</p>
<p>c</p>
<p>b</p>
<p>a:b</p>
<p>c:b</p>
<p>a</p>
<p>b</p>
<p>c</p>
`);
assert.deepEqual(component.reads, {
_0: 2,
_1: 2,
});
}
};

@ -0,0 +1,30 @@
<script>
import Echo from './Echo.svelte';
export let reads = {};
export let _0 = '0';
export let _1 = '1';
export let _2 = '2';
$: bar = read(_0, '_0') + ':' + read(_1, '_1');
const read = (value, label) => {
if (!reads[label]) reads[label] = 0;
reads[label] += 1;
return value;
};
</script>
<Echo
let:dummy
d33={_1}
d32={_2}
>
<p>{bar}</p>
<p>{dummy}</p>
<p>{_0}</p>
<p>{_1}</p>
<p>{_2}</p>
</Echo>

@ -0,0 +1,5 @@
<script>
export let dummy;
</script>
<slot dummy={dummy}></slot>

@ -0,0 +1,124 @@
export default {
html: `
<p>0</p>
<p>1</p>
<p>2</p>
<p>3</p>
<p>4</p>
<p>5</p>
<p>6</p>
<p>7</p>
<p>8</p>
<p>9</p>
<p>10</p>
<p>11</p>
<p>12</p>
<p>13</p>
<p>14</p>
<p>15</p>
<p>16</p>
<p>17</p>
<p>18</p>
<p>19</p>
<p>20</p>
<p>21</p>
<p>22</p>
<p>23</p>
<p>24</p>
<p>25</p>
<p>26</p>
<p>27</p>
<p>28</p>
<p>29</p>
<p>30</p>
<p>31</p>
<p>32</p>
<p>33</p>
<p>34</p>
<p>35</p>
<p>36</p>
<p>37</p>
<p>38</p>
<p>39</p>
<p>40</p>
<p>5:36</p>
<p>6:37</p>
<p>38</p>
<p>0</p>
`,
test({ assert, component, target }) {
component.reads = {};
component._0 = 'a';
component._30 = 'b';
component._31 = 'c';
component._32 = 'd';
component._40 = 'e';
component._5 = 'f';
component._6 = 'g';
component._36 = 'h';
component._37 = 'i';
assert.htmlEqual(target.innerHTML, `
<p>a</p>
<p>1</p>
<p>2</p>
<p>3</p>
<p>4</p>
<p>f</p>
<p>g</p>
<p>7</p>
<p>8</p>
<p>9</p>
<p>10</p>
<p>11</p>
<p>12</p>
<p>13</p>
<p>14</p>
<p>15</p>
<p>16</p>
<p>17</p>
<p>18</p>
<p>19</p>
<p>20</p>
<p>21</p>
<p>22</p>
<p>23</p>
<p>24</p>
<p>25</p>
<p>26</p>
<p>27</p>
<p>28</p>
<p>29</p>
<p>b</p>
<p>c</p>
<p>d</p>
<p>33</p>
<p>34</p>
<p>35</p>
<p>h</p>
<p>i</p>
<p>38</p>
<p>39</p>
<p>e</p>
<p>f:h</p>
<p>g:i</p>
<p>38</p>
<p>a</p>
`);
assert.deepEqual(component.reads, {
_0: 1,
_5: 3,
_6: 3,
_30: 1,
_31: 1,
_32: 1,
_36: 3,
_37: 3,
_40: 1
});
}
};

@ -0,0 +1,107 @@
<script>
import Echo from './Echo.svelte';
export let reads = {};
export let _0 = '0';
export let _1 = '1';
export let _2 = '2';
export let _3 = '3';
export let _4 = '4';
export let _5 = '5';
export let _6 = '6';
export let _7 = '7';
export let _8 = '8';
export let _9 = '9';
export let _10 = '10';
export let _11 = '11';
export let _12 = '12';
export let _13 = '13';
export let _14 = '14';
export let _15 = '15';
export let _16 = '16';
export let _17 = '17';
export let _18 = '18';
export let _19 = '19';
export let _20 = '20';
export let _21 = '21';
export let _22 = '22';
export let _23 = '23';
export let _24 = '24';
export let _25 = '25';
export let _26 = '26';
export let _27 = '27';
export let _28 = '28';
export let _29 = '29';
export let _30 = '30';
export let _31 = '31';
export let _32 = '32';
export let _33 = '33';
export let _34 = '34';
export let _35 = '35';
export let _36 = '36';
export let _37 = '37';
export let _38 = '38';
export let _39 = '39';
export let _40 = '40';
$: foo = read(_6, '_6') + ':' + read(_37, '_37');
$: bar = read(_38, '_38');
const read = (value, label) => {
if (!reads[label]) reads[label] = 0;
reads[label] += 1;
return value;
};
</script>
<Echo dummy={_0} let:dummy>
<p>{read(_0, '_0')}</p>
<p>{read(_1, '_1')}</p>
<p>{read(_2, '_2')}</p>
<p>{read(_3, '_3')}</p>
<p>{read(_4, '_4')}</p>
<p>{read(_5, '_5')}</p>
<p>{read(_6, '_6')}</p>
<p>{read(_7, '_7')}</p>
<p>{read(_8, '_8')}</p>
<p>{read(_9, '_9')}</p>
<p>{read(_10, '_10')}</p>
<p>{read(_11, '_11')}</p>
<p>{read(_12, '_12')}</p>
<p>{read(_13, '_13')}</p>
<p>{read(_14, '_14')}</p>
<p>{read(_15, '_15')}</p>
<p>{read(_16, '_16')}</p>
<p>{read(_17, '_17')}</p>
<p>{read(_18, '_18')}</p>
<p>{read(_19, '_19')}</p>
<p>{read(_20, '_20')}</p>
<p>{read(_21, '_21')}</p>
<p>{read(_22, '_22')}</p>
<p>{read(_23, '_23')}</p>
<p>{read(_24, '_24')}</p>
<p>{read(_25, '_25')}</p>
<p>{read(_26, '_26')}</p>
<p>{read(_27, '_27')}</p>
<p>{read(_28, '_28')}</p>
<p>{read(_29, '_29')}</p>
<p>{read(_30, '_30')}</p>
<p>{read(_31, '_31')}</p>
<p>{read(_32, '_32')}</p>
<p>{read(_33, '_33')}</p>
<p>{read(_34, '_34')}</p>
<p>{read(_35, '_35')}</p>
<p>{read(_36, '_36')}</p>
<p>{read(_37, '_37')}</p>
<p>{read(_38, '_38')}</p>
<p>{read(_39, '_39')}</p>
<p>{read(_40, '_40')}</p>
<p>{read(_5, '_5') + ':' + read(_36, '_36')}</p>
<p>{foo}</p>
<p>{bar}</p>
<p>{dummy}</p>
</Echo>

@ -0,0 +1,5 @@
<script>
export let prop
</script>
<slot value={prop} />

@ -0,0 +1,12 @@
export default {
props: {
prop: 'a',
},
html: 'a',
test({ assert, component, target }) {
component.prop = 'b';
assert.htmlEqual( target.innerHTML, 'b' );
}
};

@ -0,0 +1,10 @@
<script>
import Outer from './Outer.svelte'
import Inner from './Inner.svelte'
export let prop
</script>
<Outer {prop} let:value>
<Inner>{value}</Inner>
</Outer>

@ -0,0 +1,8 @@
export default {
html: `
<p>OK</p>
<p>OK</p>
<pre>one</pre>
<pre>two</pre>
`
};

@ -0,0 +1,13 @@
<script>
let a = () => true;
export let data = [{ foo: [{ foo: [{bar: "one"}, {bar: "two"}] }] }];
</script>
{#each data as datum}
{#if datum.foo && a()}
<p>OK</p>
<svelte:self data={datum.foo}/>
{:else}
<pre>{datum.bar}</pre>
{/if}
{/each}

@ -0,0 +1,12 @@
<script>
import {writable} from 'svelte/store';
import Widget from './Widget.svelte';
let a = (writable({}));
let b = () => true;
</script>
<!-- if (reactive && non-reactive) -->
{#if $a || b() }
<Widget></Widget>
{:else}
<pre>fail</pre>
{/if}

@ -0,0 +1,22 @@
export default {
html: `
<input class="input" placeholder="Type here" type="text">
Dirty: false
Valid: false
`,
async test({ assert, target, window }) {
const input = target.querySelector('input');
input.value = 'foo';
const inputEvent = new window.InputEvent('input');
await input.dispatchEvent(inputEvent);
assert.htmlEqual(target.innerHTML, `
<input class="input" placeholder="Type here" type="text">
Dirty: true
Valid: true
`);
},
};

@ -0,0 +1,29 @@
<script>
import { writable } from 'svelte/store';
export function createValidator() {
const { subscribe, set } = writable({ dirty: false, valid: false });
function action(node, binding) {
return {
update(value) {
set({ dirty: true, valid: value !== '' });
}
};
}
return [{ subscribe }, action];
}
const [validity, validate] = createValidator();
let email = null;
</script>
<input class="input"
type="text"
bind:value={email}
placeholder="Type here"
use:validate={email}
/>
Dirty: {$validity.dirty}
Valid: {$validity.valid}

@ -0,0 +1,44 @@
export default {
html: `
<input>
<div></div>
<div>simple</div>
<button>click me</button>
`,
async test({ assert, component, target, window }) {
const input = target.querySelector('input');
const button = target.querySelector('button');
const inputEvent = new window.InputEvent('input');
const clickEvent = new window.MouseEvent('click');
input.value = 'foo';
await input.dispatchEvent(inputEvent);
assert.htmlEqual(target.innerHTML, `
<input>
<div>foo</div>
<div>foo</div>
<button>click me</button>
`);
await button.dispatchEvent(clickEvent);
assert.htmlEqual(target.innerHTML, `
<input>
<div>foo</div>
<div>clicked</div>
<button>click me</button>
`);
input.value = 'bar';
await input.dispatchEvent(inputEvent);
assert.htmlEqual(target.innerHTML, `
<input>
<div>bar</div>
<div>bar</div>
<button>click me</button>
`);
}
};

@ -0,0 +1,20 @@
<script>
import {writable} from 'svelte/store';
function action(node, binding) {
return {
update: (value) => s.set(value),
}
}
let s = writable("simple");
let v = "";
function click() {
s.set('clicked');
}
</script>
<input bind:value={v} use:action={v}>
<div>{v}</div>
<div>{$s}</div>
<button on:click={click}>click me</button>

@ -0,0 +1,44 @@
export default {
html: `
<div></div>
<div>simple</div>
<input>
<button>click me</button>
`,
async test({ assert, component, target, window }) {
const input = target.querySelector('input');
const button = target.querySelector('button');
const inputEvent = new window.InputEvent('input');
const clickEvent = new window.MouseEvent('click');
input.value = 'foo';
await input.dispatchEvent(inputEvent);
assert.htmlEqual(target.innerHTML, `
<div>foo</div>
<div>foo</div>
<input>
<button>click me</button>
`);
await button.dispatchEvent(clickEvent);
assert.htmlEqual(target.innerHTML, `
<div>foo</div>
<div>clicked</div>
<input>
<button>click me</button>
`);
input.value = 'bar';
await input.dispatchEvent(inputEvent);
assert.htmlEqual(target.innerHTML, `
<div>bar</div>
<div>bar</div>
<input>
<button>click me</button>
`);
}
};

@ -0,0 +1,20 @@
<script>
import {writable} from 'svelte/store';
function action(node, binding) {
return {
update: (value) => s.set(value),
}
}
let s = writable("simple");
let v = "";
function click() {
s.set('clicked');
}
</script>
<div>{v}</div>
<div>{$s}</div>
<input bind:value={v} use:action={v}>
<button on:click={click}>click me</button>

@ -8,7 +8,7 @@ export default {
module: false, module: false,
mutated: false, mutated: false,
reassigned: false, reassigned: false,
referenced: false, referenced: true,
referenced_from_script: false, referenced_from_script: false,
writable: true writable: true
}, },

Loading…
Cancel
Save