jokingzhang / blog

尽量多写一些原创的文章 ~
15 stars 0 forks source link

手摸手,打造属于自己的 React 组件库03 — 打包篇 #2

Open jokingzhang opened 4 years ago

jokingzhang commented 4 years ago

第三部分 - 打包篇:项目打包,并发布至 NPM

原文链接

引言

在前面的部分,我们使用 React 等相关技术构建了库并对其进行了测试。现在,我们准备对前面的代码进行打包,并将其发布至 NPM,方便其他人使用。

photo-1522199710521-72d69614c702

教程部分

本篇文章,是这个系列的第三篇::项目打包,并发布至 NPM

靠谱的(文档 + 打包)工具 :father

组件库开发到了这里,终于也到了最重要的部分,解决(文档 + 打包)的问题。

在尝试了一些打包库(比如create-react-library),和文档库(React Styleguidist)之后,都没有达到想要的效果。

直到B站的一个视频:利用 umi-library 做组件打包,答案就变得简单而明显了,就是利用云谦大大开源的组件打包利器:umijs/father,来完成最后一步。

因为目前整个打包工具会把src作为入口。为了避免前面路由,首页等代码被打包进去,这里对项目结构做出了较大的改动,新增加了 entry 作为路由的入口,而 src则作为组件的入口。建议参考下 dantd 中的目录结构。

package.json 以及字段详解

项目初始化之后,接下来,用编辑器打开这个项目,并修改 package.json 中下面属性:

{
  "main": "lib/index.js",
  "module": "es/index.js",
  "typings": "lib/index.d.ts",c
  "files": [
      "dist",
      "lib",
      "es"
  ],
  "scripts": {
    "start": "father doc dev",
    "doc:build": "father doc build",
    "doc:deploy": "father doc deploy",
    "lib:build": "father build"
  },
  "peerDependencies": {
    "react": ">=16.8.0",
    "react-dom": ">=16.8.0",
    "antd": ">=3.21.0"
  },
  "devDependencies": {
    "babel-plugin-import": "^1.13.0",
    "father": "^2.29.2",
    "fs-extra": "^8.1.0",
    "klaw-sync": "^6.0.0"
  },
  "dependencies": {
    "antd": "^3.21.0",
    "classnames": "^2.2.6",
    "lodash": "^4.17.15"
  },
}

添加好之后,运行:npm install 安装依赖。在等待的同时,让我们了解一下上述属性的具体含义:

上面两个都是程序的入口,当我们使用打包工具(webpack)进行打包的时候:1、如果它已经支持 pkg.module 字段则会优先使用 ES6 模块规范的版本 import,这样可以启用 Tree Shaking 机制。2、如果它还不识别 pkg.module 字段则会使用我们已经编译成 CommonJS 规范的版本 require('package1'),也不会阻碍打包流程。

father 初体验:打包第一个组件

这里,我们来给:EmptyLine 加上文档即可。为了方便阅读,这里还是放上了组件的所有相关代码。

src/index.tsx
export { default as EmptyLine } from './empty-line';
src/empty-line/index.tsx
import './style/index.less';
import EmptyLine from './EmptyLine';

export default EmptyLine;
src/empty-line/index.mdx
---
name: EmptyLine
route: /empty-line
menu: 组件
---

import { Playground } from 'docz';
import EmptyLine from './index';

## EmptyLine

> 组件名称:空行(EmptyLine),自定义组件 ,宽度 100%

### 代码演示

#### 复制信息

<Playground>
    <p>第一行文字</p>
    <EmptyLine />
    <p>第二行文字</p>
</Playground>

## API

|参数|说明|类型|默认值|
|:--|:--|:--|:--|
|height|空行的高度|number?|20|
src/empty-line/EmptyLine.tsx
import React from 'react';
import './style/index.less';

export interface IEmptyLineProps {
  height?: number;
}

const EmptyLine = ({ height = 20 }: IEmptyLineProps) => {
  return <div className="empty-line" style={{ height }} />;
};

export default EmptyLine;
src/empty-line/style/index.less
.empty-line {
    width: 100%;
    height: 20px;
}
.fatherrc.js
export default {
    // cssModules: true, // 默认是 .module.css 走 css modules,.css 不走 css modules。配置 cssModules 为 true 后,全部 css 文件都走 css modules。(less 文件同理)
    extractCSS: true,
    esm: 'babel',
    cjs: 'babel',
    umd: {
      name: 'dantd',
      sourcemap: true,
      globals: {
        react: 'React',
        antd: 'antd'
      },
    },
    extraBabelPlugins: [
      ['import', { libraryName: 'antd', libraryDirectory: 'es', style: true }],
    ],
    entry: 'src/index.tsx',
    lessInBabelMode: true,
    doc: {
      base: '/dantd/',
      menu: [
        '首页',
        '组件'
      ]
    },
}

更多配置项,欢迎探索文档:umijs/father

tsconfig.json
{
    "compilerOptions": {
      "baseUrl": "./src",
      "paths": {
        "antd": ["src/index.tsx"],
        "antd/es/*": ["src/*"]
      },
      "strictNullChecks": true,
      "moduleResolution": "node",
      "esModuleInterop": true,
      "experimentalDecorators": true,
      "jsx": "preserve",
      "noUnusedParameters": true,
      "noUnusedLocals": false,
      "noImplicitAny": true,
      "target": "es6",
      "lib": ["dom", "es2017"],
      "skipLibCheck": true
    },
    "exclude": ["node_modules", "lib", "es"]
}

添加这些文件之后,运行 npm start,就可以看到下面的界面了。

image

如果想引入 Antd,直接引入就行,上面的配置中,已经增加了:extraBabelPlugins。可以按需加载 antd。

组件中引入代码:

import { Card, Typography } from 'antd';

ES6 打包代码:

import "antd/es/card/style";
import _Card from "antd/es/card";
import "antd/es/typography/style";
import _Typography from "antd/es/typography";

CommonJS 打包代码:

require("antd/es/card/style");

var _card = _interopRequireDefault(require("antd/es/card"));

require("antd/es/typography/style");

var _typography = _interopRequireDefault(require("antd/es/typography"));

打包代码,并发布至 NPM

首先,运行 father build 打包代码。

可以看到,father 会分别根据:umdcjses 这三种格式进行打包,打包之后会看到多出了下面这些文件。

├── dist
|  ├── empty-line
|  |  ├── EmptyLine.d.ts
|  |  ├── index.d.ts
|  |  └── style
|  |     └── index.d.ts
|  ├── index.d.ts
|  ├── index.umd.css
|  ├── index.umd.js
|  ├── index.umd.js.map
|  ├── index.umd.min.css
|  ├── index.umd.min.js
|  └── index.umd.min.js.map
├── es
|  ├── empty-line
|  |  ├── EmptyLine.js
|  |  ├── index.js
|  |  └── style
|  |     ├── index.css
|  |     └── index.js
|  └── index.js
├── lib
|  ├── empty-line
|  |  ├── EmptyLine.js
|  |  ├── index.js
|  |  └── style
|  |     ├── index.css
|  |     └── index.js
|  └── index.js

此时,可以看到三种类型的包,已经被成功打出来了。那是不是这个时候就可以上传至 npm 了呢?

还不行,对比 Antdnpm 包之后,会发现 eslib 两个目录下,还没有类型文件。需要将 dist 目录下的文件拷贝过来,并把文件中的 .less 改成 .css。这里准备写2个脚本 hack 一下。

安装依赖:

npm install klaw-sync fs-extra -D

增加2个脚本:

const filesRegex = /.d.ts$/;

const declarePaths = klawSync(path.resolve(__dirname, '../dist'), { nodir: true }).filter(pathItem => filesRegex.test(pathItem.path))

declarePaths.forEach((pathItem) => { const esPath = pathItem.path.replace('/dist', '/es'); const libPath = pathItem.path.replace('/dist', '/lib'); fs.copyFileSync(pathItem.path, esPath); fs.copyFileSync(pathItem.path, libPath); })

console.log('.d.ts 文件拷贝成功!');

- **scripts/changeLess2Css.js**

const path = require('path'); const klawSync = require('klaw-sync'); const fs = require('fs');

const filesRegex = /(.js|.d.ts)$/;

const fileFilterFn = item => { const basename = path.basename(item.path); return filesRegex.test(basename) || basename.indexOf('.') < 0; }

const esPaths = klawSync(path.resolve(__dirname, '../es'), { filter: fileFilterFn, nodir: true }).map(item => item.path)

const libPaths = klawSync(path.resolve(__dirname, '../lib'), { filter: fileFilterFn, nodir: true }).map(item => item.path)

const allPaths = esPaths.concat(libPaths);

allPaths.forEach((fileItem) => { const fileContent = fs.readFileSync(fileItem, 'utf8'); const newFileContent = fileContent.replace(/.less/gi, '.css'); fs.writeFileSync(fileItem, newFileContent, 'utf8'); })

console.log('.less => .css 文件后缀改写成功!');


修改打包命令:

"build": "father build && node ./scripts/moveDeclare.js && node ./scripts/changeLess2Css.js"


运行:`npm run build`

这次打包之后的文件,就可以上传至 `npm` 了。

首先登陆 `npm`:

![image](https://user-images.githubusercontent.com/10766546/73185275-bb171100-4158-11ea-87b4-9d5b6dac888f.png)

使用 git 提交所有代码,然后修改版本号,并发布代码:

npm version patch git push npm publish


> 这里如果包名已经被注册,或者npm源不对,会报403错误,需要自行处理下

到了这里,我们的第一个属于自己的组件库就被上传至 `npm` 了,我们可以使用:`npm install dantd` 下载我们的安装包,并在项目中使用里面的组件了。 

![image](https://user-images.githubusercontent.com/10766546/73477929-19015e00-43d0-11ea-93f2-a28e8641bd3c.png)

### 打包文档,并发布至 GitHub Pages

首先,在 `package.json` 文件中,添加 `git` 地址,便于之后文档的发布:

"repository": { "type": "git", "url": "https://github.com/jokingzhang/dantd" },


运行下面命令打包文档:

npm run doc:build


运行下面命令发布文档:

npm run doc:deploy



然后访问对应的地址就可以看到我们发布到线上的组件文档了:[dantd](https://jokingzhang.github.io/dantd/empty-line)

![image](https://user-images.githubusercontent.com/10766546/73463932-0c254000-43b9-11ea-9f15-d8d9ef62ced3.png)

# 结束语

属于我们自己的第一个组件库,就这样被发布到 `npm` 了。🎉🎉🎉 但是,这个组件库需要写哪些组件进去,是我们接下来需要考虑的。

如果您喜欢这个系列,欢迎评论,分享文章链接。此外,也欢迎多多吐槽,🙏 这些反馈对我来说是非常宝贵的,以便我将来写出更优秀的文章。
Linbubin commented 4 years ago

npm start会提示 createContext undefined, 能贴个demo吗

jokingzhang commented 4 years ago

这个确实很久没有维护了,现在也推荐dumi + father