Teach you step by step how to build monorepo project architecture

highlight: github
theme: juejin

pnpm workspace practice

The project architecture adopts pnpm workspace + changelog + husky

github example address 1 – Vue + Nest full stack template

github example address 2 – Vue + TS front-end template

pnpm

Initialize the project and modify private:true in the package.json file generated by the car to prevent root directory publishing.

pnpm init

Create pnpm-workspace.yaml file in the root directory

Configure the monorepo workspace of pnpm. For example, here we configure blog as the main blog, play for testing, and packages under packages can have theme packages, component packages, tool packages, etc.

Note here that if it is a multi-project mode, the name in the root directory package.json is the project name (set it according to what you want)

The name in the package.json file in the sub-project needs to be @ + the name in the root directory package.json + / + the sub-project name

packages:
  - 'packages/*'
  - 'doc'

Specify the running version of Node and pnpm

{<!-- -->
    "engines": {<!-- -->
        "node": ">=20",
        "pnpm": ">=8"
    }
}

Next we switch to the packages directory and create subprojects
For example, I want to create a vitepress project here

cddoc

pnpm dlx vitepress init

Create a vue project and use

pnpm create vite

Only pnpm is allowed

If you don’t want others to use package managers such as yarn and npm, just add the following command to the package.json file

{<!-- -->
    "scripts": {<!-- -->
        "preinstall": "npx only-allow pnpm"
    }
}

As long as someone uses other package managers to install dependencies, an error will be reported!

Add dependencies

  • Global dependencies
pnpm add xxx -D -W
# or
pnpm add xxx -Dw

-W: workspace-root installs dependencies into global node_modules
-D: development dependencies

Dependency difference:
dependencies: production dependencies
devDependencies: development dependencies
peerDependencies: host dependencies (running main dependencies), specify the dependencies that need to be installed when using the current module package

  • local dependency
# The path is in the directory where dependencies need to be introduced
pnpm add xxx -D

# In the root directory @xxx/xxx is the name of the subproject
pnpm add xxx -F @xxx/xxx

Global startup command

The pnpm -C command can execute multiple commands in sequence, which allows us to run the script commands in package.json in the root directory

e.g.:

{<!-- -->
  "scripts":{<!-- -->
     "dev:docs": "pnpm -C ./packages/doc docs:dev",
  }
}

ESLint & amp; & amp; Prettier

Introduce dependencies

pnpm add eslint prettier -Dw

#Initialize eslint configuration
pnpm eslint --init

# Other dependencies
pnpm add -Dw @typescript-eslint/parser eslint-plugin-vue @typescript-eslint/eslint-plugin

# Resolve conflicts
pnpm add eslint-config-prettier eslint-plugin-prettier -Dw

resolve conflicts

.eslintrc.cjs

{<!-- -->
  extends: [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:vue/vue3-essential",
    //Put prettier at the end to give it a lower priority than eslint
    "plugin:prettier/recommended",
  ],
}

Add check command

{<!-- -->
    "lint": "eslint . --ext .vue,.js,.ts,.jsx,.tsx",
    "format": "prettier --write ./**/*.{vue,ts,tsx,js,jsx,css,less,scss,json,md}",
}

Internal package references

husky + lint-staged code pre-check

Introduce dependencies

pnpm add husky lint-staged -Dw

Add script commands to json

{<!-- -->
  "prepare": "husky install"
}

Run the command to create a pre-commit hook

#Manual run
pnpm prepare

#Add pre-commit hook
npx husky add .husky/pre-commit "npx --no-install lint-staged"

pre-commit

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

# This was added by myself
echo "--------- Code pre-check ---------"

npx --no-install lint-staged

To configure lint-staged, add the following configuration information in package.json

//Same level as scripts
{<!-- -->
  "lint-staged": {<!-- -->
    "*.{js,vue,ts,jsx,tsx}": [
      "prettier --write",
      "eslint --fix"
    ],
    "*.{html,css,less,scss,md}": [
      "prettier --write"
    ]
  },
}

Submission specifications

Introduce dependencies

pnpm add @commitlint/cli @commitlint/config-conventional cz-git -Dw

Add command

{<!-- -->
    "type":"module",
      "scripts": {<!-- -->
    "cz": "git cz",
  },
      "config": {<!-- -->
    "commitizen": {<!-- -->
      "path": "node_modules/cz-git"
    }
  },
}

We use it to limit submission information that does not comply with the specification. Create a commitlint.config.js file in the root directory – if an error is reported later, you need to change the file to .cjs and also modify some contents.

// commitlint.config.js

/** @type {import('cz-git').UserConfig} */
export default {<!-- -->
  extends: ["@commitlint/config-conventional"],
  rules: {<!-- -->
    "type-enum": [
      2,
      "always",
      [
        "feat",
        "fix",
        "docs",
        "style",
        "refactor",
        "test",
        "build",
        "chore",
        "perf",
        "ci",
        "revert",
      ],
    ],
    "type-case": [2, "always", "lower-case"],
    "type-empty": [2, "never"],
    "subject-full-stop": [2, "never", "."],
    "header-max-length": [2, "always", 100],
    "subject-case": [
      2,
      "never",
      ["sentence-case", "start-case", "pascal-case", "upper-case"],
    ],
  },
  prompt: {<!-- -->
    useEmoji: true,
    allowCustomIssuePrefix: true,
    allowEmptyIssuePrefix: true,
    confirmColorize: true,
    messages: {<!-- -->
      type: "Select the type you want to submit:",
      scope: "Select a submission scope (optional):",
      customScope: "Please enter a custom submission scope:",
      subject: "Fill in a short and concise description of the change:\
",
      body: "Fill in a more detailed description of the change (optional). Use '|' for line breaks:\
",
      breaking: "List non-compatible breaking changes (optional). Use '|' for line breaks:\
",
      footerPrefixesSelect: "Select the associated issue prefix (optional):",
      customFooterPrefix: "Enter custom issue prefix:",
      footer: "List related issues (optional) For example: #31, #I3244:\
",
      confirmCommit: "Do you want to submit or modify the commit?",
    },
    types: [
      {<!-- -->
        value: "feat",
        emoji: "?",
        name: "feat: new feature | A new feature",
      },
      {<!-- --> value: "fix", name: "fix: A bug fix" },
      {<!-- -->
        value: "docs",
        emoji: "",
        name: "docs: Documentation updates | Documentation only changes",
      },
      {<!-- -->
        value: "style",
        emoji: "",
        name: "style: code format | Changes that do not affect the meaning of the code",
      },
      {<!-- -->
        value: "refactor",
        emoji: "",
        name: "refactor: code refactoring | A code change that neither fixes a bug nor adds a feature",
      },
      {<!-- -->
        value: "perf",
        emoji: "",
        name: "perf: performance improvement | A code change that improves performance",
      },
      {<!-- -->
        value: "test",
        emoji: "",
        name: "test: test related | Adding missing tests or correcting existing tests",
      },
      {<!-- -->
        value: "build",
        emoji: "",
        name: "build: Build related | Changes that affect the build system or external dependencies",
      },
      {<!-- -->
        value: "ci",
        emoji: "",
        name: "ci: Continuous Integration | Changes to our CI configuration files and scripts",
      },
      {<!-- -->
        value: "revert",
        emoji: "",
        name: "revert: rollback code | Revert to a commit",
      },
      {<!-- -->
        value: "chore",
        emoji: "",
        name: "chore: Other changes | Other changes that do not modify src or test files",
      },
    ],
  },
};

Use husky to generate commit-msg file and verify submission information

npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"

commit-msg

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

# Added by myself
echo "--------- Perform commit-msg verification ---------"

npx --no-install commitlint --edit $1

changeset

Introduce dependencies and initialize

# -D development dependencies
# -w public dependencies
pnpm add -Dw @changesets/cli

# initialization
pnpm changeset init

After initialization, a .changeset folder will be generated in the root directory

Among them config.json is used as the default changeset configuration file

  • changelog: changelog generation method
  • commit: Don’t let changeset do git add for us when publishing
  • linked: Configure which packages should share versions
  • access: public and private security settings, restricted is recommended for intranet, public is used for open source
  • baseBranch: the main branch of the project
  • updateInternalDependencies: The measurement unit (magnitude) that ensures that the package that a certain package depends on undergoes an upgrade, and that the package also undergoes a version upgrade.
  • ignore: packages that do not need to change version
  • ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: Every time the version changes, there must be no reason to patch to upgrade the versions of the packages that depend on it to prevent major priority non-update problems.

changeset common commands and instructions

# Use changeset to record version modifications
npx changeset add

# Use changeset version to submit version modifications
npx changeset version

# Use changeset publish to send packages
npx changeset publish

The version number generally has three parts, separated by ., like X.Y.Z, where

  • X: major version number, incompatible major changes, major
  • Y: minor version number, functional changes, minor
  • Z: revision number, bug fix, patch

changeset version Submit version changes

.npmrc

pnpm commonly used

strict-peer-dependencies=false
shell-emulator=true
auto-install-peers=false