From 4bbb5454e2a189a72e734cc33d98bce9a4b6f5a1 Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Tue, 18 Jul 2023 08:36:36 -0700 Subject: [PATCH 01/10] perf: optimize imports that are not mutated or reassigned (#8948) this means such imports are seen as static and subsequently Svelte needs to generate way less code --- .changeset/shaggy-pans-repair.md | 5 +++++ packages/svelte/src/compiler/compile/Component.js | 8 +++----- .../svelte/src/compiler/compile/internal_exports.js | 2 +- .../src/compiler/compile/nodes/shared/Expression.js | 10 ++++++++-- 4 files changed, 17 insertions(+), 8 deletions(-) create mode 100644 .changeset/shaggy-pans-repair.md diff --git a/.changeset/shaggy-pans-repair.md b/.changeset/shaggy-pans-repair.md new file mode 100644 index 0000000000..7d44a6b861 --- /dev/null +++ b/.changeset/shaggy-pans-repair.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +perf: optimize imports that are not mutated or reassigned diff --git a/packages/svelte/src/compiler/compile/Component.js b/packages/svelte/src/compiler/compile/Component.js index 06e849fe13..bba2dd47ed 100644 --- a/packages/svelte/src/compiler/compile/Component.js +++ b/packages/svelte/src/compiler/compile/Component.js @@ -770,14 +770,12 @@ export default class Component { if (name[0] === '$') { return this.error(/** @type {any} */ (node), compiler_errors.illegal_declaration); } - const writable = - node.type === 'VariableDeclaration' && (node.kind === 'var' || node.kind === 'let'); - const imported = node.type.startsWith('Import'); + const { type } = node; this.add_var(node, { name, initialised: instance_scope.initialised_declarations.has(name), - writable, - imported + imported: type.startsWith('Import'), + writable: type === 'VariableDeclaration' && (node.kind === 'var' || node.kind === 'let') }); this.node_for_declaration.set(name, node); }); diff --git a/packages/svelte/src/compiler/compile/internal_exports.js b/packages/svelte/src/compiler/compile/internal_exports.js index d2b7356214..7656f52f3a 100644 --- a/packages/svelte/src/compiler/compile/internal_exports.js +++ b/packages/svelte/src/compiler/compile/internal_exports.js @@ -1,2 +1,2 @@ // This file is automatically generated -export default new Set(["HtmlTag","HtmlTagHydration","ResizeObserverSingleton","SvelteComponent","SvelteComponentDev","SvelteComponentTyped","SvelteElement","action_destroyer","add_attribute","add_classes","add_flush_callback","add_iframe_resize_listener","add_location","add_render_callback","add_styles","add_transform","afterUpdate","append","append_dev","append_empty_stylesheet","append_hydration","append_hydration_dev","append_styles","assign","attr","attr_dev","attribute_to_object","beforeUpdate","bind","binding_callbacks","blank_object","bubble","check_outros","children","claim_comment","claim_component","claim_element","claim_html_tag","claim_space","claim_svg_element","claim_text","clear_loops","comment","component_subscribe","compute_rest_props","compute_slots","construct_svelte_component","construct_svelte_component_dev","contenteditable_truthy_values","createEventDispatcher","create_animation","create_bidirectional_transition","create_component","create_custom_element","create_in_transition","create_out_transition","create_slot","create_ssr_component","current_component","custom_event","dataset_dev","debug","destroy_block","destroy_component","destroy_each","detach","detach_after_dev","detach_before_dev","detach_between_dev","detach_dev","dirty_components","dispatch_dev","each","element","element_is","empty","end_hydrating","ensure_array_like","ensure_array_like_dev","escape","escape_attribute_value","escape_object","exclude_internal_props","fix_and_destroy_block","fix_and_outro_and_destroy_block","fix_position","flush","flush_render_callbacks","getAllContexts","getContext","get_all_dirty_from_scope","get_binding_group_value","get_current_component","get_custom_elements_slots","get_root_for_style","get_slot_changes","get_spread_object","get_spread_update","get_store_value","get_svelte_dataset","globals","group_outros","handle_promise","hasContext","has_prop","head_selector","identity","init","init_binding_group","init_binding_group_dynamic","insert","insert_dev","insert_hydration","insert_hydration_dev","intros","invalid_attribute_name_character","is_client","is_crossorigin","is_empty","is_function","is_promise","is_void","listen","listen_dev","loop","loop_guard","merge_ssr_styles","missing_component","mount_component","noop","not_equal","now","null_to_empty","object_without_properties","onDestroy","onMount","once","outro_and_destroy_block","prevent_default","prop_dev","query_selector_all","raf","resize_observer_border_box","resize_observer_content_box","resize_observer_device_pixel_content_box","run","run_all","safe_not_equal","schedule_update","select_multiple_value","select_option","select_options","select_value","self","setContext","set_attributes","set_current_component","set_custom_element_data","set_custom_element_data_map","set_data","set_data_contenteditable","set_data_contenteditable_dev","set_data_dev","set_data_maybe_contenteditable","set_data_maybe_contenteditable_dev","set_dynamic_element_data","set_input_type","set_input_value","set_now","set_raf","set_store_value","set_style","set_svg_attributes","space","split_css_unit","spread","src_url_equal","start_hydrating","stop_immediate_propagation","stop_propagation","subscribe","svg_element","text","tick","time_ranges_to_array","to_number","toggle_class","transition_in","transition_out","trusted","update_await_block_branch","update_keyed_each","update_slot","update_slot_base","validate_component","validate_dynamic_element","validate_each_keys","validate_slots","validate_store","validate_void_dynamic_element","xlink_attr"]); \ No newline at end of file +export default new Set(["HtmlTag","HtmlTagHydration","ResizeObserverSingleton","SvelteComponent","SvelteComponentDev","SvelteComponentTyped","SvelteElement","action_destroyer","add_attribute","add_classes","add_flush_callback","add_iframe_resize_listener","add_location","add_render_callback","add_styles","add_transform","afterUpdate","append","append_dev","append_empty_stylesheet","append_hydration","append_hydration_dev","append_styles","assign","attr","attr_dev","attribute_to_object","beforeUpdate","bind","binding_callbacks","blank_object","bubble","check_outros","children","claim_comment","claim_component","claim_element","claim_html_tag","claim_space","claim_svg_element","claim_text","clear_loops","comment","component_subscribe","compute_rest_props","compute_slots","construct_svelte_component","construct_svelte_component_dev","contenteditable_truthy_values","createEventDispatcher","create_animation","create_bidirectional_transition","create_component","create_custom_element","create_in_transition","create_out_transition","create_slot","create_ssr_component","current_component","custom_event","dataset_dev","debug","destroy_block","destroy_component","destroy_each","detach","detach_after_dev","detach_before_dev","detach_between_dev","detach_dev","dirty_components","dispatch_dev","each","element","element_is","empty","end_hydrating","ensure_array_like","ensure_array_like_dev","escape","escape_attribute_value","escape_object","exclude_internal_props","fix_and_destroy_block","fix_and_outro_and_destroy_block","fix_position","flush","flush_render_callbacks","getAllContexts","getContext","get_all_dirty_from_scope","get_binding_group_value","get_current_component","get_custom_elements_slots","get_root_for_style","get_slot_changes","get_spread_object","get_spread_update","get_store_value","get_svelte_dataset","globals","group_outros","handle_promise","hasContext","has_prop","head_selector","identity","init","init_binding_group","init_binding_group_dynamic","insert","insert_dev","insert_hydration","insert_hydration_dev","intros","invalid_attribute_name_character","is_client","is_crossorigin","is_empty","is_function","is_promise","is_void","listen","listen_dev","loop","loop_guard","merge_ssr_styles","missing_component","mount_component","noop","not_equal","now","null_to_empty","object_without_properties","onDestroy","onMount","once","outro_and_destroy_block","prevent_default","prop_dev","query_selector_all","raf","resize_observer_border_box","resize_observer_content_box","resize_observer_device_pixel_content_box","run","run_all","safe_not_equal","schedule_update","select_multiple_value","select_option","select_options","select_value","self","setContext","set_attributes","set_current_component","set_custom_element_data","set_custom_element_data_map","set_data","set_data_contenteditable","set_data_contenteditable_dev","set_data_dev","set_data_maybe_contenteditable","set_data_maybe_contenteditable_dev","set_dynamic_element_data","set_input_type","set_input_value","set_now","set_raf","set_store_value","set_style","set_svg_attributes","space","split_css_unit","spread","src_url_equal","srcset_url_equal","start_hydrating","stop_immediate_propagation","stop_propagation","subscribe","svg_element","text","tick","time_ranges_to_array","to_number","toggle_class","transition_in","transition_out","trusted","update_await_block_branch","update_keyed_each","update_slot","update_slot_base","validate_component","validate_dynamic_element","validate_each_keys","validate_slots","validate_store","validate_void_dynamic_element","xlink_attr"]); \ No newline at end of file diff --git a/packages/svelte/src/compiler/compile/nodes/shared/Expression.js b/packages/svelte/src/compiler/compile/nodes/shared/Expression.js index fe1feac534..69310f77b4 100644 --- a/packages/svelte/src/compiler/compile/nodes/shared/Expression.js +++ b/packages/svelte/src/compiler/compile/nodes/shared/Expression.js @@ -54,7 +54,8 @@ export default class Expression { /** @type {Array} */ declarations = []; - /** */ + + /** @type {boolean} */ uses_context = false; /** @type {import('estree').Node} */ @@ -129,7 +130,10 @@ export default class Expression { } } else { if (!lazy) { - dependencies.add(name); + const variable = component.var_lookup.get(name); + if (!variable || !variable.imported || variable.mutated || variable.reassigned) { + dependencies.add(name); + } } component.add_reference(node, name); component.warn_if_undefined(name, nodes[0], template_scope, owner); @@ -231,6 +235,8 @@ export default class Expression { if (this.manipulated) return this.manipulated; const { component, declarations, scope_map: map, template_scope, owner } = this; let scope = this.scope; + + /** @type {import('estree').FunctionExpression | import('estree').ArrowFunctionExpression | null} */ let function_expression; /** @type {Set} */ From 657f11376cf2ef0cc2f855559a02526a2d8412d6 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Tue, 18 Jul 2023 18:21:19 +0200 Subject: [PATCH 02/10] feat: add ability to extend custom element class (#8991) This should help everyone who has special needs and use cases around custom elements. Since Svelte components are wrapped and only run on connectedCallback, it makes sense to expose the custom element class for modification before that. - fixes #8954 / closes #8955 - use extend to attach the function manually and save possible values to a prop - closes #8473 / closes #4168 - use extend to set the proper static attribute and then call attachInternals in the constructor - closes #8472 - use extend to attach anything custom you need - closes #3091 - pass `this` to a prop of your choice and use it inside your component - add some doc for #8987 --- .changeset/green-cats-matter.md | 5 ++ .../04-custom-elements-api.md | 49 ++++++++++++++++--- packages/svelte/elements.d.ts | 3 ++ .../svelte/src/compiler/compile/Component.js | 16 +++++- .../src/compiler/compile/render_dom/index.js | 19 ++++--- .../svelte/src/runtime/internal/Component.js | 10 +++- .../custom-class/main.svelte | 22 +++++++++ .../custom-class/test.js | 14 ++++++ 8 files changed, 119 insertions(+), 19 deletions(-) create mode 100644 .changeset/green-cats-matter.md create mode 100644 packages/svelte/test/runtime-browser/custom-elements-samples/custom-class/main.svelte create mode 100644 packages/svelte/test/runtime-browser/custom-elements-samples/custom-class/test.js diff --git a/.changeset/green-cats-matter.md b/.changeset/green-cats-matter.md new file mode 100644 index 0000000000..2597640ff8 --- /dev/null +++ b/.changeset/green-cats-matter.md @@ -0,0 +1,5 @@ +--- +'svelte': minor +--- + +feat: add ability to extend custom element class diff --git a/documentation/docs/04-compiler-and-api/04-custom-elements-api.md b/documentation/docs/04-compiler-and-api/04-custom-elements-api.md index 12f51b6440..2227ae83c2 100644 --- a/documentation/docs/04-compiler-and-api/04-custom-elements-api.md +++ b/documentation/docs/04-compiler-and-api/04-custom-elements-api.md @@ -55,13 +55,28 @@ console.log(el.name); el.name = 'everybody'; ``` +## Component lifecycle + +Custom elements are created from Svelte components using a wrapper approach. This means the inner Svelte component has no knowledge that it is a custom element. The custom element wrapper takes care of handling its lifecycle appropriately. + +When a custom element is created, the Svelte component it wraps is _not_ created right away. It is only created in the next tick after the `connectedCallback` is invoked. Properties assigned to the custom element before it is inserted into the DOM are temporarily saved and then set on component creation, so their values are not lost. The same does not work for invoking exported functions on the custom element though, they are only available after the element has mounted. If you need to invoke functions before component creation, you can work around it by using the [`extend` option](#component-options). + +When a custom element written with Svelte is created or updated, the shadow DOM will reflect the value in the next tick, not immediately. This way updates can be batched, and DOM moves which temporarily (but synchronously) detach the element from the DOM don't lead to unmounting the inner component. + +The inner Svelte component is destroyed in the next tick after the `disconnectedCallback` is invoked. + ## Component options -When constructing a custom element, you can tailor several aspects by defining `customElement` as an object within `` since Svelte 4. This object comprises a mandatory `tag` property for the custom element's name, an optional `shadow` property that can be set to `"none"` to forgo shadow root creation (note that styles are then no longer encapsulated, and you can't use slots), and a `props` option, which offers the following settings: +When constructing a custom element, you can tailor several aspects by defining `customElement` as an object within `` since Svelte 4. This object may contain the following properties: -- `attribute: string`: To update a custom element's prop, you have two alternatives: either set the property on the custom element's reference as illustrated above or use an HTML attribute. For the latter, the default attribute name is the lowercase property name. Modify this by assigning `attribute: ""`. -- `reflect: boolean`: By default, updated prop values do not reflect back to the DOM. To enable this behavior, set `reflect: true`. -- `type: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object'`: While converting an attribute value to a prop value and reflecting it back, the prop value is assumed to be a `String` by default. This may not always be accurate. For instance, for a number type, define it using `type: "Number"` +- `tag`: the mandatory `tag` property for the custom element's name +- `shadow`: an optional property that can be set to `"none"` to forgo shadow root creation. Note that styles are then no longer encapsulated, and you can't use slots +- `props`: an optional property to modify certain details and behaviors of your component's properties. It offers the following settings: + - `attribute: string`: To update a custom element's prop, you have two alternatives: either set the property on the custom element's reference as illustrated above or use an HTML attribute. For the latter, the default attribute name is the lowercase property name. Modify this by assigning `attribute: ""`. + - `reflect: boolean`: By default, updated prop values do not reflect back to the DOM. To enable this behavior, set `reflect: true`. + - `type: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object'`: While converting an attribute value to a prop value and reflecting it back, the prop value is assumed to be a `String` by default. This may not always be accurate. For instance, for a number type, define it using `type: "Number"` + You don't need to list all properties, those not listed will use the default settings. +- `extend`: an optional property which expects a function as its argument. It is passed the custom element class generated by Svelte and expects you to return a custom element class. This comes in handy if you have very specific requirements to the life cycle of the custom element or want to enhance the class to for example use [ElementInternals](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals#examples) for better HTML form integration. ```svelte { + // Extend the class so we can let it participate in HTML forms + return class extends customElementConstructor { + static formAssociated = true; + + constructor() { + super(); + this.attachedInternals = this.attachInternals(); + } + + // Add the function here, not below in the component so that + // it's always available, not just when the inner Svelte component + // is mounted + randomIndex() { + this.elementIndex = Math.random(); + } + }; } }} /> ... @@ -91,5 +129,4 @@ Custom elements can be a useful way to package components for consumption in a n - In Svelte, slotted content renders _lazily_. In the DOM, it renders _eagerly_. In other words, it will always be created even if the component's `` element is inside an `{#if ...}` block. Similarly, including a `` in an `{#each ...}` block will not cause the slotted content to be rendered multiple times - The `let:` directive has no effect, because custom elements do not have a way to pass data to the parent component that fills the slot - Polyfills are required to support older browsers - -When a custom element written with Svelte is created or updated, the shadow dom will reflect the value in the next tick, not immediately. This way updates can be batched, and DOM moves which temporarily (but synchronously) detach the element from the DOM don't lead to unmounting the inner component. +- You can use Svelte's context feature between regular Svelte components within a custom element, but you can't use them across custom elements. In other words, you can't use `setContext` on a parent custom element and read that with `getContext` in a child custom element. diff --git a/packages/svelte/elements.d.ts b/packages/svelte/elements.d.ts index 046ba94786..ec450284c5 100644 --- a/packages/svelte/elements.d.ts +++ b/packages/svelte/elements.d.ts @@ -1676,6 +1676,9 @@ export interface SvelteHTMLElements { } > | undefined; + extend?: ( + svelteCustomElementClass: new () => HTMLElement + ) => new () => HTMLElement | undefined; }; immutable?: boolean | undefined; accessors?: boolean | undefined; diff --git a/packages/svelte/src/compiler/compile/Component.js b/packages/svelte/src/compiler/compile/Component.js index bba2dd47ed..7598e60967 100644 --- a/packages/svelte/src/compiler/compile/Component.js +++ b/packages/svelte/src/compiler/compile/Component.js @@ -1708,6 +1708,7 @@ function process_component_options(component, nodes) { case 'customElement': { component_options.customElement = component_options.customElement || /** @type {any} */ ({}); + const { value } = attribute; if (value[0].type === 'MustacheTag' && value[0].expression?.value === null) { component_options.customElement.tag = null; @@ -1718,12 +1719,14 @@ function process_component_options(component, nodes) { } else if (value[0].expression.type !== 'ObjectExpression') { return component.error(attribute, compiler_errors.invalid_customElement_attribute); } + const tag = value[0].expression.properties.find((prop) => prop.key.name === 'tag'); if (tag) { parse_tag(tag, tag.value?.value); } else { return component.error(attribute, compiler_errors.invalid_customElement_attribute); } + const props = value[0].expression.properties.find((prop) => prop.key.name === 'props'); if (props) { const error = () => @@ -1768,6 +1771,7 @@ function process_component_options(component, nodes) { } } } + const shadow = value[0].expression.properties.find( (prop) => prop.key.name === 'shadow' ); @@ -1778,6 +1782,14 @@ function process_component_options(component, nodes) { } component_options.customElement.shadow = shadowdom; } + + const extend = value[0].expression.properties.find( + (prop) => prop.key.name === 'extend' + ); + if (extend?.value) { + component_options.customElement.extend = extend.value; + } + break; } case 'namespace': { @@ -1851,7 +1863,8 @@ function get_sourcemap_source_filename(compile_options) { : get_basename(compile_options.filename); } -/** @typedef {Object} ComponentOptions +/** + * @typedef {Object} ComponentOptions * @property {string} [namespace] * @property {boolean} [immutable] * @property {boolean} [accessors] @@ -1860,4 +1873,5 @@ function get_sourcemap_source_filename(compile_options) { * @property {string|null} customElement.tag * @property {'open'|'none'} [customElement.shadow] * @property {Record} [customElement.props] + * @property {(ceClass: new () => HTMLElement) => new () => HTMLElement} [customElement.extend] */ diff --git a/packages/svelte/src/compiler/compile/render_dom/index.js b/packages/svelte/src/compiler/compile/render_dom/index.js index 9a11013fc9..b2d02bb78c 100644 --- a/packages/svelte/src/compiler/compile/render_dom/index.js +++ b/packages/svelte/src/compiler/compile/render_dom/index.js @@ -588,20 +588,19 @@ export default function dom(component, options) { .join(','); const use_shadow_dom = component.component_options.customElement?.shadow !== 'none' ? 'true' : 'false'; + + const create_ce = x`@create_custom_element(${name}, ${JSON.stringify( + props_str + )}, [${slots_str}], [${accessors_str}], ${use_shadow_dom}, ${ + component.component_options.customElement?.extend + })`; + if (component.component_options.customElement?.tag) { body.push( - b`@_customElements.define("${ - component.component_options.customElement.tag - }", @create_custom_element(${name}, ${JSON.stringify( - props_str - )}, [${slots_str}], [${accessors_str}], ${use_shadow_dom}));` + b`@_customElements.define("${component.component_options.customElement.tag}", ${create_ce});` ); } else { - body.push( - b`@create_custom_element(${name}, ${JSON.stringify( - props_str - )}, [${slots_str}], [${accessors_str}], ${use_shadow_dom});` - ); + body.push(b`${create_ce}`); } } diff --git a/packages/svelte/src/runtime/internal/Component.js b/packages/svelte/src/runtime/internal/Component.js index 4733b1d745..bc5b117c2f 100644 --- a/packages/svelte/src/runtime/internal/Component.js +++ b/packages/svelte/src/runtime/internal/Component.js @@ -383,15 +383,17 @@ function get_custom_element_value(prop, value, props_definition, transform) { * @param {string[]} slots The slots to create * @param {string[]} accessors Other accessors besides the ones for props the component has * @param {boolean} use_shadow_dom Whether to use shadow DOM + * @param {(ce: new () => HTMLElement) => new () => HTMLElement} [extend] */ export function create_custom_element( Component, props_definition, slots, accessors, - use_shadow_dom + use_shadow_dom, + extend ) { - const Class = class extends SvelteElement { + let Class = class extends SvelteElement { constructor() { super(Component, slots, use_shadow_dom); this.$$p_d = props_definition; @@ -421,6 +423,10 @@ export function create_custom_element( } }); }); + if (extend) { + // @ts-expect-error - assigning here is fine + Class = extend(Class); + } Component.element = /** @type {any} */ (Class); return Class; } diff --git a/packages/svelte/test/runtime-browser/custom-elements-samples/custom-class/main.svelte b/packages/svelte/test/runtime-browser/custom-elements-samples/custom-class/main.svelte new file mode 100644 index 0000000000..f110b118f4 --- /dev/null +++ b/packages/svelte/test/runtime-browser/custom-elements-samples/custom-class/main.svelte @@ -0,0 +1,22 @@ + { + return class extends CeClass { + updateFoo(value) { + this.foo = value; + } + }; + } + }} +/> + + + +

{foo}

diff --git a/packages/svelte/test/runtime-browser/custom-elements-samples/custom-class/test.js b/packages/svelte/test/runtime-browser/custom-elements-samples/custom-class/test.js new file mode 100644 index 0000000000..b794f02e25 --- /dev/null +++ b/packages/svelte/test/runtime-browser/custom-elements-samples/custom-class/test.js @@ -0,0 +1,14 @@ +import * as assert from 'assert.js'; +import { tick } from 'svelte'; +import './main.svelte'; + +export default async function (target) { + const element = document.createElement('custom-element'); + element.updateFoo('42'); + target.appendChild(element); + await tick(); + + const el = target.querySelector('custom-element'); + const p = el.shadowRoot.querySelector('p'); + assert.equal(p.textContent, '42'); +} From cb1358cc417effeb2e747f811260936c11b8923d Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Wed, 19 Jul 2023 09:39:45 +0200 Subject: [PATCH 03/10] fix: don't add accessor twice (#8996) In dev mode, Svelte creates a setter to throw an error noting that you can't set that readonly prop, which resulted in the accessor getting applied twice to the custom element wrapper, causing an error fixes #8971 --- .changeset/ten-gifts-design.md | 5 +++++ packages/svelte/src/compiler/compile/render_dom/index.js | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 .changeset/ten-gifts-design.md diff --git a/.changeset/ten-gifts-design.md b/.changeset/ten-gifts-design.md new file mode 100644 index 0000000000..a4e154fc9c --- /dev/null +++ b/.changeset/ten-gifts-design.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: don't add accessor twice diff --git a/packages/svelte/src/compiler/compile/render_dom/index.js b/packages/svelte/src/compiler/compile/render_dom/index.js index b2d02bb78c..0627e7e0cb 100644 --- a/packages/svelte/src/compiler/compile/render_dom/index.js +++ b/packages/svelte/src/compiler/compile/render_dom/index.js @@ -583,7 +583,11 @@ export default function dom(component, options) { }, {}); const slots_str = [...component.slots.keys()].map((key) => `"${key}"`).join(','); const accessors_str = accessors - .filter((accessor) => !writable_props.some((prop) => prop.export_name === accessor.key.name)) + .filter( + (accessor) => + accessor.kind === 'get' && + !writable_props.some((prop) => prop.export_name === accessor.key.name) + ) .map((accessor) => `"${accessor.key.name}"`) .join(','); const use_shadow_dom = From f8283fbc775d94d2ea081d06153cc1f9d89eea72 Mon Sep 17 00:00:00 2001 From: Nguyen Tran <88808276+ngtr6788@users.noreply.github.com> Date: Wed, 19 Jul 2023 07:55:27 -0400 Subject: [PATCH 04/10] fix: remove let variable from select binding dependencies (#8969) Fixes #2943 The issue linked above invalidates a variable that is not defined in + + + diff --git a/packages/svelte/test/runtime/samples/binding-select-from-let-2/_config.js b/packages/svelte/test/runtime/samples/binding-select-from-let-2/_config.js new file mode 100644 index 0000000000..125455819a --- /dev/null +++ b/packages/svelte/test/runtime/samples/binding-select-from-let-2/_config.js @@ -0,0 +1,25 @@ +export default { + html: ` + +

1

+ `, + + async test({ assert, component, target, window }) { + const select = target.querySelector('select'); + const options = target.querySelectorAll('option'); + + assert.equal(component.tasks_touched, 1); + + const change = new window.Event('change'); + options[1].selected = true; + await select.dispatchEvent(change); + + assert.equal(component.selected, options[1].value); + assert.equal(component.tasks_touched, 1); + } +}; diff --git a/packages/svelte/test/runtime/samples/binding-select-from-let-2/main.svelte b/packages/svelte/test/runtime/samples/binding-select-from-let-2/main.svelte new file mode 100644 index 0000000000..3c21eba829 --- /dev/null +++ b/packages/svelte/test/runtime/samples/binding-select-from-let-2/main.svelte @@ -0,0 +1,19 @@ + + + + + +

{tasks_touched}

diff --git a/packages/svelte/test/runtime/samples/binding-select-from-let/Parent.svelte b/packages/svelte/test/runtime/samples/binding-select-from-let/Parent.svelte new file mode 100644 index 0000000000..565c3bc6b7 --- /dev/null +++ b/packages/svelte/test/runtime/samples/binding-select-from-let/Parent.svelte @@ -0,0 +1,6 @@ + + + + diff --git a/packages/svelte/test/runtime/samples/binding-select-from-let/_config.js b/packages/svelte/test/runtime/samples/binding-select-from-let/_config.js new file mode 100644 index 0000000000..30f42a2251 --- /dev/null +++ b/packages/svelte/test/runtime/samples/binding-select-from-let/_config.js @@ -0,0 +1,21 @@ +export default { + html: ` + + `, + + async test({ assert, component, target, window }) { + const select = target.querySelector('select'); + const options = target.querySelectorAll('option'); + + const change = new window.Event('change'); + options[1].selected = true; + await select.dispatchEvent(change); + + assert.equal(component.selected, options[1].value); + } +}; diff --git a/packages/svelte/test/runtime/samples/binding-select-from-let/main.svelte b/packages/svelte/test/runtime/samples/binding-select-from-let/main.svelte new file mode 100644 index 0000000000..494653bb4c --- /dev/null +++ b/packages/svelte/test/runtime/samples/binding-select-from-let/main.svelte @@ -0,0 +1,12 @@ + + + + + From 5d66f96d011fc9c933f6bbf627d5a2f61e765db9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 19 Jul 2023 15:55:43 +0200 Subject: [PATCH 05/10] Version Packages (#8928) Co-authored-by: github-actions[bot] --- .changeset/clever-ghosts-laugh.md | 5 ----- .changeset/cyan-geese-film.md | 5 ----- .changeset/green-cats-matter.md | 5 ----- .changeset/lucky-knives-crash.md | 5 ----- .changeset/shaggy-pans-repair.md | 5 ----- .changeset/ten-gifts-design.md | 5 ----- packages/svelte/CHANGELOG.md | 18 ++++++++++++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/shared/version.js | 2 +- 9 files changed, 20 insertions(+), 32 deletions(-) delete mode 100644 .changeset/clever-ghosts-laugh.md delete mode 100644 .changeset/cyan-geese-film.md delete mode 100644 .changeset/green-cats-matter.md delete mode 100644 .changeset/lucky-knives-crash.md delete mode 100644 .changeset/shaggy-pans-repair.md delete mode 100644 .changeset/ten-gifts-design.md diff --git a/.changeset/clever-ghosts-laugh.md b/.changeset/clever-ghosts-laugh.md deleted file mode 100644 index 3de871ba79..0000000000 --- a/.changeset/clever-ghosts-laugh.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: ensure `svelte:component` evaluates props once diff --git a/.changeset/cyan-geese-film.md b/.changeset/cyan-geese-film.md deleted file mode 100644 index 19b82cb165..0000000000 --- a/.changeset/cyan-geese-film.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: remove `let:variable` slot bindings from select binding dependencies diff --git a/.changeset/green-cats-matter.md b/.changeset/green-cats-matter.md deleted file mode 100644 index 2597640ff8..0000000000 --- a/.changeset/green-cats-matter.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': minor ---- - -feat: add ability to extend custom element class diff --git a/.changeset/lucky-knives-crash.md b/.changeset/lucky-knives-crash.md deleted file mode 100644 index b3506ecc4f..0000000000 --- a/.changeset/lucky-knives-crash.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: handle destructured primitive literals diff --git a/.changeset/shaggy-pans-repair.md b/.changeset/shaggy-pans-repair.md deleted file mode 100644 index 7d44a6b861..0000000000 --- a/.changeset/shaggy-pans-repair.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -perf: optimize imports that are not mutated or reassigned diff --git a/.changeset/ten-gifts-design.md b/.changeset/ten-gifts-design.md deleted file mode 100644 index a4e154fc9c..0000000000 --- a/.changeset/ten-gifts-design.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: don't add accessor twice diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 2b76f02650..91d1575730 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,23 @@ # svelte +## 4.1.0 + +### Minor Changes + +- feat: add ability to extend custom element class ([#8991](https://github.com/sveltejs/svelte/pull/8991)) + +### Patch Changes + +- fix: ensure `svelte:component` evaluates props once ([#8946](https://github.com/sveltejs/svelte/pull/8946)) + +- fix: remove `let:variable` slot bindings from select binding dependencies ([#8969](https://github.com/sveltejs/svelte/pull/8969)) + +- fix: handle destructured primitive literals ([#8871](https://github.com/sveltejs/svelte/pull/8871)) + +- perf: optimize imports that are not mutated or reassigned ([#8948](https://github.com/sveltejs/svelte/pull/8948)) + +- fix: don't add accessor twice ([#8996](https://github.com/sveltejs/svelte/pull/8996)) + ## 4.0.5 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index c869a3caa8..1aaf9d1022 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "4.0.5", + "version": "4.1.0", "description": "Cybernetically enhanced web apps", "type": "module", "module": "src/runtime/index.js", diff --git a/packages/svelte/src/shared/version.js b/packages/svelte/src/shared/version.js index a9675ed742..c4f74f9d18 100644 --- a/packages/svelte/src/shared/version.js +++ b/packages/svelte/src/shared/version.js @@ -6,5 +6,5 @@ * https://svelte.dev/docs/svelte-compiler#svelte-version * @type {string} */ -export const VERSION = '4.0.5'; +export const VERSION = '4.1.0'; export const PUBLIC_VERSION = '4'; From 3dccf711f8d4a7a65e602494a414e38ad87d92b8 Mon Sep 17 00:00:00 2001 From: Jo Burgard Date: Wed, 19 Jul 2023 22:37:38 +0200 Subject: [PATCH 06/10] Fix error in example in 03-typescript.md (#9001) --- documentation/docs/05-misc/03-typescript.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/docs/05-misc/03-typescript.md b/documentation/docs/05-misc/03-typescript.md index 5d164324a3..d5772fa157 100644 --- a/documentation/docs/05-misc/03-typescript.md +++ b/documentation/docs/05-misc/03-typescript.md @@ -97,8 +97,8 @@ Events can be typed with `createEventDispatcher`: const dispatch = createEventDispatcher<{ event: null; // does not accept a payload - type: string; // has a required string payload - click: string | null; // has an optional string payload + click: string; // has a required string payload + type: string | null; // has an optional string payload }>(); function handleClick() { From a2b6401c43c44454bb30bc785a5b358fe2bdb6e0 Mon Sep 17 00:00:00 2001 From: hackape Date: Thu, 20 Jul 2023 21:55:22 +0800 Subject: [PATCH 07/10] fix: `svelte:component` spread props change not picked up (#9006) fix #9003, amend #8946 (comment) --- .changeset/heavy-wasps-give.md | 5 ++++ .../wrappers/InlineComponent/index.js | 19 ++++++++++++-- .../Comp1.svelte | 5 ++++ .../Comp2.svelte | 5 ++++ .../dynamic-component-spread-props/_config.js | 26 +++++++++++++++++++ .../main.svelte | 12 +++++++++ 6 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 .changeset/heavy-wasps-give.md create mode 100644 packages/svelte/test/runtime/samples/dynamic-component-spread-props/Comp1.svelte create mode 100644 packages/svelte/test/runtime/samples/dynamic-component-spread-props/Comp2.svelte create mode 100644 packages/svelte/test/runtime/samples/dynamic-component-spread-props/_config.js create mode 100644 packages/svelte/test/runtime/samples/dynamic-component-spread-props/main.svelte diff --git a/.changeset/heavy-wasps-give.md b/.changeset/heavy-wasps-give.md new file mode 100644 index 0000000000..495583e86f --- /dev/null +++ b/.changeset/heavy-wasps-give.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: `svelte:component` spread props change not picked up diff --git a/packages/svelte/src/compiler/compile/render_dom/wrappers/InlineComponent/index.js b/packages/svelte/src/compiler/compile/render_dom/wrappers/InlineComponent/index.js index ef7e5432d4..77fb1ac96b 100644 --- a/packages/svelte/src/compiler/compile/render_dom/wrappers/InlineComponent/index.js +++ b/packages/svelte/src/compiler/compile/render_dom/wrappers/InlineComponent/index.js @@ -268,6 +268,21 @@ export default class InlineComponentWrapper extends Wrapper { `); if (all_dependencies.size) { const condition = renderer.dirty(Array.from(all_dependencies)); + if (this.node.name === 'svelte:component') { + // statements will become switch_props function body + // rewrite last statement, add props update logic + statements[statements.length - 1] = b` + if (#dirty !== undefined && ${condition}) { + ${props} = @get_spread_update(${levels}, [ + ${changes} + ]); + } else { + for (let #i = 0; #i < ${levels}.length; #i += 1) { + ${props} = @assign(${props}, ${levels}[#i]); + } + } + `; + } updates.push(b` const ${name_changes} = ${condition} ? @get_spread_update(${levels}, [ ${changes} @@ -396,7 +411,7 @@ export default class InlineComponentWrapper extends Wrapper { block.chunks.init.push(b` var ${switch_value} = ${snippet}; - function ${switch_props}(#ctx) { + function ${switch_props}(#ctx, #dirty) { ${ (this.node.attributes.length > 0 || this.node.bindings.length > 0) && b` @@ -464,7 +479,7 @@ export default class InlineComponentWrapper extends Wrapper { if (${switch_value}) { ${update_insert} - ${name} = @construct_svelte_component(${switch_value}, ${switch_props}(#ctx)); + ${name} = @construct_svelte_component(${switch_value}, ${switch_props}(#ctx, #dirty)); ${munged_bindings} ${munged_handlers} diff --git a/packages/svelte/test/runtime/samples/dynamic-component-spread-props/Comp1.svelte b/packages/svelte/test/runtime/samples/dynamic-component-spread-props/Comp1.svelte new file mode 100644 index 0000000000..d4fe28a8a3 --- /dev/null +++ b/packages/svelte/test/runtime/samples/dynamic-component-spread-props/Comp1.svelte @@ -0,0 +1,5 @@ + + +

value(1) = {value}

diff --git a/packages/svelte/test/runtime/samples/dynamic-component-spread-props/Comp2.svelte b/packages/svelte/test/runtime/samples/dynamic-component-spread-props/Comp2.svelte new file mode 100644 index 0000000000..07d41f3d84 --- /dev/null +++ b/packages/svelte/test/runtime/samples/dynamic-component-spread-props/Comp2.svelte @@ -0,0 +1,5 @@ + + +

value(2) = {value}

diff --git a/packages/svelte/test/runtime/samples/dynamic-component-spread-props/_config.js b/packages/svelte/test/runtime/samples/dynamic-component-spread-props/_config.js new file mode 100644 index 0000000000..d5ba1ef64b --- /dev/null +++ b/packages/svelte/test/runtime/samples/dynamic-component-spread-props/_config.js @@ -0,0 +1,26 @@ +export default { + html: ` +

value(1) = 1

+ + `, + + async test({ assert, window, target }) { + const button = target.querySelector('button'); + await button.dispatchEvent(new window.Event('click')); + assert.htmlEqual( + target.innerHTML, + ` +

value(2) = 2

+ + ` + ); + await button.dispatchEvent(new window.Event('click')); + assert.htmlEqual( + target.innerHTML, + ` +

value(1) = 1

+ + ` + ); + } +}; diff --git a/packages/svelte/test/runtime/samples/dynamic-component-spread-props/main.svelte b/packages/svelte/test/runtime/samples/dynamic-component-spread-props/main.svelte new file mode 100644 index 0000000000..b8c45c83a0 --- /dev/null +++ b/packages/svelte/test/runtime/samples/dynamic-component-spread-props/main.svelte @@ -0,0 +1,12 @@ + + + + + From 6827709373faf01245d0284968b365b7e032058c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 20 Jul 2023 18:29:21 +0200 Subject: [PATCH 08/10] Version Packages (#9008) Co-authored-by: github-actions[bot] --- .changeset/heavy-wasps-give.md | 5 ----- packages/svelte/CHANGELOG.md | 6 ++++++ packages/svelte/package.json | 2 +- packages/svelte/src/shared/version.js | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) delete mode 100644 .changeset/heavy-wasps-give.md diff --git a/.changeset/heavy-wasps-give.md b/.changeset/heavy-wasps-give.md deleted file mode 100644 index 495583e86f..0000000000 --- a/.changeset/heavy-wasps-give.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: `svelte:component` spread props change not picked up diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 91d1575730..4c5b4382f7 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,11 @@ # svelte +## 4.1.1 + +### Patch Changes + +- fix: `svelte:component` spread props change not picked up ([#9006](https://github.com/sveltejs/svelte/pull/9006)) + ## 4.1.0 ### Minor Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 1aaf9d1022..c3621ab6dc 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "4.1.0", + "version": "4.1.1", "description": "Cybernetically enhanced web apps", "type": "module", "module": "src/runtime/index.js", diff --git a/packages/svelte/src/shared/version.js b/packages/svelte/src/shared/version.js index c4f74f9d18..e4e0dea0aa 100644 --- a/packages/svelte/src/shared/version.js +++ b/packages/svelte/src/shared/version.js @@ -6,5 +6,5 @@ * https://svelte.dev/docs/svelte-compiler#svelte-version * @type {string} */ -export const VERSION = '4.1.0'; +export const VERSION = '4.1.1'; export const PUBLIC_VERSION = '4'; From 60cd0ec950c808673d0838c5f9403d5c31ca48b3 Mon Sep 17 00:00:00 2001 From: Lennard Langenbruch <107924746+lennard-langenbruch@users.noreply.github.com> Date: Fri, 21 Jul 2023 07:03:46 +0200 Subject: [PATCH 09/10] fix: update link for doucmentation & sites in README (#9014) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a5e17f71e4..4dbfee3823 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ pnpm test -- -g transition ### svelte.dev -The source code for https://svelte.dev lives in the [sites](https://github.com/sveltejs/sites) repository, with all the documentation in the [site/content](site/content) directory. The site is built with [SvelteKit](https://kit.svelte.dev). +The source code for https://svelte.dev lives in the [sites](https://github.com/sveltejs/svelte/tree/master/sites/svelte.dev) folder, with all the documentation right [here](https://github.com/sveltejs/svelte/tree/master/documentation). The site is built with [SvelteKit](https://kit.svelte.dev). ## Is svelte.dev down? From 7af165de0902e533f581af28be024e7d1805ca80 Mon Sep 17 00:00:00 2001 From: Posandu Mapa Date: Fri, 21 Jul 2023 23:54:25 +0530 Subject: [PATCH 10/10] docs: make transition global in deferred transition example (#9020) --- .../examples/09-transitions/06-deferred-transitions/App.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/examples/09-transitions/06-deferred-transitions/App.svelte b/documentation/examples/09-transitions/06-deferred-transitions/App.svelte index 74db062fc8..8f603165c1 100644 --- a/documentation/examples/09-transitions/06-deferred-transitions/App.svelte +++ b/documentation/examples/09-transitions/06-deferred-transitions/App.svelte @@ -48,7 +48,7 @@ {#if selected} {#await selected then d} -
+
{d.alt} (selected = null)} />