前言
最近把活动项目从 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" ]
}