Monorepo pnpm 模式管理多个 web 项目

嚴華  论坛元老 | 2024-11-18 10:05:30 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 1050|帖子 1050|积分 3150

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
Monorepo pnpm 模式管理多个 web 项目

项目地点



  • Vue: Monorepo pnpm 模式管理多个 web 项目(Vue3)
  • Nuxt:Monorepo pnpm 模式管理多个 web 项目(Nuxt)
git flow 工作流程



  • 参考链接:[https://danielkummer.github.io/git-flow-cheatsheet/index.zh_CN.html]
  • 新建功能

    • 新建feature分支
    1. git checkout -b feature/MYFEATURE
    2. git flow feature start MYFEATURE
    复制代码
      

    • 完成新功能开发,将feature分支合并到develop分支
    • 现在已写成文件,直接执行命令 yarn feature即可
    1. git flow feature finish MYFEATURE
    复制代码
      

    • 基于最新的develop分支,切出release分支,此版本为预发布版本,分支为版本号
    1. git checkout -b release/1.0.0
    2. git flow release start release/1.0.0
    复制代码
      

    • 测试无问题, 执行git flow release finish 1.0.0 ,输出提交日志,合并到develop分支和mian/master分支,输入版本信息
    1. # -m tag提交信息
    2. # 参数参考:https://github.com/nvie/gitflow/wiki/Command-Line-Arguments#hotfix
    3. git flow release finish 1.0.0 -m ""
    复制代码
      

    • 在develop分支,并推送develop分支到长途
    1. git push origin develop
    复制代码
      

    • 切换到main/master 分支, 推送到长途,推送最新的tag到长途
    1. git push origin main
    2. git push origin v1.0.0
    复制代码

  • bug修改

    • 新增一个hotfix分支
    1. git checkout -b hotfix/1.0.1
    2. git flow hotfix start 1.0.1
    复制代码
      

    • 修改完成后的操作与release一样

pnpm workspace



  • 创建 pnpm-workspace.yaml 文件
  1. touch pnpm-workspace.yaml
复制代码


  • pnpm-workspace.yaml
  1. packages:
  2.   - 'packages/*' # 代表所有项目都放在packages文件夹之下
复制代码
.npmrc

  1. # 注释:三方依赖也有依赖,要是项目中使用了第三方的依赖,
  2. # 要是哪天第三方卸载不在该包了,那就找不到了,称之为“幽灵依赖” ,
  3. # 所以需要“羞耻提升”,暴露到外层中,即在根目录下的node_modules内,而非在.pnpm文件夹中。
  4. shamefully-hoist = true
  5. # 根目录下的node_modules里,vue安装到了与.pnpm同层级位置当中了,
  6. # 这就是shamefully-hoist = true的效果,把vue从.pnpm内提到node_modules中,
  7. # 并且vue的相关依赖,也拍平到了该层级文件夹中。
复制代码
初始化项目架构



  • Conventional Changelog 生态探索: https://zhuanlan.zhihu.com/p/392303778
  1. # 初始化package.json
  2. pnpm init
  3. # 初始化项目
  4. pnpm run init
  5. # git flow init 前需要 执行 git init
  6. # 每修改一次filter-flow-hotfix-finish-tag-message.sh,filter-flow-release-finish-tag-message.sh 需要重新init
复制代码


  • package.json
  1. {
  2.         "script": {
  3.                 "init": "sh ./scripts/shell/init.sh --all"
  4.         }
  5. }
复制代码


  • init.sh
  1. # 项目初始化
  2. # 初始化项目配置
  3. SCRIPTPATH=$(pwd -P)
  4. # 初始化git设置
  5. git config core.filemode false
  6. git config tag.sort version:refname
  7. git config pull.rebase true
  8. if [ $1 ]; then
  9.     # 安装 editorconfig 扩展
  10.     command -v code && code --install-extension editorconfig.editorconfig || echo "Make sure your IDEs support for \`EditorConfig\`. You can check by https://editorconfig.org/"
  11.     # 设置git filter-flow-hotfix-finish-tag-message hook 软连接
  12.     rm -f ./.git/hooks/filter-flow-hotfix-finish-tag-message
  13.     chmod +x $SCRIPTPATH/scripts/shell/filter-flow-hotfix-finish-tag-message.sh
  14.     ln -s $SCRIPTPATH/scripts/shell/filter-flow-hotfix-finish-tag-message.sh ./.git/hooks/filter-flow-hotfix-finish-tag-message
  15.     # 设置git filter-flow-release-finish-tag-message hook 软连接
  16.     rm -f ./.git/hooks/filter-flow-release-finish-tag-message
  17.     chmod +x $SCRIPTPATH/scripts/shell/filter-flow-release-finish-tag-message.sh
  18.     ln -s $SCRIPTPATH/scripts/shell/filter-flow-release-finish-tag-message.sh ./.git/hooks/filter-flow-release-finish-tag-message
  19.     # 初始化git-flow设置
  20.     git config gitflow.branch.master master
  21.     git config gitflow.branch.develop develop
  22.     git config gitflow.prefix.versiontag v
  23.     git config gitflow.path.hooks $SCRIPTPATH/.git/hooks
  24.     git flow init
  25. fi
  26. if [ $? -eq 0 ]; then
  27.     echo 'init finish'
  28. else
  29.     echo 'init failed'
  30. fi
复制代码


  • filter-flow-hotfix-finish-tag-message.sh,filter-flow-release-finish-tag-message.sh

    • git flow 工作流钩子,用于release,hotfix 打版,自定义两个命令增长中途写入CHANGELOG的功能
    • 执行git flow release finish 和 git flow hotfix finish 工作流时,会执行hooks中的filter-flow-hotfix-finish-tag-message.sh,filter-flow-release-finish-tag-message.sh
    • 具体参考:https://github.com/jaspernbrouwer/git-flow-hooks/blob/master/filter-flow-hotfix-finish-tag-message

  • filter-flow-hotfix-finish-tag-message.sh, filter-flow-release-finish-tag-message.sh
  • 为保证CHANGELOG正常写入,release的命名格式为xxx-tagname,tagname和提交时的scope保持同等
  1. #!/usr/bin/env bash
  2. # Runs during git flow release finish and a tag message is given
  3. #
  4. # Positional arguments:
  5. # $1 Message
  6. # $2 Full version
  7. #
  8. # Return MESSAGE
  9. #
  10. # The following variables are available as they are exported by git-flow:
  11. #
  12. # MASTER_BRANCH - The branch defined as Master
  13. # DEVELOP_BRANCH - The branch defined as Develop
  14. MESSAGE=$1
  15. VERSION=$2
  16. # 同步远程tag,防止本地打版写入多个版本changelog-needed
  17. git fetch --tags
  18. BEHIND_COMMIT=$(git rev-list --count ..origin/develop)
  19. ROOT_DIR=$PWD
  20. # 根据tag来截取需要写入日志的package
  21. PACKAGE=${VERSION#*-}
  22. # 获取需要写入日志的package最近的一个tag
  23. PREVIOUSTAG=$(git tag -l | grep $PACKAGE | tail -n 1)
  24. # 获取semver格式的版本号
  25. PACKAGE_VERSION=${VERSION%%-*}
  26. # 获取两个tag之间的changelog信息
  27. CHANGELOG_MESSAGE=`pnpm cross-env PACKAGE=$PACKAGE PREVIOUS_TAG=$PREVIOUSTAG CURRENT_TAG=$VERSION  conventional-changelog -p custom-config -i -n ./scripts/changelog/changelog-option.cjs | tail -n +4 | sed '$d' | sed 's/(changelog-needed)/ /g'`
  28. # 判断是否需要rebase,落后于target branch合并会失败
  29. [ $BEHIND_COMMIT -ne 0 ] && { echo 'Please rebase develop before finishing this branch'; exit 1; }
  30. isMono=$(echo $VERSION | grep "mono")
  31. # 判断是否为mono的更新,是的话changelog会更新到changelogs目录的mono.md内
  32. if [[ "$isMono" != "" ]]; then
  33.     # 更新版本号
  34.     pnpm version --new-version ${PACKAGE_VERSION/v/} --no-git-tag-version > /dev/null
  35.     TEMP_CHANGELOG_MESSAGE=$(echo "### $PACKAGE_VERSION";git log -1 --pretty="#### %ci";printf "\n";echo "${CHANGELOG_MESSAGE}";printf "\n---\n\n";cat ./changelogs/mono.md)
  36.     echo "$TEMP_CHANGELOG_MESSAGE" > ./changelogs/mono.md
  37. # 否则更新到changelogs目录对应package的package.md内
  38. else
  39.     TEMP_CHANGELOG_MESSAGE=$(echo "### $PACKAGE_VERSION";git log -1 --pretty="#### %ci";printf "\n";echo "${CHANGELOG_MESSAGE}";printf "\n---\n\n";cat ./changelogs/$PACKAGE.md)
  40.     echo "$TEMP_CHANGELOG_MESSAGE" > ./changelogs/$PACKAGE.md
  41. fi
  42. git add . > /dev/null
  43. git commit --amend --no-edit --no-verify > /dev/null
  44. echo $MESSAGE
  45. exit 0
复制代码


  • 设置commit规范以及自动生成CHNAGELOG并自定义必要的依赖包
  1. # conventional-changelog-cli 要配合conventional-changelog-custom-config使用,指定版本为@^2
  2. # conventional-changelog-custom-config参考:https://itxiaohao.github.io/passages/git-commit/#%E6%B7%B1%E5%85%A5-conventional-changelog-%E6%BA%90%E7%A0%81
  3. # lerna 要配合 cz-lerna-changelog 使用,指定版本为@^3.22.1
  4. #  cz-lerna-changelog参考:https://www.npmjs.com/package/cz-lerna-changelog
  5. # -w 有工作区的时候使用
  6. pnpm add @commitlint/cli @commitlint/config-conventional commitizen conventional-changelog-cli@^2.2.2 conventional-changelog-custom-config cz-lerna-changelog lerna@^3.22.1 -D -w
  7. pnpm add cross-env -w
复制代码


  • 添加comitizen相应设置,创建commitlint.config.js
  1. touch commitlint.config.js
复制代码
  1. const fs = require('fs')
  2. const path = require('path')
  3. module.exports = {
  4.         extends: ['monorepo'],
  5.         rules: {
  6.                 'header-max-length': [0, 'always'],
  7.                 // scope 不允许为空,保证CHANGELOG正常写入,release的命名格式为xxx-tagname,tagname和scope保持一致
  8.                 'scope-empty': [2, 'never'],
  9.                 'scope-enum': [2, 'always', [...fs.readdirSync(path.join(__dirname, 'packages')), 'mono']],
  10.                 'type-enum': [2, 'always', ['build', 'ci', 'chore', 'feat', 'fix', 'refactor', 'style', 'test', 'config', 'docs']],
  11.                 'close-issue-needed': [2, 'always'],
  12.         },
  13.         plugins: [
  14.                 {
  15.                         rules: {
  16.                                 'close-issue-needed': (msg) => {
  17.                                         const ISSUES_CLOSED = 'ISSUES CLOSED:'
  18.                                         return [msg.raw.includes(ISSUES_CLOSED), 'Your commit message must contain ISSUES message']
  19.                                 },
  20.                         },
  21.                 },
  22.         ],
  23. }
复制代码


  • 自定定义CHANGELOG设置 changelog-option.cjs
  1. const path = require('path')
  2. const compareFunc = require('compare-func')
  3. // 自定义配置
  4. let pkgJson = {}
  5. try {
  6.         pkgJson = require(path.join(__dirname, '../../package.json'))
  7. } catch (err) {
  8.         console.error('no root package.json found')
  9. }
  10. const { changelog } = pkgJson
  11. let bugsUrl = changelog ? changelog.bugsUrl || false : false
  12. if (typeof bugsUrl !== 'string') bugsUrl = false
  13. const authorName = changelog ? changelog.authorName || false : false
  14. const authorEmail = changelog ? changelog.authorEmail || false : false
  15. let gitUserInfo = ''
  16. if (authorName && authorEmail) {
  17.         gitUserInfo = 'by: **{{authorName}}** ({{authorEmail}})'
  18. }
  19. if (authorName && authorEmail === false) {
  20.         gitUserInfo = 'by: **{{authorName}}**'
  21. }
  22. if (authorName === false && authorEmail) {
  23.         gitUserInfo = 'by: ({{authorEmail}})'
  24. }
  25. const getWriterOpts = () => {
  26.         return {
  27.                 transform: (commit, context) => {
  28.                         let discard = true
  29.                         const issues = []
  30.                         commit.notes.forEach((note) => {
  31.                                 note.title = 'BREAKING CHANGES'
  32.                                 discard = false
  33.                         })
  34.                         if (commit.type === 'feat') {
  35.                                 commit.type = 'Features'
  36.                         } else if (commit.type === 'fix') {
  37.                                 commit.type = 'Bug Fixes'
  38.                         } else if (commit.type === 'perf') {
  39.                                 commit.type = 'Performance Improvements'
  40.                         } else if (commit.type === 'revert') {
  41.                                 commit.type = 'Reverts'
  42.                         } else if (commit.type === 'docs') {
  43.                                 commit.type = 'Documentation'
  44.                         } else if (commit.type === 'style') {
  45.                                 commit.type = 'Styles'
  46.                         } else if (commit.type === 'refactor') {
  47.                                 commit.type = 'Code Refactoring'
  48.                         } else if (commit.type === 'test') {
  49.                                 commit.type = 'Tests'
  50.                         } else if (commit.type === 'build') {
  51.                                 commit.type = 'Build System'
  52.                         } else if (commit.type === 'ci') {
  53.                                 commit.type = 'Continuous Integration'
  54.                         } else if (commit.type === 'chore') {
  55.                                 commit.type = 'Chores'
  56.                         } else if (discard) {
  57.                                 return
  58.                         }
  59.                         if (commit.scope === '*') {
  60.                                 commit.scope = ''
  61.                         }
  62.                         if (typeof commit.hash === 'string') {
  63.                                 commit.hash = commit.hash.substring(0, 7)
  64.                         }
  65.                         if (typeof commit.subject === 'string') {
  66.                                 let url = context.repository ? `${context.host}/${context.owner}/${context.repository}` : context.repoUrl
  67.                                 if (url) {
  68.                                         url = `${url}/issues/`
  69.                                         // Issue URLs.
  70.                                         commit.subject = commit.subject.replace(/#([0-9]+)/g, (_, issue) => {
  71.                                                 issues.push(issue)
  72.                                                 return `[#${issue}](${url}${issue})`
  73.                                         })
  74.                                 }
  75.                                 if (context.host) {
  76.                                         // User URLs.
  77.                                         commit.subject = commit.subject.replace(/\B@([a-z0-9](?:-?[a-z0-9/]){0,38})/g, (_, username) => {
  78.                                                 if (username.includes('/')) {
  79.                                                         return `@${username}`
  80.                                                 }
  81.                                                 return `[@${username}](${context.host}/${username})`
  82.                                         })
  83.                                 }
  84.                         }
  85.                         // remove references that already appear in the subject
  86.                         commit.references = commit.references.filter((reference) => {
  87.                                 if (!issues.includes(reference.issue)) {
  88.                                         return true
  89.                                 }
  90.                                 return false
  91.                         })
  92.                         if (bugsUrl) {
  93.                                 commit.references = commit.references.map((ref) => {
  94.                                         return {
  95.                                                 ...ref,
  96.                                                 bugsUrl,
  97.                                         }
  98.                                 })
  99.                         }
  100.                         const needChangelog = commit.header.includes('(changelog-needed)') && commit.header.includes(`(${process.env.PACKAGE}):`)
  101.                         // 可在此过滤所需要的commit信息
  102.                         if (needChangelog) {
  103.                                 commit.header = commit.header.replace(/\(changelog-needed\)/g, '')
  104.                         }
  105.                         return needChangelog ? commit : null
  106.                 },
  107.                 groupBy: 'type',
  108.                 commitGroupsSort: 'title',
  109.                 commitsSort: ['scope', 'subject'],
  110.                 noteGroupsSort: 'title',
  111.                 notesSort: compareFunc,
  112.                 finalizeContext: (context) => {
  113.                         return Object.assign(context, {
  114.                                 version: process.env.CURRENT_TAG,
  115.                                 linkCompare: false,
  116.                         })
  117.                 },
  118.         }
  119. }
  120. module.exports = {
  121.         gitRawCommitsOpts: {
  122.                 from: process.env.PREVIOUS_TAG,
  123.                 to: process.env.CURRENT_TAG,
  124.         },
  125.         writerOpts: getWriterOpts(),
  126. }
复制代码
  1. {
  2.         "repository": {
  3.                 "type": "git",
  4.                 "url": "https://github.com/example.git"
  5.         },
  6.         "changelog": {
  7.                 "bugsUrl": "https://github.com/",
  8.                 "authorName": true,
  9.                 "authorEmail": false
  10.         }
  11. }
复制代码


  • 设置 cz-lerna-changlog,支持选择packages
  1. const czLernaChangelog = require('cz-lerna-changelog')
  2. function makePrompter() {
  3.         return function (cz, commit) {
  4.                 cz.prompt([
  5.                         {
  6.                                 type: 'confirm',
  7.                                 name: 'addChangeLog',
  8.                                 message: 'Auto add `(changelog-needed)` to subject line?\n',
  9.                         },
  10.                 ]).then((answer) => {
  11.                         let customQuestion = [
  12.                                 {
  13.                                         type: 'input',
  14.                                         name: 'subject',
  15.                                         message: 'Write a short, imperative tense description of the change:\n',
  16.                                         filter: function (value) {
  17.                                                 const mark = (answer.addChangeLog && '(changelog-needed)') || ''
  18.                                                 return value.charAt(0).toLowerCase() + value.slice(1) + mark
  19.                                         },
  20.                                         validate: function (value) {
  21.                                                 return !!value
  22.                                         },
  23.                                 },
  24.                         ]
  25.                         return czLernaChangelog.makePrompter(() => customQuestion)(cz, commit)
  26.                 })
  27.         }
  28. }
  29. module.exports = {
  30.         prompter: makePrompter(),
  31.         makePrompter: makePrompter,
  32. }
复制代码
  1. {
  2.         "config": {
  3.                 "commitizen": {
  4.                         "path": "./scripts/changelog/cz-lerna-changelog.cjs"
  5.                 }
  6.         },
  7.         "workspaces": ["packages/*"]
  8. }
复制代码
引入Husky规范git提交



  • 安装 husky
  1. pnpm add husky -D -w
复制代码


  • 在 package.json 中 scripts 中设置 prepare 钩子:husky install,在使用pnpm install的时间就会自动执行husky,以便于别人拉取完我们代码进行pnpm insall的时间直接进行husky install(版本8操作,版本9直接执行 init)
  1. pnpm pkg set scripts.prepare="husky install"
复制代码
或者
  1. {
  2.         "scripts": {
  3.                 "prepare": "husky install"
  4.         }
  5. }
复制代码


  • 执行install, 生成.husky文件夹
  1. # 版本 8
  2. npx husky install
  3. #版本9
  4. npx husky init
复制代码


  • 添加一个 commit 钩子文件
  1. # 版本8
  2. npx husky add .husky/pre-commit
  3. # 版本8 .husky/commit-msg 中添加npx --no -- commitlint --edit "$1"
  4. npx --no -- commitlint --edit "$1"
  5. # 版本9
  6. echo 'npx --no -- commitlint --edit "$1"' > .husky/commit-msg
  7. # .husky/pre-commit中写入以下命令,配合eslint使用
  8. pnpm run lint-staged
复制代码
设置eslint和prettier

eslint 设置



  • 安装依赖包
  1. # eslint eslint依赖包
  2. # eslint-config-standard JavaScript标准样式的ESLint可配置,基础配置,比较流行的有 airbnb、standard、prettier等
  3. # eslint-plugin-import 支持ES6以上的导入/导出语法,并防止文件路径和导入名称拼写错误的问题
  4. # eslint-plugin-node 为node准备的eslint规则配置
  5. # eslint-plugin-promise es语法promise的eslint最佳配置
  6. # eslint-plugin-vue vue项目的的配置,vue项目必须
  7. # @typescript-eslint/parser 解析器
  8. # @typescript-eslint/eslint-plugin  ts语法的配置
  9. # eslint-define-config eslint-define-config可以帮助我们做语法提示
  10. pnpm add eslint eslint-config-standard eslint-plugin-import eslint-plugin-node eslint-plugin-promise eslint-plugin-vue @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-define-config -D -w
复制代码


  • 根目录下创建.eslintrc.cjs、.eslintignore文件
  1. // .eslinttrc.cjs
  2. // eslint-define-config可以帮助我们做语法提示
  3. const { defineConfig } = require('eslint-define-config')
  4. module.exports = defineConfig({
  5.         // ESLint 一旦发现配置文件中有 "root": true,它就会停止在父级目录中寻找。
  6.         root: true,
  7.         // 解析器
  8.         parser: 'vue-eslint-parser',
  9.         parserOptions: {
  10.                 // 解析器
  11.                 parser: '@typescript-eslint/parser',
  12.                 // js的版本
  13.                 ecmaVersion: 2020,
  14.                 // 模块化方案
  15.                 sourceType: 'module',
  16.                 ecmaFeatures: {
  17.                         jsx: true,
  18.                 },
  19.         },
  20.         // 启用的规则
  21.         extends: ['plugin:vue/vue3-recommended', 'plugin:@typescript-eslint/recommended', 'standard'],
  22.         rules: {
  23.                 quotes: ['error', 'single'],
  24.                 '@typescript-eslint/ban-ts-ignore': 'off',
  25.                 '@typescript-eslint/explicit-function-return-type': 'off',
  26.                 '@typescript-eslint/no-explicit-any': 'off',
  27.                 '@typescript-eslint/no-var-requires': 'off',
  28.                 '@typescript-eslint/no-empty-function': 'off',
  29.                 '@typescript-eslint/no-use-before-define': 'off',
  30.                 '@typescript-eslint/ban-ts-comment': 'off',
  31.                 '@typescript-eslint/ban-types': 'off',
  32.                 '@typescript-eslint/no-non-null-assertion': 'off',
  33.                 '@typescript-eslint/explicit-module-boundary-types': 'off',
  34.                 '@typescript-eslint/no-unused-vars': [
  35.                         'error',
  36.                         {
  37.                                 argsIgnorePattern: '^h$',
  38.                                 varsIgnorePattern: '^h$',
  39.                         },
  40.                 ],
  41.                 'no-use-before-define': 'off',
  42.                 'no-unused-vars': [
  43.                         'error',
  44.                         {
  45.                                 argsIgnorePattern: '^h$',
  46.                                 varsIgnorePattern: '^h$',
  47.                         },
  48.                 ],
  49.                 'no-tabs': 'off',
  50.                 indent: 'off',
  51.                 'vue/custom-event-name-casing': 'off',
  52.                 'vue/html-indent': 'off',
  53.                 'vue/max-attributes-per-line': 'off',
  54.                 'vue/html-self-closing': 'off',
  55.                 'vue/singleline-html-element-content-newline': 'off',
  56.                 'vue/multi-word-component-names': 'off',
  57.                 'space-before-function-paren': 'off',
  58.                 'comma-dangle': 'off',
  59.         },
  60. })
复制代码
  1. .eslintignore
  2. node_modules
  3. .vscode
  4. .idea
  5. dist
  6. .eslintrc.cjs
复制代码
prettier 设置



  • 安装依赖
  1. # eslint-config-prettier&eslint-plugin-prettier  用于解决eslint和prettier的冲突问题
  2. pnpm add prettier eslint-config-prettier eslint-plugin-prettier -D -w
复制代码


  • 根目录创建.prettierrc文件
  1. {
  2.         "printWidth": 150,
  3.         "tabWidth": 2,
  4.         "useTabs": true,
  5.         "semi": false,
  6.         "singleQuote": true,
  7.         "quoteProps": "as-needed",
  8.         "jsxSingleQuote": false,
  9.         "trailingComma": "es5",
  10.         "bracketSpacing": true,
  11.         "jsxBracketSameLine": false,
  12.         "arrowParens": "always",
  13.         "rangeStart": 0,
  14.         "requirePragma": false,
  15.         "insertPragma": false,
  16.         "proseWrap": "preserve",
  17.         "htmlWhitespaceSensitivity": "css",
  18.         "vueIndentScriptAndStyle": false,
  19.         "endOfLine": "auto"
  20. }
复制代码


  • 根目录创建.prettierignore文件用于忽略prewitter格式化
  • 安装 VSCode 插件 Prettier - Code formatter

    • 安装该插件以实现在保存的时间自动完成格式化

  • 在 .vscode/settings.json 中添加一下规则
  1. {
  2.         // 保存的时候自动格式化
  3.         "editor.formatOnSave": true,
  4.         // 默认格式化工具选择prettier
  5.         "editor.defaultFormatter": "esbenp.prettier-vscode"
  6. }
复制代码
设置lint-staged



  • 提交前在pre-commit中应该做一次lint 校验,在package.json添加
  1. {
  2.         "scripts": {
  3.                 "lint:script": "eslint --ext .js,.jsx,.vue,.ts,.tsx --fix --quiet ./"
  4.         }
  5. }
复制代码


  • 在pre-commit 中添加命令 npm lint:script
  • 根据上面的设置是可以实现我们想要的效果的,但是我们会发现每次提交代码的时间 ESlint 或 Stylelint 都会检查所有文件,而我们必要的是只让它们检测新增的文件,因此我们可以使用lint-staged来解决这个问题
  • 安装lint-staged
  1. pnpm add lint-staged -D -w
复制代码


  • 在package.json中添加设置,更改pre-commit的命令 pnpm run lint-staged
  1. {
  2.         "lint-staged": {
  3.                 "src/**/*.{js,jsx,ts,tsx,vue}": ["eslint --ext .js,.jsx,.vue,.ts,.tsx --fix --quiet ./"]
  4.         },
  5.         "scripts": {
  6.                 "lint-staged": "lint-staged"
  7.         }
  8. }
复制代码
创建项目

创建shared



  • shared项目用来服务其他多个web项目,提供公共方法、组件、样式等等
项目全局安装 vue

  1. # -w的意思是,workspace-root把依赖包安装到工作目录的根路径下,
  2. # 则根目录下会生成node_modules文件夹。可以共用,后续每个项目需要用到vue的
  3. # 都直接从根目录node_modules里取。
  4. pnpm add vue -w
复制代码
在 packages 项目下创建 vue 项目



  • 执行创建命令,根据提示选择
  1. pnpm create vite
复制代码


  • 删除 vue @vitejs/plugin-vue vite vue-tsc typescript 等依赖,安装到全局中
  1. # 子项目下执行
  2. pnpm remove vue
  3. pnpm remove @vitejs/plugin-vue vite vue-tsc typescript -D
  4. # 根目录下执行
  5. pnpm add @vitejs/plugin-vue vite vue-tsc typescript -D -w
复制代码


  • 运行项目
  1. # 子项目下运行
  2. pnpm dev
复制代码
设置全局指令

  1. {
  2.         "script": {
  3.                 "dev:project": "cd packages/vite-project & pnpm dev"
  4.                 // pnpm -C packages/vue-config-1 & pnpm dev 亦可
  5.         }
  6. }
复制代码
引用 shared 内容



  • 加入 tsconfig.json 来设置路径
  1. # 根目录下
  2. pnpm add typescript -D -w
  3. pnpm tsc --init
复制代码


  • 设置
  1. {
  2.         "compilerOptions": {
  3.                 "outDir": "dist", // 输出的目录
  4.                 "sourceMap": true, //采用sourcemap
  5.                 "target": "es2016", // 目标语法
  6.                 "module": "esnext", // 模块格式
  7.                 "moduleResolution": "node", // 模块解析
  8.                 "strict": false, // 严格模式
  9.                 "resolveJsonModule": true, // 解析json模块
  10.                 "esModuleInterop": true, // 允许通过es6语法引入commonjs模块
  11.                 "jsx": "preserve", // jsx不转义
  12.                 "lib": ["esnext", "dom"], // 支持的类库esnext及dom
  13.                 "baseUrl": ".", // 当前是以该路径进行查找
  14.                 "paths": {
  15.                         "@monorepo/shared/components": ["packages/shared/components"],
  16.                         "@monorepo/shared/utils": ["packages/shared/utils"],
  17.                         "@monorepo/shared/fetch": ["packages/shared/fetch"],
  18.                         "@monorepo/shared/styles": ["packages/shared/styles"],
  19.                         // 或者用*号处理匹配
  20.                         "@monorepo/shared/*": ["packages/shared/*"]
  21.                 }
  22.         }
  23. }
复制代码
建立关联

  1. # 指定版本号
  2. pnpm add @monorepo/shared@workspace --filter @monorepo/vite-project
复制代码
打包插件



  • 安装 minimist esbuild
  1. pnpm add minimist esbuild -D -w
复制代码


  • 新增 打包脚本
  1. // minimist 可以解析命令行参数,非常好用,功能简单
  2. import minimist from 'minimist'
  3. // 打包模块
  4. import { build } from 'esbuild'
  5. // node 中的内置模块
  6. import path from 'path'
  7. import fs from 'fs'
  8. const __dirname = path.resolve()
  9. const args = minimist(process.argv.slice(2))
  10. const target = args._[0]
  11. const format = args.f || 'global'
  12. const entry = path.resolve(__dirname, `./packages/plugins/${target}/src/index.ts`)
  13. /*  iife 立即执行函数(function(){})()
  14.     cjs node中的模块 module.exports
  15.     esm 浏览器中的esModule模块 import */
  16. const outputFormat = format.startsWith('global') ? 'iife' : format === 'cjs' ? 'cjs' : 'esm'
  17. const outfile = path.resolve(__dirname, `./packages/plugins/${target}/dist/${target}.${format}.js`)
  18. const pkaPath = path.resolve(__dirname, `./packages/plugins/${target}/package.json`)
  19. const pkaOps = JSON.parse(fs.readFileSync(pkaPath, 'utf8'))
  20. const packageName = pkaOps.buildOptions?.name
  21. build({
  22.         entryPoints: [entry],
  23.         outfile,
  24.         bundle: true,
  25.         sourcemap: true,
  26.         format: outputFormat,
  27.         globalName: packageName,
  28.         platform: format === 'cjs' ? 'node' : 'browser',
  29. }).then(() => {
  30.         console.log('watching~~~')
  31. })
复制代码


  • 设置 plugins package.json
  1. {
  2.         "name": "@monorepo/common",
  3.         "version": "1.0.0",
  4.         "description": "",
  5.         "main": "index.js",
  6.         "scripts": {
  7.                 "test": "echo "Error: no test specified" && exit 1"
  8.         },
  9.         // 打包会用到, 用于定义全局变量
  10.         "buildOptions": {
  11.                 "name": "common"
  12.         },
  13.         "keywords": [],
  14.         "author": "",
  15.         "license": "ISC",
  16.         "dependencies": {
  17.                 "@monorepo/shared": "workspace:^"
  18.         }
  19. }
复制代码


  • 设置并执行打包命令
  1. {
  2.         "scripts": {
  3.                 "dev:common": "node scripts/dev-plugins.js common -f global"
  4.         }
  5. }
复制代码
  1. pnpm dev:common
复制代码


  • 测试使用
  1. <!doctype html>
  2. <html lang="en">
  3.         <head>
  4.                 <meta charset="UTF-8" />
  5.                 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  6.                 <title>Document</title>
  7.         </head>
  8.         <body>
  9.                 <script src="./common.global.js"></script>
  10.                 <script>
  11.                         const { testFunc } = common
  12.                         console.log(testFunc())
  13.                 </script>
  14.         </body>
  15. </html>
复制代码
设置单命令启动多项目

  1. {
  2.         "scripts": {
  3.                 "serve": "node ./scripts/build/build.cjs",
  4.                 "build": "node ./scripts/build/build.cjs --production"
  5.         }
  6. }
复制代码


  • build.cjs
  1. const { spawn } = require('child_process')
  2. const core = require('./core.cjs')
  3. const path = require('path')
  4. class BuildCore extends core {
  5.         constructor(optionArray = []) {
  6.                 super()
  7.                 this.initOption(optionArray)
  8.                 this.start()
  9.         }
  10.         /**
  11.          * @description 执行构建流程
  12.          */
  13.         start() {
  14.                 this.getPackages().then(async () => {
  15.                         this._argument = this._program.parse(process.argv).args
  16.                         try {
  17.                                 const answer = await this.selectPackage()
  18.                                 answer && this._argument.unshift(answer)
  19.                                 this.initBuildSpawn()
  20.                         } catch (error) {
  21.                                 console.error('the application must be selected!')
  22.                         }
  23.                 })
  24.         }
  25.         /**
  26.          * @description 初始化本地开发或者构建build过程
  27.          */
  28.         initBuildSpawn() {
  29.                 if (!this.validatePackage()) return false
  30.                 const isProduction = this._program.parse(process.argv).production
  31.                 process.env.PACKAGE = this._argument[0]
  32.                 process.env.NODE_ENV = isProduction ? 'production' : 'development'
  33.                 const args = isProduction ? ['build'] : ['dev']
  34.                 const clinetPath = path.resolve(__dirname, `../../packages/${process.env.PACKAGE}/`)
  35.                 try {
  36.                         const clientSpawnInstance = spawn('pnpm', ['-C', clinetPath, args], {
  37.                                 stdio: 'inherit',
  38.                                 shell: true, // 兼容部分win10系统Error: spawn yarn ENOENT报错
  39.                         })
  40.                         this.registErrorHandle(clientSpawnInstance)
  41.                 } catch (error) {
  42.                         console.log(error)
  43.                 }
  44.         }
  45. }
  46. new BuildCore([
  47.         {
  48.                 short: '-p',
  49.                 long: '--production',
  50.                 description: 'build package in production mode',
  51.         },
  52. ])
复制代码


  • core.js
  1. /**
  2. * 操作终端面板,选择项目启动
  3. * 思路:读取packages下的文件,获取每个项目的名称,存到项目数组中(this._packageArray) getPackages
  4. * 根据项目数组构建命令行选择器,选择对应的项目 selectPackage
  5. */
  6. const process = require('process')
  7. const fs = require('fs')
  8. const path = require('path')
  9. const { Command } = require('commander')
  10. const { Select } = require('enquirer')
  11. class Core {
  12.         constructor() {
  13.                 // 项目数组
  14.                 this._packageArray = []
  15.                 // 命令实例
  16.                 this._program = new Command()
  17.         }
  18.         /**
  19.          * @description 选择应用
  20.          * @return Promise
  21.          */
  22.         selectPackage() {
  23.                 // 深拷贝应用数组
  24.                 const packages = JSON.parse(JSON.stringify(this._packageArray))
  25.                 // 判断选择的包是否包含在应用数组中,包含则返回Promise成功状态
  26.                 if (this._argument && packages.includes(this._argument[0])) return Promise.resolve()
  27.                 // 终端命令行选择
  28.                 const prompt = new Select({
  29.                         name: 'apps',
  30.                         message: 'Please select the application to run',
  31.                         choices: packages,
  32.                 })
  33.                 return prompt.run()
  34.         }
  35.         /**
  36.          * @description 初始化自定义command参数
  37.          * @param {Object[]} optionArray 自定义参数数组
  38.          * @param {String} optionArray[].short 自定义参数缩写,如 -p
  39.          * @param {String} optionArray[].long 自定义参数全称, 如 --production
  40.          * @param {String} optionArray[].description 自定义参数作用的描述
  41.          */
  42.         initOption(optionArray) {
  43.                 optionArray.forEach((obj) => {
  44.                         this._program.option(`${obj.short}, ${obj.long}`, obj.description)
  45.                 })
  46.         }
  47.         /**
  48.          * @description 检测自定义的package参数是否匹配packages目录下的项目
  49.          */
  50.         validatePackage() {
  51.                 let pass = true
  52.                 if (!this._packageArray.includes(this._argument[0])) {
  53.                         console.error(`package param should be one of [${this._packageArray.join(',')}]`)
  54.                         console.log('eg: yarn <script> auth-overseas')
  55.                         pass = false
  56.                 }
  57.                 return pass
  58.         }
  59.         /**
  60.          * @description 获取packages目录下的项目
  61.          */
  62.         getPackages() {
  63.                 return new Promise((resolve, reject) => {
  64.                         // 读取packages下的文件
  65.                         fs.readdir(path.join(__dirname, '../../packages'), { withFileTypes: true }, (err, dir) => {
  66.                                 if (err) reject(err)
  67.                                 // 将目录的文件名筛选读取,添加到应用数组中
  68.                                 this._packageArray = dir
  69.                                         .filter((i) => {
  70.                                                 const typeKey = Object.getOwnPropertySymbols(i)[0]
  71.                                                 return i.name !== 'plugins' && i.name !== 'shared' && i[typeKey] === 2
  72.                                         })
  73.                                         .map((j) => j.name)
  74.                                 resolve()
  75.                         })
  76.                 })
  77.         }
  78.         /**
  79.          * @description 注册对子进程错误进行异常处理
  80.          * @param {Object} spawnInstance 子进程
  81.          * @param {Function} callback 子进程执行完成后回调
  82.          * @param {Function} errorCallback 子进程执行报错后回调
  83.          */
  84.         registErrorHandle(spawnInstance, callback, errorCallback) {
  85.                 spawnInstance.on('error', (err) => {
  86.                         console.log(err)
  87.                         errorCallback && errorCallback(err)
  88.                         process.exit(1)
  89.                 })
  90.                 spawnInstance.on('exit', (code) => {
  91.                         callback && callback()
  92.                         // code = 0表示流程正常
  93.                         if (code !== 0) {
  94.                                 process.exit(1)
  95.                         }
  96.                 })
  97.         }
  98. }
  99. module.exports = Core
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

嚴華

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表