前言
利用 Rollup 构建一个文章展示抽屉SDK,用来记录前端自定义SDK的过程
工程实践
初始化
- 创建项目
mkdir packageName
npm init -y
- Typescript功能初始化
npm install typescript -D
npx tsc -init
- 创建 rollup.config.js
import typescript from 'rollup-plugin-typescript2';
import less from 'rollup-plugin-less';
import clear from 'rollup-plugin-clear';
export default {
input: ['./src/index.ts'],
output: [
{
file: './lib/umd/index.js',
name: 'DumboUI',
format: 'umd',
sourcemap: true,
},
{
file: './lib/esm/index.js',
format: 'es',
sourcemap: true,
},
],
plugins: [
typescript(), // 会自动读取 文件tsconfig.json配置
less({ output: './lib/index.less' }),
clear({
targets: ['lib'],
}),
],
external: ['react', 'react-dom'],
};
添加插件依赖包
npm install rollup-plugin-typescript2 rollup-plugin-less rollup-plugin-clear -D
package.json 添加dev脚本
"scripts": {
"dev": "rollup -c rollup.config.js -w"
}
业务开发
- 安装 React 必需依赖包
npm install react react-dom
npm install @types/react @types/react-dom -D
- 编写组件代码
参考文章最后业务代码
-
本地开发调试
sdk项目包执行
sudo npm link
业务项目执行
npm link @levenx/grouper
- 效果图
视频地址
https://cdn.jsdelivr.net/gh/levenx/picture@master/material/docs.mov
构建打包
- package.json
{
"name": "@levenx/grouper",
"version": "0.0.2",
"description": "",
"main": "./lib/umd/index.js",
"module": "./lib/esm/index.js",
"types": "./lib/esm/index.d.ts",
"publishConfig": {
"access": "public"
},
"scripts": {
"dev": "rollup -c rollup.config.js -w",
"build":"rollup -c rollup.config.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/marked": "^4.0.1",
"@types/react": "^17.0.37",
"@types/react-dom": "^17.0.11",
"rollup": "^2.61.1",
"rollup-plugin-clear": "^2.0.7",
"rollup-plugin-less": "^1.1.3",
"rollup-plugin-typescript2": "^0.31.1",
"typescript": "^4.5.3"
},
"peerDependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"dependencies": {
"classnames": "^2.3.1",
"marked": "^4.0.7"
}
}
执行打包命令
npm run build
发布npm包
- Npm登陆
npm login
设置好username, password,email
- Npm发布
npm publish
SDK使用步骤
-
引入 sdk 依赖
这个包已经成功上传,感兴趣的可以尝试使用下
npm install @levenx/grouper
- sdk使用
import Grouper from '@dumbo/grouper';
const docs = new Grouper();
// 展示
docs.render({title:'title',content:'content'})
业务代码
入口 index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import Preview from '@/components/preview';
export interface GrouperDocConfig {
root?: HTMLElement;
title?: string;
content?: string;
}
export default class GrouperDoc {
config;
root;
constructor(config: GrouperDocConfig) {
this.config = config;
}
destory() {
ReactDOM.unmountComponentAtNode(this.root);
}
render(config: GrouperDocConfig) {
const { title, content, root } = config;
let div = root;
if (!root) {
div = document.createElement('div');
document.body.appendChild(div);
}
this.root = div;
const App = <Preview onClose={this.destory.bind(this)} title={title} content={content} />;
ReactDOM.render(App, div)
}
}
// components/preview/index.tsx
import React, { useState, useEffect, useMemo } from 'react';
import classnames from 'classnames';
import { marked } from 'marked';
import './index.less';
interface PreviewProps {
onClose: () => void;
title: string;
content: string;
}
export default function Preview(props: PreviewProps) {
const { onClose, title, content } = props;
const [visible, setVisible] = useState(false);
useEffect(() => {
setTimeout(() => {
setVisible(true);
}, 0);
}, [])
const article = useMemo(() => {
return marked(content)
}, [content]);
return (
<div className={classnames("grouper-preview", { "grouper-preview--visible": visible })}>
<div className="grouper-preview__title">
<span className="title">{title}</span>
<span className="close" onClick={() => {
setVisible(false);
setTimeout(() => {
onClose()
}, 300);
}}>x</span>
</div>
<div className="grouper-preview__content" dangerouslySetInnerHTML={{ __html: article }} />
</div>
)
}
// components/preview/index.less
.grouper-preview {
width: 400px;
height: 800px;
position: fixed;
right: 10px;
bottom: 10px;
background: #ffffff;
border-radius: 4px;
box-shadow: 0 0 5px 1px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
transform: translateX(100%);
transition: all 0.3s ease-in-out;
}
.grouper-preview--visible {
transform: translateX(0);
}
.grouper-preview__title {
padding: 10px;
font-weight: 600;
font-size: 18px;
border-bottom: 1px solid #f1f1f1;
display: flex;
gap: 20px;
align-items: center;
justify-content: space-between;
.title {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.close {
cursor: pointer;
}
}
.grouper-preview__content {
padding: 10px;
overflow-y: scroll;
img{
max-width: 100%;
}
}