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

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

网络异常,图片无法展示
|

开场

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

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

pnpm monorepo搭建

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

1. 初始化项目

1.1. 安装pnpm

npm install pnpm -g
复制代码

1.2. 初始化package.json

pnpm init
复制代码

1.3. 配置 .npmrc

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

shamefully-hoist=false
detect_chromedriver_version=true
strict-peer-dependencies=false
复制代码

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

1.4. 创建工作空间

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

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

.
└── packages
    ├── playground
    ├── small-color-ui
    └── utils
复制代码

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

packages:
  - 'packages/*'
复制代码

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

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

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

网络异常,图片无法展示
|

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

2. 组件的package.json配置

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

pnpm i -Sw react
复制代码

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

pnpm i -S react
复制代码

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

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

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

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

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

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

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

网络异常,图片无法展示
|

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

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

5. 打包配置

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

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即可。

网络异常,图片无法展示
|

6.2. 发布命令

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

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

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

网络异常,图片无法展示
|

6.3. 组件打包

众所周知,我们发布到npm肯定是构建产物,所以在publish前要对组件执行build操作,在根目录的package.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

pnpm install @changesets/cli
复制代码

6.4.2. 初始化changeset配置

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

{
  "$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添加以下命令:

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

其中:

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

6.4.4. 生成changeset临时日志

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

网络异常,图片无法展示
|

网络异常,图片无法展示
|

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

6.4.5. 消耗日志

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

网络异常,图片无法展示
|

6.4.6. 发版

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

网络异常,图片无法展示
|

7. eslint与prettier

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

7.1. eslint

根目录安装:

pnpm i -Dw eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-plugin-react
复制代码

新建.eslintrc

{
  "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增加以下命令,一键检查代码:

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

7.2. prettier

根目录安装:

pnpm i -Dw prettier eslint-config-prettier eslint-plugin-prettier
复制代码

新建.prettierrc.js

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的例子:

网络异常,图片无法展示
|

8.2. 配置代码提交规范

8.2.1. 工具

  • huskygit 钩子捕获
  • lint-staged:暂存区代码检查工具

8.2.2. 安装

pnpm i -Dw husky lint-staged
复制代码

8.2.3. 初始化husky

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

npm pkg set scripts.prepare="husky install"
复制代码

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

pnpm prepare"
复制代码

8.2.4. husky关联lint-staged

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

关联pre-commit hook

pnpx husky add .husky/pre-commit "pnpx lint-staged"
复制代码

完成后.husky目录如下:

网络异常,图片无法展示
|

8.2.5. 添加lint-staged检查逻辑

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

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

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

测试一下,OJBK了。

网络异常,图片无法展示
|

8.3. 配置提交message规范

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

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

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

8.3.1. 工具

  • commitlint:commit 信息校验工具
  • commitizen:命令行交互插件

8.3.2. 安装

pnpm i -Dw commitizen cz-conventional-changelog @commitlint/config-conventional @commitlint/cli
复制代码

8.3.3. 配置commitlint

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

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

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

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

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

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

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

网络异常,图片无法展示
|

8.3.4. 配置commitizen

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

我们再增加一条script

npm pkg set scripts.commit="cz"
复制代码

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

网络异常,图片无法展示
|

10. 单元测试

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

结尾

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

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

github传送门

github.com/JohnnyZhang…

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
1月前
|
缓存 前端开发 JavaScript
Vite 构建流程大揭秘:快速构建前端项目的秘密武器
Vite 构建流程大揭秘:快速构建前端项目的秘密武器
|
1月前
|
IDE jenkins Java
告别繁琐配置:Alibaba Cloud Toolkit插件打破Jenkins自动化部署的局限
告别繁琐配置:Alibaba Cloud Toolkit插件打破Jenkins自动化部署的局限
153 0
|
1月前
|
jenkins Java 持续交付
Jenkins,持续交付的利器:揭秘插件生态系统(一)
Jenkins,持续交付的利器:揭秘插件生态系统
|
1月前
|
前端开发 JavaScript API
Vite 3.0 正式发布,下一代前端构建工具!
Vite 3.0 正式发布,下一代前端构建工具!
|
存储 JSON 资源调度
10分钟带你从0到1搭建monorepo 工程化项目(一)
前言 大家好,我是Fly哥, 之前写博客的仓库,还是用的原生的html 和js 也没有引入 ts , 和一些工程化的东西, 所以自己重新搭建了一套前端项目架构 基于 lerna + yarn 的 monrepo的仓库, 主要是后面会学习输出的一些东西, 整个架子先搭建起来。 2d 和 3d 公共 util 的封装 个人 npm 包的发布 (rollup) 2d react 项目 搭建(vite) 3d react 项目 搭建 (webpack) 搭建一套基于webpack 5 的cli 每个项目都有一些特定的依赖, 但是也会有一些相同的依赖。比如eslint、 babel 的一些基础配置,
10分钟带你从0到1搭建monorepo 工程化项目(一)
|
1天前
|
资源调度 前端开发 JavaScript
前端工程化实践:Monorepo与Lerna管理
**前端工程化中,Monorepo和Lerna用于大型项目管理。Monorepo集纳所有项目,便于代码共享、版本控制和CI/CD。Lerna是Monorepo工具,管理多npm包,支持独立或共享版本。安装Lerna用`npm install --save-dev lerna`,初始化后可创建、管理包,通过`lerna bootstrap`、`lerna add`、`lerna publish`等命令协同工作。Lerna配置可在`lerna.json`,与CI/CD工具集成实现自动化。
3 0
|
1月前
|
Kubernetes jenkins 测试技术
Jenkins,持续交付的利器:揭秘插件生态系统(二)
Jenkins,持续交付的利器:揭秘插件生态系统
|
1月前
|
Devops Java 开发工具
【DevOps】(三)jekins插件下载和安装
【DevOps】(三)jekins插件下载和安装
19 0
|
1月前
|
数据安全/隐私保护
|
运维 供应链 前端开发
化繁为简,如何快速实现企业级私有 npm 包依赖管理服务?
化繁为简,如何快速实现企业级私有 npm 包依赖管理服务?
101 0

热门文章

最新文章