With the continuous expansion and maturation of Vue3 ecology, Vue3 has stepped into the production project from the initial stage. Along with the development scaffolding update, the new Vite scaffolding, esbuild based use of go language performance advantages, compared to Webpack has not an order of magnitude of performance advantages, packaging based on Rollup expansion, inherit the advantages of lightweight and bright plug-in Api.

What, you don’t know? You should hold on tight.

Vue3 official Chinese document

Vite official Chinese document

Blah, blah, blah, blah, blah, blah, blah, blah.

Create a project

This article focuses on generating type declaration files, so the project creation section is just a brief description.

Build a simple project quickly using official templates:

yarn create @vitejs/app my-vue-app --template vue-ts
Copy the code

Then rename SRC /main.ts to SRC /index.ts and modify its contents:

export { default as App } from './App.vue'
Copy the code

Never mind the name App, let’s just assume we wrote a component and exported it as a plug-in.

Then adjust the vite.config.ts configuration to library mode packaging:

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

At this point, a simple plug-in project is complete.

Generate a type declaration file

Rollup-plugin-typescript2 is used to generate.d.ts declaration files from source code.

Vue files can’t be parsed. vue files can’t be parsed. vue files can’t be parsed. vue files can’t be parsed. Vite + Vue3 doesn’t support typescript.

Of course, some people in the issue want Vite to support the export of declaration files when the library mode is packaged, but the Vite official said that they do not want to increase the maintenance burden and structural complexity.

So in Vite development, we had to come up with other ways to generate declaration files.

The generation method described in this article relies on some existing libraries, and then some programming scripts to achieve the purpose, after all, there is not enough space to start with packaging principles.

Install the library used to build the declaration file:

yarn add ts-morph -D
Copy the code

The core of the.vue file’s intention to generate a type declaration file is to extract the contents of the

Start writing our script after creating new 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() {
  // Please refer to the ts-MORph documentation for details about this section
  // This is used to process the ts file and generate the type declaration file
  const project = new Project({
    compilerOptions: {
      declaration: true.emitDeclarationOnly: true.noEmitOnError: true.allowJs: true.// If you want to be compatible with JS syntax, you need to add it
      outDir: 'dist' // You can set custom packing folders such as 'types'
    },
    tsConfigFilePath: path.resolve(__dirname, '.. /tsconfig.json'),
    skipAddingFilesFromTsConfig: true
  })

  // Get.vue and.ts files under SRC
  const files = await glob(['src/**/*.ts'.'src/**/*.vue'])
  const sourceFiles = []

  await Promise.all(
    files.map(async file => {
      if (/\.vue$/.test(file)) {
        // For vue files, parse with @vue/ compiler-SFC
        const sfc = parse(await fs.promises.readFile(file, 'utf-8'))
        // Extract the contents of 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(
            // Create a ts/js mapping file with the same path
            project.createSourceFile(file + (isTs ? '.ts' : '.js'), content)
          )
        }
      } else {
        // If it is a ts file, add it directly
        sourceFiles.push(project.addSourceFileAtPath(file))
      }
    })
  )

  const diagnostics = project.getPreEmitDiagnostics()

  // Output error information during parsing
  console.log(project.formatDiagnosticsWithColorAndContext(diagnostics))

  project.emitToMemory()

  // Then write the parsed file to the package path
  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')}}}Copy the code

Add a command to package type files in package.json:

{
  "scripts": {
    "build:types": "node scripts/build-types.js"}}Copy the code

In the project root directory, run the following command:

yarn run build:types
Copy the code

You can see that the dist directory now has index.d.ts, app.vue.d. ts and other type declaration files.

Vite plug-in

In Vite packaging, the @vitejs/plugin-vue plugin compiles and splits the.vue file into three parts, including templates, scripts and styles. We just need to take the content of the compiled part of the script, and through the above method, we can easily generate the type declaration file without even compiling the file ourselves.

Create plugins/dts.ts and start with the following code:

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.// If you want to be compatible with JS syntax, you need to add it
      outDir: 'dist' // You can set custom packing folders such as 'types'
    },
    tsConfigFilePath: resolve(__dirname, '.. /tsconfig.json'),
    skipAddingFilesFromTsConfig: true
  })

  let root: string
  
  return {
    name: 'gen-dts'.apply: 'build'.enforce: 'pre'.// You need to be in pre to get the ts script properly
    configResolved(config) {
      root = config.root
    },
    transform(code, id) {
      if(! code || ! filter(id))return null

      // The split file ID has some characteristics that can be captured in a regular manner
      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()

      // Output error information during parsing
      console.log(project.formatDiagnosticsWithColorAndContext(diagnostics))

      project.emitToMemory()

      // Then write the parsed file to the package path
      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')}}}}}Copy the code

So easy, a simple DTS plug-in is done.

We just need to reference the plugin in viet.config.ts:

import { resolve } from 'path'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import dts from './plugins/dts'

export default defineConfig({
  build: {
    lib: {
      entry: resolve(__dirname, 'src/index.ts'),
      name: 'Plugin'.formats: ['es'].fileName: 'index'}},plugins: [vue(), dts()]
})
Copy the code

Then execute the original command and you can see the packaging and generation of the type declaration file in one go:

yarn run build
Copy the code

Write in the last

Of course, the above plug-in only contains the most basic functions, the author wrote a plug-in that covers more extensive functions, the source code has been put on Github, and NPM has also been released.

yarn add vite-plugin-dts -D
Copy the code

Welcome to use and feedback, if you think this is helpful, please also like, favorites, and a reward ⭐.

Plugin address: github.com/qmhc/vite-p…

Finally, I secretly amway my Vue3 component library, which is mainly used for the company’s project: VEXIP-UI

In this paper, starting from segmentfault.com/a/119000004…