如何发布一个 TypeScript 编写的 npm 包

news2024/12/26 21:42:09

本文正在参加「金石计划 . 瓜分6万现金大奖」

原文链接:www.strictmode.io/articles/bu…

作者:strictmode.io

前言

在这篇文章中,我们将使用TypeScript和Jest从头开始构建和发布一个NPM包。

我们将初始化一个项目,设置TypeScript,用Jest编写测试,并将其发布到NPM。

项目

我们的库称为digx。它允许从嵌套对象中根据路径找出值,类似于lodash中的get函数。

比如说:

const source = { my: { nested: [1, 2, 3] } }
digx(source, "my.nested[1]") //=> 2 

就本文而言,只要它是简洁的和可测试的,它做什么并不那么重要。

npm包可以在这里找到。GitHub仓库地址在这里。

初始化项目

让我们从创建空目录并初始化它开始。

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}
} 

最重要的设置是这些:

1.库的主文件会位于src文件夹下,因此需要这么设置"files": ["src/index.ts"]
2."target": "es2015" 确保我们的库支持现代平台,并且不会携带不必要的垫片。
3."module": "es2015"。我们的模块将是一个标准的ES模块(默认是CommonJS)。ES模式在现代浏览器下没有任何问题;甚至Node从13版本开始就支持ES模式。
4."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 getterif (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.jsindex.d.ts

接下来就来发布吧。

发布

如果你还没有在npm上注册,就先注册。

注册成功后,通过你的终端用npm login登录。

我们离发布我们的新包只有一步之遥。不过,还有几件事情需要处理。

首先,确保我们的package.json中拥有正确的元数据。

1.确保main属性设置为打包的文件"main": "dist/index.js"
2.为TypeScript用户添加"types": "dist/index.d.ts"
3.因为我们的库会作为ES Module被使用,因此需要指定"type": "module"
4.namedescription也应填写。

接着,我们应该处理好我们希望发布的文件。我不觉得要发布任何配置文件,也不觉得要发布源文件和测试文件。

我们可以做的一件事是使用.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覆盖测试用例。

你可能会认为,这其实一点都不难,的确如此。

以上就是本文的所有内容,如果对你有所帮助,欢迎收藏、点赞、转发~

最后

整理了75个JS高频面试题,并给出了答案和解析,基本上可以保证你能应付面试官关于JS的提问。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/344040.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

关于在Interceptor拦截器中使用autowired注入,但是却注入为null。引出在自动装配时,只有在ioc容器中的bean可以互相自动装配。

问题描述 在拦截器拦截登录请求,想通过从redis中取出token,判断token是否为null,进而判断是否登录。 Component public class LoginInterceptor implements HandlerInterceptor {AutowiredStringRedisTemplate redisTemplate;Overridepubli…

基于matlab的SAR图像中自动目标识别

一、前言此示例演示如何使用深度学习工具箱和并行计算工具箱™™训练基于区域的卷积神经网络 (R-CNN) 以识别大场景合成孔径雷达 (SAR) 图像中的目标。深度学习工具箱提供了一个框架,用于设计和实现具有算法、预训练模…

MyBatis_自定义映射resultMap

自定义映射resultMap 文章目录自定义映射resultMap创建数据表实体类字段名和属性名不一致(三种方式)取别名设置全局配置设置resultMap处理多对一的映射关系(三种方式)级联方式处理association分步查询处理一对多的映射关系(两种方式)collection分步查询创建数据表 复制进MySQL…

强化学习笔记-03有限马尔可夫决策过程MDP

本文是博主对《Reinforcement Learning- An introduction》的阅读笔记,不涉及内容的翻译,主要为个人的理解和思考。 前文我们了解了强化学习主要是为了实现最大化某个特定目标(收益),而学习如何通过环境进行交互。 而学…

PCI Express体系结构导读_3PCI总线的数据交换--读书笔记

前言本文为读书笔记,如有误可以指正,一块学习交流本章节主要介绍两种类型的数据传输:a- host读写pci设备的bar寄存器。b- pci设备通过DMA方式读写内存。对于PCI设备读写其他PCI设备的bar寄存器只了解3.1- pci设备bar空间的初始化3.1.1 内存域…

33复杂美:一文看懂加密算法为何物

加密算法 ,区块链底层技术的心脏究竟为何物?加密,简而言之,加密就是借助一种或多种算法将明文信息转换成密文信息,信息的接收方通过密钥对密文信息进行解密获得明文信息的过程。根据加解密的密钥是否相同,加…

C++:类和对象(下)

文章目录1 再谈构造函数1.1 构造函数体赋值1.2 初始化列表1.3 explicit关键字2 static成员2.1 概念2.2 特性3 友元3.1 友元函数&#xff08;流插入&#xff08;<<&#xff09;及流提取&#xff08;>>&#xff09;运算符重载&#xff09;3.2 友元类4 内部类5 匿名对…

使用脚本以可读的 JSON 格式显示 curl 命令输出

在我们经常调试微服务或者使用 Elasticsearch API 时&#xff0c;经常会使用curl 来进行调试。但是有时我们的输出不尽如意。显示的不是一 pretty 格式进行输出的。我们有时还必须借助于其他的一些网站工具&#xff0c;比如 Best JSON Formatter and JSON Validator: Online JS…

叮!一大波来自客户的感谢信

春风渐暖&#xff0c;美好如期&#xff0c;祝福的话语在日子的酝酿里更值得期待。神策数据走过 7 载春秋&#xff0c;描绘的大数据分析和营销科技图景在时间的打磨下清晰可见。时光沉淀经验&#xff0c;匠心兑换卓越&#xff0c;这条终点叫做「帮助中国三千万企业重构数据根基&…

类与对象(上)

类与对象(上) 1.面向过程和面向对象初步认识 C语言是面向过程的&#xff0c;关注的是过程&#xff0c;分析出求解问题的步骤&#xff0c;通过函数调用逐步解决问题。 C是基于面向对象的&#xff0c;关注的是对象&#xff0c;将一件事情拆分成不同的对象&#xff0c;靠对象之间…

vite从零创建react-ts项目

1.创建vite-react-ts文件夹&#xff0c;初始化项目 npm init 初始化后可以看到包管理文件package.json 2.项目结构 根目录下新建index.html模板文件。以往都是放在public文件夹的&#xff0c;但是官方推荐放在根目录。这是有意而为之的&#xff1a;在开发期间 Vite 是一个服…

qt之条形码与二维码的生成

一、简介 条形码&#xff1a; 条形码(barcode)是将宽度不等的多个黑条和空白&#xff0c;按照一定的编码规则排列&#xff0c;用以表达一组信息的图形标识符。常见的条形码是由反射率相差很大的黑条&#xff08;简称条&#xff09;和白条&#xff08;简称空&#xff09;排…

探讨接口测试以及其用例设计方法

接口测试的总结文档 第一部分&#xff1a;主要从问题出发&#xff0c;引入接口测试的相关内容并与前端测试进行简单对比&#xff0c;总结两者之前的区别与联系。但该部分只交代了怎么做和如何做&#xff1f;并没有解释为什么要做&#xff1f; 第二部分&#xff1a;主要介绍为什…

数据结构:带环单链表基础OJ练习笔记(leetcode142. 环形链表 II)(leetcode三题大串烧)

目录 一.前言 二.leetcode160. 相交链表 1.问题描述 2.问题分析与求解 三.leetcode141. 环形链表 1.问题描述 2.代码思路 3.证明分析 下一题会用到的重要小结论&#xff1a; 四.leetcode142. 环形链表 II 1.问题描述 2.问题分析与求解 Judgecycle接口&#xf…

婴儿监视器美国亚马逊CPC认证ASTM F2951标准要求?

婴儿监视器&#xff0c;又称婴儿监听器、婴儿监护器&#xff0c;英文名为( baby monitor其主要用于用于居家和婴儿的监听和护理。欧美市场上广泛使用&#xff0c;已经存在30年历史的 Baby Monitor是采用现代无线电技术应用于居家和婴儿的监听和护理的好帮手。婴儿监护器由看器(…

基于Jeecgboot前后端分离的ERP系统开发系列--出库单(1)

这次从出库单开始进行整个单据录入显示的模板&#xff0c;不再采用默认的online表单代码生成的方式&#xff0c;以满足实际的业务需要&#xff0c;当然刚开始做&#xff0c;以后还需要进行改进。 一、首先单号生成 采用系统开发里的代码编码规则&#xff0c;相应的修改增加代码…

7、MyBatis框架——MyBatis对一对一关系的处理、分步查询、MyBatis对一对多关系的处理

目录 一、项目框架搭建 二、在实体类中添加额外属性实现多表查询 1、mybatis两表关联查询 &#xff08;1&#xff09;实体类类型映射规则 &#xff08;2&#xff09;代码演示 2、分步查询 &#xff08;1&#xff09;autoMapping开启自动映射 &#xff08;2&#xff09;…

Python-正则表达式详解-快速掌握正则表达式核心函数

正则表达式为为高级的文本模式匹配、抽取或文本形式的搜索和替换功能提供了基础。本文主要介绍python正则表达式的一些基础功能&#xff0c;掌握它也可以使得在python编程中处理字符串游刃有余。1.简介正则表达式是一些由字符和特殊符号组成的字符串&#xff0c;匹配一系列有相…

Ansible的安装及部署

目录 一、Ansible对于企业运维的重大意义 二、Ansible的安装 三、构建Ansible清单 1.直接书写受管主机名或ip&#xff0c;每行一个 2.设定受管主机的组[组名称] 四、Ansible配置文件参数详解 1、配置文件的分类与优先级 2.配置新用户的Ansible配置 3.生成免密认证 本章…

算法 ——世界 二

个人简介&#xff1a;云计算网络运维专业人员&#xff0c;了解运维知识&#xff0c;掌握TCP/IP协议&#xff0c;每天分享网络运维知识与技能。个人爱好: 编程&#xff0c;打篮球&#xff0c;计算机知识个人名言&#xff1a;海不辞水&#xff0c;故能成其大&#xff1b;山不辞石…