在 必不可少的UI组件——组件库开发的基础知识(Vue篇) 中,我们介绍了一些封装 Vue 组件的过程中高频使用到的框架技巧,但是,这并不足以支持我们实现完善的组件库。
建设一个成熟的组件库就像盖一幢大楼,工程化基础就如同脚手架一般,虽然不是组件库核心、必备的部分,但没有它们,整个施工过程就会充满危险、处处收到掣肘。
构建组件库的工程基础需要的工具又广又杂。考虑一个成熟的组件库,它的工程化应当有以下需求:
package.json
,熟练运用一款包管理工具(npm
/yarn
/pnpm
)。monorepo
模式提供了在一个代码仓中集中管理多个零散模块的优秀实践,我们需要掌握这种架构。cjs
、esm
、browser
、d.ts
、样式),满足用户在各个场景下的使用。semver
版本号、git 仓打 tag、生成版本更新记录 CHANGELOG
等操作。本章节主要给大家介绍组件库开发工程化方面的基础知识,先从最基础的包管理和文件组织入手,理清下面两个问题。而后续的构建、测试、发布等流程将在未来实践性更强的篇章中分享。
monorepo
架构对它们进行集中的组织管理?组件库工程往往会拆分出许多子模块:
我们可以先来参考 tinyVue 是如何进行拆分的:
renderless
- 为了使组件的视图与逻辑分离,tinyVue
在这个模块实现组件的逻辑。theme
- 实现组件的主题与样式。vue
- 组件的主体实现,将 renderless
中的逻辑、theme
中的样式与
模板关联起来。其中每一个组件都被进一步划分为了子模块:vue-common
- 与 Vue
相关的公用方法,包括兼容不同 Vue
版本的适配器模块。vue-icon
- 实现 icon 矢量图标。vue-locale
- 多语言支持。examples/docs
- 组件库的文档。按照传统的 mutirepo
思路,需要为每一个模块建立一个代码仓进行管理。在这种分散管理的模式下,每一个包都有其独立的版本号、配置、发布流程。
而 monorepo
模式就是把所有这些模块集中到一个代码仓中,对它们进行集中统一管理。
monorepo
是当下构建复杂的多模块前端工程更为推崇的一种方式。 我们用下面的例子直观地说明 monorepo
的核心优势:
假设存在以下依赖关系:文档模块 docs
作为本地开发环境,依赖于组件模块 vue
,而组件模块又依赖公共方法 vue-common
。
假如我更新了最下游 vue-common
包中的工具方法,我当然希望上层组件 vue
也能立即适应更新,并即刻反馈在 docs
模块的 demo 示例中,实现丝滑的热更新。
但是很可惜,在传统的 mutirepo
模式下,我们必须先发布 vue-common
模块,再更新组件 vue
包中的依赖版本,接下来执行 vue
包的发布,最后升级 docs
项目中的依赖,才能够查看到更新后的效果。
上述整个过程在顺序上不能出现失误,否则只能废弃这个版本号重新发布。更难受的是,如果我们的修改不到位,再次微调仍然要走一遍发布流程!
另一方面,这些包虽然功能不同,但是它们的项目配置、构建流程、发布流程是不是也有很多相似之处?分散在多个代码仓中,对于许多相似的配置,免不了一顿复制粘贴,一旦我有整体性修改 CI 流程的需求,是不是也要分别修改多个仓,再分别验证其正确性?
从上面的例子,我们可以梳理出 monorepo
对于大部分库的构建者而言最无法拒绝的优势:
更多关于 mutirepo
和 monorepo
的优劣对比,可以参考下面的表格。
通常来说,只要你的项目由多模块组成,并对于各个模块代码的权限管控不敏感,就可以放心使用 monorepo
架构。这里再给大家推荐两篇文章,它们更加理性、全面地对比了两种模式的优劣:
monorepo
不仅仅适用于组件库的开发。这里我们列举其他的适用场景,帮助大家更好地理解这种架构。
核心库与周边适配器
例子:
├── packages
| ├── core
| ├── adapterA
| ├── adapterB
| ├── pluginA
| ├── pluginB
| ├── ...
├── package.json
常规 Web 应用
即使是传统 Web 应用,采用 monorepo
模式也有利于代码的复用,促使团队成员以组件化的思想进行开发,不断抽离公共模块,产生技术沉淀。
├── packages
| ├── portal # 门户网站
| ├── mis # 管理后台
| ├── mobile # 移动端网站
| ├── docs # 开发文档
| ├── shared # 公共库
| ├── api # API 层
| ├── ... # 监控埋码、Nodejs 服务、更多公共模块...
├── package.json
monorepo
的重点在与单仓多包管理,这自然地引出了包管理这一概念。
包管理是处理模块之间依赖关系的核心,在当今的开源模式下,几乎没有任何的项目在开发过程中不需要引用他人发布的公共模块,缺少成熟的包管理机制,我们就只能通过源码拷贝的方式去复用他人的产出。
在正式上手搭建组件库之前,我们应该对“什么是包”有一个清晰的概念,去了解 npm 包的配置文件 package.json
。
一个包或者子模块不一定发布到 npm
仓库,但一定有 package.json
文件。package.json
所在的目录就代表了一个模块/包,这个 json
文件定义了包的各种配置,例如基本信息、依赖关系、构建配置等等。所有包管理器(npm
/yarn
/pnpm
)以及绝大多数构建工具都依赖于这个配置文件的信息。
package.json
中的字段并没有一个绝对统一的标准,除了官方约定的部分标准字段外,很多字段其实是特定的工具约定的。我们在分析配置的时候,要明确各个字段到底由谁读取。
我们只介绍后续搭建组件库的过程中将用到的字段,如果你希望对 package.json
建立更加全面的了解,可以前往以下文章:
官方文档:package.json
你真的了解package.json吗?
关于前端大管家 package.json,你知道多少?
包管理器、Node.js
、构建工具都会读取标识字段,未正确设置会导致模块无法被被识别为 npm
包。
name
是区分 npm
包的唯一标识。当一个 npm
仓库中的包被安装到本地,我们能通过名称引用,而不必写复杂的 node_modules/...
引入路径就是得益于此。
对于包名称我们还要了解一个概念叫坐标,具有相同坐标的包会被安装到同一子目录下。例如 @vue/reactivity
和 @vue/runtime-core
会被安装到 node_modules
目录的 @vue
目录下,vue
不属于任何坐标,就会被安装到 node_modules
根目录。
node_modules
┣ @vue
┃ ┣ reactivity
┃ ┗ runtime-core
┣ vue
通常情况下,属于同一个体系、项目下的包会被安排在一个坐标下,比如 OpenTiny 开源项目下的包就都会发布到 @opentiny
这个坐标下,那么包名就需要设定为 @opentiny/xxx
。
每个人都可以登录 npm.js,建立自己的坐标。
version
字段表示包的版本号,符合 major.minor.patch
(主版本号.次版本号.修订号
) 的格式(例如 1.0.1
),如果要进一步了解版本号相关的知识,我们可以阅读以下文章来详细了解什么是语义化版本:
语义化版本 2.0.0
semver:语义版本号标准 + npm的版本控制器
可以通过以下命令来查看 npm 包的版本信息,以 @opentiny/vue
为例:
npm view @opentiny/vue versions
基本信息主要由 npm
负责读取,未正确设置不影响包的功能,但会导致该包在 npm.js 中缺失信息,不能给用户正确的引导。
这些信息不涉及包管理的核心,简单做一些了解即可。以 vue 的基本信息 为例子:
{
"name": "vue",
// 一句话简介,可以作为关键字搜索的依据
"description": "The progressive JavaScript framework for building modern web UI.",
// 关键字、标签,正确设置可以提高在 npm 的搜索权重与曝光度
"keywords": ["vue"],
// 包的作者,主要 Owner
"author": "Evan You",
// 开源许可证
"license": "MIT",
// 项目主页
"homepage": "https://github.com/vuejs/core/tree/main/packages/vue#readme",
// 源码仓库
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/core.git"
},
// BUG 反馈方式,支持 `bugs.email` 邮箱字段
"bugs": {
"url" : "https://github.com/vuejs/core/issue"
}
}
入口信息主要被 Node.js
、各路构建工具(Vite
/ Rollup
/ Webpack
/ TypeScript
)所识别。未正确设置会导致 npm
包无法被加载或者实际加载了预料之外的文件。
入口文件的加载机制是比较复杂的,在不同的构建工具中有着不同的加载逻辑,对此给大家分享一篇文章:package.json 导入模块入口文件优先级详解。这里不倾向于深挖细节,只讲到足够我们搭建组件库的程度。
当然,你可能需要有模块化规范的前置知识,前端的模块规范有着源远流长的历史,直到现在也并不是统一的。我们至少应该了解正被广泛使用的 cjs
、esm
两种现代化规范,这里同样给大家分享一些文章:
阮一峰 - ES6 Module 的加载实现。
ESM和CJS模块杂谈
在后续讲解打包,涉及到生成多场景产物时,我们会对做详细一些的讨论。
这里列举几个需要被关注的入口信息字段:main
、module
、types
、exports
。我们尽量使用贴近实践的描述,以代码中引入方式的不同来分析它们之间的区别:
1. main
和 exports.*.require
字段用于设置 require()
方式的加载入口(cjs
规范)。
// 入口定义
{
"name": "my-module",
"main": "index.js",
"exports": {
".": {
"require": "index.js"
},
// ...
}
}
// 代码中使用
const app = require('my-module') // 实际路径 node_modules/my-module/index.js
2. module
和 exports.*.import
字段用于设置 import
的加载入口(esm
规范)。
// 入口定义
{
"name": "my-module",
"main": "index.js",
"module": "index.mjs",
"exports": {
".": {
"require": "index.js",
"import": "index.mjs"
},
// ...
}
}
// 使用
import app from 'my-module' // 实际路径 node_modules/my-module/index.mjs
3. types
和 exports.*.types
字段用于设置 d.ts
类型声明的加载入口(TypeScript
专属)。
// 入口定义
{
"name": "my-module",
"main": "index.js",
"module": "index.mjs",
"types": "index.d.ts",
"exports": {
".": {
"require": "index.js",
"import": "index.mjs",
"types": "index.d.ts"
},
// ...
}
}
这里我们以 axios
为例子看看实际效果:
4. exports
比起 main
、module
、types
,它可以暴露更多的出口,而后者只能定义主出口。
// 入口定义
{
"name": "my-module",
"main": "index.js",
"exports": {
".": {
"require": "index.js",
},
"./locale/*": {
"require": "./locale/*",
},
"./plugins/*": {
"require": "./dist/plugins/*",
}
// ...
}
}
// 使用
const app = require('my-module') // 实际路径 node_modules/my-module/index.js
const zhCn = require('my-module/locale/zh-Cn') // 实际路径 node_modules/my-module/locale/zh-Cn.js
const testPlugin = require('my-module/plugins/test') // 实际路径 node_modules/my-module/dist/plugins/test.js
// import 同理
最后,当 exports
和另外三个入口字段出现重复定义时,会有更高的优先级。
更多关于 exports
的规则和细节,可以去 Webpack Package exports 学习,我建议在有需要的时候查阅即可。
依赖信息的读取方只有包管理器。未正确设置会导致项目实际安装的依赖包不符合预期,进而导致项目无法正常运行或构建。
依赖信息的结构是一个对象,其中依赖包的名称作为键(key),依赖的版本约束作为值(value)。
{
"dependencies": {
"lodash": "^4.17.21",
},
"devDependencies": {
"vite": "~4.2.0"
}
}
版本约束限制了包管理器为项目安装依赖时可选的版本范围:
^
的含义是安装最新的 minor
版本。例如 ^1.2.0
的约束下,会为项目安装最新的 minor
版本 1.X.Y
,但不会安装下一个 major
版本 2.0.0
。~
的含义是安装最新的 patch
版本。例如 ~1.2.0
的约束下,会为项目安装最新的 patch
版本 1.2.X
,但不会安装下一个 minor
版本 1.3.0
。关于版本约束的进阶阅读:工程的 package.json 中的 ^~ 该保留吗?
很多情况下,我们其实并没有真正搞懂常见的三种依赖类型—— dependencies
、devDependencies
、peerDependencies
的真正含义与表现。这里简单给出一个表格说明帮助大家正确理解。
项目中
理解为依赖信息被定义在我们正在开发的模块,对应根目录下的 package.json
中;依赖中
理解为依赖信息被定义在 node_modules
内的依赖包中(即依赖的依赖),对应 node_modules/${packageName}/package.json
。依赖类型 | 项目中 | 依赖中 | 用途 |
---|---|---|---|
dependencies |
会被安装 | 会被安装 | 项目运行时依赖 |
devDependencies |
会被安装 | 不会被安装 | 项目在开发过程需要的依赖。一般构建工具、测试框架、代码规范工具都会被作为开发依赖 |
peerDependencies |
不会被安装 | 不会被安装。但是若其中声明的依赖没有被项目安装,或者版本不匹配时,会生成警告信息提示用户 | 定义项目需要的依赖环境。常用于表示插件和主框架的关系,如 @vitejs/plugin-vue 的 peerDependencies 中就声明了主框架 vite 和 vue |
我自己做的关于 pnpm
的分享中,为了理解“幽灵依赖”现象,也花了不少篇幅去介绍这三个依赖字段的实际效果,大家可以参考阅读:新一代包管理工具 pnpm 使用心得。
同样再分享一篇其他介绍这个机制的文章:一文彻底看懂 package.json 中的各种 dependencies
发布信息的读取方只有包管理器。未正确设置会导致项目的发布行为不符合预期。
files
指定了发布为 npm
包时,哪些文件或目录需要被提交到 npm
服务器中。
{
"files": [
"LICENSE",
"README.md",
"dist"
]
}
private
用于指定项目是否为私有包。当我们的项目不想被意外发布到公共 npm
仓库时,就设置 private: true
。
当我们的项目需要发布到私有的 npm
仓库时(比如公司内网的仓库),需要设置 publishConfig
对象。
{
"publishConfig": {
"registry": "https://mynpm.com",
},
}
脚本信息的读取方只有包管理器。这是包管理器给我们提供的一项福利功能,允许我们给复杂的命令赋予一个简单的别名。
{
"script": {
"show": "echo 'Hello World!'",
"dev": "vite"
},
"dependencies": {
"vite": "^4.3.0"
}
}
在上面的例子中,我们运行 npm run show
就可以执行打印 Hello World
的命令。
运行 npm run dev
就可以调用 vite
的命令行程序,启动 vite
开发服务器。
然而直接在命令行中执行 vite
命令是会报错的,这是因为包管理器会将项目中所有相关的可执行命令二进制文件放入 node_modules/.bin
中,这个目录会在运行时被加入到系统环境变量 PATH
。
monorepo
的单仓分模块的要求,使得仓库内的模块不仅要处理与外部模块的关系,还要处理内部之间相互的依赖关系。因此我们需要选择一个强大的包管理工具帮助处理这些任务。
目前前端包管理的根基是 npm,在其基础上衍生出了 yarn、pnpm。在 2022 年以后,我们推荐使用 pnpm 来管理项目依赖。pnpm
覆盖了 npm
、yarn
的大部分能力,且多个维度的体验都有大幅度提升。
pnpm 是一款快速、高效使用磁盘空间的包管理器。
它具有以下优势:
npm/yarn
的 2 - 3 倍。node_modules
的扁平结构,提供了**限制依赖的非法访问(幽灵依赖)**的手段。monorepo
:自身能力就对 monorepo
工程模式提供了有力的支持。在轻量场景下,无需集成 lerna、Turborepo 等工具。对于 pnpm
选型的更多理由以及其原理、应用的简单说明,可以参考:关于现代包管理器的深度思考——为什么现在我更推荐 pnpm 而不是 npm/yarn?。
pnpm
支持 monorepo
模式的工作机制叫做 workspace(工作空间)。
它要求在代码仓的根目录下存有 pnpm-workspace.yaml
文件指定哪些目录作为独立的工作空间,这个工作空间可以理解为一个子模块或者 npm
包。
例如以下的 pnpm-workspace.yaml
文件定义:a
目录、b
目录、c
目录下的所有子目录,都会各自被视为独立的模块。
packages:
- a
- b
- c/*
my-project
┣ a
┃ ┗ package.json
┣ b
┃ ┗ package.json
┣ c
┃ ┣ c-1
┃ ┃ ┗ package.json
┃ ┣ c-2
┃ ┃ ┗ package.json
┃ ┗ c-3
┃ ┗ package.json
┣ package.json
┣ pnpm-workspace.yaml
需要注意的是,pnpm
并不是通过目录名称,而是通过目录下 package.json
文件的 name
字段来识别仓库内的包与模块的。
在 workspace
模式下,代码仓根目录通常不会作为一个子模块或者 npm
包,而是**主要作为一个管理中枢,执行一些全局操作,安装一些共有的依赖。**下面介绍一些常用的全局管理操作。
package.json
文件。pnpm init
.npmrc
配置。pnpm config set <key> <value>
package.json
中的依赖声明安装全部依赖,在 workspace 模式下会一并处理所有子模块的依赖安装。pnpm install
-w
选项代表在 monorepo
模式下的根目录进行操作。每个子模块都能访问根目录的依赖,适合把 TypeScript
、Vite
、eslint
等公共开发依赖装在这里。pnpm install -wD xxx
pnpm uninstall -w xxx
pnpm run xxx
在 workspace
模式下,pnpm
主要通过 --filter
选项过滤子模块,实现对各个工作空间进行精细化操作的目的。
1. 为指定模块安装外部依赖。
下面的例子为 a
包安装 lodash
外部依赖。与常规安装指令相同,-S
和 -D
选项分别可以将依赖安装为正式依赖(dependencies
)或者开发依赖(devDependencies
)。
# 为 a 包安装 lodash
pnpm --filter a install -S lodash
pnpm --filter a install -D lodash
2. 指定内部模块之间的互相依赖。
指定内部模块之间的互相依赖。下面的例子演示了为 a
包安装内部依赖 b
。
# 指定 a 模块依赖于 b 模块
pnpm --filter a install -S b
pnpm workspace
对内部依赖关系的表示不同于外部,它自己约定了一套 Workspace 协议 (workspace:)。下面给出一个内部模块 a
依赖同是内部模块 b
的例子。
{
"name": "a",
// ...
"dependencies": {
"b": "workspace:^"
}
}
后续使用 pnpm publish
命令发布 npm
包时,workspace:^
会被替换成内部模块 b
的对应版本号(对应 package.json
中的 version
字段)。替换规律如下:
{
"dependencies": {
"a": "workspace:*", // 固定版本依赖,被转换成 x.x.x
"b": "workspace:~", // minor 版本依赖,将被转换成 ~x.x.x
"c": "workspace:^" // major 版本依赖,将被转换成 ^x.x.x
}
}
3. 过滤的高级用法
--filter
过滤出目标工作空间集合后,不仅支持 install
安装依赖,run
(执行脚本)、publish
(发布包) 等绝大多数包管理操作都能够执行。# 发布所有包名为 @a/ 开头的包
pnpm --filter @a/* publish
--filter
筛选出多个包时,默认情况下,它会首先分析多个包之间的内部依赖关系,按照依赖关系拓扑排序的顺序对这些包执行指令,即按依赖树从叶到根的顺序。例如下图所示的依赖关系中,执行顺序为 C -> D -> B -> A
。--filter
的还有更多超乎我们想象的能力,它支持依赖关系筛选,甚至支持根据 git
提交记录进行筛选。# 为 a 以及 a 的所有依赖项执行测试脚本
pnpm --filter a... run test
# 为 b 以及依赖 b 的所有包执行测试脚本
pnpm --filter ...b run test
# 找出自 origin/master 提交以来所有变更涉及的包
# 为这些包以及依赖它们的所有包执行构建脚本
# README.md 的变更不会触发此机制
pnpm --filter="...{packages/**}[origin/master]"
--changed-files-ignore-pattern="**/README.md" run build
# 找出自上次 commit 以来所有变更涉及的包
pnpm --filter "...[HEAD~1]" run build
更多 pnpm
使用方面的细节还需自行查阅官方文档:pnpm 官方文档。
最后,我们通过搭建一个 monorepo
组件库工程的雏形,来实践前面讲到的工程化基础知识。
我们先划分出各个 UI 组件、文档的模块,并为组件库工程命名为 my-tiny-vue
,指定其 npm 发布坐标为 @mytinyvue
。
my-tiny-vue
├── docs # 组件库文档 demo 模块
├── packages # 组件库的各个组件模块
| ├── button # 按钮组件
| ├── input # 输入框组件
| ├── form # 表单组件
| ├── theme # 组件库的样式与主题
| ├── ... # 更多 UI 组件
| ├── ui # 归纳各个 UI 组件的入口,即组件库的主包
| ├── shared # 其他工具方法
├── package.json
我们默认大家已经装好了开发环境,Node.js
与 npm
都可以正常工作,首先通过 npm i -g pnpm
安装好 pnpm
,后续包管理相关的命令一律使用 pnpm
执行。
首先,我们来创建工程目录:
mkdir my-tiny-vue
cd my-tiny-vue
pnpm init
在项目根目录中生成了 packages.json
文件,但是根目录并不是待发布的模块,它将作为整个组件库 monorepo
项目的管理中枢。我们把对这个 package.json
中 name
以外的字段都删去,后续我们要根据自己的需要自定义。
{
"name": "my-tiny-vue",
- "version": "1.0.0",
- "description": "",
- "main": "index.js",
- "scripts": {
- "test": "echo \"Error: no test specified\" && exit 1"
- },
- "keywords": [],
- "author": "",
- "license": "ISC"
}
之后,我们在根目录下创建 pnpm-workspace.yaml
文件,这个文件的存在本身,会让 pnpm
要使用 monorepo
的模式管理这个项目。它的内容告知 pnpm
哪些目录将被划分为子模块,这些独立模块被包管理器称作 workspace
(工作空间)。
我们在 pnpm-workspace.yaml
中写入以下内容。
packages:
# 根目录下的 docs 是一个独立的文档应用,应该被划分为一个模块
- docs
# packages 目录下的每一个目录都作为一个独立的模块
- packages/*
那么我们开始实际建立这些工作空间,并将根目录下的 package.json
文件复制到每个工作空间中。为了方便演示,暂时只建立 UI 组件 button(按钮)
、input(输入框)
以及公共方法模块 shared
。这里展示出完成操作后的目录树:
my-tiny-vue
┣ docs
┃ ┗ package.json
┣ packages
┃ ┣ button
┃ ┃ ┗ package.json
┃ ┣ input
┃ ┃ ┗ package.json
┃ ┗ shared
┃ ┗ package.json
┣ package.json
┣ pnpm-workspace.yaml
┗ README.md
package.json
接下来,我们要明确每一个模块的属性,设置它们的 package.json
文件。
注意:下面例子中的注释只是为了方便讲解,实操请务必删除注释,带有注释的 package.json
会在执行命令时报错。
package.json
// my-tiny-vue/package.json
{
"name": "my-tiny-vue",
"private": true,
"scripts": {
// 定义脚本
"hello": "echo 'hello world'"
},
"devDependencies": {
// 定义各个模块的公共开发依赖
"typescript": "^5.1.6",
"vite": "^4.4.4",
"vue": "^3.3.4",
}
}
private: true
:根目录在 monorepo 模式下只是一个管理中枢,它不会被发布为 npm 包。devDependencies
:所有子模块共享的公共的开发依赖,例如构建工具、TypeScript、Vue、代码规范等,将公共开发依赖安装在根目录可以大幅减少子模块的依赖声明。package.json
这里只举一个组件的例子,其他组件包的配置除了 name
以外大体相同。
// my-tiny-vue/packages/button/package.json
{
// 标识信息
"name": "@mytinyvue/button",
"version": "0.0.0",
// 基本信息
"description": "",
"keywords": ["vue", "ui", "component library"],
"author": "XXX",
"license": "MIT",
"homepage": "https://github.com/xxx/my-tiny-vue/blob/master/README.md",
"repository": {
"type": "git",
"url": "git+https://github.com/xxx/my-tiny-vue.git"
},
"bugs": {
"url" : "https://github.com/xxx/my-tiny-vue/issues"
},
// 定义脚本,由于还没有集成实际的构建流程,这里先以打印命令代替
"scripts": {
"build": "echo build",
"test": "echo test"
},
// 入口信息,由于没有实际产物,先设置为空字符串
"main": "",
"module": "",
"types": "",
"exports": {
".": {
"require": "",
"module": "",
"types": ""
}
},
// 发布信息
"files": [
"dist",
"README.md"
],
// "publishConfig": {},
// 依赖信息
"peerDependencies": {
"vue": ">=3.0.0"
},
"dependencies": {},
"devDependencies": {}
}
name
:组件统一发布到 @mytinyvue
坐标下,有坐标限制了命名空间,组件的名称可以尽可能简单。files
:我们规定每个包的产物目录为 dist
。产物目录必须在发布时被提交,此外还要一并发布 README.md
文档。publishConfig
:如果我们需要发布到私有 npm 仓,请取消 publishConfig
的注释并根据实际情况填写。peerDependencies
: 既然是使用 vue3
的组件库,我们需要正确声明主框架的版本。这里不将 vue
放入 dependencies
是因为用户项目同样也直接依赖 vue
框架,这样可能造成依赖版本不同的风险。这就是为什么周边库、插件总是要把主框架声明为 peerDependencies
的原因,我们的组件库也不例外。dependencies
:项目的运行时依赖,用户安装该包时,这些依赖也将被递归安装。devDependencies
:项目的开发依赖(公共开发依赖之外),不会在用户安装时被递归安装。package.json
// my-tiny-vue/docs/package.json
{
"name": "@mytinyvue/docs",
"private": true,
"scripts": {
// 定义脚本,由于还没有集成实际的构建流程,这里先以打印命令代替
"dev": "echo dev",
"build": "echo build"
},
"dependencies": {
// 安装文档特有依赖
},
"devDependencies": {
// 安装文档特有依赖
}
}
private: true
:项目文档的 packages.json
与根目录类似,它同样不需要被发布到 npm
仓库。至此,我们的 monorepo
项目的雏形就已经建立完毕,可以通过下面的命令,一次性执行所有子模块的 build
脚本。不难想到,如果我们将 echo build
换成构建命令,就能够实现组件库的整体构建。
> pnpm --filter "*" run build
Scope: 4 of 5 workspace projects
docs build$ echo build
│ build
└─ Done in 58ms
packages/button build$ echo build
│ build
└─ Done in 54ms
packages/input build$ echo build
│ build
└─ Done in 58ms
packages/shared build$ echo build
│ build
└─ Done in 55ms
后续,我们将以这个简单例子为基础,一步步地搭建出完善的组件库项目。
OpenTiny 是一套企业级 Web 前端开发解决方案,提供跨端、跨框架的UI组件库,适配 PC 端 / 移动端等多端,支持 Vue2 / Vue3 / Angular 多技术栈,拥有灵活扩展的低代码引擎,包含主题配置系统 / 中后台模板 / CLI 命令行等丰富的效率提升工具,可帮助开发者高效开发 Web 应用。
核心亮点:
欢迎加入 OpenTiny 开源社区。添加微信小助手:opentiny-official 一起参与交流前端技术~
OpenTiny 官网:opentiny.design/
OpenTiny 代码仓库:github.com/opentiny/
TinyEngine 源码: https://github.com/opentiny/tiny-engine
欢迎进入代码仓库 StarTinyEngine、TinyVue、TinyNG、TinyCLI~
如果你也想要共建,可以进入代码仓库,找到 good first issue标签,一起参与开源贡献~