多个轻量级项目monorepo管理方案

1,038 阅读5分钟

引言

       Monorepo是一种管理代码库的方式,它将多个相关代码仓库合并成一个大型的代码仓库,并使用工具集来管理和协调它们之间的依赖关系。Monorepo可以提高代码共享和复用的效率,减少代码冗余和过时,简化代码构建和部署流程,促进跨团队和跨项目的协作和协调。

monorepo适用场景

  • 长期维护需要考虑:Monorepo往往包含大量代码和依赖关系。因此,在管理和维护Monorepo时必须谨慎分析和仔细考虑,包括代码提交流程、版本管理、构建和测试等方面的细节,以确保代码的稳定性和可靠性。

  • 依赖管理和版本控制:Monorepo中的模块使用相同的代码仓库,并相互依赖。因此,必须确保良好的依赖管理和版本控制,以避免潜在的问题和错误。同时,还应该要考虑到各种依赖关系之间的冲突和版本问题,确保其互相之间能够兼容并且正常运行。

  • 构建和测试:构建和测试是保证代码质量的重要步骤,对于Monorepo也是如此。需要以高质量的方式编写测试用例,并且确保构建工具链能够正常工作。使用适当的持续集成(CI)工具来管理和自动化构建和测试流程,以一致而可靠的方式构建和运行代码。

  • 团队合作和协调:使用Monorepo需要各个团队之间的密切协作和协调,避免不同的修改之间产生冲突、互相干扰或损害项目的整体稳定性。团队需要严格遵守规范化的提交流程以及代码审查机制,确保代码的及时更新和维护。

  • 开放沟通和信息共享:使用Monorepo建议开放沟通和信息共享,增强透明度,确保各个团队之间的信息可以互相共享,这样可以促进协同工作,更有效的协作和讨论。

     总的来说,monorepo适用场景

  1. 中大型复杂项目中多个模块既可以独立发布适用,又可以内部相互引用作为整体发布,比如:babel、 vue3等;
  2. 开发许多轻量的项目(页面不会很多),各个项目又互相独立,需要单独部署,但是各个项目之间又有许多相同的功能,比如合并、登录等。

multirepo 与 monorepo 优缺点对比

monorepo开发管理

scripts脚本命令管理

通过bin命令式交互读取执行

"scripts": {
    "task": "node ./bin/index.js task",
    "commit": "git add . && cz-customizable",
    "postinstall": "husky install",
    "preinstall": "node ./bin/preinstall.js",
    "format": "prettier --write \"**/*.{js,ts,vue}\""
}

// pnpm task

代码目录:/bin/index.js

#!/usr/bin/env node// 引用commander 处理命令行参数和选项 --helpconst program = require('commander')const pkg = require('../package.json')const cli = require('../cli')// 设置版本号program.version(pkg.version, '-v --version').usage('<command> [options]')// 初始化命令program  .command('task')  .description('运行项目')  // .option('-t, --template [template]', 'JSON数据 http地址或者是文件相对路径')  .action((options) => {    cli.exec('task', options)  })program.parse(process.argv)

/cli/index.js

class CliScript {
    async exec(name, options) {
        await require(`./${name}`).setup(options)
    }
}
module.exports = new CliScript()

/cli/task.js

const path = require('path')
const fs = require('fs')
const shell = require('shelljs')
// const fs = require('fs-extra')

const PACKAGES_DIR = path.join(__dirname, '..', 'packages')
class Task {
    subject = '' //当前选中的子目录,
    selectScript = '' //当前选中的命令
    async setup(options) {
        const dirList = fs.readdirSync(PACKAGES_DIR)
        this.selectSubject(dirList)
    }

    async selectSubject(subjects) {
        // console.log(20000, await require('inquirer'))
        const inquirer = await require('inquirer')
        const answers = await inquirer.prompt([
            {
                type: 'list',
                name: 'subject',
                message: '请选择子项目',
                choices: subjects
      }
    ])
        this.subject = answers.subject
        this.getPackageCommander(
            path.join(__dirname, '..', `packages/${answers.subject}/package.json`)
        )
    }

    async getPackageCommander(subjectPackage) {
        const subjectPkg = await require(subjectPackage)
        const pkgCommander = subjectPkg.scripts || {}
        const inquirer = await require('inquirer')
        const answers = await inquirer.prompt([
            {
                type: 'list',
                name: 'script',
                message: '请选择当前目录命令',
                choices: Object.keys(pkgCommander)
      }
    ])
        this.selectScript = answers.script
        this.runCommander(this.subject, this.selectScript)
    }

    async runCommander(subjectPath, selectScript) {
        shell.exec(`pnpm -C ./packages/${subjectPath} ${selectScript}`)
    }
}
module.exports = new Task()

执行效果如下:

统一配置:增加commit提交规范 以及 统一配置执行 - Eslint,Typescript 与 Babel规则

1、.cz-config.js、commitlint.config.js等配置对commit必要格式和内容进行规范。

{
    "name": "monorepo-demo",
    "version": "1.0.0",
    "description": "",
    "scripts": {
        "task": "node ./bin/index.js task",
        "commit": "git add . && cz-customizable",
        "postinstall": "husky install",
        "preinstall": "node ./bin/preinstall.js",
        "format": "prettier --write \"**/*.{js,ts,vue}\""
    },
    "author": "",
    "license": "ISC",
    "devDependencies": {
        "@commitlint/cli": "^17.6.1",
        "@commitlint/config-conventional": "^17.6.1",
        "@rollup/plugin-commonjs": "^21.0.1",
        "@rollup/plugin-json": "^6.0.0",
        "@rollup/plugin-node-resolve": "^13.0.6",
        "@rollup/plugin-terser": "^0.4.1",
        "@vue/compiler-sfc": "^3.2.21",
        "chalk": "4.0.0",
        "commitizen": "^4.3.0",
        "commitlint-config-cz": "^0.13.3",
        "commitlint-config-git-commit-emoji": "^1.0.0",
        "cz-conventional-changelog": "^3.3.0",
        "cz-customizable": "^7.0.0",
        "fast-glob": "^3.2.7",
        "husky": "^8.0.3",
        "inquirer": "8.2.0",
        "lint-staged": "^13.2.1",
        "minimist": "^1.2.8",
        "nanospinner": "^1.1.0",
        "npm-run-all": "^4.1.5",
        "rollup": "^2.79.1",
        "rollup-plugin-typescript2": "^0.30.0",
        "sass": "^1.43.2",
        "sucrase": "^3.20.2",
        "ts-morph": "^12.2.0",
        "typescript": "^4.9.5"
    },
    "husky": {
        "hooks": {
            "pre-commit": "lint-staged"
        }
    },
    "lint-staged": {
        "**/*.{js,ts,vue}": ["prettier --write", "eslint --fix", "git add"]
    },
    "config": {
        "commitizen": {
            "path": "cz-conventional-changelog"
        }
    },
    "volta": {
        "node": "16.14.0"
    }
}

增加commit格式校验.cz-config.js

module.exports = {
    types: [
        {
            value: ':sparkles: feat',
            name: '✨ feat:     新功能'
    },
        {
            value: ':bug: fix',
            name: '🐛 fix:      修复bug'
    },
        {
            value: ':package: build',
            name: '📦️ build:    打包'
    },
        {
            value: ':zap: perf',
            name: '⚡️ perf:     性能优化'
    },
        {
            value: ':tada: release',
            name: '🎉 release:  发布正式版'
    },
        {
            value: ':lipstick: style',
            name: '💄 style:    代码的样式美化'
    },
        {
            value: ':recycle: refactor',
            name: '♻️  refactor: 重构'
    },
        {
            value: ':pencil2: docs',
            name: '✏️  docs:     文档变更'
    },
        {
            value: ':white_check_mark: test',
            name: '✅ test:     测试'
    },
        {
            value: ':rewind: revert',
            name: '⏪️ revert:   回退'
    },
        {
            value: ':rocket: chore',
            name: '🚀 chore:    构建/工程依赖/工具'
    },
        {
            value: ':construction_worker: ci',
            name: '👷 ci:       CI related changes'
    }
  ],
    messages: {
        type: '请选择提交类型(必填)',
        customScope: '请输入文件修改范围(必填)',
        subject: '请简要描述提交(必填)',
        body: '请输入详细描述(可选)',
        breaking: '列出任何BREAKING CHANGES(可选)',
        footer: '请输入要关闭的issue(可选)',
        confirmCommit: '确定提交此说明吗?'
    },
    allowCustomScopes: true,
    // 跳过问题
    skipQuestions: ['body', 'footer'],
    subjectLimit: 72
}

辅助提交拦截commitlint.config.js

module.exports = {  extends: ['git-commit-emoji', 'cz']}

锁定环境配置:Volta

  • Volta 是一个 JavaScript 工具管理器,它可以让我们轻松地在项目中锁定 node,npm 和 yarn 的版本。你只需在安装完 Volta 后,在项目的根目录中执行 volta pin 命令,那么无论您当前使用的 node 或 npm(yarn)版本是什么,volta 都会自动切换为您指定的版本。

执行如下命令

 volta pin yarn@1.22.4

volta pin node@16.14.0

会在当前根目录的package.json中追加如下内容,这样后面从其他目录切换回当前目录后,会自动切换对应的绑定工具版本

pnpm安装依赖校验

preinstall.js内容如下:

#!/usr/bin/env nodeif (!/pnpm/.test(process.env.npm_execpath || '')) {  console.warn(`请使用pnpm install 安装依赖`)  process.exit(1)}

在package.json中增加如下代码:在使用非pnpm安装依赖时会提示错误,禁止安装,强制使用pnpm安装

 "scripts": {    "preinstall": "node ./bin/preinstall.js",  },