关于前端:Vite-开发插件并生成-dts-类型声明文件

7次阅读

共计 5285 个字符,预计需要花费 14 分钟才能阅读完成。

随着 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 = 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.tsApp.vue.d.ts 等类型申明文件。

Vite 插件

其实,在 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

正文完
 0