從零到一搭建react業務組件庫

在實際項目中,咱們經常會遇到這樣的一些場景:好比如今須要作一個下載報表的功能,並且這個功能在不少頁面都有,下載的也是同類型的報表,若是在每一個頁面都去寫重複功能的代碼顯得特別冗餘,可不將其封裝成一個組件多出調用呢?只不過這個組件跟咱們常見的一些基礎組件有些區別,區別在於這個組件裏頭包含了業務邏輯,稱之爲「業務組件」,這類組件是專門爲了提升開發效率衍生出來的一種方案,這個組件庫可能由專門維護組件庫的人來維護,也多是單個項目組本身的業務組件庫。廢話很少說,咱們來着手實操一下:css

初始化項目

lerna解決什麼問題

  • 假設主項目是 Angular 技術棧的,依賴兩個自研 npm 包,這兩個包也依賴 Angular,如今主項目要升級 Angular 版本,那麼這兩個 npm 包也得跟着升級,且須要升級兩次(一個包一次),能否只升級一次?
  • 假設有兩個 npm 包A和B,A依賴B,那麼每當B有更新時,要想讓A用上B的更新,須要B發版,而後A升級B的依賴,能否更簡單些?

解法就是 lerna,一種多包依賴解決方案,簡單來說:node

一、能夠管理公共依賴和單獨依賴;
二、多package相互依賴直接內部 link,沒必要發版;
三、能夠單獨發佈和全體發佈
四、多包放一個git倉庫,也有利於代碼管理,如配置統一的代碼規範react

Lerna 是一個優化使用 git 和 npm 管理多包存儲庫的工做流工具,用於管理具備多個包的 JavaScript 項目。ios

將大型代碼庫拆分爲獨立的獨立版本包對於代碼共享很是有用。 然而,在許多存儲庫中進行更改是麻煩和難以跟蹤的事情。爲了解決這些(和許多其餘)問題,一些項目將它們的代碼庫組織成多包存儲庫。 像 Babel、React、Angular、Ember、Meteor、Jest 等等。git

lerna搭建工程

首先使用 npm 將 Lerna 安裝到全局環境中,推薦使用 Lerna 2.x 版本:github

npm install --global lerna

接下來,咱們將建立一個新的 git 代碼倉庫:typescript

git init pony-bre-component && cd pony-bre-component

並與github遠程倉庫關聯npm

get remote add origin xxx

如今,咱們將上述倉庫轉變爲一個 Lerna 倉庫:json

lerna init

另外,項目須要安裝react、react-dom、typescript、@types/react、@types/react-domaxios

yarn add typescript react react-dom @types/react @types/react-dom
npx typescript --init 在根目錄生成tsconfig.json

你的代碼倉庫目前應該是以下結構:

pony-business-component/
  packages/  存放每個組件
  package.json
  lerna.json  lerna配置
  tsconfig.json  typescript編譯配置文件

lerna.json:

{
  "packages": [
    "packages/*"
  ],
  "version": "0.0.0"
}

package.json:

{
  "name": "root",
  "private": true,
  "devDependencies": {
    "lerna": "^4.0.0"
  },
  "dependencies": {
    "@types/react": "^17.0.4",
    "@types/react-dom": "^17.0.3",
    "axios": "^0.21.1",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "typescript": "^4.2.4"
  }
}

建立組件分包

每個組件都是一個倉庫包,好比咱們建立一個TodoList業務組件,在packages目錄下建立bre-todo-list文件夾,執行下面命令初始化組件包

cd packages/bre-todo-list
npm init 生成package.json
npx typescript --init

接着,按照以下結構搭建目錄

pony-business-component
├── packages
    ├── bre-todo-list
        ├── api  接口api定義
            └── index.ts
        ├── interfaces  類型定義
            └── index.ts
        ├── styles   樣式
            └── index.scss
        ├── src   組件
            ├── TodoList.tsx
            └── index.ts
        ├── package.json  
        └── tsconfig.json

接口定義

TodoList組件內部須要經過接口獲取清單數據,在src/api/index.ts定義好接口方法

import axios from 'axios';

export const getTodoList = (id: string) => {
  return axios.get(`/mock/16430/todolist/${id}`).then(res => res.data)
}

編寫組件

編寫TodoList組件,初始渲染時調用接口獲取清單數據,並渲染

// src/TodoList.tsx
import React, { useCallback, useEffect, useState } from 'react';
import { getTodoList } from './api';

interface TodoListProps {
  id: string;
}


const TodoList = (props: TodoListProps) => {
  const [source, setSource] = useState<string[]>([]);

  const init = useCallback(async () => {
    const { id } = props;
    if (id) {
      const { code , data} = await getTodoList(id);
      if (code === 200 && !!data) {
        setSource(data);
      }
    }
  }, [])

  useEffect(() => {
    init();
  }, []);

  return (
    <ul>
      {
        source.map((s: string, index: number) => <li key={index}>{s}</li>)
      }
    </ul>
  )
}

export { TodoList };

在src/index.ts中拋出組件和類型

export * from './TodoList';
export * from './interfaces';

這樣,一個業務組件示例就寫好了,接下來須要對它進行編譯配置

tsc編譯

業務組件屬於公司私有組件,只須要知足公司內部使用便可。所以,咱們只採用es module打包格式,不用知足AMD、CommonJS使用場景。

lerna提供了一個命令,能夠在每個分包下執行某些指令

lerna exec tsc  表示在每個分包在會執行tsc指令

在每一個分包下執行tsc編譯時,會優先在分包下找tsconfig.json文件,若是沒有再向上一級去找,若是都沒有找到就使用根目錄下的tsconfig.json文件

咱們在分包tsconfig.json文件作以下配置,並繼承根目錄下的配置選項:

// 分包tsconfig.json
{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "outDir": "./lib",                        /* Redirect output structure to the directory. */
    "rootDir": "./src",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
  },
  "include": ["src"],
  "exclude": ["*/__tests__/*"],
}

根目錄tsconfig.json配置:

{
  "compilerOptions": {
    "target": "ES2015",                                /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
    "module": "es2015",                           /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
    "lib": [
      "ES2015"
    ],                                   /* Specify library files to be included in the compilation. */
    "jsx": "react",                           /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
    "declaration": true,                         /* Generates corresponding '.d.ts' file. */
    "declarationMap": true,                      /* Generates a sourcemap for each corresponding '.d.ts' file. */
    "sourceMap": true,                           /* Generates corresponding '.map' file. */
    "downlevelIteration": true,                  /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
    "strict": true,                                 /* Enable all strict type-checking options. */
    "moduleResolution": "node",                  /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
    "baseUrl": "./",                             /* Base directory to resolve non-absolute module names. */
    "paths": {
      "bre-*": ["packages/bre-*/src"],
      "*": [
        "node_modules"
      ]
    },                                 /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
    "allowSyntheticDefaultImports": true,        /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
    "esModuleInterop": true,                        /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
    "experimentalDecorators": true,              /* Enables experimental support for ES7 decorators. */
    "emitDecoratorMetadata": true,               /* Enables experimental support for emitting type metadata for decorators. */
    "skipLibCheck": true,                           /* Skip type checking of declaration files. */
    "forceConsistentCasingInFileNames": true,        /* Disallow inconsistently-cased references to the same file. */
  },
  "include": [
    "packages/bre-*/src"
  ],
  "exclude": [
    "node_modules",
    "packages/**/node_modules",
    "packages/**/lib"
  ]
}

而後在根目錄下package.json中加上執行命令

"scripts": {
    "build": "lerna exec tsc",
},

當在根目錄下執行yarn build時,會對每個分包的src目錄編譯,編譯生成的JS腳本、類型聲明文件以及source-map文件會輸出到分包下lib文件夾下

image.png

執行yarn build時可能報錯Yarn workspaces need to be defined in the root package.json.,須要在根目錄package.json加上

"private": "true", // 加上workspaces後private選項必需要加上
"workspaces": [  // 工做空間,表示yarn命令執行的空間
    "packages/*"
],

打包完成後,須要在分包package.json中指定拋出的文件

  • name:指定組件包的名稱
  • main:指定組件包的入口文件,好比AMD引入
  • types:指定組件包類型聲明的入口文件
  • module:指定es module引入時的入口文件
  • files:指定安裝pony-bre-todo-list組件包時會下載哪些文件
{
  "name": "bre-todo-list",
  "version": "1.0.0",
  "description": "任務清單業務組件",
  "main": "lib/index.js",
  "types": "lib/index.d.ts",
  "module": "lib/index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "react": "^17.0.2",
    "react-dom": "^17.0.2"
  },
  "files": [
    "lib/*",
    "src/*",
    "styles/*",
    "package.json"
  ]
}

你們是否有個疑問:爲何每個分包下都須要有一個tsconfig.json文件呢?爲何不直接用根目錄下的tsconfig.json文件呢?

這是由於在根目錄tsconfig.json沒法配置將每個分包編譯後生成的文件輸出到對應分包下的lib目錄,所以須要在每一個分包下量身配置tsconfig.json

docz搭建組件文檔

安裝docz

yarn add docz

在項目中安裝Docz以後,將三個腳本添加到package.json以便運行

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

編寫組件文檔,在根目錄下建立docs文件夾

/docs/bre-todo-list.mdx
---
name: bre-todo-list
menu: Components
---

import { Playground, Props } from "docz";
import { TodoList } from "../packages/bre-todo-list/src/index.ts";
import '../packages/bre-todo-list/styles/index.scss';


## 安裝
yarn add bre-todo-list


## 引用
import { TodoList } from 'bre-todo-list';
import 'bre-todo-list/styles/index.scss';


## 屬性

<Props of={TodoList} isToggle/>

## 基礎用法

<Playground>
  <TodoList id="123456"></TodoList>
</Playground>

自定義dcoz配置,根目錄下建立doczrc.js

export default {
  title: 'bre-component', // 網站的標題
  typescript: true, // 若是須要在.mdx文件中引入Typescript組件,則使用此選項
  dest: 'build-docs', // 指定docz構建的輸出目錄
  files: 'docs/*.mdx', // Glob模式用於查找文件。默認狀況下,Docz會在源文件夾中找到全部擴展名爲.mdx的文件。
  ignore: ['README.md', 'CHANGELOG.md'] // 用於忽略由docz解析的文件的選項
};

還須要讓Gatsby在構建mdx時支持scss,安裝node-sass和gatsby-plugin-sass,並在根目錄下建立gatsby-config.js,添加以下配置:

module.exports = {
  plugins: [
    {
      resolve: `gatsby-plugin-sass`,
      options: {
        implementation: require("node-sass"),
      },
    }
  ],
}

因爲組件中使用了接口數據,在靜態網站調用時會出現跨域問題,須要向gatsby-config.js中添加跨域配置:

const proxy = require('http-proxy-middleware')

module.exports = {
  plugins: [
    {
      resolve: `gatsby-plugin-sass`,
      options: {
        implementation: require("node-sass"),
      },
    }
  ],
  developMiddleware: app => {
    app.use(
      proxy(['/mock/16430'], {
        target: 'https://mock.yonyoucloud.com/',
        changeOrigin: true,
      })
    )
  }
}

在終端執行yarn docz:dev
image.png

發佈

lerna publish命令能夠幫助咱們將分包發佈到npm上,將其加入到scripts中:

"scripts": {
    "docz:dev": "docz dev",
    "docz:build": "docz build",
    "docz:serve": "docz build && docz serve",
    "build": "lerna exec tsc",
    "publish": "lerna exec tsc && lerna publish"
},

首先,npm login登陸npm,注意當前設置的register必定要是npm鏡像,若是不是須要更改

npm config set registry http://registry.npmjs.org

執行npm run publish發佈,可是報了以下錯誤,這是由於本地代碼沒有提交
image.png
將本地倉庫與遠程倉庫關聯,讓後提交推到遠程倉庫

git remote add origin git@github.com:Revelation2019/pony-business-component.git
git push --set-upstream origin master

再次執行npm run publish發佈,成功發佈到npm倉庫
image.png

相關文章
相關標籤/搜索