likes
comments
collection
share

组件库文档被我从 webpack 迁移到了 vite,首次启动速度提升了60倍

作者站长头像
站长
· 阅读数 14

背景

这个标题可能有点标题党的意思,但正如标题所说,我最近把组件库文档从 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 是基于rollupesbuild 去进行构建的。当然如果你要写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去实现的一个插件流程。

这个是 tapablehook 分类:

  • 同步 hook
  • 异步 hook
  • 并行 hook
  • 串行 hook
  • 熔断 hook (当前函数有返回值,就停止执行)
  • loop hook(如果某个hook 调用的时候,有返回值,则从第一个函数执行到当前函数(无限循环),如果没返回值,所有函数执行完成就结束)
  • waterfall hook 瀑布 hook(调用时,值会传递给下一个函数)

rolluphook 为:

  • async (如果 hook 返回一个 promise 则被作为异步 hook, 否则是同步的)
  • first(如果有 多个插件,实现了这个hook,这些插件会顺序执行,直到有一个 返回 null
  • sequential (如果有 多个插件,实现了这个hook,将按照插件的特定顺序执行,如果有一个是异步的,则后续的 hook 都将等到当前hook 执行完成才会执行)
  • parallel(如果有 多个插件,实现了这个hook,将按照插件的特定顺序执行,如果一个hook 是异步的,那么后续的hook将并行执行,并不会等待当前hook 执行完成)

rollup的构建流程分为buildoutput generator 两个阶段,build 阶段负责模块解析,转换,构建模块依赖图等。output 阶段,主要是并行的执行一些hook,并检查是否有动态的模块引入,并执行自定义的动态模块渲染,之后检查是否需要生成hash,再之后则是对于import.meta.url 调用resolveFileUrl 进行解析,而其他的用resolveImportMeta解析,之后去执行 renderChunk,并调用generateBundle 去生成bundle,然后就是写入了,如果有异常就抛出异常,没有就写入完成,到这就编译完成了。

这个是build 的流程图:

组件库文档被我从 webpack 迁移到了 vite,首次启动速度提升了60倍

这个是输出阶段的流程图:

组件库文档被我从 webpack 迁移到了 vite,首次启动速度提升了60倍

当然这些了解下就行了,对于本文来讲,其实也用不到这么多东西,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,报告)

至此,viterollup 的插件流程就算有了个简单的了解,接下来就可以去写插件了。

实现

我们需要去官网创建一个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 又完成了一个。