webpack,vite为JS、HTML、CSS开启条件编译,宏剔除,代码剔除


需求背景/用户故事

  1. 不使用mock的那部分假数据,总不能放在代码里发布生产环境吧?
  2. debug工具代码,如移动端比较流行的vconsole插件,我们不希望测试前引入这份代码,发布生产的时候手动关闭这部分代码吧,我们需要一个功能去自动化引入和移除。
  3. 使用JS做游戏,开发时引用了大量debug代码,然后打包后因为代码中还有引用没有成为dead code被剔除,从而导致了包体较大。
  4. 希望开发时用的测试API接口不会出现在打包后的代码中。
  5. 网页在桌面端和移动端希望使用不同的CSS,互相不影响包体。

在以上这些情况中都很适合做条件编译,让不希望在生产环境出现的相关代码彻底消失。

那么在目前主流的vite,webpack打包工具中如何实现这种效果呢。

// main.js
if (DEBUG) {
    console.log("debug stuff");
    const donotLikeDropConsole = 'donotLikeDropConsole';
    console.log(donotLikeDropConsole);
}else{
      console.log("noting");
}
// dist.js
console.log("noting");

方式1 宏注释

这种方式非常简单,但不太灵活,原理是利用注释声明起点和终点,再利用正则删除注释和注释中的代码。

这里给出Unix下使用sed的示例

# 去除文件夹echatim 下所有IFTRUE_WXAPP的平台相关代码
for f in `find . echatim -name '*.ts'`; do echo $f  && sed -e ':a' -e 'N' -e '$!ba' -e 's/[ ]*\/\*IFTRUE_WXAPP\*\/.*\n.*\/\*FITRUE_WXAPP\*\// /g' -i '' $f; done

也可以使用webpack下的js-conditional-compile-loader插件来实现,但它目前只支持JS

module: {rules: [{
    test: /\.tsx?$/i,
    use: [{
        loader: 'ts-loader',
        options: {configFile: path.resolve(__dirname, '../tslint.json')}
      },
            //  引入js-conditional-compile-loader插件
      {
        loader: 'js-conditional-compile-loader',
        options: {
          isDebug: process.env.NODE_ENV === 'development', // optional, this is default
          WEBAPP: process.env.platform === 'web', // any name, used for /* IFTRUE_WEBAPP ...js code... FITRUE_WEBAPP */
          WXAPP: process.env.platform === 'wx', // any name, used for /* IFTRUE_WXAPP ...js code... FITRUE_WXAPP */
          // RNAPP: process.env.platform === 'rn', // any name, used for /* IFTRUE_RNAPP ...js code... FITRUE_RNAPP */
        }
      }
    ],
    exclude: /node_modules/
}]}

具体使用方式如下

// 以下的代码仅会在设置WEBAPP:true时才会条件编译
/*IFTRUE_WEBAPP*/
let webfetch = Fetch.getFetchToolkit();
return webfetch(url as string, request).then(response =>{
    return response.json();
}).then(res =>{
    console.log(`==> [${request.method}] ${url} back:` + Beans.json(res));
    const resp = this.response2ApiResponse(res);
    if(resp.isFailed()){
        // return Promise.reject(new Error(Beans.json(resp)));
        return Promise.reject(resp);
    }
    return Promise.resolve(this.response2ApiResponse(res));
});
/*FITRUE_WEBAPP*/

方式2 UglifyJsPlugin

利用UglifyJsPlugin插件去除死代码(不执行的代码块),使用方式非常简单,但该插件只能在webpack中使用

compress: {
    warnings: false, // 去除warning警告
    dead_code: true, // 去除不可达代码
}

Untitled.png

方式3 conditional-webpack-plugin

如果希望条件编译能够覆盖项目中的其他文本文件,可以参考这个插件和其实现。

https://www.npmjs.com/package/conditional-webpack-plugin

方式4 Terser

上面的一些插件的底层就是Terser,这是一个非常著名的代码混淆、压缩库,条件编译的实现也是依赖他,相关中文文档看这里https://github.com/LiPinghai/UglifyJSDocCN/tree/UglifyJs2

使用Terser的Global definitions加dead code剔除实现,Global definitions支持命令行输入和compress参数中的global_defs 对象作为输入,点击这里查看官方文档https://github.com/terser/terser#conditional-compilation

📝 terser Vite配置

利用vite内置的terser,调整选项即可实现条件编译

export default defineConfig({
  build: {
    minify:false,
    terserOptions:{
      compress: {
        global_defs:{
          'DEBUG': false
        }
      }
    }
  }
})

📝 terser Webpack

webpack可以 使用TerserWebpackPlugin,并配置terser参数来实现Global definitions

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
                global_defs:{
                  'DEBUG': false
                }
              }
        }
      }),
    ],
  },
};

📎 参考文章

💡 欢迎您在底部评论区留言,一起交流~