worldzhao / blog

个人博客,内容在 issue 里。
416 stars 16 forks source link

React 组件库搭建指南(五):标准化发布流程 #7

Open worldzhao opened 3 years ago

worldzhao commented 3 years ago

前言

本节主要是讲解如何编写脚本完成以下内容:

  1. 版本更新
  2. 生成 CHANGELOG
  3. 推送至 git 仓库
  4. 组件库打包
  5. 发布至 npm
  6. 打 tag 并推送至 git

如果你对这一节不感兴趣,也可以直接使用 np 进行发布,只需要自定义配置一些钩子。

本项目是使用 np 进行发布的,本节仅作个人学习用。

package.json

"scripts": {
+ "release": "ts-node ./scripts/release.ts"
},
/* eslint-disable  import/no-extraneous-dependencies,@typescript-eslint/camelcase, no-console */
import inquirer from 'inquirer';
import fs from 'fs';
import path from 'path';
import child_process from 'child_process';
import util from 'util';
import chalk from 'chalk';
import semverInc from 'semver/functions/inc';
import { ReleaseType } from 'semver';

import pkg from '../package.json';

const exec = util.promisify(child_process.exec);

const run = async (command: string) => {
  console.log(chalk.green(command));
  await exec(command);
};

const currentVersion = pkg.version;

const getNextVersions = (): { [key in ReleaseType]: string | null } => ({
  major: semverInc(currentVersion, 'major'),
  minor: semverInc(currentVersion, 'minor'),
  patch: semverInc(currentVersion, 'patch'),
  premajor: semverInc(currentVersion, 'premajor'),
  preminor: semverInc(currentVersion, 'preminor'),
  prepatch: semverInc(currentVersion, 'prepatch'),
  prerelease: semverInc(currentVersion, 'prerelease'),
});

const timeLog = (logInfo: string, type: 'start' | 'end') => {
  let info = '';
  if (type === 'start') {
    info = `=> 开始任务:${logInfo}`;
  } else {
    info = `✨ 结束任务:${logInfo}`;
  }
  const nowDate = new Date();
  console.log(
    `[${nowDate.toLocaleString()}.${nowDate.getMilliseconds().toString().padStart(3, '0')}] ${info}
    `,
  );
};

/**
 * 询问获取下一次版本号
 */
async function prompt(): Promise<string> {
  const nextVersions = getNextVersions();
  const { nextVersion } = await inquirer.prompt([
    {
      type: 'list',
      name: 'nextVersion',
      message: `请选择将要发布的版本 (当前版本 ${currentVersion})`,
      choices: (Object.keys(nextVersions) as Array<ReleaseType>).map((level) => ({
        name: `${level} => ${nextVersions[level]}`,
        value: nextVersions[level],
      })),
    },
  ]);
  return nextVersion;
}

/**
 * 更新版本号
 * @param nextVersion 新版本号
 */
async function updateVersion(nextVersion: string) {
  pkg.version = nextVersion;
  timeLog('修改package.json版本号', 'start');
  await fs.writeFileSync(path.resolve(__dirname, './../package.json'), JSON.stringify(pkg));
  await run('npx prettier package.json --write');
  timeLog('修改package.json版本号', 'end');
}

/**
 * 生成CHANGELOG
 */
async function generateChangelog() {
  timeLog('生成CHANGELOG.md', 'start');
  await run(' npx conventional-changelog -p angular -i CHANGELOG.md -s -r 0');
  timeLog('生成CHANGELOG.md', 'end');
}

/**
 * 将代码提交至git
 */
async function push(nextVersion: string) {
  timeLog('推送代码至git仓库', 'start');
  await run('git add package.json CHANGELOG.md');
  await run(`git commit -m "v${nextVersion}" -n`);
  await run('git push');
  timeLog('推送代码至git仓库', 'end');
}

/**
 * 组件库打包
 */
async function build() {
  timeLog('组件库打包', 'start');
  await run('npm run build');
  timeLog('组件库打包', 'end');
}

/**
 * 发布至npm
 */
async function publish() {
  timeLog('发布组件库', 'start');
  await run('npm publish');
  timeLog('发布组件库', 'end');
}

/**
 * 打tag提交至git
 */
async function tag(nextVersion: string) {
  timeLog('打tag并推送至git', 'start');
  await run(`git tag v${nextVersion}`);
  await run(`git push origin tag v${nextVersion}`);
  timeLog('打tag并推送至git', 'end');
}

async function main() {
  try {
    const nextVersion = await prompt();
    const startTime = Date.now();
    // =================== 更新版本号 ===================
    await updateVersion(nextVersion);
    // =================== 更新changelog ===================
    await generateChangelog();
    // =================== 代码推送git仓库 ===================
    await push(nextVersion);
    // =================== 组件库打包 ===================
    await build();
    // =================== 发布至npm ===================
    await publish();
    // =================== 打tag并推送至git ===================
    await tag(nextVersion);
    console.log(`✨ 发布流程结束 共耗时${((Date.now() - startTime) / 1000).toFixed(3)}s`);
  } catch (error) {
    console.log('💣 发布失败,失败原因:', error);
  }
}

main();

其他

每次初始化一个组件就要新建许多文件(夹),复制粘贴也可,不过还可以使用更高级一点的偷懒方式。

思路如下:

  1. 创建组件模板,预留动态信息插槽(组件名称,组件描述等等);
  2. 基于inquirer.js询问动态信息;
  3. 将信息插入模板,渲染至components文件夹下;
  4. 向 components/index.ts 插入导出语句。

我们只需要配置好模板以及问题,至于询问以及渲染就交给plop.js吧。

yarn add plop --dev

新增脚本命令。

package.json

"scripts": {
+ "new": "plop --plopfile ./scripts/plopfile.ts",
},

新增配置文件以及组件模板,详情可见:

cylqinsmoon commented 2 years ago

生成CHANGELOG的这段命令是不是有问题 Command failed: npx conventional-changelog -p angular -i CHANGELOG.md -s -r

worldzhao commented 2 years ago

生成CHANGELOG的这段命令是不是有问题 Command failed: npx conventional-changelog -p angular -i CHANGELOG.md -s -r

这里只是单纯使用第三方包,也许版本更新了命令就不一样了,在本文里没有详细解释的价值,可以参考其官方文档哈。

SoldierAb commented 2 years ago

new 脚本配之后报

image

配置是一样的,是版本的原因?

{
  "compilerOptions": {
    "target": "esnext",                                  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
    "module": "commonjs",                                /* Specify what module code is generated. */
    "moduleResolution": "node",                       /* Specify how TypeScript looks up a file from a given module specifier. */
    "baseUrl": "./",                                  /* Specify the base directory to resolve non-relative module names. */
    "resolveJsonModule": true,                        /* Enable importing .json files */
    "declaration": true,                              /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
    "declarationDir": "lib",                           /* Specify the output directory for generated declaration files. */
    "allowSyntheticDefaultImports": true,             /* Allow 'import x from y' when a module doesn't have a default export. */
    "esModuleInterop": true,                             /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
    "forceConsistentCasingInFileNames": true,            /* Ensure that casing is correct in imports. */
    "strict": true,                                      /* Enable all strict type-checking options. */
    "skipLibCheck": true                                 /* Skip type checking all .d.ts files. */
  },
  "include":["src","typings.d.ts"],
  "exclude":["node_modules", "src/**/demos", "src/**/__tests__"]
}

package.json

{
  "name": "k-view-react",
  "version": "1.0.0",
  "description": "",
  "typings": "lib/index.d.ts",
  "main": "lib/index.js",
  "module": "es/index.js",
  "authors": {
    "name": "cgj",
    "email": "ab140140@163.com"
  },
  "files": [
    "lib",
    "es"
  ],
  "scripts": {
    "dev": "dumi dev",
    "build:preview": "npm run build:site && serve site",
    "build:site": "rimraf site && dumi build",
    "build:types": "tsc -p tsconfig.build.json && cpr lib es",
    "clean": "rimraf lib es dist",
    "build": "npm run clean && npm run build:types && gulp",
    "deploy:site": "npm run build:site && gh-pages -d site",
    "commit": "git add . && git-cz",
    "test": "jest --coverage",
    "test:watch": "jest --watch",
    "test:update": "jest --updateSnapshot",
    "gen": "plop --plopfile ./scripts/plopfile.ts"
  },
  "author": "cgj",
  "license": "MIT",
  "devDependencies": {
    "@babel/core": "^7.18.0",
    "@babel/plugin-proposal-class-properties": "^7.17.12",
    "@babel/plugin-transform-runtime": "^7.18.0",
    "@babel/preset-env": "^7.18.0",
    "@babel/preset-typescript": "^7.17.12",
    "@babel/runtime": "^7.18.0",
    "@commitlint/cli": "^17.0.0",
    "@commitlint/config-conventional": "^17.0.0",
    "@testing-library/jest-dom": "^5.16.4",
    "@testing-library/react": "^13.2.0",
    "@types/jest": "^27.5.1",
    "@types/react": "^18.0.9",
    "@types/react-dom": "^18.0.4",
    "@umijs/fabric": "^2.10.2",
    "commitizen": "^4.2.4",
    "cpr": "^3.0.1",
    "cross-env": "^7.0.3",
    "cz-conventional-changelog": "^3.3.0",
    "dumi": "^1.1.42",
    "gh-pages": "^4.0.0",
    "git-cz": "^4.9.0",
    "gulp": "^4.0.2",
    "gulp-autoprefixer": "^8.0.0",
    "gulp-babel": "^8.0.0",
    "gulp-cssnano": "^2.1.3",
    "gulp-less": "^5.0.0",
    "husky": "^8.0.1",
    "identity-obj-proxy": "^3.0.0",
    "jest": "^28.1.0",
    "jest-environment-jsdom": "^28.1.0",
    "lint-staged": "^12.4.1",
    "plop": "^3.1.0",
    "prettier": "^2.6.2",
    "react": "^18.1.0",
    "react-dom": "^18.1.0",
    "serve": "^13.0.2",
    "through2": "^4.0.2",
    "ts-jest": "^28.0.2",
    "ts-node": "^10.8.0",
    "typescript": "^4.6.4"
  },
  "sideEffects": [
    "dist/**",
    "es/**/style/*",
    "lib/**/style/*",
    "*.less"
  ],
  "lint-staged": {
    "src/**/*.ts?(x)": [
      "prettier --write",
      "eslint --fix",
      "jest --bail --findRelatedTests",
      "git add"
    ],
    "src/**/*.less": [
      "stylelint --syntax less --fix",
      "git add"
    ]
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "config": {
    "commitizen": {
      "path": "cz-conventional-changelog"
    }
  },
  "dependencies": {
    "prop-types": "^15.8.1"
  }
}

对应仓库 https://github.com/SoldierAb/k-view-react

worldzhao commented 2 years ago

new 脚本配之后报 image

配置是一样的,是版本的原因?

{
  "compilerOptions": {
    "target": "esnext",                                  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
    "module": "commonjs",                                /* Specify what module code is generated. */
    "moduleResolution": "node",                       /* Specify how TypeScript looks up a file from a given module specifier. */
    "baseUrl": "./",                                  /* Specify the base directory to resolve non-relative module names. */
    "resolveJsonModule": true,                        /* Enable importing .json files */
    "declaration": true,                              /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
    "declarationDir": "lib",                           /* Specify the output directory for generated declaration files. */
    "allowSyntheticDefaultImports": true,             /* Allow 'import x from y' when a module doesn't have a default export. */
    "esModuleInterop": true,                             /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
    "forceConsistentCasingInFileNames": true,            /* Ensure that casing is correct in imports. */
    "strict": true,                                      /* Enable all strict type-checking options. */
    "skipLibCheck": true                                 /* Skip type checking all .d.ts files. */
  },
  "include":["src","typings.d.ts"],
  "exclude":["node_modules", "src/**/demos", "src/**/__tests__"]
}

package.json

{
  "name": "k-view-react",
  "version": "1.0.0",
  "description": "",
  "typings": "lib/index.d.ts",
  "main": "lib/index.js",
  "module": "es/index.js",
  "authors": {
    "name": "cgj",
    "email": "ab140140@163.com"
  },
  "files": [
    "lib",
    "es"
  ],
  "scripts": {
    "dev": "dumi dev",
    "build:preview": "npm run build:site && serve site",
    "build:site": "rimraf site && dumi build",
    "build:types": "tsc -p tsconfig.build.json && cpr lib es",
    "clean": "rimraf lib es dist",
    "build": "npm run clean && npm run build:types && gulp",
    "deploy:site": "npm run build:site && gh-pages -d site",
    "commit": "git add . && git-cz",
    "test": "jest --coverage",
    "test:watch": "jest --watch",
    "test:update": "jest --updateSnapshot",
    "gen": "plop --plopfile ./scripts/plopfile.ts"
  },
  "author": "cgj",
  "license": "MIT",
  "devDependencies": {
    "@babel/core": "^7.18.0",
    "@babel/plugin-proposal-class-properties": "^7.17.12",
    "@babel/plugin-transform-runtime": "^7.18.0",
    "@babel/preset-env": "^7.18.0",
    "@babel/preset-typescript": "^7.17.12",
    "@babel/runtime": "^7.18.0",
    "@commitlint/cli": "^17.0.0",
    "@commitlint/config-conventional": "^17.0.0",
    "@testing-library/jest-dom": "^5.16.4",
    "@testing-library/react": "^13.2.0",
    "@types/jest": "^27.5.1",
    "@types/react": "^18.0.9",
    "@types/react-dom": "^18.0.4",
    "@umijs/fabric": "^2.10.2",
    "commitizen": "^4.2.4",
    "cpr": "^3.0.1",
    "cross-env": "^7.0.3",
    "cz-conventional-changelog": "^3.3.0",
    "dumi": "^1.1.42",
    "gh-pages": "^4.0.0",
    "git-cz": "^4.9.0",
    "gulp": "^4.0.2",
    "gulp-autoprefixer": "^8.0.0",
    "gulp-babel": "^8.0.0",
    "gulp-cssnano": "^2.1.3",
    "gulp-less": "^5.0.0",
    "husky": "^8.0.1",
    "identity-obj-proxy": "^3.0.0",
    "jest": "^28.1.0",
    "jest-environment-jsdom": "^28.1.0",
    "lint-staged": "^12.4.1",
    "plop": "^3.1.0",
    "prettier": "^2.6.2",
    "react": "^18.1.0",
    "react-dom": "^18.1.0",
    "serve": "^13.0.2",
    "through2": "^4.0.2",
    "ts-jest": "^28.0.2",
    "ts-node": "^10.8.0",
    "typescript": "^4.6.4"
  },
  "sideEffects": [
    "dist/**",
    "es/**/style/*",
    "lib/**/style/*",
    "*.less"
  ],
  "lint-staged": {
    "src/**/*.ts?(x)": [
      "prettier --write",
      "eslint --fix",
      "jest --bail --findRelatedTests",
      "git add"
    ],
    "src/**/*.less": [
      "stylelint --syntax less --fix",
      "git add"
    ]
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "config": {
    "commitizen": {
      "path": "cz-conventional-changelog"
    }
  },
  "dependencies": {
    "prop-types": "^15.8.1"
  }
}

对应仓库 https://github.com/SoldierAb/k-view-react

plop 从 2.x 升级了一个大版本至 3.x,大概率是版本问题导致的,尝试降级看看。

不过还是建议使用最新版本,我周末会看看哪里出了问题,周中需要上班暂时没有时间,如果解决了也欢迎同步一下结论哈。

SoldierAb commented 2 years ago

new 脚本配之后报 image 配置是一样的,是版本的原因?

{
  "compilerOptions": {
    "target": "esnext",                                  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
    "module": "commonjs",                                /* Specify what module code is generated. */
    "moduleResolution": "node",                       /* Specify how TypeScript looks up a file from a given module specifier. */
    "baseUrl": "./",                                  /* Specify the base directory to resolve non-relative module names. */
    "resolveJsonModule": true,                        /* Enable importing .json files */
    "declaration": true,                              /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
    "declarationDir": "lib",                           /* Specify the output directory for generated declaration files. */
    "allowSyntheticDefaultImports": true,             /* Allow 'import x from y' when a module doesn't have a default export. */
    "esModuleInterop": true,                             /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
    "forceConsistentCasingInFileNames": true,            /* Ensure that casing is correct in imports. */
    "strict": true,                                      /* Enable all strict type-checking options. */
    "skipLibCheck": true                                 /* Skip type checking all .d.ts files. */
  },
  "include":["src","typings.d.ts"],
  "exclude":["node_modules", "src/**/demos", "src/**/__tests__"]
}

package.json

{
  "name": "k-view-react",
  "version": "1.0.0",
  "description": "",
  "typings": "lib/index.d.ts",
  "main": "lib/index.js",
  "module": "es/index.js",
  "authors": {
    "name": "cgj",
    "email": "ab140140@163.com"
  },
  "files": [
    "lib",
    "es"
  ],
  "scripts": {
    "dev": "dumi dev",
    "build:preview": "npm run build:site && serve site",
    "build:site": "rimraf site && dumi build",
    "build:types": "tsc -p tsconfig.build.json && cpr lib es",
    "clean": "rimraf lib es dist",
    "build": "npm run clean && npm run build:types && gulp",
    "deploy:site": "npm run build:site && gh-pages -d site",
    "commit": "git add . && git-cz",
    "test": "jest --coverage",
    "test:watch": "jest --watch",
    "test:update": "jest --updateSnapshot",
    "gen": "plop --plopfile ./scripts/plopfile.ts"
  },
  "author": "cgj",
  "license": "MIT",
  "devDependencies": {
    "@babel/core": "^7.18.0",
    "@babel/plugin-proposal-class-properties": "^7.17.12",
    "@babel/plugin-transform-runtime": "^7.18.0",
    "@babel/preset-env": "^7.18.0",
    "@babel/preset-typescript": "^7.17.12",
    "@babel/runtime": "^7.18.0",
    "@commitlint/cli": "^17.0.0",
    "@commitlint/config-conventional": "^17.0.0",
    "@testing-library/jest-dom": "^5.16.4",
    "@testing-library/react": "^13.2.0",
    "@types/jest": "^27.5.1",
    "@types/react": "^18.0.9",
    "@types/react-dom": "^18.0.4",
    "@umijs/fabric": "^2.10.2",
    "commitizen": "^4.2.4",
    "cpr": "^3.0.1",
    "cross-env": "^7.0.3",
    "cz-conventional-changelog": "^3.3.0",
    "dumi": "^1.1.42",
    "gh-pages": "^4.0.0",
    "git-cz": "^4.9.0",
    "gulp": "^4.0.2",
    "gulp-autoprefixer": "^8.0.0",
    "gulp-babel": "^8.0.0",
    "gulp-cssnano": "^2.1.3",
    "gulp-less": "^5.0.0",
    "husky": "^8.0.1",
    "identity-obj-proxy": "^3.0.0",
    "jest": "^28.1.0",
    "jest-environment-jsdom": "^28.1.0",
    "lint-staged": "^12.4.1",
    "plop": "^3.1.0",
    "prettier": "^2.6.2",
    "react": "^18.1.0",
    "react-dom": "^18.1.0",
    "serve": "^13.0.2",
    "through2": "^4.0.2",
    "ts-jest": "^28.0.2",
    "ts-node": "^10.8.0",
    "typescript": "^4.6.4"
  },
  "sideEffects": [
    "dist/**",
    "es/**/style/*",
    "lib/**/style/*",
    "*.less"
  ],
  "lint-staged": {
    "src/**/*.ts?(x)": [
      "prettier --write",
      "eslint --fix",
      "jest --bail --findRelatedTests",
      "git add"
    ],
    "src/**/*.less": [
      "stylelint --syntax less --fix",
      "git add"
    ]
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "config": {
    "commitizen": {
      "path": "cz-conventional-changelog"
    }
  },
  "dependencies": {
    "prop-types": "^15.8.1"
  }
}

对应仓库 https://github.com/SoldierAb/k-view-react

plop 从 2.x 升级了一个大版本至 3.x,大概率是版本问题导致的,尝试降级看看。

不过还是建议使用最新版本,我周末会看看哪里出了问题,周中需要上班暂时没有时间,如果解决了也欢迎同步一下结论哈。

降级是没问题的。

不过我用了3.x的版本, 关于 .ts 的支持issue 上有讨论 https://github.com/plopjs/plop/issues/297#issuecomment-983677121

最终我采用了 esbuild-node-loader 这个方案, 参考 https://github.com/plopjs/plop/issues/297#issuecomment-1046621424, 不过这是个experimental api , 后期应该会改动 = =...

仓库也更新了, 可参考 gen 脚本 https://github.com/SoldierAb/k-view-react/commit/3e383b5b5113a1092df3c0fea62e53731c8c1e20#diff-7ae45ad102eab3b6d7e7896acd08c427a9b25b346470d7bc6507b6481575d519R28

兄台如果有更好的方案欢迎交流~

ps: 之前没用过这个plop, 其他仓库用的自己实现的脚本类似 https://github.com/SoldierAb/k-view-next/blob/main/build/genComp.js。 不过值得肯定的是 plop 体验还是很不错的,学习一波~

worldzhao commented 2 years ago

@SoldierAb 感谢,我周末也研究下更新一版 hhhh