dead code

生产环境打包时,去掉 vConsole

背景

仅在开发模式下引入 vConsole

通常情况下,我们会在项目入口文件index.js里通过p rocess.env.NODE_ENV环境变量判断是否要引入 vConsole:

// 测试vconsole
if (p rocess.env.NODE_ENV === 'development') {
    const VConsole = require('vconsole');
    new VConsole();
}
1
2
3
4
5

若是在打包时将 Webpack 的optimization.minimize设置为false即不使用 TerserPlugin 压缩bundle,则在这段代码在生产环境打包好的bundle里对应的是

// 测试vconsole
if (false) { var VConsole; }
1
2

可以看到,p rocess.env.NODE_ENV === 'development'被编译成了false,且并没有require('vconsole')对应的代码,因此在生产环境最终的bundle里是不会引入 vConsole 代码的。而且,在打包时将 Webpack 的optimization.minimize设置为true,则生产环境最终的bundle里连if (false) { var VConsole; }和注释都会删除掉。

在开发和测试模式下引入 vConsole

但是,我们一般在测试时,为了方便调试和追踪 bug,也会选择将 vConsole 打开,因此index.js可能会这么写:

// 测试vconsole
if (p rocess.env.NODE_ENV === 'development' || process.env.VUE_APP_ENV === 'test') {
    const VConsole = require('vconsole');
    new VConsole();
}
1
2
3
4
5

我们通过添加一个VUE_APP_ENV环境变量来区分是测试环境还是生产环境。当在测试环境构建时,会指定VUE_APP_ENV环境变量的值为test,这样就能打开 vConsole;当在生产环境构建时,不指定VUE_APP_ENV环境变量,这样就不会引入 vConsole 了。

提示

  • 测试环境构建时,p rocess.env.NODE_ENV都会设置为production,以保证测试环境和生产环境使用的 Webpack 配置尽可能一致。
  • VUE_APP_开头的环境变量在构建时会被webpack.DefinePlugin静态嵌入到客户端侧的包中,因此可以在应用代码里console.log(process.env.VUE_APP_ENV)这样访问它们。

理想很丰满,现实很骨感。

我们先将 Webpack 的optimization.minimize设置为false,看一下生产环境下打出的bundle里对应的代码:

// 测试vconsole
if ( false || Object({"NODE_ENV":"production","BASE_URL":"//blog.windstone.com/"}).VUE_APP_ENV === 'test') {
  var VConsole = __webpack_require__("3a34");
  new VConsole();
}
1
2
3
4
5

p rocess.env.NODE_ENV === 'development'依然被编译成false,但是process.env.VUE_APP_ENV === 'test'被编译成Object({"NODE_ENV":"production","BASE_URL":"//blog.windstone.com/"}).VUE_APP_ENV === 'test'

更为严重的是,require('vconsole')编译成了__webpack_require__("3a34"),这意味着,在生产环境的bundle里最终包含了 vConsole 的 NPM 包代码,而这根本不是我们想要的。

详见压缩工具的dead_code选项,比如terser - Compress options - dead_codeopen in new window

解决方案

方案一: JS 里注入 vConsole

为了解决在生产环境的bundle里引入了 vConsole 代码的问题,我们选择在 Webpack 打包阶段就开始判断是本地环境/测试环境/生产环境的构建。若非本地环境和测试环境,我们将const VConsole = require('vconsole');new VConsole();代码片段动态注入到应用代码里,再进行编译打包;若是生产环境构建,则不注入这段代码片段。

# 安装 loader
npm install --save-dev string-replace-loader
1
2

应用入口index.js文件加入注释:

import Vue from 'vue';
import router from './router';
import store from './store';
import App from './App';

/* <vconsole-placeholder> */

new Vue({
    router,
    store,
    render: h => h(App)
}).$mount('#app');
1
2
3
4
5
6
7
8
9
10
11
12

vue.config.js添加如下代码:

const isDevOrTest = p rocess.env.NODE_ENV === 'development' || process.env.VUE_APP_ENV === 'test';

module.exports = {
    // ...
    chainWebpack: config => {
        // ...
        config.module
            .rule('development tools')
            .test(/\.(js|ts|vue)$/)
            .use('string-replace')
            .loader('string-replace-loader')
            .options({
                multiple: [{
                    search: '/* <vconsole-placeholder> */',
                    replace: isDevOrTest
                        ? `const VConsole = require('vconsole');
                            new VConsole();`
                        : '',
                }],
            });
        // ...
    }
    // ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

Webpack 编译打包时,string-replace-loader判断是本地环境或测试环境,会将所有js/ts/vue文件里的/* <vconsole-placeholder> */字符串替换为const VConsole = require('vconsole');new VConsole();;若是生产环境,则将其替换为空字符''。如此,在生产环境的bundle里就不会包含 vConsole 的代码了。

方案二: HTML 里注入 vConsole

Vue CLI 生成的项目里,public/index.html文件是一个会被html-webpack-plugin处理的模板,因此可以使用lodash template语法插入内容。

而以VUE_APP_开头的变量会被 webpack.DefinePlugin静态嵌入到客户端侧的包中,而这些环境变量和NODE_ENVBASE_URL都可以在public/index.html中以 HTML 插值的方式使用。

因此,可结合环境变量,在 HTML 模板里注入 vConsole:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta
            name="viewport"
            content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, viewport-fit=cover"
        />
        <meta name="format-detection" content="telephone=no" />
        <link rel="icon" href="/favicon.ico" />
        <title><%= htmlWebpackPlugin.options.title %></title>
        <% if (p rocess.env.NODE_ENV === 'development' || process.env.VUE_APP_ENV === 'test') { %>
            <script src="https://unpkg.com/vconsole@3.3.4/dist/vconsole.min.js"></script>
            <script>
                var vConsole = new VConsole();
            </script>
        <% } %>
        <body>
            <div id="app"></div>
        </body>
    </head>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23