外观
第40章—组件库实战:构建esm和cjs产物,发布到npm
3018字约10分钟
2024-09-19
我们已经写了很多组件了,比如 Calendar、Watermark、OnBoarding 等,但都是用 cra 或者 vite 单独创建项目来写的。
这节我们把它们整合一下,加上构建脚本,发布到 npm,做成和 Ant Design 一样的组件库。
组件库的构建我们上节分析过,就是构建出 esm、commonjs、umd 3 种格式的代码,再加上 css 的构建就好了。
ant design、arco design、semi design 都是这样。
我们再看几个组件库:
mkdir tmp
cd tmp
npm init -y
安装 react-bootstrap:
pnpm install react-bootstrap
(用 pnpm 安装,node_modules 下目录比较简洁)
看下 node_modules/react-bootstrap 的 package.json
可以看到,它也有 main 和 module,也就是 commonjs 和 es module 两种入口。
当你 import 的时候,引入的是 esm 的代码。
当你 require 的时候,引入的是 commonjs 的代码。
看一下 esm 和 cjs 下的代码:
当然,它也是支持 umd 的:
看下 build 脚本:
就是分别用 babel 编译出 commonjs 和 esm 的代码就可以了:
用 tsc 也行。
umd 格式代码也同样是 webpack 打包的:
不同于 antd、arco design 和 semi design,它就没有用 gulp 来组织流程。
gulp 本来就不是必须的,可用可不用。
甚至连单独的脚本都不需要,直接 tsc 编译就行:
比如这个 blueprint 组件库:
之前总结的组件库的构建流程是没问题的:
然后我们新建一个项目来试一下:
npx create-vite guang-components
进入项目,复制几个之前的组件过来:
复制 Calendar、Watermark、Message 这三个组件:
然后安装下依赖:
npm install
npm install --save react-transition-group lodash-es dayjs classnames
npm i --save-dev @types/react-transition-group
然后去掉 Calendar 和 Message 组件里样式的引入,css 和 js 是分开编译的:
把这些没用的文件删掉:
加一个 index.ts 来导出组件:
import Calendar, { CalendarProps } from './Calendar';
import Watermark, { WatermarkProps } from './Watermark';
import { MessageProps, Position, MessageRef} from './Message';
import { useMessage } from './Message/useMessage';
import { ConfigProvider } from './Message/ConfigProvider';
export {
Calendar,
Watermark,
ConfigProvider,
useMessage
}
export type {
CalendarProps,
WatermarkProps,
MessageProps,
Position,
MessageRef
}
接下来加上 tsc 和 sass 的编译:
添加一个 tsconfig.build.json 的配置文件:
{
"compilerOptions": {
"declaration": true,
"allowSyntheticDefaultImports": true,
"target": "es2015",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"allowImportingTsExtensions":null,
"strict": true,
},
"include": [
"src"
],
"exclude": [
"src/**/*.test.tsx",
"src/**/*.stories.tsx"
]
}
然后编译下:
npx tsc -p tsconfig.build.json --module ESNext --outDir dist/esm
npx tsc -p tsconfig.build.json --module commonjs --outDir dist/cjs
看下 dist 的产物:
没啥问题,esm 和 commonjs 格式的代码都生成了。
然后再编译下样式:
npx sass ./src/Calendar/index.scss ./dist/esm/Calendar/index.css
npx sass ./src/Calendar/index.scss ./dist/cjs/Calendar/index.css
npx sass ./src/Message/index.scss ./dist/esm/Message/index.css
npx sass ./src/Message/index.scss ./dist/cjs/Message/index.css
看下产物:
没问题。
当然,sass 文件多了以后你可以写个 node 脚本来自动查找所有 sass 文件然后编译。
接下来只要把这个 dist 目录发到 npm 仓库就可以了。
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"types": "dist/esm/index.d.ts",
"files": [
"dist",
"package.json",
"README.md"
],
main 和 module 分别是 commonjs 和 es module 的入口。
types 是 dts 的路径。
files 是哪些文件发布到 npm 仓库,没列出来的会被过滤掉。
并且,还需要把 private: true 和 type: module 的字段给去掉。
然后我们来发布 npm 包:
npm adduser
执行 npm adduser 命令,会打印一个链接让你登录或者注册:
登录后就可以 npm publish 了:
npm publish
发布完之后,在 https://www.npmjs.com 就可以搜索到:
我们新建个项目来用用看:
npx create-vite guang-test
安装依赖:
pnpm install
pnpm install guang-components
在 App.tsx 里用一下:
import { Watermark } from 'guang-components';
const App = () => {
return <Watermark
content={['测试水印', '神说要有光']}
>
<div style={{height: 800}}>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quos quod deserunt quidem quas in rem ipsam ut nesciunt asperiores dignissimos recusandae minus, eaque, harum exercitationem esse sapiente? Eveniet, id provident!</p>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quos quod deserunt quidem quas in rem ipsam ut nesciunt asperiores dignissimos recusandae minus, eaque, harum exercitationem esse sapiente? Eveniet, id provident!</p>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quos quod deserunt quidem quas in rem ipsam ut nesciunt asperiores dignissimos recusandae minus, eaque, harum exercitationem esse sapiente? Eveniet, id provident!</p>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quos quod deserunt quidem quas in rem ipsam ut nesciunt asperiores dignissimos recusandae minus, eaque, harum exercitationem esse sapiente? Eveniet, id provident!</p>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quos quod deserunt quidem quas in rem ipsam ut nesciunt asperiores dignissimos recusandae minus, eaque, harum exercitationem esse sapiente? Eveniet, id provident!</p>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quos quod deserunt quidem quas in rem ipsam ut nesciunt asperiores dignissimos recusandae minus, eaque, harum exercitationem esse sapiente? Eveniet, id provident!</p>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quos quod deserunt quidem quas in rem ipsam ut nesciunt asperiores dignissimos recusandae minus, eaque, harum exercitationem esse sapiente? Eveniet, id provident!</p>
</div>
</Watermark>
};
export default App;
跑下开发服务:
npm run dev
浏览器访问下:
再试下另外两个组件:
import dayjs from 'dayjs';
import {Calendar} from 'guang-components';
import 'guang-components/dist/esm/Calendar/index.css';
function App() {
return (
<div>
<Calendar value={dayjs('2024-07-01')}></Calendar>
</div>
);
}
export default App;
这里用到了 dayjs:
pnpm install dayjs
这里样式受 index.css 影响了,去掉就好了:
再来试下 Message 组件:
import { ConfigProvider, useMessage } from "guang-components"
import 'guang-components/dist/esm/Message/index.css';
function Aaa() {
const message = useMessage();
return <button onClick={() =>{
message.add({
content:'请求成功'
})
}}>成功</button>
}
function App() {
return (
<ConfigProvider>
<div>
<Aaa></Aaa>
</div>
</ConfigProvider>
);
}
export default App;
没啥问题。
然后我们优化下依赖:
其实用到 guang-components 的项目都会安装 react 和 react-dom 包,不需要把它放在 dependencies 里。
而是放在 peerDependencies 里:
它和 dependencies 一样,都是依赖,但是 dependencies 是子级,而 peerDependencies 是平级。
如果和其他 react 包的版本冲突时,dependencies 会保留一份副本,而 peerDependencies 会提示开发者去解决冲突,不会保留副本。
改下版本号,重新 npm publish:
这样,我们的组件库的 npm 包就发布成功了!
再测试下 commonjs 的代码。
用 cra 创建个项目:
npx create-react-app --template=typescript tmp4
进入项目,安装组件库:
npm install --save guang-components
在 App.tsx 用一下:
const { Watermark } = require('guang-components');
const App = () => {
return <Watermark
content={['测试水印', '神说要有光']}
>
<div style={{height: 800}}>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quos quod deserunt quidem quas in rem ipsam ut nesciunt asperiores dignissimos recusandae minus, eaque, harum exercitationem esse sapiente? Eveniet, id provident!</p>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quos quod deserunt quidem quas in rem ipsam ut nesciunt asperiores dignissimos recusandae minus, eaque, harum exercitationem esse sapiente? Eveniet, id provident!</p>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quos quod deserunt quidem quas in rem ipsam ut nesciunt asperiores dignissimos recusandae minus, eaque, harum exercitationem esse sapiente? Eveniet, id provident!</p>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quos quod deserunt quidem quas in rem ipsam ut nesciunt asperiores dignissimos recusandae minus, eaque, harum exercitationem esse sapiente? Eveniet, id provident!</p>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quos quod deserunt quidem quas in rem ipsam ut nesciunt asperiores dignissimos recusandae minus, eaque, harum exercitationem esse sapiente? Eveniet, id provident!</p>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quos quod deserunt quidem quas in rem ipsam ut nesciunt asperiores dignissimos recusandae minus, eaque, harum exercitationem esse sapiente? Eveniet, id provident!</p>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quos quod deserunt quidem quas in rem ipsam ut nesciunt asperiores dignissimos recusandae minus, eaque, harum exercitationem esse sapiente? Eveniet, id provident!</p>
</div>
</Watermark>
};
export default App;
注意,这次用 require 引入代码。
然后把开发服务跑起来:
npm run start
浏览器里看一下:
没啥问题。
案例代码上传了小册仓库。
总结
今天我们把之前写过的部分组件封装成了组件库并发布到了 npm 仓库。
可以直接在项目里引入来用,和 antd 等组件库一样。
构建部分我们分析过很多组件库,都是一样的:
- commonjs 和 esm 的代码通过 tsc 或者 babel 编译产生
- umd 代码通过 webpack 打包产生
- css 代码通过 sass 或者 less 等编译产生
- dts 类型也是通过 tsc 编译产生
我们在 package.json 里配置了 main 和 module,分别声明 commonjs 和 es module 的入口,配置了 types 指定类型的入口。
然后通过 npm adduser 登录,之后 npm publish 发布到 npm。
这样,react 项目里就可以引入这个组件库来用了,之前写过的所有组件都可以加到这个组件库里。