ESBuild SWC浅谈: 新一代构建工具

2023-12-20 18:30

本文主要是介绍ESBuild SWC浅谈: 新一代构建工具,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

大厂技术  坚持周更  精选好文

首先, ESBuild & swc是什么?

  • ESBuild[1]是基于Go语言开发的JavaScript Bundler, 由Figma前CTO Evan Wallace开发, 并且也被Vite用于开发环境的依赖解析和Transform.

  • SWC[2]则是基于Rust的JavaScript Compiler(其生态中也包含打包工具spack), 目前为Next.JS/Parcel/Deno等前端圈知名项目使用.

为什么要关注这两个工具?

  • 因为...

  • 大家可能在日常工作中遇到过, 项目的构建时间随着项目体积和复杂度逐渐递增, 有的时候本地编辑一个项目要等上个大几分钟(此处@Webpack)

  • 这个是ESBuild官网对于其打包10份three.js的速度对比

  • SWC则宣称其比Babel快20倍(四核情况下可以快70倍)

ca27c9e717e5ad082cbd02863f08797f.png
  • 那么ESBuild & SWC是真的有这么快? 还是开发者的自说自话? 我们通过实验来检验一下, 先看ESBuild

    • 用ESBuild打包一下

      # 编译
      > build-esb
      > esbuild ./src/app.jsx --bundle --outfile=out_esb.js --minify# 构建产物的大小和构建时间
      out_esb.js  27.4kb
      ⚡ Done in 13ms# 运行产物
      node out_esb.js 
      <h1 data-reactroot="">Hello, world!</h1>
    • 用Webpack打包一下

      # 编译
      > build-wp
      > webpack --mode=production# 构建产物
      asset out_webpack.js 25.9 KiB [compared for emit] [minimized] (name: main) 1 related asset
      modules by path ./node_modules/react/ 8.5 KiB./node_modules/react/index.js 189 bytes [built] [code generated]./node_modules/react/cjs/react.production.min.js 8.32 KiB [built] [code generated]
      modules by path ./node_modules/react-dom/ 28.2 KiB./node_modules/react-dom/server.browser.js 227 bytes [built] [code generated]./node_modules/react-dom/cjs/react-dom-server.browser.production.min.js 28 KiB [built] [code generated]
      ./src/app.jsx 254 bytes [built] [code generated]
      ./node_modules/object-assign/index.js 2.17 KiB [built] [code generated]# 构建时间
      webpack 5.72.0 compiled successfully in 1680 msnpm run build-wp  2.79s user 0.61s system 84% cpu 4.033 total# 运行
      node out_webpack.js  
      <h1 data-reactroot="">Hello, world!</h1>
    • 让我们先写一段非常简单的代码

      import * as React from 'react'
      import * as ReactServer from 'react-dom/server'const Greet = () => <h1>Hello, world!</h1>
      console.log(ReactServer.renderToString(<Greet />))
    • 然后我们来通过Webpack & ESBuild构建它

  • 再来看看swc的编译效率

    • 又是一段简单的ES6代码

      // 一些变量声明
      const PI = 3.1415;
      let x = 1;// spread
      let [foo, [[bar], baz]] = [1, [[2], 3]];
      const node = {loc: {start: {line: 1,column: 5}}
      };
      let { loc, loc: { start }, loc: { start: { line }} } = node;// arrow function
      var sum = (num1, num2) => { return num1 + num2; }// set
      const s = new Set();
      [2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));// class
      class Point {constructor(x, y) {this.x = x;this.y = y;}toString() {return '(' + this.x + ', ' + this.y + ')';}
      }
    • 先用Babel转译一下

      yarn compile-babel
      yarn run v1.16.0
      warning package.json: No license field
      $ babel src/es6.js -o es6_babel.js
      ✨  Done in 2.38s.
    • 再用swc转译一下

      yarn compile-swc  
      yarn run v1.16.0
      warning package.json: No license field
      $ swc src/es6.js -o es6_swc.js
      Successfully compiled 1 file with swc.
      ✨  Done in 0.63s.
    • 两者的产物对比

      // es6_babel
      "use strict";function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }var PI = 3.1415;
      var x = 1;
      var foo = 1,bar = 2,baz = 3;
      var node = {loc: {start: {line: 1,column: 5}}
      };
      var loc = node.loc,start = node.loc.start,line = node.loc.start.line;var sum = function sum(num1, num2) {return num1 + num2;
      };var s = new Set();
      [2, 3, 5, 4, 5, 2, 2].forEach(function (x) {return s.add(x);
      });var Point = /*#__PURE__*/function () {function Point(x, y) {_classCallCheck(this, Point);this.x = x;this.y = y;}_createClass(Point, [{key: "toString",value: function toString() {return '(' + this.x + ', ' + this.y + ')';}}]);return Point;
      }();// es6 swc
      function _classCallCheck(instance, Constructor) {if (!(instance instanceof Constructor)) {throw new TypeError("Cannot call a class as a function");}
      }
      function _defineProperties(target, props) {for(var i = 0; i < props.length; i++){var descriptor = props[i];descriptor.enumerable = descriptor.enumerable || false;descriptor.configurable = true;if ("value" in descriptor) descriptor.writable = true;Object.defineProperty(target, descriptor.key, descriptor);}
      }
      function _createClass(Constructor, protoProps, staticProps) {if (protoProps) _defineProperties(Constructor.prototype, protoProps);if (staticProps) _defineProperties(Constructor, staticProps);return Constructor;
      }
      var PI = 3.1415;
      var x = 1;
      var foo = 1, bar = 2, baz = 3;
      var node = {loc: {start: {line: 1,column: 5}}
      };
      var loc = node.loc, start = node.loc.start, _loc = node.loc, line = _loc.start.line;
      var sum = function(num1, num2) {return num1 + num2;
      };
      var s = new Set();
      [2,3,5,4,5,2,2
      ].forEach(function(x1) {return s.add(x1);
      });
      var Point = /*#__PURE__*/ function() {"use strict";function Point(x2, y) {_classCallCheck(this, Point);this.x = x2;this.y = y;}_createClass(Point, [{key: "toString",value: function toString() {return "(" + this.x + ", " + this.y + ")";}}]);return Point;
      }();//# sourceMappingURL=es6_swc.js.map
  • 从上面的数据可以看出

    • 在打包代码的对比, ESBuild的速度(20ms)远快于Webpack(1680ms)

    • 在编译代码的对比, swc也对babel有比较明显的性能优势(0.63s vs 2.38s).

    • 需要额外说明的是, 用作实例的代码非常简单, 并且在对比中也没有充分使用各个构建工具所有的构建优化策略, 只是对比最基础的配置下几种工具的速度, 这个和各个工具所罗列的benchmark数据会有差异, 并且构建速度也和硬件性能/运行时状态有关.

  • ESBuild/swc这么快? 那是不是可以直接把Webpack/Babel扔掉了? 也别急, 目前的ESBuild和Swc可能还不能完全替代Webpack. 但是通过这篇分享我们也许可以对它们有一个更全面的认知, 也可以探索后边在工作中使用这些新一代前端工具的机会

ESBuild/swc在前端生态中的定位

  • 在当今的前端世界里, 新工具层出不穷, 有的时候不同的工具太多以至于有段时间我完全分不清这些工具各自的功能是什么, 所以我们先来研究一下ESBuild/swc在当今前端工程体系中的角色.

b2fecd194251038c06e03ca74085b466.png
  • 从上面的截图中选择几个我们日常接触最频繁的前端工程化工具:

    • Loader: 因为前端项目中包含各种文件类型和数据, 需要将其进行相应的转换变成JS模块才能为打包工具使用并进行构建. JS的Compiler和其他类型文件的Loader可以统称为Transfomer.

    • Plugin: 可以更一步定制化构建流程, 对模块进行改造(比如压缩JS的Terser)

    • 还有一些前端构建工具是基于通用构建工具进行了一定封装或者增加额外功能的, 比如CRA/Jupiter/Vite/Umi

    • Task Runner 任务运行器: 开发者设置脚本让构建工具完成开发、构建、部署中的一系列任务, 大家日常常用的是npm/yarn的脚本功能; 在更早一些时候, 比较流行Gulp/Grunt这样的工具

    • Package Manager 包管理器: 这个大家都不会陌生, npm/Yarn/pnmp帮开发者下载并管理好依赖, 对于现在的前端开发来说必不可少.

    • Compiler/Transpiler 编译器: 在市场上很多浏览器还只支持ES5语法的时候, Babel这样的Comipler在前端开发中必不可少; 如果你是用TypeScript的话, 也需要通过tsc或者ts-loader进行编译.

    • Bundler 打包工具: 从开发者设置的入口出发, 分析模块依赖, 加载并将各类资源最终打包成1个或多个文件的工具.

639f46332c1abab1d014e44c8313e109.png

  • ESBuild的定位是Bundler, 但是它也是Compiler(有Transform代码的能力)

3d4399f686fbc5027068e206f9f670f2.png
  • swc自称其定位为Compiler + Bundler, 但是目前spack还不是很好用

ecbf5a87c415c5744dd45613a30e76fc.png

ESBuild/SWC为何这么快?

  • 思考一下, Go & Rust这两个语言和JavaScript相比有什么差异?

ESBuild的实现(参考ESBuild FAQ[3])

  • 由Go实现并编译成本地代码: 多数Bundler都是由JavaScript实现的, 但是CLI应用对于JIT编译语言来说是性能表现最不好的。每次运行Bundler的时候, JS虚拟机都是以第一次运行代码的视角来解析Bundler(比如Webpack)的代码, 没有优化信息. 当ESBuild在解析JavaScript的时候, Node还在解析Bundler的JS代码

  • 重度使用并行计算: Go语言本身的设计就很重视并行计算, 所以ESBuild对这一点会加以利用. 在构建中主要有三个环节: 解析(Parsing), 链接(Linking)和代码生成(Code generation), 在解析和代码生成环节会尽可能使用多核进行并行计算

  • ESBuild 中的一切代码从零实现: 通过自行实现所有逻辑来避免第三方库带来的性能问题, 统一的数据结构可以减少数据转换开销, 并且可以根据需要改变架构, 当然最大的缺点就是工作量倍增.

    • 令人想到了SpaceX这家公司, 大量零部件都是自己内部生产, 有效降低生产成本1cbdbabc52242fe07101ba52f25c1b4f.png

  • 对内存的高效使用: ESBuild在实现时尽量减少数据的传递以及数据的转换, ESBuild尽量减少了对整体AST的传递, 并且尽可能复用AST数据, 其他的Bundler可能会在编译的不同阶段往复转换数据格式(string -> TS -> JS -> older JS -> string...). 在内存存储效率方面Go也比JavaScript更高效.

swc的实现

  • swc的官方文档和网站并没有对swc内部实现的较为具体的解释, 根据其博客[4]中的一些分析, babel缓慢的主要原因还是来自于其单线程的特性

一点总结

  • 从ESBuild和swc的官方资源中, 共同提到的一点就是利用好并行计算。JS因为在设计之初的目标就是服务好浏览器场景, 所以单线程 & 事件驱动并不适合用来进行CPU密集的计算, 而ESBuild/Rust也正是在这一点上对基于Node的构建工具拥有系统性的速度优势。

如何用ESBuild/swc提效?

  • 现在我们知道ESBuild/Rust是做什么的, 并且有什么特点, 我们可以在工作中如何利用ESBuild/swc去改善我们的开发体验呢?

使用ESBuild

  • ESBuild在API层面上非常简洁, 主要的API只有两个: Transform和Build, 这两个API可以通过CLI, JavaScript, Go的方式调用

    • Transform主要用于对源代码的转换, 接受的输入是字符串, 输出的是转换后的代码

      # 用CLI方式调用, 将ts代码转化为js代码
      echo 'let x: number = 1' | esbuild --loader=ts => let x = 1;
    • Build主要用于构建, 接受的输入是一个或多个文件

      // 用JS模式调用build方法
      require('esbuild').buildSync({entryPoints: ['in.js'],bundle: true,outfile: 'out.js',
      })
  • ESBuild的内容类型(Content Type)包括了ES在打包时可以解析的文件类型, 这一点和Webpack的loader概念类似, 下面的例子是在打包时用JSX Loader解析JS文件.

require('esbuild').buildSync({entryPoints: ['app.js'],bundle: true,loader: { '.js': 'jsx' },outfile: 'out.js',
})
  • ESBuild也包含插件系统, 可以在构建过程中(Transform API无法使用插件)通过插件更改你的构建流程

// 来自于官网的插件示范
let envPlugin = {name: 'env',setup(build) {// Intercept import paths called "env" so esbuild doesn't attempt// to map them to a file system location. Tag them with the "env-ns"// namespace to reserve them for this plugin.build.onResolve({ filter: /^env$/ }, args => ({path: args.path,namespace: 'env-ns',}))// Load paths tagged with the "env-ns" namespace and behave as if// they point to a JSON file containing the environment variables.build.onLoad({ filter: /.*/, namespace: 'env-ns' }, () => ({contents: JSON.stringify(process.env),loader: 'json',}))},
}// 使用插件
require('esbuild').build({entryPoints: ['app.js'],bundle: true,outfile: 'out.js',plugins: [envPlugin],
}).catch(() => process.exit(1))
  • 在其他工具中使用ESBuild

    • 如果你觉得目前完全使用ESBuild还不成熟, 也可以在Webpack体系中使用ESBuild的loader来替代babel用于进行代码转换, 除此之外, esbuild-loader[5]还可以用于JS & CSS的代码最小化.

      const { ESBuildMinifyPlugin } = require('esbuild-loader')module.exports = {rules: [{test: /.js$/,// 使用esbuild作为js/ts/jsx/tsx loaderloader: 'esbuild-loader',options: {loader: 'jsx',  target: 'es2015'}},],// 或者使用esbuild-loader作为JS压缩工具optimization: {minimizer: [new ESBuildMinifyPlugin({target: 'es2015'})]}
      }
  • 注意点

    • ESBuild不能转ES5代码和一些其他语法, 详情可参考https://esbuild.github.io/content-types/#javascript-caveats

使用Vite

  • 要说2021年前端圈关注度较高的新工具, Vite可以说是名列前茅, 那么Vite和ESBuild/swc有什么关系呢?

5f1fad5c8379290e1df3873071de77d1.png
  • Vite的核心理念是使用ESM+编译语言工具(ESBuild)加快本地运行

b00c1ca42d4423f0227ba5e7934d3e36.png
  • Vite在开发环境使用了ESBuild进行预构建, 在生产环境使用了Rollup打包, 后续也有可能使用ESBuild进行生产环境的构建.

84bfc708c5e2b213980c9209019088e7.png
  • 支持ES5需要引入插件 https://github.com/vitejs/vite/tree/main/packages/plugin-legacy

使用swc

  • Comilation

    • Transform: 代码转换API, 输入源代码 => 输出转换后的代码

    • Parse: 对源代码进行解析, 输出AST

    • Minify: 对代码进行最小化

    • 可以使用swc命令行工具(swc/cli)配合配置文件[6]对文件进行编译

      # Transpile one file and emit to stdout
      npx swc ./file.js# Transpile one file and emit to `output.js`
      npx swc ./file.js -o output.js# Transpile and write to /output dir
      npx swc ./my-dir -d output
    • swc的核心部分swc/core主要有三种API

    • swc也推出了swc/wasm模块, 可以让用户在浏览器环境使用wasm进行代码转换

    • 如果你想在Webpack体系下使用swc(替代babel), 也可以使用swc-loader

      8094089a2c2e04bb920a5b60954844cc.png
  • Bundle

    • ⚠️swc也支持进行打包功能, 但是目前功能还不很完备, 并且在使用中也有不少Bug. 笔者目前在本地尝试用spack打包一个简单的React应用目前还不成功, 还做不到开箱即用

    • 6bbee43f8f394eea204ace0acc9ae52a.png

    • 目前swc的Bundle工具叫spack, 后续会改名为swcpack.

    • 打包可以通过spack.config.js[7]文件进行配置

一点点总结和思考

全文总结

  • ESBuild/swc是用编译型语言编写的新一代前端工具, 对JS编写的构建工具有系统级的速度优势

  • ESBuild可以用于编译JS代码和模块打包, swc号称也都可以支持两者但是其打包工具还处于早期开发阶段

  • 目前这两个工具还不能完全替代Webpack等主流工具这些年发展出的庞大生态

  • 当已有的基础设施稳定并且替换成本较大时, 可以尝试渐进式的利用新工具(loader)或者Vite这种基于ESBuild二次封装的构建工具

延伸思考

  • 持续关注前端生态新发展, 利用好开源社区提升研发效率和体验的新工具.

  • 在使用新工具的同时, 了解或参与到其背后的技术原理, Go可以作为服务端语言, Rust可以作为系统编程语言, 学习新语言能打开新天地, 岂不美哉?

❤️感谢收看❤️

❤️ 谢谢支持

以上便是本次分享的全部内容,希望对你有所帮助^_^

喜欢的话别忘了 分享、点赞、收藏 三连哦~。

欢迎关注公众号 趣谈前端 收获大厂一手好文章~

这篇关于ESBuild SWC浅谈: 新一代构建工具的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/517032

相关文章

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

高效录音转文字:2024年四大工具精选!

在快节奏的工作生活中,能够快速将录音转换成文字是一项非常实用的能力。特别是在需要记录会议纪要、讲座内容或者是采访素材的时候,一款优秀的在线录音转文字工具能派上大用场。以下推荐几个好用的录音转文字工具! 365在线转文字 直达链接:https://www.pdf365.cn/ 365在线转文字是一款提供在线录音转文字服务的工具,它以其高效、便捷的特点受到用户的青睐。用户无需下载安装任何软件,只

Retrieval-based-Voice-Conversion-WebUI模型构建指南

一、模型介绍 Retrieval-based-Voice-Conversion-WebUI(简称 RVC)模型是一个基于 VITS(Variational Inference with adversarial learning for end-to-end Text-to-Speech)的简单易用的语音转换框架。 具有以下特点 简单易用:RVC 模型通过简单易用的网页界面,使得用户无需深入了

浅谈主机加固,六种有效的主机加固方法

在数字化时代,数据的价值不言而喻,但随之而来的安全威胁也日益严峻。从勒索病毒到内部泄露,企业的数据安全面临着前所未有的挑战。为了应对这些挑战,一种全新的主机加固解决方案应运而生。 MCK主机加固解决方案,采用先进的安全容器中间件技术,构建起一套内核级的纵深立体防护体系。这一体系突破了传统安全防护的局限,即使在管理员权限被恶意利用的情况下,也能确保服务器的安全稳定运行。 普适主机加固措施:

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念

maven 编译构建可以执行的jar包

💝💝💝欢迎莅临我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」👈,「stormsha的知识库」👈持续学习,不断总结,共同进步,为了踏实,做好当下事儿~ 专栏导航 Python系列: Python面试题合集,剑指大厂Git系列: Git操作技巧GO

嵌入式Openharmony系统构建与启动详解

大家好,今天主要给大家分享一下,如何构建Openharmony子系统以及系统的启动过程分解。 第一:OpenHarmony系统构建      首先熟悉一下,构建系统是一种自动化处理工具的集合,通过将源代码文件进行一系列处理,最终生成和用户可以使用的目标文件。这里的目标文件包括静态链接库文件、动态链接库文件、可执行文件、脚本文件、配置文件等。      我们在编写hellowor

超强的截图工具:PixPin

你是否还在为寻找一款功能强大、操作简便的截图工具而烦恼?市面上那么多工具,常常让人无从选择。今天,想给大家安利一款神器——PixPin,一款真正解放双手的截图工具。 想象一下,你只需要按下快捷键就能轻松完成多种截图任务,还能快速编辑、标注甚至保存多种格式的图片。这款工具能满足这些需求吗? PixPin不仅支持全屏、窗口、区域截图等基础功能,它还可以进行延时截图,让你捕捉到每个关键画面。不仅如此

利用命令模式构建高效的手游后端架构

在现代手游开发中,后端架构的设计对于支持高并发、快速迭代和复杂游戏逻辑至关重要。命令模式作为一种行为设计模式,可以有效地解耦请求的发起者与接收者,提升系统的可维护性和扩展性。本文将深入探讨如何利用命令模式构建一个强大且灵活的手游后端架构。 1. 命令模式的概念与优势 命令模式通过将请求封装为对象,使得请求的发起者和接收者之间的耦合度降低。这种模式的主要优势包括: 解耦请求发起者与处理者

Jenkins构建Maven聚合工程,指定构建子模块

一、设置单独编译构建子模块 配置: 1、Root POM指向父pom.xml 2、Goals and options指定构建模块的参数: mvn -pl project1/project1-son -am clean package 单独构建project1-son项目以及它所依赖的其它项目。 说明: mvn clean package -pl 父级模块名/子模块名 -am参数