likes
comments
collection
share

Vite + TypeScript 搭建 Vue3 组件库

作者站长头像
站长
· 阅读数 60

本文主要是提供一个思路,中间可能有不详细的地方,可评论区留言,我尽量修缮完整。 本人也不是什么高手,只是希望留下一些记录,让后边的人少走弯路。

Vue.js 3.X 有很多现成的组件库,例如 Element Plus 和 Ant Design Vue 等都可以拿来直接用。为什么还要自己搭建 Vue.js 3.X 的组件库?

每个企业或多或少都会有一些定制化的需求,并不能完全直接使用组件库的组件完成;这些定制化的需求,可能会应用到企业中的所有相同类型的系统当中,把这部分公共的组件抽离出来就更加有必要了。

实现 TypeScript 类型支持的 Vue.js 3.X 组件库思路

  • *.vue 文件导出并打包成 esmcjs 等结构的 *.js 文件;
  • 生成 *.d.ts 类型文件,让组件库支持 TypeScript 类型;
  • *.css 样式文件与 *.vue 文件分离,有利于实现组件和样式的按需加载。

初始化项目

使用自己喜欢的包管理工具,初始化一个 Vite + Vue.js 3.X + TypeScript 的项目。

下方是使用 npm create vite@latest 初始化的一个项目。

$ npm create vite@latest

Need to install the following packages:
  create-vite@4.3.2
Ok to proceed? (y)
√ Project name: ... demo-components
√ Select a framework: » Vue
√ Select a variant: » TypeScript

Scaffolding project in code\demo-components...

Done. Now run:

  cd demo-components
  npm install
  npm run dev

创建完成后,执行 npm install 安装相关依赖。

目录改造

为了让此组件库更加简洁,把默认没用的文件夹和文件,该删的删,该挪位置的挪位置,当你理解这整个项目的运行流程之后,可自行修改这个目录结构,现在先照着我的这个目录来整。

  1. 删除 publicsrc/assetssrc/components 文件夹;
  2. 删除 src/style.css 文件;
  3. 移动 src/App.vuesrc/main.tssrc/vite-env.d.ts 文件到项目根目录;
  4. 清空 App.vue 文件里的代码,随便写入一个 *.vue 模板即可;
<template>
  <div>Example</div>
</template>

<script setup lang="ts"></script>

<style scoped></style>
  1. (可选操作) App.vue 改名为 example.vue
  2. 修改 main.ts 文件,把没用的引用删除,错误引用调整正确即可;
import { createApp } from 'vue'
import Example from './example.vue'

createApp(Example).mount('#app')
  1. 修改 index.html 文件的 main.ts 引入路径,改为引入当前目录下的 main.ts (有疑问回到第 3 步);
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + Vue + TS</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/main.ts"></script>
  </body>
</html>
  1. 解决 main.ts 文件出现的找不到 *.vue 模块类型声明,需要 tsconfig.json 文件的 include 参数,把 main.tsexample.vue 文件也添加进去 (改完后,编辑器还是报类型错误,重启一下编辑器即可)。
{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "module": "ESNext",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "preserve",

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.d.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "main.ts",
    "example.vue"
  ],
  "references": [{ "path": "./tsconfig.node.json" }]
}

经过上面的改造后,项目目录结构如下:

demo-components
├─.gitignore 
├─example.vue 
├─index.html 
├─main.ts 
├─package-lock.json 
├─package.json 
├─README.md 
├─src 
├─tsconfig.json 
├─tsconfig.node.json 
├─vite-env.d.ts 
└─vite.config.ts 

编写组件

组件库怎么能少了组件,这里只写两个简单组件,无任何效果,只是为了测试后面的组件库打包。在 src 目录下添加 Button 和 Input 组件。

demo-components
├─.gitignore 
├─example.vue 
├─index.html 
├─main.ts 
├─package-lock.json 
├─package.json 
├─README.md 
├─src 
│ ├─button 
│ │ ├─index.ts 
│ │ ├─src 
│ │ │ └─button.vue 
│ │ └─style 
│ │   └─index.less 
│ ├─input 
│ │ ├─index.ts 
│ │ ├─src 
│ │ │ └─input.vue 
│ │ └─style 
│ │   └─index.less 
│ ├─index.less 
│ ├─index.ts 
│ └─install.ts 
├─tsconfig.json 
├─tsconfig.node.json 
├─vite-env.d.ts 
└─vite.config.ts 

每个组件单独放在一个文件夹内,文件夹内都需要有 srcstyle 目录加一个 index.ts 文件,style 文件夹内必须有一个 index.less 文件(本文用 less 做 css 预处理语言,可自行更换其他 css 预处理器,更换后,需要修改构建 css 的代码逻辑,此处先跟着用 less)。

拿 Button 举例:

  • src/button/src 主要放 *.vue 文件,里边的文件名无特殊规定;
<!-- src/button/src/button.vue -->
<template>
  <button class="v-button">
    <slot></slot>
  </button>
</template>

<script setup lang="ts">
defineOptions({
  name: 'VButton',
})
</script>
  • src/button/style 放组件的样式文件,必须要有一个入口文件(此处为 index.less),用于样式构建;
/* src/button/style/index.less */
.v-button {
  color: green;
}
  • src/button/index.ts 文件为此组件的入口文件,组件按需引入时使用此入口;
import { withInstall } from '../install'

import Button from './src/button.vue'

const VButton = withInstall(Button)

export {
  Button as VButton
}

export default VButton

以后新增的组件,都按上方规则进行创建即可。

src 目录下,除了两个组件文件夹,还多了 3 个文件,这 3 个文件的作用如下:

  • src/index.ts 是整个组件库的总入口,使用两种导出模式,export {} 用于 import { VButton } from 'demo-components'export default {} 用于导出一个自动注册所有组件的 Vue.js 插件,可以通过 Vue.use() 进行全局注册所有组件;
import { App, Plugin } from 'vue';

import Button, { VButton } from './button';
import Input, { VInput } from './input';

const components = [Button, Input];

export { VButton, VInput };

export default {
  install: (app: App) => {
    components.forEach((component) => {
      app.use(component);
    });
  },
} as Plugin;
  • src/index.less 为所有组件样式的汇总,当全局注册所有组件时,可以只引入此样式文件;
@import './button/style/index.less';
@import './input/style/index.less';
  • src/install.ts 导出了一个辅助方法 withInstall() 用于每个组件的 index.ts 辅助组件添加 install 属性;
import type { Plugin } from 'vue'

export type SFCWithInstall<T> = T & Plugin

export const withInstall = <T extends Record<string, any>>(
  main: T
) => {
  ;(main as SFCWithInstall<T>).install = (app): void => {
    for (const comp of [main]) {
      app.component(comp.name, comp)
    }
  }
  return main as SFCWithInstall<T>
}

调试/预览组件

例子组件编写完成,要怎样预览我们的组件呢?非常简单,只要在 example.vue 引入我们的组件和样式即可,如下:

<template>
  <div>
    <div>Example</div>
    <v-button>Click</v-button>
    <v-input></v-input>
  </div>
</template>

<script setup lang="ts">
import { VButton, VInput } from './src';
</script>

<style lang="less" scoped>
@import './src/index.less';
</style>

然后执行 npm run dev 启动项目,肯定是跑不起来的,因为我们没有安装 less ,安装一下:

$ npm i less -D

安装完成后,再启动项目,打开 http://127.0.0.1:5173/ 就能看到这两个组件的效果。

Vite + TypeScript 搭建 Vue3 组件库

根据 class 名字,可以确定,这就是我们刚写的两个组件。

虽然现在可以正常预览,但是还是有一些其他隐藏问题没有解决,我们编写的组件中,在 setup 语法中使用了 defineOptions 这个宏来设置组件的名字,这个宏只有在 Vue.js 3.3+ 版本才适用,为了解决这个问题,我们需要使用 VueMacros 这个 Vite 插件,这个插件可以让我们可以提前使用 defineOptions 这个宏。

$ npm i unplugin-vue-macros -D

安装完成后,修改 vite.config.ts 配置:

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import VueMacros from 'unplugin-vue-macros/dist/rollup';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    VueMacros({
      setupComponent: false,
      setupSFC: false,
      plugins: {
        vue: vue({
          isProduction: true,
        }),
      },
    }),
  ],
});

如果需要用到 JSX 语法编写组件,需要安装 @vitejs/plugin-vue-jsx 插件:

npm i @vitejs/plugin-vue-jsx -D

修改 vite.config.ts 配置如下即可:

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import VueMacros from 'unplugin-vue-macros/dist/rollup';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    VueMacros({
      setupComponent: false,
      setupSFC: false,
      plugins: {
        vue: vue({
          isProduction: true,
        }),
        vueJsx: vueJsx(),
      },
    }),
  ],
});

编译组件库

编译组件库需要借助 Rollup 的 JavaScript API 。

有人这时候会有疑问,我们不是已经有 Vite 了吗?Vite 也有一个库模式,为什么还要用 Rollup ?

因为我们不仅仅是要编译一个入口文件,需要将主的 src/index.ts 和所有组件内的 src/*-components/index.ts 也都单独编译成独立组件。

在项目根目录下新建一个 scripts 目录,用来存放我们的编译脚本。首先实现一个将 *.vue 文件编译成 esmcjs 结构的脚本,就叫 build-module.ts

demo-components
├─.gitignore 
├─example.vue 
├─index.html 
├─main.ts 
├─package-lock.json 
├─package.json 
├─README.md 
├─scripts 
│ ├─build-module.ts 
│ └─utils.ts 
├─src 
│ ├─button 
│ │ ├─index.ts 
│ │ ├─src 
│ │ │ └─button.vue 
│ │ └─style 
│ │   └─index.less 
│ ├─index.less 
│ ├─index.ts 
│ ├─input 
│ │ ├─index.ts 
│ │ ├─src 
│ │ │ └─input.vue 
│ │ └─style 
│ │   └─index.less 
│ └─install.ts 
├─tsconfig.json 
├─tsconfig.node.json 
├─vite-env.d.ts 
└─vite.config.ts 

scripts/utils.ts 用于存放一些公共函数,编译脚本会用到。

import fs from 'node:fs';
import path from 'node:path';

// 获取根目录
export const resolvePath = (...args: string[]) => {
  return path.resolve(__dirname, '..', ...args);
};

// 写入文件
export const wirteFile = (file: string, text: string) => {
  const dir = path.dirname(file);
  if (!(fs.existsSync(dir) && fs.statSync(dir).isDirectory())) {
    fs.mkdirSync(dir, { recursive: true });
  }
  fs.writeFileSync(file, text);
};

如果出现找不到相应模块的类型声明,需要安装 @types/node :

npm i @types/node -D

src/build-module.ts 文件如下:

import fs from 'node:fs';
import { rollup } from 'rollup';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import VueMacros from 'unplugin-vue-macros/dist/rollup';
import { nodeResolve } from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import esbuild from 'rollup-plugin-esbuild';
import glob from 'fast-glob';
import type { OutputOptions } from 'rollup';
import { resolvePath } from './utils';

const getExternal = async () => {
  const pkgPath = resolvePath('package.json');
  const manifest = require(pkgPath) as any;
  const {
    dependencies = {},
    devDependencies = {},
  } = manifest;
  const deps: string[] = [
    ...new Set([
      ...Object.keys(dependencies),
      ...Object.keys(devDependencies),
    ]),
  ];
  return (id: string) => {
    if (id.endsWith('.less')) {
      return true;
    }
    return deps.some((pkg) => id === pkg || id.startsWith(`${pkg}/`));
  };
};

const build = async () => {
  const pkgDistPath = resolvePath('dist');
  if (fs.existsSync(pkgDistPath) && fs.statSync(pkgDistPath).isDirectory()) {
    fs.rmSync(pkgDistPath, { recursive: true });
  }

  const input = await glob(['**/*.{js,jsx,ts,tsx,vue}', '!node_modules'], {
    cwd: resolvePath('src'),
    absolute: true,
    onlyFiles: true,
  });

  const bundle = await rollup({
    input,
    plugins: [
      VueMacros({
        setupComponent: false,
        setupSFC: false,
        plugins: {
          vue: vue({
            isProduction: true,
          }),
          vueJsx: vueJsx(),
        },
      }),
      nodeResolve({
        extensions: ['.mjs', '.js', '.json', '.ts'],
      }),
      commonjs(),
      esbuild({
        sourceMap: true,
        target: 'es2015',
        loaders: {
          '.vue': 'ts',
        },
      }),
    ],
    external: await getExternal(),
    treeshake: false,
  });

  const options: OutputOptions[] = [
    // CommonJS 模块格式的编译
    {
      format: 'cjs',
      dir: resolvePath('dist', 'cjs'),
      exports: 'named',
      preserveModules: true,
      preserveModulesRoot: resolvePath('src'),
      sourcemap: true,
      entryFileNames: '[name].cjs',
    },
    // ES Module 模块格式的编译
    {
      format: 'esm',
      dir: resolvePath('dist', 'esm'),
      exports: undefined,
      preserveModules: true,
      preserveModulesRoot: resolvePath('src'),
      sourcemap: true,
      entryFileNames: '[name].mjs',
    },
  ];
  return Promise.all(options.map((option) => bundle.write(option)));
};

console.log('[JS] 开始编译所有子模块···');
await build();
console.log('[JS] 编译所有子模块成功!');

getExternal() 方法根据 package.json 生成 Rollup 的 external 配置。

build() 方法就是编译的主要逻辑,大概就是扫描 src 文件夹下的文件,然后生成 Rollup 的编译配置,最后根据配置进行编译,具体流程可自己 Debuger 了解。

此脚本需要安装以下依赖:

$ npm i @vitejs/plugin-vue-jsx unplugin-vue-macros @rollup/plugin-node-resolve @rollup/plugin-commonjs rollup-plugin-esbuild fast-glob -D

因为脚本是 TypeScript 编写的,并不能直接在 Node 环境执行。需要借助 vite-node 这个库:

$ npm i vite-node -D

测试一下这个脚本是否可以成功编译组件库:

$ npx vite-node .\scripts\build-module.ts

执行成功后,会多出一个 dist 文件夹,里边已经打包出了 esmcjs 格式的组件库。

此时编译的产物,其实已经可以直接使用,只是缺少 TypeScript 的类型提示,这样我们就得实现生成 *.d.ts 的类型编译脚本。

scripts 目录下新增一个 build-dts.ts 脚本:

import process from 'node:process';
import path from 'node:path';
import fs from 'node:fs';
import * as vueCompiler from 'vue/compiler-sfc';
import glob from 'fast-glob';
import { Project } from 'ts-morph';
import type { CompilerOptions, SourceFile } from 'ts-morph';
import { resolvePath } from './utils';

const tsWebBuildConfigPath = resolvePath('tsconfig.build.json');

// 检查项目的类型是否正确
function checkPackageType(project: Project) {
  const diagnostics = project.getPreEmitDiagnostics();
  if (diagnostics.length > 0) {
    console.error(project.formatDiagnosticsWithColorAndContext(diagnostics));
    const err = new Error('TypeScript 类型描述文件构建失败!');
    console.error(err);
    throw err;
  }
}

// 将*.d.ts文件复制到指定格式模块目录里
async function copyDts() {
  const dtsPaths = await glob(['**/*.d.ts'], {
    cwd: resolvePath('dist', 'types', 'src'),
    absolute: false,
    onlyFiles: true,
  });

  dtsPaths.forEach((dts: string) => {
    const dtsPath = resolvePath(
      'dist',
      'types',
      'src',
      dts
    );
    const cjsPath = resolvePath('dist', 'cjs', dts);
    const esmPath = resolvePath('dist', 'esm', dts);
    const content = fs.readFileSync(dtsPath, { encoding: 'utf8' });
    fs.writeFileSync(cjsPath, content);
    fs.writeFileSync(esmPath, content);
  });
}

// 添加源文件到项目里
async function addSourceFiles(project: Project, pkgSrcDir: string) {
  project.addSourceFileAtPath(resolvePath('vite-env.d.ts'));

  const globSourceFile = '**/*.{js?(x),ts?(x),vue}';
  const filePaths = await glob([globSourceFile], {
    cwd: pkgSrcDir,
    absolute: true,
    onlyFiles: true,
  });

  const sourceFiles: SourceFile[] = [];
  await Promise.all([
    ...filePaths.map(async (file) => {
      if (file.endsWith('.vue')) {
        const content = fs.readFileSync(file, { encoding: 'utf8' });
        const hasTsNoCheck = content.includes('@ts-nocheck');

        const sfc = vueCompiler.parse(content);
        const { script, scriptSetup } = sfc.descriptor;
        if (script || scriptSetup) {
          let content =
            (hasTsNoCheck ? '// @ts-nocheck\n' : '') + (script?.content ?? '');

          if (scriptSetup) {
            const compiled = vueCompiler.compileScript(sfc.descriptor, {
              id: 'temp',
            });
            content += compiled.content;
          }

          const lang = scriptSetup?.lang || script?.lang || 'js';
          const sourceFile = project.createSourceFile(
            `${path.relative(process.cwd(), file)}.${lang}`,
            content
          );
          sourceFiles.push(sourceFile);
        }
      } else {
        const sourceFile = project.addSourceFileAtPath(file);
        sourceFiles.push(sourceFile);
      }
    }),
  ]);

  return sourceFiles;
}

// 生产 Typescript 类型描述文件
async function generateTypesDefinitions(
  pkgDir: string,
  pkgSrcDir: string,
  outDir: string
) {
  const compilerOptions: CompilerOptions = {
    emitDeclarationOnly: true,
    outDir,
  };
  const project = new Project({
    compilerOptions,
    tsConfigFilePath: tsWebBuildConfigPath,
  });

  const sourceFiles = await addSourceFiles(project, pkgSrcDir);
  checkPackageType(project);
  await project.emit({
    emitOnlyDtsFiles: true,
  });

  const tasks = sourceFiles.map(async (sourceFile) => {
    const relativePath = path.relative(pkgDir, sourceFile.getFilePath());

    const emitOutput = sourceFile.getEmitOutput();
    const emitFiles = emitOutput.getOutputFiles();
    if (emitFiles.length === 0) {
      throw new Error(`异常文件: ${relativePath}`);
    }

    const subTasks = emitFiles.map(async (outputFile) => {
      const filepath = outputFile.getFilePath();
      fs.mkdirSync(path.dirname(filepath), {
        recursive: true,
      });
    });

    await Promise.all(subTasks);
  });
  await Promise.all(tasks);
}

async function build() {
  const outDir = resolvePath('dist', 'types');
  const pkgDir = resolvePath();
  const pkgSrcDir = resolvePath('src');
  await generateTypesDefinitions(pkgDir, pkgSrcDir, outDir);
  await copyDts();
}

console.log('[Dts] 开始编译 d.ts 文件···');
await build();
console.log('[Dts] 编译 d.ts 文件成功!');

生成 *.d.ts 文件主要借助了 vue/compiler-sfcts-morph 这两个依赖,具体什么用处,可以查看他们的官方文档,毕竟细说的话,就又是长篇大论了。

缺少了 ts-morph 依赖,使用下方命令安装:

$ npm i ts-morph -D

接着在项目根目录下,添加一个新的 TypeScript 配置文件 tsconfig.build.json ,这个配置文件的名字需要跟 scripts/build-dts.ts 脚本中的 tsWebBuildConfigPath 变量对应上。

tsconfig.build.json 配置如下:

{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "module": "ESNext",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "NodeNext",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": false,
    "jsx": "preserve",

    "types": ["unplugin-vue-macros/macros-global"],
    "esModuleInterop": true,
    "composite": true
  },
  "include": ["src", "vite-env.d.ts"],
  "exclude": [
    "node_modules",
    "**/dist",
    "**/*.md"
  ]
}

这份配置文件是基于一开始 Vite 初始化创建的 tsconfig.json 进行修改的:

  • 去除了 compilerOptions.strictcompilerOptions.noUnusedLocalscompilerOptions.noUnusedParameterscompilerOptions.noFallthroughCasesInSwitch 配置;
  • 添加了 compilerOptions.typescompilerOptions.esModuleInteropcompilerOptions.composite 配置;
  • 修改了 compilerOptions.noEmit 配置。

执行下方命令,测试是否能编译出 dist/types 目录:

$ npx vite-node .\scripts\build-dts.ts

编译成功后,就剩最后一步,编译样式文件。跟上面一样,写一个专门将 *.less 文件处理成 *.css 文件的脚本,并添加到 dist 目录下。

scripts/build-css.ts 代码如下:

import fs from 'node:fs';
import path from 'node:path';
import glob from 'fast-glob';
import less from 'less';
import { resolvePath, wirteFile } from './utils';

function compileLess(file: string): Promise<string> {
  return new Promise((resolve, reject) => {
    const content = fs.readFileSync(file, { encoding: 'utf8' });
    less
      .render(content, {
        paths: [path.dirname(file)],
        filename: file,
        plugins: [],
        javascriptEnabled: true,
      })
      .then((result) => {
        resolve(result.css);
      })
      .catch((err) => {
        reject(err);
      });
  });
}

async function build() {
  const pkgDir = resolvePath('src');
  const filePaths = await glob(['**/style/index.less'], {
    cwd: pkgDir,
  });
  const indexLessFilePath = resolvePath('src', 'index.less');
  if (fs.existsSync(indexLessFilePath)) {
    filePaths.push('index.less');
  }
  for (let i = 0; i < filePaths.length; i++) {
    const file = filePaths[i];
    const absoluteFilePath = resolvePath('src', file);
    const cssContent = await compileLess(absoluteFilePath);
    const cssPath = resolvePath(
      'dist',
      'css',
      file.replace(/.less$/, '.css')
    );
    wirteFile(cssPath, cssContent);
  }
}
console.log('[CSS] 开始编译 Less 文件···');
await build();
console.log('[CSS] 编译 Less 成功!');

这个脚本就比较简单了,也不需要额外安装其他依赖,编写完成后,直接执行下方命令测试是否能编译成功:

$ npx vite-node .\scripts\build-css.ts

以上 3 个脚本都正常运行结束的话,编译组件库部分就完工了。

配置 package.json

package.json 主要是处理发布到 npm 的一些配置,无论是私有仓库还是公有仓库,只有正确配置了,当别的项目安装了我们的组件库,才能正常的使用。

配置如下:

  • private 设置为 false 才能发布使用;
  • 设置 main 入口,用于 CommonJS 环境下的入口文件;
  • 设置 module 入口,用于 ES Module 环境下的入口文件;
  • 设置 types 是为了能让编辑器顺利找到 TypeScript 类型文件;
  • exports 设置了一些其他导入路径,具体看 package.json 官方配置文档了解;
  • files 用于发布 npm 包时,该上传的文件。
{
  "name": "demo-components",
  "private": false,
  "version": "0.0.0",
  "type": "module",
  "main": "dist/cjs/index.cjs",
  "module": "dist/esm/index.mjs",
  "types": "dist/esm/index.d.ts",
  "exports": {
    ".": {
      "require": "./dist/cjs/index.cjs",
      "import": "./dist/esm/index.mjs",
      "types": "./dist/esm/index.d.ts"
    },
    "./esm/*": {
      "import": "./dist/esm/*/index.mjs",
      "types": "./dist/esm/*/index.d.ts"
    },
    "./cjs/*": {
      "require": "./dist/cjs/*/index.cjs",
      "types": "./dist/cjs/*/index.d.ts"
    },
    "./css/*": "./dist/css/*"
  },
  "files": [
    "dist",
    "package.json"
  ],
  "scripts": {
    "dev": "vite",
    "build": "yarn build:components && yarn build:dts && yarn build:css",
    "preview": "vite preview",
    "build:components": "vite-node ./scripts/build-module.ts",
    "build:dts": "vite-node ./scripts/build-dts.ts",
    "build:css": "vite-node ./scripts/build-css.ts"
  },
  "dependencies": {
    "vue": "^3.2.47"
  },
  "devDependencies": {
    "@rollup/plugin-commonjs": "^25.0.0",
    "@rollup/plugin-node-resolve": "^15.1.0",
    "@types/node": "^20.2.5",
    "@vitejs/plugin-vue": "^4.1.0",
    "@vitejs/plugin-vue-jsx": "^3.0.1",
    "fast-glob": "^3.2.12",
    "less": "^4.1.3",
    "rollup-plugin-esbuild": "^5.0.0",
    "ts-morph": "^18.0.0",
    "typescript": "^5.0.2",
    "unplugin-vue-macros": "^2.2.1",
    "vite": "^4.3.9",
    "vite-node": "^0.31.4",
    "vue-tsc": "^1.4.2"
  }
}

以上配置加以搜索引擎配合,应该可以更加加深印象。

测试编译后的组件库

当构建完成,你的项目目录下有 dist 文件夹和配置完 package.json 文件,需要使用的话,只要像下面这样修改一下 example.vue 文件即可:

<template>
  <div>
    <div>Example</div>
    <v-button>Click</v-button>
    <v-input></v-input>
  </div>
</template>

<script setup lang="ts">
import { VButton, VInput } from '.';
</script>

<style lang="less" scoped>
@import 'dist/css/index.css';
</style>

查看 VScode 的引入提示可以知道,我们使用的就是 dist 文件夹下的 js 文件:

Vite + TypeScript 搭建 Vue3 组件库

总结

  1. 使用 Vite 实现组件调试预览;
  2. 使用 Rollup 的 JavaScript API 编写编译脚本;

本文主要是提供一些思路,对实现组件库有个大概了解,就当作是引路文章,少走弯路。

转载自:https://juejin.cn/post/7241492331952717885
评论
请登录