在实际项目中,咱们经常会遇到这样的一些场景:好比如今须要作一个下载报表的功能,并且这个功能在不少页面都有,下载的也是同类型的报表,若是在每一个页面都去写重复功能的代码显得特别冗余,可不将其封装成一个组件多出调用呢?只不过这个组件跟咱们常见的一些基础组件有些区别,区别在于这个组件里头包含了业务逻辑,称之为“业务组件”,这类组件是专门为了提升开发效率衍生出来的一种方案,这个组件库可能由专门维护组件库的人来维护,也多是单个项目组本身的业务组件库。废话很少说,咱们来着手实操一下:css
解法就是 lerna,一种多包依赖解决方案,简单来说:node
一、能够管理公共依赖和单独依赖;
二、多package相互依赖直接内部 link,没必要发版;
三、能够单独发布和全体发布
四、多包放一个git仓库,也有利于代码管理,如配置统一的代码规范react
Lerna 是一个优化使用 git 和 npm 管理多包存储库的工做流工具,用于管理具备多个包的 JavaScript 项目。ios
将大型代码库拆分为独立的独立版本包对于代码共享很是有用。 然而,在许多存储库中进行更改是麻烦和难以跟踪的事情。为了解决这些(和许多其余)问题,一些项目将它们的代码库组织成多包存储库。 像 Babel、React、Angular、Ember、Meteor、Jest 等等。git
首先使用 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';
这样,一个业务组件示例就写好了,接下来须要对它进行编译配置
业务组件属于公司私有组件,只须要知足公司内部使用便可。所以,咱们只采用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文件夹下
执行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": "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
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
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发布,可是报了以下错误,这是由于本地代码没有提交
将本地仓库与远程仓库关联,让后提交推到远程仓库
git remote add origin git@github.com:Revelation2019/pony-business-component.git git push --set-upstream origin master
再次执行npm run publish发布,成功发布到npm仓库