前言
随着 Rust 语言大火后,国内的字节跳动公司使用 Rust 编写一个高性能 JavaScript 打包工具,叫 Rspack。它提供对 webpack 生态良好的兼容性,能够无缝替换 webpack, 并提供闪电般的构建速度
Rspack 诞生的原因是为了解决在字节跳动维护构建工具时遇到的各种性能问题。在字节跳动内部存在许多巨石应用,它们都具有复杂的构建配置,生产环境的构建需要耗费十几分钟,甚至超过半小时;开发环境的耗时也超过十几分钟。
我们在 webpack 上尝试了多种方法来优化这些巨石应用,但是效果甚微。我们意识到在 webpack 上的优化已经难以为继,必须要从底层改造,才能适应我们的需求,所以字节跳动跳动做了 Rspack
前端开发人员接触最早的打包工具就是 webpack, 开启了前端工程化的革命。很多学习前端工程化的同学,使用 webpack 从0到1搭建过前端工程化项目,而 Rspack 兼容 webpack,并且性能比 webpack 好,那我们也应该学习 Rspack 搭建前端工程化项目
rspack-react-template
Rspack 在 2024 年 8 月发布了 Rspack 1.0 版本,覆盖了 webpack 绝大多数的 API 和功能,并达到生产稳定。然后提供基于 React 的开发模版,供前端开发人员开箱即用,不用从头开始配置 Rspack。
那么请读者跟随笔者一起分析 Rspack 提供的这套 React 的开发模板
这套开发模版整体非常简洁,接下来笔者逐个分析这些文件,.gitigore/yarn.lock/README.md 这几个文件可以直接滤过,没有什么重要内容
index.html
点开 index.html 文件,其内容如下
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Rspack + React</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
不难发现 index.html 的入口文件非常简单,只是设置了一个 div 容器节点
rspack.config.mjs
rspack.config.mjs 文件是 rspack 的配置文件,是非常重要的,我们重点要学习里面的各项配置
前面五个 import 语句是为了导入一些模块,其中 dirname 和 fileURLPath 方法是为了获得配置文件的 dirname,也就是为这一行代码服务
const __dirname = dirname(fileURLToPath(import.meta.url));
实际上在最新的 Node 版本中已经支持 dirname 参数,不需要再用 fileURLToPath 方法处理, 简化后的代码是这样的
const __dirname = import.meta.dirname
是不是比原写法简洁太多,建议小伙伴试试
defineConfig 和 rspack 在后面的具体配置中会用到,后面再细讲,RefreshPlugin 是一个用于 React 模块热替换相关的插件,具体作用等后面配置 plugin 时细讲
const isDev = process.env.NODE_ENV === "development";
这一行代码是执行不同的 npm scripts 时获取对应的环境变量,该模版代码中使用 cross-env 来处理不同操作系统之间的环境变量差异
用 isDev 区分开发环境和生产环境,在开发环境可以不用压缩 CSS 和 JS,开启 sourceMap 方便调试代码
下面是详细的 defineConfig 配置
从上往下分析,首先是 context 配置
context
在 rspack.config.mjs 中,它是这样用的
context: __dirname
context 是一个配置选项,用于指定项目的根目录。它通常设置为 __dirname,即当前配置文件所在的目录。这个选项的作用是告诉构建工具(如 Rspack)从哪个目录开始解析相对路径。
在 import 语句后面那行获取 __dirname 的值就是用在这里
以下是 context 的作用和重要性:
- 解析相对路径:构建工具会使用 context 作为基准目录来解析配置文件中所有相对路径。例如,入口文件、输出目录等相对路径都会基于这个目录进行解析。
- 简化配置:通过设置 context,可以避免在配置文件中使用绝对路径,从而使配置更加简洁和可移植。
- 一致性:确保所有相对路径的解析都是从同一个目录开始,避免路径解析错误。
entry
在 JavaScript 项目中,特别是使用构建工具(如 Rspack、Webpack)时,entry 是一个非常重要的配置项。它指定了应用程序的入口点,也就是构建工具开始解析和打包的文件。
entry 的主要作用是告诉构建工具从哪里开始构建依赖图。构建工具会从这个入口文件开始,递归地解析所有依赖的模块,最终生成一个或多个打包后的文件。
模板代码中用的是
entry:'./src/main.js'
resolve
接下来,就是我们熟悉的 resolve 配置项,请看下面的代码
resolve: {
extensions: ["...", ".ts", ".tsx", ".jsx"]
},
resolve 包含很多配置项,但是这里只用了 extensions,extensions 用于文件扩展名,但是这里有一个特殊的值 '...'
在这个上下文中,"..." 是一个特殊的语法,用于扩展默认的文件扩展名列表。具体来说,它表示保留 Rspack(或 Webpack)默认的扩展名设置,并在其基础上添加额外的扩展名。
在你的配置中,extensions 数组包含了 "..."、.ts、.tsx 和 .jsx。这意味着除了默认的扩展名(如 .js、.json 等),还会额外支持 TypeScript 和 JSX 文件。
这是一个简洁的方式来扩展默认配置,而不需要显式地列出所有默认的扩展名。这样,Rspack 会首先尝试解析默认的扩展名,然后再尝试解析 .ts、.tsx 和 .jsx 文件。
optimization
optimization 是一个配置对象,用于控制构建过程中的优化行为。它可以帮助你减少输出文件的大小,提高应用程序的性能。以下是一些常见的 optimization 配置选项及其作用:
- minimizer: 是否启用代码压缩。启用后,构建工具会尽可能地缩小输出文件的大小。
- splitChunks: 用于分割代码块,减少重复代码,提高加载速度。
- runtimeChunk: 将运行时代码分离到一个单独的文件中,减少入口文件的大小。
- concatenateModules: 启用模块连接优化,提高运行时的性能。
配置项很多,但是模版代码中只用了 minimizer 配置项,我们回过头来思考,在前端项目中什么样的代码需要压缩,这个问题很简单,HTML/CSS/JS 代码都需要压缩,但是 HTML 在工程化项目里很少,主要是 CSS 和 JS 比较多
所以需要在项目中压缩 CSS 和 JS,Rspack 内置 SwcJsMinimizerRspackPlugin 和 LightningCssMinimizerRspackPlugin 两个插件压缩 CSS 和 JS
optimization: {
minimizer: [
new rspack.SwcJsMinimizerRspackPlugin(),
new rspack.LightningCssMinimizerRspackPlugin({
minimizerOptions: { targets } // 目标浏览器的版本
})
]
},
LightningCssMinimizerRspackPlugin 还支持单独的配置,模版代码中传入 targets,表示支持的目前浏览器的版本
experiments
这个属性是 rspack.config.mjs 配置文件中的一部分,用于启用 Rspack 的实验性功能。在这个例子中,experiments 对象中的 css 属性被设置为 true,这意味着启用了 CSS 相关的实验性功能。
具体来说,启用 css: true 后,Rspack 将支持一些尚未完全稳定或正式发布的 CSS 功能。这些功能可能包括 CSS 模块、CSS 处理优化等。
需要注意的是,实验性功能可能会在未来的版本中发生变化,因此在生产环境中使用时需要谨慎
手把手搭建 Vue 脚手架
前面我们分析 Rspack 官方提供的 React 模板代码,有些小伙伴们可能喜欢 Vue 框架,那么笔者带小伙伴们一起搭建 Vue 的模板代码
pluginVue
用过 Vue 框架的小伙伴都知道,Vue 单文件组件是以 .vue 扩展名结尾的文件,所以需要使用解析.vue 文件的模块。而 Rspack 内置解析 Vue 的模块
import { pluginVue } from "@rsbuild/plugin-vue";
pluginVue 是一个 Rspack 插件,支持 Vue 的所有功能
export default defineConfig({
plugins: [pluginVue()],
});
context 和 entry
context 和 entry 完全可以参照 React 的模板代码, 如下所示
import { defineConfig } from "@rsbuild/core";
import { pluginVue } from "@rsbuild/plugin-vue";
const __dirname = import.meta.dirname;
export default defineConfig({
plugins: [pluginVue()],
context: __dirname,
source: {
entry: {
index: "./main.js",
},
},
});
入口的 HTML
接下来就是提供入口 HTML 文件,Vue 的入口文件比 React 简单,只需要提供字符串文件路径就行了
html: {
template: "./index.html",
},
完整的 rspack.config.mjs 如下所示
import { defineConfig } from "@rsbuild/core";
import { pluginVue } from "@rsbuild/plugin-vue";
const __dirname = import.meta.dirname;
export default defineConfig({
plugins: [pluginVue()],
context: __dirname,
source: {
entry: {
index: "./main.js",
},
},
html: {
template: "./index.html",
},
});
main.js
由于 Vue 与 React 提供的 API 不同,Vue 对外提供 createApp 等方法,
// main.js
import { createApp } from "vue";
import App from "./App.vue";
const app = createApp(App);
app.mount("#app");
index.html
index.html 与 React 基本上一样,
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>rspack+vue</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
完整的 Vue 目录所示
使用 Rspack 搭建前端工程化项目比使用 Webpack 简单很多