Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions changelog.d/13890-als-seed-dynamic-import.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
A program with a dynamic `import()` no longer gets a
`globalThis.AsyncLocalStorage` that node does not have. The entry seeds that
global only for Next.js programs, and every module loaded by `import()` used
to count as a Next.js module. Such a module now gets its path initializer
registered lazily, like an on-demand CommonJS module, so startup also skips
one registry insert per deferred module.
25 changes: 18 additions & 7 deletions crates/perry/src/commands/compile/run_pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3900,9 +3900,14 @@ pub fn run_with_parse_cache(
let nextjs_path_init_modules: Vec<(String, String)> = if is_entry {
ctx.native_modules
.iter()
.filter(|(p, module)| {
module.init_kind == perry_hir::ModuleInitKind::Deferred
|| self::collect_modules::is_nextjs_runtime_module(p)
// A module is on this list by WHERE it is (a Next.js server
// module, or a compilePackages module), never by how it is
// loaded: a non-empty list makes the entry seed
// `globalThis.AsyncLocalStorage`, which node has only in a
// Next.js process. A module reached only through `import()`
// gets its path anchor from the lazy list below.
.filter(|(p, _)| {
self::collect_modules::is_nextjs_runtime_module(p)
// A `perry.compilePackages` module may be reachable
// ONLY through a runtime-computed require (Next's
// require-hook aliases `styled-jsx` to its resolved
Expand All @@ -3919,13 +3924,19 @@ pub fn run_with_parse_cache(
} else {
Vec::new()
};
// A require edge runs its target's init directly; only a program that
// can require by path (not closed) needs the anchor of an on-demand
// module, and only once it looks a path up: registered lazily.
// A require edge runs its target's init directly, and an `import()`
// site calls its target's init directly; only a program that can
// require by path (not closed) needs the anchor of an on-demand or
// deferred module, and only once it looks a path up: registered lazily.
let lazy_path_init_modules: Vec<(String, String)> = if is_entry && !ctx.cjs_closed {
ctx.native_modules
.iter()
.filter(|(_, module)| module.init_kind == perry_hir::ModuleInitKind::OnDemand)
.filter(|(_, module)| {
matches!(
module.init_kind,
perry_hir::ModuleInitKind::OnDemand | perry_hir::ModuleInitKind::Deferred
)
})
.map(|(p, m)| (p.to_string_lossy().into_owned(), sanitize_name(&m.name)))
.filter(|pair| !nextjs_path_init_modules.contains(pair))
.collect()
Expand Down
2 changes: 2 additions & 0 deletions test-files/dynamic_import_als_seed_target.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Helper for test_gap_als_seed_dynamic_import.ts: reached only through import().
export const x: number = 1;
8 changes: 8 additions & 0 deletions test-files/test_gap_als_seed_dynamic_import.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// A program with a dynamic `import()` must not get `globalThis.AsyncLocalStorage`:
// node has no such global (only Next.js assigns one). The entry seeds it for
// Next.js server modules alone; a module reached only through `import()` used
// to put the program on that path, so this printed "function".

const m = await import("./dynamic_import_als_seed_target.ts");
console.log(typeof (globalThis as any).AsyncLocalStorage, m.x);
console.log("AsyncLocalStorage" in globalThis);
Loading