Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pages Module Transition #49962

Merged
merged 10 commits into from May 19, 2023
38 changes: 25 additions & 13 deletions packages/next/src/build/entries.ts
Expand Up @@ -52,6 +52,8 @@ import { EdgeFunctionLoaderOptions } from './webpack/loaders/next-edge-function-
import { isAppRouteRoute } from '../lib/is-app-route-route'
import { normalizeMetadataRoute } from '../lib/metadata/get-metadata-route'
import { fileExists } from '../lib/file-exists'
import { getRouteLoaderEntry } from './webpack/loaders/next-route-loader'
import { isInternalPathname } from '../lib/is-internal-pathname'

export async function getStaticInfoIncludingLayouts({
isInsideAppDir,
Expand Down Expand Up @@ -538,7 +540,7 @@ export async function createEntrypoints(
// server compiler inject them instead.
} else {
client[clientBundlePath] = getClientEntry({
absolutePagePath: mappings[page],
absolutePagePath,
page,
})
}
Expand All @@ -549,7 +551,7 @@ export async function createEntrypoints(
server[serverBundlePath] = getAppEntry({
page,
name: serverBundlePath,
pagePath: mappings[page],
pagePath: absolutePagePath,
appDir,
appPaths: matchedAppPaths,
pageExtensions,
Expand All @@ -558,16 +560,26 @@ export async function createEntrypoints(
nextConfigOutput: config.output,
preferredRegion: staticInfo.preferredRegion,
})
} else {
if (isInstrumentationHookFile(page) && pagesType === 'root') {
server[serverBundlePath.replace('src/', '')] = {
import: mappings[page],
// the '../' is needed to make sure the file is not chunked
filename: `../${INSTRUMENTATION_HOOK_FILENAME}.js`,
}
} else {
server[serverBundlePath] = [mappings[page]]
} else if (isInstrumentationHookFile(page) && pagesType === 'root') {
server[serverBundlePath.replace('src/', '')] = {
import: absolutePagePath,
// the '../' is needed to make sure the file is not chunked
filename: `../${INSTRUMENTATION_HOOK_FILENAME}.js`,
}
} else if (
!isAPIRoute(page) &&
!isMiddlewareFile(page) &&
!isInternalPathname(absolutePagePath)
) {
server[serverBundlePath] = [
getRouteLoaderEntry({
page,
absolutePagePath,
preferredRegion: staticInfo.preferredRegion,
}),
]
} else {
server[serverBundlePath] = [absolutePagePath]
}
},
onEdgeServer: () => {
Expand All @@ -577,7 +589,7 @@ export async function createEntrypoints(
appDirLoader = getAppEntry({
name: serverBundlePath,
page,
pagePath: mappings[page],
pagePath: absolutePagePath,
appDir: appDir!,
appPaths: matchedAppPaths,
pageExtensions,
Expand All @@ -596,7 +608,7 @@ export async function createEntrypoints(
edgeServer[normalizedServerBundlePath] = getEdgeServerEntry({
...params,
rootDir,
absolutePagePath: mappings[page],
absolutePagePath: absolutePagePath,
bundlePath: clientBundlePath,
isDev: false,
isServerComponent,
Expand Down
1 change: 1 addition & 0 deletions packages/next/src/build/webpack-config.ts
Expand Up @@ -1781,6 +1781,7 @@ export default async function getBaseWebpackConfig(
'next-middleware-asset-loader',
'next-middleware-wasm-loader',
'next-app-loader',
'next-route-loader',
'next-font-loader',
'next-invalid-import-error-loader',
'next-metadata-route-loader',
Expand Down
64 changes: 64 additions & 0 deletions packages/next/src/build/webpack/loaders/next-route-loader.ts
@@ -0,0 +1,64 @@
import type { webpack } from 'next/dist/compiled/webpack/webpack'

import { stringify } from 'querystring'
import { getModuleBuildInfo } from './get-module-build-info'

/**
* The options for the route loader.
*/
type RouteLoaderOptions = {
/**
* The page name for this particular route.
*/
page: string

/**
* The preferred region for this route.
*/
preferredRegion: string | string[] | undefined

/**
* The absolute path to the userland page file.
*/
absolutePagePath: string
}

/**
* Returns the loader entry for a given page.
*
* @param query the options to create the loader entry
* @returns the encoded loader entry
*/
export function getRouteLoaderEntry(query: RouteLoaderOptions): string {
return `next-route-loader?${stringify(query)}!`
}

/**
* Handles the `next-route-loader` options.
* @returns the loader definition function
*/
const loader: webpack.LoaderDefinitionFunction<RouteLoaderOptions> =
function () {
const { page, preferredRegion, absolutePagePath } = this.getOptions()

// Ensure we only run this loader for as a module.
if (!this._module) {
throw new Error('Invariant: expected this to reference a module')
}

// Attach build info to the module.
const buildInfo = getModuleBuildInfo(this._module)
buildInfo.route = {
page,
absolutePagePath,
preferredRegion,
}

return `
// Next.js Route Loader
export * from ${JSON.stringify(absolutePagePath)}
export { default } from ${JSON.stringify(absolutePagePath)}
`
}

export default loader
Expand Up @@ -285,20 +285,24 @@ export class TraceEntryPointsPlugin implements webpack.WebpackPluginInstance {
// don't include the entry itself in the trace
entryFiles.delete(nodePath.join(outputPath, `../${entrypoint.name}.js`))

const finalFiles: string[] = []

for (const file of new Set([
...entryFiles,
...allEntryFiles,
...(this.entryTraces.get(entrypoint.name) || []),
])) {
if (file) {
finalFiles.push(
nodePath.relative(traceOutputPath, file).replace(/\\/g, '/')
)
}
}

assets[traceOutputName] = new sources.RawSource(
JSON.stringify({
version: TRACE_OUTPUT_VERSION,
files: [
...new Set([
...entryFiles,
...allEntryFiles,
...(this.entryTraces.get(entrypoint.name) || []),
]),
].map((file) => {
return nodePath
.relative(traceOutputPath, file)
.replace(/\\/g, '/')
}),
files: finalFiles,
})
)
}
Expand Down Expand Up @@ -369,42 +373,35 @@ export class TraceEntryPointsPlugin implements webpack.WebpackPluginInstance {
entryModMap.set(absolutePath, entryMod)
entryNameMap.set(absolutePath, name)
}
} else {
// If there was no `route` property, we can assume that it was something custom instead.
// In order to trace these we add them to the additionalEntries map.
if (entryMod.request) {
let curMap = additionalEntries.get(name)

if (!curMap) {
curMap = new Map()
additionalEntries.set(name, curMap)
}
depModMap.set(entryMod.request, entryMod)
curMap.set(entryMod.resource, entryMod)
}
}
}

if (entryMod && entryMod.resource) {
const normalizedResource = entryMod.resource.replace(
/\\/g,
'/'
)

if (normalizedResource.includes('pages/')) {
entryNameMap.set(entryMod.resource, name)
entryModMap.set(entryMod.resource, entryMod)
} else {
// If there was no `route` property, we can assume that it was something custom instead.
// In order to trace these we add them to the additionalEntries map.
if (entryMod.request) {
let curMap = additionalEntries.get(name)

if (!curMap) {
curMap = new Map()
additionalEntries.set(name, curMap)
}
depModMap.set(entryMod.resource, entryMod)
depModMap.set(entryMod.request, entryMod)
curMap.set(entryMod.resource, entryMod)
}
}

if (entryMod && entryMod.resource) {
entryNameMap.set(entryMod.resource, name)
entryModMap.set(entryMod.resource, entryMod)

let curMap = additionalEntries.get(name)

if (!curMap) {
curMap = new Map()
additionalEntries.set(name, curMap)
}
depModMap.set(entryMod.resource, entryMod)
curMap.set(entryMod.resource, entryMod)
}
}
}
})
Expand Down Expand Up @@ -545,6 +542,7 @@ export class TraceEntryPointsPlugin implements webpack.WebpackPluginInstance {
this.tracingRoot,
entry
)

const curExtraEntries = additionalEntries.get(entryName)
const finalDeps = new Set<string>()

Expand Down
Expand Up @@ -136,7 +136,7 @@ export async function getNotFoundError(
.filter(
(name) =>
name &&
!/next-(app|middleware|client-pages|flight-(client|server|client-entry))-loader\.js/.test(
!/next-(app|middleware|client-pages|route|flight-(client|server|client-entry))-loader\.js/.test(
name
) &&
!/css-loader.+\.js/.test(name)
Expand Down
Expand Up @@ -48,7 +48,7 @@ function formatMessage(
message.moduleTrace &&
message.moduleTrace.filter(
(trace: any) =>
!/next-(middleware|client-pages|edge-function)-loader\.js/.test(
!/next-(middleware|client-pages|route|edge-function)-loader\.js/.test(
trace.originName
)
)
Expand Down
3 changes: 3 additions & 0 deletions packages/next/src/lib/is-internal-pathname.ts
@@ -0,0 +1,3 @@
export function isInternalPathname(pathname: string): boolean {
return pathname.startsWith('next/dist/pages/')
}
71 changes: 47 additions & 24 deletions packages/next/src/server/dev/hot-reloader.ts
Expand Up @@ -42,7 +42,11 @@ import { denormalizePagePath } from '../../shared/lib/page-path/denormalize-page
import { normalizePathSep } from '../../shared/lib/page-path/normalize-path-sep'
import getRouteFromEntrypoint from '../get-route-from-entrypoint'
import { fileExists } from '../../lib/file-exists'
import { difference, isMiddlewareFilename } from '../../build/utils'
import {
difference,
isMiddlewareFile,
isMiddlewareFilename,
} from '../../build/utils'
import { DecodeError } from '../../shared/lib/utils'
import { Span, trace } from '../../trace'
import { getProperError } from '../../lib/is-error'
Expand All @@ -53,6 +57,9 @@ import { getRegistry } from '../../lib/helpers/get-registry'
import { RouteMatch } from '../future/route-matches/route-match'
import type { Telemetry } from '../../telemetry/storage'
import { parseVersionInfo, VersionInfo } from './parse-version-info'
import { isAPIRoute } from '../../lib/is-api-route'
import { getRouteLoaderEntry } from '../../build/webpack/loaders/next-route-loader'
import { isInternalPathname } from '../../lib/is-internal-pathname'

function diff(a: Set<any>, b: Set<any>) {
return new Set([...a].filter((v) => !b.has(v)))
Expand Down Expand Up @@ -820,33 +827,49 @@ export default class HotReloader {
) {
relativeRequest = `./${relativeRequest}`
}

let value: { import: string; layer?: string } | string
if (isAppPath) {
value = getAppEntry({
name: bundlePath,
page,
appPaths: entryData.appPaths,
pagePath: posix.join(
APP_DIR_ALIAS,
relative(
this.appDir!,
entryData.absolutePagePath
).replace(/\\/g, '/')
),
appDir: this.appDir!,
pageExtensions: this.config.pageExtensions,
rootDir: this.dir,
isDev: true,
tsconfigPath: this.config.typescript.tsconfigPath,
basePath: this.config.basePath,
assetPrefix: this.config.assetPrefix,
nextConfigOutput: this.config.output,
preferredRegion: staticInfo.preferredRegion,
})
} else if (
!isAPIRoute(page) &&
!isMiddlewareFile(page) &&
!isInternalPathname(relativeRequest)
) {
value = getRouteLoaderEntry({
page,
absolutePagePath: relativeRequest,
preferredRegion: staticInfo.preferredRegion,
})
} else {
value = relativeRequest
}

entrypoints[bundlePath] = finalizeEntrypoint({
compilerType: COMPILER_NAMES.server,
name: bundlePath,
isServerComponent,
value: isAppPath
? getAppEntry({
name: bundlePath,
page,
appPaths: entryData.appPaths,
pagePath: posix.join(
APP_DIR_ALIAS,
relative(
this.appDir!,
entryData.absolutePagePath
).replace(/\\/g, '/')
),
appDir: this.appDir!,
pageExtensions: this.config.pageExtensions,
rootDir: this.dir,
isDev: true,
tsconfigPath: this.config.typescript.tsconfigPath,
basePath: this.config.basePath,
assetPrefix: this.config.assetPrefix,
nextConfigOutput: this.config.output,
preferredRegion: staticInfo.preferredRegion,
})
: relativeRequest,
value,
hasAppDir,
})
},
Expand Down