轻松掌握基于pnpm构建单体仓库monorepo模式

文/晨风不可依米 (2025.07。02)

重要概念

简单理解

  • 单体仓库通过工作区配置文件(pnpm-workspace.yaml)的字段packages记录所有模块包,又通过hoisthoistWorkspacePackageshoistPattern等字段进行符号连接绑定,解决字段packages所定义模块包的寻址问题。
  • 单体仓库相当于中枢系统,集中管理所有模块包的共同资源,并共享给所有模块包。也就是说,模块包不需要安装与单体仓库相同的依赖包,可在模块包直接使用。
  • 模块包相当于独立库,继承了单体仓库所共享出来的资源内容,并完成实现特定需求的功能工具库组件库业务逻辑库基于特定平台开发的功能库)。模块包一般都是功能明确、可独立发行的模块库(方便维护、细粒控制)。

架构优点

  • 所有相关的工程项目形成子包管理,独立打包发行;可实现跨仓使用。
  • 技术栈高度统一,工程模块功能分类明确,减少重复代码实现。
  • 统一遵守一套代码质量(eslintprettier)规范标准。
  • 工程模块的依赖、版本、构建等统一管理。
  • 采用自动化脚本统一部署流程。

工程模块的划分

  • 基建模块:针对普遍性的工具函数构建模块(请求文件数据转换与加密)。
  • 组件模块:自研框剪或基于特定框架(vuereact)所开发的组件库。
  • 业务模块:针对特定业务流程构建的功能模块(授权认证支付流程)。
  • 环境模块:针对特定环境所开发的功能模块(iosandroidbrowser)。

具体搭建流程(案例:搭建具有 monorepo 架构的组件库 ymui

技术栈选型

  • 构建工具:vite.
  • 框剪选择:vue@3.5.13
  • 代码质量规范:eslintprettier
  • pnpm 常用命令:
    • 单体仓库中安装依赖:pnpm install -w [-S | -D] <repository>
    • 单词仓库指定模块包安装依赖:pnpm --filter <package> add [-S -D] <repository | workspace>
    • 单词仓库指定模块包运行脚本:pnpm --filter <package> run <command>

构建流程

初始化单体仓库(ymui

使用终端命令在合适的位置创建单体仓库(ymui),并初始化包管理配置(package.json)和工作区配置(pnpm-workspace.yaml)等文件。

## 创建并进入创建单体仓库(`ymui`)根目录
mkdir ymui && cd ymui

## 初始化包管理配置
pnpm init

## 初始化工作区配置
touch pnpm-workspace.yaml


## 初始化组件库示例
cd demo
pnpm create vite (选择 vue、typescript;查看安装依赖,并交给单体仓库去安装)
cd ..


## 由于 ymui 基于 vue@3.5.13 框架开发 + 样式处理 Sass,因此需要安装必要依赖,以及相应的开发依赖。
## 生产依赖
pnpm install -wS vue@3.5.13
## 开发依赖
pnpm install -wD sass@^1.89.2

## 开发依赖(依赖版本与初始化组件库示例的依赖保持一致;此处我就不带版本号了)
pnpm install -wD @vitejs/plugin-vue @vue/tsconfig typescript vue-tsc vite 

模块划分

定义工作区配置(pnpm-workspace.yaml);即声明所有工程模块的路径(可指定、正则模糊匹配)。

packages:
  - docs ## 组件库文档服务
  - demo ## 组件库示例服务
  - packages/* ## 组件库的各个工程模块

规范模块包结构目录

为单体仓库(ymui)下,创建所有工程模块并初始化包配置文件,目录结构的最终效果如下:

ymui(单体仓库)
├── package.json(单体仓库的包配置文件)
├── pnpm-workspace.yaml(工作区配置文件)
│
├── tsconfig.json(单体仓库的配置,会引用  tsconfig.node.json、 tsconfig.app.json 等配置文件)
├── tsconfig.node.json(单体仓库的包配置文件)
├── tsconfig.app.json(单体仓库的包配置文件)
│
├──.gitignore(远程仓库推送的忽略项规则配置)
├── LICENSE(版权许可证类型,自行查找下载)
├── README.md(组件库主页介绍,自行创建)
│
├── docs(组件库文档服务;通过 vitepress 初始化项目;好像是这个)
├── demo(组件库示例服务;进入该目录,通过 pnpm create vite,选择 vue、typescript 即可)
│
├── packages(所有独立开发的工程模块的腹肌目录)
│      ├── global(组件库整体模块包)
│               ├── dist(打包自动生成构建产物)
│               ├── src(源码实现目录)
│                        ├── index.ts(功能模块的入口文件,通过依赖项导出其它工程模块)
│               ├── package.json(global 工程模块的包配置文件)
│               └── vite.config.ts(global 工程模块的构建配置文件)
│               
│      ├── theme(组件库主题模块包)
│               ├── dist(打包自动生成构建产物)
│               ├── src(源码实现目录)
│                        ├── modules(功能模块的具体实现)
│                                 ├── datatype.ts(数据类型工具函数)
│                        ├── index.ts(功能模块的入口文件,会引用目录 modules 里的模块并导出所有实现内容)
│               ├── package.json(theme 工程模块的包配置文件)
│               └── vite.config.ts(theme 工程模块的构建配置文件)
│               
│      ├── shared(组件库工具函数包)
│               ├── dist(打包自动生成构建产物)
│               ├── src(源码实现目录)
│                        ├── modules(功能模块的具体实现)
│                                 ├── dayjs.ts(第三方依赖包)
│                                 ├── datatype.ts(数据类型工具函数)
│                        ├── index.ts(功能模块的入口文件,会引用目录 modules 里的模块并导出所有实现内容)
│               ├── package.json(shared 工程模块的包配置文件)
│               └── vite.config.ts(shared 工程模块的构建配置文件)
│               
│      ├── button(组件库按钮组件包)
│               ├── dist(打包自动生成构建产物)
│               ├── src(源码实现目录)
│                        ├── modules(功能模块的具体实现)
│                                 ├── index.vue(默认基础组件)
│                                 ├── submit.vue(提交类型组件;可能封装多种类型 button 组件,此处举了个栗子)
│                        ├── index.ts(功能模块的入口文件,会引用目录 modules 里的模块并导出所有实现内容)
│               ├── package.json(button 工程模块的包配置文件)
│               └── vite.config.ts(button 工程模块的构建配置文件)
│ 
│      ├── 可自行封装更多的组件模块包
│
│──develop.md(开发笔记)
  1. 单体仓库(ymui)的包配置文件(package.json)内容,如下:
{
  "name": "ymui",
  "version": "0.0.1",
  "description": "Building Vue3 Component Library Based on Monorepo Architecture Pattern",
  "author": "chenfengbukeyimi",
  "license": "ISC",
  "packageManager": "pnpm@10.4.1",
  "keywords": [
    "ui",
    "vue",
    "library",
    "monorepo"
  ],
  "scripts": {
    "test": "echo welcome to use ymui"
  },
  "dependencies": {
    "vue": "^3.5.13"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^5.2.3",
    "@vue/tsconfig": "^0.7.0",
    "typescript": "~5.8.3",
    "vite": "^6.3.5",
    "vue-tsc": "^2.2.8"
  },
  "main": ""
}

  1. 实现组件库工具函数包(shared

特别提醒:开辟新的终端命令面板,并切换到ymui/packages/shared目录;组件库工具函数包(shared)的相关命令操作,都在该终端面板操作。

安装第三方依赖包(dayjs

pnpm add -S dayjs

对第三方依赖包的处理,如第三方依赖包(dayjs),详见文件ymui/packages/shared/src/modules/dayjs.ts内容,如下:

import dayjs from 'dayjs'

// 通过函数原封不动的将第三方依赖包内容返回;该函数约定以 use 开头来拼接第三方依赖包名。
function useDayJs () {
  return dayjs
}

对自定义封装工具函数的处理,如工具函数(datatype),详见文件ymui/packages/shared/src/modules/datatype.ts内容,如下:

// 类型判断
export function getDataType(value: any): string {
  return Object.prototype.toString.call(value).slice(8, -1);
}

export function isString(value: any): boolean {
  return getDataType(value) === "String";
}

// 其它更多类型判断函数

组件库工具函数包(shared)的入口文件,导出所有工具函数,详见文件ymui/packages/shared/src/index.ts内容,如下:

export * from "./modules/dayjs";
export * from "./modules/datatype";

// 导出其它更多自定义的工具函数

定义组件库工具函数包(shared)的构建配置文件,详见文件ymui/packages/shared/vite.config.ts内容,如下:

import { defineConfig } from "vite";

export default defineConfig({
  build: {
    minify: true,
    lib: {
      // 模块包的入口文件
      entry: "./src/index.ts",
      // 根据 fileName 转换为首字母大写驼峰法。
      name: "YmuiShared",
      // 默认生成 <fileName>.[mjs | umd].js 文件;可指定 formats 配置,本案例不用。
      fileName: "ymui-shared",
      // formats: ["es", "cjs", "umd"]
    },
    rollupOptions: {
      // 忽略打包外部依赖包,减少构建产物的体积
      external: ["dayjs"],
      output: {
        globals: {
          // 在特定环境下,可使用全局变量引用指定模块包;以 key 为模块包名,value 为全局变量名进行定义。
          dayjs: "dayjs",
        },
      },
    },
  },
});


定义组件库工具函数包(shared)的包管理配置文件,详见文件ymui/packages/shared/package.json内容,如下:

{
  // 字段 name 值,必须以 @<单体仓库名>/<模块包名> 命名。
  "name": "@ymui/shared",
  "version": "0.0.1",
  "description": "YmuiShared Tools",
  "author": "chenfengbukeyimi <chenfengbukeyimi@qq.com>",
  "license": "ISC",
  "packageManager": "pnpm@10.4.1",
  "keywords": [
    "vue",
    "tool",
    "library"
  ],
  // 模块包的脚本命令集
  "scripts": {
    // (基于 vite.config.ts 文件配置)打包构建产物命令
    "build": "vite build"
  },
  "dependencies": {
    "dayjs": "^1.11.13"
  },


  // 打包构建产物(模块包),指定默认引用入口配置(类型声明文件、CommonJs系统模块入口、ESModule系统模块入口)
  "types": "./dist/ymui-shared.d.ts",
  "main": "./dist/ymui-shared.umd.js",
  "module": "./dist/ymui-shared.mjs",
  "exports": {
    ".": {
      "types": "./dist/ymui-shared.d.ts",
      "import": "./dist/ymui-shared.mjs",
      "require": "./dist/ymui-shared.umd.js"
    }
  },


  // 是否运行发行模块包到 npmjs 官网
  "private": false,
  // 发行模块包到 npmjs 官网的资源内容
  "files": [
    "dist",
    "README.md"
  ],
  // 发行模块包命令的相关配置
  "publishConfig": {
    "access": "public"
  }
}

打包构建组件库工具函数包(shared

pnpm build

## 后续将统一在单体仓库下管理所有模块的相关脚本命令

  1. 实现组件库模块包(button

特别提醒:开辟新的终端命令面板,并切换到ymui/packages/button目录;组件库模块包(button)的相关命令操作,都在该终端面板操作。

为组件库模块包(button)安装相关依赖配置,命令执行如下:


## 由于组件库模块包(`button`)是基于`vue@^3.5.13`版本开发的,因此需要安装对等依赖;详见包管理配置 package.json 字段对象 peerDependencies。(一定保证安装到这个位置,否则增大打包体积
pnpm add --save-peer vue@^3.5.13

## 由于组件库模块包(`button`)经常会使用到单体仓库(ymui)里的工具函数包(shared),因此需要安装本地模块包。
### 本地模块包安装方式一(不推荐: link:../shared  直接引用本地工作区中解析模块的版本;本地模块包变动更改,影响输出模块产物的功能)
pnpm add ../packages/shared

### 本地模块包安装方式二(推荐:workspace:^ 解析本地工作区中解析模块的最新版本;仅使用稳定的最新版本)
操作实现:手动复制("@<单体仓库名>/<模块包名>": "workspace:^")括号里的内容到包管理配置 package.json 字段对象 peerDependencies 里;然后执行以下命令:
pnpm install

定义组件库模块包(button)具体实现逻辑,详见文件ymui/packages/button/src/modules/index.vue内容,如下:

<template>
  <section>Button</section>
</template>

<script setup lang="ts">
import { useDayJs, isString } from "@ymui/shared";
const DayJs = useDayJs();

// 此处可测试打包是否会 tree-shaking 成功(案例:仅使用工具函数模块包一个或两个函数分别打包测试)。
console.log(DayJs.now());
console.log(isString("123"));

interface ButtonProps {}
const props = withDefaults(defineProps<ButtonProps>(), {
  color: "primary", // 后续将通过主题包的字段配置值
  size: "md", // 后续将通过主题包的字段配置值
});
</script>

<style scoped lang="scss"></style>

组件库模块包(button)的入口文件,导出所有封装的组件模块,详见文件ymui/packages/button/src/index.ts内容,如下:

import Button from "./modules/index.vue";
// import Submit from "./modules/submit.vue";  举了个栗子

export {
  // Submit,
  Button as default
};

定义组件库模块包(button)的构建配置文件,详见文件ymui/packages/button/vite.config.ts内容,如下:

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";

export default defineConfig({
  plugins: [vue()],
  build: {
    // 遵循本案上述规范
    lib: {
      entry: "./src/index.ts",
      name: "YmuiButton",
      fileName: "ymui-button",
    },
    rollupOptions: {
      // 使用正则模式忽略本地模块包,以减少打包体积;若单独使用,则会提示用户自行安装相关依赖。
      external: [/@ymui.*/],
      output: {
        globals: {
          // 在特定环境下,可使用全局变量引用指定模块包;以 key 为模块包名,value 为全局变量名进行定义。
          "@ymui/shared": "YmuiShared",
        },
      },
    },
  },
});


定义组件库模块包(button)的包管理配置文件,详见文件ymui/packages/button/package.json内容,如下:

{
  "name": "@ymui/button",
  "version": "0.0.1",
  "description": "Ymui Button Component",
  "author": "chenfengbukeyimi <chenfengbukeyimi@qq.com>",
  "license": "ISC",
  "packageManager": "pnpm@10.4.1",
  "keywords": [
    "ui",
    "vue",
    "library"
  ],
  "scripts": {
    "build": "vite build"
  },
  "dependencies": {
    "@ymui/shared": "workspace:^"
  },
  "devDependencies": {},
  "peerDependencies": {
    "vue": "^3.5.13"
  },
  "types": "dist/index.d.ts",
  "main": "dist/ymui-button.umd.js",
  "module": "dist/ymui-button.mjs",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/ymui-button.mjs",
      "require": "./dist/ymui-button.umd.js"
    }
  },
  "private": false,
  "files": [
    "dist",
    "README.md"
  ],
  "publishConfig": {
    "access": "public"
  }
}


打包构建组件库模块包(button

pnpm build

## 后续将统一在单体仓库下管理所有模块的相关脚本命令

  1. 实现组件库整体包(global

特别提醒:开辟新的终端命令面板,并切换到ymui/packages/global目录;组件库整体包(global)的相关命令操作,都在该终端面板操作。

为组件库整体包(global)安装相关依赖配置,命令执行如下:

## 由于组件库整体包(global)是基于 vue@^3.5.13 版本开发的,因此需要安装对等依赖;
pnpm add --save-peer vue@^3.5.13

## 由于组件库整体包(`global`)是应用于全局导入的使用场景,具有所有本地模块包的功能;
采用手动配置安装(ymui/packages 目录下)本地所有模块包,具体详见包管理配置 package.json 内容字段 dependencies 的对象内容。

定义组件库整体包(global)的包管理配置文件,详见文件ymui/packages/global/package.json内容,如下:

{
  "name": "@ymui/global",
  "version": "0.0.1",
  "description": "Ymui Library",
  "author": "chenfengbukeyimi <chenfengbukeyimi@qq.com>",
  "license": "ISC",
  "packageManager": "pnpm@10.4.1",
  "keywords": [
    "ui",
    "vue",
    "library"
  ],
  "scripts": {
    "build": "vite build"
  },
  "dependencies": {
    "@ymui/shared": "workspace:^",
    "@ymui/button": "workspace:^",
    // 其它本地模块包(如主题包、语言包)
  },
  "devDependencies": {
     // 使用 pnpm add --save-peer vue@^3.5.13 可能会自动在这里生成,不影响
     "vue": "^3.5.13"
  },
  "peerDependencies": {
    // 作为外部依赖,减少打包构建体积,同时以用户使用版本为基准适配
    "vue": "^3.5.13"
  },
  "types": "dist/index.d.ts",
  "main": "dist/index.cjs.js",
  "module": "dist/index.esm.js",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.esm.js",
      "require": "./dist/index.cjs.js"
    }
  },
  "private": false,
  "files": [
    "dist",
    "README.md"
  ],
  "publishConfig": {
    "access": "public"
  }
}


组件库整体包(global)的入口文件,导出所有工具函数,详见文件ymui/packages/global/src/index.ts内容,如下:

export * from "@ymui/shared";
export * from "@ymui/button";
// 其它本地模块包(如主题包、语言包)

组件库整体包(global)的构建配置文件,详见文件ymui/packages/global/vite.config.ts内容,如下:

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";

export default defineConfig({
  plugins: [vue()],
  build: {
    lib: {
      entry: "./src/index.ts",
      name: "YmuiGlobal",
      fileName: "ymui-global",
    },
    rollupOptions: {
      external: ["vue"],
      output: {
        globals: {
          vue: "Vue",
        },
      },
    },
  },
});

打包构建组件库整体包(global

pnpm build

## 后续将统一在单体仓库下管理所有模块的相关脚本命令

  1. 实现组件库示例服务(demo

特别提醒:开辟新的终端命令面板,并切换到ymui/demo目录;组件库示例服务(demo)的相关命令操作,都在该终端面板操作。

初始化组件库示例服务(demo),执行命令,如下:

pnpm create vite .

## 特别提醒:终端面板里选择 Vue Typescript

## 安装项目依赖;因为要测试 组件库功能,需安装 @ymui/global 进行全局导入示例项目;由于是本地服务模块,采用手动配置 @ymui/global 模块包依赖(配置内容往下看)。

组件库示例服务(demo)的包管理配置文件,内容如下:

{
  "name": "@ymui/demo",
  "dependencies": {
    ... 省略内容
    "@ymui/global": "workspace:^"
  },
  // 其它配置已隐藏查看
}

安装包管理配置里的相关依赖,命令执行,如下:

pnpm install

编写测试案例,详见ymui/demo/src/App.vue文件,(测试结果眼见为实),内容如下:

<template>
  <section>App</section>
  <Button />
</template>

<script setup lang="ts">
// 引用组件库整体包(global)里的工具函数、button 组件
import { getDataType, Button } from "@ymui/global";
console.log(getDataType(new Date()));

interface AppProps {}
const props = defineProps<AppProps>();
</script>

<style scoped lang="scss"></style>

  1. 统一规范化tsconfig.json编译配置

特别提醒:请在单体仓库(ymui)根目录下操作配置。

定义通用基础场景编译配置(tsconfig.base.json

{
  "compilerOptions": {
    // 基本配置
    "rootDir": ".",
    "baseUrl": ".",
    "outDir": "dist",
    "lib": [],
    "types": [],
    "paths": {
      // 给(packages目录下的)组件模块包路径定义别名
      "@ymui/*": ["packages/*/src"]
    },

    // 编译配置
    "strict": true,
    "target": "es2022",
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    // ES 功能支持库(DOM、Promise、ESNext)

    // cjs 与 es 转换配置
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,

    // 产物配置
    "sourceMap": true,
    "skipLibCheck": true,
    "noUnusedLocals": true,
    "removeComments": true,
    "isolatedModules": true
  }
}

定义脚本命令场景编译配置(tsconfig.node.json

{
  // 继承 通用基础编译配置
   "include": [
    // 匹配到所有模块的 vite.config.ts 文件配置
    "**/*.config.*"
    // 用于单体仓库的脚本文件
    "scripts"
  ],
  "exclude": [
    // 排除第三方依赖,构建产物
    "**/dist",
    "**/node_modules"
  ],

  "compilerOptions": {
    "allowJs": true,
    // 允许被 references 引用
    "composite": true,
    // 命令脚本需要支持的功能库模块
    "lib": ["ESNext"],
    "types": ["node"]
  }
}

定义模块包通用场景编译配置(tsconfig.module.json

{
  // 继承通用基础编译配置
  "extends": "./tsconfig.base.json",
  // 源码实现目录,类型申明文件
  "include": ["packages/**/src"],
  "compilerOptions": {
    // 允许被 references 引用
    "composite": true,
    // 源码实现需要支持的功能库模块
    "types": ["node"],
    "lib": ["ESNext", "DOM", "DOM.Iterable"]
  }
}

聚合集成单体仓库(ymui)场景编译配置(tsconfig.json

{
  "compilerOptions": {
    "target": "es2022",
    "moduleResolution": "node",

    "isolatedModules": true,
    "useDefineForClassFields": true
  },
  "files": [],
  "references": [
    { "path": "./tsconfig.module.json" },
    {
      "path": "./tsconfig.node.json"
    }
  ]
}


为方便查看最终编译配置结果:,可执行以下命令:

npx tsc -p <编译配置文件> --showConfig

## 比如查看 脚本命令场景(tsconfig.node.json) 的最终编译配置结果;也可以尝试替换为 模块包通用场景(tsconfig.module.json)查看最终编译配置结果。

npx tsc -p tsconfig.node.json --showConfig

自动生成*.d.ts类型声明文件

在单体仓库(ymui)根目录下执行命令,如下:

## 参数详解
## -p 表示指定编译配置文件;本例只对 模块包 生成 *.d.ts 类型声明文件;以供模块包类型适配。
## --composite 编译配置文件是否为可被引用配置; 
## --declaration 生成 *.d.ts 类型声明文件(含 *.js 文件);
## --emitDeclarationOnly 只生成  *.d.ts 类型声明文件。

npx vue-tsc -p tsconfig.module.json  --composite false --declaration --emitDeclarationOnly

## 为简化脚本命令执行,只需要把该命令添加到单体仓库(ymui)的包管理配置文件(package.json)的字段(scripts)命令集对象里(dts:generate)。
## 脚本命令配置详见后续最终配置效果(往下看)。

vue-tsc生成的类型声明文件目录结构,详见单体仓库(ymui)根目录下的dist目录(可与模块包目录结构对比),如下图:


'类型声明文件目录结构与命令'

需要将生成的类型声明文件迁移到相应模块包的构建产物里。由于tsc不具有清空类型声明文件目录的功能,而且ts不能直接执行脚本命令。因此借用rimraftsx等功能库来实现(1. 清空旧的类型声明文件目录;2.构建打包类型声明文件目录和模块包源码产物;3. 类型声明文件目录迁移到相应模块包产物目录里)等流程命令脚本

## 安装脚本命令所需功能库依赖
pnpm add -wD tsx rimraf

编写命令脚本文件内容。在单体仓库(ymui)根目录下创建目录(scripts),用于存放脚本命令文件,并创建文件(dts-mv.tsx)实现类型声明文件迁移逻辑。

import { join } from "node:path";
import { readdir, cp } from "node:fs/promises";

function fromRoot(...paths: string[]): string {
  return join(__dirname, "..", ...paths);
}

// 通过 npx vue-tsc -p tsconfig.module.json --composite false --declaration --emitDeclarationOnly 命令所生成。
// 详见单体仓库根目录下 dist 目录,目录结构与源码库所有模块包的父级目录一致。
const MODULES_DTS_DIR = fromRoot("dist/packages");

// 源码库所有模块包的父级目录()
const SOURCES_DIR = fromRoot("packages");

// 定义单个模块包的源码目录和打包目录
const MODULE_DIST_DIR = "dist";
const MODULE_ENTRY_DIR = "src";

async function match() {
  const res = await readdir(MODULES_DTS_DIR, { withFileTypes: true });
  return res.filter((item) => item.isDirectory()).map((item) => item.name);
}

// 将目录 MODULES_DTS_DIR 里的module.d.ts 文件复制到对应模块的源码目录下
async function mvModuleDts(moduleName: string) {
  try {
    // DTS 产物目录位置
    const DtsSourceDir = join(MODULES_DTS_DIR, moduleName, MODULE_ENTRY_DIR);
    // 模块包产物目录位置
    const ModuleResolveDir = join(SOURCES_DIR, moduleName, MODULE_DIST_DIR);

    // 读取对应模块包的 DTS 产物目录位置
    const dtsFiles = await readdir(DtsSourceDir);

    // 复制 DTS 产物到模块包产物目录下
    function cpModuleDts() {
      return dtsFiles.map((file) => {
        const source = join(DtsSourceDir, file);
        const target = join(ModuleResolveDir, file);
        return cp(source, target, { force: true, recursive: true });
      });
    }

    await Promise.all(cpModuleDts());
    console.info(`${moduleName} dts move success.`);
  } catch (error) {
    console.error(`${moduleName} dts move failed.`);
  }
}

// 主函数运行入口
async function main() {
  const modules = await match();
  const tasks = modules.map(mvModuleDts);
  await Promise.all(tasks);
}

// 运行脚本命令主函数
main().catch((e) => {
  console.error(e);
  process.exit(1);
});

配置单体仓库(ymui)的包管理配置文件(package.json)的脚本命令字段集对象(scripts,内容如下:

// 单体仓库包管理配置文件
{

    "name": "ymui",

    // 已隐藏部分内容

    // 脚本命令字段集对象
    "scripts": {
      // 清除类型声明文件目录
      "dts:clean": "rimraf ./dist",
      
      // 类型声明文件迁移到模块包产物(dist)目录
      "dts:move": "tsx ./scripts/dts-mv.ts",
      
      // 构建类型声明文件(构建前清除旧的目录内容)
      "dts:generate": "pnpm run dts:clean && vue-tsc -p tsconfig.module.json --composite false --declaration --emitDeclarationOnly",
      
      // 打包构建组件库所有模块(优先构建独立模块包,最后才构建组件库整体包;因为构建组件库整体包,它需要依赖其它独立模块,所以有优先级打包构建顺序)
      "build:all": "pnpm  --filter '!global' run build && pnpm --filter global run build",

      // 打包构建组件库
      "build:ymui": "pnpm dts:generate && pnpm build:all && pnpm dts:move",

      // 启动测试示例服务(Vite 项目)
      "start:demo": "pnpm --filter @ymui/demo run dev"
  },
}


  1. 编辑器IDE支持配置

在使用VueTs项目中,根据官方推荐IDE支持配置,在单体仓库(ymui)根目录下创建IDE支持配置目录(.vscode),同时在新建目录里分别创建extensions.jsonsettings.json等配置文件

特别提醒:TypeScript 版本上使用 Volar 集成。默认情况下,Volar 将用于 TypeScript 5.0 及更高版本。

插件拓展配置文件(extensions.json)内容,如下:

{
  "recommendations": ["Vue.volar"]
}

编辑器工作区配置文件(settings.json.json)内容,如下:

{
  "typescript.tsdk": "../node_modules/typescript/lib"
}

  1. 统一代码规范化(eslint9prettierstylelint

特别提醒:终端命令在单体仓库(ymui)根目录下执行。

安装开发依赖,命令如下:


## 基础依赖(eslint、prettier):eslint @eslint/js typescript-eslint eslint-config-prettier
## 框架依赖(vue):globals eslint-plugin-vue
## 配置依赖(支持 *.ts 文件配置):jiti 

pnpm i -wD eslint @eslint/js typescript-eslint globals eslint-plugin-vue eslint-config-prettier jiti

## 同时在单体仓库(ymui)根目录下创建代码检查配置文件 eslint.config.ts
touch eslint.config.ts

定义代码规范化配置文件(eslint.config.ts;内容如下:

// @ts-check
// 参见官方支持配置(https://eslint.vuejs.org/user-guide/#usage)设置

import globals from "globals";
import eslint from "@eslint/js";

import TsESLint from "typescript-eslint";
import ESLintPluginVue from "eslint-plugin-vue";
import ESLintConfigPrettier from "eslint-config-prettier";

// eslint.config.mjs 配置文件内容应省略定义类型声明 ConfigArray。
type ConfigArray = ReturnType<typeof TsESLint.config>;

const YmuiESLintConfig: ConfigArray = TsESLint.config([
  { ignores: ["**/dist", "**/node_modules", "*.d.ts"] },
  {
    extends: [
      eslint.configs.recommended,
      ...TsESLint.configs.recommended,
      ...ESLintPluginVue.configs["flat/recommended"],
    ],
    languageOptions: {
      ecmaVersion: "latest",
      sourceType: "module",
      globals: globals.browser,
      parserOptions: {
        parser: TsESLint.parser,
      },
    },
    files: ["**/*.{ts,vue}"],
    rules: {
      // 组件(驼峰法)命名规则
      "vue/multi-word-component-names": "off",

      // 组件 props 类型检查
      "@typescript-eslint/no-empty-object-type": `off`,
    },
  },
  ESLintConfigPrettier,
]);

export default YmuiESLintConfig;


为什么要用 ts 来写配置文文件(eslint.config.ts)

  • eslint.config.mjs配置文件在导出(export default <配置内容>)配置内容是会报错(又或者在终端problems提示修复);报错内容如下:
## 特别提示
使用变量 YmuiESLintConfig 存储配置定义,是为了更好定位问题;(若直接使用 export default <配置定义>;你有可能以为缺少某个必填配置选项的定义)

## 报错提示
The inferred type of 'YmuiESLintConfig' cannot be named without a reference to 
'.pnpm/@typescript-eslint+utils@8.34.0_eslint@9.28.0_typescript@5.8.3/node_modules/@typescript-eslint/utils/dist/ts-eslint'. 
This is likely not portable. A type annotation is necessary.ts(2742)

## 解决问题
### 根据官方解释 ts(2742) 原因,类型声明没有正确导出或路径引用不正确。
### 因此采用手动修复该问题,在定义需要类型提供支持;所以我就用了 ts 来写配置,而它需要安装 jiti 该项依赖。


## 若在使用 ts 来定义配置内容,没有安装 jiti 依赖,则在执行代码检验命令(pnpm eslint .)时将提示报错,如下:
Error: The 'jiti' library is required for loading TypeScript configuration files. Make sure to install it.

## 根据报错提示,可以前往官方链接 https://eslint.org/docs/latest/use/configure/configuration-files#typescript-configuration-files 页面内容解决问题。

测试代码规范化配置,执行代码检查命令,命令如下:


pnpm eslint .

## 特别提示,为简化或统一管理命令集,需在单体仓库(ymui)的包管理配置(package.json)中的命令集对象(scripts)追加该命令定义。可直接复制下方内容。
## 复制内容:"lint:eslint": "eslint ."

根据终端执行pnpm eslint命令提示统一代码规范

  • 问题一:模块包(shared)的datatype文件定义函数参数的类型为any,将any修改为unknow
  • 问题二:模块包(demo)的App.vue文件定义interface AppProps {}为空对象;由于组件库模块包定义组件会默认定义props的类型,因此我们通过eslint.config.ts配置文件设置对应rules规则为off就行了。

配置并定义prettier规则,详见单体仓库(ymui)根目录下prettier.js配置文件,内容如下:



配置并定义stylelint规则

安装开发依赖,命令执行请在单体仓库(ymui)根目录下执行,命令如下:

## 由于项目采用 sass 样式处理模块,需安装相应依赖(stylelint-config-standard-scss);
## 详细配置可参见官网 https://stylelint.io/user-guide/get-started
pnpm i-wD stylelint stylelint-config-standard-scss

## 同时创建样式检查配置文件 stylelint.config.mjs
touch stylelint.config.mjs

定义样式检查配置文件(stylelint.config.mjs)规则,内容如下:

export default {
  extends: ["stylelint-config-standard-scss"],

  // 不对构建产物文件进行检查
  ignoreFiles: ["**/dist/**/*.css"],

  rules: {
    // 指定颜色函数的别名符号(rgb[a]、hsl[a])
    "color-function-alias-notation": "with-alpha",
    // 指定字母值的百分比或数字表示法(数字 0.5, 百分比 50%)。
    "alpha-value-notation": ["percentage", { exceptProperties: ["opacity"] }],
    // 指定十六进制颜色的短或长表示法(短命名法 #fff,长命名法 #ffffff[ab])
    "color-hex-length": "long",
    // 颜色函数采用旧式语法(rgba(0, 0, 0, 1));即数值之间需要用逗号隔开
    "color-function-notation": "legacy",
    // css 样式规则值使用小写命名
    "value-keyword-case": [
      "lower",
      {
        ignoreProperties: [
          // 控制文本的渲染方式,非标准样式规则(css3 没有该样式属性)
          "text-rendering",
        ],
      },
    ],
  },
};


测试代码规范化配置,执行代码检查命令,命令如下:


npx stylelint "**/*.{css,scss}"

## 特别提示,为简化或统一管理命令集,需在单体仓库(ymui)的包管理配置(package.json)中的命令集对象(scripts)追加该命令定义。可直接复制下方内容。
## 复制内容:"lint:styleinit": "stylelint **/*.{css,scss}"

修改编辑器IDE支持配置

插件拓展配置文件(extensions.json)内容,最终内容如下:

{
  "recommendations": [
    "Vue.volar",
    "esbenp.prettier-vscode",
    "dbaeumer.vscode-eslint",
    "stylelint.vscode-stylelint"
  ]
}

编辑器工作区配置文件(settings.json.json)内容,最终内容如下:

{
  // typescript.tsdk 支持
  "typescript.tsdk": "../node_modules/typescript/lib",

  // 禁用格式化
  "editor.formatOnSave": false,
  "editor.formatOnPaste": false,

  // 不同文件的格式化支持和自动保存配置
  "[vue]": {
    "editor.defaultFormatter": "Vue.volar"
  },

  "[json]": {
    "editor.formatOnSave": true
  },
  "[jsonc]": {
    "editor.formatOnSave": true
  },
  "[yaml]": {
    "editor.formatOnSave": true
  },

  // 保存时自动修复 eslint、styleint 错误
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "always",
    "source.fixAll.stylelint": "always"
  }
}


  1. 统一仓库日志(git)提交规范

特别提醒:请在单体仓库(ymui)根目录下操作配置。

安装开发依赖,命令如下:

## 开发依赖
### huksy:处理 git 提交过程所触发的钩子函数 。
### lint-staged:负责代码检查。常用于执行 husky 的 pre-commit 钩子函数。
### @commitlint/{config-conventional,cli} :负责弹出仓库日志提交规范的终端面板。
### cz-customizable:自定义仓库日志提交规范的终端面板提示配置。

## 特别提示:本例版本 husky@^9.1.7、lint-staged@^16.1.0、@commitlint/cli@^19.8.1、@commitlint/config-conventional@^19.8.1;操作命令有部分变化!!!

pnpm i -wD husky lint-staged @commitlint/{config-conventional,cli}  cz-customizable

初始化.husky相关配置

## 初始化 .husky 配置目录内容;(执行命令与老版本 npx husky install 不同)
npx husky init

## 注册 pre-commit 钩子脚本文件
echo `pnpm lint-staged` > .husky/pre-commit

## 注册commit-msg 钩子脚本文件
echo 'npx --no -- commitlint --edit \' > .husky/commit-msg

配置仓库日志提交相关配置,详见本体仓库(ymui)的包管理配置文件(package.json)内容,如下:

{
  "name": "ymui",
  // 隐藏部分内容

  // 新增命令集
  "scripts": {
    "prepare": "husky",
    "lint:lint-staged": "lint-staged"
  },

  // 代码提交检查配置
  "lint-staged": {
    "*.{ts,js,tsx,jsx,vue}": [
      "eslint . --fix",
      "prettier --write",
      "git add"
    ],
    "*.{css,scss}": [
      "stylelint **/*.{css,scss} --fix",
      "git add"
    ]
  },

  // 仓库日志提交规范的终端面板弹窗配置
  "config": {
    "commitizen": {
      "path": "./node_modules/cz-customizable"
    },
    "cz-customizable": {
      "config": "./.cz-config.js"
    }
  },
  "commitlint": {
    "extends": [
      "./commitlint.config.mjs"
    ]
  },

}

定义仓库日志提交规范的终端面板弹窗配置,详见commitlint.config.mjs.cz-config.js等配置文件。

commitlint.config.mjs,内容如下:


export default {
  extends: ["@commitlint/config-conventional"],

  // 配置规则
  rules: {
    'subject-case': [0],
    'type-enum': [
      2, 'always',
      // 与配置文件 .cz-config.js 内容的 types 对象顺序保持一致。
      [
        'fix', 
        'feat', 
        'perf', 
        'test',
        'docs',
        'style',
        'build', 
        'revert', 
        'refactor', 
        'chore', 
        'cicd', 
       ]
    ]
  }
}

.cz-config.js,内容如下:

"use strict";

// 由于 cz-customizable 依赖不支持 ESModule,故用 CommonJS 语法来写。
module.exports = {
  types: [
    { value: "fix", name: "🐛 缺陷修复 (修复 bug)" },
    { value: "feat", name: "✨ 功能新增 (新业务功能)" },
    { value: "perf", name: "⚡️ 性能优化 (产品性能提升)" },
    { value: "test", name: "✅ 测试用例 (功能测试)" },
    { value: "docs", name: "📝 文档修改 (文档更新)" },
    { value: "style", name: "💄 代码规范 (代码风格)" },
    { value: "build", name: "📦️ 打包构建 (依赖更新、构建配置等)" },
    { value: "revert", name: "⏪️ 撤销提交 (撤销远程提交)" },
    { value: "refactor", name: "♻️  代码重构 (业务功能新方案)" },
    {
      value: "chore",
      name: "🔨 日常事务 (非源码、测试用例改动,且不影响功能逻辑)",
    },
    { value: "cicd", name: "🎡 CI/CD (持续集成/持续部署)" },
  ],
  messages: {
    type: "请选择提交类型:",
    customScope: "请输入本次提交涉及的范围 (可选): ",
    subject: "请简要描述提交内容:",
    body: "请输入详细描述 (可选): ",
    breaking: "列出 BREAKING CHANGES (可选): ",
    footer: "列出相关 issues (可选): ",
    confirmCommit: "确认使用以上信息提交? (y/n)",
  },
  subjectLimit: 100,
};


仓库日志提交规范的终端面板弹窗配置用例测试,执行命令:

git status

git add .

## 特别注意:建议用 git cz 命令来替代 git commit -m '' 命令来弹出终端弹窗提示,规范化提交仓库日志内容。
git cz

- 请选择提交类型:📦️ 打包构建 (依赖更新、构建配置等)
- 请简要描述提交内容: 基于 monorepo 搭建 vue3 组件库
- 请输入详细描述 (可选):  (建议在文本写好日志内容,复制粘贴即可)

1.构建工具:vite、pnpm。
2.框架选型:vue、typescript。
3.样式规范:sass、unocss。
4.代码规范:eslint、prettier。
5.仓库日志规范:husky、lint-staged、commitlint。
6.脚本命令支持:tsx。
7.其它辅助支持:rimraf。

- 列出相关 issues (可选):(如果你是修复问题,根据 github 里的 issues 编号填写)

- 确认使用以上信息提交? (y/n):(自行决定是否提交啦)

  1. 统一打包流程

特别注意

  • 通过<script src=*.umd.js>方式无法直接使用,源于构建打包时缺少外部依赖配置。
  • 模块包构建配置文件vite.config.ts内容,存在重复配置内容,因此可选择自动化生成。
  • 全量打包构建,需要对build.rollupOptions.external进行处理,且引用中不采用路径别名,方便打包后而直接使用,以及沙箱测试。

特别提醒:构建流程如下;

  1. 规范定义;
    1-1. 目录规范;构建产物输出在build/dist目录下,且目录结构保持与单体仓库目录结构一致。
    1-2. 打包模式;独立打包、全量打包(.full)、全量压缩(.full-min),体现在构建产物的文件名上。
    1-3. 产物格式;默认esumd; 在build.lib.fileName只需返回模块包名[打包模式]即可,文件拓展名会自动填充。

  2. 流程如下;
    2-1. 总纲;获取对应模块包包管理配置文件(package.json),并根据打包模式返回构建配置vite.config.ts所需的字段配置。
    2-2. 包配置信息内容转换;根据对应模块包包管理配置文件(package.json)和打包模式返回以下字段,如下:

    • mode: 打包模式("IndependentModule " | "FullCompression" | "IndependentModule")
    • minify: 代码压缩(false)
    • sourcemap: 代码地图(false)
    • moduleDirName: 模块名称,由小写与分隔符组合('button')
    • moduleDirPath: 模块根目录路径,绝对路径('/ymui/packages/button')
    • moduleFileName: 模块名称,小写与分隔符组合,需携带前缀单体仓库名字符.('ymui-button')
    • moduleVariable: 模块全局变量,首字母大写驼峰命名;提供umd环境变量引用,('YmuiButton')
    • moduleEntryPath: 模块程序入口,('/ymui/packages/button/src/index.ts')
    • outputDir: 构建产物输出目录,规范输出到当前模块的dist目录,产物目录结构与单体仓库目录结构一致('/ymui/build/dist/packages/button')
    • outputFormats: 构建产物格式,([ 'es', 'umd' ])
    • outputDtsPath: 构建产物类型声明文件位置,对应包管理配置字段types,('/ymui/build/dist/packages/button/ymui-button.d.ts')
    • outputEsmFilePath: 构建产物类型声明文件位置,对应包管理配置字段moduleimport,('i/ymui/build/dist/packages/button/ymui-button.mjs')
    • outputCjsFilePath: 构建产物类型声明文件位置,对应包管理配置字段mainrequire,('i/ymui/build/dist/packages/button/ymui-button.umd.js')
    • dependencies: 来源于对应包管理配置字段dependencies,打包构建必须必须存在。({ '@ymui/shared': 'workspace:^' })
    • peerDependencies: 来源于对应包管理配置字段peerDependencies,({ vue: '^3.5.13' })
    • external: 来源于对应包管理配置字段peerDependencies的键值,并转换为正则式,([ /^node:.*/ ]),减少构建产物体积。
    • defaults: 来源于对应包管理配置信息内容(方便校对,配置构建是否正确)。

  3. 配置文件模块输出
    3-1. 构建打包配置vite.config.ts字段lib配置.
    3-2. 构建打包配置vite.config.ts字段rollupOptions配置.
    3-3. 构建打包配置vite.config.ts字段plugins配置.根据组件模块或非组件模块追加相应插件(如样式插件、图标插件);并提供预设插件以观察构建产物打包流程、输出内容等信息。

  4. 生产对应模块包的类型声明文件,并移动到对应构建产物的模块位置。

  5. 提供包管理配置·package.json·到对应构建产物的模块位置。

  6. 集成unocss样式原子化库

特别提醒:在单体仓库(ymui)根目录下执行命令安装开发依赖,命令如下:

pnpm i -wD unocss

## 在单体仓库创建原子化样式配置文件(uno.config.ts)
touch uno.config.ts

参见官方文档https://unocss.dev/integrations/vite定义原子化样式配置(uno.config.ts),内容如下:







一级标题

二级分类

三级内容

重要提醒: 四级提醒

特别注意:五级强调




文章收获

  • 如果觉得对你有所帮助,请点下“推荐”吧!
  • 如果担心文忘记章地址,请点下“收藏”吧!
  • 如果对博主文章内容喜欢,可进行“关注”博主,更好地获悉最新文章内容。
posted @ 2025-07-02 11:24  晨风不可依米  阅读(233)  评论(0)    收藏  举报