Vite 插件开发实践
2021/10/16
前言
Vite 是一种新型的前端构建工具,它使用了JavaScript 原生的模块 ,浏览器请求模块时再对该模块进行转换,相对于 Webpack 这种将模块打包合并的方式速度有了质的飞跃。同时 Vite 也基于 Rollup 插件机制提供了强大的插件 API,下面将介绍 Vite 插件 API 并进行 Vite 插件的开发实践。
插件的 API
对于插件API,官方是这样描述的:
Vite 插件扩展了设计出色的 Rollup 接口,带有一些 Vite 独有的配置项。因此,你只需要编写一个 Vite 插件,就可以同时为开发环境和生产环境工作。
插件结构
通常的惯例是创建一个 Vite/Rollup 插件作为一个返回实际插件对象的工厂函数。该函数可以接受允许用户自定义插件行为的选项。
通用钩子
开发中,Vite 开发服务器会创建一个插件容器来调用 Rollup 构建钩子。
仅使用通用钩子的插件是兼容 Rollup 的插件。
以下钩子在服务器启动时被调用:
options
buildStart
以下钩子会在每个传入模块请求时被调用:
resolveId
load
transform
以下钩子在服务器关闭时被调用:
buildEnd
closeBundle
下面对这7个钩子进行详细解释:
options
类型: (options: InputOptions) => InputOptions | null
种类: async, sequential
读取并替换或操作传递给rollup.rollup
的options
对象。 返回null
时会替换任何内容。如果只需要读取options
对象,建议使用buildStart
钩子。
buildStart
类型: (options: InputOptions) => void
种类: async, parallel
在每次rollup.rollup
构建时被调用。当需要访问传递给rollup.rollup()
的options
对象时,推荐使用此钩子,因为它考虑了所有选项钩子的转换,并且还包含未设置选项的正确默认值。
resolveId
类型: (source: string, importer: string | undefined, options: {isEntry: boolean, custom?: {[plugin: string]: any}) => string | false | null | {id: string, external?: boolean | "relative" | "absolute", moduleSideEffects?: boolean | "no-treeshake" | null, syntheticNamedExports?: boolean | string | null, meta?: {[plugin: string]: any} | null}
种类: async, first
自定义一个解析器。可以用于定位第三方依赖项。
比如,引入了一个模块 import { foo } from '../bar.js';
,使用此钩子可以获取到源文件../bar.js
。
load
类型: (id: string) => string | null | {code: string, map?: string | SourceMap, ast? : ESTree.Program, moduleSideEffects?: boolean | "no-treeshake" | null, syntheticNamedExports?: boolean | string | null, meta?: {[plugin: string]: any} | null}
种类: async, first
自定义一个解析器。可以用来返回代码。
transform
类型: (code: string, id: string) => string | null | {code?: string, map?: string | SourceMap, ast? : ESTree.Program, moduleSideEffects?: boolean | "no-treeshake" | null, syntheticNamedExports?: boolean | string | null, meta?: {[plugin: string]: any} | null}
种类: async, sequential
可以被用来转换引入的模块。可以拿到模块代码,可以用于转换已经加载的模块代码。
buildEnd
类型: (error?: Error) => void
种类: async, parallel
在rollup
完成打包且generate或write被调用之前被调用;也可以返回一个Promise。如果在构建过程中发生了错误,它将被传给此钩子。
closeBundle
类型: closeBundle: () => Promise<void> | void
种类: async, parallel
可以用来清理任何可能正在运行的外部服务。Rollup CLI会确保这个钩子在每次运行后被调用,但JavaScript API的用户应该在他们完成生成bundle.close()
后手动调用。
vite独有钩子
vite独有钩子会被 Rollup 忽略。
config
类型: (config: UserConfig, env: { mode: string, command: string }) => UserConfig | null | void
种类: async
, sequential
解析 vite 配置前调用,可以用来修改 vite 配置。
configResolved
类型:(config: ResolvedConfig) => void | Promise<void>
种类: async
, parallel
解析 vite 配置后调用,可以用来读取、存储解析的配置。
configureServer
类型: (server: ViteDevServer) => (() => void) | void | Promise<(() => void) | void>
种类: async
, sequential
用于配置开发服务器,可以添加一些中间件来处理请求。
transformIndexHtml
类型: IndexHtmlTransformHook | { enforce?: 'pre' | 'post', transform: IndexHtmlTransformHook }
种类: async
, sequential
用于转换 index.html
文件。
handleHotUpdate
类型: (ctx: HmrContext) => Array<ModuleNode> | void | Promise<Array<ModuleNode> | void>
用于自定义 hmr
更新处理。
钩子函数执行顺序
config
-> configResolved
-> options
-> configureServer
-> buildStart
-> transformIndexHtml
-> resolvedId
-> load
-> transform
插件编写实践
下面将创建 vite-plugin-md2react 插件,此插件可以将导入的 markdown 文件转换为 React 组件。
前期配置
- 先初始化一个 npm 包,并创建
src/index.ts
、tsconfig.json
。
package.json
配置。
-
tsconfig.json
配置,用来tsc
打包使用。打包后的产物将保存在dist
目录。
编写插件
前期配置做好了,下面进行插件的编写。
将 markdown 转换为 React 组件,具体的步骤大概分为 3 步,下面是具体内容:
- 我们需要拿到导入的 md 文件内容才能进行转换,所以需要用到
transform
钩子。
- 将 markdown 文本转换为 html 文本,这一步使用 marked 来完成。另外需要 heightlight.js 来实现代码高亮。
- 创建一个 jsx 模板,用来渲染 marked 生成的 html 文本。
- 浏览器无法解析 jsx ,所以我们需要将 jsx 模板转换成 js ,这里使用 esbuild 的 Transform API来完成。
完成,最终的 index.ts
内容如下: