基于pnpm搭建属于自己的UI库
发表于:2023-11-08 | 分类: 学习 教程

pnpm 介绍

  • 目前前端包管理的根基是 npm,在其基础上衍生出了 yarn、pnpm。在 2022 年以后,我们推荐使用 pnpm 来管理项目依赖。pnpm 覆盖了 npm、yarn 的大部分能力,且多个维度的体验都有大幅度提升。

  • 具有以下优势

    • 速度快:多数场景下,安装速度是 npm/yarn 的 2 - 3 倍。
    • 基于内容寻址:硬链接节约磁盘空间,不会重复安装同一个包,对于同一个包的不同版本采取增量写入新文件的策略。
    • 依赖访问安全性强:优化了 node_modules 的扁平结构,提供了限制依赖的非法访问(幽灵依赖)的手段。
    • 支持 monorepo:自身能力就对 monorepo 工程模式提供了有力的支持。在轻量场景下,无需集成 lerna、Turborepo 等工具。
  • pnpm 支持 monorepo 模式的工作机制叫做 workspace(工作空间)。它要求在代码仓的根目录下存有 pnpm-workspace.yaml 文件指定哪些目录作为独立的工作空间,这个工作空间可以理解为一个子模块或者 npm 包。

    需要注意的是,pnpm 并不是通过目录名称,而是通过目录下 package.json 文件的 name 字段来识别仓库内的包与模块的。

包管理

中枢管理操作

  • 创建一个 package.json 文件

    • pnpm init
  • 设置用户的全局.npmrc配置

    • pnpm config set <key> <value>
    • 也可以在根目录直接创建.npmrc文件加上一行设置镜像源:
      • registry=https://registry.npm.taobao.org
  • 根据当前目录 package.json 中的依赖声明安装全部依赖,在 workspace 模式下会一并处理所有子模块的依赖安装。

    • pnpm install
  • 根目录的创建 pnpm-workspace.yaml 文件指定包目录

    • - packages/*
  • 添加.gitignore文件排除 node_modules

    • node_modules
  • 安装项目公共开发依赖,声明在根目录的 package.json - devDependencies 中。-w 选项代表在 monorepo 模式下的根目录进行操作。

  • 每个子包都能访问根目录的依赖,适合把 TypeScript、Vite、eslint 等公共开发依赖装在这里。

    • pnpm install -wD xxx
  • 卸载公共依赖,在根目录的package.json - devDependencies中删去对应声明

    • pnpm uninstall -w xxx
  • 执行根目录的 package.json 中的脚本

    • pnpm run xxx

子包管理操作

  1. 为指定模块安装外部依赖。
    • 下面的例子指为 a 包安装 lodash 外部依赖。-S 和 -D 选项分别可以将依赖安装为正式依赖(dependencies)或者开发依赖(devDependencies)。

      pnpm --filter a i -S lodash
      pnpm --filter a i -D lodash

  2. 指定内部模块之间的互相依赖。
    • 指定模块之间的互相依赖。下面的例子演示了为 a 包安装内部依赖 b。

      pnpm --filter a i -S b

  3. 过滤的高级用法
  • 用 –filter 过滤出目标工作空间集合后,不仅支持 install 安装依赖,run(执行脚本)、publish(发布包) 等绝大多数包管理操作都能够执行。

    发布所有包名为 @a/ 开头的包
    pnpm --filter @a/\* publish

  • –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

开始

  1. 创建工程
1
2
3
mkdir openx-ui
cd openx-ui
pnpm init
  1. 根目录生成package.json文件,去掉 name 以外的所有字段
1
2
3
4
5
6
7
8
9
10
11
12
{
"name": "my-ui",
- "version": "1.0.0",
- "description": "",
- "main": "index.js",
- "scripts": {
- "test": "echo \"Error: no test specified\" && exit 1"
- },
- "keywords": [],
- "author": "",
- "license": "ISC"
}
  1. 根目录下创建 pnpm-workspace.yaml 文件
1
2
3
4
5
packages:
# 根目录下的 docs 是一个独立的文档应用,应该被划分为一个模块
- docs
# packages 目录下的每一个目录都作为一个独立的模块
- packages/*
  1. 设置根目录 package.json
1
2
3
4
5
6
7
8
9
10
11
12
{
"name": "my-ui",
"private": true,
"scripts": {
// 定义脚本
"test": "echo 'test project'"
},
"devDependencies": {
// 定义各个模块的公共开发依赖
}
}

private: true:根目录在 monorepo 模式下只是一个管理中枢,它不会被发布为 npm 包。
devDependencies:所有模块都会有一些公共的开发依赖,例如构建工具、TypeScript、Vue、代码规范等,将公共开发依赖安装在根目录可以大幅减少子模块的依赖声明。

  1. 组件包的 package.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
{
// 标识信息
"name": "@myui/component",
"version": "0.0.0",

// 基本信息
"description": "",
"keywords": ["vue", "ui", "component library"],
"author": "myui",
"license": "MIT",
"homepage": "https://github.com/test/my-ui/blob/master/README.md",
"repository": {
"type": "git",
"url": "git+https://github.com/test/my-ui.git"
},
"bugs": {
"url" : "https://github.com/test/my-ui/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:组件统一发布到 @openxui 坐标下,有坐标限制了命名空间,组件的名称可以尽可能简单。
files:我们规定每个包的产物目录为 dist,此外还要一并发布 README.md 文档。
publishConfig:如果我们需要发布到私有 npm 仓,请取消 publishConfig 的注释并根据实际情况填写。
peerDependencies: 既然是使用 vue3 的组件库,我们需要正确声明主框架的版本。这里不将 vue 放入 dependencies 是因为用户项目同样也直接依赖 vue 框架,这样可能造成依赖版本不同的风险。这就是为什么周边库、插件总是要把主框架声明为 peerDependencies 的原因,我们的组件库也不例外。
dependencies:项目的运行依赖都安装在这里。一般不容易或是不介意出现版本冲突的依赖都放在这里。比如 lodash 这样的工具方法库,即使版本冲突出现多实例的现象,也不会出现问题。
devDependencies:大部分开发依赖都会被定义在根目录下,这里只会声明特有的、本模块专属的开发依赖。比如某个特定的 Vite 插件。

  1. 项目文档的 package.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"name": "@myui/docs",
"private": true,
"scripts": {
// 定义脚本,由于还没有集成实际的构建流程,这里先以打印命令代替
"dev": "echo dev",
"build": "echo build"
},
"dependencies": {
// 安装文档特有依赖
},
"devDependencies": {
// 安装文档特有依赖
}
}

private: true:项目文档的 packages.json 与根目录类似,它同样不需要被发布到 npm 仓库。
dependenciesdevDependencies:由于不涉及发包,因此依赖声明无需仔细考量,安装到那个里面效果都是一样的。不过还是建议大家还是按照“实际的含义”来决定安装类型。

  1. 最后根目录加上.gitignore
1
2
# .gitignore
node_modules
上一篇:
前端glob语法
下一篇:
lint代码规范工具