组件库文档被我从 webpack 迁移到了 vite,首次启动速度提升了60倍
背景
这个标题可能有点标题党的意思,但正如标题所说,我最近把组件库文档从 webpack5
迁移到了 vite3
,大概看了下,首次启动从 60s 降到了 不到 1s, 提升了60倍, HMR
从 20+s 提高了 不到 1s 的样子,大概提升了 2-30倍的样子吧,具体没做过统计。
当前这个取决于你的电脑硬件水平(只要我电脑够卡,提升就越明显),我个人电脑配置是 i7 6700k + 16G
,显卡是核显,双32寸 4K 显示器。迁移整体也并没花太多时间,总共也就花了不到1天的时间(cv
大法), 不得不说,vite
真香,主要还是快,可以提升很多效率。
需求分析
因为之前写文档的时候,给 webpack
写了 一个 loader
,用来解析 markdown
,并且还添加了一些功能,当然如果要迁移到 vite
的话,我们也要去给 vite
写一个插件,用来解析 markdown
文件,从而进行渲染。不过在这之前,我也简单的了解了下 vite
的插件构建流程。众所周知,vite
是基于rollup
和esbuild
去进行构建的。当然如果你要写vite
插件的话,也有必要去了解下rollup
的插件流程,这个我也简单的去看了下。
rollup
的插件需要是一个函数函数里面需要返回一个对象,如:
function testPlugin () {
return {
name: 'rollup-plugin-xxx',
load () {},
transform () {}
}
}
当然这个和 vite
插件也是一样的,vite
插件兼容了rollup
的插件,只是名字换成了 vite-plugin-xxx
,如果是 vite-plugin-react-xxx
这种会被认为是只会应用在react
环境下的hook
。
rollup
的 插件流程 和 webpack
是差不多的,而 webpack
是基于tapable
去实现的一个插件流程。
这个是 tapable
的 hook
分类:
- 同步
hook
- 异步
hook
- 并行
hook
- 串行
hook
- 熔断
hook
(当前函数有返回值,就停止执行) loop hook
(如果某个hook 调用的时候,有返回值,则从第一个函数执行到当前函数(无限循环),如果没返回值,所有函数执行完成就结束)waterfall hook
瀑布hook
(调用时,值会传递给下一个函数)
rollup
的 hook
为:
async
(如果hook
返回一个promise
则被作为异步hook
, 否则是同步的)first
(如果有 多个插件,实现了这个hook
,这些插件会顺序执行,直到有一个 返回null
)sequential
(如果有 多个插件,实现了这个hook
,将按照插件的特定顺序执行,如果有一个是异步的,则后续的hook
都将等到当前hook
执行完成才会执行)parallel
(如果有 多个插件,实现了这个hook
,将按照插件的特定顺序执行,如果一个hook
是异步的,那么后续的hook
将并行执行,并不会等待当前hook
执行完成)
rollup
的构建流程分为build
和 output generator
两个阶段,build
阶段负责模块解析,转换,构建模块依赖图等。output
阶段,主要是并行的执行一些hook
,并检查是否有动态的模块引入,并执行自定义的动态模块渲染,之后检查是否需要生成hash
,再之后则是对于import.meta.url
调用resolveFileUrl
进行解析,而其他的用resolveImportMeta
解析,之后去执行 renderChunk
,并调用generateBundle
去生成bundle
,然后就是写入了,如果有异常就抛出异常,没有就写入完成,到这就编译完成了。
这个是build
的流程图:
这个是输出阶段的流程图:
当然这些了解下就行了,对于本文来讲,其实也用不到这么多东西,u1s1,其实我也不是很了解,也就刚看。接下来,来了解下 vite
的一些插件流程。
服务启动时调用:
options
buildStart
传入模块请求时调用:
resolveId
(解析模块)load
transform
(本文用到的,和webapck
里面去写loader
类似,需要处理文件输出内容的可以在这个节点进行处理)
服务器关闭时:
buildEnd
closeBundle
当然 vite
在构建的时候,也加入了一些钩子,这部分可以去官网查看。
插件执行顺序
一个 Vite 插件可以额外指定一个 enforce
属性(类似于 webpack 加载器)来调整它的应用顺序,enforce
的值可以是pre
或 post
,解析后的插件将按照以下顺序排列:
Alias
- 带有
enforce: 'pre'
的用户插件 - Vite 核心插件
- 没有 enforce 值的用户插件
- Vite 构建用的插件
- 带有
enforce: 'post'
的用户插件 - Vite 后置构建插件(最小化,manifest,报告)
至此,vite
和rollup
的插件流程就算有了个简单的了解,接下来就可以去写插件了。
实现
我们需要去官网创建一个vite react
项目,创建完后,去建一个project/plugins/index.ts
的文件,project
是你用vite
创建的项目名称。
然后里面添加如下代码
import MarkdownIt from 'markdown-it'
import esbuild from 'esbuild'
function reactCode (code: string) {
const sourceCode = `
import react from 'react'
export default function Md () {
return (
<div>${code}</div>
)
}
`
return esbuild.transformSync(sourceCode, {
loader: 'jsx'
})?.code
}
function md () {
return {
name: 'vite-plugin-md',
transform (source, path) {
const md = new MarkdownIt()
return {
code: reactCode(md.render(source))
}
}
}
}
然后在你的vite.config.ts
引入,并在里面添加如下代码:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import md from './plugins/index'
import inspect from 'vite-plugin-inspect'
export default defineConfig({
plugins: [
inspect(),
react({
include: [/\.(ts|tsx)$/, /\.md$/],
}),
md()
]
})
然后一个简易的 vite
插件就算完成了。之后在App.tsx
,引入你写的md
文件,就可以看到编译后的效果了,这里用到了一个vite-plugin-inspect
用来调试,通过这个你可以看到每个插件处理完后的结果。
为了页面渲染,我们需要返回一个react component
来进行处理,但是如果你直接这样输出,不通过esbuild
做编译的话,页面渲染会报错,所以这里加入了esbuild
,把 react code
给处理成react.createElement
这种形式来进行渲染。具体不是很懂vite
的执行机制,所以就先暂时这么处理了,如果有dalao
懂的话,可以说下。
接下来的话,只要把之前webpack
写过的md-loader
的代码,cv
过来就可以了,这里就不贴了,有兴趣的可以去Github 进行查看,之前的文章也说过实现方式:
不过相比那个时候,文档改动会比较大,暂时就先这样吧,如果各位 dalao
觉得不错,欢迎来个 star
,地址:Github。
结束
至此,本文就算结束了,不知道对你来讲,有没有了解一点底层原理,不过就我来说的话,算是了解到了一点点,至此,组件库的flag
又完成了一个。
转载自:https://juejin.cn/post/7124185207778836487