From c5cc6072d04f63412dd4451c808b7705bbcda9e5 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Wed, 1 Jul 2020 17:34:00 +0200 Subject: [PATCH] Use single webpack runtimeChunk for Node.js compilation (#14722) Webpack 5 supports a single runtimechunk for the Node.js compilation, this solves sharing modules between entrypoints. --- packages/next/build/webpack-config.ts | 7 +- .../nextjs-require-cache-hot-reloader.ts | 22 ++- .../webpack/plugins/pages-manifest-plugin.ts | 5 +- .../compiled/webpack-hot-middleware/LICENSE | 20 --- .../webpack-hot-middleware/middleware.js | 1 - .../webpack-hot-middleware/package.json | 1 - packages/next/package.json | 3 +- packages/next/server/hot-middleware.ts | 157 ++++++++++++++++++ packages/next/server/hot-reloader.ts | 19 +-- .../next/server/on-demand-entry-handler.ts | 34 ++-- packages/next/taskfile.js | 119 ++++++------- packages/next/types/misc.d.ts | 4 - packages/react-dev-overlay/src/middleware.ts | 30 +++- test/acceptance/helpers.js | 4 +- test/integration/basic/test/error-recovery.js | 8 +- test/lib/next-test-utils.js | 9 +- yarn.lock | 17 -- 17 files changed, 297 insertions(+), 163 deletions(-) delete mode 100644 packages/next/compiled/webpack-hot-middleware/LICENSE delete mode 100644 packages/next/compiled/webpack-hot-middleware/middleware.js delete mode 100644 packages/next/compiled/webpack-hot-middleware/package.json create mode 100644 packages/next/server/hot-middleware.ts diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index 0a7be45f88..2c20abbc93 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -660,7 +660,9 @@ export default async function getBaseWebpackConfig( nodeEnv: false, splitChunks: isServer ? false : splitChunksConfig, runtimeChunk: isServer - ? undefined + ? isWebpack5 && !isLikeServerless + ? { name: 'webpack-runtime' } + : undefined : { name: CLIENT_STATIC_FILES_RUNTIME_WEBPACK }, minimize: !(dev || isServer), minimizer: [ @@ -926,7 +928,8 @@ export default async function getBaseWebpackConfig( }), isServerless && isServer && new ServerlessPlugin(), isServer && new PagesManifestPlugin(isLikeServerless), - target === 'server' && + !isWebpack5 && + target === 'server' && isServer && new NextJsSSRModuleCachePlugin({ outputPath }), isServer && new NextJsSsrImportPlugin(), diff --git a/packages/next/build/webpack/plugins/nextjs-require-cache-hot-reloader.ts b/packages/next/build/webpack/plugins/nextjs-require-cache-hot-reloader.ts index 127c99ba6b..3d9276b253 100644 --- a/packages/next/build/webpack/plugins/nextjs-require-cache-hot-reloader.ts +++ b/packages/next/build/webpack/plugins/nextjs-require-cache-hot-reloader.ts @@ -1,15 +1,16 @@ import { Compiler, Plugin, version } from 'webpack' import { realpathSync } from 'fs' +import path from 'path' const isWebpack5 = parseInt(version!) === 5 -function deleteCache(path: string) { +function deleteCache(filePath: string) { try { - delete require.cache[realpathSync(path)] + delete require.cache[realpathSync(filePath)] } catch (e) { if (e.code !== 'ENOENT') throw e } finally { - delete require.cache[path] + delete require.cache[filePath] } } @@ -32,10 +33,17 @@ export class NextJsRequireCacheHotReloader implements Plugin { } ) - compiler.hooks.afterEmit.tap(PLUGIN_NAME, () => { - for (const path of this.previousOutputPathsWebpack5) { - if (!this.currentOutputPathsWebpack5.has(path)) { - deleteCache(path) + compiler.hooks.afterEmit.tap(PLUGIN_NAME, (compilation) => { + const runtimeChunkPath = path.join( + compilation.outputOptions.path, + 'webpack-runtime.js' + ) + + deleteCache(runtimeChunkPath) + + for (const outputPath of this.previousOutputPathsWebpack5) { + if (!this.currentOutputPathsWebpack5.has(outputPath)) { + deleteCache(outputPath) } } diff --git a/packages/next/build/webpack/plugins/pages-manifest-plugin.ts b/packages/next/build/webpack/plugins/pages-manifest-plugin.ts index bb983c7f7a..53fffe7b1f 100644 --- a/packages/next/build/webpack/plugins/pages-manifest-plugin.ts +++ b/packages/next/build/webpack/plugins/pages-manifest-plugin.ts @@ -32,7 +32,10 @@ export default class PagesManifestPlugin implements Plugin { const files = entrypoint .getFiles() - .filter((file: string) => file.endsWith('.js')) + .filter( + (file: string) => + !file.includes('webpack-runtime') && file.endsWith('.js') + ) if (files.length > 1) { console.log( diff --git a/packages/next/compiled/webpack-hot-middleware/LICENSE b/packages/next/compiled/webpack-hot-middleware/LICENSE deleted file mode 100644 index 8c11fc7289..0000000000 --- a/packages/next/compiled/webpack-hot-middleware/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -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. diff --git a/packages/next/compiled/webpack-hot-middleware/middleware.js b/packages/next/compiled/webpack-hot-middleware/middleware.js deleted file mode 100644 index 22ad381e5a..0000000000 --- a/packages/next/compiled/webpack-hot-middleware/middleware.js +++ /dev/null @@ -1 +0,0 @@ -module.exports=function(e,n){"use strict";var t={};function __webpack_require__(n){if(t[n]){return t[n].exports}var r=t[n]={i:n,l:false,exports:{}};e[n].call(r.exports,r,r.exports,__webpack_require__);r.l=true;return r.exports}__webpack_require__.ab=__dirname+"/";function startup(){return __webpack_require__(340)}return startup()}({340:function(e,n,t){e.exports=webpackHotMiddleware;var r=t(430);var i=r.pathMatch;function webpackHotMiddleware(e,n){n=n||{};n.log=typeof n.log=="undefined"?console.log.bind(console):n.log;n.path=n.path||"/__webpack_hmr";n.heartbeat=n.heartbeat||10*1e3;var t=createEventStream(n.heartbeat);var r=null;var a=false;if(e.hooks){e.hooks.invalid.tap("webpack-hot-middleware",onInvalid);e.hooks.done.tap("webpack-hot-middleware",onDone)}else{e.plugin("invalid",onInvalid);e.plugin("done",onDone)}function onInvalid(){if(a)return;r=null;if(n.log)n.log("webpack building...");t.publish({action:"building"})}function onDone(e){if(a)return;r=e;publishStats("built",r,t,n.log)}var o=function(e,o,u){if(a)return u();if(!i(e.url,n.path))return u();t.handler(e,o);if(r){publishStats("sync",r,t)}};o.publish=function(e){if(a)return;t.publish(e)};o.close=function(){if(a)return;a=true;t.close();t=null};return o}function createEventStream(e){var n=0;var t={};function everyClient(e){Object.keys(t).forEach(function(n){e(t[n])})}var r=setInterval(function heartbeatTick(){everyClient(function(e){e.write("data: 💓\n\n")})},e).unref();return{close:function(){clearInterval(r);everyClient(function(e){if(!e.finished)e.end()});t={}},handler:function(e,r){var i={"Access-Control-Allow-Origin":"*","Content-Type":"text/event-stream;charset=utf-8","Cache-Control":"no-cache, no-transform","X-Accel-Buffering":"no"};var a=!(parseInt(e.httpVersion)>=2);if(a){e.socket.setKeepAlive(true);Object.assign(i,{Connection:"keep-alive"})}r.writeHead(200,i);r.write("\n");var o=n++;t[o]=r;e.on("close",function(){if(!r.finished)r.end();delete t[o]})},publish:function(e){everyClient(function(n){n.write("data: "+JSON.stringify(e)+"\n\n")})}}}function publishStats(e,n,t,r){var i=n.toJson({all:false,cached:true,children:true,modules:true,timings:true,hash:true});var a=extractBundles(i);a.forEach(function(i){var o=i.name||"";if(a.length===1&&!o&&n.compilation){o=n.compilation.name||""}if(r){r("webpack built "+(o?o+" ":"")+i.hash+" in "+i.time+"ms")}t.publish({name:o,action:e,time:i.time,hash:i.hash,warnings:i.warnings||[],errors:i.errors||[],modules:buildModuleMap(i.modules)})})}function extractBundles(e){if(e.modules)return[e];if(e.children&&e.children.length)return e.children;return[e]}function buildModuleMap(e){var n={};e.forEach(function(e){n[e.id]=e.name});return n}},430:function(e,n,t){var r=t(835).parse;n.pathMatch=function(e,n){try{return r(e).pathname===n}catch(e){return false}}},835:function(e){e.exports=require("url")}}); \ No newline at end of file diff --git a/packages/next/compiled/webpack-hot-middleware/package.json b/packages/next/compiled/webpack-hot-middleware/package.json deleted file mode 100644 index 3338715663..0000000000 --- a/packages/next/compiled/webpack-hot-middleware/package.json +++ /dev/null @@ -1 +0,0 @@ -{"name":"webpack-hot-middleware","main":"middleware.js","author":"Glen Mailer ","license":"MIT"} diff --git a/packages/next/package.json b/packages/next/package.json index 564a7ec775..730b8a5b76 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -211,8 +211,7 @@ "thread-loader": "2.1.3", "typescript": "3.8.3", "unfetch": "4.1.0", - "unistore": "3.4.1", - "webpack-hot-middleware": "2.25.0" + "unistore": "3.4.1" }, "engines": { "node": ">=10.13.0" diff --git a/packages/next/server/hot-middleware.ts b/packages/next/server/hot-middleware.ts new file mode 100644 index 0000000000..31d2bc906b --- /dev/null +++ b/packages/next/server/hot-middleware.ts @@ -0,0 +1,157 @@ +// 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 type webpack from 'webpack' +import type http from 'http' + +export class WebpackHotMiddleware { + eventStream: EventStream + latestStats: webpack.Stats | null + closed: boolean + + constructor(compiler: webpack.Compiler) { + this.eventStream = new EventStream() + this.latestStats = null + this.closed = false + + compiler.hooks.invalid.tap('webpack-hot-middleware', this.onInvalid) + compiler.hooks.done.tap('webpack-hot-middleware', this.onDone) + } + + onInvalid = () => { + if (this.closed) return + this.latestStats = null + this.eventStream.publish({ action: 'building' }) + } + onDone = (statsResult: webpack.Stats) => { + if (this.closed) return + // Keep hold of latest stats so they can be propagated to new clients + this.latestStats = statsResult + this.publishStats('built', this.latestStats) + } + middleware = ( + req: http.IncomingMessage, + res: http.ServerResponse, + next: () => void + ) => { + if (this.closed) return next() + if (!req.url?.startsWith('/_next/webpack-hmr')) return next() + this.eventStream.handler(req, res) + if (this.latestStats) { + // Explicitly not passing in `log` fn as we don't want to log again on + // the server + this.publishStats('sync', this.latestStats) + } + } + + publishStats = (action: string, statsResult: webpack.Stats) => { + const stats = statsResult.toJson({ + all: false, + hash: true, + warnings: true, + errors: true, + }) + + this.eventStream.publish({ + action: action, + hash: stats.hash, + warnings: stats.warnings || [], + errors: stats.errors || [], + }) + } + + publish = (payload: any) => { + if (this.closed) return + this.eventStream.publish(payload) + } + 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 + this.eventStream.close() + } +} + +class EventStream { + clients: Set + interval: NodeJS.Timeout + constructor() { + this.clients = new Set() + + this.interval = setInterval(this.heartbeatTick, 2500).unref() + } + + heartbeatTick = () => { + this.everyClient((client) => { + client.write('data: \uD83D\uDC93\n\n') + }) + } + + everyClient(fn: (client: http.ServerResponse) => void) { + for (const client of this.clients) { + fn(client) + } + } + + close() { + clearInterval(this.interval) + this.everyClient((client) => { + if (!client.finished) client.end() + }) + this.clients.clear() + } + + handler(req: http.IncomingMessage, res: http.ServerResponse) { + const headers = { + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'text/event-stream;charset=utf-8', + 'Cache-Control': 'no-cache, no-transform', + // While behind nginx, event stream should not be buffered: + // http://nginx.org/docs/http/ngx_http_proxy_module.html#proxy_buffering + 'X-Accel-Buffering': 'no', + } + + const isHttp1 = !(parseInt(req.httpVersion) >= 2) + if (isHttp1) { + req.socket.setKeepAlive(true) + Object.assign(headers, { + Connection: 'keep-alive', + }) + } + + res.writeHead(200, headers) + res.write('\n') + this.clients.add(res) + req.on('close', () => { + if (!res.finished) res.end() + this.clients.delete(res) + }) + } + + publish(payload: any) { + this.everyClient((client) => { + client.write('data: ' + JSON.stringify(payload) + '\n\n') + }) + } +} diff --git a/packages/next/server/hot-reloader.ts b/packages/next/server/hot-reloader.ts index c6d88872ab..b31fd146b7 100644 --- a/packages/next/server/hot-reloader.ts +++ b/packages/next/server/hot-reloader.ts @@ -1,7 +1,7 @@ import { getOverlayMiddleware } from '@next/react-dev-overlay/lib/middleware' import { NextHandleFunction } from 'connect' import { IncomingMessage, ServerResponse } from 'http' -import WebpackHotMiddleware from 'next/dist/compiled/webpack-hot-middleware' +import { WebpackHotMiddleware } from './hot-middleware' import { join, normalize, relative as relativePath, sep } from 'path' import { UrlObject } from 'url' import webpack from 'webpack' @@ -135,9 +135,7 @@ export default class HotReloader { private buildId: string private middlewares: any[] private pagesDir: string - private webpackHotMiddleware: - | (NextHandleFunction & WebpackHotMiddleware.EventStream) - | null + private webpackHotMiddleware: (NextHandleFunction & any) | null private config: any private stats: any private serverStats: any @@ -416,13 +414,8 @@ export default class HotReloader { } ) - this.webpackHotMiddleware = WebpackHotMiddleware( - multiCompiler.compilers[0], - { - path: '/_next/webpack-hmr', - log: false, - heartbeat: 2500, - } + this.webpackHotMiddleware = new WebpackHotMiddleware( + multiCompiler.compilers[0] ) let booted = false @@ -452,8 +445,8 @@ export default class HotReloader { this.middlewares = [ // must come before hotMiddleware - this.onDemandEntries.middleware(), - this.webpackHotMiddleware, + this.onDemandEntries.middleware, + this.webpackHotMiddleware.middleware, errorOverlayMiddleware({ dir: this.dir }), getOverlayMiddleware({ rootDirectory: this.dir, diff --git a/packages/next/server/on-demand-entry-handler.ts b/packages/next/server/on-demand-entry-handler.ts index 57e7c867d0..280534d643 100644 --- a/packages/next/server/on-demand-entry-handler.ts +++ b/packages/next/server/on-demand-entry-handler.ts @@ -211,26 +211,24 @@ export default function onDemandEntryHandler( }) }, - middleware() { - return (req: IncomingMessage, res: ServerResponse, next: Function) => { - if (!/^\/_next\/webpack-hmr/.test(req.url!)) return next() - - const { query } = parse(req.url!, true) - const page = query.page - if (!page) return next() - - const runPing = () => { - const data = handlePing(query.page as string) - if (!data) return - res.write('data: ' + JSON.stringify(data) + '\n\n') - } - const pingInterval = setInterval(() => runPing(), 5000) + middleware(req: IncomingMessage, res: ServerResponse, next: Function) { + if (!req.url?.startsWith('/_next/webpack-hmr')) return next() + + const { query } = parse(req.url!, true) + const page = query.page + if (!page) return next() - req.on('close', () => { - clearInterval(pingInterval) - }) - next() + const runPing = () => { + const data = handlePing(query.page as string) + if (!data) return + res.write('data: ' + JSON.stringify(data) + '\n\n') } + const pingInterval = setInterval(() => runPing(), 5000) + + req.on('close', () => { + clearInterval(pingInterval) + }) + next() }, } } diff --git a/packages/next/taskfile.js b/packages/next/taskfile.js index 7bbc55e0c5..e93d2bc661 100644 --- a/packages/next/taskfile.js +++ b/packages/next/taskfile.js @@ -470,17 +470,6 @@ export async function ncc_unistore(task, opts) { .target('compiled/unistore') } -// eslint-disable-next-line camelcase -externals['webpack-hot-middleware'] = - 'next/dist/compiled/webpack-hot-middleware' -export async function ncc_webpack_hot_middleware(task, opts) { - await task - .source( - opts.src || relative(__dirname, require.resolve('webpack-hot-middleware')) - ) - .ncc({ packageName: 'webpack-hot-middleware', externals }) - .target('compiled/webpack-hot-middleware') -} externals['terser-webpack-plugin'] = 'next/dist/compiled/terser-webpack-plugin' export async function ncc_terser_webpack_plugin(task, opts) { await task @@ -518,60 +507,60 @@ export async function copy_ncced(task) { } export async function ncc(task) { - await task.clear('compiled').parallel([ - 'ncc_amphtml_validator', - 'ncc_arg', - 'ncc_async_retry', - 'ncc_async_sema', - 'ncc_babel_loader', - 'ncc_cache_loader', - 'ncc_chalk', - 'ncc_ci_info', - 'ncc_compression', - 'ncc_conf', - 'ncc_content_type', - 'ncc_cookie', - 'ncc_debug', - 'ncc_devalue', - 'ncc_dotenv', - 'ncc_dotenv_expand', - 'ncc_escape_string_regexp', - 'ncc_etag', - 'ncc_file_loader', - 'ncc_find_up', - 'ncc_fresh', - 'ncc_gzip_size', - 'ncc_http_proxy', - 'ncc_ignore_loader', - 'ncc_is_docker', - 'ncc_is_wsl', - 'ncc_json5', - 'ncc_jsonwebtoken', - 'ncc_launch_editor', - 'ncc_lodash_curry', - 'ncc_lru_cache', - 'ncc_nanoid', - 'ncc_node_fetch', - 'ncc_ora', - 'ncc_postcss_flexbugs_fixes', - 'ncc_postcss_loader', - 'ncc_postcss_preset_env', - 'ncc_raw_body', - 'ncc_recast', - 'ncc_resolve', - 'ncc_send', - 'ncc_source_map', - 'ncc_string_hash', - 'ncc_strip_ansi', - 'ncc_terser', - 'ncc_text_table', - 'ncc_thread_loader', - 'ncc_unistore', - // 'ncc_webpack_dev_middleware', - 'ncc_webpack_hot_middleware', - 'ncc_terser_webpack_plugin', - 'ncc_comment_json', - ]) + await task + .clear('compiled') + .parallel([ + 'ncc_amphtml_validator', + 'ncc_arg', + 'ncc_async_retry', + 'ncc_async_sema', + 'ncc_babel_loader', + 'ncc_cache_loader', + 'ncc_chalk', + 'ncc_ci_info', + 'ncc_compression', + 'ncc_conf', + 'ncc_content_type', + 'ncc_cookie', + 'ncc_debug', + 'ncc_devalue', + 'ncc_dotenv', + 'ncc_dotenv_expand', + 'ncc_escape_string_regexp', + 'ncc_etag', + 'ncc_file_loader', + 'ncc_find_up', + 'ncc_fresh', + 'ncc_gzip_size', + 'ncc_http_proxy', + 'ncc_ignore_loader', + 'ncc_is_docker', + 'ncc_is_wsl', + 'ncc_json5', + 'ncc_jsonwebtoken', + 'ncc_launch_editor', + 'ncc_lodash_curry', + 'ncc_lru_cache', + 'ncc_nanoid', + 'ncc_node_fetch', + 'ncc_ora', + 'ncc_postcss_flexbugs_fixes', + 'ncc_postcss_loader', + 'ncc_postcss_preset_env', + 'ncc_raw_body', + 'ncc_recast', + 'ncc_resolve', + 'ncc_send', + 'ncc_source_map', + 'ncc_string_hash', + 'ncc_strip_ansi', + 'ncc_terser', + 'ncc_text_table', + 'ncc_thread_loader', + 'ncc_unistore', + 'ncc_terser_webpack_plugin', + 'ncc_comment_json', + ]) } export async function compile(task) { diff --git a/packages/next/types/misc.d.ts b/packages/next/types/misc.d.ts index ff910c6d8e..766a703fd1 100644 --- a/packages/next/types/misc.d.ts +++ b/packages/next/types/misc.d.ts @@ -207,10 +207,6 @@ declare module 'next/dist/compiled/unistore' { export = m } -declare module 'next/dist/compiled/webpack-hot-middleware' { - import m from 'webpack-hot-middleware' - export = m -} declare module 'next/dist/compiled/terser-webpack-plugin' { import m from 'terser-webpack-plugin' export = m diff --git a/packages/react-dev-overlay/src/middleware.ts b/packages/react-dev-overlay/src/middleware.ts index dbe77291d4..225a1f40ee 100644 --- a/packages/react-dev-overlay/src/middleware.ts +++ b/packages/react-dev-overlay/src/middleware.ts @@ -27,6 +27,27 @@ export type OriginalStackFrameResponse = { type Source = { map: () => RawSourceMap } | null +const isWebpack5 = parseInt(webpack.version!) === 5 + +function getModuleSource(compilation: any, module: any): any { + if (isWebpack5) { + return ( + (module && + compilation.codeGenerationResults + .get(module) + ?.sources.get('javascript')) ?? + null + ) + } + + return ( + module?.source( + compilation.dependencyTemplates, + compilation.runtimeTemplate + ) ?? null + ) +} + function getSourcePath(source: string) { // Webpack prefixes certain source paths with this path if (source.startsWith('webpack:///')) { @@ -72,15 +93,10 @@ function getOverlayMiddleware(options: OverlayMiddlewareOptions) { const compilation = isServerSide ? options.serverStats()?.compilation : options.stats()?.compilation - const m = compilation?.modules?.find( + const module = [...compilation.modules].find( (searchModule) => searchModule.id === id ) - return ( - m?.source( - compilation.dependencyTemplates, - compilation.runtimeTemplate - ) ?? null - ) + return getModuleSource(compilation, module) } catch (err) { console.error(`Failed to lookup module by ID ("${id}"):`, err) return null diff --git a/test/acceptance/helpers.js b/test/acceptance/helpers.js index d4e56047c4..44a5f4b72b 100644 --- a/test/acceptance/helpers.js +++ b/test/acceptance/helpers.js @@ -145,7 +145,9 @@ export async function sandbox(id = nanoid(), initialFiles = new Map()) { p.shadowRoot.querySelector('[data-nextjs-dialog-header') ) const root = portal.shadowRoot - return root.querySelector('[data-nextjs-dialog-header]').innerText + return root + .querySelector('[data-nextjs-dialog-header]') + .innerText.replace(/__WEBPACK_DEFAULT_EXPORT__/, 'Unknown') }) : '' diff --git a/test/integration/basic/test/error-recovery.js b/test/integration/basic/test/error-recovery.js index a61e3dacb1..9b3fd4e8a8 100644 --- a/test/integration/basic/test/error-recovery.js +++ b/test/integration/basic/test/error-recovery.js @@ -247,7 +247,13 @@ export default (context, renderViaHTTP) => { ) expect(await hasRedbox(browser)).toBe(true) - expect(await getRedboxHeader(browser)).toMatchInlineSnapshot(` + // TODO: Replace this when webpack 5 is the default + expect( + (await getRedboxHeader(browser)).replace( + '__WEBPACK_DEFAULT_EXPORT__', + 'Unknown' + ) + ).toMatchInlineSnapshot(` " 1 of 1 unhandled error Server Error diff --git a/test/lib/next-test-utils.js b/test/lib/next-test-utils.js index a2f550b1a2..ba201e981b 100644 --- a/test/lib/next-test-utils.js +++ b/test/lib/next-test-utils.js @@ -466,7 +466,9 @@ export async function getRedboxHeader(browser) { .call(document.querySelectorAll('nextjs-portal')) .find((p) => p.shadowRoot.querySelector('[data-nextjs-dialog-header')) const root = portal.shadowRoot - return root.querySelector('[data-nextjs-dialog-header]').innerText + return root + .querySelector('[data-nextjs-dialog-header]') + .innerText.replace(/__WEBPACK_DEFAULT_EXPORT__/, 'Unknown') }) } @@ -480,8 +482,9 @@ export async function getRedboxSource(browser) { ) ) const root = portal.shadowRoot - return root.querySelector('[data-nextjs-codeframe], [data-nextjs-terminal]') - .innerText + return root + .querySelector('[data-nextjs-codeframe], [data-nextjs-terminal]') + .innerText.replace(/__WEBPACK_DEFAULT_EXPORT__/, 'Unknown') }) } diff --git a/yarn.lock b/yarn.lock index 4329b90d27..c81f338b6c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3425,10 +3425,6 @@ ansi-escapes@^4.2.1: dependencies: type-fest "^0.8.1" -ansi-html@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" - ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" @@ -7805,10 +7801,6 @@ html-encoding-sniffer@^2.0.1: dependencies: whatwg-encoding "^1.0.5" -html-entities@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f" - htmlparser2@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-4.1.0.tgz#9a4ef161f2e4625ebf7dfbe6c0a2f52d18a59e78" @@ -15949,15 +15941,6 @@ webpack-bundle-analyzer@3.6.1: opener "^1.5.1" ws "^6.0.0" -webpack-hot-middleware@2.25.0: - version "2.25.0" - resolved "https://registry.yarnpkg.com/webpack-hot-middleware/-/webpack-hot-middleware-2.25.0.tgz#4528a0a63ec37f8f8ef565cf9e534d57d09fe706" - dependencies: - ansi-html "0.0.7" - html-entities "^1.2.0" - querystring "^0.2.0" - strip-ansi "^3.0.0" - webpack-sources@1.4.3, webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" -- GitLab