「这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战」。
当前我们所有的 webpack 配置信息都是写在一个配置文件(webpack.config.js)中的,这会存在两个问题:
- 当配置越来越多时,这个文件会变得越来越不容易维护;
- 有些配置在开发环境和生产环境都会用到,将这些配置放在一个文件中没有问题,但是,也有些配置是只在开发环境需要使用的,还有些配置是只在生产环境需要使用的,如果把它们都放在同一个文件中肯定是不合理的;
- 比如如果现在想要运行项目,我们可以在当前项目目录下执行
npm run serve命令,那么它就会去执行package.json中的scripts中的serve对应的webpack serve命令(相当于执行npx webpack serve命令);而如果现在想要打包项目,我们可以在当前项目目录下执行npm run build命令,那么它就会去执行package.json中的scripts中的build对应的webpack命令(相当于执行npx webpack命令)。执行前面两条命令时都会优先去加载项目目录下的webpack.config.js文件,但是,当前这个配置文件中既有mode: 'development'、devtool: 'source-map'和devServer等在开发阶段需要而在生产阶段不需要的配置信息,又有CleanWebpackPlugin、CopyWebpackPlugin等只在生产环境才需要而在开发环境不需要的插件。
- 比如如果现在想要运行项目,我们可以在当前项目目录下执行
因此,我们最好对配置进行划分(分离),分别对开发环境和生产环境做配置,以方便维护和管理。
下面,我们就来对 webpack 的配置文件做开发环境和生产环境配置的分离。
先来做下准备工作:
-
删除
webpack_server目录下的abc和node_modules文件夹; -
拷贝一份
webpack_server目录,并重命名为webpack_分离; -
打开
VS Code的终端,切换目录到webpack_分离下,运行npm install安装当前项目(webpack_分离)所需依赖,安装完成后,项目目录如下:
对 webpack 的配置文件进行分离,我们通常会这样做:
-
在项目目录下新建
config文件夹,用来存放webpack的配置文件; -
在
config文件夹下新建webpack.dev.config.js(用来作为开发环境下Webpack的配置文件)、webpack.prod.config.js(用来作为生产环境下Webpack的配置文件)以及webpack.comm.config.js(用来作为开发和生产环境下共用的Webpack的配置文件); -
修改
package.json文件中scripts中运行项目和打包项目的脚本,分别为它们指定对应的配置文件:{ ... "scripts": { "build": "webpack --config ./config/webpack.prod.config.js", "serve": "webpack serve --config ./config/webpack.dev.config.js" }, ... }也就是说,到时候我们运行
npm run serve时加载的是webpack.dev.config.js这个文件,而运行npm run build时加载的则是webpack.prod.config.js这个文件。这样一来,就做到了开发阶段和生产阶段配置的分离。 -
具体怎么分离呢?我们可以先复制一份原先
webpack.config.js文件中的内容,然后拷贝到config文件夹下的webpack.comm.config.js这一公共的配置文件中,并把之前注释掉的new CopyWebpackPlugin(...)这块代码取消注释。接下来,我们要做的就是在这个文件中,把不是开发环境和生产环境都需要用到的配置信息删除掉。我们依次来看:-
target: 'web'应该是公共的,因为不管是开发阶段还是生产阶段,打包的内容都是针对web的,所以留着; -
mode: 'development'显然需要分情况来配置,因为开发环境和生产环境的mode显然是不一样的,所以我们需要在当前文件(webpack.comm.config.js)中删除掉这行mode配置信息,然后分别在webpack.dev.config.js和webpack.prod.config.js中导出对应的mode配置信息:webpack.dev.config.js文件:module.exports = { mode: 'development' }webpack.prod.config.js文件:module.exports = { mode: 'production' } -
devtool: 'source-map'应该只会在开发阶段需要,生产环境其实并不需要,所以我们在当前文件(webpack.comm.config.js)中剪切掉这行source-map配置信息,然后粘贴到webpack.dev.config.js中:module.exports = { mode: 'development', devtool: 'source-map', } -
entry和output是入口和出口,对于开发和生产环境都需要有,所以留着; -
devServer是开发时服务器,显然只有开发阶段需要,所以我们剪切掉devServer这块配置,粘贴到webpack.dev.config.js中:module.exports = { mode: 'development', devtool: 'source-map', devServer: { // contentBase: './public', static: { directory: './public' }, hot: true, // host: '0.0.0.0', port: 8010, // open: true, open: { app: { name: 'chrome' } }, // compress: true, proxy: { '/api': { target: 'http://localhost:8000', pathRewrite: { '^/api': '' // 将开头的 '/api' 替换成空字符串(^ 是开头的意思,'^/api' 表示以 /api 开头) }, secure: false, changeOrigin: true } } }, } -
resolve涉及到路径的解析查找,不管是开发阶段还是生产阶段,都是需要的,所以留着; -
module涉及对模块的解析,不管是开发阶段还是生产阶段,都是需要的,所以留着; -
plugins中的CleanWebpackPlugin和CopyWebpackPlugin插件是生产阶段需要而开发阶段并不需要的,所以我们剪切掉这两个插件对应的配置(包括导入它们的两行代码),粘贴到webpack.prod.config.js中:const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin'); module.exports = { mode: 'production', plugins: [ new CleanWebpackPlugin(), new CopyWebpackPlugin({ patterns: [ { from: 'public', to: './', globOptions: { ignore: [ "**/index.html", "**/.DS_Store" ] } } ] }), ] }
这样一来,我们就将原来的一个
webpack.config.js配置文件中的内容分离到了3个配置文件中。 -
-
接下来,我们需要将公共的配置(
webpack.comm.config.js)合并到开发环境的配置(webpack.dev.config.js)和生产环境的配置(webpack.prod.config.js)中去,怎么实现呢?我们可以使用Webpack官方给我们提供的一个工具:webpack-merge,我们先来安装它:npm install -D webpack-merge安装成功后,我们就可以使用这个工具来合并我们的配置了。具体做法如下:
-
将
webpack.comm.config.js中导出的配置内容合并到webpack.dev.config.js文件中:const { merge } = require('webpack-merge') const commonConfig = require('./webpack.comm.config') module.exports = merge(commonConfig, { mode: 'development', devtool: 'source-map', devServer: { // contentBase: './public', static: { directory: './public' }, hot: true, // host: '0.0.0.0', port: 8010, // open: true, open: { app: { name: 'chrome' } }, // compress: true, proxy: { '/api': { target: 'http://localhost:8000', pathRewrite: { '^/api': '' // 将开头的 '/api' 替换成空字符串(^ 是开头的意思,'^/api' 表示以 /api 开头) }, secure: false, changeOrigin: true } } }, }) -
将
webpack.comm.config.js中导出的配置内容合并到webpack.prod.config.js文件中:const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin'); const { merge } = require('webpack-merge') const commonConfig = require('./webpack.comm.config') module.exports = merge(commonConfig, { mode: 'production', plugins: [ new CleanWebpackPlugin(), new CopyWebpackPlugin({ patterns: [ { from: 'public', to: './', globOptions: { ignore: [ "**/index.html", "**/.DS_Store" ] } } ] }), ] })
-
-
最后,因为我们原来使用的
webpack.config.js文件是在根目录(项目目录)下的,而我们现在使用的这三个配置文件都是在根目录下的config目录下,所以现在配置文件中的一些相对路径就需要进行修改了(但如果某些相对路径会从项目根目录开始解析,则这些相对路径不用修改,具体要看(webpack/插件)内部到底是如何查找路径的):webpack.common.config.js:... module.exports = { ... entry: './src/main.js', // entry 配置项的 ./ 不用修改为 ../ output: { path: path.resolve(__dirname, '../build'), // ./ 需要修改为 ../ filename: 'js/bundle.js', }, resolve: { ... alias: { '@': path.resolve(__dirname, '../src'), // ./ 需要修改为 ../ js: path.resolve(__dirname, '../src/js') // ./ 需要修改为 ../ } }, ... plugins: [ new HtmlWebpackPlugin({ template: './public/index.html', // HtmlWebpackPlugin 中 template 配置项中的 ./ 不用修改为 ../ title: '我是标题呀~' }), new DefinePlugin({ BASE_URL: "'./'", // BASE_URL 是在 index.html 中用到了,所以这里的 ./ 不用修改为 ../ ... }), ... ] }webpack.dev.config.js:... module.exports = merge(commonConfig, { ... devServer: { static: { directory: './public' // devServer.static.directory 配置项中的 ./ 不用修改为 ../ }, ... }, })webpack.prod.config.js中目前不需要修改(但也可以去掉CopyWebpackPlugin中patterns中的to配置项):... module.exports = merge(commonConfig, { ... plugins: [ ... new CopyWebpackPlugin({ patterns: [ { from: 'public', // 也可以写成 ./public,CopyWebpackPlugin 中 patterns 中的 from 配置项中不用修改为 ../public // to: './', // ./ 不用修改为 ../,因为这里会对 output.path 的值和 to 的值做拼接,并且 to 默认会指向 output.path 对应的文件夹,所以这里的 to 选项也可以不写 globOptions: { ignore: [ "**/index.html", "**/.DS_Store" ] } } ] }), ] })
好了,分离完配置文件后,我们先来运行 npm run serve 命令看下开发阶段有没有问题:
可以看到,没有问题。再来运行 npm run build 命令看下生产阶段有没有问题:
可以看到,也没有问题。
这里也证明了
HtmlWebpackPlugin插件中的template配置项、CopyWebpackPlugin插件中的patterns中的from配置项对路径的解析都是相对于项目根目录的。
最后,还要补充一点有关入口文件的解析的说明。
入口文件的解析
我们之前编写的入口文件的配置是这样的:entry: './src/main.js',但是如果我们的配置文件所在的位置变成了项目目录下的 config 目录下,我们是否应该将该配置修改为 entry: '../src/main.js' 呢?
如果我们这样编写,打包时会发现是会报错的,事实上,这里的入口配置还是要写成 './src/main.js',这是因为入口的配置其实还和另一个 context 配置选项有关。
context 配置选项对应一个基础目录(一个绝对路径),它的作用是用来从配置中解析入口点(entry points)和加载器(loaders)。context 的默认值是当前目录(当前工作目录,CWD(current working directory)),也就是和我们运行 npm run serve 或 npm run build 命令时,实际执行的 package.json 中的 scripts 中的脚本命令中的 ./config/webpack.dev.config.js 或 ./config/webpack.prod.config.js 这两个路径有关,此时这两个路径其实是在项目目录下的,因此,这里 context 的默认值就是我们的根目录(项目目录)。所以 entry: './src/main.js' 就不需要修改。
当然,webpack 官方推荐我们手动配置一下 context,这样可以使我们的配置独立于当前工作目录:
//...
module.exports = {
//...
context: path.resolve(__dirname, '../'), // 配置 context 为上一层目录(即项目根目录),如果没有分离配置,则应配置为当前配置文件所在的目录(path.resolve(__dirname, './'))
entry: './src/main.js', // entry 配置项的 ./ 不用修改为 ../
//...
}
此外,webpack 官方也推荐我们使用绝对路径配置 devServer.static.directory,所以我们再来修改 webpack.dev.config.js:
const path = require('path')
//...
module.exports = merge(commonConfig, {
//...
devServer: {
static: {
directory: path.resolve(__dirname, '../public')
},
//...
},
})
如果你把
entry改为了'../src/main.js',就需要指定上下文为当前配置文件所在的目录,并且还要修改某些插件中的路径://... module.exports = { //... context: path.resolve(__dirname, './'), // 配置 context 为当前配置文件所在的目录 entry: '../src/main.js', //... plugins: [ new HtmlWebpackPlugin({ template: '../public/index.html', // 原先的 ./ 要修改为 ../ title: '我是标题呀~' }), //... ] }以及修改
webpack.prod.config.js://... module.exports = merge(commonConfig, { //... plugins: [ //... new CopyWebpackPlugin({ patterns: [ { from: '../public', // 如果 entry 改为了 '../src/main.js',这里也需要修改为上一层目录 //... } ] }), ] })
以上,就是对 webpack 配置内容进行开发环境和生产环境分离的操作过程(把公共的配置放在一个文件中,再将其合并到开发环境的配置文件中和生产环境的配置文件中,之后在不同的脚本命令中去加载不同的配置文件就可以了)。