From 07e2dde83d5ea46f29fb57049c57fcd5c6e4ba64 Mon Sep 17 00:00:00 2001 From: Christopher Mardell Date: Thu, 9 Jul 2020 11:45:30 +0930 Subject: [PATCH 01/34] Initialize custom elements in connected callback --- src/compiler/compile/render_dom/index.ts | 17 ++++++--- src/runtime/internal/Component.ts | 37 ++++++++++++++++++- .../samples/props-after-create/main.svelte | 8 ++++ .../samples/props-after-create/test.js | 23 ++++++++++++ test/custom-elements/samples/props/test.js | 11 +++++- 5 files changed, 87 insertions(+), 9 deletions(-) create mode 100644 test/custom-elements/samples/props-after-create/main.svelte create mode 100644 test/custom-elements/samples/props-after-create/test.js diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index 3b5001d483f1..5b4b47895ec1 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -90,8 +90,8 @@ export default function dom( ${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}`)};` - )} + 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`)};`} } @@ -190,8 +190,8 @@ export default function dom( ${$$props} => { ${uses_props && renderer.invalidate('$$props', x`$$props = @assign(@assign({}, $$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}`)};` + )} } `; @@ -462,10 +462,15 @@ export default function dom( class ${name} extends @SvelteElement { constructor(options) { super(); - + if (options) { + this.$$runSetup(options); + } + } + $$setup(options) { + console.log("$$setup"); ${css.code && b`this.shadowRoot.innerHTML = \`\`;`} - @init(this, { target: this.shadowRoot }, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_indexes}, ${dirty}); + @init(this, { props: options ? options.props : null, target: this.shadowRoot }, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_indexes}, ${dirty}); ${dev_props_check} diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index c0f6facdd25e..f1334c791ddf 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -171,7 +171,32 @@ if (typeof HTMLElement === 'function') { this.attachShadow({ mode: 'open' }); } + $$initialProps: Record | null = {}; + $$options: Record | null = {}; + $$setup(_options) { + // overridden by instance + } + + $$runSetup(options?: Record) { + console.log("$$runSetup"); + const {$$initialProps} = this; + if ($$initialProps) { + const opts = (options || this.$$options); + this.$$initialProps = null; + this.$$options = null; + this.$$setup({ + ...opts, + props: { + ...opts.props, + ...$$initialProps + } + }); + } + } + connectedCallback() { + console.log("connectedCallback"); + this.$$runSetup(); // @ts-ignore todo: improve typings for (const key in this.$$.slotted) { // @ts-ignore todo: improve typings @@ -179,7 +204,12 @@ if (typeof HTMLElement === 'function') { } } + // initial implementation of method, will be overridden on setup attributeChangedCallback(attr, _oldValue, newValue) { + console.log(`attributeChangedCallback: ${attr}, ${newValue}`); + if (this.$$initialProps) { + this.$$initialProps[attr] = newValue; + } this[attr] = newValue; } @@ -199,7 +229,12 @@ if (typeof HTMLElement === 'function') { }; } - $set() { + $set(obj) { + if (this.$$initialProps && obj) { + for (const attr of Object.getOwnPropertyNames(obj)) { + this.$$initialProps[attr] = obj[attr]; + } + } // overridden by instance, if it has props } }; diff --git a/test/custom-elements/samples/props-after-create/main.svelte b/test/custom-elements/samples/props-after-create/main.svelte new file mode 100644 index 000000000000..22c91751d099 --- /dev/null +++ b/test/custom-elements/samples/props-after-create/main.svelte @@ -0,0 +1,8 @@ + + + + +

{items.length} items

+

{items.join(', ')}

diff --git a/test/custom-elements/samples/props-after-create/test.js b/test/custom-elements/samples/props-after-create/test.js new file mode 100644 index 000000000000..a9e08c168e8e --- /dev/null +++ b/test/custom-elements/samples/props-after-create/test.js @@ -0,0 +1,23 @@ +import * as assert from 'assert'; +import CustomElement from './main.svelte'; + +export default async function (target) { + const el = new CustomElement(); + + assert.equal(el.outerHTML, ''); + + // const el = target.querySelector('custom-element'); + + assert.equal(el.shadowRoot, undefined); + + el.items = ['a', 'b', 'c']; + const [p1, p2] = el.shadowRoot.querySelectorAll('p'); + + assert.equal(p1.textContent, '3 items'); + assert.equal(p2.textContent, 'a, b, c'); + + el.items = ['d', 'e', 'f', 'g', 'h']; + + assert.equal(p1.textContent, '5 items'); + assert.equal(p2.textContent, 'd, e, f, g, h'); +} diff --git a/test/custom-elements/samples/props/test.js b/test/custom-elements/samples/props/test.js index 9c7e44c3a3f2..f5e12e0a55b8 100644 --- a/test/custom-elements/samples/props/test.js +++ b/test/custom-elements/samples/props/test.js @@ -1,7 +1,7 @@ import * as assert from 'assert'; import CustomElement from './main.svelte'; -export default function (target) { +export default async function (target) { new CustomElement({ target }); @@ -9,8 +9,15 @@ export default function (target) { assert.equal(target.innerHTML, ''); const el = target.querySelector('custom-element'); + + // await new Promise((resolve) => setTimeout(resolve, 100)); + // await new Promise((resolve) => setTimeout(resolve, 100)); const widget = el.shadowRoot.querySelector('my-widget'); + console.log(widget); + + // await new Promise((resolve) => setTimeout(resolve, 100)); + const [p1, p2] = widget.shadowRoot.querySelectorAll('p'); assert.equal(p1.textContent, '3 items'); @@ -20,4 +27,4 @@ export default function (target) { assert.equal(p1.textContent, '5 items'); assert.equal(p2.textContent, 'd, e, f, g, h'); -} \ No newline at end of file +} From 3c69b175be220a9bf37792b0ff06944e5e6b9f01 Mon Sep 17 00:00:00 2001 From: Christopher Mardell Date: Mon, 13 Jul 2020 21:09:54 +0930 Subject: [PATCH 02/34] Refactor $$setup function --- src/compiler/compile/render_dom/index.ts | 2 +- src/runtime/internal/Component.ts | 34 ++++++++++-------------- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index 5b4b47895ec1..868e274f2ec1 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -463,7 +463,7 @@ export default function dom( constructor(options) { super(); if (options) { - this.$$runSetup(options); + this.$$setup(options); } } $$setup(options) { diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index f1334c791ddf..73b24f84d0cb 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -171,32 +171,26 @@ if (typeof HTMLElement === 'function') { this.attachShadow({ mode: 'open' }); } + // placeholder object to allow props to be set pre-$$setup $$initialProps: Record | null = {}; - $$options: Record | null = {}; - $$setup(_options) { + $$setup(_options?) { // overridden by instance } - $$runSetup(options?: Record) { - console.log("$$runSetup"); - const {$$initialProps} = this; - if ($$initialProps) { - const opts = (options || this.$$options); - this.$$initialProps = null; - this.$$options = null; - this.$$setup({ - ...opts, - props: { - ...opts.props, - ...$$initialProps - } - }); - } - } - connectedCallback() { console.log("connectedCallback"); - this.$$runSetup(); + if (!this.$$) { + // wasn't set up from constructor as options were not ready + const options = Object.keys(this.$$initialProps).length ? + { + props: this.$$initialProps + } : + null; + + this.$$setup(options); + // clean up + this.$$initialProps = null; + } // @ts-ignore todo: improve typings for (const key in this.$$.slotted) { // @ts-ignore todo: improve typings From 8be9968e40b4123f003ade7be4ee82c8355aeee5 Mon Sep 17 00:00:00 2001 From: Christopher Mardell Date: Mon, 13 Jul 2020 21:10:24 +0930 Subject: [PATCH 03/34] Fix props-after-create test --- .../samples/props-after-create/test.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/test/custom-elements/samples/props-after-create/test.js b/test/custom-elements/samples/props-after-create/test.js index a9e08c168e8e..c90ccf68b013 100644 --- a/test/custom-elements/samples/props-after-create/test.js +++ b/test/custom-elements/samples/props-after-create/test.js @@ -2,17 +2,22 @@ import * as assert from 'assert'; import CustomElement from './main.svelte'; export default async function (target) { + + // initialize without options to simulate instantiation within HTML const el = new CustomElement(); assert.equal(el.outerHTML, ''); - // const el = target.querySelector('custom-element'); + el.items = ['a', 'b', 'c']; + const p0 = el.shadowRoot.querySelector('p'); - assert.equal(el.shadowRoot, undefined); + // shouldn't be instantitated yet + assert.equal(p0, undefined); - el.items = ['a', 'b', 'c']; - const [p1, p2] = el.shadowRoot.querySelectorAll('p'); + // simulate adding to DOM to trigger setup + el.connectedCallback(); + const [p1, p2] = el.shadowRoot.querySelectorAll('p'); assert.equal(p1.textContent, '3 items'); assert.equal(p2.textContent, 'a, b, c'); From 61b596cee843c1d2b7a4c40f9b16ec8cb76980dd Mon Sep 17 00:00:00 2001 From: Christopher Mardell Date: Mon, 13 Jul 2020 21:10:42 +0930 Subject: [PATCH 04/34] Revert changes to props test --- test/custom-elements/samples/props/test.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/test/custom-elements/samples/props/test.js b/test/custom-elements/samples/props/test.js index f5e12e0a55b8..783fe8fbdbe1 100644 --- a/test/custom-elements/samples/props/test.js +++ b/test/custom-elements/samples/props/test.js @@ -9,22 +9,13 @@ export default async function (target) { assert.equal(target.innerHTML, ''); const el = target.querySelector('custom-element'); - - // await new Promise((resolve) => setTimeout(resolve, 100)); - // await new Promise((resolve) => setTimeout(resolve, 100)); const widget = el.shadowRoot.querySelector('my-widget'); - - console.log(widget); - - // await new Promise((resolve) => setTimeout(resolve, 100)); - const [p1, p2] = widget.shadowRoot.querySelectorAll('p'); assert.equal(p1.textContent, '3 items'); assert.equal(p2.textContent, 'a, b, c'); el.items = ['d', 'e', 'f', 'g', 'h']; - assert.equal(p1.textContent, '5 items'); assert.equal(p2.textContent, 'd, e, f, g, h'); } From 142bd9153002558168a8c19cd63503833aec9e01 Mon Sep 17 00:00:00 2001 From: Christopher Mardell Date: Mon, 13 Jul 2020 21:27:58 +0930 Subject: [PATCH 05/34] Remove logging --- src/compiler/compile/render_dom/index.ts | 1 - src/runtime/internal/Component.ts | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index 868e274f2ec1..7ca2f16a6fc4 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -467,7 +467,6 @@ export default function dom( } } $$setup(options) { - console.log("$$setup"); ${css.code && b`this.shadowRoot.innerHTML = \`\`;`} @init(this, { props: options ? options.props : null, target: this.shadowRoot }, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_indexes}, ${dirty}); diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index 73b24f84d0cb..e5eef9631bc9 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -178,7 +178,6 @@ if (typeof HTMLElement === 'function') { } connectedCallback() { - console.log("connectedCallback"); if (!this.$$) { // wasn't set up from constructor as options were not ready const options = Object.keys(this.$$initialProps).length ? @@ -200,7 +199,6 @@ if (typeof HTMLElement === 'function') { // initial implementation of method, will be overridden on setup attributeChangedCallback(attr, _oldValue, newValue) { - console.log(`attributeChangedCallback: ${attr}, ${newValue}`); if (this.$$initialProps) { this.$$initialProps[attr] = newValue; } From 83a86d0e7273e77b0d79a755b9c6258c52f61b5c Mon Sep 17 00:00:00 2001 From: Christopher Mardell Date: Mon, 13 Jul 2020 21:38:58 +0930 Subject: [PATCH 06/34] Update expected output for css-shadow-dom-keyframes --- .../css-shadow-dom-keyframes/expected.js | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/test/js/samples/css-shadow-dom-keyframes/expected.js b/test/js/samples/css-shadow-dom-keyframes/expected.js index a0a0ebe0211b..91ef177ec63f 100644 --- a/test/js/samples/css-shadow-dom-keyframes/expected.js +++ b/test/js/samples/css-shadow-dom-keyframes/expected.js @@ -33,8 +33,26 @@ function create_fragment(ctx) { class Component extends SvelteElement { constructor(options) { super(); + + if (options) { + this.$$setup(options); + } + } + + $$setup(options) { this.shadowRoot.innerHTML = ``; - init(this, { target: this.shadowRoot }, null, create_fragment, safe_not_equal, {}); + + init( + this, + { + props: options ? options.props : null, + target: this.shadowRoot + }, + null, + create_fragment, + safe_not_equal, + {} + ); if (options) { if (options.target) { @@ -45,4 +63,4 @@ class Component extends SvelteElement { } customElements.define("custom-element", Component); -export default Component; \ No newline at end of file +export default Component; From db80d0c429ea594bfe87c5b14d6966531776f431 Mon Sep 17 00:00:00 2001 From: Christopher Mardell Date: Mon, 13 Jul 2020 22:23:54 +0930 Subject: [PATCH 07/34] Revert accidental changes --- src/compiler/compile/render_dom/index.ts | 16 ++++++++-------- test/custom-elements/samples/props/test.js | 6 ++++-- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index 7ca2f16a6fc4..dc35c7f9a326 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -11,7 +11,7 @@ import { ClassDeclaration, FunctionExpression, Node, Statement, ObjectExpression export default function dom( component: Component, options: CompileOptions -): { js: Node[]; css: CssResult } { +): { js: Node[]; css: CssResult; } { const { name } = component; const renderer = new Renderer(component, options); @@ -90,10 +90,10 @@ export default function dom( ${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}`)};` - )} + 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`)};`} + b`if ('$$scope' in ${$$props}) ${renderer.invalidate('$$scope', x`$$scope = ${$$props}.$$scope`)};`} } ` : null; @@ -190,8 +190,8 @@ export default function dom( ${$$props} => { ${uses_props && renderer.invalidate('$$props', x`$$props = @assign(@assign({}, $$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}`)};` + )} } `; @@ -469,7 +469,7 @@ export default function dom( $$setup(options) { ${css.code && b`this.shadowRoot.innerHTML = \`\`;`} - @init(this, { props: options ? options.props : null, target: this.shadowRoot }, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_indexes}, ${dirty}); + @init(this, { props: options ? options.props : null, target: this.shadowRoot }, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, ${dirty}); ${dev_props_check} @@ -521,7 +521,7 @@ 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}); + @init(this, options, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, ${dirty}); ${options.dev && b`@dispatch_dev("SvelteRegisterComponent", { component: this, tagName: "${name.name}", options, id: create_fragment.name });`} ${dev_props_check} diff --git a/test/custom-elements/samples/props/test.js b/test/custom-elements/samples/props/test.js index 783fe8fbdbe1..9c7e44c3a3f2 100644 --- a/test/custom-elements/samples/props/test.js +++ b/test/custom-elements/samples/props/test.js @@ -1,7 +1,7 @@ import * as assert from 'assert'; import CustomElement from './main.svelte'; -export default async function (target) { +export default function (target) { new CustomElement({ target }); @@ -10,12 +10,14 @@ export default async function (target) { const el = target.querySelector('custom-element'); const widget = el.shadowRoot.querySelector('my-widget'); + const [p1, p2] = widget.shadowRoot.querySelectorAll('p'); assert.equal(p1.textContent, '3 items'); assert.equal(p2.textContent, 'a, b, c'); el.items = ['d', 'e', 'f', 'g', 'h']; + assert.equal(p1.textContent, '5 items'); assert.equal(p2.textContent, 'd, e, f, g, h'); -} +} \ No newline at end of file From 721c9c5f29ff74ea2f6245ff715d635f7bd22753 Mon Sep 17 00:00:00 2001 From: Christopher Mardell Date: Mon, 13 Jul 2020 22:27:23 +0930 Subject: [PATCH 08/34] Revert accidental changes --- src/compiler/compile/render_dom/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index dc35c7f9a326..99447a31a791 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -11,7 +11,7 @@ import { ClassDeclaration, FunctionExpression, Node, Statement, ObjectExpression export default function dom( component: Component, options: CompileOptions -): { js: Node[]; css: CssResult; } { +): { js: Node[]; css: CssResult } { const { name } = component; const renderer = new Renderer(component, options); @@ -93,7 +93,7 @@ export default function dom( 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`)};`} + b`if ('$$scope' in ${$$props}) ${renderer.invalidate('$$scope', x`$$scope = ${$$props}.$$scope`)};`} } ` : null; From 911e15d2330b67e3e4743c7f5b1c222bafe9c317 Mon Sep 17 00:00:00 2001 From: Christopher Mardell Date: Tue, 14 Jul 2020 10:18:41 +0930 Subject: [PATCH 09/34] Use DOM, remove async from props-after-create test --- test/custom-elements/samples/props-after-create/test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/custom-elements/samples/props-after-create/test.js b/test/custom-elements/samples/props-after-create/test.js index c90ccf68b013..c4dc6b31d0a8 100644 --- a/test/custom-elements/samples/props-after-create/test.js +++ b/test/custom-elements/samples/props-after-create/test.js @@ -1,7 +1,7 @@ import * as assert from 'assert'; import CustomElement from './main.svelte'; -export default async function (target) { +export default function (target) { // initialize without options to simulate instantiation within HTML const el = new CustomElement(); @@ -14,8 +14,8 @@ export default async function (target) { // shouldn't be instantitated yet assert.equal(p0, undefined); - // simulate adding to DOM to trigger setup - el.connectedCallback(); + // add to to DOM to trigger setup + target.appendChild(el); const [p1, p2] = el.shadowRoot.querySelectorAll('p'); assert.equal(p1.textContent, '3 items'); From 12c51f3f84fc3eeb09e761be9b1281f19064c99e Mon Sep 17 00:00:00 2001 From: Luca Bonavita Date: Mon, 13 Jul 2020 16:40:33 +0100 Subject: [PATCH 10/34] site: remove an obsolete TODO in blog post (#5135) --- site/content/blog/2019-04-16-svelte-for-new-developers.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/site/content/blog/2019-04-16-svelte-for-new-developers.md b/site/content/blog/2019-04-16-svelte-for-new-developers.md index 796a6c11abbb..fd7a88e6f142 100644 --- a/site/content/blog/2019-04-16-svelte-for-new-developers.md +++ b/site/content/blog/2019-04-16-svelte-for-new-developers.md @@ -74,8 +74,6 @@ This creates a new directory, `my-svelte-project`, adds files from the [sveltejs In the `package.json` file, there is a section called `"scripts"`. These scripts define shortcuts for working with your application — `dev`, `build` and `start`. To launch your app in development mode, type the following: -> TODO update the template, it needs... some work - ```bash npm run dev ``` From 04871ae70c3ee4b907715c690a53fe1b9471b877 Mon Sep 17 00:00:00 2001 From: Orta Therox Date: Mon, 13 Jul 2020 11:46:24 -0400 Subject: [PATCH 11/34] site: add FAQ entry for how to document a svelte component (#5131) --- .../450-how-do-i-document-my-components.md | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 site/content/faq/450-how-do-i-document-my-components.md diff --git a/site/content/faq/450-how-do-i-document-my-components.md b/site/content/faq/450-how-do-i-document-my-components.md new file mode 100644 index 000000000000..78177abd5498 --- /dev/null +++ b/site/content/faq/450-how-do-i-document-my-components.md @@ -0,0 +1,32 @@ +--- +question: How do I document my components? +--- + +In editors which use the Svelte Language Server you can document Components, functions and exports using specially formatted comments. + +````svelte + + + +
+

+ Hello, {name} +

+
+```` + +Note: The `@component` is necessary in the HTML comment which describes your component. From 2d6324ed4a720d1d87f05b694ad50823ca1e844c Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Tue, 14 Jul 2020 00:03:12 +0800 Subject: [PATCH 12/34] fix $$props reactive for slots (#5125) --- CHANGELOG.md | 4 +++ .../compile/render_dom/wrappers/Slot.ts | 26 +++++++++---------- .../samples/props-reactive-slot/Comp.svelte | 1 + .../samples/props-reactive-slot/_config.js | 21 +++++++++++++++ .../samples/props-reactive-slot/main.svelte | 13 ++++++++++ 5 files changed, 51 insertions(+), 14 deletions(-) create mode 100644 test/runtime/samples/props-reactive-slot/Comp.svelte create mode 100644 test/runtime/samples/props-reactive-slot/_config.js create mode 100644 test/runtime/samples/props-reactive-slot/main.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index cc535118604c..63f4f0fa2a28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Svelte changelog +## Unreleased + +* Fix reactivity when passing `$$props` to a `` ([#3364](https://github.com/sveltejs/svelte/issues/3364)) + ## 3.24.0 * Support nullish coalescing (`??`) and optional chaining (`?.`) operators ([#1972](https://github.com/sveltejs/svelte/issues/1972)) diff --git a/src/compiler/compile/render_dom/wrappers/Slot.ts b/src/compiler/compile/render_dom/wrappers/Slot.ts index 268875acafcb..bf808196c0b1 100644 --- a/src/compiler/compile/render_dom/wrappers/Slot.ts +++ b/src/compiler/compile/render_dom/wrappers/Slot.ts @@ -7,6 +7,7 @@ import { b, p, x } from 'code-red'; import { sanitize } from '../../../utils/names'; import add_to_set from '../../utils/add_to_set'; import get_slot_data from '../../utils/get_slot_data'; +import { is_reserved_keyword } from '../../utils/reserved_keywords'; import Expression from '../../nodes/shared/Expression'; import is_dynamic from './shared/is_dynamic'; import { Identifier, ObjectExpression } from 'estree'; @@ -94,11 +95,7 @@ export default class SlotWrapper extends Wrapper { } }); - const dynamic_dependencies = Array.from(attribute.dependencies).filter(name => { - if (this.node.scope.is_let(name)) return true; - const variable = renderer.component.var_lookup.get(name); - return is_dynamic(variable); - }); + const dynamic_dependencies = Array.from(attribute.dependencies).filter((name) => this.is_dependency_dynamic(name)); if (dynamic_dependencies.length > 0) { changes.properties.push(p`${attribute.name}: ${renderer.dirty(dynamic_dependencies)}`); @@ -157,17 +154,10 @@ export default class SlotWrapper extends Wrapper { b`@transition_out(${slot_or_fallback}, #local);` ); - const is_dependency_dynamic = name => { - if (name === '$$scope') return true; - if (this.node.scope.is_let(name)) return true; - const variable = renderer.component.var_lookup.get(name); - return is_dynamic(variable); - }; - - const dynamic_dependencies = Array.from(this.dependencies).filter(is_dependency_dynamic); + const dynamic_dependencies = Array.from(this.dependencies).filter((name) => this.is_dependency_dynamic(name)); const fallback_dynamic_dependencies = has_fallback - ? Array.from(this.fallback.dependencies).filter(is_dependency_dynamic) + ? Array.from(this.fallback.dependencies).filter((name) => this.is_dependency_dynamic(name)) : []; const slot_update = b` @@ -201,4 +191,12 @@ export default class SlotWrapper extends Wrapper { b`if (${slot_or_fallback}) ${slot_or_fallback}.d(detaching);` ); } + + is_dependency_dynamic(name: string) { + if (name === '$$scope') return true; + if (this.node.scope.is_let(name)) return true; + if (is_reserved_keyword(name)) return true; + const variable = this.renderer.component.var_lookup.get(name); + return is_dynamic(variable); + } } diff --git a/test/runtime/samples/props-reactive-slot/Comp.svelte b/test/runtime/samples/props-reactive-slot/Comp.svelte new file mode 100644 index 000000000000..bf9e12a58a80 --- /dev/null +++ b/test/runtime/samples/props-reactive-slot/Comp.svelte @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/runtime/samples/props-reactive-slot/_config.js b/test/runtime/samples/props-reactive-slot/_config.js new file mode 100644 index 000000000000..286bba2f08e5 --- /dev/null +++ b/test/runtime/samples/props-reactive-slot/_config.js @@ -0,0 +1,21 @@ +export default { + html: ` +

hi

+ + `, + + async test({ assert, component, target, window }) { + const btn = target.querySelector("button"); + const clickEvent = new window.MouseEvent("click"); + + await btn.dispatchEvent(clickEvent); + + assert.htmlEqual( + target.innerHTML, + ` +

changed

+ + ` + ); + }, +}; diff --git a/test/runtime/samples/props-reactive-slot/main.svelte b/test/runtime/samples/props-reactive-slot/main.svelte new file mode 100644 index 000000000000..84777bf8abe0 --- /dev/null +++ b/test/runtime/samples/props-reactive-slot/main.svelte @@ -0,0 +1,13 @@ + + + +

+ {props.someprop} +

+
+ + \ No newline at end of file From fda1b3581661dd88b3e45976c4a71412a866ac00 Mon Sep 17 00:00:00 2001 From: Stanislav Lashmanov Date: Tue, 14 Jul 2020 20:27:42 +0300 Subject: [PATCH 13/34] Simplify each block bindings example (#5094) No need for CSS here, the same effect could be achieved with just a `disabled` binding. --- .../05-bindings/07-each-block-bindings/App.svelte | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/site/content/examples/05-bindings/07-each-block-bindings/App.svelte b/site/content/examples/05-bindings/07-each-block-bindings/App.svelte index ff91612ec52b..4d3d75420cc9 100644 --- a/site/content/examples/05-bindings/07-each-block-bindings/App.svelte +++ b/site/content/examples/05-bindings/07-each-block-bindings/App.svelte @@ -16,16 +16,10 @@ $: remaining = todos.filter(t => !t.done).length; - -

Todos

{#each todos as todo} -
+
{/each} From 471c0d24849d7400080431db72eaba08c9d96def Mon Sep 17 00:00:00 2001 From: Lev Maximov Date: Wed, 15 Jul 2020 03:13:19 +0700 Subject: [PATCH 14/34] site: use https in link in blog (#5148) --- .../content/blog/2016-11-26-frameworks-without-the-framework.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/content/blog/2016-11-26-frameworks-without-the-framework.md b/site/content/blog/2016-11-26-frameworks-without-the-framework.md index 4efac90b5249..76c8b90ab311 100644 --- a/site/content/blog/2016-11-26-frameworks-without-the-framework.md +++ b/site/content/blog/2016-11-26-frameworks-without-the-framework.md @@ -30,7 +30,7 @@ Given that, what if the framework *didn't actually run in the browser*? What if, Svelte is a new framework that does exactly that. You write your components using HTML, CSS and JavaScript (plus a few extra bits you can [learn in under 5 minutes](https://v2.svelte.dev/guide)), and during your build process Svelte compiles them into tiny standalone JavaScript modules. By statically analysing the component template, we can make sure that the browser does as little work as possible. -The [Svelte implementation of TodoMVC](http://svelte-todomvc.surge.sh/) weighs 3.6kb zipped. For comparison, React plus ReactDOM *without any app code* weighs about 45kb zipped. It takes about 10x as long for the browser just to evaluate React as it does for Svelte to be up and running with an interactive TodoMVC. +The [Svelte implementation of TodoMVC](https://svelte-todomvc.surge.sh/) weighs 3.6kb zipped. For comparison, React plus ReactDOM *without any app code* weighs about 45kb zipped. It takes about 10x as long for the browser just to evaluate React as it does for Svelte to be up and running with an interactive TodoMVC. And once your app *is* up and running, according to [js-framework-benchmark](https://github.com/krausest/js-framework-benchmark) **Svelte is fast as heck**. It's faster than React. It's faster than Vue. It's faster than Angular, or Ember, or Ractive, or Preact, or Riot, or Mithril. It's competitive with Inferno, which is probably the fastest UI framework in the world, for now, because [Dominic Gannaway](https://twitter.com/trueadm) is a wizard. (Svelte is slower at removing elements. We're [working on it](https://github.com/sveltejs/svelte/issues/26).) From f59a8a8ec2bef34957d3a9bde3ea5bdb3645671d Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Wed, 15 Jul 2020 05:32:24 +0800 Subject: [PATCH 15/34] invalidate $$props and $$restProps only when there are changes (#5123) --- CHANGELOG.md | 1 + src/compiler/compile/render_dom/index.ts | 1 + .../wrappers/InlineComponent/index.ts | 2 +- src/runtime/internal/utils.ts | 4 +++ .../Comp.svelte | 6 ++++ .../_config.js | 30 +++++++++++++++++++ .../main.svelte | 8 +++++ 7 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 test/runtime/samples/props-reactive-only-with-change/Comp.svelte create mode 100644 test/runtime/samples/props-reactive-only-with-change/_config.js create mode 100644 test/runtime/samples/props-reactive-only-with-change/main.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index 63f4f0fa2a28..61b4c3684237 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased * Fix reactivity when passing `$$props` to a `` ([#3364](https://github.com/sveltejs/svelte/issues/3364)) +* Fix unneeded invalidation of `$$props` and `$$restProps` ([#4993](https://github.com/sveltejs/svelte/issues/4993), [#5118](https://github.com/sveltejs/svelte/issues/5118)) ## 3.24.0 diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index 3b5001d483f1..f6ffd8f2a649 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -86,6 +86,7 @@ export default function dom( const set = (uses_props || uses_rest || writable_props.length > 0 || component.slots.size > 0) ? x` ${$$props} => { + ${(uses_props || uses_rest) && b`if (@is_empty(${$$props})) return;`} ${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}`)} diff --git a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts index 271b3de1e1c3..814f365a848e 100644 --- a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts +++ b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts @@ -468,7 +468,7 @@ export default class InlineComponentWrapper extends Wrapper { ${name} = null; } } else if (${switch_value}) { - ${updates.length && b`${name}.$set(${name_changes});`} + ${updates.length > 0 && b`${name}.$set(${name_changes});`} } `); diff --git a/src/runtime/internal/utils.ts b/src/runtime/internal/utils.ts index d752c9de9d83..3fd0a2b70166 100644 --- a/src/runtime/internal/utils.ts +++ b/src/runtime/internal/utils.ts @@ -42,6 +42,10 @@ export function not_equal(a, b) { return a != a ? b == b : a !== b; } +export function is_empty(obj) { + return Object.keys(obj).length === 0; +} + 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`); diff --git a/test/runtime/samples/props-reactive-only-with-change/Comp.svelte b/test/runtime/samples/props-reactive-only-with-change/Comp.svelte new file mode 100644 index 000000000000..0eaf8a40d48a --- /dev/null +++ b/test/runtime/samples/props-reactive-only-with-change/Comp.svelte @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/test/runtime/samples/props-reactive-only-with-change/_config.js b/test/runtime/samples/props-reactive-only-with-change/_config.js new file mode 100644 index 000000000000..04170620c817 --- /dev/null +++ b/test/runtime/samples/props-reactive-only-with-change/_config.js @@ -0,0 +1,30 @@ +let callbacks = []; + +export default { + props: { + callback: (value) => callbacks.push(value), + val1: "1", + val2: "2", + }, + + before_test() { + callbacks = []; + }, + + async test({ assert, component, target }) { + assert.equal(callbacks.length, 2); + assert.equal(JSON.stringify(callbacks), '["1","2"]'); + + component.val1 = "3"; + assert.equal(callbacks.length, 3); + assert.equal(JSON.stringify(callbacks), '["1","2","1"]'); + + component.val1 = "4"; + assert.equal(callbacks.length, 4); + assert.equal(JSON.stringify(callbacks), '["1","2","1","1"]'); + + component.val2 = "5"; + assert.equal(callbacks.length, 5); + assert.equal(JSON.stringify(callbacks), '["1","2","1","1","2"]'); + }, +}; diff --git a/test/runtime/samples/props-reactive-only-with-change/main.svelte b/test/runtime/samples/props-reactive-only-with-change/main.svelte new file mode 100644 index 000000000000..73ddd137f5bc --- /dev/null +++ b/test/runtime/samples/props-reactive-only-with-change/main.svelte @@ -0,0 +1,8 @@ + + + + \ No newline at end of file From 269354a395c7ce9b7dfbddd145e3ce64da86acc0 Mon Sep 17 00:00:00 2001 From: Wolfr Date: Fri, 17 Jul 2020 12:54:41 +0200 Subject: [PATCH 16/34] Fixes #5153 (#5154) --- site/src/routes/repl/[id]/_components/AppControls/index.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/site/src/routes/repl/[id]/_components/AppControls/index.svelte b/site/src/routes/repl/[id]/_components/AppControls/index.svelte index 10639e91c0a6..1d4e6b3f0307 100644 --- a/site/src/routes/repl/[id]/_components/AppControls/index.svelte +++ b/site/src/routes/repl/[id]/_components/AppControls/index.svelte @@ -227,6 +227,7 @@ export default app;` }); padding: .6rem var(--side-nav); background-color: var(--second); color: white; + white-space: nowrap; } .icon { From 4bd1de654fb28c738d401d39c55cb140c65f7bd2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2020 11:57:30 +0100 Subject: [PATCH 17/34] Bump lodash from 4.17.15 to 4.17.19 in /site (#5155) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- site/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/site/package-lock.json b/site/package-lock.json index b3d12343f1a1..1132b9f324a9 100644 --- a/site/package-lock.json +++ b/site/package-lock.json @@ -2404,9 +2404,9 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", "dev": true }, "lodash.deburr": { From 179d9ff17af550a584cd5a3a5e8a91ef0c535f39 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2020 15:47:14 -0400 Subject: [PATCH 18/34] Bump lodash from 4.17.15 to 4.17.19 (#5152) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 68f251c60560..82ad8fb6f69d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2527,9 +2527,9 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", "dev": true }, "lodash.sortby": { From c3da9e4ae976c70a99c713189af76d960a422cda Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Sat, 18 Jul 2020 04:12:26 +0800 Subject: [PATCH 19/34] add updating guard to binding callback (#5126) --- CHANGELOG.md | 1 + src/compiler/compile/render_dom/index.ts | 3 +- src/runtime/internal/Component.ts | 26 +++++--- .../action-custom-event-handler/expected.js | 2 +- test/js/samples/bind-open/expected.js | 2 +- test/js/samples/bind-width-height/expected.js | 2 +- .../bindings-readonly-order/expected.js | 2 +- .../samples/capture-inject-state/expected.js | 2 +- .../expected.js | 2 +- .../samples/computed-collapsed-if/expected.js | 2 +- test/js/samples/data-attribute/expected.js | 2 +- test/js/samples/debug-empty/expected.js | 2 +- .../debug-foo-bar-baz-things/expected.js | 2 +- test/js/samples/debug-foo/expected.js | 2 +- .../samples/deconflict-builtins/expected.js | 2 +- .../js/samples/deconflict-globals/expected.js | 2 +- .../expected.js | 2 +- .../each-block-array-literal/expected.js | 2 +- .../each-block-changed-check/expected.js | 2 +- .../each-block-keyed-animated/expected.js | 2 +- test/js/samples/each-block-keyed/expected.js | 2 +- .../js/samples/if-block-no-update/expected.js | 2 +- test/js/samples/if-block-simple/expected.js | 2 +- .../expected.js | 2 +- .../inline-style-optimized-url/expected.js | 2 +- .../inline-style-optimized/expected.js | 2 +- .../inline-style-unoptimized/expected.js | 2 +- test/js/samples/input-files/expected.js | 2 +- test/js/samples/input-range/expected.js | 2 +- .../input-without-blowback-guard/expected.js | 2 +- test/js/samples/media-bindings/expected.js | 2 +- test/js/samples/optional-chaining/expected.js | 2 +- .../expected.js | 2 +- .../expected.js | 2 +- .../samples/select-dynamic-value/expected.js | 2 +- .../samples/src-attribute-check/expected.js | 2 +- test/js/samples/title/expected.js | 2 +- test/js/samples/transition-local/expected.js | 2 +- .../transition-repeated-outro/expected.js | 2 +- .../use-elements-as-anchors/expected.js | 2 +- test/js/samples/video-bindings/expected.js | 2 +- .../samples/window-binding-scroll/expected.js | 2 +- .../component-binding-store/Input.svelte | 5 ++ .../component-binding-store/_config.js | 61 +++++++++++++++++++ .../component-binding-store/main.svelte | 18 ++++++ 45 files changed, 144 insertions(+), 48 deletions(-) create mode 100644 test/runtime/samples/component-binding-store/Input.svelte create mode 100644 test/runtime/samples/component-binding-store/_config.js create mode 100644 test/runtime/samples/component-binding-store/main.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index 61b4c3684237..bd070862f359 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +* Prevent duplicate invalidation with certain two-way component bindings ([#3180](https://github.com/sveltejs/svelte/issues/3180), [#5117](https://github.com/sveltejs/svelte/issues/5117), [#5144](https://github.com/sveltejs/svelte/issues/5144)) * Fix reactivity when passing `$$props` to a `` ([#3364](https://github.com/sveltejs/svelte/issues/3364)) * Fix unneeded invalidation of `$$props` and `$$restProps` ([#4993](https://github.com/sveltejs/svelte/issues/4993), [#5118](https://github.com/sveltejs/svelte/issues/5118)) diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index f6ffd8f2a649..7d0dce831536 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -86,7 +86,6 @@ export default function dom( const set = (uses_props || uses_rest || writable_props.length > 0 || component.slots.size > 0) ? x` ${$$props} => { - ${(uses_props || uses_rest) && b`if (@is_empty(${$$props})) return;`} ${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}`)} @@ -421,7 +420,7 @@ export default function dom( ${component.partly_hoisted} - ${set && b`$$self.$set = ${set};`} + ${set && b`$$self.$$set = ${set};`} ${capture_state && b`$$self.$capture_state = ${capture_state};`} diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index c0f6facdd25e..459a78031a04 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -1,6 +1,6 @@ import { add_render_callback, flush, schedule_update, dirty_components } from './scheduler'; import { current_component, set_current_component } from './lifecycle'; -import { blank_object, is_function, run, run_all, noop } from './utils'; +import { blank_object, is_empty, is_function, run, run_all, noop } from './utils'; import { children, detach } from './dom'; import { transition_in } from './transitions'; @@ -33,6 +33,7 @@ interface T$$ { context: Map; on_mount: any[]; on_destroy: any[]; + skip_bound: boolean; } export function bind(component, name, callback) { @@ -120,7 +121,8 @@ export function init(component, options, instance, create_fragment, not_equal, p // everything else callbacks: blank_object(), - dirty + dirty, + skip_bound: false }; let ready = false; @@ -129,7 +131,7 @@ export function init(component, options, instance, create_fragment, not_equal, p ? instance(component, prop_values, (i, ret, ...rest) => { const value = rest.length ? rest[0] : ret; if ($$.ctx && not_equal($$.ctx[i], $$.ctx[i] = value)) { - if ($$.bound[i]) $$.bound[i](value); + if (!$$.skip_bound && $$.bound[i]) $$.bound[i](value); if (ready) make_dirty(component, i); } return ret; @@ -166,6 +168,7 @@ export let SvelteElement; if (typeof HTMLElement === 'function') { SvelteElement = class extends HTMLElement { $$: T$$; + $$set?: ($$props: any) => void; constructor() { super(); this.attachShadow({ mode: 'open' }); @@ -199,14 +202,19 @@ if (typeof HTMLElement === 'function') { }; } - $set() { - // overridden by instance, if it has props + $set($$props) { + if (this.$$set && !is_empty($$props)) { + this.$$.skip_bound = true; + this.$$set($$props); + this.$$.skip_bound = false; + } } }; } export class SvelteComponent { $$: T$$; + $$set?: ($$props: any) => void; $destroy() { destroy_component(this, 1); @@ -223,7 +231,11 @@ export class SvelteComponent { }; } - $set() { - // overridden by instance, if it has props + $set($$props) { + if (this.$$set && !is_empty($$props)) { + this.$$.skip_bound = true; + this.$$set($$props); + this.$$.skip_bound = false; + } } } diff --git a/test/js/samples/action-custom-event-handler/expected.js b/test/js/samples/action-custom-event-handler/expected.js index cac2f61b4450..51656290d668 100644 --- a/test/js/samples/action-custom-event-handler/expected.js +++ b/test/js/samples/action-custom-event-handler/expected.js @@ -55,7 +55,7 @@ function instance($$self, $$props, $$invalidate) { let { bar } = $$props; const foo_function = () => handleFoo(bar); - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("bar" in $$props) $$invalidate(0, bar = $$props.bar); }; diff --git a/test/js/samples/bind-open/expected.js b/test/js/samples/bind-open/expected.js index 30387d505db9..56ff30284572 100644 --- a/test/js/samples/bind-open/expected.js +++ b/test/js/samples/bind-open/expected.js @@ -52,7 +52,7 @@ function instance($$self, $$props, $$invalidate) { $$invalidate(0, open); } - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("open" in $$props) $$invalidate(0, open = $$props.open); }; diff --git a/test/js/samples/bind-width-height/expected.js b/test/js/samples/bind-width-height/expected.js index 4848704c4b0d..f23c20b68316 100644 --- a/test/js/samples/bind-width-height/expected.js +++ b/test/js/samples/bind-width-height/expected.js @@ -46,7 +46,7 @@ function instance($$self, $$props, $$invalidate) { $$invalidate(1, h); } - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("w" in $$props) $$invalidate(0, w = $$props.w); if ("h" in $$props) $$invalidate(1, h = $$props.h); }; diff --git a/test/js/samples/bindings-readonly-order/expected.js b/test/js/samples/bindings-readonly-order/expected.js index 0e845c65b8f4..78a71dcd8414 100644 --- a/test/js/samples/bindings-readonly-order/expected.js +++ b/test/js/samples/bindings-readonly-order/expected.js @@ -68,7 +68,7 @@ function instance($$self, $$props, $$invalidate) { $$invalidate(0, files); } - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("files" in $$props) $$invalidate(0, files = $$props.files); }; diff --git a/test/js/samples/capture-inject-state/expected.js b/test/js/samples/capture-inject-state/expected.js index cd719ac5d25f..6aa93b9c5aef 100644 --- a/test/js/samples/capture-inject-state/expected.js +++ b/test/js/samples/capture-inject-state/expected.js @@ -118,7 +118,7 @@ function instance($$self, $$props, $$invalidate) { let { $$slots = {}, $$scope } = $$props; validate_slots("Component", $$slots, []); - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("prop" in $$props) $$subscribe_prop($$invalidate(0, prop = $$props.prop)); if ("alias" in $$props) $$invalidate(1, realName = $$props.alias); }; diff --git a/test/js/samples/collapses-text-around-comments/expected.js b/test/js/samples/collapses-text-around-comments/expected.js index 6fef0f9490d8..67335ce2469e 100644 --- a/test/js/samples/collapses-text-around-comments/expected.js +++ b/test/js/samples/collapses-text-around-comments/expected.js @@ -48,7 +48,7 @@ function create_fragment(ctx) { function instance($$self, $$props, $$invalidate) { let { foo = 42 } = $$props; - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("foo" in $$props) $$invalidate(0, foo = $$props.foo); }; diff --git a/test/js/samples/computed-collapsed-if/expected.js b/test/js/samples/computed-collapsed-if/expected.js index 8e5964f8a691..3e70d6a7ae18 100644 --- a/test/js/samples/computed-collapsed-if/expected.js +++ b/test/js/samples/computed-collapsed-if/expected.js @@ -12,7 +12,7 @@ function instance($$self, $$props, $$invalidate) { return x * 3; } - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("x" in $$props) $$invalidate(0, x = $$props.x); }; diff --git a/test/js/samples/data-attribute/expected.js b/test/js/samples/data-attribute/expected.js index 49ad2f2626ba..8c30e6f6dbb5 100644 --- a/test/js/samples/data-attribute/expected.js +++ b/test/js/samples/data-attribute/expected.js @@ -47,7 +47,7 @@ function create_fragment(ctx) { function instance($$self, $$props, $$invalidate) { let { bar } = $$props; - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("bar" in $$props) $$invalidate(0, bar = $$props.bar); }; diff --git a/test/js/samples/debug-empty/expected.js b/test/js/samples/debug-empty/expected.js index dd142adb26b2..6781e5333cd0 100644 --- a/test/js/samples/debug-empty/expected.js +++ b/test/js/samples/debug-empty/expected.js @@ -79,7 +79,7 @@ function instance($$self, $$props, $$invalidate) { let { $$slots = {}, $$scope } = $$props; validate_slots("Component", $$slots, []); - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("name" in $$props) $$invalidate(0, name = $$props.name); }; diff --git a/test/js/samples/debug-foo-bar-baz-things/expected.js b/test/js/samples/debug-foo-bar-baz-things/expected.js index 977702b99f5c..087d2e399d3f 100644 --- a/test/js/samples/debug-foo-bar-baz-things/expected.js +++ b/test/js/samples/debug-foo-bar-baz-things/expected.js @@ -183,7 +183,7 @@ function instance($$self, $$props, $$invalidate) { let { $$slots = {}, $$scope } = $$props; validate_slots("Component", $$slots, []); - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("things" in $$props) $$invalidate(0, things = $$props.things); if ("foo" in $$props) $$invalidate(1, foo = $$props.foo); if ("bar" in $$props) $$invalidate(2, bar = $$props.bar); diff --git a/test/js/samples/debug-foo/expected.js b/test/js/samples/debug-foo/expected.js index fe62ff77bfb1..9f12bfb807a7 100644 --- a/test/js/samples/debug-foo/expected.js +++ b/test/js/samples/debug-foo/expected.js @@ -175,7 +175,7 @@ function instance($$self, $$props, $$invalidate) { let { $$slots = {}, $$scope } = $$props; validate_slots("Component", $$slots, []); - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("things" in $$props) $$invalidate(0, things = $$props.things); if ("foo" in $$props) $$invalidate(1, foo = $$props.foo); }; diff --git a/test/js/samples/deconflict-builtins/expected.js b/test/js/samples/deconflict-builtins/expected.js index fb98844ef7a9..6bc60194aaa5 100644 --- a/test/js/samples/deconflict-builtins/expected.js +++ b/test/js/samples/deconflict-builtins/expected.js @@ -104,7 +104,7 @@ function create_fragment(ctx) { function instance($$self, $$props, $$invalidate) { let { createElement } = $$props; - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("createElement" in $$props) $$invalidate(0, createElement = $$props.createElement); }; diff --git a/test/js/samples/deconflict-globals/expected.js b/test/js/samples/deconflict-globals/expected.js index 7e83c21f0e6e..7168eba6a5d9 100644 --- a/test/js/samples/deconflict-globals/expected.js +++ b/test/js/samples/deconflict-globals/expected.js @@ -10,7 +10,7 @@ function instance($$self, $$props, $$invalidate) { alert(JSON.stringify(data())); }); - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("foo" in $$props) $$invalidate(0, foo = $$props.foo); }; diff --git a/test/js/samples/dev-warning-missing-data-computed/expected.js b/test/js/samples/dev-warning-missing-data-computed/expected.js index 0a50e2cd970a..fd34778f8d25 100644 --- a/test/js/samples/dev-warning-missing-data-computed/expected.js +++ b/test/js/samples/dev-warning-missing-data-computed/expected.js @@ -76,7 +76,7 @@ function instance($$self, $$props, $$invalidate) { let { $$slots = {}, $$scope } = $$props; validate_slots("Component", $$slots, []); - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("foo" in $$props) $$invalidate(0, foo = $$props.foo); }; diff --git a/test/js/samples/each-block-array-literal/expected.js b/test/js/samples/each-block-array-literal/expected.js index 10d835cf7805..fe51ac5bc3aa 100644 --- a/test/js/samples/each-block-array-literal/expected.js +++ b/test/js/samples/each-block-array-literal/expected.js @@ -106,7 +106,7 @@ function instance($$self, $$props, $$invalidate) { let { d } = $$props; let { e } = $$props; - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("a" in $$props) $$invalidate(0, a = $$props.a); if ("b" in $$props) $$invalidate(1, b = $$props.b); if ("c" in $$props) $$invalidate(2, c = $$props.c); diff --git a/test/js/samples/each-block-changed-check/expected.js b/test/js/samples/each-block-changed-check/expected.js index f4f9df0de9c1..63bc1d8607b8 100644 --- a/test/js/samples/each-block-changed-check/expected.js +++ b/test/js/samples/each-block-changed-check/expected.js @@ -152,7 +152,7 @@ function instance($$self, $$props, $$invalidate) { let { time } = $$props; let { foo } = $$props; - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("comments" in $$props) $$invalidate(0, comments = $$props.comments); if ("elapsed" in $$props) $$invalidate(1, elapsed = $$props.elapsed); if ("time" in $$props) $$invalidate(2, time = $$props.time); diff --git a/test/js/samples/each-block-keyed-animated/expected.js b/test/js/samples/each-block-keyed-animated/expected.js index 7fb81c27a226..46ef63ee7f17 100644 --- a/test/js/samples/each-block-keyed-animated/expected.js +++ b/test/js/samples/each-block-keyed-animated/expected.js @@ -128,7 +128,7 @@ function foo(node, animation, params) { function instance($$self, $$props, $$invalidate) { let { things } = $$props; - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("things" in $$props) $$invalidate(0, things = $$props.things); }; diff --git a/test/js/samples/each-block-keyed/expected.js b/test/js/samples/each-block-keyed/expected.js index ad8c074e99f8..71853cf295ef 100644 --- a/test/js/samples/each-block-keyed/expected.js +++ b/test/js/samples/each-block-keyed/expected.js @@ -97,7 +97,7 @@ function create_fragment(ctx) { function instance($$self, $$props, $$invalidate) { let { things } = $$props; - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("things" in $$props) $$invalidate(0, things = $$props.things); }; diff --git a/test/js/samples/if-block-no-update/expected.js b/test/js/samples/if-block-no-update/expected.js index f225c221bfe3..c67b33fa8557 100644 --- a/test/js/samples/if-block-no-update/expected.js +++ b/test/js/samples/if-block-no-update/expected.js @@ -88,7 +88,7 @@ function create_fragment(ctx) { function instance($$self, $$props, $$invalidate) { let { foo } = $$props; - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("foo" in $$props) $$invalidate(0, foo = $$props.foo); }; diff --git a/test/js/samples/if-block-simple/expected.js b/test/js/samples/if-block-simple/expected.js index eb6c8e89490a..4cdd73cddbc1 100644 --- a/test/js/samples/if-block-simple/expected.js +++ b/test/js/samples/if-block-simple/expected.js @@ -66,7 +66,7 @@ function create_fragment(ctx) { function instance($$self, $$props, $$invalidate) { let { foo } = $$props; - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("foo" in $$props) $$invalidate(0, foo = $$props.foo); }; diff --git a/test/js/samples/inline-style-optimized-multiple/expected.js b/test/js/samples/inline-style-optimized-multiple/expected.js index 84a38abd7bbe..0a9d0a1e8eba 100644 --- a/test/js/samples/inline-style-optimized-multiple/expected.js +++ b/test/js/samples/inline-style-optimized-multiple/expected.js @@ -44,7 +44,7 @@ function instance($$self, $$props, $$invalidate) { let { x } = $$props; let { y } = $$props; - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("color" in $$props) $$invalidate(0, color = $$props.color); if ("x" in $$props) $$invalidate(1, x = $$props.x); if ("y" in $$props) $$invalidate(2, y = $$props.y); diff --git a/test/js/samples/inline-style-optimized-url/expected.js b/test/js/samples/inline-style-optimized-url/expected.js index 77870348a572..0debb035854f 100644 --- a/test/js/samples/inline-style-optimized-url/expected.js +++ b/test/js/samples/inline-style-optimized-url/expected.js @@ -37,7 +37,7 @@ function create_fragment(ctx) { function instance($$self, $$props, $$invalidate) { let { data } = $$props; - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("data" in $$props) $$invalidate(0, data = $$props.data); }; diff --git a/test/js/samples/inline-style-optimized/expected.js b/test/js/samples/inline-style-optimized/expected.js index 5bef284f0946..b7db0f1cf30b 100644 --- a/test/js/samples/inline-style-optimized/expected.js +++ b/test/js/samples/inline-style-optimized/expected.js @@ -37,7 +37,7 @@ function create_fragment(ctx) { function instance($$self, $$props, $$invalidate) { let { color } = $$props; - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("color" in $$props) $$invalidate(0, color = $$props.color); }; diff --git a/test/js/samples/inline-style-unoptimized/expected.js b/test/js/samples/inline-style-unoptimized/expected.js index fdff685eade2..0688f14b9b24 100644 --- a/test/js/samples/inline-style-unoptimized/expected.js +++ b/test/js/samples/inline-style-unoptimized/expected.js @@ -54,7 +54,7 @@ function instance($$self, $$props, $$invalidate) { let { key } = $$props; let { value } = $$props; - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("style" in $$props) $$invalidate(0, style = $$props.style); if ("key" in $$props) $$invalidate(1, key = $$props.key); if ("value" in $$props) $$invalidate(2, value = $$props.value); diff --git a/test/js/samples/input-files/expected.js b/test/js/samples/input-files/expected.js index 0069c2e5f8f8..8adc7443f5c1 100644 --- a/test/js/samples/input-files/expected.js +++ b/test/js/samples/input-files/expected.js @@ -49,7 +49,7 @@ function instance($$self, $$props, $$invalidate) { $$invalidate(0, files); } - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("files" in $$props) $$invalidate(0, files = $$props.files); }; diff --git a/test/js/samples/input-range/expected.js b/test/js/samples/input-range/expected.js index 770baa29ede5..a855ca3653fe 100644 --- a/test/js/samples/input-range/expected.js +++ b/test/js/samples/input-range/expected.js @@ -60,7 +60,7 @@ function instance($$self, $$props, $$invalidate) { $$invalidate(0, value); } - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("value" in $$props) $$invalidate(0, value = $$props.value); }; diff --git a/test/js/samples/input-without-blowback-guard/expected.js b/test/js/samples/input-without-blowback-guard/expected.js index f19f74dc1ef0..6c5b2156232f 100644 --- a/test/js/samples/input-without-blowback-guard/expected.js +++ b/test/js/samples/input-without-blowback-guard/expected.js @@ -53,7 +53,7 @@ function instance($$self, $$props, $$invalidate) { $$invalidate(0, foo); } - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("foo" in $$props) $$invalidate(0, foo = $$props.foo); }; diff --git a/test/js/samples/media-bindings/expected.js b/test/js/samples/media-bindings/expected.js index bcbc0647b84d..867d4a7dadfb 100644 --- a/test/js/samples/media-bindings/expected.js +++ b/test/js/samples/media-bindings/expected.js @@ -173,7 +173,7 @@ function instance($$self, $$props, $$invalidate) { $$invalidate(10, ended); } - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("buffered" in $$props) $$invalidate(0, buffered = $$props.buffered); if ("seekable" in $$props) $$invalidate(1, seekable = $$props.seekable); if ("played" in $$props) $$invalidate(2, played = $$props.played); diff --git a/test/js/samples/optional-chaining/expected.js b/test/js/samples/optional-chaining/expected.js index a28dc129aa0c..8aa94796c1b0 100644 --- a/test/js/samples/optional-chaining/expected.js +++ b/test/js/samples/optional-chaining/expected.js @@ -167,7 +167,7 @@ function instance($$self, $$props, $$invalidate) { let { f } = $$props; let Component; - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("a" in $$props) $$invalidate(0, a = $$props.a); if ("b" in $$props) $$invalidate(1, b = $$props.b); if ("c" in $$props) $$invalidate(2, c = $$props.c); diff --git a/test/js/samples/reactive-values-non-topologically-ordered/expected.js b/test/js/samples/reactive-values-non-topologically-ordered/expected.js index 3d266f10acfb..15290496d527 100644 --- a/test/js/samples/reactive-values-non-topologically-ordered/expected.js +++ b/test/js/samples/reactive-values-non-topologically-ordered/expected.js @@ -6,7 +6,7 @@ function instance($$self, $$props, $$invalidate) { let a; let b; - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("x" in $$props) $$invalidate(0, x = $$props.x); }; diff --git a/test/js/samples/reactive-values-non-writable-dependencies/expected.js b/test/js/samples/reactive-values-non-writable-dependencies/expected.js index 38bd356d85e4..5196a770d976 100644 --- a/test/js/samples/reactive-values-non-writable-dependencies/expected.js +++ b/test/js/samples/reactive-values-non-writable-dependencies/expected.js @@ -5,7 +5,7 @@ function instance($$self, $$props, $$invalidate) { let { a = 1 } = $$props; let { b = 2 } = $$props; - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("a" in $$props) $$invalidate(0, a = $$props.a); if ("b" in $$props) $$invalidate(1, b = $$props.b); }; diff --git a/test/js/samples/select-dynamic-value/expected.js b/test/js/samples/select-dynamic-value/expected.js index aa4e5004fdfe..8777cd260072 100644 --- a/test/js/samples/select-dynamic-value/expected.js +++ b/test/js/samples/select-dynamic-value/expected.js @@ -50,7 +50,7 @@ function create_fragment(ctx) { function instance($$self, $$props, $$invalidate) { let { current } = $$props; - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("current" in $$props) $$invalidate(0, current = $$props.current); }; diff --git a/test/js/samples/src-attribute-check/expected.js b/test/js/samples/src-attribute-check/expected.js index e03b3a6ba7ee..93638edfb43b 100644 --- a/test/js/samples/src-attribute-check/expected.js +++ b/test/js/samples/src-attribute-check/expected.js @@ -67,7 +67,7 @@ function instance($$self, $$props, $$invalidate) { let { url } = $$props; let { slug } = $$props; - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("url" in $$props) $$invalidate(0, url = $$props.url); if ("slug" in $$props) $$invalidate(1, slug = $$props.slug); }; diff --git a/test/js/samples/title/expected.js b/test/js/samples/title/expected.js index d4e7e1a58491..b10f569759d4 100644 --- a/test/js/samples/title/expected.js +++ b/test/js/samples/title/expected.js @@ -22,7 +22,7 @@ function create_fragment(ctx) { function instance($$self, $$props, $$invalidate) { let { custom } = $$props; - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("custom" in $$props) $$invalidate(0, custom = $$props.custom); }; diff --git a/test/js/samples/transition-local/expected.js b/test/js/samples/transition-local/expected.js index 25a03f026f75..ea3d9db3d7e6 100644 --- a/test/js/samples/transition-local/expected.js +++ b/test/js/samples/transition-local/expected.js @@ -124,7 +124,7 @@ function instance($$self, $$props, $$invalidate) { let { x } = $$props; let { y } = $$props; - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("x" in $$props) $$invalidate(0, x = $$props.x); if ("y" in $$props) $$invalidate(1, y = $$props.y); }; diff --git a/test/js/samples/transition-repeated-outro/expected.js b/test/js/samples/transition-repeated-outro/expected.js index 1f76a93666be..12483ab91af7 100644 --- a/test/js/samples/transition-repeated-outro/expected.js +++ b/test/js/samples/transition-repeated-outro/expected.js @@ -102,7 +102,7 @@ function create_fragment(ctx) { function instance($$self, $$props, $$invalidate) { let { num = 1 } = $$props; - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("num" in $$props) $$invalidate(0, num = $$props.num); }; diff --git a/test/js/samples/use-elements-as-anchors/expected.js b/test/js/samples/use-elements-as-anchors/expected.js index 5be8808edbcf..d07411518e91 100644 --- a/test/js/samples/use-elements-as-anchors/expected.js +++ b/test/js/samples/use-elements-as-anchors/expected.js @@ -243,7 +243,7 @@ function instance($$self, $$props, $$invalidate) { let { d } = $$props; let { e } = $$props; - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("a" in $$props) $$invalidate(0, a = $$props.a); if ("b" in $$props) $$invalidate(1, b = $$props.b); if ("c" in $$props) $$invalidate(2, c = $$props.c); diff --git a/test/js/samples/video-bindings/expected.js b/test/js/samples/video-bindings/expected.js index d3920ef8c287..8afa670bbb52 100644 --- a/test/js/samples/video-bindings/expected.js +++ b/test/js/samples/video-bindings/expected.js @@ -93,7 +93,7 @@ function instance($$self, $$props, $$invalidate) { $$invalidate(3, offsetWidth); } - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("currentTime" in $$props) $$invalidate(0, currentTime = $$props.currentTime); if ("videoHeight" in $$props) $$invalidate(1, videoHeight = $$props.videoHeight); if ("videoWidth" in $$props) $$invalidate(2, videoWidth = $$props.videoWidth); diff --git a/test/js/samples/window-binding-scroll/expected.js b/test/js/samples/window-binding-scroll/expected.js index 45d992c72161..09a4d3737d29 100644 --- a/test/js/samples/window-binding-scroll/expected.js +++ b/test/js/samples/window-binding-scroll/expected.js @@ -78,7 +78,7 @@ function instance($$self, $$props, $$invalidate) { $$invalidate(0, y = window.pageYOffset) } - $$self.$set = $$props => { + $$self.$$set = $$props => { if ("y" in $$props) $$invalidate(0, y = $$props.y); }; diff --git a/test/runtime/samples/component-binding-store/Input.svelte b/test/runtime/samples/component-binding-store/Input.svelte new file mode 100644 index 000000000000..792104bec8c0 --- /dev/null +++ b/test/runtime/samples/component-binding-store/Input.svelte @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/test/runtime/samples/component-binding-store/_config.js b/test/runtime/samples/component-binding-store/_config.js new file mode 100644 index 000000000000..4dec41459f56 --- /dev/null +++ b/test/runtime/samples/component-binding-store/_config.js @@ -0,0 +1,61 @@ +export default { + html: ` + + +
+ `, + + async test({ assert, component, target, window }) { + let count = 0; + component.callback = () => { + count++; + }; + + const [input1, input2] = target.querySelectorAll("input"); + + input1.value = "1"; + await input1.dispatchEvent(new window.Event("input")); + + assert.htmlEqual( + target.innerHTML, + ` + + +
1
+ ` + ); + assert.equal(input1.value, "1"); + assert.equal(input2.value, "1"); + assert.equal(count, 1); + + input2.value = "123"; + await input2.dispatchEvent(new window.Event("input")); + + assert.htmlEqual( + target.innerHTML, + ` + + +
123
+ ` + ); + assert.equal(input1.value, "123"); + assert.equal(input2.value, "123"); + assert.equal(count, 2); + + input1.value = "456"; + await input1.dispatchEvent(new window.Event("input")); + + assert.htmlEqual( + target.innerHTML, + ` + + +
456
+ ` + ); + assert.equal(input1.value, "456"); + assert.equal(input2.value, "456"); + assert.equal(count, 3); + }, +}; diff --git a/test/runtime/samples/component-binding-store/main.svelte b/test/runtime/samples/component-binding-store/main.svelte new file mode 100644 index 000000000000..dba08e527680 --- /dev/null +++ b/test/runtime/samples/component-binding-store/main.svelte @@ -0,0 +1,18 @@ + + + + + + +
{$value.value}
\ No newline at end of file From 3d81b8cdf20d8f5c009541d5d3cf28e376cb0785 Mon Sep 17 00:00:00 2001 From: Benjamin Schachter Date: Mon, 20 Jul 2020 07:32:10 -0400 Subject: [PATCH 20/34] site: explain how to use `site-kit` and `site-repl` (#5163) Co-authored-by: Luca Bonavita --- site/README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/site/README.md b/site/README.md index 64ae22f9a8a1..892b8e12499c 100644 --- a/site/README.md +++ b/site/README.md @@ -53,6 +53,26 @@ To build the website, run `npm run sapper`. The output can be found in `__sapper Tests can be run using `npm run test`. + +## Linking `@sveltejs/site-kit` and `@sveltejs/site-repl` + +This site depends on `@sveltejs/site-kit`, a collection of styles, components and icons used in common by *.svelte.dev websites, and `@sveltejs/site-repl`. + +In order to work on features that depend on those packages, you need to [link](https://docs.npmjs.com/cli/link) their repositories: + +- `cd ` +- `git clone https://github.com/sveltejs/site-kit` +- `git clone https://github.com/sveltejs/svelte-repl` +- `cd /site-kit` +- `npm link` +- `cd /svelte-repl` +- `npm link` +- `cd /site` +- `npm link @sveltejs/site-kit` +- `npm link @sveltejs/svelte-repl` + + + ## Translating the API docs Anchors are automatically generated using headings in the documentation and by default (for the english language) they are latinised to make sure the URL is always conforming to RFC3986. From 453c7270236aef9b6bab92f47124bf6d182fd484 Mon Sep 17 00:00:00 2001 From: Luca Bonavita Date: Tue, 21 Jul 2020 15:05:02 +0100 Subject: [PATCH 21/34] site: rephrase to clarify that actions can have just one parameter. Closes #5173 --- site/content/docs/02-template-syntax.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/content/docs/02-template-syntax.md b/site/content/docs/02-template-syntax.md index 070dc466871e..6d0847eb4171 100644 --- a/site/content/docs/02-template-syntax.md +++ b/site/content/docs/02-template-syntax.md @@ -773,7 +773,7 @@ Actions are functions that are called when an element is created. They can retur --- -An action can have parameters. If the returned value has an `update` method, it will be called whenever those parameters change, immediately after Svelte has applied updates to the markup. +An action can have a parameter. If the returned value has an `update` method, it will be called whenever that parameter changes, immediately after Svelte has applied updates to the markup. > Don't worry about the fact that we're redeclaring the `foo` function for every component instance — Svelte will hoist any functions that don't depend on local state out of the component definition. From b1c18c70dbdfd88c9dccfa67abad653a21c3cdb2 Mon Sep 17 00:00:00 2001 From: Orta Therox Date: Tue, 21 Jul 2020 10:28:26 -0400 Subject: [PATCH 22/34] Initial stab at a TypeScript blog post (#5101) * Initial stab at a blog post * Update site/content/blog/2020-06-04-svelte-and-typescript.md Co-authored-by: pngwn * Update 2020-06-04-svelte-and-typescript.md * Apply suggestions from code review Co-authored-by: halfnelson Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> * Tighten the post * Fix degit sample * Feedback * More feeedback * Handle feedback * Handle all the feedback in the PR * Add a note about the should work * Change date * code style consistency, fix syntax error Co-authored-by: pngwn Co-authored-by: halfnelson Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> Co-authored-by: Rich Harris --- .../blog/2020-07-17-svelte-and-typescript.md | 141 ++++++++++++++++++ site/static/media/svelte-ts.png | Bin 0 -> 189664 bytes 2 files changed, 141 insertions(+) create mode 100644 site/content/blog/2020-07-17-svelte-and-typescript.md create mode 100644 site/static/media/svelte-ts.png diff --git a/site/content/blog/2020-07-17-svelte-and-typescript.md b/site/content/blog/2020-07-17-svelte-and-typescript.md new file mode 100644 index 000000000000..75f7cd250cfc --- /dev/null +++ b/site/content/blog/2020-07-17-svelte-and-typescript.md @@ -0,0 +1,141 @@ +--- +title: Svelte <3 TypeScript +description: Typernetically enhanced web apps +author: Orta Therox +authorURL: https://twitter.com/orta +--- + +It's been by far the most requested feature for a while, and it's finally here: Svelte officially supports TypeScript. + +We think it'll give you a much nicer development experience — one that also scales beautifully to larger Svelte code bases — regardless of whether you use TypeScript or JavaScript. + +
+ Screenshot of TypeScript in Svelte +
Image of TypeScript + Svelte in VS Code (theme is Kary Pro.)
+
+ + +## Try it now + +You can start a new Svelte TypeScript project using the [normal template](https://github.com/sveltejs/template) and by running `node scripts/setupTypeScript.js` before you do anything else: + +```bash +npx degit sveltejs/template svelte-typescript-app +cd svelte-typescript-app +node scripts/setupTypeScript.js +``` + +If you're a VS Code user, make sure you're using the (new) [official extension](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode), which replaces the popular extension by James Birtles. +Later in this blog post, we'll detail the individual steps involved in using TypeScript in an existing Svelte project. + +## What does it mean to support TypeScript in Svelte? + +TypeScript support in Svelte has been possible for a long time, but you had to mix a lot of disparate tools together and each project ran independently. Today, nearly all of these tools live under the Svelte organization and are maintained by a set of people who take responsibility over the whole pipeline and have common goals. + +A week before COVID was declared a pandemic, [I pitched a consolidation](https://github.com/sveltejs/svelte/issues/4518) of the best Svelte tools and ideas from similar dev-ecosystems and provided a set of steps to get first class TypeScript support. Since then, many people have pitched in and written the code to get us there. + +When we say that Svelte now supports TypeScript, we mean a few different things: + +* You can use TypeScript inside your ` + +{#each Object.keys(list) as key} + {#each values as value} + + {/each} +

{list[key].join(', ')}

+{/each} From 0b09c70c7308a76ac8b175fd3d37133a431f83c2 Mon Sep 17 00:00:00 2001 From: "M. Habib Rosyad" Date: Wed, 29 Jul 2020 01:48:16 +0700 Subject: [PATCH 28/34] site: fix escaping in RSS feed (#5214) --- site/src/routes/blog/rss.xml.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/site/src/routes/blog/rss.xml.js b/site/src/routes/blog/rss.xml.js index 974806ad442d..544474b274e9 100644 --- a/site/src/routes/blog/rss.xml.js +++ b/site/src/routes/blog/rss.xml.js @@ -8,6 +8,18 @@ function formatPubdate(str) { return `${d} ${months[+m]} ${y} 12:00 +0000`; } +function escapeHTML(html) { + const chars = { + '"' : 'quot', + "'": '#39', + '&': 'amp', + '<' : 'lt', + '>' : 'gt' + }; + + return html.replace(/["'&<>]/g, c => `&${chars[c]};`); +} + const rss = ` @@ -23,9 +35,9 @@ const rss = ` ${get_posts().filter(post => !post.metadata.draft).map(post => ` - ${post.metadata.title} + ${escapeHTML(post.metadata.title)} https://svelte.dev/blog/${post.slug} - ${post.metadata.description} + ${escapeHTML(post.metadata.description)} ${formatPubdate(post.metadata.pubdate)} `).join('')} From 1955089a459003557a7131c829f1721b04979ec0 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Tue, 28 Jul 2020 16:39:17 -0400 Subject: [PATCH 29/34] site: use relative link to tutorial in FAQ entry --- site/content/faq/100-im-new-to-svelte.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/content/faq/100-im-new-to-svelte.md b/site/content/faq/100-im-new-to-svelte.md index 18e4b6742f70..0f1df9cff50c 100644 --- a/site/content/faq/100-im-new-to-svelte.md +++ b/site/content/faq/100-im-new-to-svelte.md @@ -2,6 +2,6 @@ question: I'm new to Svelte. Where should I start? --- -We think the best way to get started is playing through the interactive [Tutorial](https://svelte.dev/tutorial). Each step there is mainly focused on one specific aspect and is easy to follow. You'll be editing and running real Svelte components right in your browser. +We think the best way to get started is playing through the interactive [Tutorial](tutorial). Each step there is mainly focused on one specific aspect and is easy to follow. You'll be editing and running real Svelte components right in your browser. Five to ten minutes should be enough to get you up and running. An hour and a half should get you through the entire tutorial. \ No newline at end of file From c906e21f7f81f90aa01e7e3864c897484847bb40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo?= <62244135+joaopaulobdac@users.noreply.github.com> Date: Thu, 30 Jul 2020 12:43:45 -0300 Subject: [PATCH 30/34] site: clarify file -> module in tutorial (#5171) --- .../tutorial/16-special-elements/01-svelte-self/text.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/content/tutorial/16-special-elements/01-svelte-self/text.md b/site/content/tutorial/16-special-elements/01-svelte-self/text.md index 773dded83ecb..c7946f6c6d3a 100644 --- a/site/content/tutorial/16-special-elements/01-svelte-self/text.md +++ b/site/content/tutorial/16-special-elements/01-svelte-self/text.md @@ -14,7 +14,7 @@ It's useful for things like this folder tree view, where folders can contain *ot {/if} ``` -...but that's impossible, because a file can't import itself. Instead, we use ``: +...but that's impossible, because a module can't import itself. Instead, we use ``: ```html {#if file.type === 'folder'} @@ -22,4 +22,4 @@ It's useful for things like this folder tree view, where folders can contain *ot {:else} {/if} -``` \ No newline at end of file +``` From 11df5ff7e4a580319bb92c9fa53767b4f462965b Mon Sep 17 00:00:00 2001 From: Christopher Mardell Date: Wed, 5 Aug 2020 19:51:19 +0930 Subject: [PATCH 31/34] Fix failing test --- src/runtime/internal/Component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index e0e7e15fc5a1..808f60142cf5 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -229,7 +229,6 @@ if (typeof HTMLElement === 'function') { for (const attr of Object.getOwnPropertyNames($$props)) { this.$$initialProps[attr] = $$props[attr]; } - return; } if (this.$$set && !is_empty($$props)) { this.$$.skip_bound = true; From b8de8fda19082c48bdaff6f438eb88f6e1ad2003 Mon Sep 17 00:00:00 2001 From: Christopher Mardell Date: Wed, 5 Aug 2020 20:05:06 +0930 Subject: [PATCH 32/34] Only include type for $$setup in Component --- src/runtime/internal/Component.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index 808f60142cf5..fbe9de1680ce 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -176,9 +176,7 @@ if (typeof HTMLElement === 'function') { // placeholder object to allow props to be set pre-$$setup $$initialProps: Record | null = {}; - $$setup(_options?) { - // overridden by instance - } + $$setup: (options) => void; connectedCallback() { if (!this.$$) { From e7b902599c4da6cbdbe7c77fa0cbefc63616903e Mon Sep 17 00:00:00 2001 From: Christopher Mardell Date: Wed, 5 Aug 2020 20:07:57 +0930 Subject: [PATCH 33/34] Use is_empty --- src/runtime/internal/Component.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index fbe9de1680ce..489623ac0d8f 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -181,14 +181,14 @@ if (typeof HTMLElement === 'function') { connectedCallback() { if (!this.$$) { // wasn't set up from constructor as options were not ready - const options = Object.keys(this.$$initialProps).length ? + const options = is_empty(this.$$initialProps) ? + null: { props: this.$$initialProps - } : - null; + }; this.$$setup(options); - // clean up + // clean up, prevent reuse of $$initialProps this.$$initialProps = null; } // @ts-ignore todo: improve typings From ab7c6bf6b991222f0b0492e8626e1eb53cda396b Mon Sep 17 00:00:00 2001 From: Christopher Mardell Date: Thu, 6 Aug 2020 09:29:08 +0930 Subject: [PATCH 34/34] Use is_empty --- src/runtime/internal/Component.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index fbe9de1680ce..489623ac0d8f 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -181,14 +181,14 @@ if (typeof HTMLElement === 'function') { connectedCallback() { if (!this.$$) { // wasn't set up from constructor as options were not ready - const options = Object.keys(this.$$initialProps).length ? + const options = is_empty(this.$$initialProps) ? + null: { props: this.$$initialProps - } : - null; + }; this.$$setup(options); - // clean up + // clean up, prevent reuse of $$initialProps this.$$initialProps = null; } // @ts-ignore todo: improve typings