使用 PNPM 工作区创建单体仓库
客观的
使用 PNPM 包管理器及其工作区功能创建单体仓库。
与 yarn 工作空间相比,PNPM 工作空间的主要优势在于,常用包不会被提升到根目录,从而使所有工作空间包完全隔离。
使用的技术/功能
我们将要构建的单体仓库将具备以下特性。再次声明,这是我使用的工具集,您可以根据自己的喜好进行更改。
| 特征 | 使用的技术 |
|---|---|
| 软件包管理器 | PNPM |
| 编程语言 | TypeScript |
| 基本除毛 | ESLint |
| 代码格式化 | 更漂亮 |
| 预提交钩子验证器 | 沙哑 |
| 仅对暂存文件进行语法检查 | 棉絮分期 |
| 检查 git commit 主题 | commitlint |
先决条件
工具
您需要在电脑上正确安装以下软件。
PNPM 安装
- 如果您已在系统中安装了最新的 v16.x 或更高版本的 Node.js,请使用以下命令启用 pnpm:
corepack enable
corepack prepare pnpm@latest --activate
- 如果您本地系统使用的是较低版本的 Node.js,请查看此页面了解其他安装方法:https://pnpm.io/installation
仓库基本设置
- 如果需要,可以初始化 git,并在 README.md 中使用一些信息强制指定 node 版本。
mkdir pnpm-monorepo
cd pnpm-monorepo
pnpm init
git init
echo -e "node_modules" > .gitignore
npm pkg set engines.node=">=22.11.0" // Use the same node version you installed
npm pkg set type="module"
echo "#PNPM monorepo" > README.md
- 通过在 package.json 文件中设置 packageManager 属性,指定此项目要使用的最新 PNPM版本。
npm pkg set packageManager="pnpm@9.12.3"
pnpm -v
- 运行
pnpm -v此命令检查所需版本的 PNPM 是否已正确安装。如果您的系统中没有该版本,系统会自动提示您从网上下载,只需输入y下载链接即可。
! Corepack is about to download https://registry.npmjs.org/pnpm/-/pnpm-9.12.3.tgz
? Do you want to continue? [Y/n]
代码格式化程序
我选择使用Prettier来格式化代码。格式化有助于确保每个开发人员都能看到统一的代码。
安装
- 我们来安装插件并设置一些默认值。这里我将单引号设置为 true,您可以根据自己的喜好进行修改。
pnpm add -D prettier
echo '{\n "singleQuote": true\n}' > .prettierrc.json
echo -e "coverage\npublic\ndist\npnpm-lock.yaml\npnpm-workspace.yaml" > .prettierignore
VS Code 插件
- 如果您使用的是 VS Code,请导航至 [此处应填写 VS Code 的路径]
Extensions,搜索Prettier - Code formatter并安装该扩展程序。
扩展链接:https://marketplace.visualstudio.com/items? itemName=esbenp.prettier-vscode
-
让我们更新工作区,使用 Prettier 作为默认格式化程序,并在保存时自动格式化文件。
-
创建 VS Code 工作区设置 JSON 并更新以下内容。
mkdir .vscode && touch .vscode/settings.json
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
绒毛
代码检查工具(Linter)会对代码进行静态分析,快速查找问题。ESLint 是目前最常用的 JavaScript 代码检查工具。
ESLint
pnpm create @eslint/config@latest
- ESLint 会询问你一系列问题,以便根据你的需求设置代码检查工具。这是我为这个项目选择的配置。
? How would you like to use ESLint? …
To check syntax only
❯ To check syntax and find problems
? What type of modules does your project use? …
❯ JavaScript modules (import/export)
CommonJS (require/exports)
None of these
? Which framework does your project use? …
React
Vue.js
❯ None of these
Does your project use TypeScript? › No / Yes
- Yes
Where does your code run?
✔ Browser
Node
The config that you`ve selected requires the following dependencies:
eslint, globals, @eslint/js, typescript-eslint
? Would you like to install them now? › No / Yes
- Yes
? Which package manager do you want to use? …
npm
yarn
❯ pnpm
bun
- 使用忽略列表更新 eslint 配置,让 ESLint 知道哪些文件不应该被格式化。
/** @type {import('eslint').Linter.Config[]} */
export default [
...
{
// Note: there should be no other properties in this object
ignores: ['coverage', '**/public', '**/dist', 'pnpm-lock.yaml', 'pnpm-workspace.yaml'],
},
...
]
将 Prettier 与 ESLint 集成
代码检查工具通常不仅包含代码质量规则,还包含风格规则。使用 Prettier 时,大多数风格规则都是不必要的,更糟糕的是——它们甚至可能与 Prettier 冲突!
我们将使用 Prettier 处理代码格式化问题,使用代码检查工具处理代码质量问题。因此,让我们让代码检查工具运行 Prettier 的代码风格规则。
- 安装必要的插件
pnpm add -D eslint-config-prettier eslint-plugin-prettier
- 将该元素添加到数组中
eslintPluginPrettierRecommended作为最后一个元素。
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
/** @type {import('eslint').Linter.Config[]} */
export default [
...,
eslintPluginPrettierRecommended,
]
更多信息请访问:https://prettier.io/docs/en/integrating-with-linters.html
- 让我们在 package.json 文件中创建运行 linter 和 prettier 的脚本。
npm pkg set scripts.lint="eslint ."
npm pkg set scripts.format="prettier --write ."
- 运行
pnpm lintcmd 命令来运行 ESLint,并pnpm format运行 cmd 命令来格式化文件。
预提交钩子验证
即使我们添加了所有这些代码检查和格式化机制来维护代码质量,我们也不能指望所有开发人员在每次推送代码时都使用相同的编辑器并执行lint相应的命令。format
为了实现自动化,我们需要某种提交前钩子验证。这时husky和lint-staged插件就派上用场了,让我们来安装和设置它们。
- 安装 husky、commitlint 和 lint-staged 这三个 NPM 包,并按如下所示进行初始化。
pnpm add -D @commitlint/cli @commitlint/config-conventional
echo -e "export default { extends: ['@commitlint/config-conventional'] };" > commitlint.config.mjs
pnpm add -D husky lint-staged
pnpm exec husky init
echo "pnpm lint-staged" > .husky/pre-commit
echo "npx --no -- commitlint --edit \${1}" > .husky/commit-msg
- 更新 package.json 文件并添加以下属性。这将对所有脚本文件运行 ESLint,对其他文件运行 Prettier。
"lint-staged": {
"**/*.{js,ts,tsx}": [
"eslint --fix"
],
"**/*": "prettier --write --ignore-unknown"
},
工作区配置
- 创建
pnpm-workspace.yaml文件并添加以下内容
touch pnpm-workspace.yaml
packages:
- 'apps/*'
- 'packages/*'
- 在根目录下创建 apps 和 packages 目录。
mkdir apps packages
示例包 - 通用
- 创建一个可在工作区应用程序中使用的示例包。
cd packages
pnpm create vite common --template vanilla-ts
cd ../
pnpm install
npm pkg set scripts.common="pnpm --filter common"
- 使用以下内容更新 main.ts 文件,以创建一个简单的 isBlank 工具。
/* eslint-disable @typescript-eslint/no-explicit-any */
export const isEmpty = (data: any) => data === null || data === undefined
export const isObject = (data: any) => data && typeof data === 'object'
export const isBlank = (data: any) =>
isEmpty(data) ||
(Array.isArray(data) && data.length === 0) ||
(isObject(data) && Object.keys(data).length === 0) ||
(typeof data === 'string' && data.trim().length === 0)
- 删除示例文件
cd packages/common
rm -rf src/style.css src/counter.ts .gitignore
cd ../../
图书馆模式
Vite 默认以应用模式构建资源,并将 index.html 作为入口文件。但我们希望应用将 main.ts 文件作为入口文件,因此我们需要更新 Vite 配置以支持此功能。
- 在此之前,让我们安装 Vite 包,以便从库中自动生成类型定义。
pnpm common add -D vite-plugin-dts
- 创建 vite.config.ts 文件并按如下方式更新:
touch packages/common/vite.config.ts
import { defineConfig } from 'vite'
import { resolve } from 'path'
import dts from 'vite-plugin-dts'
// https://vitejs.dev/config/
export default defineConfig({
build: { lib: { entry: resolve(__dirname, 'src/main.ts'), formats: ['es'] } },
resolve: { alias: { src: resolve('src/') } },
plugins: [dts()],
})
该resolve属性允许我们使用绝对导入路径而不是相对路径。例如:
import { add } from 'src/utils/arithmetic'
- 使用我们脚本的入口文件和类型定义更新
common包文件。package.json
{
...
"main": "./dist/common.js",
"types": "./dist/main.d.ts",
}
示例应用 - Web 应用
- 创建一个可以使用 workspace 包的示例应用程序
common。
cd apps
pnpm create vite web-app --template react-ts
cd ../
pnpm install
npm pkg set scripts.app="pnpm --filter web-app"
common通过更新 package.json 文件,将该软件包作为依赖项安装到我们的 Web 应用程序中web-app。
"dependencies": {
"common": "workspace:*",
...
}
-
再次运行
pnpm install,以便“web-app”可以对工作区中存在的通用包建立符号链接。 -
运行此命令
pnpm common build,以便 Web 应用程序服务器可以找到通用软件包。 -
更新
App.tsx如下内容:
import { isBlank } from 'common'
const App = () => {
return (
<>
<p>undefined isBlank - {isBlank(undefined) ? 'true' : 'false'}</p>
<p>false isBlank - {isBlank(false) ? 'true' : 'false'}</p>
<p>true isBlank - {isBlank(true) ? 'true' : 'false'}</p>
<p>Empty object isBlank - {isBlank({}) ? 'true' : 'false'}</p>
</>
)
}
export default App
- 运行
pnpm app dev并检查该common软件包 util 是否已成功链接到应用程序。
就这样,我们已经成功地从零开始创建了一个支持 TypeScript 的 PNPM 单体仓库。
开发者模式
- 大多数情况下,你只需要构建
common一次软件包,然后在仓库应用中使用它。但是,如果你正在积极地修改common软件包,并希望立即在“web-app”中看到这些更改,那么你不可能common每次更改都重新构建应用。
为了避免这种情况,让我们common以监视模式运行该软件包,以便代码中的任何更改都会自动重新构建并实时反映在“web-app”中。
- 在不同的终端中运行这些命令。
pnpm common build --watch
pnpm web-app dev
Linter 更新
-
现在我们的单体仓库里有一个 TypeScript 工具库和一个 React 应用。ESLint 团队在 v9.x 中建议所有包使用同一个代码检查文件,但这与单体仓库架构并不兼容,详情请参阅相关讨论。ESLint 团队意识到了这一点,并开发了一个实验性功能来支持多个配置文件,该功能将在 v10.x 中稳定运行。您可以启用该功能并进行尝试,但不建议在生产环境中使用。
-
所以现在我们将尝试把使用 vite starter 模板创建的 web 应用程序 linting 配置合并到根配置文件中,方法是将 linting 依赖项从 web 应用程序移动到根目录并更新配置文件。
pnpm add -D -w eslint-plugin-react-hooks eslint-plugin-react-refresh eslint-plugin-react
pnpm app remove eslint-plugin-react-hooks eslint-plugin-react-refresh eslint @eslint/js globals typescript-eslint
- 这就是最终的 eslint 配置示例,
import globals from 'globals'
import pluginJs from '@eslint/js'
import tseslint from 'typescript-eslint'
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import react from 'eslint-plugin-react'
/** @type {import('eslint').Linter.Config[]} */
export default [
{ files: ['**/*.{js,mjs,cjs,ts}'] },
{
languageOptions: { ecmaVersion: 2020, globals: globals.browser },
},
{
// Note: there should be no other properties in this object
ignores: ['coverage', '**/public', '**/dist', 'pnpm-lock.yaml', 'pnpm-workspace.yaml'],
},
{
files: ['apps/web-app/**/*.{ts,tsx}'],
settings: { react: { version: '18.3' } },
languageOptions: {
// other options...
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: './apps/web-app',
},
},
plugins: {
react,
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...react.configs.recommended.rules,
...react.configs['jsx-runtime'].rules,
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
},
},
pluginJs.configs.recommended,
...tseslint.configs.recommended,
eslintPluginPrettierRecommended,
]
-
您可以随意删除
eslint.config.jsweb 应用程序包中的 present,因为它不再需要了,或者将其注释掉并保留以供参考。 -
请查看 Vite 创建的 Web 应用程序 Readme.md 文件,并根据需要遵循其中的建议。
优势:
- 所有代码都将放在一个单独的代码仓库中,并进行适当的隔离。
- 只需一次性设置仓库,即可进行正确的代码检查、格式化和提交前钩子验证,工作区软件包将扩展这些功能。
- 所有软件包的配置、外观和使用体验都将相似。
尖端:
- 请查看我的博客,了解如何创建一个TS Util 库和一个 React 应用,用于创建功能齐全的仓库包。请忽略这些包中的 pre-commit hook 验证,因为它们已在此 mono 仓库的根工作区中处理完毕。
示例仓库
本文代码托管在 GitHub 上,链接在此。
请查看Github代码库,并在评论区留下您的反馈和疑问。
文章来源:https://dev.to/vinomanick/create-a-monorepo-using-pnpm-workspace-1ebn