还没了解过 Vite 的 plugin?手写一个 plugin 带你熟悉 API
Plugin
如果说你没有对前端模块化有过了解,不了解 Webpack
的 plugin
,可以阅读这个章节了解 Plugin
的前世今生~
在上一代打包工具 browserify
的生涯末期,Webpack
的带着自己的代码分割和其它性能优化创建了新一代的打包工具时代,但与此同时 Webpack
使用暴露整个编译生命周期的钩子函数给予第三方插件(plugin
)的设计也大大丰富了 Webpack
系的生态,而如今基于 ESM
模块标准的新一代打包工具的 Vite
也沿用了 Webpack
的插件设计,那么两者的 plugin
有什么区别呢?
答案就是没有区别,至少在暴露钩子函数这块的设计上是没有的
硬要扯点就是 Webpack
的 plugin
写法是在 apply()
中对 compiler
的一系列 hook
做绑定,而 Vite
则是做了简化,可以直接在对应的钩子上书写逻辑,大致如下
为啥 Vite
里面没有 loader
? 从功能上来说,Webpack
的 plugin
本质上是可以替代 loader
的工作的(不过会麻烦一点),可能出于某些考量,尤大就放弃多设计一个 loader
的入口了吧?
编写
开始上手!你可以在线查看源代码
Vite
的文档中建议我们开发的时候用了 Vite
钩子函数的插件统一叫 vite-plugin-[name]
,而相应的 plugin
就用一个 .js
文件便于表示,插件如下
export default function vitePluginPrintName() {
return {
name: 'transform-file',
// ...
}
}
其实就是一个函数,JavaScript
的一等公民,使用也很简单,在 vite.config.js
直接导入就行
import { defineConfig } from "vite";
// ...
import vitePluginPrintName from "./plugin/vite-plugin-print-name";
// ...
export default defineConfig({
// ...
plugins: [..., vitePluginPrintName()],
});
Vite 专属钩子
重点介绍一下 Vite
的专属钩子,一共有下面几个,一共是 6
个
config
configResolved
configureServer
configurePreviewServer
transformIndexHtml
handleHotUpdate
config
是负责和合并用户自定义配置和插件配置,而 configResolved
则代表最终配置,比如
export default function vitePluginPrintName() {
let config = undefined;
return {
name: "vite-plugin-print-name",
config: (config, env) => {},
configResolved: (resolvedConfig) => {
config = resolvedConfig;
},
transform(code, id) {
if (config.mode === "development") {
// 开发环境
} else {
// 生产环境
}
},
};
}
通过一个闭包来判断 Vite
此时的环境,在 Vite
的插件示例中,有很多这样的设计
而 transformIndexHtml
则是负责 index.html
的转换(即根目录下的默认 html
模板),比如
const htmlPlugin = () => {
return {
name: 'html-transform',
transformIndexHtml(html) {
return html.replace(
/<title>(.*?)<\/title>/,
`<title>Title replaced!</title>`,
)
},
}
}
中间件
这是两个非常有意思的钩子函数,configureServer
和 configurePreviewServer
两个钩子函数是一样的作用,和 express
的中间件设计类型一致,如下
const myPlugin = () => ({
name: 'configure-server',
configureServer(server) {
server.middlewares.use((req, res, next) => {
// 自定义请求处理...
})
},
})
由于 Vite
采用的是 ESM
所以每个模块的调用都会请求一次,所以可以在中间件中监听模块
注意 configureServer
只会执行一次,而其中定义的中间件每次引用模块时都会执行
除了中间件以外,Vite
还提供了和客户端通信的方法
客户端与服务端间通信
在 configureServer
钩子函数中的参数 server
里面还有一个 ws
属性,其实就是 websocket
,webpack
和 vite
对于热更新的做法都是使用的 websocket
,而 vite
对于双端通信的做法就是挂载 import.meta.hot
到全局对象,然后通过监听事件通信
// vite.config.js
export default defineConfig({
plugins: [
{
// ...
configureServer(server) {
server.ws.send('my:greetings', { msg: 'hello' })
},
},
],
})
// client side
if (import.meta.hot) {
import.meta.hot.on('my:greetings', (data) => {
console.log(data.msg) // hello
})
}
transform
transform
和 load
和 resolveId
是 vite
中的通用函数,load
和 resolveId
是负责处理模块标识符的,也就是最终在磁盘 I/O
的路径,比如
D:/fe-project/vite-project/vue2-start-with-vite/node_modules/.vite/deps/vue.js?v=5417f592
而文章实现的 plugin
功能就是输出源代码中每个模块的调用顺序,即 src
目录下每个模块的调用顺序,实现如下
export default function vitePluginPrintName() {
let config = undefined;
return {
name: "vite-plugin-print-name",
configResolved: (resolvedConfig) => {
config = resolvedConfig;
},
transform(code, id) {
if (config.mode === "development") {
const rootPath = process.cwd().replace(/\\/g, "/");
if (id.indexOf(rootPath + "/src") !== -1) {
console.log(id.replace(rootPath + "/src", ""));
}
}
},
};
}
效果如下
参考资料
转载自:https://juejin.cn/post/7180374113196703801