Vue 项目之 Webpack 开发和生产环境的分离

764 阅读9分钟

「这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战」。

当前我们所有的 webpack 配置信息都是写在一个配置文件(webpack.config.js)中的,这会存在两个问题:

  1. 当配置越来越多时,这个文件会变得越来越不容易维护;
  2. 有些配置在开发环境和生产环境都会用到,将这些配置放在一个文件中没有问题,但是,也有些配置是只在开发环境需要使用的,还有些配置是只在生产环境需要使用的,如果把它们都放在同一个文件中肯定是不合理的;
    • 比如如果现在想要运行项目,我们可以在当前项目目录下执行 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 等在开发阶段需要而在生产阶段不需要的配置信息,又有 CleanWebpackPluginCopyWebpackPlugin 等只在生产环境才需要而在开发环境不需要的插件。

因此,我们最好对配置进行划分(分离),分别对开发环境和生产环境做配置,以方便维护和管理。

下面,我们就来对 webpack 的配置文件做开发环境和生产环境配置的分离。

先来做下准备工作:

  1. 删除 webpack_server 目录下的 abcnode_modules 文件夹;

  2. 拷贝一份 webpack_server 目录,并重命名为 webpack_分离

  3. 打开 VS Code 的终端,切换目录到 webpack_分离 下,运行 npm install 安装当前项目(webpack_分离)所需依赖,安装完成后,项目目录如下:

image-20211211231445118

webpack 的配置文件进行分离,我们通常会这样做:

  1. 在项目目录下新建 config 文件夹,用来存放 webpack 的配置文件;

  2. config 文件夹下新建 webpack.dev.config.js(用来作为开发环境下 Webpack 的配置文件)、webpack.prod.config.js(用来作为生产环境下 Webpack 的配置文件)以及 webpack.comm.config.js(用来作为开发和生产环境下共用的 Webpack 的配置文件);

  3. 修改 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 这个文件。这样一来,就做到了开发阶段和生产阶段配置的分离。

  4. 具体怎么分离呢?我们可以先复制一份原先 webpack.config.js 文件中的内容,然后拷贝到 config 文件夹下的 webpack.comm.config.js 这一公共的配置文件中,并把之前注释掉的 new CopyWebpackPlugin(...) 这块代码取消注释。接下来,我们要做的就是在这个文件中,把不是开发环境和生产环境都需要用到的配置信息删除掉。我们依次来看:

    • target: 'web' 应该是公共的,因为不管是开发阶段还是生产阶段,打包的内容都是针对 web 的,所以留着;

    • mode: 'development' 显然需要分情况来配置,因为开发环境和生产环境的 mode 显然是不一样的,所以我们需要在当前文件(webpack.comm.config.js)中删除掉这行 mode 配置信息,然后分别在 webpack.dev.config.jswebpack.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',
      }
      
    • entryoutput 是入口和出口,对于开发和生产环境都需要有,所以留着;

    • 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 中的 CleanWebpackPluginCopyWebpackPlugin 插件是生产阶段需要而开发阶段并不需要的,所以我们剪切掉这两个插件对应的配置(包括导入它们的两行代码),粘贴到 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 个配置文件中。

  5. 接下来,我们需要将公共的配置(webpack.comm.config.js)合并到开发环境的配置(webpack.dev.config.js)和生产环境的配置(webpack.prod.config.js)中去,怎么实现呢?我们可以使用 Webpack 官方给我们提供的一个工具:webpack-merge,我们先来安装它:

    npm install -D webpack-merge
    

    安装成功后,我们就可以使用这个工具来合并我们的配置了。具体做法如下:

    1. 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
            }
          }
        },
      })
      
    2. 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"
                  ]
                }
              }
            ]
          }),
        ]
      })
      
  6. 最后,因为我们原来使用的 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 中目前不需要修改(但也可以去掉 CopyWebpackPluginpatterns 中的 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 命令看下开发阶段有没有问题:

image-20211212151229834

可以看到,没有问题。再来运行 npm run build 命令看下生产阶段有没有问题:

image-20211212151415317

可以看到,也没有问题。

这里也证明了 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 servenpm 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 配置内容进行开发环境和生产环境分离的操作过程(把公共的配置放在一个文件中,再将其合并到开发环境的配置文件中和生产环境的配置文件中,之后在不同的脚本命令中去加载不同的配置文件就可以了)。