diff --git a/.editorconfig b/.editorconfig index ed2a319d58..90846de5ee 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,4 +13,3 @@ insert_final_newline = false [{package.json,.travis.yml,.eslintrc.json}] indent_style = space -indent_size = 2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 84552c9d2b..bd57c193de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Svelte changelog +## Unreleased + +* In custom elements, call `onMount` functions when connecting and clean up when disconnecting ([#1152](https://github.com/sveltejs/svelte/issues/1152), [#2227](https://github.com/sveltejs/svelte/issues/2227), [#4522](https://github.com/sveltejs/svelte/pull/4522)) +* Do not emit `contextual-store` warnings for function parameters or declared variables ([#6008](https://github.com/sveltejs/svelte/pull/6008)) + +## 3.32.3 + +* Fix removal of lone `:host` selectors ([#5982](https://github.com/sveltejs/svelte/issues/5982)) + ## 3.32.2 * Fix unnecessary additional invalidation with `` ([#3075](https://github.com/sveltejs/svelte/issues/3075), [#4447](https://github.com/sveltejs/svelte/issues/4447), [#5555](https://github.com/sveltejs/svelte/issues/5555)) diff --git a/check_publish_env.js b/check_publish_env.js new file mode 100644 index 0000000000..ce6f066cff --- /dev/null +++ b/check_publish_env.js @@ -0,0 +1,4 @@ +if (!process.env.PUBLISH) { + console.error('npm publish must be run with the PUBLISH environment variable set'); + process.exit(1); +} diff --git a/package-lock.json b/package-lock.json index a0fa9b6980..b4d5b5caff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.32.2", + "version": "3.32.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index d18173fa9e..5b04c3e82e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.32.2", + "version": "3.32.3", "description": "Cybernetically enhanced web apps", "module": "index.mjs", "main": "index", @@ -73,7 +73,7 @@ "dev": "rollup -cw", "pretest": "npm run build", "posttest": "agadoo internal/index.mjs", - "prepublishOnly": "npm run lint && PUBLISH=true npm test", + "prepublishOnly": "node check_publish_env.js && npm run lint && npm test", "tsd": "tsc -p src/compiler --emitDeclarationOnly && tsc -p src/runtime --emitDeclarationOnly", "lint": "eslint \"{src,test}/**/*.{ts,js}\"" }, diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index 8aab2b4898..64e658c049 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -751,7 +751,7 @@ export default class Component { return this.skip(); } - component.warn_on_undefined_store_value_references(node, parent, scope); + component.warn_on_undefined_store_value_references(node, parent, prop, scope); }, leave(node: Node) { @@ -843,7 +843,7 @@ export default class Component { }); } - warn_on_undefined_store_value_references(node, parent, scope: Scope) { + warn_on_undefined_store_value_references(node: Node, parent: Node, prop: string, scope: Scope) { if ( node.type === 'LabeledStatement' && node.label.name === '$' && @@ -855,7 +855,7 @@ export default class Component { }); } - if (is_reference(node as Node, parent as Node)) { + if (is_reference(node, parent)) { const object = get_object(node); const { name } = object; @@ -865,10 +865,12 @@ export default class Component { } if (name[1] !== '$' && scope.has(name.slice(1)) && scope.find_owner(name.slice(1)) !== this.instance_scope) { - this.error(node, { - code: 'contextual-store', - message: 'Stores must be declared at the top level of the component (this may change in a future version of Svelte)' - }); + if (!((/Function/.test(parent.type) && prop === 'params') || (parent.type === 'VariableDeclarator' && prop === 'id'))) { + this.error(node as any, { + code: 'contextual-store', + message: 'Stores must be declared at the top level of the component (this may change in a future version of Svelte)' + }); + } } } } diff --git a/src/compiler/compile/css/Selector.ts b/src/compiler/compile/css/Selector.ts index 3c9f453c8d..6d0e7c5003 100644 --- a/src/compiler/compile/css/Selector.ts +++ b/src/compiler/compile/css/Selector.ts @@ -44,7 +44,10 @@ export default class Selector { } this.local_blocks = this.blocks.slice(0, i); - this.used = this.local_blocks.length === 0; + + const host_only = this.blocks.length === 1 && this.blocks[0].host; + + this.used = this.local_blocks.length === 0 || host_only; } apply(node: Element) { diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index 8e7deb7a78..a56f3fb2b3 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -485,7 +485,7 @@ export default function dom( ${css.code && b`this.shadowRoot.innerHTML = \`\`;`} - @init(this, { target: this.shadowRoot, props: ${init_props} }, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, ${dirty}); + @init(this, { target: this.shadowRoot, props: ${init_props}, customElement: true }, ${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 6d211dfedd..5a2a966d0b 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -34,6 +34,7 @@ interface T$$ { on_mount: any[]; on_destroy: any[]; skip_bound: boolean; + on_disconnect: any[]; } export function bind(component, name, callback) { @@ -52,23 +53,26 @@ export function claim_component(block, parent_nodes) { block && block.l(parent_nodes); } -export function mount_component(component, target, anchor) { +export function mount_component(component, target, anchor, customElement) { const { fragment, on_mount, on_destroy, after_update } = component.$$; fragment && fragment.m(target, anchor); - // onMount happens before the initial afterUpdate - add_render_callback(() => { - const new_on_destroy = on_mount.map(run).filter(is_function); - if (on_destroy) { - on_destroy.push(...new_on_destroy); - } else { - // Edge case - component was destroyed immediately, - // most likely as a result of a binding initialising - run_all(new_on_destroy); - } - component.$$.on_mount = []; - }); + if (!customElement) { + // onMount happens before the initial afterUpdate + add_render_callback(() => { + + const new_on_destroy = on_mount.map(run).filter(is_function); + if (on_destroy) { + on_destroy.push(...new_on_destroy); + } else { + // Edge case - component was destroyed immediately, + // most likely as a result of a binding initialising + run_all(new_on_destroy); + } + component.$$.on_mount = []; + }); + } after_update.forEach(add_render_callback); } @@ -113,6 +117,7 @@ export function init(component, options, instance, create_fragment, not_equal, p // lifecycle on_mount: [], on_destroy: [], + on_disconnect: [], before_update: [], after_update: [], context: new Map(parent_component ? parent_component.$$.context : []), @@ -155,7 +160,7 @@ export function init(component, options, instance, create_fragment, not_equal, p } if (options.intro) transition_in(component.$$.fragment); - mount_component(component, options.target, options.anchor); + mount_component(component, options.target, options.anchor, options.customElement); flush(); } @@ -173,6 +178,9 @@ if (typeof HTMLElement === 'function') { } connectedCallback() { + const { on_mount } = this.$$; + this.$$.on_disconnect = on_mount.map(run).filter(is_function); + // @ts-ignore todo: improve typings for (const key in this.$$.slotted) { // @ts-ignore todo: improve typings @@ -184,6 +192,10 @@ if (typeof HTMLElement === 'function') { this[attr] = newValue; } + disconnectedCallback() { + run_all(this.$$.on_disconnect); + } + $destroy() { destroy_component(this, 1); this.$destroy = noop; diff --git a/test/css/samples/host/expected.css b/test/css/samples/host/expected.css index 28683691c0..d848082f5e 100644 --- a/test/css/samples/host/expected.css +++ b/test/css/samples/host/expected.css @@ -1 +1 @@ -:host h1.svelte-xyz{color:red}:host>h1.svelte-xyz{color:red}:host>.svelte-xyz{color:red}:host span.svelte-xyz{color:red} \ No newline at end of file +:host h1.svelte-xyz{color:red}:host>h1.svelte-xyz{color:red}:host>.svelte-xyz{color:red}:host span.svelte-xyz{color:red}:host{color:red} \ No newline at end of file diff --git a/test/css/samples/host/input.svelte b/test/css/samples/host/input.svelte index 4c1efcd983..85db1617ef 100644 --- a/test/css/samples/host/input.svelte +++ b/test/css/samples/host/input.svelte @@ -18,6 +18,10 @@ :host > span { color: red; } + + :host { + color: red; + }

Hello!

diff --git a/test/custom-elements/index.ts b/test/custom-elements/index.ts index d3644a3142..35df156879 100644 --- a/test/custom-elements/index.ts +++ b/test/custom-elements/index.ts @@ -110,8 +110,8 @@ describe('custom-elements', function() { const page = await browser.newPage(); - page.on('console', (type, ...args) => { - console[type](...args); + page.on('console', (type) => { + console[type._type](type._text); }); page.on('error', error => { diff --git a/test/custom-elements/samples/oncreate/main.svelte b/test/custom-elements/samples/oncreate/main.svelte index ed3980a28e..23819e660f 100644 --- a/test/custom-elements/samples/oncreate/main.svelte +++ b/test/custom-elements/samples/oncreate/main.svelte @@ -3,9 +3,12 @@ diff --git a/test/custom-elements/samples/oncreate/test.js b/test/custom-elements/samples/oncreate/test.js index c33f8a6a10..f451979976 100644 --- a/test/custom-elements/samples/oncreate/test.js +++ b/test/custom-elements/samples/oncreate/test.js @@ -2,7 +2,9 @@ import * as assert from 'assert'; import './main.svelte'; export default function (target) { - target.innerHTML = ''; + target.innerHTML = ''; const el = target.querySelector('my-app'); + assert.ok(el.wasCreated); + assert.ok(el.propsInitialized); } diff --git a/test/custom-elements/samples/ondestroy/main.svelte b/test/custom-elements/samples/ondestroy/main.svelte new file mode 100644 index 0000000000..aa945ca602 --- /dev/null +++ b/test/custom-elements/samples/ondestroy/main.svelte @@ -0,0 +1,22 @@ + + + + +
diff --git a/test/custom-elements/samples/ondestroy/test.js b/test/custom-elements/samples/ondestroy/test.js new file mode 100644 index 0000000000..61375bfa96 --- /dev/null +++ b/test/custom-elements/samples/ondestroy/test.js @@ -0,0 +1,11 @@ +import * as assert from 'assert'; +import './main.svelte'; + +export default function (target) { + target.innerHTML = ''; + const el = target.querySelector('my-app'); + target.removeChild(el); + + assert.ok(target.dataset.onMountDestroyed); + assert.equal(target.dataset.destroyed, undefined); +} diff --git a/test/js/samples/css-shadow-dom-keyframes/expected.js b/test/js/samples/css-shadow-dom-keyframes/expected.js index 82a39e5924..4d188201eb 100644 --- a/test/js/samples/css-shadow-dom-keyframes/expected.js +++ b/test/js/samples/css-shadow-dom-keyframes/expected.js @@ -40,7 +40,8 @@ class Component extends SvelteElement { this, { target: this.shadowRoot, - props: attribute_to_object(this.attributes) + props: attribute_to_object(this.attributes), + customElement: true }, null, create_fragment, diff --git a/test/runtime/samples/store-shadow-scope-declaration/_config.js b/test/runtime/samples/store-shadow-scope-declaration/_config.js new file mode 100644 index 0000000000..ff8b4c5632 --- /dev/null +++ b/test/runtime/samples/store-shadow-scope-declaration/_config.js @@ -0,0 +1 @@ +export default {}; diff --git a/test/runtime/samples/store-shadow-scope-declaration/main.svelte b/test/runtime/samples/store-shadow-scope-declaration/main.svelte new file mode 100644 index 0000000000..fa76d33df8 --- /dev/null +++ b/test/runtime/samples/store-shadow-scope-declaration/main.svelte @@ -0,0 +1,25 @@ + + +
{ + derived(store, $store => {}); + }} + on:test2={(store) => { + let $store; + }} +/>