news 2026/9/18 13:03:27

Node.js v22.8.0 发布解析:Compile Cache API、vm 原生上下文与 Test Runner 覆盖率阈值

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Node.js v22.8.0 发布解析:Compile Cache API、vm 原生上下文与 Test Runner 覆盖率阈值

Node.js v22.8.0 发布解析:Compile Cache API、vm 原生上下文与 Test Runner 覆盖率阈值

【免费下载链接】nodejs.orgThe Node.js® Website项目地址: https://gitcode.com/GitHub_Trending/no/nodejs.org

本文以 nodejs.org 仓库中收录的 Node.js v22.8.0(Current)官方发布博客(位于 v22.8.0.md,发布于 2024-09-03,由 Rafael Gonzaga 署名)为主体,深入解析该版本的三大特性——module.enableCompileCache()JS API、vm.constants.DONT_CONTEXTIFY原生上下文、test runner 覆盖率阈值——的用法与原理,并结合本仓库中生成、校验、渲染这篇发布博客的完整工程链路(发布脚本、博客数据管线、Next.js 路由),帮助读者既掌握 v22.8.0 的技术能力,也理解 nodejs.org 站点如何把一次 Node.js 版本发布变成一篇可静态化、可校验、可检索的博客文章。

一、发布概览与站点中的定位

该文档的 Frontmatter 声明了它在站点内的基本身份:

date: '2024-09-03T13:51:48.461Z' category: release title: Node.js 22.8.0 (Current) layout: blog-post author: Rafael Gonzaga

从源码结构看,category: release使该文章被归入“release”博客分类。blog.ts 中的mapBlogCategoryToPreviewTypereleaseannouncementsvulnerability一起映射为预览类型;generate.mjs 在构建期为每篇文章额外生成year-2024(发布年份)与all两个分类,并按文件名生成 slug/blog/release/v22.8.0。也就是说,这篇文章在站点上最终呈现的 URL 即由category + 文件名决定,与文档存放路径pages/en/blog/release/v22.8.0.md一一对应。

v22.8.0 属于 Node.js 22(Jod)系列的 Current 版本。22 线的大版本状态由 releaseData.mjs 中的getNodeReleaseStatus依据发布计划(是否过 EOL、是否已转 LTS)动态判定——22 线在 2024-09-03 时点尚未转 LTS,因此标题标注为 “(Current)”。

二、新特性一:module.enableCompileCache()—— 内置的磁盘代码缓存 JS API

发布文档原文指出:

This release adds a new APImodule.enableCompileCache()that can be used to enable on-disk code caching of all modules loaded after this API is called.

要点拆解:

  1. 作用对象:在调用该 API之后被加载的所有模块,都会获得 on-disk code caching(把 V8 编译后的字节码缓存到磁盘,二次加载时跳过编译阶段)。
  2. 此前限制:在此之前只能由终端用户通过环境变量NODE_COMPILE_CACHE开启,工具链与库的作者无法在代码里为“自己的代码”启用缓存。新 API 打破了这一限制,工具/库可以直接module.enableCompileCache()
  3. 与社区方案的对比:它是v8-compile-cache/v8-compile-cache-lib这类 npm 包的内置替代品,且性能更优,并原生支持 ESM(此前社区方案主要覆盖 CJS)。
  4. 配套能力:从该版本的 commit 列表中可以看到对应实现条目——src: add JS APIs for compile cache and NODE_DISABLE_COMPILE_CACHE(SEMVER-MINOR,Joyee Cheung 提交),说明除了开启 API 外,还同时补齐了NODE_DISABLE_COMPILE_CACHE环境变量所对应的 JS 侧控制面,方便嵌入方/工具在代码中显式禁用缓存。

使用上可以推断出这样的调用形态(需要 Node.js ≥ 22.8.0,且该 API 属于实验性特性,使用时可能需要--experimental-...类标志,具体以官方文档为准):

const module = require('node:module'); // 启用磁盘代码缓存后,其后加载的模块都会被缓存编译结果 module.enableCompileCache(); require('./heavy-lib'); // 首次加载后写入缓存,二次启动直接命中

对 CLI 工具与框架作者而言,这意味着冷启动耗时的优化不再依赖“用户记得设置环境变量”,而是可以随代码一起分发。

三、新特性二:vm.constants.DONT_CONTEXTIFY—— 可冻结的 vm 上下文

文档原文:

Node.js implements a flavor ofvm.createContext()and friends that creates a context without contextifying its global object when vm.constants.DONT_CONTEXTIFY is used. This is suitable when users want to freeze the context (impossible when the global is contextified i.e. has interceptors installed) or speed up the global access if they don't need the interceptor behavior.

技术解读:

  • contextify 的含义:常规vm.createContext()会为上下文对象安装“interceptor”(拦截器),使宿主侧与沙箱侧对同一对象的属性读写行为不同(例如宿主侧修改不会直接反映到沙箱内)。代价是全局对象访问路径变长,且安装了 interceptor 的 global 无法被Object.freeze()冻结。
  • 新选项的价值
    1. 可冻结:传入vm.constants.DONT_CONTEXTIFY后,创建的上下文不会被 contextify,用户可以对该上下文执行冻结,适用于“创建一次、永不变更”的沙箱模板;
    2. 更快的全局访问:如果业务不需要 interceptor 语义,跳过 contextify 直接缩短全局属性访问链路,降低 vm 内代码的运行开销。
  • 配套修复:同版本的 commitvm: return all own names and symbols in property enumerator interceptor(Chengzhong Wu)表明,property enumerator 拦截器在返回 own names/symbols 上的行为也在同一时期得到修正,vm 子系统的枚举语义更加一致。

典型适用场景:把一段只读的初始化脚本在“无拦截器”上下文中执行并冻结,再让后续多个执行复用该全局环境。

四、新特性三:test runner 支持覆盖率阈值

这是 v22.8.0 中最具 CI 实用价值的特性。文档完整说明了用法与参数:

  • 前提:需启用--experimental-test-coverage标志;
  • 三类阈值参数:
    • 分支覆盖率--test-coverage-branches=<threshold>
    • 函数覆盖率--test-coverage-functions=<threshold>
    • 行覆盖率--test-coverage-lines=<threshold>
  • 取值约束:<threshold>必须是 0–100 之间的整数,提供非法值会抛出TypeError
  • 退出行为:任意一类覆盖率未达标时,进程以退出码1结束。

文档给出的示例命令(原文照录,可直接复制运行):

$ node --experimental-test-coverage --test-coverage-lines=80 --test-coverage-branches=60 example.js

即要求行覆盖 ≥ 80%、分支覆盖 ≥ 60%,否则 CI 流水线中该命令直接失败。对应的实现提交为test_runner: add support for coverage thresholds(SEMVER-MINOR,Aviv Keller)。在 CI 里这相当于把“覆盖率门禁”内置进 Node 自身,无需再依赖外部报告工具来做阈值判定。

五、Other Notable Changes(其余值得注意的变更)

文档单列了四条 SEMVER-MINOR 级别的变更,逐条说明:

变更说明
src,lib: add performance.uvMetricsInfo新增performance.uvMetricsInfo,暴露 libuv 层指标信息(Rafael Gonzaga)
net: exclude ipv6 loopback addresses from server.listenserver.listen对 IPv6 回环地址的处理被排除在外,避免回环地址触发错误的自动绑定行为(Giovanni Bucci)
test_runner: support running tests in processnode:test 测试运行器支持在进程内(in-process)执行测试,不再强制派生子进程(Colin Ihrig)
test_runner: defer inheriting hooks until run()测试 hooks 的继承时机推迟到run(),使顶层before/after等钩子的作用范围更可控(Colin Ihrig)

其中“in-process 执行”与 v22.8.0 同期大量把内部测试迁移到node:test的 commit(如update test-abortcontroller to use node:testmove a couple of tests over to using node:test等)互为印证:22.8 正在系统性地把 Node 自身的测试基座切换到内置测试运行器。

六、Commit 全量清单(按模块归类)

以下为该版本 CHANGELOG 收录的全部提交(哈希为 node 主仓库 commit 短哈希,此处按模块归类呈现,便于检索):

构建与依赖(build / deps / tools)

  • 6f0cf35cd3build: reclaim disk space on macOS GHA runner (jakecastelli)
  • 467ac3aec4build: don't clean obj.target directory if it doesn't exist (Joyee Cheung)
  • 71fdf961dfbuild: update required python version to 3.8 (Aviv Keller)
  • 73604cf1c5deps: update nghttp2 to 1.63.0
  • b00c087285deps: V8: cherry-pick e74d0f437fcd (Joyee Cheung)
  • 33a6b3c7a9deps: backport ICU-22787 to fix ClangCL on Windows (Stefan Stojanovic)
  • fe56949cbbdeps: update c-ares to v1.33.1
  • 290f6ce619deps: update amaro to 0.1.8
  • b5843568b4deps: update amaro to 0.1.7
  • 9c709209b4deps: update undici to 6.19.8
  • a5ce24181bdeps: sqlite: fix Windows compilation (Colin Ihrig)
  • 3caf29ea88deps: update sqlite to 3.46.1
  • 38798140c4tools: remove unused python files (Aviv Keller)
  • da6c61def8tools: add swc license (Marco Ippolito)

核心模块(src / lib / node-api / sqlite / typings / zlib / stream / buffer)

  • 9ba75faf5fsrc,lib: add performance.uvMetricsInfo (SEMVER-MINOR)
  • 4baf4637ebsrc: add JS APIs for compile cache and NODE_DISABLE_COMPILE_CACHE (SEMVER-MINOR)
  • 1e01bdc0d0net: exclude ipv6 loopback addresses from server.listen (SEMVER-MINOR)
  • 04f83b50adRevert "net: validate host name for server listen"
  • ac178b094bbuffer: truncate instead of throw when writing beyond buffer (Robert Nagy)
  • afd8c1eb4fbuffer: allow invalid encoding in from (Robert Nagy)
  • e071651bb2src: disable fast methods forbuffer.write(Michaël Zasso)
  • 3a74c400d5src: improvebuffer.transcodeperformance (Yagiz Nizipli)
  • f8cbbc685asrc: use v8::Isolate::GetDefaultLocale() to compute navigator.language (Joyee Cheung)
  • 101e299656src: move more crypto_dh.cc code to ncrypto (James M Snell)
  • 909c5320fdsrc: move more crypto code to ncrypto (James M Snell)
  • e6e1f4e8bdsrc: remove redundant AESCipherMode (Tobias Nießen)
  • 1ff3f63f5esrc: handle errors correctly inpermission.cc(Michaël Zasso)
  • 4938188682src: returnv8::Objectfrom error constructors (Michaël Zasso)
  • 4578e9485bsrc: use better return types in KVStore (Michaël Zasso)
  • 7d9e994791src: change SetEncodedValue to return Maybe<void> (Tobias Nießen)
  • eef303028fsrc: remove cached data tag from snapshot metadata (Joyee Cheung)
  • 3cd10a3f40node-api: remove RefBase and CallbackWrapper (Vladimir Morozov)
  • 72c554ababsqlite: return results with null prototype (Michaël Zasso)
  • 16d4c437e1typings: provide internal types for wasi bindings (Andrew Moon)
  • 8ffdd1e2b2zlib: simplify validators (Yagiz Nizipli)
  • ffc300c6dstream: change stream to use index instead offor...of(Wiyeong Seo)
  • 4a664b5fcblib: respect terminal capabilities on styleText (Rafael Gonzaga)
  • a9ce2b6a28lib: fix emit warning for debuglog.time when disabled (Vinicius Lourenço)
  • fc08a9b0cdfs: refactor handleTimestampsAndMode to remove redundant call (HEESEUNG)
  • 8a4b26f00ctimers: fix validation (Paolo Insogna)
  • 94985df9d6benchmark: fix benchmark for file path and URL conversion (Early Riser)

test_runner / test(含覆盖率阈值特性本体)

  • d4310fe9c1test_runner: add support for coverage thresholds (SEMVER-MINOR)
  • 97fa075c2etest_runner: support running tests in process (SEMVER-MINOR)
  • 858b583c88test_runner: defer inheriting hooks until run() (SEMVER-MINOR)
  • 0cf78aa24btest_runner: refactormock_loader(Antoine du Hamel)
  • 1c29e74d30test_runner: makemock.module'sspecifierconsistent withimport()(Antoine du Hamel)
  • 45b0250692test_runner: account for newline in source maps (Colin Ihrig)
  • cbe30a02a3test_runner: finish build phase before running tests (Colin Ihrig)
  • a4bebf8559test: ensure test watcher picks up new test files (Pietro Marchini)
  • 8a4f8a9efftest: add tests for runner coverage with different stdout column widths (Pietro Marchini)
  • 846e2b2896test: refactor test_runner tests to change default reporter (Colin Ihrig)
  • b5eb24c86atest: force spec reporter in test-runner-watch-mode.mjs (Colin Ihrig)
  • 其余 test 类提交:a4a6ef8d2976345a5d7c(OpenSSL 3.2 TLS 测试适配)、522d5a359dedbecf5209bc976cfc939f1ce732a8c74f2aeb92a0be95e4ccc10aff665edbbc79094964442fce6b72345dee1c66ae9f4c0a02d664b75fb0ed8dbb2f5ee234a5a6

vm / module

  • db80eac496vm: introduce vanilla contexts via vm.constants.DONT_CONTEXTIFY (SEMVER-MINOR)
  • fe5666f006vm: return all own names and symbols in property enumerator interceptor (Chengzhong Wu)
  • 3cf645768emodule: use amaro default transform values (Marco Ippolito)
  • 336496b90emodule: add sourceURL magic comment hinting generated source (Chengzhong Wu)

文档与流程(doc / meta)

  • 80e5150160doc: fix module compile cache description;c499913732doc: fix information about including coverage files;1800a58f49doc: update websocket flag description to reflect stable API status;25419915c7doc: add esm examples to node:https;83b5efeb54doc: reserve ABI 130 for Electron 33;825d933fd4doc: add getHeapStatistics() property descriptions;f5c5529266doc: add alert on REPL from TCP socket;bf824483cddoc: fix typo in styleText description;61affd77a7doc: fix capitalization in module.md;fc57beaad3doc: add note about shasum generation failure;7fd033fe56doc: run license-builder;68758d4b08doc: add support me link for anonrig;c3dc83befcdoc: support collaborators - talk amplification;6ccbd32ae8doc, meta: add missing,to BUILDING.md
  • b5a23c9783meta: remind users to use a supported version in bug reports;0d7171d8e9meta: add more labels to dep-updaters;c4996c189fmeta: run coverage-windows whenvcbuild.batupdated

七、二进制下载与 SHASUMS 校验

该版本覆盖的安装/二进制产物(原文档完整列出,均为 nodejs.org 官方 dist 目录下的分发文件):

  • Windows:32/64-bit 与 ARM 64-bit 安装包(.msi)、32/64-bit 与 ARM 64-bit 裸二进制(win-x86 / win-x64 / win-arm64 下的 node.exe)
  • macOS:64-bit 安装包(.pkg)、Apple Silicon 与 Intel 的 .tar.gz 二进制
  • Linux:x64、ppc64le、s390x(.tar.xz),另有 ARMv7(armv7l)与 ARMv8(arm64)二进制
  • AIX ppc64(.tar.gz)与源码包(node-v22.8.0.tar.gz)

校验用 SHASUMS 为 PGP 签名的 SHA256 摘要(原文档完整收录),节选关键条目:

a832a113d2bf953c4866ad3c9f0d63fd09a33d205f26633835737f46933f8c5c node-v22.8.0-aix-ppc64.tar.gz 723d53380d500087d738ee1a255fea121060602140a8e93cf7bd3013cd7531a4 node-v22.8.0-darwin-arm64.tar.gz 2f56ac0a9b7f03aea854b8bf2246741179c0b758a0b1b7239d1d5b242aca0f1b node-v22.8.0-darwin-x64.tar.gz a0bc969f61b96575a4b2cdd239729aa3e721384fa9ca3fb926a9582c019c1bbc node-v22.8.0-linux-x64.tar.gz b6827dccd983acad09496f28a0f277218cc49302a8a7179ccbd7bf38305f5623 node-v22.8.0-linux-x64.tar.xz a23afe3a54270accabcd9be0fc0dbec6645e8c5b8b06040dcdf0086a7047622d node-v22.8.0-linux-arm64.tar.gz b59fc61871fde20018f569e9a971599b0b0c9906843f789f22b0c3983a6cc2ac node-v22.8.0-linux-armv7l.tar.gz 6d7a1239cc39387676fdad2311bc95aef6b95819c4e7a0ed0edfbc6ca2caeb52 node-v22.8.0-linux-ppc64le.tar.gz 63114451139bf8a03c90dd57ae896840d76ded1e4f72e7feaecfc9eab8da4957 node-v22.8.0-linux-s390x.tar.gz 30700aa91ec1c7d6f05b376cbe73e9d14a7148153f4d54514c9b492b0dcbf143 node-v22.8.0.pkg cd5a5bd63c6c0a44278c9da0126e5d2c06a688462f18ed44f314a346a46e7ec5 node-v22.8.0.tar.gz f7870f6da18f9f884f09eed40da74529018888d51f620f3cb7c14b75b823af3f node-v22.8.0-x64.msi c10cfe8ea214eeb3fc7e51997ee16db04d68a62b9355dbc091073c5c12535287 node-v22.8.0-x86.msi

(完整摘要含 win-arm64/win-x64/win-x86 的 .exe、.lib、.pdb、.7z、.zip 及 headers 包,均在原文档 v22.8.0.md 的 SHASUMS 代码块内。)

安全下载的推荐流程:下载目标产物后,用sha256sum -c对照上述摘要,并可用 GPG 公钥验证 SHASUMS 文件本身的 PGP 签名,确保分发链完整。

八、这篇发布博客是如何生成并渲染到站点的(仓库工程链路)

理解了文章内容后,再看 nodejs.org 仓库为它配套的两条工程链路,可以完整回答“一篇 release 博客从哪来、怎么上线”。

1. 生成链路:release-post 脚本

scripts/release-post/index.mjs 是发布博客的自动生成器,用法为node index.mjs [version](不带版本参数时从 dist 的 index.json 取最新版本)。它的工作流程是:

  1. fetchChangelog从 node 主仓库对应大版本线的 CHANGELOG 中用正则<a id="X.Y.Z"></a>锚点切出该版本的章节(见URLS.NODE_CHANGELOG_MDrxSection);
  2. fetchAuthor从章节标题## 2024-09-03, Version 22.8.0 (Current), @RafaelGSS中用正则findAuthorLogin提取发布作者,再经 GitHub API 换成姓名——这正是本文 Frontmatter 里author: Rafael Gonzaga的来源;
  3. fetchVersionPolicy用正则rxPolicy提取(Current)/(LTS)等策略标签,生成标题Node.js 22.8.0 (Current)
  4. fetchShasums拉取SHASUMS256.txt.asc原文填入文末代码块;
  5. verifyDownloads对每个下载 URL 做 HEAD 请求,不可达的标注为 “Coming soon”;
  6. 最终用 template.hbs 渲染 Frontmatter + 正文,经 Prettier 格式化后写入pages/en/blog/release/vX.Y.Z.md--force可覆盖已存在文件)。

本文档的骨架(标题行、特性小节、commit 列表、下载清单、SHASUMS 代码块)与该模板的结构完全一致,可以确认它就是这条流水线 + 少量人工润色的产物。

2. 数据管线:博客索引的构建期生成

blog-data/generate.mjs 在 RSC/静态构建期扫描pages/en/blog下全部 Markdown,用流式逐行读取只截取到第二个---为止的 Frontmatter(对数千篇博客是刻意做的 IO 优化),解析出title / author / date / categories / slug。注意其中的细节:

  • categories = [category, 'year-' + 发布年, 'all'],即本文属于releaseyear-2024all三个分类;
  • slug = /blog/release/v22.8.0由分类与文件名拼出;
  • 文章按日期倒序排序后进入构建产物,next.constants.mjs 中BLOG_POSTS_PER_PAGE = 6决定列表页每页 6 篇,blog.ts 的getBlogPosts/paginateBlogPosts负责分页切片与页码自校正。

3. 渲染链路:Next.js 路由

blog/[...path]/page.tsx 是博客详情页的入口:

  • 读取blog/<path>对应的 Markdown,经 MDX 编译为 React 组件;
  • layout取 Frontmatter 中的blog-post(本文档已声明layout: blog-post),由 layouts 中的文章版式渲染;
  • 声明dynamic = 'force-static'revalidate = 300,即该页强制静态渲染、每 300 秒可重新校验——一次发布博客上线后即为纯静态页面;
  • 开启ENABLE_STATIC_EXPORT时,generateStaticParams依据BLOG_DYNAMIC_ROUTES为默认语言生成全部静态路径。

4. 版本数据侧:与发布博客互证

同一构建期还有 releaseData.mjs 与 releaseVersions.mjs:前者通过 nodevu 拉取各 major 线的 releases 与支持阶段(support.phases.dates.end判定 EOL、isLts判定 LTS,否则 Current),输出 v22.8.0 的 npm、V8、modules 版本号与 minor 版本列表;后者把所有vX.Y.Z收集成全量版本数组,供文档站版本选择器使用。可以说,博客讲“这个版本新了什么”,而 release-data 管线回答“现在有哪些版本、各自什么状态”,两者共享同一份上游版本元数据。

九、小结

  • 对使用者:v22.8.0 的三大能力各有明确落点——module.enableCompileCache()让库/工具作者可自行启用磁盘代码缓存;vm.constants.DONT_CONTEXTIFY提供可冻结、访问更快的原生 vm 上下文;--test-coverage-{lines,branches,functions}把覆盖率门禁内置进 node:test,0–100 整数阈值、不达标即退出码 1,可直接用于 CI。
  • 对站点维护者:这篇博客本身是 release-post 脚本 半自动化的产物,Frontmatter 驱动其进入release分类与year-2024归档,构建期管线(generate.mjs、blog.ts)完成索引与分页,Next.js 路由(page.tsx)以force-static + revalidate=300完成静态化渲染。

如需进一步深入,建议按以下路径在仓库内继续阅读:v22.8.0.md(原始发布文档全文,含完整 commit 链接与 SHASUMS)、release-post/template.hbs(博客模板)、blog-data/generate.mjs 与 releaseData.mjs(构建期数据生成)。

【免费下载链接】nodejs.orgThe Node.js® Website项目地址: https://gitcode.com/GitHub_Trending/no/nodejs.org

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 13:00:45

CNN卷积神经网络详解:卷积层、池化层、全连接层与PaddlePaddle实现

简介&#xff1a;面向深度学习初学者的卷积神经网络原理详解&#xff0c;内容以PDF形式整理发布&#xff0c;共1个文件&#xff0c;约685KB。资料从传统神经网络的基本结构与数学推导讲起&#xff0c;对比全连接网络在图像处理中参数过多、易过拟合等不足&#xff0c;进而引出卷…

作者头像 李华
网站建设 2026/9/18 13:00:15

SpringBoot启动过程简述 和 SpringCloud 的五大组键

一&#xff0c;Spring Boot启动过程简述如下&#xff1a;1&#xff0c;启动类&#xff1a;标有 SpringBootApplication 注解的类是Spring Boot应用的入口点。2&#xff0c;SpringBootApplication注解是一个复合注解&#xff0c;包含 SpringBootConfiguration &#xff08;表示这…

作者头像 李华
网站建设 2026/9/18 12:58:34

C++算法从入门到工程实践:排序、查找、图论与动态规划选型指南

1. 先把 C 算法的地图画出来C 算法这个词&#xff0c;在多数人的语境里其实混着两层含义&#xff1a;一层是数据结构与算法课上那套东西&#xff0c;排序、查找、图论、动态规划&#xff1b;另一层是 C 标准库<algorithm>里已经封装好的那批函数&#xff0c;sort、lower_…

作者头像 李华
网站建设 2026/9/18 12:56:50

Agent 跑 Function Calling,Base URL 填 TaoToken

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 12:55:53

Folia QQ音乐音源接入教程:MQTT长连线扫码背后的完整原理

Folia QQ音乐音源接入教程&#xff1a;MQTT长连线扫码背后的完整原理 【免费下载链接】folia-major 专注于绚丽的歌词动画效果的本地音乐/navidrome/第三方多平台在线音乐播放器 项目地址: https://gitcode.com/GitHub_Trending/fo/folia-major Folia 是一款专注全屏歌词…

作者头像 李华