Files
next.js/packages/react-refresh-utils/ReactRefreshWebpackPlugin.ts
Arian Tron 61f56f997c
Some checks failed
Test examples / Test Examples (20) (push) Has been cancelled
Test examples / Test Examples (22) (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Trigger Release / start (push) Has been cancelled
Stale issue handler / stale (push) Has been cancelled
Update Font Data / create-pull-request (push) Has been cancelled
build-and-deploy / deploy-target (push) Has been cancelled
build-and-deploy / build (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / build-wasm (nodejs) (push) Has been cancelled
build-and-deploy / build-wasm (web) (push) Has been cancelled
build-and-deploy / Deploy preview tarball (push) Has been cancelled
build-and-deploy / Potentially publish release (push) Has been cancelled
build-and-deploy / publish-turbopack-npm-packages (push) Has been cancelled
build-and-deploy / Deploy examples (push) Has been cancelled
build-and-deploy / thank you, build (push) Has been cancelled
build-and-deploy / Upload Turbopack Bytesize metrics to Datadog (push) Has been cancelled
Rspack Next.js development integration tests / Rspack integration tests (push) Has been cancelled
Rspack Next.js production integration tests / Rspack integration tests (push) Has been cancelled
Turbopack Next.js development integration tests / Next.js integration tests (push) Has been cancelled
Turbopack Next.js production integration tests / Next.js integration tests (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack development test manifest (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack production test manifest (push) Has been cancelled
Upload bundler test manifests to areweturboyet.com / Upload test results (push) Has been cancelled
Update React / create-pull-request (push) Has been cancelled
test-e2e-project-reset-cron / reset-test-project (push) Has been cancelled
Notify about the top 15 issues/PRs/feature requests (most reacted) in the last 90 days / run (push) Has been cancelled
first commit
2026-03-10 19:37:31 +03:30

196 lines
6.6 KiB
TypeScript

import type {
Compiler as WebpackCompiler,
Template as WebpackTemplate,
RuntimeModule as WebpackRuntimeModule,
RuntimeGlobals as WebpackRuntimeGlobals,
Compilation as WebpackCompilation,
} from 'webpack'
// Shared between webpack 4 and 5:
function injectRefreshFunctions(
compilation: WebpackCompilation,
Template: typeof WebpackTemplate
) {
const hookVars: any = (compilation.mainTemplate.hooks as any).localVars
hookVars.tap('ReactFreshWebpackPlugin', (source: string) =>
Template.asString([
source,
'',
'// noop fns to prevent runtime errors during initialization',
'if (typeof self !== "undefined") {',
Template.indent('self.$RefreshReg$ = function () {};'),
Template.indent('self.$RefreshSig$ = function () {'),
Template.indent(Template.indent('return function (type) {')),
Template.indent(Template.indent(Template.indent('return type;'))),
Template.indent(Template.indent('};')),
Template.indent('};'),
'}',
])
)
}
function webpack4(this: ReactFreshWebpackPlugin, compiler: WebpackCompiler) {
const { Template } = this
// Webpack 4 does not have a method to handle interception of module
// execution.
// The closest thing we have to emulating this is mimicking the behavior of
// `strictModuleExceptionHandling` in `MainTemplate`:
// https://github.com/webpack/webpack/blob/4c644bf1f7cb067c748a52614500e0e2182b2700/lib/MainTemplate.js#L200
compiler.hooks.compilation.tap('ReactFreshWebpackPlugin', (compilation) => {
injectRefreshFunctions(compilation, Template)
const hookRequire: any = (compilation.mainTemplate.hooks as any).require
// @ts-ignore webpack 5 types compat
hookRequire.tap('ReactFreshWebpackPlugin', (source: string) => {
// Webpack 4 evaluates module code on the following line:
// ```
// modules[moduleId].call(module.exports, module, module.exports, hotCreateRequire(moduleId));
// ```
// https://github.com/webpack/webpack/blob/4c644bf1f7cb067c748a52614500e0e2182b2700/lib/MainTemplate.js#L200
const lines = source.split('\n')
// @ts-ignore webpack 5 types compat
const evalIndex = lines.findIndex((l) =>
l.includes('modules[moduleId].call(')
)
// Unable to find the module execution, that's OK:
if (evalIndex === -1) {
return source
}
// Legacy CSS implementations will `eval` browser code in a Node.js
// context to extract CSS. For backwards compatibility, we need to check
// we're in a browser context before continuing.
return Template.asString([
...lines.slice(0, evalIndex),
`
var hasRefresh = typeof self !== "undefined" && !!self.$RefreshInterceptModuleExecution$;
var cleanup = hasRefresh
? self.$RefreshInterceptModuleExecution$(moduleId)
: function() {};
try {
`,
lines[evalIndex],
`
} finally {
cleanup();
}
`,
...lines.slice(evalIndex + 1),
])
})
})
}
function webpack5(this: ReactFreshWebpackPlugin, compiler: WebpackCompiler) {
const { RuntimeGlobals, RuntimeModule, Template } = this
class ReactRefreshRuntimeModule extends RuntimeModule {
constructor() {
super('react refresh', 5)
}
generate() {
const { runtimeTemplate } = this.compilation!
return Template.asString([
`if (${RuntimeGlobals.interceptModuleExecution}) {`,
`${
RuntimeGlobals.interceptModuleExecution
}.push(${runtimeTemplate.basicFunction('options', [
`${
runtimeTemplate.supportsConst() ? 'const' : 'var'
} originalFactory = options.factory;`,
`options.factory = ${runtimeTemplate.basicFunction(
'moduleObject, moduleExports, webpackRequire',
[
// If the original factory is missing, e.g. due to race condition
// when compiling multiple entries concurrently, recover by doing
// a full page reload.
'if (!originalFactory) {',
Template.indent('document.location.reload();'),
Template.indent('return;'),
'}',
// Legacy CSS implementations will `eval` browser code in a Node.js
// context to extract CSS. For backwards compatibility, we need to check
// we're in a browser context before continuing.
`${
runtimeTemplate.supportsConst() ? 'const' : 'var'
} hasRefresh = typeof self !== "undefined" && !!self.$RefreshInterceptModuleExecution$;`,
`${
runtimeTemplate.supportsConst() ? 'const' : 'var'
} cleanup = hasRefresh ? self.$RefreshInterceptModuleExecution$(moduleObject.id) : ${
runtimeTemplate.supportsArrowFunction()
? '() => {}'
: 'function() {}'
};`,
'try {',
Template.indent(
'originalFactory.call(this, moduleObject, moduleExports, webpackRequire);'
),
'} finally {',
Template.indent(`cleanup();`),
'}',
]
)}`,
])})`,
'}',
])
}
}
// @ts-ignore webpack 5 types compat
compiler.hooks.compilation.tap('ReactFreshWebpackPlugin', (compilation) => {
injectRefreshFunctions(compilation, Template)
compilation.hooks.additionalTreeRuntimeRequirements.tap(
'ReactFreshWebpackPlugin',
(chunk: any) => {
compilation.addRuntimeModule(chunk, new ReactRefreshRuntimeModule())
}
)
})
}
class ReactFreshWebpackPlugin {
webpackMajorVersion: number
// @ts-ignore exists in webpack 5
RuntimeGlobals: typeof WebpackRuntimeGlobals
// @ts-ignore exists in webpack 5
RuntimeModule: typeof WebpackRuntimeModule
Template: typeof WebpackTemplate
constructor(
{
version,
RuntimeGlobals,
RuntimeModule,
Template,
} = require('webpack') as typeof import('webpack')
) {
this.webpackMajorVersion = parseInt(version ?? '', 10)
this.RuntimeGlobals = RuntimeGlobals
this.RuntimeModule = RuntimeModule
this.Template = Template
}
apply(compiler: WebpackCompiler) {
switch (this.webpackMajorVersion) {
case 4: {
webpack4.call(this, compiler)
break
}
case 5: {
webpack5.call(this, compiler)
break
}
default: {
throw new Error(
`ReactFreshWebpackPlugin does not support webpack v${this.webpackMajorVersion}.`
)
}
}
}
}
export default ReactFreshWebpackPlugin