技本功丨create-react-app升级webpack4填坑

技本功丨create-react-app升级webpack4填坑
都说create-react-app是业界最优秀的 React 应用开发工具之一。But,webpack4都更新到v4.20.2 它居然~还没升级,简直~不能忍

技本功丨create-react-app升级webpack4填坑

看到webpack这更新速度,本人慌得一批,刚好抽空搭建react-andt-mobx脚手架准备升级~

So,在此分享一下升级攻略,收好不谢!

技本功丨create-react-app升级webpack4填坑

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、目标结构

技本功丨create-react-app升级webpack4填坑

当然webpack升级准备,调整create-react-app的目录结构已符合我们项目开发的规范是必不可少的。这里重点需关注的为build目录下的一下文件:

技本功丨create-react-app升级webpack4填坑

paths文件更改打包路经更改:

技本功丨create-react-app升级webpack4填坑

在项目开发的过程中host配置以及proxy代理是常见的配置,在create-react-app中配置在package.json配置下,灵活性相对不太好,提取webpack中server.js配置:

技本功丨create-react-app升级webpack4填坑

别忘了修改webpackDevServer.config.js下引用host及proxy下的引用哦。

此时,目录改造全部完毕

渐入佳境,赶紧进入正题

技本功丨create-react-app升级webpack4填坑

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为实时监控文件变化包

安装完成之后,请保持淡定,

得先运行一下,万一直接能打包呢

或许它偷偷做了兼容处理呢,

梦想还是要有呢,虽然...

技本功丨create-react-app升级webpack4填坑

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",

运行一波,亲测完美~

技本功丨create-react-app升级webpack4填坑

最后附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前端技术沙龙~

恍惚中,感觉这一波操作

好像还能再优化~优化~

相关推荐