前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >pnpm技术体系之:打造企业级 pnpm 开源组件

pnpm技术体系之:打造企业级 pnpm 开源组件

原创
作者头像
南山种子外卖跑手
发布2023-01-12 10:21:21
2.2K0
发布2023-01-12 10:21:21
举报
文章被收录于专栏:南山种子外卖跑手的专栏
pnpm技术体系之:高性能包管理工具.png
pnpm技术体系之:高性能包管理工具.png

开场

pnpm 是 performant npm(高性能的 npm),它是一款快速的,节省磁盘空间的包管理工具,同时,它也较好地支持了 workspace 和 monorepos,简化开发者在多包组件开发下的复杂度和开发流程。

在上一篇《pnpm技术体系之:高性能包管理工具》讲到pnpm的优势,在本章节,我们开始着手搭建一个完整流程的开源组件。

pnpm monorepo搭建

本篇章的全部代码已上传到 github,有需要自取。

1. 初始化项目

1.1. 安装pnpm

代码语言:shell
复制
npm install pnpm -g

1.2. 初始化package.json

代码语言:shell
复制
pnpm init

1.3. 配置 .npmrc

此外,我们要额外创建pnpm的配置文件:.npmrc,配置如下:

代码语言:txt
复制
shamefully-hoist=false
detect_chromedriver_version=true
strict-peer-dependencies=false

一般教程都是这样配置的:shamefully-hoist=true,但本人不推荐。这样做会把里面的依赖提升到全局node_module里面,有可能出现幽灵依赖的风险。

1.4. 创建工作空间

pnpm 内置了对单一存储库(也称为多包存储库、多项目存储库或单体存储库)的支持, 你可以创建一个 workspace 以将多个项目合并到一个仓库中,这样的作用是能在我们开发调试多包时,彼此间的依赖引用更加简单。

创建工作空间也非常简单,假设我们的项目中有3个包:

代码语言:shell
复制
.
└── packages
    ├── playground
    ├── small-color-ui
    └── utils

这时候我们在根目录创建一个 pnpm-workspace.yaml文件,里面添加如下配置,这样在packages范围下的包都能共享工作空间了。

代码语言:yaml
复制
packages:
  - 'packages/*'

完事后,假如我们想在small-color-ui包里面使用utils,那直接在small-color-ui终端执行安装命令(安装包名为utilspackage.json文件name字段):

代码语言:shell
复制
$ cd packages/small-color-ui
$ pnpm i -D @small-color-ui/utils

接下来会看到packages/small-color-ui/package.json中已经包含utils包的依赖了。

image.png
image.png

至于utils的版本为workspace:*,是因为pnpm是由workspace管理的,所以有一个前缀workspace可以指向utils下的工作空间从而方便本地调试各个包直接的关联引用,但这种引用会在publish时自动被pnpm纠正为正常版本。你可以在 官网 找到workspace version更多信息。

2. 组件的package.json配置

基础框架搭建好后,我们再看下如何配置组件包的package.json,让其满足我们的开发&&发布需求。例如,我们的主包:packages/small-color-ui/package.json,配置如下:

代码语言:json
复制
{
  "name": "small-color-ui",
  "private": false,
  "version": "1.0.0",
  "type": "module",
  "description": "small-color-ui core",
  "license": "MIT",
  "author": "Johnny",
  "contributors": [],
  "main": "src/main.tsx",
  "module": "src/main.tsx",
  "publishConfig": {
    "main": "dist/tts-controller.cjs.js",
    "module": "dist/tts-controller.es.js",
    "typings": "dist/src/main.d.ts"
  },
  "repository": {
    "type": "git",
    "url": "git@github.com:JohnnyZhangQiao/pnpm-monorepo-learn.git"
  },
  "bugs": {
    "url": "https://github.com/JohnnyZhangQiao/pnpm-monorepo-learn/issues"
  },
  "files": [
    "dist",
    "README.md"
  ],
  "keywords": [
    "small-color-ui"
  ],
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build && pnpm run build:types",
    "preview": "vite preview",
    "build:types": "tsc --p tsconfig.types.json"
  },
  "dependencies": {
    "@small-color-ui/utils": "workspace:*",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "@types/react": "^18.0.26",
    "@types/react-dom": "^18.0.9",
    "@vitejs/plugin-react-swc": "^3.0.0",
    "less": "^4.1.3",
    "typescript": "^4.9.3",
    "vite": "^4.0.0"
  }
}

解析一下关键字段:

  • name:组件名,也是我们要发布到npm上面的名称。假如有子依赖包(如上面的utils包),请注册到同一个组织下面。这时候utils的包名就可以为:@small-color-ui/utils,代表隶属@small-color-ui组织。
  • private:布尔类型,true代表私有包,publish时不会执行发布操作。
  • version:发布版本。
  • type:文件引入规范,module | commonjs,分别代表采用ESModule或commonjs规范来引入文件。
  • mainmodule:定义入口文件,项目在具备ESM 规范情况下,module具备更高的识别优先级。
  • publishConfig:在publish时,里面对应的入口会替换掉外层,一般本地开发时指向src目录,发布后指向dist目录。
  • typings:组件的typescript类型描述,缺失会导致组件被引用时失去类型提示。
  • files:组件作为依赖项时会安装的目录/文件,支持正则匹配,默认会带上4项:package.jsonREADMELICENSE / LICENCE 和 主入口文件。
  • dependencies:打包带上的子依赖。
  • devDependencies:开发环境的子依赖。

3. 关于依赖安装

一般来讲,pnpm对于工作空间的依赖安装分2种,一种是普通安装,另一种是使用-w(--workspace-root)参数,它代表把依赖安装到工作空间中。关于-w的作用,举个例子:

假如你使用以下命令,那么在整个工作空间内的所有组件都能直接使用react

代码语言:shell
复制
pnpm i -Sw react

但如果你在某个包使用以下命令,那么react只能在这个包内被引用,其他组件不会识别到react依赖。

代码语言:shell
复制
pnpm i -S react

这里的建议是,假如多包共享的依赖,可以直接安装到工作空间里,特性包则避免使用这参数。

关于-w的更多用法,你可以参考官网说明

4. 生产.d.ts类型描述文件

一般优秀的开源组件,都会在发布时顺便发布一份类型描述文件,这样的作用:一是能友好给使用者方法引入以及参数类型提示;二是能保证组件参数传递规范。

我们要生成对应的类型文件,只需要在tsconfig.json加上以下配置:

代码语言:json
复制
"compilerOptions": {
  "declaration": true,
  "emitDeclarationOnly": true,
}

为了能达到更好的项目配置分离,我们可以把生成类型的配置单独抽离出来,配合extends把通用的tsconfig.json融合进来即可,如下图:

image.png
image.png

最后,在package.json增加以下命令,在构建类型文件时指定tsconfig

代码语言:json
复制
"scripts": {
  "build:types": "tsc --p tsconfig.types.json"
},

5. 打包配置

由于本项目用vite来做打包工具,所以主要用到rollup的打包策略,具体vite.config.ts配置如下:

代码语言:typescript
复制
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';
import type { OutputOptions } from 'rollup';

export default defineConfig({
  plugins: [react()],
  build: {
    target: 'modules',
    //打包文件目录
    outDir: 'dist',
    //压缩
    minify: false,
    //css分离
    cssCodeSplit: false,
    // rbga色值禁止转成十六进制
    cssTarget: 'chrome61',
    lib: {
      entry: './src/App.tsx',
      formats: ['es', 'cjs'],
      name: 'small-color-ui-core',
    },
    rollupOptions: {
      // 排除打包的库
      external: ['react', 'react-dom'],
      input: ['./src/App.tsx'],
      output: ['esm', 'cjs'].map((format) => ({
        name: 'small-color-ui',
        format,
        dir: 'dist',
        entryFileNames: `small-color-ui.[format].js`,
        assetFileNames: 'index.css',
        preserveModulesRoot: 'src',
      })) as OutputOptions[],
    },
  },
});

6. 发布组件

6.1. npm创建账号与组织

要发布自己的软件包到npm,先要注册一个个人或企业账号,注册入口

另外,假如你包里有子依赖,并隶属一个组织下,还要再添加个组织,一般组织名和你主包名一致。组织创建入口

对于免费开源包,一般选下面Unlimited public packages即可。

image.png
image.png

6.2. 发布命令

万事俱备,我们回到项目控制台里面,在发包前先登录npm账号:

代码语言:shell
复制
# 建议指定registry,避免登录到公司内部的开源库中去
pnpm login --registry https://registry.npmjs.org/

按部就班输入以下4项,便能登录成功。

image.png
image.png

6.3. 组件打包

众所周知,我们发布到npm肯定是构建产物,所以在publish前要对组件执行build操作,在根目录的package.json添加以下命令:

代码语言:json
复制
"build": "pnpm build:utils && pnpm build:core",
"build:core": "pnpm --filter small-color-ui build",
"build:utils": "pnpm --filter @small-color-ui/utils build",

因为有2个发布包,所以要对它们都要构建,其中pnpm --filter <package_name> <command>是pnpm的检索属性,它能执行指定的package目录下的某个命令。上面的 build:corebuild:utils 就是分别执行2个包的构建,再把2条命令整合到 build 中,完成发包前的组件构建流程。

6.4. 自动化发布流和生成发布记录

这里要借用到某个插件——changesets

它是一款切合pnpm体系下的一款管理版本控制和变更日志的工具,专注于多包存储库。虽然pnpm下暂时没有像lerna完善的发布流程工具,但changesets也算的上是官方推荐的一款,将就用吧。

changesets的执行流程大概可以理解为:生成临时的changelog → 消耗changelog生成组件的更新记录,并更新组件version → 发布组件

6.4.1. 安装changeset
代码语言:shell
复制
pnpm install @changesets/cli
6.4.2. 初始化changeset配置

根目录运行changeset init,会生成一个 .changeset 目录,里面会生成一个 changesetconfig 文件(linked字段改成你自己的包名):

代码语言:json
复制
{
  "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json",
  "changelog": "@changesets/cli/changelog",
  "commit": false,
  "fixed": [],
  "linked": [["@small-color-ui/*"]],
  "access": "restricted",
  "baseBranch": "main",
  "updateInternalDependencies": "patch",
  "ignore": [],
  "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
    "onlyUpdatePeerDependentsWhenOutOfRange": true
  }
}
6.4.3. 配置changeset发布流命令

然后在根目录的package.json添加以下命令:

代码语言:json
复制
"changeset": "changeset",
"update:version": "changeset version",
"release": "changeset publish",

其中:

  • changeset:生成临时的changelog
  • update:version:消耗changelog生成组件的更新记录,并更新组件version
  • release:发布组件
6.4.4. 生成changeset临时日志

执行命令:pnpm changeset,按提示输出,最后生成临时日志。

2023-01-06 17.34.19.gif
2023-01-06 17.34.19.gif
image.png
image.png

日志里面包含发版的组件包,版本更新类型(major | minor | patch),最下面带有更新内容。

6.4.5. 消耗日志

执行命令:pnpm update:version,临时日志被消耗,会在组件包生成CHANGELOG.md,另外,package.json的版本号也同步修改。

image.png
image.png
6.4.6. 发版

执行命令:pnpm release,更新组件会发布到npm。

image.png
image.png

7. eslint与prettier

到上面为止,我们已经完成在pnpm monorepo的完整开发到发布流程,但对于企业开发者来讲,代码仓库的质量也是追求的重要指标之一,我们现在把eslintprettier引入到项目中。

7.1. eslint

根目录安装:

代码语言:shell
复制
pnpm i -Dw eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-plugin-react

新建.eslintrc

代码语言:json
复制
{
  "env": {
    "node": true,
    "browser": true,
    "es2021": true
  },
  "extends": [
    "eslint:recommended",
    "plugin:react/recommended",
    "plugin:prettier/recommended",
    "plugin:@typescript-eslint/recommended"
  ],
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaFeatures": {
      "jsx": true
    },
    "ecmaVersion": "latest",
    "sourceType": "module"
  },
  "plugins": ["react", "@typescript-eslint", "prettier"],
  "rules": {
    "react/react-in-jsx-scope": "off",
    "react/display-name": 0
  }
}

最后,在根目录的package.json增加以下命令,一键检查代码:

代码语言:json
复制
"scripts": {
  "lint": "eslint --fix --ext .js,.tsx,ts packages"
},

7.2. prettier

根目录安装:

代码语言:shell
复制
pnpm i -Dw prettier eslint-config-prettier eslint-plugin-prettier

新建.prettierrc.js

代码语言:javascript
复制
module.exports = {
  // 一行最多 120 字符..
  printWidth: 120,
  // 使用 2 个空格缩进
  tabWidth: 2,
  // 不使用缩进符,而使用空格
  useTabs: false,
  // 行尾需要有分号
  semi: true,
  // 使用单引号
  singleQuote: true,
  // 对象的 key 仅在必要时用引号
  quoteProps: 'as-needed',
  // 末尾需要有逗号
  trailingComma: 'all',
  // 大括号内的首尾需要空格
  bracketSpacing: true,
  // jsx 标签的反尖括号需要换行
  jsxBracketSameLine: false,
  // 箭头函数,只有一个参数的时候,也需要括号
  arrowParens: 'always',
  // 每个文件格式化的范围是文件的全部内容
  rangeStart: 0,
  rangeEnd: Infinity,
  // 不需要写文件开头的 @prettier
  requirePragma: false,
  // 不需要自动在文件开头插入 @prettier
  insertPragma: false,
  // 使用默认的折行标准
  proseWrap: 'preserve',
  // 根据显示样式决定 html 要不要折行
  htmlWhitespaceSensitivity: 'css',
  // 换行符使用 lf
  endOfLine: 'lf',
};

8. git规范

8.1. git hooks

众所周知 Git 有很多的钩子函数,让我们在不同的阶段对代码进行不同的操作。我们可以在项目的.git/hooks目录中,找到所有的hooks的例子:

image.png
image.png

8.2. 配置代码提交规范

8.2.1. 工具
  • huskygit 钩子捕获
  • lint-staged:暂存区代码检查工具
8.2.2. 安装
代码语言:shell
复制
pnpm i -Dw husky lint-staged
8.2.3. 初始化husky

添加husky安装命令,执行完后会自动在package.json添加一条script:

代码语言:shell
复制
npm pkg set scripts.prepare="husky install"

接下来执行prepare命令,完成husky初始化,最终会在项目根路径生成.husky目录。

代码语言:shell
复制
pnpm prepare"
8.2.4. husky关联lint-staged

上面讲了,lint-staged会检查缓存区代码,但假如需要git hooks触发时执行检查操作,那么就要把lint-staged关联到husky中去了。

关联pre-commit hook

代码语言:shell
复制
pnpx husky add .husky/pre-commit "pnpx lint-staged"

完成后.husky目录如下:

image.png
image.png
8.2.5. 添加lint-staged检查逻辑

在package.json文件下添加如下代码:

代码语言:json
复制
"lint-staged": {
  "*.{js,jsx,ts,tsx}": [
    "prettier --write",
    "eslint --fix"
  ]
},

这里在触发代码检查会做两件事:1. 修复缓存区代码风格;2. 修复缓存区代码格式错误;

测试一下,OJBK了。

image.png
image.png

8.3. 配置提交message规范

对于提交信息的规范,当然是大名鼎鼎的Google AnguarJS 规范。 格式如下:

代码语言:txt
复制
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>

要完成上面的规范化提交格式,我们需要借用2个工具。

8.3.1. 工具
  • commitlint:commit 信息校验工具
  • commitizen:命令行交互插件
8.3.2. 安装
代码语言:shell
复制
pnpm i -Dw commitizen cz-conventional-changelog @commitlint/config-conventional @commitlint/cli
8.3.3. 配置commitlint

在根目录创建commitlint.config.js,并写入以下配置:

代码语言:javascript
复制
module.exports = { extends: ['@commitlint/config-conventional'] };

接下来,我们要在husky配置commit-msg钩子,让提交信息与commitlint关联起来:

代码语言:shell
复制
pnpx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"'

最后,在根目录的package.json添加配置:

代码语言:json
复制
"config": {
  "commitizen": {
    "path": "./node_modules/cz-conventional-changelog"
  }
},

至此,我们测试下,又OJBK了。。。因为commit信息不规范,所以被husky拦截了。

image.png
image.png
8.3.4. 配置commitizen

假如是我们纯粹输入commit message的话,要完全符合规范实属鸡肋,接下来,我们要使用命令交互式流程嵌入到commitlint中。

我们再增加一条script

代码语言:shell
复制
npm pkg set scripts.commit="cz"

然后运行pnpm commit命令,控制台交互如下:

image.png
image.png

10. 单元测试

对于规范的组件开源法则来讲,单测也是重要一环,它能保证组件的稳定性。由于单测是持续性建设的工作,这块日后有空再补齐。👻👻

结尾

好了好了,卷到这里又要和朋友们说拜拜,聪明的小伙伴已经跟着搭建起来了,赶紧去试试吧。。。

感谢大家阅览并欢迎纠错,欢迎大家关注本人公众号「似马非马」,一起玩耍起来!🌹🌹

github传送门

https://github.com/JohnnyZhangQiao/pnpm-monorepo-learn

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 开场
  • pnpm monorepo搭建
    • 1. 初始化项目
      • 1.1. 安装pnpm
      • 1.2. 初始化package.json
      • 1.3. 配置 .npmrc
      • 1.4. 创建工作空间
    • 2. 组件的package.json配置
      • 3. 关于依赖安装
        • 4. 生产.d.ts类型描述文件
          • 5. 打包配置
            • 6. 发布组件
              • 6.1. npm创建账号与组织
              • 6.2. 发布命令
              • 6.3. 组件打包
              • 6.4. 自动化发布流和生成发布记录
            • 7. eslint与prettier
              • 7.1. eslint
              • 7.2. prettier
            • 8. git规范
              • 8.1. git hooks
              • 8.2. 配置代码提交规范
              • 8.3. 配置提交message规范
            • 10. 单元测试
            • 结尾
            • github传送门
            相关产品与服务
            腾讯云代码分析
            腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,助力维护团队卓越代码文化。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档