先使用vite 搭建基础版本脚手架这里不做讲解、由于vite初始化什么都没有安装、我采用了自己改造过的脚手架

详情见GitHub 内置vue-router、commitlint、stylelint、eslint、prettier、unplugin-vue-components、unplugin-auto-import、lint-staged、sentry、pinia、husky...基本上可以满足团队日常开发需求。

😅言归正传、生成目录大概是这样的

 1├── README.md
 2├── commitlint.config.cjs
 3├── index.html
 4├── package.json
 5├── pnpm-lock.yaml
 6├── public
 7│   ├── version.txt
 8│   └── vite.svg
 9├── src
10│   ├── App.vue
11│   ├── api
12│   │   └── index.ts
13│   ├── assets
14│   ├── auto-imports.d.ts
15│   ├── components
16│   ├── components.d.ts
17│   ├── global.d.ts
18│   ├── hooks
19│   ├── intercept.ts
20│   ├── main.ts
21│   ├── router
22│   ├── store
23│   ├── utils
24│   ├── views
25│   ├── vite-env.d.ts
26│   └── workers
27│       ├── versionCheckWorker.ts
28│       └── worker.ts
29├── tsconfig.json
30├── tsconfig.node.json
31└── vite.config.ts

一系列的修改操作

  1. 修改srcpackages,删除views目录
  2. 修改alias把所有和src相关的别名路径修改为components、修改目录为vite.config.tstsconfig.json
 1
 2 alias: [
 3        {
 4            find: "@",
 5            replacement: resolve(__dirname, "../packages"),
 6        },
 7    ],
 1
 2{
 3...
 4 "paths": {
 5            "@/*": [
 6                "packages/*"
 7            ],
 8          },
 9...
10}
  1. 删除不需要用到的文件夹 store
  2. 修改index.html中script标签中src路径为 /packages/main.ts
  3. packages 下新建文件夹theme-chalk 后续用来存放样式

修改后的目录结构

 1├── README.md
 2├── commitlint.config.cjs
 3├── index.html
 4├── package.json
 5├── pnpm-lock.yaml
 6├── public
 7│   ├── version.txt
 8│   └── vite.svg
 9├── packages
10│   ├── App.vue
11│   ├── api
12│   ├── assets
13│   ├── auto-imports.d.ts
14│   ├── components
15│   ├── components.d.ts
16│   ├── global.d.ts
17│   ├── hooks
18│   ├── intercept.ts
19│   ├── main.ts
20│   ├── router
21│   ├── store
22│   ├── utils
23│   └── vite-env.d.ts
24├── tsconfig.json
25├── tsconfig.node.json
26└── vite.config.ts

后续就可以在components文件夹下开发组件了,查看组件的话直接在router上配置路径即可

敲黑板:每个组件必须设置name,不然后续全局注册会有问题 components整体格式如下

 1├── components
 2    ├── button
 3    │   ├──Button.vue
 4    │   └──index.ts
 5    └──index.ts

相关代码如下

 1<!-- Button.vue -->
 2<template>
 3    <div class="bq-button">
 4        <span>-测试按钮-6</span>
 5    </div>
 6</template>
 7
 8<script setup lang="ts">
 9defineOptions({
10    name: "BqButton",
11});
12</script>
13
14<style lang="scss" scoped>
15@import "@theme-chalk/button.scss";
16</style>
 1
 2import Button from "./Button.vue";
 3import { withInstall } from "../../utils/tool";
 4export const BqButton = withInstall(Button);
 5export default BqButton;

withInstall相关方法如下、主要是为了全局注册

 1import type { App } from "vue";
 2export const withInstall = <T extends Component>(comp: T) => {
 3    (comp as Record<string, unknown>).install = (app: App) => {
 4        const compName = comp.name;
 5        if (!compName) return;
 6        app.component(compName, comp);
 7    };
 8    return comp;
 9};
 1
 2export * from "./button";

这时候我们的组件基本就写好了,查看组件只需要在router下配置路径即可

 1 {
 2        path: "/",
 3        name: "button",
 4        meta: {
 5            title: "login",
 6        },
 7        component: () => import( "@/components/button/Button.vue"),
 8    },

先给大家看配置、再给大家讲为什么

 1
 2import { defineConfig } from "vite";
 3import vue from "@vitejs/plugin-vue";
 4import { resolve } from "path";
 5import { pluginsConfig, resolveConfig } from "./scripts/preview";
 6import dts from "vite-plugin-dts";
 7export default defineConfig(() => {
 8    return {
 9        build: {
10            outDir: "build",
11            cssCodeSplit: true,
12            rollupOptions: {
13                external: ["three", "@ant-design/icons-vue", "ant-design-vue", "unplugin-vue-components", "unplugin-auto-import", "vue"],
14                output: [
15                    {
16                        format: "es",
17                        entryFileNames: "[name].js",
18                        exports: "named",
19                        name: "BqDesign",
20                        dir: "./build/dist",
21                    },
22                    {
23                        format: "es",
24                        entryFileNames: "[name].js",
25                        exports: "named",
26                        preserveModules: true,
27                        preserveModulesRoot: "packages",
28                        dir: "./build/es",
29                    },
30                    {
31                        format: "cjs",
32                        entryFileNames: "[name].js",
33                        exports: "named",
34                        preserveModules: true,
35                        preserveModulesRoot: "packages",
36                        dir: "./build/lib",
37                    },
38                ],
39            },
40            lib: {
41                entry: resolve(__dirname, "./packages/index.ts"),
42                name: "BqDesign",
43                fileName: (format) => `bq-design.${format}.js`,
44                formats: ["es", "cjs"],
45            },
46        },
47        plugins: [
48            vue(),
49            dts({
50                tsconfigPath: "./tsconfig.prod.json",
51                outDir: "build/lib",
52            }),
53            dts({
54                tsconfigPath: "./tsconfig.prod.json",
55                outDir: "build/es",
56            }),
57            ...pluginsConfig,
58        ],
59        resolve: resolveConfig,
60    };
61});
62

一点一点给大家捋一下为什么这么写

1、为什么output需要这样配置

首先按照官网给的示例配置并执行

打包后,有问题吗?没问题。但就是有点奇怪,因为我们打包后结构是这样的

 1└── dist
 2    ├── index.js
 3    └── style.css

毕竟我们都是见过世面的🤓,为什么我们打包出来的和ant-design-vueElementPlus差距这么大, 其实小编在做这个事情的时候也看了这些优秀的开源组件库、毕竟学习的过程就是先模仿创造

大概看了一下 ElementPlus 是基于vite开发的,ant-design-vue还是webpack、那我们先看一下ElementPlus打包后的结构大概如下

 1└── build
 2    ├── dist
 3    ├── es
 4    ├── lib
 5    ├── README.md
 6    └── package.json

很明显他输出了三个包(说明我们在output中也需要配置3个输出文件)、首先es文件夹用来兼容esm语法、lib文件夹兼容commentJsdist也是esm语法他的css是打包在一个文件里的主要是为了全局引入css,配置好后大概是这个样子

 1output: [
 2                    {
 3                        format: "es",
 4                        entryFileNames: "[name].js",
 5                        dir: "./build/dist",
 6                    },
 7                    {
 8                        format: "es",
 9                        entryFileNames: "[name].js",
10                        dir: "./build/es",
11                    },
12                    {
13                        format: "cjs",
14                        entryFileNames: "[name].js",
15                        dir: "./build/lib",
16                    },
17                ],

之后我们执行打包脚步 pnpm run build 不报错的情况下打包出来应该已经是有三个文件了,但好像jscss还是在一起,怎么办?当然是看文档了🤑

原来如此、配置后执行打包命令,果然没问题

2、为什么cssCodeSplit是要改为true

因为在vitelib模式下cssCodeSplit默认是false,其实官方有说明,可只在英文文档做了说明🥲

没注意到的打包后所有组件css都在一个文件、后续就没有办法实现我们的按需引入了

3、如何打包出ts类型标注

使用pnpm下载 vite-plugin-dts 该插件

pnpm add vite-plugin-dts -D

由于我们在libes库里都需要打包ts,所以需要配置两个dts,代码如下

 1  dts({
 2                tsconfigPath: "./tsconfig.prod.json",
 3                outDir: "build/lib",
 4            }),
 5            dts({
 6                tsconfigPath: "./tsconfig.prod.json",
 7                outDir: "build/es",
 8            }),

tsconfigPath,需要单独引入一个新的tsconfig配置、主要是因为我们打包的include和exclude配置和实际开发中还是有一定区别。并且include配置有问题会导致打包出的类型没有放在实际文件夹下。

新建了一个tsconfig.prod.json文件,代码如下

 1{
 2    "extends": "./tsconfig.json",
 3    "include": [
 4        "packages/**/*.vue",
 5        "packages/**/*.d.ts",
 6        "packages/**/*.ts",
 7    ],
 8    "exclude": [
 9        "./packages/main.ts",
10        "node_modules",
11        "./packages/router/*"
12    ]
13}

走到这里其实我们的组件已经小有所成了

一个好的组件离不开一个优秀的文档、这里我推荐大家使用VitePress,相对于市面上其他的文档生成工具VitePress拥有着强大的生态环境和相对稳定的版本,文档地址,根据文档操作,之后会在最外层目录下生成一个docs文件,里面就可以快乐的写我们的文档了,这里就不做演示了。

 1const BqDesignResolver = () => {
 2    return {
 3        type: "component" as const,
 4        resolve: (name) => {
 5            if (name.startsWith("Bq")) {
 6                const pathName = name.slice(2).toLowerCase();
 7                return {
 8                    importName: name,
 9                    from: "bq-design",
10                    path: `bq-design/es/components/${pathName}/index.js`,
11                    sideEffects: `bq-design/es/components/${pathName}/${name.slice(2)}.css`,
12                };
13            }
14        },
15    };
16};

更改名称为自己组件库即可

在vite中严格意义上是不需要手动导入,因为Vite提供了基于 ES Module 的开箱即用的Tree Shaking 功能,但有一种情况就是我开发的组件库引入了第三方包,比如threeJs,但在实际运用中,我只引用了我的Button按钮,会报错,因为我们的导出模块是有关联的,实际这么引用

上述情况使用Vite一定要在 optimizeDeps.exclude 添加相应的包如bq-design,因为预加载发现缺少依赖会报错

 1import {Button} from 'bq-design'

开发环境下是会导出全部的bq-design(生产环境并不会哦),默认情况按需引入需要这样导入

 1import BqButton from "bq-design/es/components/button";

但这样还存在着无法引入样式的问题,所以我们需要自己开发一个vite插件进行转换,代码如下

 1export default function importPlugin() {
 2    const regStr = /(?<!\/\/.*|\/\*[\s\S]*?\*\/\s*)import\s*{\s*([^{}]+)\s*}\s*from\s*['"]bq-design['"]/g;
 3    return {
 4        name: "vite-plugin-import",
 5        enforce: "pre",
 6        transform: (code: string, id: string) => {
 7            if (id.endsWith(".vue")) {
 8                const str = code.replaceAll(regStr, (match, imports) => {
 9                    const list = imports.split(",");
10                    const newPath: string[] = [];
11                    list.forEach((item: string) => {
12                        item = item.trim();
13                        const name = item.slice(2).charAt(0).toLowerCase() + item.slice(3);
14                        const str = `import ${item.trim()} from 'bq-design/es/components/${name.trim()}';
15                        import 'bq-design/es/components/${name.trim()}/${item.trim().slice(2)}.css'`;
16                        newPath.push(str);
17                    });
18                    return newPath.join(";");
19                });
20                return str;
21            }
22            return code;
23        },
24    };
25}

Webpack用户可以使用 babel-plugin-import 进行处理同样这也是ant-design-vue的处理方式

这是小编自己搭建的组件库地址

biuat.ibaiqiu.com/bq-design/

相关github地址

github.com/Js-Man-H5/b…

个人笔记记录 2021 ~ 2025