Catalyst

Vue3 组件库组织学习(二)

下篇是:文档、添加类型定义和打包。

文档

  • storybook,但目前 storybook 对 Vue(+ts) 的支持还不够完美。
cd rect-ui
npx sb init
yarn add --dev vitepress
  • vue-styleguidist 从 Vue 组件&注释中生成文档,也能利用 vue-docgen-cli 抽取的数据用在其他文档生成器中(比如 vuepress)。

新建项目

vue-docgen-cli + vitepress 示例:

# 新建 docs 文件夹,在文件夹里
yarn add -D vue-docgen-cli vitepress

docs里新建 docgen.config.js, 按 设置 填写。

const path = require("path");

module.exports = {
  componentsRoot: "../packages/components", // 开始搜寻组件的文件夹
  components: "**/index.vue", // 需要生成文档的文件的 glob
  outDir: "docs/components", // 存放生成的文档的地方
  getDestFile: (file, config) =>
    path.join(config.outDir, file).replace(/\.vue$/, ".md"), // 确认生成的文档的名字
};

运行抽取:

yarn vue-docgen

生成的 md 文档就会放到 docs/docs/components 下了。

编写文档

  • Vue Styleguidist 的方式写注释
  • 在组件文件夹下的 Readme.md(或者getDocFileName里规定的名字)

导入组件示例:

# Docs

This is a .md using a custom component

<CustomComponent />

## More docs

...

<script setup>
import CustomComponent from '../components/CustomComponent.vue'
</script>

将文件作为代码块渲染:

<<< ../filepath

别名

由于使用 vitedocs 文件夹下(.vitepress 同级)放vite.config.js即可套用设定,如设置别名:

import { defineConfig } from "vite";
const path = require("path");

export default defineConfig({
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "../../packages/components"), // <script> 中用
    },
  },
});

然后在文档中就可以:

import Basic from "@/button/demo/basic.vue";
这里和 <<< 方法导入的别名不通用,在<<<方法中,@ 指根文件夹(.vitepress放置的文件夹,这里是docs/docs)。修改方法是修改 srcDir,但目前无法设置在根目录外的文件夹。

主题

导入 css 到 vitepress 方法:

创建主题:新建文件 ./vitepress/theme/index.js

import Theme from "vitepress/theme";
import "../../../../packages/color.css"; // ← 要导入的 css

export default { ...Theme };

类型定义

如果只是想让 ts 不报错的话,写个粗略的定义就可以了。

// packages/components/index.d.ts

import {Component} from "vue";
declare module "rect-ui";

export RButton:Component;
export function install(...args: any[]): void;

详细的定义:

declare module "rect-ui" {
  import { DefineComponent } from "vue";
  export const RButton: DefineComponent<
    {
      flat: { type: Boolean; default: false };
      color: { type: String; default: string };
      ghost: { type: Boolean; default: false };
      disabled: { type: Boolean; default: false };
    },
    {} // 后面还有很多,不写了,大概是这样……
  >;
}

既然都用 TS 了,我们可以利用现代化的工具 → vite-plugin-dts

安装:yarn add -D vite-plugin-dts

设置 vite.config.ts

// vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import dts from "vite-plugin-dts";
import { resolve } from "path";

export default defineConfig({
  plugins: [dts(), vue()],
  build: {
    lib: {
      entry: resolve(__dirname, "packages/index.ts"),
      name: "rect-ui",
      fileName: (format) => `rect-ui.${format}.js`,
    },
    rollupOptions: {
      // make sure to externalize deps that shouldn't be bundled
      // into your library
      external: ["vue"],
      output: {
        // Provide global variables to use in the UMD build
        // for externalized deps
        globals: {
          vue: "Vue",
        },
      },
    },
  },
});

按照我们目前的目录结构,设置一下tsconfig.json以免检查到demotests文件:

// tsconfig.json

{
  "compilerOptions": {
    "esModuleInterop": true,
    "target": "ES2015",
    "moduleResolution": "node"
  },
  "include": ["packages/**/**/*"],
  "exclude": ["packages/components/**/demo", "packages/components/**/tests"]
}

build 之后目标文件夹就会生成 .d.ts文件了。

由于插件自带的打包没给路径设置,单独使用 API Extractor 把分散的.d.ts打包成一个。因为不用它生成文档,所以关闭了相关功能。

api-extractor.json
{
  "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
  "mainEntryPointFilePath": "dist/index.d.ts",
  "bundledPackages": [],
  "compiler": { "skipLibCheck": false },
  "apiReport": {
    "enabled": false
  },
  "docModel": {
    "enabled": false
  },
  "dtsRollup": {
    "enabled": true,
    "omitTrimmingComments": true,
    "untrimmedFilePath": "./index.d.ts"
  },
  "tsdocMetadata": {
    "enabled": false
  },
  "messages": {
    "compilerMessageReporting": {
      "default": {
        "logLevel": "error"
      }
    },
    "extractorMessageReporting": {
      "default": {
        "logLevel": "none"
      }
    },
    "tsdocMessageReporting": {
      "default": {
        "logLevel": "none"
      }
    }
  }
}
说起来那些大组件库的发布版都是一个一个的小包,每个文件夹里都有自己的 js、css、dts,可能是为了兼容旧版本的“按需引用”吧。

编译打包

vite 的文档 - Library Mode

然后运行vite build打包,不设置outDir的话默认在 dist 文件夹里。

给编译好的包准备 package.json其它内容 自己填),和原来的 package.json 合并。

如果想让按需引用起效,设置 sideEffects
{
  "name": "rect-ui",
  "version": "0.0.1",
  "peerDependencies": {
    "vue": "^3.0.4"
  },
  /* 中略 */
  "files": ["dist/", "index.d.ts"],
  "main": "./dist/rect-ui.umd.js",
  "module": "./dist/rect-ui.es.js",
  "exports": {
    ".": {
      "import": "./dist/rect-ui.es.js",
      "require": "./dist/rect-ui.umd.js"
    },
    "./dist/style.css": "./dist/style.css"
  },
  "sideEffects": ["*.css"]
}

npm pack 打包后的文件仅会留下 file 里的文件(还有 readme.md等一定会留下的文件)。

使用

新建一个 vite 项目,本地安装编译出来的包。

npm init vite-app lib-test -- --template vue-ts
cd lib-test
yarn add <npm pack 打出的包>

基础测试

修改 main.ts 引入自己的库和 css。

// main.ts
import { createApp } from "vue";
import App from "./App.vue";
import RectUI from "rect-ui";
import "rect-ui/dist/style.css";

createApp(App).use(RectUI).mount("#app");

修改 App.vue

<template>
  <r-button>显示按钮!</r-button>
</template>

<script lang="ts">
import { defineComponent } from "vue";

export default defineComponent({
  name: "App",
});
</script>

查看能否被正确渲染。

按需引用测试

将代码引用改按需安装:

import { createApp } from "vue";
import App from "./App.vue";
import "rect-ui/dist/style.css";
import { Button } from "rect-ui";

createApp(App).use(Button).mount("#app");

全部引入打包:

vite v2.9.5 building for production...
✓ 12 modules transformed.
dist/index.html                  0.39 KiB
dist/assets/index.76546b54.css   15.17 KiB / gzip: 3.05 KiB
dist/assets/index.4c0e342d.js    60.03 KiB / gzip: 23.34 KiB

部分引入打包:

vite v2.9.5 building for production...
✓ 12 modules transformed.
dist/index.html                  0.39 KiB
dist/assets/index.76546b54.css   15.17 KiB / gzip: 3.05 KiB
dist/assets/index.1e459a5a.js    52.18 KiB / gzip: 21.04 KiB

在生成的 js 文件里没有搜索到其他组件的信息。

参考资料