Modern.js v2 发布:支持 Rspack 构建

发表于 2023.03.16

大家好,很高兴地向大家宣布,Modern.js v2 版本已经正式发布了!

Modern.js 是字节跳动 Web Infra 团队开源的一套 Web 工程体系。在开源以来的一年多时间里,Modern.js 保持稳定的迭代节奏,数十位贡献者参与了开发,累计提交 2000+ 个 Pull Request,并支持了 Rspack 构建、嵌套路由、流式渲染等新特性。

在这篇文章里,我们会和大家一起聊一聊 Modern.js 在过去一年多时间里的变化。

Modern.js v2 官网

Modern.js v2 官网

背景

首先介绍一下我们为什么要做 Modern.js v2 版本,主要有以下几个原因:

  1. 下沉更多能力:在字节跳动内部,Web Infra 团队整合了多个 Web 开发框架和解决方案,收敛技术栈,将通用能力下沉到底层的 Modern.js 工程体系中。
  2. 拥抱 Rust 生态:随着社区中更多的前端工具链基于 Rust 重写,Modern.js 正在积极拥抱这一变化,将底层工具逐步替换为 Rust 实现。
  3. 底层依赖升级:为了对 Modern.js 的底层依赖进行大版本升级,包括 React v18、React Router v6 等,从而引入流式渲染、嵌套路由等新能力。
  4. 定位调整:从 2021 年开源以来,社区中的伙伴们给予我们很多有价值的反馈,这帮助我们对 Modern.js 的定位和架构进行更多地思考(详见下文)。

以上因素促使我们完成了 Modern.js v2 版本。

定位

在 v2 版本中,我们重新明确了 Modern.js 的定位:Modern.js 是字节跳动 Web 工程体系的开源版本,它提供多个解决方案,来帮助开发者解决不同研发场景下的问题。

目前我们开源了三个解决方案,分别面向 Web 应用开发场景、npm 包开发场景和文档站开发场景:

Modern.js 解决方案

Modern.js 解决方案

作为 Modern.js 工程体系的一部分,以上每个解决方案都可以被单独使用,并且各自部署了独立的文档站点。开发者可以按需选择其中的一个或多个解决方案来使用。

在代码层面,这三个解决方案按照统一的研发规范迭代,并复用同一套插件机制,因此,它们提供的研发体验也较为一致。大家如果对内部实现感兴趣,可以在 GitHub 上的 Modern.js 仓库 中找到它们的源代码。

下面让我们来了解一下这三个解决方案提供的最新能力。

Modern.js 框架

Modern.js 框架是一个基于 React 的渐进式 Web 开发框架。在字节跳动内部,我们将 Modern.js 封装为上层框架,并支撑了数千个 Web 应用的研发。

由于 Modern.js 框架的使用最为广泛,在下文中,我们会省略「框架」,直接称其为 Modern.js。

在 Modern.js 落地的过程中,我们收到很多业务同学提出的诉求,包括构建性能、框架的灵活性和可扩展性、页面加载性能等方面,相信社区中的开发者也会面临相似的问题。

为了更好地满足这些诉求,Modern.js v2 提供了以下特性:

  • 通过引入 Rust 构建工具 —— Rspack 来提升构建性能。
  • 通过渐进式设计来保证框架的灵活性。
  • 通过插件系统来提供更好的可扩展性。
  • 通过嵌套路由来改善开发效率、优化加载性能。

Rspack 构建模式

Modern.js v2 提供开箱即用的双构建工具支持,开发者可以在成熟的 Webpack 和更快的 Rspack 之间进行切换。大家对 Rspack 可能还不太了解,Rspack 是由字节跳动开源的 Web 构建工具,基于 Rust 语言开发,具有高性能、可定制、兼容 Webpack 生态等特性。

Rspack Logo

相较于 webpack,Rspack 的构建性能有明显提升,除了 Rust 带来的语言优势,这也来自于它的并行架构和增量编译等特性。经过 benchmark 验证,Rspack 可以给 Modern.js 项目带来 5 ~ 10 倍编译性能的提升。

Rspack Benchmark

Modern.js 已支持在 Rspack 模式下使用框架的 70% 功能和配置项,包括服务器端渲染(SSR),静态站点生成(SSG)、嵌套路由、CSS Modules 和 Tailwind CSS 等功能。除了功能,Modern.js 在 Rspack 模式下的配置项也与 Webpack 模式基本一致,在我们的实际验证中,一个小型项目花几分钟就可以平滑切换到 Rspack 模式。

目前,Rspack 项目以及 Modern.js 的 Rspack 模式仍在快速迭代中。在未来几个月内,我们将逐步对齐 Webpack 模式和 Rspack 模式的绝大多数功能和配置,使更多项目能从 Webpack 平滑地过渡到 Rspack。

Rspack 已于 2023.03.10 号正式开源,你也可以在非 Modern.js 的项目中直接使用 Rspack 进行构建。如果你对 Rspack 感兴趣,请阅读 「Rspack 正式发布了」来了解更多。

渐进式设计

大家对 Modern.js 框架的第一印象可能是「一个大而全的框架」,但事实上,为了避免变得臃肿,Modern.js 采取了渐进式设计,将所有功能建立在灵活的插件系统之上,因此具备按需启用和可插拔的能力。

Modern.js 期望能支持不同规模的项目研发,考虑到中大型项目和小型项目对功能的诉求存在差异,Modern.js 的大多数功能都是按需启用的。在创建项目时,Modern.js 默认只安装核心模块,使 npm 依赖保持轻量;当需要用到额外功能时,你可以通过 modern new 命令一键开启,并自动安装相关依赖。

modern new 命令

modern new 命令

比如:

  • 对于基础的开发场景,项目中只需安装 Modern.js 的 CLI 工具 @modern-js/app-tools,即具备了开发调试、生产构建的能力。
  • 当你需要在应用中增加一些 BFF 接口时,可以执行 modern new 命令来启用 BFF 功能。启用后,Modern.js 会自动安装所需的 BFF 插件,以及某个 Node.js 框架对应的插件(如 Koa / Express):

目前,你可以通过 modern new 命令来按需开启以下功能,未来我们也会将更多功能加入到 new 命令中,使其能够被便捷地集成。

插件系统

开发者在实际项目中使用 Web 研发框架时,除了使用开箱即用的能力,也会有很强的定制化诉求。因此,Modern.js 设计了一套灵活的插件系统来满足这一点。

Modern.js 可以划分为三个核心部分:CLI 工具、服务端和运行时。其中,CLI 工具负责提供 CLI 命令和打包构建能力;服务端提供 SSR、BFF 等能力;运行时提供状态管理、路由等能力。

Modern.js 核心模块

Modern.js 核心模块

我们为这三者分别设计了插件:

  • CLI 插件:对应 CLI 工具,负责控制 CLI 命令、打包构建流程。
  • Server 插件:对应服务端,用于处理服务端的生命周期以及客户端请求。
  • Runtime 插件:对应运行时,用于处理 React 组件渲染逻辑。

这三种插件使用统一的 Hook 模型,并提供丰富的插件 API。比如,你可以使用插件做到:

  • 注册自定义的 CLI 命令
  • 修改 Rspack、PostCSS、Less、Sass 等基础工具的配置
  • 修改运行时需要渲染的 React 组件
  • 自定义控制台日志
  • 自定义 Node.js 服务器框架
  • ...

在字节跳动内部,我们借助这些插件 API,结合公司内的基建和平台,封装出内部的企业级框架。如果你需要对 Modern.js 框架进行深度定制,也可以借助这些插件 API 来完成。

如果你对 Modern.js 的插件系统感兴趣,请阅读 「Modern.js - 自定义插件」文档。

嵌套路由

Modern.js v2 基于 React Router v6 提供了新的路由方式 —— 嵌套路由(Nested Routes)。

如果大家了解过 Remix 或 Next.js 13,应该对嵌套路由不陌生了。嵌套路由与 Modern.js v1 提供的约定式路由相似,也是一种基于文件系统的路由。

在 Modern.js v2 中,我们约定在 src/routes 目录下的文件,在应用启动时会自动生成对应的路由结构,页面的路由与文件结构是相呼应的,并且可以为资源加载带来一些性能优化。

Modern.js 的嵌套路由包含以下功能:

  • 资源加载:基于路由结构,并行加载多级路由资源,子组件资源加载不再受父组件渲染影响。并且将路由资源进一步细分,保证路由跳转时的最小资源加载。
  • 共享布局:在路由之间轻松共享 UI,同时保持状态,并避免多余的重新渲染。
  • 数据获取:基于路由结构完成多级路由的数据并行获取。

在 Modern.js v2 中,只需要配置 runtime.router,即可开启嵌套路由:

如果你对嵌套路由的细节感兴趣,请阅读「Modern.js - 路由」文档。

流式渲染

Modern.js v2 基于 React v18 的全新 API 支持了流式渲染(Streaming SSR)。

如果大家关注去年 React v18 的版本更新,可能会注意到它提供了新的 Server Side APIs,并对 Suspense 做了完整支持。在 Modern.js v2 中,我们通过 React Router v6 暴露的能力,在框架层面支持了流式渲染。

流式渲染利用 HTTP 的能力,允许将页面的 HTML 分割成较小的块,并逐步将这些块从服务器发送到客户端。页面无需等待所有数据加载完成,即可呈现完整用户界面,因此与数据无关的局部内容能够更早地显示出来。

在 Modern.js v2 中,只需要配置 server.ssr.mode,即可开启流式渲染:

如果你想在 Modern.js 中使用 SSR 和流式渲染,请阅读「 Modern.js 服务端渲染(SSR)」文档。

Modern.js Module

Modern.js Module 是我们针对 npm 包开发场景提供的解决方案,它为开发者提供了专业的 npm 包开发工具,能够更轻松地构建、调试和发布一个 npm 包。

Modern.js Module 首页

Modern.js Module 首页

Modern.js Module 的核心能力包括:

  • 构建能力:基于 esbuild 实现的构建工具,提供类型生成能力,能够高度定制构建产物。
  • 调试能力:基于 Storybook 调试项目,可以测试代码功能、验证构建产物可用性。
  • 版本管理能力:基于 Changeset 提供版本管理命令,涵盖从开发到发布的过程。
  • 扩展能力:提供包含丰富 Hooks 的插件机制,支持扩展调试能力,或对流程进行自定义。

基于 esbuild 构建

在 v2 版本中,我们以 esbuild 为核心,扩展其插件机制,并结合 SWC 实现了一个通用的模块构建工具,它的特性包括:

  • 支持 bundle 和 bundleless 两种模式的构建。你可以选择构建产物的形式 —— 是打包处理后的 bundle 产物,还是直接通过 transform 处理的 bundleless 产物。
  • 相较于 esbuild,提供更丰富的构建能力和插件 Hook。例如,在样式方面,默认支持 Less、Sass、PostCSS 和 CSS Modules,并支持静态资源处理、SVGR 等常用能力。这些能力在 bundle 和 bundleless 模式下均可使用。

在生成 TS 类型文件上,Modern.js Module v2 也支持新的方式:

  • 类型文件的 bundle 处理:对生成的类型文件进行打包处理,生成一个单独的类型文件。
  • 类型文件里的别名处理:对生成的类型文件里包含的别名路径进行转换。

总的来说,相较于 v1 版本,Modern.js Module v2 提供了更完整的构建能力,同时构建效率也有明显提升。

如果你对 Modern.js Module 感兴趣,请移步「Modern.js Module 文档」来了解更多。

Rspress

Rspress 是一个面向文档站场景的框架,它的目标是给开发者提供一个简单、高效、可扩展的文档站解决方案。

Rspress 官网介绍页

Rspress 官网介绍页

目前,Rspress 包含如下能力:

  • 基于 Rspack 构建:项目构建和 HMR 速度飞快,提供愉悦的开发和生产部署体验。
  • 支持 MDX 语法:在普通 Markdown 语法的基础上可以嵌入 React 组件和 JSX 语法。
  • 全文搜索:内置开箱即用的全文搜索能力,基于 FlexSearch 实现。
  • 支持国际化:提供多语言文档切换功能,且支持多语言内容搜索。
  • 自定义主题:内置一套默认主题,你可以基于这套主题进行扩展,也可以从零定制一套自定义主题。
  • 插件机制:提供丰富的插件钩子能力,让你能够充分自定义扩展框架功能。

我们做 Rspress 的初心,是为了解决我们团队内的文档站搭建需求。随着 Rspress 的功能逐渐完整,我们也将它开放给社区使用,使它能发挥更大的价值。

目前,Rspress 仍处于 beta 测试状态,相关源代码已经开放在 Rspress 仓库 中,并提供了基础的 使用文档。在未来,我们将继续投入,使之成为一个完成度更高的文档解决方案。

致谢

Modern.js 中的部分代码实现参考了社区中的开源项目,我们对这些项目表示衷心的感谢:

最后

现阶段 Modern.js 仍是一个很年轻的开源项目,在未来,我们将继续拥抱长期主义,持续打磨功能和文档,并以更透明的方式与社区共同协作。

如果你有兴趣尝试一下 Modern.js,欢迎试用并留下你的反馈意见~