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; return binding.expression;
} }
if (binding.kind === 'prop' && binding.node.name === '$$props') { if (binding.kind === 'prop') {
// Special case for $$props which only exists in the old world if (binding.node.name === '$$props') {
return node; // Special case for $$props which only exists in the old world
} // TODO this probably shouldn't have a 'prop' binding kind
return node;
}
if ( if (
binding.kind === 'prop' && !state.analysis.accessors &&
!(state.analysis.immutable ? binding.reassigned : binding.mutated) && !(state.analysis.immutable ? binding.reassigned : binding.mutated) &&
!binding.initial && !binding.initial
!state.analysis.accessors ) {
) { return b.member(b.id('$$props'), node);
return b.call(node); }
} }
if (binding.kind === 'legacy_reactive_import') { 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 {import('./types').ComponentClientTransformState} state
* @param {string} name * @param {string} name
* @param {import('estree').Expression | null} [default_value] * @param {import('estree').Expression | null} [initial]
* @returns * @returns
*/ */
export function get_props_method(binding, state, name, default_value) { export function get_prop_source(state, name, initial) {
/** @type {import('estree').Expression[]} */ /** @type {import('estree').Expression[]} */
const args = [b.id('$$props'), b.literal(name)]; const args = [b.id('$$props'), b.literal(name)];
// Use $.prop_source in the following cases: let flags = 0;
// - 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;
/** @type {import('estree').Expression | undefined} */ if (state.analysis.immutable) {
let arg; flags |= PROPS_IS_IMMUTABLE;
}
if (state.analysis.immutable) { if (state.analysis.runes) {
flags |= PROPS_IS_IMMUTABLE; flags |= PROPS_IS_RUNES;
} }
if (state.analysis.runes) { /** @type {import('estree').Expression | undefined} */
flags |= PROPS_IS_RUNES; let arg;
}
if (default_value) { if (initial) {
// To avoid eagerly evaluating the right-hand-side, we wrap it in a thunk if necessary // To avoid eagerly evaluating the right-hand-side, we wrap it in a thunk if necessary
if (is_simple_expression(default_value)) { if (is_simple_expression(initial)) {
arg = default_value; arg = initial;
} else {
if (
initial.type === 'CallExpression' &&
initial.callee.type === 'Identifier' &&
initial.arguments.length === 0
) {
arg = initial.callee;
} else { } else {
if ( arg = b.thunk(initial);
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;
} }
}
if (flags || arg) { flags |= PROPS_CALL_DEFAULT_VALUE;
args.push(b.literal(flags));
if (arg) args.push(arg);
} }
}
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 { 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 { 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} */ /** @type {import('../types.js').ComponentVisitors} */
export const javascript_visitors_legacy = { export const javascript_visitors_legacy = {
@ -54,8 +54,8 @@ export const javascript_visitors_legacy = {
declarations.push( declarations.push(
b.declarator( b.declarator(
path.node, path.node,
binding.kind === 'prop' || binding.kind === 'rest_prop' binding.kind === 'prop'
? get_props_method(binding, state, binding.prop_alias ?? name, value) ? get_prop_source(state, binding.prop_alias ?? name, value)
: value : value
) )
); );
@ -67,17 +67,23 @@ export const javascript_visitors_legacy = {
state.scope.get(declarator.id.name) state.scope.get(declarator.id.name)
); );
declarations.push( if (
b.declarator( state.analysis.accessors ||
declarator.id, (state.analysis.immutable ? binding.reassigned : binding.mutated) ||
get_props_method( declarator.init
binding, ) {
state, declarations.push(
binding.prop_alias ?? declarator.id.name, b.declarator(
declarator.init && /** @type {import('estree').Expression} */ (visit(declarator.init)) declarator.id,
get_prop_source(
state,
binding.prop_alias ?? declarator.id.name,
declarator.init &&
/** @type {import('estree').Expression} */ (visit(declarator.init))
)
) )
) );
); }
continue; continue;
} }

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

@ -1490,17 +1490,6 @@ export function prop_source(props, key, flags, default_value) {
return /** @type {import('./types.js').Signal<V>} */ (source_signal); 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 {boolean} immutable
* @param {unknown} a * @param {unknown} a

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

Loading…
Cancel
Save