mirror of
https://github.com/morluto/rea.git
synced 2026-11-11 01:13:39 +00:00
fix(javascript): distinguish deferred bootstrap installer writes
This commit is contained in:
1 parent
bfa60d596e
commit
9fb63ad066
3 files changed
+120
-2
No files matched your search
@@ -438,6 +438,12 @@ const referencesAreContained = (
|
||||
[...tracked.keys()].flatMap((id) => state.bindingsById.get(id)?.name ?? []),
|
||||
);
|
||||
const replacedKeys = new Set<string>();
|
||||
const unreadDefinitions = unreadRuntimeDefinitions(
|
||||
call,
|
||||
shape,
|
||||
loaderIds,
|
||||
state,
|
||||
);
|
||||
let contained = true;
|
||||
traverseJavaScriptAst(call, {
|
||||
skip: () => !contained,
|
||||
@@ -452,12 +458,87 @@ const referencesAreContained = (
|
||||
const use: Use = { node, parent, ancestors, shape, loaderIds, state };
|
||||
if (role !== "loader") contained = containedUse(role, use);
|
||||
else if (exposedTableUse(use) === "escaped") contained = false;
|
||||
else contained = exposedSlotWrite(use, replacedKeys);
|
||||
else {
|
||||
const activation = [...ancestors]
|
||||
.reverse()
|
||||
.find((ancestor) => t.isFunction(ancestor));
|
||||
contained =
|
||||
(activation !== undefined && unreadDefinitions.has(activation)) ||
|
||||
exposedSlotWrite(use, replacedKeys);
|
||||
}
|
||||
},
|
||||
});
|
||||
return contained ? { replacedKeys } : undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* A function stored on the confined runtime cannot execute until its member is
|
||||
* read. Keep this proof local to the bootstrap's existing loader/table def-use
|
||||
* boundary: aliases, dynamic member reads and escapes invalidate it. Merely
|
||||
* defining Webpack's chunk installer does not install any chunks.
|
||||
*/
|
||||
const unreadRuntimeDefinitions = (
|
||||
call: t.CallExpression,
|
||||
shape: LoaderShape,
|
||||
loaderIds: ReadonlySet<string>,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
): ReadonlySet<t.Node> => {
|
||||
const definitions = new Map<t.Node, string>();
|
||||
const readKeys = new Set<string>();
|
||||
let confined = true;
|
||||
traverseJavaScriptAst(call, {
|
||||
enter: (node, parent, readAncestors) => {
|
||||
// A helper invoked as `loader.helper()` can obtain the namespace through
|
||||
// its receiver instead of a tracked loader identifier.
|
||||
if (t.isThisExpression(node)) confined = false;
|
||||
if (!t.isIdentifier(node) || parent === null) return;
|
||||
const ancestors = readAncestors();
|
||||
if (
|
||||
!t.isReferenced(node, parent, ancestors.at(-2)) ||
|
||||
!loaderIds.has(bindingOf(state, node) ?? "")
|
||||
)
|
||||
return;
|
||||
if (
|
||||
t.isCallExpression(parent) &&
|
||||
(parent.callee === node ||
|
||||
(parent === shape.dispatch && parent.arguments.at(-1) === node))
|
||||
)
|
||||
return;
|
||||
if (!t.isMemberExpression(parent) || parent.object !== node) {
|
||||
confined = false;
|
||||
return;
|
||||
}
|
||||
const key = semanticStaticPropertyKey(parent.property, parent.computed);
|
||||
if (key === null) {
|
||||
confined = false;
|
||||
return;
|
||||
}
|
||||
const assignment = ancestors.at(-2);
|
||||
if (
|
||||
t.isAssignmentExpression(assignment, { operator: "=" }) &&
|
||||
assignment.left === parent
|
||||
) {
|
||||
if (
|
||||
key !== "m" &&
|
||||
key !== "c" &&
|
||||
t.isFunction(assignment.right) &&
|
||||
t.isExpressionStatement(ancestors.at(-3))
|
||||
)
|
||||
definitions.set(assignment.right, key);
|
||||
return;
|
||||
}
|
||||
readKeys.add(key);
|
||||
},
|
||||
});
|
||||
return new Set(
|
||||
confined
|
||||
? [...definitions]
|
||||
.filter(([, key]) => !readKeys.has(key))
|
||||
.map(([node]) => node)
|
||||
: [],
|
||||
);
|
||||
};
|
||||
|
||||
interface Use {
|
||||
readonly node: t.Identifier;
|
||||
readonly parent: t.Node;
|
||||
|
||||
Reference in new issue
Block a user