前言
最近在通过 vite
简单的创建了 handWritten
项目,主要就是用于实现和记录一些关于 JavaScript
手写相关的内容,起初为了简单没有集成 Vitest
(主要还是不太想写测试用例)。
项目文件导入关系如下:
- 在
handWritten\src\code
目录下实现对应的功能 - 在
handWritten\src\code\index.ts
中统一导入对应的文件 - 在
handWritten\src\main.ts
中导入indext.ts
最终在浏览器上观察对应的输出或展示结果,为什么不在 node
中查看呢?因为有些实现的效果是基于 dom
的,所以在浏览器上查看最终的结果会更方便些.
随着实现的功能越来越多(通常一个功能对应一个文件),需要手动导入的文件也越来越多,其实社区中已经有不少的实现方案,但很多东西还是需要自己动手实现才能有更深入的理解、才不会只局限于 API 的使用,于是就花了点时间基于 node
实现这个自动导入的功能.
当然还是需要先简单了解下 vite
插件相关的前置知识内容.
Vite 插件
插件类型
在 vite
中可用的插件分两种:
- 插件命名通常 是一个带
vite-plugin-
前缀
- 插件命名通常 是一个带
rollup-plugin-
前缀
如果对应插件只适用于特定的框架,那么需要在增加对应框架的名称:
vite-plugin-vue-
前缀作为 Vue 插件vite-plugin-react-
前缀作为 React 插件vite-plugin-svelte-
前缀作为 Svelte 插件
插件钩子
通用钩子
在开发中,Vite
开发服务器会创建一个插件容器来调用 Rollup 构建钩子,这类钩子在 rollup
和 vite
中都可以使用.
服务器启动时被调用
每个传入模块请求时被调用
服务器关闭时被调用
Vite 独有钩子
Vite
插件中也可以设置只为 Vite
提供特定钩子用于实现特定目标,这些会被 Rollup
忽略.
config
:在解析Vite
配置前调用configResolved
:在解析Vite
配置后调用configureServer
:是用于配置开发服务器的钩子,一般用于添加自定义中间件transformIndexHtml
:转换index.html
的专用钩子,会接收当前的HTML
字符串和转换上下文handleHotUpdate
:执行自定义HMR
更新处理,钩子会接收一个上下文对象
插件执行顺序
Vite
插件可以额外指定一个 enforce
属性来调整插件的执行顺序,enforce
的值可以是pre
或 post
.
解析后的插件将按照以下顺序排列:
- Alias
- 带有
enforce: 'pre'
的用户插件 - Vite 核心插件
- 没有 enforce 值的用户插件
- Vite 构建用的插件
- 带有
enforce: 'post'
的用户插件 - Vite 后置构建插件(最小化,manifest,报告)
实现自定义插件 autoImport
定位插件类型
首先 自动导入 功能肯定不属于 Vite 独有的
,也不属于 框架特有的
,因此,这个插件的在定位上应该要作为 兼容 Rollup 的插件
,于是我们可以给它命名为 rollup-plugin-auto-import
,但是后来发现这个插件名称已经被使用了,为了避免混淆将其重新命名为:rollup-plugin-simple-auto-import
.
核心功能分析
为了方便介绍和演示,这里就只要针对两个核心功能做实现即可:
- 监听
src\code
目录下文件的新增、重命名、删除等操作 - 根据具体的文件操作,做不同的处理
- 对新增、重命名的文件,自动在
src\code\index.ts
中进行导入 - 对删除的文件,自动在
src\code\index.ts
中进行剔除
核心功能实现
监听 src\code
目录下的文件变化
在 node
中可以通过 fs.watch()
来监听对应目录下文件的变化,值得注意的是当对应目录下文件被新增、重命名、删除时,其对应的回调函数接收到的 eventTyoe === 'rename'
和 当前文件的命名 filename
// 监听对应目录下文件的变化 function watchFile(dirPath) { fs.watch( dirPath, { encoding: "utf-8", }, (eventType, filename) => { // 重命名文件、新增文件、删除文件时触发 if (eventType === "rename") { console.log("监听到文件的变化了"); } } ); } 复制代码
文件变化时自动处理
- 对新增、重命名的文件,自动在
src\code\index.ts
中进行导入 - 对删除的文件,自动在
src\code\index.ts
中进行剔除
在 node
中可以通过 fs.exists()
来判断是否存在对应的文件,但在官方文档中已经被废弃了:
这里我们通过 fs.access()
来代替 fs.exists()
实现对应的功能:
// 监听对应目录下文件的变化 function watchFile(dirPath) { fs.watch( dirPath, { encoding: "utf-8", }, (eventType, filename: string) => { // 重命名文件、新增文件、删除文件时触发 if (eventType === "rename") { const filePath = path.join(defaultDirPath, `/${filename}`); fs.access(filePath, (error: any) => { // 判断是否存在对应文件 if (error) { // 不存在文件则将对应文件的导入语句清除 const repalceStr = `import "./${filename}"`; rewriteFile(repalceStr, ""); } else { // 存在对应文件则向目标文件添加导入语句 const appendContent = `\nimport "./${filename}"`; appendFile(appendContent); } }); } } ); } 复制代码
rewriteFile 方法
该方法主要负责对 src\codes\index.ts
文件进行重写,针对被删除的文件,需要将原本导入的语句进行删除,内容比较简单:
// 重写文件内容 function rewriteFile(repalceStr, content) { const rawContent = fs.readFileSync(defaultImportPath, { encoding: "utf8" }); fs.writeFileSync(defaultImportPath, rawContent.replace(repalceStr, content)); } 复制代码
appendFile 方法该方法是基于 src\codes\index.ts
文件的原有内容,添加对新增、重命名文件导入语句,内容也比较简单:
// 追加文件内容 function appendFile(appendContent) { fs.appendFile(defaultImportPath, appendContent, function (error) { if (error) { console.log("~ 自动导入文件【失败】 ~"); return; } console.log("~ 自动导入文件【成功】 ~"); }); } 复制代码
定义插件
有了核心功能的实现,最后只需要将这个核心功能定义为 vite
能识别的插件形式就可以了,具体内容在 src\plugins\autoImport.ts
中:
export default function () { return { name: "rollup-plugin-simple-auto-import", apply: "serve", // 指明它们仅在 'build' 或 'serve' 模式时调用 buildStart() { watchFile(defaultDirPath); }, }; } 复制代码
核心演示效果
最后
这里只是实现了最基本的核心功能,还可以扩展出其他更多的内容,但从实现过程上来看需要了解不算少的前置知识,这也是一个能够让我们学到更多的一个过程,具体代码可查看 源代码.