likes
comments
collection
share

webpack5 的使用(二):多个环境配置

作者站长头像
站长
· 阅读数 10

多个环境配置

有时候在一个项目里,我们的环境并不是只有一个,可能会分为多个环境,比如:开发环境、生产环境。这个时候如果把开发环境的配置和生产环境的配置都冗余在一起就很不合理。

webpack-merge:分离配置文件

这个时候,我们可以使用 webpack-merge 插件

npm i -D webpack-merge

我们在 build 目录创建多两个配置文件,webpack.dev.js(开发环境配置) 和 webpack.prod.js(生产环境配置)。

生产环境是不需要用到 webpack-dev-server,而开发环境需要用到。

我们先把 webpack.config.js 的 devServer 注释掉或删掉。

然后在 webpack.dev.js 里写上以下代码

const { merge } = require('webpack-merge');
const common = require('./webpack.config.js');

module.exports = merge(common, {
  devServer: {
    port: 3000,
    hot: true,
    contentBase: path.resolve(__dirname, '../dist')
  },
})

webpack.prod.js 写上这几行代码

const { merge } = require('webpack-merge');
const common = require('./webpack.config.js');

module.exports = merge(common, {
  
})

merge 的作用是在一个公用配置文件的基础下,添加额外配置,这里 webpack.config.js 是公用配置。

我们再修改一下 package.json 的 dev 命令,配置指向 webpack.dev.js;顺便也将 build 命令的配置指向 webpack.prod.js。

"scripts": {
    "dev": "webpack server --config build/webpack.dev.js",
    "build": "webpack --config ./build/webpack.prod.js",
    ...
}

这样,我们的环境配置就区分开来了,运行 dev 就是开发环境,运行 build 就是生产环境。 不过,环境真的就这么简单区分开来了吗?我们还需要注意一点,webpack 还有一个环境模式(mode)的概念,我们还需要进行设置 mode。

环境模式 mode

webpack 的 mode 提供三个值:none、development(开发模式)、production(生产模式,默认)。

由于我们一直都没有定义 webpack 的 mode,所以上述的命令运行都是以 production 模式运行的。

如果没有定义 mode,编译项目时会抛出警告。

webpack5 的使用(二):多个环境配置

不同的模式,会起到不同的作用,作用如下(官方文档摘取):

选项描述
development会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 development. 为模块和 chunk 启用有效的名。
production会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 production。为模块和 chunk 启用确定性的混淆名称,FlagDependencyUsagePlugin,FlagIncludedChunksPlugin,ModuleConcatenationPlugin,NoEmitOnErrorsPlugin 和 TerserPlugin 。
none不使用任何默认优化选项

我们可以看到 production 模式比 development 模式启用了很多插件,在这里,你可以简单认为 production 模式就是自动帮你做了压缩代码和混淆代码功能。

设置 mode 的方法如下。

设置 mode 方法1:webpack 配置

在 webpack 配置文件直接写上。

webpack.dev.js

module.exports = merge(common, {
    mode: 'development',
    ...
})

webpack.prod.js 同理,写上 production。

设置 mode 方法2:脚本命令

在 package.json 的脚本命令添加 --mode xxxx,如下:

"dev": "webpack server --config build/webpack.dev.js --mode development",
"build": "webpack --config ./build/webpack.prod.js --mode production"

环境变量 NODE_ENV

process.env.NODE_ENV 是 node 的一个环境变量,在程序里,我们应当依据 NODE_ENV 作为依据去判断当前是哪种环境。注意环境模式和环境变量的区别,环境模式是针对 webpack,环境变量是针对程序。

通过 mode 间接改变 NODE_ENV

上面章节有提到,设置 mode 会更变 process.env.NODE_ENV,我们来测试一下是否有更变。

我们在 dev 命令添加 --mode development 后,分别在 webpack.config.js 和 src/js/index.js 添加以下代码。

console.log('环境变量:', process.env.NODE_ENV)

npm run dev 运行一下,发现 webpack.config.js 输出的是 undefined。

webpack5 的使用(二):多个环境配置

而在 index.html 的控制台里,输出的是 development。

webpack5 的使用(二):多个环境配置

在 index.js 里,环境变量确实被赋予了 development 值,但是在 webpack 配置文件里却没有被赋值。

我们再仔细点看文档,文档里面写的是:

会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 development

这里说的 process.env.NODE_ENV 是 DefinePlugin 里面的,没有表明是 node 里面的 process.env.NODE_ENV,难道两者不等同?

下面我们先带着问题去看下另一种改变 NODE_ENV 方式。

通过命令行直接改变 NODE_ENV

在 windows 命令行里,我们可以通过 set 来设置环境变量,例如:

"dev": "set NODE_ENV=development && webpack server --config build/webpack.dev.js"

在 linux 命令行里,我们可以通过 export 来设置环境变量,例如:

"dev": "export NODE_ENV=development && webpack server --config build/webpack.dev.js"

这里推荐使用 cross-env,这个工具可以兼容多个系统,可以统一使用命令 cross-env。

npm i -D cross-env

命令这样写。

"scripts": {
    "dev": "cross-env NODE_ENV=development webpack server --config build/webpack.dev.js",
    "build": "cross-env NODE_ENV=production webpack --config ./build/webpack.prod.js",
    ...
}

注意: cross-env 和 webpack 之间没有 && 连接。

运行 npm run dev 命令后,可以发现 webpack 配置文件和 index.js 均会输出“环境变量:development”。

webpack5 的使用(二):多个环境配置

我们再把 dev 命令的 NODE_ENV 改为 production,“环境变量:production” 的输出来源是 main.xxx.js,而不是 index.js。

webpack5 的使用(二):多个环境配置

这说明使用命令行直接更改 NODE_ENV,也会令 webpack 的 mode 发生变化,在官方文档也有提到。

webpack5 的使用(二):多个环境配置

思考

我们来分析一下。

  1. 环境模式 mode 可以改变环境变量 NODE_ENV,在运行环境里可以获取 NODE_ENV,但是 webpack 配置里无法获取 NODE_ENV。
  2. 命令行里设置环境变量 NODE_ENV 也可以改变环境模式 mode,在运行环境里和 webpack 配置里都可以获取 NODE_ENV。
  3. DefinePlugin 是 webpack 编译时定义全局常量的一个插件。
  4. mode 是通过 DefinePlugin 来定义全局常量 NODE_ENV。

因此,我是这样理解的。

mode 改变 NODE_ENV 的行为是发生在 webpack 编译时,因此在加载 webpack 配置时,DefinePlugin 才开始进行操作,在这之前,NODE_ENV 一直是 undefined,在加载 webpack 配置之后,DefinePlugin 才能真正完成工作,NODE_ENV 才有被赋值,DefinePlugin 的 NODE_ENV 和 node 的 NODE_ENV 应该是同一个概念。

而使用命令行直接赋值 NODE_ENV,在 webpack 编译之前就已经赋值了,所以 NODE_ENV 可以被 webpack 配置文件读取。

另外一种配置 mode 方法

除了使用 webpack-merge 分离配置文件,在不同的配置文件写上不同的 mode的方法外,我们似乎也找到了另外一种配置 mode 的方法,可以使用 NODE_ENV 判断该用哪个 mode,例如:

webpack.config.js 里

const isProMode = process.env.NODE_ENV === "production"
module.exports = {
    mode: isProMode ? 'production' : 'development', // 根据不同的 NODE_ENV 去改变 mode
    ...
}

补充

针对 mode 的优先级。 命令行的 mode > webpack 配置里的 mode > 命令行的 NODE_ENV 改变 mode

针对运行环境里的 NODE_ENV 优先级。 命令行的 mode 改变 NODE_ENV > webpack 配置里的 mode 改变 NODE_ENV > 命令行的 NODE_ENV

针对 webpack 配置文件里的 NODE_ENV,只有命令行的 NODE_ENV 才能赋值。

不建议 --mode 和 cross-env 命令同时使用,避免混乱。

完整代码

目录

webpack5 的使用(二):多个环境配置

webpack.config.js

const path = require('path')

const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

console.log('环境变量:', process.env.NODE_ENV)

module.exports = {
  // entry: path.resolve(__dirname, '../src/js/index.js'),
  entry: {
    main: path.resolve(__dirname, '../src/js/index.js'),
    header: path.resolve(__dirname, '../src/js/header.js'),
    footer: path.resolve(__dirname, '../src/js/footer.js'),
  },
  output: {
    // filename: 'main.js',
    filename: '[name].[fullhash].js',
    path: path.resolve(__dirname, '../dist')
  },
  // devServer: {
  //   port: 3000,
  //   hot: true,
  //   contentBase: '../dist'
  // },
  plugins: [
    // new HtmlWebpackPlugin({
    //   title: '首页'
    // }),
    // 配置多个 HtmlWebpackPlugin,有多少个页面就配置多少个
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../src/html/index.html'),
      filename: 'index.html',
      chunks: ['main'] // 与入口文件对应的模块名(entry 配置),这里可以理解为引入 main.js
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../src/html/header.html'),
      filename: 'header.html',
      chunks: ['header']
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../src/html/footer.html'),
      filename: 'footer.html',
      chunks: ['footer']
    }),
    new CleanWebpackPlugin(),
  ]
}

webpack.dev.js

const path = require('path')

const { merge } = require('webpack-merge');
const common = require('./webpack.config.js');

module.exports = merge(common, {
  mode: 'development',
  devServer: {
    port: 3000,
    hot: true,
    contentBase: path.resolve(__dirname, '../dist')
  },
})

webpack.prod.js

const { merge } = require('webpack-merge');
const common = require('./webpack.config.js');

module.exports = merge(common, {
  mode: 'production',
})

package.json

{
  "name": "test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "webpack server --config build/webpack.dev.js",
    "build": "webpack --config ./build/webpack.prod.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "clean-webpack-plugin": "^4.0.0-alpha.0",
    "cross-env": "^7.0.3",
    "html-webpack-plugin": "^5.3.1",
    "webpack": "^5.38.1",
    "webpack-cli": "^4.7.0",
    "webpack-dev-server": "^3.11.2",
    "webpack-merge": "^5.7.3"
  }
}

系列文章