随着 Vue3 生态的不断扩展与日渐成熟,Vue3 已从最开始的尝鲜阶段步入到投入生产项目中。随之而来的还有开发脚手架的更新换代,全新的 Vite 脚手架,基于 esbuild 利用 go 语言的性能优点,相较 Webpack 有着不在一个量级的性能优点,打包方面基于 Rollup 拓展,继承了轻量化和明朗的插件 Api 的优势。vue
什么,你还不知道?你该抓紧了。node
Vue3 官方中文文档git
Vite 官方中文文档github
废话很少说,开始进入的正题。typescript
本文重点讲述如何生成类型声明文件,所以项目建立部分只一些简单描述。npm
经过官方提供的模版快速搭建一个简单的项目:编程
yarn create @vitejs/app my-vue-app --template vue-ts
随后改名 src/main.ts
为 src/index.ts
并修改其内容:json
export { default as App } from './App.vue'
不要在乎 App 这个名字,咱们只是假设咱们写了一个组件,而且做为插件导出。
接着调整 vite.config.ts
的配置为库模式打包:promise
import { resolve } from 'path' import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ build: { lib: { entry: resolve(__dirname, 'src/index.ts'), name: 'Plugin', formats: ['es'], fileName: 'index' } }, plugins: [vue()] })
至此,一个简单的插件项目就完成了。app
在使用 rollup
开发插件时,咱们主要借助 rollup-plugin-typescript2
这个插件来实现根据源码生成 .d.ts
声明文件。
可是该插件存在几个问题,一是没法解析 .vue
文件,二是在 Vite + Vue3 的环境下,存在不兼容性(三是 Vite 内部支持 typescript
,该插件存在很大部分的重复功能),说白了就是用不了。
固然,也有人在 issue 中提出但愿 Vite 内部支持在库模式导出声明文件,但 Vite 官方表示不但愿所以增长维护的负担和结构的复杂性。
所以在 Vite 开发中,咱们要想一些其余办法来生成声明文件。
本文介绍的生成方式仍是依赖一些现成的库,而后经过一些编程脚本以达到目的,毕竟从打包原理开始讲,那篇幅可能不太够。
安装生成声明文件的核心库:
yarn add ts-morph -D
其实 .vue
文件想要生成类型声明文件的核心点在于把 <script>
部分的内容提取出来进行解析,当明白了这个原理后,其实不少东西就很简单了。
新建 scripts/build-types.js
后开始编写咱们的脚本。
const path = require('path') const fs = require('fs') const glob = require('fast-glob') const { Project } = require('ts-morph') const { parse, compileScript } = require('@vue/compiler-sfc') let index = 1 main() async function main() { // 这部份内容具体能够查阅 ts-morph 的文档 // 这里仅须要知道这是用来处理 ts 文件并生成类型声明文件便可 const project = new Project({ compilerOptions: { declaration: true, emitDeclarationOnly: true, noEmitOnError: true, allowJs: true, // 若是想兼容 js 语法须要加上 outDir: 'dist' // 能够设置自定义的打包文件夹,如 'types' }, tsConfigFilePath: path.resolve(__dirname, '../tsconfig.json'), skipAddingFilesFromTsConfig: true }) // 获取 src 下的 .vue 和 .ts 文件 const files = await glob(['src/**/*.ts', 'src/**/*.vue']) const sourceFiles = [] await Promise.all( files.map(async file => { if (/\.vue$/.test(file)) { // 对于 vue 文件,借助 @vue/compiler-sfc 的 parse 进行解析 const sfc = parse(await fs.promises.readFile(file, 'utf-8')) // 提取出 script 中的内容 const { script, scriptSetup } = sfc.descriptor if (script || scriptSetup) { let content = '' let isTs = false if (script && script.content) { content += script.content if (script.lang === 'ts') isTs = true } if (scriptSetup) { const compiled = compileScript(sfc.descriptor, { id: `${index++}` }) content += compiled.content if (scriptSetup.lang === 'ts') isTs = true } sourceFiles.push( // 建立一个同路径的同名 ts/js 的映射文件 project.createSourceFile(file + (isTs ? '.ts' : '.js'), content) ) } } else { // 若是是 ts 文件则直接添加便可 sourceFiles.push(project.addSourceFileAtPath(file)) } }) ) const diagnostics = project.getPreEmitDiagnostics() // 输出解析过程当中的错误信息 console.log(project.formatDiagnosticsWithColorAndContext(diagnostics)) project.emitToMemory() // 随后将解析完的文件写道打包路径 for (const sourceFile of sourceFiles) { const emitOutput = sourceFile.getEmitOutput() for (const outputFile of emitOutput.getOutputFiles()) { const filePath = outputFile.getFilePath() await fs.promises.mkdir(path.dirname(filePath), { recursive: true }) await fs.promises.writeFile(filePath, outputFile.getText(), 'utf8') } } }
在 package.json
添加一个打包类型文件的命令:
{ "scripts": { "build:types": "node scripts/build-types.js" } }
在项目根路径下,执行如下命令:
yarn run build:types
大功告成,能够看到 dist
目录下已经有了 index.d.ts
、App.vue.d.ts
等类型声明文件。
其实,在 Vite 打包的过程当中,@vitejs/plugin-vue
插件会将 .vue
文件编译并拆分红三个部分,包括模版,脚本和样式;咱们只须要拿到编译后的脚本部分的内容,经过上面的方法,甚至不须要本身编译文件,就能够轻松生成类型声明文件。
开始撸代码:
// plugins/dts.ts import { resolve, dirname } from 'path' import fs from 'fs/promises' import { createFilter } from '@rollup/pluginutils' import { normalizePath } from 'vite' import { Project } from 'ts-morph' import type { Plugin } from 'vite' import type { SourceFile } from 'ts-morph' export default (): Plugin => { const filter = createFilter(['**/*.vue', '**/*.ts'], 'node_modules/**') const sourceFiles: SourceFile[] = [] const project = new Project({ compilerOptions: { declaration: true, emitDeclarationOnly: true, noEmitOnError: true, allowJs: true, // 若是想兼容 js 语法须要加上 outDir: 'dist' // 能够设置自定义的打包文件夹,如 'types' }, tsConfigFilePath: resolve(__dirname, '../tsconfig.json'), skipAddingFilesFromTsConfig: true }) const root = process.cwd() return { name: 'gen-dts', apply: 'build', enforce: 'post', transform(code, id) { if (!code || !filter(id)) return null // 拆分后的文件 id 具备一些特征,能够用正则的方式来捕获 if (/\.vue(\?.*type=script.*)$/.test(id)) { const filePath = resolve(root, normalizePath(id.split('?')[0])) sourceFiles.push( project.createSourceFile(filePath + (/lang.ts/.test(id) ? '.ts' : '.js'), code) ) } else if (/\.ts$/.test(id)) { const filePath = resolve(root, normalizePath(id)) sourceFiles.push(project.addSourceFileAtPath(filePath)) } }, async generateBundle() { const diagnostics = project.getPreEmitDiagnostics() // 输出解析过程当中的错误信息 console.log(project.formatDiagnosticsWithColorAndContext(diagnostics)) project.emitToMemory() // 随后将解析完的文件写道打包路径 for (const sourceFile of sourceFiles) { const emitOutput = sourceFile.getEmitOutput() for (const outputFile of emitOutput.getOutputFiles()) { const filePath = outputFile.getFilePath() await fs.mkdir(dirname(filePath), { recursive: true }) await fs.writeFile(filePath, outputFile.getText(), 'utf8') } } } } }
如此轻松,一个简单的 dts 插件就完成了。
咱们只须要在 vite.config.ts
中引用插件:
import { resolve } from 'path' import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import dts from './dts' export default defineConfig({ build: { lib: { entry: resolve(__dirname, 'src/index.ts'), name: 'Plugin', formats: ['es'], fileName: 'index' } }, plugins: [vue(), dts()] })
而后执行原来的命令,就能够看到打包和生成类型声明文件一条龙了:
yarn run build
固然了,上述插件只包含了最基础的功能,笔者本身写了一个涵盖功能更加普遍的插件,源码已放在 github 上,同时 npm 也进行了发布。
yarn add vite-plugin-dts -D
欢迎你们进行使用和反馈,若是以为这对你有所帮助,还请点赞、收藏,和赏一颗⭐。
插件地址:https://github.com/qmhc/vite-...
最后偷偷安利一下本身的组件库:vexip-ui