chore: use `$$props` directly where possible (#9813)

* use $$props directly in runes mode

* this makes no sense

* use $$props directly in runes mode

* tidy up

* typo

* remove unreachable code

---------

Co-authored-by: Rich Harris <rich.harris@vercel.com>
pull/9821/head
Rich Harris 1 year ago committed by GitHub
parent 074615d7fd
commit 1e4af19404
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -65,18 +65,20 @@ export function serialize_get_binding(node, state) {
return binding.expression;
}
if (binding.kind === 'prop' && binding.node.name === '$$props') {
// Special case for $$props which only exists in the old world
return node;
}
if (binding.kind === 'prop') {
if (binding.node.name === '$$props') {
// Special case for $$props which only exists in the old world
// TODO this probably shouldn't have a 'prop' binding kind
return node;
}
if (
binding.kind === 'prop' &&
!(state.analysis.immutable ? binding.reassigned : binding.mutated) &&
!binding.initial &&
!state.analysis.accessors
) {
return b.call(node);
if (
!state.analysis.accessors &&
!(state.analysis.immutable ? binding.reassigned : binding.mutated) &&
!binding.initial
) {
return b.member(b.id('$$props'), node);
}
}
if (binding.kind === 'legacy_reactive_import') {
@ -343,67 +345,53 @@ export function serialize_hoistable_params(node, context) {
}
/**
*
* @param {import('#compiler').Binding} binding
* @param {import('./types').ComponentClientTransformState} state
* @param {string} name
* @param {import('estree').Expression | null} [default_value]
* @param {import('estree').Expression | null} [initial]
* @returns
*/
export function get_props_method(binding, state, name, default_value) {
export function get_prop_source(state, name, initial) {
/** @type {import('estree').Expression[]} */
const args = [b.id('$$props'), b.literal(name)];
// Use $.prop_source in the following cases:
// - accessors/mutated: needs to be able to set the prop value from within
// - default value: we set the fallback value only initially, and it's not possible to know this timing in $.prop
const needs_source =
default_value ||
state.analysis.accessors ||
(state.analysis.immutable ? binding.reassigned : binding.mutated);
if (needs_source) {
let flags = 0;
let flags = 0;
/** @type {import('estree').Expression | undefined} */
let arg;
if (state.analysis.immutable) {
flags |= PROPS_IS_IMMUTABLE;
}
if (state.analysis.immutable) {
flags |= PROPS_IS_IMMUTABLE;
}
if (state.analysis.runes) {
flags |= PROPS_IS_RUNES;
}
if (state.analysis.runes) {
flags |= PROPS_IS_RUNES;
}
/** @type {import('estree').Expression | undefined} */
let arg;
if (default_value) {
// To avoid eagerly evaluating the right-hand-side, we wrap it in a thunk if necessary
if (is_simple_expression(default_value)) {
arg = default_value;
if (initial) {
// To avoid eagerly evaluating the right-hand-side, we wrap it in a thunk if necessary
if (is_simple_expression(initial)) {
arg = initial;
} else {
if (
initial.type === 'CallExpression' &&
initial.callee.type === 'Identifier' &&
initial.arguments.length === 0
) {
arg = initial.callee;
} else {
if (
default_value.type === 'CallExpression' &&
default_value.callee.type === 'Identifier' &&
default_value.arguments.length === 0
) {
arg = default_value.callee;
} else {
arg = b.thunk(default_value);
}
flags |= PROPS_CALL_DEFAULT_VALUE;
arg = b.thunk(initial);
}
}
if (flags || arg) {
args.push(b.literal(flags));
if (arg) args.push(arg);
flags |= PROPS_CALL_DEFAULT_VALUE;
}
}
return b.call('$.prop_source', ...args);
if (flags || arg) {
args.push(b.literal(flags));
if (arg) args.push(arg);
}
return b.call('$.prop', ...args);
return b.call('$.prop_source', ...args);
}
/**

@ -1,7 +1,7 @@
import { is_hoistable_function } from '../../utils.js';
import * as b from '../../../../utils/builders.js';
import { extract_paths } from '../../../../utils/ast.js';
import { create_state_declarators, get_props_method, serialize_get_binding } from '../utils.js';
import { create_state_declarators, get_prop_source, serialize_get_binding } from '../utils.js';
/** @type {import('../types.js').ComponentVisitors} */
export const javascript_visitors_legacy = {
@ -54,8 +54,8 @@ export const javascript_visitors_legacy = {
declarations.push(
b.declarator(
path.node,
binding.kind === 'prop' || binding.kind === 'rest_prop'
? get_props_method(binding, state, binding.prop_alias ?? name, value)
binding.kind === 'prop'
? get_prop_source(state, binding.prop_alias ?? name, value)
: value
)
);
@ -67,17 +67,23 @@ export const javascript_visitors_legacy = {
state.scope.get(declarator.id.name)
);
declarations.push(
b.declarator(
declarator.id,
get_props_method(
binding,
state,
binding.prop_alias ?? declarator.id.name,
declarator.init && /** @type {import('estree').Expression} */ (visit(declarator.init))
if (
state.analysis.accessors ||
(state.analysis.immutable ? binding.reassigned : binding.mutated) ||
declarator.init
) {
declarations.push(
b.declarator(
declarator.id,
get_prop_source(
state,
binding.prop_alias ?? declarator.id.name,
declarator.init &&
/** @type {import('estree').Expression} */ (visit(declarator.init))
)
)
)
);
);
}
continue;
}

@ -2,7 +2,7 @@ import { get_rune } from '../../../scope.js';
import { is_hoistable_function } from '../../utils.js';
import * as b from '../../../../utils/builders.js';
import * as assert from '../../../../utils/assert.js';
import { create_state_declarators, get_props_method, should_proxy } from '../utils.js';
import { create_state_declarators, get_prop_source, should_proxy } from '../utils.js';
import { unwrap_ts_expression } from '../../../../utils/ast.js';
/** @type {import('../types.js').ComponentVisitors} */
@ -165,36 +165,27 @@ export const javascript_visitors_runes = {
for (const property of declarator.id.properties) {
if (property.type === 'Property') {
assert.ok(property.key.type === 'Identifier' || property.key.type === 'Literal');
let name;
if (property.key.type === 'Identifier') {
name = property.key.name;
} else if (property.key.type === 'Literal') {
name = /** @type {string} */ (property.key.value).toString();
} else {
throw new Error('unreachable');
}
const key = /** @type {import('estree').Identifier | import('estree').Literal} */ (
property.key
);
const name = key.type === 'Identifier' ? key.name : /** @type {string} */ (key.value);
seen.push(name);
if (property.value.type === 'Identifier') {
const binding = /** @type {import('#compiler').Binding} */ (
state.scope.get(property.value.name)
);
declarations.push(
b.declarator(property.value, get_props_method(binding, state, name))
);
} else if (property.value.type === 'AssignmentPattern') {
assert.equal(property.value.left.type, 'Identifier');
const binding = /** @type {import('#compiler').Binding} */ (
state.scope.get(property.value.left.name)
);
declarations.push(
b.declarator(
property.value.left,
get_props_method(binding, state, name, property.value.right)
)
);
let id = property.value;
let initial = undefined;
if (property.value.type === 'AssignmentPattern') {
id = property.value.left;
initial = property.value.right;
}
assert.equal(id.type, 'Identifier');
const binding = /** @type {import('#compiler').Binding} */ (state.scope.get(id.name));
if (binding.reassigned || state.analysis.accessors || initial) {
declarations.push(b.declarator(id, get_prop_source(state, name, initial)));
}
} else {
// RestElement

@ -1490,17 +1490,6 @@ export function prop_source(props, key, flags, default_value) {
return /** @type {import('./types.js').Signal<V>} */ (source_signal);
}
/**
* If the prop is readonly and has no fallback value, we can use this function, else we need to use `prop_source`.
* @param {Record<string, unknown>} props
* @param {string} key
* @returns {any}
*/
export function prop(props, key) {
// TODO skip this, and rewrite as `$$props.foo`
return () => props[key];
}
/**
* @param {boolean} immutable
* @param {unknown} a

@ -7,7 +7,6 @@ export {
source,
mutable_source,
derived,
prop,
prop_source,
user_effect,
render_effect,

Loading…
Cancel
Save