797dabe351
Currently `new URL()` for server assets is completely broken because of the `publicPath` that is used for them too. `new URL()` for SSR is broken on windows as it's using absolute urls on the windows filesystem. And `new URL()` is using an incorrect filename * Place all `asset`s correctly in `/_next/static/media` with `[name].[hash:8][ext]` * Added a separate runtime chunk for api entries, without `publicPath` * Introduce separate layer for api entries, which uses server-side URLs. * Otherwise new URL() will return a faked relative URL, that is identical in SSR and CSR * Disables react-refresh for api entries Fixes #27413 ## Bug - [ ] Related issues linked using `fixes #number` - [x] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` ## Feature - [ ] 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 - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have helpful link attached, see `contributing.md` ## Documentation / Examples - [ ] Make sure the linting passes
107 lines
3.2 KiB
TypeScript
107 lines
3.2 KiB
TypeScript
import { webpack } from 'next/dist/compiled/webpack/webpack'
|
|
import { isWebpack5 } from 'next/dist/compiled/webpack/webpack'
|
|
import { realpathSync } from 'fs'
|
|
import path from 'path'
|
|
import isError from '../../../lib/is-error'
|
|
|
|
const originModules = [
|
|
require.resolve('../../../server/require'),
|
|
require.resolve('../../../server/load-components'),
|
|
]
|
|
|
|
const RUNTIME_NAMES = ['webpack-runtime', 'webpack-api-runtime']
|
|
|
|
function deleteCache(filePath: string) {
|
|
try {
|
|
filePath = realpathSync(filePath)
|
|
} catch (e) {
|
|
if (isError(e) && e.code !== 'ENOENT') throw e
|
|
}
|
|
const module = require.cache[filePath]
|
|
if (module) {
|
|
// remove the child reference from the originModules
|
|
for (const originModule of originModules) {
|
|
const parent = require.cache[originModule]
|
|
if (parent) {
|
|
const idx = parent.children.indexOf(module)
|
|
if (idx >= 0) parent.children.splice(idx, 1)
|
|
}
|
|
}
|
|
// remove parent references from external modules
|
|
for (const child of module.children) {
|
|
child.parent = null
|
|
}
|
|
}
|
|
delete require.cache[filePath]
|
|
}
|
|
|
|
const PLUGIN_NAME = 'NextJsRequireCacheHotReloader'
|
|
|
|
// This plugin flushes require.cache after emitting the files. Providing 'hot reloading' of server files.
|
|
export class NextJsRequireCacheHotReloader implements webpack.Plugin {
|
|
prevAssets: any = null
|
|
previousOutputPathsWebpack5: Set<string> = new Set()
|
|
currentOutputPathsWebpack5: Set<string> = new Set()
|
|
|
|
apply(compiler: webpack.Compiler) {
|
|
if (isWebpack5) {
|
|
// @ts-ignored Webpack has this hooks
|
|
compiler.hooks.assetEmitted.tap(
|
|
PLUGIN_NAME,
|
|
(_file: any, { targetPath }: any) => {
|
|
this.currentOutputPathsWebpack5.add(targetPath)
|
|
deleteCache(targetPath)
|
|
}
|
|
)
|
|
|
|
compiler.hooks.afterEmit.tap(PLUGIN_NAME, (compilation) => {
|
|
RUNTIME_NAMES.forEach((name) => {
|
|
const runtimeChunkPath = path.join(
|
|
compilation.outputOptions.path,
|
|
`${name}.js`
|
|
)
|
|
deleteCache(runtimeChunkPath)
|
|
})
|
|
|
|
// we need to make sure to clear all server entries from cache
|
|
// since they can have a stale webpack-runtime cache
|
|
// which needs to always be in-sync
|
|
const entries = [...compilation.entries.keys()].filter((entry) =>
|
|
entry.toString().startsWith('pages/')
|
|
)
|
|
|
|
entries.forEach((page) => {
|
|
const outputPath = path.join(
|
|
compilation.outputOptions.path,
|
|
page + '.js'
|
|
)
|
|
deleteCache(outputPath)
|
|
})
|
|
})
|
|
|
|
this.previousOutputPathsWebpack5 = new Set(
|
|
this.currentOutputPathsWebpack5
|
|
)
|
|
this.currentOutputPathsWebpack5.clear()
|
|
return
|
|
}
|
|
|
|
compiler.hooks.afterEmit.tapAsync(PLUGIN_NAME, (compilation, callback) => {
|
|
const { assets } = compilation
|
|
|
|
if (this.prevAssets) {
|
|
for (const f of Object.keys(assets)) {
|
|
deleteCache(assets[f].existsAt)
|
|
}
|
|
for (const f of Object.keys(this.prevAssets)) {
|
|
if (!assets[f]) {
|
|
deleteCache(this.prevAssets[f].existsAt)
|
|
}
|
|
}
|
|
}
|
|
this.prevAssets = assets
|
|
|
|
callback()
|
|
})
|
|
}
|
|
}
|