技本功丨create-react-app升级webpack4填坑
都说create-react-app是业界最优秀的 React 应用开发工具之一。But,webpack4都更新到v4.20.2 它居然~还没升级,简直~不能忍
看到webpack这更新速度,本人慌得一批,刚好抽空搭建react-andt-mobx脚手架准备升级~
So,在此分享一下升级攻略,收好不谢!
01 安装
npm install -g create-react-app
02 创建应用
//create-react-app是全局命令来创建react项目
create-react-app react-demo
03 自定义webpack配置
npm run eject //自定义模式,暴露出webpack配置,不可逆
04 着手自定义webpack配置
1、目标结构
当然webpack升级准备,调整create-react-app的目录结构已符合我们项目开发的规范是必不可少的。这里重点需关注的为build目录下的一下文件:
paths文件更改打包路经更改:
在项目开发的过程中host配置以及proxy代理是常见的配置,在create-react-app中配置在package.json配置下,灵活性相对不太好,提取webpack中server.js配置:
别忘了修改webpackDevServer.config.js下引用host及proxy下的引用哦。
此时,目录改造全部完毕
渐入佳境,赶紧进入正题
2、webpack3升级webpack4
webpack4新出了一个mode模式,有三种选择,none,development,production.最直观的感受就是你可以少些很多配置,因为一旦你开启了mode模式,webpack4就会给你设置很多基本的东西。
development模式下,将侧重于功能调试和优化开发体验,包含如下内容:
浏览器调试工具
开发阶段的详细错误日志和提示
快速和优化的增量构建机制
production模式下,将侧重于模块体积优化和线上部署,包含如下内容:
开启所有的优化代码
更小的bundle大小
去除掉只在开发阶段运行的代码
Scope hoisting和Tree-shaking
自动启用uglifyjs对代码进行压缩
话不多说,下安装:
yarn add webpack webpack-cli webpack-dev-server
这3个包是webpack4的基础功能
webpack 在 webpack 4 里将命令行相关的都迁移至 webpack-cli 包
webpack-dev-server为实时监控文件变化包
安装完成之后,请保持淡定,
得先运行一下,万一直接能打包呢
或许它偷偷做了兼容处理呢,
梦想还是要有呢,虽然...
Plugin could not be registered at 'html-webpack-plugin-before-html-processing'. Hook was not found. BREAKING CHANGE: There need to exist a hook at 'this.hooks'. To create a compatiblity layer for this hook, hook into 'this._pluginCompat'.
果然还是熟悉的味道
上面这个问题是HtmlWebpackPlugin 和 react-dev-utils/InterpolateHtmlPlugin 先后顺序问题,调整下他们的顺序
new HtmlWebpackPlugin({
inject: true,
template: paths.appHtml,
chunksSortMode: 'none',
}),
new InterpolateHtmlPlugin(env.raw),
嗯,不出意外的话,搞定
再跑一下代码,这个时候可能就出现了一些百度不到问题,你需要升级各种loader了,
less-loader,sass-loader style-loader url-loader
具体命令:
yarn add less-loader@next
和上面的命令相同,依次升级,运行代码,查看报错,缺啥补啥,成功的选择,值得拥有....
需要升级的有:
html-webpack-plugin
react-dev-utils
修改代码完整篇 webpack.config.dev.js:
const autoprefixer = require('autoprefixer');
const path = require('path'); const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
const eslintFormatter = require('react-dev-utils/eslintFormatter');
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
const getClientEnvironment = require('./env'); const paths = require('./paths');
function resolve (dir) { return path.join(__dirname, '..', dir) } const publicPath = '/';
const publicUrl = '';
const env = getClientEnvironment(publicUrl);
module.exports = {
mode: 'development',
devtool: 'cheap-module-source-map',
entry: [ require.resolve('./polyfills'),
require.resolve('react-dev-utils/webpackHotDevClient'),
paths.appIndexJs, ],
output: {
pathinfo: true,
filename: 'static/js/bundle.js',
chunkFilename: 'static/js/[name].chunk.js',
publicPath: publicPath,
devtoolModuleFilenameTemplate: info =>
path.resolve(info.absoluteResourcePath).replace(/\/g, '/'), },
resolve: { modules: ['node_modules', paths.appNodeModules].concat(
process.env.NODE_PATH.split(path.delimiter).filter(Boolean) ),
extensions: ['.web.js', '.mjs', '.js', '.json', '.web.jsx', '.jsx','.less','.scss'],
alias: {
'@': resolve('src'),
'public': resolve('src/public'),
'components': resolve('src/components'),
'pages': resolve('src/pages'),
'api': resolve('src/api'),
'mock': resolve('src/public/mock'), },
plugins: [ new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
],
},
module: {
strictExportPresence: true,
rules: [
{
test: /.(js|jsx|mjs)$/,
enforce: 'pre',
use: [
{
options: {
formatter: eslintFormatter,
eslintPath: require.resolve('eslint'),
},
loader: require.resolve('eslint-loader'),
},
],
include: paths.appSrc,
},
{
oneOf: [
{
test: [/.bmp$/, /\.gif$/, /.jpe?g$/, /\.png$/],
loader: require.resolve('url-loader'),
options: {
limit: 10000,
name: 'static/media/[name].[hash:8].[ext]',
},
},
{
test: /.(js|jsx|mjs)$/,
include: paths.appSrc,
loader: require.resolve('babel-loader'),
options: {
cacheDirectory: true,
}
},
{
test: /.(css|less)$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
},
},
{
loader: require.resolve('postcss-loader'),
options: {
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9', // React doesn't support IE8 anyway
],
flexbox: 'no-2009',
}),
],
},
},
{
loader: require.resolve('less-loader') // compiles Less to CSS
},
],
},
{
test: /.(css|scss)$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
},
},
{
loader: require.resolve('postcss-loader'),
options: {
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9', // React doesn't support IE8 anyway
],
flexbox: 'no-2009',
}),
],
},
},
{
loader: require.resolve('sass-loader') // compiles Less to CSS
},
],
},
{
exclude: [/.(js|jsx|mjs)$/,/\.(css|less)$/, /.html$/, /\.json$/],
loader: require.resolve('file-loader'),
options: {
name: 'static/media/[name].[hash:8].[ext]',
},
},
],
},
],
},
plugins: [
new HtmlWebpackPlugin({
inject: true,
template: paths.appHtml,
chunksSortMode: 'none',
}),
new InterpolateHtmlPlugin(env.raw),
new webpack.DefinePlugin(env.stringified),
new webpack.HotModuleReplacementPlugin(),
new CaseSensitivePathsPlugin(),
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
new webpack.IgnorePlugin(/^./locale$/, /moment$/),
],
node: { dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty',
},
performance: {
hints: false,
},
optimization: {
namedModules: true,
nodeEnv: 'development',
},
};
还需注意的是webpack4对ExtractTextWebpackPlugin做了调整,建议选用新的CSS文件提取插件mini-css-extract-plugin。生产环境下我们需要做一下配置调整:
webpack.config.prod.js
const autoprefixer = require('autoprefixer');
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ManifestPlugin = require('webpack-manifest-plugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
const eslintFormatter = require('react-dev-utils/eslintFormatter');
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const paths = require('./paths');
const getClientEnvironment = require('./env');
const theme = require('../antd-theme.js');
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
const publicPath = paths.servedPath;
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
const publicUrl = publicPath.slice(0, -1);
const env = getClientEnvironment(publicUrl);
if (env.stringified['process.env'].NODE_ENV !== '"production"') {
throw new Error('Production builds must have NODE_ENV=production.');
}
module.exports = {
mode: "production",
bail: true,
devtool: shouldUseSourceMap ? 'source-map' : false,
entry: [require.resolve('./polyfills'), paths.appIndexJs],
output: {
path: paths.appBuild,
filename: 'static/js/[name].[chunkhash:8].js',
chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js',
publicPath: publicPath,
devtoolModuleFilenameTemplate: info =>
path
.relative(paths.appSrc, info.absoluteResourcePath)
.replace(/\/g, '/'),
},
resolve: {
modules: ['node_modules', paths.appNodeModules].concat(
// It is guaranteed to exist because we tweak it in env.js
process.env.NODE_PATH.split(path.delimiter).filter(Boolean)
),
extensions: ['.web.js', '.mjs', '.js', '.json', '.web.jsx', '.jsx','.less'],
alias: {
'@': resolve('src'),
'public': resolve('src/public'),
'components': resolve('src/components'),
'pages': resolve('src/pages'),
'mock': resolve('src/public/mock'),
'api': resolve('src/api'),
'react-native': 'react-native-web',
},
plugins: [
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
],
},
module: {
strictExportPresence: true,
rules: [
{
test: /.(js|jsx|mjs)$/,
enforce: 'pre',
use: [
{
options: {
formatter: eslintFormatter,
eslintPath: require.resolve('eslint'),
},
loader: require.resolve('eslint-loader'),
},
],
include: paths.appSrc,
},
{
oneOf: [
{
test: [/.bmp$/, /\.gif$/, /.jpe?g$/, /\.png$/],
loader: require.resolve('url-loader'),
options: {
limit: 10000,
name: 'static/media/[name].[hash:8].[ext]',
},
},
// Process JS with Babel.
{
test: /.(js|jsx|mjs)$/,
include: paths.appSrc,
loader: require.resolve('babel-loader'),
options: {
plugins: [
['import', [{ libraryName: 'antd', style: true }]], // import less
],
compact: true,
},
},
{
test: /.(less|css)$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"less-loader?{modifyVars:" + JSON.stringify(theme) + "}"
],
},
{
test: /.(scss|sass)$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"sass-loader"
]
},
{
loader: require.resolve('file-loader'),
exclude: [/.(js|jsx|mjs)$/,/\.(css|less)$/, /.html$/, /\.json$/],
options: {
name: 'static/media/[name].[hash:8].[ext]',
},
},
],
},
],
},
optimization: {
runtimeChunk: {
name: 'manifest'
},
minimize: true,
noEmitOnErrors: true,
minimizer: [
new UglifyJsPlugin({
cache: true,
parallel: true,
sourceMap: false
}),
new OptimizeCSSAssetsPlugin({})
],
splitChunks: {
minSize: 30000,
maxSize: 3000000,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
name: true,
cacheGroups: {
vendor: {
chunks: 'initial',
name: 'vendor',
test: 'vendor'
},
echarts: {
chunks: 'all',
name: 'echarts',
test: /[\/]echarts[\/]/,
}
}
}
},
plugins: [
new HtmlWebpackPlugin({
inject: true,
template: paths.appHtml,
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
}),
new InterpolateHtmlPlugin(env.raw),
new webpack.DefinePlugin(env.stringified),
new webpack.NamedModulesPlugin(),
new webpack.optimize.OccurrenceOrderPlugin(true),
new MiniCssExtractPlugin({
filename: "css/[name].[hash].css",
chunkFilename: "css/[name].[hash].css"
}),
new ManifestPlugin({
fileName: 'asset-manifest.json',
}),
new SWPrecacheWebpackPlugin({
dontCacheBustUrlsMatching: /.w{8}./,
filename: 'service-worker.js',
logger(message) {
if (message.indexOf('Total precache size is') === 0) {
return;
}
if (message.indexOf('Skipping static resource') === 0) {
return;
}
console.log(message);
},
minify: true,
navigateFallback: publicUrl + '/index.html',
navigateFallbackWhitelist: [/^(?!/__).*/],
staticFileGlobsIgnorePatterns: [/.map$/, /asset-manifest\.json$/],
}),
new webpack.IgnorePlugin(/^./locale$/, /moment$/),
],
node: {
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty',
},
};
此时基本完成了webpack4升级的改造
但是运行下,还有个隐形的坑待处理……
因为引入了andt组件库,
高版本对less编译会出错。。
噗~升级了一路,碰到个需要降级处理的,
好,降低less版本 "less": "2.7.3",
运行一波,亲测完美~
最后附package.json
{
"dependencies": {
"antd": "^3.9.0-beta.6",
"autoprefixer": "7.1.6",
"axios": "^0.18.0",
"babel-core": "6.26.0",
"babel-eslint": "7.2.3",
"babel-jest": "20.0.3",
"babel-loader": "7.1.2",
"babel-plugin-import": "^1.8.0",
"babel-polyfill": "^6.26.0",
"babel-preset-react-app": "^3.1.1",
"babel-runtime": "^6.26.0",
"case-sensitive-paths-webpack-plugin": "2.1.1",
"chalk": "1.1.3",
"classnames": "^2.2.6",
"core-decorators": "^0.20.0",
"create-keyframe-animation": "^0.1.0",
"css-loader": "0.28.7",
"dotenv": "4.0.0",
"dotenv-expand": "4.2.0",
"eslint": "4.10.0",
"eslint-config-react-app": "^2.1.0",
"eslint-loader": "^2.1.1",
"eslint-plugin-flowtype": "2.39.1",
"eslint-plugin-import": "2.8.0",
"eslint-plugin-jsx-a11y": "5.1.1",
"eslint-plugin-react": "7.4.0",
"express": "^4.16.3",
"fastclick": "^1.0.6",
"file-loader": "2.0.0",
"fs-extra": "3.0.1",
"good-storage": "^1.0.1",
"history": "^4.7.2",
"html-webpack-plugin": "^3.2.0",
"immutable": "^3.8.2",
"jest": "20.0.4",
"js-base64": "^2.4.3",
"jsonp": "^0.2.1",
"less": "2.7.3",
"less-loader": "^4.0.1",
"lyric-parser": "^1.0.1",
"mini-css-extract-plugin": "^0.4.3",
"mobx": "^4.1.1",
"mobx-react": "^5.0.0",
"mobx-react-devtools": "^5.0.1",
"node-sass": "^4.9.3",
"object-assign": "4.1.1",
"optimize-css-assets-webpack-plugin": "^5.0.1",
"postcss-flexbugs-fixes": "3.2.0",
"postcss-loader": "2.0.8",
"promise": "8.0.1",
"prop-types": "^15.6.1",
"raf": "3.4.0",
"react": "^16.3.0",
"react-addons-css-transition-group": "^15.6.2",
"react-dev-utils": "^6.0.0-next.a671462c",
"react-dom": "^16.3.0",
"react-hot-loader": "^4.3.4",
"react-lazyload": "^2.3.0",
"react-loadable": "^5.5.0",
"react-router-dom": "^4.2.2",
"react-transition-group": "^2.3.1",
"sass-loader": "^7.1.0",
"style-loader": "0.19.0",
"sw-precache-webpack-plugin": "^0.11.5",
"uglifyjs-webpack-plugin": "^2.0.1",
"url-loader": "0.6.2",
"webpack": "^4.19.0",
"webpack-cli": "^3.1.0",
"webpack-dev-server": "^3.1.8",
"webpack-manifest-plugin": "^2.0.4",
"whatwg-fetch": "2.0.3"
},
"scripts": {
"start": "node scripts/start.js",
"build": "node scripts/build.js"
},
"babel": {
"presets": [
"react-app"
],
"plugins": [
"transform-decorators-legacy"
]
},
"eslintConfig": {
"extends": "react-app"
},
"devDependencies": {
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"better-scroll": "^1.9.1"
},
}
结 语
前端的框架更新速度,悄无声息又超乎想象,需要不断保持着对前端的热情和主动,研究一波前沿的技术架构和设计理念....
比如参加D2前端技术沙龙~
恍惚中,感觉这一波操作
好像还能再优化~优化~