Skip to content

Commit 6683edf

Browse files
committed
Base JS runtime for builds
Currently we ship dev runtimes (with things like HMR logic) along with code for loading chunks. This separates them and allows us to include a minimal runtime for builds. Test Plan: `TURBOPACK=1 TURBOPACK_BUILD=1 pnpm build` on an app with a `middleware.ts` and verified it loads when started.
1 parent 74d2136 commit 6683edf

25 files changed

+896
-588
lines changed

packages/next/src/build/swc/generated-native.d.ts

-5
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,6 @@ export class ExternalObject<T> {
3232
[K: symbol]: T
3333
}
3434
}
35-
export interface TransformOutput {
36-
code: string
37-
map?: string
38-
output?: string
39-
}
4035
export function mdxCompile(
4136
value: string,
4237
option: Buffer,

turbopack/crates/turbopack-browser/src/ecmascript/evaluate/chunk.rs

+2
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ impl EcmascriptDevEvaluateChunk {
147147
let runtime_code = turbopack_ecmascript_runtime::get_browser_runtime_code(
148148
environment,
149149
chunking_context.chunk_base_path(),
150+
Value::new(chunking_context.runtime_type()),
150151
Vc::cell(output_root.to_string().into()),
151152
);
152153
code.push_code(&*runtime_code.await?);
@@ -155,6 +156,7 @@ impl EcmascriptDevEvaluateChunk {
155156
let runtime_code = turbopack_ecmascript_runtime::get_browser_runtime_code(
156157
environment,
157158
chunking_context.chunk_base_path(),
159+
Value::new(chunking_context.runtime_type()),
158160
Vc::cell(output_root.to_string().into()),
159161
);
160162
code.push_code(&*runtime_code.await?);

turbopack/crates/turbopack-ecmascript-runtime/js/package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
"check": "run-p check:*",
99
"check:nodejs": "tsc -p src/nodejs",
1010
"check:browser-dev-client": "tsc -p src/browser/dev/hmr-client",
11-
"check:browser-dev-runtime-base": "tsc -p src/browser/dev/runtime/base",
12-
"check:browser-dev-runtime-dom": "tsc -p src/browser/dev/runtime/dom",
13-
"check:browser-dev-runtime-edge": "tsc -p src/browser/dev/runtime/edge"
11+
"check:browser-runtime-base": "tsc -p src/browser/runtime/base",
12+
"check:browser-runtime-dom": "tsc -p src/browser/runtime/dom",
13+
"check:browser-runtime-edge": "tsc -p src/browser/runtime/edge"
1414
},
1515
"exports": {
1616
".": "./src/main.js",

turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/dev/hmr-client/hmr-client.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/// <reference path="../../../shared/runtime-types.d.ts" />
2-
/// <reference path="../runtime/base/globals.d.ts" />
3-
/// <reference path="../runtime/base/protocol.d.ts" />
4-
/// <reference path="../runtime/base/extensions.d.ts" />
2+
/// <reference path="../../runtime/base/dev-globals.d.ts" />
3+
/// <reference path="../../runtime/base/dev-protocol.d.ts" />
4+
/// <reference path="../../runtime/base/dev-extensions.ts" />
55

66
import {
77
addMessageListener as turboSocketAddMessageListener,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/// <reference path="./runtime-base.ts" />
2+
3+
const moduleCache: ModuleCache<BaseModule> = {};
4+
5+
/**
6+
* Gets or instantiates a runtime module.
7+
*/
8+
// @ts-ignore
9+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
10+
function getOrInstantiateRuntimeModule(
11+
moduleId: ModuleId,
12+
chunkPath: ChunkPath,
13+
): BaseModule {
14+
const module = moduleCache[moduleId];
15+
if (module) {
16+
if (module.error) {
17+
throw module.error;
18+
}
19+
return module;
20+
}
21+
22+
return instantiateModule(moduleId, { type: SourceType.Runtime, chunkPath });
23+
}
24+
25+
/**
26+
* Retrieves a module from the cache, or instantiate it if it is not cached.
27+
*/
28+
// Used by the backend
29+
// @ts-ignore
30+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
31+
const getOrInstantiateModuleFromParent: GetOrInstantiateModuleFromParent<BaseModule> = (
32+
id,
33+
sourceModule
34+
) => {
35+
const module = moduleCache[id];
36+
37+
if (sourceModule.children.indexOf(id) === -1) {
38+
sourceModule.children.push(id);
39+
}
40+
41+
if (module) {
42+
if (module.parents.indexOf(sourceModule.id) === -1) {
43+
module.parents.push(sourceModule.id);
44+
}
45+
46+
return module;
47+
}
48+
49+
return instantiateModule(id, {
50+
type: SourceType.Parent,
51+
parentId: sourceModule.id,
52+
});
53+
};
54+
55+
function instantiateModule(id: ModuleId, source: SourceInfo): BaseModule {
56+
const moduleFactory = moduleFactories[id];
57+
if (typeof moduleFactory !== "function") {
58+
// This can happen if modules incorrectly handle HMR disposes/updates,
59+
// e.g. when they keep a `setTimeout` around which still executes old code
60+
// and contains e.g. a `require("something")` call.
61+
let instantiationReason;
62+
switch (source.type) {
63+
case SourceType.Runtime:
64+
instantiationReason = `as a runtime entry of chunk ${source.chunkPath}`;
65+
break;
66+
case SourceType.Parent:
67+
instantiationReason = `because it was required from module ${source.parentId}`;
68+
break;
69+
case SourceType.Update:
70+
instantiationReason = "because of an HMR update";
71+
break;
72+
default:
73+
invariant(source, (source) => `Unknown source type: ${source?.type}`);
74+
}
75+
throw new Error(
76+
`Module ${id} was instantiated ${instantiationReason}, but the module factory is not available. It might have been deleted in an HMR update.`
77+
);
78+
}
79+
80+
let parents: ModuleId[];
81+
switch (source.type) {
82+
case SourceType.Runtime:
83+
runtimeModules.add(id);
84+
parents = [];
85+
break;
86+
case SourceType.Parent:
87+
// No need to add this module as a child of the parent module here, this
88+
// has already been taken care of in `getOrInstantiateModuleFromParent`.
89+
parents = [source.parentId];
90+
break;
91+
case SourceType.Update:
92+
parents = source.parents || [];
93+
break;
94+
default:
95+
invariant(source, (source) => `Unknown source type: ${source?.type}`);
96+
}
97+
98+
const module: BaseModule = {
99+
exports: {},
100+
error: undefined,
101+
loaded: false,
102+
id,
103+
parents,
104+
children: [],
105+
namespaceObject: undefined,
106+
};
107+
108+
moduleCache[id] = module;
109+
110+
// NOTE(alexkirsz) This can fail when the module encounters a runtime error.
111+
try {
112+
const sourceInfo: SourceInfo = { type: SourceType.Parent, parentId: id };
113+
114+
const r = commonJsRequire.bind(null, module);
115+
moduleFactory.call(
116+
module.exports,
117+
{
118+
a: asyncModule.bind(null, module),
119+
e: module.exports,
120+
r: commonJsRequire.bind(null, module),
121+
t: runtimeRequire,
122+
f: moduleContext,
123+
i: esmImport.bind(null, module),
124+
s: esmExport.bind(null, module, module.exports),
125+
j: dynamicExport.bind(null, module, module.exports),
126+
v: exportValue.bind(null, module),
127+
n: exportNamespace.bind(null, module),
128+
m: module,
129+
c: moduleCache,
130+
M: moduleFactories,
131+
l: loadChunk.bind(null, sourceInfo),
132+
w: loadWebAssembly.bind(null, sourceInfo),
133+
u: loadWebAssemblyModule.bind(null, sourceInfo),
134+
g: globalThis,
135+
P: resolveAbsolutePath,
136+
U: relativeURL,
137+
R: createResolvePathFromModule(r),
138+
b: getWorkerBlobURL,
139+
__dirname: typeof module.id === "string" ? module.id.replace(/(^|\/)\/+$/, "") : module.id
140+
}
141+
);
142+
} catch (error) {
143+
module.error = error as any;
144+
throw error;
145+
}
146+
147+
module.loaded = true;
148+
if (module.namespaceObject && module.exports !== module.namespaceObject) {
149+
// in case of a circular dependency: cjs1 -> esm2 -> cjs1
150+
interopEsm(module.exports, module.namespaceObject);
151+
}
152+
153+
return module;
154+
}
155+

0 commit comments

Comments
 (0)