使用PNPM工作区创建monorepo
#javascript #typescript #monorepo

客观的

使用PNPM软件包管理器及其工作区功能创建monorePo。

与纱线工作空间相比,PNPM工作区的主要优点不悬浮到根目录中,从而使所有工作区包完全隔离。

使用的技术/功能

我们将要构建的MonorePo将具有以下功能。同样,这是我的一组工具,可以根据您的喜好随意更改它。

功能 使用的技术
软件包管理器 pnpm
编程语言 打字稿
基本鳞片 ES Lint
代码格式 漂亮
预加入挂钩验证器 沙哑
linting仅上演文件 绒毛阶段
lint git commit主题 commitlint

先决条件

工具

您需要在计算机上正确安装以下内容。

PNPM安装

  • 如果您在系统中安装了最新的v16.x或更大的节点版本,则使用以下CMD启用PNPM
corepack enable 
corepack prepare pnpm@latest --activate

回购基本设置

  • 初始git(如果需要),并在readme.md中使用一些信息执行节点版本。
  mkdir pnpm-monorepo
  cd pnpm-monorepo
  pnpm init
  git init
  echo -e "node_modules" > .gitignore
  npm pkg set engines.node=">=18.16.1" // Use the same node version you installed
  echo "#PNPM monorepo" > README.md

代码格式

我要使用Prettier格式化代码。格式化有助于我们为每个开发人员保持代码统一。

安装

让我们安装插件并设置一些默认值。在这里,我将单个报价设置为真,请根据您的喜好进行更新。

  pnpm add -D prettier
  echo '{\n  "singleQuote": true\n}' > .prettierrc.json
  echo -e ".husky\n.vscode\n.git\ncoverage\ndist\nnode_modules\npublic\npackage.json\npnpm-lock.yaml" > .prettierignore

我还获得了一些自由,包括我们将稍后将安装和使用的一些忽略文件中的一些包裹路径。

VS代码插件

  • 如果您使用的是VS代码,请导航到Extensions并搜索Prettier - Code formatter并安装扩展名。

扩展链接:https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode

  • 让我们更新工作空间以使用漂亮的默认格式格式,并在保存上自动格式化文件。

  • 创建工作区设置JSON并使用以下内容进行更新。

mkdir .vscode && touch .vscode/settings.json
{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
}

覆盖

linter静态分析您的代码以快速发现问题。 ES Lint是覆盖JavaScript代码的最喜欢的工具。

埃什特

  pnpm create @eslint/config
  • ESLINT会要求您根据您的需求设置一组问题来设置Linter。 这是我为此项目选择的配置。
? How would you like to use ESLint? …
  To check syntax only
❯ To check syntax and find problems
  To check syntax, find problems, and enforce code style

? 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

? What format do you want your config file to be in? …
❯ JavaScript
  YAML
  JSON

The config that you`ve selected requires the following dependencies:
@typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest

? Would you like to install them now? › No / Yes
- Yes

? Which package manager do you want to use? …
  npm
  yarn
❯ pnpm
  • 将根属性设置为es lint配置中的true。这将确保在此处停止覆盖配置泡泡。
module.exports = {
  root: true,
  ...
}
  • 创建Eslintignore文件,以让Eslint知道要不格式的文件。
  touch .eslintignore
  echo -e ".husky\n.vscode\n.git\ncoverage\ndist\nnode_modules\n*.config.ts\n.eslintrc.cjs\npackage.json\npnpm-lock.yaml" > .eslintignore

将更漂亮的Eslint整合

衬里通常不仅包含代码质量规则,还包含风格规则。使用更漂亮时,大多数风格规则都是不必要的,但是更糟糕的是 - 它们可能与更漂亮的相冲突!

我们将使用更漂亮的代码格式问题,以及用于代码质量问题的Linter。

  • 安装必要的插件
  pnpm add -D eslint-config-prettier eslint-plugin-prettier
  • plugin:prettier/recommended添加为的最后一个元素在Eslintrc.js中的扩展属性中
module.exports = {
  extends: [..., 'plugin:prettier/recommended'],
}

有关此信息的更多信息:https://prettier.io/docs/en/integrating-with-linters.html

  • 让我们创建用于在package.json文件中运行linter和更漂亮的脚本。
  npm pkg set scripts.lint="eslint ." 
  npm pkg set scripts.format="prettier --write ."
  • 运行pnpm lint cmd以运行eslint和pnpm format cmd以格式化文件。

预加入挂钩验证

即使我们添加了所有这些衬里和格式化机制来维护代码质量,我们也不能期望所有开发人员在每次推动代码时每次使用相同的编辑器并执行lintformat命令。

自动化我们需要某种预加入挂钩验证。那就是huskylint-staged插件的位置,让我们安装并设置它们。

  • 安装沙哑,commitlint和lint阶段的NPM软件包,并将其初始化,如下所示,
  pnpm add -D @commitlint/cli @commitlint/config-conventional
  echo -e "module.exports = { extends: ['@commitlint/config-conventional'] };" > commitlint.config.cjs
  pnpm add -D husky lint-staged
  npx husky install
  npx husky add .husky/pre-commit "pnpm lint-staged"
  npx husky add .husky/commit-msg  'npx --no -- commitlint --edit ${1}'
  npm pkg set scripts.prepare="husky install"
  • 更新package.json文件并包括以下属性。这将在所有脚本文件上运行ESLINT,并在其他文件上更漂亮。
  "lint-staged": {
    "**/*.{js,ts,tsx}": [
      "eslint --fix"
    ],
    "**/*": "prettier --write --ignore-unknown"
  },

工作区配置

  • 创建pnpm-workspace.yaml文件并添加以下内容
touch pnpm-workspace.yaml
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"
  • 清理示例文件并创建一个简单的isblank util。

使用以下内容更新main.ts文件,

/* 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

库模式

vite默认情况下,用index.html作为条目文件以应用程序模式构建资产。但是我们希望我们的应用程序将我们的main.ts文件公开为输入文件,所以让我们更新Vite配置以支持该文件。

  • 在此之前,让我们安装Vite软件包以自动从库中生成类型定义。
pnpm common add -D vite-plugin-dts
  • 创建vite.config.ts文件并像这样更新
 touch 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属性可帮助我们使用绝对导入路径而不是相对。

for ex:

import { add } from 'src/utils/arithmetic'
  • 使用我们脚本的输入文件以及打字更新常见的package.json文件。
{
 ...,
 "main": "./dist/common.js",
 "types": "./dist/main.d.ts",
}

示例应用程序 - 网络应用

  • 让我们创建一个可以利用Workspace软件包common的示例应用程序。
cd apps
pnpm create vite web-app --template react-ts
cd ../
pnpm install
npm pkg set scripts.app="pnpm --filter web-app"
  • 通过更新Web-App package.json,将通用软件包作为依赖项安装为依赖关系。
"dependencies": {
 "common": "workspace:*",
 ...,
 }
  • 再次运行pnpm install,以便Web-app可以与工作区中存在的常见软件包并运行pnpm common build,以便可以通过Web-App Server找到通用软件包。

  • 如下所示,更新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并检查是否成功链接到应用程序。

就是这样。我们已经成功地创建了一个来自Scratch的PNPM MonorePo,并获得了打字稿支持。

开发模式

  • 大多数情况下,您只需要一次构建common软件包即可在Repo应用中使用它。但是,如果您正在积极地对common软件包进行更改,并希望立即在Web应用中看到,您将无法一次又一次地构建common应用程序。

为了避免这种情况,让我们以观察模式运行common包,以便代码中的任何更改都会自动重建并在Web-App中进行实时反思。

  • 在不同终端运行这些命令。
pnpm common build --watch
pnpm web-app dev

优点:

  • 您所有的代码都将在一个单一的回购中,并适当隔离。
  • 仅需要一次时间来设置回购,并使用适当的覆盖,格式和预加入挂钩验证所有软件包将扩展相同的配置。
  • 所有包装都将具有类似的设置,外观和感觉。

尖端:

  • 请查看我的博客有关创建TS Util libraryReact app的博客,用于创建用所有铃铛和哨子创建回购软件包。忽略这些软件包已经在此Monorepo的根工作空间中处理的那些软件包中更漂亮的预订挂钩验证。

对于Linter,如果您对根工作区中存在的基本覆盖物很好,则不必在包装中做任何特别的事情。但是,对于像React这样的应用程序,我们将提供更多插件来填充React库。

ex:

module.exports = {
  root: true,
  env: { browser: true, es2020: true },
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:react-hooks/recommended',
  ],
  ignorePatterns: ['dist', '.eslintrc.cjs'],
  parser: '@typescript-eslint/parser',
  plugins: ['react-refresh'],
  rules: {
    'react-refresh/only-export-components': [
      'warn',
      { allowConstantExport: true },
    ],
  },
};

您可以像这样保持它本身,否则您可以通过简单地删除根属性并删除重复项来扩展根。

module.exports = {
  extends: ['plugin:react-hooks/recommended'],
  plugins: ['react-refresh'],
  rules: {
    'react-refresh/only-export-components': [
      'warn',
      { allowConstantExport: true },
    ],
  },
};

样品回购

该系列的代码托管在github here

请看一下GitHub回购,让我知道您的反馈,在评论部分中查询。