Rspack 2.0 正式发布 — 前端构建工具格局再变天

从 5.6s 到 1.4s,从 192 个依赖砍到 1 个,Rspack 2.0 不只是"更快的 webpack"了。

Rspack 2.0 在 7 月 29 日正式发布。不只是"字节跳动的 webpack 替代品"。

这次 2.0 的核心变化可以浓缩成三句话:

  1. 性能翻倍------相比 1.0,构建速度最多快 100%,10000 组件项目缓存构建从 5.6s 降到 1.4s
  2. 依赖瘦身 ------@rspack/dev-server 依赖从 192 个砍到 1 个,安装体积减少 90%+
  3. 全面 ESM 化------核心包转为纯 ESM,支持 RSC、import defer 等现代特性

总结就一句它不再满足于只做"更快的 webpack",开始走自己的路了。

1. Rspack 2.0 到底更新了什么?

1.1 性能提升

先看一组最直观的基准测试数据(来源:rspack-react-10k-benchmark,测试环境:M3 Pro 芯片、32GB 内存、NVMe SSD、Node.js 22.12):

版本 生产构建(无缓存) 生产构建(有缓存) HMR 热更新
Rspack 1.0 5.6s 5.6s 128ms
Rspack 1.7 3.6s 2.2s 134ms
Rspack 2.0 3.1s 1.4s 118ms

简单来说:启用持久化缓存后,10000 个组件的 React 项目,生产构建只要 1.4 秒。

这些数字都传递了什么信息呢?未做深度性能优化、未开启持久化缓存的原生 webpack 5,同规模项目生产构建通常在 30-60 秒量级。Rspack 2.0 无缓存首次构建约为 webpack 的 10-20 倍,持久化缓存命中场景约为 webpack 缓存构建的 20 倍以上。

除了构建速度,2.0 还在缓存场景做了两个优化:

  • SWC 压缩器的压缩结果可以缓存复用,命中缓存时构建性能再提升约 50%
  • 启用缓存后内存占用下降了 20%+

同项目在 webpack 5 下的生产构建时间约为 35-45 秒(首次)/ 28-35 秒(缓存命中)。Rspack 2.0 缓存构建 1.4 秒约为 webpack 缓存构建的 20-25 倍

CI 环境的缓存陷阱 :持久化缓存依赖 package-lock.json 和环境变量来生成缓存 key。如果 CI 构建每次都是全新环境且未持久化 node_modules/.cache 目录,缓存命中率几乎为零,反而会因磁盘读写拖慢构建。建议通过 CI 缓存能力持久化缓存目录,或固定 RSPACK_CONFIG_CACHE_KEY 环境变量提升命中率。

1.2 依赖大瘦身:从 192 到 1

这个变化在 Hacker News 上反而比性能数据更火。

1.x 依赖数 2.0 依赖数 安装体积
@rspack/dev-server 192 1 15MB → 1.4MB
@rspack/core 8 1 ---
@rspack/cli --- 0(零依赖) ---

它是怎么做到的呢?

  1. connect-next 替代 Express------开发服务器的中间件模型用 Connect 就够了,不需要完整的 Express
  2. 依赖打包进发布产物 ------把非核心依赖直接打包进 npm 包,由 Rspack 统一控制版本,避免供应链风险。不过是两面性的:这种「vendor 化」策略意味着你无法通过 npm overrides 直接升级传递依赖版本来修复 CVE 漏洞,需依赖官方发版;patch-package 虽可对打包后的产物生效,但维护成本极高,不推荐企业级项目使用。大型企业需要评估内部安全扫描策略是否兼容这种方式
  3. 用 Node.js 20+ 原生 API ------比如用内置的 styleText 替代 picocolors
  4. 非核心依赖改为可选 ------比如 @module-federation/runtime-tools 只在使用模块联邦插件时才需要手动安装

有位评论者说得挺好:与性能数据相比,依赖项减少的数字更让我印象深刻------这是一种理念的转变,而不仅仅是基准测试。

1.3 纯 ESM 核心

Rspack 2.0 的核心包(@rspack/core@rspack/cli@rspack/dev-server@rspack/plugin-react-refresh)全部转为纯 ESM 包发布,移除了 CommonJS 构建产物。

那么问题来了,我的现有项目还在用 CommonJS,会不会挂?

答案是在 Node.js 版本符合要求且未引入含顶层 await 的 ESM 依赖的前提下,大概率不会。Node.js 22.12+ 已稳定支持 CommonJS 中通过 require() 加载 ESM 模块(require(esm)),20.19+ 为实验性支持。因此大多数通过 JavaScript API 使用 Rspack 的项目无需修改代码。

但有一点需要注意:如果 ESM 模块内部使用了顶层 await(Top-level await)require() 会直接抛出 ERR_REQUIRE_ASYNC_MODULE 错误,没法绕过。目前 Rspack 2.0 自身没有使用顶层 await,但如果你在配置文件中引入了含顶层 await 的第三方 ESM 依赖,CJS 项目就会挂:

示例:

js 复制代码
// ❌ 会报错:CJS 配置文件引入了含顶层 await 的 ESM 依赖
// rspack.config.js (CommonJS)
const someEsmPkg = require('some-esm-pkg-with-tla'); // 抛出 ERR_REQUIRE_ASYNC_MODULE

// ✅ 修复方案1:将配置文件改为 rspack.config.mjs
import someEsmPkg from 'some-esm-pkg-with-tla';

// ✅ 修复方案2:package.json 中设置 "type": "module"

另外说一下:这里说的是 Rspack 自己的包变成 ESM,不影响你用 Rspack 构建 CommonJS 产物的能力。构建行为和配置方式跟以前一样。

1.4 产物优化:tree shaking 全面加强

Rspack 2.0 在静态分析能力上做了大幅增强:

1. CommonJS require 解构也能 tree shake 了

js 复制代码
// ✅ 支持 tree shake:静态解构
const { bar } = require('./foo');

// ❌ 不支持 tree shake:运行时属性访问
const foo = require('./foo');
const bar = foo.bar;

// ❌ 不支持 tree shake:条件导出
module.exports = process.env.NODE_ENV === 'development' ? devApi : prodApi;

这个优化依赖于 exports 对象的静态可分析性 。如果你的 CJS 模块存在运行时条件导出或动态属性赋值(module.exports['prop' + 'Name']),Rspack 会保守起见保留整个模块。建议在这种情况显式声明 sideEffects: false 来辅助判断。

2. 构建器注解 #__NO_SIDE_EFFECTS__

js 复制代码
/*#__NO_SIDE_EFFECTS__*/
export function join(a, b) {
  return `${a}-${b}`;
}
js 复制代码
import { join } from './utils';

join('btn', 'primary'); // 返回值没被使用 → 整个调用会被移除

这个功能目前还是实验性的,需要通过 experiments.pureFunctions 开启,后续版本会默认开启。

顺便说下它和 /*#__PURE__*/ 的区别------可能很多人更熟悉后者。/*#__PURE__*/ 作用于函数调用 (标记某一次具体调用无副作用),而 #__NO_SIDE_EFFECTS__ 作用于函数声明本身 (标记整个函数无副作用)。后者更强大:即使跨模块引用且未使用返回值,Rspack 也能安全删除整个函数调用。以前要做到这件事得靠 webpack-deep-scope-plugin 这类第三方插件,现在原生支持了。

3. 模块联邦(Module Federation) tree shaking

以前用模块联邦的 shared 声明依赖,运行时会加载完整包。现在可以开启 treeShaking 选项,只打包实际用到的导出:

js 复制代码
// 方式1:运行时自动推断(推荐,无需手动声明用到的导出)
new rspack.container.ModuleFederationPlugin({
  shared: {
    'lodash-es': {
      singleton: true,
      treeShaking: { mode: 'runtime-infer' },
    },
  },
})

// 方式2:手动指定使用的导出(确定性更高)
new rspack.container.ModuleFederationPlugin({
  shared: {
    'lodash-es': {
      singleton: true,
      treeShaking: {
        mode: 'manual',
        usedExports: ['debounce'], // 只打包 debounce
      },
    },
  },
})

1.5 React Server Components 实验性支持

Rspack 2.0 开始提供 RSC 的底层构建支持:

  • 支持 "use client" 声明和模块/函数级别的 "use server" 声明
  • 编译期检查违反 RSC 规范的 API 调用
  • 服务端和客户端组件的 CSS 收集与注入
  • 同时支持服务端和客户端组件的 HMR

如何手动启用 :需要设置 experiments.rsc: true,并配置 resolve.conditionNames 包含 'react-server' 条件导出,确保服务端构建能正确解析 RSC 专用入口。更推荐通过 rsbuild-plugin-rsc 开箱即用,插件会自动处理 client/server 双端构建配置的分裂,避免你手写两套配置。

能力边界:当前仅支持基础的 RSC 构建链路,不支持 Streaming SSR、Server Actions 等进阶特性。仅推荐与 Modern.js、TanStack Start 等上层框架搭配使用,不建议手动从零配置。目前 Modern.js 已基于 Rspack 提供了完整的 RSC 支持,Rspack 也在跟 TanStack 团队合作,后续会支持 TanStack Start。

1.6 其他值得关注的新特性

特性 说明
import.meta 支持 保留无法识别的 import.meta 属性,不再替换为 undefined
import defer 支持 stage-3 提案,延后模块求值,TS 5.9 已支持
modern-module 库构建 生成更适合库发布的 ESM 产物,保留目录结构
#/ 子路径别名 直接用 package.jsonimports 字段,不用额外配 alias
简化 target 配置 顶层 target 自动继承到 loader 和压缩插件,不用重复声明
简化 swc-loader 配置 detectSyntax: 'auto' 自动推断语法,一条规则覆盖所有文件类型
哈希模块 ID optimization.moduleIds: 'hashed',更短更稳定的模块 ID
代码分割改进 支持 splitChunks.enforceSizeThreshold,强制拆分大 chunk

2. Why:为什么需要 Rspack 2.0?

2.1 webpack 的痛,Vite 解决不了的部分

前端构建工具的格局在 2026 年变成了"三足鼎立":

Vite 在中小型项目上依然是王者------零配置启动、HMR 极快、插件生态丰富。到 2026 年 Vite 的生产构建底层已切换至 Rolldown(Rust),大型项目的构建性能也有了质变。但 Rspack 在两个场景上依然有不可替代性:一是微前端主应用的子应用加载 (webpack 的 ModuleFederationPlugin 生态深度依赖),二是需要深度定制 webpack compiler hooks 的企业级项目。Vite 的生产构建走 Rollup/Rolldown 路线,跟 webpack 的 loader/plugin 生态天然不兼容,这不是性能问题,是路线问题。

Rspack 的核心差异化就一句话:让 webpack 生态的项目低成本获得 Rust 级别的性能。

这不是一个小市场。全球有海量的 webpack 项目,它们的配置文件、自定义 loader、公司内部的插件------这些东西不是想扔就能扔的。Rspack 对 webpack 常用 API 与主流生态有 95% 左右的兼容度,意味着大多数项目改个包名就能跑起来。

2.2 1.x 阶段的回顾:目标达成

Rspack 1.0 在 2024 年 8 月发布,设定的目标是"在保持 webpack 兼容的前提下实现 10 倍性能提升"。

从 1.0 到 2.0 发布的近两年间,做到了什么?

  • 周下载量从 10 万增长到 500 万------50 倍增长
  • 陆续引入增量构建、按需编译、持久化缓存、常量折叠(Constant Folding)、桶文件(Barrel 文件,即 index 统一导出文件)优化等特性
  • 围绕 Rspack 打造了完整工具链 Rstack(Rsbuild、Rslib、Rstest、Rspress、Rsdoctor、Rslint)
  • 社区支持:Angular、Nuxt、Next.js、Storybook、Docusaurus、Modern.js 等框架已提供 Rspack 适配

2.3 为什么要搞 2.0?

Rspack 团队自己说得很直白:

"Rspack 的目标不只是成为一个「更快的 webpack」。在 1.x 阶段,我们有意让 Rspack 的 API 和默认值与 webpack 5 保持一致,这是为了帮助现有项目低成本迁移。但随着 JavaScript 模块规范和生态的发展,一些历史设计已不再适合作为当下的默认选择。"

1.x 是"长得像 webpack"来吸引你迁移,2.0 开始"做自己"。

2.0 的 ESM 化、RSC 支持、产物优化、配置简化------这些都不是 webpack 会做的事情。Rspack 在保持兼容的前提下,开始引入更符合 2026 年 JavaScript 生态的默认行为。

3. 怎么用上 Rspack 2.0?

3.1 新项目直接用Rsbuild

如果你是首次使用 Rspack,推荐直接用 Rsbuild(Rspack 驱动的开箱即用构建工具):

bash 复制代码
npm create rsbuild@latest

Rsbuild 2.0 已经跟 Rspack 2.0 同步发布,开箱即用,不需要折腾配置。

3.2 从 Rspack 1.x 升级

第一步:升级依赖

json 复制代码
{
  "devDependencies": {
    "@rspack/core": "^2.0.0",
    "@rspack/cli": "^2.0.0",
    "@rspack/dev-server": "^2.0.0",
    "@rspack/plugin-react-refresh": "^2.0.0"
  }
}

第二步:确认 Node.js 版本

Rspack 2.0 要求 Node.js 20.19+ 或 22.12+,不再支持 Node.js 18。

bash 复制代码
node -v
# 如果低于 20.19,先升级 Node.js

第三步:处理 Breaking Changes

主要的破坏性变更:

变更 影响 解决方案
移除 module.unsafeCache 缓存配置需调整 cache.type: 'persistent' 替代
@rspack/cli 不再默认依赖 @rspack/dev-server rspack serve 命令需要手动安装 dev-server npm add @rspack/dev-server -D
resolve.exportsPresence 默认值从 warn 改为 error 导出不存在时直接报错 ⚠️ 最容易导致 CI 构建失败的破坏性变更!许多第三方库的 exports 字段书写不规范会被误杀。升级后如果构建失败,可在 resolve.exportsPresence 设为 'warn' 降级为警告(不中断构建)
builtin:swc-loader 不再读 .swcrc SWC 配置需迁移到 loader options 把配置移到 rspack.config.mjs
output.chunkLoadingGlobal 默认值变更 chunk 全局变量名从 webpackChunk 变为 rspackChunk 如有依赖可显式配置
webpack-bundler-analyzer 被移除 --analyze 参数失效 改用 Rsdoctor
output.libraryTarget 等配置迁移 库构建配置语法变更 迁移到 output.library.type
devServer.hot 默认值变更 热更新默认不再自动开启 需手动配置 devServer.hot: true,否则升级后 HMR 失效
统计输出(stats)配置重构 部分 webpack 兼容的 stats 字段被移除 自定义日志输出需适配新格式

resolve.exportsPresence 降级示例:

js 复制代码
export default defineConfig({
  resolve: { exportsPresence: 'warn' },
});

第四步(可选):用 Agent 辅助迁移

如果你在用 Coding Agent(比如 Cursor、Claude Code 等),可以安装 Rspack 官方的迁移 Skill:

bash 复制代码
npx skills add rstackjs/agent-skills --skill rspack-v2-upgrade

装好后让 Agent 帮你完成升级,通常比手动改更高效。

3.3 从 webpack 迁移

如果你还在用 webpack,Rspack 的迁移路径已经非常成熟了。核心步骤:

bash 复制代码
# 1. 安装 Rsbuild(最简路径)
npm create rsbuild@latest

# 2. 选择 "Convert from webpack" 模式
# Rsbuild 会自动尝试转换你的 webpack.config.js

或者手动迁移:

js 复制代码
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { defineConfig } from '@rspack/core';

// 2.0 推荐 ESM 配置文件,不再默认有 __dirname,需手动声明
const __dirname = path.dirname(fileURLToPath(import.meta.url));

export default defineConfig({
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, './dist'), // 必须使用绝对路径
  },
  module: {
    rules: [
      {
        test: /\.(?:js|mjs|jsx|ts|tsx)$/,
        use: {
          loader: 'builtin:swc-loader',
          options: {
            // 2.0 新特性:自动推断语法,替代手写 syntax + jsx/tsx 布尔值
            detectSyntax: 'auto',
            jsc: {
              transform: {
                react: {
                  runtime: 'automatic',
                },
              },
            },
          },
        },
      },
    ],
  },
});

2.0 新增的 detectSyntax: 'auto' 大幅简化了多文件类型的 loader 配置

以前要写多条 rules 分别处理 .js.jsx.ts.tsx,还要手动指定 jsc.parser.syntaxjsxtsx 等选项,现在一条规则全搞定,根据文件扩展名自动推断该用哪种语法解析。

注意: output.path 必须传绝对路径 ,直接写相对路径在 ESM 配置文件下会报错。因为 2.0 推荐用 ESM 配置文件,不再默认有 __dirname,需要通过 fileURLToPath 手动声明------这是很多用户升级会踩的坑。

3.4 从 webpack 迁移要注意什么

需要注意的是,95% 兼容主要覆盖常用 API 与主流生态,深度定制的项目迁移仍可能遇到兼容性问题。几个常见问题:

1. 自定义 webpack 插件

如果你的项目有自己写的 webpack 插件,需要检查是否用到了 Rspack 未实现的 API。大部分插件的 tapable hooks 都是兼容的,但一些边缘 API 可能没覆盖。

2. 特定 loader 兼容性

大部分常用 loader(babel-loader、css-loader、style-loader、postcss-loader)都是兼容的。但一些小众 loader 可能有问题,需要在 Plugin 兼容列表 里查一下。

3. 模块联邦

Rspack 支持模块联邦,但 @module-federation/runtime-tools 在 2.0 中变成了可选依赖。如果你用了模块联邦,需要手动安装:

bash 复制代码
npm add @module-federation/runtime-tools

4. 产物验证与灰度方案

大型项目迁移最关心风险控制,建议分阶段推进:

  1. 开发环境先切------先在开发环境切换到 Rspack,验证 HMR、代理、静态资源等基础功能
  2. 生产环境双构建对比------同时跑 webpack 和 Rspack 生产构建,对比产物体积、sourcemap、核心功能回归
  3. 逐步全量------确认无差异后,逐步将 CI 流水线切换到 Rspack

4. 该选谁?

维度 Rspack 2.0 Vite Turbopack
底层语言 Rust Rust (Rolldown 生产构建) Rust
webpack 兼容 ⭐⭐⭐⭐⭐ (常用 API ~95%)
开发体验 ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ (仅 Next.js)
大型项目构建 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐
产物质量 / Tree Shaking ⭐⭐⭐⭐⭐ (CJS + ESM 均强) ⭐⭐⭐⭐⭐ (纯 ESM 项目上限更高,CJS 兼容项目弱于 Rspack) ⭐⭐⭐⭐
生态成熟度 ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐
RSC 支持 ✅ (实验性,基础链路) ✅ (通过框架) ✅ (Next.js)
适用场景 webpack 迁移 / 大型项目 中小型 SPA Next.js 项目
维护方 字节跳动 VoidZero (尤雨溪) Vercel

并不是说 Rolldown 跑不动大项目,它在底层性能上已经足够优秀。差异在于生态兼容性:Vite 的产物构建走 Rollup/Rolldown 链路,无法直接复用 webpack 的 loader 和插件。如果你的大型项目重度依赖 webpack 生态(比如自定义 compiler hooks、特定的 webpack loader),Rspack 是更平滑的选择。

一个比较务实的选型原则:

  • 新项目,中小型 SPA → Vite,开发体验最好,Rolldown 核心已经成熟
  • webpack 存量项目 → Rspack,迁移成本最低,常用 API 95% 兼容
  • Next.js 项目 → Turbopack,原生集成,Next.js 专属第一公民
  • 微前端 / 深度定制 webpack hooks → Rspack,ModuleFederation 和 compiler hooks 生态不可替代
  • 不确定 / 想留后路 → Rspack,因为它的 webpack 兼容性给你最大的退路

5. Q&A

Q1: Rspack 和 webpack 的核心区别是什么?

核心区别是底层实现语言。webpack 用 JavaScript 编写,受限于 Node.js 单线程模型和 V8 引擎的 GC 压力;Rspack 用 Rust 编写核心,利用 Rust 的内存安全、零成本抽象和多线程能力。性能方面,无缓存首次构建约为 webpack 的 5-10 倍,持久化缓存命中场景可达 20 倍以上。同时 Rspack 对 webpack 常用 API 与主流生态有约 95% 的兼容度,大幅降低迁移成本。

Q2: Rspack 2.0 转为纯 ESM 包,对使用者有什么影响?

对大多数项目没有影响。Node.js 22.12+ 已稳定支持 CommonJS 中通过 require() 加载 ESM 模块,20.19+ 为实验性支持。所以即使你的项目是 CommonJS 的,在符合 Node 版本要求的前提下也能正常使用 Rspack 2.0。

⚠️ 但有一个边界情况:如果 ESM 模块内部使用了顶层 await(Top-level await)require() 会直接抛出 ERR_REQUIRE_ASYNC_MODULE 错误,无法绕过。目前 Rspack 2.0 核心包自身没有使用顶层 await,但如果你在配置文件中引入的第三方 ESM 依赖含有顶层 await,CJS 项目就会挂掉。解决方案:把 rspack.config.js 改名为 rspack.config.mjs,或在 package.json 中设置 "type": "module",将项目切换为 ESM 模式。

另外,Rspack 2.0 不再支持 Node.js 18,最低要求 20.19+ 或 22.12+。

Q3: 什么是 #__NO_SIDE_EFFECTS__ 注解?它解决了什么问题?

这是构建器注解(Bundler Annotations)规范中定义的注解,用于将函数标记为无副作用。当被标记函数的调用返回值未被使用时,tree shaking 可以安全移除该调用。这解决了一个长期问题:跨模块的函数调用,即使返回值没被使用,因为函数体可能有副作用(比如修改全局变量、发请求),打包工具不敢删。有了这个注解,打包工具就知道可以安全删除。

Q4: Rspack 2.0 的依赖瘦身为什么重要?

两个原因。第一,依赖越少,安装越快,磁盘占用越小------@rspack/dev-server 从 15MB 降到 1.4MB 是实打实的体感提升。第二,依赖越少,供应链风险越低。npm 生态中一个传递依赖被投毒或意外破坏的事件并不罕见。Rspack 通过把依赖打包进发布产物(vendor 化)、用 Node.js 原生 API 替代第三方包等方式,大幅降低了这种风险。但要注意,vendor 化意味着你无法通过 npm overrides 直接升级传递依赖版本来修复 CVE 漏洞,需依赖官方发版;patch-package 虽可对打包后的产物生效,但维护成本极高,不推荐企业级项目使用。

Q5: 在什么场景下应该选择 Rspack 而不是 Vite?

两个典型场景:1)有大量 webpack 存量代码 ------包括自定义 loader、插件、配置文件,或重度依赖 webpack 特有的能力(如模块联邦、特定的 compiler hooks),迁移到 Vite 意味着重写整个构建链路,成本远高于切换到 Rspack;2)微前端主应用------需要与多个基于 webpack 构建的子应用协同,Rspack 的模块联邦生态兼容性是刚需。

反之,如果是从零开始的中小型 SPA,不涉及 webpack 历史包袱,Vite 的零配置开发体验依然是首选。


参考资料:


以上就是本节的全部内容,如果你觉得有收获,欢迎关注,持续分享好文,下期见~

相关推荐
Flynt6 小时前
我用Biome替换ESLint+Prettier在项目上踩了不少坑
rust·eslint·前端工程化
Revolution611 天前
页面更新后为什么出现 Loading chunk failed:旧页面如何请求了已删除的构建产物
前端·面试·前端工程化
Revolution611 天前
首屏变慢后,应该先查资源下载、脚本执行还是接口请求
前端·性能优化·前端工程化
AINative软件工程1 天前
LLM 应用的 Eval 数据工程实践:Golden Set 构建、版本管理与生产 Trace 回放
llm·测试·前端工程化
至乐活着2 天前
Vite 构建工具原理解析与实战:从 ES Module 到极速 HMR
vite·热更新·构建工具·前端工程化·es module
京东云开发者2 天前
拆解海博 AI-Native 落地保障:Harness、双 Loop、知识库与技能自主迭代实践
llm·ai编程·前端工程化
鱼樱前端3 天前
别再"学工具"了,先搭你的 AI 工作流
前端·ai编程·前端工程化
Revolution614 天前
改一个订单筛选,为什么要在六个目录里来回找,前端项目目录到底要怎么拆
前端·前端工程化
Revolution615 天前
一个公共表格组件,是怎么一步步失控的
前端·前端工程化