在前面的章节中我们学习了webpack
的基础配置(五大核心属性),以及一些高级优化配置(source map、Tree Shaking、 HMR、Code Split
等),并且分别开发了webpack.dev.js
(开发环境配置),和webpack.prod.js
(生产环境配置)。
这一章节我们将两个配置合并为一个整体配置,内部差异通过process.env.NODE_ENV
环境变量区分,并再结合一下react的插件,自己实现一个react-cli脚手架
一、react-cli开发步骤
1. webpack.dev.js和webpack.prod.js的配置
- webpack.dev.js
const path = require("path");
const EslintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");// 返回处理样式loader函数
const getStyleLoaders = (pre) => {return ["style-loader","css-loader",{// 处理css兼容性问题// 配合package.json中browserslist来指定兼容性loader: "postcss-loader",options: {postcssOptions: {plugins: ["postcss-preset-env"],},},},pre,].filter(Boolean);
};module.exports = {entry: "./src/main.js",output: {path: undefined,filename: "static/js/[name].js",chunkFilename: "static/js/[name].chunk.js",assetModuleFilename: "static/media/[hash:10][ext][query]",},module: {rules: [// 处理css{test: /\.css$/,use: getStyleLoaders(),},{test: /\.less$/,use: getStyleLoaders("less-loader"),},{test: /\.s[ac]ss$/,use: getStyleLoaders("sass-loader"),},{test: /\.styl$/,use: getStyleLoaders("stylus-loader"),},// 处理图片{test: /\.(jpe?g|png|gif|webp|svg)$/,type: "asset",parser: {dataUrlCondition: {maxSize: 10 * 1024,},},},// 处理其他资源{test: /\.(woff2?|ttf)$/,type: "asset/resource",},// 处理js{test: /\.jsx?$/,include: path.resolve(__dirname, "../src"),loader: "babel-loader",options: {cacheDirectory: true,cacheCompression: false,plugins: ["react-refresh/babel", // 激活js的HMR],},},],},// 处理htmlplugins: [new EslintWebpackPlugin({context: path.resolve(__dirname, "../src"),exclude: "node_modules",cache: true,cacheLocation: path.resolve(__dirname, "../node_modules/.cache/.eslintcache"),}),new HtmlWebpackPlugin({template: path.resolve(__dirname, "../public/index.html"),}),new ReactRefreshWebpackPlugin(), // 激活js的HMR],mode: "development",devtool: "cheap-module-source-map",optimization: {splitChunks: {chunks: "all",},runtimeChunk: {name: (entrypoint) => `runtime~${entrypoint.name}.js`,},},// webpack解析模块加载选项resolve: {// 自动补全文件扩展名extensions: [".jsx", ".js", ".json"],},devServer: {host: "localhost",port: 3000,open: true,hot: true, // 开启HMRhistoryApiFallback: true, // 解决前端路由刷新404问题},
};
- webpack.prod.js
const path = require("path");
const EslintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin");
const TerserWebpackPlugin = require("terser-webpack-plugin");
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
const CopyPlugin = require("copy-webpack-plugin");// 返回处理样式loader函数
const getStyleLoaders = (pre) => {return [MiniCssExtractPlugin.loader,"css-loader",{// 处理css兼容性问题// 配合package.json中browserslist来指定兼容性loader: "postcss-loader",options: {postcssOptions: {plugins: ["postcss-preset-env"],},},},pre,].filter(Boolean);
};module.exports = {entry: "./src/main.js",output: {path: path.resolve(__dirname, "../dist"),filename: "static/js/[name].[contenthash:10].js",chunkFilename: "static/js/[name].[contenthash:10].chunk.js",assetModuleFilename: "static/media/[hash:10][ext][query]",clean: true,},module: {rules: [// 处理css{test: /\.css$/,use: getStyleLoaders(),},{test: /\.less$/,use: getStyleLoaders("less-loader"),},{test: /\.s[ac]ss$/,use: getStyleLoaders("sass-loader"),},{test: /\.styl$/,use: getStyleLoaders("stylus-loader"),},// 处理图片{test: /\.(jpe?g|png|gif|webp|svg)$/,type: "asset",parser: {dataUrlCondition: {maxSize: 10 * 1024,},},},// 处理其他资源{test: /\.(woff2?|ttf)$/,type: "asset/resource",},// 处理js{test: /\.jsx?$/,include: path.resolve(__dirname, "../src"),loader: "babel-loader",options: {cacheDirectory: true,cacheCompression: false,},},],},// 处理htmlplugins: [new EslintWebpackPlugin({context: path.resolve(__dirname, "../src"),exclude: "node_modules",cache: true,cacheLocation: path.resolve(__dirname, "../node_modules/.cache/.eslintcache"),}),new HtmlWebpackPlugin({template: path.resolve(__dirname, "../public/index.html"),}),new MiniCssExtractPlugin({filename: "static/css/[name].[contenthash:10].css",chunkFilename: "static/css/[name].[contenthash:10].chunk.css",}),new CopyPlugin({patterns: [{from: path.resolve(__dirname, "../public"),to: path.resolve(__dirname, "../dist"),globOptions: {// 忽略index.html文件ignore: ["**/index.html"],},},],}),],mode: "production",devtool: "source-map",optimization: {splitChunks: {chunks: "all",},runtimeChunk: {name: (entrypoint) => `runtime~${entrypoint.name}.js`,},minimizer: [new CssMinimizerWebpackPlugin(),new TerserWebpackPlugin(),new ImageMinimizerPlugin({minimizer: {implementation: ImageMinimizerPlugin.imageminGenerate,options: {plugins: [["gifsicle", { interlaced: true }],["jpegtran", { progressive: true }],["optipng", { optimizationLevel: 5 }],["svgo",{plugins: ["preset-default","prefixIds",{name: "sortAttrs",params: {xmlnsOrder: "alphabetical",},},],},],],},},}),],},// webpack解析模块加载选项resolve: {// 自动补全文件扩展名extensions: [".jsx", ".js", ".json"],},
};
2. 下载cross-env 获取环境变量,配置打包命令
npm i cross-env -D
更改package.json中的打包方式
- package.json
"scripts": {"start": "npm run dev","dev": "cross-env NODE_ENV=development webpack serve --config ./config/webpack.config.js","build": "cross-env NODE_ENV=production webpack --config ./config/webpack.config.js"},
3. 合并生产和开发配置为webpack.config.js
完整代码如下:
const path = require("path");
const EslintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin");
const TerserWebpackPlugin = require("terser-webpack-plugin");
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
const CopyPlugin = require("copy-webpack-plugin");
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");// 获取cross-env定义的环境变量
const isProduction = process.env.NODE_ENV === "production";// 返回处理样式loader函数
const getStyleLoaders = (pre) => {return [isProduction ? MiniCssExtractPlugin.loader : "style-loader","css-loader",{// 处理css兼容性问题// 配合package.json中browserslist来指定兼容性loader: "postcss-loader",options: {postcssOptions: {plugins: ["postcss-preset-env"],},},},pre && {loader: pre,options:pre === "less-loader"? {// antd自定义主题配置// 主题色文档:https://ant.design/docs/react/customize-theme-cn#Ant-Design-%E7%9A%84%E6%A0%B7%E5%BC%8F%E5%8F%98%E9%87%8FlessOptions: {modifyVars: { "@primary-color": "#1DA57A" },javascriptEnabled: true,},}: {},},].filter(Boolean);
};module.exports = {entry: "./src/main.js",output: {path: isProduction ? path.resolve(__dirname, "../dist") : undefined, // 开发环境不指定输出目录,使用默认的输出目录filename: isProduction ? "static/js/[name].[contenthash:10].js" : "static/js/[name].js", // 生产环境使用contenthash,开发环境使用hashchunkFilename: isProduction ? "static/js/[name].[contenthash:10].chunk.js" : "static/js/[name].chunk.js", assetModuleFilename: "static/media/[hash:10][ext][query]", // 配置图片资源输出的文件名clean: true, // 每次打包前清除dist目录},module: {rules: [// 处理css{test: /\.css$/,use: getStyleLoaders(),},{test: /\.less$/,use: getStyleLoaders("less-loader"),},{test: /\.s[ac]ss$/,use: getStyleLoaders("sass-loader"),},{test: /\.styl$/,use: getStyleLoaders("stylus-loader"),},// 处理图片{test: /\.(jpe?g|png|gif|webp|svg)$/,type: "asset",parser: {dataUrlCondition: {maxSize: 10 * 1024,},},},// 处理其他资源{test: /\.(woff2?|ttf)$/,type: "asset/resource",},// 处理js{test: /\.jsx?$/,include: path.resolve(__dirname, "../src"),loader: "babel-loader",options: {cacheDirectory: true,cacheCompression: false,plugins: [!isProduction && "react-refresh/babel", // 激活js的HMR].filter(Boolean),},},],},// 处理htmlplugins: [new EslintWebpackPlugin({ // eslint检查context: path.resolve(__dirname, "../src"), // 指定检查的目录exclude: "node_modules", // 指定排除的目录cache: true, // 开启缓存,提升eslint检查速度cacheLocation: path.resolve(__dirname, "../node_modules/.cache/.eslintcache"), // 指定缓存文件存放位置}),new HtmlWebpackPlugin({template: path.resolve(__dirname, "../public/index.html"), // 指定html模板}),isProduction &&new MiniCssExtractPlugin({ // 提取css为单独文件filename: "static/css/[name].[contenthash:10].css", // 提取的css文件名chunkFilename: "static/css/[name].[contenthash:10].chunk.css", // 提取的css chunk文件名}),isProduction &&new CopyPlugin({ // 将public目录下的文件拷贝到dist目录下patterns: [{from: path.resolve(__dirname, "../public"),to: path.resolve(__dirname, "../dist"),globOptions: {// 忽略index.html文件ignore: ["**/index.html"],},},],}),!isProduction && new ReactRefreshWebpackPlugin(), // 开发环境开启HMR].filter(Boolean),mode: isProduction ? "production" : "development",devtool: isProduction ? "source-map" : "cheap-module-source-map", // 配置source-map映射, 生产环境使用source-map,开发环境使用cheap-module-source-mapoptimization: {splitChunks: {chunks: "all",cacheGroups: {// react react-dom react-router-dom 一起打包成一个js文件react: {test: /[\\/]node_modules[\\/]react(.*)?[\\/]/,name: "chunk-react",priority: 40,},// antd 单独打包antd: {test: /[\\/]node_modules[\\/]antd[\\/]/,name: "chunk-antd",priority: 30,},// 剩下node_modules单独打包libs: {test: /[\\/]node_modules[\\/]/,name: "chunk-libs",priority: 20,},},},runtimeChunk: {name: (entrypoint) => `runtime~${entrypoint.name}.js`, // 为每个入口添加一个runtime文件},// 是否需要进行压缩minimize: isProduction, // 生产环境压缩minimizer: [new CssMinimizerWebpackPlugin(), // 压缩cssnew TerserWebpackPlugin(), // 压缩jsnew ImageMinimizerPlugin({ // 压缩图片minimizer: {implementation: ImageMinimizerPlugin.imageminGenerate,options: {plugins: [["gifsicle", { interlaced: true }],["jpegtran", { progressive: true }],["optipng", { optimizationLevel: 5 }],["svgo",{plugins: ["preset-default","prefixIds",{name: "sortAttrs",params: {xmlnsOrder: "alphabetical",},},],},],],},},}),],},// webpack解析模块加载选项resolve: {// 自动补全文件扩展名extensions: [".jsx", ".js", ".json"],},devServer: {host: "localhost",port: 3000,open: true,hot: true, // 开启HMRhistoryApiFallback: true, // 解决前端路由刷新404问题},performance: false, // 关闭性能分析,提升打包速度
};
二、react-cli新增配置介绍
1. 定义生产环境变量
// 获取cross-env定义的环境变量
const isProduction = process.env.NODE_ENV === "production";
2. 借助CopyPlugin ,将public目录下的文件拷贝到dist目录下
const CopyPlugin = require("copy-webpack-plugin");plugins: [new CopyPlugin({ // 将public目录下的文件拷贝到dist目录下patterns: [{from: path.resolve(__dirname, "../public"),to: path.resolve(__dirname, "../dist"),globOptions: {// 忽略index.html文件ignore: ["**/index.html"],},},],}),]
3. 借助ReactRefreshWebpackPlugin 开启开发环境HMR
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
module:{rules:[// 处理js{test: /\.jsx?$/,include: path.resolve(__dirname, "../src"),loader: "babel-loader",options: {cacheDirectory: true,cacheCompression: false,plugins: [!isProduction && "react-refresh/babel", // 激活js的HMR].filter(Boolean),},},]
},
plugins: [!isProduction && new ReactRefreshWebpackPlugin(), // 开发环境开启HMR
]
4. 解析模块,自动补全扩展名
// webpack解析模块加载选项resolve: {// 自动补全文件扩展名extensions: [".jsx", ".js", ".json"],},
5. 配置 historyApiFallback: true, 解决前端路由刷新404问题
devServer: {host: "localhost",port: 3000,open: true,hot: true, // 开启HMRhistoryApiFallback: true, // 解决前端路由刷新404问题},
6. 关闭性能分析,提升打包速度
performance: false, // 关闭性能分析,提升打包速度
7. 通过cssloader自定义antd主题
pre && {loader: pre,options:pre === "less-loader"? {// antd自定义主题配置lessOptions: {modifyVars: { "@primary-color": "#1DA57A" },javascriptEnabled: true,},}: {},},
8. 关闭多进程打包
在项目没有达到一定规模之前,开启多进程反而会减慢打包速度
9. 模块分离打包
cacheGroups: {// react react-dom react-router-dom 一起打包成一个js文件react: {test: /[\\/]node_modules[\\/]react(.*)?[\\/]/,name: "chunk-react",priority: 40,},// antd 单独打包antd: {test: /[\\/]node_modules[\\/]antd[\\/]/,name: "chunk-antd",priority: 30,},// 剩下node_modules单独打包libs: {test: /[\\/]node_modules[\\/]/,name: "chunk-libs",priority: 20,},},