hoist-unmodified-var
Ben McCann 2 years ago
parent 2169608d89
commit eec1110b80

@ -535,11 +535,12 @@ export function should_proxy_or_freeze(node) {
} }
/** /**
* Whether a variable can be referenced directly from template string.
* @param {import('#compiler').Binding | undefined} binding * @param {import('#compiler').Binding | undefined} binding
* @param {string} name * @param {string} name
* @returns {boolean} * @returns {boolean}
*/ */
export function is_hoistable_declaration(binding, name) { export function can_inline_variable(binding, name) {
// TODO: allow object expressions that are not passed to functions or components as props // TODO: allow object expressions that are not passed to functions or components as props
// and expressions as long as they do not reference non-hoistable variables // and expressions as long as they do not reference non-hoistable variables
return ( return (
@ -549,8 +550,18 @@ export function is_hoistable_declaration(binding, name) {
!binding.mutated && !binding.mutated &&
!binding.reassigned && !binding.reassigned &&
binding.initial?.type === 'Literal' && binding.initial?.type === 'Literal' &&
binding.scope.has_parent() && // i.e. not when context="module"
!binding.scope.declared_in_outer_scope(name) && !binding.scope.declared_in_outer_scope(name) &&
!GlobalBindings.has(name) !GlobalBindings.has(name)
); );
} }
/**
* @param {import('#compiler').Binding | undefined} binding
* @param {string} name
* @returns {boolean}
*/
export function can_hoist_declaration(binding, name) {
return (
can_inline_variable(binding, name) && !!binding && binding.scope.has_parent() // i.e. not when context="module"
);
}

@ -2,9 +2,9 @@ import { is_hoistable_function } from '../../utils.js';
import * as b from '../../../../utils/builders.js'; import * as b from '../../../../utils/builders.js';
import { extract_paths } from '../../../../utils/ast.js'; import { extract_paths } from '../../../../utils/ast.js';
import { import {
can_hoist_declaration,
create_state_declarators, create_state_declarators,
get_prop_source, get_prop_source,
is_hoistable_declaration,
serialize_get_binding serialize_get_binding
} from '../utils.js'; } from '../utils.js';
@ -29,7 +29,7 @@ export const javascript_visitors_legacy = {
.owner(declarator.id.name) .owner(declarator.id.name)
?.declarations.get(declarator.id.name); ?.declarations.get(declarator.id.name);
if (is_hoistable_declaration(binding, declarator.id.name)) { if (can_hoist_declaration(binding, declarator.id.name)) {
state.hoisted.push(b.declaration('const', declarator.id, init)); state.hoisted.push(b.declaration('const', declarator.id, init));
continue; continue;
} }

@ -3,9 +3,9 @@ import { is_hoistable_function, transform_inspect_rune } from '../../utils.js';
import * as b from '../../../../utils/builders.js'; import * as b from '../../../../utils/builders.js';
import * as assert from '../../../../utils/assert.js'; import * as assert from '../../../../utils/assert.js';
import { import {
can_hoist_declaration,
create_state_declarators, create_state_declarators,
get_prop_source, get_prop_source,
is_hoistable_declaration,
should_proxy_or_freeze should_proxy_or_freeze
} from '../utils.js'; } from '../utils.js';
import { unwrap_ts_expression } from '../../../../utils/ast.js'; import { unwrap_ts_expression } from '../../../../utils/ast.js';
@ -164,7 +164,7 @@ export const javascript_visitors_runes = {
if (init != null && declarator.id.type === 'Identifier') { if (init != null && declarator.id.type === 'Identifier') {
const binding = state.scope.owner(declarator.id.name)?.declarations.get(declarator.id.name); const binding = state.scope.owner(declarator.id.name)?.declarations.get(declarator.id.name);
if (is_hoistable_declaration(binding, declarator.id.name)) { if (can_hoist_declaration(binding, declarator.id.name)) {
state.hoisted.push(b.declaration('const', declarator.id, init)); state.hoisted.push(b.declaration('const', declarator.id, init));
continue; continue;
} }

@ -17,9 +17,9 @@ import { is_custom_element_node, is_element_node } from '../../../nodes.js';
import * as b from '../../../../utils/builders.js'; import * as b from '../../../../utils/builders.js';
import { error } from '../../../../errors.js'; import { error } from '../../../../errors.js';
import { import {
can_hoist_declaration,
function_visitor, function_visitor,
get_assignment_value, get_assignment_value,
is_hoistable_declaration,
serialize_get_binding, serialize_get_binding,
serialize_set_binding serialize_set_binding
} from '../utils.js'; } from '../utils.js';
@ -564,19 +564,21 @@ function serialize_element_attribute_update_assignment(element, node_id, attribu
} }
}; };
let hoistable = false; let can_inline = false;
if (Array.isArray(attribute.value)) { if (Array.isArray(attribute.value)) {
for (let value of attribute.value) { for (let value of attribute.value) {
if (value.type === 'ExpressionTag' && value.expression.type === 'Identifier') { if (value.type === 'ExpressionTag' && value.expression.type === 'Identifier') {
const binding = context.state.scope const binding = context.state.scope
.owner(value.expression.name) .owner(value.expression.name)
?.declarations.get(value.expression.name); ?.declarations.get(value.expression.name);
hoistable ||= is_hoistable_declaration(binding, value.expression.name); // TODO: use can_inline_variable instead
// and insert template strings after module variables
can_inline ||= can_hoist_declaration(binding, value.expression.name);
} }
} }
} }
if (attribute.metadata.dynamic && !hoistable) { if (attribute.metadata.dynamic && !can_inline) {
const id = state.scope.generate(`${node_id.name}_${name}`); const id = state.scope.generate(`${node_id.name}_${name}`);
serialize_update_assignment( serialize_update_assignment(
state, state,
@ -588,7 +590,7 @@ function serialize_element_attribute_update_assignment(element, node_id, attribu
); );
return true; return true;
} else { } else {
if (hoistable) { if (can_inline) {
push_template_quasi(context.state, ` ${name}="`); push_template_quasi(context.state, ` ${name}="`);
push_template_expression(context.state, grouped_value); push_template_expression(context.state, grouped_value);
push_template_quasi(context.state, `"`); push_template_quasi(context.state, `"`);

Loading…
Cancel
Save