feat(blog): add file-based blog with dynamic slugs, MDX content and layout shell

- Introduced blog routing using Next.js App Router
- Implemented dynamic [slug] pages for blog posts
- Added MDX-based content loading via lib/posts
- Integrated shared TopBar layout with navigation
- Established clear content, lib and component separation
This commit is contained in:
PascalSchattenburg
2026-01-22 14:14:15 +01:00
parent b717952234
commit d147843c76
10412 changed files with 2475583 additions and 0 deletions

View File

@@ -0,0 +1,154 @@
import fs from 'fs';
import path from 'path';
// Logging server and browser logs to a file
export class FileLogger {
initialize(distDir, mcpServerEnabled) {
this.logFilePath = path.join(distDir, 'logs', `next-development.log`);
this.mcpServerEnabled = mcpServerEnabled;
if (this.isInitialized) {
return;
}
// Only initialize if mcpServer is enabled
if (!this.mcpServerEnabled) {
return;
}
try {
// Clean up the log file on each initialization
// ensure the directory exists
fs.mkdirSync(path.dirname(this.logFilePath), {
recursive: true
});
fs.writeFileSync(this.logFilePath, '');
this.isInitialized = true;
} catch (error) {
console.error(error);
}
}
formatTimestamp() {
// Use performance.now() instead of Date.now() for avoid sync IO of cache components
const now = performance.now();
const hours = Math.floor(now / 3600000).toString().padStart(2, '0');
const minutes = Math.floor(now % 3600000 / 60000).toString().padStart(2, '0');
const seconds = Math.floor(now % 60000 / 1000).toString().padStart(2, '0');
const milliseconds = Math.floor(now % 1000).toString().padStart(3, '0');
return `${hours}:${minutes}:${seconds}.${milliseconds}`;
}
formatLogEntry(entry) {
const { timestamp, source, level, message } = entry;
const levelPadded = level.toUpperCase().padEnd(7, ' ') // Pad level to 7 characters for alignment
;
const sourcePadded = source === 'Browser' ? source : 'Server ';
return `[${timestamp}] ${sourcePadded} ${levelPadded} ${message}\n`;
}
scheduleFlush() {
// Debounce the flush
if (this.flushTimer) {
clearTimeout(this.flushTimer);
this.flushTimer = null;
}
// Delay the log flush to ensure more logs can be batched together asynchronously
this.flushTimer = setTimeout(()=>{
this.flush();
}, 100);
}
getLogQueue() {
return this.logQueue;
}
flush() {
if (this.logQueue.length === 0) {
return;
}
// Only flush to disk if mcpServer is enabled
if (!this.mcpServerEnabled) {
this.logQueue = [] // Clear the queue without writing
;
this.flushTimer = null;
return;
}
try {
// Ensure the directory exists before writing
const logDir = path.dirname(this.logFilePath);
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, {
recursive: true
});
}
const logsToWrite = this.logQueue.join('');
// Writing logs to files synchronously to ensure they're written before returning
fs.appendFileSync(this.logFilePath, logsToWrite);
this.logQueue = [];
} catch (error) {
console.error('Failed to flush logs to file:', error);
} finally{
this.flushTimer = null;
}
}
enqueueLog(formattedEntry) {
this.logQueue.push(formattedEntry);
// Cancel existing timer and start a new one to ensure all logs are flushed together
if (this.flushTimer) {
clearTimeout(this.flushTimer);
this.flushTimer = null;
}
this.scheduleFlush();
}
log(source, level, message) {
// Don't log anything if mcpServer is disabled
if (!this.mcpServerEnabled) {
return;
}
if (!this.isInitialized) {
return;
}
const logEntry = {
timestamp: this.formatTimestamp(),
source,
level,
message
};
const formattedEntry = this.formatLogEntry(logEntry);
this.enqueueLog(formattedEntry);
}
logServer(level, message) {
this.log('Server', level, message);
}
logBrowser(level, message) {
this.log('Browser', level, message);
}
// Force flush all queued logs immediately
forceFlush() {
if (this.flushTimer) {
clearTimeout(this.flushTimer);
this.flushTimer = null;
}
this.flush();
}
// Cleanup method to flush logs on process exit
destroy() {
this.forceFlush();
}
constructor(){
this.logFilePath = '';
this.isInitialized = false;
this.logQueue = [];
this.flushTimer = null;
this.mcpServerEnabled = false;
}
}
// Singleton instance
let fileLogger = null;
export function getFileLogger() {
if (!fileLogger || process.env.NODE_ENV === 'test') {
fileLogger = new FileLogger();
}
return fileLogger;
}
// Only used for testing
export function test__resetFileLogger() {
if (fileLogger) {
fileLogger.destroy();
}
fileLogger = null;
}
//# sourceMappingURL=file-logger.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,475 @@
import { cyan, dim, red, yellow } from '../../../lib/picocolors';
import util from 'util';
import { getConsoleLocation, getSourceMappedStackFrames, withLocation } from './source-map';
import { UNDEFINED_MARKER } from '../../../next-devtools/shared/forward-logs-shared';
import { formatConsoleArgs } from '../../../client/lib/console';
import { getFileLogger } from './file-logger';
export function restoreUndefined(x) {
if (x === UNDEFINED_MARKER) return undefined;
if (Array.isArray(x)) return x.map(restoreUndefined);
if (x && typeof x === 'object') {
for(let k in x){
x[k] = restoreUndefined(x[k]);
}
}
return x;
}
function cleanConsoleArgsForFileLogging(args) {
/**
* Use formatConsoleArgs to strip out background and color format specifiers
* and keep only the original string content for file logging
*/ try {
return formatConsoleArgs(args);
} catch {
// Fallback to simple string conversion if formatting fails
return args.map((arg)=>typeof arg === 'string' ? arg : util.inspect(arg, {
depth: 2
})).join(' ');
}
}
const methods = [
'log',
'info',
'warn',
'debug',
'table',
'error',
'assert',
'dir',
'dirxml',
'group',
'groupCollapsed',
'groupEnd'
];
const methodsToSkipInspect = new Set([
'table',
'dir',
'dirxml',
'group',
'groupCollapsed',
'groupEnd'
]);
// we aren't overriding console, we're just making a (slightly convoluted) helper for replaying user console methods
const forwardConsole = {
...console,
...Object.fromEntries(methods.map((method)=>[
method,
(...args)=>console[method](...args.map((arg)=>methodsToSkipInspect.has(method) || typeof arg !== 'object' || arg === null ? arg : util.inspect(arg, {
depth: Infinity,
colors: true
})))
]))
};
async function deserializeArgData(arg) {
try {
// we want undefined to be represented as it would be in the browser from the user's perspective (otherwise it would be stripped away/shown as null)
if (arg === UNDEFINED_MARKER) {
return restoreUndefined(arg);
}
return restoreUndefined(JSON.parse(arg));
} catch {
return arg;
}
}
const colorError = (mapped, config)=>{
const colorFn = (config == null ? void 0 : config.applyColor) === undefined || config.applyColor ? red : (x)=>x;
switch(mapped.kind){
case 'mapped-stack':
case 'stack':
{
return ((config == null ? void 0 : config.prefix) ? colorFn(config == null ? void 0 : config.prefix) : '') + `\n${colorFn(mapped.stack)}`;
}
case 'with-frame-code':
{
return ((config == null ? void 0 : config.prefix) ? colorFn(config == null ? void 0 : config.prefix) : '') + `\n${colorFn(mapped.stack)}\n${mapped.frameCode}`;
}
// a more sophisticated version of this allows the user to config if they want ignored frames (but we need to be sure to source map them)
case 'all-ignored':
{
return (config == null ? void 0 : config.prefix) ? colorFn(config == null ? void 0 : config.prefix) : '';
}
default:
{}
}
mapped;
};
function processConsoleFormatStrings(args) {
/**
* this handles the case formatting is applied to the console log
* otherwise we will see the format specifier directly in the terminal output
*/ if (args.length > 0 && typeof args[0] === 'string') {
const formatString = args[0];
if (formatString.includes('%s') || formatString.includes('%d') || formatString.includes('%i') || formatString.includes('%f') || formatString.includes('%o') || formatString.includes('%O') || formatString.includes('%c')) {
try {
const formatted = util.format(...args);
return [
formatted
];
} catch {
return args;
}
}
}
return args;
}
// in the case of logging errors, we want to strip formatting
// modifiers since we apply our own custom coloring to error
// stacks and code blocks, and otherwise it would conflict
// and cause awful output
export function stripFormatSpecifiers(args) {
if (args.length === 0 || typeof args[0] !== 'string') return args;
const fmtIn = String(args[0]);
const rest = args.slice(1);
if (!fmtIn.includes('%')) return args;
let fmtOut = '';
let argPtr = 0;
for(let i = 0; i < fmtIn.length; i++){
if (fmtIn[i] !== '%') {
fmtOut += fmtIn[i];
continue;
}
if (fmtIn[i + 1] === '%') {
fmtOut += '%';
i++;
continue;
}
const token = fmtIn[++i];
if (!token) {
fmtOut += '%';
continue;
}
if ('csdifoOj'.includes(token) || token === 'O') {
if (argPtr < rest.length) {
if (token === 'c') {
argPtr++;
} else if (token === 'o' || token === 'O' || token === 'j') {
const obj = rest[argPtr++];
fmtOut += util.inspect(obj, {
depth: 2,
colors: false
});
} else {
// string(...) is safe for remaining specifiers
fmtOut += String(rest[argPtr++]);
}
}
continue;
}
fmtOut += '%' + token;
}
const result = [
fmtOut
];
if (argPtr < rest.length) {
result.push(...rest.slice(argPtr));
}
return result;
}
async function prepareFormattedErrorArgs(entry, ctx, distDir) {
const mapped = await getSourceMappedStackFrames(entry.stack, ctx, distDir);
return [
colorError(mapped, {
prefix: entry.prefix
})
];
}
async function prepareConsoleArgs(entry, ctx, distDir) {
const deserialized = await Promise.all(entry.args.map(async (arg)=>{
if (arg.kind === 'arg') {
const data = await deserializeArgData(arg.data);
if (entry.method === 'warn' && typeof data === 'string') {
return yellow(data);
}
return data;
}
if (!arg.stack) return red(arg.prefix);
const mapped = await getSourceMappedStackFrames(arg.stack, ctx, distDir);
return colorError(mapped, {
prefix: arg.prefix,
applyColor: false
});
}));
return processConsoleFormatStrings(deserialized);
}
async function prepareConsoleErrorArgs(entry, ctx, distDir) {
const deserialized = await Promise.all(entry.args.map(async (arg)=>{
if (arg.kind === 'arg') {
if (arg.isRejectionMessage) return red(arg.data);
return deserializeArgData(arg.data);
}
if (!arg.stack) return red(arg.prefix);
const mapped = await getSourceMappedStackFrames(arg.stack, ctx, distDir);
return colorError(mapped, {
prefix: arg.prefix
});
}));
const mappedStack = await getSourceMappedStackFrames(entry.consoleErrorStack, ctx, distDir);
/**
* don't show the stack + codeblock when there are errors present, since:
* - it will look overwhelming to see 2 stacks and 2 code blocks
* - the user already knows where the console.error is at because we append the location
*/ const location = getConsoleLocation(mappedStack);
if (entry.args.some((a)=>a.kind === 'formatted-error-arg')) {
const result = stripFormatSpecifiers(deserialized);
if (location) {
result.push(dim(`(${location})`));
}
return result;
}
const result = [
...processConsoleFormatStrings(deserialized),
colorError(mappedStack)
];
if (location) {
result.push(dim(`(${location})`));
}
return result;
}
async function handleTable(entry, browserPrefix, ctx, distDir) {
const deserializedArgs = await Promise.all(entry.args.map(async (arg)=>{
if (arg.kind === 'formatted-error-arg') {
return {
stack: arg.stack
};
}
return deserializeArgData(arg.data);
}));
const location = await (async ()=>{
if (!entry.consoleMethodStack) {
return;
}
const frames = await getSourceMappedStackFrames(entry.consoleMethodStack, ctx, distDir);
return getConsoleLocation(frames);
})();
// we can't inline pass browser prefix, but it looks better multiline for table anyways
forwardConsole.log(browserPrefix);
forwardConsole.table(...deserializedArgs);
if (location) {
forwardConsole.log(dim(`(${location})`));
}
}
async function handleTrace(entry, browserPrefix, ctx, distDir) {
const deserializedArgs = await Promise.all(entry.args.map(async (arg)=>{
if (arg.kind === 'formatted-error-arg') {
if (!arg.stack) return red(arg.prefix);
const mapped = await getSourceMappedStackFrames(arg.stack, ctx, distDir);
return colorError(mapped, {
prefix: arg.prefix
});
}
return deserializeArgData(arg.data);
}));
if (!entry.consoleMethodStack) {
forwardConsole.log(browserPrefix, ...deserializedArgs, '[Trace unavailable]');
return;
}
// TODO(rob): refactor so we can re-use result and not re-run the entire source map to avoid trivial post processing
const [mapped, mappedIgnored] = await Promise.all([
getSourceMappedStackFrames(entry.consoleMethodStack, ctx, distDir, false),
getSourceMappedStackFrames(entry.consoleMethodStack, ctx, distDir)
]);
const location = getConsoleLocation(mappedIgnored);
forwardConsole.log(browserPrefix, ...deserializedArgs, `\n${mapped.stack}`, ...location ? [
`\n${dim(`(${location})`)}`
] : []);
}
async function handleDir(entry, browserPrefix, ctx, distDir) {
const loggableEntry = await prepareConsoleArgs(entry, ctx, distDir);
const consoleMethod = forwardConsole[entry.method] || forwardConsole.log;
if (entry.consoleMethodStack) {
const mapped = await getSourceMappedStackFrames(entry.consoleMethodStack, ctx, distDir);
const location = dim(`(${getConsoleLocation(mapped)})`);
const originalWrite = process.stdout.write.bind(process.stdout);
let captured = '';
process.stdout.write = (chunk)=>{
captured += chunk;
return true;
};
try {
consoleMethod(...loggableEntry);
} finally{
process.stdout.write = originalWrite;
}
const preserved = captured.replace(/\r?\n$/, '');
originalWrite(`${browserPrefix}${preserved} ${location}\n`);
return;
}
consoleMethod(browserPrefix, ...loggableEntry);
}
async function handleDefaultConsole(entry, browserPrefix, ctx, distDir, config, isServerLog) {
const consoleArgs = await prepareConsoleArgs(entry, ctx, distDir);
const withStackEntry = await withLocation({
original: consoleArgs,
stack: entry.consoleMethodStack || null
}, ctx, distDir, config);
const consoleMethod = forwardConsole[entry.method] || forwardConsole.log;
consoleMethod(browserPrefix, ...withStackEntry);
// Process enqueued logs and write to file
// Log to file with correct source based on context
const fileLogger = getFileLogger();
// Use cleaned console args to strip out background and color format specifiers
const message = cleanConsoleArgsForFileLogging(consoleArgs);
if (isServerLog) {
fileLogger.logServer(entry.method.toUpperCase(), message);
} else {
fileLogger.logBrowser(entry.method.toUpperCase(), message);
}
}
export async function handleLog(entries, ctx, distDir, config) {
// Determine the source based on the context
const isServerLog = ctx.isServer || ctx.isEdgeServer;
const browserPrefix = isServerLog ? cyan('[server]') : cyan('[browser]');
const fileLogger = getFileLogger();
for (const entry of entries){
try {
switch(entry.kind){
case 'console':
{
switch(entry.method){
case 'table':
{
// timeout based abort on source mapping result
await handleTable(entry, browserPrefix, ctx, distDir);
break;
}
// ignore frames
case 'trace':
{
await handleTrace(entry, browserPrefix, ctx, distDir);
break;
}
case 'dir':
{
await handleDir(entry, browserPrefix, ctx, distDir);
break;
}
case 'dirxml':
{
// xml log thing maybe needs an impl
// fallthrough
}
case 'group':
case 'groupCollapsed':
case 'groupEnd':
{
// [browser] undefined (app/page.tsx:8:11) console.group
// fallthrough
}
case 'assert':
{
// check console assert
// fallthrough
}
case 'log':
case 'info':
case 'debug':
case 'error':
case 'warn':
{
await handleDefaultConsole(entry, browserPrefix, ctx, distDir, config, isServerLog);
break;
}
default:
{
entry;
}
}
break;
}
// any logged errors are anything that are logged as "red" in the browser but aren't only an Error (console.error, Promise.reject(100))
case 'any-logged-error':
{
const consoleArgs = await prepareConsoleErrorArgs(entry, ctx, distDir);
forwardConsole.error(browserPrefix, ...consoleArgs);
// Process enqueued logs and write to file
fileLogger.logBrowser('ERROR', cleanConsoleArgsForFileLogging(consoleArgs));
break;
}
// formatted error is an explicit error event (rejections, uncaught errors)
case 'formatted-error':
{
const formattedArgs = await prepareFormattedErrorArgs(entry, ctx, distDir);
forwardConsole.error(browserPrefix, ...formattedArgs);
// Process enqueued logs and write to file
fileLogger.logBrowser('ERROR', cleanConsoleArgsForFileLogging(formattedArgs));
break;
}
default:
{}
}
} catch {
switch(entry.kind){
case 'any-logged-error':
{
const consoleArgs = await prepareConsoleErrorArgs(entry, ctx, distDir);
forwardConsole.error(browserPrefix, ...consoleArgs);
// Process enqueued logs and write to file
fileLogger.logBrowser('ERROR', cleanConsoleArgsForFileLogging(consoleArgs));
break;
}
case 'console':
{
const consoleMethod = forwardConsole[entry.method] || forwardConsole.log;
const consoleArgs = await prepareConsoleArgs(entry, ctx, distDir);
consoleMethod(browserPrefix, ...consoleArgs);
// Process enqueued logs and write to file
fileLogger.logBrowser('ERROR', cleanConsoleArgsForFileLogging(consoleArgs));
break;
}
case 'formatted-error':
{
forwardConsole.error(browserPrefix, `${entry.prefix}\n`, entry.stack);
// Process enqueued logs and write to file
fileLogger.logBrowser('ERROR', cleanConsoleArgsForFileLogging([
`${entry.prefix}\n${entry.stack}`
]));
break;
}
default:
{}
}
}
}
}
// the data is used later when we need to get sourcemaps for error stacks
export async function receiveBrowserLogsWebpack(opts) {
const { entries, router, sourceType, clientStats, serverStats, edgeServerStats, rootDirectory, distDir } = opts;
const isAppDirectory = router === 'app';
const isServer = sourceType === 'server';
const isEdgeServer = sourceType === 'edge-server';
const ctx = {
bundler: 'webpack',
isServer,
isEdgeServer,
isAppDirectory,
clientStats,
serverStats,
edgeServerStats,
rootDirectory
};
await handleLog(entries, ctx, distDir, opts.config);
}
export async function receiveBrowserLogsTurbopack(opts) {
const { entries, router, sourceType, project, projectPath, distDir } = opts;
const isAppDirectory = router === 'app';
const isServer = sourceType === 'server';
const isEdgeServer = sourceType === 'edge-server';
const ctx = {
bundler: 'turbopack',
project,
projectPath,
isServer,
isEdgeServer,
isAppDirectory
};
await handleLog(entries, ctx, distDir, opts.config);
}
// Handle client file logs (always logged regardless of terminal flag)
export async function handleClientFileLogs(logs) {
const fileLogger = getFileLogger();
for (const log of logs){
fileLogger.logBrowser(log.level, log.message);
}
}
//# sourceMappingURL=receive-logs.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,193 @@
import { getOriginalStackFrames as getOriginalStackFramesWebpack } from '../middleware-webpack';
import { getOriginalStackFrames as getOriginalStackFramesTurbopack } from '../middleware-turbopack';
import { dim } from '../../../lib/picocolors';
import { parseStack } from '../../lib/parse-stack';
import path from 'path';
import { LRUCache } from '../../lib/lru-cache';
// TODO: handle server vs browser error source mapping correctly
export async function mapFramesUsingBundler(frames, ctx) {
switch(ctx.bundler){
case 'webpack':
{
const { isServer, isEdgeServer, isAppDirectory, clientStats, serverStats, edgeServerStats, rootDirectory } = ctx;
const res = await getOriginalStackFramesWebpack({
isServer,
isEdgeServer,
isAppDirectory,
frames,
clientStats,
serverStats,
edgeServerStats,
rootDirectory
});
return res;
}
case 'turbopack':
{
const { project, projectPath, isServer, isEdgeServer, isAppDirectory } = ctx;
const res = await getOriginalStackFramesTurbopack({
project,
projectPath,
frames,
isServer,
isEdgeServer,
isAppDirectory
});
return res;
}
default:
{
return null;
}
}
}
// converts _next/static/chunks/... to file:///.next/static/chunks/... for parseStack
// todo: where does next dev overlay handle this case and re-use that logic
function preprocessStackTrace(stackTrace, distDir) {
return stackTrace.split('\n').map((line)=>{
const match = line.match(/^(\s*at\s+.*?)\s+\(([^)]+)\)$/);
if (match) {
const [, prefix, location] = match;
if (location.startsWith('_next/static/') && distDir) {
const normalizedDistDir = distDir.replace(/\\/g, '/').replace(/\/$/, '');
const absolutePath = normalizedDistDir + '/' + location.slice('_next/'.length);
const fileUrl = `file://${path.resolve(absolutePath)}`;
return `${prefix} (${fileUrl})`;
}
}
return line;
}).join('\n');
}
const cache = new LRUCache(25);
async function getSourceMappedStackFramesInternal(stackTrace, ctx, distDir, ignore = true) {
try {
var _filteredFrames_find;
const normalizedStack = preprocessStackTrace(stackTrace, distDir);
const frames = parseStack(normalizedStack, distDir);
if (frames.length === 0) {
return {
kind: 'stack',
stack: stackTrace
};
}
const mappingResults = await mapFramesUsingBundler(frames, ctx);
const processedFrames = mappingResults.map((result, index)=>({
result,
originalFrame: frames[index]
})).map(({ result, originalFrame })=>{
var _originalStackFrame_file;
if (result.status === 'rejected') {
return {
kind: 'rejected',
frameText: formatStackFrame(originalFrame),
codeFrame: null
};
}
const { originalStackFrame, originalCodeFrame } = result.value;
if ((originalStackFrame == null ? void 0 : originalStackFrame.ignored) && ignore) {
return {
kind: 'ignored'
};
}
// should we apply this generally to dev overlay (dev overlay does not ignore chrome-extension://)
if (originalStackFrame == null ? void 0 : (_originalStackFrame_file = originalStackFrame.file) == null ? void 0 : _originalStackFrame_file.startsWith('chrome-extension://')) {
return {
kind: 'ignored'
};
}
return {
kind: 'success',
// invariant: if result is not rejected and not ignored, then original stack frame exists
// verifiable by tracing `getOriginalStackFrame`. The invariant exists because of bad types
frameText: formatStackFrame(originalStackFrame),
codeFrame: originalCodeFrame
};
});
const allIgnored = processedFrames.every((frame)=>frame.kind === 'ignored');
// we want to handle **all** ignored vs all/some rejected differently
// if all are ignored we should show no frames
// if all are rejected, we want to fallback to showing original stack frames
if (allIgnored) {
return {
kind: 'all-ignored'
};
}
const filteredFrames = processedFrames.filter((frame)=>frame.kind !== 'ignored');
if (filteredFrames.length === 0) {
return {
kind: 'stack',
stack: stackTrace
};
}
const stackOutput = filteredFrames.map((frame)=>frame.frameText).join('\n');
const firstFrameCode = (_filteredFrames_find = filteredFrames.find((frame)=>frame.codeFrame)) == null ? void 0 : _filteredFrames_find.codeFrame;
if (firstFrameCode) {
return {
kind: 'with-frame-code',
frameCode: firstFrameCode,
stack: stackOutput,
frames: filteredFrames
};
}
// i don't think this a real case, but good for exhaustion
return {
kind: 'mapped-stack',
stack: stackOutput,
frames: filteredFrames
};
} catch (error) {
return {
kind: 'stack',
stack: stackTrace
};
}
}
// todo: cache the actual async call, not the wrapper with post processing
export async function getSourceMappedStackFrames(stackTrace, ctx, distDir, ignore = true) {
const cacheKey = `sm_${stackTrace}-${ctx.bundler}-${ctx.isAppDirectory}-${ctx.isEdgeServer}-${ctx.isServer}-${distDir}-${ignore}`;
const cacheItem = cache.get(cacheKey);
if (cacheItem) {
return cacheItem;
}
const result = await getSourceMappedStackFramesInternal(stackTrace, ctx, distDir, ignore);
cache.set(cacheKey, result);
return result;
}
function formatStackFrame(frame) {
const functionName = frame.methodName || '<anonymous>';
const location = frame.file && frame.line1 ? `${frame.file}:${frame.line1}${frame.column1 ? `:${frame.column1}` : ''}` : frame.file || '<unknown>';
return ` at ${functionName} (${location})`;
}
// appends the source mapped location of the console method
export const withLocation = async ({ original, stack }, ctx, distDir, config)=>{
if (typeof config === 'object' && config.showSourceLocation === false) {
return original;
}
if (!stack) {
return original;
}
const res = await getSourceMappedStackFrames(stack, ctx, distDir);
const location = getConsoleLocation(res);
if (!location) {
return original;
}
return [
...original,
dim(`(${location})`)
];
};
export const getConsoleLocation = (mapped)=>{
if (mapped.kind !== 'mapped-stack' && mapped.kind !== 'with-frame-code') {
return null;
}
const first = mapped.frames.at(0);
if (!first) {
return null;
}
// we don't want to show the name of parent function (at <fn> thing in stack), just source location for minimal noise
const match = first.frameText.match(/\(([^)]+)\)/);
const locationText = match ? match[1] : first.frameText;
return locationText;
};
//# sourceMappingURL=source-map.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,57 @@
import { createBufferedTransformStream } from '../stream-utils/node-web-streams-helper';
import { HMR_MESSAGE_SENT_TO_BROWSER } from './hot-reloader-types';
const reactDebugChannelsByHtmlRequestId = new Map();
export function connectReactDebugChannel(requestId, debugChannel, sendToClient) {
const reader = debugChannel.readable.pipeThrough(// We're sending the chunks in batches to reduce overhead in the browser.
createBufferedTransformStream({
maxBufferByteLength: 128 * 1024
})).getReader();
const stop = ()=>{
sendToClient({
type: HMR_MESSAGE_SENT_TO_BROWSER.REACT_DEBUG_CHUNK,
requestId,
chunk: null
});
};
const onError = (err)=>{
console.error(Object.defineProperty(new Error('React debug channel stream error', {
cause: err
}), "__NEXT_ERROR_CODE", {
value: "E810",
enumerable: false,
configurable: true
}));
stop();
};
const progress = (entry)=>{
if (entry.done) {
stop();
} else {
sendToClient({
type: HMR_MESSAGE_SENT_TO_BROWSER.REACT_DEBUG_CHUNK,
requestId,
chunk: entry.value
});
reader.read().then(progress, onError);
}
};
reader.read().then(progress, onError);
}
export function connectReactDebugChannelForHtmlRequest(htmlRequestId, sendToClient) {
const debugChannel = reactDebugChannelsByHtmlRequestId.get(htmlRequestId);
if (!debugChannel) {
return;
}
reactDebugChannelsByHtmlRequestId.delete(htmlRequestId);
connectReactDebugChannel(htmlRequestId, debugChannel, sendToClient);
}
export function setReactDebugChannelForHtmlRequest(htmlRequestId, debugChannel) {
// TODO: Clean up after a timeout, in case the client never connects, e.g.
// when CURL'ing the page, or loading the page with JavaScript disabled etc.
reactDebugChannelsByHtmlRequestId.set(htmlRequestId, debugChannel);
}
export function deleteReactDebugChannelForHtmlRequest(htmlRequestId) {
reactDebugChannelsByHtmlRequestId.delete(htmlRequestId);
}
//# sourceMappingURL=debug-channel.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/server/dev/debug-channel.ts"],"sourcesContent":["import { createBufferedTransformStream } from '../stream-utils/node-web-streams-helper'\nimport {\n HMR_MESSAGE_SENT_TO_BROWSER,\n type HmrMessageSentToBrowser,\n} from './hot-reloader-types'\n\nexport interface ReactDebugChannelForBrowser {\n readonly readable: ReadableStream<Uint8Array>\n // Might also get a writable stream as return channel in the future.\n}\n\nconst reactDebugChannelsByHtmlRequestId = new Map<\n string,\n ReactDebugChannelForBrowser\n>()\n\nexport function connectReactDebugChannel(\n requestId: string,\n debugChannel: ReactDebugChannelForBrowser,\n sendToClient: (message: HmrMessageSentToBrowser) => void\n) {\n const reader = debugChannel.readable\n .pipeThrough(\n // We're sending the chunks in batches to reduce overhead in the browser.\n createBufferedTransformStream({ maxBufferByteLength: 128 * 1024 })\n )\n .getReader()\n\n const stop = () => {\n sendToClient({\n type: HMR_MESSAGE_SENT_TO_BROWSER.REACT_DEBUG_CHUNK,\n requestId,\n chunk: null,\n })\n }\n\n const onError = (err: unknown) => {\n console.error(new Error('React debug channel stream error', { cause: err }))\n stop()\n }\n\n const progress = (entry: ReadableStreamReadResult<Uint8Array>) => {\n if (entry.done) {\n stop()\n } else {\n sendToClient({\n type: HMR_MESSAGE_SENT_TO_BROWSER.REACT_DEBUG_CHUNK,\n requestId,\n chunk: entry.value,\n })\n\n reader.read().then(progress, onError)\n }\n }\n\n reader.read().then(progress, onError)\n}\n\nexport function connectReactDebugChannelForHtmlRequest(\n htmlRequestId: string,\n sendToClient: (message: HmrMessageSentToBrowser) => void\n) {\n const debugChannel = reactDebugChannelsByHtmlRequestId.get(htmlRequestId)\n\n if (!debugChannel) {\n return\n }\n\n reactDebugChannelsByHtmlRequestId.delete(htmlRequestId)\n\n connectReactDebugChannel(htmlRequestId, debugChannel, sendToClient)\n}\n\nexport function setReactDebugChannelForHtmlRequest(\n htmlRequestId: string,\n debugChannel: ReactDebugChannelForBrowser\n) {\n // TODO: Clean up after a timeout, in case the client never connects, e.g.\n // when CURL'ing the page, or loading the page with JavaScript disabled etc.\n reactDebugChannelsByHtmlRequestId.set(htmlRequestId, debugChannel)\n}\n\nexport function deleteReactDebugChannelForHtmlRequest(htmlRequestId: string) {\n reactDebugChannelsByHtmlRequestId.delete(htmlRequestId)\n}\n"],"names":["createBufferedTransformStream","HMR_MESSAGE_SENT_TO_BROWSER","reactDebugChannelsByHtmlRequestId","Map","connectReactDebugChannel","requestId","debugChannel","sendToClient","reader","readable","pipeThrough","maxBufferByteLength","getReader","stop","type","REACT_DEBUG_CHUNK","chunk","onError","err","console","error","Error","cause","progress","entry","done","value","read","then","connectReactDebugChannelForHtmlRequest","htmlRequestId","get","delete","setReactDebugChannelForHtmlRequest","set","deleteReactDebugChannelForHtmlRequest"],"mappings":"AAAA,SAASA,6BAA6B,QAAQ,0CAAyC;AACvF,SACEC,2BAA2B,QAEtB,uBAAsB;AAO7B,MAAMC,oCAAoC,IAAIC;AAK9C,OAAO,SAASC,yBACdC,SAAiB,EACjBC,YAAyC,EACzCC,YAAwD;IAExD,MAAMC,SAASF,aAAaG,QAAQ,CACjCC,WAAW,CACV,yEAAyE;IACzEV,8BAA8B;QAAEW,qBAAqB,MAAM;IAAK,IAEjEC,SAAS;IAEZ,MAAMC,OAAO;QACXN,aAAa;YACXO,MAAMb,4BAA4Bc,iBAAiB;YACnDV;YACAW,OAAO;QACT;IACF;IAEA,MAAMC,UAAU,CAACC;QACfC,QAAQC,KAAK,CAAC,qBAA6D,CAA7D,IAAIC,MAAM,oCAAoC;YAAEC,OAAOJ;QAAI,IAA3D,qBAAA;mBAAA;wBAAA;0BAAA;QAA4D;QAC1EL;IACF;IAEA,MAAMU,WAAW,CAACC;QAChB,IAAIA,MAAMC,IAAI,EAAE;YACdZ;QACF,OAAO;YACLN,aAAa;gBACXO,MAAMb,4BAA4Bc,iBAAiB;gBACnDV;gBACAW,OAAOQ,MAAME,KAAK;YACpB;YAEAlB,OAAOmB,IAAI,GAAGC,IAAI,CAACL,UAAUN;QAC/B;IACF;IAEAT,OAAOmB,IAAI,GAAGC,IAAI,CAACL,UAAUN;AAC/B;AAEA,OAAO,SAASY,uCACdC,aAAqB,EACrBvB,YAAwD;IAExD,MAAMD,eAAeJ,kCAAkC6B,GAAG,CAACD;IAE3D,IAAI,CAACxB,cAAc;QACjB;IACF;IAEAJ,kCAAkC8B,MAAM,CAACF;IAEzC1B,yBAAyB0B,eAAexB,cAAcC;AACxD;AAEA,OAAO,SAAS0B,mCACdH,aAAqB,EACrBxB,YAAyC;IAEzC,0EAA0E;IAC1E,4EAA4E;IAC5EJ,kCAAkCgC,GAAG,CAACJ,eAAexB;AACvD;AAEA,OAAO,SAAS6B,sCAAsCL,aAAqB;IACzE5B,kCAAkC8B,MAAM,CAACF;AAC3C","ignoreList":[0]}

View File

@@ -0,0 +1,5 @@
export const devIndicatorServerState = {
disabledUntil: 0
};
//# sourceMappingURL=dev-indicator-server-state.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/server/dev/dev-indicator-server-state.ts"],"sourcesContent":["export type DevIndicatorServerState = typeof devIndicatorServerState\n\nexport const devIndicatorServerState = {\n disabledUntil: 0,\n}\n"],"names":["devIndicatorServerState","disabledUntil"],"mappings":"AAEA,OAAO,MAAMA,0BAA0B;IACrCC,eAAe;AACjB,EAAC","ignoreList":[0]}

View File

@@ -0,0 +1,85 @@
import fs from 'fs/promises';
import path from 'path';
import url from 'url';
import dataUriToBuffer from 'next/dist/compiled/data-uri-to-buffer';
function getSourceMapUrl(fileContents) {
const regex = /\/\/[#@] ?sourceMappingURL=([^\s'"]+)\s*$/gm;
let match = null;
for(;;){
let next = regex.exec(fileContents);
if (next == null) {
break;
}
match = next;
}
if (!(match && match[1])) {
return null;
}
return match[1].toString();
}
export async function getSourceMapFromFile(filename) {
filename = filename.startsWith('file://') ? url.fileURLToPath(filename) : filename;
let fileContents;
try {
fileContents = await fs.readFile(filename, 'utf-8');
} catch (error) {
throw Object.defineProperty(new Error(`Failed to read file contents of ${filename}.`, {
cause: error
}), "__NEXT_ERROR_CODE", {
value: "E466",
enumerable: false,
configurable: true
});
}
const sourceUrl = getSourceMapUrl(fileContents);
if (!sourceUrl) {
return undefined;
}
if (sourceUrl.startsWith('data:')) {
let buffer;
try {
buffer = dataUriToBuffer(sourceUrl);
} catch (error) {
throw Object.defineProperty(new Error(`Failed to parse source map URL for ${filename}.`, {
cause: error
}), "__NEXT_ERROR_CODE", {
value: "E199",
enumerable: false,
configurable: true
});
}
if (buffer.type !== 'application/json') {
throw Object.defineProperty(new Error(`Unknown source map type for ${filename}: ${buffer.typeFull}.`), "__NEXT_ERROR_CODE", {
value: "E113",
enumerable: false,
configurable: true
});
}
try {
return JSON.parse(buffer.toString());
} catch (error) {
throw Object.defineProperty(new Error(`Failed to parse source map for ${filename}.`, {
cause: error
}), "__NEXT_ERROR_CODE", {
value: "E318",
enumerable: false,
configurable: true
});
}
}
const sourceMapFilename = path.resolve(path.dirname(filename), decodeURIComponent(sourceUrl));
try {
const sourceMapContents = await fs.readFile(sourceMapFilename, 'utf-8');
return JSON.parse(sourceMapContents.toString());
} catch (error) {
throw Object.defineProperty(new Error(`Failed to parse source map ${sourceMapFilename}.`, {
cause: error
}), "__NEXT_ERROR_CODE", {
value: "E220",
enumerable: false,
configurable: true
});
}
}
//# sourceMappingURL=get-source-map-from-file.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/server/dev/get-source-map-from-file.ts"],"sourcesContent":["import fs from 'fs/promises'\nimport path from 'path'\nimport url from 'url'\nimport type { RawSourceMap } from 'next/dist/compiled/source-map08'\nimport dataUriToBuffer from 'next/dist/compiled/data-uri-to-buffer'\n\nfunction getSourceMapUrl(fileContents: string): string | null {\n const regex = /\\/\\/[#@] ?sourceMappingURL=([^\\s'\"]+)\\s*$/gm\n let match = null\n for (;;) {\n let next = regex.exec(fileContents)\n if (next == null) {\n break\n }\n match = next\n }\n if (!(match && match[1])) {\n return null\n }\n return match[1].toString()\n}\n\nexport async function getSourceMapFromFile(\n filename: string\n): Promise<RawSourceMap | undefined> {\n filename = filename.startsWith('file://')\n ? url.fileURLToPath(filename)\n : filename\n\n let fileContents: string\n\n try {\n fileContents = await fs.readFile(filename, 'utf-8')\n } catch (error) {\n throw new Error(`Failed to read file contents of ${filename}.`, {\n cause: error,\n })\n }\n\n const sourceUrl = getSourceMapUrl(fileContents)\n\n if (!sourceUrl) {\n return undefined\n }\n\n if (sourceUrl.startsWith('data:')) {\n let buffer: dataUriToBuffer.MimeBuffer\n\n try {\n buffer = dataUriToBuffer(sourceUrl)\n } catch (error) {\n throw new Error(`Failed to parse source map URL for ${filename}.`, {\n cause: error,\n })\n }\n\n if (buffer.type !== 'application/json') {\n throw new Error(\n `Unknown source map type for ${filename}: ${buffer.typeFull}.`\n )\n }\n\n try {\n return JSON.parse(buffer.toString())\n } catch (error) {\n throw new Error(`Failed to parse source map for ${filename}.`, {\n cause: error,\n })\n }\n }\n\n const sourceMapFilename = path.resolve(\n path.dirname(filename),\n decodeURIComponent(sourceUrl)\n )\n\n try {\n const sourceMapContents = await fs.readFile(sourceMapFilename, 'utf-8')\n\n return JSON.parse(sourceMapContents.toString())\n } catch (error) {\n throw new Error(`Failed to parse source map ${sourceMapFilename}.`, {\n cause: error,\n })\n }\n}\n"],"names":["fs","path","url","dataUriToBuffer","getSourceMapUrl","fileContents","regex","match","next","exec","toString","getSourceMapFromFile","filename","startsWith","fileURLToPath","readFile","error","Error","cause","sourceUrl","undefined","buffer","type","typeFull","JSON","parse","sourceMapFilename","resolve","dirname","decodeURIComponent","sourceMapContents"],"mappings":"AAAA,OAAOA,QAAQ,cAAa;AAC5B,OAAOC,UAAU,OAAM;AACvB,OAAOC,SAAS,MAAK;AAErB,OAAOC,qBAAqB,wCAAuC;AAEnE,SAASC,gBAAgBC,YAAoB;IAC3C,MAAMC,QAAQ;IACd,IAAIC,QAAQ;IACZ,OAAS;QACP,IAAIC,OAAOF,MAAMG,IAAI,CAACJ;QACtB,IAAIG,QAAQ,MAAM;YAChB;QACF;QACAD,QAAQC;IACV;IACA,IAAI,CAAED,CAAAA,SAASA,KAAK,CAAC,EAAE,AAAD,GAAI;QACxB,OAAO;IACT;IACA,OAAOA,KAAK,CAAC,EAAE,CAACG,QAAQ;AAC1B;AAEA,OAAO,eAAeC,qBACpBC,QAAgB;IAEhBA,WAAWA,SAASC,UAAU,CAAC,aAC3BX,IAAIY,aAAa,CAACF,YAClBA;IAEJ,IAAIP;IAEJ,IAAI;QACFA,eAAe,MAAML,GAAGe,QAAQ,CAACH,UAAU;IAC7C,EAAE,OAAOI,OAAO;QACd,MAAM,qBAEJ,CAFI,IAAIC,MAAM,CAAC,gCAAgC,EAAEL,SAAS,CAAC,CAAC,EAAE;YAC9DM,OAAOF;QACT,IAFM,qBAAA;mBAAA;wBAAA;0BAAA;QAEL;IACH;IAEA,MAAMG,YAAYf,gBAAgBC;IAElC,IAAI,CAACc,WAAW;QACd,OAAOC;IACT;IAEA,IAAID,UAAUN,UAAU,CAAC,UAAU;QACjC,IAAIQ;QAEJ,IAAI;YACFA,SAASlB,gBAAgBgB;QAC3B,EAAE,OAAOH,OAAO;YACd,MAAM,qBAEJ,CAFI,IAAIC,MAAM,CAAC,mCAAmC,EAAEL,SAAS,CAAC,CAAC,EAAE;gBACjEM,OAAOF;YACT,IAFM,qBAAA;uBAAA;4BAAA;8BAAA;YAEL;QACH;QAEA,IAAIK,OAAOC,IAAI,KAAK,oBAAoB;YACtC,MAAM,qBAEL,CAFK,IAAIL,MACR,CAAC,4BAA4B,EAAEL,SAAS,EAAE,EAAES,OAAOE,QAAQ,CAAC,CAAC,CAAC,GAD1D,qBAAA;uBAAA;4BAAA;8BAAA;YAEN;QACF;QAEA,IAAI;YACF,OAAOC,KAAKC,KAAK,CAACJ,OAAOX,QAAQ;QACnC,EAAE,OAAOM,OAAO;YACd,MAAM,qBAEJ,CAFI,IAAIC,MAAM,CAAC,+BAA+B,EAAEL,SAAS,CAAC,CAAC,EAAE;gBAC7DM,OAAOF;YACT,IAFM,qBAAA;uBAAA;4BAAA;8BAAA;YAEL;QACH;IACF;IAEA,MAAMU,oBAAoBzB,KAAK0B,OAAO,CACpC1B,KAAK2B,OAAO,CAAChB,WACbiB,mBAAmBV;IAGrB,IAAI;QACF,MAAMW,oBAAoB,MAAM9B,GAAGe,QAAQ,CAACW,mBAAmB;QAE/D,OAAOF,KAAKC,KAAK,CAACK,kBAAkBpB,QAAQ;IAC9C,EAAE,OAAOM,OAAO;QACd,MAAM,qBAEJ,CAFI,IAAIC,MAAM,CAAC,2BAA2B,EAAES,kBAAkB,CAAC,CAAC,EAAE;YAClER,OAAOF;QACT,IAFM,qBAAA;mBAAA;wBAAA;0BAAA;QAEL;IACH;AACF","ignoreList":[0]}

View File

@@ -0,0 +1,264 @@
// Based on https://github.com/webpack-contrib/webpack-hot-middleware/blob/9708d781ae0e46179cf8ea1a94719de4679aaf53/middleware.js
// Included License below
// Copyright JS Foundation and other contributors
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// 'Software'), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import { isMiddlewareFilename } from '../../build/utils';
import { HMR_MESSAGE_SENT_TO_BROWSER } from './hot-reloader-types';
import { devIndicatorServerState } from './dev-indicator-server-state';
import { createBinaryHmrMessageData } from './messages';
function isMiddlewareStats(stats) {
for (const key of stats.compilation.entrypoints.keys()){
if (isMiddlewareFilename(key)) {
return true;
}
}
return false;
}
function statsToJson(stats) {
if (!stats) return {};
return stats.toJson({
all: false,
errors: true,
hash: true,
warnings: true
});
}
function getStatsForSyncEvent(clientStats, serverStats) {
if (!clientStats) return serverStats == null ? void 0 : serverStats.stats;
if (!serverStats) return clientStats == null ? void 0 : clientStats.stats;
// Prefer the server compiler stats if it has errors.
// Otherwise we may end up in a state where the client compilation is the latest but without errors.
// This causes the error overlay to not display the build error.
if (serverStats.stats.hasErrors()) {
return serverStats.stats;
}
// Return the latest stats
return serverStats.ts > clientStats.ts ? serverStats.stats : clientStats.stats;
}
export class WebpackHotMiddleware {
constructor(compilers, versionInfo, devtoolsFrontendUrl, config, devToolsConfig){
this.versionInfo = versionInfo;
this.devtoolsFrontendUrl = devtoolsFrontendUrl;
this.config = config;
this.devToolsConfig = devToolsConfig;
this.clientsWithoutHtmlRequestId = new Set();
this.clientsByHtmlRequestId = new Map();
this.closed = false;
this.clientLatestStats = null;
this.middlewareLatestStats = null;
this.serverLatestStats = null;
this.onClientInvalid = ()=>{
var _this_serverLatestStats;
if (this.closed || ((_this_serverLatestStats = this.serverLatestStats) == null ? void 0 : _this_serverLatestStats.stats.hasErrors())) return;
this.publish({
type: HMR_MESSAGE_SENT_TO_BROWSER.BUILDING
});
};
this.onClientDone = (statsResult)=>{
var _this_serverLatestStats;
this.clientLatestStats = {
ts: Date.now(),
stats: statsResult
};
if (this.closed || ((_this_serverLatestStats = this.serverLatestStats) == null ? void 0 : _this_serverLatestStats.stats.hasErrors())) return;
this.publishStats(statsResult);
};
this.onServerInvalid = ()=>{
var _this_serverLatestStats, _this_clientLatestStats;
if (!((_this_serverLatestStats = this.serverLatestStats) == null ? void 0 : _this_serverLatestStats.stats.hasErrors())) return;
this.serverLatestStats = null;
if ((_this_clientLatestStats = this.clientLatestStats) == null ? void 0 : _this_clientLatestStats.stats) {
this.publishStats(this.clientLatestStats.stats);
}
};
this.onServerDone = (statsResult)=>{
if (this.closed) return;
if (statsResult.hasErrors()) {
this.serverLatestStats = {
ts: Date.now(),
stats: statsResult
};
this.publishStats(statsResult);
}
};
this.onEdgeServerInvalid = ()=>{
var _this_middlewareLatestStats, _this_clientLatestStats;
if (!((_this_middlewareLatestStats = this.middlewareLatestStats) == null ? void 0 : _this_middlewareLatestStats.stats.hasErrors())) return;
this.middlewareLatestStats = null;
if ((_this_clientLatestStats = this.clientLatestStats) == null ? void 0 : _this_clientLatestStats.stats) {
this.publishStats(this.clientLatestStats.stats);
}
};
this.onEdgeServerDone = (statsResult)=>{
if (this.closed) return;
if (!isMiddlewareStats(statsResult)) {
this.onServerInvalid();
this.onServerDone(statsResult);
}
if (statsResult.hasErrors()) {
this.middlewareLatestStats = {
ts: Date.now(),
stats: statsResult
};
this.publishStats(statsResult);
}
};
this./**
* To sync we use the most recent stats but also we append middleware
* errors. This is because it is possible that middleware fails to compile
* and we still want to show the client overlay with the error while
* the error page should be rendered just fine.
*/ onHMR = (client, htmlRequestId)=>{
if (this.closed) return;
if (htmlRequestId) {
this.clientsByHtmlRequestId.set(htmlRequestId, client);
} else {
this.clientsWithoutHtmlRequestId.add(client);
}
client.addEventListener('close', ()=>{
if (htmlRequestId) {
this.clientsByHtmlRequestId.delete(htmlRequestId);
} else {
this.clientsWithoutHtmlRequestId.delete(client);
}
});
const syncStats = getStatsForSyncEvent(this.clientLatestStats, this.serverLatestStats);
if (syncStats) {
var _this_middlewareLatestStats;
const stats = statsToJson(syncStats);
const middlewareStats = statsToJson((_this_middlewareLatestStats = this.middlewareLatestStats) == null ? void 0 : _this_middlewareLatestStats.stats);
if (devIndicatorServerState.disabledUntil < Date.now()) {
devIndicatorServerState.disabledUntil = 0;
}
this.publish({
type: HMR_MESSAGE_SENT_TO_BROWSER.SYNC,
hash: stats.hash,
errors: [
...stats.errors || [],
...middlewareStats.errors || []
],
warnings: [
...stats.warnings || [],
...middlewareStats.warnings || []
],
versionInfo: this.versionInfo,
debug: {
devtoolsFrontendUrl: this.devtoolsFrontendUrl
},
devIndicator: devIndicatorServerState,
devToolsConfig: this.devToolsConfig
});
}
};
this.publishStats = (statsResult)=>{
const stats = statsResult.toJson({
all: false,
hash: true,
warnings: true,
errors: true,
moduleTrace: true
});
this.publish({
type: HMR_MESSAGE_SENT_TO_BROWSER.BUILT,
hash: stats.hash,
warnings: stats.warnings || [],
errors: stats.errors || []
});
};
this.getClient = (htmlRequestId)=>{
return this.clientsByHtmlRequestId.get(htmlRequestId);
};
this.publishToClient = (client, message)=>{
if (this.closed) {
return;
}
const data = typeof message.type === 'number' ? createBinaryHmrMessageData(message) : JSON.stringify(message);
client.send(data);
};
this.publish = (message)=>{
if (this.closed) {
return;
}
for (const wsClient of [
...this.clientsWithoutHtmlRequestId,
...this.clientsByHtmlRequestId.values()
]){
this.publishToClient(wsClient, message);
}
};
this.publishToLegacyClients = (message)=>{
if (this.closed) {
return;
}
// Clients with a request ID are inferred App Router clients. If Cache
// Components is not enabled, we consider those legacy clients. Pages
// Router clients are also considered legacy clients. TODO: Maybe mark
// clients as App Router / Pages Router clients explicitly, instead of
// inferring it from the presence of a request ID.
if (!this.config.cacheComponents) {
for (const wsClient of this.clientsByHtmlRequestId.values()){
this.publishToClient(wsClient, message);
}
}
for (const wsClient of this.clientsWithoutHtmlRequestId){
this.publishToClient(wsClient, message);
}
};
this.close = ()=>{
if (this.closed) {
return;
}
// Can't remove compiler plugins, so we just set a flag and noop if closed
// https://github.com/webpack/tapable/issues/32#issuecomment-350644466
this.closed = true;
for (const wsClient of [
...this.clientsWithoutHtmlRequestId,
...this.clientsByHtmlRequestId.values()
]){
// it's okay to not cleanly close these websocket connections, this is dev
wsClient.terminate();
}
this.clientsWithoutHtmlRequestId.clear();
this.clientsByHtmlRequestId.clear();
};
this.deleteClient = (client, htmlRequestId)=>{
if (htmlRequestId) {
this.clientsByHtmlRequestId.delete(htmlRequestId);
} else {
this.clientsWithoutHtmlRequestId.delete(client);
}
};
this.hasClients = ()=>{
return this.clientsWithoutHtmlRequestId.size + this.clientsByHtmlRequestId.size > 0;
};
this.getClientCount = ()=>{
return this.clientsWithoutHtmlRequestId.size + this.clientsByHtmlRequestId.size;
};
compilers[0].hooks.invalid.tap('webpack-hot-middleware', this.onClientInvalid);
compilers[0].hooks.done.tap('webpack-hot-middleware', this.onClientDone);
compilers[1].hooks.invalid.tap('webpack-hot-middleware', this.onServerInvalid);
compilers[1].hooks.done.tap('webpack-hot-middleware', this.onServerDone);
compilers[2].hooks.done.tap('webpack-hot-middleware', this.onEdgeServerDone);
compilers[2].hooks.invalid.tap('webpack-hot-middleware', this.onEdgeServerInvalid);
}
updateDevToolsConfig(newConfig) {
this.devToolsConfig = newConfig;
}
}
//# sourceMappingURL=hot-middleware.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,156 @@
import path from 'path';
import fs from 'fs/promises';
import { createHash } from 'crypto';
import HotReloaderWebpack from './hot-reloader-webpack';
import { BUILT, EntryTypes, getEntries } from './on-demand-entry-handler';
import { COMPILER_NAMES } from '../../shared/lib/constants';
/**
* Rspack Persistent Cache Strategy for Next.js Development
*
* Rspack's persistent caching differs from Webpack in how it manages module graphs.
* While Webpack incrementally updates modules, Rspack operates on complete module
* graph snapshots for cache restoration.
*
* Problem:
* - Next.js dev server starts with no page modules in the initial entry points
* - When Rspack restores from persistent cache, it finds no modules and purges
* the entire module graph
* - Later page requests find no cached module information, preventing cache reuse
*
* Solution:
* - Track successfully built page entries after each compilation
* - Restore these entries on dev server restart to maintain module graph continuity
* - This ensures previously compiled pages can leverage persistent cache for faster builds
*/ export default class HotReloaderRspack extends HotReloaderWebpack {
async afterCompile(multiCompiler) {
// Always initialize the fallback error watcher for Rspack.
// Rspack may restore/retain the previous build's error state, so without this
// a page that previously failed to build might not be rebuilt on the next request.
await super.buildFallbackError();
const rspackStartSpan = this.hotReloaderSpan.traceChild('rspack-after-compile');
await rspackStartSpan.traceAsyncFn(async ()=>{
const hash = createHash('sha1');
multiCompiler.compilers.forEach((compiler)=>{
const cache = compiler.options.cache;
if (typeof cache === 'object' && 'version' in cache && cache.version) {
hash.update(cache.version);
if (compiler.name === COMPILER_NAMES.client) {
this.isClientCacheEnabled = true;
} else if (compiler.name === COMPILER_NAMES.server) {
this.isServerCacheEnabled = true;
} else if (compiler.name === COMPILER_NAMES.edgeServer) {
this.isEdgeServerCacheEnabled = true;
}
} else {
hash.update('-');
}
return undefined;
});
this.builtEntriesCachePath = path.join(this.distDir, 'cache', 'rspack', hash.digest('hex').substring(0, 16), 'built-entries.json');
const hasBuiltEntriesCache = await fs.access(this.builtEntriesCachePath).then(()=>true, ()=>false);
if (hasBuiltEntriesCache) {
try {
const builtEntries = JSON.parse(await fs.readFile(this.builtEntriesCachePath, 'utf-8') || '{}');
await Promise.all(Object.keys(builtEntries).map(async (entryKey)=>{
const entryData = builtEntries[entryKey];
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 = !entryData.dispose && await fs.access(entryData.absolutePagePath).then(()=>true, ()=>false);
if (!pageExists) {
delete builtEntries[entryKey];
return;
} else if (!('hash' in builtEntries[entryKey]) || builtEntries[entryKey].hash !== await calculateFileHash(entryData.absolutePagePath)) {
delete builtEntries[entryKey];
return;
}
}
// For child entries, if it has an entry file and it's gone, remove it
if (isChildEntry) {
if (entryData.absoluteEntryFilePath) {
const pageExists = !entryData.dispose && await fs.access(entryData.absoluteEntryFilePath).then(()=>true, ()=>false);
if (!pageExists) {
delete builtEntries[entryKey];
return;
} else {
if (!('hash' in builtEntries[entryKey]) || builtEntries[entryKey].hash !== await calculateFileHash(entryData.absoluteEntryFilePath)) {
delete builtEntries[entryKey];
return;
}
}
}
}
}));
Object.assign(getEntries(multiCompiler.outputPath), builtEntries);
} catch (error) {
console.error('Rspack failed to read built entries cache: ', error);
}
}
});
}
async ensurePage({ page, clientOnly, appPaths, definition, isApp, url }) {
await super.ensurePage({
page,
clientOnly,
appPaths,
definition,
isApp,
url
});
const entries = getEntries(this.multiCompiler.outputPath);
const builtEntries = {};
await Promise.all(Object.keys(entries).map(async (entryName)=>{
const entry = entries[entryName];
if (entry.status !== BUILT) return;
const result = /^(client|server|edge-server)@(app|pages|root)@(.*)/g.exec(entryName);
const [, key /* pageType */ , , ] = result// this match should always happen
;
if (key === 'client' && !this.isClientCacheEnabled) return;
if (key === 'server' && !this.isServerCacheEnabled) return;
if (key === 'edge-server' && !this.isEdgeServerCacheEnabled) return;
// TODO: Rspack does not store middleware entries in persistent cache, causing
// test/integration/middleware-src/test/index.test.ts to fail. This is a temporary
// workaround to skip middleware entry caching until Rspack properly supports it.
if (page === '/middleware') {
return;
}
let hash;
if (entry.type === EntryTypes.ENTRY) {
hash = await calculateFileHash(entry.absolutePagePath);
} else if (entry.absoluteEntryFilePath) {
hash = await calculateFileHash(entry.absoluteEntryFilePath);
}
if (!hash) {
return;
}
builtEntries[entryName] = entry;
builtEntries[entryName].hash = hash;
}));
const hasBuitEntriesCache = await fs.access(this.builtEntriesCachePath).then(()=>true, ()=>false);
try {
if (!hasBuitEntriesCache) {
await fs.mkdir(path.dirname(this.builtEntriesCachePath), {
recursive: true
});
}
await fs.writeFile(this.builtEntriesCachePath, JSON.stringify(builtEntries, null, 2));
} catch (error) {
console.error('Rspack failed to write built entries cache: ', error);
}
}
constructor(...args){
super(...args), this.isClientCacheEnabled = false, this.isServerCacheEnabled = false, this.isEdgeServerCacheEnabled = false;
}
}
async function calculateFileHash(filePath, algorithm = 'sha256') {
if (!await fs.access(filePath).then(()=>true, ()=>false)) {
return;
}
const fileBuffer = await fs.readFile(filePath);
const hash = createHash(algorithm);
hash.update(fileBuffer);
return hash.digest('hex');
}
//# sourceMappingURL=hot-reloader-rspack.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,34 @@
import { getPathMatch } from '../../shared/lib/router/utils/path-match';
import { parseVersionInfo } from './parse-version-info';
export const matchNextPageBundleRequest = getPathMatch('/_next/static/chunks/pages/:path*.js(\\.map|)');
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'
};
}
}
//# sourceMappingURL=hot-reloader-shared-utils.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/server/dev/hot-reloader-shared-utils.ts"],"sourcesContent":["import { getPathMatch } from '../../shared/lib/router/utils/path-match'\nimport { parseVersionInfo, type VersionInfo } from './parse-version-info'\n\nexport const matchNextPageBundleRequest = getPathMatch(\n '/_next/static/chunks/pages/:path*.js(\\\\.map|)'\n)\nexport async function getVersionInfo(): Promise<VersionInfo> {\n let installed = '0.0.0'\n\n try {\n installed = require('next/package.json').version\n\n let res\n\n try {\n // use NPM registry regardless user using Yarn\n res = await fetch('https://registry.npmjs.org/-/package/next/dist-tags')\n } catch {\n // ignore fetch errors\n }\n\n if (!res || !res.ok) return { installed, staleness: 'unknown' }\n\n const { latest, canary } = await res.json()\n\n return parseVersionInfo({ installed, latest, canary })\n } catch (e: any) {\n console.error(e)\n return { installed, staleness: 'unknown' }\n }\n}\n"],"names":["getPathMatch","parseVersionInfo","matchNextPageBundleRequest","getVersionInfo","installed","require","version","res","fetch","ok","staleness","latest","canary","json","e","console","error"],"mappings":"AAAA,SAASA,YAAY,QAAQ,2CAA0C;AACvE,SAASC,gBAAgB,QAA0B,uBAAsB;AAEzE,OAAO,MAAMC,6BAA6BF,aACxC,iDACD;AACD,OAAO,eAAeG;IACpB,IAAIC,YAAY;IAEhB,IAAI;QACFA,YAAYC,QAAQ,qBAAqBC,OAAO;QAEhD,IAAIC;QAEJ,IAAI;YACF,8CAA8C;YAC9CA,MAAM,MAAMC,MAAM;QACpB,EAAE,OAAM;QACN,sBAAsB;QACxB;QAEA,IAAI,CAACD,OAAO,CAACA,IAAIE,EAAE,EAAE,OAAO;YAAEL;YAAWM,WAAW;QAAU;QAE9D,MAAM,EAAEC,MAAM,EAAEC,MAAM,EAAE,GAAG,MAAML,IAAIM,IAAI;QAEzC,OAAOZ,iBAAiB;YAAEG;YAAWO;YAAQC;QAAO;IACtD,EAAE,OAAOE,GAAQ;QACfC,QAAQC,KAAK,CAACF;QACd,OAAO;YAAEV;YAAWM,WAAW;QAAU;IAC3C;AACF","ignoreList":[0]}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,36 @@
export var HMR_MESSAGE_SENT_TO_BROWSER = /*#__PURE__*/ function(HMR_MESSAGE_SENT_TO_BROWSER) {
// JSON messages:
HMR_MESSAGE_SENT_TO_BROWSER["ADDED_PAGE"] = "addedPage";
HMR_MESSAGE_SENT_TO_BROWSER["REMOVED_PAGE"] = "removedPage";
HMR_MESSAGE_SENT_TO_BROWSER["RELOAD_PAGE"] = "reloadPage";
HMR_MESSAGE_SENT_TO_BROWSER["SERVER_COMPONENT_CHANGES"] = "serverComponentChanges";
HMR_MESSAGE_SENT_TO_BROWSER["MIDDLEWARE_CHANGES"] = "middlewareChanges";
HMR_MESSAGE_SENT_TO_BROWSER["CLIENT_CHANGES"] = "clientChanges";
HMR_MESSAGE_SENT_TO_BROWSER["SERVER_ONLY_CHANGES"] = "serverOnlyChanges";
HMR_MESSAGE_SENT_TO_BROWSER["SYNC"] = "sync";
HMR_MESSAGE_SENT_TO_BROWSER["BUILT"] = "built";
HMR_MESSAGE_SENT_TO_BROWSER["BUILDING"] = "building";
HMR_MESSAGE_SENT_TO_BROWSER["DEV_PAGES_MANIFEST_UPDATE"] = "devPagesManifestUpdate";
HMR_MESSAGE_SENT_TO_BROWSER["TURBOPACK_MESSAGE"] = "turbopack-message";
HMR_MESSAGE_SENT_TO_BROWSER["SERVER_ERROR"] = "serverError";
HMR_MESSAGE_SENT_TO_BROWSER["TURBOPACK_CONNECTED"] = "turbopack-connected";
HMR_MESSAGE_SENT_TO_BROWSER["ISR_MANIFEST"] = "isrManifest";
HMR_MESSAGE_SENT_TO_BROWSER["CACHE_INDICATOR"] = "cacheIndicator";
HMR_MESSAGE_SENT_TO_BROWSER["DEV_INDICATOR"] = "devIndicator";
HMR_MESSAGE_SENT_TO_BROWSER["DEVTOOLS_CONFIG"] = "devtoolsConfig";
HMR_MESSAGE_SENT_TO_BROWSER["REQUEST_CURRENT_ERROR_STATE"] = "requestCurrentErrorState";
HMR_MESSAGE_SENT_TO_BROWSER["REQUEST_PAGE_METADATA"] = "requestPageMetadata";
// Binary messages:
HMR_MESSAGE_SENT_TO_BROWSER[HMR_MESSAGE_SENT_TO_BROWSER["REACT_DEBUG_CHUNK"] = 0] = "REACT_DEBUG_CHUNK";
HMR_MESSAGE_SENT_TO_BROWSER[HMR_MESSAGE_SENT_TO_BROWSER["ERRORS_TO_SHOW_IN_BROWSER"] = 1] = "ERRORS_TO_SHOW_IN_BROWSER";
return HMR_MESSAGE_SENT_TO_BROWSER;
}({});
export var HMR_MESSAGE_SENT_TO_SERVER = /*#__PURE__*/ function(HMR_MESSAGE_SENT_TO_SERVER) {
// JSON messages:
HMR_MESSAGE_SENT_TO_SERVER["MCP_ERROR_STATE_RESPONSE"] = "mcp-error-state-response";
HMR_MESSAGE_SENT_TO_SERVER["MCP_PAGE_METADATA_RESPONSE"] = "mcp-page-metadata-response";
HMR_MESSAGE_SENT_TO_SERVER["PING"] = "ping";
return HMR_MESSAGE_SENT_TO_SERVER;
}({});
//# sourceMappingURL=hot-reloader-types.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,121 @@
import { hrtimeBigIntDurationToString } from '../../build/duration-to-string';
import { blue, bold, gray, green, red, white, yellow, dim } from '../../lib/picocolors';
import { stripNextRscUnionQuery } from '../../lib/url';
import { getRequestMeta } from '../request-meta';
/**
* Returns true if the incoming request should be ignored for logging.
*/ export function ignoreLoggingIncomingRequests(request, loggingConfig) {
var _loggingConfig_incomingRequests;
// If it's boolean use the boolean value
if (typeof (loggingConfig == null ? void 0 : loggingConfig.incomingRequests) === 'boolean') {
return !loggingConfig.incomingRequests;
}
// Any of the value on the chain is falsy, will not ignore the request.
const ignore = loggingConfig == null ? void 0 : (_loggingConfig_incomingRequests = loggingConfig.incomingRequests) == null ? void 0 : _loggingConfig_incomingRequests.ignore;
// If ignore is not set, don't ignore anything
if (!ignore) {
return false;
}
// If array of RegExp, ignore if any pattern matches
return ignore.some((pattern)=>pattern.test(request.url));
}
export function logRequests(request, response, loggingConfig, requestStartTime, requestEndTime, devRequestTimingMiddlewareStart, devRequestTimingMiddlewareEnd, devRequestTimingInternalsEnd, devGenerateStaticParamsDuration) {
if (!ignoreLoggingIncomingRequests(request, loggingConfig)) {
logIncomingRequests(request, requestStartTime, requestEndTime, response.statusCode, devRequestTimingMiddlewareStart, devRequestTimingMiddlewareEnd, devRequestTimingInternalsEnd, devGenerateStaticParamsDuration);
}
if (request.fetchMetrics) {
for (const fetchMetric of request.fetchMetrics){
logFetchMetric(fetchMetric, loggingConfig);
}
}
}
function logIncomingRequests(request, requestStartTime, requestEndTime, statusCode, devRequestTimingMiddlewareStart, devRequestTimingMiddlewareEnd, devRequestTimingInternalsEnd, devGenerateStaticParamsDuration) {
const isRSC = getRequestMeta(request, 'isRSCRequest');
const url = isRSC ? stripNextRscUnionQuery(request.url) : request.url;
const statusCodeColor = statusCode < 200 ? white : statusCode < 300 ? green : statusCode < 400 ? blue : statusCode < 500 ? yellow : red;
const coloredStatus = statusCodeColor(statusCode.toString());
const totalRequestTime = requestEndTime - requestStartTime;
const times = [];
let middlewareTime;
if (devRequestTimingMiddlewareStart && devRequestTimingMiddlewareEnd) {
middlewareTime = devRequestTimingMiddlewareEnd - devRequestTimingMiddlewareStart;
times.push([
'proxy.ts',
middlewareTime
]);
}
if (devRequestTimingInternalsEnd) {
let frameworkTime = devRequestTimingInternalsEnd - requestStartTime;
/* Middleware runs during the internals so we have to subtract it from the framework time */ if (middlewareTime) {
frameworkTime -= middlewareTime;
}
// Insert as the first item to be rendered in the list
times.unshift([
'compile',
frameworkTime
]);
// Insert after compile, before render based on the execution order.
if (devGenerateStaticParamsDuration) {
// Pages Router getStaticPaths are technically "generate params" as well.
times.push([
'generate-params',
devGenerateStaticParamsDuration
]);
}
times.push([
'render',
requestEndTime - devRequestTimingInternalsEnd
]);
}
return writeLine(`${request.method} ${url} ${coloredStatus} in ${hrtimeBigIntDurationToString(totalRequestTime)}${times.length > 0 ? dim(` (${times.map(([label, time])=>`${label}: ${hrtimeBigIntDurationToString(time)}`).join(', ')})`) : ''}`);
}
function logFetchMetric(fetchMetric, loggingConfig) {
var _loggingConfig_fetches;
let { cacheReason, cacheStatus, cacheWarning, end, method, start, status, url } = fetchMetric;
if (cacheStatus === 'hmr' && !(loggingConfig == null ? void 0 : (_loggingConfig_fetches = loggingConfig.fetches) == null ? void 0 : _loggingConfig_fetches.hmrRefreshes)) {
// Cache hits during HMR refreshes are intentionally not logged, unless
// explicitly enabled in the logging config.
return;
}
if (loggingConfig == null ? void 0 : loggingConfig.fetches) {
if (url.length > 48 && !loggingConfig.fetches.fullUrl) {
url = truncateUrl(url);
}
writeLine(white(`${method} ${url} ${status} in ${Math.round(end - start)}ms ${formatCacheStatus(cacheStatus)}`), 1);
if (cacheStatus === 'skip' || cacheStatus === 'miss') {
writeLine(gray(`Cache ${cacheStatus === 'skip' ? 'skipped' : 'missed'} reason: (${white(cacheReason)})`), 2);
}
} else if (cacheWarning) {
// When logging for fetches is not enabled, we still want to print any
// associated warnings, so we print the request first to provide context.
writeLine(white(`${method} ${url}`), 1);
}
if (cacheWarning) {
writeLine(`${yellow(bold('⚠'))} ${white(cacheWarning)}`, 2);
}
}
function writeLine(text, indentationLevel = 0) {
process.stdout.write(` ${'│ '.repeat(indentationLevel)}${text}\n`);
}
function truncate(text, maxLength) {
return maxLength !== undefined && text.length > maxLength ? text.substring(0, maxLength) + '..' : text;
}
function truncateUrl(url) {
const { protocol, host, pathname, search } = new URL(url);
return protocol + '//' + truncate(host, 16) + truncate(pathname, 24) + truncate(search, 16);
}
function formatCacheStatus(cacheStatus) {
switch(cacheStatus){
case 'hmr':
return green('(HMR cache)');
case 'hit':
return green('(cache hit)');
case 'miss':
case 'skip':
return yellow(`(cache ${cacheStatus})`);
default:
return cacheStatus;
}
}
//# sourceMappingURL=log-requests.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,52 @@
import { InvariantError } from '../../shared/lib/invariant-error';
import { HMR_MESSAGE_SENT_TO_BROWSER } from './hot-reloader-types';
export const FAST_REFRESH_RUNTIME_RELOAD = 'Fast Refresh had to perform a full reload due to a runtime error.';
const textEncoder = new TextEncoder();
export function createBinaryHmrMessageData(message) {
switch(message.type){
case HMR_MESSAGE_SENT_TO_BROWSER.ERRORS_TO_SHOW_IN_BROWSER:
{
const { serializedErrors } = message;
const totalLength = 1 + serializedErrors.length;
const data = new Uint8Array(totalLength);
const view = new DataView(data.buffer);
view.setUint8(0, HMR_MESSAGE_SENT_TO_BROWSER.ERRORS_TO_SHOW_IN_BROWSER);
data.set(serializedErrors, 1);
return data;
}
case HMR_MESSAGE_SENT_TO_BROWSER.REACT_DEBUG_CHUNK:
{
const { requestId, chunk } = message;
const requestIdBytes = textEncoder.encode(requestId);
const requestIdLength = requestIdBytes.length;
if (requestIdLength > 255) {
throw Object.defineProperty(new InvariantError('Request ID is too long for the binary HMR message.'), "__NEXT_ERROR_CODE", {
value: "E805",
enumerable: false,
configurable: true
});
}
const chunkLength = chunk ? chunk.length : 0;
const totalLength = 2 + requestIdLength + chunkLength;
const data = new Uint8Array(totalLength);
const view = new DataView(data.buffer);
view.setUint8(0, HMR_MESSAGE_SENT_TO_BROWSER.REACT_DEBUG_CHUNK);
view.setUint8(1, requestIdLength);
textEncoder.encodeInto(requestId, data.subarray(2, 2 + requestIdLength));
if (chunk) {
data.set(chunk, 2 + requestIdLength);
}
return data;
}
default:
{
throw Object.defineProperty(new InvariantError(`Invalid binary HMR message of type ${message.type}`), "__NEXT_ERROR_CODE", {
value: "E809",
enumerable: false,
configurable: true
});
}
}
}
//# sourceMappingURL=messages.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/server/dev/messages.ts"],"sourcesContent":["import { InvariantError } from '../../shared/lib/invariant-error'\nimport {\n HMR_MESSAGE_SENT_TO_BROWSER,\n type BinaryHmrMessageSentToBrowser,\n} from './hot-reloader-types'\n\nexport const FAST_REFRESH_RUNTIME_RELOAD =\n 'Fast Refresh had to perform a full reload due to a runtime error.'\n\nconst textEncoder = new TextEncoder()\n\nexport function createBinaryHmrMessageData(\n message: BinaryHmrMessageSentToBrowser\n): Uint8Array {\n switch (message.type) {\n case HMR_MESSAGE_SENT_TO_BROWSER.ERRORS_TO_SHOW_IN_BROWSER: {\n const { serializedErrors } = message\n const totalLength = 1 + serializedErrors.length\n const data = new Uint8Array(totalLength)\n const view = new DataView(data.buffer)\n\n view.setUint8(0, HMR_MESSAGE_SENT_TO_BROWSER.ERRORS_TO_SHOW_IN_BROWSER)\n data.set(serializedErrors, 1)\n\n return data\n }\n case HMR_MESSAGE_SENT_TO_BROWSER.REACT_DEBUG_CHUNK: {\n const { requestId, chunk } = message\n const requestIdBytes = textEncoder.encode(requestId)\n const requestIdLength = requestIdBytes.length\n\n if (requestIdLength > 255) {\n throw new InvariantError(\n 'Request ID is too long for the binary HMR message.'\n )\n }\n\n const chunkLength = chunk ? chunk.length : 0\n const totalLength = 2 + requestIdLength + chunkLength\n const data = new Uint8Array(totalLength)\n const view = new DataView(data.buffer)\n\n view.setUint8(0, HMR_MESSAGE_SENT_TO_BROWSER.REACT_DEBUG_CHUNK)\n view.setUint8(1, requestIdLength)\n textEncoder.encodeInto(requestId, data.subarray(2, 2 + requestIdLength))\n\n if (chunk) {\n data.set(chunk, 2 + requestIdLength)\n }\n\n return data\n }\n default: {\n throw new InvariantError(\n `Invalid binary HMR message of type ${(message as any).type}`\n )\n }\n }\n}\n"],"names":["InvariantError","HMR_MESSAGE_SENT_TO_BROWSER","FAST_REFRESH_RUNTIME_RELOAD","textEncoder","TextEncoder","createBinaryHmrMessageData","message","type","ERRORS_TO_SHOW_IN_BROWSER","serializedErrors","totalLength","length","data","Uint8Array","view","DataView","buffer","setUint8","set","REACT_DEBUG_CHUNK","requestId","chunk","requestIdBytes","encode","requestIdLength","chunkLength","encodeInto","subarray"],"mappings":"AAAA,SAASA,cAAc,QAAQ,mCAAkC;AACjE,SACEC,2BAA2B,QAEtB,uBAAsB;AAE7B,OAAO,MAAMC,8BACX,oEAAmE;AAErE,MAAMC,cAAc,IAAIC;AAExB,OAAO,SAASC,2BACdC,OAAsC;IAEtC,OAAQA,QAAQC,IAAI;QAClB,KAAKN,4BAA4BO,yBAAyB;YAAE;gBAC1D,MAAM,EAAEC,gBAAgB,EAAE,GAAGH;gBAC7B,MAAMI,cAAc,IAAID,iBAAiBE,MAAM;gBAC/C,MAAMC,OAAO,IAAIC,WAAWH;gBAC5B,MAAMI,OAAO,IAAIC,SAASH,KAAKI,MAAM;gBAErCF,KAAKG,QAAQ,CAAC,GAAGhB,4BAA4BO,yBAAyB;gBACtEI,KAAKM,GAAG,CAACT,kBAAkB;gBAE3B,OAAOG;YACT;QACA,KAAKX,4BAA4BkB,iBAAiB;YAAE;gBAClD,MAAM,EAAEC,SAAS,EAAEC,KAAK,EAAE,GAAGf;gBAC7B,MAAMgB,iBAAiBnB,YAAYoB,MAAM,CAACH;gBAC1C,MAAMI,kBAAkBF,eAAeX,MAAM;gBAE7C,IAAIa,kBAAkB,KAAK;oBACzB,MAAM,qBAEL,CAFK,IAAIxB,eACR,uDADI,qBAAA;+BAAA;oCAAA;sCAAA;oBAEN;gBACF;gBAEA,MAAMyB,cAAcJ,QAAQA,MAAMV,MAAM,GAAG;gBAC3C,MAAMD,cAAc,IAAIc,kBAAkBC;gBAC1C,MAAMb,OAAO,IAAIC,WAAWH;gBAC5B,MAAMI,OAAO,IAAIC,SAASH,KAAKI,MAAM;gBAErCF,KAAKG,QAAQ,CAAC,GAAGhB,4BAA4BkB,iBAAiB;gBAC9DL,KAAKG,QAAQ,CAAC,GAAGO;gBACjBrB,YAAYuB,UAAU,CAACN,WAAWR,KAAKe,QAAQ,CAAC,GAAG,IAAIH;gBAEvD,IAAIH,OAAO;oBACTT,KAAKM,GAAG,CAACG,OAAO,IAAIG;gBACtB;gBAEA,OAAOZ;YACT;QACA;YAAS;gBACP,MAAM,qBAEL,CAFK,IAAIZ,eACR,CAAC,mCAAmC,EAAE,AAACM,QAAgBC,IAAI,EAAE,GADzD,qBAAA;2BAAA;gCAAA;kCAAA;gBAEN;YACF;IACF;AACF","ignoreList":[0]}

View File

@@ -0,0 +1,362 @@
import { getOriginalCodeFrame, ignoreListAnonymousStackFramesIfSandwiched } from '../../next-devtools/server/shared';
import { middlewareResponse } from '../../next-devtools/server/middleware-response';
import path from 'path';
import { openFileInEditor } from '../../next-devtools/server/launch-editor';
import { SourceMapConsumer } from 'next/dist/compiled/source-map08';
import { devirtualizeReactServerURL, findApplicableSourceMapPayload } from '../lib/source-maps';
import { findSourceMap } from 'node:module';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { inspect } from 'node:util';
function shouldIgnorePath(modulePath) {
return modulePath.includes('node_modules') || // Only relevant for when Next.js is symlinked e.g. in the Next.js monorepo
modulePath.includes('next/dist') || modulePath.startsWith('node:');
}
const currentSourcesByFile = new Map();
/**
* @returns 1-based lines and 1-based columns
*/ async function batchedTraceSource(project, frame) {
const file = frame.file ? decodeURIComponent(frame.file) : undefined;
if (!file) return;
// For node internals they cannot traced the actual source code with project.traceSource,
// we need an early return to indicate it's ignored to avoid the unknown scheme error from `project.traceSource`.
if (file.startsWith('node:')) {
return {
frame: {
file,
line1: frame.line ?? null,
column1: frame.column ?? null,
methodName: frame.methodName ?? '<unknown>',
ignored: true,
arguments: []
},
source: null
};
}
const currentDirectoryFileUrl = pathToFileURL(process.cwd()).href;
const sourceFrame = await project.traceSource(frame, currentDirectoryFileUrl);
if (!sourceFrame) {
return {
frame: {
file,
line1: frame.line ?? null,
column1: frame.column ?? null,
methodName: frame.methodName ?? '<unknown>',
ignored: shouldIgnorePath(file),
arguments: []
},
source: null
};
}
let source = null;
const originalFile = sourceFrame.originalFile;
// Don't look up source for node_modules or internals. These can often be large bundled files.
const ignored = shouldIgnorePath(originalFile ?? sourceFrame.file) || // isInternal means resource starts with turbopack:///[turbopack]
!!sourceFrame.isInternal;
if (originalFile && !ignored) {
let sourcePromise = currentSourcesByFile.get(originalFile);
if (!sourcePromise) {
sourcePromise = project.getSourceForAsset(originalFile);
currentSourcesByFile.set(originalFile, sourcePromise);
setTimeout(()=>{
// Cache file reads for 100ms, as frames will often reference the same
// files and can be large.
currentSourcesByFile.delete(originalFile);
}, 100);
}
source = await sourcePromise;
}
// TODO: get ignoredList from turbopack source map
const ignorableFrame = {
file: sourceFrame.file,
line1: sourceFrame.line ?? null,
column1: sourceFrame.column ?? null,
methodName: // We ignore the sourcemapped name since it won't be the correct name.
// The callsite will point to the column of the variable name instead of the
// name of the enclosing function.
// TODO(NDX-531): Spy on prepareStackTrace to get the enclosing line number for method name mapping.
frame.methodName ?? '<unknown>',
ignored,
arguments: []
};
return {
frame: ignorableFrame,
source
};
}
function parseFile(fileParam) {
if (!fileParam) {
return undefined;
}
return devirtualizeReactServerURL(fileParam);
}
function createStackFrames(body) {
const { frames, isServer } = body;
return frames.map((frame)=>{
const file = parseFile(frame.file);
if (!file) {
return undefined;
}
return {
file,
methodName: frame.methodName ?? '<unknown>',
line: frame.line1 ?? undefined,
column: frame.column1 ?? undefined,
isServer
};
}).filter((f)=>f !== undefined);
}
function createStackFrame(searchParams) {
const file = parseFile(searchParams.get('file'));
if (!file) {
return undefined;
}
return {
file,
methodName: searchParams.get('methodName') ?? '<unknown>',
line: parseInt(searchParams.get('line1') ?? '0', 10) || undefined,
column: parseInt(searchParams.get('column1') ?? '0', 10) || undefined,
isServer: searchParams.get('isServer') === 'true'
};
}
/**
* @returns 1-based lines and 1-based columns
*/ async function nativeTraceSource(frame) {
const sourceURL = frame.file;
let sourceMapPayload;
try {
var _findSourceMap;
sourceMapPayload = (_findSourceMap = findSourceMap(sourceURL)) == null ? void 0 : _findSourceMap.payload;
} catch (cause) {
throw Object.defineProperty(new Error(`${sourceURL}: Invalid source map. Only conformant source maps can be used to find the original code.`, {
cause
}), "__NEXT_ERROR_CODE", {
value: "E635",
enumerable: false,
configurable: true
});
}
if (sourceMapPayload !== undefined) {
let consumer;
try {
consumer = await new SourceMapConsumer(sourceMapPayload);
} catch (cause) {
throw Object.defineProperty(new Error(`${sourceURL}: Invalid source map. Only conformant source maps can be used to find the original code.`, {
cause
}), "__NEXT_ERROR_CODE", {
value: "E635",
enumerable: false,
configurable: true
});
}
let traced;
try {
const originalPosition = consumer.originalPositionFor({
line: frame.line ?? 1,
// 0-based columns out requires 0-based columns in.
column: (frame.column ?? 1) - 1
});
if (originalPosition.source === null) {
traced = null;
} else {
const sourceContent = consumer.sourceContentFor(originalPosition.source, /* returnNullOnMissing */ true) ?? null;
traced = {
originalPosition,
sourceContent
};
}
} finally{
consumer.destroy();
}
if (traced !== null) {
var // We ignore the sourcemapped name since it won't be the correct name.
// The callsite will point to the column of the variable name instead of the
// name of the enclosing function.
// TODO(NDX-531): Spy on prepareStackTrace to get the enclosing line number for method name mapping.
_frame_methodName_replace, _frame_methodName;
const { originalPosition, sourceContent } = traced;
const applicableSourceMap = findApplicableSourceMapPayload((frame.line ?? 1) - 1, (frame.column ?? 1) - 1, sourceMapPayload);
// TODO(veil): Upstream a method to sourcemap consumer that immediately says if a frame is ignored or not.
let ignored = false;
if (applicableSourceMap === undefined) {
console.error('No applicable source map found in sections for frame', frame);
} else {
var _applicableSourceMap_ignoreList;
// TODO: O(n^2). Consider moving `ignoreList` into a Set
const sourceIndex = applicableSourceMap.sources.indexOf(originalPosition.source);
ignored = ((_applicableSourceMap_ignoreList = applicableSourceMap.ignoreList) == null ? void 0 : _applicableSourceMap_ignoreList.includes(sourceIndex)) ?? // When sourcemap is not available, fallback to checking `frame.file`.
// e.g. In pages router, nextjs server code is not bundled into the page.
shouldIgnorePath(frame.file);
}
const originalStackFrame = {
methodName: ((_frame_methodName = frame.methodName) == null ? void 0 : (_frame_methodName_replace = _frame_methodName.replace('__WEBPACK_DEFAULT_EXPORT__', 'default')) == null ? void 0 : _frame_methodName_replace.replace('__webpack_exports__.', '')) || '<unknown>',
file: originalPosition.source,
line1: originalPosition.line,
column1: originalPosition.column === null ? null : originalPosition.column + 1,
// TODO: c&p from async createOriginalStackFrame but why not frame.arguments?
arguments: [],
ignored
};
return {
frame: originalStackFrame,
source: sourceContent
};
}
}
return undefined;
}
async function createOriginalStackFrame(project, projectPath, frame) {
const traced = await nativeTraceSource(frame) ?? // TODO(veil): When would the bundler know more than native?
// If it's faster, try the bundler first and fall back to native later.
await batchedTraceSource(project, frame);
if (!traced) {
return null;
}
let normalizedStackFrameLocation = traced.frame.file;
if (normalizedStackFrameLocation !== null && normalizedStackFrameLocation.startsWith('file://')) {
normalizedStackFrameLocation = path.relative(projectPath, fileURLToPath(normalizedStackFrameLocation));
}
return {
originalStackFrame: {
arguments: traced.frame.arguments,
file: normalizedStackFrameLocation,
line1: traced.frame.line1,
column1: traced.frame.column1,
ignored: traced.frame.ignored,
methodName: traced.frame.methodName
},
originalCodeFrame: getOriginalCodeFrame(traced.frame, traced.source)
};
}
export function getOverlayMiddleware({ project, projectPath, isSrcDir }) {
return async function(req, res, next) {
const { pathname, searchParams } = new URL(req.url, 'http://n');
if (pathname === '/__nextjs_original-stack-frames') {
if (req.method !== 'POST') {
return middlewareResponse.badRequest(res);
}
const body = await new Promise((resolve, reject)=>{
let data = '';
req.on('data', (chunk)=>{
data += chunk;
});
req.on('end', ()=>resolve(data));
req.on('error', reject);
});
const request = JSON.parse(body);
const result = await getOriginalStackFrames({
project,
projectPath,
frames: request.frames,
isServer: request.isServer,
isEdgeServer: request.isEdgeServer,
isAppDirectory: request.isAppDirectory
});
ignoreListAnonymousStackFramesIfSandwiched(result);
return middlewareResponse.json(res, result);
} else if (pathname === '/__nextjs_launch-editor') {
const isAppRelativePath = searchParams.get('isAppRelativePath') === '1';
let openEditorResult;
if (isAppRelativePath) {
const relativeFilePath = searchParams.get('file') || '';
const appPath = path.join('app', isSrcDir ? 'src' : '', relativeFilePath);
openEditorResult = await openFileInEditor(appPath, 1, 1, projectPath);
} else {
const frame = createStackFrame(searchParams);
if (!frame) return middlewareResponse.badRequest(res);
openEditorResult = await openFileInEditor(frame.file, frame.line ?? 1, frame.column ?? 1, projectPath);
}
if (openEditorResult.error) {
return middlewareResponse.internalServerError(res, openEditorResult.error);
}
if (!openEditorResult.found) {
return middlewareResponse.notFound(res);
}
return middlewareResponse.noContent(res);
}
return next();
};
}
export function getSourceMapMiddleware(project) {
return async function(req, res, next) {
const { pathname, searchParams } = new URL(req.url, 'http://n');
if (pathname !== '/__nextjs_source-map') {
return next();
}
let filename = searchParams.get('filename');
if (!filename) {
return middlewareResponse.badRequest(res);
}
let nativeSourceMap;
try {
nativeSourceMap = findSourceMap(filename);
} catch (cause) {
return middlewareResponse.internalServerError(res, Object.defineProperty(new Error(`${filename}: Invalid source map. Only conformant source maps can be used to find the original code.`, {
cause
}), "__NEXT_ERROR_CODE", {
value: "E635",
enumerable: false,
configurable: true
}));
}
if (nativeSourceMap !== undefined) {
const sourceMapPayload = nativeSourceMap.payload;
return middlewareResponse.json(res, sourceMapPayload);
}
try {
// Turbopack chunk filenames might be URL-encoded.
filename = decodeURI(filename);
} catch {
return middlewareResponse.badRequest(res);
}
if (path.isAbsolute(filename)) {
filename = pathToFileURL(filename).href;
}
try {
const sourceMapString = await project.getSourceMap(filename);
if (sourceMapString) {
return middlewareResponse.jsonString(res, sourceMapString);
}
} catch (cause) {
return middlewareResponse.internalServerError(res, Object.defineProperty(new Error(`Failed to get source map for '${filename}'. This is a bug in Next.js`, {
cause
}), "__NEXT_ERROR_CODE", {
value: "E719",
enumerable: false,
configurable: true
}));
}
middlewareResponse.noContent(res);
};
}
export async function getOriginalStackFrames({ project, projectPath, frames, isServer, isEdgeServer, isAppDirectory }) {
const stackFrames = createStackFrames({
frames,
isServer,
isEdgeServer,
isAppDirectory
});
return Promise.all(stackFrames.map(async (frame)=>{
try {
const stackFrame = await createOriginalStackFrame(project, projectPath, frame);
if (stackFrame === null) {
return {
status: 'rejected',
reason: 'Failed to create original stack frame'
};
}
return {
status: 'fulfilled',
value: stackFrame
};
} catch (error) {
return {
status: 'rejected',
reason: inspect(error, {
colors: false
})
};
}
}));
}
//# sourceMappingURL=middleware-turbopack.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,434 @@
import { findSourceMap } from 'module';
import path from 'path';
import { fileURLToPath, pathToFileURL } from 'url';
import { SourceMapConsumer } from 'next/dist/compiled/source-map08';
import { getSourceMapFromFile } from './get-source-map-from-file';
import { devirtualizeReactServerURL, findApplicableSourceMapPayload, sourceMapIgnoreListsEverything } from '../lib/source-maps';
import { openFileInEditor } from '../../next-devtools/server/launch-editor';
import { getOriginalCodeFrame, ignoreListAnonymousStackFramesIfSandwiched } from '../../next-devtools/server/shared';
import { middlewareResponse } from '../../next-devtools/server/middleware-response';
import { formatFrameSourceFile } from '../../next-devtools/shared/webpack-module-path';
import { inspect } from 'util';
function shouldIgnoreSource(sourceURL) {
return sourceURL.includes('node_modules') || // Only relevant for when Next.js is symlinked e.g. in the Next.js monorepo
sourceURL.includes('next/dist') || sourceURL.startsWith('node:');
}
function getModuleById(id, compilation) {
const { chunkGraph, modules } = compilation;
return [
...modules
].find((module)=>chunkGraph.getModuleId(module) === id);
}
function findModuleNotFoundFromError(errorMessage) {
var _errorMessage_match;
return errorMessage == null ? void 0 : (_errorMessage_match = errorMessage.match(/'([^']+)' module/)) == null ? void 0 : _errorMessage_match[1];
}
function getSourcePath(source) {
if (source.startsWith('file://')) {
return fileURLToPath(source);
}
return source.replace(/^(webpack:\/\/\/|webpack:\/\/|webpack:\/\/_N_E\/)/, '');
}
/**
* @returns 1-based lines and 0-based columns
*/ async function findOriginalSourcePositionAndContent(sourceMap, position) {
let consumer;
try {
consumer = await new SourceMapConsumer(sourceMap);
} catch (cause) {
console.error(Object.defineProperty(new Error(`${sourceMap.file}: Invalid source map. Only conformant source maps can be used to find the original code.`, {
cause
}), "__NEXT_ERROR_CODE", {
value: "E635",
enumerable: false,
configurable: true
}));
return null;
}
try {
const sourcePosition = consumer.originalPositionFor({
line: position.line1 ?? 1,
// 0-based columns out requires 0-based columns in.
column: (position.column1 ?? 1) - 1
});
if (!sourcePosition.source) {
return null;
}
const sourceContent = consumer.sourceContentFor(sourcePosition.source, /* returnNullOnMissing */ true) ?? null;
return {
sourcePosition,
sourceContent
};
} finally{
consumer.destroy();
}
}
export function getIgnoredSources(sourceMap) {
const ignoreList = new Set(sourceMap.ignoreList ?? []);
const moduleFilenames = (sourceMap == null ? void 0 : sourceMap.sources) ?? [];
for(let index = 0; index < moduleFilenames.length; index++){
// bundlerFilePath case: webpack://./app/page.tsx
const webpackSourceURL = moduleFilenames[index];
// Format the path to the normal file path
const formattedFilePath = formatFrameSourceFile(webpackSourceURL);
if (shouldIgnoreSource(formattedFilePath)) {
ignoreList.add(index);
}
}
const ignoredSources = sourceMap.sources.map((source, index)=>{
var _sourceMap_sourcesContent;
return {
url: source,
ignored: ignoreList.has(sourceMap.sources.indexOf(source)),
content: ((_sourceMap_sourcesContent = sourceMap.sourcesContent) == null ? void 0 : _sourceMap_sourcesContent[index]) ?? null
};
});
return ignoredSources;
}
function isIgnoredSource(source, sourcePosition) {
if (sourcePosition.source == null) {
return true;
}
for (const ignoredSource of source.ignoredSources){
if (ignoredSource.ignored && ignoredSource.url === sourcePosition.source) {
return true;
}
}
return false;
}
function findOriginalSourcePositionAndContentFromCompilation(moduleId, importedModule, compilation) {
var _module_buildInfo_importLocByPath, _module_buildInfo;
const module = getModuleById(moduleId, compilation);
return (module == null ? void 0 : (_module_buildInfo = module.buildInfo) == null ? void 0 : (_module_buildInfo_importLocByPath = _module_buildInfo.importLocByPath) == null ? void 0 : _module_buildInfo_importLocByPath.get(importedModule)) ?? null;
}
export async function createOriginalStackFrame({ ignoredByDefault, source, rootDirectory, frame, errorMessage }) {
var // We ignore the sourcemapped name since it won't be the correct name.
// The callsite will point to the column of the variable name instead of the
// name of the enclosing function.
// TODO(NDX-531): Spy on prepareStackTrace to get the enclosing line number for method name mapping.
// default is not a valid identifier in JS so webpack uses a custom variable when it's an unnamed default export
// Resolve it back to `default` for the method name if the source position didn't have the method.
_frame_methodName_replace, _frame_methodName;
const moduleNotFound = findModuleNotFoundFromError(errorMessage);
const result = await (()=>{
if (moduleNotFound) {
if (source.type === 'file') {
return undefined;
}
return findOriginalSourcePositionAndContentFromCompilation(source.moduleId, moduleNotFound, source.compilation);
}
return findOriginalSourcePositionAndContent(source.sourceMap, frame);
})();
if (!result) {
return null;
}
const { sourcePosition, sourceContent } = result;
if (!sourcePosition.source) {
return null;
}
const ignored = ignoredByDefault || isIgnoredSource(source, sourcePosition) || // If the source file is externals, should be excluded even it's not ignored source.
// e.g. webpack://next/dist/.. needs to be ignored
shouldIgnoreSource(source.moduleURL);
const sourcePath = getSourcePath(// When sourcePosition.source is the loader path the modulePath is generally better.
(sourcePosition.source.includes('|') ? source.moduleURL : sourcePosition.source) || source.moduleURL);
const filePath = path.resolve(rootDirectory, sourcePath);
const resolvedFilePath = path.relative(rootDirectory, filePath);
const traced = {
file: resolvedFilePath,
line1: sourcePosition.line,
column1: sourcePosition.column === null ? null : sourcePosition.column + 1,
methodName: (_frame_methodName = frame.methodName) == null ? void 0 : (_frame_methodName_replace = _frame_methodName.replace('__WEBPACK_DEFAULT_EXPORT__', 'default')) == null ? void 0 : _frame_methodName_replace.replace('__webpack_exports__.', ''),
arguments: [],
ignored
};
return {
originalStackFrame: traced,
originalCodeFrame: getOriginalCodeFrame(traced, sourceContent)
};
}
async function getSourceMapFromCompilation(id, compilation) {
try {
const module = getModuleById(id, compilation);
if (!module) {
return undefined;
}
// @ts-expect-error The types for `CodeGenerationResults.get` require a
// runtime to be passed as second argument, but apparently it also works
// without it.
const codeGenerationResult = compilation.codeGenerationResults.get(module);
const source = codeGenerationResult == null ? void 0 : codeGenerationResult.sources.get('javascript');
return (source == null ? void 0 : source.map()) ?? undefined;
} catch (err) {
console.error(`Failed to lookup module by ID ("${id}"):`, err);
return undefined;
}
}
async function getSource(frame, options) {
let sourceURL = frame.file ?? '';
const { getCompilations } = options;
sourceURL = devirtualizeReactServerURL(sourceURL);
let nativeSourceMap;
try {
nativeSourceMap = findSourceMap(sourceURL);
} catch (cause) {
throw Object.defineProperty(new Error(`${sourceURL}: Invalid source map. Only conformant source maps can be used to find the original code.`, {
cause
}), "__NEXT_ERROR_CODE", {
value: "E635",
enumerable: false,
configurable: true
});
}
if (nativeSourceMap !== undefined) {
const sourceMapPayload = nativeSourceMap.payload;
return {
type: 'file',
sourceMap: findApplicableSourceMapPayload((frame.line1 ?? 1) - 1, (frame.column1 ?? 1) - 1, sourceMapPayload),
ignoredSources: getIgnoredSources(// @ts-expect-error -- TODO: Support IndexSourceMap
sourceMapPayload),
moduleURL: sourceURL
};
}
if (path.isAbsolute(sourceURL)) {
sourceURL = pathToFileURL(sourceURL).href;
}
if (sourceURL.startsWith('file:')) {
const sourceMap = await getSourceMapFromFile(sourceURL);
return sourceMap ? {
type: 'file',
sourceMap,
ignoredSources: getIgnoredSources(sourceMap),
moduleURL: sourceURL
} : undefined;
}
// webpack-internal:///./src/hello.tsx => ./src/hello.tsx
// webpack://_N_E/./src/hello.tsx => ./src/hello.tsx
const moduleId = sourceURL.replace(/^(webpack-internal:\/\/\/|webpack:\/\/(_N_E\/)?)/, '').replace(/\?\d+$/, '');
// (rsc)/./src/hello.tsx => ./src/hello.tsx
const moduleURL = moduleId.replace(/^(\(.*\)\/?)/, '');
for (const compilation of getCompilations()){
const sourceMap = await getSourceMapFromCompilation(moduleId, compilation);
if (sourceMap) {
const ignoredSources = getIgnoredSources(sourceMap);
return {
type: 'bundle',
sourceMap,
compilation,
moduleId,
moduleURL,
ignoredSources
};
}
}
return undefined;
}
export async function getOriginalStackFrames({ isServer, isEdgeServer, isAppDirectory, frames, clientStats, serverStats, edgeServerStats, rootDirectory }) {
const frameResponses = await Promise.all(frames.map((frame)=>getOriginalStackFrame({
isServer,
isEdgeServer,
isAppDirectory,
frame,
clientStats,
serverStats,
edgeServerStats,
rootDirectory
}).then((value)=>{
return {
status: 'fulfilled',
value
};
}, (reason)=>{
return {
status: 'rejected',
reason: inspect(reason, {
colors: false
})
};
})));
ignoreListAnonymousStackFramesIfSandwiched(frameResponses);
return frameResponses;
}
async function getOriginalStackFrame({ isServer, isEdgeServer, isAppDirectory, frame, clientStats, serverStats, edgeServerStats, rootDirectory }) {
const filename = frame.file ?? '';
const source = await getSource(frame, {
getCompilations: ()=>{
const compilations = [];
// Try Client Compilation first. In `pages` we leverage
// `isClientError` to check. In `app` it depends on if it's a server
// / client component and when the code throws. E.g. during HTML
// rendering it's the server/edge compilation.
if (!isEdgeServer && !isServer || isAppDirectory) {
var _clientStats;
const compilation = (_clientStats = clientStats()) == null ? void 0 : _clientStats.compilation;
if (compilation) {
compilations.push(compilation);
}
}
// Try Server Compilation. In `pages` this could be something
// imported in getServerSideProps/getStaticProps as the code for
// those is tree-shaken. In `app` this finds server components and
// code that was imported from a server component. It also covers
// when client component code throws during HTML rendering.
if (isServer || isAppDirectory) {
var _serverStats;
const compilation = (_serverStats = serverStats()) == null ? void 0 : _serverStats.compilation;
if (compilation) {
compilations.push(compilation);
}
}
// Try Edge Server Compilation. Both cases are the same as Server
// Compilation, main difference is that it covers `runtime: 'edge'`
// pages/app routes.
if (isEdgeServer || isAppDirectory) {
var _edgeServerStats;
const compilation = (_edgeServerStats = edgeServerStats()) == null ? void 0 : _edgeServerStats.compilation;
if (compilation) {
compilations.push(compilation);
}
}
return compilations;
}
});
let defaultNormalizedStackFrameLocation = frame.file;
if (defaultNormalizedStackFrameLocation !== null && defaultNormalizedStackFrameLocation.startsWith('file://')) {
defaultNormalizedStackFrameLocation = path.relative(rootDirectory, fileURLToPath(defaultNormalizedStackFrameLocation));
}
// This stack frame is used for the one that couldn't locate the source or source mapped frame
const defaultStackFrame = {
file: defaultNormalizedStackFrameLocation,
line1: frame.line1,
column1: frame.column1,
methodName: frame.methodName,
ignored: shouldIgnoreSource(filename),
arguments: []
};
if (!source) {
// return original stack frame with no source map
return {
originalStackFrame: defaultStackFrame,
originalCodeFrame: null
};
}
defaultStackFrame.ignored ||= sourceMapIgnoreListsEverything(source.sourceMap);
const originalStackFrameResponse = await createOriginalStackFrame({
ignoredByDefault: defaultStackFrame.ignored,
frame,
source,
rootDirectory
});
if (!originalStackFrameResponse) {
return {
originalStackFrame: defaultStackFrame,
originalCodeFrame: null
};
}
return originalStackFrameResponse;
}
export function getOverlayMiddleware(options) {
const { rootDirectory, isSrcDir, clientStats, serverStats, edgeServerStats } = options;
return async function(req, res, next) {
const { pathname, searchParams } = new URL(`http://n${req.url}`);
if (pathname === '/__nextjs_original-stack-frames') {
if (req.method !== 'POST') {
return middlewareResponse.badRequest(res);
}
const body = await new Promise((resolve, reject)=>{
let data = '';
req.on('data', (chunk)=>{
data += chunk;
});
req.on('end', ()=>resolve(data));
req.on('error', reject);
});
try {
const { frames, isServer, isEdgeServer, isAppDirectory } = JSON.parse(body);
return middlewareResponse.json(res, await getOriginalStackFrames({
isServer,
isEdgeServer,
isAppDirectory,
frames,
clientStats,
serverStats,
edgeServerStats,
rootDirectory
}));
} catch (err) {
return middlewareResponse.badRequest(res);
}
} else if (pathname === '/__nextjs_launch-editor') {
const frame = {
file: searchParams.get('file'),
methodName: searchParams.get('methodName'),
line1: parseInt(searchParams.get('line1') ?? '1', 10) || 1,
column1: parseInt(searchParams.get('column1') ?? '1', 10) || 1,
arguments: searchParams.getAll('arguments').filter(Boolean)
};
if (!frame.file) return middlewareResponse.badRequest(res);
let openEditorResult;
const isAppRelativePath = searchParams.get('isAppRelativePath') === '1';
if (isAppRelativePath) {
const relativeFilePath = searchParams.get('file') || '';
const appPath = path.join('app', isSrcDir ? 'src' : '', relativeFilePath);
openEditorResult = await openFileInEditor(appPath, 1, 1, rootDirectory);
} else {
// TODO: How do we differentiate layers and actual file paths with round brackets?
// frame files may start with their webpack layer, like (middleware)/middleware.js
const filePath = frame.file.replace(/^\([^)]+\)\//, '');
openEditorResult = await openFileInEditor(filePath, frame.line1, frame.column1 ?? 1, rootDirectory);
}
if (openEditorResult.error) {
console.error('Failed to launch editor:', openEditorResult.error);
return middlewareResponse.internalServerError(res, openEditorResult.error);
}
if (!openEditorResult.found) {
return middlewareResponse.notFound(res);
}
return middlewareResponse.noContent(res);
}
return next();
};
}
export function getSourceMapMiddleware(options) {
const { clientStats, serverStats, edgeServerStats } = options;
return async function(req, res, next) {
const { pathname, searchParams } = new URL(`http://n${req.url}`);
if (pathname !== '/__nextjs_source-map') {
return next();
}
const filename = searchParams.get('filename');
if (!filename) {
return middlewareResponse.badRequest(res);
}
let source;
try {
source = await getSource({
file: filename,
// Webpack doesn't use Index Source Maps
line1: null,
column1: null
}, {
getCompilations: ()=>{
const compilations = [];
for (const stats of [
clientStats(),
serverStats(),
edgeServerStats()
]){
if (stats == null ? void 0 : stats.compilation) {
compilations.push(stats.compilation);
}
}
return compilations;
}
});
} catch (error) {
return middlewareResponse.internalServerError(res, error);
}
if (!source) {
return middlewareResponse.noContent(res);
}
return middlewareResponse.json(res, source.sourceMap);
};
}
//# sourceMappingURL=middleware-webpack.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,661 @@
import { addRequestMeta, getRequestMeta } from '../request-meta';
import * as React from 'react';
import fs from 'fs';
import { Worker } from 'next/dist/compiled/jest-worker';
import { join as pathJoin } from 'path';
import { PUBLIC_DIR_MIDDLEWARE_CONFLICT } from '../../lib/constants';
import { findPagesDir } from '../../lib/find-pages-dir';
import { PHASE_DEVELOPMENT_SERVER, PAGES_MANIFEST, APP_PATHS_MANIFEST, COMPILER_NAMES, PRERENDER_MANIFEST } from '../../shared/lib/constants';
import Server, { WrappedBuildError } from '../next-server';
import { normalizePagePath } from '../../shared/lib/page-path/normalize-page-path';
import { pathHasPrefix } from '../../shared/lib/router/utils/path-has-prefix';
import { removePathPrefix } from '../../shared/lib/router/utils/remove-path-prefix';
import { Telemetry } from '../../telemetry/storage';
import { setGlobal, trace } from '../../trace';
import { traceGlobals } from '../../trace/shared';
import { findPageFile } from '../lib/find-page-file';
import { getFormattedNodeOptionsWithoutInspect } from '../lib/utils';
import { withCoalescedInvoke } from '../../lib/coalesced-function';
import { loadDefaultErrorComponents } from '../load-default-error-components';
import { DecodeError, MiddlewareNotFoundError } from '../../shared/lib/utils';
import * as Log from '../../build/output/log';
import isError, { getProperError } from '../../lib/is-error';
import { defaultConfig } from '../config-shared';
import { isMiddlewareFile } from '../../build/utils';
import { formatServerError } from '../../lib/format-server-error';
import { DevRouteMatcherManager } from '../route-matcher-managers/dev-route-matcher-manager';
import { DevPagesRouteMatcherProvider } from '../route-matcher-providers/dev/dev-pages-route-matcher-provider';
import { DevPagesAPIRouteMatcherProvider } from '../route-matcher-providers/dev/dev-pages-api-route-matcher-provider';
import { DevAppPageRouteMatcherProvider } from '../route-matcher-providers/dev/dev-app-page-route-matcher-provider';
import { DevAppRouteRouteMatcherProvider } from '../route-matcher-providers/dev/dev-app-route-route-matcher-provider';
import { NodeManifestLoader } from '../route-matcher-providers/helpers/manifest-loaders/node-manifest-loader';
import { BatchedFileReader } from '../route-matcher-providers/dev/helpers/file-reader/batched-file-reader';
import { DefaultFileReader } from '../route-matcher-providers/dev/helpers/file-reader/default-file-reader';
import { LRUCache } from '../lib/lru-cache';
import { getMiddlewareRouteMatcher } from '../../shared/lib/router/utils/middleware-route-matcher';
import { DetachedPromise } from '../../lib/detached-promise';
import { isPostpone } from '../lib/router-utils/is-postpone';
import { generateInterceptionRoutesRewrites } from '../../lib/generate-interception-routes-rewrites';
import { buildCustomRoute } from '../../lib/build-custom-route';
import { decorateServerError } from '../../shared/lib/error-source';
import { logRequests } from './log-requests';
import { FallbackMode, fallbackModeToFallbackField } from '../../lib/fallback';
import { ensureInstrumentationRegistered, getInstrumentationModule } from '../lib/router-utils/instrumentation-globals.external';
import { getRouteRegex } from '../../shared/lib/router/utils/route-regex';
import { HMR_MESSAGE_SENT_TO_BROWSER } from './hot-reloader-types';
// Load ReactDevOverlay only when needed
let PagesDevOverlayBridgeImpl;
const ReactDevOverlay = (props)=>{
if (PagesDevOverlayBridgeImpl === undefined) {
PagesDevOverlayBridgeImpl = require('../../next-devtools/userspace/pages/pages-dev-overlay-setup').PagesDevOverlayBridge;
}
return React.createElement(PagesDevOverlayBridgeImpl, props);
};
export default class DevServer extends Server {
getStaticPathsWorker() {
const worker = new Worker(require.resolve('./static-paths-worker'), {
maxRetries: 1,
// For dev server, it's not necessary to spin up too many workers as long as you are not doing a load test.
// This helps reusing the memory a lot.
numWorkers: 1,
enableWorkerThreads: this.nextConfig.experimental.workerThreads,
forkOptions: {
env: {
...process.env,
// discard --inspect/--inspect-brk flags from process.env.NODE_OPTIONS. Otherwise multiple Node.js debuggers
// would be started if user launch Next.js in debugging mode. The number of debuggers is linked to
// the number of workers Next.js tries to launch. The only worker users are interested in debugging
// is the main Next.js one
NODE_OPTIONS: getFormattedNodeOptionsWithoutInspect()
}
}
});
worker.getStdout().pipe(process.stdout);
worker.getStderr().pipe(process.stderr);
return worker;
}
constructor(options){
try {
// Increase the number of stack frames on the server
Error.stackTraceLimit = 50;
} catch {}
super({
...options,
dev: true
}), /**
* The promise that resolves when the server is ready. When this is unset
* the server is ready.
*/ this.ready = new DetachedPromise();
this.nextConfig = options.conf;
this.bundlerService = options.bundlerService;
this.startServerSpan = options.startServerSpan ?? trace('start-next-dev-server');
this.renderOpts.dev = true;
this.renderOpts.ErrorDebug = ReactDevOverlay;
this.staticPathsCache = new LRUCache(// 5MB
5 * 1024 * 1024, function length(value) {
var _JSON_stringify;
return ((_JSON_stringify = JSON.stringify(value.staticPaths)) == null ? void 0 : _JSON_stringify.length) ?? 0;
});
const { pagesDir, appDir } = findPagesDir(this.dir);
this.pagesDir = pagesDir;
this.appDir = appDir;
if (this.nextConfig.experimental.serverComponentsHmrCache) {
// Ensure HMR cache has a minimum size equal to the default cacheMaxMemorySize,
// but allow it to grow if the user has configured a larger value.
const hmrCacheSize = Math.max(this.nextConfig.cacheMaxMemorySize, defaultConfig.cacheMaxMemorySize);
this.serverComponentsHmrCache = new LRUCache(hmrCacheSize, function length(value) {
return JSON.stringify(value).length;
});
}
}
getServerComponentsHmrCache() {
return this.serverComponentsHmrCache;
}
getRouteMatchers() {
const { pagesDir, appDir } = findPagesDir(this.dir);
const ensurer = {
ensure: async (match, pathname)=>{
await this.ensurePage({
definition: match.definition,
page: match.definition.page,
clientOnly: false,
url: pathname
});
}
};
const matchers = new DevRouteMatcherManager(super.getRouteMatchers(), ensurer, this.dir);
const extensions = this.nextConfig.pageExtensions;
const extensionsExpression = new RegExp(`\\.(?:${extensions.join('|')})$`);
// If the pages directory is available, then configure those matchers.
if (pagesDir) {
const fileReader = new BatchedFileReader(new DefaultFileReader({
// Only allow files that have the correct extensions.
pathnameFilter: (pathname)=>extensionsExpression.test(pathname)
}));
matchers.push(new DevPagesRouteMatcherProvider(pagesDir, extensions, fileReader, this.localeNormalizer));
matchers.push(new DevPagesAPIRouteMatcherProvider(pagesDir, extensions, fileReader, this.localeNormalizer));
}
if (appDir) {
// We create a new file reader for the app directory because we don't want
// to include any folders or files starting with an underscore. This will
// prevent the reader from wasting time reading files that we know we
// don't care about.
const fileReader = new BatchedFileReader(new DefaultFileReader({
// Ignore any directory prefixed with an underscore.
ignorePartFilter: (part)=>part.startsWith('_')
}));
// TODO: Improve passing of "is running with Turbopack"
const isTurbopack = !!process.env.TURBOPACK;
matchers.push(new DevAppPageRouteMatcherProvider(appDir, extensions, fileReader, isTurbopack));
matchers.push(new DevAppRouteRouteMatcherProvider(appDir, extensions, fileReader, isTurbopack));
}
return matchers;
}
getBuildId() {
return 'development';
}
async prepareImpl() {
var _this_ready;
setGlobal('distDir', this.distDir);
setGlobal('phase', PHASE_DEVELOPMENT_SERVER);
// Use existing telemetry instance from traceGlobals instead of creating a new one.
// Creating a new instance would overwrite the existing one, causing any telemetry
// events recorded to the original instance to be lost during cleanup/flush.
const existingTelemetry = traceGlobals.get('telemetry');
const telemetry = existingTelemetry || new Telemetry({
distDir: this.distDir
});
await super.prepareImpl();
await this.matchers.reload();
(_this_ready = this.ready) == null ? void 0 : _this_ready.resolve();
this.ready = undefined;
// In dev, this needs to be called after prepare because the build entries won't be known in the constructor
this.interceptionRoutePatterns = this.getinterceptionRoutePatterns();
// This is required by the tracing subsystem.
setGlobal('appDir', this.appDir);
setGlobal('pagesDir', this.pagesDir);
// Only set telemetry if it wasn't already set
if (!existingTelemetry) {
setGlobal('telemetry', telemetry);
}
process.on('unhandledRejection', (reason)=>{
if (isPostpone(reason)) {
// React postpones that are unhandled might end up logged here but they're
// not really errors. They're just part of rendering.
return;
}
this.logErrorWithOriginalStack(reason, 'unhandledRejection');
});
process.on('uncaughtException', (err)=>{
this.logErrorWithOriginalStack(err, 'uncaughtException');
});
}
async hasPage(pathname) {
let normalizedPath;
try {
normalizedPath = normalizePagePath(pathname);
} catch (err) {
console.error(err);
// if normalizing the page fails it means it isn't valid
// so it doesn't exist so don't throw and return false
// to ensure we return 404 instead of 500
return false;
}
if (isMiddlewareFile(normalizedPath)) {
return findPageFile(this.dir, normalizedPath, this.nextConfig.pageExtensions, false).then(Boolean);
}
let appFile = null;
let pagesFile = null;
if (this.appDir) {
appFile = await findPageFile(this.appDir, normalizedPath + '/page', this.nextConfig.pageExtensions, true);
}
if (this.pagesDir) {
pagesFile = await findPageFile(this.pagesDir, normalizedPath, this.nextConfig.pageExtensions, false);
}
if (appFile && pagesFile) {
return false;
}
return Boolean(appFile || pagesFile);
}
async runMiddleware(params) {
try {
const result = await super.runMiddleware({
...params,
onWarning: (warn)=>{
this.logErrorWithOriginalStack(warn, 'warning');
}
});
if ('finished' in result) {
return result;
}
result.waitUntil.catch((error)=>{
this.logErrorWithOriginalStack(error, 'unhandledRejection');
});
return result;
} catch (error) {
if (error instanceof DecodeError) {
throw error;
}
/**
* We only log the error when it is not a MiddlewareNotFound error as
* in that case we should be already displaying a compilation error
* which is what makes the module not found.
*/ if (!(error instanceof MiddlewareNotFoundError)) {
this.logErrorWithOriginalStack(error);
}
const err = getProperError(error);
decorateServerError(err, COMPILER_NAMES.edgeServer);
const { request, response, parsedUrl } = params;
/**
* When there is a failure for an internal Next.js request from
* middleware we bypass the error without finishing the request
* so we can serve the required chunks to render the error.
*/ if (request.url.includes('/_next/static') || request.url.includes('/__nextjs_attach-nodejs-inspector') || request.url.includes('/__nextjs_original-stack-frame') || request.url.includes('/__nextjs_source-map') || request.url.includes('/__nextjs_error_feedback')) {
return {
finished: false
};
}
response.statusCode = 500;
await this.renderError(err, request, response, parsedUrl.pathname);
return {
finished: true
};
}
}
async runEdgeFunction(params) {
try {
return super.runEdgeFunction({
...params,
onError: (err)=>this.logErrorWithOriginalStack(err, 'app-dir'),
onWarning: (warn)=>{
this.logErrorWithOriginalStack(warn, 'warning');
}
});
} catch (error) {
if (error instanceof DecodeError) {
throw error;
}
this.logErrorWithOriginalStack(error, 'warning');
const err = getProperError(error);
const { req, res, page } = params;
res.statusCode = 500;
await this.renderError(err, req, res, page);
return null;
}
}
getRequestHandler() {
const handler = super.getRequestHandler();
return (req, res, parsedUrl)=>{
const request = this.normalizeReq(req);
const response = this.normalizeRes(res);
const loggingConfig = this.nextConfig.logging;
if (loggingConfig !== false) {
// The closure variable is not used here because the request handler may be invoked twice for one request when middleware is added in the application.
// By setting the start time we can ensure that the middleware timing is correctly included.
if (!getRequestMeta(req, 'devRequestTimingStart')) {
const requestStart = process.hrtime.bigint();
addRequestMeta(req, 'devRequestTimingStart', requestStart);
}
const isMiddlewareRequest = getRequestMeta(req, 'middlewareInvoke') ?? false;
if (!isMiddlewareRequest) {
response.originalResponse.once('close', ()=>{
// NOTE: The route match is only attached to the request's meta data
// after the request handler is created, so we need to check it in the
// close handler and not before.
const routeMatch = getRequestMeta(req).match;
if (!routeMatch) {
return;
}
// The closure variable is not used here because the request handler may be invoked twice for one request when middleware is added in the application.
// By setting the start time we can ensure that the middleware timing is correctly included.
const requestStart = getRequestMeta(req, 'devRequestTimingStart');
if (!requestStart) {
return;
}
const requestEnd = process.hrtime.bigint();
logRequests(request, response, loggingConfig, requestStart, requestEnd, getRequestMeta(req, 'devRequestTimingMiddlewareStart'), getRequestMeta(req, 'devRequestTimingMiddlewareEnd'), getRequestMeta(req, 'devRequestTimingInternalsEnd'), getRequestMeta(req, 'devGenerateStaticParamsDuration'));
});
}
}
return handler(request, response, parsedUrl);
};
}
async handleRequest(req, res, parsedUrl) {
const span = trace('handle-request', undefined, {
url: req.url
});
const result = await span.traceAsyncFn(async ()=>{
var _this_ready;
await ((_this_ready = this.ready) == null ? void 0 : _this_ready.promise);
addRequestMeta(req, 'PagesErrorDebug', this.renderOpts.ErrorDebug);
return await super.handleRequest(req, res, parsedUrl);
});
const memoryUsage = process.memoryUsage();
span.traceChild('memory-usage', {
url: req.url,
'memory.rss': String(memoryUsage.rss),
'memory.heapUsed': String(memoryUsage.heapUsed),
'memory.heapTotal': String(memoryUsage.heapTotal)
}).stop();
return result;
}
async run(req, res, parsedUrl) {
var _this_ready;
await ((_this_ready = this.ready) == null ? void 0 : _this_ready.promise);
const { basePath } = this.nextConfig;
let originalPathname = null;
// TODO: see if we can remove this in the future
if (basePath && pathHasPrefix(parsedUrl.pathname || '/', basePath)) {
// strip basePath before handling dev bundles
// If replace ends up replacing the full url it'll be `undefined`, meaning we have to default it to `/`
originalPathname = parsedUrl.pathname;
parsedUrl.pathname = removePathPrefix(parsedUrl.pathname || '/', basePath);
}
const { pathname } = parsedUrl;
if (pathname.startsWith('/_next')) {
if (fs.existsSync(pathJoin(this.publicDir, '_next'))) {
throw Object.defineProperty(new Error(PUBLIC_DIR_MIDDLEWARE_CONFLICT), "__NEXT_ERROR_CODE", {
value: "E394",
enumerable: false,
configurable: true
});
}
}
if (originalPathname) {
// restore the path before continuing so that custom-routes can accurately determine
// if they should match against the basePath or not
parsedUrl.pathname = originalPathname;
}
try {
return await super.run(req, res, parsedUrl);
} catch (error) {
const err = getProperError(error);
formatServerError(err);
this.logErrorWithOriginalStack(err);
if (!res.sent) {
res.statusCode = 500;
try {
return await this.renderError(err, req, res, pathname, {
__NEXT_PAGE: isError(err) && err.page || pathname || ''
});
} catch (internalErr) {
console.error(internalErr);
res.body('Internal Server Error').send();
}
}
}
}
logErrorWithOriginalStack(err, type) {
this.bundlerService.logErrorWithOriginalStack(err, type);
}
getPagesManifest() {
return NodeManifestLoader.require(pathJoin(this.serverDistDir, PAGES_MANIFEST)) ?? undefined;
}
getAppPathsManifest() {
if (!this.enabledDirectories.app) return undefined;
return NodeManifestLoader.require(pathJoin(this.serverDistDir, APP_PATHS_MANIFEST)) ?? undefined;
}
getinterceptionRoutePatterns() {
const rewrites = generateInterceptionRoutesRewrites(Object.keys(this.appPathRoutes ?? {}), this.nextConfig.basePath).map((route)=>new RegExp(buildCustomRoute('rewrite', route).regex));
if (this.nextConfig.output === 'export' && rewrites.length > 0) {
Log.error('Intercepting routes are not supported with static export.\nRead more: https://nextjs.org/docs/app/building-your-application/deploying/static-exports#unsupported-features');
process.exit(1);
}
return rewrites ?? [];
}
async getMiddleware() {
var _this_middleware;
// We need to populate the match
// field as it isn't serializable
if (((_this_middleware = this.middleware) == null ? void 0 : _this_middleware.match) === null) {
this.middleware.match = getMiddlewareRouteMatcher(this.middleware.matchers || []);
}
return this.middleware;
}
getNextFontManifest() {
return undefined;
}
async hasMiddleware() {
return this.hasPage(this.actualMiddlewareFile);
}
async ensureMiddleware(url) {
return this.ensurePage({
page: this.actualMiddlewareFile,
clientOnly: false,
definition: undefined,
url
});
}
async loadInstrumentationModule() {
let instrumentationModule;
if (this.actualInstrumentationHookFile && await this.ensurePage({
page: this.actualInstrumentationHookFile,
clientOnly: false,
definition: undefined
}).then(()=>true).catch(()=>false)) {
try {
instrumentationModule = await getInstrumentationModule(this.dir, this.nextConfig.distDir);
} catch (err) {
err.message = `An error occurred while loading instrumentation hook: ${err.message}`;
throw err;
}
}
return instrumentationModule;
}
async runInstrumentationHookIfAvailable() {
await ensureInstrumentationRegistered(this.dir, this.nextConfig.distDir);
}
async ensureEdgeFunction({ page, appPaths, url }) {
return this.ensurePage({
page,
appPaths,
clientOnly: false,
definition: undefined,
url
});
}
generateRoutes(_dev) {
// In development we expose all compiled files for react-error-overlay's line show feature
// We use unshift so that we're sure the routes is defined before Next's default routes
// routes.unshift({
// match: getPathMatch('/_next/development/:path*'),
// type: 'route',
// name: '_next/development catchall',
// fn: async (req, res, params) => {
// const p = pathJoin(this.distDir, ...(params.path || []))
// await this.serveStatic(req, res, p)
// return {
// finished: true,
// }
// },
// })
}
async getStaticPaths({ pathname, urlPathname, requestHeaders, page, isAppPath }) {
// we lazy load the staticPaths to prevent the user
// from waiting on them for the page to load in dev mode
const __getStaticPaths = async ()=>{
const { configFileName, httpAgentOptions } = this.nextConfig;
const { locales, defaultLocale } = this.nextConfig.i18n || {};
const staticPathsWorker = this.getStaticPathsWorker();
try {
var _this_nextConfig_experimental_sri;
const pathsResult = await staticPathsWorker.loadStaticPaths({
dir: this.dir,
distDir: this.distDir,
pathname,
config: {
pprConfig: this.nextConfig.experimental.ppr,
configFileName,
cacheComponents: Boolean(this.nextConfig.cacheComponents)
},
httpAgentOptions,
locales,
defaultLocale,
page,
isAppPath,
requestHeaders,
cacheHandler: this.nextConfig.cacheHandler,
cacheHandlers: this.nextConfig.cacheHandlers,
cacheLifeProfiles: this.nextConfig.cacheLife,
fetchCacheKeyPrefix: this.nextConfig.experimental.fetchCacheKeyPrefix,
isrFlushToDisk: this.nextConfig.experimental.isrFlushToDisk,
cacheMaxMemorySize: this.nextConfig.cacheMaxMemorySize,
nextConfigOutput: this.nextConfig.output,
buildId: this.buildId,
authInterrupts: Boolean(this.nextConfig.experimental.authInterrupts),
sriEnabled: Boolean((_this_nextConfig_experimental_sri = this.nextConfig.experimental.sri) == null ? void 0 : _this_nextConfig_experimental_sri.algorithm)
});
return pathsResult;
} finally{
// we don't re-use workers so destroy the used one
staticPathsWorker.end();
}
};
const result = this.staticPathsCache.get(pathname);
const nextInvoke = withCoalescedInvoke(__getStaticPaths)(`staticPaths-${pathname}`, []).then(async (res)=>{
var _res_value;
const { prerenderedRoutes, fallbackMode: fallback } = res.value;
if (isAppPath) {
var // Ideally, we would want to compare the whole objects, but that is too expensive.
_result_prerenderedRoutes;
if (this.nextConfig.output === 'export') {
if (!prerenderedRoutes) {
throw Object.defineProperty(new Error(`Page "${page}" is missing exported function "generateStaticParams()", which is required with "output: export" config.`), "__NEXT_ERROR_CODE", {
value: "E353",
enumerable: false,
configurable: true
});
}
if (!prerenderedRoutes.some((item)=>item.pathname === urlPathname)) {
throw Object.defineProperty(new Error(`Page "${page}" is missing param "${pathname}" in "generateStaticParams()", which is required with "output: export" config.`), "__NEXT_ERROR_CODE", {
value: "E443",
enumerable: false,
configurable: true
});
}
}
// Since generateStaticParams run on the background, when accessing the
// devFallbackParams during the render, it is still set to the previous
// result from the cache. Therefore when the result has changed, re-render
// the Server Component to sync the devFallbackParams with the new result.
if (isAppPath && this.nextConfig.cacheComponents && // Ensure this is not the first invocation.
result && ((_result_prerenderedRoutes = result.prerenderedRoutes) == null ? void 0 : _result_prerenderedRoutes.length) !== (prerenderedRoutes == null ? void 0 : prerenderedRoutes.length)) {
this.bundlerService.sendHmrMessage({
type: HMR_MESSAGE_SENT_TO_BROWSER.SERVER_COMPONENT_CHANGES,
hash: `generateStaticParams-${Date.now()}`
});
}
}
if (!isAppPath && this.nextConfig.output === 'export') {
if (fallback === FallbackMode.BLOCKING_STATIC_RENDER) {
throw Object.defineProperty(new Error('getStaticPaths with "fallback: blocking" cannot be used with "output: export". See more info here: https://nextjs.org/docs/advanced-features/static-html-export'), "__NEXT_ERROR_CODE", {
value: "E11",
enumerable: false,
configurable: true
});
} else if (fallback === FallbackMode.PRERENDER) {
throw Object.defineProperty(new Error('getStaticPaths with "fallback: true" cannot be used with "output: export". See more info here: https://nextjs.org/docs/advanced-features/static-html-export'), "__NEXT_ERROR_CODE", {
value: "E210",
enumerable: false,
configurable: true
});
}
}
const value = {
staticPaths: prerenderedRoutes == null ? void 0 : prerenderedRoutes.map((route)=>route.pathname),
prerenderedRoutes,
fallbackMode: fallback
};
if (((_res_value = res.value) == null ? void 0 : _res_value.fallbackMode) !== undefined && // This matches the hasGenerateStaticParams logic we do during build.
(!isAppPath || prerenderedRoutes && prerenderedRoutes.length > 0)) {
// we write the static paths to partial manifest for
// fallback handling inside of entry handler's
const rawExistingManifest = await fs.promises.readFile(pathJoin(this.distDir, PRERENDER_MANIFEST), 'utf8');
const existingManifest = JSON.parse(rawExistingManifest);
for (const staticPath of value.staticPaths || []){
existingManifest.routes[staticPath] = {};
}
existingManifest.dynamicRoutes[pathname] = {
dataRoute: null,
dataRouteRegex: null,
fallback: fallbackModeToFallbackField(res.value.fallbackMode, page),
fallbackRevalidate: false,
fallbackExpire: undefined,
fallbackHeaders: undefined,
fallbackStatus: undefined,
fallbackRootParams: undefined,
fallbackRouteParams: undefined,
fallbackSourceRoute: pathname,
prefetchDataRoute: undefined,
prefetchDataRouteRegex: undefined,
routeRegex: getRouteRegex(pathname).re.source,
experimentalPPR: undefined,
renderingMode: undefined,
allowHeader: []
};
const updatedManifest = JSON.stringify(existingManifest);
if (updatedManifest !== rawExistingManifest) {
await fs.promises.writeFile(pathJoin(this.distDir, PRERENDER_MANIFEST), updatedManifest);
}
}
this.staticPathsCache.set(pathname, value);
return value;
}).catch((err)=>{
this.staticPathsCache.remove(pathname);
if (!result) throw err;
Log.error(`Failed to generate static paths for ${pathname}:`);
console.error(err);
});
if (result) {
return result;
}
return nextInvoke;
}
async ensurePage(opts) {
await this.bundlerService.ensurePage(opts);
}
async findPageComponents({ locale, page, query, params, isAppPath, appPaths = null, shouldEnsure, url }) {
var _this_ready;
await ((_this_ready = this.ready) == null ? void 0 : _this_ready.promise);
const compilationErr = await this.getCompilationError(page);
if (compilationErr) {
// Wrap build errors so that they don't get logged again
throw new WrappedBuildError(compilationErr);
}
if (shouldEnsure || this.serverOptions.customServer) {
await this.ensurePage({
page,
appPaths,
clientOnly: false,
definition: undefined,
url
});
}
this.nextFontManifest = super.getNextFontManifest();
return await super.findPageComponents({
page,
query,
params,
locale,
isAppPath,
shouldEnsure,
url
});
}
async getFallbackErrorComponents(url) {
await this.bundlerService.getFallbackErrorComponents(url);
return await loadDefaultErrorComponents(this.distDir);
}
async getCompilationError(page) {
return await this.bundlerService.getCompilationError(page);
}
async instrumentationOnRequestError(...args) {
await super.instrumentationOnRequestError(...args);
const [err, , , silenceLog] = args;
if (!silenceLog) {
this.logErrorWithOriginalStack(err, 'app-dir');
}
}
}
//# sourceMappingURL=next-dev-server.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,62 @@
import { parse } from 'next/dist/compiled/stacktrace-parser';
import { decorateServerError } from '../../shared/lib/error-source';
function getFilesystemFrame(frame) {
const f = {
...frame
};
if (typeof f.file === 'string') {
if (// Posix:
f.file.startsWith('/') || // Win32:
/^[a-z]:\\/i.test(f.file) || // Win32 UNC:
f.file.startsWith('\\\\')) {
f.file = `file://${f.file}`;
}
}
return f;
}
export function getServerError(error, type) {
if (error.name === 'TurbopackInternalError') {
// If this is an internal Turbopack error we shouldn't show internal details
// to the user. These are written to a log file instead.
const turbopackInternalError = Object.defineProperty(new Error('An unexpected Turbopack error occurred. Please see the output of `next dev` for more details.'), "__NEXT_ERROR_CODE", {
value: "E167",
enumerable: false,
configurable: true
});
decorateServerError(turbopackInternalError, type);
return turbopackInternalError;
}
let n;
try {
throw Object.defineProperty(new Error(error.message), "__NEXT_ERROR_CODE", {
value: "E394",
enumerable: false,
configurable: true
});
} catch (e) {
n = e;
}
n.name = error.name;
try {
n.stack = `${n.toString()}\n${parse(error.stack).map(getFilesystemFrame).map((f)=>{
let str = ` at ${f.methodName}`;
if (f.file) {
let loc = f.file;
if (f.lineNumber) {
loc += `:${f.lineNumber}`;
if (f.column) {
loc += `:${f.column}`;
}
}
str += ` (${loc})`;
}
return str;
}).join('\n')}`;
} catch {
n.stack = error.stack;
}
decorateServerError(n, type);
return n;
}
//# sourceMappingURL=node-stack-frames.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/server/dev/node-stack-frames.ts"],"sourcesContent":["import { parse } from 'next/dist/compiled/stacktrace-parser'\nimport type { StackFrame } from 'next/dist/compiled/stacktrace-parser'\nimport {\n decorateServerError,\n type ErrorSourceType,\n} from '../../shared/lib/error-source'\n\nfunction getFilesystemFrame(frame: StackFrame): StackFrame {\n const f: StackFrame = { ...frame }\n\n if (typeof f.file === 'string') {\n if (\n // Posix:\n f.file.startsWith('/') ||\n // Win32:\n /^[a-z]:\\\\/i.test(f.file) ||\n // Win32 UNC:\n f.file.startsWith('\\\\\\\\')\n ) {\n f.file = `file://${f.file}`\n }\n }\n\n return f\n}\n\nexport function getServerError(error: Error, type: ErrorSourceType): Error {\n if (error.name === 'TurbopackInternalError') {\n // If this is an internal Turbopack error we shouldn't show internal details\n // to the user. These are written to a log file instead.\n const turbopackInternalError = new Error(\n 'An unexpected Turbopack error occurred. Please see the output of `next dev` for more details.'\n )\n decorateServerError(turbopackInternalError, type)\n return turbopackInternalError\n }\n\n let n: Error\n try {\n throw new Error(error.message)\n } catch (e) {\n n = e as Error\n }\n\n n.name = error.name\n try {\n n.stack = `${n.toString()}\\n${parse(error.stack!)\n .map(getFilesystemFrame)\n .map((f) => {\n let str = ` at ${f.methodName}`\n if (f.file) {\n let loc = f.file\n if (f.lineNumber) {\n loc += `:${f.lineNumber}`\n if (f.column) {\n loc += `:${f.column}`\n }\n }\n str += ` (${loc})`\n }\n return str\n })\n .join('\\n')}`\n } catch {\n n.stack = error.stack\n }\n\n decorateServerError(n, type)\n return n\n}\n"],"names":["parse","decorateServerError","getFilesystemFrame","frame","f","file","startsWith","test","getServerError","error","type","name","turbopackInternalError","Error","n","message","e","stack","toString","map","str","methodName","loc","lineNumber","column","join"],"mappings":"AAAA,SAASA,KAAK,QAAQ,uCAAsC;AAE5D,SACEC,mBAAmB,QAEd,gCAA+B;AAEtC,SAASC,mBAAmBC,KAAiB;IAC3C,MAAMC,IAAgB;QAAE,GAAGD,KAAK;IAAC;IAEjC,IAAI,OAAOC,EAAEC,IAAI,KAAK,UAAU;QAC9B,IACE,SAAS;QACTD,EAAEC,IAAI,CAACC,UAAU,CAAC,QAClB,SAAS;QACT,aAAaC,IAAI,CAACH,EAAEC,IAAI,KACxB,aAAa;QACbD,EAAEC,IAAI,CAACC,UAAU,CAAC,SAClB;YACAF,EAAEC,IAAI,GAAG,CAAC,OAAO,EAAED,EAAEC,IAAI,EAAE;QAC7B;IACF;IAEA,OAAOD;AACT;AAEA,OAAO,SAASI,eAAeC,KAAY,EAAEC,IAAqB;IAChE,IAAID,MAAME,IAAI,KAAK,0BAA0B;QAC3C,4EAA4E;QAC5E,wDAAwD;QACxD,MAAMC,yBAAyB,qBAE9B,CAF8B,IAAIC,MACjC,kGAD6B,qBAAA;mBAAA;wBAAA;0BAAA;QAE/B;QACAZ,oBAAoBW,wBAAwBF;QAC5C,OAAOE;IACT;IAEA,IAAIE;IACJ,IAAI;QACF,MAAM,qBAAwB,CAAxB,IAAID,MAAMJ,MAAMM,OAAO,GAAvB,qBAAA;mBAAA;wBAAA;0BAAA;QAAuB;IAC/B,EAAE,OAAOC,GAAG;QACVF,IAAIE;IACN;IAEAF,EAAEH,IAAI,GAAGF,MAAME,IAAI;IACnB,IAAI;QACFG,EAAEG,KAAK,GAAG,GAAGH,EAAEI,QAAQ,GAAG,EAAE,EAAElB,MAAMS,MAAMQ,KAAK,EAC5CE,GAAG,CAACjB,oBACJiB,GAAG,CAAC,CAACf;YACJ,IAAIgB,MAAM,CAAC,OAAO,EAAEhB,EAAEiB,UAAU,EAAE;YAClC,IAAIjB,EAAEC,IAAI,EAAE;gBACV,IAAIiB,MAAMlB,EAAEC,IAAI;gBAChB,IAAID,EAAEmB,UAAU,EAAE;oBAChBD,OAAO,CAAC,CAAC,EAAElB,EAAEmB,UAAU,EAAE;oBACzB,IAAInB,EAAEoB,MAAM,EAAE;wBACZF,OAAO,CAAC,CAAC,EAAElB,EAAEoB,MAAM,EAAE;oBACvB;gBACF;gBACAJ,OAAO,CAAC,EAAE,EAAEE,IAAI,CAAC,CAAC;YACpB;YACA,OAAOF;QACT,GACCK,IAAI,CAAC,OAAO;IACjB,EAAE,OAAM;QACNX,EAAEG,KAAK,GAAGR,MAAMQ,KAAK;IACvB;IAEAhB,oBAAoBa,GAAGJ;IACvB,OAAOI;AACT","ignoreList":[0]}

View File

@@ -0,0 +1,666 @@
import createDebug from 'next/dist/compiled/debug';
import { EventEmitter } from 'events';
import { findPageFile } from '../lib/find-page-file';
import { runDependingOnPageType } from '../../build/entries';
import { getStaticInfoIncludingLayouts } from '../../build/get-static-info-including-layouts';
import { join, posix } from 'path';
import { normalizePathSep } from '../../shared/lib/page-path/normalize-path-sep';
import { normalizePagePath } from '../../shared/lib/page-path/normalize-page-path';
import { ensureLeadingSlash } from '../../shared/lib/page-path/ensure-leading-slash';
import { removePagePathTail } from '../../shared/lib/page-path/remove-page-path-tail';
import { reportTrigger } from '../../build/output';
import getRouteFromEntrypoint from '../get-route-from-entrypoint';
import { isInstrumentationHookFile, isInstrumentationHookFilename, isMiddlewareFile, isMiddlewareFilename } from '../../build/utils';
import { PageNotFoundError, stringifyError } from '../../shared/lib/utils';
import { COMPILER_INDEXES, COMPILER_NAMES, RSC_MODULE_TYPES, UNDERSCORE_NOT_FOUND_ROUTE_ENTRY } from '../../shared/lib/constants';
import { PAGE_SEGMENT_KEY } from '../../shared/lib/segment';
import { HMR_MESSAGE_SENT_TO_BROWSER, HMR_MESSAGE_SENT_TO_SERVER } from './hot-reloader-types';
import { isAppPageRouteDefinition } from '../route-definitions/app-page-route-definition';
import { scheduleOnNextTick } from '../../lib/scheduler';
import { Batcher } from '../../lib/batcher';
import { normalizeAppPath } from '../../shared/lib/router/utils/app-paths';
import { PAGE_TYPES } from '../../lib/page-types';
import { getNextFlightSegmentPath } from '../../client/flight-data-helpers';
import { handleErrorStateResponse } from '../mcp/tools/get-errors';
import { handlePageMetadataResponse } from '../mcp/tools/get-page-metadata';
const debug = createDebug('next:on-demand-entry-handler');
/**
* Returns object keys with type inferred from the object key
*/ const keys = Object.keys;
const COMPILER_KEYS = keys(COMPILER_INDEXES);
function treePathToEntrypoint(segmentPath, parentPath) {
const [parallelRouteKey, segment] = segmentPath;
// TODO-APP: modify this path to cover parallelRouteKey convention
const path = (parentPath ? parentPath + '/' : '') + (parallelRouteKey !== 'children' && !segment.startsWith('@') ? `@${parallelRouteKey}/` : '') + (segment === '' ? 'page' : segment);
// Last segment
if (segmentPath.length === 2) {
return path;
}
const childSegmentPath = getNextFlightSegmentPath(segmentPath);
return treePathToEntrypoint(childSegmentPath, path);
}
function convertDynamicParamTypeToSyntax(dynamicParamTypeShort, param) {
switch(dynamicParamTypeShort){
case 'c':
case 'ci(..)(..)':
case 'ci(.)':
case 'ci(..)':
case 'ci(...)':
return `[...${param}]`;
case 'oc':
return `[[...${param}]]`;
case 'd':
case 'di(..)(..)':
case 'di(.)':
case 'di(..)':
case 'di(...)':
return `[${param}]`;
default:
throw Object.defineProperty(new Error('Unknown dynamic param type'), "__NEXT_ERROR_CODE", {
value: "E378",
enumerable: false,
configurable: true
});
}
}
/**
* format: {compiler type}@{page type}@{page path}
* e.g. client@pages@/index
* e.g. server@app@app/page
*
* This guarantees the uniqueness for each page, to avoid conflicts between app/ and pages/
*/ export function getEntryKey(compilerType, pageBundleType, page) {
// TODO: handle the /children slot better
// this is a quick hack to handle when children is provided as children/page instead of /page
const pageKey = page.replace(/(@[^/]+)\/children/g, '$1');
return `${compilerType}@${pageBundleType}@${pageKey}`;
}
function getPageBundleType(pageBundlePath) {
// Handle special case for /_error
if (pageBundlePath === '/_error') return PAGE_TYPES.PAGES;
if (isMiddlewareFilename(pageBundlePath)) return PAGE_TYPES.ROOT;
return pageBundlePath.startsWith('pages/') ? PAGE_TYPES.PAGES : pageBundlePath.startsWith('app/') ? PAGE_TYPES.APP : PAGE_TYPES.ROOT;
}
function getEntrypointsFromTree(tree, isFirst, parentPath = []) {
const [segment, parallelRoutes] = tree;
const currentSegment = Array.isArray(segment) ? convertDynamicParamTypeToSyntax(segment[2], segment[0]) : segment;
const isPageSegment = currentSegment.startsWith(PAGE_SEGMENT_KEY);
const currentPath = [
...parentPath,
isPageSegment ? '' : currentSegment
];
if (!isFirst && isPageSegment) {
// TODO get rid of '' at the start of tree
return [
treePathToEntrypoint(currentPath.slice(1))
];
}
return Object.keys(parallelRoutes).reduce((paths, key)=>{
const childTree = parallelRoutes[key];
const childPages = getEntrypointsFromTree(childTree, false, [
...currentPath,
key
]);
return [
...paths,
...childPages
];
}, []);
}
export const ADDED = Symbol('added');
export const BUILDING = Symbol('building');
export const BUILT = Symbol('built');
// Shadowing check in ESLint does not account for enum
export var EntryTypes = /*#__PURE__*/ function(EntryTypes) {
EntryTypes[EntryTypes["ENTRY"] = 0] = "ENTRY";
EntryTypes[EntryTypes["CHILD_ENTRY"] = 1] = "CHILD_ENTRY";
return EntryTypes;
}({});
const entriesMap = new Map();
// remove /server from end of output for server compiler
const normalizeOutputPath = (dir)=>dir.replace(/[/\\]server$/, '');
export const getEntries = (dir)=>{
dir = normalizeOutputPath(dir);
const entries = entriesMap.get(dir) || {};
entriesMap.set(dir, entries);
return entries;
};
const invalidators = new Map();
export const getInvalidator = (dir)=>{
dir = normalizeOutputPath(dir);
return invalidators.get(dir);
};
const doneCallbacks = new EventEmitter();
const lastClientAccessPages = [
''
];
const lastServerAccessPagesForAppDir = [
''
];
// Make sure only one invalidation happens at a time
// Otherwise, webpack hash gets changed and it'll force the client to reload.
class Invalidator {
constructor(multiCompiler){
this.building = new Set();
this.rebuildAgain = new Set();
this.multiCompiler = multiCompiler;
}
shouldRebuildAll() {
return this.rebuildAgain.size > 0;
}
invalidate(compilerKeys = COMPILER_KEYS) {
for (const key of compilerKeys){
var _this_multiCompiler_compilers_COMPILER_INDEXES_key_watching;
// If there's a current build is processing, we won't abort it by invalidating.
// (If aborted, it'll cause a client side hard reload)
// But let it to invalidate just after the completion.
// So, it can re-build the queued pages at once.
if (this.building.has(key)) {
this.rebuildAgain.add(key);
continue;
}
this.building.add(key);
(_this_multiCompiler_compilers_COMPILER_INDEXES_key_watching = this.multiCompiler.compilers[COMPILER_INDEXES[key]].watching) == null ? void 0 : _this_multiCompiler_compilers_COMPILER_INDEXES_key_watching.invalidate();
}
}
startBuilding(compilerKey) {
this.building.add(compilerKey);
}
doneBuilding(compilerKeys = []) {
const rebuild = [];
for (const key of compilerKeys){
this.building.delete(key);
if (this.rebuildAgain.has(key)) {
rebuild.push(key);
this.rebuildAgain.delete(key);
}
}
if (rebuild.length > 0) {
this.invalidate(rebuild);
}
}
willRebuild(compilerKey) {
return this.rebuildAgain.has(compilerKey);
}
}
function disposeInactiveEntries(entries, maxInactiveAge) {
Object.keys(entries).forEach((entryKey)=>{
const entryData = entries[entryKey];
const { lastActiveTime, status, dispose, bundlePath } = entryData;
// TODO-APP: implement disposing of CHILD_ENTRY
if (entryData.type === 1) {
return;
}
// For the root middleware and the instrumentation hook files,
// we don't dispose them periodically as it's needed for every request.
if (isMiddlewareFilename(bundlePath) || isInstrumentationHookFilename(bundlePath)) {
return;
}
if (dispose) // Skip pages already scheduled for disposing
return;
// This means this entry is currently building or just added
// We don't need to dispose those entries.
if (status !== BUILT) return;
// We should not build the last accessed page even we didn't get any pings
// Sometimes, it's possible our XHR ping to wait before completing other requests.
// In that case, we should not dispose the current viewing page
if (lastClientAccessPages.includes(entryKey) || lastServerAccessPagesForAppDir.includes(entryKey)) return;
if (lastActiveTime && Date.now() - lastActiveTime > maxInactiveAge) {
entries[entryKey].dispose = true;
}
});
}
// Normalize both app paths and page paths
function tryToNormalizePagePath(page) {
try {
return normalizePagePath(page);
} catch (err) {
console.error(err);
throw new PageNotFoundError(page);
}
}
/**
* Attempts to find a page file path from the given pages absolute directory,
* a page and allowed extensions. If the page can't be found it will throw an
* error. It defaults the `/_error` page to Next.js internal error page.
*
* @param rootDir Absolute path to the project root.
* @param page The page normalized (it will be denormalized).
* @param extensions Array of page extensions.
* @param pagesDir Absolute path to the pages folder with trailing `/pages`.
* @param appDir Absolute path to the app folder with trailing `/app`.
*/ export async function findPagePathData(rootDir, page, extensions, pagesDir, appDir, isGlobalNotFoundEnabled) {
const normalizedPagePath = tryToNormalizePagePath(page);
let pagePath = null;
const isInstrumentation = isInstrumentationHookFile(normalizedPagePath);
if (isMiddlewareFile(normalizedPagePath) || isInstrumentation) {
pagePath = await findPageFile(rootDir, normalizedPagePath, extensions, false);
if (!pagePath) {
throw new PageNotFoundError(normalizedPagePath);
}
const pageUrl = ensureLeadingSlash(removePagePathTail(normalizePathSep(pagePath), {
extensions
}));
let bundlePath = normalizedPagePath;
let pageKey = posix.normalize(pageUrl);
if (isInstrumentation || isMiddlewareFile(normalizedPagePath)) {
bundlePath = bundlePath.replace('/src', '');
pageKey = page.replace('/src', '');
}
return {
filename: join(rootDir, pagePath),
bundlePath: bundlePath.slice(1),
page: pageKey
};
}
// Check appDir first falling back to pagesDir
if (appDir) {
if (page === UNDERSCORE_NOT_FOUND_ROUTE_ENTRY) {
// Load `global-not-found` when global-not-found is enabled.
// Prefer to load it when both `global-not-found` and root `not-found` present.
if (isGlobalNotFoundEnabled) {
const globalNotFoundPath = await findPageFile(appDir, 'global-not-found', extensions, true);
if (globalNotFoundPath) {
return {
filename: join(appDir, globalNotFoundPath),
bundlePath: `app${UNDERSCORE_NOT_FOUND_ROUTE_ENTRY}`,
page: UNDERSCORE_NOT_FOUND_ROUTE_ENTRY
};
}
} else {
// Then if global-not-found.js doesn't exist then load not-found.js
const notFoundPath = await findPageFile(appDir, 'not-found', extensions, true);
if (notFoundPath) {
return {
filename: join(appDir, notFoundPath),
bundlePath: `app${UNDERSCORE_NOT_FOUND_ROUTE_ENTRY}`,
page: UNDERSCORE_NOT_FOUND_ROUTE_ENTRY
};
}
}
// If they're not presented, then fallback to global-not-found
return {
filename: require.resolve('next/dist/client/components/builtin/global-not-found'),
bundlePath: `app${UNDERSCORE_NOT_FOUND_ROUTE_ENTRY}`,
page: UNDERSCORE_NOT_FOUND_ROUTE_ENTRY
};
}
pagePath = await findPageFile(appDir, normalizedPagePath, extensions, true);
if (pagePath) {
const pageUrl = ensureLeadingSlash(removePagePathTail(normalizePathSep(pagePath), {
keepIndex: true,
extensions
}));
return {
filename: join(appDir, pagePath),
bundlePath: posix.join('app', pageUrl),
page: posix.normalize(pageUrl)
};
}
}
if (!pagePath && pagesDir) {
pagePath = await findPageFile(pagesDir, normalizedPagePath, extensions, false);
}
if (pagePath !== null && pagesDir) {
const pageUrl = ensureLeadingSlash(removePagePathTail(normalizePathSep(pagePath), {
extensions
}));
return {
filename: join(pagesDir, pagePath),
bundlePath: posix.join('pages', normalizePagePath(pageUrl)),
page: posix.normalize(pageUrl)
};
}
if (page === '/_error') {
return {
filename: require.resolve('next/dist/pages/_error'),
bundlePath: page,
page: normalizePathSep(page)
};
} else {
throw new PageNotFoundError(normalizedPagePath);
}
}
export function onDemandEntryHandler({ hotReloader, maxInactiveAge, multiCompiler, nextConfig, pagesBufferLength, pagesDir, rootDir, appDir }) {
const hasAppDir = !!appDir;
let curInvalidator = getInvalidator(multiCompiler.outputPath);
const curEntries = getEntries(multiCompiler.outputPath);
if (!curInvalidator) {
curInvalidator = new Invalidator(multiCompiler);
invalidators.set(multiCompiler.outputPath, curInvalidator);
}
const startBuilding = (compilation)=>{
const compilationName = compilation.name;
curInvalidator.startBuilding(compilationName);
};
for (const compiler of multiCompiler.compilers){
compiler.hooks.make.tap('NextJsOnDemandEntries', startBuilding);
}
function getPagePathsFromEntrypoints(type, entrypoints) {
const pagePaths = [];
for (const entrypoint of entrypoints.values()){
const page = getRouteFromEntrypoint(entrypoint.name, hasAppDir);
if (page) {
var _entrypoint_name;
const pageBundleType = ((_entrypoint_name = entrypoint.name) == null ? void 0 : _entrypoint_name.startsWith('app/')) ? PAGE_TYPES.APP : PAGE_TYPES.PAGES;
pagePaths.push(getEntryKey(type, pageBundleType, page));
} else if (isMiddlewareFilename(entrypoint.name) || isInstrumentationHookFilename(entrypoint.name)) {
pagePaths.push(getEntryKey(type, PAGE_TYPES.ROOT, `/${entrypoint.name}`));
}
}
return pagePaths;
}
for (const compiler of multiCompiler.compilers){
compiler.hooks.done.tap('NextJsOnDemandEntries', ()=>{
var _getInvalidator;
return (_getInvalidator = getInvalidator(compiler.outputPath)) == null ? void 0 : _getInvalidator.doneBuilding([
compiler.name
]);
});
}
multiCompiler.hooks.done.tap('NextJsOnDemandEntries', (multiStats)=>{
var _getInvalidator;
const [clientStats, serverStats, edgeServerStats] = multiStats.stats;
const entryNames = [
...getPagePathsFromEntrypoints(COMPILER_NAMES.client, clientStats.compilation.entrypoints),
...getPagePathsFromEntrypoints(COMPILER_NAMES.server, serverStats.compilation.entrypoints),
...edgeServerStats ? getPagePathsFromEntrypoints(COMPILER_NAMES.edgeServer, edgeServerStats.compilation.entrypoints) : []
];
for (const name of entryNames){
const entry = curEntries[name];
if (!entry) {
continue;
}
if (entry.status !== BUILDING) {
continue;
}
entry.status = BUILT;
doneCallbacks.emit(name);
}
(_getInvalidator = getInvalidator(multiCompiler.outputPath)) == null ? void 0 : _getInvalidator.doneBuilding([
...COMPILER_KEYS
]);
});
const pingIntervalTime = Math.max(1000, Math.min(5000, maxInactiveAge));
setInterval(function() {
disposeInactiveEntries(curEntries, maxInactiveAge);
}, pingIntervalTime + 1000).unref();
function handleAppDirPing(tree) {
const pages = getEntrypointsFromTree(tree, true);
for (const page of pages){
for (const compilerType of [
COMPILER_NAMES.client,
COMPILER_NAMES.server,
COMPILER_NAMES.edgeServer
]){
const entryKey = getEntryKey(compilerType, PAGE_TYPES.APP, `/${page}`);
const entryInfo = curEntries[entryKey];
// If there's no entry, it may have been invalidated and needs to be re-built.
if (!entryInfo) {
continue;
}
// We don't need to maintain active state of anything other than BUILT entries
if (entryInfo.status !== BUILT) continue;
// If there's an entryInfo
if (!lastServerAccessPagesForAppDir.includes(entryKey)) {
lastServerAccessPagesForAppDir.unshift(entryKey);
// Maintain the buffer max length
// TODO: verify that the current pageKey is not at the end of the array as multiple entrypoints can exist
if (lastServerAccessPagesForAppDir.length > pagesBufferLength) {
lastServerAccessPagesForAppDir.pop();
}
}
entryInfo.lastActiveTime = Date.now();
entryInfo.dispose = false;
}
}
}
function handlePing(pg) {
const page = normalizePathSep(pg);
for (const compilerType of [
COMPILER_NAMES.client,
COMPILER_NAMES.server,
COMPILER_NAMES.edgeServer
]){
const entryKey = getEntryKey(compilerType, PAGE_TYPES.PAGES, page);
const entryInfo = curEntries[entryKey];
// If there's no entry, it may have been invalidated and needs to be re-built.
if (!entryInfo) {
// if (page !== lastEntry) client pings, but there's no entry for page
if (compilerType === COMPILER_NAMES.client) {
return;
}
continue;
}
// We don't need to maintain active state of anything other than BUILT entries
if (entryInfo.status !== BUILT) continue;
// If there's an entryInfo
if (!lastClientAccessPages.includes(entryKey)) {
lastClientAccessPages.unshift(entryKey);
// Maintain the buffer max length
if (lastClientAccessPages.length > pagesBufferLength) {
lastClientAccessPages.pop();
}
}
entryInfo.lastActiveTime = Date.now();
entryInfo.dispose = false;
}
return;
}
async function ensurePageImpl({ page, appPaths, definition, isApp, url }) {
const stalledTime = 60;
const stalledEnsureTimeout = setTimeout(()=>{
debug(`Ensuring ${page} has taken longer than ${stalledTime}s, if this continues to stall this may be a bug`);
}, stalledTime * 1000);
try {
let route;
if (definition) {
route = definition;
} else {
route = await findPagePathData(rootDir, page, nextConfig.pageExtensions, pagesDir, appDir, !!nextConfig.experimental.globalNotFound);
}
const isInsideAppDir = !!appDir && route.filename.startsWith(appDir);
if (typeof isApp === 'boolean' && isApp !== isInsideAppDir) {
Error.stackTraceLimit = 15;
throw Object.defineProperty(new Error(`Ensure bailed, found path "${route.page}" does not match ensure type (${isApp ? 'app' : 'pages'})`), "__NEXT_ERROR_CODE", {
value: "E419",
enumerable: false,
configurable: true
});
}
const pageBundleType = getPageBundleType(route.bundlePath);
const addEntry = (compilerType)=>{
const entryKey = getEntryKey(compilerType, pageBundleType, route.page);
if (curEntries[entryKey] && // there can be an overlap in the entryKey for the instrumentation hook file and a page named the same
// this is a quick fix to support this scenario by overwriting the instrumentation hook entry, since we only use it one time
// any changes to the instrumentation hook file will require a restart of the dev server anyway
!isInstrumentationHookFilename(curEntries[entryKey].bundlePath)) {
curEntries[entryKey].dispose = false;
curEntries[entryKey].lastActiveTime = Date.now();
if (curEntries[entryKey].status === BUILT) {
return {
entryKey,
newEntry: false,
shouldInvalidate: false
};
}
return {
entryKey,
newEntry: false,
shouldInvalidate: true
};
}
curEntries[entryKey] = {
type: 0,
appPaths,
absolutePagePath: route.filename,
request: route.filename,
bundlePath: route.bundlePath,
dispose: false,
lastActiveTime: Date.now(),
status: ADDED
};
return {
entryKey: entryKey,
newEntry: true,
shouldInvalidate: true
};
};
const staticInfo = await getStaticInfoIncludingLayouts({
page,
pageFilePath: route.filename,
isInsideAppDir,
pageExtensions: nextConfig.pageExtensions,
isDev: true,
config: nextConfig,
appDir
});
const added = new Map();
const isServerComponent = isInsideAppDir && staticInfo.rsc !== RSC_MODULE_TYPES.client;
let pageRuntime = staticInfo.runtime;
runDependingOnPageType({
page: route.page,
pageRuntime,
pageType: pageBundleType,
onClient: ()=>{
// Skip adding the client entry for app / Server Components.
if (isServerComponent || isInsideAppDir) {
return;
}
added.set(COMPILER_NAMES.client, addEntry(COMPILER_NAMES.client));
},
onServer: ()=>{
added.set(COMPILER_NAMES.server, addEntry(COMPILER_NAMES.server));
const edgeServerEntry = getEntryKey(COMPILER_NAMES.edgeServer, pageBundleType, route.page);
if (curEntries[edgeServerEntry] && !isInstrumentationHookFile(route.page)) {
// Runtime switched from edge to server
delete curEntries[edgeServerEntry];
}
},
onEdgeServer: ()=>{
added.set(COMPILER_NAMES.edgeServer, addEntry(COMPILER_NAMES.edgeServer));
const serverEntry = getEntryKey(COMPILER_NAMES.server, pageBundleType, route.page);
if (curEntries[serverEntry] && !isInstrumentationHookFile(route.page)) {
// Runtime switched from server to edge
delete curEntries[serverEntry];
}
}
});
const addedValues = [
...added.values()
];
const entriesThatShouldBeInvalidated = [
...added.entries()
].filter(([, entry])=>entry.shouldInvalidate);
const hasNewEntry = addedValues.some((entry)=>entry.newEntry);
if (hasNewEntry) {
const routePage = isApp ? route.page : normalizeAppPath(route.page);
// If proxy file, remove the leading slash from "/proxy" to "proxy".
reportTrigger(isMiddlewareFile(routePage) ? routePage.slice(1) : routePage, url);
}
if (entriesThatShouldBeInvalidated.length > 0) {
const invalidatePromise = Promise.all(entriesThatShouldBeInvalidated.map(([compilerKey, { entryKey }])=>{
return new Promise((resolve, reject)=>{
doneCallbacks.once(entryKey, (err)=>{
if (err) {
return reject(err);
}
// If the invalidation also triggers a rebuild, we need to
// wait for that additional build to prevent race conditions.
const needsRebuild = curInvalidator.willRebuild(compilerKey);
if (needsRebuild) {
doneCallbacks.once(entryKey, (rebuildErr)=>{
if (rebuildErr) {
return reject(rebuildErr);
}
resolve();
});
} else {
resolve();
}
});
});
}));
curInvalidator.invalidate([
...added.keys()
]);
await invalidatePromise;
}
} finally{
clearTimeout(stalledEnsureTimeout);
}
}
// Make sure that we won't have multiple invalidations ongoing concurrently.
const batcher = Batcher.create({
// The cache key here is composed of the elements that affect the
// compilation, namely, the page, whether it's client only, and whether
// it's an app page. This ensures that we don't have multiple compilations
// for the same page happening concurrently.
//
// We don't include the whole match because it contains match specific
// parameters (like route params) that would just bust this cache. Any
// details that would possibly bust the cache should be listed here.
cacheKeyFn: (options)=>JSON.stringify(options),
// Schedule the invocation of the ensurePageImpl function on the next tick.
schedulerFn: scheduleOnNextTick
});
return {
async ensurePage ({ page, appPaths = null, definition, isApp, url }) {
// If the route is actually an app page route, then we should have access
// to the app route definition, and therefore, the appPaths from it.
if (!appPaths && definition && isAppPageRouteDefinition(definition)) {
appPaths = definition.appPaths;
}
// Wrap the invocation of the ensurePageImpl function in the pending
// wrapper, which will ensure that we don't have multiple compilations
// for the same page happening concurrently.
return batcher.batch({
page,
appPaths,
definition,
isApp
}, async ()=>{
await ensurePageImpl({
page,
appPaths,
definition,
isApp,
url
});
});
},
onHMR (client, getHmrServerError) {
let bufferedHmrServerError = null;
client.addEventListener('close', ()=>{
bufferedHmrServerError = null;
});
client.addEventListener('message', ({ data })=>{
try {
const error = getHmrServerError();
// New error occurred: buffered error is flushed and new error occurred
if (!bufferedHmrServerError && error) {
hotReloader.send({
type: HMR_MESSAGE_SENT_TO_BROWSER.SERVER_ERROR,
errorJSON: stringifyError(error)
});
bufferedHmrServerError = null;
}
const parsedData = JSON.parse(typeof data !== 'string' ? data.toString() : data);
if (parsedData.event === HMR_MESSAGE_SENT_TO_SERVER.PING) {
if (parsedData.appDirRoute) {
handleAppDirPing(parsedData.tree);
} else {
handlePing(parsedData.page);
}
} else if (parsedData.event === HMR_MESSAGE_SENT_TO_SERVER.MCP_ERROR_STATE_RESPONSE) {
handleErrorStateResponse(parsedData.requestId, parsedData.errorState, parsedData.url);
} else if (parsedData.event === HMR_MESSAGE_SENT_TO_SERVER.MCP_PAGE_METADATA_RESPONSE) {
handlePageMetadataResponse(parsedData.requestId, parsedData.segmentTrieData, parsedData.url);
}
} catch {}
});
}
};
}
//# sourceMappingURL=on-demand-entry-handler.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,57 @@
import * as semver from 'next/dist/compiled/semver';
export function parseVersionInfo(o) {
const latest = semver.parse(o.latest);
const canary = semver.parse(o.canary);
const installedParsed = semver.parse(o.installed);
const installed = o.installed;
if (installedParsed && latest && canary) {
if (installedParsed.major < latest.major) {
// Old major version
return {
staleness: 'stale-major',
expected: latest.raw,
installed
};
} else if (installedParsed.prerelease[0] === 'canary' && semver.lt(installedParsed, canary)) {
// Matching major, but old canary
return {
staleness: 'stale-prerelease',
expected: canary.raw,
installed
};
} else if (!installedParsed.prerelease.length && semver.lt(installedParsed, latest)) {
// Stable, but not the latest
if (installedParsed.minor === latest.minor) {
// Same major and minor, but not the latest patch
return {
staleness: 'stale-patch',
expected: latest.raw,
installed
};
}
return {
staleness: 'stale-minor',
expected: latest.raw,
installed
};
} else if (semver.gt(installedParsed, latest) && installedParsed.version !== canary.version) {
// Newer major version
return {
staleness: 'newer-than-npm',
installed
};
} else {
// Latest and greatest
return {
staleness: 'fresh',
installed
};
}
}
return {
installed: (installedParsed == null ? void 0 : installedParsed.raw) ?? '0.0.0',
staleness: 'unknown'
};
}
//# sourceMappingURL=parse-version-info.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/server/dev/parse-version-info.ts"],"sourcesContent":["import * as semver from 'next/dist/compiled/semver'\n\nexport interface VersionInfo {\n installed: string\n staleness:\n | 'fresh'\n | 'stale-patch'\n | 'stale-minor'\n | 'stale-major'\n | 'stale-prerelease'\n | 'newer-than-npm'\n | 'unknown'\n expected?: string\n}\n\nexport function parseVersionInfo(o: {\n installed: string\n latest: string\n canary: string\n}): VersionInfo {\n const latest = semver.parse(o.latest)\n const canary = semver.parse(o.canary)\n const installedParsed = semver.parse(o.installed)\n const installed = o.installed\n if (installedParsed && latest && canary) {\n if (installedParsed.major < latest.major) {\n // Old major version\n return { staleness: 'stale-major', expected: latest.raw, installed }\n } else if (\n installedParsed.prerelease[0] === 'canary' &&\n semver.lt(installedParsed, canary)\n ) {\n // Matching major, but old canary\n return {\n staleness: 'stale-prerelease',\n expected: canary.raw,\n installed,\n }\n } else if (\n !installedParsed.prerelease.length &&\n semver.lt(installedParsed, latest)\n ) {\n // Stable, but not the latest\n if (installedParsed.minor === latest.minor) {\n // Same major and minor, but not the latest patch\n return {\n staleness: 'stale-patch',\n expected: latest.raw,\n installed,\n }\n }\n return { staleness: 'stale-minor', expected: latest.raw, installed }\n } else if (\n semver.gt(installedParsed, latest) &&\n installedParsed.version !== canary.version\n ) {\n // Newer major version\n return { staleness: 'newer-than-npm', installed }\n } else {\n // Latest and greatest\n return { staleness: 'fresh', installed }\n }\n }\n\n return {\n installed: installedParsed?.raw ?? '0.0.0',\n staleness: 'unknown',\n }\n}\n"],"names":["semver","parseVersionInfo","o","latest","parse","canary","installedParsed","installed","major","staleness","expected","raw","prerelease","lt","length","minor","gt","version"],"mappings":"AAAA,YAAYA,YAAY,4BAA2B;AAenD,OAAO,SAASC,iBAAiBC,CAIhC;IACC,MAAMC,SAASH,OAAOI,KAAK,CAACF,EAAEC,MAAM;IACpC,MAAME,SAASL,OAAOI,KAAK,CAACF,EAAEG,MAAM;IACpC,MAAMC,kBAAkBN,OAAOI,KAAK,CAACF,EAAEK,SAAS;IAChD,MAAMA,YAAYL,EAAEK,SAAS;IAC7B,IAAID,mBAAmBH,UAAUE,QAAQ;QACvC,IAAIC,gBAAgBE,KAAK,GAAGL,OAAOK,KAAK,EAAE;YACxC,oBAAoB;YACpB,OAAO;gBAAEC,WAAW;gBAAeC,UAAUP,OAAOQ,GAAG;gBAAEJ;YAAU;QACrE,OAAO,IACLD,gBAAgBM,UAAU,CAAC,EAAE,KAAK,YAClCZ,OAAOa,EAAE,CAACP,iBAAiBD,SAC3B;YACA,iCAAiC;YACjC,OAAO;gBACLI,WAAW;gBACXC,UAAUL,OAAOM,GAAG;gBACpBJ;YACF;QACF,OAAO,IACL,CAACD,gBAAgBM,UAAU,CAACE,MAAM,IAClCd,OAAOa,EAAE,CAACP,iBAAiBH,SAC3B;YACA,6BAA6B;YAC7B,IAAIG,gBAAgBS,KAAK,KAAKZ,OAAOY,KAAK,EAAE;gBAC1C,iDAAiD;gBACjD,OAAO;oBACLN,WAAW;oBACXC,UAAUP,OAAOQ,GAAG;oBACpBJ;gBACF;YACF;YACA,OAAO;gBAAEE,WAAW;gBAAeC,UAAUP,OAAOQ,GAAG;gBAAEJ;YAAU;QACrE,OAAO,IACLP,OAAOgB,EAAE,CAACV,iBAAiBH,WAC3BG,gBAAgBW,OAAO,KAAKZ,OAAOY,OAAO,EAC1C;YACA,sBAAsB;YACtB,OAAO;gBAAER,WAAW;gBAAkBF;YAAU;QAClD,OAAO;YACL,sBAAsB;YACtB,OAAO;gBAAEE,WAAW;gBAASF;YAAU;QACzC;IACF;IAEA,OAAO;QACLA,WAAWD,CAAAA,mCAAAA,gBAAiBK,GAAG,KAAI;QACnCF,WAAW;IACb;AACF","ignoreList":[0]}

View File

@@ -0,0 +1,34 @@
import isError from '../../lib/is-error';
import { realpathSync } from '../../lib/realpath';
import { clearManifestCache } from '../load-manifest.external';
function deleteFromRequireCache(filePath) {
try {
filePath = realpathSync(filePath);
} catch (e) {
if (isError(e) && e.code !== 'ENOENT') throw e;
}
const mod = require.cache[filePath];
if (mod) {
// remove the child reference from all parent modules
for (const parent of Object.values(require.cache)){
if (parent == null ? void 0 : parent.children) {
const idx = parent.children.indexOf(mod);
if (idx >= 0) parent.children.splice(idx, 1);
}
}
// remove parent references from external modules
for (const child of mod.children){
child.parent = null;
}
delete require.cache[filePath];
return true;
}
return false;
}
export function deleteCache(filePath) {
// try to clear it from the fs cache
clearManifestCache(filePath);
deleteFromRequireCache(filePath);
}
//# sourceMappingURL=require-cache.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/server/dev/require-cache.ts"],"sourcesContent":["import isError from '../../lib/is-error'\nimport { realpathSync } from '../../lib/realpath'\nimport { clearManifestCache } from '../load-manifest.external'\n\nfunction deleteFromRequireCache(filePath: string) {\n try {\n filePath = realpathSync(filePath)\n } catch (e) {\n if (isError(e) && e.code !== 'ENOENT') throw e\n }\n const mod = require.cache[filePath]\n if (mod) {\n // remove the child reference from all parent modules\n for (const parent of Object.values(require.cache)) {\n if (parent?.children) {\n const idx = parent.children.indexOf(mod)\n if (idx >= 0) parent.children.splice(idx, 1)\n }\n }\n // remove parent references from external modules\n for (const child of mod.children) {\n child.parent = null\n }\n delete require.cache[filePath]\n return true\n }\n return false\n}\n\nexport function deleteCache(filePath: string) {\n // try to clear it from the fs cache\n clearManifestCache(filePath)\n\n deleteFromRequireCache(filePath)\n}\n"],"names":["isError","realpathSync","clearManifestCache","deleteFromRequireCache","filePath","e","code","mod","require","cache","parent","Object","values","children","idx","indexOf","splice","child","deleteCache"],"mappings":"AAAA,OAAOA,aAAa,qBAAoB;AACxC,SAASC,YAAY,QAAQ,qBAAoB;AACjD,SAASC,kBAAkB,QAAQ,4BAA2B;AAE9D,SAASC,uBAAuBC,QAAgB;IAC9C,IAAI;QACFA,WAAWH,aAAaG;IAC1B,EAAE,OAAOC,GAAG;QACV,IAAIL,QAAQK,MAAMA,EAAEC,IAAI,KAAK,UAAU,MAAMD;IAC/C;IACA,MAAME,MAAMC,QAAQC,KAAK,CAACL,SAAS;IACnC,IAAIG,KAAK;QACP,qDAAqD;QACrD,KAAK,MAAMG,UAAUC,OAAOC,MAAM,CAACJ,QAAQC,KAAK,EAAG;YACjD,IAAIC,0BAAAA,OAAQG,QAAQ,EAAE;gBACpB,MAAMC,MAAMJ,OAAOG,QAAQ,CAACE,OAAO,CAACR;gBACpC,IAAIO,OAAO,GAAGJ,OAAOG,QAAQ,CAACG,MAAM,CAACF,KAAK;YAC5C;QACF;QACA,iDAAiD;QACjD,KAAK,MAAMG,SAASV,IAAIM,QAAQ,CAAE;YAChCI,MAAMP,MAAM,GAAG;QACjB;QACA,OAAOF,QAAQC,KAAK,CAACL,SAAS;QAC9B,OAAO;IACT;IACA,OAAO;AACT;AAEA,OAAO,SAASc,YAAYd,QAAgB;IAC1C,oCAAoC;IACpCF,mBAAmBE;IAEnBD,uBAAuBC;AACzB","ignoreList":[0]}

View File

@@ -0,0 +1,37 @@
import { streamToUint8Array } from '../stream-utils/node-web-streams-helper';
import { HMR_MESSAGE_SENT_TO_BROWSER } from './hot-reloader-types';
const errorsRscStreamsByHtmlRequestId = new Map();
export function sendSerializedErrorsToClient(errorsRscStream, sendToClient) {
streamToUint8Array(errorsRscStream).then((serializedErrors)=>{
sendToClient({
type: HMR_MESSAGE_SENT_TO_BROWSER.ERRORS_TO_SHOW_IN_BROWSER,
serializedErrors
});
}, (err)=>{
console.error(Object.defineProperty(new Error('Failed to serialize errors.', {
cause: err
}), "__NEXT_ERROR_CODE", {
value: "E948",
enumerable: false,
configurable: true
}));
});
}
export function sendSerializedErrorsToClientForHtmlRequest(htmlRequestId, sendToClient) {
const errorsRscStream = errorsRscStreamsByHtmlRequestId.get(htmlRequestId);
if (!errorsRscStream) {
return;
}
errorsRscStreamsByHtmlRequestId.delete(htmlRequestId);
sendSerializedErrorsToClient(errorsRscStream, sendToClient);
}
export function setErrorsRscStreamForHtmlRequest(htmlRequestId, errorsRscStream) {
// TODO: Clean up after a timeout, in case the client never connects, e.g.
// when CURL'ing the page, or loading the page with JavaScript disabled etc.
errorsRscStreamsByHtmlRequestId.set(htmlRequestId, errorsRscStream);
}
export function deleteErrorsRscStreamForHtmlRequest(htmlRequestId) {
errorsRscStreamsByHtmlRequestId.delete(htmlRequestId);
}
//# sourceMappingURL=serialized-errors.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/server/dev/serialized-errors.ts"],"sourcesContent":["import { streamToUint8Array } from '../stream-utils/node-web-streams-helper'\nimport {\n HMR_MESSAGE_SENT_TO_BROWSER,\n type HmrMessageSentToBrowser,\n} from './hot-reloader-types'\n\nconst errorsRscStreamsByHtmlRequestId = new Map<\n string,\n ReadableStream<Uint8Array>\n>()\n\nexport function sendSerializedErrorsToClient(\n errorsRscStream: ReadableStream<Uint8Array>,\n sendToClient: (message: HmrMessageSentToBrowser) => void\n) {\n streamToUint8Array(errorsRscStream).then(\n (serializedErrors) => {\n sendToClient({\n type: HMR_MESSAGE_SENT_TO_BROWSER.ERRORS_TO_SHOW_IN_BROWSER,\n serializedErrors,\n })\n },\n (err) => {\n console.error(new Error('Failed to serialize errors.', { cause: err }))\n }\n )\n}\n\nexport function sendSerializedErrorsToClientForHtmlRequest(\n htmlRequestId: string,\n sendToClient: (message: HmrMessageSentToBrowser) => void\n) {\n const errorsRscStream = errorsRscStreamsByHtmlRequestId.get(htmlRequestId)\n\n if (!errorsRscStream) {\n return\n }\n\n errorsRscStreamsByHtmlRequestId.delete(htmlRequestId)\n\n sendSerializedErrorsToClient(errorsRscStream, sendToClient)\n}\n\nexport function setErrorsRscStreamForHtmlRequest(\n htmlRequestId: string,\n errorsRscStream: ReadableStream<Uint8Array>\n) {\n // TODO: Clean up after a timeout, in case the client never connects, e.g.\n // when CURL'ing the page, or loading the page with JavaScript disabled etc.\n errorsRscStreamsByHtmlRequestId.set(htmlRequestId, errorsRscStream)\n}\n\nexport function deleteErrorsRscStreamForHtmlRequest(htmlRequestId: string) {\n errorsRscStreamsByHtmlRequestId.delete(htmlRequestId)\n}\n"],"names":["streamToUint8Array","HMR_MESSAGE_SENT_TO_BROWSER","errorsRscStreamsByHtmlRequestId","Map","sendSerializedErrorsToClient","errorsRscStream","sendToClient","then","serializedErrors","type","ERRORS_TO_SHOW_IN_BROWSER","err","console","error","Error","cause","sendSerializedErrorsToClientForHtmlRequest","htmlRequestId","get","delete","setErrorsRscStreamForHtmlRequest","set","deleteErrorsRscStreamForHtmlRequest"],"mappings":"AAAA,SAASA,kBAAkB,QAAQ,0CAAyC;AAC5E,SACEC,2BAA2B,QAEtB,uBAAsB;AAE7B,MAAMC,kCAAkC,IAAIC;AAK5C,OAAO,SAASC,6BACdC,eAA2C,EAC3CC,YAAwD;IAExDN,mBAAmBK,iBAAiBE,IAAI,CACtC,CAACC;QACCF,aAAa;YACXG,MAAMR,4BAA4BS,yBAAyB;YAC3DF;QACF;IACF,GACA,CAACG;QACCC,QAAQC,KAAK,CAAC,qBAAwD,CAAxD,IAAIC,MAAM,+BAA+B;YAAEC,OAAOJ;QAAI,IAAtD,qBAAA;mBAAA;wBAAA;0BAAA;QAAuD;IACvE;AAEJ;AAEA,OAAO,SAASK,2CACdC,aAAqB,EACrBX,YAAwD;IAExD,MAAMD,kBAAkBH,gCAAgCgB,GAAG,CAACD;IAE5D,IAAI,CAACZ,iBAAiB;QACpB;IACF;IAEAH,gCAAgCiB,MAAM,CAACF;IAEvCb,6BAA6BC,iBAAiBC;AAChD;AAEA,OAAO,SAASc,iCACdH,aAAqB,EACrBZ,eAA2C;IAE3C,0EAA0E;IAC1E,4EAA4E;IAC5EH,gCAAgCmB,GAAG,CAACJ,eAAeZ;AACrD;AAEA,OAAO,SAASiB,oCAAoCL,aAAqB;IACvEf,gCAAgCiB,MAAM,CAACF;AACzC","ignoreList":[0]}

View File

@@ -0,0 +1,96 @@
import '../require-hook';
import '../node-environment';
import { collectSegments } from '../../build/segment-config/app/app-segments';
import { loadComponents } from '../load-components';
import { setHttpClientAndAgentOptions } from '../setup-http-agent-env';
import { isAppPageRouteModule } from '../route-modules/checks';
import { checkIsRoutePPREnabled } from '../lib/experimental/ppr';
import { InvariantError } from '../../shared/lib/invariant-error';
import { collectRootParamKeys } from '../../build/segment-config/app/collect-root-param-keys';
import { buildAppStaticPaths } from '../../build/static-paths/app';
import { buildPagesStaticPaths } from '../../build/static-paths/pages';
import { createIncrementalCache } from '../../export/helpers/create-incremental-cache';
import { parseAppRoute } from '../../shared/lib/router/routes/app';
// we call getStaticPaths in a separate process to ensure
// side-effects aren't relied on in dev that will break
// during a production build
export async function loadStaticPaths({ dir, distDir, pathname, config, httpAgentOptions, locales, defaultLocale, isAppPath, page, isrFlushToDisk, fetchCacheKeyPrefix, cacheMaxMemorySize, requestHeaders, cacheHandler, cacheHandlers, cacheLifeProfiles, nextConfigOutput, buildId, authInterrupts, sriEnabled }) {
// this needs to be initialized before loadComponents otherwise
// "use cache" could be missing it's cache handlers
await createIncrementalCache({
dir,
distDir,
cacheHandler,
cacheHandlers,
requestHeaders,
fetchCacheKeyPrefix,
flushToDisk: isrFlushToDisk,
cacheMaxMemorySize
});
// update work memory runtime-config
setHttpClientAndAgentOptions({
httpAgentOptions
});
const components = await loadComponents({
distDir,
// In `pages/`, the page is the same as the pathname.
page: page || pathname,
isAppPath,
isDev: true,
sriEnabled,
needsManifestsForLegacyReasons: true
});
if (isAppPath) {
const routeModule = components.routeModule;
const segments = await collectSegments(// We know this is an app page or app route module because we checked
// above that the page type is 'app'.
routeModule);
const route = parseAppRoute(pathname, true);
if (route.dynamicSegments.length === 0) {
throw Object.defineProperty(new InvariantError(`Expected a dynamic route, but got a static route: ${pathname}`), "__NEXT_ERROR_CODE", {
value: "E930",
enumerable: false,
configurable: true
});
}
const isRoutePPREnabled = isAppPageRouteModule(routeModule) && checkIsRoutePPREnabled(config.pprConfig);
const rootParamKeys = collectRootParamKeys(routeModule);
return buildAppStaticPaths({
dir,
page: pathname,
route,
cacheComponents: config.cacheComponents,
segments,
distDir,
requestHeaders,
cacheHandler,
cacheLifeProfiles,
isrFlushToDisk,
fetchCacheKeyPrefix,
cacheMaxMemorySize,
ComponentMod: components.ComponentMod,
nextConfigOutput,
isRoutePPREnabled,
buildId,
authInterrupts,
rootParamKeys
});
} else if (!components.getStaticPaths) {
// We shouldn't get to this point since the worker should only be called for
// SSG pages with getStaticPaths.
throw Object.defineProperty(new InvariantError(`Failed to load page with getStaticPaths for ${pathname}`), "__NEXT_ERROR_CODE", {
value: "E605",
enumerable: false,
configurable: true
});
}
return buildPagesStaticPaths({
page: pathname,
getStaticPaths: components.getStaticPaths,
configFileName: config.configFileName,
locales,
defaultLocale
});
}
//# sourceMappingURL=static-paths-worker.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,601 @@
import { HMR_MESSAGE_SENT_TO_BROWSER } from './hot-reloader-types';
import * as Log from '../../build/output/log';
import { getEntryKey, splitEntryKey } from '../../shared/lib/turbopack/entry-key';
import { isMetadataRoute } from '../../lib/metadata/is-metadata-route';
import { formatIssue, getIssueKey, isRelevantWarning, processIssues, renderStyledStringToErrorAnsi } from '../../shared/lib/turbopack/utils';
import { MIDDLEWARE_FILENAME, PROXY_FILENAME } from '../../lib/constants';
const onceErrorSet = new Set();
/**
* Check if given issue is a warning to be display only once.
* This mimics behavior of get-page-static-info's warnOnce.
* @param issue
* @returns
*/ function shouldEmitOnceWarning(issue) {
const { severity, title, stage } = issue;
if (severity === 'warning' && title.value === 'Invalid page configuration') {
if (onceErrorSet.has(issue)) {
return false;
}
onceErrorSet.add(issue);
}
if (severity === 'warning' && stage === 'config' && renderStyledStringToErrorAnsi(issue.title).includes("can't be external")) {
if (onceErrorSet.has(issue)) {
return false;
}
onceErrorSet.add(issue);
}
return true;
}
/// Print out an issue to the console which should not block
/// the build by throwing out or blocking error overlay.
export function printNonFatalIssue(issue) {
if (isRelevantWarning(issue) && shouldEmitOnceWarning(issue)) {
Log.warn(formatIssue(issue));
}
}
export function processTopLevelIssues(currentTopLevelIssues, result) {
currentTopLevelIssues.clear();
for (const issue of result.issues){
const issueKey = getIssueKey(issue);
currentTopLevelIssues.set(issueKey, issue);
}
}
const MILLISECONDS_IN_NANOSECOND = BigInt(1000000);
export function msToNs(ms) {
return BigInt(Math.floor(ms)) * MILLISECONDS_IN_NANOSECOND;
}
export async function handleRouteType({ dev, page, pathname, route, currentEntryIssues, entrypoints, manifestLoader, readyIds, devRewrites, productionRewrites, hooks, logErrors }) {
const shouldCreateWebpackStats = process.env.TURBOPACK_STATS != null;
switch(route.type){
case 'page':
{
const clientKey = getEntryKey('pages', 'client', page);
const serverKey = getEntryKey('pages', 'server', page);
try {
// In the best case scenario, Turbopack chunks document, app, page separately in that order,
// so it can happen that the chunks of document change, but the chunks of app and page
// don't. We still need to reload the page chunks in that case though, otherwise the version
// of the document or app component export from the pages template is stale.
let documentOrAppChanged = false;
if (entrypoints.global.app) {
const key = getEntryKey('pages', 'server', '_app');
const writtenEndpoint = await entrypoints.global.app.writeToDisk();
documentOrAppChanged ||= (hooks == null ? void 0 : hooks.handleWrittenEndpoint(key, writtenEndpoint, false)) ?? false;
processIssues(currentEntryIssues, key, writtenEndpoint, false, logErrors);
}
await manifestLoader.loadBuildManifest('_app');
await manifestLoader.loadPagesManifest('_app');
if (entrypoints.global.document) {
const key = getEntryKey('pages', 'server', '_document');
const writtenEndpoint = await entrypoints.global.document.writeToDisk();
documentOrAppChanged ||= (hooks == null ? void 0 : hooks.handleWrittenEndpoint(key, writtenEndpoint, false)) ?? false;
processIssues(currentEntryIssues, key, writtenEndpoint, false, logErrors);
}
await manifestLoader.loadPagesManifest('_document');
const writtenEndpoint = await route.htmlEndpoint.writeToDisk();
hooks == null ? void 0 : hooks.handleWrittenEndpoint(serverKey, writtenEndpoint, documentOrAppChanged);
const type = writtenEndpoint == null ? void 0 : writtenEndpoint.type;
await manifestLoader.loadClientBuildManifest(page);
await manifestLoader.loadBuildManifest(page);
await manifestLoader.loadPagesManifest(page);
if (type === 'edge') {
await manifestLoader.loadMiddlewareManifest(page, 'pages');
} else {
manifestLoader.deleteMiddlewareManifest(serverKey);
}
await manifestLoader.loadFontManifest('/_app', 'pages');
await manifestLoader.loadFontManifest(page, 'pages');
if (shouldCreateWebpackStats) {
await manifestLoader.loadWebpackStats(page, 'pages');
}
manifestLoader.writeManifests({
devRewrites,
productionRewrites,
entrypoints
});
processIssues(currentEntryIssues, serverKey, writtenEndpoint, false, logErrors);
} finally{
if (dev) {
// TODO subscriptions should only be caused by the WebSocket connections
// otherwise we don't known when to unsubscribe and this leaking
hooks == null ? void 0 : hooks.subscribeToChanges(serverKey, false, route.dataEndpoint, ()=>{
// Report the next compilation again
readyIds == null ? void 0 : readyIds.delete(pathname);
return {
type: HMR_MESSAGE_SENT_TO_BROWSER.SERVER_ONLY_CHANGES,
pages: [
page
]
};
}, (e)=>{
return {
type: HMR_MESSAGE_SENT_TO_BROWSER.RELOAD_PAGE,
data: `error in ${page} data subscription: ${e}`
};
});
hooks == null ? void 0 : hooks.subscribeToChanges(clientKey, false, route.htmlEndpoint, ()=>{
return {
type: HMR_MESSAGE_SENT_TO_BROWSER.CLIENT_CHANGES
};
}, (e)=>{
return {
type: HMR_MESSAGE_SENT_TO_BROWSER.RELOAD_PAGE,
data: `error in ${page} html subscription: ${e}`
};
});
if (entrypoints.global.document) {
hooks == null ? void 0 : hooks.subscribeToChanges(getEntryKey('pages', 'server', '_document'), false, entrypoints.global.document, ()=>{
return {
type: HMR_MESSAGE_SENT_TO_BROWSER.RELOAD_PAGE,
data: '_document has changed (page route)'
};
}, (e)=>{
return {
type: HMR_MESSAGE_SENT_TO_BROWSER.RELOAD_PAGE,
data: `error in _document subscription (page route): ${e}`
};
});
}
}
}
break;
}
case 'page-api':
{
const key = getEntryKey('pages', 'server', page);
const writtenEndpoint = await route.endpoint.writeToDisk();
hooks == null ? void 0 : hooks.handleWrittenEndpoint(key, writtenEndpoint, false);
const type = writtenEndpoint.type;
await manifestLoader.loadPagesManifest(page);
if (type === 'edge') {
await manifestLoader.loadMiddlewareManifest(page, 'pages');
} else {
manifestLoader.deleteMiddlewareManifest(key);
}
manifestLoader.writeManifests({
devRewrites,
productionRewrites,
entrypoints
});
processIssues(currentEntryIssues, key, writtenEndpoint, true, logErrors);
break;
}
case 'app-page':
{
const key = getEntryKey('app', 'server', page);
const writtenEndpoint = await route.htmlEndpoint.writeToDisk();
hooks == null ? void 0 : hooks.handleWrittenEndpoint(key, writtenEndpoint, false);
if (dev) {
// TODO subscriptions should only be caused by the WebSocket connections
// otherwise we don't known when to unsubscribe and this leaking
hooks == null ? void 0 : hooks.subscribeToChanges(key, true, route.rscEndpoint, (change, hash)=>{
if (change.issues.some((issue)=>issue.severity === 'error')) {
// Ignore any updates that has errors
// There will be another update without errors eventually
return;
}
// Report the next compilation again
readyIds == null ? void 0 : readyIds.delete(pathname);
return {
type: HMR_MESSAGE_SENT_TO_BROWSER.SERVER_COMPONENT_CHANGES,
hash
};
}, (e)=>{
return {
type: HMR_MESSAGE_SENT_TO_BROWSER.RELOAD_PAGE,
data: `error in ${page} app-page subscription: ${e}`
};
});
}
const type = writtenEndpoint.type;
if (type === 'edge') {
manifestLoader.loadMiddlewareManifest(page, 'app');
} else {
manifestLoader.deleteMiddlewareManifest(key);
}
manifestLoader.loadBuildManifest(page, 'app');
manifestLoader.loadAppPathsManifest(page);
manifestLoader.loadActionManifest(page);
manifestLoader.loadFontManifest(page, 'app');
if (shouldCreateWebpackStats) {
manifestLoader.loadWebpackStats(page, 'app');
}
manifestLoader.writeManifests({
devRewrites,
productionRewrites,
entrypoints
});
processIssues(currentEntryIssues, key, writtenEndpoint, dev, logErrors);
break;
}
case 'app-route':
{
const key = getEntryKey('app', 'server', page);
const writtenEndpoint = await route.endpoint.writeToDisk();
hooks == null ? void 0 : hooks.handleWrittenEndpoint(key, writtenEndpoint, false);
const type = writtenEndpoint.type;
manifestLoader.loadAppPathsManifest(page);
if (type === 'edge') {
manifestLoader.loadMiddlewareManifest(page, 'app');
} else {
manifestLoader.deleteMiddlewareManifest(key);
}
manifestLoader.writeManifests({
devRewrites,
productionRewrites,
entrypoints
});
processIssues(currentEntryIssues, key, writtenEndpoint, true, logErrors);
break;
}
default:
{
throw Object.defineProperty(new Error(`unknown route type ${route.type} for ${page}`), "__NEXT_ERROR_CODE", {
value: "E316",
enumerable: false,
configurable: true
});
}
}
}
/**
* Maintains a mapping between entrypoins and the corresponding client asset paths.
*/ export class AssetMapper {
/**
* Overrides asset paths for a key and updates the mapping from path to key.
*
* @param key
* @param assetPaths asset paths relative to the .next directory
*/ setPathsForKey(key, assetPaths) {
this.delete(key);
const newAssetPaths = new Set(assetPaths);
this.entryMap.set(key, newAssetPaths);
for (const assetPath of newAssetPaths){
let assetPathKeys = this.assetMap.get(assetPath);
if (!assetPathKeys) {
assetPathKeys = new Set();
this.assetMap.set(assetPath, assetPathKeys);
}
assetPathKeys.add(key);
}
}
/**
* Deletes the key and any asset only referenced by this key.
*
* @param key
*/ delete(key) {
for (const assetPath of this.getAssetPathsByKey(key)){
const assetPathKeys = this.assetMap.get(assetPath);
assetPathKeys == null ? void 0 : assetPathKeys.delete(key);
if (!(assetPathKeys == null ? void 0 : assetPathKeys.size)) {
this.assetMap.delete(assetPath);
}
}
this.entryMap.delete(key);
}
getAssetPathsByKey(key) {
return Array.from(this.entryMap.get(key) ?? []);
}
getKeysByAsset(path) {
return Array.from(this.assetMap.get(path) ?? []);
}
keys() {
return this.entryMap.keys();
}
constructor(){
this.entryMap = new Map();
this.assetMap = new Map();
}
}
export function hasEntrypointForKey(entrypoints, key, assetMapper) {
const { type, page } = splitEntryKey(key);
switch(type){
case 'app':
return entrypoints.app.has(page);
case 'pages':
switch(page){
case '_app':
return entrypoints.global.app != null;
case '_document':
return entrypoints.global.document != null;
case '_error':
return entrypoints.global.error != null;
default:
return entrypoints.page.has(page);
}
case 'root':
switch(page){
case 'middleware':
return entrypoints.global.middleware != null;
case 'instrumentation':
return entrypoints.global.instrumentation != null;
default:
return false;
}
case 'assets':
if (!assetMapper) {
return false;
}
return assetMapper.getKeysByAsset(page).some((pageKey)=>hasEntrypointForKey(entrypoints, pageKey, assetMapper));
default:
{
// validation that we covered all cases, this should never run.
const _ = type;
return false;
}
}
}
export async function handleEntrypoints({ entrypoints, currentEntrypoints, currentEntryIssues, manifestLoader, devRewrites, logErrors, dev }) {
currentEntrypoints.global.app = entrypoints.pagesAppEndpoint;
currentEntrypoints.global.document = entrypoints.pagesDocumentEndpoint;
currentEntrypoints.global.error = entrypoints.pagesErrorEndpoint;
currentEntrypoints.global.instrumentation = entrypoints.instrumentation;
currentEntrypoints.page.clear();
currentEntrypoints.app.clear();
for (const [pathname, route] of entrypoints.routes){
switch(route.type){
case 'page':
case 'page-api':
currentEntrypoints.page.set(pathname, route);
break;
case 'app-page':
{
route.pages.forEach((page)=>{
currentEntrypoints.app.set(page.originalName, {
type: 'app-page',
...page
});
});
break;
}
case 'app-route':
{
currentEntrypoints.app.set(route.originalName, route);
break;
}
case 'conflict':
Log.info(`skipping ${pathname} (${route.type})`);
break;
default:
route;
}
}
if (dev) {
await handleEntrypointsDevCleanup({
currentEntryIssues,
currentEntrypoints,
...dev
});
}
const { middleware, instrumentation } = entrypoints;
// We check for explicit true/false, since it's initialized to
// undefined during the first loop (middlewareChanges event is
// unnecessary during the first serve)
if (currentEntrypoints.global.middleware && !middleware) {
const key = getEntryKey('root', 'server', 'middleware');
// Went from middleware to no middleware
await (dev == null ? void 0 : dev.hooks.unsubscribeFromChanges(key));
currentEntryIssues.delete(key);
dev.hooks.sendHmr('middleware', {
type: HMR_MESSAGE_SENT_TO_BROWSER.MIDDLEWARE_CHANGES
});
} else if (!currentEntrypoints.global.middleware && middleware) {
// Went from no middleware to middleware
dev.hooks.sendHmr('middleware', {
type: HMR_MESSAGE_SENT_TO_BROWSER.MIDDLEWARE_CHANGES
});
}
currentEntrypoints.global.middleware = middleware;
if (instrumentation) {
const processInstrumentation = async (name, prop)=>{
const prettyName = {
nodeJs: 'Node.js',
edge: 'Edge'
};
const finishBuilding = dev.hooks.startBuilding(`instrumentation ${prettyName[prop]}`, undefined, true);
const key = getEntryKey('root', 'server', name);
const writtenEndpoint = await instrumentation[prop].writeToDisk();
dev.hooks.handleWrittenEndpoint(key, writtenEndpoint, false);
processIssues(currentEntryIssues, key, writtenEndpoint, false, logErrors);
finishBuilding();
};
await processInstrumentation('instrumentation.nodeJs', 'nodeJs');
await processInstrumentation('instrumentation.edge', 'edge');
await manifestLoader.loadMiddlewareManifest('instrumentation', 'instrumentation');
manifestLoader.writeManifests({
devRewrites,
productionRewrites: undefined,
entrypoints: currentEntrypoints
});
dev.serverFields.actualInstrumentationHookFile = '/instrumentation';
await dev.hooks.propagateServerField('actualInstrumentationHookFile', dev.serverFields.actualInstrumentationHookFile);
} else {
dev.serverFields.actualInstrumentationHookFile = undefined;
await dev.hooks.propagateServerField('actualInstrumentationHookFile', dev.serverFields.actualInstrumentationHookFile);
}
if (middleware) {
const key = getEntryKey('root', 'server', 'middleware');
const endpoint = middleware.endpoint;
const triggerName = middleware.isProxy ? PROXY_FILENAME : MIDDLEWARE_FILENAME;
async function processMiddleware() {
var _manifestLoader_getMiddlewareManifest;
const finishBuilding = dev.hooks.startBuilding(triggerName, undefined, true);
const writtenEndpoint = await endpoint.writeToDisk();
dev.hooks.handleWrittenEndpoint(key, writtenEndpoint, false);
processIssues(currentEntryIssues, key, writtenEndpoint, false, logErrors);
await manifestLoader.loadMiddlewareManifest('middleware', 'middleware');
const middlewareConfig = (_manifestLoader_getMiddlewareManifest = manifestLoader.getMiddlewareManifest(key)) == null ? void 0 : _manifestLoader_getMiddlewareManifest.middleware['/'];
if (dev && middlewareConfig) {
dev.serverFields.middleware = {
match: null,
page: '/',
matchers: middlewareConfig.matchers
};
}
finishBuilding();
}
await processMiddleware();
if (dev) {
dev == null ? void 0 : dev.hooks.subscribeToChanges(key, false, endpoint, async ()=>{
const finishBuilding = dev.hooks.startBuilding(triggerName, undefined, true);
await processMiddleware();
await dev.hooks.propagateServerField('actualMiddlewareFile', dev.serverFields.actualMiddlewareFile);
await dev.hooks.propagateServerField('middleware', dev.serverFields.middleware);
manifestLoader.writeManifests({
devRewrites,
productionRewrites: undefined,
entrypoints: currentEntrypoints
});
finishBuilding == null ? void 0 : finishBuilding();
return {
type: HMR_MESSAGE_SENT_TO_BROWSER.MIDDLEWARE_CHANGES
};
}, ()=>{
return {
type: HMR_MESSAGE_SENT_TO_BROWSER.MIDDLEWARE_CHANGES
};
});
}
} else {
manifestLoader.deleteMiddlewareManifest(getEntryKey('root', 'server', 'middleware'));
dev.serverFields.actualMiddlewareFile = undefined;
dev.serverFields.middleware = undefined;
}
await dev.hooks.propagateServerField('actualMiddlewareFile', dev.serverFields.actualMiddlewareFile);
await dev.hooks.propagateServerField('middleware', dev.serverFields.middleware);
}
async function handleEntrypointsDevCleanup({ currentEntryIssues, currentEntrypoints, assetMapper, changeSubscriptions, clients, clientStates, hooks }) {
// this needs to be first as `hasEntrypointForKey` uses the `assetMapper`
for (const key of assetMapper.keys()){
if (!hasEntrypointForKey(currentEntrypoints, key, assetMapper)) {
assetMapper.delete(key);
}
}
for (const key of changeSubscriptions.keys()){
// middleware is handled separately
if (!hasEntrypointForKey(currentEntrypoints, key, assetMapper)) {
await hooks.unsubscribeFromChanges(key);
}
}
for (const [key] of currentEntryIssues){
if (!hasEntrypointForKey(currentEntrypoints, key, assetMapper)) {
currentEntryIssues.delete(key);
}
}
for (const client of clients){
const state = clientStates.get(client);
if (!state) {
continue;
}
for (const key of state.clientIssues.keys()){
if (!hasEntrypointForKey(currentEntrypoints, key, assetMapper)) {
state.clientIssues.delete(key);
}
}
for (const id of state.subscriptions.keys()){
if (!hasEntrypointForKey(currentEntrypoints, getEntryKey('assets', 'client', id), assetMapper)) {
hooks.unsubscribeFromHmrEvents(client, id);
}
}
}
}
export async function handlePagesErrorRoute({ currentEntryIssues, entrypoints, manifestLoader, devRewrites, productionRewrites, logErrors, hooks }) {
if (entrypoints.global.app) {
const key = getEntryKey('pages', 'server', '_app');
const writtenEndpoint = await entrypoints.global.app.writeToDisk();
hooks.handleWrittenEndpoint(key, writtenEndpoint, false);
hooks.subscribeToChanges(key, false, entrypoints.global.app, ()=>{
// There's a special case for this in `../client/page-bootstrap.ts`.
// https://github.com/vercel/next.js/blob/08d7a7e5189a835f5dcb82af026174e587575c0e/packages/next/src/client/page-bootstrap.ts#L69-L71
return {
type: HMR_MESSAGE_SENT_TO_BROWSER.CLIENT_CHANGES
};
}, ()=>{
return {
type: HMR_MESSAGE_SENT_TO_BROWSER.RELOAD_PAGE,
data: '_app has changed (error route)'
};
});
processIssues(currentEntryIssues, key, writtenEndpoint, false, logErrors);
}
await manifestLoader.loadBuildManifest('_app');
await manifestLoader.loadPagesManifest('_app');
await manifestLoader.loadFontManifest('_app');
if (entrypoints.global.document) {
const key = getEntryKey('pages', 'server', '_document');
const writtenEndpoint = await entrypoints.global.document.writeToDisk();
hooks.handleWrittenEndpoint(key, writtenEndpoint, false);
hooks.subscribeToChanges(key, false, entrypoints.global.document, ()=>{
return {
type: HMR_MESSAGE_SENT_TO_BROWSER.RELOAD_PAGE,
data: '_document has changed (error route)'
};
}, (e)=>{
return {
type: HMR_MESSAGE_SENT_TO_BROWSER.RELOAD_PAGE,
data: `error in _document subscription (error route): ${e}`
};
});
processIssues(currentEntryIssues, key, writtenEndpoint, false, logErrors);
}
await manifestLoader.loadPagesManifest('_document');
if (entrypoints.global.error) {
const key = getEntryKey('pages', 'server', '_error');
const writtenEndpoint = await entrypoints.global.error.writeToDisk();
hooks.handleWrittenEndpoint(key, writtenEndpoint, false);
hooks.subscribeToChanges(key, false, entrypoints.global.error, ()=>{
// There's a special case for this in `../client/page-bootstrap.ts`.
// https://github.com/vercel/next.js/blob/08d7a7e5189a835f5dcb82af026174e587575c0e/packages/next/src/client/page-bootstrap.ts#L69-L71
return {
type: HMR_MESSAGE_SENT_TO_BROWSER.CLIENT_CHANGES
};
}, (e)=>{
return {
type: HMR_MESSAGE_SENT_TO_BROWSER.RELOAD_PAGE,
data: `error in _error subscription: ${e}`
};
});
processIssues(currentEntryIssues, key, writtenEndpoint, false, logErrors);
}
await manifestLoader.loadClientBuildManifest('_error');
await manifestLoader.loadBuildManifest('_error');
await manifestLoader.loadPagesManifest('_error');
await manifestLoader.loadFontManifest('_error');
manifestLoader.writeManifests({
devRewrites,
productionRewrites,
entrypoints
});
}
export function removeRouteSuffix(route) {
return route.replace(/\/route$/, '');
}
export function addRouteSuffix(route) {
return route + '/route';
}
export function addMetadataIdToRoute(route) {
return route + '/[__metadata_id__]';
}
// Since turbopack will create app pages/route entries based on the structure,
// which means the entry keys are based on file names.
// But for special metadata conventions we'll change the page/pathname to a different path.
// So we need this helper to map the new path back to original turbopack entry key.
export function normalizedPageToTurbopackStructureRoute(route, ext) {
let entrypointKey = route;
if (isMetadataRoute(entrypointKey)) {
entrypointKey = entrypointKey.endsWith('/route') ? entrypointKey.slice(0, -'/route'.length) : entrypointKey;
if (ext) {
if (entrypointKey.endsWith('/[__metadata_id__]')) {
entrypointKey = entrypointKey.slice(0, -'/[__metadata_id__]'.length);
}
if (entrypointKey.endsWith('/sitemap.xml') && ext !== '.xml') {
// For dynamic sitemap route, remove the extension
entrypointKey = entrypointKey.slice(0, -'.xml'.length);
}
}
entrypointKey = entrypointKey + '/route';
}
return entrypointKey;
}
//# sourceMappingURL=turbopack-utils.js.map

File diff suppressed because one or more lines are too long