如何发布一个 TypeScript 编写的 npm 包
前言
在这篇文章中,我们将使用TypeScript和Jest从头开始构建和发布一个NPM包。
我们将初始化一个项目,设置TypeScript,用Jest编写测试,并将其发布到NPM。
项目
我们的库称为digx。它允许从嵌套对象中根据路径找出值,类似于lodash中的get函数。
比如说:
const source = { my: { nested: [1, 2, 3] } }
digx(source, "my.nested[1]") //=> 2
就本文而言,只要它是简洁的和可测试的,它做什么并不那么重要。
初始化项目
让我们从创建空目录并初始化它开始。
mkdir digx
cd digx
npm init --yes
npm init --yes命令将为你创建package.json文件,并填充一些默认值。
让我们也在同一文件夹中设置一个git仓库。
git init
echo "node_modules" >> .gitignore
echo "dist" >> .gitignore
git add .
git commit -m "initial"
构建库
这里会用到TypeScript,我们来安装它。
npm i -D typescript
使用下面的配置创建tsconfig.json文件:
{
"files": ["src/index.ts"],
"compilerOptions": {
"target": "es2015",
"module": "es2015",
"declaration": true,
"outDir": "./dist",
"noEmit": false,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true
}
}
最重要的设置是这些:
- 库的主文件会位于
src文件夹下,因此需要这么设置"files": ["src/index.ts"]。 "target": "es2015"确保我们的库支持现代平台,并且不会携带不必要的垫片。"module": "es2015"。我们的模块将是一个标准的ES模块(默认是CommonJS)。ES模式在现代浏览器下没有任何问题;甚至Node从13版本开始就支持ES模式。"declaration": true- 因为我们想要自动生成d.ts声明文件。我们的TypeScript用户将需要这些声明文件。
其他大部分选项只是各种可选的TypeScript检查,我更喜欢开启这些检查。
打开package.json,更新scripts的内容:
"scripts": {
"build": "tsc"
}
现在我们可以用npm run build来运行构建...这样会失败的,因为我们还没有任何可以构建的代码。
我们从另一端开始。
添加测试
作为一名负责任的开发,我们将从测试开始。我们将使用jest,因为它简单且好用。
npm i -D jest @types/jest ts-jest
ts-jest包是Jest理解TypeScript所需要的。另一个选择是使用babel,这将需要更多的配置和额外的模块。我们就保持简洁,采用ts-jest。
使用如下命令初始化jest配置文件:
./node_modules/.bin/jest --init
一路狂按回车键就行,默认值就很好。
这会使用一些默认选项创建jest.config.js文件,并添加"test": "jest"脚本到package.json中。
打开jest.config.js,找到以preset开始的行,并更新为:
{
// ...
preset: "ts-jest",
// ...
}
最后,创建src目录,以及测试文件src/digx.test.ts,填入如下代码:
import dg from "./index";
test("works with a shallow object", () => {
expect(dg({ param: 1 }, "param")).toBe(1);
});
test("works with a shallow array", () => {
expect(dg([1, 2, 3], "[2]")).toBe(3);
});
test("works with a shallow array when shouldThrow is true", () => {
expect(dg([1, 2, 3], "[2]", true)).toBe(3);
});
test("works with a nested object", () => {
const source = { param: [{}, { test: "A" }] };
expect(dg(source, "param[1].test")).toBe("A");
});
test("returns undefined when source is null", () => {
expect(dg(null, "param[1].test")).toBeUndefined();
});
test("returns undefined when path is wrong", () => {
expect(dg({ param: [] }, "param[1].test")).toBeUndefined();
});
test("throws an exception when path is wrong and shouldThrow is true", () => {
expect(() => dg({ param: [] }, "param[1].test", true)).toThrow();
});
test("works tranparently with Sets and Maps", () => {
const source = new Map([
["param", new Set()],
["innerSet", new Set([new Map(), new Map([["innerKey", "value"]])])],
]);
expect(dg(source, "innerSet[1].innerKey")).toBe("value");
});
这些单元测试让我们对正在构建的东西有一个直观的了解。
我们的模块导出一个单一函数,digx。它接收任意对象,字符串参数path,以及可选参数shouldThrow,该参数使得提供的路径在源对象的嵌套结构中不被允许时,抛出一个异常。
嵌套结构可以是对象和数组,也可以是Map和Set。
使用npm t运行测试,当然,不出意外会失败。
现在打开src/index.ts文件,并写入下面内容:
export default dig;
/**
* A dig function that takes any object with a nested structure and a path,
* and returns the value under that path or undefined when no value is found.
*
* @param {any} source - A nested objects.
* @param {string} path - A path string, for example `my[1].test.field`
* @param {boolean} [shouldThrow=false] - Optionally throw an exception when nothing found
*
*/
function dig(source: any, path: string, shouldThrow: boolean = false) {
if (source === null || source === undefined) {
return undefined;
}
// split path: "param[3].test" => ["param", 3, "test"]
const parts = splitPath(path);
return parts.reduce((acc, el) => {
if (acc === undefined) {
if (shouldThrow) {
throw new Error(`Could not dig the value using path: ${path}`);
} else {
return undefined;
}
}
if (isNum(el)) {
// array getter [3]
const arrIndex = parseInt(el);
if (acc instanceof Set) {
return Array.from(acc)[arrIndex];
} else {
return acc[arrIndex];
}
} else {
// object getter
if (acc instanceof Map) {
return acc.get(el);
} else {
return acc[el];
}
}
}, source);
}
const ALL_DIGITS_REGEX = /^\d+$/;
function isNum(str: string) {
return str.match(ALL_DIGITS_REGEX);
}
const PATH_SPLIT_REGEX = /\.|\]|\[/;
function splitPath(str: string) {
return (
str
.split(PATH_SPLIT_REGEX)
// remove empty strings
.filter((x) => !!x)
);
}
这个实现可以更好,但对我们来说重要的是,现在测试通过了。自己用npm t试试吧。
现在,如果运行npm run build,可以看到dist目录下会有两个文件,index.js和index.d.ts。
接下来就来发布吧。
发布
如果你还没有在npm上注册,就先注册。
注册成功后,通过你的终端用npm login登录。
我们离发布我们的新包只有一步之遥。不过,还有几件事情需要处理。
首先,确保我们的package.json中拥有正确的元数据。
- 确保
main属性设置为打包的文件"main": "dist/index.js"。 - 为TypeScript用户添加
"types": "dist/index.d.ts"。 - 因为我们的库会作为ES Module被使用,因此需要指定
"type": "module"。 name和description也应填写。
接着,我们应该处理好我们希望发布的文件。我不觉得要发布任何配置文件,也不觉得要发布源文件和测试文件。
我们可以做的一件事是使用.npmignore,列出所有我们不想发布的文件。我更希望有一个"白名单",所以让我们使用package.json中的files字段来指定我们想要包含的文件。
{
// ...
"files": ["dist", "LICENSE", "README.md", "package.json"],
// ...
}
终于,我们已经准备好发包了。
运行以下命令:
npm publish --dry-run
并确保只包括所需的文件。当一切准备就绪时,就可以运行:
npm publish
测试一下
让我们创建一个全新的项目并安装我们的模块。
npm install --save digx
现在,让我们写一个简单的程序来测试它。
import dg from "digx"
console.log(dg({ test: [1, 2, 3] }, "test[0]"))
结果非常棒!
然后运行node index.js,你会看到屏幕上打印1。
总结
我们从头开始创建并发布了一个简单的npm包。
我们的库提供了一个ESM模块,TypeScript的类型,使用jest覆盖测试用例。
你可能会认为,这其实一点都不难,的确如此。
以上就是本文的所有内容,如果对你有所帮助,欢迎收藏、点赞、转发~
如何发布一个 TypeScript 编写的 npm 包的更多相关文章
- 在2018年如何优雅的开发一个typescript语言的npm包?
欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由小明plus发表 很多时候,我们可能想要用 typescript 语言来创建一些模块,并提交到 npm 供别人使用, 那么在 2018 ...
- 结合docker发布前端项目(基于npm包管理)的shell脚本
结合docker发布前端项目(基于npm包管理)的shell脚本 本教程依据个人理解并经过实际验证为正确,特此记录下来,权当笔记. 注:基于linux操作系统 目前主流的前后端分离的项目中,常常在部署 ...
- 怎么样写一个能告诉你npm包名字是否被占用的工具
事情是这样的: 因为我经常会写一些npm包,但是有时候我写完一个包,npm publish 的时候却被提示说包名字被占用了,要不就改名字,要不就加scope,很无奈.npm 命令行可以通过 npm v ...
- 怎么写一个带 bin 的 npm 包
只需要2步: 1. 在package.json 定义 一下 : { "name": "my-cli", ..., "bin": { &quo ...
- 自定义npm包——typeScript版本
前言 这篇文章是在我之前的文章 [自定义npm包的创建.发布.更新和撤销] 的基础上做的扩展,主要是针对如何创建以及发布一个typeScript语言的npm包. 大纲 1.创建关于typeScript ...
- 如何发布一个npm包(基于vue)
前言:工作的时候总是使用别人的npm包,然而我有时心底会好奇自己如何发布一个npm包呢,什么时候自己的包能够被很多人喜欢并使用呢...今天我终于迈出了第一步. 前提:会使用 npm,有 vue 基础, ...
- 从0到1发布一个npm包
从0到1发布一个npm包 author: @TiffanysBear 最近在项目业务中有遇到一些问题,一些通用的方法或者封装的模块在PC.WAP甚至是APP中都需要使用,但是对于业务的PC.WAP.A ...
- 如何开发一个npm包并发布到npm中央仓库
转自: https://liaolongdong.com/2019/01/24/publish-public-npm.html 如何开发一个npm包并发布到npm中央仓库需求背景:平时在项目工作中可能 ...
- 如何开发一个npm包并发布
一.安装nodejs 不多说了,网上教程多得是 二.创建自己的npm包 目录结构 npm-test a.js b.js package.json 开发 为了简单便于理解,就开发一个简单地hello程序 ...
- 不会发布npm包?进来看看?
前言 npm(Node Package Manager),一个Node的包管理器,平时我们常用的公共模块(插件)或者叫做包大多都放在上面,所以接下来要封装的插件,我们就简单称它为npm包,本文从就从这 ...
随机推荐
- .NET静态代码织入——肉夹馍(Rougamo) 发布1.2.0
肉夹馍(https://github.com/inversionhourglass/Rougamo)通过静态代码织入方式实现AOP的组件,其主要特点是在编译时完成AOP代码织入,相比动态代理可以减少应 ...
- 四元数Quaternion的基本运算
技术背景 在前面一篇文章中我们介绍了欧拉角死锁问题的一些产生背景,还有基于四元数的求解方案.四元数这个概念虽然重要,但是很少会在通识教育课程中涉及到,更多的是一些图形学或者是工程学当中才会进行讲解.本 ...
- 安装ceph (快速) 步骤三: Ceph 客户端
大多数 Ceph 用户不会直接往 Ceph 存储集群里存储对象,他们通常会使用 Ceph 块设备. Ceph 文件系统.或 Ceph 对象存储这三大功能中的一个或多个. 前提条件 先完成存储集群快速入 ...
- nginx日志参数及含义
参数含义 $remote_addr,$http_x_forwarded_for #记录客户端IP地址 $remote_user #记录客户端用户名称 $request #记录请求的URL和HTTP协议 ...
- Gitlab备份以及恢复
1.迁移准备工作和思路 从a服务器迁移到b服务器,由于Gitlab自身的兼容性问题,高版本的Gitlab无法恢复低版本备份的数据,需要注意在b服务器部署和a服务器一样版本的gitlab,部署好环境后开 ...
- useEffect 和 useLayoutEffect浅析
执行时期的区别 useEffect 回调函数的执行时期 useEffect为异步执行,执行时期为 触发状态更新(如:setState,forceUpdate) React渲染函数执行(render) ...
- SpringBoot+MyBatis Plus对Map中Date格式转换的处理
在 SpringBoot 项目中, 如何统一 JSON 格式化中的日期格式 问题 现在的关系型数据库例如PostgreSQL/MySQL, 都已经对 JSON 类型提供相当丰富的功能, 项目中对于不需 ...
- 如何用AR Engine环境Mesh能力实现虚实遮挡
在AR应用中,用户最不希望看到不真实的穿模现象发生,如虚拟形象部分身体陷入墙壁之中,或者未碰到墙壁却已无法移动,这种不真实的交互十分影响用户体验.那如何才能让避免虚拟物体的穿模问题呢?使用AR Eng ...
- Hbase之命令
Hbase之命令 -- 查询数据量 hbase org.apache.hadoop.hbase.mapreduce.RowCounter '{namespaceName:tablename}' cou ...
- 有人相爱,有人夜里开车看海,有人leetcode第一题都做不出来。
第一题 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标. 你可以假设每种输入只会对应一个答案.但是,数 ...