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

7,753 阅读6分钟

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

原文链接

组件库现在由我们团队在维护,欢迎围观,Start ~

引言

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

教程部分

本篇文章,是这个系列的第三篇:项目打包,并发布至 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 安装依赖。在等待的同时,让我们了解一下上述属性的具体含义:

  • "main": "lib/index.js": 定义了 npm 包的入口文件,browser 环境和 node 环境均可使用
  • "module": "es/index.js" : 定义 npm 包的 ESM 规范的入口文件,browser 环境和 node 环境均可使用

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

  • typings: 包的类型声明文件
  • files:他描述了软件包作为依赖项被安装时会包含的所有条目,解析详见官方文档
  • scripts/"start": "father doc dev":以 docz 的方式开发组件
  • scripts/"doc:build": "father doc build":构建文档
  • scripts/"doc:deploy": "father doc deploy":发布文档
  • scripts/"build": "father build":打包组件
  • peerDependencies:是一种特殊类型的依赖项,只有在发布自己的程序包时才会出现。如果写了这个属性,意味着使用您在开发软件的依赖包需要和这个个属性所设置的依赖完全相等。这里因为有些组件是基于 Antd 进行简单封装的,所以,在使用这个组件库的时候,也需要安装对应版本的 Antd 等依赖。
  • dependencies:这些是您的常规依赖项,或者是运行代码时所需的依赖项(例如React或ImmutableJS)。
  • devDependencies:这些是您的开发依赖项。在开发工作流中某些时候需要的依赖关系,而在运行代码时(例如Babel或Flow)则不需要。

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个脚本:

  • scripts/moveDeclare.js

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

    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

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

npm version patch
git push
npm publish

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

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

image

打包文档,并发布至 GitHub Pages

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

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

运行下面命令打包文档:

npm run doc:build

运行下面命令发布文档:

npm run doc:deploy

然后访问对应的地址就可以看到我们发布到线上的组件文档了:dantd

image

结束语

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

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