15 年来,Node.js 一直是 Web 开发的基石。自 2009 年发布以来,它已经支持超过 630 万个网站,98% 的《财富》500 强公司都在使用它。
作为一个强大的开源运行时环境,Node.js 建立在我们熟悉的 JavaScript 基础之上,拥有轻量级和事件驱动的架构。这使得它非常适合用来构建可以处理大量并发请求的可扩展的实时应用程序。
加上其活跃并且不断增长的开源社区,以及来自 OpenJS 基金会的强大支持,使其成为了现代 Web 开发的支柱。
但最近,关于 Node.js 衰退的传言开始流传起来,Bun、Deno 等强大的 JavaScipt 运行时环境如雨后春笋一样陆续推出,这是否意味着 Node.js 真的走向衰退了呢?
在今天这篇文章里,我们一起来深入研究一些关键指标,看看 Node.js 是否依然坚挺,以及再看一看已经发布和即将在 Node.js 上线的关键特性。
有些人可能会认为新的技术必然会淘汰掉旧的技术。但事实是,进步往往建立在现有的基础之上。以 COBOL 为例,这个在 1959 年创建的编程语言至今仍在被广泛使用。虽然它可能不是进行 Web 开发的首选,但 COBOL 对于维护银行、金融和政府机构的核心业务系统仍至关重要。根据最新的 Tiobe 指数,COBOL 的普及率依然正在增加,目前在 Ruby 和 Rust 之间。它持久的相关性突显了一个关键点:技术进步并不总是意味着丢弃过去。
让我们考虑另一个在 Web 开发领域中的老牌选手:jQuery。这个 JavaScript 库比 Node.js 还要早三年发布,目前超过 95% 的 JavaScript 网站和总体上 77% 的网站都在使用 jQuery。jQuery 持久的流行表明,一个技术的年龄并不一定决定其相关性。就像 jQuery 一样,尽管 Node.js 年轻一些,但它同样有潜力保持其作为 Web 开发者宝贵工具的地位。
根据 StackOverflow 的调查,Node.js 依然是最受欢迎的技术。这种成功依赖于 Node.js 和 npm 的强大组合。它们解决了大规模软件复用的挑战,这在以前是难以实现的。
因此,预先编写的代码模块的使用呈爆炸式增长,这也巩固了 Node. js 作为霸主的地位。
readable-stream 的下载量从 2022 年略超过30亿次增长到 2023 年接近70亿次,这意味着使用量在三年内翻了一番。
Node.js 每个月的下载量高达 1.3 亿次。
当然,其中很大一部分下载量实际上是头文件。这些头文件是在执行 npm i 命令期间临时下载的,用于编译二进制附加组件。一旦编译完成,这些附加组件会存储在你的系统中以供以后使用。
从操作系统的下载量来看,Linux 高居榜首。这也是合理的,因为 Linux 通常是持续集成(CI)- 软件在开发过程中进行的自动化测试过程的首选平台。尽管 Linux 在 CI 中占据主导地位,开源项目(OSS)通常也会在 Windows 上进行额外的测试以确保万无一失。
这个高下载量的趋势转化为实际使用情况。在 2021 年,Node.js 二进制文件的下载量为 3000 万次,这一数字在 2024 年跃升至 5000 万次。在 2023 年,Docker 中心上的 Node.js 镜像下载量超过了 8 亿次,这为我们提供了 Node.js 在生产环境中使用情况的重要参考。
许多开发者和团队在不知不觉中由于没有更新 Node.js 而使他们的应用程序面临风险,但是保持最新版本的更新还是非常重要的。
Node.js 提供了长期支持(LTS)计划,以确保关键应用程序的稳定性和安全性。然而,版本最终会达到其生命周期的终点,这意味着它们将不再接收安全补丁。这使得使用这些过时版本构建的应用程序容易受到攻击。
例如,Node.js 版本 14 和 16 现在已经被弃用。尽管如此,这些版本每月仍然有数百万次下载 —— 在2 月份,Node 16 被下载了 2500 万次,而 Node 14 被下载了大约 1000 万次。令人震惊的是,一些开发者甚至还在使用更旧的版本,如 Node 10 和 Node 12。
好消息是:更新 Node.js 非常简单。推荐的方法是每两个 LTS 版本进行一次升级。例如,如果你当前使用的是 Node.js 16(已不再支持),你应该迁移到最新的 LTS 版本,目前是 Node.js 20。不要让过时的软件使你的应用程序暴露在安全威胁之中。
安全修复程序按季度批量发布。去年,TSC 共收到 80 份意见书。
当然,这种对安全性的承诺离不开开源安全基金会(OpenSSF)的支持。通过由 Microsoft、Google 和 Amazon 资助的 OpenSSF 领导的 Alpha-Omega 项目,Node.js 获得了专门用于提升其安全状况的资助。Alpha-Omega 项目于 2022 年启动,旨在通过更快地识别和解决漏洞,使关键的开源项目更加安全。这种合作关系,再加上 Node.js 专门用于安全工作的资金,展示了在保护 Node.js 用户安全方面的强烈承诺。
ESM
Node.js 已经全面支持 ECMAScript 模块(ESM)。ESM 提供了一种更现代的代码结构方式,使代码更简洁、更易于维护。
ESM 的一个关键优势是能够在 import 语句中显式声明依赖项。这不仅提升了代码的可读性,还能帮助你清晰地了解项目依赖。正因如此,ESM 正快速成为新 Node.js 项目的首选模块格式。
下面是如何在 Node 中使用 ESM 模块的示例:
// conardLi.mjsfunction addTwo(num) { return num + 17;}export { addTwo };// app.mjsimport { addTwo } from './conardLi.mjs';// Prints: 34console.log(addTwo(17));
Threads
Node.js 还引入了 Worker Threads,让用户可以将复杂的计算任务卸载到独立的线程中。这可以解放主线程用于处理用户请求,从而带来更流畅和更具响应性的用户体验。
const { Worker, isMainThread, setEnvironmentData, getEnvironmentData,} = require('node:worker_threads');if (isMainThread) { setEnvironmentData('Hello', 'code秘密花园!'); const worker = new Worker(__filename);} else { console.log(getEnvironmentData('Hello')); // Prints 'code秘密花园!'.}
Fetch
Node.js 现在内置了 Fetch API 的实现,这是一种现代且符合规范的方式,用于通过网络获取资源。这意味着你可以编写更简洁、统一的代码,而无需依赖外部库。
Node.js 还引入了几个新功能来增强与 Web 平台的兼容性。这些功能包括:
// 使用 Fetch API 进行网络请求const fetch = require('node-fetch');async function fetchData() { const response = await fetch('https://api.example.com/data'); const data = await response.json(); console.log(data);}fetchData();// 使用 Web Streams 处理大数据流const { WritableStream } = require('web-streams-polyfill/ponyfill/es2018');const writableStream = new WritableStream({ write(chunk) { console.log('接收到的数据块:', chunk); }, close() { console.log('数据流处理完成'); }, abort(err) { console.error('数据流处理出错:', err); },});// 创建 FormData 并发送const formData = new FormData();formData.append('key', 'value');fetch('https://api.example.com/submit', { method: 'POST', body: formData}).then(response => response.json()) .then(data => console.log(data));// 使用 StructuredClone 创建数据的深拷贝const structuredClone = require('structured-clone');const originalData = { a: 1, b: { c: 2 } };const clonedData = structuredClone(originalData);console.log(clonedData);// 使用 TextEncoder 和 TextDecoder 处理文本const { TextEncoder, TextDecoder } = require('util');const encoder = new TextEncoder();const decoder = new TextDecoder('utf-8');const encoded = encoder.encode('Hello, world!');console.log(encoded);const decoded = decoder.decode(encoded);console.log(decoded);// 使用 Blob 处理二进制数据const { Blob } = require('buffer');const blob = new Blob(['Hello, world!'], { type: 'text/plain' });blob.text().then(text => console.log(text));
Promise
Node.js 提供了内置的 Promise 功能,为处理异步任务的结果提供了一种更简洁、更结构化的方式。
告别了回调地狱 — 使用 Promise,你可以编写更自然流畅、易于理解的代码。
下面是一个使用 fs/promises 模块中的 readFile 方法的实用示例:
const fs = require('fs/promises');async function readFileAsync(filePath) { try { const data = await fs.readFile(filePath, 'utf8'); console.log('文件内容:', data); } catch (error) { console.error('读取文件时出错:', error); }}// 调用示例readFileAsync('./example.txt');
Node.js 核心模块
Node.js 引入了 node: 前缀为核心模块建立了一个清晰的区分,使核心模块和用户引入的模块区别更加明显。
这个前缀就像一个标签,瞬间将一个模块标识为 Node.js 的核心构建模块。这一改变给开发者带来了几个好处:
下面是如何使用 node: 前缀导入核心模块的示例:
import test from 'test';import assert from 'assert';Vs import test from 'node:test';import assert from 'node:assert';
Watch
在引入此功能之前,nodemon 是用于监视文件更改的最流行的软件包。
现在,--watch 标志可以提供下面的功能:
为了进行更精细的控制,--watch-path 标志可以让你准确指定要监视的文件。
AsyncLocalStorage
AsyncLocalStorage 允许在 Web 请求的整个生命周期或任何其他异步持续时间内存储数据。它类似于其他语言中的线程本地存储。
AsyncLocalStorage 使我们能够创建 React Server 组件这样的功能,它充当了 Next.js 请求存储的基础。这些组件简化了 React 应用程序中的服务器渲染,最终改善了开发者的体验。
import http from 'node:http';import { AsyncLocalStorage } from 'node:async_hooks';const asyncLocalStorage = new AsyncLocalStorage();function logWithId(msg) { const id = asyncLocalStorage.getStore(); console.log(`${id !== undefined ? id : '-'}:`, msg);}let idSeq = 0;http.createServer((req, res) => { asyncLocalStorage.run(idSeq++, () => { logWithId('start'); // Imagine any chain of async operations here setImmediate(() => { logWithId('finish'); res.end(); }); });}).listen(8080);http.get('http://localhost:8080');http.get('http://localhost:8080');// Prints:// 0: start// 1: start// 0: finish// 1: finish
WebCrypto
这个标准化 API 直接在 Node.js 环境中提供了一组强大的加密工具。
借助 WebCrypto,我们可以利用以下功能:
通过将 WebCrypto 合并到 Node.js 应用程序中,我们可以显着增强其安全状况并保护用户的数据。
const {subtle} = require('node:crypto').webcrypto;(async function () {const key = await subtle.generateKey({name:'HMAC',hash: 'SHA-256',length: 256}, true, ['sign', 'verify'] )const enc = new TextEncoder();const message = enc.encode('I love cupcakes');const digest = await subtle.sign({name: 'HMAC'}, key, message)}) ()
Utils.ParseArgs()
Node.js 提供了一个名为 Utils.ParseArgs() 的内置实用程序(或 node:util 模块中的 parseArgs 函数),它简化了在应用程序中解析命令行参数的任务。这消除了对外部模块的需求,使我们的代码库更加精简。
它接受传递给 Node.js 脚本的命令行参数,并将它们转换为更可用的格式,可以让我们轻松地在代码中访问和使用这些参数。
import { parseArgs } from 'node:util';const args = ['-f', '--bar', 'b'];const options = { foo: { type: 'boolean', short: 'f', }, bar: { type: 'string', },};const { values, positionals,} = parseArgs({ args, options });console.log(values, positionals);// Prints: [Object: null prototype] { foo: true, bar: 'b' } []
权限模型
Node.js 进程对系统资源的访问及其可以使用这些资源执行的操作可以通过权限进行管理。其他模块可以访问哪些模块也可以通过权限来管理。
process.permission.has('fs.write');//trueprocess.permission.deny('fs.write', '/home/user');process.permission.has('fs.write');//trueprocess.permission.has('fs.write', '/home/user');//false
详细可以看这篇文章:Node.js 20 为啥要搞个权限模型?到底有啥用?
require(esm)
一个新的标志已经发布,允许开发人员同步使用 ESM 模块。
'use strict'const {answer} = require('./esm.mjs')console.log(answer)
此外,新标志 --experimental-detect-module 允许 Node.js 检测模块是 commonJS 还是 esm。这个新标志简化了用 Javascript 编写 Bash 脚本。