forked from vercel/next.js
-
Notifications
You must be signed in to change notification settings - Fork 1
/
nextjs-require-cache-hot-reloader.ts
90 lines (79 loc) · 2.64 KB
/
nextjs-require-cache-hot-reloader.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import type { webpack5 } from 'next/dist/compiled/webpack/webpack'
import { clearModuleContext } from '../../../server/web/sandbox'
import { realpathSync } from 'fs'
import path from 'path'
import isError from '../../../lib/is-error'
type Compiler = webpack5.Compiler
type WebpackPluginInstance = webpack5.WebpackPluginInstance
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 mod = require.cache[filePath]
if (mod) {
// remove the child reference from the originModules
for (const originModule of originModules) {
const parent = require.cache[originModule]
if (parent) {
const idx = parent.children.indexOf(mod)
if (idx >= 0) parent.children.splice(idx, 1)
}
}
// remove parent references from external modules
for (const child of mod.children) {
child.parent = null
}
delete require.cache[filePath]
return true
}
return false
}
const PLUGIN_NAME = 'NextJsRequireCacheHotReloader'
// This plugin flushes require.cache after emitting the files. Providing 'hot reloading' of server files.
export class NextJsRequireCacheHotReloader implements WebpackPluginInstance {
prevAssets: any = null
hasServerComponents: boolean
constructor(opts: { hasServerComponents: boolean }) {
this.hasServerComponents = opts.hasServerComponents
}
apply(compiler: Compiler) {
compiler.hooks.assetEmitted.tap(
PLUGIN_NAME,
(_file, { targetPath, content }) => {
deleteCache(targetPath)
clearModuleContext(targetPath, content.toString('utf-8'))
}
)
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/') ||
entry.toString().startsWith('app/')
)
entries.forEach((page) => {
const outputPath = path.join(
compilation.outputOptions.path!,
page + '.js'
)
deleteCache(outputPath)
})
})
}
}