692 lines
30 KiB
JavaScript
692 lines
30 KiB
JavaScript
import { mkdir, writeFile } from "fs/promises";
|
|
import { join } from "path";
|
|
import ws from "next/dist/compiled/ws";
|
|
import { store as consoleStore } from "../../build/output/store";
|
|
import { HMR_ACTIONS_SENT_TO_BROWSER } from "./hot-reloader-types";
|
|
import { createDefineEnv } from "../../build/swc";
|
|
import * as Log from "../../build/output/log";
|
|
import { getVersionInfo, matchNextPageBundleRequest } from "./hot-reloader-webpack";
|
|
import { BLOCKED_PAGES } from "../../shared/lib/constants";
|
|
import { getOverlayMiddleware } from "../../client/components/react-dev-overlay/server/middleware-turbopack";
|
|
import { PageNotFoundError } from "../../shared/lib/utils";
|
|
import { debounce } from "../utils";
|
|
import { deleteAppClientCache, deleteCache } from "../../build/webpack/plugins/nextjs-require-cache-hot-reloader";
|
|
import { clearAllModuleContexts, clearModuleContext } from "../lib/render-server";
|
|
import { denormalizePagePath } from "../../shared/lib/page-path/denormalize-page-path";
|
|
import { trace } from "../../trace";
|
|
import { AssetMapper, formatIssue, getTurbopackJsConfig, handleEntrypoints, handlePagesErrorRoute, handleRouteType, hasEntrypointForKey, msToNs, processIssues, renderStyledStringToErrorAnsi, processTopLevelIssues, isWellKnownError, printNonFatalIssue } from "./turbopack-utils";
|
|
import { propagateServerField } from "../lib/router-utils/setup-dev-bundler";
|
|
import { TurbopackManifestLoader } from "./turbopack/manifest-loader";
|
|
import { findPagePathData } from "./on-demand-entry-handler";
|
|
import { getEntryKey, splitEntryKey } from "./turbopack/entry-key";
|
|
import { FAST_REFRESH_RUNTIME_RELOAD } from "./messages";
|
|
import { generateEncryptionKeyBase64 } from "../app-render/encryption-utils";
|
|
const wsServer = new ws.Server({
|
|
noServer: true
|
|
});
|
|
const isTestMode = !!(process.env.NEXT_TEST_MODE || process.env.__NEXT_TEST_MODE || process.env.DEBUG);
|
|
export async function createHotReloaderTurbopack(opts, serverFields, distDir) {
|
|
const buildId = "development";
|
|
const { nextConfig, dir } = opts;
|
|
const { loadBindings } = require("../../build/swc");
|
|
let bindings = await loadBindings();
|
|
// For the debugging purpose, check if createNext or equivalent next instance setup in test cases
|
|
// works correctly. Normally `run-test` hides output so only will be visible when `--debug` flag is used.
|
|
if (process.env.TURBOPACK && isTestMode) {
|
|
require("console").log("Creating turbopack project", {
|
|
dir,
|
|
testMode: isTestMode
|
|
});
|
|
}
|
|
const hasRewrites = opts.fsChecker.rewrites.afterFiles.length > 0 || opts.fsChecker.rewrites.beforeFiles.length > 0 || opts.fsChecker.rewrites.fallback.length > 0;
|
|
const hotReloaderSpan = trace("hot-reloader", undefined, {
|
|
version: "14.2.13"
|
|
});
|
|
// Ensure the hotReloaderSpan is flushed immediately as it's the parentSpan for all processing
|
|
// of the current `next dev` invocation.
|
|
hotReloaderSpan.stop();
|
|
const encryptionKey = await generateEncryptionKeyBase64(true);
|
|
const project = await bindings.turbo.createProject({
|
|
projectPath: dir,
|
|
rootPath: opts.nextConfig.experimental.outputFileTracingRoot || dir,
|
|
nextConfig: opts.nextConfig,
|
|
jsConfig: await getTurbopackJsConfig(dir, nextConfig),
|
|
watch: true,
|
|
dev: true,
|
|
env: process.env,
|
|
defineEnv: createDefineEnv({
|
|
isTurbopack: true,
|
|
// TODO: Implement
|
|
clientRouterFilters: undefined,
|
|
config: nextConfig,
|
|
dev: true,
|
|
distDir,
|
|
fetchCacheKeyPrefix: opts.nextConfig.experimental.fetchCacheKeyPrefix,
|
|
hasRewrites,
|
|
// TODO: Implement
|
|
middlewareMatchers: undefined
|
|
}),
|
|
buildId,
|
|
encryptionKey,
|
|
previewProps: opts.fsChecker.prerenderManifest.preview
|
|
});
|
|
const entrypointsSubscription = project.entrypointsSubscribe();
|
|
const currentEntrypoints = {
|
|
global: {
|
|
app: undefined,
|
|
document: undefined,
|
|
error: undefined,
|
|
middleware: undefined,
|
|
instrumentation: undefined
|
|
},
|
|
page: new Map(),
|
|
app: new Map()
|
|
};
|
|
const currentTopLevelIssues = new Map();
|
|
const currentEntryIssues = new Map();
|
|
const manifestLoader = new TurbopackManifestLoader({
|
|
buildId,
|
|
distDir,
|
|
encryptionKey
|
|
});
|
|
// Dev specific
|
|
const changeSubscriptions = new Map();
|
|
const serverPathState = new Map();
|
|
const readyIds = new Set();
|
|
let currentEntriesHandlingResolve;
|
|
let currentEntriesHandling = new Promise((resolve)=>currentEntriesHandlingResolve = resolve);
|
|
const assetMapper = new AssetMapper();
|
|
function clearRequireCache(key, writtenEndpoint) {
|
|
// Figure out if the server files have changed
|
|
let hasChange = false;
|
|
for (const { path, contentHash } of writtenEndpoint.serverPaths){
|
|
// We ignore source maps
|
|
if (path.endsWith(".map")) continue;
|
|
const localKey = `${key}:${path}`;
|
|
const localHash = serverPathState.get(localKey);
|
|
const globalHash = serverPathState.get(path);
|
|
if (localHash && localHash !== contentHash || globalHash && globalHash !== contentHash) {
|
|
hasChange = true;
|
|
serverPathState.set(key, contentHash);
|
|
serverPathState.set(path, contentHash);
|
|
} else {
|
|
if (!localHash) {
|
|
serverPathState.set(key, contentHash);
|
|
}
|
|
if (!globalHash) {
|
|
serverPathState.set(path, contentHash);
|
|
}
|
|
}
|
|
}
|
|
if (!hasChange) {
|
|
return;
|
|
}
|
|
const hasAppPaths = writtenEndpoint.serverPaths.some(({ path: p })=>p.startsWith("server/app"));
|
|
if (hasAppPaths) {
|
|
deleteAppClientCache();
|
|
}
|
|
const serverPaths = writtenEndpoint.serverPaths.map(({ path: p })=>join(distDir, p));
|
|
for (const file of serverPaths){
|
|
clearModuleContext(file);
|
|
deleteCache(file);
|
|
}
|
|
return;
|
|
}
|
|
const buildingIds = new Set();
|
|
const startBuilding = (id, requestUrl, forceRebuild)=>{
|
|
if (!forceRebuild && readyIds.has(id)) {
|
|
return ()=>{};
|
|
}
|
|
if (buildingIds.size === 0) {
|
|
consoleStore.setState({
|
|
loading: true,
|
|
trigger: id,
|
|
url: requestUrl
|
|
}, true);
|
|
}
|
|
buildingIds.add(id);
|
|
return function finishBuilding() {
|
|
if (buildingIds.size === 0) {
|
|
return;
|
|
}
|
|
readyIds.add(id);
|
|
buildingIds.delete(id);
|
|
if (buildingIds.size === 0) {
|
|
hmrEventHappened = false;
|
|
consoleStore.setState({
|
|
loading: false
|
|
}, true);
|
|
}
|
|
};
|
|
};
|
|
let hmrEventHappened = false;
|
|
let hmrHash = 0;
|
|
const clients = new Set();
|
|
const clientStates = new WeakMap();
|
|
function sendToClient(client, payload) {
|
|
client.send(JSON.stringify(payload));
|
|
}
|
|
function sendEnqueuedMessages() {
|
|
for (const [, issueMap] of currentEntryIssues){
|
|
if ([
|
|
...issueMap.values()
|
|
].filter((i)=>i.severity !== "warning").length > 0) {
|
|
// During compilation errors we want to delay the HMR events until errors are fixed
|
|
return;
|
|
}
|
|
}
|
|
for (const client of clients){
|
|
const state = clientStates.get(client);
|
|
if (!state) {
|
|
continue;
|
|
}
|
|
for (const [, issueMap] of state.clientIssues){
|
|
if ([
|
|
...issueMap.values()
|
|
].filter((i)=>i.severity !== "warning").length > 0) {
|
|
// During compilation errors we want to delay the HMR events until errors are fixed
|
|
return;
|
|
}
|
|
}
|
|
for (const payload of state.hmrPayloads.values()){
|
|
sendToClient(client, payload);
|
|
}
|
|
state.hmrPayloads.clear();
|
|
if (state.turbopackUpdates.length > 0) {
|
|
sendToClient(client, {
|
|
action: HMR_ACTIONS_SENT_TO_BROWSER.TURBOPACK_MESSAGE,
|
|
data: state.turbopackUpdates
|
|
});
|
|
state.turbopackUpdates.length = 0;
|
|
}
|
|
}
|
|
}
|
|
const sendEnqueuedMessagesDebounce = debounce(sendEnqueuedMessages, 2);
|
|
const sendHmr = (id, payload)=>{
|
|
for (const client of clients){
|
|
var _clientStates_get;
|
|
(_clientStates_get = clientStates.get(client)) == null ? void 0 : _clientStates_get.hmrPayloads.set(id, payload);
|
|
}
|
|
hmrEventHappened = true;
|
|
sendEnqueuedMessagesDebounce();
|
|
};
|
|
function sendTurbopackMessage(payload) {
|
|
// TODO(PACK-2049): For some reason we end up emitting hundreds of issues messages on bigger apps,
|
|
// a lot of which are duplicates.
|
|
// They are currently not handled on the client at all, so might as well not send them for now.
|
|
payload.diagnostics = [];
|
|
payload.issues = [];
|
|
for (const client of clients){
|
|
var _clientStates_get;
|
|
(_clientStates_get = clientStates.get(client)) == null ? void 0 : _clientStates_get.turbopackUpdates.push(payload);
|
|
}
|
|
hmrEventHappened = true;
|
|
sendEnqueuedMessagesDebounce();
|
|
}
|
|
async function subscribeToChanges(key, includeIssues, endpoint, makePayload) {
|
|
if (changeSubscriptions.has(key)) {
|
|
return;
|
|
}
|
|
const { side } = splitEntryKey(key);
|
|
const changedPromise = endpoint[`${side}Changed`](includeIssues);
|
|
changeSubscriptions.set(key, changedPromise);
|
|
const changed = await changedPromise;
|
|
for await (const change of changed){
|
|
processIssues(currentEntryIssues, key, change, false, true);
|
|
const payload = await makePayload(change);
|
|
if (payload) {
|
|
sendHmr(key, payload);
|
|
}
|
|
}
|
|
}
|
|
async function unsubscribeFromChanges(key) {
|
|
const subscription = await changeSubscriptions.get(key);
|
|
if (subscription) {
|
|
await (subscription.return == null ? void 0 : subscription.return.call(subscription));
|
|
changeSubscriptions.delete(key);
|
|
}
|
|
currentEntryIssues.delete(key);
|
|
}
|
|
async function subscribeToHmrEvents(client, id) {
|
|
const key = getEntryKey("assets", "client", id);
|
|
if (!hasEntrypointForKey(currentEntrypoints, key, assetMapper)) {
|
|
// maybe throw an error / force the client to reload?
|
|
return;
|
|
}
|
|
const state = clientStates.get(client);
|
|
if (!state || state.subscriptions.has(id)) {
|
|
return;
|
|
}
|
|
const subscription = project.hmrEvents(id);
|
|
state.subscriptions.set(id, subscription);
|
|
// The subscription will always emit once, which is the initial
|
|
// computation. This is not a change, so swallow it.
|
|
try {
|
|
await subscription.next();
|
|
for await (const data of subscription){
|
|
processIssues(state.clientIssues, key, data, false, true);
|
|
if (data.type !== "issues") {
|
|
sendTurbopackMessage(data);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
// The client might be using an HMR session from a previous server, tell them
|
|
// to fully reload the page to resolve the issue. We can't use
|
|
// `hotReloader.send` since that would force every connected client to
|
|
// reload, only this client is out of date.
|
|
const reloadAction = {
|
|
action: HMR_ACTIONS_SENT_TO_BROWSER.RELOAD_PAGE
|
|
};
|
|
sendToClient(client, reloadAction);
|
|
client.close();
|
|
return;
|
|
}
|
|
}
|
|
function unsubscribeFromHmrEvents(client, id) {
|
|
const state = clientStates.get(client);
|
|
if (!state) {
|
|
return;
|
|
}
|
|
const subscription = state.subscriptions.get(id);
|
|
subscription == null ? void 0 : subscription.return();
|
|
const key = getEntryKey("assets", "client", id);
|
|
state.clientIssues.delete(key);
|
|
}
|
|
async function handleEntrypointsSubscription() {
|
|
for await (const entrypoints of entrypointsSubscription){
|
|
if (!currentEntriesHandlingResolve) {
|
|
currentEntriesHandling = new Promise(// eslint-disable-next-line no-loop-func
|
|
(resolve)=>currentEntriesHandlingResolve = resolve);
|
|
}
|
|
processTopLevelIssues(currentTopLevelIssues, entrypoints);
|
|
await handleEntrypoints({
|
|
entrypoints,
|
|
currentEntrypoints,
|
|
currentEntryIssues,
|
|
manifestLoader,
|
|
nextConfig: opts.nextConfig,
|
|
rewrites: opts.fsChecker.rewrites,
|
|
logErrors: true,
|
|
dev: {
|
|
assetMapper,
|
|
changeSubscriptions,
|
|
clients,
|
|
clientStates,
|
|
serverFields,
|
|
hooks: {
|
|
handleWrittenEndpoint: (id, result)=>{
|
|
clearRequireCache(id, result);
|
|
},
|
|
propagateServerField: propagateServerField.bind(null, opts),
|
|
sendHmr,
|
|
startBuilding,
|
|
subscribeToChanges,
|
|
unsubscribeFromChanges,
|
|
unsubscribeFromHmrEvents
|
|
}
|
|
}
|
|
});
|
|
currentEntriesHandlingResolve();
|
|
currentEntriesHandlingResolve = undefined;
|
|
}
|
|
}
|
|
await mkdir(join(distDir, "server"), {
|
|
recursive: true
|
|
});
|
|
await mkdir(join(distDir, "static", buildId), {
|
|
recursive: true
|
|
});
|
|
await writeFile(join(distDir, "package.json"), JSON.stringify({
|
|
type: "commonjs"
|
|
}, null, 2));
|
|
const overlayMiddleware = getOverlayMiddleware(project);
|
|
const versionInfoPromise = getVersionInfo(isTestMode || opts.telemetry.isEnabled);
|
|
const hotReloader = {
|
|
turbopackProject: project,
|
|
activeWebpackConfigs: undefined,
|
|
serverStats: null,
|
|
edgeServerStats: null,
|
|
async run (req, res, _parsedUrl) {
|
|
var _req_url;
|
|
// intercept page chunks request and ensure them with turbopack
|
|
if ((_req_url = req.url) == null ? void 0 : _req_url.startsWith("/_next/static/chunks/pages/")) {
|
|
const params = matchNextPageBundleRequest(req.url);
|
|
if (params) {
|
|
const decodedPagePath = `/${params.path.map((param)=>decodeURIComponent(param)).join("/")}`;
|
|
const denormalizedPagePath = denormalizePagePath(decodedPagePath);
|
|
await hotReloader.ensurePage({
|
|
page: denormalizedPagePath,
|
|
clientOnly: false,
|
|
definition: undefined,
|
|
url: req.url
|
|
}).catch(console.error);
|
|
}
|
|
}
|
|
await overlayMiddleware(req, res);
|
|
// Request was not finished.
|
|
return {
|
|
finished: undefined
|
|
};
|
|
},
|
|
// TODO: Figure out if socket type can match the NextJsHotReloaderInterface
|
|
onHMR (req, socket, head) {
|
|
wsServer.handleUpgrade(req, socket, head, (client)=>{
|
|
const clientIssues = new Map();
|
|
const subscriptions = new Map();
|
|
clients.add(client);
|
|
clientStates.set(client, {
|
|
clientIssues,
|
|
hmrPayloads: new Map(),
|
|
turbopackUpdates: [],
|
|
subscriptions
|
|
});
|
|
client.on("close", ()=>{
|
|
// Remove active subscriptions
|
|
for (const subscription of subscriptions.values()){
|
|
subscription.return == null ? void 0 : subscription.return.call(subscription);
|
|
}
|
|
clientStates.delete(client);
|
|
clients.delete(client);
|
|
});
|
|
client.addEventListener("message", ({ data })=>{
|
|
const parsedData = JSON.parse(typeof data !== "string" ? data.toString() : data);
|
|
// Next.js messages
|
|
switch(parsedData.event){
|
|
case "ping":
|
|
break;
|
|
case "span-end":
|
|
{
|
|
hotReloaderSpan.manualTraceChild(parsedData.spanName, msToNs(parsedData.startTime), msToNs(parsedData.endTime), parsedData.attributes);
|
|
break;
|
|
}
|
|
case "client-hmr-latency":
|
|
hotReloaderSpan.manualTraceChild(parsedData.event, msToNs(parsedData.startTime), msToNs(parsedData.endTime), {
|
|
updatedModules: parsedData.updatedModules,
|
|
page: parsedData.page,
|
|
isPageHidden: parsedData.isPageHidden
|
|
});
|
|
break;
|
|
case "client-error":
|
|
case "client-warning":
|
|
case "client-success":
|
|
case "server-component-reload-page":
|
|
case "client-reload-page":
|
|
case "client-removed-page":
|
|
case "client-full-reload":
|
|
const { hadRuntimeError, dependencyChain } = parsedData;
|
|
if (hadRuntimeError) {
|
|
Log.warn(FAST_REFRESH_RUNTIME_RELOAD);
|
|
}
|
|
if (Array.isArray(dependencyChain) && typeof dependencyChain[0] === "string") {
|
|
const cleanedModulePath = dependencyChain[0].replace(/^\[project\]/, ".").replace(/ \[.*\] \(.*\)$/, "");
|
|
Log.warn(`Fast Refresh had to perform a full reload when ${cleanedModulePath} changed. Read more: https://nextjs.org/docs/messages/fast-refresh-reload`);
|
|
}
|
|
break;
|
|
case "client-added-page":
|
|
break;
|
|
default:
|
|
// Might be a Turbopack message...
|
|
if (!parsedData.type) {
|
|
throw new Error(`unrecognized HMR message "${data}"`);
|
|
}
|
|
}
|
|
// Turbopack messages
|
|
switch(parsedData.type){
|
|
case "turbopack-subscribe":
|
|
subscribeToHmrEvents(client, parsedData.path);
|
|
break;
|
|
case "turbopack-unsubscribe":
|
|
unsubscribeFromHmrEvents(client, parsedData.path);
|
|
break;
|
|
default:
|
|
if (!parsedData.event) {
|
|
throw new Error(`unrecognized Turbopack HMR message "${data}"`);
|
|
}
|
|
}
|
|
});
|
|
const turbopackConnected = {
|
|
action: HMR_ACTIONS_SENT_TO_BROWSER.TURBOPACK_CONNECTED
|
|
};
|
|
sendToClient(client, turbopackConnected);
|
|
const errors = [];
|
|
for (const entryIssues of currentEntryIssues.values()){
|
|
for (const issue of entryIssues.values()){
|
|
if (issue.severity !== "warning") {
|
|
errors.push({
|
|
message: formatIssue(issue)
|
|
});
|
|
} else {
|
|
printNonFatalIssue(issue);
|
|
}
|
|
}
|
|
}
|
|
(async function() {
|
|
const versionInfo = await versionInfoPromise;
|
|
const sync = {
|
|
action: HMR_ACTIONS_SENT_TO_BROWSER.SYNC,
|
|
errors,
|
|
warnings: [],
|
|
hash: "",
|
|
versionInfo
|
|
};
|
|
sendToClient(client, sync);
|
|
})();
|
|
});
|
|
},
|
|
send (action) {
|
|
const payload = JSON.stringify(action);
|
|
for (const client of clients){
|
|
client.send(payload);
|
|
}
|
|
},
|
|
setHmrServerError (_error) {
|
|
// Not implemented yet.
|
|
},
|
|
clearHmrServerError () {
|
|
// Not implemented yet.
|
|
},
|
|
async start () {},
|
|
async stop () {
|
|
// Not implemented yet.
|
|
},
|
|
async getCompilationErrors (page) {
|
|
const appEntryKey = getEntryKey("app", "server", page);
|
|
const pagesEntryKey = getEntryKey("pages", "server", page);
|
|
const topLevelIssues = currentTopLevelIssues.values();
|
|
const thisEntryIssues = currentEntryIssues.get(appEntryKey) ?? currentEntryIssues.get(pagesEntryKey);
|
|
if (thisEntryIssues !== undefined && thisEntryIssues.size > 0) {
|
|
// If there is an error related to the requesting page we display it instead of the first error
|
|
return [
|
|
...topLevelIssues,
|
|
...thisEntryIssues.values()
|
|
].map((issue)=>{
|
|
const formattedIssue = formatIssue(issue);
|
|
if (issue.severity === "warning") {
|
|
printNonFatalIssue(issue);
|
|
return null;
|
|
} else if (isWellKnownError(issue)) {
|
|
Log.error(formattedIssue);
|
|
}
|
|
return new Error(formattedIssue);
|
|
}).filter((error)=>error !== null);
|
|
}
|
|
// Otherwise, return all errors across pages
|
|
const errors = [];
|
|
for (const issue of topLevelIssues){
|
|
if (issue.severity !== "warning") {
|
|
errors.push(new Error(formatIssue(issue)));
|
|
}
|
|
}
|
|
for (const entryIssues of currentEntryIssues.values()){
|
|
for (const issue of entryIssues.values()){
|
|
if (issue.severity !== "warning") {
|
|
const message = formatIssue(issue);
|
|
errors.push(new Error(message));
|
|
} else {
|
|
printNonFatalIssue(issue);
|
|
}
|
|
}
|
|
}
|
|
return errors;
|
|
},
|
|
async invalidate ({ // .env files or tsconfig/jsconfig change
|
|
reloadAfterInvalidation }) {
|
|
if (reloadAfterInvalidation) {
|
|
await clearAllModuleContexts();
|
|
this.send({
|
|
action: HMR_ACTIONS_SENT_TO_BROWSER.SERVER_COMPONENT_CHANGES
|
|
});
|
|
}
|
|
},
|
|
async buildFallbackError () {
|
|
// Not implemented yet.
|
|
},
|
|
async ensurePage ({ page: inputPage, // Unused parameters
|
|
// clientOnly,
|
|
// appPaths,
|
|
definition, isApp, url: requestUrl }) {
|
|
if (BLOCKED_PAGES.includes(inputPage) && inputPage !== "/_error") {
|
|
return;
|
|
}
|
|
let routeDef = definition ?? await findPagePathData(dir, inputPage, nextConfig.pageExtensions, opts.pagesDir, opts.appDir);
|
|
const page = routeDef.page;
|
|
const pathname = (definition == null ? void 0 : definition.pathname) ?? inputPage;
|
|
if (page === "/_error") {
|
|
let finishBuilding = startBuilding(pathname, requestUrl, false);
|
|
try {
|
|
await handlePagesErrorRoute({
|
|
currentEntryIssues,
|
|
entrypoints: currentEntrypoints,
|
|
manifestLoader,
|
|
rewrites: opts.fsChecker.rewrites,
|
|
logErrors: true,
|
|
hooks: {
|
|
subscribeToChanges,
|
|
handleWrittenEndpoint: (id, result)=>{
|
|
clearRequireCache(id, result);
|
|
assetMapper.setPathsForKey(id, result.clientPaths);
|
|
}
|
|
}
|
|
});
|
|
} finally{
|
|
finishBuilding();
|
|
}
|
|
return;
|
|
}
|
|
await currentEntriesHandling;
|
|
const isInsideAppDir = routeDef.bundlePath.startsWith("app/");
|
|
const route = isInsideAppDir ? currentEntrypoints.app.get(page) : currentEntrypoints.page.get(page);
|
|
if (!route) {
|
|
// TODO: why is this entry missing in turbopack?
|
|
if (page === "/middleware") return;
|
|
if (page === "/src/middleware") return;
|
|
if (page === "/instrumentation") return;
|
|
if (page === "/src/instrumentation") return;
|
|
throw new PageNotFoundError(`route not found ${page}`);
|
|
}
|
|
// We don't throw on ensureOpts.isApp === true for page-api
|
|
// since this can happen when app pages make
|
|
// api requests to page API routes.
|
|
if (isApp && route.type === "page") {
|
|
throw new Error(`mis-matched route type: isApp && page for ${page}`);
|
|
}
|
|
const finishBuilding = startBuilding(pathname, requestUrl, false);
|
|
try {
|
|
await handleRouteType({
|
|
dev: true,
|
|
page,
|
|
pathname,
|
|
route,
|
|
currentEntryIssues,
|
|
entrypoints: currentEntrypoints,
|
|
manifestLoader,
|
|
readyIds,
|
|
rewrites: opts.fsChecker.rewrites,
|
|
logErrors: true,
|
|
hooks: {
|
|
subscribeToChanges,
|
|
handleWrittenEndpoint: (id, result)=>{
|
|
clearRequireCache(id, result);
|
|
assetMapper.setPathsForKey(id, result.clientPaths);
|
|
}
|
|
}
|
|
});
|
|
} finally{
|
|
finishBuilding();
|
|
}
|
|
}
|
|
};
|
|
handleEntrypointsSubscription().catch((err)=>{
|
|
console.error(err);
|
|
process.exit(1);
|
|
});
|
|
// Write empty manifests
|
|
await currentEntriesHandling;
|
|
await manifestLoader.writeManifests({
|
|
rewrites: opts.fsChecker.rewrites,
|
|
pageEntrypoints: currentEntrypoints.page
|
|
});
|
|
async function handleProjectUpdates() {
|
|
for await (const updateMessage of project.updateInfoSubscribe(30)){
|
|
switch(updateMessage.updateType){
|
|
case "start":
|
|
{
|
|
hotReloader.send({
|
|
action: HMR_ACTIONS_SENT_TO_BROWSER.BUILDING
|
|
});
|
|
break;
|
|
}
|
|
case "end":
|
|
{
|
|
sendEnqueuedMessages();
|
|
function addErrors(errorsMap, issues) {
|
|
for (const issueMap of issues.values()){
|
|
for (const [key, issue] of issueMap){
|
|
if (issue.severity === "warning") continue;
|
|
if (errorsMap.has(key)) continue;
|
|
const message = formatIssue(issue);
|
|
errorsMap.set(key, {
|
|
message,
|
|
details: issue.detail ? renderStyledStringToErrorAnsi(issue.detail) : undefined
|
|
});
|
|
}
|
|
}
|
|
}
|
|
const errors = new Map();
|
|
addErrors(errors, currentEntryIssues);
|
|
for (const client of clients){
|
|
const state = clientStates.get(client);
|
|
if (!state) {
|
|
continue;
|
|
}
|
|
const clientErrors = new Map(errors);
|
|
addErrors(clientErrors, state.clientIssues);
|
|
sendToClient(client, {
|
|
action: HMR_ACTIONS_SENT_TO_BROWSER.BUILT,
|
|
hash: String(++hmrHash),
|
|
errors: [
|
|
...clientErrors.values()
|
|
],
|
|
warnings: []
|
|
});
|
|
}
|
|
if (hmrEventHappened) {
|
|
const time = updateMessage.value.duration;
|
|
const timeMessage = time > 2000 ? `${Math.round(time / 100) / 10}s` : `${time}ms`;
|
|
Log.event(`Compiled in ${timeMessage}`);
|
|
hmrEventHappened = false;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
}
|
|
}
|
|
}
|
|
handleProjectUpdates().catch((err)=>{
|
|
console.error(err);
|
|
process.exit(1);
|
|
});
|
|
return hotReloader;
|
|
}
|
|
|
|
//# sourceMappingURL=hot-reloader-turbopack.js.map
|