pull/17038/head
Rich Harris 1 week ago
parent b8a27f4050
commit 2f69c1c34e

@ -1,7 +1,7 @@
/** @import * as ESTree from 'estree' */
/** @import { Binding, AST, ValidatedCompileOptions, ValidatedModuleCompileOptions } from '#compiler' */
/** @import { AnalysisState, Visitors } from './types' */
/** @import { Analysis, ComponentAnalysis, Js, ReactiveStatement, Template } from '../types' */
/** @import { Analysis, AwaitedStatement, ComponentAnalysis, Js, ReactiveStatement, Template } from '../types' */
import { walk } from 'zimmerframe';
import { parse } from '../1-parse/acorn.js';
import * as e from '../../errors.js';
@ -701,44 +701,62 @@ export function analyze_component(root, source, options) {
}
if (instance.has_await) {
let awaiting = false;
let i = 0;
/**
* @param {ESTree.Node} node
* @param {Set<Binding>} dependencies
*/
const trace_dependencies = (node, dependencies) => {
walk(
node,
{ scope: instance.scope },
{
_(node, context) {
const scope = scopes.get(node);
if (scope) {
context.next({ scope });
} else {
context.next();
}
},
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) {
dependencies.add(binding);
}
// TODO recurse into function definitions
}
}
}
);
return dependencies;
};
/**
* @param {ESTree.Statement | ESTree.VariableDeclarator | ESTree.FunctionDeclaration | ESTree.ClassDeclaration} node
*/
const push = (node) => {
const has_await = has_await_expression(node);
awaiting ||= has_await;
/** @type {AwaitedStatement} */
const statement = {
node,
has_await: has_await_expression(node),
declarations: [],
dependencies: trace_dependencies(node, new Set())
};
if (!awaiting) return;
const id = b.id(`$$${i++}`);
analysis.awaited_statements.set(node, {
id,
has_await,
metadata: create_expression_metadata()
});
analysis.awaited_statements.set(node, statement);
if (node.type === 'VariableDeclarator') {
for (const identifier of extract_identifiers(node.id)) {
analysis.awaited_declarations.set(identifier.name, {
id,
has_await,
pattern: node.id,
updated_by: new Set(),
metadata: create_expression_metadata()
});
const binding = /** @type {Binding} */ (instance.scope.get(identifier.name));
statement.declarations.push(binding);
}
} else if (node.type === 'ClassDeclaration' || node.type === 'FunctionDeclaration') {
analysis.awaited_declarations.set(node.id.name, {
id,
has_await,
pattern: node.id,
updated_by: new Set(),
metadata: create_expression_metadata()
});
const binding = /** @type {Binding} */ (instance.scope.get(node.id.name));
statement.declarations.push(binding);
}
};

@ -1,8 +1,10 @@
/** @import { ClassDeclaration, Expression, FunctionDeclaration, Identifier, ImportDeclaration, MemberExpression, Program, Statement, VariableDeclarator } from 'estree' */
/** @import { BlockStatement, ClassDeclaration, ClassExpression, Expression, FunctionDeclaration, FunctionExpression, Identifier, ImportDeclaration, MemberExpression, Program, Statement, VariableDeclaration, VariableDeclarator } from 'estree' */
/** @import { ComponentContext } from '../types' */
/** @import { AwaitedStatement } from '../../../types' */
import { build_getter, is_prop_source } from '../utils.js';
import * as b from '#compiler/builders';
import { add_state_transformers } from './shared/declarations.js';
import { get_rune } from '../../../scope.js';
/**
* @param {Program} node
@ -155,55 +157,47 @@ function transform_body(program, context) {
/** @type {Statement[]} */
const out = [];
const { awaited_declarations, awaited_statements } = context.state.analysis;
/** @type {Identifier[]} */
const ids = [];
/** @type {Identifier | null} */
let last = null;
/** @type {AwaitedStatement[]} */
const statements = [];
/** @type {AwaitedStatement[]} */
const deriveds = [];
const { awaited_statements } = context.state.analysis;
let awaited = false;
/**
* @param {Statement | VariableDeclarator | FunctionDeclaration | ClassDeclaration} node
* @param {Statement | VariableDeclarator | ClassDeclaration | FunctionDeclaration} node
*/
const push = (node) => {
const awaited = awaited_statements.get(node);
const statement = awaited_statements.get(node);
if (awaited) {
const ids = new Set();
const patterns = new Set();
awaited ||= !!statement?.has_await;
for (const binding of awaited.metadata.dependencies) {
const dep = awaited_declarations.get(binding.node.name);
if (dep && dep.id !== awaited.id && !ids.has(dep.id)) {
ids.add(dep.id);
patterns.add(dep.pattern);
}
if (!awaited || !statement || node.type === 'FunctionDeclaration') {
if (node.type === 'VariableDeclarator') {
out.push(/** @type {VariableDeclaration} */ (context.visit(b.var(node.id, node.init))));
} else {
out.push(/** @type {Statement} */ (context.visit(node)));
}
if (last) {
ids.add(last);
}
return;
}
const rhs =
node.type === 'VariableDeclarator'
? node.init ?? b.block([])
: node.type === 'ExpressionStatement'
? node.expression
: node.type === 'FunctionDeclaration'
? node
: b.block([node]);
out.push(
b.var(
awaited.id,
b.call('$.run', b.array([...ids]), b.arrow([...patterns], rhs, awaited.has_await))
)
);
if (node.type === 'VariableDeclarator') {
const rune = get_rune(node.init, context.state.scope);
last = awaited.id;
} else if (node.type === 'VariableDeclarator') {
out.push(b.var(node.id, node.init));
} else {
out.push(node);
if (rune === '$derived' || rune === '$derived.by') {
deriveds.push(statement);
return;
}
}
statements.push(statement);
};
for (let node of program.body) {
@ -231,13 +225,86 @@ function transform_body(program, context) {
for (const declarator of node.declarations) {
push(declarator);
}
} else if (node.type === 'ClassDeclaration' || node.type === 'FunctionDeclaration') {
// TODO
push(node);
} else {
push(node);
}
}
return out.map((node) => /** @type {Statement} */ (context.visit(node)));
for (const derived of deriveds) {
// find the earliest point we can insert this derived
let index = -1;
for (const binding of derived.dependencies) {
index = Math.max(
index,
statements.findIndex((s) => s.declarations.includes(binding))
);
}
if (index === -1 && !derived.has_await) {
const node = /** @type {VariableDeclarator} */ (derived.node);
out.push(/** @type {VariableDeclaration} */ (context.visit(b.var(node.id, node.init))));
} else {
// TODO combine deriveds with Promise.all where necessary
statements.splice(index + 1, 0, derived);
}
}
if (statements.length > 0) {
var declarations = statements.map((s) => s.declarations).flat();
out.push(
b.declaration(
'var',
declarations.map((d) => b.declarator(d.node))
)
);
const thunks = statements.map((s) => {
if (s.node.type === 'VariableDeclarator') {
const visited = /** @type {VariableDeclaration} */ (
context.visit(b.var(s.node.id, s.node.init))
);
return b.thunk(
b.assignment('=', s.node.id, visited.declarations[0].init ?? b.void0),
s.has_await
);
}
if (s.node.type === 'ClassDeclaration') {
return b.thunk(
b.assignment(
'=',
s.node.id,
/** @type {ClassExpression} */ ({ ...s.node, type: 'ClassExpression' })
),
s.has_await
);
}
if (s.node.type === 'FunctionDeclaration') {
return b.thunk(
b.assignment(
'=',
s.node.id,
/** @type {FunctionExpression} */ ({ ...s.node, type: 'FunctionExpression' })
),
s.has_await
);
}
if (s.node.type === 'ExpressionStatement') {
return b.thunk(b.unary('void', /** @type {Expression} */ (s.node.expression)), s.has_await);
}
return b.thunk(b.block([/** @type {Statement} */ (s.node)]), s.has_await);
});
out.push(b.var('$$promises', b.call('$.run', b.array(thunks))));
}
// console.log('statements', statements);
// console.log('deriveds', deriveds);
return out;
}

@ -3,6 +3,8 @@ import type {
AwaitExpression,
CallExpression,
ClassBody,
ClassDeclaration,
FunctionDeclaration,
Identifier,
LabeledStatement,
ModuleDeclaration,
@ -40,9 +42,10 @@ export interface AwaitedDeclaration {
}
export interface AwaitedStatement {
id: Identifier;
node: Statement | VariableDeclarator | ClassDeclaration | FunctionDeclaration;
has_await: boolean;
metadata: ExpressionMetadata;
declarations: Binding[];
dependencies: Set<Binding>;
}
/**

@ -82,6 +82,17 @@ export function block(body) {
return { type: 'BlockStatement', body };
}
/**
* @param {ESTree.Identifier | null} id
* @param {ESTree.ClassBody} body
* @param {ESTree.Expression | null} [superClass]
* @param {ESTree.Decorator[]} [decorators]
* @returns {ESTree.ClassExpression}
*/
export function class_expression(id, body, superClass, decorators = []) {
return { type: 'ClassExpression', body, superClass, decorators };
}
/**
* @param {string} name
* @param {ESTree.Statement} body

Loading…
Cancel
Save