React 16.5.2 + webpack 4.2 多页面配置

450 阅读4分钟

前言

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