esbuild快速打包助力webpack开发研效

3,789 阅读3分钟

背景

随着项目开发周期越来越长,项目越来越来大,基于webpack构建的项目打包速度会越来越长。 如果采用ts + tsx构成,开发环境下编译速度慢的情况会非常明显 目前接手项目的打包速度如下

首次打包

2min30s

单次编辑打包

15s

迫切的需要提高构建效率

优化思路

基于vite重构打包内容

优点:

  1.  直接启动开发服务器,对请求模块进行实时编译,进行毫秒级别打包
  2.  天然支持HMR,浏览器仅需要请求改动模块

缺点:

  1.  目前对代码打包方式侵入较大
  2.  目前vite工具对react支持上可能存在缺陷

采用esbuild-loader + webpack

优点:

  1. 可以快速提高打包速度
  2. 对现有打包工程侵入较小,可以仅以插件的形式注入

缺点:

  1. 优化点不够彻底,仍有较大提升空间
  2. esbuild-loader本身提供的能力较弱,可能需要针对修复

优化方案

单次编译速度优化

目前采用esbuild-loader方案进行优化

单次优化只针对ts tsx占比比较大的单一类型进行优化

通过 webpackEsBuildPlugin 插件介入webpack生命周期 替换掉ts-loader babel-loader 为 esbuild-loader

提前记录文件变化点 方便后期做热更新校验优化

首次编译速度优化

针对首次耗时较大的babel-loader开启多线程打包

采用 HappyPack 进行打包拆分

esbuild-loader使用问题

不支持装饰器

esbuild本身不支持ts装饰器

解决方案

 使用oneOf对单文件采用单一loader, 当returen false的时候即采用babel-loader + ts-loader形式打包文件;

    oneOf: [
      {
        test: filePath => {
          if (!filePath) {
            return false;
          }
          try {
            const fileContent = fs.readFileSync(filePath).toString();
            return !hasDecorator(fileContent);
          } catch (e) {
            return false;
          }
        },
        use: [
          {
            loader: 'esbuild-loader',
            options: {
              loader: 'tsx',
              target: 'es2018',
            },
          },
        ],
      },
      {
        use: [
          {
            loader: 'babel-loader',
            options: {
              cacheDirectory: true,
            },
          },
          {
            loader: 'ts-loader',
            options: {
              transpileOnly: true,
            },
          },
        ],
      },
    ],

判断输入文件是否有装饰器

/** 判断是否具有装饰器 */
function hasDecorator(fileContent, offset = 0) {
  const atPosition = fileContent.indexOf('@', offset);

  if (atPosition === -1) {
    return false;
  }

  if (atPosition === 1) {
    return true;
  }

  if (["'", '"'].includes(fileContent.substr(atPosition - 1, 1))) {
    return hasDecorator(fileContent, atPosition + 1);
  }

  return true;
}

不支持循环依赖

分离依赖项,解决循环依赖

不支持按需加载

因为是在开发环境使用esbuild,开发环境可以进行全量引入。

以antd为例,生产环境必须开启按需引入

image.png

开发环境对于按需引入导致的样式缺失部分只能采取全量引入

image.png

开发环境需要打包成es5

开发环境需要兼容低端浏览器,对于tsconfig, target需要保留为es5

image.png

通过ts-loader打包出来的es5代码和esbuild打包出来的es6代码可能存在冲突

开发环境需要指定配置,将ts-loader设置成es6代码

尽量保留开发环境不变的前提下,写入config文件

{ 
"extends": "../../tsconfig.json", // 继承开发环境的配置 
"compilerOptions": { 
"target": "es6", // 覆盖设置 
"baseUrl": "../../src", // 注意根目录修改 
}, 
"include": ["../../src"], "exclude": ["../../node_modules", "../../build", "../../dist"] 
}

查阅文档得知最新的ts-loader设置可以使用

image.png

configFile字段指定对应的json文件

如果想全局替换

可以使用

const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');

image.png 进行更换

如果想替换esbuild下config指定配置可以通过

MinifyPlugin

下替换tsconfigRaw

image.png

后期迭代更新计划

  1. 使用vite重构打包方式(慎重考虑)
  2. 把开发环境更多的loader改成esbuild打包
  3. 使用splitcode单独拆分库进行打包(dllpugin因为库更新问题暂不考虑)

优化效果

单次编译速度

10s-15s => 1-3s

首次编译速度

2min30s => 20s