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-developednpm
packages, and these two packages also depend onAngular
. Now the main project needs to be upgraded to theAngular
version, then the twonpm
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
packagesA
andB
,A
relyB
, so wheneverB
when there are updates, to getA
spendB
update requiresB
fat version, thenA
upgradeB
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
After the packaging is complete, you need to specify the file to be thrown package.json
name
: Specify the name of the component packagemain
: Specify the entry file of the component package, such as the introduction ofAMD
types
: Specify the entry file of the component package type declarationmodule
: Specify the entry file whenes module
files
: Specify which files will be downloaded when thebre-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
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
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
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
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.
tag
played by the two publishing methods are also different. The following is an example picture:
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:
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;
}
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。