2021-01-14 02:59:08 +01:00
|
|
|
// types only import
|
2020-07-03 13:30:52 +02:00
|
|
|
import {
|
2021-01-14 02:59:08 +01:00
|
|
|
Compiler as WebpackCompiler,
|
|
|
|
Template as WebpackTemplate,
|
2020-07-03 13:30:52 +02:00
|
|
|
// @ts-ignore exists in webpack 5
|
2021-01-14 02:59:08 +01:00
|
|
|
RuntimeModule as WebpackRuntimeModule,
|
2020-07-03 13:30:52 +02:00
|
|
|
// @ts-ignore exists in webpack 5
|
2021-01-14 02:59:08 +01:00
|
|
|
RuntimeGlobals as WebpackRuntimeGlobals,
|
2020-07-20 17:14:02 +02:00
|
|
|
// @ts-ignore exists in webpack 5
|
2021-01-14 02:59:08 +01:00
|
|
|
compilation as WebpackCompilation,
|
2020-07-03 13:30:52 +02:00
|
|
|
} from 'webpack'
|
2020-04-18 19:55:10 +02:00
|
|
|
|
2020-07-14 18:17:10 +02:00
|
|
|
// Shared between webpack 4 and 5:
|
2021-01-14 02:59:08 +01:00
|
|
|
function injectRefreshFunctions(
|
|
|
|
compilation: WebpackCompilation.Compilation,
|
|
|
|
Template: typeof WebpackTemplate
|
|
|
|
) {
|
2020-07-15 19:56:27 +02:00
|
|
|
const hookVars: any = (compilation.mainTemplate.hooks as any).localVars
|
2020-07-14 18:17:10 +02:00
|
|
|
|
2020-07-15 19:56:27 +02:00
|
|
|
hookVars.tap('ReactFreshWebpackPlugin', (source: string) =>
|
2020-07-14 18:17:10 +02:00
|
|
|
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('};'),
|
|
|
|
'}',
|
|
|
|
])
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-01-14 02:59:08 +01:00
|
|
|
function webpack4(this: ReactFreshWebpackPlugin, compiler: WebpackCompiler) {
|
|
|
|
const { Template } = this
|
2020-04-18 19:55:10 +02:00
|
|
|
// 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
|
|
|
|
|
2020-05-18 21:24:37 +02:00
|
|
|
compiler.hooks.compilation.tap('ReactFreshWebpackPlugin', (compilation) => {
|
2021-01-14 02:59:08 +01:00
|
|
|
injectRefreshFunctions(compilation, Template)
|
2020-07-14 18:17:10 +02:00
|
|
|
|
2020-07-15 19:56:27 +02:00
|
|
|
const hookRequire: any = (compilation.mainTemplate.hooks as any).require
|
2020-04-18 19:55:10 +02:00
|
|
|
|
2020-07-15 19:56:27 +02:00
|
|
|
// @ts-ignore webpack 5 types compat
|
|
|
|
hookRequire.tap('ReactFreshWebpackPlugin', (source: string) => {
|
2020-04-18 19:55:10 +02:00
|
|
|
// 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')
|
2020-07-15 19:56:27 +02:00
|
|
|
// @ts-ignore webpack 5 types compat
|
2020-05-18 21:24:37 +02:00
|
|
|
const evalIndex = lines.findIndex((l) =>
|
2020-04-18 19:55:10 +02:00
|
|
|
l.includes('modules[moduleId].call(')
|
|
|
|
)
|
|
|
|
// Unable to find the module execution, that's OK:
|
|
|
|
if (evalIndex === -1) {
|
|
|
|
return source
|
|
|
|
}
|
|
|
|
|
2020-05-04 01:56:51 +02:00
|
|
|
// 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.
|
2020-04-18 19:55:10 +02:00
|
|
|
return Template.asString([
|
|
|
|
...lines.slice(0, evalIndex),
|
|
|
|
`
|
2020-05-04 01:56:51 +02:00
|
|
|
var hasRefresh = typeof self !== "undefined" && !!self.$RefreshInterceptModuleExecution$;
|
2020-04-18 19:55:10 +02:00
|
|
|
var cleanup = hasRefresh
|
|
|
|
? self.$RefreshInterceptModuleExecution$(moduleId)
|
|
|
|
: function() {};
|
|
|
|
try {
|
|
|
|
`,
|
|
|
|
lines[evalIndex],
|
|
|
|
`
|
|
|
|
} finally {
|
|
|
|
cleanup();
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
...lines.slice(evalIndex + 1),
|
|
|
|
])
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-01-14 02:59:08 +01:00
|
|
|
function webpack5(this: ReactFreshWebpackPlugin, compiler: WebpackCompiler) {
|
|
|
|
const { RuntimeGlobals, RuntimeModule, Template } = this
|
2020-07-03 13:30:52 +02:00
|
|
|
class ReactRefreshRuntimeModule extends RuntimeModule {
|
|
|
|
constructor() {
|
|
|
|
super('react refresh', 5)
|
|
|
|
}
|
|
|
|
|
|
|
|
generate() {
|
|
|
|
// @ts-ignore This exists in webpack 5
|
|
|
|
const { runtimeTemplate } = this.compilation
|
|
|
|
return Template.asString([
|
2021-01-14 07:48:49 +01:00
|
|
|
`if (${RuntimeGlobals.interceptModuleExecution}) {`,
|
2020-07-03 13:30:52 +02:00
|
|
|
`${
|
|
|
|
RuntimeGlobals.interceptModuleExecution
|
|
|
|
}.push(${runtimeTemplate.basicFunction('options', [
|
|
|
|
`${
|
|
|
|
runtimeTemplate.supportsConst() ? 'const' : 'var'
|
|
|
|
} originalFactory = options.factory;`,
|
|
|
|
`options.factory = ${runtimeTemplate.basicFunction(
|
|
|
|
'moduleObject, moduleExports, webpackRequire',
|
|
|
|
[
|
|
|
|
// 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();`),
|
|
|
|
'}',
|
|
|
|
]
|
|
|
|
)}`,
|
|
|
|
])})`,
|
2021-01-14 07:48:49 +01:00
|
|
|
'}',
|
2020-07-03 13:30:52 +02:00
|
|
|
])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-15 19:56:27 +02:00
|
|
|
// @ts-ignore webpack 5 types compat
|
2020-07-03 13:30:52 +02:00
|
|
|
compiler.hooks.compilation.tap('ReactFreshWebpackPlugin', (compilation) => {
|
2021-01-14 02:59:08 +01:00
|
|
|
injectRefreshFunctions(compilation, Template)
|
2020-07-14 18:17:10 +02:00
|
|
|
|
2020-07-03 13:30:52 +02:00
|
|
|
// @ts-ignore Exists in webpack 5
|
|
|
|
compilation.hooks.additionalTreeRuntimeRequirements.tap(
|
|
|
|
'ReactFreshWebpackPlugin',
|
|
|
|
(chunk: any) => {
|
|
|
|
// @ts-ignore Exists in webpack 5
|
|
|
|
compilation.addRuntimeModule(chunk, new ReactRefreshRuntimeModule())
|
|
|
|
}
|
|
|
|
)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-04-18 19:55:10 +02:00
|
|
|
class ReactFreshWebpackPlugin {
|
2021-01-14 02:59:08 +01:00
|
|
|
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')
|
|
|
|
) {
|
|
|
|
this.webpackMajorVersion = parseInt(version ?? '', 10)
|
|
|
|
this.RuntimeGlobals = RuntimeGlobals
|
|
|
|
this.RuntimeModule = RuntimeModule
|
|
|
|
this.Template = Template
|
|
|
|
}
|
|
|
|
apply(compiler: WebpackCompiler) {
|
|
|
|
switch (this.webpackMajorVersion) {
|
2020-04-18 19:55:10 +02:00
|
|
|
case 4: {
|
2021-01-14 02:59:08 +01:00
|
|
|
webpack4.call(this, compiler)
|
2020-04-18 19:55:10 +02:00
|
|
|
break
|
|
|
|
}
|
2020-07-03 13:30:52 +02:00
|
|
|
case 5: {
|
2021-01-14 02:59:08 +01:00
|
|
|
webpack5.call(this, compiler)
|
2020-07-03 13:30:52 +02:00
|
|
|
break
|
|
|
|
}
|
2020-04-18 19:55:10 +02:00
|
|
|
default: {
|
|
|
|
throw new Error(
|
2021-01-14 02:59:08 +01:00
|
|
|
`ReactFreshWebpackPlugin does not support webpack v${this.webpackMajorVersion}.`
|
2020-04-18 19:55:10 +02:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export default ReactFreshWebpackPlugin
|