随着 Vue3 生态的一直扩大与日渐成熟,Vue3 已从最开始的尝鲜阶段步入到投入生产我的项目中。随之而来的还有开发脚手架的更新换代,全新的 Vite 脚手架,基于 esbuild 利用 go 语言的性能劣势,相较 Webpack 有着不在一个量级的性能劣势,打包方面基于 Rollup 拓展,继承了轻量化和清朗的插件 Api 的长处。

什么,你还不晓得?你该放松了。

Vue3 官网中文文档

Vite 官网中文文档

废话不多说,开始进入的正题。

创立我的项目

本文重点讲述如何生成类型申明文件,因而我的项目创立局部只一些简略形容。

通过官网提供的模版疾速搭建一个简略的我的项目:

yarn create @vitejs/app my-vue-app --template vue-ts

随后更名 src/main.tssrc/index.ts 并批改其内容:

export { default as App } from './App.vue'
不要在意 App 这个名字,咱们只是假如咱们写了一个组件,并且作为插件导出。

接着调整 vite.config.ts 的配置为库模式打包:

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()]})

至此,一个简略的插件我的项目就实现了。

生成类型申明文件

在应用 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 = 1main()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.tsApp.vue.d.ts 等类型申明文件。

Vite 插件

其实,在 Vite 打包的过程中,@vitejs/plugin-vue 插件会将 .vue 文件编译并拆分成三个局部,包含模版,脚本和款式;咱们只须要拿到编译后的脚本局部的内容,通过下面的办法,甚至不须要本人编译文件,就能够轻松生成类型申明文件。

开始撸代码:

// plugins/dts.tsimport { 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