发布于 2026-01-05 0 阅读
0

使用 PNPM 工作区创建单体仓库

使用 PNPM 工作区创建单体仓库

客观的

使用 PNPM 包管理器及其工作区功能创建单体仓库。

与 yarn 工作空间相比,PNPM 工作空间的主要优势在于,常用包不会被提升到根目录,从而使所有工作空间包完全隔离。

使用的技术/功能

我们将要构建的单体仓库将具备以下特性。再次声明,这是我使用的工具集,您可以根据自己的喜好进行更改。

特征 使用的技术
软件包管理器 PNPM
编程语言 TypeScript
基本除毛 ESLint
代码格式化 更漂亮
预提交钩子验证器 沙哑
仅对暂存文件进行语法检查 棉絮分期
检查 git commit 主题 commitlint

先决条件

工具

您需要在电脑上正确安装以下软件。

PNPM 安装

  • 如果您已在系统中安装了最新的 v16.x 或更高版本的 Node.js,请使用以下命令启用 pnpm:
corepack enable
corepack prepare pnpm@latest --activate
Enter fullscreen mode Exit fullscreen mode

仓库基本设置

  • 如果需要,可以初始化 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
Enter fullscreen mode Exit fullscreen mode
  • 通过在 package.json 文件中设置 packageManager 属性,指定此项目要使用的最新 PNPM版本。
  npm pkg set packageManager="pnpm@9.12.3"
  pnpm -v
Enter fullscreen mode Exit fullscreen mode
  • 运行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]
Enter fullscreen mode Exit fullscreen mode

代码格式化程序

我选择使用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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode"
}
Enter fullscreen mode Exit fullscreen mode

绒毛

代码检查工具(Linter)会对代码进行静态分析,快速查找问题。ESLint 是目前最常用的 JavaScript 代码检查工具。

ESLint

  pnpm create @eslint/config@latest
Enter fullscreen mode Exit fullscreen mode
  • 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
Enter fullscreen mode Exit fullscreen mode
  • 使用忽略列表更新 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'],
  },
  ...
]
Enter fullscreen mode Exit fullscreen mode

将 Prettier 与 ESLint 集成

代码检查工具通常不仅包含代码质量规则,还包含风格规则。使用 Prettier 时,大多数风格规则都是不必要的,更糟糕​​的是——它们甚至可能与 Prettier 冲突!

我们将使用 Prettier 处理代码格式化问题,使用代码检查工具处理代码质量问题。因此,让我们让代码检查工具运行 Prettier 的代码风格规则。

  • 安装必要的插件
  pnpm add -D eslint-config-prettier eslint-plugin-prettier
Enter fullscreen mode Exit fullscreen mode
  • 将该元素添加到数组中eslintPluginPrettierRecommended作为最后一个元素。
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'

/** @type {import('eslint').Linter.Config[]} */
export default [
  ...,
  eslintPluginPrettierRecommended,
]
Enter fullscreen mode Exit fullscreen mode

更多信息请访问: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 ."
Enter fullscreen mode Exit fullscreen mode
  • 运行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
Enter fullscreen mode Exit fullscreen mode
  • 更新 package.json 文件并添加以下属性。这将对所有脚本文件运行 ESLint,对其他文件运行 Prettier。
  "lint-staged": {
    "**/*.{js,ts,tsx}": [
      "eslint --fix"
    ],
    "**/*": "prettier --write --ignore-unknown"
  },
Enter fullscreen mode Exit fullscreen mode

工作区配置

  • 创建pnpm-workspace.yaml文件并添加以下内容
touch pnpm-workspace.yaml
Enter fullscreen mode Exit fullscreen mode
packages:
  - 'apps/*'
  - 'packages/*'
Enter fullscreen mode Exit fullscreen mode
  • 在根目录下创建 apps 和 packages 目录。
mkdir apps packages
Enter fullscreen mode Exit fullscreen mode

示例包 - 通用

  • 创建一个可在工作区应用程序中使用的示例包。
cd packages
pnpm create vite common --template vanilla-ts
cd ../
pnpm install
npm pkg set scripts.common="pnpm --filter common"
Enter fullscreen mode Exit fullscreen mode
  • 使用以下内容更新 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)
Enter fullscreen mode Exit fullscreen mode
  • 删除示例文件
cd packages/common
rm -rf src/style.css src/counter.ts .gitignore
cd ../../
Enter fullscreen mode Exit fullscreen mode

图书馆模式

Vite 默认以应用模式构建资源,并将 index.html 作为入口文件。但我们希望应用将 main.ts 文件作为入口文件,因此我们需要更新 Vite 配置以支持此功能。

  • 在此之前,让我们安装 Vite 包,以便从库中自动生成类型定义。
pnpm common add -D vite-plugin-dts
Enter fullscreen mode Exit fullscreen mode
  • 创建 vite.config.ts 文件并按如下方式更新:
 touch packages/common/vite.config.ts
Enter fullscreen mode Exit fullscreen mode
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()],
})
Enter fullscreen mode Exit fullscreen mode

resolve属性允许我们使用绝对导入路径而不是相对路径。例如:

import { add } from 'src/utils/arithmetic'
Enter fullscreen mode Exit fullscreen mode
  • 使用我们脚本的入口文件和类型定义更新common包文件。package.json
{
 ...
 "main": "./dist/common.js",
 "types": "./dist/main.d.ts",
}
Enter fullscreen mode Exit fullscreen mode

示例应用 - 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"
Enter fullscreen mode Exit fullscreen mode
  • common通过更新 package.json 文件,将该软件包作为依赖项安装到我们的 Web 应用程序中web-app
"dependencies": {
 "common": "workspace:*",
 ...
 }
Enter fullscreen mode Exit fullscreen mode
  • 再次运行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
Enter fullscreen mode Exit fullscreen mode
  • 运行pnpm app dev并检查该common软件包 util 是否已成功链接到应用程序。

就这样,我们已经成功地从零开始创建了一个支持 TypeScript 的 PNPM 单体仓库。

开发者模式

  • 大多数情况下,你只需要构建common一次软件包,然后在仓库应用中使用它。但是,如果你正在积极地修改common软件包,并希望立即在“web-app”中看到这些更改,那么你不可能common每次更改都重新构建应用。

为了避免这种情况,让我们common以监视模式运行该软件包,以便代码中的任何更改都会自动重新构建并实时反映在“web-app”中。

  • 在不同的终端中运行这些命令。
pnpm common build --watch
pnpm web-app dev
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
  • 这就是最终的 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,
]
Enter fullscreen mode Exit fullscreen mode
  • 您可以随意删除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