Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
worldzhao opened this issue May 9, 2021 · 6 comments
Open

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

worldzhao opened this issue May 9, 2021 · 6 comments

Comments

@worldzhao
Copy link
Owner

worldzhao commented May 9, 2021

前言

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

  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
Copy link

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

@worldzhao
Copy link
Owner Author

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

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

@SoldierAb
Copy link

SoldierAb commented May 24, 2022

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": "[email protected]"
  },
  "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
Copy link
Owner Author

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": "[email protected]"
  },
  "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
Copy link

SoldierAb commented May 24, 2022

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": "[email protected]"
  },
  "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 上有讨论 plopjs/plop#297 (comment)

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

仓库也更新了, 可参考 gen 脚本 SoldierAb/k-view-react@3e383b5#diff-7ae45ad102eab3b6d7e7896acd08c427a9b25b346470d7bc6507b6481575d519R28

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

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

@worldzhao
Copy link
Owner Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants