0.2.0 - Mid migration

This commit is contained in:
Daniel Mason 2022-04-25 14:47:15 +12:00
parent 139e6a915e
commit 7e38fdbd7d
42393 changed files with 5358157 additions and 62 deletions

View file

@ -0,0 +1,10 @@
/**
* Creates an error with the plugin's prefix.
* @param {string} message The error's message.
* @returns {Error} The created error object.
*/
function createError(message) {
return new Error(`[React Refresh] ${message}`);
}
module.exports = createError;

View file

@ -0,0 +1,44 @@
const { webpackVersion } = require('../globals');
/**
* @callback EvaluateToString
* @param {string} value
* @returns {function(*): *}
*/
/**
* @callback ToConstantDependency
* @param {*} parser
* @param {string} value
* @param {string[]} [runtimeRequirements]
* @returns {function(*): boolean}
*/
/**
* @typedef {Object} ParserHelpers
* @property {EvaluateToString} evaluateToString
* @property {ToConstantDependency} toConstantDependency
*/
/**
* Gets parser helpers for specific webpack versions.
* @returns {ParserHelpers} The needed parser helpers.
*/
function getParserHelpers() {
if (webpackVersion === 4) {
const {
evaluateToString,
toConstantDependencyWithWebpackRequire,
} = require('webpack/lib/ParserHelpers');
return {
evaluateToString,
toConstantDependency: toConstantDependencyWithWebpackRequire,
};
}
if (webpackVersion === 5) {
return require('webpack/lib/javascript/JavascriptParserHelpers');
}
}
module.exports = getParserHelpers;

View file

@ -0,0 +1,69 @@
const Template = require('webpack/lib/Template');
const { refreshGlobal } = require('../globals');
/**
* @typedef {Object} RuntimeTemplate
* @property {function(string, string[]): string} basicFunction
* @property {function(): boolean} supportsConst
* @property {function(string, string=): string} returningFunction
*/
/** @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.
* @returns {string} The refresh global runtime template.
*/
function getRefreshGlobal(runtimeTemplate = FALLBACK_RUNTIME_TEMPLATE) {
const declaration = runtimeTemplate.supportsConst() ? 'const' : 'var';
return Template.asString([
`${refreshGlobal} = {`,
Template.indent([
`init: ${runtimeTemplate.basicFunction('', [
`${refreshGlobal}.cleanup = ${runtimeTemplate.returningFunction('undefined')};`,
`${refreshGlobal}.register = ${runtimeTemplate.returningFunction('undefined')};`,
`${refreshGlobal}.runtime = {};`,
`${refreshGlobal}.signature = ${runtimeTemplate.returningFunction(
runtimeTemplate.returningFunction('type', 'type')
)};`,
])},`,
`setup: ${runtimeTemplate.basicFunction('currentModuleId', [
`${declaration} prevCleanup = ${refreshGlobal}.cleanup;`,
`${declaration} prevReg = ${refreshGlobal}.register;`,
`${declaration} prevSig = ${refreshGlobal}.signature;`,
'',
`${refreshGlobal}.register = ${runtimeTemplate.basicFunction('type, id', [
`${declaration} typeId = currentModuleId + " " + id;`,
`${refreshGlobal}.runtime.register(type, typeId);`,
])}`,
'',
`${refreshGlobal}.signature = ${refreshGlobal}.runtime.createSignatureFunctionForTransform;`,
'',
`${refreshGlobal}.cleanup = ${runtimeTemplate.basicFunction('cleanupModuleId', [
'if (currentModuleId === cleanupModuleId) {',
Template.indent([
`${refreshGlobal}.register = prevReg;`,
`${refreshGlobal}.signature = prevSig;`,
`${refreshGlobal}.cleanup = prevCleanup;`,
]),
'}',
])}`,
])},`,
]),
'};',
]);
}
module.exports = getRefreshGlobal;

View file

@ -0,0 +1,30 @@
/**
* Gets the socket integration to use for Webpack messages.
* @param {'wds' | 'whm' | 'wps' | string} integrationType A valid socket integration type or a path to a module.
* @returns {string} Path to the resolved socket integration module.
*/
function getSocketIntegration(integrationType) {
let resolvedSocketIntegration;
switch (integrationType) {
case 'wds': {
resolvedSocketIntegration = require.resolve('../../sockets/WDSSocket');
break;
}
case 'whm': {
resolvedSocketIntegration = require.resolve('../../sockets/WHMEventSource');
break;
}
case 'wps': {
resolvedSocketIntegration = require.resolve('../../sockets/WPSSocket');
break;
}
default: {
resolvedSocketIntegration = require.resolve(integrationType);
break;
}
}
return resolvedSocketIntegration;
}
module.exports = getSocketIntegration;

View file

@ -0,0 +1,17 @@
const createError = require('./createError');
const getParserHelpers = require('./getParserHelpers');
const getRefreshGlobal = require('./getRefreshGlobal');
const getSocketIntegration = require('./getSocketIntegration');
const injectRefreshEntry = require('./injectRefreshEntry');
const injectRefreshLoader = require('./injectRefreshLoader');
const normalizeOptions = require('./normalizeOptions');
module.exports = {
createError,
getParserHelpers,
getRefreshGlobal,
getSocketIntegration,
injectRefreshEntry,
injectRefreshLoader,
normalizeOptions,
};

View file

@ -0,0 +1,116 @@
const querystring = require('querystring');
const createError = require('./createError');
/** @typedef {string | string[] | import('webpack').Entry} StaticEntry */
/** @typedef {StaticEntry | import('webpack').EntryFunc} WebpackEntry */
/**
* Checks if a Webpack entry string is related to socket integrations.
* @param {string} entry A Webpack entry string.
* @returns {boolean} Whether the entry is related to socket integrations.
*/
function isSocketEntry(entry) {
/**
* Webpack entries related to socket integrations.
* They have to run before any code that sets up the error overlay.
* @type {string[]}
*/
const socketEntries = [
'webpack-dev-server/client',
'webpack-hot-middleware/client',
'webpack-plugin-serve/client',
'react-dev-utils/webpackHotDevClient',
];
return socketEntries.some((socketEntry) => entry.includes(socketEntry));
}
/**
* Injects an entry to the bundle for react-refresh.
* @param {WebpackEntry} [originalEntry] A Webpack entry object.
* @param {import('../types').NormalizedPluginOptions} options Configuration options for this plugin.
* @returns {WebpackEntry} An injected entry object.
*/
function injectRefreshEntry(originalEntry, options) {
/** @type {Record<string, *>} */
let resourceQuery = {};
if (options.overlay) {
options.overlay.sockHost && (resourceQuery.sockHost = options.overlay.sockHost);
options.overlay.sockPath && (resourceQuery.sockPath = options.overlay.sockPath);
options.overlay.sockPort && (resourceQuery.sockPort = options.overlay.sockPort);
}
// We don't need to URI encode the resourceQuery as it will be parsed by Webpack
const queryString = querystring.stringify(resourceQuery, undefined, undefined, {
/**
* @param {string} string
* @returns {string}
*/
encodeURIComponent(string) {
return string;
},
});
const prependEntries = [
// React-refresh runtime
require.resolve('../../client/ReactRefreshEntry'),
];
const overlayEntries = [
// Legacy WDS SockJS integration
options.overlay &&
options.overlay.useLegacyWDSSockets &&
require.resolve('../../client/LegacyWDSSocketEntry'),
// Error overlay runtime
options.overlay &&
options.overlay.entry &&
options.overlay.entry + (queryString && `?${queryString}`),
].filter(Boolean);
// Single string entry point
if (typeof originalEntry === 'string') {
if (isSocketEntry(originalEntry)) {
return [...prependEntries, originalEntry, ...overlayEntries];
}
return [...prependEntries, ...overlayEntries, originalEntry];
}
// Single array entry point
if (Array.isArray(originalEntry)) {
const socketEntryIndex = originalEntry.findIndex(isSocketEntry);
let socketAndPrecedingEntries = [];
if (socketEntryIndex !== -1) {
socketAndPrecedingEntries = originalEntry.splice(0, socketEntryIndex + 1);
}
return [...prependEntries, ...socketAndPrecedingEntries, ...overlayEntries, ...originalEntry];
}
// Multiple entry points
if (typeof originalEntry === 'object') {
return Object.entries(originalEntry).reduce(
(acc, [curKey, curEntry]) => ({
...acc,
[curKey]:
typeof curEntry === 'object' && curEntry.import
? {
...curEntry,
import: injectRefreshEntry(curEntry.import, options),
}
: injectRefreshEntry(curEntry, options),
}),
{}
);
}
// Dynamic entry points
if (typeof originalEntry === 'function') {
return (...args) =>
Promise.resolve(originalEntry(...args)).then((resolvedEntry) =>
injectRefreshEntry(resolvedEntry, options)
);
}
throw createError('Failed to parse the Webpack `entry` object!');
}
module.exports = injectRefreshEntry;

View file

@ -0,0 +1,40 @@
const path = require('path');
/**
* @callback MatchObject
* @param {string} [str]
* @returns {boolean}
*/
const resolvedLoader = require.resolve('../../loader');
/**
* Injects refresh loader to all JavaScript-like and user-specified files.
* @param {*} data Module factory creation data.
* @param {MatchObject} matchObject A function to include/exclude files to be processed.
* @returns {*} The injected module factory creation data.
*/
function injectRefreshLoader(data, matchObject) {
if (
// Include and exclude user-specified files
matchObject(data.resource) &&
// Skip plugin's runtime utils to prevent self-referencing -
// this is useful when using the plugin as a direct dependency.
!data.resource.includes(path.join(__dirname, '../runtime/RefreshUtils')) &&
// Check to prevent double injection
!data.loaders.find(({ loader }) => loader === resolvedLoader)
) {
// As we inject runtime code for each module,
// it is important to run the injected loader after everything.
// This way we can ensure that all code-processing have been done,
// and we won't risk breaking tools like Flow or ESLint.
data.loaders.unshift({
loader: resolvedLoader,
options: undefined,
});
}
return data;
}
module.exports = injectRefreshLoader;

View file

@ -0,0 +1,82 @@
/**
* Sets a constant default value for the property when it is undefined.
* @template T
* @template {keyof T} Property
* @param {T} object An object.
* @param {Property} property A property of the provided object.
* @param {T[Property]} defaultValue The default value to set for the property.
* @returns {T[Property]} The defaulted property value.
*/
const d = (object, property, defaultValue) => {
if (typeof object[property] === 'undefined' && typeof defaultValue !== 'undefined') {
object[property] = defaultValue;
}
return object[property];
};
/**
* Resolves the value for a nested object option.
* @template T
* @template {keyof T} Property
* @template Result
* @param {T} object An object.
* @param {Property} property A property of the provided object.
* @param {function(T | undefined): Result} fn The handler to resolve the property's value.
* @returns {Result} The resolved option value.
*/
const nestedOption = (object, property, fn) => {
object[property] = fn(object[property]);
return object[property];
};
/**
* Normalizes the options for the plugin.
* @param {import('../types').ReactRefreshPluginOptions} options Non-normalized plugin options.
* @returns {import('../types').NormalizedPluginOptions} Normalized plugin options.
*/
const normalizeOptions = (options) => {
// Show deprecation notice for the `disableRefreshCheck` option and remove it
if (typeof options.disableRefreshCheck !== 'undefined') {
delete options.disableRefreshCheck;
console.warn(
[
'The "disableRefreshCheck" option has been deprecated and will not have any effect on how the plugin parses files.',
'Please remove it from your configuration.',
].join(' ')
);
}
d(options, 'exclude', /node_modules/i);
d(options, 'include', /\.([jt]sx?|flow)$/i);
d(options, 'forceEnable');
nestedOption(options, 'overlay', (overlay) => {
/** @type {import('../types').NormalizedErrorOverlayOptions} */
const defaults = {
entry: require.resolve('../../client/ErrorOverlayEntry'),
module: require.resolve('../../overlay'),
sockIntegration: 'wds',
};
if (overlay === false) {
return false;
}
if (typeof overlay === 'undefined' || overlay === true) {
return defaults;
}
d(overlay, 'entry', defaults.entry);
d(overlay, 'module', defaults.module);
d(overlay, 'sockIntegration', defaults.sockIntegration);
d(overlay, 'sockHost');
d(overlay, 'sockPath');
d(overlay, 'sockPort');
d(options, 'useLegacyWDSSockets');
return overlay;
});
return options;
};
module.exports = normalizeOptions;