Skip to content

Commit f4ac92d

Browse files
huozhiEthan-Arrowoodijjk
authored
Add flag for early import app router modules (vercel#61168)
Closes NEXT-2236 --------- Co-authored-by: Ethan Arrowood <ethan@arrowood.dev> Co-authored-by: JJ Kasper <jj@jjsweb.site>
1 parent 5be8135 commit f4ac92d

File tree

3 files changed

+46
-7
lines changed

3 files changed

+46
-7
lines changed

packages/next/src/build/entries.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,8 @@ export async function createEntrypoints(
647647
basePath: config.basePath,
648648
assetPrefix: config.assetPrefix,
649649
nextConfigOutput: config.output,
650+
nextConfigExperimentalUseEarlyImport:
651+
config.experimental.useEarlyImport,
650652
preferredRegion: staticInfo.preferredRegion,
651653
middlewareConfig: encodeToBase64(staticInfo.middleware || {}),
652654
})

packages/next/src/build/webpack/loaders/next-app-loader.ts

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export type AppLoaderOptions = {
4747
isDev?: boolean
4848
basePath: string
4949
nextConfigOutput?: NextConfig['output']
50+
nextConfigExperimentalUseEarlyImport?: boolean
5051
middlewareConfig: string
5152
}
5253
type AppLoader = webpack.LoaderDefinitionFunction<AppLoaderOptions>
@@ -170,6 +171,7 @@ async function createTreeCodeFromPath(
170171
metadataResolver,
171172
pageExtensions,
172173
basePath,
174+
collectedAsyncImports,
173175
}: {
174176
page: string
175177
resolveDir: DirResolver
@@ -181,6 +183,7 @@ async function createTreeCodeFromPath(
181183
loaderContext: webpack.LoaderContext<AppLoaderOptions>
182184
pageExtensions: PageExtensions
183185
basePath: string
186+
collectedAsyncImports: string[]
184187
}
185188
): Promise<{
186189
treeCode: string
@@ -233,7 +236,8 @@ async function createTreeCodeFromPath(
233236
}
234237

235238
async function createSubtreePropsFromSegmentPath(
236-
segments: string[]
239+
segments: string[],
240+
nestedCollectedAsyncImports: string[]
237241
): Promise<{
238242
treeCode: string
239243
}> {
@@ -280,7 +284,10 @@ async function createTreeCodeFromPath(
280284
}/page`
281285

282286
const resolvedPagePath = await resolver(matchedPagePath)
283-
if (resolvedPagePath) pages.push(resolvedPagePath)
287+
if (resolvedPagePath) {
288+
pages.push(resolvedPagePath)
289+
nestedCollectedAsyncImports.push(resolvedPagePath)
290+
}
284291

285292
// Use '' for segment as it's the page. There can't be a segment called '' so this is the safest way to add it.
286293
props[
@@ -291,8 +298,7 @@ async function createTreeCodeFromPath(
291298
)}), ${JSON.stringify(resolvedPagePath)}],
292299
${createMetadataExportsCode(metadata)}
293300
}]`
294-
295-
continue
301+
if (resolvedPagePath) continue
296302
}
297303

298304
// if the parallelSegment was not matched to the __PAGE__ slot, then it's a parallel route at this level.
@@ -321,7 +327,10 @@ async function createTreeCodeFromPath(
321327
}
322328

323329
const { treeCode: pageSubtreeCode } =
324-
await createSubtreePropsFromSegmentPath(subSegmentPath)
330+
await createSubtreePropsFromSegmentPath(
331+
subSegmentPath,
332+
nestedCollectedAsyncImports
333+
)
325334

326335
const parallelSegmentPath = subSegmentPath.join('/')
327336

@@ -399,6 +408,7 @@ async function createTreeCodeFromPath(
399408
const notFoundPath =
400409
definedFilePaths.find(([type]) => type === 'not-found')?.[1] ??
401410
defaultNotFoundPath
411+
nestedCollectedAsyncImports.push(notFoundPath)
402412
subtreeCode = `{
403413
children: ['${PAGE_SEGMENT_KEY}', {}, {
404414
page: [
@@ -414,6 +424,7 @@ async function createTreeCodeFromPath(
414424
const componentsCode = `{
415425
${definedFilePaths
416426
.map(([file, filePath]) => {
427+
if (filePath) nestedCollectedAsyncImports.push(filePath)
417428
return `'${file}': [() => import(/* webpackMode: "eager" */ ${JSON.stringify(
418429
filePath
419430
)}), ${JSON.stringify(filePath)}],`
@@ -456,6 +467,7 @@ async function createTreeCodeFromPath(
456467
defaultPath = normalizedDefault ?? PARALLEL_ROUTE_DEFAULT_PATH
457468
}
458469

470+
nestedCollectedAsyncImports.push(defaultPath)
459471
props[normalizeParallelKey(adjacentParallelSegment)] = `[
460472
'${DEFAULT_SEGMENT_KEY}',
461473
{},
@@ -476,7 +488,10 @@ async function createTreeCodeFromPath(
476488
}
477489
}
478490

479-
const { treeCode } = await createSubtreePropsFromSegmentPath([])
491+
const { treeCode } = await createSubtreePropsFromSegmentPath(
492+
[],
493+
collectedAsyncImports
494+
)
480495

481496
return {
482497
treeCode: `${treeCode}.children;`,
@@ -510,9 +525,11 @@ const nextAppLoader: AppLoader = async function nextAppLoader() {
510525
preferredRegion,
511526
basePath,
512527
middlewareConfig: middlewareConfigBase64,
528+
nextConfigExperimentalUseEarlyImport,
513529
} = loaderOptions
514530

515531
const buildInfo = getModuleBuildInfo((this as any)._module)
532+
const collectedAsyncImports: string[] = []
516533
const page = name.replace(/^app/, '')
517534
const middlewareConfig: MiddlewareConfig = JSON.parse(
518535
Buffer.from(middlewareConfigBase64, 'base64').toString()
@@ -690,6 +707,7 @@ const nextAppLoader: AppLoader = async function nextAppLoader() {
690707
loaderContext: this,
691708
pageExtensions,
692709
basePath,
710+
collectedAsyncImports,
693711
})
694712

695713
if (!treeCodeResult.rootLayout) {
@@ -738,6 +756,7 @@ const nextAppLoader: AppLoader = async function nextAppLoader() {
738756
loaderContext: this,
739757
pageExtensions,
740758
basePath,
759+
collectedAsyncImports,
741760
})
742761
}
743762
}
@@ -746,7 +765,7 @@ const nextAppLoader: AppLoader = async function nextAppLoader() {
746765

747766
// Prefer to modify next/src/server/app-render/entry-base.ts since this is shared with Turbopack.
748767
// Any changes to this code should be reflected in Turbopack's app_source.rs and/or app-renderer.tsx as well.
749-
return await loadEntrypoint(
768+
const code = await loadEntrypoint(
750769
'app-page',
751770
{
752771
VAR_DEFINITION_PAGE: page,
@@ -761,6 +780,19 @@ const nextAppLoader: AppLoader = async function nextAppLoader() {
761780
__next_app_load_chunk__: '() => Promise.resolve()',
762781
}
763782
)
783+
784+
// Evaluated the imported modules early in the generated code
785+
const earlyEvaluateCode =
786+
nextConfigExperimentalUseEarlyImport &&
787+
process.env.NODE_ENV === 'production'
788+
? collectedAsyncImports
789+
.map((modulePath) => {
790+
return `import ${JSON.stringify(modulePath)};`
791+
})
792+
.join('\n')
793+
: ''
794+
795+
return earlyEvaluateCode + code
764796
}
765797

766798
export default nextAppLoader

packages/next/src/server/config-shared.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,11 @@ export interface ExperimentalConfig {
404404
* @default true
405405
*/
406406
missingSuspenseWithCSRBailout?: boolean
407+
408+
/**
409+
* Enables early import feature for app router modules
410+
*/
411+
useEarlyImport?: boolean
407412
}
408413

409414
export type ExportPathMap = {

0 commit comments

Comments
 (0)