How to build a public style npm library for your team

1. Background

At present, Internet companies often have multiple projects maintained by a front-end team, and these multiple projects often have some overlapping styles or common basic capabilities. At this time, we need to develop related public component npm packages for internal maintenance of the team. Generally, two public components are maintained, one for the precipitation of public styles, and one for the precipitation of functional capabilities (uploading, hump Hungarian conversion, etc.). I think a relatively complete public component library is in line with engineering, and should meet the following points:
(1) meet the needs of contemporary front-end development (typescript, sass)
(2) have code specification capabilities (eslint)
(3) have automation Test ability (jest)
(4) Have UI ability on the development environment, so that developers can easily view UI status before npm release (storybook) (5) Have complete commit access control and script access control (commitlint, husky)
( 6) With automatic log function, and automatic version management (standard-version)
(7) Very convenient version release

The following is a full process of how to build an npm style library that meets the above requirements. Of course, if it is a public capability library, not all steps are necessary, such as sass, storybook, etc. may not need to be introduced.

2. Development related construction

1. Initialize the project

npm init
复制代码

2. Install react related packages

yarn add react react-dom
yarn add @types/react --dev
复制代码

3. Install typescript

yarn add typescript --dev

tsc --init
复制代码

Here, a tsconfig.json will be generated in the root directory, which is the configuration file of ts. Change the configuration item of jsx to react-jsx

{
    ...
    "jsx": "react-jsx", 
    ...
}
复制代码

4. Install eslint

yarn add eslint --dev

eslint --init
复制代码

After selecting the corresponding configuration, an eslint configuration file, .eslintrc.js, will be generated in the root directory. And add lint command in package.json file.

scripts: {
    ...
    "lint": "eslint --ext .tsx,.ts,.js src --cache --fix",
    ...
}
复制代码

After executing the script command yarn lint, as shown in the figureimage.png

5. Create a code directory

--
  ...
  --src
    --components
      --MyFirstComponent
        index.tsx
    --index.ts
  tsconfig.json
  .eslintrc.js
  ...
复制代码

Each component is under components, MyFirstComponent/index.tsx code:

import React from 'react';
function FirstComponent() {

  return (
    <div >
      这是第一个组件
    </div>
  );
}
export default FirstComponent;
复制代码

Create entry file index.ts

export { default as FirstComponent } from './components/FirstComponent';
复制代码

6,引入测试组件ts-jest以及相关依赖包

yarn add --dev jest ts-jest @types/jest @testing-library/react
复制代码

根目录创建jest配置文件jest.config.js,这里指定了匹配*.test.tsx文件。

module.exports = {
    preset: 'ts-jest',
    testEnvironment: 'jsdom',
    testMatch: [
        '**/*.test.tsx',
    ],
};
复制代码

添加在package.json中添加jest脚本命令

"scripts": {
  ...
  "test": "npx jest",
  ...
}
复制代码

在组件目录下添加相应的测试脚本文件,FirstComponent.test.tsx

  --src
    --components
      --MyFirstComponent
        + FirstComponent.test.tsx
        index.tsx
    --index.ts
复制代码

其中内容就jest的脚本执行文件

import React from 'react';
import { render } from '@testing-library/react';
import FirstComponent from './index';
    
test('rende my test', () => {
  render(<FirstComponent />);
});
复制代码

执行yarn test如图

image.png

7,安装storybook

storybook可以更加方便的让我们本地就可以看到组件的样式以及真实使用情况,这里需要根据自己的需求选择相应的配置项目

npx sb init 
复制代码

执行完成之后会在根目录下生成.storybook文件夹,以及在src目录下生成stories文件夹。.storybook目录下为storybook的配置文件。src下的stories文件夹直接删除。

--
  --.storybook
     main.js
     preview.js
  --src
  ...
复制代码

并在会在package.json自动生成相应的脚本命令。

"scripts": {
  "test": "npx jest",
  "lint": "eslint --ext .tsx,.ts,.js src --cache --fix",
  "storybook": "start-storybook -p 6006",
  "build-storybook": "build-storybook",
},
复制代码

在相应的组件目录下添加.stories文件,./src/components/FirstComponent/FirstComponent.stories.tsx

  --src
    --components
      --MyFirstComponent
        FirstComponent.test.tsx
        + FirstComponent.stoires.tsx
        index.tsx
    --index.ts
复制代码
import React from 'react';
import FirstComponent from './index';

export default {
  title: 'FirstComponent',
  component: FirstComponent,
};

export function FirstComponentStories() {
  return <FirstComponent />;
}
复制代码

执行yarn storybook启动storybook就可以看到结果,如图,左边是组件名称,右边是组件的渲染结果。

image.png

8,引入sass,以及所需组件

完成上述步骤,基本能力就差不多了,这时候引入css相关配置。

yarn add node-sass --dev
yarn add classnames
复制代码

在src目录下创建styles的相关文件_varibales.scss,index.scss

--src
  --styles
    _variables.scss
    index.scss  
复制代码

相关变量配置文件_variables.scss,这里使用类名的前缀的方式来将当前组件中的css隔离出来,一般的组件库都是采取了这种方式,如ant-design。其实更好的方法是cssModules,但是在打包时会产生一些问题,这里不展开赘述了。

$css-prefix: 'cru'
复制代码

在相应组件中添加style.scss样式文件

  --src
    --components
      --MyFirstComponent
        FirstComponent.test.tsx
        FirstComponent.stoires.tsx
        + styles.scss
        index.tsx
    --index.ts
复制代码
$first-component-prefix: 'first-component';

.#{$css-prefix} {
  &-#{$first-component-prefix} {
    &-red{
      color: red;
    }
  }
}
复制代码

创建样式入口文件,index.scss,并引入所有scss文件。

@import "variables";
@import './src/components/FirstComponent/style';
复制代码

创建公共前缀获取js代码src/utils,前缀和scss保持一致。

const cssPrefix = 'cru';
const getCssPrefix = (suffixCls: string) => `${cssPrefix}-${suffixCls}`;

export default getCssPrefix;
复制代码

在相应的组件中引入样式,修改./src/components/FirstComponent/index.tsx文件

import React from 'react';
import classNames from 'classnames';
import getCssPrefix from '../../utils';

function FirstComponent() {
  const cssPrefix = getCssPrefix('first-component');
  const wrapperClass = classNames(
    `${cssPrefix}-red`,
    `${cssPrefix}-font-size`,

  );
  return (
    <div className={wrapperClass}>
      这是第一个组件啊
    </div>
  );
}

export default FirstComponent;
复制代码

在storybook全局中引入入口样式文件,修改.storybook/preview.js。

import '../src/styles/index.scss'
export const parameters = {
  actions: { argTypesRegex: "^on[A-Z].*" },
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
  },
}
复制代码

这时候启动storybook是会报错的,原因是storybook内部引入webpack,并启动了一个devserver,这时候还无法识别scss文件,所以需要引入相关的loader。这里最好按照如下的版本,因为storybook中的webpack版本问题,无法识别较高版本的loader,所以自动安装的版本会报错。

yarn add [email protected]  [email protected] [email protected] --dev
复制代码

安装loader后,我们还需要让storybook引入loader配置项目,storybook为我们留了一个入口,这里修改main.js,添加webpackFinal这一字段。

const path = require('path')
module.exports = {
  "stories": [
    "../src/**/*.stories.mdx",
    "../src/**/*.stories.@(js|jsx|ts|tsx)"
  ],
  "addons": [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@storybook/addon-interactions"
  ],
  "framework": "@storybook/react",
  "webpackFinal": async (config) => {
    // `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION'
    // You can change the configuration based on that.
    // 'PRODUCTION' is used when building the static version of storybook.

    // Make whatever fine-grained changes you need
    config.module.rules.push({
      test: /.scss$/,
      use: ['style-loader', 'css-loader', 'sass-loader'],
      include: path.resolve(__dirname, '../'),
    });

    // Return the altered config
    return config;
  }
}
复制代码

这之后启动storybook,如图,发现样式已经变了,说明已经成功。

image.png

三,代码打包相关能力搭建

1,打包ts文件

在根目录下配置,config/tsconfig.build.json文件,配置*.test和*.stoires文件,输出

{
  "compilerOptions": {
    "outDir": "../dist",
    "module": "esnext",
    "target": "es5",
    "declaration": true,
    "jsx": "react",
    "moduleResolution":"Node",
    "allowSyntheticDefaultImports": true
  },
  "include": [
    "../src"
  ],
  "exclude": [
    "../src/**/*.test.tsx",
    "../src/**/*.stories.tsx"
  ]
}
复制代码

编写打包ts文件脚本命令,在package.json文件中添加

scripts: {
  ...
  "build-ts": "tsc -p ./config/tsconfig.build.json",
  ...
}
复制代码

2,打包css文件

创建脚本命令build-css,使用node-sass帮忙打包sass文件

scripts: {
  ...
  "build-css": "node-sass ./src/styles/index.scss ./dist/index.css",
  ...
}
复制代码

3,创建打包总命令

安装删除组件组件,让我们每次打包的时候可以自动删除dist文件夹

yarn add rimraf --dev
复制代码

创建删除文件命令

scripts: {
  ...
  "clean": "rimraf ./dist",
  ...
}
复制代码

4,创建打包总命令

"scripts": {
  "test": "npx jest",
  "lint": "eslint --ext .tsx,.ts,.js src --cache --fix",
  "storybook": "start-storybook -p 6006",
  "build-storybook": "build-storybook",
  "build-css": "node-sass ./src/styles/index.scss ./dist/index.css",
  "build-ts": "tsc -p ./config/tsconfig.build.json",
  "clean": "rimraf ./dist",
  "build": "yarn clean && yarn build-ts && yarn build-css",
},
复制代码

执行yarn build,可以看到根目录下创建dist文件,其中内容就是打包后的内容。

image.png

四,代码门禁commitlint

代码相关代码门禁,将commit不符合要求的,以及未通过测试和编码规范的代码进行拦截,安装相关依赖。这里的husky使用4.2.5版本,原因是新版本的husky入口配置所有变更,有兴趣的同学可以自行研究并更新版本。

yarn add @commitlint/cli @commitlint/config-conventional [email protected] --dev
复制代码

在根目录添加commitlint.config.js相关文件,

module.exports = {
    extends: ['@commitlint/config-conventional'],
    rules: {
    },
};
复制代码

在package.json中添加一个新的字段husky

"husky": {
    "hooks": {
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
      "pre-commit": "npm run lint && npm run test"
    }
}
复制代码

这时候,当我们发起commit的时候,commit msg需要符合commitlint规范(feat: a new feat或fix: bug fix等等,具体配置可以参考相关文档)且需要通过lint和test命令。 当我们eslint未通过并发起commit时,如图,此时commit发起失败

image.png

当我们的commitlint不符合规范时

image.png

五,自动会发布流程

我们希望当我们更新代码并合入到master后,一行命令就帮助我们完成日志的生成以及版本的管理,并最终在npm上发布新的包。流程如下

Untitled Diagram.jpg

1,安装standard-version

standard-version可以根据我们的之前的commit内容自动生成日志,并修改package.json中的版本号,之后再发起一个commit命令

yarn add standard-version --dev
复制代码

当我们发起feat:xx的commit的时候,版本会更新一个大版本,1.0.0 -> 1.1.0。而当我们发起一个fix:xx的commit的时候,会更新一个小版本,从1.0.0 -> 1.0.1。添加脚本命令。

scripts: {
   ...
   update-log-and-version: "standard-version",
   ...
}
复制代码

执行yarn update-log-and-version,会在根目录下自动生成CHANGELOG.md文件,并且更新package中的version版本。

image.png 并已经发起了commit

image.png

2,发布版本

指定发布的代码,在package.json中添加新的files字段。指定发布dist之下也就是打包后的文件,并不需要发布相关源码。

...
"files": [
  "dist"
],
...
复制代码

修改package.json中的main.js字段,让使用的时候直接指向dist文件下的index.js

...
"main": "dist/index.js",
...
复制代码

在上述基础上我们只需要push代码并发布即可,添加相关脚本命令

scripts: {
  ...
  "update-npm-and-git": "git push --follow-tags origin master && npm publish",
  "release": "yarn build && standard-version && yarn update-npm-and-git"
  ...
}
复制代码

这时候我们执行yarn release就会完成发布的全流程。完成的scripts脚本如下。

"scripts": {
  "test": "npx jest",
  "lint": "eslint --ext .tsx,.ts,.js src --cache --fix",
  "storybook": "start-storybook -p 6006",
  "build-storybook": "build-storybook",
  "build-css": "node-sass ./src/styles/index.scss ./dist/index.css",
  "build-ts": "tsc -p ./config/tsconfig.build.json",
  "clean": "rimraf ./dist",
  "build": "yarn clean && yarn build-ts && yarn build-css",
  "publish": "npm publish  public",
  "update-npm-and-git": "git push --follow-tags origin master && npm publish",
  "release": "yarn build && standard-version && yarn update-npm-and-git"
},
复制代码

六,开发人员所需要做的

开发人员只需要编写相关代码,并合入master,由版本管理员更新最新的master代码,并执行yarn release即可。

七,如何使用

和使用大部分公共样式组件一样,直接安装后,引入样式文件以及相应组件即可

yarn add common_react_utils
复制代码

image.png

Guess you like

Origin juejin.im/post/7084624678873989133