网站首页 > 技术文章 正文
大家好,很高兴又见面了,我是"高级前端?进阶?",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!
今天给大家带来的主题是运行时 Bun v0.6.0版本发布后又添王炸,即支持Bun宏。关于JavaScript运行时的发展,以前有很多文章重点介绍过,下面是我已发布文章的传送门:
- 《 JS Runtime vs. JS Engine!Deno/Bun/Node是运行时!》
- 《 前有Deno、后有Bun、Node.js已穷途末路?》
- 《 Node.js已死!Bun永生?》
- 《 Node.js、Deno、Bun 6大典型场景性能大PK?》
- 《 Deno v1.34 发布!全面拥抱 npm 生态 》
- 《 盘点全网最火的6+ JavaScript 运行时!Node/Deno/Bun 在列! 》
对于每个运行时不了解的可以仔细阅读上面的文章,甚至可以关注我在头条发布的关于运行时的合集。话不多说,直接进入正题。
1.Bun v0.6.0 新特性概览
Bun v0.6 号称是迄今为止最大的 Bun 版本更新,在 v0.6 版本中,Bun 内置了一个 JavaScript 和 TypeScript 打包器和代码压缩器。可以使用 Bun v0.6 来打包前端应用程序或将代码打包到独立的可执行文件中。
同时,Bun 官方团队还一如既往地忙于提高运行时性能和修复错误,比如:
- writeFile() 在 Linux 上的速度提高了 20%
- 对 Node.js 兼容性和 Web API 兼容性的大量错误修复
- 对 TypeScript 5.0 语法的支持
- 修复 bun install 引起的各种问题
借助于这个新的 Bun 运行时,Bun 的代码库已经包含了快速解析、转换源代码的大量基础工作(在 Zig 中实现)。打包测试结果表明: 在基准测试中(源自 esbuild 的 three.js 基准测试),Bun 比 esbuild 快 1.75 倍,比 Parcel 2 快 150 倍,比 Rollup + Terser 快 180 倍,比 Webpack 快 220 倍,比 rsPack 快 26 倍。
目前 Bun 在 Github 上有超过 41.7k 的 star、1.1k 的 fork、55.2k 的项目依赖量,代码贡献者 234+,妥妥的前端顶级开源项目。同时 Bun 在 2023 年也被评为最火的前端 10 大明星开源项目,引起了前端开发者的无限热情。
2.什么是 Bun 宏(Macros)
两周前,Bun 官方发布了 v0.6.0 版本, 推出了新的 JavaScript 打包器。 2023 年 5 月 31 日,Bun 官方又发布了一项新功能,实现了 Bun 打包程序和运行时之间的紧密集成,即 Bun 宏(Bun Macros)。
宏是一种在打包时运行 JavaScript 函数的机制。 从这些函数返回的值直接内联到 JavaScript 包中。
比如下面的示例,其表示返回随机数的简单函数:
export function random() {
return Math.random();
}
在源代码中,可以使用 import 属性语法将此函数导入为宏。如果以前没有见过这种语法,它是第 3 阶段 TC39 提案,可让开发者将额外的元数据附加到 import 语句。
import { random } from './random.ts' with { type: 'macro' };
console.log(`Your random number is ${random()}`);
现在可以将这个文件与 bun build 打包在一起,打包文件将打印到标准输出。
bun build ./cli.tsx
// console.log(`Your random number is ${0.6805550949689833}`);
如上面所见,随机函数的源代码在包中没有出现。 相反,它在打包期间执行,并且函数调用 (random()) 被替换为函数的结果。 由于源代码永远不会包含在最终的包中,因此宏可以安全地执行特权操作,例如:从数据库中读取。
3.何时使用宏
对于本来可以使用一次性构建脚本的事情,打包时候代码执行可以更容易维护。 它与其余代码一起存在,与构建的其余部分一起运行,自动并行化,如果失败,构建也会失败。
但是,如果发现自己在打包时需要运行大量代码,可以考虑运行一个服务器,接下来一起看看宏的一些使用场景。
嵌入最新的 git commit hash
import { getGitCommitHash } from './getGitCommitHash.ts' with { type: 'macro' };
console.log(`The current Git commit hash is ${getGitCommitHash()}`);
下面是 getGitCommitHash.ts 的内容:
export function getGitCommitHash() {
const { stdout } = Bun.spawnSync({
cmd: ["git", "rev-parse", "HEAD"],
stdout: "pipe",
});
return stdout.toString();
}
当构建代码时,getGitCommitHash 被替换为调用函数的结果:
//output.js
console.log(`The current Git commit hash is 3ee3259104f`);
// cli中输入下面内容
bun build --target=browser ./in-the-browser.ts
在打包时发出 fetch() 请求
在下面的示例中,使用 fetch() 发出 HTTP 请求,使用 HTMLRewriter 解析 HTML 响应,并返回一个包含标题和元标记的对象,所有这些操作都是在打包时进行的。
// in-the-browser.tsx
import { extractMetaTags } from './meta.ts' with { type: 'macro' };
export const Head = () => {
const headTags = extractMetaTags("https://example.com");
if (headTags.title !== "Example Domain") {
throw new Error("Expected title to be 'Example Domain'");
}
return <head>
<title>{headTags.title}</title>
<meta name="viewport" content={headTags.viewport} />
</head>;
};
下面是 meta.ts 的内容:
export async function extractMetaTags(url: string) {
const response = await fetch(url);
const meta = {
title: "",
};
new HTMLRewriter()
.on("title", {
text(element) {
meta.title += element.text;
},
})
.on("meta", {
element(element) {
const name =
element.getAttribute("name") ||
element.getAttribute("property") ||
element.getAttribute("itemprop");
if (name) meta[name] = element.getAttribute("content");
},
})
.transform(response);
return meta;
}
extractMetaTags 函数在打包时被擦除并替换为函数调用的结果, 这意味着 fetch 请求发生在打包时,结果嵌入到打包产物中。 此外,抛出错误的分支也会被删除,因为它无法访问。
output.js 内容如下(打包命令为: bun build --target=browser --minify-syntax ./in-the-browser.ts ):
import { jsx, jsxs } from "react/jsx-runtime";
export const Head = () => {
jsxs("head", {
children: [
jsx("title", {
children: "Example Domain",
}),
jsx("meta", {
name: "viewport",
content: "width=device-width, initial-scale=1",
}),
],
});
};
export { Head };
4.Bun 宏如何工作
import 属性
Bun Macros 在 import 语句中注释了 {type: 'macro'},比如下面的示例:
import { myMacro } from './macro.ts' with { type: 'macro' }
import 属性是第 3 阶段 ECMAScript 提案,这意味着极有可能被添加为 JavaScript 语言的官方部分。当然,Bun 还支持导入断言语法,导入断言是导入属性的早期化身,现在已废弃(但已经被许多浏览器和运行时支持)。
import { myMacro } from "./macro.ts" assert { type: "macro" };
// Chrome>91、Edge>91、Safari>15、火狐、Opera不支持,整体支持率87.42%
// 但是目前已经废弃
当 Bun 的转译器遇到这种特殊 import 时,它会使用 Bun 的 JavaScript 运行时调用转译器内部的函数,并将 JavaScript 的返回值转换为 AST 节点。 这些 JavaScript 函数是在打包时调用的,而不是运行时。
宏的执行顺序
Bun 宏在遍历阶段在转译器中同步执行,但是发生在插件和转译器生成 AST 之前。 它们按照被调用的顺序执行。 转译器将等待宏完成执行后再继续,转译器还将等待宏返回的任何 Promise。
Bun 的打包器是多线程的, 因此,宏在多个生成的 JavaScript“worker”中并行执行。
死代码消除
打包器在运行和内联宏后执行无用代码消除,所以给定以下宏:
export function returnFalse() {
return false;
}
然后打包以下文件将产生一个空的 bundle。
import {returnFalse} from './returnFalse.ts' with { type: 'macro' };
if (returnFalse()) {
console.log("This code is eliminated");
}
5.宏的安全性
支持禁用
宏必须使用 { type: "macro" } 显式导入才能在打包时执行。 如果不调用这些导入,则不会产生任何效果,这与可能有副作用的常规 JavaScript 导入不同。
开发者可以通过将 --no-macros 标志传递给 Bun 来完全禁用宏,它会产生下面的构建错误:
error: Macros are disabled
foo();
^
./hello.js:3:1 53
宏在 node_modules 中被禁用
为了减少恶意包的潜在攻击面,不能从 node_modules/**/*内部调用宏。如果包试图调用宏,将看到如下错误抛出:
error: For security reasons, macros cannot be run from node_modules.
beEvil();
^
node_modules/evil/index.js:3:1 50
应用程序代码仍然可以从 node_modules 导入宏并调用它们:
import {macro} from "some-package" with { type: "macro" };
macro();
6.宏的限制
宏的结果必须是可序列化的
Bun 的转译器需要能够序列化宏的结果,以便将其内联到 AST 中。不过,目前支持所有 JSON 兼容的数据结构:
export function getObject() {
return {
foo: "bar",
baz: 123,
array: [1, 2, { nested: "value" }],
};
}
宏可以是异步的,也可以返回 Promise 实例, Bun 的转译器将自动等待 Promise 并内联结果。
export async function getText() {
return "async value";
}
转译器实现了用于序列化常见数据格式(如 Response、Blob、TypedArray)的特殊逻辑。
- TypedArray:解析为 base64 编码的字符串。
- Response:在相关的地方,Bun 将读取 Content-Type 并相应地序列化; 例如,类型为 application/json 的 Response 将被自动解析为一个对象,而 text/plain 将被内联为一个字 符串。 具有未知或未定义类型的响应将采用 base-64 编码。
- Blob:与 Response 一样,序列化取决于 type 属性
fetch 的结果是 Promise,所以可以直接返回。
export function getObject() {
return fetch("https://bun.sh");
}
大多数类的函数和实例(除了上面提到的那些)是不可序列化的,比如:
export function getText(url: string) {
// 这行不通!
return () => {};
}
输入参数必须是静态可分析的
宏可以接受输入,但仅限于有限的情况。该值必须是静态已知的。例如,不允许以下内容:
import {getText} from './getText.ts' with { type: 'macro' };
export function howLong() {
// `foo` 的值无法静态获知
const foo = Math.random() ? "foo" : "bar";
const text = getText(`https://example.com/${foo}`);
console.log("The page is ", text.length, " characters long");
}
但是,如果 foo 的值在打包时已知(例如,如果它是常量或另一个宏的结果),那么它是允许的:
import {getText} from './getText.ts' with { type: 'macro' };
import {getFoo} from './getFoo.ts' with { type: 'macro' };
export function howLong() {
// 这是有效的,因为 getFoo() 是静态已知的
const foo = getFoo();
const text = getText(`https://example.com/${foo}`);
console.log("The page is", text.length, "characters long");
}
将会输出以下结果:
function howLong() {
console.log("The page is", 1322, "characters long");
}
export { howLong };
7.本文总结
本文主要和大家介绍下 JavaScript 运行时 Bun v0.6.0版本发布后又添王炸,即支持Bun宏。相信通过本文的阅读,大家对 Bun宏 都会有一个初步的了解。
因为篇幅有限,文章并没有过多展开,如果有兴趣,可以在我的主页继续阅读,同时文末的参考资料提供了大量优秀文档以供学习。最后,欢迎大家点赞、评论、转发、收藏!
参考资料
https://bun.sh/blog/bun-macros#make-fetch-requests-at-bundle-time
https://github.com/tc39/proposal-import-attributes
https://caniuse.com/mdn-javascript_statements_import_import_assertions
https://bun.sh/blog/bun-v0.6.0
https://www.toutiao.com/article/7234320367169913347/
https://github.com/oven-sh/bun
猜你喜欢
- 2025-05-25 前端怎么打包成小程序和APP圈子系统开发平台圈子系统源码
- 2024-09-24 五种可视化方案分析webpack打包性能瓶颈
- 2024-09-24 我在实际前端项目中遇到的坑(Electron)
- 2024-09-24 vue-cli 大型项目打包优化
- 2024-09-24 vscode运行打包后的npm前端dist文件
- 2024-09-24 关于vue-cli 3配置打包优化要点
- 2024-09-24 一起来学习打包工具 rollup.js 入门,也许你会需要
- 2024-09-24 探寻webpack打包vue项目特别慢问题
- 2024-09-24 将前端框架vue打包后部署到node服务中,本文采用egg框架演示
- 2024-09-24 若依框架-前端静态资源如何整合到后端访问
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- 前端设计模式 (75)
- 前端性能优化 (51)
- 前端模板 (66)
- 前端跨域 (52)
- 前端缓存 (63)
- 前端react (48)
- 前端aes加密 (58)
- 前端md5加密 (49)
- 前端路由 (55)
- 前端数组 (65)
- 前端定时器 (47)
- 前端接口 (46)
- Oracle RAC (73)
- oracle恢复 (76)
- oracle 删除表 (48)
- oracle 用户名 (74)
- oracle 工具 (55)
- oracle 内存 (50)
- oracle 导出表 (57)
- oracle 中文 (51)
- oracle链接 (47)
- oracle的函数 (57)
- mac oracle (47)
- 前端调试 (52)
- 前端登录页面 (48)
本文暂时没有评论,来添加一个吧(●'◡'●)