pull/17038/head
Rich Harris 4 weeks ago
parent 3ad47c324d
commit c856ea2032

@ -687,203 +687,203 @@ export function analyze_component(root, source, options) {
} }
} }
if (analysis.runes) { /**
const props_refs = module.scope.references.get('$$props'); * @param {ESTree.Node} expression
if (props_refs) { * @param {Scope} scope
e.legacy_props_invalid(props_refs[0].node); * @param {Set<Binding>} touched
} * @param {Set<ESTree.Node>} seen
*/
const rest_props_refs = module.scope.references.get('$$restProps'); const touch = (expression, scope, touched, seen = new Set()) => {
if (rest_props_refs) { if (seen.has(expression)) return;
e.legacy_rest_props_invalid(rest_props_refs[0].node); seen.add(expression);
}
walk(
/** expression,
* @param {ESTree.Node} expression { scope },
* @param {Scope} scope {
* @param {Set<Binding>} touched ImportDeclaration(node) {},
* @param {Set<ESTree.Node>} seen Identifier(node, context) {
*/ const parent = /** @type {ESTree.Node} */ (context.path.at(-1));
const touch = (expression, scope, touched, seen = new Set()) => { if (is_reference(node, parent)) {
if (seen.has(expression)) return; const binding = context.state.scope.get(node.name);
seen.add(expression); if (binding) {
touched.add(binding);
walk(
expression, for (const assignment of binding.assignments) {
{ scope }, touch(assignment.value, assignment.scope, touched, seen);
{
ImportDeclaration(node) {},
Identifier(node, context) {
const parent = /** @type {ESTree.Node} */ (context.path.at(-1));
if (is_reference(node, parent)) {
const binding = context.state.scope.get(node.name);
if (binding) {
touched.add(binding);
for (const assignment of binding.assignments) {
touch(assignment.value, assignment.scope, touched, seen);
}
} }
} }
} }
} }
); }
}; );
};
/**
* @param {ESTree.Node} node
* @param {Set<ESTree.Node>} seen
* @param {Set<Binding>} reads
* @param {Set<Binding>} writes
*/
const trace_references = (node, reads, writes, seen = new Set()) => {
if (seen.has(node)) return;
seen.add(node);
/** /**
* @param {ESTree.Node} node * @param {ESTree.Pattern} node
* @param {Set<ESTree.Node>} seen * @param {Scope} scope
* @param {Set<Binding>} reads
* @param {Set<Binding>} writes
*/ */
const trace_references = (node, reads, writes, seen = new Set()) => { function update(node, scope) {
if (seen.has(node)) return; for (const pattern of unwrap_pattern(node)) {
seen.add(node); const node = object(pattern);
if (!node) return;
/**
* @param {ESTree.Pattern} node const binding = scope.get(node.name);
* @param {Scope} scope if (!binding) return;
*/
function update(node, scope) { writes.add(binding);
for (const pattern of unwrap_pattern(node)) {
const node = object(pattern);
if (!node) return;
const binding = scope.get(node.name);
if (!binding) return;
writes.add(binding);
}
} }
}
walk( walk(
node, node,
{ scope: instance.scope }, { scope: instance.scope },
{ {
_(node, context) { _(node, context) {
const scope = scopes.get(node); const scope = scopes.get(node);
if (scope) { if (scope) {
context.next({ scope }); context.next({ scope });
} else { } else {
context.next(); context.next();
} }
}, },
AssignmentExpression(node, context) { AssignmentExpression(node, context) {
update(node.left, context.state.scope); update(node.left, context.state.scope);
}, },
UpdateExpression(node, context) { UpdateExpression(node, context) {
update( update(
/** @type {ESTree.Identifier | ESTree.MemberExpression} */ (node.argument), /** @type {ESTree.Identifier | ESTree.MemberExpression} */ (node.argument),
context.state.scope context.state.scope
); );
}, },
CallExpression(node, context) { CallExpression(node, context) {
// for now, assume everything touched by the callee ends up mutating the object // for now, assume everything touched by the callee ends up mutating the object
// TODO optimise this better // TODO optimise this better
// special case — no need to peek inside effects // special case — no need to peek inside effects
const rune = get_rune(node, context.state.scope); const rune = get_rune(node, context.state.scope);
if (rune === '$effect') return; if (rune === '$effect') return;
/** @type {Set<Binding>} */ /** @type {Set<Binding>} */
const touched = new Set(); const touched = new Set();
touch(node, context.state.scope, touched); touch(node, context.state.scope, touched);
for (const b of touched) { for (const b of touched) {
writes.add(b); writes.add(b);
} }
}, },
// don't look inside functions until they are called // don't look inside functions until they are called
ArrowFunctionExpression(_, context) {}, ArrowFunctionExpression(_, context) {},
FunctionDeclaration(_, context) {}, FunctionDeclaration(_, context) {},
FunctionExpression(_, context) {}, FunctionExpression(_, context) {},
Identifier(node, context) { Identifier(node, context) {
const parent = /** @type {ESTree.Node} */ (context.path.at(-1)); const parent = /** @type {ESTree.Node} */ (context.path.at(-1));
if (is_reference(node, parent)) { if (is_reference(node, parent)) {
const binding = context.state.scope.get(node.name); const binding = context.state.scope.get(node.name);
if (binding) { if (binding) {
reads.add(binding); reads.add(binding);
}
} }
} }
} }
); }
}; );
};
let awaited = false; let awaited = false;
// TODO this should probably be attached to the scope? // TODO this should probably be attached to the scope?
var promises = b.id('$$promises'); var promises = b.id('$$promises');
/** /**
* @param {ESTree.Identifier} id * @param {ESTree.Identifier} id
* @param {ESTree.Expression} blocker * @param {ESTree.Expression} blocker
*/ */
function push_declaration(id, blocker) { function push_declaration(id, blocker) {
analysis.instance_body.declarations.push(id); analysis.instance_body.declarations.push(id);
const binding = /** @type {Binding} */ (instance.scope.get(id.name));
binding.blocker = blocker;
}
const binding = /** @type {Binding} */ (instance.scope.get(id.name)); for (let node of instance.ast.body) {
binding.blocker = blocker; if (node.type === 'ImportDeclaration') {
analysis.instance_body.hoisted.push(node);
continue;
} }
for (let node of instance.ast.body) { if (node.type === 'ExportDefaultDeclaration' || node.type === 'ExportAllDeclaration') {
if (node.type === 'ImportDeclaration') { // these can't exist inside `<script>` but TypeScript doesn't know that
analysis.instance_body.hoisted.push(node); continue;
continue; }
}
if (node.type === 'ExportDefaultDeclaration' || node.type === 'ExportAllDeclaration') { if (node.type === 'ExportNamedDeclaration') {
// these can't exist inside `<script>` but TypeScript doesn't know that if (node.declaration) {
node = node.declaration;
} else {
continue; continue;
} }
}
if (node.type === 'ExportNamedDeclaration') { const has_await = has_await_expression(node);
if (node.declaration) { awaited ||= has_await;
node = node.declaration;
} else {
continue;
}
}
const has_await = has_await_expression(node);
awaited ||= has_await;
if (awaited && node.type !== 'FunctionDeclaration') { if (awaited && node.type !== 'FunctionDeclaration') {
/** @type {Set<Binding>} */ /** @type {Set<Binding>} */
const reads = new Set(); // TODO we're not actually using this yet const reads = new Set(); // TODO we're not actually using this yet
/** @type {Set<Binding>} */ /** @type {Set<Binding>} */
const writes = new Set(); const writes = new Set();
trace_references(node, reads, writes); trace_references(node, reads, writes);
const blocker = b.member(promises, b.literal(analysis.instance_body.async.length), true); const blocker = b.member(promises, b.literal(analysis.instance_body.async.length), true);
for (const binding of writes) { for (const binding of writes) {
binding.blocker = blocker; binding.blocker = blocker;
} }
if (node.type === 'VariableDeclaration') {
for (const declarator of node.declarations) {
for (const id of extract_identifiers(declarator.id)) {
push_declaration(id, blocker);
}
// one declarator per declaration, makes things simpler if (node.type === 'VariableDeclaration') {
analysis.instance_body.async.push({ for (const declarator of node.declarations) {
node: declarator, for (const id of extract_identifiers(declarator.id)) {
has_await push_declaration(id, blocker);
});
} }
} else if (node.type === 'ClassDeclaration') {
push_declaration(node.id, blocker); // one declarator per declaration, makes things simpler
analysis.instance_body.async.push({ node, has_await }); analysis.instance_body.async.push({
} else { node: declarator,
analysis.instance_body.async.push({ node, has_await }); has_await
});
} }
} else if (node.type === 'ClassDeclaration') {
push_declaration(node.id, blocker);
analysis.instance_body.async.push({ node, has_await });
} else { } else {
analysis.instance_body.sync.push(node); analysis.instance_body.async.push({ node, has_await });
} }
} else {
analysis.instance_body.sync.push(node);
}
}
if (analysis.runes) {
const props_refs = module.scope.references.get('$$props');
if (props_refs) {
e.legacy_props_invalid(props_refs[0].node);
}
const rest_props_refs = module.scope.references.get('$$restProps');
if (rest_props_refs) {
e.legacy_rest_props_invalid(rest_props_refs[0].node);
} }
for (const { ast, scope, scopes } of [module, instance, template]) { for (const { ast, scope, scopes } of [module, instance, template]) {

@ -33,7 +33,6 @@ import { FunctionExpression } from './visitors/FunctionExpression.js';
import { HtmlTag } from './visitors/HtmlTag.js'; import { HtmlTag } from './visitors/HtmlTag.js';
import { Identifier } from './visitors/Identifier.js'; import { Identifier } from './visitors/Identifier.js';
import { IfBlock } from './visitors/IfBlock.js'; import { IfBlock } from './visitors/IfBlock.js';
import { ImportDeclaration } from './visitors/ImportDeclaration.js';
import { KeyBlock } from './visitors/KeyBlock.js'; import { KeyBlock } from './visitors/KeyBlock.js';
import { LabeledStatement } from './visitors/LabeledStatement.js'; import { LabeledStatement } from './visitors/LabeledStatement.js';
import { LetDirective } from './visitors/LetDirective.js'; import { LetDirective } from './visitors/LetDirective.js';
@ -111,7 +110,6 @@ const visitors = {
HtmlTag, HtmlTag,
Identifier, Identifier,
IfBlock, IfBlock,
ImportDeclaration,
KeyBlock, KeyBlock,
LabeledStatement, LabeledStatement,
LetDirective, LetDirective,

@ -1,17 +0,0 @@
/** @import { ImportDeclaration } from 'estree' */
/** @import { ComponentContext } from '../types' */
import * as b from '#compiler/builders';
/**
* @param {ImportDeclaration} node
* @param {ComponentContext} context
*/
export function ImportDeclaration(node, context) {
if ('hoisted' in context.state) {
// TODO we can get rid of this visitor
context.state.hoisted.push(node);
return b.empty;
}
context.next();
}

@ -3,7 +3,6 @@
import { build_getter, is_prop_source } from '../utils.js'; import { build_getter, is_prop_source } from '../utils.js';
import * as b from '#compiler/builders'; import * as b from '#compiler/builders';
import { add_state_transformers } from './shared/declarations.js'; import { add_state_transformers } from './shared/declarations.js';
import { runes } from '../../../../state.js';
import { transform_body } from '../../shared/transform-async.js'; import { transform_body } from '../../shared/transform-async.js';
/** /**
@ -139,15 +138,13 @@ export function Program(node, context) {
add_state_transformers(context); add_state_transformers(context);
if (context.state.is_instance && runes) { if (context.state.is_instance) {
return { return {
...node, ...node,
body: transform_body( body: transform_body(
node,
context.state.analysis.instance_body, context.state.analysis.instance_body,
b.id('$.run'), b.id('$.run'),
(node) => /** @type {Node} */ (context.visit(node)), (node) => /** @type {Node} */ (context.visit(node))
(statement) => context.state.hoisted.push(statement)
) )
}; };
} }

@ -1,7 +1,6 @@
/** @import { Node, Program } from 'estree' */ /** @import { Node, Program } from 'estree' */
/** @import { Context, ComponentContext } from '../types' */ /** @import { Context, ComponentContext } from '../types' */
import * as b from '#compiler/builders'; import * as b from '#compiler/builders';
import { runes } from '../../../../state.js';
import { transform_body } from '../../shared/transform-async.js'; import { transform_body } from '../../shared/transform-async.js';
/** /**
@ -9,18 +8,16 @@ import { transform_body } from '../../shared/transform-async.js';
* @param {Context} context * @param {Context} context
*/ */
export function Program(node, context) { export function Program(node, context) {
if (context.state.is_instance && runes) { if (context.state.is_instance) {
// @ts-ignore wtf // @ts-ignore wtf
const c = /** @type {ComponentContext} */ (context); const c = /** @type {ComponentContext} */ (context);
return { return {
...node, ...node,
body: transform_body( body: transform_body(
node,
c.state.analysis.instance_body, c.state.analysis.instance_body,
b.id('$$renderer.run'), b.id('$$renderer.run'),
(node) => /** @type {Node} */ (context.visit(node)), (node) => /** @type {Node} */ (context.visit(node))
(statement) => c.state.hoisted.push(statement)
) )
}; };
} }

@ -2,15 +2,16 @@
/** @import { ComponentAnalysis } from '../../types' */ /** @import { ComponentAnalysis } from '../../types' */
import * as b from '#compiler/builders'; import * as b from '#compiler/builders';
// TODO find a way to DRY out this and the corresponding server visitor
/** /**
* @param {ESTree.Program} program
* @param {ComponentAnalysis['instance_body']} instance_body * @param {ComponentAnalysis['instance_body']} instance_body
* @param {ESTree.Expression} runner * @param {ESTree.Expression} runner
* @param {(node: ESTree.Node) => ESTree.Node} transform * @param {(node: ESTree.Node) => ESTree.Node} transform
* @returns {Array<ESTree.Statement | ESTree.VariableDeclaration>}
*/ */
export function transform_body(program, instance_body, runner, transform) { export function transform_body(instance_body, runner, transform) {
const statements = instance_body.sync.map(transform); const statements = instance_body.sync.map(
(node) => /** @type {ESTree.Statement | ESTree.VariableDeclaration} */ (transform(node))
);
if (instance_body.declarations.length > 0) { if (instance_body.declarations.length > 0) {
statements.push( statements.push(

Loading…
Cancel
Save