@pmmmwh/react-refresh-webpack-plugin
Advanced tools
Comparing version 0.5.0-beta.9 to 0.5.0-rc.0
@@ -1,13 +0,22 @@ | ||
const { version } = require('webpack'); | ||
/** | ||
* Gets current bundle's global scope identifier for React Refresh. | ||
* @param {Record<string, string>} runtimeGlobals The Webpack runtime globals. | ||
* @returns {string} The React Refresh global scope within the Webpack bundle. | ||
*/ | ||
module.exports.getRefreshGlobalScope = (runtimeGlobals) => { | ||
return `${runtimeGlobals.require || '__webpack_require__'}.$Refresh$`; | ||
}; | ||
// Parse the major version of Webpack: x.y.z => x | ||
const webpackVersion = parseInt(version || '', 10); | ||
/** | ||
* Gets current Webpack version according to features on the compiler instance. | ||
* @param {import('webpack').Compiler} compiler The current Webpack compiler instance. | ||
* @returns {number} The current Webpack version. | ||
*/ | ||
module.exports.getWebpackVersion = (compiler) => { | ||
if (!compiler.hooks) { | ||
throw new Error(`[ReactRefreshPlugin] Webpack version is not supported!`); | ||
} | ||
let webpackGlobals = {}; | ||
if (webpackVersion === 5) { | ||
webpackGlobals = require('webpack/lib/RuntimeGlobals'); | ||
} | ||
module.exports.webpackVersion = webpackVersion; | ||
module.exports.webpackRequire = webpackGlobals.require || '__webpack_require__'; | ||
module.exports.refreshGlobal = `${module.exports.webpackRequire}.$Refresh$`; | ||
// Webpack v5+ implements compiler caching | ||
return 'cache' in compiler ? 5 : 4; | ||
}; |
108
lib/index.js
const { validate: validateOptions } = require('schema-utils'); | ||
const { getRefreshGlobalScope, getWebpackVersion } = require('./globals'); | ||
const { | ||
DefinePlugin, | ||
EntryPlugin, | ||
ModuleFilenameHelpers, | ||
ProvidePlugin, | ||
Template, | ||
} = require('webpack'); | ||
const ConstDependency = require('webpack/lib/dependencies/ConstDependency'); | ||
const { refreshGlobal, webpackRequire, webpackVersion } = require('./globals'); | ||
const { | ||
getAdditionalEntries, | ||
getIntegrationEntry, | ||
getParserHelpers, | ||
getRefreshGlobal, | ||
@@ -19,2 +10,3 @@ getSocketIntegration, | ||
injectRefreshLoader, | ||
makeRefreshRuntimeModule, | ||
normalizeOptions, | ||
@@ -24,16 +16,2 @@ } = require('./utils'); | ||
// Mapping of react-refresh globals to Webpack runtime globals | ||
const REPLACEMENTS = { | ||
$RefreshReg$: { | ||
expr: `${refreshGlobal}.register`, | ||
req: [webpackRequire, `${refreshGlobal}.register`], | ||
type: 'function', | ||
}, | ||
$RefreshSig$: { | ||
expr: `${refreshGlobal}.signature`, | ||
req: [webpackRequire, `${refreshGlobal}.signature`], | ||
type: 'function', | ||
}, | ||
}; | ||
class ReactRefreshPlugin { | ||
@@ -62,8 +40,2 @@ /** | ||
apply(compiler) { | ||
// Throw if we encounter an unsupported Webpack version, | ||
// since things will most likely not work. | ||
if (webpackVersion !== 4 && webpackVersion !== 5) { | ||
throw new Error(`[ReactRefreshPlugin] Webpack v${webpackVersion} is not supported!`); | ||
} | ||
// Skip processing in non-development mode, but allow manual force-enabling | ||
@@ -82,9 +54,23 @@ if ( | ||
const webpackVersion = getWebpackVersion(compiler); | ||
const logger = compiler.getInfrastructureLogger(this.constructor.name); | ||
let loggedHotWarning = false; | ||
// Get Webpack imports from compiler instance (if available) - | ||
// this allow mono-repos to use different versions of Webpack without conflicts. | ||
const webpack = compiler.webpack || require('webpack'); | ||
const { | ||
DefinePlugin, | ||
EntryDependency, | ||
EntryPlugin, | ||
ModuleFilenameHelpers, | ||
NormalModule, | ||
ProvidePlugin, | ||
RuntimeGlobals, | ||
Template, | ||
} = webpack; | ||
// Inject react-refresh context to all Webpack entry points. | ||
// This should create `EntryDependency` objects when available, | ||
// and fallback to patching the `entry` object for legacy workflows. | ||
const additional = getAdditionalEntries({ | ||
const addEntries = getAdditionalEntries({ | ||
devServer: compiler.options.devServer, | ||
@@ -96,3 +82,3 @@ options: this.options, | ||
// so we can utilise EntryPlugin for simpler logic. | ||
additional.prependEntries.forEach((entry) => { | ||
addEntries.prependEntries.forEach((entry) => { | ||
new EntryPlugin(compiler.context, entry, { name: undefined }).apply(compiler); | ||
@@ -122,3 +108,3 @@ }); | ||
// This ensures we can traverse all entry points and inject stuff with the correct order. | ||
additional.overlayEntries.forEach((entry, idx, arr) => { | ||
addEntries.overlayEntries.forEach((entry, idx, arr) => { | ||
compiler.hooks.finishMake.tapPromise( | ||
@@ -163,8 +149,16 @@ { name: this.constructor.name, stage: Number.MIN_SAFE_INTEGER + (arr.length - idx - 1) }, | ||
} else { | ||
compiler.options.entry = injectRefreshEntry(compiler.options.entry, additional); | ||
compiler.options.entry = injectRefreshEntry(compiler.options.entry, addEntries); | ||
} | ||
// Inject necessary modules to bundle's global scope | ||
// Inject necessary modules and variables to bundle's global scope | ||
const refreshGlobal = getRefreshGlobalScope(RuntimeGlobals || {}); | ||
/** @type {Record<string, string | boolean>}*/ | ||
const definedModules = { | ||
// Mapping of react-refresh globals to Webpack runtime globals | ||
$RefreshReg$: `${refreshGlobal}.register`, | ||
$RefreshSig$: `${refreshGlobal}.signature`, | ||
'typeof $RefreshReg$': 'function', | ||
'typeof $RefreshSig$': 'function', | ||
// Library mode | ||
__react_refresh_library__: JSON.stringify( | ||
@@ -207,3 +201,3 @@ Template.toIdentifier( | ||
const match = ModuleFilenameHelpers.matchObject.bind(undefined, this.options); | ||
const { evaluateToString, toConstantDependency } = getParserHelpers(); | ||
let loggedHotWarning = false; | ||
compiler.hooks.compilation.tap( | ||
@@ -217,5 +211,2 @@ this.constructor.name, | ||
// Set template for ConstDependency which is used by parser hooks | ||
compilation.dependencyTemplates.set(ConstDependency, new ConstDependency.Template()); | ||
// Tap into version-specific compilation hooks | ||
@@ -290,3 +281,3 @@ switch (webpackVersion) { | ||
(source) => { | ||
return Template.asString([source, '', getRefreshGlobal()]); | ||
return Template.asString([source, '', getRefreshGlobal(Template)]); | ||
} | ||
@@ -327,10 +318,6 @@ ); | ||
case 5: { | ||
const EntryDependency = require('webpack/lib/dependencies/EntryDependency'); | ||
const NormalModule = require('webpack/lib/NormalModule'); | ||
const RuntimeGlobals = require('webpack/lib/RuntimeGlobals'); | ||
const ReactRefreshRuntimeModule = require('./RefreshRuntimeModule'); | ||
// Set factory for EntryDependency which is used to initialise the module | ||
compilation.dependencyFactories.set(EntryDependency, normalModuleFactory); | ||
const ReactRefreshRuntimeModule = makeRefreshRuntimeModule(webpack); | ||
compilation.hooks.additionalTreeRuntimeRequirements.tap( | ||
@@ -385,31 +372,2 @@ this.constructor.name, | ||
} | ||
/** | ||
* Transform global calls into Webpack runtime calls. | ||
* @param {*} parser | ||
* @returns {void} | ||
*/ | ||
const parserHandler = (parser) => { | ||
Object.entries(REPLACEMENTS).forEach(([key, info]) => { | ||
parser.hooks.expression | ||
.for(key) | ||
.tap(this.constructor.name, toConstantDependency(parser, info.expr, info.req)); | ||
if (info.type) { | ||
parser.hooks.evaluateTypeof | ||
.for(key) | ||
.tap(this.constructor.name, evaluateToString(info.type)); | ||
} | ||
}); | ||
}; | ||
normalModuleFactory.hooks.parser | ||
.for('javascript/auto') | ||
.tap(this.constructor.name, parserHandler); | ||
normalModuleFactory.hooks.parser | ||
.for('javascript/dynamic') | ||
.tap(this.constructor.name, parserHandler); | ||
normalModuleFactory.hooks.parser | ||
.for('javascript/esm') | ||
.tap(this.constructor.name, parserHandler); | ||
} | ||
@@ -416,0 +374,0 @@ ); |
@@ -1,3 +0,2 @@ | ||
const Template = require('webpack/lib/Template'); | ||
const { refreshGlobal } = require('../globals'); | ||
const { getRefreshGlobalScope } = require('../globals'); | ||
@@ -11,22 +10,26 @@ /** | ||
/** @type {RuntimeTemplate} */ | ||
const FALLBACK_RUNTIME_TEMPLATE = { | ||
basicFunction(args, body) { | ||
return `function(${args}) {\n${Template.indent(body)}\n}`; | ||
}, | ||
supportsConst() { | ||
return false; | ||
}, | ||
returningFunction(returnValue, args = '') { | ||
return `function(${args}) { return ${returnValue}; }`; | ||
}, | ||
}; | ||
/** | ||
* Generates the refresh global runtime template. | ||
* @param {RuntimeTemplate} [runtimeTemplate] The runtime template helpers. | ||
* @param {import('webpack').Template} Template The template helpers. | ||
* @param {Record<string, string>} [RuntimeGlobals] The runtime globals. | ||
* @param {RuntimeTemplate} [RuntimeTemplate] The runtime template helpers. | ||
* @returns {string} The refresh global runtime template. | ||
*/ | ||
function getRefreshGlobal(runtimeTemplate = FALLBACK_RUNTIME_TEMPLATE) { | ||
const declaration = runtimeTemplate.supportsConst() ? 'const' : 'var'; | ||
function getRefreshGlobal( | ||
Template, | ||
RuntimeGlobals = {}, | ||
RuntimeTemplate = { | ||
basicFunction(args, body) { | ||
return `function(${args}) {\n${Template.indent(body)}\n}`; | ||
}, | ||
supportsConst() { | ||
return false; | ||
}, | ||
returningFunction(returnValue, args = '') { | ||
return `function(${args}) { return ${returnValue}; }`; | ||
}, | ||
} | ||
) { | ||
const declaration = RuntimeTemplate.supportsConst() ? 'const' : 'var'; | ||
const refreshGlobal = getRefreshGlobalScope(RuntimeGlobals); | ||
return Template.asString([ | ||
@@ -39,7 +42,7 @@ `${refreshGlobal} = {`, | ||
// they will be mutated in place during module initialisation by the `setup` function below. | ||
`register: ${runtimeTemplate.returningFunction('undefined')},`, | ||
`signature: ${runtimeTemplate.returningFunction( | ||
runtimeTemplate.returningFunction('type', 'type') | ||
`register: ${RuntimeTemplate.returningFunction('undefined')},`, | ||
`signature: ${RuntimeTemplate.returningFunction( | ||
RuntimeTemplate.returningFunction('type', 'type') | ||
)},`, | ||
`setup: ${runtimeTemplate.basicFunction('currentModuleId', [ | ||
`setup: ${RuntimeTemplate.basicFunction('currentModuleId', [ | ||
// Store all previous values for fields on `refreshGlobal` - | ||
@@ -60,10 +63,10 @@ // this allows proper restoration in the `cleanup` phase. | ||
Template.indent([ | ||
`createSignatureFunctionForTransform: ${runtimeTemplate.returningFunction( | ||
runtimeTemplate.returningFunction('type', 'type') | ||
`createSignatureFunctionForTransform: ${RuntimeTemplate.returningFunction( | ||
RuntimeTemplate.returningFunction('type', 'type') | ||
)},`, | ||
`register: ${runtimeTemplate.returningFunction('undefined')}`, | ||
`register: ${RuntimeTemplate.returningFunction('undefined')}`, | ||
]), | ||
'};', | ||
'', | ||
`${refreshGlobal}.register = ${runtimeTemplate.basicFunction('type, id', [ | ||
`${refreshGlobal}.register = ${RuntimeTemplate.basicFunction('type, id', [ | ||
`${declaration} typeId = currentModuleId + " " + id;`, | ||
@@ -75,3 +78,3 @@ `${refreshGlobal}.runtime.register(type, typeId);`, | ||
'', | ||
`${refreshGlobal}.cleanup = ${runtimeTemplate.basicFunction('cleanupModuleId', [ | ||
`${refreshGlobal}.cleanup = ${RuntimeTemplate.basicFunction('cleanupModuleId', [ | ||
// Only cleanup if the module IDs match. | ||
@@ -78,0 +81,0 @@ // In rare cases, it might get called in another module's `cleanup` phase. |
const getAdditionalEntries = require('./getAdditionalEntries'); | ||
const getIntegrationEntry = require('./getIntegrationEntry'); | ||
const getParserHelpers = require('./getParserHelpers'); | ||
const getRefreshGlobal = require('./getRefreshGlobal'); | ||
@@ -8,2 +7,3 @@ const getSocketIntegration = require('./getSocketIntegration'); | ||
const injectRefreshLoader = require('./injectRefreshLoader'); | ||
const makeRefreshRuntimeModule = require('./makeRefreshRuntimeModule'); | ||
const normalizeOptions = require('./normalizeOptions'); | ||
@@ -14,3 +14,2 @@ | ||
getIntegrationEntry, | ||
getParserHelpers, | ||
getRefreshGlobal, | ||
@@ -20,3 +19,4 @@ getSocketIntegration, | ||
injectRefreshLoader, | ||
makeRefreshRuntimeModule, | ||
normalizeOptions, | ||
}; |
@@ -11,4 +11,2 @@ // This is a patch for mozilla/source-map#349 - | ||
const { SourceMapConsumer, SourceNode } = require('source-map'); | ||
const { Template } = require('webpack'); | ||
const { refreshGlobal } = require('../lib/globals'); | ||
const { | ||
@@ -27,10 +25,2 @@ getIdentitySourceMap, | ||
const RefreshSetupRuntimes = { | ||
cjs: Template.asString(`${refreshGlobal}.runtime = require('${RefreshRuntimePath}');`), | ||
esm: Template.asString([ | ||
`import * as __react_refresh_runtime__ from '${RefreshRuntimePath}';`, | ||
`${refreshGlobal}.runtime = __react_refresh_runtime__;`, | ||
]), | ||
}; | ||
/** | ||
@@ -57,2 +47,14 @@ * A simple Webpack loader to inject react-refresh HMR code into modules. | ||
const { ModuleFilenameHelpers, Template } = this._compiler.webpack || require('webpack'); | ||
const RefreshSetupRuntimes = { | ||
cjs: Template.asString( | ||
`__webpack_require__.$Refresh$.runtime = require('${RefreshRuntimePath}');` | ||
), | ||
esm: Template.asString([ | ||
`import * as __react_refresh_runtime__ from '${RefreshRuntimePath}';`, | ||
`__webpack_require__.$Refresh$.runtime = __react_refresh_runtime__;`, | ||
]), | ||
}; | ||
/** | ||
@@ -65,6 +67,9 @@ * @this {import('webpack').loader.LoaderContext} | ||
async function _loader(source, inputSourceMap) { | ||
const moduleSystem = await getModuleSystem(this, options); | ||
const moduleSystem = await getModuleSystem.call(this, ModuleFilenameHelpers, options); | ||
const RefreshSetupRuntime = RefreshSetupRuntimes[moduleSystem]; | ||
const RefreshModuleRuntime = getRefreshModuleRuntime({ const: options.const, moduleSystem }); | ||
const RefreshModuleRuntime = getRefreshModuleRuntime(Template, { | ||
const: options.const, | ||
moduleSystem, | ||
}); | ||
@@ -71,0 +76,0 @@ if (this.sourceMap) { |
const { promises: fsPromises } = require('fs'); | ||
const path = require('path'); | ||
const { ModuleFilenameHelpers } = require('webpack'); | ||
@@ -10,7 +9,8 @@ /** @type {string | undefined} */ | ||
* Infers the current active module system from loader context and options. | ||
* @param {import('webpack').loader.LoaderContext} loaderContext The Webpack loader context. | ||
* @this {import('webpack').loader.LoaderContext} | ||
* @param {import('webpack').ModuleFilenameHelpers} ModuleFilenameHelpers Webpack's module filename helpers. | ||
* @param {import('../types').NormalizedLoaderOptions} options The normalized loader options. | ||
* @return {Promise<'esm' | 'cjs'>} The inferred module system. | ||
*/ | ||
async function getModuleSystem(loaderContext, options) { | ||
async function getModuleSystem(ModuleFilenameHelpers, options) { | ||
// Check loader options - | ||
@@ -25,3 +25,3 @@ // if `esModule` is set we don't have to do extra guess work. | ||
options.esModule.include && | ||
ModuleFilenameHelpers.matchPart(loaderContext.resourcePath, options.esModule.include) | ||
ModuleFilenameHelpers.matchPart(this.resourcePath, options.esModule.include) | ||
) { | ||
@@ -32,3 +32,3 @@ return 'esm'; | ||
options.esModule.exclude && | ||
ModuleFilenameHelpers.matchPart(loaderContext.resourcePath, options.esModule.exclude) | ||
ModuleFilenameHelpers.matchPart(this.resourcePath, options.esModule.exclude) | ||
) { | ||
@@ -44,4 +44,4 @@ return 'cjs'; | ||
// Check current resource's extension | ||
if (/\.mjs$/.test(loaderContext.resourcePath)) return 'esm'; | ||
if (/\.cjs$/.test(loaderContext.resourcePath)) return 'cjs'; | ||
if (/\.mjs$/.test(this.resourcePath)) return 'esm'; | ||
if (/\.cjs$/.test(this.resourcePath)) return 'cjs'; | ||
@@ -52,3 +52,3 @@ // Load users' `package.json` - | ||
try { | ||
const packageJsonPath = require.resolve(path.join(loaderContext.rootContext, 'package.json')); | ||
const packageJsonPath = require.resolve(path.join(this.rootContext, 'package.json')); | ||
const buffer = await fsPromises.readFile(packageJsonPath, { encoding: 'utf-8' }); | ||
@@ -55,0 +55,0 @@ const rawPackageJson = buffer.toString('utf-8'); |
@@ -1,3 +0,1 @@ | ||
const { Template } = require('webpack'); | ||
/** | ||
@@ -17,6 +15,7 @@ * @typedef ModuleRuntimeOptions {Object} | ||
* | ||
* @param {import('webpack').Template} Webpack's templating helpers. | ||
* @param {ModuleRuntimeOptions} options The refresh module runtime options. | ||
* @returns {string} The refresh module runtime template. | ||
*/ | ||
function getRefreshModuleRuntime(options) { | ||
function getRefreshModuleRuntime(Template, options) { | ||
const constDeclaration = options.const ? 'const' : 'var'; | ||
@@ -23,0 +22,0 @@ const letDeclaration = options.const ? 'let' : 'var'; |
{ | ||
"name": "@pmmmwh/react-refresh-webpack-plugin", | ||
"version": "0.5.0-beta.9", | ||
"version": "0.5.0-rc.0", | ||
"description": "An **EXPERIMENTAL** Webpack plugin to enable \"Fast Refresh\" (also previously known as _Hot Reloading_) for React components.", | ||
@@ -72,3 +72,3 @@ "keywords": [ | ||
"@types/webpack": "^4.41.28", | ||
"babel-jest": "^26.6.3", | ||
"babel-jest": "^27.0.2", | ||
"babel-loader": "^8.1.0", | ||
@@ -82,5 +82,5 @@ "cross-spawn": "^7.0.3", | ||
"get-port": "^5.1.1", | ||
"jest": "^26.4.2", | ||
"jest-circus": "^26.4.2", | ||
"jest-environment-node": "^26.3.0", | ||
"jest": "^27.0.4", | ||
"jest-circus": "^27.0.4", | ||
"jest-environment-node": "^27.0.3", | ||
"jest-junit": "^12.0.0", | ||
@@ -92,7 +92,7 @@ "jest-watch-typeahead": "^0.6.3", | ||
"prettier": "^2.3.0", | ||
"puppeteer": "^8.0.0", | ||
"puppeteer": "^9.1.1", | ||
"react-refresh": "^0.10.0", | ||
"sourcemap-validator": "^2.1.0", | ||
"type-fest": "^1.1.3", | ||
"typescript": "4.2.4", | ||
"typescript": "4.3.2", | ||
"webpack": "^4.46.0", | ||
@@ -138,3 +138,3 @@ "webpack-cli": "^3.3.12", | ||
"resolutions": { | ||
"type-fest": "^1.1.3" | ||
"type-fest": "^1.2.0" | ||
}, | ||
@@ -141,0 +141,0 @@ "engines": { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
121691
55
2982