mirror of
https://github.com/aspnet/JavaScriptServices.git
synced 2025-12-22 17:47:53 +00:00
Update aspnet-webpack-react to v2.0.0, now supporting Webpack 2+ and React Hot Loader 3+ only
This commit is contained in:
@@ -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
|
||||
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.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
@@ -16,22 +16,13 @@
|
||||
"type": "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": {
|
||||
"@types/webpack": "^2.2.0",
|
||||
"rimraf": "^2.5.4",
|
||||
"typescript": "^2.0.0",
|
||||
"webpack": "^1.12.14"
|
||||
"webpack": "^2.2.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"webpack": "^1.13.2 || ^2.2.0"
|
||||
"webpack": "^2.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,78 +1,66 @@
|
||||
import * as webpack from 'webpack';
|
||||
type OldOrNewModule = webpack.OldModule & webpack.NewModule;
|
||||
|
||||
export function addReactHotModuleReplacementBabelTransform(webpackConfig: webpack.Configuration) {
|
||||
const moduleConfig = webpackConfig.module as OldOrNewModule;
|
||||
const moduleRules = moduleConfig.rules // Webpack >= 2.1.0 beta 23
|
||||
|| moduleConfig.loaders; // Legacy/back-compat
|
||||
const reactHotLoaderWebpackLoader = 'react-hot-loader/webpack';
|
||||
const reactHotLoaderPatch = 'react-hot-loader/patch';
|
||||
const supportedTypeScriptLoaders = ['ts-loader', 'awesome-typescript-loader'];
|
||||
|
||||
export function addReactHotModuleReplacementConfig(webpackConfig: webpack.Configuration) {
|
||||
const moduleConfig = webpackConfig.module as webpack.NewModule;
|
||||
const moduleRules = moduleConfig.rules;
|
||||
if (!moduleRules) {
|
||||
return; // Unknown rules list format
|
||||
return; // Unknown rules list format. Might be Webpack 1.x, which is not supported.
|
||||
}
|
||||
|
||||
moduleRules.forEach(rule => {
|
||||
// Allow rules/loaders entries to be either { loader: ... } or { use: ... }
|
||||
// Ignore other config formats (too many combinations to support them all)
|
||||
let loaderConfig =
|
||||
(rule as webpack.NewUseRule).use // Recommended config format for Webpack 2.x
|
||||
|| (rule as webpack.LoaderRule).loader; // Typical config format for Webpack 1.x
|
||||
if (!loaderConfig) {
|
||||
return; // Not a supported rule format (e.g., an array)
|
||||
// Find the rule that loads TypeScript files, and prepend 'react-hot-loader/webpack'
|
||||
// to its array of loaders
|
||||
for (let ruleIndex = 0; ruleIndex < moduleRules.length; ruleIndex++) {
|
||||
// We only support NewUseRule (i.e., { use: ... }) because OldUseRule doesn't accept array values
|
||||
const rule = moduleRules[ruleIndex] as webpack.NewUseRule;
|
||||
if (!rule.use) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Allow use/loader values to be either { loader: 'name' } or 'name'
|
||||
// We don't need to support other possible ways of specifying loaders (e.g., arrays),
|
||||
// so skip unrecognized formats.
|
||||
const loaderNameString =
|
||||
(loaderConfig as (webpack.OldLoader | webpack.NewLoader)).loader
|
||||
|| (loaderConfig as string);
|
||||
if (!loaderNameString || (typeof loaderNameString !== 'string')) {
|
||||
return; // Not a supported loader format (e.g., an array)
|
||||
// We're looking for the first 'use' value that's a TypeScript loader
|
||||
const loadersArray = rule.use instanceof Array ? rule.use : [rule.use];
|
||||
const isTypescriptLoader = supportedTypeScriptLoaders.some(typeScriptLoaderName => containsLoader(loadersArray, typeScriptLoaderName));
|
||||
if (!isTypescriptLoader) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the babel-loader entry
|
||||
if (loaderNameString.match(/\bbabel-loader\b/)) {
|
||||
// If the rule is of the form { use: 'name' }, then replace it
|
||||
// with { use: { loader: 'name' }} so we can attach options
|
||||
if ((rule as webpack.NewUseRule).use && typeof loaderConfig === 'string') {
|
||||
loaderConfig = (rule as webpack.NewUseRule).use = { loader: loaderConfig };
|
||||
// This is the one - prefix it with the react-hot-loader loader
|
||||
// (unless it's already in there somewhere)
|
||||
if (!containsLoader(loadersArray, reactHotLoaderWebpackLoader)) {
|
||||
loadersArray.unshift(reactHotLoaderWebpackLoader);
|
||||
rule.use = loadersArray; // In case we normalised it to an array
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const configItemWithOptions = typeof loaderConfig === 'string'
|
||||
? rule // The rule is of the form { loader: 'name' }, so put options on the rule
|
||||
: loaderConfig; // The rule is of the form { use/loader: { loader: 'name' }}, so put options on the use/loader
|
||||
|
||||
// Ensure the config has an 'options' (or a legacy 'query')
|
||||
let optionsObject =
|
||||
(configItemWithOptions as webpack.NewLoader).options // Recommended config format for Webpack 2.x
|
||||
|| (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 the entrypoint is prefixed with 'react-hot-loader/patch' (unless it's already in there).
|
||||
// We only support entrypoints of the form { name: value } (not just 'name' or ['name'])
|
||||
// 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 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
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
let entryValueArray = entryConfig[entrypointName] as string[];
|
||||
if (entryValueArray.indexOf(reactHotLoaderPatch) < 0) {
|
||||
entryValueArray.unshift(reactHotLoaderPatch);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user