Update aspnet-webpack-react to v2.0.0, now supporting Webpack 2+ and React Hot Loader 3+ only

This commit is contained in:
Steve Sanderson
2017-05-17 21:04:47 +01:00
parent e658ee6375
commit ef9dbfe44b
4 changed files with 66 additions and 77 deletions

View File

@@ -4,3 +4,8 @@ This NPM package is an internal implementation detail of the `Microsoft.AspNetCo
You should not use this package directly in your own applications, because it is not supported, and there are no You should not use this package directly in your own applications, because it is not supported, and there are no
guarantees about how its APIs will change in the future. guarantees about how its APIs will change in the future.
## History
* Version 1.x amends the Webpack config to insert `react-transform` and `react-transform-hmr` entries on `babel-loader`.
* Version 2.x drops support for the Babel plugin, and instead amends the Webpack config to insert `react-hot-loader/webpack` and `react-hot-loader/patch` entries. This means it works with React Hot Loader v3.

View File

@@ -1,6 +1,6 @@
{ {
"name": "aspnet-webpack-react", "name": "aspnet-webpack-react",
"version": "1.0.5", "version": "2.0.0",
"description": "Helpers for using Webpack with React in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.", "description": "Helpers for using Webpack with React in ASP.NET Core projects. Works in conjunction with the Microsoft.AspNetCore.SpaServices NuGet package.",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
@@ -16,22 +16,13 @@
"type": "git", "type": "git",
"url": "https://github.com/aspnet/JavaScriptServices.git" "url": "https://github.com/aspnet/JavaScriptServices.git"
}, },
"dependencies": {
"babel-core": "^6.7.2",
"babel-loader": "^6.2.4",
"babel-plugin-react-transform": "^2.0.2",
"babel-preset-es2015": "^6.6.0",
"babel-preset-react": "^6.5.0",
"react": "^15.0.0",
"react-transform-hmr": "^1.0.4"
},
"devDependencies": { "devDependencies": {
"@types/webpack": "^2.2.0", "@types/webpack": "^2.2.0",
"rimraf": "^2.5.4", "rimraf": "^2.5.4",
"typescript": "^2.0.0", "typescript": "^2.0.0",
"webpack": "^1.12.14" "webpack": "^2.2.0"
}, },
"peerDependencies": { "peerDependencies": {
"webpack": "^1.13.2 || ^2.2.0" "webpack": "^2.2.0"
} }
} }

View File

@@ -1,78 +1,66 @@
import * as webpack from 'webpack'; import * as webpack from 'webpack';
type OldOrNewModule = webpack.OldModule & webpack.NewModule;
export function addReactHotModuleReplacementBabelTransform(webpackConfig: webpack.Configuration) { const reactHotLoaderWebpackLoader = 'react-hot-loader/webpack';
const moduleConfig = webpackConfig.module as OldOrNewModule; const reactHotLoaderPatch = 'react-hot-loader/patch';
const moduleRules = moduleConfig.rules // Webpack >= 2.1.0 beta 23 const supportedTypeScriptLoaders = ['ts-loader', 'awesome-typescript-loader'];
|| moduleConfig.loaders; // Legacy/back-compat
export function addReactHotModuleReplacementConfig(webpackConfig: webpack.Configuration) {
const moduleConfig = webpackConfig.module as webpack.NewModule;
const moduleRules = moduleConfig.rules;
if (!moduleRules) { if (!moduleRules) {
return; // Unknown rules list format return; // Unknown rules list format. Might be Webpack 1.x, which is not supported.
} }
moduleRules.forEach(rule => { // Find the rule that loads TypeScript files, and prepend 'react-hot-loader/webpack'
// Allow rules/loaders entries to be either { loader: ... } or { use: ... } // to its array of loaders
// Ignore other config formats (too many combinations to support them all) for (let ruleIndex = 0; ruleIndex < moduleRules.length; ruleIndex++) {
let loaderConfig = // We only support NewUseRule (i.e., { use: ... }) because OldUseRule doesn't accept array values
(rule as webpack.NewUseRule).use // Recommended config format for Webpack 2.x const rule = moduleRules[ruleIndex] as webpack.NewUseRule;
|| (rule as webpack.LoaderRule).loader; // Typical config format for Webpack 1.x if (!rule.use) {
if (!loaderConfig) { continue;
return; // Not a supported rule format (e.g., an array)
} }
// Allow use/loader values to be either { loader: 'name' } or 'name' // We're looking for the first 'use' value that's a TypeScript loader
// We don't need to support other possible ways of specifying loaders (e.g., arrays), const loadersArray = rule.use instanceof Array ? rule.use : [rule.use];
// so skip unrecognized formats. const isTypescriptLoader = supportedTypeScriptLoaders.some(typeScriptLoaderName => containsLoader(loadersArray, typeScriptLoaderName));
const loaderNameString = if (!isTypescriptLoader) {
(loaderConfig as (webpack.OldLoader | webpack.NewLoader)).loader continue;
|| (loaderConfig as string);
if (!loaderNameString || (typeof loaderNameString !== 'string')) {
return; // Not a supported loader format (e.g., an array)
} }
// Find the babel-loader entry // This is the one - prefix it with the react-hot-loader loader
if (loaderNameString.match(/\bbabel-loader\b/)) { // (unless it's already in there somewhere)
// If the rule is of the form { use: 'name' }, then replace it if (!containsLoader(loadersArray, reactHotLoaderWebpackLoader)) {
// with { use: { loader: 'name' }} so we can attach options loadersArray.unshift(reactHotLoaderWebpackLoader);
if ((rule as webpack.NewUseRule).use && typeof loaderConfig === 'string') { rule.use = loadersArray; // In case we normalised it to an array
loaderConfig = (rule as webpack.NewUseRule).use = { loader: loaderConfig }; }
} break;
}
const configItemWithOptions = typeof loaderConfig === 'string' // Ensure the entrypoint is prefixed with 'react-hot-loader/patch' (unless it's already in there).
? rule // The rule is of the form { loader: 'name' }, so put options on the rule // We only support entrypoints of the form { name: value } (not just 'name' or ['name'])
: loaderConfig; // The rule is of the form { use/loader: { loader: 'name' }}, so put options on the use/loader // because that gives us a place to prepend the new value
if (!webpackConfig.entry || typeof webpackConfig.entry === 'string' || webpackConfig.entry instanceof Array) {
throw new Error('Cannot enable React HMR because \'entry\' in Webpack config is not of the form { name: value }');
}
const entryConfig = webpackConfig.entry as webpack.Entry;
Object.getOwnPropertyNames(entryConfig).forEach(entrypointName => {
if (typeof(entryConfig[entrypointName]) === 'string') {
// Normalise to array
entryConfig[entrypointName] = [entryConfig[entrypointName] as string];
}
// Ensure the config has an 'options' (or a legacy 'query') let entryValueArray = entryConfig[entrypointName] as string[];
let optionsObject = if (entryValueArray.indexOf(reactHotLoaderPatch) < 0) {
(configItemWithOptions as webpack.NewLoader).options // Recommended config format for Webpack 2.x entryValueArray.unshift(reactHotLoaderPatch);
|| (configItemWithOptions as webpack.OldLoaderRule).query; // Legacy
if (!optionsObject) {
// If neither options nor query was set, define a new value,
// using the legacy format ('query') for compatibility with Webpack 1.x
optionsObject = (configItemWithOptions as webpack.OldLoaderRule).query = {};
}
// Ensure Babel plugins includes 'react-transform'
const plugins = optionsObject['plugins'] = optionsObject['plugins'] || [];
const hasReactTransform = plugins.some(p => p && p[0] === 'react-transform');
if (!hasReactTransform) {
plugins.push(['react-transform', {}]);
}
// Ensure 'react-transform' plugin is configured to use 'react-transform-hmr'
plugins.forEach(pluginConfig => {
if (pluginConfig && pluginConfig[0] === 'react-transform') {
const pluginOpts = pluginConfig[1] = pluginConfig[1] || {};
const transforms = pluginOpts.transforms = pluginOpts.transforms || [];
const hasReactTransformHmr = transforms.some(t => t.transform === 'react-transform-hmr');
if (!hasReactTransformHmr) {
transforms.push({
transform: 'react-transform-hmr',
imports: ['react'],
locals: ['module'] // Important for Webpack HMR
});
}
}
});
} }
}); });
} }
function containsLoader(loadersArray: webpack.Loader[], loaderName: string) {
return loadersArray.some(loader => {
// Allow 'use' values to be either { loader: 'name' } or 'name'
// No need to support legacy webpack.OldLoader
const actualLoaderName = (loader as webpack.NewLoader).loader || (loader as string);
return actualLoaderName && new RegExp(`\\b${ loaderName }\\b`).test(actualLoaderName);
});
}

View File

@@ -1 +1,6 @@
export { addReactHotModuleReplacementBabelTransform } from './HotModuleReplacement'; export { addReactHotModuleReplacementConfig } from './HotModuleReplacement';
// Temporarily alias addReactHotModuleReplacementConfig as addReactHotModuleReplacementBabelTransform for backward
// compatibility with aspnet-webpack 1.x. In aspnet-webpack 2.0, we can drop the old name (and also deprecate
// some other no-longer-supported functionality, such as LoadViaWebpack).
export { addReactHotModuleReplacementConfig as addReactHotModuleReplacementBabelTransform } from './HotModuleReplacement';