diff --git a/CHANGELOG.md b/CHANGELOG.md index ad33840527..c514152672 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## Unreleased +* Fix keyed `{#each}` not reacting to key changing ([#5444](https://github.com/sveltejs/svelte/issues/5444)) +* Fix destructuring into store values ([#5449](https://github.com/sveltejs/svelte/issues/5449)) +* Fix erroneous `missing-declaration` warning with `use:obj.method` ([#5451](https://github.com/sveltejs/svelte/issues/5451)) + +## 3.26.0 + * Support `use:obj.method` as actions ([#3935](https://github.com/sveltejs/svelte/issues/3935)) * Support `_` as numeric separator ([#5407](https://github.com/sveltejs/svelte/issues/5407)) * Fix assignments to properties on store values ([#5412](https://github.com/sveltejs/svelte/issues/5412)) diff --git a/package-lock.json b/package-lock.json index 2e2a95b362..6ea2f97a27 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.25.1", + "version": "3.26.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 7ebb41fdfb..6d558f35fc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.25.1", + "version": "3.26.0", "description": "Cybernetically enhanced web apps", "module": "index.mjs", "main": "index", diff --git a/site/content/blog/2019-04-15-setting-up-your-editor.md b/site/content/blog/2019-04-15-setting-up-your-editor.md index b80223d820..fb8287d0cb 100644 --- a/site/content/blog/2019-04-15-setting-up-your-editor.md +++ b/site/content/blog/2019-04-15-setting-up-your-editor.md @@ -30,7 +30,9 @@ To treat `*.svelte` files as HTML, open *__Edit → Config...__* and add the fol ## Vim/Neovim -To treat all `*.svelte` files as HTML, add the following line to your `init.vim`: +You can use the [coc-svelte extension](https://github.com/coc-extensions/coc-svelte) which utilises the official language-server. + +As an alternative you can treat all `*.svelte` files as HTML. Add the following line to your `init.vim`: ``` au! BufNewFile,BufRead *.svelte set ft=html @@ -50,13 +52,7 @@ To set the filetype for a single file, use a [modeline](https://vim.fandom.com/w ## Visual Studio Code -To treat `*.svelte` files as HTML, add the following lines to your `settings.json` file: - -```cson - "files.associations": { - "*.svelte": "html" - } -``` +We recommend using the official [Svelte for VS Code extension](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). ## JetBrains WebStorm diff --git a/site/content/tutorial/01-introduction/07-making-an-app/text.md b/site/content/tutorial/01-introduction/07-making-an-app/text.md index 4d044272b5..d75a9a626c 100644 --- a/site/content/tutorial/01-introduction/07-making-an-app/text.md +++ b/site/content/tutorial/01-introduction/07-making-an-app/text.md @@ -13,7 +13,7 @@ First, you'll need to integrate Svelte with a build tool. There are officially m Don't worry if you're relatively new to web development and haven't used these tools before. We've prepared a simple step-by-step guide, [Svelte for new developers](blog/svelte-for-new-developers), which walks you through the process. -You'll also want to configure your text editor to treat `.svelte` files the same as `.html` for the sake of syntax highlighting. [Read this guide to learn how](blog/setting-up-your-editor). +You'll also want to configure your text editor. If you're using VS Code, install the [Svelte extension](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode), otherwise follow [this guide](blog/setting-up-your-editor) to configure your text editor to treat `.svelte` files the same as `.html` for the sake of syntax highlighting. Then, once you've got your project set up, using Svelte components is easy. The compiler turns each component into a regular JavaScript class — just import it and instantiate with `new`: diff --git a/src/compiler/compile/nodes/Action.ts b/src/compiler/compile/nodes/Action.ts index 86aefa0ced..dc6f080b71 100644 --- a/src/compiler/compile/nodes/Action.ts +++ b/src/compiler/compile/nodes/Action.ts @@ -11,10 +11,11 @@ export default class Action extends Node { constructor(component: Component, parent, scope, info) { super(component, parent, scope, info); - component.warn_if_undefined(info.name, info, scope); + const object = info.name.split('.')[0]; + component.warn_if_undefined(object, info, scope); this.name = info.name; - component.add_reference(info.name.split('.')[0]); + component.add_reference(object); this.expression = info.expression ? new Expression(component, this, scope, info.expression) diff --git a/src/compiler/compile/render_dom/invalidate.ts b/src/compiler/compile/render_dom/invalidate.ts index b045db079f..b891b48cb5 100644 --- a/src/compiler/compile/render_dom/invalidate.ts +++ b/src/compiler/compile/render_dom/invalidate.ts @@ -36,47 +36,46 @@ export function invalidate(renderer: Renderer, scope: Scope, node: Node, names: return renderer.invalidate(variable.name, undefined, main_execution_context); } - if (head) { - component.has_reactive_assignments = true; - - if (node.type === 'AssignmentExpression' && node.operator === '=' && nodes_match(node.left, node.right) && tail.length === 0) { - 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)).filter(Boolean); - - const pass_value = ( - !main_execution_context && - ( - extra_args.length > 0 || - (node.type === 'AssignmentExpression' && node.left.type !== 'Identifier') || - (node.type === 'UpdateExpression' && (!node.prefix || node.argument.type !== 'Identifier')) - ) - ); + if (!head) { + return node; + } - if (pass_value) { - extra_args.unshift({ - type: 'Identifier', - name: head.name - }); - } + component.has_reactive_assignments = true; - let invalidate = is_store_value - ? x`@set_store_value(${head.name.slice(1)}, ${node}, ${head.name})` - : !main_execution_context - ? x`$$invalidate(${renderer.context_lookup.get(head.name).index}, ${node}, ${extra_args})` - : extra_args.length - ? [node, ...extra_args] - : node; + if (node.type === 'AssignmentExpression' && node.operator === '=' && nodes_match(node.left, node.right) && tail.length === 0) { + return get_invalidated(head, node); + } - if (head.subscribable && head.reassigned) { - const subscribe = `$$subscribe_${head.name}`; - invalidate = x`${subscribe}(${invalidate})`; - } + const is_store_value = head.name[0] === '$' && head.name[1] !== '$'; + const extra_args = tail.map(variable => get_invalidated(variable)).filter(Boolean); - return invalidate; + if (is_store_value) { + return x`@set_store_value(${head.name.slice(1)}, ${node}, ${head.name}, ${extra_args})`; + } + + let invalidate; + if (!main_execution_context) { + const pass_value = ( + extra_args.length > 0 || + (node.type === 'AssignmentExpression' && node.left.type !== 'Identifier') || + (node.type === 'UpdateExpression' && (!node.prefix || node.argument.type !== 'Identifier')) + ); + if (pass_value) { + extra_args.unshift({ + type: 'Identifier', + name: head.name + }); } + invalidate = x`$$invalidate(${renderer.context_lookup.get(head.name).index}, ${node}, ${extra_args})`; + } else { + // skip `$$invalidate` if it is in the main execution context + invalidate = extra_args.length ? [node, ...extra_args] : node; + } + + if (head.subscribable && head.reassigned) { + const subscribe = `$$subscribe_${head.name}`; + invalidate = x`${subscribe}(${invalidate})`; } - return node; + return invalidate; } \ No newline at end of file diff --git a/src/compiler/compile/render_dom/wrappers/EachBlock.ts b/src/compiler/compile/render_dom/wrappers/EachBlock.ts index 2513ea1adc..fe1c21a8df 100644 --- a/src/compiler/compile/render_dom/wrappers/EachBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/EachBlock.ts @@ -239,6 +239,11 @@ export default class EachBlockWrapper extends Wrapper { this.node.expression.dynamic_dependencies().forEach((dependency: string) => { all_dependencies.add(dependency); }); + if (this.node.key) { + this.node.key.dynamic_dependencies().forEach((dependency: string) => { + all_dependencies.add(dependency); + }); + } this.dependencies = all_dependencies; if (this.node.key) { diff --git a/test/runtime/samples/each-block-keyed-dyanmic-key/_config.js b/test/runtime/samples/each-block-keyed-dyanmic-key/_config.js new file mode 100644 index 0000000000..0aed1b0e07 --- /dev/null +++ b/test/runtime/samples/each-block-keyed-dyanmic-key/_config.js @@ -0,0 +1,27 @@ +let count = 0; +let value = 'foo'; + +export default { + props: { + value() { + count++; + return value; + } + }, + + html: ` +
foo
+
foo
+ `, + + test({ assert, component, target }) { + value = 'bar'; + component.id = 1; + + assert.equal(count, 4); + assert.htmlEqual(target.innerHTML, ` +
bar
+
bar
+ `); + } +}; diff --git a/test/runtime/samples/each-block-keyed-dyanmic-key/main.svelte b/test/runtime/samples/each-block-keyed-dyanmic-key/main.svelte new file mode 100644 index 0000000000..9a15c7d98e --- /dev/null +++ b/test/runtime/samples/each-block-keyed-dyanmic-key/main.svelte @@ -0,0 +1,8 @@ + + +{#each ['foo', 'bar'] as key (id + key)} +
{value()}
+{/each} \ No newline at end of file diff --git a/test/runtime/samples/reactive-assignment-in-complex-declaration-with-store-2/_config.js b/test/runtime/samples/reactive-assignment-in-complex-declaration-with-store-2/_config.js new file mode 100644 index 0000000000..f737cad2aa --- /dev/null +++ b/test/runtime/samples/reactive-assignment-in-complex-declaration-with-store-2/_config.js @@ -0,0 +1,9 @@ +// destructure to store value +export default { + skip_if_ssr: true, // pending https://github.com/sveltejs/svelte/issues/3582 + html: `

2 2 xxx 5 6 9 10 2

`, + async test({ assert, target, component }) { + await component.update(); + assert.htmlEqual(target.innerHTML, `

11 11 yyy 12 13 14 15 11

`); + } +}; \ No newline at end of file diff --git a/test/runtime/samples/reactive-assignment-in-complex-declaration-with-store-2/main.svelte b/test/runtime/samples/reactive-assignment-in-complex-declaration-with-store-2/main.svelte new file mode 100644 index 0000000000..c1bf63d9cd --- /dev/null +++ b/test/runtime/samples/reactive-assignment-in-complex-declaration-with-store-2/main.svelte @@ -0,0 +1,29 @@ + + +

{foo} {$eid} {$u.name} {$v} {$w} {$x} {$y} {$z}

diff --git a/test/runtime/samples/reactive-assignment-in-complex-declaration-with-store/_config.js b/test/runtime/samples/reactive-assignment-in-complex-declaration-with-store/_config.js index e74cea70fe..0318e63b0a 100644 --- a/test/runtime/samples/reactive-assignment-in-complex-declaration-with-store/_config.js +++ b/test/runtime/samples/reactive-assignment-in-complex-declaration-with-store/_config.js @@ -1,3 +1,9 @@ +// destructure to store export default { - html: `

2 2 xxx 5 6

` + html: `

2 2 xxx 5 6 9 10 2

`, + skip_if_ssr: true, + async test({ assert, target, component }) { + await component.update(); + assert.htmlEqual(target.innerHTML, `

11 11 yyy 12 13 14 15 11

`); + } }; \ No newline at end of file diff --git a/test/runtime/samples/reactive-assignment-in-complex-declaration-with-store/main.svelte b/test/runtime/samples/reactive-assignment-in-complex-declaration-with-store/main.svelte index 5ad442e1da..cd49223535 100644 --- a/test/runtime/samples/reactive-assignment-in-complex-declaration-with-store/main.svelte +++ b/test/runtime/samples/reactive-assignment-in-complex-declaration-with-store/main.svelte @@ -6,11 +6,24 @@ let u; let v; let w; + let x; + let y; [u, v, w] = [ {id: eid = writable(foo = 2), name: 'xxx'}, 5, writable(6) ]; + ({ a: x, b: y } = { a: writable(9), b: writable(10) }); + $: z = u.id; + + export function update() { + [u, v, w] = [ + {id: eid = writable(foo = 11), name: 'yyy'}, + 12, + writable(13) + ]; + ({ a: x, b: y } = { a: writable(14), b: writable(15) }); + } -

{foo} {$eid} {u.name} {v} {$w}

+

{foo} {$eid} {u.name} {v} {$w} {$x} {$y} {$z}

diff --git a/test/validator/samples/action-object/input.svelte b/test/validator/samples/action-object/input.svelte new file mode 100644 index 0000000000..e0962fa594 --- /dev/null +++ b/test/validator/samples/action-object/input.svelte @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/test/validator/samples/action-object/warnings.json b/test/validator/samples/action-object/warnings.json new file mode 100644 index 0000000000..e1ccbb55a2 --- /dev/null +++ b/test/validator/samples/action-object/warnings.json @@ -0,0 +1,17 @@ +[ + { + "code": "missing-declaration", + "end": { + "character": 217, + "column": 39, + "line": 11 + }, + "message": "'foo' is not defined", + "pos": 186, + "start": { + "character": 186, + "column": 8, + "line": 11 + } + } +]