<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          從零打造組件庫

          共 16525字,需瀏覽 34分鐘

           ·

          2021-02-16 14:12

          作者:liuxuan
          來源:SegmentFault 思否社區(qū)



          前言



          組件庫,一套標準化的組件集合,是前端工程師開發(fā)提效不可或缺的工具。

          業(yè)內(nèi)優(yōu)秀的組件庫比如 Antd Design 和 Element UI,大大節(jié)省了我們的開發(fā)時間。那么,做一套組件庫,容易嗎?

          答案肯定是不容易,當你去做這件事的時候,會發(fā)現(xiàn)它其實是一套體系。從開發(fā)、編譯、測試,到最后發(fā)布,每一個流程都需要大量的知識積累。但是當你真正完成了一個組件庫的搭建后,會發(fā)現(xiàn)收獲的也許比想象中更多。

          希望能夠通過本文幫助大家梳理一套組件庫搭建的知識體系,聚點成面,如果能夠幫助到你。



          概覽


          本文主要包括以下內(nèi)容:

          • 環(huán)境搭建:Typescript + ESLint + StyleLint + Prettier + Husky
          • 組件開發(fā):標準化的組件開發(fā)目錄及代碼結(jié)構(gòu)
          • 文檔站點:基于 docz 的文檔演示站點
          • 編譯打包:輸出符合 umd / esm / cjs 三種規(guī)范的打包產(chǎn)物
          • 單元測試:基于 jest 的 React 組件測試方案及完整報告
          • 一鍵發(fā)版:整合多條命令,流水線控制 npm publish 全部過程
          • 線上部署:基于 now 快速部署線上文檔站點

          如有錯誤歡迎在評論區(qū)進行交流~



          初始化


          整體目錄


          ├──?CHANGELOG.md????//?CHANGELOG
          ├──?README.md????//?README
          ├──?babel.config.js????//?babel?配置
          ├──?build????//?編譯發(fā)布相關(guān)
          │???├──?constant.js
          │???├──?release.js
          │???└──?rollup.config.dist.js
          ├──?components????//?組件源碼
          │???├──?Alert
          │???├──?Button
          │???├──?index.tsx
          │???└──?style
          ├──?coverage????//?測試報告
          │???├──?clover.xml
          │???├──?coverage-final.json
          │???├──?lcov-report
          │???└──?lcov.info
          ├── dist ???//?組件庫打包產(chǎn)物:UMD
          │???├──?frog.css
          │???├──?frog.js
          │???├──?frog.js.map
          │???├──?frog.min.css
          │???├──?frog.min.js
          │???└──?frog.min.js.map
          ├──?doc????//?組件庫文檔站點
          │???├──?Alert.mdx
          │???└──?button.mdx
          ├──?doczrc.js????//?docz?配置
          ├── es ???//?組件庫打包產(chǎn)物:ESM
          │???├──?Alert
          │???├──?Button
          │???├──?index.js
          │???└──?style
          ├──?gatsby-config.js????//?docz?主題配置
          ├──?gulpfile.js????//?gulp?配置
          ├── lib ???//?組件庫打包產(chǎn)物:CJS
          │???├──?Alert
          │???├──?Button
          │???├──?index.js
          │???└──?style
          ├──?package-lock.json
          ├──?package.json????//?package.json
          └──?tsconfig.json????//?typescript?配置

          配置 ESLint + StyleLint + Prettier


          每個 Lint 都可以單獨拿出來寫一篇文章,但配置不是我們的重點,所以這里使用?@umijs/fabric,一個包含 ESLint + StyleLint + Prettier 的配置文件合集,能夠大大節(jié)省我們的時間。

          感興趣的同學可以去查看它的源碼,在時間允許的情況下自己從零配置當做學習也是不錯的。

          安裝

          yarn?add?@umijs/fabric?prettier?@typescript-eslint/eslint-plugin?-D

          .eslintrc.js

          module.exports?=?{
          ??parser:?'@typescript-eslint/parser',
          ??extends:?[
          ????require.resolve('@umijs/fabric/dist/eslint'),
          ????'prettier/@typescript-eslint',
          ????'plugin:react/recommended'
          ??],
          ??rules:?{
          ????'react/prop-types':?'off',
          ????"no-unused-expressions":?"off",
          ????"@typescript-eslint/no-unused-expressions":?["error",?{?"allowShortCircuit":?true?}]
          ??},
          ??ignorePatterns:?['.eslintrc.js'],
          ??settings:?{
          ????react:?{
          ??????version:?"detect"
          ????}
          ??}
          }


          由于?@umijs/fabric 中判斷 isTsProject 的目錄路徑如圖所示是基于 src 的,且無法修改,我們這里組件源碼在 components 路徑下,所以這里要手動添加相關(guān) typescript 的配置。

          .prettierrc.js

          const?fabric?=?require('@umijs/fabric');

          module.exports?=?{
          ??...fabric.prettier,
          };

          .stylelintrc.js

          module.exports?=?{
          ??extends:?[require.resolve('@umijs/fabric/dist/stylelint')],
          };

          配置 Husky + Lint-Staged


          husky 提供了多種鉤子來攔截 git 操作,比如 git commit 或 git push 等。但是一般情況我們都是接手已有的項目,如果對所有代碼都做 Lint 檢查的話修復(fù)成本太高了,所以我們希望能夠只對自己提交的代碼做檢查,這樣就可以從現(xiàn)在開始對大家的開發(fā)規(guī)范進行約束,已有的代碼等修改的時候再做檢查。

          這樣就引入了 lint-staged,可以只對當前 commit 的代碼做檢查并且可以編寫正則匹配文件。

          安裝

          yarn?add?husky?lint-staged?-D

          package.json

          "lint-staged":?{
          ??"components/**/*.ts?(x)":?[
          ????"prettier?--write",
          ????"eslint?--fix"
          ??],
          ??"components/**/**/*.less":?[
          ????"stylelint?--syntax?less?--fix"
          ??]
          },
          "husky":?{
          ??"hooks":?{
          ????"pre-commit":?"lint-staged"
          ??}
          }

          配置 Typescript


          typescript.json

          {
          ??"compilerOptions":?{
          ????"baseUrl":?"./",
          ????"module":?"commonjs",
          ????"target":?"es5",
          ????"lib":?["es6",?"dom"],
          ????"sourceMap":?true,
          ????"allowJs":?true,
          ????"jsx":?"react",
          ????"moduleResolution":?"node",
          ????"rootDir":?"src",
          ????"noImplicitReturns":?true,
          ????"noImplicitThis":?true,
          ????"noImplicitAny":?true,
          ????"strictNullChecks":?true,
          ????"experimentalDecorators":?true,
          ????"allowSyntheticDefaultImports":?true,
          ????"esModuleInterop":?true,
          ????"paths":?{
          ??????"components/*":?["src/components/*"]
          ????}
          ??},
          ??"include":?[
          ????"components"
          ??],
          ??"exclude":?[
          ????"node_modules",
          ????"build",
          ????"dist",
          ????"lib",
          ????"es"
          ??]
          }



          組件開發(fā)


          正常寫組件大家都很熟悉了,這里我們主要看一下目錄結(jié)構(gòu)和部分代碼:

          ├──?Alert
          │???├──?__tests__
          │???├──?index.tsx
          │???└──?style
          ├──?Button
          │???├──?__tests__
          │???├──?index.tsx
          │???└──?style
          ├──?index.tsx
          └──?style
          ????├──?color
          ????├──?core
          ????├──?index.less
          ????└──?index.tsx

          components/index.ts 是整個組件庫的入口,負責收集所有組件并導(dǎo)出:

          export?{?default?as?Button?}?from?'./Button';
          export?{?default?as?Alert?}?from?'./Alert';

          components/style 包含組件庫的基礎(chǔ) less 文件,包含 core、color 等通用樣式及變量設(shè)置。

          每個 style 目錄下都至少包含 index.tsx 及 index.less 兩個文件:

          style/index.tsx

          import?'./index.less';

          style/index.less

          @import?'./core/index';
          @import?'./color/default';

          可以看到,style/index.tsx 是作為每個組件樣式引用的唯一入口而存在。

          __tests__ 是組件的單元測試目錄,后續(xù)會單獨講到。具體 Alert 和 Button 組件的代碼都很簡單,這里就不贅述,大家可以去源碼里找到。



          組件測試


          為什么要寫測試以及是否有必要做測試,社區(qū)內(nèi)已經(jīng)有很多的探討,大家可以根據(jù)自己的實際業(yè)務(wù)場景來做決定,我個人的意見是:

          • 基礎(chǔ)工具,一定要做好單元測試,比如 utils、hooks、components
          • 業(yè)務(wù)代碼,由于更新迭代快,不一定有時間去寫單測,根據(jù)節(jié)奏自行決定

          但是單測的意義肯定是正向的:

          The more your tests resemble the way your software is used, the more confidence they can give you. -?Kent C. Dodds

          安裝

          yarn?add?jest?babel-jest?@babel/preset-env?@babel/preset-react?react-test-renderer?@testing-library/react?-D
          yarn?add?@types/jest?@types/react-test-renderer?-D

          package.json

          "scripts":?{
          ??"test":?"jest",
          ??"test:coverage":?"jest?--coverage"
          }

          在每個組件下新增 __tests__/index.test.tsx,作為單測入口文件。

          import?React?from?'react';
          import?renderer?from?'react-test-renderer';
          import?Alert?from?'../index';

          describe('Component??Test',?()?=>?{
          ??test('should?render?default',?()?=>?{
          ????const?component?=?renderer.create("default"?/>);
          ????const?tree?=?component.toJSON();
          ????expect(tree).toMatchSnapshot();
          ??});

          ??test('should?render?specific?type',?()?=>?{
          ????const?types:?any[]?=?['success',?'info',?'warning',?'error'];
          ????const?component?=?renderer.create(
          ??????<>
          ????????{types.map((type)?=>?(
          ??????????type}?type={type}?message={type}?/>
          ????????))}
          ??????,
          ????);
          ????const?tree?=?component.toJSON();
          ????expect(tree).toMatchSnapshot();
          ??});
          });

          這里采用的是 snapshot 快照的測試方式,所謂快照,就是在當前執(zhí)行測試用例的時候,生成一份測試結(jié)果的快照,保存在 __snapshots__/index.test.tsx.snap 文件中。下次再執(zhí)行測試用例的時候,如果我們修改了組件的源碼,那么會將本次的結(jié)果快照和上次的快照進行比對,如果不匹配,則測試不通過,需要我們修改測試用例更新快照。這樣就保證了每次源碼的修改必須要和上次測試的結(jié)果快照做比對,才能確定是否通過,省去了寫復(fù)雜的邏輯測試代碼,是一種簡化的測試手段。

          還有一種是基于 DOM 的測試,基于 @testing-library/react:

          import?React?from?'react';
          import?{?fireEvent,?render,?screen?}?from?'@testing-library/react';
          import?renderer?from?'react-test-renderer';
          import?Button?from?'../index';

          describe('Component??Test',?()?=>?{
          ??let?testButtonClicked?=?false;
          ??const?onClick?=?()?=>?{
          ????testButtonClicked?=?true;
          ??};

          ??test('should?render?default',?()?=>?{
          ????//?snapshot?test
          ????const?component?=?renderer.create(default);
          ????const?tree?=?component.toJSON();
          ????expect(tree).toMatchSnapshot();

          ????//?dom?test
          ????render(default);
          ????const?btn?=?screen.getByText('default');
          ????fireEvent.click(btn);
          ????expect(testButtonClicked).toEqual(true);
          ??});
          });

          可以看到,@testing-library/react 提供了一些方法,render 將組件渲染到 DOM 中,screen 提供了各種方法可以從頁面中獲取相應(yīng) DOM 元素,fireEvent 負責觸發(fā) DOM 元素綁定的事件。

          更多關(guān)于組件測試的細節(jié)推薦閱讀以下文章:

          • The Complete Beginner's Guide to Testing React Apps:通過簡單的 測試講到 ToDoApp 的完整測試,并且對比了 Enzyme 和?@testing-library/react 的區(qū)別,是很好的入門文章
          • React 單元測試策略及落地:系統(tǒng)的講述了單元測試的意義及落地方案




          組件庫打包


          組件庫打包是我們的重頭戲,我們主要實現(xiàn)以下目標:

          • 導(dǎo)出 umd / cjs / esm 三種規(guī)范文件
          • 導(dǎo)出組件庫 css 樣式文件
          • 支持按需加載

          這里我們圍繞 package.json 中的三個字段展開:main、module 以及 unpkg。

          {
          ??"main":?"lib/index.js",
          ??"module":?"es/index.js",
          ??"unpkg":?"dist/frog.min.js"
          }

          我們?nèi)タ礃I(yè)內(nèi)各個組件庫的源碼時,總能看到這三個字段,那么它們的作用究竟是什么呢?

          • main,是包的入口文件,我們通過 require 或者 import 加載 npm 包的時候,會從 main 字段獲取需要加載的文件
          • module,是由打包工具提出的一個字段,目前還不在 package.json 官方規(guī)范中,負責指定符合 esm 規(guī)范的入口文件。當 webpack 或者 rollup 在加載 npm 包的時候,如果看到有 module 字段,會優(yōu)先加載 esm 入口文件,因為可以更好的做 tree-shaking,減小代碼體積。
          • unpkg,也是一個非官方字段,負責讓 npm 包中的文件開啟 CDN 服務(wù),意味著我們可以通過 https://unpkg.com/?直接獲取到文件內(nèi)容。比如這里我們就可以通過 https://unpkg.com/[email protected]... 直接獲取到 umd 版本的庫文件。

          我們使用 gulp 來串聯(lián)工作流,并通過三條命令分別導(dǎo)出三種格式文件:

          "scripts":?{
          ??"build":?"yarn?build:dist?&&?yarn?build:lib?&&?yarn?build:es",
          ??"build:dist":?"rm?-rf?dist?&&?gulp?compileDistTask",
          ??"build:lib":?"rm?-rf?lib?&&?gulp",
          ??"build:es":?"rm?-rf?es?&&?cross-env?ENV_ES=true?gulp"
          }

          • build,聚合命令
          • build:es,輸出 esm 規(guī)范,目錄為 es
          • build:lib,輸出?cjs?規(guī)范,目錄為?lib
          • build:dist,輸出 umd 規(guī)范,目錄為 dist


          導(dǎo)出 umd


          通過執(zhí)行 gulp compileDistTask 來導(dǎo)出 umd 文件,具體看一下 gulpfile:

          gulpfile

          function?_transformLess(lessFile,?config?=?{})?{
          ??const?{?cwd?=?process.cwd()?}?=?config;
          ??const?resolvedLessFile?=?path.resolve(cwd,?lessFile);

          ??let?data?=?readFileSync(resolvedLessFile,?'utf-8');
          ??data?=?data.replace(/^\uFEFF/,?'');

          ??const?lessOption?=?{
          ????paths:?[path.dirname(resolvedLessFile)],
          ????filename:?resolvedLessFile,
          ????plugins:?[new?NpmImportPlugin({?prefix:?'~'?})],
          ????javascriptEnabled:?true,
          ??};

          ??return?less
          ????.render(data,?lessOption)
          ????.then(result?=>?postcss([autoprefixer]).process(result.css,?{?from:?undefined?}))
          ????.then(r?=>?r.css);
          }

          async?function?_compileDistJS()?{
          ??const?inputOptions?=?rollupConfig;
          ??const?outputOptions?=?rollupConfig.output;

          ??//?打包?frog.js
          ??const?bundle?=?await?rollup.rollup(inputOptions);
          ??await?bundle.generate(outputOptions);
          ??await?bundle.write(outputOptions);

          ??//?打包?frog.min.js
          ??inputOptions.plugins.push(terser());
          ??outputOptions.file?=?`${DIST_DIR}/${DIST_NAME}.min.js`;

          ??const?bundleUglify?=?await?rollup.rollup(inputOptions);
          ??await?bundleUglify.generate(outputOptions);
          ??await?bundleUglify.write(outputOptions);
          }

          function?_compileDistCSS()?{
          ??return?src('components/**/*.less')
          ????.pipe(
          ??????through2.obj(function?(file,?encoding,?next)?{
          ????????if?(
          ??????????//?編譯?style/index.less?為?.css
          ??????????file.path.match(/(\/|\\)style(\/|\\)index\.less$/)
          ????????)?{
          ??????????_transformLess(file.path)
          ????????????.then(css?=>?{
          ??????????????file.contents?=?Buffer.from(css);
          ??????????????file.path?=?file.path.replace(/\.less$/,?'.css');
          ??????????????this.push(file);
          ??????????????next();
          ????????????})
          ????????????.catch(e?=>?{
          ??????????????console.error(e);
          ????????????});
          ????????}?else?{
          ??????????next();
          ????????}
          ??????}),
          ????)
          ????.pipe(concat(`./${DIST_NAME}.css`))
          ????.pipe(dest(DIST_DIR))
          ????.pipe(uglifycss())
          ????.pipe(rename(`./${DIST_NAME}.min.css`))
          ????.pipe(dest(DIST_DIR));
          }

          exports.compileDistTask?=?series(_compileDistJS,?_compileDistCSS);

          rollup.config.dist.js

          const?resolve?=?require('@rollup/plugin-node-resolve');
          const?{?babel?}?=?require('@rollup/plugin-babel');
          const?peerDepsExternal?=?require('rollup-plugin-peer-deps-external');
          const?commonjs?=?require('@rollup/plugin-commonjs');
          const?{?terser?}?=?require('rollup-plugin-terser');
          const?image?=?require('@rollup/plugin-image');
          const?{?DIST_DIR,?DIST_NAME?}?=?require('./constant');

          module.exports?=?{
          ??input:?'components/index.tsx',
          ??output:?{
          ????name:?'Frog',
          ????file:?`${DIST_DIR}/${DIST_NAME}.js`,
          ????format:?'umd',
          ????sourcemap:?true,
          ????globals:?{
          ??????'react':?'React',
          ??????'react-dom':?'ReactDOM'
          ????}
          ??},
          ??plugins:?[
          ????peerDepsExternal(),
          ????commonjs({
          ??????include:?['node_modules/**',?'../../node_modules/**'],
          ??????namedExports:?{
          ????????'react-is':?['isForwardRef',?'isValidElementType'],
          ??????}
          ????}),
          ????resolve({
          ??????extensions:?['.tsx',?'.ts',?'.js'],
          ??????jsnext:?true,
          ??????main:?true,
          ??????browser:?true
          ????}),
          ????babel({
          ??????exclude:?'node_modules/**',
          ??????babelHelpers:?'bundled',
          ??????extensions:?['.js',?'.jsx',?'ts',?'tsx']
          ????}),
          ????image()
          ??]
          }

          rollup 或者 webpack 這類打包工具,最擅長的就是由一個或多個入口文件,依次尋找依賴,打包成一個或多個 Chunk 文件,而 umd 就是要輸出為一個 js 文件。

          所以這里選用 rollup 負責打包 umd 文件,入口為 component/index.tsx,輸出 format 為 umd 格式。

          為了同時打包 frog.js 和 frog.min.js,在 _compileDistJS 中引入了 teser 插件,執(zhí)行了兩次 rollup 打包。

          一個組件庫只有 JS 文件肯定不夠用,還需要有樣式文件,比如使用 Antd 時:

          import?{?DatePicker?}?from?'antd';
          import?'antd/dist/antd.css';?//?or?'antd/dist/antd.less'

          ReactDOM.render(,?mountNode);

          所以,我們也要打包出一份組件庫的 CSS 文件。

          這里 _compileDistCSS 的作用是,遍歷 components 目錄下的所有 less 文件,匹配到所有的 index.less 入口樣式文件,使用 less 編譯為 CSS 文件,并且進行聚合,最后輸出為 frog.css 和 frog.min.css。

          最終 dist 目錄結(jié)構(gòu)如下:

          ├──?frog.css
          ├──?frog.js
          ├──?frog.js.map
          ├──?frog.min.css
          ├──?frog.min.js
          └──?frog.min.js.map


          導(dǎo)出 cjs 和 esm


          導(dǎo)出 cjs 或者 esm,意味著模塊化導(dǎo)出,并不是一個聚合的 JS 文件,而是每個組件是一個模塊,只不過 cjs 的代碼時符合 Commonjs 標準,esm 的代碼時 ES Module 標準。

          所以,我們自然的就想到了 babel,它的作用不就是編譯高級別的代碼到各種格式嘛。

          gulpfile

          function?_compileJS()?{
          ??return?src(['components/**/*.{tsx,?ts,?js}',?'!components/**/__tests__/*.{tsx,?ts,?js}'])
          ????.pipe(
          ??????babel({
          ????????presets:?[
          ??????????[
          ????????????'@babel/preset-env',
          ????????????{
          ??????????????modules:?ENV_ES?===?'true'???false?:?'commonjs',
          ????????????},
          ??????????],
          ????????],
          ??????}),
          ????)
          ????.pipe(dest(ENV_ES?===?'true'???ES_DIR?:?LIB_DIR));
          }

          function?_copyLess()?{
          ??return?src('components/**/*.less').pipe(dest(ENV_ES?===?'true'???ES_DIR?:?LIB_DIR));
          }

          function?_copyImage()?{
          ??return?src('components/**/*.@(jpg|jpeg|png|svg)').pipe(
          ????dest(ENV_ES?===?'true'???ES_DIR?:?LIB_DIR),
          ??);
          }

          exports.default?=?series(_compileJS,?_copyLess,?_copyImage);

          babel.config.js

          module.exports?=?{
          ??presets:?[
          ????"@babel/preset-react",
          ????"@babel/preset-typescript",
          ????"@babel/preset-env"
          ??],
          ??plugins:?[
          ????"@babel/plugin-proposal-class-properties"
          ??]
          };

          這里代碼就相對簡單了,掃描 components 目錄下的 tsx 文件,使用 babel 編譯后,拷貝到 es 或 lib 目錄。less 文件直接拷貝,這里 _copyImage 是為了防止有圖片,也直接拷貝過去,但是組件庫中不建議用圖片,可以用字體圖標代替。



          組件文檔


          這里使用 docz 來搭建文檔站點,更具體的使用方法大家可以閱讀官網(wǎng)文檔,這里不再贅述。

          doc/Alert.mdx

          ---
          name:?Alert?警告提示
          route:?/alert
          menu:?反饋
          ---

          import?{?Playground,?Props?}?from?'docz'
          import?{?Alert?}?from?'../components/';
          import?'../components/Alert/style';

          #?Alert
          警告提示,展現(xiàn)需要關(guān)注的信息。



          ##?基本用法


          ??"Success?Text"?type="success"?/>
          ??"Info?Text"?type="info"?/>
          ??"Warning?Text"?type="warning"?/>
          ??"Error?Text"?type="error"?/>


          package.json

          "scripts":?{
          ??"docz:dev":?"docz?dev",
          ??"docz:build":?"docz?build",
          ??"docz:serve":?"docz?build?&&?docz?serve"
          }



          線上文檔站點部署


          這里使用 now.sh 來部署線上站點,注冊后安裝命令行,登錄成功。

          yarn?docz:build
          cd?.docz/dist
          now?deploy
          vercel?--production



          一鍵發(fā)版


          我們在發(fā)布新版 npm 包時會有很多步驟,這里提供一套腳本來實現(xiàn)一鍵發(fā)版。

          安裝

          yarn?add?conventional-changelog-cli?-D

          release.js

          const?child_process?=?require('child_process');
          const?fs?=?require('fs');
          const?path?=?require('path');
          const?inquirer?=?require('inquirer');
          const?chalk?=?require('chalk');
          const?util?=?require('util');
          const?semver?=?require('semver');
          const?exec?=?util.promisify(child_process.exec);
          const?semverInc?=?semver.inc;
          const?pkg?=?require('../package.json');

          const?currentVersion?=?pkg.version;

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

          const?logTime?=?(logInfo,?type)?=>?{
          ??const?info?=?`=>?${type}${logInfo}`;
          ??console.log((chalk.blue(`[${new?Date().toLocaleString()}]?${info}`)));
          };

          const?getNextVersions?=?()?=>?({
          ??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?promptNextVersion?=?async?()?=>?{
          ??const?nextVersions?=?getNextVersions();
          ??const?{?nextVersion?}?=?await?inquirer.prompt([
          ????{
          ??????type:?'list',
          ??????name:?'nextVersion',
          ??????message:?`Please?select?the?next?version?(current?version?is?${currentVersion})`,
          ??????choices:?Object.keys(nextVersions).map(name?=>?({
          ????????name:?`${name}?=>?${nextVersions[name]}`,
          ????????value:?nextVersions[name]
          ??????}))
          ????}
          ??]);
          ??return?nextVersion;
          };

          const?updatePkgVersion?=?async?nextVersion?=>?{
          ??pkg.version?=?nextVersion;
          ??logTime('Update?package.json?version',?'start');
          ??await?fs.writeFileSync(path.resolve(__dirname,?'../package.json'),?JSON.stringify(pkg));
          ??await?run('npx?prettier?package.json?--write');
          ??logTime('Update?package.json?version',?'end');
          };

          const?test?=?async?()?=>?{
          ??logTime('Test',?'start');
          ??await?run(`yarn?test:coverage`);
          ??logTime('Test',?'end');
          };

          const?genChangelog?=?async?()?=>?{
          ??logTime('Generate?CHANGELOG.md',?'start');
          ??await?run('?npx?conventional-changelog?-p?angular?-i?CHANGELOG.md?-s?-r?0');
          ??logTime('Generate?CHANGELOG.md',?'end');
          };

          const?push?=?async?nextVersion?=>?{
          ??logTime('Push?Git',?'start');
          ??await?run('git?add?.');
          ??await?run(`git?commit?-m?"publish?frog-ui@${nextVersion}"?-n`);
          ??await?run('git?push');
          ??logTime('Push?Git',?'end');
          };

          const?tag?=?async?nextVersion?=>?{
          ??logTime('Push?Git',?'start');
          ??await?run(`git?tag?v${nextVersion}`);
          ??await?run(`git?push?origin?tag?frog-ui@${nextVersion}`);
          ??logTime('Push?Git?Tag',?'end');
          };

          const?build?=?async?()?=>?{
          ??logTime('Components?Build',?'start');
          ??await?run(`yarn?build`);
          ??logTime('Components?Build',?'end');
          };

          const?publish?=?async?()?=>?{
          ??logTime('Publish?Npm',?'start');
          ??await?run('npm?publish');
          ??logTime('Publish?Npm',?'end');
          };

          const?main?=?async?()?=>?{
          ??try?{
          ????const?nextVersion?=?await?promptNextVersion();
          ????const?startTime?=?Date.now();

          ????await?test();
          ????await?updatePkgVersion(nextVersion);
          ????await?genChangelog();
          ????await?push(nextVersion);
          ????await?build();
          ????await?publish();
          ????await?tag(nextVersion);

          ????console.log(chalk.green(`Publish?Success,?Cost?${((Date.now()?-?startTime)?/?1000).toFixed(3)}s`));
          ??}?catch?(err)?{
          ????console.log(chalk.red(`Publish?Fail:?${err}`));
          ??}
          }

          main();

          package.json

          "scripts":?{
          ??"publish":?"node?build/release.js"
          }

          代碼也比較簡單,都是對一些工具的基本使用,通過執(zhí)行 yarn publish 就可以一鍵發(fā)版。



          點擊左下角閱讀原文,到?SegmentFault 思否社區(qū)?和文章作者展開更多互動和交流,掃描下方”二維碼“或在“公眾號后臺回復(fù)“?入群?”即可加入我們的技術(shù)交流群,收獲更多的技術(shù)文章~

          -?END -


          瀏覽 131
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本顶级黄色网址 | A片免费在线 | 成人AV天天干 | 操操操综合网 | 亚洲天堂手机在线 |