写在最前:
环境要求:
node(必需!)
npm 包的开发,就是利用 nodejs 的包机制来实现的,这没啥说的
涉及技术栈:
最基础:nodejs + JS
为了增强代码的可读写和可维护性,可以加上:TypeScript。
如果是设计组件,可以使用我们当前 react。
为了优化,或者更好地开发,还可以使用到 webpack 等。
其它一些如 ESLint、Prettier 等一些配置。
总之,把 npm 包的开发,当成是一个项目的开发就行了。
前言
npm
包的使用,作为前端开发都不陌生,我们经常在网上下载一些 npm
包(或者也有说成第三方库)在自己的项目中使用, 如 lodash
、dayjs
、axios
等等。
平时我们都是从网上下载使用其他人写的 npm
包,肯定也有想自己写个 npm
包的想法。于是有了这篇文档。
npm
包的开发和平时我们业务项目开发一样,也有易有难,这篇只是简单创建个 npm
包,仅仅做为一个了解入门的项目。
npm 带来的好处?使用场景有哪些?
下面结合平时项目开发,举例两个简单的场景来说明。
场景一:
我们平时项目开发时,通常一套规则,可能会使用到好几个项目中,比如说目前管理端的
- 判断设备是
argos
设备还是lora
设备还是普通设备。 - 根据设备型号和硬件版本推断设备类型名。
- 判断设备是否是网关。
- 等...
如果你的业务涉及到多个端,如小程序,公众号,PC端管理端/用户端等,假如,我们公司有这么多端的项目,而且这些项目都用到了某个规则,突然某一天,产品说这个规则需要改一下,或者在原来基础上加一些新的规则,我们要改的话,就需要每个端的项目都去改一遍,而如果这里我们是用的 npm
包来做的话,我们只需要去更改 npm
包的代码规则,然后发布,然后各个项目只需要更新到最新版本的 npm
包即可。
场景二:
一些规定的数据枚举,其实也可以使用 npm
包来返回,如
- 性别。
- 设备工作模式。
- 设备工作状态。
- 等...
这种通过后端返回某个字段,展示对应的文案,在 npm
包统一返回,前端所有项目,直接拿到对应的数据进行使用即可,如果有所变化,直接改变 npm
包,前端所有项目更新 npm
包版本即可。
场景三:
上面只是涉及到数据类的处理,npm
包,也可以是某个组件,比如说,我们好几个 PC 端的项目,都有涉及到某些公共的模块,每个项目里开发,都会将这部分抽离成一个公共的组件来使用,但是如果是开发一个新的项目,遇到这种模块的时候,我们又得重新在新的项目里封装。这时使用 npm
包来开发,可以直接在新的项目中下载这个包来直接使用。
经常有变动,最好就不要用 npm
包的形式开发了
npm
包,也是有版本管理的,前端不同项目,通过使用不同版本的 npm
包,也能拿到不一样的数据或者可以使用不一样的处理逻辑。
通过上面几个场景,大概应该能了解到 npm
包的开发使用带来了哪些好处了。下面就开始简单介绍 npm
包项目是如何创建、发布、更新的。
npm 包项目的创建
默认 node
已安装。
1. 新建 npm 包项目目录
字面意思,随便使用命令或者手动新建一个目录就行了。
2. 初始化 package.json 文件
npm
包的项目目录建好之后,在此目录下,执行下面命令行,初始化一个新的 package.json
文件。
npm init
如果只输入 npm init
,它会向你提一系列的问题,如果你觉得不用修改,或者创建完之后你再去修改,可以直接一路回车即可。或者执行 npm init -y
3. 绑定远程仓库
这里以 GitHub
为例
- 去
GitHub
上创建一个新项目。 - 回到项目目录下,执行
git init
命令初始化。
这个时候通过 git status
就能看到,工作区已经有个上面新添加的 package.json
文件了。
- 上传当前修改到远程仓库。
git add .
git commit -m 'feat: git init'
git remote add origin git@github.com:kivet-h/kivet_npm_package.git // ? 本地项目绑定远程仓库,地址是仓库的地址
git push -u origin master
4. 新建 README.md 文件
这个文件看似可以无,但在 npm
包开发的项目中十分重要,需要用它来记录你的 npm
包怎么安装,怎么使用,每个版本,更新了哪些内容等,这样使用你 npm
包的开发者才能看懂你 npm
包的相关信息。如果内容过多,还可以建个专门的目录来总结归纳。
暂时先将文件建起,等项目开发完最后再来添加内容。
5. 新建 src 目录,写入逻辑测试代码
通常情况,我们源码都是写在 src
目录下的。在 src
目录下,新建出口文件 index.js
文件以及随便建一个存放逻辑的文件如处理数组的文件 array.js
,也可以再创建一个存放基础数据的文件如 baseData.js
。
index.js
文件,最好只作为一个导出出口文件,不做逻辑的书写。逻辑的书写写在专门的文件里,如上面的
array.js
文件,写了一个处理数组的方法,然后导出,baseData.js
文件,写个个基础数据的枚举配置,然后导出。index.js
文件,统一导入项目中所有的逻辑处理或者一些基础数据,再统一导出。
使用这个 npm
包的时候,其实就是导入使用这里 index.js
统一导出的内容。
// index.js
const { joinArr } = require('./array');
const genderData = require('./baseData');
module.exports = {
joinArr,
genderData,
};
// array.js
/** 随便写个数组相关的函数,如拼接数组元素 */
const joinArr = (arr = []) => arr.join('-');
exports.joinArr = joinArr;
// baseData.js
module.exports = {
1: '男',
2: '女',
};
6. 修改设置包的入口点
上面第一步,使用 npm init
命令创建初始化的 package.json
,它默认的入口文件是项目根目录下的 index.js
文件,上面我们第 5 步新建了 src
,并以 src/index.js
文件统一导出,所以需要将项目的入口点改成这个文件
// package.json
{
"name": "kivet_npm_package",
"version": "1.0.0",
"description": "一个简单的 npm 包项目",
- "main": "index.js",
+ "main": "src/index.js",
"scripts": {
"test": "echo "Error: no test specified" && exit 1"
},
"author": "kivet",
"license": "ISC"
}
npm 包本地开发测试
npm
包的开发,其实就可以想象成平时项目中对公共模块的抽离,只是这里将公共模块抽离写成了一个 npm
包而已。但是这样该如何本地测试呢?毕竟不可能一直通过发布 npm
包,然后又去项目中更新到最新版本来测试。我们想要做到的,肯定是本地开发测试都没问题了,然后再发布。
网上有很多解决办法,这里使用的是 yalc
,
下面假设我们开发了个名为 kivet_npm_package
的本地 npm
包。
- 全局安装
yalc
npm install yalc -g
有可能安装不起,这有可能是 mac
权限的问题,前面加个 sudo
就行了 sudo npm install yalc -g
- 发布本地 npm 包
// npm 包项目目录下
yalc publish
- 本地安装上面发布的 npm 包
可以自己新建一个,或者随便找个什么项目,能安装 npm 包就行
在新的项目中安装上面本地发布的 npm 包
yalc add kivet_npm_package
或安装指定版本
yalc add [my_package_name@version]
安装完成后,可以看到,新的项目中会多出一个名为 yalc.lock
文件。
同时,在 package.json
对应的包名会有个地址为 file:.yalc/[my_package_name]
的值。
- 本地使用上面发布的 npm 包
- 更新
本地开发,肯定设计到修改,然后重新看结果的情况,这里的更新比较麻烦,不像我们平时前端开发,直接保存后浏览器就自己刷新了,它这里需要两步操作。
- 修改了
npm
包项目代码后,在npm
包项目目录下,重新执行
yalc publish
- 在使用这个 npm 包的项目目录下,执行
yalc update
或
yalc update kivet_npm_package
这样,在使用 npm 包的项目中,才能看到修改后的 npm 包的样子。
附:
暂时我只知道这样子操作才能生效,不清楚是否还有更简便的方式来实现更新。以后要是知道有什么更好的办法,再来加上。
- 移除
npm
包此次版本开发完成,需要在测试使用这个 npm
包的项目中移除本地发布的 npm
包
// 在测试使用这个 npm 包的项目目录下执行
yalc remove kivet_npm_package
附:
其它,yalc
的使用方法,自行去翻阅 yalc
相关的文档。
npm 包的发布
本地开发测试完成后,就要将包发布到 npm
上了
- 注册
npm
账号
要想将自己的 npm
包发布到 npm 上,首先你肯定是需要一个 npm
包的账号的。所以需要先去 npm 官网注册一个 mpm
的账号。
- 在终端登录
npm
npm adduser
或
npm login
两个二选其一即可,首次登录,会叫你输入 username
、password
和 email
,这就是上面注册 npm
输入的内容。
- 发布
登录成功,就可以发布我们的 npm 包到网上了。执行
npm publish
这样,就已经发布成功了,去 npm 官网 搜索,就能看到我们发布的包了。
或者直接访问 www.npmjs.com/package/kiv…,npm
包的连接,就是(www.npmjs.com/package/[my…
下载使用发布的 npm 包
还是在刚才测试的项目中来使用,先使用 yalc remove kivet_npm_package
命令移除项目中本地的 npm 包,然后执行 yarn add kivet_npm_package -D
进行安装。
安装成功后,还是刚才那个测试文件测试一下。
npm 包的更新
每次发布 npm
包,都会有一个对应的 npm
包版本号,而且这个版本号还不能与上一次发布的一样,
如上面已经发布了第一个 npm
包,版本号就是 v1.0.0
,如果你再次执行 npm publish
,是发布不成功了,因为现在的版本号和显示的版本号是一样的。不过如果你忘记修改版本号也没关系,因为你根本推不上去。
这里先简单说一下 npm
版本号的构造。
在 package.json
中,有这样一个属性: "version":"1.0.0"
这就是 npm
的版本号,它是由三个数字组成。
- 第一个数字是主版本号。
- 第二个数字是次版本号。
- 第三个数字是补丁版本号。
修改对应的版本号,代表着不同的含义:
- 补丁版本号:仅修复缺陷的版本。
- 次版本号 :引入向后兼容的更改的版本。
- 主版本号 :具有重大更改的版本。
再次发布的时候,必须先更新版本号,而且只能通过执行命令的方式来修改,亲测直接手动去修改 package.json
里的 version
是不起作用的。
针对三个不同的版本号,npm
提供了不同的命令行来更新版本号
patch:增加补丁版本号 v1.0.1->v1.0.2
$ npm version patch
minor:增加次版本号 v1.0.2->v1.1.0
$ npm version minor
major:增加主版本号 v1.1.0->v2.0.0
$ npm version major
这里就不测试了,步骤大概就分下面几步:
- 默认第一个版本(v1.0.0)的
npm
包以开发完成,并成功发布。 - 假设准备开始开发新的功能,本地开发测试时,还是和上面本地开发测试的步骤一样
- 开发完成,如想发布一个补丁版本,即递增补丁版本号,执行
npm version patch
,这个时候,版本号就变成了 v1.0.1 了 - 然后发布,执行
npm publish
。新的版本的 npm 包发布成功。
提示:
如果你的项目绑定了远程仓库,这个时候你执行修改版本号的命令是会报错的,提示你 npm ERR! Git working directory not clean.
,这个时候直接先将本地代码 push
了就可以了。
npm 包的版本控制
从上面每次发布都需要修改版本号可以看出,npm
包也是有版本控制这一概念的。每个版本都有自己的一套代码逻辑,简单举个路子,如下图
————————分割线
截止这里,npm
包的开发,测试,发布,更新的一系列步骤都已经完成了。下面是针对 npm
包项目做的一些优化配置操作。
package.json 文件部分配置属性解释
首先,先了解一下 package.json
文件的一些配置,这里只说明一些可能需要用到的配置,具体所有的配置对应的是什么功能,可访问 npm 给出的 package.json 配置官方解释文档查看。或者自行 Google。
name
npm
包名字。
version
包当前版本号
上面执行修改版本号的命令后,这里也会跟着变化。
description
包的一个简单描述
main
设置包的入口文件
如果你的包需要先 build
打包后再发布,name这个就得设置成你 build
后的打包目录下面的入口文件。
repository
当你需要给你的 npm
包添加你的 GitHub
仓库地址
// ? package.json
{
"repository": {
"type": "git",
"url": "https://github.com/kivet-h/kivet_npm_package"
},
}
bugs
你的 npm
包,发布到网上去了,如果有人使用并发现有问题,肯定需要有途径来告知你出了什么 bug
。
// ? package.json
{
"bugs": {
"url": "https://github.com/kivet-h/kivet_npm_package/issues",
"email": "kivet666@dingtalk.com"
}
}
homepage
为你的项目添加一个首页,这样用户就能快速访问你的项目,通常是项目的 README.md
文档。
// package.json
{
"homepage": "https://github.com/kivet-h/kivet_npm_package/blob/master/README.md"
}
type
项目中加载模块的方式,它可以被设置成两个值
{
"type": "commonjs", // Node.js 专用的 CommonJS 模块,不设置 type 时默认是 commonjs
"type": "module", // ES6 模块(简称ESM)
}
具体区别可访问查看阮一峰老师的Node.js 如何处理 ES6 模块
其它,遇到需要的再添加...
添加 npm 包发布时忽略文件 .npmignore 文件
这个和 git
提交时的忽略文件 .gitignore
是一个道理的,只是 .gitignore
是 git
提交时忽略上传某些文件,而 .npmignore
是 npm 包发布时忽略上传某些文件。
// .npmignore
/node_modules
// .gitignore git 的忽略文件也应该添加上
/node_modules
目前暂时只有 node_modules 目录需要被忽略,随着项目的开发进度,肯定会有更多的目录或者文件需要被忽略,后面遇到再说。
添加 TypeScript 支持
安装 TypeScript
yarn add typescript -D
添加 TS 的配置文件 tsconfig.json
大概简单配置下
// tsconfig.json
{
"compilerOptions": {
"target": "es5",
"module": "ES6",
"declaration": true,
"outDir": "./build",
"strict": true,
"lib": ["es6"],
"downlevelIteration": true
},
"include": ["src"],
"exclude": ["node_modules", "**/__tests__/*"]
}
简单介绍下部分配置项的含义
-
target
:编译之后生成的 JavaScript 文件需要遵循的标准,可选值:"ES3"(默认),"ES5","ES6"/"ES2015","ES2016","ES2017"或"ESNext"。我们选择了 es5 为了使包具有更好的浏览器兼容性。 -
module
:指定生成哪个模块系统代码,默认值:target === "ES3" or "ES5" ? "CommonJS" : "ES6"。 -
declaration
:是否生成对应的声明文件,默认值:false。在构建包时,应该设置为 true,这样 TypeScript 会将生成的声明文件和对应编译后的 JavaScript 代码一起导出,以便包可以在 TypeScript 和 JavaScript 项目中同时使用。声明文件,就是打包 ts 代码后,生成的每个 js 文件,对应的一个后缀名为.d.ts
的文件。 -
outDir
:指定输出目录。如上面的配置,编译后的 JavaScript 代码会在与 tsconfig.json 同级的 build 文件夹中。 -
strict
:是否启用所有严格类型检查选项,默认值:false。 -
lib
:编译需要的库文件,例如你指定的 target 是 ES5,但是在代码中使用了 ES6 特性,就需要在 lib 中加上 ES6。默认值:如果 lib 没有指定默认注入的库的列表,默认注入的库为:- target ES5:DOM,ES5,ScriptHost。
- target ES6:DOM,ES6,DOM.Iterable,ScriptHost。
-
include
:指定要编译的目录。 -
exclude
:指定不编译的目录。node_modules 和 tests 只是在开发阶段使用,构建阶段无需编译。
其它更多编译选项,可访问 此链接 查看。
将之前所有 .js 文件后缀改成 .ts
将后缀名改了之后,你会发现有很多报错,
一些是 TS 的校验报错,有些 TS 校验报错是可以不需要的,直接去 tsconfig.json
文件中去配置即可
还有一些原因,是因为 node
的 Commonjs
导致的,前面说了, package.json
中的 type
配置,如果没有设置,默认就是 commonjs
,这就可能导致一些 TS 校验报错的问题,将 type
设置成 "mudole"
,以 ES6
的模块形式加载
// package.json
{
"type": "module"
}
如果你非要想要在 commonjs
模式下开发,也没问题,不过需要安装额外的名为 @types/node
的一个包,但是这个包也并不能解决所有的 TS 校验报错的问题,
如当你在 array.ts
文件中使用 joinArr
函数名导出,然后在 index.ts
文件下也使用 joinArr
这个名字导入,就会报
之所以会这样,是因为在 Commonjs
规范里,没有像 ESModule
能形成闭包的「模块」概念,所有的模块在引用时都默认被抛至全局,因此当再次声明某个模块时,TypeScript
会认为重复声明了两次相同的变量进而抛错。
所有干脆就直接将模式改成 ES6,毕竟我们平时写代码也是这个模式下写的。
然后优化下之前写的代码
// src/array.ts
/**
* 拼接数组
* @param arr 需要拼接的数组数据
* @param {string} str 以传入 str 进行拼接,默认为 '-'
* @returns {string} 拼接完成后的字符串
*/
const joinArr = <T>(arr: T[] = [], str: string = '-'): string => arr.join(str);
/**
* 数组去重
* @param arr 需要去重的数组数据
* @returns 去重完成后的数组
*/
const clearSame = <T>(arr: T[] = []): T[] => [...new Set(arr)];
export { joinArr, clearSame };
// src/baseData.ts
/**
* 性别基础数据枚举
*/
export const genderData = {
1: '男',
2: '女',
};
// src/index.ts
export { joinArr, clearSame } from './array.js';
export { genderData } from './baseData.js';
// !!! 注意:type: 'module' 后,导入文件的 .js 后缀不能省略
参考:https://stackoverflow.com/questions/65384754/error-err-module-not-found-cannot-find-module
我们平时写 ts 的项目可以省略,是因为通过 webpack 等工具配置了的,比如说它会默认先找 .ts,没有再找 .es,再找 .js
参考:https://segmentfault.com/q/1010000009891745
提示:
养成写注释的好习惯,这样开发者在使用你的包的时候,能一眼看出你的数据是什么数据,你的方法是干什么用的,需要传些什么值
在 package.json 文件中添加 build script
// package.json
{
"scripts": {
"test": "echo "Error: no test specified" && exit 1",
"build": "tsc"
},
}
node 是无法直接运行 .ts
的文件的,所有需要将项目所有的 .ts
文件都编译成 .js
文件。
执行 npm run build
测试一下,发现成功打包。
这里每个文件对应的 .d.ts
,就是上面说的声明文件。
重新配置忽略文件
// .npmignore
/node_modules
/src
// .gitignore
/node_modules
/build
npm
包发布时,直接发布打包后的 build
目录即可,所以源码文件目录 src
可以忽略上传。
git
代码提交时,直接提交源码目录 src
即可,所有打包文件目录 build
可以忽略上传。
修改入口文件
上面说了,npm
包上传时,只会上传打包目录 build
目录,所有线上的入口文件也需要换成 build/index.js
// package.json
{
"main": "build/index.js",
}
优化
前面,通过 .npmignore
文件,可以忽略部分文件或目录的上传,相当于是一个黑名单,但是这并不是很友好,因为这样的话,你每在根目录下新增一个文件或目录,都要在这里面添加,我们想要的只是将打包后的文件上传,要是有个白名单,设置只上传打包后的目录文件即可。
// package.json
{
"files": ["build/**/*"]
}
然后删除 .npmignore
文件。
完善 README.md 文档
每次新的一个版本的 npm
包的开发,都需要在 README.md
文档中记录清楚,无论是增加、删除还是修改了什么功能。
还有,你新增加的功能是怎么使用的。给个简单的示例 demo
。
其它
其它一些配置
- 添加代码校验命令行。
- 添加格式化命令行。
- 添加webpack。
- 添加git提交规范。
- 等等。。。
不一一说明了。
其它创建 npm 包项目
可以直接使用 TSDX,有点像 react
的 create-react-app
,直接开箱即用。
如果是想使用 react + TS
等开发一个组件,也可以使用 dumi。
应该还有其它方式,目前只知道这些。
参考链接:
[0] 如何优雅地在本地测试 npm 包