From dc784919fff18da82ad1066d74c6828623c21674 Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Sun, 9 Jul 2023 17:43:17 -0700 Subject: [PATCH 1/4] perf: don't count primitive const values as dependencies --- .../svelte/src/compiler/compile/Component.js | 42 +++++++++++-------- .../src/compiler/compile/internal_exports.js | 2 +- .../compile/nodes/shared/Expression.js | 7 +++- packages/svelte/src/compiler/interfaces.d.ts | 1 + 4 files changed, 31 insertions(+), 21 deletions(-) diff --git a/packages/svelte/src/compiler/compile/Component.js b/packages/svelte/src/compiler/compile/Component.js index 03d2c2fad323..1ebd20f374f0 100644 --- a/packages/svelte/src/compiler/compile/Component.js +++ b/packages/svelte/src/compiler/compile/Component.js @@ -807,26 +807,32 @@ export default class Component { const { scope: instance_scope, map, globals } = create_scopes(script.content); this.instance_scope = instance_scope; this.instance_scope_map = map; - instance_scope.declarations.forEach( - /** - * @param {any} node - * @param {any} name - */ (node, name) => { - if (name[0] === '$') { - return this.error(/** @type {any} */ (node), compiler_errors.illegal_declaration); + instance_scope.declarations.forEach((node, name) => { + if (name[0] === '$') { + return this.error(/** @type {any} */ (node), compiler_errors.illegal_declaration); + } + const writable = + node.type === 'VariableDeclaration' && (node.kind === 'var' || node.kind === 'let'); + + let immutable = false; + if (node.type === 'VariableDeclaration' && node.kind === 'const') { + immutable = true; + for (const declaration of node.declarations) { + if (declaration.init.type !== 'Literal' || typeof declaration.init.value === 'object') { + immutable = false; + } } - const writable = - node.type === 'VariableDeclaration' && (node.kind === 'var' || node.kind === 'let'); - const imported = node.type.startsWith('Import'); - this.add_var(node, { - name, - initialised: instance_scope.initialised_declarations.has(name), - writable, - imported - }); - this.node_for_declaration.set(name, node); } - ); + + this.add_var(node, { + name, + initialised: instance_scope.initialised_declarations.has(name), + imported: node.type.startsWith('Import'), + writable, + immutable + }); + this.node_for_declaration.set(name, node); + }); // NOTE: add store variable first, then only $store value // as `$store` will mark `store` variable as referenced and subscribable const global_keys = Array.from(globals.keys()); diff --git a/packages/svelte/src/compiler/compile/internal_exports.js b/packages/svelte/src/compiler/compile/internal_exports.js index d2b735621429..7656f52f3a19 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 c4877f7c84ee..e3e1aaf06a06 100644 --- a/packages/svelte/src/compiler/compile/nodes/shared/Expression.js +++ b/packages/svelte/src/compiler/compile/nodes/shared/Expression.js @@ -48,7 +48,8 @@ export default class Expression { /** @type {Array} */ declarations = []; - /** */ + + /** @type {boolean} */ uses_context = false; /** @type {import('estree').Node} */ @@ -122,7 +123,7 @@ export default class Expression { .forEach(/** @param {any} name */ (name) => dependencies.add(name)); } } else { - if (!lazy) { + if (!lazy && !component.var_lookup.get(name)?.immutable) { dependencies.add(name); } component.add_reference(node, name); @@ -231,6 +232,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} */ diff --git a/packages/svelte/src/compiler/interfaces.d.ts b/packages/svelte/src/compiler/interfaces.d.ts index 64be9698f5b3..b38f2a597af0 100644 --- a/packages/svelte/src/compiler/interfaces.d.ts +++ b/packages/svelte/src/compiler/interfaces.d.ts @@ -383,6 +383,7 @@ export interface Var { writable?: boolean; // used internally, but not exposed + immutable?: boolean; global?: boolean; internal?: boolean; // event handlers, bindings initialised?: boolean; From a0393b27a7a900374002c9a4dfa282bf5d2fc38b Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Mon, 10 Jul 2023 09:26:08 -0700 Subject: [PATCH 2/4] don't need to check if value is object as it won't be literal --- packages/svelte/src/compiler/compile/Component.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/compiler/compile/Component.js b/packages/svelte/src/compiler/compile/Component.js index f889be70815a..37a03c78d2a0 100644 --- a/packages/svelte/src/compiler/compile/Component.js +++ b/packages/svelte/src/compiler/compile/Component.js @@ -777,7 +777,7 @@ export default class Component { if (node.type === 'VariableDeclaration' && node.kind === 'const') { immutable = true; for (const declaration of node.declarations) { - if (declaration.init.type !== 'Literal' || typeof declaration.init.value === 'object') { + if (declaration.init.type !== 'Literal') { immutable = false; } } From 73586aa9ad1ec1b866716569f592a51952a686d3 Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Mon, 10 Jul 2023 10:36:13 -0700 Subject: [PATCH 3/4] look at whether the variable was mutated or reassigned instead --- .../svelte/src/compiler/compile/Component.js | 19 +++---------------- .../compile/nodes/shared/Expression.js | 7 +++++-- packages/svelte/src/compiler/interfaces.d.ts | 1 - 3 files changed, 8 insertions(+), 19 deletions(-) diff --git a/packages/svelte/src/compiler/compile/Component.js b/packages/svelte/src/compiler/compile/Component.js index 37a03c78d2a0..17a1adc2cd28 100644 --- a/packages/svelte/src/compiler/compile/Component.js +++ b/packages/svelte/src/compiler/compile/Component.js @@ -770,25 +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'); - - let immutable = false; - if (node.type === 'VariableDeclaration' && node.kind === 'const') { - immutable = true; - for (const declaration of node.declarations) { - if (declaration.init.type !== 'Literal') { - immutable = false; - } - } - } - + const { type } = node; this.add_var(node, { name, initialised: instance_scope.initialised_declarations.has(name), - imported: node.type.startsWith('Import'), - writable, - immutable + 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/nodes/shared/Expression.js b/packages/svelte/src/compiler/compile/nodes/shared/Expression.js index 7836809b25f9..69310f77b418 100644 --- a/packages/svelte/src/compiler/compile/nodes/shared/Expression.js +++ b/packages/svelte/src/compiler/compile/nodes/shared/Expression.js @@ -129,8 +129,11 @@ export default class Expression { .forEach((name) => dependencies.add(name)); } } else { - if (!lazy && !component.var_lookup.get(name)?.immutable) { - dependencies.add(name); + if (!lazy) { + 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); diff --git a/packages/svelte/src/compiler/interfaces.d.ts b/packages/svelte/src/compiler/interfaces.d.ts index b38f2a597af0..64be9698f5b3 100644 --- a/packages/svelte/src/compiler/interfaces.d.ts +++ b/packages/svelte/src/compiler/interfaces.d.ts @@ -383,7 +383,6 @@ export interface Var { writable?: boolean; // used internally, but not exposed - immutable?: boolean; global?: boolean; internal?: boolean; // event handlers, bindings initialised?: boolean; From 2d183079ae8184e70bcb545b9f0595ae91900ff8 Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Tue, 18 Jul 2023 06:59:59 -0700 Subject: [PATCH 4/4] changeset --- .changeset/shaggy-pans-repair.md | 5 +++++ 1 file changed, 5 insertions(+) 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 000000000000..7d44a6b86125 --- /dev/null +++ b/.changeset/shaggy-pans-repair.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +perf: optimize imports that are not mutated or reassigned