关于前端:下一个时代的打包工具-esbuild

7次阅读

共计 3838 个字符,预计需要花费 10 分钟才能阅读完成。

前言


关注「Vite」底层实现的同学,我想应该分明它应用「esbuild」来实现对 .tsjsx.js 代码的转化。当然,在「Vite」之前更早应用「esbuild」的就是「Snowpack」。不过,相比拟「Vite」领有的微小社区,显然「Snowpack」的关注度较小。

「Vite」的外围是基于浏览器原生的 ES Module。然而,相比拟传统的打包工具和开发工具而言,它做出了很多扭转,采纳「esbuild」来反对 .tsjsx.js 代码的转化就是其中之一。

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

1 什么是 esbuild

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

目前「esbuild」反对的性能:

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

这里,咱们列出了几点常关注的,至于其余,有趣味的同学能够移步官网文档自行理解。

目前对于「JavaScript」语法转化不反对的个性有:

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

须要留神的是对于不反对转化的语法会 原样输入

2 比照现有的打包工具

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

对于这些基准测试,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 & 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。

❤️爱心三连击

通过浏览,如果你感觉有播种的话,能够爱心三连击!!!

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

正文完
 0