编程 Rolldown 1.0 深度实战:当 Vite 用 Rust 重写打包器,10–30 倍性能背后到底发生了什么?

2026-07-11 00:44:38 +0800 CST views 11

Rolldown 1.0 深度实战:当 Vite 用 Rust 重写打包器,10–30 倍性能背后到底发生了什么?

2026 年 6 月,前端工程化圈被一条消息引爆:Rolldown 1.0 正式发布,作为 Vite 8 的默认打包器,构建速度比 Rollup 快 10–30 倍,同时保持 Rollup 插件 API 兼容(来源:JavaScript 技术周刊 2026 第 20 周)。这不是又一个"又快一点"的玩具,而是 Vite 团队(VoidZero)对前端构建范式的一次彻底重建。本文从架构、代码、性能三个维度,把 Rolldown 拆开给你看。


引子:一个被忽视的"构建之痛"

如果你是前端开发者,大概率经历过这样的场景:

本地 vite 跑得好好的,一上线 vite build 就报错;或者反过来,构建产物里某个行为跟 dev 完全不一致,排查半天发现是打包器语义差异。

这个问题在 2023–2025 年的 Vite 用户群里几乎是人尽皆知的"暗伤"。它的根子不在你的代码,而在 Vite 的双引擎架构——dev 用 esbuild,build 用 Rollup。两套工具、两套语义,天然会漂移。

而当项目膨胀到几千个模块、几十 MB 依赖时,另一个痛点浮出水面:vite build 越来越慢。这个慢,本质上是 Rollup 跑在单线程 JS 里的物理极限。

Rolldown 要解决的,就是这两个痛点合起来的那个大问题——能不能有一个打包器,既有 Rollup 的插件生态和 tree-shaking 精度,又有 esbuild 量级的性能,还能让 dev 和 build 共用同一套语义?

答案是:用 Rust 把整个打包器重写一遍。


一、背景介绍:前端构建器的"双引擎"原罪

要理解 Rolldown 为什么重要,得先理解 Vite 过去三年的结构性尴尬。

1.1 Vite 的两条流水线

Vite 的设计哲学是"开发时用原生 ESM,生产时再打包"。为了兼顾"秒开"和"可上生产",它实际上维护了两套完全不同的工具链:

阶段工具语言职责
dev(开发)esbuildGo依赖预构建(pre-bundling)+ TS/JSX 转译,主打极速冷启动
build(生产)RollupJS生产打包,主打精细 tree-shaking 和成熟插件生态

这带来一个经典痛点:dev/prod 漂移(drift)。根因是 esbuild 和 Rollup 对以下语义的处理并不完全相同:

  • ESM/CJS 互操作:同一个混合依赖,esbuild 和 Rollup 解析出的 require/import 边界可能不一致。
  • Side Effect 判定package.json"sideEffects" 字段,两边的处理粒度不同。
  • this 绑定与全局对象:某些老 CJS 库依赖 this === module.exports,在 ESM 严格模式下会变成 undefined

开发者管这叫"本地能跑、构建就挂"。根因不是你写错了,而是两个打包器在"最后一公里"的语义没对齐。

1.2 Rollup 的物理极限

更深一层,Rollup 本身跑在单线程 JS里。当项目膨胀后,它的几个环节会成为肉眼可见的瓶颈:

  1. 解析(acorn):纯 JS 的递归下降解析器,逐个文件同步解析,遇到大依赖图直接卡住。
  2. 作用域分析:用大量 JS 对象建模块图,分配/回收频繁,GC 压力大。
  3. 代码拼接(magic-string):用字符串切片 + 索引做源码映射,大文件会产生海量临时字符串。

社区里"Vite build 太慢"的抱怨,90% 归根结底是 Rollup 在 JS 单线程里的物理极限,而不是你的业务代码差。

1.3 问题的重新定义

于是问题被重新定义成一句话:

能不能有一个打包器,既拥有 Rollup 的插件生态和 tree-shaking 精度,又有 esbuild 量级的性能,还能让 dev 和 build 共用同一套语义?

Rolldown 就是 VoidZero(Vite / Vue 作者尤雨溪创立的公司)给出的答案。它不是一个从零拍脑袋的项目,而是 OXC 工具链成熟后水到渠成的产物。


二、核心概念:Rolldown 到底是什么

一句话定义:Rolldown 是一个用 Rust 写的 JavaScript/TypeScript 打包器,对外暴露与 Rollup 高度兼容的 API。

官方仓库的副标题说得更直白:Fast Rust bundler for JavaScript/TypeScript with Rollup-compatible API

下面几个定位必须厘清,否则容易把它和 esbuild / webpack 搞混。

2.1 它是 Rollup 的替代品,不是 esbuild 的

很多人第一反应是"那它跟 esbuild 比谁快"。这个对比方向就错了。

  • esbuild 快,但它的 tree-shaking 相对粗糙、插件模型简单,适合做"依赖预构建"和"快速转译",不适合做应用级生产打包的"最后一公里"。
  • Rollup 慢,但 tree-shaking 精度高、插件生态成熟,是应用生产打包的事实标准。

Rolldown 的目标是把 Rollup 的位置整个替换掉——同样的 inputOptions / outputOptions / 插件钩子,但内核是 Rust。它要的是"Rollup 级的精度 + esbuild 级的速度"。

2.2 它构建在 OXC 之上

OXC(Oxidation Compiler) 是另一个 Rust 写的 JS/TS 工具链,提供极快的 parser(oxc_parser)、transformer、linter、minifier。你可以把它理解成"Rust 版的 Babel + ESLint + Terser 合体"。

Rolldown 的分工很清晰:OXC 负责"读懂代码"(解析 + 转译),Rolldown 自己负责"打包代码"(模块图、符号链接、分块、代码生成)。这种分层让 Rolldown 不必重复造解析器的轮子,直接吃到 OXC 的 Rust 级解析速度。

2.3 它向后兼容 Rollup 插件 API

这是 Rolldown 能"零摩擦"落地的关键。你写过的 resolveId / load / transform / renderChunk 钩子,几乎可以不改直接跑在 Rolldown 上。社区里大量现成的 Rollup 插件(如 @rollup/plugin-node-resolve@rollup/plugin-commonjs、各种框架插件)可以平滑迁移。

官方在 1.0 发布时明确宣称"保持 Rollup 插件 API 兼容性"。这意味着存量的 Vite 项目不需要重写插件,就能切换到 Rust 引擎。

2.4 它顺手把 minify 也做了

借助 OXC 的 minifier(比 terser 快几个量级),Rolldown 可以在打包流程内部直接压缩,省掉原先 Rollup + terserRollup + esbuild 的两段式压缩。少一道 I/O、少一次 AST 往返,整体又省一笔时间。

2.5 性能数字怎么读

来自 2026 年 6 月 Rolldown 1.0 发布时的社区基准:比 Rollup 快 10–30 倍,早期采用者报告大型项目构建时间从"分钟级"降到"秒级"。

注意两个细节:

  • 这个倍数是 vs Rollup,不是 vs esbuild。esbuild 本身也很快,但 Rolldown 在保有 Rollup 级精度的同时追平甚至超过 esbuild。
  • "10–30 倍"是区间,取决于项目特征:模块越多、依赖图越复杂,Rolldown 的并行和 bitset 优势越明显,倍率越靠近 30;小而简单的项目,倍率会靠近 10。

三、架构分析:10–30 倍从哪里来

Rolldown 的构建流程可以抽象成三阶段流水线:模块扫描(Scan)→ 符号链接(Link)→ 代码生成(Generate)。我们逐段拆开,看它为什么快。

3.1 模块扫描:OXC 并行解析

入口确定后,Rolldown 用 OXC 把每个源文件解析成 AST。关键点在于:解析是天然可并行的——每个文件相互独立、互不依赖。

Rust 的多线程(rayon 风格的 work-stealing 线程池)可以把 N 个文件的解析摊到所有 CPU 核心上:

文件 A ──┐
文件 B ──┼─> [线程池并行解析] ──> N 棵 AST
文件 C ──┘

对比 Rollup:Rollup 用 acorn(纯 JS)逐个文件同步解析,JS 单线程 + 大量小对象分配 + GC 压力,光解析阶段就落后一个数量级。

3.2 符号链接:用 bitset 做可达性分析

解析完所有模块后,Rolldown 建立模块图(module graph),并做作用域分析与跨模块符号解析——这是 tree-shaking 的核心。它要回答一个根本问题:

从入口出发,哪些导出真正被用到?哪些代码是 dead code,可以安全删掉?

Rolldown 的巧妙之处在于用 bitset(位掩码) 表示模块 / 符号的可达性:

  • 每个模块、每个导出符号占用一个 bit。
  • "从入口可达"的传播,就是一串位运算(OR),而不是 Rollup 那种层层递归遍历对象图。
  • 位运算在 CPU 上是纳秒级,且对缓存极度友好(一段连续内存,不是散落的 JS 对象)。
入口模块 bit=1
  └─> 引用模块 A bit=1 (OR)
        └─> 引用模块 B bit=1 (OR)
              └─> 未引用模块 C bit=0 (被 tree-shake 掉)

这就是为什么 Rolldown 在"大型依赖图的 tree-shaking"上能甩开 Rollup——可达性分析从 O(对象图遍历) 变成了 O(位运算)。模块越多,差距越大。

3.3 代码生成:保留模式 + 智能分块

生成阶段 Rolldown 提供两种代码生成策略:

  • 普通模式(normal):常规打包,按模块依赖顺序拼接成 chunk。
  • 保留模式(advanced chunks):保留一定的模块结构边界,便于调试和长缓存。

真正的亮点是智能代码分割(code splitting)算法。它的逻辑可以概括为三步:

  1. 先处理开发者手动声明的 manualChunks(比如把 react / vendor 拆出来做长缓存)。
  2. 再自动优化剩余模块的分块,用 bitset 可达性信息把"总是一起被加载"的模块聚到同一 chunk。
  3. 最后合并冗余代码,消除跨 chunk 的重复。

整个过程兼顾"开发者的缓存控制权"和"打包体积 / 加载速度"。这正是前面搜索资料显示的核心机制:"通过位掩码标记模块可达性,先处理用户定义的手动分块,再自动优化剩余模块,最后合并冗余代码。"

3.4 Rust 内存模型的隐性红利

除了算法层面,Rust 本身带来三重红利,这些在 JS 版 Rollup 里是拿不到的:

  1. 无 GC 停顿:不像 JS 那样有不可预测的 GC 暂停,构建时间稳定可复现。
  2. 所有权 / 借用保证内存安全:编译期就排除了数据竞争,多线程并行零心智负担。
  3. 零拷贝字符串:Rolldown 内部用 String / Arc<str> 做引用共享,避免了 Rollup 用 magic-string 反复切片拼接产生的大量临时字符串分配。

小结:"10–30 倍"的真实来源是一个组合拳——

并行解析(OXC)+ 位运算可达性(bitset)+ 无 GC 内存模型(Rust)+ 流程内压缩(OXC minifier)

任何单点都不是银弹,但四者叠加,就形成了对单线程 JS 版 Rollup 的代差。


四、代码实战:把 Rolldown 真正跑起来

光讲架构太空。下面用真实可运行的代码,从"直接打包"到"Vite 接入"到"库构建"逐层打通。

4.1 最小可用:用 Rolldown 直接打包一个库

先装包:

npm init -y
npm install rolldown

写一个最简单可跑的 Node API 示例(build.mjs):

// build.mjs
import { rolldown } from 'rolldown'

const bundle = await rolldown({
  // 入口,支持多入口
  input: ['./src/index.js'],
  // 插件数组,这里先为空
  plugins: [],
  // 开启 Rolldown 内置的 oxc minifier
  minify: true,
})

await bundle.write({
  dir: './dist',
  format: 'esm',
  sourcemap: true,
})

console.log('✅ Rolldown build done')

配套一个最小源码:

// src/index.js
export function add(a, b) {
  return a + b
}

export const PI = 3.1415926

node build.mjs,你会在 dist/ 下得到 index.mjsindex.mjs.map。这就是 Rolldown 的最简闭环——API 形态和 Rollup 几乎一模一样。

4.2 写一个真正有意义的 Rollup 兼容插件

Rolldown 的卖点是"Rollup 插件 API 兼容"。下面写一个生产环境剥离 console.log 的插件,证明钩子可以直接复用:

// plugins/strip-console.mjs
/**
 * @returns {import('rolldown').Plugin}
 */
export function stripConsole() {
  return {
    name: 'strip-console',

    // transform 钩子:对每个模块源码做转换
    transform(code, id) {
      // 只对 JS/TS 源码生效,跳过 node_modules
      if (!id.includes('node_modules') && /\.(js|ts|jsx|tsx)$/.test(id)) {
        const stripped = code.replace(
          /console\.(log|debug|info)\([\s\S]*?\);?/g,
          '',
        )
        return {
          code: stripped,
          map: null, // 生产构建可不保留 sourcemap
        }
      }
      return null
    },
  }
}

接入:

// build.mjs
import { rolldown } from 'rolldown'
import { stripConsole } from './plugins/strip-console.mjs'

const bundle = await rolldown({
  input: ['./src/index.js'],
  plugins: [stripConsole()],
  minify: true,
})

await bundle.write({ dir: './dist', format: 'esm' })

注意:resolveId / load / transform / renderChunk 这些钩子的签名、执行顺序、返回结构都和 Rollup 一致。你从 Rollup 项目里搬来的插件,绝大多数不用改就能跑。

4.3 接入 Vite:让 dev 和 build 共用一套引擎

Vite 8 已经把 Rolldown 设为默认打包器。对于还在 Vite 5/6/7 的项目,社区提供 rolldown-vite 这个 drop-in 替换包:

npm install rolldown-vite --save-dev

然后在 package.json 里把 vite 的调用重定向到 rolldown-vite(或用它的 bin 覆盖):

{
  "scripts": {
    "dev": "rolldown-vite",
    "build": "rolldown-vite build",
    "preview": "rolldown-vite preview"
  }
}

vite.config.ts 里你熟悉的 build.rollupOptions 此时会由 Rolldown 接管,语义完全一致:

// vite.config.ts
import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    // 原来的 rollupOptions 现在由 Rolldown 解释执行
    rollupOptions: {
      output: {
        manualChunks: {
          'react-vendor': ['react', 'react-dom'],
        },
      },
    },
    // Rolldown 内置压缩,可省略独立的 minifier 插件
    minify: 'rolldown',
  },
})

迁移的最大收益不是"更快",而是dev 和 build 用同一套 Rust 引擎,语义彻底对齐——前面说的"本地能跑、构建就挂"类问题,会在架构层面消失。

4.4 用 tsdown 一键 Rust 化你的库构建

如果你做的是** npm 库**(不是应用),Rolldown 生态里有一个专门为此打造的工具:tsdown——构建在 Rolldown 之上的库打包器(2026 年 7 月已到 v0.11.4)。它把"库要出的 esm/cjs/dts、要做的 minify、要清理的 dist"都收敛成一个极简配置。

npm install tsdown --save-dev
// tsdown.config.ts
import { defineConfig } from 'tsdown'

export default defineConfig({
  entry: ['src/index.ts'],     // 入口
  format: ['esm', 'cjs'],      // 同时出 ESM 和 CJS
  dts: true,                   // 自动生成 .d.ts 类型声明
  minify: true,                // 用 Rolldown 内置 oxc minifier
  clean: true,                // 每次构建前清空 dist
  sourcemap: true,
})

npx tsdown,你会同时得到:

dist/
  index.mjs      # ESM,已压缩
  index.cjs      # CJS,已压缩
  index.d.mts    # ESM 类型声明
  index.d.cts    # CJS 类型声明

对比传统 tsup(基于 esbuild)或 tsc + rollup 的两段式流程,tsdown 用 Rolldown 一次性把解析、转译、打包、压缩、dts 全做了,速度提升显著,配置还更短。

4.5 实战:双模式打包 + 智能分块

真实项目里,你往往既要"应用入口"又要"拆 vendor 长缓存"。用 Rolldown 的 manualChunks 函数式写法可以精确控制:

// build.app.mjs
import { rolldown } from 'rolldown'

const bundle = await rolldown({
  input: {
    main: './src/main.js',
    admin: './src/admin.js', // 第二个入口,自动 code-split
  },
  plugins: [],
})

await bundle.write({
  dir: './dist',
  format: 'esm',
  sourcemap: true,
  // 函数式 manualChunks:按路径把依赖分组
  manualChunks(id) {
    if (id.includes('node_modules')) {
      if (id.includes('react')) return 'vendor-react'
      if (id.includes('lodash') || id.includes('date-fns')) return 'vendor-utils'
      return 'vendor' // 其余第三方统一进 vendor
    }
  },
})

这个配置会产出:

dist/
  main.js          # 业务主入口
  admin.js         # 管理后台入口(独立 chunk)
  vendor-react.js  # react/react-dom,长缓存
  vendor-utils.js  # 工具库,长缓存
  vendor.js        # 其余依赖

配合 HTTP 长缓存(Cache-Control: immutable),用户二次访问时 vendor 几乎不重新下载。这正是 Rolldown bitset 分块算法的用武之地:它能保证"总是一起被加载的模块"聚到同一 chunk,避免把不相关的代码强行塞进一个巨大的 vendor。

4.6 跑一个真实 benchmark,亲眼验证倍率

光看官方数字不够,自己跑一个对照实验最踏实。下面这个脚本在同一项目上分别用 Rolldown 和 Rollup 构建,算出倍率:

// benchmark.mjs
import { rolldown } from 'rolldown'
import { build as rollupBuild } from 'rollup'
import { rm } from 'node:fs/promises'

const ENTRY = './fixtures/big-app/src/index.js'

async function time(label, fn) {
  const start = performance.now()
  await fn()
  const ms = Math.round(performance.now() - start)
  console.log(`${label}: ${ms}ms`)
  return ms
}

const tRolldown = await time('Rolldown', async () => {
  const b = await rolldown({ input: ENTRY, minify: true })
  await b.write({ dir: './dist-rolldown', format: 'esm' })
  await b.close()
})

const tRollup = await time('Rollup', async () => {
  const b = await rollupBuild({ input: ENTRY, plugins: [] })
  await b.write({ dir: './dist-rollup', format: 'esm' })
  await b.close()
})

console.log(`\nSpeedup: ${(tRollup / tRolldown).toFixed(1)}x`)
await rm('./dist-rolldown', { recursive: true, force: true })
await rm('./dist-rollup', { recursive: true, force: true })

跑在几百个模块、依赖密集的项目上,你大概率会看到 10–30x 的区间结果。模块越多、依赖图越大,倍率越靠近上限。


五、性能优化:把 30 倍真正吃到嘴里

Rolldown 快是默认的,但要吃满红利,还得避开几个常见坑。下面是一份可落地的优化清单。

5.1 用 Rolldown 内置 minify,别再叠 terser

很多老 Vite 项目配了 terser 做生产压缩。迁移到 Rolldown 后,直接用内置的 oxc minifier

// vite.config.ts
export default defineConfig({
  build: {
    minify: 'rolldown', // 流程内压缩,省掉额外的 AST 往返
  },
})

terser 是纯 JS,慢且单线程;oxc minifier 是 Rust,本身就快几个量级,还能和打包共用一份 AST。叠 terser 等于把前面省的时间又花回去。

5.2 用 external 避免重复打包

库项目(被别人 import 的 npm 包)一定要把 peer 依赖标 external,否则你会把 react 也打进你的包,体积爆炸且和宿主项目冲突:

// build.lib.mjs
import { rolldown } from 'rolldown'

const bundle = await rolldown({
  input: ['./src/index.js'],
  external: ['react', 'react-dom', 'vue'], // 交给使用方提供
})

await bundle.write({ dir: './dist', format: 'esm' })

5.3 大依赖用 manualChunks 拆长缓存

这点前面 4.5 演示过。核心原则:变动频率不同的代码要分到不同 chunk。框架(react/vue)几乎不变,业务代码天天变,拆开后用户二次访问只拉业务 chunk。

5.4 生产环境关掉不必要的 sourcemap

sourcemap: true 在开发很有用,但生产全量 sourcemap 既占空间又拖慢。生产可只保留 hidden 模式(生成 map 但不挂 //# sourceMappingURL)或干脆关闭:

await bundle.write({
  dir: './dist',
  format: 'esm',
  sourcemap: false, // 生产关;出错时用 Sentry 等回源
})

5.5 让 dev 和 build 统一到 rolldown-vite

前面反复强调:用 rolldown-vite 替代 vite,让 dev 和 build 共用同一引擎。这本身不是"提速",而是消灭 dev/prod 漂移带来的隐性排错成本——这类 bug 往往比构建慢更折磨人。

5.6 用 watch 模式做增量构建

Rolldown 的 bundle.watch() 支持增量——只重新打包变动的模块图子树,配合 Rust 引擎,HMR 链路整体更顺:

const watcher = await bundle.watch({
  dir: './dist',
  format: 'esm',
})
// 需要时 watcher.close()

5.7 陷阱:插件兼容的灰色地带

Rolldown 兼容大多数 Rollup 插件,但有几类要小心:

  • 重度依赖 this 上下文或 function 钩子的插件:Rolldown 的钩子执行模型是 Rust 调度,部分依赖 JS this 动态的插件可能行为偏移。
  • 直接操作 Rollup 内部 AST 对象形状的插件:Rolldown 内部是 Rust 结构,跨语言时做了映射,极少数依赖内部字段的插件要改。
  • 依赖特定 magic-string 行为的插件:Rolldown 不用 magic-string,相关 hack 会失效。

迁移建议:先在小项目验证插件兼容性,再推全量。大部分主流插件(社区维护的)在 1.0 发布时已经适配。


六、总结展望:前端工程化进入"原生编译"时代

Rolldown 1.0 不是孤立事件,它和 2026 年前后的一系列动作属于同一股浪潮——前端工具链正在整体"原生化"

  • TypeScript 7 用 Go 重写编译器(提速约 10 倍);
  • Bun 用 Zig/Rust 重写 JS 运行时;
  • LiteRT.js 用 WebGPU/WebNN/WASM 重构浏览器 ML;
  • esbuild / SWC / OXC / Rolldown 用 Go / Rust 重写转译与打包;
  • Vite 8 把默认打包器换成 Rolldown。

这条主线的逻辑非常清晰:当 JS 生态的规模膨胀到单线程 JS 工具处理不了时,唯一的解法是把工具本身下沉到系统级语言(Rust/Go/Zig),而把 JS/TS 留给业务。开发者再也不用为了"构建慢"去调一堆玄学参数,因为瓶颈已经从算法挪到了语言运行时。

对开发者的实际意义

  1. 构建不再是痛点:大型项目 vite build 从分钟级降到秒级,CI 时长直接砍掉一截。
  2. dev/prod 一致性:同一引擎,漂移类 bug 在架构层面消失。
  3. 库作者零成本提速:用 tsdown 一行配置,Rust 级构建自动到账。

需要保持清醒的地方

  • 生态成熟度:Rolldown 1.0 虽已发布,但部分长尾 Rollup 插件的兼容仍在补齐,迁移前先验证。
  • Vite 8 落地节奏:新项目直接上 rolldown-vite 最稳;存量大仓建议灰度。
  • 不是银弹:Rolldown 解决的是"打包"环节,网络传输、运行时性能、框架本身的运行时开销不归它管。

行动建议

  • 新项目:直接 rolldown-vite + minify: 'rolldown',从第一天就吃满红利。
  • 库项目:用 tsdown 替代 tsup/tsc+rollup,配置更短、速度更快。
  • 存量应用:先在预发环境用 rolldown-vite 跑一轮,确认插件兼容后再切生产。

写在最后

Rolldown 1.0 的意义,远不止"比 Rollup 快 30 倍"这个数字。它标志着 Vite 终于填上了"双引擎"这个存在了三年的结构性坑,也标志着前端构建正式进入 Rust 原生时代。对于每天被 vite build 卡进度条的我们来说,这是一次真正意义上的"苦尽甘来"。

下次当你 npm run build 秒回结果时,记得背后是 OXC 的并行解析、是 bitset 的位运算可达性、是 Rust 无 GC 的内存模型,在替你把时间一点点省回来。

参考资料(可溯源)

  • Rolldown 官方仓库:github.com/rolldown/rolldown("Fast Rust bundler for JavaScript/TypeScript with Rollup-compatible API")
  • JavaScript 技术周刊 2026 第 20 周(2026-06-12):Rolldown 1.0 发布,Vite 8 默认打包器,比 Rollup 快 10–30 倍,保持 Rollup 插件 API 兼容
  • OXC 工具链:oxc.rs(Rust 版 parser / transformer / linter / minifier)
  • tsdown(构建于 Rolldown 之上的库打包器):2026-07 已发布 v0.11.4

推荐文章

ElasticSearch简介与安装指南
2024-11-19 02:17:38 +0800 CST
一个有趣的进度条
2024-11-19 09:56:04 +0800 CST
Python上下文管理器:with语句
2024-11-19 06:25:31 +0800 CST
如何将TypeScript与Vue3结合使用
2024-11-19 01:47:20 +0800 CST
程序员茄子在线接单