14

In actual projects, we often encounter such scenarios: For example, we now need to make a report download function, and this function is available on many pages, and the download is the same type of report. If you write it on each page The code of the repetitive function is particularly redundant. Can it be encapsulated into one component and called in multiple places? It’s just that this component is somewhat different from some of our common basic components. The difference is that this component contains business logic, which is called "business component". This type of component is a program derived specifically to improve development efficiency. This component The library may be maintained by a person who specializes in maintaining a component library, or it may be a business component library of a single project group. Not much nonsense, let's do it:

Initialize the project

lerna

Lerna is a workflow tool that optimizes the use of git and npm manage multi-package repositories. It is used to manage the JavaScript project with multiple packages.

Splitting a large code base into independent independent version packages is very useful for code sharing. However, making changes in many repositories is cumbersome and difficult to track. To solve these (and many other) problems, some projects organize their code bases into multi-package repositories. Like Babel、React、Angular、Ember、Meteor、Jest and so on.

What problem does lerna solve

  • Assuming that the main project is the Angular technology stack, it relies on two self-developed npm packages, and these two packages also depend on Angular . Now the main project needs to be upgraded to the Angular version, then the two npm packages must also be upgraded, and two upgrades are required ( One package at a time), can it be upgraded only once?
  • Suppose there are two npm packages A and B , A rely B , so whenever B when there are updates, to get A spend B update requires B fat version, then A upgrade B dependent, whether simpler?

The solution is lerna , a multi-package dependency solution, in simple terms:

1. Can manage public dependencies and individual dependencies;
2. Multiple package interdependence directly internal link , no need to publish version;
3. It can be released individually and collectively
4. Putting a git warehouse in multiple packages is also conducive to code management, such as configuring a unified code specification

lerna build project

First use npm to Lerna into the global environment. It is recommended to use the Lerna 2.x version:

yarn add lerna -g

Next, create a new code repository git

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

And associate with github remote warehouse

get remote add origin xxx

Now, we will transform the above warehouse into a Lerna warehouse:

lerna init

In addition, install react、react-dom、typescript、@types/react、@types/react-dom

yarn add typescript react react-dom @types/react @types/react-dom

Execute npx typescript --init tsconfig.json configuration file in the root directory

Your code repository should currently have the following structure:

pony-business-component/
  packages/  存放每一个组件
  package.json
  lerna.json  lerna配置
  tsconfig.json  typescript编译配置文件

The lerna.json configuration is as follows:

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

The package.json configuration is as follows:

{
  "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"
  }
}

Create component subcontracting

Each component is a warehouse pack, such as we create a TodoList business component, in packages create the directory bre-todo-list folder, execute the following command to initialize Package

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

Then, build the directory according to the following structure

pony-business-component
├── packages
    ├── bre-todo-list
        ├── src   组件
            ├── api  接口api定义
                └── index.ts
            ├── interfaces  类型定义
                └── index.ts
            ├── styles   样式
                └── index.scss
            ├── TodoList.tsx
            └── index.ts
        ├── package.json  
        └── tsconfig.json

Interface definition

TodoList component needs to obtain the inventory data through the interface, and the interface method is defined src/api/index.ts

import axios from 'axios';

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

Writing components

Write the TodoList component, call the interface to obtain the inventory data during the initial rendering, and render it

// 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 };

Throw components and types in src/index.ts

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

In this way, a business component example is written, and then it needs to be compiled and configured

es module packaging

Business components generally belong to the company or project group private components, and only need to meet special scenarios. Now in front-end development, the most es module way to organize code is 061285ba0b0366. Therefore, it is only necessary to use the es module packaging format to output the component subcontracting, and it does not need to meet the AMD、CommonJS usage scenarios.

lerna provides a command that can execute some commands under each sub-package

lerna exec tsc  // 表示在每一个分包在会执行tsc指令

Performed in each sub- tsc when compiled, it will give priority to looking at subcontractors tsconfig.json file, if there is no one to look up again until you find the root directory of the tsconfig.json file

We do the following configuration in the subpackage tsconfig.json file, as long as we specify the entry and exit of the compilation, and inherit the configuration options under the root directory:

// 分包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__/*"],
}

Root directory tsconfig.json configuration is as follows, focusing module、declaration two options, module organization after the specified module compiled using es module , declaration show at compile time to generate the corresponding .d.ts type declaration documents, the project does not use typescript can not specify

{
  "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"
  ]
}

Add script commands package.json in the root directory

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

When executed in the root directory yarn build time, will each subcontracting src directory is compiled, the compiler-generated JS script, type declarations and documents source-map file is output to a subcontractor under lib folder

image.png

After the packaging is complete, you need to specify the file to be thrown package.json

  • name : Specify the name of the component package
  • main : Specify the entry file of the component package, such as the introduction of AMD
  • types : Specify the entry file of the component package type declaration
  • module : Specify the entry file when es module
  • files : Specify which files will be downloaded when the 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"
  ]
}

you have a question: Why is there a tsconfig.json file under each subcontract? Why not just use the tsconfig.json file in the root directory?

This is because the root directory tsconfig.json cannot be configured to output the files generated after each subpackage compilation to the lib directory under the corresponding subpackage, so you need to configure the compilation entry and export file paths in tsconfig.json

docz build component documentation

Docz is a Storybook . It solves the main problems of component library document development (component list, component display, component attribute list, component editing and debugging)

Docz implemented based on MDX MDX(Markdown + jsx) allows you to import the root use JSX markdown style file, and Docz provides some built-in components, which can be convenient and fast to implement the document construction, and Docz can support the Typescript according to the ts . Annotation to generate documentation

Gatsby is a static site generator based on React . It has a rich plugin ecology. One of its main goals is to deliver fast-access web pages. It uses good caching, static page generation and edge-based CDN data sources to achieve this. One goal.

Install docz

yarn add docz

Docz in the project, add three script commands to package.json to run

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

docs folder in the root directory and write component documentation

/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>

doczrc.js in the root directory, customize dcoz configuration

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解析的文件的选项
};

mdx used in the document scss pre-processor, coupled with the need for scss configuration process, the installation node-sass and gatsby-plugin-sass , and created in the root directory gatsby-config.js , add the following configuration:

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

Interface data is used in the component, and cross-domain problems will occur when static website calls are started locally. You also need to add proxy configuration 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,
      })
    )
  }
}

If the execution yarn docz:build produces the following error Following construction, because Gastby uses a server-side rendering ( SSR ), the server does not support rendering window module, thus reported window not defined

image.png

gastby-node.js in the root directory and add the following code to replace the problematic module with a virtual module during server rendering. For details, refer to the official website: https://www.gatsbyjs.cn/docs/debugging-html-builds/

exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
  /** 服务端渲染中不支持使用window的模块,在服务器渲染期间用虚拟模块替换有问题的模块
   * https://www.gatsbyjs.com/docs/debugging-html-builds/
   */
  if (stage === 'build-html' || stage === 'develop-html') {
    actions.setWebpackConfig({
      module: {
        rules: [
          {
            test: /bad-module/, // 需要处理的模块
            use: loaders.null(),
          },
        ],
      },
    });
  }
};

Here that a problem encountered in the actual project, based on the company foundation component library within the time set bricks build business component library, but bricks component library individual components used in window object, if the above configuration /bad-module/ defined as /bricks/ can cause build Failed, because many business components use the bricks component, bricks component is used, and the build can be successful. Further analysis found that only error message bricks in scrollbar components use a window, so I will /bad-module/ defined as /scrollbar/ , so they do not use a business component scrollbar components can build successful, this is only a temporary treatment options, later we switched dumi build The documentation completely avoids this problem.

Finally, execute yarn docz:dev

image.png

release

lerna publish command can release the sub-package to npm . After the release, it will execute git push --follow-tags --no-verify origin master to push the code to the warehouse. The submission remark is Publish and marked with tag . Add the package scripts script command to 061285ba0b092c:

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

First execute npm login log in to npm . Note that the set register must be the npm , otherwise the login will not be possible. If not, you need to change register

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

Then, execute yarn release or npm run publish release ( npm run publish is npm ), but the following error is reported, which is because the local code did not submit

image.png

Submit code and push to remote warehouse

git add .
git commit -m 'xxx'
git push --set-upstream origin master

In addition, if the name of the package @ , for example, @deepred/core , npm is considered private release by default, and needs to be released npm publish --access public However, lerna publish does not support this parameter. Refer to the solution: issues . The package does not start @

Re-execute yarn release or npm run publish publish, and successfully publish to npm warehouse

image.png

image.png

image.png

Independent release

The above version hair the way we use the lerna default centralized model, all package share a version , the version number in lerna.json maintenance. Each component subcontracting in the business component library should be closed-loop, and a component subcontracting change should not update the version numbers of all component subcontracting, which is different from the basic component library.

lerna provides an independent release method. In this mode, lerna will only release the changed component sub-packages, so that different package have their own versions. You only need to lerna.json the version field “independent”

We add another component subpackage bre-mutil-list on the original basis, submit the code, compile and publish, you will see that only the newly created component subpackage bre-mutil-list bre-todo-list will not be released, and the version number will not be updated.

image.png

image.png

tag played by the two publishing methods are also different. The following is an example picture:

image.png

deploy

Our company did not deploy the built directory to the server, but used the nginx agent to pull gitlab . The detailed process is as follows:

组件文档部署confluence.png

Nginx configuration:

server {
    listen       83;
    server_name  10.118.71.232;
    location / {
        root  /opt/web/gitweb/inquiry-bre-component/build-docs;
        index  index.html index.htm;
      if ( $request_uri !~* \. ) {
        rewrite ^/([\w/]+).*/? /index.html break;
      }
    } 
}

server {
    listen       82;
    server_name  10.118.71.232;
    location / {
        root  /opt/web/gitweb/bre-components/build-docs;
        index  index.html index.htm;
      if ( $request_uri !~* \. ) {
        rewrite ^/([\w/]+).*/? /index.html break;
      }
    } 
}
          
server { 
    listen       81;
    server_name  10.118.71.232; 
    location ~ ^/v {
        root  /opt/web/gitweb/bricks-docs;
        index  index.html index.htm;
    }
    location / {
        root  /opt/web/gitweb/bricks-docs;
        index  index.html index.htm; 
      if ( $request_uri !~* \. ) {
        rewrite ^/([\w/]+).*/? /index.html break;
      }
    }
}  

记得要微笑
1.9k 声望4.5k 粉丝

知不足而奋进,望远山而前行,卯足劲,不减热爱。