Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add vite rfc #18

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 184 additions & 0 deletions 0009-vite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
- 提案时间: 2022-08-08
- 影响版本: 3.x

## 概述

支持使用 [Vite](https://vitejs.dev/) 编译小程序和 H5 端应用。

## 动机

提升 H5 端开发时的编译速度,复用 Vite 生态。

## 使用案例

```jsx
// config/index.js
const config = {
// 使用 Vite 进行编译
compiler: 'vite'
}
```

## 详细设计

### 1. 新增 `@tarojs/vite-runner` 依赖

和现有 Webpack 编译系统的 `@tarojs/webpack5-runner` 作用类似, `@tarojs/vite-runner` 需要实现三个功能:

- 设置 Vite 编译配置。
- 根据不同编译平台(H5 & 小程序)实现 Taro 的自定义编译逻辑。
- 启动 Vite 编译。

### 2. 编译配置兼容

Taro 编译配置包括对 Taro 自身和对底层 Webpack 的配置,我们需要尽量在 Vite 中找到对应的配置进行兼容。

此外 Taro 暴露了 `webpackChain` 配置, 开发者利用此配置来自定义修改 Webpack 配置。对于 Vite 来说,它的插件系统暴露了 `config` 与 `configResolved` 钩子,开发者可以自行编写 Vite 插件去修改 Vite 配置,因此 Taro 将不再暴露额外的 API,这样设计也能和 Vite 生态保持一致性。

### 3. H5 编译逻辑简析

为了兼容 Taro H5 的编译,我们需要实现:编译配置对齐、设置编译 Entry、注入运行时代码。其余的工作交给浏览器和 Vite 处理,开发者就能拥有快速的项目启动和更新体验。

**3.1 编译配置对齐**

Taro H5 主要使用了 Webpack 的 Resolve 配置,如 `alias`、`mainFields` 等,这些配置在 Vite 中都能找到对应。

**3.2 设置编译 Entry**

Development 环境,[项目内的 HTML 文件就是 Vite 的编译入口](https://vitejs.dev/guide/#index-html-and-project-root)。我们可以使用 Vite 插件的 `transformIndexHtml` 钩子在 `index.html` 中注入对 `app.config` 的引用:

```html
<script type="module" src="./app.config.js"></script>
```

**3.3 注入运行时代码**

目前 Taro H5 使用了自定义的 Webpack Loader 去注入运行时代码,从而实现页面加载、组件渲染等功能。在 Vite 中可以使用 Vite 插件的 `load` 钩子达到同样的目的。

经代码注入后的 `app.config`,伪代码:

```jsx
import { createRouter } from '@tarojs/taro'
import { defineCustomElements } from '@tarojs/components'
import App from './app.js'

// 初始化 WebComponents
defineCustomElements()

// 通过异步 import 页面入口 js 文件,触发 Vite 对页面的构建
const routerConfig = [
{
path: 'pages/index/index',
load: () => import('src/pages/index/index.jsx')
},
{
path: 'pages/detail/index',
load: () => import('src/pages/detail/index.jsx')
}
]

// 初始化 Taro 路由
createRouter(App, routerConfig)
```

### 4. 小程序编译逻辑简析

小程序不支持从网络中加载 JS 资源,只能使用 Vite 的 `lib` 模式进行打包,输出小程序规范下的四种产物:`js` `xml` `css` `json`。

**4.1 设置编译 Entry**

和 H5 一样,我们使用 `app.config` 作为 Entry,输出产物则使用小程序支持的 CommonJS 格式:

```json
{
"build": {
"lib": {
"entry": "./app.config.js",
"formats": ["cjs"],
"fileName": "app.js"
}
}
}
```

一开始 Vite 将加载 `app.config.js` ,这时除了使用 `load` 钩子注入 Taro 运行时代码外,还需要使用 `emitFile` API 加载所有页面文件:

```jsx
// Vite Plugin
{
load (id) {
if (id === ENTRY) {
pages.forEach(page => {
this.emitFile({
type: 'chunk',
id: page.path,
})
})
// ...
}
}
}
```

经过这一步,我们可以得到 app 和各页面的 js 文件。

**4.2 拆分输出样式文件**

Vite 在打包时提供了 [cssCodeSplit](https://vitejs.dev/config/build-options.html#build-csscodesplit) 配置,它能把异步 chunk 的样式抽取为单独的样式文件,这个配置在 lib 打包模式下默认关闭,所有样式会被输出到同一个样式文件。

而小程序的入口和页面都需要一份对应的样式文件,我们可以借助 Vite 插件的 `transform` 和 `generateBundle` 钩子实现类似 `cssCodeSplit` 的功能,伪代码:

```jsx
// Vite Plugin
{
transform (code, id) {
// 收集样式代码
if (isStyleModule(id)) {
styleCodes[id] = code
}
},
generateBunle (_, bundle) {
for (chunk of bundle) {
// 收集当前 chunk 引用的样式
let code = ''
chunk.modules.forEach(module => {
if (isStyleModule(module)) {
code += styleCodes[module.id] + '\n'
}
})
// 输出单独的样式文件
this.emitFile({
type: 'asset',
source: code
})
}
}
}
```

**4.3 输出 xml 和 json 文件**

通过以上两步,我们得到了 js 和 css 文件,还剩下 xml 和 json 文件需要输出。因为它们都是静态文件,不需要经过 Vite 编译,所以在 `generateBundle` 阶段调用 `emitFile` 输出即可。

## 规划与排期

基于 Vite 实现现有编译系统的所有功能将需要较多工时,因此我们把工作分为第一期、第二期还有希望社区一起参与共建的三个部分。

其中第一期将是核心的编译能力,支撑 H5 和小程序的基础编译功能。第二期则是对功能的扩充与补全,包括适配更多的 Taro 编译配置和优化工作等。共建部分是相对独立的功能模块,例如小程序的插件编译、独立分包功能等,社区可以单独编写对应的 Vite 插件进行适配。

![https://storage.360buyimg.com/cjj-pub-images/vite.png](https://storage.360buyimg.com/cjj-pub-images/vite.png)

## 缺陷

- Vite 生态与 Webpack 不同,旧项目从 Webpack 迁移到 Vite 有一定的学习与改造成本
- 小程序使用 CommonJS 格式打包,splitchunk 后可能产生循环依赖并最终导致报错

## 替代选择

N/A

## 适配策略

- 提供详尽的升级教程与配置文档
- 尽可能抹平两种编译系统之间的差异,例子兼容现有编译配置等
- 规范编译进度、错误输出等提升,方便开发者定位问题