前言
最近把活动项目从 React 15 + webpack 2.7 迁移到 React 16.5.2 + webpack 4.2。踩过的一些坑和大家分享一下。当然,本文着重介绍 webpack 4.2 的配置。React 16 方面升级的坑大家可以自行谷歌一下,或者评论私聊都行。
一、安装依赖
"devDependencies": { "autoprefixer": "^9.1.5", "babel-core": "^6.26.3", "babel-plugin-react-transform": "^3.0.0", "babel-plugin-transform-es2015-arrow-functions": "^6.22.0", "babel-polyfill": "^6.26.0", "babel-preset-es2015": "^6.24.1", "babel-preset-react": "^6.24.1", "babel-preset-react-hmre": "^1.1.1", "babel-preset-stage-0": "^6.24.1", "cross-env": "^5.2.0", "css-loader": "^1.0.0", "cssnano": "^4.1.4", "eventsource-polyfill": "^0.9.6", "file-loader": "^2.0.0", "html-webpack-plugin": "^3.2.0", "img-loader": "^3.0.0", "mini-css-extract-plugin": "^0.4.4", "optimize-css-assets-webpack-plugin": "^5.0.1", "postcss-loader": "^3.0.0", "react-transform-hmr": "^1.0.4", "sass-loader": "^7.1.0", "url-loader": "^1.1.2", "webpack-dev-server": "^3.1.9", "webpack-hot-middleware": "^2.24.3" },"dependencies": { "babel": "^6.23.0", "babel-loader": "^7.1.5", "core-js": "^2.5.7", "es6-promise": "^4.2.5", "express": "^4.16.4", "node-sass": "^4.9.3", "qs": "^6.5.2", "react": "^16.5.2", "react-addons-update": "^15.6.2", "react-dom": "^16.5.2", "react-hot-loader": "^4.3.11", "react-router": "^4.3.1", "react-router-dom": "^4.3.1", "uglifyjs-webpack-plugin": "^2.0.1", "validator": "^10.8.0", "webpack": "^4.20.2", "webpack-cli": "^3.1.2", "webpack-merge": "^4.1.4" }复制代码
这是 package.json 依赖方面的配置,大家可以按需增减。接下来我们看一下 npm script :
"scripts": { "build": "cross-env NODE_ENV=production webpack --mode production", "dev": "cross-env NODE_ENV=development webpack --mode development", "start": "node server.js", "test": "cross-env NODE_ENV=test webpack --mode production" },复制代码
cross-env 是为了跨平台兼容,自行 npm i cross-env --save
不多说了。
webpack 需要在 --mode 后传参打包环境,没有就默认是 production 。
NODE_ENV=production
这里是为了兼容 webpack 2.7 时的 webpack 配置。大家可以根据 --mode production
传进去的参数优化,只是我比较懒就保留着。
二、webpack 配置
1, 读取多页面入口
const webpack = require('webpack');const path = require('path')const HtmlWebpackPlugin = require('html-webpack-plugin')const fs = require('fs');const files = fs.readdirSync('./asset/js/entry/');const plugins = []; //将会使用到的插件plugins.push(new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)}));const entriesName = files.map(function (file) { return file.replace(/.jsx?$/, '');});entriesName.forEach(function (entryName) { plugins.push(new HtmlWebpackPlugin({ filename: entryName + '.html', template: 'asset/html/' + entryName + '.html', chunks: ['common', entryName] }));});const entry = { // vendor: ["react"]};entriesName.forEach(function (entryName) { entry[entryName] = ['./asset/js/entry/' + entryName + '.js'];}); 复制代码
然后就是 entry 和 output 目录的配置了:
entry: entry,output: { publicPath: "", path: path.resolve(__dirname, './dist/resources/h5/activity'), filename: '[name].js?_=[hash]'},复制代码
rules 的配置:
module: { rules: [ { test: /\.jsx?/, // 匹配文件路径的正则表达式,通常我们都是匹配文件类型后缀 include: [ path.resolve(__dirname, 'asset/js') // 指定哪些路径下的文件需要经过 loader 处理 ], use: 'babel-loader', // 指定使用的 loader } ]}复制代码
ok。其实到这里和 webpack 4 以下的配置基本没什么区别。各位请轻喷。
这是对 jsx 文件格式的匹配规则。本文项目采用 scss ,所以对于 样式方面的匹配规则如下:
module: { rules: [ { test: /\.(scss|css)$/, use: [ MiniCssExtractPlugin.loader, //注意此处 { loader: 'css-loader', options: { minimize: { safe: true }, sourceMap: true } }, { loader: 'postcss-loader', options: { autoprefixer: { browsers: ['last 2 versions'] }, plugins: () => [ autoprefixer ], sourceMap: true }, }, { loader: 'sass-loader', options: { sourceMap: true } } ] },]}复制代码
到这里。我们和 webpack 4 以下的配置差别就体现出来了。先看一下以前我们是怎么配置:
const webpack = require('webpack'); const path = require('path'); const ExtractTextPlugin = require("extract-text-webpack-plugin"); //独立打包css模块;const HtmlWebpackPlugin = require('html-webpack-plugin'); //html模板模块;const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); //压缩CSS模块;//此处省略啰嗦重复若干项....module.exports = { ... module: { //模块; rules: [ ... { //正则匹配后缀.css文件; test: /\.css$/, //使用html-webpack-plugin插件独立css到一个文件; use: ExtractTextPlugin.extract({ //加载css-loader、postcss-loader(编译顺序从下往上)转译css use: [{ loader : 'css-loader?importLoaders=1', }, { loader : 'postcss-loader', //配置参数; options: { //从postcss插件autoprefixer 添加css3前缀; plugins: function() { return [ //加载autoprefixer并配置前缀,可加载更多postcss插件; require('autoprefixer')({ browsers: ['ios >= 7.0'] }) ]; } } }] }) }, { //正则匹配后缀.sass、.scss文件; test: /\.(sass|scss)$/, //使用html-webpack-plugin插件独立css到一个文件; use: ExtractTextPlugin.extract({ use: [{ loader : 'css-loader?importLoaders=1', }, { loader : 'postcss-loader', //配置参数; options: { plugins: function() { return [ require('autoprefixer')({ browsers: ['ios >= 7.0'] }) ]; } } }, { //加载sass-loader同时也得安装node-sass; loader: "sass-loader", //配置参数; options: { //sass的sourceMap sourceMap:true, //输出css的格式两个常用选项:compact({}行), compressed(压缩一行) outputStyle : 'compact' } } ] }) }, .... ]}, ...};复制代码
对比一下,可以看到,我们不再采用 extract-text-webpack-plugin 来打包 css 了。原因是 extract-text-webpack-plugin 还没有完全支持 webpack 4 。当然你非要用也可以。 extract-text-webpack-plugin 有 4.0 beta 版的支持 webpack 4。
所以我们的 webpack 配置还有引入下面两个包:
const MiniCssExtractPlugin = require('mini-css-extract-plugin');const autoprefixer = require('autoprefixer');复制代码
然后在 plugins 中:
plugins.push(new MiniCssExtractPlugin({ filename: '[name].css?_=[hash]'}));复制代码
这是打包,压缩这么玩:
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");....plugins.push( new OptimizeCSSAssetsPlugin({ assetNameRegExp: /\.css\.*(?!.*map)/g, //注意不要写成 /\.css$/g cssProcessor: require('cssnano'), cssProcessorOptions: { discardComments: { removeAll: true }, // 避免 cssnano 重新计算 z-index safe: true, // cssnano 集成了autoprefixer的功能 // 会使用到autoprefixer进行无关前缀的清理 // 关闭autoprefixer功能 // 使用postcss的autoprefixer功能 autoprefixer: false }, canPrint: true }));复制代码
接着声明一个 optimization 字段:
module.exports = { ... optimization: { minimizer: [ new OptimizeCSSAssetsPlugin({}) ] }}复制代码
webpack 4 本来是自带 js 压缩功能的。但这么配置之后我们发现 js 没有被压缩了~
研究了一遍之后发现是配置 optimization.minimizer 之后需要手动配置 js 的压缩。果然这个坑有点不科学。
于是就变成这样:
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");......minimizer: [ //不声明的话webpack 4会自动进行压缩。声明之后需要手动压缩。 new UglifyJsPlugin({ cache: true, parallel: true, sourceMap: true // set to true if you want JS source maps }), new OptimizeCSSAssetsPlugin({})]复制代码
最后是公共模块的提取。还记 webpack 2.7 是用什么方式提取的?
对。是用 webpack 自带的插件:
new webpack.optimize.CommonsChunkPlugin('common');复制代码
当然,在 webpack 4 我们也不需要安装其他依赖,直接作为配置写进去就可以了:
optimization: { splitChunks: { cacheGroups: { polyfill: { //polyfill test: /[\\/]node_modules[\\/](core-js|raf|@babel|babel)[\\/]/, name: 'polyfill', priority: 2, }, vendor: { test: /react|lodash|ajax|GLOBAL|object-assign|schedule/, chunks: "initial", name: "common", enforce: true, }, }, }, minimizer: [ ... ]复制代码
到这里就配置完成了。最后贴一下 .babelrc 的代码:
{ "presets": [["es2015", { "modules": false}], "stage-0", "react"], "plugins": [ "react-hot-loader/babel" ]}复制代码