Skip to content

Commit 6b89fe9

Browse files
authored
Turbopack: use prototype for turbopack context for better runtime performance (#81547)
### What? * use prototype for turbopack context for better runtime performance * remove destructuring when unneeded * ensure all calls use the correct context * create static module object at one location ### Why? Improve performance by reducing allocations in the hot path of module evaluation: Based on our module-cost benchmark this is about a 15% runtime improvement * CommonJS Execution Duration: 25.60ms → 21.85ms (14.6% improvement) * ESM Execution Duration: 53.34ms → 44.82ms (16.0% improvement) Which addressed most of the known gap with webpack. Part of PACK-5057
1 parent 76200c7 commit 6b89fe9

File tree

57 files changed

+918
-873
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+918
-873
lines changed

crates/next-core/src/next_app/app_page_entry.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ pub async fn get_app_page_entry(
9898
fxindexmap! {
9999
"tree" => loader_tree_code,
100100
"pages" => StringifyJs(&pages).to_string().into(),
101-
"__next_app_require__" => TURBOPACK_REQUIRE.full.into(),
102-
"__next_app_load_chunk__" => TURBOPACK_LOAD.full.into(),
101+
"__next_app_require__" => TURBOPACK_REQUIRE.bound().into(),
102+
"__next_app_load_chunk__" => TURBOPACK_LOAD.bound().into(),
103103
},
104104
fxindexmap! {},
105105
)

packages/next/src/client/next-dev-turbopack.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ initialize({
3131
page: string,
3232
chunksData: any
3333
) => {
34-
const chunkPromises = chunksData.map(__turbopack_load__)
34+
const chunkPromises = chunksData.map((c: unknown) =>
35+
__turbopack_load__(c)
36+
)
3537

3638
Promise.all(chunkPromises).catch((err) =>
3739
console.error('failed to load chunks for page ' + page, err)

packages/next/src/client/next-turbopack.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ initialize({})
2929
page: string,
3030
chunksData: any
3131
) => {
32-
const chunkPromises = chunksData.map(__turbopack_load__)
32+
const chunkPromises = chunksData.map((c: unknown) =>
33+
__turbopack_load__(c)
34+
)
3335

3436
Promise.all(chunkPromises).catch((err) =>
3537
console.error('failed to load chunks for page ' + page, err)

test/development/acceptance-app/error-recovery.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,10 @@ describe('Error recovery app', () => {
286286

287287
if (isTurbopack) {
288288
// TODO(veil): Location of Page should be app/page.js
289+
// TODO(sokra): The location is wrong because of an bug with HMR and source maps.
290+
// When the code `index.js` changes, this also moves the locations of `app/page.js` in the bundled file.
291+
// So the SourceMap is updated to reflect that. But the browser still has the old bundled file loaded.
292+
// So we look up locations from the old bundle in the new source maps, which leads to mismatched locations.
289293
await expect(browser).toDisplayCollapsedRedbox(`
290294
{
291295
"description": "oops",
@@ -298,7 +302,7 @@ describe('Error recovery app', () => {
298302
"Index.useCallback[increment] index.js (7:11)",
299303
"button <anonymous>",
300304
"Index index.js (12:7)",
301-
"Page index.js (10:6)",
305+
"Page index.js (8:16)",
302306
],
303307
}
304308
`)
Lines changed: 19 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
/// <reference path="./runtime-base.ts" />
22
/// <reference path="./dummy.ts" />
33

4-
declare var augmentContext: (context: unknown) => unknown
5-
64
const moduleCache: ModuleCache<Module> = {}
5+
contextPrototype.c = moduleCache
76

87
/**
98
* Gets or instantiates a runtime module.
@@ -53,83 +52,17 @@ function instantiateModule(
5352
// This can happen if modules incorrectly handle HMR disposes/updates,
5453
// e.g. when they keep a `setTimeout` around which still executes old code
5554
// and contains e.g. a `require("something")` call.
56-
let instantiationReason
57-
switch (sourceType) {
58-
case SourceType.Runtime:
59-
instantiationReason = `as a runtime entry of chunk ${sourceData}`
60-
break
61-
case SourceType.Parent:
62-
instantiationReason = `because it was required from module ${sourceData}`
63-
break
64-
case SourceType.Update:
65-
instantiationReason = 'because of an HMR update'
66-
break
67-
default:
68-
invariant(
69-
sourceType,
70-
(sourceType) => `Unknown source type: ${sourceType}`
71-
)
72-
}
73-
throw new Error(
74-
`Module ${id} was instantiated ${instantiationReason}, but the module factory is not available. It might have been deleted in an HMR update.`
75-
)
55+
factoryNotAvailable(id, sourceType, sourceData)
7656
}
7757

78-
switch (sourceType) {
79-
case SourceType.Runtime:
80-
runtimeModules.add(id)
81-
break
82-
case SourceType.Parent:
83-
// No need to add this module as a child of the parent module here, this
84-
// has already been taken care of in `getOrInstantiateModuleFromParent`.
85-
break
86-
case SourceType.Update:
87-
throw new Error('Unexpected')
88-
default:
89-
invariant(
90-
sourceType,
91-
(sourceType) => `Unknown source type: ${sourceType}`
92-
)
93-
}
94-
95-
const module: Module = {
96-
exports: {},
97-
error: undefined,
98-
loaded: false,
99-
id,
100-
namespaceObject: undefined,
101-
}
58+
const module: Module = createModuleObject(id)
10259

10360
moduleCache[id] = module
10461

10562
// NOTE(alexkirsz) This can fail when the module encounters a runtime error.
10663
try {
107-
const r = commonJsRequire.bind(null, module)
108-
moduleFactory(
109-
augmentContext({
110-
a: asyncModule.bind(null, module),
111-
e: module.exports,
112-
r: commonJsRequire.bind(null, module),
113-
t: runtimeRequire,
114-
f: moduleContext,
115-
i: esmImport.bind(null, module),
116-
s: esmExport.bind(null, module, module.exports, moduleCache),
117-
j: dynamicExport.bind(null, module, module.exports, moduleCache),
118-
v: exportValue.bind(null, module, moduleCache),
119-
n: exportNamespace.bind(null, module, moduleCache),
120-
m: module,
121-
c: moduleCache,
122-
M: moduleFactories,
123-
l: loadChunk.bind(null, SourceType.Parent, id),
124-
L: loadChunkByUrl.bind(null, SourceType.Parent, id),
125-
w: loadWebAssembly.bind(null, SourceType.Parent, id),
126-
u: loadWebAssemblyModule.bind(null, SourceType.Parent, id),
127-
P: resolveAbsolutePath,
128-
U: relativeURL,
129-
R: createResolvePathFromModule(r),
130-
b: getWorkerBlobURL,
131-
})
132-
)
64+
const context = new (Context as any as ContextConstructor<Module>)(module)
65+
moduleFactory(context)
13366
} catch (error) {
13467
module.error = error as any
13568
throw error
@@ -143,3 +76,17 @@ function instantiateModule(
14376

14477
return module
14578
}
79+
80+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
81+
function registerChunk([
82+
chunkScript,
83+
chunkModules,
84+
runtimeParams,
85+
]: ChunkRegistration) {
86+
const chunkPath = getPathFromScript(chunkScript)
87+
for (const [moduleId, moduleFactory] of Object.entries(chunkModules)) {
88+
registerCompressedModuleFactory(moduleId, moduleFactory)
89+
}
90+
91+
return BACKEND.registerChunk(chunkPath, runtimeParams)
92+
}

0 commit comments

Comments
 (0)