2022-08-16 11:55:37 +02:00
|
|
|
import { webpack, StringXor } from 'next/dist/compiled/webpack/webpack'
|
2022-08-09 21:34:25 +02:00
|
|
|
import type { NextConfigComplete } from '../config-shared'
|
|
|
|
import type { CustomRoutes } from '../../lib/load-custom-routes'
|
2022-05-31 02:05:27 +02:00
|
|
|
import { getOverlayMiddleware } from 'next/dist/compiled/@next/react-dev-overlay/dist/middleware'
|
2019-10-04 18:11:39 +02:00
|
|
|
import { IncomingMessage, ServerResponse } from 'http'
|
2020-07-01 17:34:00 +02:00
|
|
|
import { WebpackHotMiddleware } from './hot-middleware'
|
2022-08-17 19:14:03 +02:00
|
|
|
import { join, relative, isAbsolute, posix } from 'path'
|
2020-02-12 02:16:42 +01:00
|
|
|
import { UrlObject } from 'url'
|
2021-09-17 21:20:09 +02:00
|
|
|
import {
|
|
|
|
createEntrypoints,
|
|
|
|
createPagesMapping,
|
|
|
|
finalizeEntrypoint,
|
2022-04-27 11:50:29 +02:00
|
|
|
getClientEntry,
|
|
|
|
getEdgeServerEntry,
|
2022-05-25 11:46:26 +02:00
|
|
|
getAppEntry,
|
2022-04-27 11:50:29 +02:00
|
|
|
runDependingOnPageType,
|
2021-09-17 21:20:09 +02:00
|
|
|
} from '../../build/entries'
|
2021-06-30 13:44:40 +02:00
|
|
|
import { watchCompilers } from '../../build/output'
|
2022-06-22 17:41:23 +02:00
|
|
|
import * as Log from '../../build/output/log'
|
2021-06-30 13:44:40 +02:00
|
|
|
import getBaseWebpackConfig from '../../build/webpack-config'
|
2022-07-30 04:39:43 +02:00
|
|
|
import { APP_DIR_ALIAS } from '../../lib/constants'
|
2021-06-30 13:44:40 +02:00
|
|
|
import { recursiveDelete } from '../../lib/recursive-delete'
|
2022-09-18 02:00:16 +02:00
|
|
|
import {
|
|
|
|
BLOCKED_PAGES,
|
|
|
|
COMPILER_NAMES,
|
|
|
|
RSC_MODULE_TYPES,
|
|
|
|
} from '../../shared/lib/constants'
|
2021-06-30 13:44:40 +02:00
|
|
|
import { __ApiPreviewProps } from '../api-utils'
|
2022-04-27 11:50:29 +02:00
|
|
|
import { getPathMatch } from '../../shared/lib/router/utils/path-match'
|
2021-06-30 13:44:40 +02:00
|
|
|
import { findPageFile } from '../lib/find-page-file'
|
2022-04-30 13:19:27 +02:00
|
|
|
import {
|
2020-06-26 06:26:09 +02:00
|
|
|
BUILDING,
|
2022-04-30 13:19:27 +02:00
|
|
|
entries,
|
2022-08-12 15:01:19 +02:00
|
|
|
EntryTypes,
|
2022-08-11 05:27:48 +02:00
|
|
|
getInvalidator,
|
2022-04-30 13:19:27 +02:00
|
|
|
onDemandEntryHandler,
|
2020-06-26 06:26:09 +02:00
|
|
|
} from './on-demand-entry-handler'
|
2022-04-30 13:19:27 +02:00
|
|
|
import { denormalizePagePath } from '../../shared/lib/page-path/denormalize-page-path'
|
|
|
|
import { normalizePathSep } from '../../shared/lib/page-path/normalize-path-sep'
|
2021-06-30 13:44:40 +02:00
|
|
|
import getRouteFromEntrypoint from '../get-route-from-entrypoint'
|
2021-11-02 20:48:23 +01:00
|
|
|
import { fileExists } from '../../lib/file-exists'
|
2022-09-18 02:00:16 +02:00
|
|
|
import { difference, isMiddlewareFilename } from '../../build/utils'
|
2021-07-05 18:31:32 +02:00
|
|
|
import { DecodeError } from '../../shared/lib/utils'
|
2021-09-13 15:49:29 +02:00
|
|
|
import { Span, trace } from '../../trace'
|
2022-01-11 21:40:03 +01:00
|
|
|
import { getProperError } from '../../lib/is-error'
|
2021-10-15 09:09:54 +02:00
|
|
|
import ws from 'next/dist/compiled/ws'
|
2022-02-15 17:24:11 +01:00
|
|
|
import { promises as fs } from 'fs'
|
2022-05-20 14:24:00 +02:00
|
|
|
import { getPageStaticInfo } from '../../build/analysis/get-page-static-info'
|
2022-08-13 18:55:55 +02:00
|
|
|
import { UnwrapPromise } from '../../lib/coalesced-function'
|
2021-10-15 09:09:54 +02:00
|
|
|
|
2022-08-15 16:29:51 +02:00
|
|
|
function diff(a: Set<any>, b: Set<any>) {
|
|
|
|
return new Set([...a].filter((v) => !b.has(v)))
|
|
|
|
}
|
|
|
|
|
2021-10-15 09:09:54 +02:00
|
|
|
const wsServer = new ws.Server({ noServer: true })
|
2019-04-28 20:08:38 +02:00
|
|
|
|
2020-07-19 20:09:41 +02:00
|
|
|
export async function renderScriptError(
|
|
|
|
res: ServerResponse,
|
|
|
|
error: Error,
|
|
|
|
{ verbose = true } = {}
|
|
|
|
) {
|
2018-12-06 16:47:10 +01:00
|
|
|
// Asks CDNs and others to not to cache the errored page
|
2019-05-29 13:57:26 +02:00
|
|
|
res.setHeader(
|
|
|
|
'Cache-Control',
|
|
|
|
'no-cache, no-store, max-age=0, must-revalidate'
|
|
|
|
)
|
2018-12-06 16:47:10 +01:00
|
|
|
|
2021-02-19 11:10:19 +01:00
|
|
|
if ((error as any).code === 'ENOENT') {
|
2018-12-06 16:47:10 +01:00
|
|
|
res.statusCode = 404
|
|
|
|
res.end('404 - Not Found')
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-07-19 20:09:41 +02:00
|
|
|
if (verbose) {
|
|
|
|
console.error(error.stack)
|
|
|
|
}
|
2018-12-06 16:47:10 +01:00
|
|
|
res.statusCode = 500
|
|
|
|
res.end('500 - Internal Error')
|
|
|
|
}
|
2018-10-02 00:55:31 +02:00
|
|
|
|
2019-10-04 18:11:39 +02:00
|
|
|
function addCorsSupport(req: IncomingMessage, res: ServerResponse) {
|
2022-07-30 04:39:43 +02:00
|
|
|
// Only rewrite CORS handling when URL matches a hot-reloader middleware
|
|
|
|
if (!req.url!.startsWith('/__next')) {
|
2020-05-16 23:15:12 +02:00
|
|
|
return { preflight: false }
|
|
|
|
}
|
|
|
|
|
2018-10-02 00:55:31 +02:00
|
|
|
if (!req.headers.origin) {
|
|
|
|
return { preflight: false }
|
|
|
|
}
|
|
|
|
|
|
|
|
res.setHeader('Access-Control-Allow-Origin', req.headers.origin)
|
|
|
|
res.setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET')
|
|
|
|
// Based on https://github.com/primus/access-control/blob/4cf1bc0e54b086c91e6aa44fb14966fa5ef7549c/index.js#L158
|
|
|
|
if (req.headers['access-control-request-headers']) {
|
2019-11-11 04:24:53 +01:00
|
|
|
res.setHeader(
|
|
|
|
'Access-Control-Allow-Headers',
|
|
|
|
req.headers['access-control-request-headers'] as string
|
|
|
|
)
|
2018-10-02 00:55:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (req.method === 'OPTIONS') {
|
|
|
|
res.writeHead(200)
|
|
|
|
res.end()
|
|
|
|
return { preflight: true }
|
|
|
|
}
|
|
|
|
|
|
|
|
return { preflight: false }
|
|
|
|
}
|
|
|
|
|
2022-04-27 11:50:29 +02:00
|
|
|
const matchNextPageBundleRequest = getPathMatch(
|
2020-07-17 10:38:06 +02:00
|
|
|
'/_next/static/chunks/pages/:path*.js(\\.map|)'
|
2019-05-29 13:57:26 +02:00
|
|
|
)
|
2018-07-24 11:24:40 +02:00
|
|
|
|
|
|
|
// Recursively look up the issuer till it ends up at the root
|
2022-04-21 16:14:03 +02:00
|
|
|
function findEntryModule(
|
2022-08-16 11:55:37 +02:00
|
|
|
compilation: webpack.Compilation,
|
2022-04-21 16:14:03 +02:00
|
|
|
issuerModule: any
|
|
|
|
): any {
|
|
|
|
const issuer = compilation.moduleGraph.getIssuer(issuerModule)
|
|
|
|
if (issuer) {
|
|
|
|
return findEntryModule(compilation, issuer)
|
2018-07-24 11:24:40 +02:00
|
|
|
}
|
|
|
|
|
2022-04-21 16:14:03 +02:00
|
|
|
return issuerModule
|
2018-07-24 11:24:40 +02:00
|
|
|
}
|
|
|
|
|
2022-08-16 11:55:37 +02:00
|
|
|
function erroredPages(compilation: webpack.Compilation) {
|
2019-10-04 18:11:39 +02:00
|
|
|
const failedPages: { [page: string]: any[] } = {}
|
2018-07-24 11:24:40 +02:00
|
|
|
for (const error of compilation.errors) {
|
2021-10-24 23:04:26 +02:00
|
|
|
if (!error.module) {
|
2019-05-08 03:11:56 +02:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2022-04-21 16:14:03 +02:00
|
|
|
const entryModule = findEntryModule(compilation, error.module)
|
2019-02-19 22:45:07 +01:00
|
|
|
const { name } = entryModule
|
2018-07-24 11:24:40 +02:00
|
|
|
if (!name) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Only pages have to be reloaded
|
2020-06-26 06:26:09 +02:00
|
|
|
const enhancedName = getRouteFromEntrypoint(name)
|
|
|
|
|
|
|
|
if (!enhancedName) {
|
|
|
|
continue
|
|
|
|
}
|
2018-07-24 11:24:40 +02:00
|
|
|
|
|
|
|
if (!failedPages[enhancedName]) {
|
|
|
|
failedPages[enhancedName] = []
|
|
|
|
}
|
|
|
|
|
|
|
|
failedPages[enhancedName].push(error)
|
|
|
|
}
|
|
|
|
|
|
|
|
return failedPages
|
|
|
|
}
|
2017-06-05 17:07:20 +02:00
|
|
|
|
2016-10-14 17:05:08 +02:00
|
|
|
export default class HotReloader {
|
2019-10-04 18:11:39 +02:00
|
|
|
private dir: string
|
|
|
|
private buildId: string
|
2022-08-03 19:23:01 +02:00
|
|
|
private interceptors: any[]
|
2022-09-03 02:13:47 +02:00
|
|
|
private pagesDir?: string
|
2022-02-25 13:40:24 +01:00
|
|
|
private distDir: string
|
2021-10-15 09:09:54 +02:00
|
|
|
private webpackHotMiddleware?: WebpackHotMiddleware
|
2021-07-12 23:38:57 +02:00
|
|
|
private config: NextConfigComplete
|
2022-08-13 18:55:55 +02:00
|
|
|
public hasServerComponents: boolean
|
|
|
|
public hasReactRoot: boolean
|
2022-08-16 11:55:37 +02:00
|
|
|
public clientStats: webpack.Stats | null
|
|
|
|
public serverStats: webpack.Stats | null
|
|
|
|
public edgeServerStats: webpack.Stats | null
|
2020-07-30 05:44:25 +02:00
|
|
|
private clientError: Error | null = null
|
|
|
|
private serverError: Error | null = null
|
2019-10-04 18:11:39 +02:00
|
|
|
private serverPrevDocumentHash: string | null
|
|
|
|
private prevChunkNames?: Set<any>
|
2021-10-15 09:09:54 +02:00
|
|
|
private onDemandEntries?: ReturnType<typeof onDemandEntryHandler>
|
2020-02-12 02:16:42 +01:00
|
|
|
private previewProps: __ApiPreviewProps
|
2020-06-26 06:26:09 +02:00
|
|
|
private watcher: any
|
2021-03-26 16:19:48 +01:00
|
|
|
private rewrites: CustomRoutes['rewrites']
|
2021-04-22 13:08:47 +02:00
|
|
|
private fallbackWatcher: any
|
2021-08-25 10:47:16 +02:00
|
|
|
private hotReloaderSpan: Span
|
2022-04-27 11:50:29 +02:00
|
|
|
private pagesMapping: { [key: string]: string } = {}
|
2022-05-25 11:46:26 +02:00
|
|
|
private appDir?: string
|
2022-08-16 11:55:37 +02:00
|
|
|
public multiCompiler?: webpack.MultiCompiler
|
2022-08-13 18:55:55 +02:00
|
|
|
public activeConfigs?: Array<
|
|
|
|
UnwrapPromise<ReturnType<typeof getBaseWebpackConfig>>
|
|
|
|
>
|
2019-10-04 18:11:39 +02:00
|
|
|
|
|
|
|
constructor(
|
|
|
|
dir: string,
|
|
|
|
{
|
|
|
|
config,
|
|
|
|
pagesDir,
|
2022-02-25 13:40:24 +01:00
|
|
|
distDir,
|
2019-10-04 18:11:39 +02:00
|
|
|
buildId,
|
2020-02-12 02:16:42 +01:00
|
|
|
previewProps,
|
2020-08-13 14:39:36 +02:00
|
|
|
rewrites,
|
2022-05-25 11:46:26 +02:00
|
|
|
appDir,
|
2020-02-12 02:16:42 +01:00
|
|
|
}: {
|
2021-07-12 23:38:57 +02:00
|
|
|
config: NextConfigComplete
|
2022-09-03 02:13:47 +02:00
|
|
|
pagesDir?: string
|
2022-02-25 13:40:24 +01:00
|
|
|
distDir: string
|
2020-02-12 02:16:42 +01:00
|
|
|
buildId: string
|
|
|
|
previewProps: __ApiPreviewProps
|
2021-03-26 16:19:48 +01:00
|
|
|
rewrites: CustomRoutes['rewrites']
|
2022-05-25 11:46:26 +02:00
|
|
|
appDir?: string
|
2020-02-12 02:16:42 +01:00
|
|
|
}
|
2019-10-04 18:11:39 +02:00
|
|
|
) {
|
2018-06-25 23:06:46 +02:00
|
|
|
this.buildId = buildId
|
2016-10-17 09:05:46 +02:00
|
|
|
this.dir = dir
|
2022-08-03 19:23:01 +02:00
|
|
|
this.interceptors = []
|
2019-09-24 17:15:14 +02:00
|
|
|
this.pagesDir = pagesDir
|
2022-05-25 11:46:26 +02:00
|
|
|
this.appDir = appDir
|
2022-02-25 13:40:24 +01:00
|
|
|
this.distDir = distDir
|
2021-10-20 19:52:11 +02:00
|
|
|
this.clientStats = null
|
2020-05-20 07:00:50 +02:00
|
|
|
this.serverStats = null
|
2022-04-27 11:50:29 +02:00
|
|
|
this.edgeServerStats = null
|
2018-07-24 11:24:40 +02:00
|
|
|
this.serverPrevDocumentHash = null
|
2017-02-26 20:45:16 +01:00
|
|
|
|
2018-02-14 16:17:41 +01:00
|
|
|
this.config = config
|
2022-04-28 21:17:23 +02:00
|
|
|
this.hasReactRoot = !!process.env.__NEXT_REACT_ROOT
|
2022-09-21 21:30:46 +02:00
|
|
|
this.hasServerComponents = this.hasReactRoot && !!config.experimental.appDir
|
2020-02-12 02:16:42 +01:00
|
|
|
this.previewProps = previewProps
|
2020-08-13 14:39:36 +02:00
|
|
|
this.rewrites = rewrites
|
2021-11-03 12:51:55 +01:00
|
|
|
this.hotReloaderSpan = trace('hot-reloader', undefined, {
|
2021-11-04 21:56:30 +01:00
|
|
|
version: process.env.__NEXT_VERSION as string,
|
2021-11-03 12:51:55 +01:00
|
|
|
})
|
2021-09-13 15:49:29 +02:00
|
|
|
// Ensure the hotReloaderSpan is flushed immediately as it's the parentSpan for all processing
|
|
|
|
// of the current `next dev` invocation.
|
|
|
|
this.hotReloaderSpan.stop()
|
2016-10-17 09:05:46 +02:00
|
|
|
}
|
|
|
|
|
2020-05-31 23:28:23 +02:00
|
|
|
public async run(
|
|
|
|
req: IncomingMessage,
|
|
|
|
res: ServerResponse,
|
|
|
|
parsedUrl: UrlObject
|
2020-07-13 16:59:40 +02:00
|
|
|
): Promise<{ finished?: true }> {
|
2018-01-30 16:40:52 +01:00
|
|
|
// Usually CORS support is not needed for the hot-reloader (this is dev only feature)
|
|
|
|
// With when the app runs for multi-zones support behind a proxy,
|
|
|
|
// the current page is trying to access this URL via assetPrefix.
|
|
|
|
// That's when the CORS support is needed.
|
|
|
|
const { preflight } = addCorsSupport(req, res)
|
|
|
|
if (preflight) {
|
2020-07-13 16:59:40 +02:00
|
|
|
return {}
|
2018-01-30 16:40:52 +01:00
|
|
|
}
|
|
|
|
|
2018-07-25 13:45:42 +02:00
|
|
|
// When a request comes in that is a page bundle, e.g. /_next/static/<buildid>/pages/index.js
|
|
|
|
// we have to compile the page using on-demand-entries, this middleware will handle doing that
|
|
|
|
// by adding the page to on-demand-entries, waiting till it's done
|
|
|
|
// and then the bundle will be served like usual by the actual route in server/index.js
|
2019-10-04 18:11:39 +02:00
|
|
|
const handlePageBundleRequest = async (
|
2020-06-01 23:00:22 +02:00
|
|
|
pageBundleRes: ServerResponse,
|
|
|
|
parsedPageBundleUrl: UrlObject
|
2020-05-24 14:38:27 +02:00
|
|
|
): Promise<{ finished?: true }> => {
|
2020-06-01 23:00:22 +02:00
|
|
|
const { pathname } = parsedPageBundleUrl
|
2022-04-27 11:50:29 +02:00
|
|
|
const params = matchNextPageBundleRequest<{ path: string[] }>(pathname)
|
2018-07-25 13:45:42 +02:00
|
|
|
if (!params) {
|
|
|
|
return {}
|
|
|
|
}
|
|
|
|
|
2020-09-24 08:05:40 +02:00
|
|
|
let decodedPagePath: string
|
|
|
|
|
|
|
|
try {
|
|
|
|
decodedPagePath = `/${params.path
|
|
|
|
.map((param) => decodeURIComponent(param))
|
|
|
|
.join('/')}`
|
|
|
|
} catch (_) {
|
2021-07-05 18:31:32 +02:00
|
|
|
throw new DecodeError('failed to decode param')
|
2020-09-24 08:05:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const page = denormalizePagePath(decodedPagePath)
|
|
|
|
|
2019-02-03 15:34:28 +01:00
|
|
|
if (page === '/_error' || BLOCKED_PAGES.indexOf(page) === -1) {
|
2018-07-25 13:45:42 +02:00
|
|
|
try {
|
2022-09-06 19:03:21 +02:00
|
|
|
await this.ensurePage({ page, clientOnly: true })
|
2018-07-25 13:45:42 +02:00
|
|
|
} catch (error) {
|
2022-01-11 21:40:03 +01:00
|
|
|
await renderScriptError(pageBundleRes, getProperError(error))
|
2019-02-19 22:45:07 +01:00
|
|
|
return { finished: true }
|
2018-07-25 13:45:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const errors = await this.getCompilationErrors(page)
|
|
|
|
if (errors.length > 0) {
|
2020-07-19 20:09:41 +02:00
|
|
|
await renderScriptError(pageBundleRes, errors[0], { verbose: false })
|
2019-02-19 22:45:07 +01:00
|
|
|
return { finished: true }
|
2018-07-25 13:45:42 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {}
|
|
|
|
}
|
|
|
|
|
2020-05-24 14:38:27 +02:00
|
|
|
const { finished } = await handlePageBundleRequest(res, parsedUrl)
|
2018-07-25 13:45:42 +02:00
|
|
|
|
2022-08-03 19:23:01 +02:00
|
|
|
for (const fn of this.interceptors) {
|
2021-02-19 11:10:19 +01:00
|
|
|
await new Promise<void>((resolve, reject) => {
|
2019-10-04 18:11:39 +02:00
|
|
|
fn(req, res, (err: Error) => {
|
2017-01-08 03:02:29 +01:00
|
|
|
if (err) return reject(err)
|
2016-11-23 19:32:49 +01:00
|
|
|
resolve()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
2018-07-25 13:45:42 +02:00
|
|
|
|
2019-02-19 22:45:07 +01:00
|
|
|
return { finished }
|
2016-10-17 09:05:46 +02:00
|
|
|
}
|
|
|
|
|
2021-10-15 09:09:54 +02:00
|
|
|
public onHMR(req: IncomingMessage, _res: ServerResponse, head: Buffer) {
|
|
|
|
wsServer.handleUpgrade(req, req.socket, head, (client) => {
|
|
|
|
this.webpackHotMiddleware?.onHMR(client)
|
|
|
|
this.onDemandEntries?.onHMR(client)
|
2022-04-21 10:30:23 +02:00
|
|
|
|
|
|
|
client.addEventListener('message', ({ data }) => {
|
|
|
|
data = typeof data !== 'string' ? data.toString() : data
|
|
|
|
|
|
|
|
try {
|
|
|
|
const payload = JSON.parse(data)
|
|
|
|
|
|
|
|
let traceChild:
|
|
|
|
| {
|
|
|
|
name: string
|
|
|
|
startTime?: bigint
|
|
|
|
endTime?: bigint
|
|
|
|
attrs?: Record<string, number | string>
|
|
|
|
}
|
|
|
|
| undefined
|
|
|
|
|
|
|
|
switch (payload.event) {
|
|
|
|
case 'client-hmr-latency': {
|
|
|
|
traceChild = {
|
|
|
|
name: payload.event,
|
|
|
|
startTime: BigInt(payload.startTime * 1000 * 1000),
|
|
|
|
endTime: BigInt(payload.endTime * 1000 * 1000),
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
case 'client-reload-page':
|
|
|
|
case 'client-success': {
|
|
|
|
traceChild = {
|
|
|
|
name: payload.event,
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
case 'client-error': {
|
|
|
|
traceChild = {
|
|
|
|
name: payload.event,
|
|
|
|
attrs: { errorCount: payload.errorCount },
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
case 'client-warning': {
|
|
|
|
traceChild = {
|
|
|
|
name: payload.event,
|
|
|
|
attrs: { warningCount: payload.warningCount },
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
case 'client-removed-page':
|
|
|
|
case 'client-added-page': {
|
|
|
|
traceChild = {
|
|
|
|
name: payload.event,
|
|
|
|
attrs: { page: payload.page || '' },
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
2022-06-22 17:41:23 +02:00
|
|
|
case 'client-full-reload': {
|
2022-09-09 22:09:15 +02:00
|
|
|
traceChild = {
|
|
|
|
name: payload.event,
|
|
|
|
attrs: { stackTrace: payload.stackTrace ?? '' },
|
|
|
|
}
|
2022-06-22 17:41:23 +02:00
|
|
|
Log.warn(
|
|
|
|
'Fast Refresh had to perform a full reload. Read more: https://nextjs.org/docs/basic-features/fast-refresh#how-it-works'
|
|
|
|
)
|
|
|
|
if (payload.stackTrace) {
|
|
|
|
console.warn(payload.stackTrace)
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
2022-04-21 10:30:23 +02:00
|
|
|
default: {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (traceChild) {
|
|
|
|
this.hotReloaderSpan.manualTraceChild(
|
|
|
|
traceChild.name,
|
|
|
|
traceChild.startTime || process.hrtime.bigint(),
|
|
|
|
traceChild.endTime || process.hrtime.bigint(),
|
|
|
|
{ ...traceChild.attrs, clientId: payload.id }
|
|
|
|
)
|
|
|
|
}
|
|
|
|
} catch (_) {
|
|
|
|
// invalid WebSocket message
|
|
|
|
}
|
|
|
|
})
|
2021-10-15 09:09:54 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-09-21 09:49:53 +02:00
|
|
|
private async clean(span: Span): Promise<void> {
|
|
|
|
return span
|
|
|
|
.traceChild('clean')
|
|
|
|
.traceAsyncFn(() =>
|
|
|
|
recursiveDelete(join(this.dir, this.config.distDir), /^cache/)
|
|
|
|
)
|
2018-02-14 16:17:41 +01:00
|
|
|
}
|
|
|
|
|
2021-09-21 09:49:53 +02:00
|
|
|
private async getWebpackConfig(span: Span) {
|
|
|
|
const webpackConfigSpan = span.traceChild('get-webpack-config')
|
|
|
|
|
2022-09-18 02:00:16 +02:00
|
|
|
const pageExtensions = this.config.pageExtensions
|
2022-06-03 20:47:16 +02:00
|
|
|
|
2021-09-21 09:49:53 +02:00
|
|
|
return webpackConfigSpan.traceAsyncFn(async () => {
|
2022-09-03 02:13:47 +02:00
|
|
|
const pagePaths = !this.pagesDir
|
|
|
|
? ([] as (string | null)[])
|
|
|
|
: await webpackConfigSpan
|
|
|
|
.traceChild('get-page-paths')
|
|
|
|
.traceAsyncFn(() =>
|
|
|
|
Promise.all([
|
2022-09-18 02:00:16 +02:00
|
|
|
findPageFile(this.pagesDir!, '/_app', pageExtensions, false),
|
2022-09-03 02:13:47 +02:00
|
|
|
findPageFile(
|
|
|
|
this.pagesDir!,
|
|
|
|
'/_document',
|
2022-09-18 02:00:16 +02:00
|
|
|
pageExtensions,
|
2022-09-03 02:13:47 +02:00
|
|
|
false
|
|
|
|
),
|
|
|
|
])
|
|
|
|
)
|
2021-09-21 09:49:53 +02:00
|
|
|
|
2021-11-04 20:10:07 +01:00
|
|
|
this.pagesMapping = webpackConfigSpan
|
2021-09-21 09:49:53 +02:00
|
|
|
.traceChild('create-pages-mapping')
|
|
|
|
.traceFn(() =>
|
2022-04-27 11:50:29 +02:00
|
|
|
createPagesMapping({
|
|
|
|
isDev: true,
|
|
|
|
pageExtensions: this.config.pageExtensions,
|
2022-05-19 17:46:21 +02:00
|
|
|
pagesType: 'pages',
|
2022-04-27 11:50:29 +02:00
|
|
|
pagePaths: pagePaths.filter(
|
2022-09-03 02:13:47 +02:00
|
|
|
(i: string | null): i is string => typeof i === 'string'
|
2022-04-27 11:50:29 +02:00
|
|
|
),
|
2022-09-03 02:13:47 +02:00
|
|
|
pagesDir: this.pagesDir,
|
2022-04-27 11:50:29 +02:00
|
|
|
})
|
2021-09-21 09:49:53 +02:00
|
|
|
)
|
2021-11-04 20:10:07 +01:00
|
|
|
|
2022-03-08 21:55:14 +01:00
|
|
|
const entrypoints = await webpackConfigSpan
|
2021-09-21 09:49:53 +02:00
|
|
|
.traceChild('create-entrypoints')
|
2022-03-08 21:55:14 +01:00
|
|
|
.traceAsyncFn(() =>
|
2022-04-27 11:50:29 +02:00
|
|
|
createEntrypoints({
|
2022-09-18 02:00:16 +02:00
|
|
|
appDir: this.appDir,
|
2022-04-27 11:50:29 +02:00
|
|
|
buildId: this.buildId,
|
|
|
|
config: this.config,
|
|
|
|
envFiles: [],
|
|
|
|
isDev: true,
|
|
|
|
pages: this.pagesMapping,
|
|
|
|
pagesDir: this.pagesDir,
|
|
|
|
previewMode: this.previewProps,
|
2022-05-19 17:46:21 +02:00
|
|
|
rootDir: this.dir,
|
2022-04-27 11:50:29 +02:00
|
|
|
target: 'server',
|
2022-05-07 15:37:14 +02:00
|
|
|
pageExtensions: this.config.pageExtensions,
|
2022-04-27 11:50:29 +02:00
|
|
|
})
|
2021-09-21 09:49:53 +02:00
|
|
|
)
|
|
|
|
|
2022-04-27 11:50:29 +02:00
|
|
|
const commonWebpackOptions = {
|
|
|
|
dev: true,
|
|
|
|
buildId: this.buildId,
|
|
|
|
config: this.config,
|
|
|
|
hasReactRoot: this.hasReactRoot,
|
|
|
|
pagesDir: this.pagesDir,
|
|
|
|
rewrites: this.rewrites,
|
|
|
|
runWebpackSpan: this.hotReloaderSpan,
|
2022-05-25 11:46:26 +02:00
|
|
|
appDir: this.appDir,
|
2022-04-27 11:50:29 +02:00
|
|
|
}
|
|
|
|
|
2021-09-21 09:49:53 +02:00
|
|
|
return webpackConfigSpan
|
|
|
|
.traceChild('generate-webpack-config')
|
|
|
|
.traceAsyncFn(() =>
|
2022-04-27 11:50:29 +02:00
|
|
|
Promise.all([
|
2022-08-13 18:55:55 +02:00
|
|
|
// order is important here
|
2022-04-27 11:50:29 +02:00
|
|
|
getBaseWebpackConfig(this.dir, {
|
|
|
|
...commonWebpackOptions,
|
2022-08-12 15:01:19 +02:00
|
|
|
compilerType: COMPILER_NAMES.client,
|
2022-04-27 11:50:29 +02:00
|
|
|
entrypoints: entrypoints.client,
|
|
|
|
}),
|
|
|
|
getBaseWebpackConfig(this.dir, {
|
|
|
|
...commonWebpackOptions,
|
2022-08-12 15:01:19 +02:00
|
|
|
compilerType: COMPILER_NAMES.server,
|
2022-04-27 11:50:29 +02:00
|
|
|
entrypoints: entrypoints.server,
|
|
|
|
}),
|
|
|
|
getBaseWebpackConfig(this.dir, {
|
|
|
|
...commonWebpackOptions,
|
2022-08-12 15:01:19 +02:00
|
|
|
compilerType: COMPILER_NAMES.edgeServer,
|
2022-04-27 11:50:29 +02:00
|
|
|
entrypoints: entrypoints.edgeServer,
|
|
|
|
}),
|
|
|
|
])
|
2021-09-21 09:49:53 +02:00
|
|
|
)
|
|
|
|
})
|
2019-01-08 23:10:32 +01:00
|
|
|
}
|
|
|
|
|
2021-04-22 13:08:47 +02:00
|
|
|
public async buildFallbackError(): Promise<void> {
|
|
|
|
if (this.fallbackWatcher) return
|
|
|
|
|
|
|
|
const fallbackConfig = await getBaseWebpackConfig(this.dir, {
|
2021-08-25 10:47:16 +02:00
|
|
|
runWebpackSpan: this.hotReloaderSpan,
|
2021-04-22 13:08:47 +02:00
|
|
|
dev: true,
|
2022-08-12 15:01:19 +02:00
|
|
|
compilerType: COMPILER_NAMES.client,
|
2021-04-22 13:08:47 +02:00
|
|
|
config: this.config,
|
|
|
|
buildId: this.buildId,
|
|
|
|
pagesDir: this.pagesDir,
|
|
|
|
rewrites: {
|
|
|
|
beforeFiles: [],
|
|
|
|
afterFiles: [],
|
|
|
|
fallback: [],
|
|
|
|
},
|
|
|
|
isDevFallback: true,
|
2022-03-08 21:55:14 +01:00
|
|
|
entrypoints: (
|
2022-04-27 11:50:29 +02:00
|
|
|
await createEntrypoints({
|
2022-09-18 02:00:16 +02:00
|
|
|
appDir: this.appDir,
|
2022-04-27 11:50:29 +02:00
|
|
|
buildId: this.buildId,
|
|
|
|
config: this.config,
|
|
|
|
envFiles: [],
|
|
|
|
isDev: true,
|
|
|
|
pages: {
|
2022-03-08 21:55:14 +01:00
|
|
|
'/_app': 'next/dist/pages/_app',
|
|
|
|
'/_error': 'next/dist/pages/_error',
|
|
|
|
},
|
2022-04-27 11:50:29 +02:00
|
|
|
pagesDir: this.pagesDir,
|
|
|
|
previewMode: this.previewProps,
|
2022-05-19 17:46:21 +02:00
|
|
|
rootDir: this.dir,
|
2022-04-27 11:50:29 +02:00
|
|
|
target: 'server',
|
2022-05-07 15:37:14 +02:00
|
|
|
pageExtensions: this.config.pageExtensions,
|
2022-04-27 11:50:29 +02:00
|
|
|
})
|
2021-04-22 13:08:47 +02:00
|
|
|
).client,
|
2022-03-26 00:05:35 +01:00
|
|
|
hasReactRoot: this.hasReactRoot,
|
2021-04-22 13:08:47 +02:00
|
|
|
})
|
|
|
|
const fallbackCompiler = webpack(fallbackConfig)
|
|
|
|
|
|
|
|
this.fallbackWatcher = await new Promise((resolve) => {
|
|
|
|
let bootedFallbackCompiler = false
|
|
|
|
fallbackCompiler.watch(
|
|
|
|
// @ts-ignore webpack supports an array of watchOptions when using a multiCompiler
|
|
|
|
fallbackConfig.watchOptions,
|
|
|
|
// Errors are handled separately
|
|
|
|
(_err: any) => {
|
|
|
|
if (!bootedFallbackCompiler) {
|
|
|
|
bootedFallbackCompiler = true
|
|
|
|
resolve(true)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-08-13 18:55:55 +02:00
|
|
|
public async start(initial?: boolean): Promise<void> {
|
2021-09-21 09:49:53 +02:00
|
|
|
const startSpan = this.hotReloaderSpan.traceChild('start')
|
|
|
|
startSpan.stop() // Stop immediately to create an artificial parent span
|
|
|
|
|
2022-08-13 18:55:55 +02:00
|
|
|
if (initial) {
|
|
|
|
await this.clean(startSpan)
|
|
|
|
// Ensure distDir exists before writing package.json
|
|
|
|
await fs.mkdir(this.distDir, { recursive: true })
|
2022-02-25 13:40:24 +01:00
|
|
|
|
2022-08-13 18:55:55 +02:00
|
|
|
const distPackageJsonPath = join(this.distDir, 'package.json')
|
|
|
|
// Ensure commonjs handling is used for files in the distDir (generally .next)
|
|
|
|
// Files outside of the distDir can be "type": "module"
|
|
|
|
await fs.writeFile(distPackageJsonPath, '{"type": "commonjs"}')
|
|
|
|
}
|
|
|
|
this.activeConfigs = await this.getWebpackConfig(startSpan)
|
2016-12-16 21:33:08 +01:00
|
|
|
|
2022-08-13 18:55:55 +02:00
|
|
|
for (const config of this.activeConfigs) {
|
2020-06-26 06:26:09 +02:00
|
|
|
const defaultEntry = config.entry
|
|
|
|
config.entry = async (...args) => {
|
2021-10-26 18:50:56 +02:00
|
|
|
// @ts-ignore entry is always a function
|
2020-06-26 06:26:09 +02:00
|
|
|
const entrypoints = await defaultEntry(...args)
|
2022-08-12 15:01:19 +02:00
|
|
|
const isClientCompilation = config.name === COMPILER_NAMES.client
|
|
|
|
const isNodeServerCompilation = config.name === COMPILER_NAMES.server
|
|
|
|
const isEdgeServerCompilation =
|
|
|
|
config.name === COMPILER_NAMES.edgeServer
|
2017-06-07 00:32:02 +02:00
|
|
|
|
2020-06-26 06:26:09 +02:00
|
|
|
await Promise.all(
|
2022-08-12 15:01:19 +02:00
|
|
|
Object.keys(entries).map(async (entryKey) => {
|
|
|
|
const entryData = entries[entryKey]
|
|
|
|
const { bundlePath, dispose } = entryData
|
2022-05-13 19:48:53 +02:00
|
|
|
|
2022-08-12 15:01:19 +02:00
|
|
|
const result = /^(client|server|edge-server)(.*)/g.exec(entryKey)
|
2022-04-27 11:50:29 +02:00
|
|
|
const [, key, page] = result! // this match should always happen
|
2022-08-12 15:01:19 +02:00
|
|
|
if (key === COMPILER_NAMES.client && !isClientCompilation) return
|
|
|
|
if (key === COMPILER_NAMES.server && !isNodeServerCompilation)
|
|
|
|
return
|
|
|
|
if (key === COMPILER_NAMES.edgeServer && !isEdgeServerCompilation)
|
|
|
|
return
|
|
|
|
|
|
|
|
const isEntry = entryData.type === EntryTypes.ENTRY
|
|
|
|
const isChildEntry = entryData.type === EntryTypes.CHILD_ENTRY
|
2022-04-27 11:50:29 +02:00
|
|
|
|
|
|
|
// Check if the page was removed or disposed and remove it
|
2022-08-12 15:01:19 +02:00
|
|
|
if (isEntry) {
|
|
|
|
const pageExists =
|
|
|
|
!dispose && (await fileExists(entryData.absolutePagePath))
|
|
|
|
if (!pageExists) {
|
|
|
|
delete entries[entryKey]
|
|
|
|
return
|
|
|
|
}
|
2020-06-26 06:26:09 +02:00
|
|
|
}
|
2016-10-17 09:05:46 +02:00
|
|
|
|
2022-09-18 02:00:16 +02:00
|
|
|
const isAppPath = !!this.appDir && bundlePath.startsWith('app/')
|
2022-08-12 15:01:19 +02:00
|
|
|
const staticInfo = isEntry
|
|
|
|
? await getPageStaticInfo({
|
|
|
|
pageFilePath: entryData.absolutePagePath,
|
|
|
|
nextConfig: this.config,
|
2022-09-07 22:12:13 +02:00
|
|
|
isDev: true,
|
2022-08-12 15:01:19 +02:00
|
|
|
})
|
|
|
|
: {}
|
2022-09-18 02:00:16 +02:00
|
|
|
const isServerComponent =
|
|
|
|
isAppPath && staticInfo.rsc !== RSC_MODULE_TYPES.client
|
2022-05-20 14:24:00 +02:00
|
|
|
|
2022-08-12 15:01:19 +02:00
|
|
|
await runDependingOnPageType({
|
2020-06-26 06:26:09 +02:00
|
|
|
page,
|
2022-05-20 14:24:00 +02:00
|
|
|
pageRuntime: staticInfo.runtime,
|
2022-04-27 11:50:29 +02:00
|
|
|
onEdgeServer: () => {
|
2022-08-12 15:01:19 +02:00
|
|
|
// TODO-APP: verify if child entry should support.
|
|
|
|
if (!isEdgeServerCompilation || !isEntry) return
|
2022-08-24 21:49:47 +02:00
|
|
|
const appDirLoader =
|
2022-09-18 02:00:16 +02:00
|
|
|
isAppPath && this.appDir
|
2022-08-24 21:49:47 +02:00
|
|
|
? getAppEntry({
|
|
|
|
name: bundlePath,
|
2022-09-06 19:03:21 +02:00
|
|
|
appPaths: entryData.appPaths,
|
2022-08-24 21:49:47 +02:00
|
|
|
pagePath: posix.join(
|
|
|
|
APP_DIR_ALIAS,
|
|
|
|
relative(
|
|
|
|
this.appDir!,
|
|
|
|
entryData.absolutePagePath
|
|
|
|
).replace(/\\/g, '/')
|
|
|
|
),
|
|
|
|
appDir: this.appDir!,
|
|
|
|
pageExtensions: this.config.pageExtensions,
|
|
|
|
}).import
|
|
|
|
: undefined
|
|
|
|
|
2022-08-12 15:01:19 +02:00
|
|
|
entries[entryKey].status = BUILDING
|
2022-05-13 19:48:53 +02:00
|
|
|
entrypoints[bundlePath] = finalizeEntrypoint({
|
2022-08-12 15:01:19 +02:00
|
|
|
compilerType: COMPILER_NAMES.edgeServer,
|
2022-05-13 19:48:53 +02:00
|
|
|
name: bundlePath,
|
|
|
|
value: getEdgeServerEntry({
|
2022-08-12 15:01:19 +02:00
|
|
|
absolutePagePath: entryData.absolutePagePath,
|
feat(edge): allows configuring Dynamic code execution guard (#39539)
### 📖 What's in there?
Dynamic code evaluation (`eval()`, `new Function()`, ...) is not
supported on the edge runtime, hence why we fail the build when
detecting such statement in the middleware or `experimental-edge` routes
at build time.
However, there could be false positives, which static analysis and
tree-shaking can not exclude:
- `qs` through these dependencies (get-intrinsic:
[source](https://github.com/ljharb/get-intrinsic/blob/main/index.js#L12))
- `function-bind`
([source](https://github.com/Raynos/function-bind/blob/master/implementation.js#L42))
- `has`
([source](https://github.com/tarruda/has/blob/master/src/index.js#L5))
This PR leverages the existing `config` export to let user allow some of
their files.
it’s meant for allowing users to import 3rd party modules who embed
dynamic code evaluation, but do not use it (because or code paths), and
can't be tree-shaked.
By default, it’s keeping the existing behavior: warn in dev, fails to
build.
If users allow dynamic code, and that code is reached at runtime, their
app stills breaks.
### 🧪 How to test?
- (existing) integration tests for disallowing dynamic code evaluation:
`pnpm testheadless --testPathPattern=runtime-dynamic`
- (new) integration tests for allowing dynamic code evaluation: `pnpm
testheadless --testPathPattern=runtime-configurable`
- (amended) production tests for validating the new configuration keys:
`pnpm testheadless --testPathPattern=config-validations`
To try it live, you could have an application such as:
```js
// lib/index.js
/* eslint-disable no-eval */
export function hasUnusedDynamic() {
if ((() => false)()) {
eval('100')
}
}
export function hasDynamic() {
eval('100')
}
// pages/index.jsx
export default function Page({ edgeRoute }) {
return <p>{edgeRoute}</p>
}
export const getServerSideProps = async (req) => {
const res = await fetch(`http://localhost:3000/api/route`)
const data = await res.json()
return { props: { edgeRoute: data.ok ? `Hi from the edge route` : '' } }
}
// pages/api/route.js
import { hasDynamic } from '../../lib'
export default async function handle() {
hasDynamic()
return Response.json({ ok: true })
}
export const config = {
runtime: 'experimental-edge' ,
allowDynamic: '/lib/**'
}
```
Playing with `config.allowDynamic`, you should be able to:
- build the app even if it uses `eval()` (it will obviously fail at
runtime)
- build the app that _imports but does not use_ `eval()`
- run the app in dev, even if it uses `eval()` with no warning
### 🆙 Notes to reviewers
Before adding documentation and telemetry, I'd like to collect comments
on a couple of points:
- the overall design for this feature: is a list of globs useful and
easy enough?
- should the globs be relative to the application root (current
implementation) to to the edge route/middleware file?
- (especially to @sokra) is the implementation idiomatic enough? I've
leverage loaders to read the _entry point_ configuration once, then the
ModuleGraph to get it back during the parsing phase. I couldn't re-use
the existing `getExtractMetadata()` facility since it's happening late
after the parsing.
- there's a glitch with `import { ServerRuntime } from '../../types'` in
`get-page-static-info.ts`
([here](https://github.com/vercel/next.js/pull/39539/files#diff-cb7ac6392c3dd707c5edab159c3144ec114eafea92dad5d98f4eedfc612174d2L12)).
I had to use `next/types` because it was failing during lint. Any clue
why?
### ☑️ Checklist
- [ ] Implements an existing feature request or RFC. Make sure the
feature request has been accepted for implementation before opening a
PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [x] Documentation added
- [x] Telemetry added. In case of a feature if it's used or not.
- [x] Errors have helpful link attached, see `contributing.md`
2022-09-13 00:01:00 +02:00
|
|
|
rootDir: this.dir,
|
2022-05-13 19:48:53 +02:00
|
|
|
buildId: this.buildId,
|
|
|
|
bundlePath,
|
|
|
|
config: this.config,
|
|
|
|
isDev: true,
|
|
|
|
page,
|
|
|
|
pages: this.pagesMapping,
|
|
|
|
isServerComponent,
|
2022-08-24 21:49:47 +02:00
|
|
|
appDirLoader,
|
2022-09-18 02:00:16 +02:00
|
|
|
pagesType: isAppPath ? 'app' : undefined,
|
2022-05-13 19:48:53 +02:00
|
|
|
}),
|
2022-06-01 13:52:57 +02:00
|
|
|
appDir: this.config.experimental.appDir,
|
2022-05-13 19:48:53 +02:00
|
|
|
})
|
|
|
|
},
|
|
|
|
onClient: () => {
|
|
|
|
if (!isClientCompilation) return
|
2022-08-12 15:01:19 +02:00
|
|
|
if (isChildEntry) {
|
|
|
|
entries[entryKey].status = BUILDING
|
2022-04-27 11:50:29 +02:00
|
|
|
entrypoints[bundlePath] = finalizeEntrypoint({
|
|
|
|
name: bundlePath,
|
2022-08-12 15:01:19 +02:00
|
|
|
compilerType: COMPILER_NAMES.client,
|
|
|
|
value: entryData.request,
|
2022-06-01 13:52:57 +02:00
|
|
|
appDir: this.config.experimental.appDir,
|
2022-04-27 11:50:29 +02:00
|
|
|
})
|
2022-05-13 19:48:53 +02:00
|
|
|
} else {
|
2022-08-12 15:01:19 +02:00
|
|
|
entries[entryKey].status = BUILDING
|
2022-04-27 11:50:29 +02:00
|
|
|
entrypoints[bundlePath] = finalizeEntrypoint({
|
|
|
|
name: bundlePath,
|
2022-08-12 15:01:19 +02:00
|
|
|
compilerType: COMPILER_NAMES.client,
|
2022-04-27 11:50:29 +02:00
|
|
|
value: getClientEntry({
|
2022-08-12 15:01:19 +02:00
|
|
|
absolutePagePath: entryData.absolutePagePath,
|
2022-04-27 11:50:29 +02:00
|
|
|
page,
|
|
|
|
}),
|
2022-06-01 13:52:57 +02:00
|
|
|
appDir: this.config.experimental.appDir,
|
2022-04-27 11:50:29 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
},
|
|
|
|
onServer: () => {
|
2022-08-12 15:01:19 +02:00
|
|
|
// TODO-APP: verify if child entry should support.
|
|
|
|
if (!isNodeServerCompilation || !isEntry) return
|
|
|
|
entries[entryKey].status = BUILDING
|
|
|
|
let relativeRequest = relative(
|
|
|
|
config.context!,
|
|
|
|
entryData.absolutePagePath
|
|
|
|
)
|
|
|
|
if (
|
|
|
|
!isAbsolute(relativeRequest) &&
|
|
|
|
!relativeRequest.startsWith('../')
|
|
|
|
) {
|
|
|
|
relativeRequest = `./${relativeRequest}`
|
2022-04-27 11:50:29 +02:00
|
|
|
}
|
2022-05-13 19:48:53 +02:00
|
|
|
|
|
|
|
entrypoints[bundlePath] = finalizeEntrypoint({
|
|
|
|
compilerType: 'server',
|
|
|
|
name: bundlePath,
|
|
|
|
isServerComponent,
|
|
|
|
value:
|
2022-05-25 11:46:26 +02:00
|
|
|
this.appDir && bundlePath.startsWith('app/')
|
|
|
|
? getAppEntry({
|
2022-05-13 19:48:53 +02:00
|
|
|
name: bundlePath,
|
2022-09-06 19:03:21 +02:00
|
|
|
appPaths: entryData.appPaths,
|
2022-08-17 19:14:03 +02:00
|
|
|
pagePath: posix.join(
|
2022-05-25 11:46:26 +02:00
|
|
|
APP_DIR_ALIAS,
|
2022-08-17 19:14:03 +02:00
|
|
|
relative(
|
|
|
|
this.appDir!,
|
|
|
|
entryData.absolutePagePath
|
|
|
|
).replace(/\\/g, '/')
|
2022-05-13 19:48:53 +02:00
|
|
|
),
|
2022-05-25 11:46:26 +02:00
|
|
|
appDir: this.appDir!,
|
2022-05-13 19:48:53 +02:00
|
|
|
pageExtensions: this.config.pageExtensions,
|
|
|
|
})
|
2022-08-12 15:01:19 +02:00
|
|
|
: relativeRequest,
|
2022-06-01 13:52:57 +02:00
|
|
|
appDir: this.config.experimental.appDir,
|
2022-05-13 19:48:53 +02:00
|
|
|
})
|
2022-04-27 11:50:29 +02:00
|
|
|
},
|
|
|
|
})
|
2020-06-26 06:26:09 +02:00
|
|
|
})
|
|
|
|
)
|
|
|
|
|
|
|
|
return entrypoints
|
|
|
|
}
|
2016-12-17 05:04:40 +01:00
|
|
|
}
|
|
|
|
|
2021-09-30 15:52:26 +02:00
|
|
|
// Enable building of client compilation before server compilation in development
|
|
|
|
// @ts-ignore webpack 5
|
2022-08-13 18:55:55 +02:00
|
|
|
this.activeConfigs.parallelism = 1
|
2021-09-30 15:52:26 +02:00
|
|
|
|
2022-08-13 18:55:55 +02:00
|
|
|
this.multiCompiler = webpack(
|
|
|
|
this.activeConfigs
|
2022-08-16 11:55:37 +02:00
|
|
|
) as unknown as webpack.MultiCompiler
|
2017-06-07 00:32:02 +02:00
|
|
|
|
2021-10-26 18:50:56 +02:00
|
|
|
watchCompilers(
|
2022-08-13 18:55:55 +02:00
|
|
|
this.multiCompiler.compilers[0],
|
|
|
|
this.multiCompiler.compilers[1],
|
|
|
|
this.multiCompiler.compilers[2]
|
2021-10-26 18:50:56 +02:00
|
|
|
)
|
2019-05-08 03:11:56 +02:00
|
|
|
|
2020-09-02 18:57:21 +02:00
|
|
|
// Watch for changes to client/server page files so we can tell when just
|
|
|
|
// the server file changes and trigger a reload for GS(S)P pages
|
|
|
|
const changedClientPages = new Set<string>()
|
|
|
|
const changedServerPages = new Set<string>()
|
2022-04-27 11:50:29 +02:00
|
|
|
const changedEdgeServerPages = new Set<string>()
|
2022-08-25 18:40:16 +02:00
|
|
|
const changedCSSImportPages = new Set<string>()
|
|
|
|
|
2020-09-02 18:57:21 +02:00
|
|
|
const prevClientPageHashes = new Map<string, string>()
|
|
|
|
const prevServerPageHashes = new Map<string, string>()
|
2022-04-27 11:50:29 +02:00
|
|
|
const prevEdgeServerPageHashes = new Map<string, string>()
|
2022-08-25 18:40:16 +02:00
|
|
|
const prevCSSImportModuleHashes = new Map<string, string>()
|
2020-09-02 18:57:21 +02:00
|
|
|
|
2021-08-17 09:18:08 +02:00
|
|
|
const trackPageChanges =
|
|
|
|
(pageHashMap: Map<string, string>, changedItems: Set<string>) =>
|
2022-08-16 11:55:37 +02:00
|
|
|
(stats: webpack.Compilation) => {
|
2022-02-16 23:32:24 +01:00
|
|
|
try {
|
|
|
|
stats.entrypoints.forEach((entry, key) => {
|
2022-07-11 14:02:46 +02:00
|
|
|
if (
|
|
|
|
key.startsWith('pages/') ||
|
2022-08-12 15:01:19 +02:00
|
|
|
key.startsWith('app/') ||
|
2022-07-11 14:02:46 +02:00
|
|
|
isMiddlewareFilename(key)
|
|
|
|
) {
|
2022-02-16 23:32:24 +01:00
|
|
|
// TODO this doesn't handle on demand loaded chunks
|
|
|
|
entry.chunks.forEach((chunk) => {
|
|
|
|
if (chunk.id === key) {
|
|
|
|
const modsIterable: any =
|
|
|
|
stats.chunkGraph.getChunkModulesIterable(chunk)
|
|
|
|
|
2022-08-25 18:40:16 +02:00
|
|
|
let hasCSSModuleChanges = false
|
2022-02-16 23:32:24 +01:00
|
|
|
let chunksHash = new StringXor()
|
|
|
|
|
|
|
|
modsIterable.forEach((mod: any) => {
|
|
|
|
if (
|
|
|
|
mod.resource &&
|
|
|
|
mod.resource.replace(/\\/g, '/').includes(key)
|
|
|
|
) {
|
|
|
|
// use original source to calculate hash since mod.hash
|
|
|
|
// includes the source map in development which changes
|
|
|
|
// every time for both server and client so we calculate
|
|
|
|
// the hash without the source map for the page module
|
|
|
|
const hash = require('crypto')
|
|
|
|
.createHash('sha256')
|
|
|
|
.update(mod.originalSource().buffer())
|
|
|
|
.digest()
|
|
|
|
.toString('hex')
|
|
|
|
|
|
|
|
chunksHash.add(hash)
|
|
|
|
} else {
|
|
|
|
// for non-pages we can use the module hash directly
|
|
|
|
const hash = stats.chunkGraph.getModuleHash(
|
|
|
|
mod,
|
|
|
|
chunk.runtime
|
|
|
|
)
|
|
|
|
chunksHash.add(hash)
|
2022-08-25 18:40:16 +02:00
|
|
|
|
|
|
|
// Both CSS import changes from server and client
|
|
|
|
// components are tracked.
|
|
|
|
if (
|
|
|
|
key.startsWith('app/') &&
|
|
|
|
mod.resource?.endsWith('.css')
|
|
|
|
) {
|
|
|
|
const prevHash = prevCSSImportModuleHashes.get(
|
|
|
|
mod.resource
|
|
|
|
)
|
|
|
|
if (prevHash && prevHash !== hash) {
|
|
|
|
hasCSSModuleChanges = true
|
|
|
|
}
|
|
|
|
prevCSSImportModuleHashes.set(mod.resource, hash)
|
|
|
|
}
|
2022-02-16 23:32:24 +01:00
|
|
|
}
|
|
|
|
})
|
|
|
|
const prevHash = pageHashMap.get(key)
|
|
|
|
const curHash = chunksHash.toString()
|
|
|
|
|
|
|
|
if (prevHash && prevHash !== curHash) {
|
|
|
|
changedItems.add(key)
|
|
|
|
}
|
|
|
|
pageHashMap.set(key, curHash)
|
2022-08-25 18:40:16 +02:00
|
|
|
|
|
|
|
if (hasCSSModuleChanges) {
|
|
|
|
changedCSSImportPages.add(key)
|
|
|
|
}
|
2021-08-17 09:18:08 +02:00
|
|
|
}
|
2022-02-16 23:32:24 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
} catch (err) {
|
|
|
|
console.error(err)
|
|
|
|
}
|
2021-08-17 09:18:08 +02:00
|
|
|
}
|
2020-09-02 18:57:21 +02:00
|
|
|
|
2022-08-13 18:55:55 +02:00
|
|
|
this.multiCompiler.compilers[0].hooks.emit.tap(
|
2020-09-02 18:57:21 +02:00
|
|
|
'NextjsHotReloaderForClient',
|
|
|
|
trackPageChanges(prevClientPageHashes, changedClientPages)
|
|
|
|
)
|
2022-08-13 18:55:55 +02:00
|
|
|
this.multiCompiler.compilers[1].hooks.emit.tap(
|
2020-09-02 18:57:21 +02:00
|
|
|
'NextjsHotReloaderForServer',
|
|
|
|
trackPageChanges(prevServerPageHashes, changedServerPages)
|
|
|
|
)
|
2022-08-13 18:55:55 +02:00
|
|
|
this.multiCompiler.compilers[2].hooks.emit.tap(
|
2022-04-27 11:50:29 +02:00
|
|
|
'NextjsHotReloaderForServer',
|
|
|
|
trackPageChanges(prevEdgeServerPageHashes, changedEdgeServerPages)
|
|
|
|
)
|
2020-09-02 18:57:21 +02:00
|
|
|
|
2018-07-24 11:24:40 +02:00
|
|
|
// This plugin watches for changes to _document.js and notifies the client side that it should reload the page
|
2022-08-13 18:55:55 +02:00
|
|
|
this.multiCompiler.compilers[1].hooks.failed.tap(
|
2020-07-30 05:44:25 +02:00
|
|
|
'NextjsHotReloaderForServer',
|
|
|
|
(err: Error) => {
|
|
|
|
this.serverError = err
|
|
|
|
this.serverStats = null
|
|
|
|
}
|
|
|
|
)
|
2022-04-27 11:50:29 +02:00
|
|
|
|
2022-08-13 18:55:55 +02:00
|
|
|
this.multiCompiler.compilers[2].hooks.done.tap(
|
2022-04-27 11:50:29 +02:00
|
|
|
'NextjsHotReloaderForServer',
|
|
|
|
(stats) => {
|
|
|
|
this.serverError = null
|
|
|
|
this.edgeServerStats = stats
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2022-08-13 18:55:55 +02:00
|
|
|
this.multiCompiler.compilers[1].hooks.done.tap(
|
2019-05-29 13:57:26 +02:00
|
|
|
'NextjsHotReloaderForServer',
|
2020-05-18 21:24:37 +02:00
|
|
|
(stats) => {
|
2020-07-30 05:44:25 +02:00
|
|
|
this.serverError = null
|
2020-05-20 07:00:50 +02:00
|
|
|
this.serverStats = stats
|
2016-10-24 09:22:15 +02:00
|
|
|
|
2022-09-03 02:13:47 +02:00
|
|
|
if (!this.pagesDir) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-05-29 13:57:26 +02:00
|
|
|
const { compilation } = stats
|
2018-07-24 11:24:40 +02:00
|
|
|
|
2019-05-29 13:57:26 +02:00
|
|
|
// We only watch `_document` for changes on the server compilation
|
|
|
|
// the rest of the files will be triggered by the client compilation
|
2020-08-03 14:26:23 +02:00
|
|
|
const documentChunk = compilation.namedChunks.get('pages/_document')
|
2019-05-29 13:57:26 +02:00
|
|
|
// If the document chunk can't be found we do nothing
|
|
|
|
if (!documentChunk) {
|
|
|
|
console.warn('_document.js chunk not found')
|
|
|
|
return
|
|
|
|
}
|
2018-07-24 11:24:40 +02:00
|
|
|
|
2019-05-29 13:57:26 +02:00
|
|
|
// Initial value
|
|
|
|
if (this.serverPrevDocumentHash === null) {
|
2021-10-24 23:04:26 +02:00
|
|
|
this.serverPrevDocumentHash = documentChunk.hash || null
|
2019-05-29 13:57:26 +02:00
|
|
|
return
|
|
|
|
}
|
2018-07-24 11:24:40 +02:00
|
|
|
|
2019-05-29 13:57:26 +02:00
|
|
|
// If _document.js didn't change we don't trigger a reload
|
|
|
|
if (documentChunk.hash === this.serverPrevDocumentHash) {
|
|
|
|
return
|
|
|
|
}
|
2018-07-24 11:24:40 +02:00
|
|
|
|
2019-05-29 13:57:26 +02:00
|
|
|
// Notify reload to reload the page, as _document.js was changed (different hash)
|
|
|
|
this.send('reloadPage')
|
2021-10-24 23:04:26 +02:00
|
|
|
this.serverPrevDocumentHash = documentChunk.hash || null
|
2019-05-29 13:57:26 +02:00
|
|
|
}
|
|
|
|
)
|
2022-08-13 18:55:55 +02:00
|
|
|
this.multiCompiler.hooks.done.tap('NextjsHotReloaderForServer', () => {
|
2021-10-09 11:50:51 +02:00
|
|
|
const serverOnlyChanges = difference<string>(
|
|
|
|
changedServerPages,
|
|
|
|
changedClientPages
|
|
|
|
)
|
2022-08-25 18:40:16 +02:00
|
|
|
const serverComponentChanges = serverOnlyChanges
|
|
|
|
.filter((key) => key.startsWith('app/'))
|
|
|
|
.concat(Array.from(changedCSSImportPages))
|
2022-07-11 14:02:46 +02:00
|
|
|
const pageChanges = serverOnlyChanges.filter((key) =>
|
|
|
|
key.startsWith('pages/')
|
|
|
|
)
|
2022-04-27 11:50:29 +02:00
|
|
|
const middlewareChanges = Array.from(changedEdgeServerPages).filter(
|
2022-06-08 16:10:05 +02:00
|
|
|
(name) => isMiddlewareFilename(name)
|
2021-10-20 19:52:11 +02:00
|
|
|
)
|
2022-08-25 18:40:16 +02:00
|
|
|
|
2021-10-09 11:50:51 +02:00
|
|
|
changedClientPages.clear()
|
|
|
|
changedServerPages.clear()
|
2022-04-27 11:50:29 +02:00
|
|
|
changedEdgeServerPages.clear()
|
2022-08-25 18:40:16 +02:00
|
|
|
changedCSSImportPages.clear()
|
2021-10-09 11:50:51 +02:00
|
|
|
|
2021-10-20 19:52:11 +02:00
|
|
|
if (middlewareChanges.length > 0) {
|
|
|
|
this.send({
|
|
|
|
event: 'middlewareChanges',
|
|
|
|
})
|
|
|
|
}
|
2022-07-11 14:02:46 +02:00
|
|
|
|
|
|
|
if (pageChanges.length > 0) {
|
2021-10-09 11:50:51 +02:00
|
|
|
this.send({
|
|
|
|
event: 'serverOnlyChanges',
|
|
|
|
pages: serverOnlyChanges.map((pg) =>
|
2022-03-24 22:49:38 +01:00
|
|
|
denormalizePagePath(pg.slice('pages'.length))
|
2021-10-09 11:50:51 +02:00
|
|
|
),
|
|
|
|
})
|
|
|
|
}
|
2022-07-11 14:02:46 +02:00
|
|
|
|
|
|
|
if (serverComponentChanges.length > 0) {
|
|
|
|
this.send({
|
|
|
|
action: 'serverComponentChanges',
|
|
|
|
// TODO: granular reloading of changes
|
|
|
|
// entrypoints: serverComponentChanges,
|
|
|
|
})
|
|
|
|
}
|
2021-10-09 11:50:51 +02:00
|
|
|
})
|
2016-10-14 17:05:08 +02:00
|
|
|
|
2022-08-13 18:55:55 +02:00
|
|
|
this.multiCompiler.compilers[0].hooks.failed.tap(
|
2020-07-30 05:44:25 +02:00
|
|
|
'NextjsHotReloaderForClient',
|
|
|
|
(err: Error) => {
|
|
|
|
this.clientError = err
|
2021-10-20 19:52:11 +02:00
|
|
|
this.clientStats = null
|
2020-07-30 05:44:25 +02:00
|
|
|
}
|
|
|
|
)
|
2022-08-13 18:55:55 +02:00
|
|
|
this.multiCompiler.compilers[0].hooks.done.tap(
|
2019-05-29 13:57:26 +02:00
|
|
|
'NextjsHotReloaderForClient',
|
2020-05-18 21:24:37 +02:00
|
|
|
(stats) => {
|
2020-07-30 05:44:25 +02:00
|
|
|
this.clientError = null
|
2021-10-20 19:52:11 +02:00
|
|
|
this.clientStats = stats
|
2020-07-30 05:44:25 +02:00
|
|
|
|
2019-05-29 13:57:26 +02:00
|
|
|
const { compilation } = stats
|
|
|
|
const chunkNames = new Set(
|
2020-08-03 14:26:23 +02:00
|
|
|
[...compilation.namedChunks.keys()].filter(
|
|
|
|
(name) => !!getRouteFromEntrypoint(name)
|
|
|
|
)
|
2019-05-29 13:57:26 +02:00
|
|
|
)
|
2017-06-05 17:07:20 +02:00
|
|
|
|
2020-06-26 06:26:09 +02:00
|
|
|
if (this.prevChunkNames) {
|
2019-05-29 13:57:26 +02:00
|
|
|
// detect chunks which have to be replaced with a new template
|
|
|
|
// e.g, pages/index.js <-> pages/_error.js
|
2019-10-04 18:11:39 +02:00
|
|
|
const addedPages = diff(chunkNames, this.prevChunkNames!)
|
|
|
|
const removedPages = diff(this.prevChunkNames!, chunkNames)
|
2019-05-29 13:57:26 +02:00
|
|
|
|
|
|
|
if (addedPages.size > 0) {
|
|
|
|
for (const addedPage of addedPages) {
|
2020-06-04 19:32:45 +02:00
|
|
|
const page = getRouteFromEntrypoint(addedPage)
|
2019-05-29 13:57:26 +02:00
|
|
|
this.send('addedPage', page)
|
|
|
|
}
|
2019-01-08 23:10:32 +01:00
|
|
|
}
|
2016-11-24 15:03:16 +01:00
|
|
|
|
2019-05-29 13:57:26 +02:00
|
|
|
if (removedPages.size > 0) {
|
|
|
|
for (const removedPage of removedPages) {
|
2020-06-04 19:32:45 +02:00
|
|
|
const page = getRouteFromEntrypoint(removedPage)
|
2019-05-29 13:57:26 +02:00
|
|
|
this.send('removedPage', page)
|
|
|
|
}
|
2019-01-08 23:10:32 +01:00
|
|
|
}
|
2016-11-24 15:03:16 +01:00
|
|
|
}
|
2016-10-24 17:20:50 +02:00
|
|
|
|
2019-05-29 13:57:26 +02:00
|
|
|
this.prevChunkNames = chunkNames
|
|
|
|
}
|
|
|
|
)
|
2016-10-19 14:41:45 +02:00
|
|
|
|
2020-07-01 17:34:00 +02:00
|
|
|
this.webpackHotMiddleware = new WebpackHotMiddleware(
|
2022-08-13 18:55:55 +02:00
|
|
|
this.multiCompiler.compilers
|
2019-05-29 13:57:26 +02:00
|
|
|
)
|
2018-01-30 16:40:52 +01:00
|
|
|
|
2020-06-26 06:26:09 +02:00
|
|
|
let booted = false
|
|
|
|
|
|
|
|
this.watcher = await new Promise((resolve) => {
|
2022-08-13 18:55:55 +02:00
|
|
|
const watcher = this.multiCompiler?.watch(
|
2020-06-26 06:26:09 +02:00
|
|
|
// @ts-ignore webpack supports an array of watchOptions when using a multiCompiler
|
2022-08-13 18:55:55 +02:00
|
|
|
this.activeConfigs.map((config) => config.watchOptions!),
|
2020-06-26 06:26:09 +02:00
|
|
|
// Errors are handled separately
|
|
|
|
(_err: any) => {
|
|
|
|
if (!booted) {
|
|
|
|
booted = true
|
|
|
|
resolve(watcher)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)
|
|
|
|
})
|
2016-11-23 19:32:49 +01:00
|
|
|
|
2022-04-30 13:19:27 +02:00
|
|
|
this.onDemandEntries = onDemandEntryHandler({
|
2022-08-13 18:55:55 +02:00
|
|
|
multiCompiler: this.multiCompiler,
|
2020-06-26 06:26:09 +02:00
|
|
|
pagesDir: this.pagesDir,
|
2022-05-25 11:46:26 +02:00
|
|
|
appDir: this.appDir,
|
2022-05-19 17:46:21 +02:00
|
|
|
rootDir: this.dir,
|
2021-10-26 18:50:56 +02:00
|
|
|
nextConfig: this.config,
|
2020-06-26 06:26:09 +02:00
|
|
|
...(this.config.onDemandEntries as {
|
|
|
|
maxInactiveAge: number
|
|
|
|
pagesBufferLength: number
|
|
|
|
}),
|
|
|
|
})
|
|
|
|
|
2022-08-03 19:23:01 +02:00
|
|
|
this.interceptors = [
|
2020-06-26 06:26:09 +02:00
|
|
|
getOverlayMiddleware({
|
|
|
|
rootDirectory: this.dir,
|
2021-10-20 19:52:11 +02:00
|
|
|
stats: () => this.clientStats,
|
2020-06-26 06:26:09 +02:00
|
|
|
serverStats: () => this.serverStats,
|
2022-06-15 02:58:13 +02:00
|
|
|
edgeServerStats: () => this.edgeServerStats,
|
2020-06-26 06:26:09 +02:00
|
|
|
}),
|
|
|
|
]
|
2022-08-11 05:27:48 +02:00
|
|
|
|
|
|
|
// trigger invalidation to ensure any previous callbacks
|
|
|
|
// are handled in the on-demand-entry-handler
|
2022-08-13 18:55:55 +02:00
|
|
|
if (!initial) {
|
|
|
|
this.invalidate()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public invalidate() {
|
|
|
|
return getInvalidator()?.invalidate()
|
2016-10-14 17:05:08 +02:00
|
|
|
}
|
|
|
|
|
2020-06-26 06:26:09 +02:00
|
|
|
public async stop(): Promise<void> {
|
2021-04-22 13:08:47 +02:00
|
|
|
await new Promise((resolve, reject) => {
|
|
|
|
this.watcher.close((err: any) => (err ? reject(err) : resolve(true)))
|
2016-10-14 17:05:08 +02:00
|
|
|
})
|
2021-04-22 13:08:47 +02:00
|
|
|
|
|
|
|
if (this.fallbackWatcher) {
|
|
|
|
await new Promise((resolve, reject) => {
|
|
|
|
this.fallbackWatcher.close((err: any) =>
|
|
|
|
err ? reject(err) : resolve(true)
|
|
|
|
)
|
|
|
|
})
|
|
|
|
}
|
2022-08-13 18:55:55 +02:00
|
|
|
this.multiCompiler = undefined
|
2016-10-14 17:05:08 +02:00
|
|
|
}
|
|
|
|
|
2020-05-31 23:28:23 +02:00
|
|
|
public async getCompilationErrors(page: string) {
|
2022-08-16 11:55:37 +02:00
|
|
|
const getErrors = ({ compilation }: webpack.Stats) => {
|
2022-04-27 11:50:29 +02:00
|
|
|
const failedPages = erroredPages(compilation)
|
|
|
|
const normalizedPage = normalizePathSep(page)
|
|
|
|
// If there is an error related to the requesting page we display it instead of the first error
|
|
|
|
return failedPages[normalizedPage]?.length > 0
|
|
|
|
? failedPages[normalizedPage]
|
|
|
|
: compilation.errors
|
|
|
|
}
|
2017-06-07 00:32:02 +02:00
|
|
|
|
2020-07-30 05:44:25 +02:00
|
|
|
if (this.clientError || this.serverError) {
|
|
|
|
return [this.clientError || this.serverError]
|
2021-10-20 19:52:11 +02:00
|
|
|
} else if (this.clientStats?.hasErrors()) {
|
2022-04-27 11:50:29 +02:00
|
|
|
return getErrors(this.clientStats)
|
2021-04-22 13:47:15 +02:00
|
|
|
} else if (this.serverStats?.hasErrors()) {
|
2022-04-27 11:50:29 +02:00
|
|
|
return getErrors(this.serverStats)
|
|
|
|
} else if (this.edgeServerStats?.hasErrors()) {
|
|
|
|
return getErrors(this.edgeServerStats)
|
|
|
|
} else {
|
|
|
|
return []
|
2016-10-19 14:41:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-02 18:57:21 +02:00
|
|
|
public send(action?: string | any, ...args: any[]): void {
|
|
|
|
this.webpackHotMiddleware!.publish(
|
|
|
|
action && typeof action === 'object' ? action : { action, data: args }
|
|
|
|
)
|
2016-10-24 09:22:15 +02:00
|
|
|
}
|
2017-02-26 20:45:16 +01:00
|
|
|
|
2022-09-06 19:03:21 +02:00
|
|
|
public async ensurePage({
|
|
|
|
page,
|
|
|
|
clientOnly,
|
|
|
|
appPaths,
|
|
|
|
}: {
|
|
|
|
page: string
|
|
|
|
clientOnly: boolean
|
|
|
|
appPaths?: string[] | null
|
|
|
|
}): Promise<void> {
|
2018-05-16 00:36:24 +02:00
|
|
|
// Make sure we don't re-build or dispose prebuilt pages
|
2019-02-03 15:34:28 +01:00
|
|
|
if (page !== '/_error' && BLOCKED_PAGES.indexOf(page) !== -1) {
|
2018-05-16 00:36:24 +02:00
|
|
|
return
|
|
|
|
}
|
2021-09-30 15:52:26 +02:00
|
|
|
const error = clientOnly
|
|
|
|
? this.clientError
|
|
|
|
: this.serverError || this.clientError
|
|
|
|
if (error) {
|
|
|
|
return Promise.reject(error)
|
2020-07-30 05:44:25 +02:00
|
|
|
}
|
2022-09-06 19:03:21 +02:00
|
|
|
return this.onDemandEntries?.ensurePage({
|
|
|
|
page,
|
|
|
|
clientOnly,
|
|
|
|
appPaths,
|
|
|
|
}) as any
|
2017-02-26 20:45:16 +01:00
|
|
|
}
|
2016-10-14 17:05:08 +02:00
|
|
|
}
|