在日常的工作中,我们难免会用到一些工具函数,有些工具函数会经常在不同的项目中用到;此时要么直接在需要的项目中将所用到的工具函数复制过去(前提是你有沉淀自己的工具函数的习惯)
,要么就是将工具函数以第三方库依赖的形式引用进来。
显然,以第三方库的形式引入能更加节省我们的时间,也更符合程序员DRY(Do not Repeat Yourself)
的良好习惯。
所以,本篇文章就简单讲一下如何封装一个工具函数库并发布到npm
上。
每个人要发布的npm
包类型都不尽相同,有UI组件库,有工具函数库,还有使用的插件等。这里要发布的npm
包是在项目中常用的工具函数组成的工具函数库,构建工具使用的是rollup
,代码托管在github
上。
下面简述一下一些关键点:
首先在github
上新建仓库,新建仓库时License
选择MIT
, 此步骤不选择也无妨,后续添加license
也可以。但是一定要有License
才能发布npm
包。
初始化git
仓库之后将代码拉取到本地
git clone xxxxx
执行npm init -y
生成package.json
文件
由于我们此次主要是打包一个js库
,所以我们选择更适合此场景的rollup
作为打包工具。关于webpack
和rollup
的比较可以看下我的另一篇博客webpack VS rollup。
安装rollup
pnpm add rollup
安装必要的rollup插件
pnpm add @rollup/plugin-commonjs @rollup/plugin-node-resolve @rollup/plugin-typescript
插件作用说明:
@rollup/plugin-commonjs:rollup
本身是不支持CommonJS
的,使用了这个插件,就可以解析CommonJS
模块了
@rollup/plugin-node-resolve:rollup
无法识别 node_modules
中的包,帮助 rollup
查找外部模块,然后导入
@rollup/plugin-typescript:rollup
中使用typescript
必备的插件
在安装@rollup/plugin-typescript
插件时提示我们需要tslib
和typescript
,所以我们安装这两个第三方库
pnpm add tslib typescript
如果需要进行代码压缩和清除注释等需要安装如下插件
pnpm add rollup-plugin-terser rollup-plugin-cleanup
rollup-plugin-terser
插件用于代码压缩
rollup-plugin-cleanup
插件用于去除无效代码
typescript
在终端中执行npx tsc --init
命令,生成tsconfig.json
文件
修改tsconfig.json
文件如下:
{
"compilerOptions": {
"baseUrl": ".",
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"downlevelIteration": true,
// 是否自动创建类型声明文件
"declaration": true,
// 类型声明文件的输出目录
"declarationDir": "dist/types",
// 只生成声明文件,而不会生成js文件
"emitDeclarationOnly": true,
// 指定输出文件目录(用于输出),用于控制输出目录结构
"rootDir": "src",
"paths": {
"@/*": ["src/*"],
"@types/*": ["src/types/*"],
"@utils/*": ["src/utils/*"]
}
},
"include": ["src"]
}
如果要为工具库生成类型声明文件则必须有如下配置
{
// 是否自动创建类型声明文件
"declaration": true,
// 类型声明文件的输出目录
"declarationDir": "dist/types",
// 只生成声明文件,而不会生成js文件
"emitDeclarationOnly": true,
// 指定输出文件目录(用于输出),用于控制输出目录结构
"rootDir": "src",
}
为什么要为工具库生成类型声明文件呢?
主要是为了避免引入 JS 库的时候,遇到这样的报错:无法找到模块“@superying/remote-ui”的声明文件
,让工具库有更好的typescript
类型支持,且生成类型声明文件之后可以在引入工具库之后很方便的校验和看到工具方法的出入参。
rollup
在项目根目录下新建文件rollup.config.js
,关于rollup
的详细配置可参考官网教程。对于我们的工具库而言,rollup
可简单配置如下:
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
import { terser } from 'rollup-plugin-terser';
import cleanup from 'rollup-plugin-cleanup';
export default [
{
input: './src/index.ts',
output: {
dir: 'dist',
format: 'cjs',
entryFileNames: '[name].cjs.js',
},
plugins: [resolve(), commonjs(), typescript(), terser(), cleanup()],
}, {
input: './src/index.ts',
output: {
dir: 'dist',
format: 'esm',
entryFileNames: '[name].esm.js',
},
plugins: [resolve(), commonjs(), typescript(), terser(), cleanup()],
}
];
在根目录下新建src
目录,在src
目录下新建index.ts
编写我们的工具函数;这里我们先写一个简单的工具函数
// 返回传入的日期是今年的第几天,如果不传参数则默认是当前日期
export const dayOfYear = (date?: Date | string): number => {
let formatDate = null;
if (!date) {
formatDate = new Date();
} else {
formatDate = typeof date === "string" ? new Date(date) : date;
}
// 如果传入的是无效的字符串,那么就默认是当前日期
if (!formatDate.getFullYear) {
formatDate = new Date();
}
const year = formatDate.getFullYear();
const firstDayOfYear = new Date(year, 0, 0);
const timeGap = formatDate.getTime() - firstDayOfYear.getTime();
return Math.floor(timeGap / 1000 / 60 / 60 / 24);
};
package.json
配置{
"name": "wangcc-utils",
"version": "0.1.0",
"description": "wangchaochuan's util function library",
"main": "./dist/index.cjs.js",
"module": "./dist/index.esm.js",
"types": "./dist/types/index.d.ts",
"files": [
"dist"
],
"scripts": {
"dev": "rollup -w -c",
"build:types": "tsc -b ./tsconfig.json",
"build": "npm run build:types && rollup -c",
"prepublish": "pnpm version && pnpm build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/wangchaochuan/wangcc-utils.git"
},
"keywords": [
"utils"
],
"author": "wangchaochuan",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"bugs": {
"url": "https://github.com/wangchaochuan/wangcc-utils/issues"
},
"homepage": "https://github.com/wangchaochuan/wangcc-utils#readme",
"dependencies": {
"@rollup/plugin-commonjs": "^22.0.2",
"@rollup/plugin-node-resolve": "^14.1.0",
"@rollup/plugin-typescript": "^8.5.0",
"rollup": "^2.79.1",
"rollup-plugin-cleanup": "^3.2.1",
"rollup-plugin-terser": "^7.0.2",
"tslib": "^2.4.0",
"typescript": "^4.8.4"
}
}
说明:
"types": "./dist/types/index.d.ts"
的作用:package.json
中配置 types
属性,将其指向生成的类型声明文件。package.json
中增加publishConfig
的配置"publishConfig": {
"access": "public"
},
在终端中执行pnpm build
打包文件后会发现在根目录下生成了dist
文件夹,在文件夹下会生成如下文件
此时我们的工具库已经初步形成,接下来就是不断地沉淀我们的工具函数即可。
发布npm
包需要我们先有一个npm
账号,如果没有npm
账号的话需要先注册一个。
注册网址:www.npmjs.com,根据提示进行账号注册即可
npm
包发布npm
账号执行npm login
命令即可根据提示登录npm
账号;
如果顺利的话根据提示依次输入用户名、密码、邮箱和验证码之后即可顺利登录;
如果不顺利的话可能会遇到如下问题Error: 500 Internal Server Error - PUT https://registry.npm.taobao.org/-/user/org.couchdb.user:linshen
,这个错误时因为没有设置npm源
导致的,我们在终端执行如下命令即可,npm config set registry https://registry.npmjs.org/
;然后重新执行npm login
即可正常登录。
登录成功之后会有如下提示Logged in as xxx on https://registry.npmjs.org/.
npm包
成功登录npm账号
之后执行如下命令即可发布npm包
npm publish
发布成功之后会有如下提示:
同时会向你的邮箱发送一封发布成功的邮件,然后我们就可以在npm
上搜索到自己发布的npm包
,就可以正常当做第三方依赖添加进项目使用了。
npm包
更新npm包两步走:
执行第一步的时候有可能会报错,如下图:
这个报错是因为我们的代码还没有提交,提交代码之后就可以正常执行了。
重新npm publish
之后会提示我们新的版本已经发布成功了。
npm包
npm unpublish <包名> -force
**注意:**删除后需要等24h
后才能重新发布同名的npm包
npm包
在另外一个项目中执行如下命令
npm install wangcc-utils --save
在需要用到的地方引入依赖,此处我们仅作为示例,故在home.tsx
中引入
import { FC } from 'react';
import { Button } from 'antd';
import styles from './index.module.scss';
import { useNavigate } from 'react-router-dom';
// 引入依赖
import { dayOfYear } from 'wangcc-utils';
const Home: FC = () => {
const navigate = useNavigate();
const jump = () => {
navigate('/product');
};
// 使用工具函数
console.log(dayOfYear());
return (
<div className={styles.home}>
<div className={styles.header}>react app home</div>
<Button size="large" onClick={jump}>
跳转
</Button>
</div>
);
};
export default Home;
我们启动这个项目,发现在控制台中正确的打印了今天是今年的第几天,说明这个工具函数被正确引用和使用了。
同时,当我们鼠标移到dayOfYear
的时候,开发工具给我们提示了dayOfYear
这个工具函数的出入参,这说明我们生成的类型声明文件被正确的引用了。
以上就是封装自己的工具函数库并发布npm包
的简略流程,均为笔者亲测。如果有什么遗漏或者错误的地方欢迎指正。
详细代码可参考笔者github。