下一个时代的打包工具 esbuild

前言

关注「Vite」底层实现的同窗,我想应该清楚它使用「esbuild」来实现对 .tsjsx.js 代码的转化。固然,在「Vite」以前更早使用「esbuild」的就是「Snowpack」。不过,相比较「Vite」拥有的巨大社区,显然「Snowpack」的关注度较小。css

「Vite」的核心是基于浏览器原生的 ES Module。可是,相比较传统的打包工具和开发工具而言,它作出了不少改变,采用「esbuild」来支持 .tsjsx.js 代码的转化就是其中之一。前端

那么,接下来咱们就步入今天的正题,What is esbuild, and how to use it?node

1 什么是 esbuild

「esbuild」官方的介绍:它是一个「JavaScript」Bundler 打包和压缩工具,它能够将「JavaScript」和「TypeScript」代码打包分发在网页上运行。npm

目前「esbuild」支持的功能:json

  • 加载器
  • 压缩
  • 打包
  • Tree shaking
  • Source map 生成
  • 将 JSX 和较新的 JS 语法移植到 ES6
  • ...

这里,咱们列出了几点常关注的,至于其余,有兴趣的同窗能够移步官方文档自行了解。浏览器

目前对于「JavaScript」语法转化不支持的特性有:微信

  • Top-level await
  • async await
  • BigInt
  • Hashbang 语法

须要注意的是对于不支持转化的语法会原样输出markdown

2 对比现有的打包工具

「esbuild」的做者对比目前现阶段相似的工具作了基准测试。最后的结果是:async

对于这些基准测试,esbuild 比我测试的其余 JavaScript 打包程序 快至少 100 倍。函数

100 倍,能够说快到飞起了...而「esbuild」快的缘由,这里我分两个层面解释:

2.1 官方解释

  • 它是用「Go」语言编写的,该语言能够编译为本地代码。
  • 解析,生成最终文件和生成 source maps 所有彻底并行化。
  • 无需昂贵的数据转换,只需不多的几步便可完成全部操做。
  • 该库以提升编译速度为编写代码时的第一原则,并尽可能避免没必要要的内存分配。

2.2 语言层面解释

  • 现阶段的相似工具,底层的实现都是基于「JavaScript」,其受限于自己是一门解释型的语言,并不能充分利用 CPU。
  • 「Chrome V8」引擎虽然对「JavaScript」的运行作了优化,引进「JIT」的机制,可是部分代码实现机器码与「esbuild」所有实现机器码的形式,性能上的差距不可弥补。

固然,语言层面仅仅是官方解释中的一点的展开,其余解释有时间等后续分析其源码实现后讲解。

3 esbuild API 详解

虽然,「esbuild」早已开源和使用,可是官方文档只是简单介绍了如何使用,而对于 API 介绍部分是欠缺的,建议读者本身去阅读源码中的定义。

「esbuild」总共提供了四个函数:transformbuildbuildSyncService。下面,咱们从源码定义的角度来认识一下它们。

3.1 transform

transform 能够用于转化 .js.tsxts 等文件,而后输出为旧的语法的 .js 文件,它提供了两个参数:

  • 第一个参数(必填,字符串),指须要转化的代码(模块内容)。
  • 第二个参数(可选),指转化须要的选项,如源文件路径 sourcefile、须要加载的 loader,其中 loader 的定义:
type Loader = 'js' | 'jsx' | 'ts' | 'tsx' | 'css' | 'json' | 'text' | 'base64' | 'file' | 'dataurl' | 'binary';
复制代码

transform 会返回一个 Promise,对应的 TransformResult 为一个对象,它会包含转化后的旧的 js 代码、sourceMap 映射、警告信息:

interface TransformResult {
 js: string;  jsSourceMap: string;  warnings: Message[]; } 复制代码

3.2 build

build 实现了 transform 的能力,即代码转化,而且它还会将转换后的代码压缩并生成 .js 文件到指定 output 目录。build 只提供了一个参数(对象),来指定须要转化的入口文件、输出文件、loader 等选项:

interface BuildOptions extends CommonOptions {
 bundle?: boolean;  splitting?: boolean;  outfile?: string;  metafile?: string;  outdir?: string;  platform?: Platform;  color?: boolean;  external?: string[];  loader?: { [ext: string]: Loader };  resolveExtensions?: string[];  mainFields?: string[];  write?: boolean;  tsconfig?: string;  outExtension?: { [ext: string]: string };   entryPoints?: string[];  stdin?: StdinOptions; } 复制代码

build 函数调用会输出 BuildResult,它包含了生成的文件 outputFiles 和提示信息 warnings

interface BuildResult {
 warnings: Message[];  outputFiles?: OutputFile[]; } 复制代码

可是,须要注意的是 outputFiles 只有在 writefalse 的状况下才会输出,它是一个 Uint8Array

3.3 buildSync

buidSync 顾名思义,相比较 build 而言,它是同步的构建方式,即若是使用 build 咱们须要借助 async await 来实现同步调用,而使用 buildSync 能够直接实现同步调用。

3.4 Service

Service 的出现是为了解决调用上述 API 时都会建立一个子进行来完成的问题,若是存在屡次调用 API 的状况出现,那么就会出现性能上的浪费,这一点在文档中也有讲解。

因此,使用了 Service 来实现代码的转化或打包,则会建立一个长期的用于共享的子进程,避免了性能上的浪费。而在「Vite」中也正是使用 Service 的方式来进行 .ts.js.jsx 代码的转化工做。

Service 定义:

interface Service {
 build(options: BuildOptions): Promise<BuildResult>;  transform(input: string, options?: TransformOptions): Promise<TransformResult>;  stop(): void; } 复制代码

能够看到,Service 的本质封装了 buildtransformstop 函数,只是不一样于单独调用它们,Service 底层的实现是一个长期存在可供共享的子进程。

可是,在实际使用上,咱们并非直接使用 Service 建立实例,而是经过 startService 来建立一个 Service 实例:

const {
 startService,  build, } = require("esbuild") const service = await startService()  try {  const res = await service.build({  entryPoints: ["./src/main.js"],  write: false  })  console.log(res) } finally {  service.stop() } 复制代码

而且,在使用 stop 的时候须要注意,它会结束这个子进程,这也意味着任何在此时处于 pendingPromise 也会被终止。

4 实现一个小而美的 Bundler 打包

在简单地认识「esbuild」,咱们就来实现一个小而美的 Bunder 打包:

1.初始化项目和安装「esbuild」:

mkdir esbuild-bundler; cd esbuild-bundler; npm init -y; npm i esbuild
复制代码

2.目录结构:

|——— src
 |—— main.js #项目入口文件 |——— index.js #bundler实现核心文件 复制代码

3.index.js

(async () => {
 const {  startService,  build,  } = require("esbuild")  const service = await startService()   try {  const res = await service.build({  entryPoints: ["./src/main.js"],  outfile: './dist/main.js',  minify: true,  bundle: true,  })  } finally {  service.stop()  } })() 复制代码

4.运行一下 node index 便可体验一下闪电般的 bundler 打包!

写在最后

想必看完这篇文章,你们对「esbuild」应该创建起一个基础的认知。而且,文中的源码只是基于「Go」实现的底层能力上的,而真正的底层实现仍是得看「Go」是如何实现的,因为脱离了你们熟知的前端,因此就不作介绍。那么,在一下篇文章中,我将会讲解在「Vite」的源码设计中是怎么使用 esbuild 来实现 .tsjsx.js 语法解析,以及咱们如何自定义 plugin 来实现一些代码转化。最后,文章中若是存在表述不当的地方,欢迎各位同窗提 Issue。

往期文章回顾

深度解读 Vue3 源码 | 组件建立过程

深度解读 Vue3 源码 | 内置组件 teleport 是什么“来头”?

深度解读 Vue3 源码 | compile 和 runtime 结合的 patch 过程

❤️爱心三连击

经过阅读,若是你以为有收获的话,能够爱心三连击!!!

前端问路人 —— 五柳(微信公众号: Code center)

相关文章
相关标签/搜索