next
Version:
The React Framework
935 lines • 61.3 kB
JavaScript
import { StringXor } from 'next/dist/compiled/webpack/webpack';
import { getOverlayMiddleware, getSourceMapMiddleware } from '../../client/components/react-dev-overlay/server/middleware-webpack';
import { WebpackHotMiddleware } from './hot-middleware';
import { join, relative, isAbsolute, posix } from 'path';
import { createEntrypoints, createPagesMapping, finalizeEntrypoint, getClientEntry, getEdgeServerEntry, getAppEntry, runDependingOnPageType, getStaticInfoIncludingLayouts, getInstrumentationEntry } from '../../build/entries';
import { watchCompilers } from '../../build/output';
import * as Log from '../../build/output/log';
import getBaseWebpackConfig, { loadProjectInfo } from '../../build/webpack-config';
import { APP_DIR_ALIAS, WEBPACK_LAYERS } from '../../lib/constants';
import { recursiveDelete } from '../../lib/recursive-delete';
import { BLOCKED_PAGES, CLIENT_STATIC_FILES_RUNTIME_AMP, CLIENT_STATIC_FILES_RUNTIME_MAIN, CLIENT_STATIC_FILES_RUNTIME_MAIN_APP, CLIENT_STATIC_FILES_RUNTIME_REACT_REFRESH, COMPILER_NAMES, RSC_MODULE_TYPES } from '../../shared/lib/constants';
import { getPathMatch } from '../../shared/lib/router/utils/path-match';
import { findPageFile } from '../lib/find-page-file';
import { BUILDING, getEntries, EntryTypes, getInvalidator, onDemandEntryHandler } from './on-demand-entry-handler';
import { denormalizePagePath } from '../../shared/lib/page-path/denormalize-page-path';
import { normalizePathSep } from '../../shared/lib/page-path/normalize-path-sep';
import getRouteFromEntrypoint from '../get-route-from-entrypoint';
import { difference, isInstrumentationHookFile, isMiddlewareFile, isMiddlewareFilename } from '../../build/utils';
import { DecodeError } from '../../shared/lib/utils';
import { trace } from '../../trace';
import { getProperError } from '../../lib/is-error';
import ws from 'next/dist/compiled/ws';
import { existsSync, promises as fs } from 'fs';
import { parseVersionInfo } from './parse-version-info';
import { isAPIRoute } from '../../lib/is-api-route';
import { getRouteLoaderEntry } from '../../build/webpack/loaders/next-route-loader';
import { isInternalComponent, isNonRoutePagesPage } from '../../lib/is-internal-component';
import { RouteKind } from '../route-kind';
import { HMR_ACTIONS_SENT_TO_BROWSER } from './hot-reloader-types';
import { PAGE_TYPES } from '../../lib/page-types';
import { FAST_REFRESH_RUNTIME_RELOAD } from './messages';
import { getNodeDebugType } from '../lib/utils';
import { getNextErrorFeedbackMiddleware } from '../../client/components/react-dev-overlay/server/get-next-error-feedback-middleware';
import { getDevOverlayFontMiddleware } from '../../client/components/react-dev-overlay/font/get-dev-overlay-font-middleware';
import { getDisableDevIndicatorMiddleware } from './dev-indicator-middleware';
import getWebpackBundler from '../../shared/lib/get-webpack-bundler';
const MILLISECONDS_IN_NANOSECOND = BigInt(1000000);
function diff(a, b) {
return new Set([
...a
].filter((v)=>!b.has(v)));
}
const wsServer = new ws.Server({
noServer: true
});
export async function renderScriptError(res, error, { verbose = true } = {}) {
// Asks CDNs and others to not to cache the errored page
res.setHeader('Cache-Control', 'no-cache, no-store, max-age=0, must-revalidate');
if (error.code === 'ENOENT') {
return {
finished: undefined
};
}
if (verbose) {
console.error(error.stack);
}
res.statusCode = 500;
res.end('500 - Internal Error');
return {
finished: true
};
}
function addCorsSupport(req, res) {
// Only rewrite CORS handling when URL matches a hot-reloader middleware
if (!req.url.startsWith('/__next')) {
return {
preflight: false
};
}
if (!req.headers.origin) {
return {
preflight: false
};
}
res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
res.setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET');
// Based on https://github.com/primus/access-control/blob/4cf1bc0e54b086c91e6aa44fb14966fa5ef7549c/index.js#L158
if (req.headers['access-control-request-headers']) {
res.setHeader('Access-Control-Allow-Headers', req.headers['access-control-request-headers']);
}
if (req.method === 'OPTIONS') {
res.writeHead(200);
res.end();
return {
preflight: true
};
}
return {
preflight: false
};
}
export const matchNextPageBundleRequest = getPathMatch('/_next/static/chunks/pages/:path*.js(\\.map|)');
// Iteratively look up the issuer till it ends up at the root
function findEntryModule(module, compilation) {
for(;;){
const issuer = compilation.moduleGraph.getIssuer(module);
if (!issuer) return module;
module = issuer;
}
}
function erroredPages(compilation) {
const failedPages = {};
for (const error of compilation.errors){
if (!error.module) {
continue;
}
const entryModule = findEntryModule(error.module, compilation);
const { name } = entryModule;
if (!name) {
continue;
}
// Only pages have to be reloaded
const enhancedName = getRouteFromEntrypoint(name);
if (!enhancedName) {
continue;
}
if (!failedPages[enhancedName]) {
failedPages[enhancedName] = [];
}
failedPages[enhancedName].push(error);
}
return failedPages;
}
export async function getVersionInfo() {
let installed = '0.0.0';
try {
installed = require('next/package.json').version;
let res;
try {
// use NPM registry regardless user using Yarn
res = await fetch('https://registry.npmjs.org/-/package/next/dist-tags');
} catch {
// ignore fetch errors
}
if (!res || !res.ok) return {
installed,
staleness: 'unknown'
};
const { latest, canary } = await res.json();
return parseVersionInfo({
installed,
latest,
canary
});
} catch (e) {
console.error(e);
return {
installed,
staleness: 'unknown'
};
}
}
export default class HotReloaderWebpack {
constructor(dir, { config, pagesDir, distDir, buildId, encryptionKey, previewProps, rewrites, appDir, telemetry, resetFetch }){
this.clientError = null;
this.serverError = null;
this.hmrServerError = null;
this.pagesMapping = {};
this.versionInfo = {
staleness: 'unknown',
installed: '0.0.0'
};
this.reloadAfterInvalidation = false;
this.hasAmpEntrypoints = false;
this.hasAppRouterEntrypoints = false;
this.hasPagesRouterEntrypoints = false;
this.buildId = buildId;
this.encryptionKey = encryptionKey;
this.dir = dir;
this.middlewares = [];
this.pagesDir = pagesDir;
this.appDir = appDir;
this.distDir = distDir;
this.clientStats = null;
this.serverStats = null;
this.edgeServerStats = null;
this.serverPrevDocumentHash = null;
this.telemetry = telemetry;
this.resetFetch = resetFetch;
this.config = config;
this.previewProps = previewProps;
this.rewrites = rewrites;
this.hotReloaderSpan = trace('hot-reloader', undefined, {
version: "15.3.3"
});
// Ensure the hotReloaderSpan is flushed immediately as it's the parentSpan for all processing
// of the current `next dev` invocation.
this.hotReloaderSpan.stop();
}
async run(req, res, parsedUrl) {
// Usually CORS support is not needed for the hot-reloader (this is dev only feature)
// With when the app runs for multi-zones support behind a proxy,
// the current page is trying to access this URL via assetPrefix.
// That's when the CORS support is needed.
const { preflight } = addCorsSupport(req, res);
if (preflight) {
return {};
}
// When a request comes in that is a page bundle, e.g. /_next/static/<buildid>/pages/index.js
// we have to compile the page using on-demand-entries, this middleware will handle doing that
// by adding the page to on-demand-entries, waiting till it's done
// and then the bundle will be served like usual by the actual route in server/index.js
const handlePageBundleRequest = async (pageBundleRes, parsedPageBundleUrl)=>{
const { pathname } = parsedPageBundleUrl;
if (!pathname) return {};
const params = matchNextPageBundleRequest(pathname);
if (!params) return {};
let decodedPagePath;
try {
decodedPagePath = `/${params.path.map((param)=>decodeURIComponent(param)).join('/')}`;
} catch (_) {
throw Object.defineProperty(new DecodeError('failed to decode param'), "__NEXT_ERROR_CODE", {
value: "E528",
enumerable: false,
configurable: true
});
}
const page = denormalizePagePath(decodedPagePath);
if (page === '/_error' || BLOCKED_PAGES.indexOf(page) === -1) {
try {
await this.ensurePage({
page,
clientOnly: true,
url: req.url
});
} catch (error) {
return await renderScriptError(pageBundleRes, getProperError(error));
}
const errors = await this.getCompilationErrors(page);
if (errors.length > 0) {
return await renderScriptError(pageBundleRes, errors[0], {
verbose: false
});
}
}
return {};
};
const { finished } = await handlePageBundleRequest(res, parsedUrl);
for (const middleware of this.middlewares){
let calledNext = false;
await middleware(req, res, ()=>{
calledNext = true;
});
if (!calledNext) {
return {
finished: true
};
}
}
return {
finished
};
}
setHmrServerError(error) {
this.hmrServerError = error;
}
clearHmrServerError() {
if (this.hmrServerError) {
this.setHmrServerError(null);
this.send({
action: HMR_ACTIONS_SENT_TO_BROWSER.RELOAD_PAGE,
data: 'clear hmr server error'
});
}
}
async refreshServerComponents(hash) {
this.send({
action: HMR_ACTIONS_SENT_TO_BROWSER.SERVER_COMPONENT_CHANGES,
hash
});
}
onHMR(req, _socket, head, callback) {
wsServer.handleUpgrade(req, req.socket, head, (client)=>{
var _this_webpackHotMiddleware, _this_onDemandEntries;
(_this_webpackHotMiddleware = this.webpackHotMiddleware) == null ? void 0 : _this_webpackHotMiddleware.onHMR(client);
(_this_onDemandEntries = this.onDemandEntries) == null ? void 0 : _this_onDemandEntries.onHMR(client, ()=>this.hmrServerError);
callback(client);
client.addEventListener('message', ({ data })=>{
data = typeof data !== 'string' ? data.toString() : data;
try {
const payload = JSON.parse(data);
let traceChild;
switch(payload.event){
case 'span-end':
{
traceChild = {
name: payload.spanName,
startTime: BigInt(Math.floor(payload.startTime)) * MILLISECONDS_IN_NANOSECOND,
attrs: payload.attributes,
endTime: BigInt(Math.floor(payload.endTime)) * MILLISECONDS_IN_NANOSECOND
};
break;
}
case 'client-hmr-latency':
{
traceChild = {
name: payload.event,
startTime: BigInt(payload.startTime) * MILLISECONDS_IN_NANOSECOND,
endTime: BigInt(payload.endTime) * MILLISECONDS_IN_NANOSECOND,
attrs: {
updatedModules: payload.updatedModules.map((m)=>m.replace(`(${WEBPACK_LAYERS.appPagesBrowser})/`, '').replace(/^\.\//, '[project]/')),
page: payload.page,
isPageHidden: payload.isPageHidden
}
};
break;
}
case 'client-reload-page':
case 'client-success':
{
traceChild = {
name: payload.event
};
break;
}
case 'client-error':
{
traceChild = {
name: payload.event,
attrs: {
errorCount: payload.errorCount
}
};
break;
}
case 'client-warning':
{
traceChild = {
name: payload.event,
attrs: {
warningCount: payload.warningCount
}
};
break;
}
case 'client-removed-page':
case 'client-added-page':
{
traceChild = {
name: payload.event,
attrs: {
page: payload.page || ''
}
};
break;
}
case 'client-full-reload':
{
const { event, stackTrace, hadRuntimeError } = payload;
traceChild = {
name: event,
attrs: {
stackTrace: stackTrace ?? ''
}
};
if (hadRuntimeError) {
Log.warn(FAST_REFRESH_RUNTIME_RELOAD);
break;
}
let fileMessage = '';
if (stackTrace) {
var _exec;
const file = (_exec = /Aborted because (.+) is not accepted/.exec(stackTrace)) == null ? void 0 : _exec[1];
if (file) {
// `file` is filepath in `pages/` but it can be a webpack url.
// If it's a webpack loader URL, it will include the app-pages layer
if (file.startsWith(`(${WEBPACK_LAYERS.appPagesBrowser})/`)) {
const fileUrl = new URL(file, 'file://');
const cwd = process.cwd();
const modules = fileUrl.searchParams.getAll('modules').map((filepath)=>filepath.slice(cwd.length + 1)).filter((filepath)=>!filepath.startsWith('node_modules'));
if (modules.length > 0) {
fileMessage = ` when ${modules.join(', ')} changed`;
}
} else if (// Handle known webpack layers
file.startsWith(`(${WEBPACK_LAYERS.pagesDirBrowser})/`)) {
const cleanedFilePath = file.slice(`(${WEBPACK_LAYERS.pagesDirBrowser})/`.length);
fileMessage = ` when ${cleanedFilePath} changed`;
} else {
fileMessage = ` when ${file} changed`;
}
}
}
Log.warn(`Fast Refresh had to perform a full reload${fileMessage}. Read more: https://nextjs.org/docs/messages/fast-refresh-reload`);
break;
}
default:
{
break;
}
}
if (traceChild) {
this.hotReloaderSpan.manualTraceChild(traceChild.name, traceChild.startTime, traceChild.endTime, {
...traceChild.attrs,
clientId: payload.id
});
}
} catch (_) {
// invalid WebSocket message
}
});
});
}
async clean(span) {
return span.traceChild('clean').traceAsyncFn(()=>recursiveDelete(join(this.dir, this.config.distDir), /^cache/));
}
async getWebpackConfig(span) {
const webpackConfigSpan = span.traceChild('get-webpack-config');
const pageExtensions = this.config.pageExtensions;
return webpackConfigSpan.traceAsyncFn(async ()=>{
const pagePaths = !this.pagesDir ? [] : await webpackConfigSpan.traceChild('get-page-paths').traceAsyncFn(()=>Promise.all([
findPageFile(this.pagesDir, '/_app', pageExtensions, false),
findPageFile(this.pagesDir, '/_document', pageExtensions, false)
]));
this.pagesMapping = await webpackConfigSpan.traceChild('create-pages-mapping').traceAsyncFn(()=>createPagesMapping({
isDev: true,
pageExtensions: this.config.pageExtensions,
pagesType: PAGE_TYPES.PAGES,
pagePaths: pagePaths.filter((i)=>typeof i === 'string'),
pagesDir: this.pagesDir,
appDir: this.appDir
}));
const entrypoints = await webpackConfigSpan.traceChild('create-entrypoints').traceAsyncFn(()=>createEntrypoints({
appDir: this.appDir,
buildId: this.buildId,
config: this.config,
envFiles: [],
isDev: true,
pages: this.pagesMapping,
pagesDir: this.pagesDir,
previewMode: this.previewProps,
rootDir: this.dir,
pageExtensions: this.config.pageExtensions
}));
const commonWebpackOptions = {
dev: true,
buildId: this.buildId,
encryptionKey: this.encryptionKey,
config: this.config,
pagesDir: this.pagesDir,
rewrites: this.rewrites,
originalRewrites: this.config._originalRewrites,
originalRedirects: this.config._originalRedirects,
runWebpackSpan: this.hotReloaderSpan,
appDir: this.appDir
};
return webpackConfigSpan.traceChild('generate-webpack-config').traceAsyncFn(async ()=>{
const info = await loadProjectInfo({
dir: this.dir,
config: commonWebpackOptions.config,
dev: true
});
return Promise.all([
// order is important here
getBaseWebpackConfig(this.dir, {
...commonWebpackOptions,
compilerType: COMPILER_NAMES.client,
entrypoints: entrypoints.client,
...info
}),
getBaseWebpackConfig(this.dir, {
...commonWebpackOptions,
compilerType: COMPILER_NAMES.server,
entrypoints: entrypoints.server,
...info
}),
getBaseWebpackConfig(this.dir, {
...commonWebpackOptions,
compilerType: COMPILER_NAMES.edgeServer,
entrypoints: entrypoints.edgeServer,
...info
})
]);
});
});
}
async buildFallbackError() {
if (this.fallbackWatcher) return;
const info = await loadProjectInfo({
dir: this.dir,
config: this.config,
dev: true
});
const fallbackConfig = await getBaseWebpackConfig(this.dir, {
runWebpackSpan: this.hotReloaderSpan,
dev: true,
compilerType: COMPILER_NAMES.client,
config: this.config,
buildId: this.buildId,
encryptionKey: this.encryptionKey,
appDir: this.appDir,
pagesDir: this.pagesDir,
rewrites: {
beforeFiles: [],
afterFiles: [],
fallback: []
},
originalRewrites: {
beforeFiles: [],
afterFiles: [],
fallback: []
},
originalRedirects: [],
isDevFallback: true,
entrypoints: (await createEntrypoints({
appDir: this.appDir,
buildId: this.buildId,
config: this.config,
envFiles: [],
isDev: true,
pages: {
'/_app': 'next/dist/pages/_app',
'/_error': 'next/dist/pages/_error'
},
pagesDir: this.pagesDir,
previewMode: this.previewProps,
rootDir: this.dir,
pageExtensions: this.config.pageExtensions
})).client,
...info
});
const fallbackCompiler = getWebpackBundler()(fallbackConfig);
this.fallbackWatcher = await new Promise((resolve)=>{
let bootedFallbackCompiler = false;
fallbackCompiler.watch(// @ts-ignore webpack supports an array of watchOptions when using a multiCompiler
fallbackConfig.watchOptions, // Errors are handled separately
(_err)=>{
if (!bootedFallbackCompiler) {
bootedFallbackCompiler = true;
resolve(true);
}
});
});
}
async tracedGetVersionInfo(span) {
const versionInfoSpan = span.traceChild('get-version-info');
return versionInfoSpan.traceAsyncFn(async ()=>getVersionInfo());
}
async start() {
const startSpan = this.hotReloaderSpan.traceChild('start');
startSpan.stop() // Stop immediately to create an artificial parent span
;
this.versionInfo = await this.tracedGetVersionInfo(startSpan);
const nodeDebugType = getNodeDebugType();
if (nodeDebugType && !this.devtoolsFrontendUrl) {
const debugPort = process.debugPort;
let debugInfo;
try {
// It requires to use 127.0.0.1 instead of localhost for server-side fetching.
const debugInfoList = await fetch(`http://127.0.0.1:${debugPort}/json/list`).then((res)=>res.json());
// There will be only one item for current process, so always get the first item.
debugInfo = debugInfoList[0];
} catch {}
if (debugInfo) {
this.devtoolsFrontendUrl = debugInfo.devtoolsFrontendUrl;
}
}
await this.clean(startSpan);
// Ensure distDir exists before writing package.json
await fs.mkdir(this.distDir, {
recursive: true
});
const distPackageJsonPath = join(this.distDir, 'package.json');
// Ensure commonjs handling is used for files in the distDir (generally .next)
// Files outside of the distDir can be "type": "module"
await fs.writeFile(distPackageJsonPath, '{"type": "commonjs"}');
this.activeWebpackConfigs = await this.getWebpackConfig(startSpan);
for (const config of this.activeWebpackConfigs){
const defaultEntry = config.entry;
config.entry = async (...args)=>{
var _this_multiCompiler;
const outputPath = ((_this_multiCompiler = this.multiCompiler) == null ? void 0 : _this_multiCompiler.outputPath) || '';
const entries = getEntries(outputPath);
// @ts-ignore entry is always a function
const entrypoints = await defaultEntry(...args);
const isClientCompilation = config.name === COMPILER_NAMES.client;
const isNodeServerCompilation = config.name === COMPILER_NAMES.server;
const isEdgeServerCompilation = config.name === COMPILER_NAMES.edgeServer;
await Promise.all(Object.keys(entries).map(async (entryKey)=>{
const entryData = entries[entryKey];
const { bundlePath, dispose } = entryData;
const result = /^(client|server|edge-server)@(app|pages|root)@(.*)/g.exec(entryKey);
const [, key /* pageType */ , , page] = result// this match should always happen
;
if (key === COMPILER_NAMES.client && !isClientCompilation) return;
if (key === COMPILER_NAMES.server && !isNodeServerCompilation) return;
if (key === COMPILER_NAMES.edgeServer && !isEdgeServerCompilation) return;
const isEntry = entryData.type === EntryTypes.ENTRY;
const isChildEntry = entryData.type === EntryTypes.CHILD_ENTRY;
// Check if the page was removed or disposed and remove it
if (isEntry) {
const pageExists = !dispose && existsSync(entryData.absolutePagePath);
if (!pageExists) {
delete entries[entryKey];
return;
}
}
// For child entries, if it has an entry file and it's gone, remove it
if (isChildEntry) {
if (entryData.absoluteEntryFilePath) {
const pageExists = !dispose && existsSync(entryData.absoluteEntryFilePath);
if (!pageExists) {
delete entries[entryKey];
return;
}
}
}
// Ensure _error is considered a `pages` page.
if (page === '/_error') {
this.hasPagesRouterEntrypoints = true;
}
const hasAppDir = !!this.appDir;
const isAppPath = hasAppDir && bundlePath.startsWith('app/');
const staticInfo = isEntry ? await getStaticInfoIncludingLayouts({
isInsideAppDir: isAppPath,
pageExtensions: this.config.pageExtensions,
pageFilePath: entryData.absolutePagePath,
appDir: this.appDir,
config: this.config,
isDev: true,
page
}) : undefined;
if ((staticInfo == null ? void 0 : staticInfo.type) === PAGE_TYPES.PAGES) {
var _staticInfo_config_config, _staticInfo_config, _staticInfo_config_config1, _staticInfo_config1;
if (((_staticInfo_config = staticInfo.config) == null ? void 0 : (_staticInfo_config_config = _staticInfo_config.config) == null ? void 0 : _staticInfo_config_config.amp) === true || ((_staticInfo_config1 = staticInfo.config) == null ? void 0 : (_staticInfo_config_config1 = _staticInfo_config1.config) == null ? void 0 : _staticInfo_config_config1.amp) === 'hybrid') {
this.hasAmpEntrypoints = true;
}
}
const isServerComponent = isAppPath && (staticInfo == null ? void 0 : staticInfo.rsc) !== RSC_MODULE_TYPES.client;
const pageType = entryData.bundlePath.startsWith('pages/') ? PAGE_TYPES.PAGES : entryData.bundlePath.startsWith('app/') ? PAGE_TYPES.APP : PAGE_TYPES.ROOT;
if (pageType === 'pages') {
this.hasPagesRouterEntrypoints = true;
}
if (pageType === 'app') {
this.hasAppRouterEntrypoints = true;
}
const isInstrumentation = isInstrumentationHookFile(page) && pageType === PAGE_TYPES.ROOT;
let pageRuntime = staticInfo == null ? void 0 : staticInfo.runtime;
if (isMiddlewareFile(page) && !this.config.experimental.nodeMiddleware && pageRuntime === 'nodejs') {
Log.warn('nodejs runtime support for middleware requires experimental.nodeMiddleware be enabled in your next.config');
pageRuntime = 'edge';
}
runDependingOnPageType({
page,
pageRuntime,
pageType,
onEdgeServer: ()=>{
// TODO-APP: verify if child entry should support.
if (!isEdgeServerCompilation || !isEntry) return;
entries[entryKey].status = BUILDING;
if (isInstrumentation) {
const normalizedBundlePath = bundlePath.replace('src/', '');
entrypoints[normalizedBundlePath] = finalizeEntrypoint({
compilerType: COMPILER_NAMES.edgeServer,
name: normalizedBundlePath,
value: getInstrumentationEntry({
absolutePagePath: entryData.absolutePagePath,
isEdgeServer: true,
isDev: true
}),
isServerComponent: true,
hasAppDir
});
return;
}
const appDirLoader = 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 == null ? void 0 : staticInfo.preferredRegion,
middlewareConfig: Buffer.from(JSON.stringify((staticInfo == null ? void 0 : staticInfo.middleware) || {})).toString('base64')
}).import : undefined;
entrypoints[bundlePath] = finalizeEntrypoint({
compilerType: COMPILER_NAMES.edgeServer,
name: bundlePath,
value: getEdgeServerEntry({
absolutePagePath: entryData.absolutePagePath,
rootDir: this.dir,
buildId: this.buildId,
bundlePath,
config: this.config,
isDev: true,
page,
pages: this.pagesMapping,
isServerComponent,
appDirLoader,
pagesType: isAppPath ? PAGE_TYPES.APP : PAGE_TYPES.PAGES,
preferredRegion: staticInfo == null ? void 0 : staticInfo.preferredRegion
}),
hasAppDir
});
},
onClient: ()=>{
if (!isClientCompilation) return;
if (isChildEntry) {
entries[entryKey].status = BUILDING;
entrypoints[bundlePath] = finalizeEntrypoint({
name: bundlePath,
compilerType: COMPILER_NAMES.client,
value: entryData.request,
hasAppDir
});
} else {
entries[entryKey].status = BUILDING;
entrypoints[bundlePath] = finalizeEntrypoint({
name: bundlePath,
compilerType: COMPILER_NAMES.client,
value: getClientEntry({
absolutePagePath: entryData.absolutePagePath,
page
}),
hasAppDir
});
}
},
onServer: ()=>{
// TODO-APP: verify if child entry should support.
if (!isNodeServerCompilation || !isEntry) return;
entries[entryKey].status = BUILDING;
let relativeRequest = relative(config.context, entryData.absolutePagePath);
if (!isAbsolute(relativeRequest) && !relativeRequest.startsWith('../')) {
relativeRequest = `./${relativeRequest}`;
}
let value;
if (isInstrumentation) {
value = getInstrumentationEntry({
absolutePagePath: entryData.absolutePagePath,
isEdgeServer: false,
isDev: true
});
entrypoints[bundlePath] = finalizeEntrypoint({
compilerType: COMPILER_NAMES.server,
name: bundlePath,
isServerComponent: true,
value,
hasAppDir
});
} else if (isMiddlewareFile(page)) {
value = getEdgeServerEntry({
absolutePagePath: entryData.absolutePagePath,
rootDir: this.dir,
buildId: this.buildId,
bundlePath,
config: this.config,
isDev: true,
page,
pages: this.pagesMapping,
isServerComponent,
pagesType: PAGE_TYPES.PAGES,
preferredRegion: staticInfo == null ? void 0 : staticInfo.preferredRegion
});
} else 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 == null ? void 0 : staticInfo.preferredRegion,
middlewareConfig: Buffer.from(JSON.stringify((staticInfo == null ? void 0 : staticInfo.middleware) || {})).toString('base64')
});
} else if (isAPIRoute(page)) {
value = getRouteLoaderEntry({
kind: RouteKind.PAGES_API,
page,
absolutePagePath: relativeRequest,
preferredRegion: staticInfo == null ? void 0 : staticInfo.preferredRegion,
middlewareConfig: (staticInfo == null ? void 0 : staticInfo.middleware) || {}
});
} else if (!isMiddlewareFile(page) && !isInternalComponent(relativeRequest) && !isNonRoutePagesPage(page) && !isInstrumentation) {
value = getRouteLoaderEntry({
kind: RouteKind.PAGES,
page,
pages: this.pagesMapping,
absolutePagePath: relativeRequest,
preferredRegion: staticInfo == null ? void 0 : staticInfo.preferredRegion,
middlewareConfig: (staticInfo == null ? void 0 : staticInfo.middleware) ?? {}
});
} else {
value = relativeRequest;
}
entrypoints[bundlePath] = finalizeEntrypoint({
compilerType: COMPILER_NAMES.server,
name: bundlePath,
isServerComponent,
value,
hasAppDir
});
}
});
}));
if (!this.hasAmpEntrypoints) {
delete entrypoints[CLIENT_STATIC_FILES_RUNTIME_AMP];
}
if (!this.hasPagesRouterEntrypoints) {
delete entrypoints[CLIENT_STATIC_FILES_RUNTIME_MAIN];
delete entrypoints['pages/_app'];
delete entrypoints['pages/_error'];
delete entrypoints['/_error'];
delete entrypoints['pages/_document'];
}
// Remove React Refresh entrypoint chunk as `app` doesn't require it.
if (!this.hasAmpEntrypoints && !this.hasPagesRouterEntrypoints) {
delete entrypoints[CLIENT_STATIC_FILES_RUNTIME_REACT_REFRESH];
}
if (!this.hasAppRouterEntrypoints) {
delete entrypoints[CLIENT_STATIC_FILES_RUNTIME_MAIN_APP];
}
return entrypoints;
};
}
// Enable building of client compilation before server compilation in development
// @ts-ignore webpack 5
this.activeWebpackConfigs.parallelism = 1;
this.multiCompiler = getWebpackBundler()(this.activeWebpackConfigs);
// Copy over the filesystem so that it is shared between all compilers.
const inputFileSystem = this.multiCompiler.compilers[0].inputFileSystem;
for (const compiler of this.multiCompiler.compilers){
compiler.inputFileSystem = inputFileSystem;
// This is set for the initial compile. After that Watching class in webpack adds it.
compiler.fsStartTime = Date.now();
// Ensure NodeEnvironmentPlugin doesn't purge the inputFileSystem. Purging is handled in `done` below.
compiler.hooks.beforeRun.intercept({
register (tapInfo) {
if (tapInfo.name === 'NodeEnvironmentPlugin') {
return null;
}
return tapInfo;
}
});
}
this.multiCompiler.hooks.done.tap('NextjsHotReloader', ()=>{
var _inputFileSystem_purge;
inputFileSystem == null ? void 0 : (_inputFileSystem_purge = inputFileSystem.purge) == null ? void 0 : _inputFileSystem_purge.call(inputFileSystem);
});
watchCompilers(this.multiCompiler.compilers[0], this.multiCompiler.compilers[1], this.multiCompiler.compilers[2]);
// Watch for changes to client/server page files so we can tell when just
// the server file changes and trigger a reload for GS(S)P pages
const changedClientPages = new Set();
const changedServerPages = new Set();
const changedEdgeServerPages = new Set();
const changedServerComponentPages = new Set();
const changedCSSImportPages = new Set();
const prevClientPageHashes = new Map();
const prevServerPageHashes = new Map();
const prevEdgeServerPageHashes = new Map();
const prevCSSImportModuleHashes = new Map();
const pageExtensionRegex = new RegExp(`\\.(?:${this.config.pageExtensions.join('|')})$`);
const trackPageChanges = (pageHashMap, changedItems, serverComponentChangedItems)=>(stats)=>{
try {
stats.entrypoints.forEach((entry, key)=>{
if (key.startsWith('pages/') || key.startsWith('app/') || isMiddlewareFilename(key)) {
// TODO this doesn't handle on demand loaded chunks
entry.chunks.forEach((chunk)=>{
if (chunk.id === key) {
const modsIterable = stats.chunkGraph.getChunkModulesIterable(chunk);
let hasCSSModuleChanges = false;
let chunksHash = new StringXor();
let chunksHashServerLayer = new StringXor();
modsIterable.forEach((mod)=>{
if (mod.resource && mod.resource.replace(/\\/g, '/').includes(key) && // Shouldn't match CSS modules, etc.
pageExtensionRegex.test(mod.resource)) {
var _mod_buildInfo_rsc, _mod_buildInfo;
// use original source to calculate hash since mod.hash
// includes the source map in development which changes
// every time for both server and client so we calculate
// the hash without the source map for the page module
const hash = require('crypto').createHash('sha1').update(mod.originalSource().buffer()).digest().toString('hex');
if (mod.layer === WEBPACK_LAYERS.reactServerComponents && (mod == null ? void 0 : (_mod_buildInfo = mod.buildInfo) == null ? void 0 : (_mod_buildInfo_rsc = _mod_buildInfo.rsc) == null ? void 0 : _mod_buildInfo_rsc.type) !== 'client') {
chunksHashServerLayer.add(hash);
}
chunksHash.add(hash);
} else {
var _mod_buildInfo_rsc1, _mod_buildInfo1;
// for non-pages we can use the module hash directly
const hash = stats.chunkGraph.getModuleHash(mod, chunk.runtime);
if (mod.layer === WEBPACK_LAYERS.reactServerComponents && (mod == null ? void 0 : (_mod_buildInfo1 = mod.buildInfo) == null ? void 0 : (_mod_buildInfo_rsc1 = _mod_buildInfo1.rsc) == null ? void 0 : _mod_buildInfo_rsc1.type) !== 'client') {
chunksHashServerLayer.add(hash);
}
chunksHash.add(hash);
// Both CSS import changes from server and client
// components are tracked.
if (key.startsWith('app/') && /\.(css|scss|sass)$/.test(mod.resource || '')) {
const resourceKey = mod.layer + ':' + mod.resource;
const prevHash = prevCSSImportModuleHashes.get(resourceKey);
if (prevHash && prevHash !== hash) {
hasCSSModuleChanges = true;
}
prevCSSImportModuleHashes.set(resourceKey, hash);
}
}
});
const prevHash = pageHashMap.get(key);
const curHash = chunksHash.toString();
if (prevHash && prevHash !== curHash) {
changedItems.add(key);
}
pageHashMap.set(key, curHash);
if (serverComponentChangedItems) {
const serverKey = WEBPACK_LAYERS.reactServerComponents + ':' + key;
const prevServerHash = pageHashMap.get(serverKey);
const curServerHash = chunksHashServerLayer.toString();
if (prevServerHash && prevServerHash !== curServerHash) {
serverComponentChangedItems.add(key);
}
pageHashMap.set(serverKey, curServerHash);
}
if (hasCSSModuleChanges) {
changedCSSImportPages.add(key);
}
}
});
}
});
} catch (err) {
console.error(err);
}
};
this.multiCompiler.compilers[0].hooks.emit.tap('NextjsHotReloaderForClient', trackPageChanges(prevClientPageHashes, changedClientPages));
this.multiCompiler.compilers[1].hooks.emit.tap('NextjsHotReloaderForServer', trackPageChanges(prevServerPageHashes, changedServerPages, c