【webpack】浅入浅出理解webpack中的NODE_ENV及新特性mode

NODE_ENV是个什么?

类似于下面这样的代码大家或多或少都见过,总是见其形尚未思其义:

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

其实,从技术的角度来讲,NODE_ENV其实就是由nodeJS暴露给执行脚本的一个环境变量,通常用来帮助我们在构建脚本中判断当前是devlopment还是production环境

上述脚本中的NODE_ENV变量会赋值给process.env对象

那么,问题又来了,process又是个啥玩意?

process是Node的一个全局变量,提供了当前Node进程的信息,它可以在脚本的任意位置使用,不需要通过require命令加载

process对象提供一系列属性,用于返回系统信息。

  • process.argv:返回当前进程的命令行参数数组。
  • process.env:返回一个对象,成员为当前Shell的环境变量,比如process.env.HOME。
  • process.installPrefix:node的安装路径的前缀,比如/usr/local,则node的执行文件目录为/usr/local/bin/node。
  • process.pid:当前进程的进程号。
  • process.platform:当前系统平台,比如Linux。
  • process.title:默认值为“node”,可以自定义该值。
  • process.version:Node的版本,比如v0.10.18。

更多关于process的内容,此处不做过多讲解,请大家自行查看官方文档,点击进入直升机通道

process.env.NODE_ENV的作用

1.这个变量并不是 process.env 直接就有的,而是通过设置得到的。其实,更准确的来说是前端工程化过程中大家约定俗成的做法,尤其是webpack构建前端工程时,会经常使用。

2.其值通常为“production”(生产环境)和“development”(开发环境),或者“prod”和“dev”,以此来区分不同环境下的逻辑行为

if (process.env.NODE_ENV === 'development') { 
    //开发环境 do something
} else {
    //生产环境 do something
}

那这个属性是什么时候赋值给process.env的呢?

以webpack的工程为例,通常是运行脚本时来做这件事,例如package.json中的脚本:

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

说明:NODE_ENV=development在windows环境下会报错,需要改为set NODE_ENV=production,为了解决这个差异,可以使用cross-env跨平台的设置和使用环境变量,这里就不解释具体使用方法了。

这样,就可以在webpack.config.js中使用process.env.NODE_ENV了,但是要注意不能在webpack.config.js引入的模块中使用,要想在模块当中直接使用,我们还需要一些配置。

webpack4之前可以使用DefinePlugin插件配置

// webpack.config.js
const webpack = require('webpack');

module.exports = {
    entry: {
        app: './src/app'
    },
    output: {
        path: 'dist',
        filename: 'bundle.js'
    },
    plugins: [
        new webpack.DefinePlugin({
            'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV) // 这里需要注意的是,值对应的格式必须是" "XXXX" "这种格式,所以会用 JSON.stringify 进行转换。
        })
    ]
};

webpack4版本之后可以通过mode选项实现

module.exports = {
    // 定义环境变量
    mode: 'development',
    // JavaScript 执行入口文件
    entry: './main.js',
    output: {
        // 把所有依赖的模块合并输出到一个 bundle.js 文件
        filename: 'bundle.js',
        // 输出文件都放到 dist 目录下
        path: path.resolve(__dirname, './dist'),
    }, 
};

注意【关键点,勿混淆】:

通过npm script设置的NODE_ENV和通过DefinePlugin、mode选项定义的NODE_ENV是两个相互独立的变量

NODE_ENV=developmen这种方式定义的NODE_ENV只能在当前脚本中生效,是个runtime。

举个栗子:

例如webpack.config.js的mode我们设置为production,而脚本中执行NODE_ENV=development,那么在模块当中NODE_ENV的值为production,而配置文件webpack.config.js中的NODE_ENV的值为development

再举个栗子:

如果没有在npm script脚本中
设置NODE_ENV,但在webpack.config.js中设置了mode,那么我们在webpack.config.js中console出process.env.NODE_ENV的值就是undefined,因此如下代码可能就达不到我们想要的效果

let env = process.env.NODE_ENV === 'development' ? 'none' : 'production';

小结:

  • DefinePlugin和mode选项定义的NODE_ENV 作用于webpack入口文件下的业务代码,通常为src文件夹下的代码
  • 而 npm脚本里的设置多用于配置相关,例如在webpack.config.js里区分环境配置不同插件。

webpack4新特性mode

设置 mode ,可以让 webpack 自动调起相应的内置优化。

module.exports = {
  // 可以是 none、development、production
  // 默认为 production
  mode: 'production'
}

或在命令行里配置:

"build:prod": "webpack --config config/webpack.prod.config.js --mode production"

那么,在设置了mode之后,webpack4 会同步配置 process.env.NODE_ENV 为 development 或 production 。

webpack4 支持零配置使用,这里的零配置就是指,mode 以及 entry (默认为 src/index.js)都可以通过入口文件指定,并且 webpack4 针对对不同的 mode 内置相应的优化策略。

1. production

配置:

// webpack.prod.config.js
module.exports = {
  mode: 'production',
}

相当于默认内置了:

// webpack.prod.config.js
module.exports = {
  performance: {
    // 性能设置,文件打包过大时,会报警告
    hints: 'warning'
  },
  output: {
    // 打包时,在包中不包含所属模块的信息的注释
    pathinfo: false
  },
  optimization: {
    // 不使用可读的模块标识符进行调试
    namedModules: false,
    // 不使用可读的块标识符进行调试
    namedChunks: false,
    // 设置 process.env.NODE_ENV 为 production
    nodeEnv: 'production',
    // 标记块是否是其它块的子集
    // 控制加载块的大小(加载较大块时,不加载其子集)
    flagIncludedChunks: true,
    // 标记模块的加载顺序,使初始包更小
    occurrenceOrder: true,
    // 启用副作用
    sideEffects: true,
    // 确定每个模块的使用导出,
    // 不会为未使用的导出生成导出
    // 最小化的消除死代码
    // optimization.usedExports 收集的信息将被其他优化或代码生成所使用
    usedExports: true,
    // 查找模块图中可以安全的连接到其它模块的片段
    concatenateModules: true,
    // SplitChunksPlugin 配置项
    splitChunks: {
      // 默认 webpack4 只会对按需加载的代码做分割
      chunks: 'async',
      // 表示在压缩前的最小模块大小,默认值是30kb
      minSize: 30000,
      minRemainingSize: 0,
      // 旨在与HTTP/2和长期缓存一起使用 
      // 它增加了请求数量以实现更好的缓存
      // 它还可以用于减小文件大小,以加快重建速度。
      maxSize: 0,
      // 分割一个模块之前必须共享的最小块数
      minChunks: 1,
      // 按需加载时的最大并行请求数
      maxAsyncRequests: 6,
      // 入口的最大并行请求数
      maxInitialRequests: 4,
      // 界定符
      automaticNameDelimiter: '~',
      // 块名最大字符数
      automaticNameMaxLength: 30,
      cacheGroups: { // 缓存组
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        }
      }
    },
    // 当打包时,遇到错误编译,将不会把打包文件输出
    // 确保 webpack 不会输入任何错误的包
    noEmitOnErrors: true,
    checkWasmTypes: true,
    // 使用 optimization.minimizer || TerserPlugin 来最小化包
    minimize: true,
  },
  plugins: [
    // 使用 terser 来优化 JavaScript
    new TerserPlugin(/* ... */),
    // 定义环境变量
    new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("production") }),
    // 预编译所有模块到一个闭包中,提升代码在浏览器中的执行速度
    new webpack.optimize.ModuleConcatenationPlugin(),
    // 在编译出现错误时,使用 NoEmitOnErrorsPlugin 来跳过输出阶段。
    // 这样可以确保输出资源不会包含错误
    new webpack.NoEmitOnErrorsPlugin()
  ]
}

2. development

配置:

// webpack.dev.config.js
module.exports = {
  mode: 'development',
}

相当于默认内置了:

// webpack.dev.config.js
module.exports = {
  devtool: 'eval',
  cache: true,
  performance: {
    // 性能设置,文件打包过大时,不报错和警告,只做提示
    hints: false
  },
  output: {
    // 打包时,在包中包含所属模块的信息的注释
    pathinfo: true
  },
  optimization: {
    // 使用可读的模块标识符进行调试
    namedModules: true,
    // 使用可读的块标识符进行调试
    namedChunks: true,
    // 设置 process.env.NODE_ENV 为 development
    nodeEnv: 'development',
    // 不标记块是否是其它块的子集
    flagIncludedChunks: false,
    // 不标记模块的加载顺序
    occurrenceOrder: false,
    // 不启用副作用
    sideEffects: false,
    usedExports: false,
    concatenateModules: false,
    splitChunks: {
      hidePathInfo: false,
      minSize: 10000,
      maxAsyncRequests: Infinity,
      maxInitialRequests: Infinity,
    },
    // 当打包时,遇到错误编译,仍把打包文件输出
    noEmitOnErrors: false,
    checkWasmTypes: false,
    // 不使用 optimization.minimizer || TerserPlugin 来最小化包
    minimize: false,
    removeAvailableModules: false
  },
  plugins: [
    // 当启用 HMR 时,使用该插件会显示模块的相对路径
    // 建议用于开发环境
    new webpack.NamedModulesPlugin(),
    // webpack 内部维护了一个自增的 id,每个 chunk 都有一个 id。
    // 所以当增加 entry 或者其他类型 chunk 的时候,id 就会变化,
    // 导致内容没有变化的 chunk 的 id 也发生了变化
    // NamedChunksPlugin 将内部 chunk id 映射成一个字符串标识符(模块的相对路径)
    // 这样 chunk id 就稳定了下来
    new webpack.NamedChunksPlugin(),
    // 定义环境变量
    new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development") }),
  ]
}

3. none

不进行任何默认优化选项。

// webpack.com.config.js
module.exports = {
  performance: {
   // 性能设置,文件打包过大时,不报错和警告,只做提示
   hints: false
  },
  optimization: {
    // 不标记块是否是其它块的子集
    flagIncludedChunks: false,
    // 不标记模块的加载顺序
    occurrenceOrder: false,
    // 不启用副作用
    sideEffects: false,
    usedExports: false,
    concatenateModules: false,
    splitChunks: {
      hidePathInfo: false,
      minSize: 10000,
      maxAsyncRequests: Infinity,
      maxInitialRequests: Infinity,
    },
    // 当打包时,遇到错误编译,仍把打包文件输出
    noEmitOnErrors: false,
    checkWasmTypes: false,
    // 不使用 optimization.minimizer || TerserPlugin 来最小化包
    minimize: false,
  },
  plugins: []
}

小结

production 模式下给你更好的用户体验:

  • 较小的输出包体积
  • 浏览器中更快的代码执行速度
  • 忽略开发中的代码
  • 不公开源代码或文件路径
  • 易于使用的输出资产

development 模式会给予你最好的开发体验:

  • 浏览器调试工具
  • 快速增量编译可加快开发周期
  • 运行时提供有用的错误消息
posted @ 2019-11-30 20:05  林璡  阅读(2701)  评论(0编辑  收藏  举报