编写跨运行时的 JavaScript 程序

前端技术百花齐放,但割裂也是全方位的:

  • 小程序。小程序是中国特色
  • 视图框架。 React、Vue 等视图框架割据。甚至框架大版本升级也会进一步造成割裂。前有 Angular、后有 Vue 2/3、现在 React RSC 也饱受争议
  • 服务端 vs 客户端。随着 SSR 以及全栈框架的流行,前端需要考虑编写服务端/客户端的同构代码,即 Write Once, Run on Both Client and Server。那 React Server Component 的 'use client'、'use server' 的写法可能会进一步加剧这种心智负担
  • 浏览器兼容性。浏览器兼容性适配是每个前端开发者必备的技能,现在很多开发者都不知道那个被 IE 蹂躏的年代。
  • npm、yarn、pnpm、pnpm 7、8... bun!
  • Webpack、Vite...
  • ...

前端一年,人间三年,技术迭代之快,一般人还真的很难跟上。前端开发者似乎一直摆脱不了'兼容性'、'跨平台'、碎片化的这些话题。

不过也有好的一面,这恰巧说明它生命力非常旺盛,前端开发的边界也一而再地被拓宽,打脸了前端已死的论调。

在 JavaScript 运行时(Runtime)领域,近些年也诞生了若干个 Node.js 的挑战者,比如 Deno、Bun...

Deno,Destroy Node?

Deno 和 Node.js 的创造者都是 Ryan Dahl, 如果说 Nodejs 是奥创,那个 Deno 就是为了打败奥创而发明的"幻视"。

从它的名字就可以看出,它的目标就是为了解决 Node.js 的设计缺陷。

Deno 一开始主打的特性是: 安全、开箱即用的 Typescript 支持、去中心化的模块、支持标准的 Web API、性能(基于 Rust)、完整的开发工具链(单元测试、格式化、检查等)

Deno 对标法

但是仅凭这些在 Node.js 庞大的生态市场面前,就是蚍蜉撼树:

事实对标法

所以,Deno 还是向现实低了头,在 Deno 1.28 开始就支持导入 npm 模块、Node.js 内置模块:

jsx 复制代码
import { readFileSync } from 'node:fs'

console.log(readFileSync('deno.json', { encoding: 'utf8' }))

Deno 作为一个二次创造的'轮子',自然在设计上、开发者体验上面要更加优秀。

除了从历史失败的设计中吸取的教训,它也从其他编程语言,譬如 Rust、Go 借鉴了一些设计和工程理念。

比如在设计方面,去中心化的模块加载、安全模型、向 Web 标准 API 看齐、开箱即用的 Typescript;

在工程化方面则体现在内置单元测试、基准测试、格式化、文档生成、打包成可执行文件。

在当前被各种'过度'工程化蹂躏的阶段,显得难得可贵。

Nextjs 的配置地狱

我觉得,另外一个比较重要的亮点就是向 Web 标准 API 看齐。和浏览器兼容是 Deno 的目标之一

比如支持使用 URL 来加载模块;还有一些看起来在服务端用不上的 API,如 Location、Navigator、localStorage,甚至还有 window

Web 标准 API 经过更加严格的设计,在质量和稳定性上都要较高的保证。

上文我们也提到了客户端/服务端的同构应用的开发,会给开发者带来额外的心智负担,那么对齐浏览器和服务端的 API 就可以缩小这个 Gap, 降低学习成本。

还有一个重要的意义如本文标题所示 ------ 跨平台。

不管是运行在浏览器、Worker、 Node.js、Deno、Bun、小程序的逻辑线程、还是各种云服务厂商提供的边缘计算运行时(Edge Runtime, 例如 Vercel Edge FunctionCloudflare Workers)、Serverless 运行时。Web 标准 API 将会是那条最低"水位线"

很多边缘计算/Serverless 运行时,出于轻量化和安全性考虑,仅支持部分 Web 标准 API。

不同运行时的对比,来源 Nextjs 官网

虽然现在各种 runtime 比较割裂, 不过我相信未来它们将走向统一的道路,谁能担此重任?

现在还不清楚,可能是 Bun、可能是 Deno,也有可能还是 Nodejs 吞并了其他竞争者,毕竟它也不是停滞不前(下文会详细介绍) 。

但不管怎样, Web Standard API 将在这个统一的道路上扮演重要的角色


Bun 1.0!

再看看 Bun,包子!

它也才发布一年多,在我落笔的此刻,它刚好也发布了 1.0 版本。

它的宣传点就是 ------ 快

它的目的很简单,就是为了取代 Node.js,就是要提供一个更快的运行时,消灭现在复杂的开发乱象。同时尽量不影响现有的框架和程序的运行(兼容 Node.js)

用"兼容并包"来描述它最好不过,比如它同时支持 ESM 和 CommonJS,甚至允许这两个模块在一个文件中并存,而现在主流的观点是 CommonJS 是一个过时的技术。

因此除了作为运行时,开箱支持 Typescript 之外。他还将提供比 Deno 更丰富的工具链:

  • 包管理器。扬言要取代 pnpm、yarn、npm
  • 打包工具。拳打 Vite、脚踢 rollup、深度碾压 Webpack
  • 测试运行器。Vitest、Jest 在它面前就是弟弟
  • ...

大有一番一统天下的架势(取代 Node、npm、webpack、jest 等)。不过是不是'大杂烩', 能不能吃得下这么多大饼还不确定,交给时间去验证吧!


Node 变得越来越好

不管是 Deno、还是后来的搅局者 Bun。我们可以发现一些趋势:

  • 除了核心的运行时,他们还花了很多精力打造一套开箱即用、开发工具链,更加注重开发体验。
  • 更加注重香 Web 标准 API 对齐。

有了这些鲶鱼,Node.js 也不是等着挨打的,这不:

  • 20.0
    • 内置支持 .env 文件,node --env-file=config.env index.js
    • 支持 await using
    • 加入了实验性的权限模型,借鉴 Deno
    • 单元测试运行器稳定了。Node 可以直接写单元测试了
    • Web Crypto API
    • 性能优化
  • 19.0
    • 支持 ---watch ,可以取代 nodemon
  • 18.0
    • Web Streams API
    • 引入实验性的单元测试运行器
    • 引入实验性的 watch mode
    • 支持 File
    • 支持编译从单文件可执行文件
    • 引入 Web Crypto API
    • 引入实验性的 ESM Loader Hooks API
  • 17.0
    • 引入实验性的 fetch
    • 支持 JSON Import assertions
    • readline 模块支持 Promise API
  • 16.0
    • Timer Promise API
    • 引入实验性的 Web Streams API
    • 引入 Corepack
    • Importing JSON modules now requires experimental import assertions syntax
    • 新增 util.parseArgs 可以解析命令行参数
    • 新增 --experimental-network-imports 可以像 Deno 一样导入 HTTP/HTTPS 模块
  • 14.0
    • 正式支持 ECMAScript Modules
    • 支持 Top-Level Await
    • 支持 EventTarget
    • 实验性的 AsyncLocalStorage
    • 支持 AbortControllerAbortSignal
  • ...

Node.js 正在变得更加'现代',尤其是近几个版本不乏有 Web API、工具链、性能优化这些更新。

所以,不管你用不用 Deno、Bun, 都要感谢它们让 Node 变得越来越好用。

与此同时,通过这些变化趋势,我们可以推测这些运行时会变得越来越同质化。卷嘛


编写跨运行时程序 ------ Web Standard API

随着运行时的百花齐放, 越来越多的现代的前端'框架' 都避免自己和 Node.js 直接耦合。

比如 Remix、Qwik、Astro、SvelteKit...

qwik 支持的部署平台

SvelteKit 各种平台的适配器

Astro 不建议你直接使用 Node.js API

Next.js 下,为了支持你的程序跑在不同的运行时上,也强加了一些约束,比如:

  • Middleware 的 request、response 继承自 Request 和 Response,只能进行非常有限的逻辑处理

  • Route Handler 使用的就是 Web Request / Response API

    jsx 复制代码
    import { cookies } from 'next/headers'
    
    export async function GET(request: Request) {
      const cookieStore = cookies()
      const token = cookieStore.get('token')
    
      return new Response('Hello, Next.js!', {
        status: 200,
        headers: { 'Set-Cookie': `token=${token.value}` },
      })
    }
  • page 和 layout 支持指定 runtime, 如果是 edge 只能使用受限的 Web API。Next.js 在构建时会严格检查你是否使用非法 API.

    jsx 复制代码
    // app/page.tsx
    // ❌ fs/promises 模块找不到
    import fs from 'fs/promises'
    
    export const runtime = 'edge'
    
    export default async function Home() {
      const content = await fs.readFile('package.json', 'utf-8')
      return (
        <main className="flex min-h-screen flex-col items-center justify-between p-24">
          {content}
        </main>
      )
    }

自'去 JavaScript' 之后,似乎 '去 Node.js' 也是一波潜在的小趋势

而编写跨运行时的 JavaScript 程序的秘诀在于:尽量往 Web Standard API 靠拢,比如:

  • 在设计服务端程序时,优先使用 RequestResponseURLBlob 这些 Web API 来响应 HTTP 请求;
  • 使用 fetchWebSocket 进行网络请求;
  • 文件系统操作可以使用 File APIFile System APIWeb Stream API
  • 使用 Worker 跑多线程任务;
  • 还有强大的 WebAssembly ...

当然,目前 Web API 的还是功能太弱了,毕竟不是专门为服务端设计的,很难覆盖复杂的需求。因此短期内 Node.js 还难以撼动,JavaScript 运行时领域也还会继续内卷。

期待有一天 Web Standard API 能一统天下,那时候就无所谓 Deno、Bun、还是 Node 了。

扩展阅读

相关推荐
JUNAI_Strive_ving14 分钟前
番茄小说逆向爬取
javascript·python
看到请催我学习23 分钟前
如何实现两个标签页之间的通信
javascript·css·typescript·node.js·html5
twins352042 分钟前
解决Vue应用中遇到路由刷新后出现 404 错误
前端·javascript·vue.js
qiyi.sky1 小时前
JavaWeb——Vue组件库Element(3/6):常见组件:Dialog对话框、Form表单(介绍、使用、实际效果)
前端·javascript·vue.js
煸橙干儿~~1 小时前
分析JS Crash(进程崩溃)
java·前端·javascript
哪 吒1 小时前
华为OD机试 - 几何平均值最大子数(Python/JS/C/C++ 2024 E卷 200分)
javascript·python·华为od
安冬的码畜日常1 小时前
【D3.js in Action 3 精译_027】3.4 让 D3 数据适应屏幕(下)—— D3 分段比例尺的用法
前端·javascript·信息可视化·数据可视化·d3.js·d3比例尺·分段比例尺
l1x1n02 小时前
No.3 笔记 | Web安全基础:Web1.0 - 3.0 发展史
前端·http·html
Q_w77422 小时前
一个真实可用的登录界面!
javascript·mysql·php·html5·网站登录
昨天;明天。今天。2 小时前
案例-任务清单
前端·javascript·css