esbuild 基于 Go 语言的 js/ts 打包工具

6/28/2021

# esbuild 是什么

esbuild 是一个用 go 语言写的 javascript, typescript 打包工具,速度比 webpack 快 100 倍以上。

img

# 为什么这么快 ?

  1. js是单线程串行,esbuild 是新开一个进程,然后多线程并行,充分发挥多核优势
  2. go 是纯机器码,肯定要比 JIT 快
  3. 不使用 AST,优化了构建流程。(也带来了一些缺点,后面会说)

# ESbuild 使用

ESbulid 文档: esbuild.github.io/api/ (opens new window)

Esbuild 有命令行 ,js 调用, go 调用三种使用方式。

# cmd >> minify 为压缩, outfile 为输出文件
esbuild app.jsx --bundle --outfile=out.js --minify
import { build } from 'esbuild'
// 编译路径文件
build({
    entryPoints: ['app.jsx'],
    outfile: 'out.js',
    bundle: true
})

# Plugin 的概念

在 esbuild 中,插件被设计为一个函数,该函数需要返回一个对象(Object),对象中包含 namesetup 等 2 个属性:

其中,name 的值是一个字符串,它表示你的插件名称 。 setup 的值是一个函数,它会被传入一个参数 build(对象)。

build 对象上会暴露整个构建过程中非常重要的 2 个函数:onResolveonLoad,它们都需要传入 Options(选项)和 CallBack(回调)等 2 个参数。

其中,Options 是一个对象,它包含 filter(必须)和 namespace 等 2 个属性:

interface OnResolveOptions {
  filter: RegExp;
  namespace?: string;
}

而 CallBack 是一个函数,即回调函数。插件实现的关键则是在 onResolveonLoad 中定义的回调函数内部做一些特殊的处理。

那么,接下来先来认识一下 Options 的 2 个属性:namespacefilter(划重点,它们非常重要

# namespace

默认情况下,esbuild 是在文件系统上的文件(File Modules)相对应的 namespace 中运行的,即此时 namespace 的值为 file

esbuild 的插件可以创建 Virtual Modules,而 Virtual Modules 则会使用 namespace 来和 File Modules 做区分。

注意,每个 namespace 都是特定于该插件的。

简单地理解,Virtual Modules 是指在系统中不存在的模块,往往需要我们构造出 Virtual Modules 对应的模块内容。

# filter

filter 作为 Options 上必须的属性,它的值是一个正则。它主要用于匹配指定规则的导入(import)路径的模块,避免执行不需要的回调,从而提高整体打包性能。

# onResolve

onResolve 函数的回调函数会在 esbuild 构建每个模块的导入路径(可匹配的)时执行。

onResolve 函数的回调函数需要返回一个对象,其中会包含 pathnamespaceexternal 等属性。

通常,该回调函数会用于自定义 esbuild 处理 path 的方式,例如:

  • 重写原本的路径,例如重定向到其他路径
  • 将该路径所对应的模块标记为 external,即不会对改文件进行构建操作(原样输出)

# onLoad

onLoad 函数的回调函数会在 esbuild 解析模块之前调用,主要是用于处理并返回模块的内容,并告知 esbuild 要如何解析它们。并且,需要注意的是 onLoad 的回调函数不会处理被标记为 external 的模块。

onLoad 函数的回调函数需要返回一个对象,该对象总共有 9 个属性。这里我们来认识一下较为常见 3 个属性:

  • contents 处理过的模块内容
  • loader 告知 esbuild 要如何解释该内容(默认为 js)。例如,返回的模块内容是 CSS,则声明 loadercss
  • resolveDir 是在将导入路径解析为文件系统上实际路径时,要使用的文件系统目录

# 自定义 Plugin

import axios from 'axios'
import esbuild, { Plugin, transform } from 'esbuild'

const importCdnUrlPlugin: Plugin = {
  name: 'url-import',
  setup: ({ onResolve, onLoad }) => {
    // 在 onResolve 的时候将对应的 filter 引入更改命名空间
    onResolve({ filter: /^https?:\/\// }, ({ path }) => {
      return { namespace: 'url-import', path }
    })

    // 在进入 onResolve 时,假如是 url-import 空间, 将地址转换为绝对地址
    // ./add.js > https.../add.js
    onResolve({ filter: /.*/, namespace: 'url-import' }, ({ path, importer }) => {
      const urlPath = new URL(path, importer).toString()
      return { namespace: 'url-import', path: urlPath }
    })

    // 在 onLoad 时只接受 url-import 的命名空间
    // 在这里请求 path, 将内容 return 出去
    onLoad({ filter: /.*/, namespace: 'url-import' }, async ({ path }) => {
      const { data: contents } = await axios.get(path)
      return { contents }
    })
  }
}

esbuild.build({
  entryPoints: ['app.js'],
  bundle: true,
  outfile: 'out.js',
  plugins: [importCdnUrlPlugin]
})

# 文章引用

https://juejin.cn/post/6918927987056312327#heading-4

https://zhuanlan.zhihu.com/p/362046068