前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >前端打包工具Esbuild--模块化、ESM、esbuild-loader、

前端打包工具Esbuild--模块化、ESM、esbuild-loader、

作者头像
奋飛
发布2021-08-31 17:00:15
3.9K0
发布2021-08-31 17:00:15
举报
文章被收录于专栏:Super 前端

模块化编程在前端领域已非常普遍,应用程序中将各种功能细分成独立的模块(单独文件)进行开发。module bundler 将所有文件串联起来变成了必须。

JavaScript 社区中有很多程序的打包工具,如 Webpack、Rollup、Parcle 等,它们都是使用 JavaScript 构建的,性能方面有很多不足。下面要介绍的 Esbuild,采用 Go 语言开发,运行速度得到了显著提高。

Esbuild 也被称为下一代构建工具(使用 Go 语言编写,基于 ESM)。

esbuild:An extremely fast JavaScript bundler Our current build tools for the web are 10-100x slower than they could be. The main goal of the esbuild bundler project is to bring about a new era of build tool performance, and create an easy-to-use modern bundler along the way.

其定位为一款极快的 JavaScript 打包工具。“极快”是源于同当前市场上比较流行工具的对比(下图来自官方Github)。

在 ESM 出现之前,在浏览器中运行 JavaScript 有两种方法:

第一种方式,引用一些脚本来存放每个功能;此解决方案很难扩展,因为加载太多脚本会导致网络瓶颈; 第二种方式,使用一个包含所有项目代码的大型 .js 文件,但是这会导致作用域、文件大小、可读性和可维护性方面的问题。总之,在浏览器支持 ES 模块之前,没有 JavaScript 的原生机制可以让开发者以模块化的方式开发。这是 webpack 等打包工具诞生的原因之一。

ESM 的出现后及相关主流浏览器的支持,ESM 模块成了首选,因为原生速度要于远远优于其他方式(不再需要引入额外的库来实现模块化)。

本文的重点是要讲述 esbuild,但在讲述之前,不得不提及ESM、Babel 和 Webpack中几个相关联的重要知识 。

ESM

Snowpack 是首次提出利用浏览器原生 ESM 能力的工具。开发过程中,Snowpack 为你的应用程序提供 unbundled server**。**每个文件只需要构建一次,就可以永久缓存。文件更改时,Snowpack 会重新构建该单个文件。在重新构建每次变更时没有任何的时间浪费,只需要在浏览器中进行HMR更新。

ESM 代表 ES 模块。这是 Javascript 提出的实现一个标准模块化解决方案。模块化的出现为了更好的维护代码的同时,对 scope(作用域)有了更好的管理,关于前端模块化的汇总,可以看这里这里

代码语言:javascript
复制
<script src="index.js" type="module">script>

通过 type="module" 告诉浏览器,当前脚本使用 ESM 模式,浏览器会构建一个依赖关系图,借助浏览器原生的 ESM 能力完成模块的查找、解析、实例化到执行的过程。

这里也不再赘述 ESM 的使用方式及相关语法,重点介绍执行机制,详细内容可以看这篇

  1. Parsing(解析): 递归(深度优先后序遍历)的加载所有导入的模块,构建一个依赖关系图。每一个模块都只有一个module record,这也保证了每一个模块只会被执行一次。
  2. Instantiating(实例化): 对于每个新加载的模块,都会创建一个模块实例,并使用该模块中 export 的内容的 内存地址import 进行映射(导出的模块和导入的模块都指向同一段内存地址–实时绑定)。
  3. Evaluating(求值): 运行每个新加载模块的主体代码。

所有模块的静态依赖在该模块代码执行前都必须下载、解析并进行实例化。

浏览器接管了打包程序的部分工作:Vite 只需要在浏览器请求源码时进行转换并按需提供源码。根据情景动态导入的代码,即只在当前屏幕上实际使用时才会被处理。

因此,引出了使用 ESM 最核心的两个特点:

1、构建复杂度非常低,修改任何组件都只需做单文件编译(不需要重新构建和重新打包应用程序的整个bundle),时间复杂度永远是 O(1) 2、借助 ESM 的能力,模块化交给浏览器端,不存在资源重复加载问题,如果不是涉及到 jsx 或者 typescript 语法,甚至可以不用编译直接运行

更加详细的,可以阅读 为什么选vite

Babel

Babel 是一个 JavaScript 编辑器,将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。

这里详细的用法不赘述,相关可以看这里,AST相关看这里

重点介绍工作过程:

  1. 「Parse(解析)」:将源代码转换成更加抽象的表示方法(如抽象语法树
  2. 「Transform(转换)」:对(抽象语法树)做一些特殊处理,让它符合编译器的期望
  3. 「Generate(代码生成)」:将第二步经过转换过的(抽象语法树)生成新的代码

webpack

Webpack 的构建流程简单来说就是递归编译每一个模块文件,对于不同类型的文件使用不同的 webpack loader 进行处理。并可以自动构建并基于你所引用或导出的内容推断出依赖的图谱

Webpack 在很多方面处理的很好,特别是在大型项目中得到了实战测试,已成熟并且可以处理很多用例。

esbuild-loader 是一个构建在 esbuild 上的 webpack loader,且可以替代 babel-loader 或 ts-loader 来提高构建速度。

代码语言:javascript
复制
module.exports = {
    module: {
      rules: [
-       {
-         test: /\.js$/,
-         use: 'babel-loader',
-       },
+       {
+         test: /\.js$/,
+         loader: 'esbuild-loader',
+         options: {
+           loader: 'jsx',  // Remove this if you're not using JSX
+           target: 'es2015'  // Syntax to compile to (see options below for possible values)
+         }
+       },
        ...
      ],
    },
  }

esbuild

esbuild 重点提到的就是构建速度方面,为什么会比 webpack 快呢?而且不在同一个数量级。

  1. Go 语言与 JavaScript 语言差异(webpack 采用 JavaScript;esbuild 采用 Go)
    • Go 是为并行性而设计的,而 JavaScript 是单线程的
    • Go 在线程之间共享内存,而 JavaScript 必须在线程之间序列化数据
    • Go 可直接编译成机器码,不依赖其他库,必然比 JIT 快(JIT相关看这里
  2. 对构建流程进行了优化,充分利用 CPU 资源
    • 解析 => 链接 => 代码生成。解析和代码生成采用并行化
    • 当导入同一 JavaScript 的不同入口时,可以轻松共享(线程间共享内存)
  3. 尽量少做全 AST 传递以获得更好的缓存局部性(esbuild 中只有三次全量 AST pass)
    • Lexing + parsing + scope setup + symbol declaration
    • Symbol binding + constant folding + syntax lowering + syntax mangling
    • Printing + source map generation

扫描阶段: 这个阶段从一组入口点开始,遍历依赖图以找到需要在包中的所有模块。这是bundler.ScanBundle()作为并行工作列表算法实现的。列表中的每个文件都在单独的 goroutine 上被解析为 AST,如果它有任何依赖项(ES6import语句、ES6import()表达式或 CommonJSrequire()表达式),可能会向工作列表添加更多文件。扫描继续,直到工作清单为空。

编译阶段: 这个阶段为每个入口点创建一个包,这涉及首先“链接”导入和导出,然后将解析的 AST 转换回 JavaScript,然后将它们连接在一起形成最终的 bundle。这发生在(*Bundle).Compile().

示例

index.js

代码语言:javascript
复制
import {add} from './math.js'

let result = add(1,2)
console.log(result)

math.js

代码语言:javascript
复制
const add = function (p1, p2) {
  return p1 + p2;
}
const sub = function (p1, p2) {
  return p1 - p2;
}
export { add, sub }

构建:

代码语言:javascript
复制
$ npx esbuild src/index.js --bundle

结果:

代码语言:javascript
复制
(() => {
  // src/math.js
  var add = function(p1, p2) {
    return p1 + p2;
  };

  // src/index.js
  var result = add(1, 2);
  console.log(result);
})();

默认输出 iife 格式,也可以通过 --format 指定其他格式输出(cjs、esm)

代码语言:javascript
复制
$ npx esbuild src/index.js --bundle --format=cjs
$ npx esbuild src/index.js --bundle --format=esm

针对 esm 格式,也支持 Tree Shaking

代码语言:javascript
复制
$ esbuild index.js main.js --bundle --splitting --outdir=dist --format=esm

main.js 内容同上述 index.js

代码语言:javascript
复制
import {add} from './math.js'
console.log(add(3,4))

结果

代码语言:javascript
复制
import { add } from "./chunk-2YHQ3R6P.js";

// main.js
console.log(add(3, 4));

chunk-2YHQ3R6P.js

代码语言:javascript
复制
// math.js
var add = function(p1, p2) {
  return p1 + p2;
};

export { add };

esbuild 构建默认开启了 Tree shaking(sub 并没有被编译输出),还可以进行 minify、 sourcemap,指定 platform ,以及 watch(监视模式)等等。具体可以查看官方 API

缺点

当然 esbuild 不是万能的,由于其为了保证编译效率,并没提供 AST 的操作能力,所以对一些处理 AST 的 plugin(如 babel-plugin-import) 暂时不能过渡到 esbuild 中。

总结

在当前前端环境中,直接使用 esbuild 代替 webpack 不现实;主流方案是在 webpack 中使用 esbuild 去做一些代码的 transform (代替 babel-loader)。当然随时后续 vite(采用 esbuild 预构建依赖) 、snowpack 等构建工具的发展,其会在某些场景下替代 webpack(vue3 的官方推荐构建工具为 vite)。esbuild 针对构建 应用 的重要功能仍然还在持续开发中 —— 特别是代码分割(可以获得最佳的加载性能)和 CSS 处理方面。当未来这些功能稳定后,不排除使用 esbuild 作为生产构建器的可能。

vite 中引用 esbuild: https://github.com/vitejs/vite/blob/main/packages/vite/src/node/optimizer/index.ts#L5

webpack 在 v5 版本中也是针对编译的性能做出了不少努力,除了提供了物理缓存的优化之外,还提供 Module Federation 的方案,这给我们上层的应用实践带来了很多想象的空间。以前 webpack 大有一统构建工具的趋势,而现在我们可以结合业务的特点有更多的选择。

最后

通过 ESM 构建,提到 esbuild,还有一个 swc;esbuild 采用 go 语言编写,而 swc 采用 rust 语言编写。他们的速度都比目前市面上成熟的打包工具要快太多,带来性能提升的关键是底层编写语言的天生特性导致。关于这个话题esbuild为什么不用Rust,而使用了Go? 在知乎上有专门讨论,感兴趣的小伙伴可以查看一下。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/07/17 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ESM
  • Babel
  • webpack
  • esbuild
    • 示例
      • 缺点
      • 总结
      • 最后
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档