詳解如何發(fā)布TypeScript編寫的npm包
前言
在這篇文章中,我們將使用TypeScript和Jest從頭開始構(gòu)建和發(fā)布一個(gè)NPM包。
我們將初始化一個(gè)項(xiàng)目,設(shè)置TypeScript,用Jest編寫測(cè)試,并將其發(fā)布到NPM。
項(xiàng)目
我們的庫稱為digx。它允許從嵌套對(duì)象中根據(jù)路徑找出值,類似于lodash中的get函數(shù)。
比如說:
const source = { my: { nested: [1, 2, 3] } }
digx(source, "my.nested[1]") //=> 2
就本文而言,只要它是簡(jiǎn)潔的和可測(cè)試的,它做什么并不那么重要。
初始化項(xiàng)目
讓我們從創(chuàng)建空目錄并初始化它開始。
mkdir digx cd digx npm init --yes
npm init --yes命令將為你創(chuàng)建package.json文件,并填充一些默認(rèn)值。
讓我們也在同一文件夾中設(shè)置一個(gè)git倉庫。
git init echo "node_modules" >> .gitignore echo "dist" >> .gitignore git add . git commit -m "initial"
構(gòu)建庫
這里會(huì)用到TypeScript,我們來安裝它。
npm i -D typescript
使用下面的配置創(chuàng)建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
}
}
最重要的設(shè)置是這些:
庫的主文件會(huì)位于src文件夾下,因此需要這么設(shè)置"files": ["src/index.ts"]。
"target": "es2015" 確保我們的庫支持現(xiàn)代平臺(tái),并且不會(huì)攜帶不必要的墊片。
"module": "es2015"。我們的模塊將是一個(gè)標(biāo)準(zhǔn)的ES模塊(默認(rèn)是CommonJS)。ES模式在現(xiàn)代瀏覽器下沒有任何問題;甚至Node從13版本開始就支持ES模式。
"declaration": true - 因?yàn)槲覀兿胍詣?dòng)生成d.ts聲明文件。我們的TypeScript用戶將需要這些聲明文件。
其他大部分選項(xiàng)只是各種可選的TypeScript檢查,我更喜歡開啟這些檢查。
打開package.json,更新scripts的內(nèi)容:
"scripts": {
"build": "tsc"
}
現(xiàn)在我們可以用npm run build來運(yùn)行構(gòu)建...這樣會(huì)失敗的,因?yàn)槲覀冞€沒有任何可以構(gòu)建的代碼。
我們從另一端開始。
添加測(cè)試
作為一名負(fù)責(zé)任的開發(fā),我們將從測(cè)試開始。我們將使用jest,因?yàn)樗?jiǎn)單且好用。
npm i -D jest @types/jest ts-jest
ts-jest包是Jest理解TypeScript所需要的。另一個(gè)選擇是使用babel,這將需要更多的配置和額外的模塊。我們就保持簡(jiǎn)潔,采用ts-jest。
使用如下命令初始化jest配置文件:
./node_modules/.bin/jest --init
一路狂按回車鍵就行,默認(rèn)值就很好。
這會(huì)使用一些默認(rèn)選項(xiàng)創(chuàng)建jest.config.js文件,并添加"test": "jest"腳本到package.json中。
打開jest.config.js,找到以preset開始的行,并更新為:
{
// ...
preset: "ts-jest",
// ...
}
最后,創(chuàng)建src目錄,以及測(cè)試文件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");
});
這些單元測(cè)試讓我們對(duì)正在構(gòu)建的東西有一個(gè)直觀的了解。
我們的模塊導(dǎo)出一個(gè)單一函數(shù),digx。它接收任意對(duì)象,字符串參數(shù)path,以及可選參數(shù)shouldThrow,該參數(shù)使得提供的路徑在源對(duì)象的嵌套結(jié)構(gòu)中不被允許時(shí),拋出一個(gè)異常。
嵌套結(jié)構(gòu)可以是對(duì)象和數(shù)組,也可以是Map和Set。
使用npm t運(yùn)行測(cè)試,當(dāng)然,不出意外會(huì)失敗。
現(xiàn)在打開src/index.ts文件,并寫入下面內(nèi)容:
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)
);
}
這個(gè)實(shí)現(xiàn)可以更好,但對(duì)我們來說重要的是,現(xiàn)在測(cè)試通過了。自己用npm t試試吧。
現(xiàn)在,如果運(yùn)行npm run build,可以看到dist目錄下會(huì)有兩個(gè)文件,index.js和index.d.ts。
接下來就來發(fā)布吧。
發(fā)布
如果你還沒有在npm上注冊(cè),就先注冊(cè)。
注冊(cè)成功后,通過你的終端用npm login登錄。
我們離發(fā)布我們的新包只有一步之遙。不過,還有幾件事情需要處理。
首先,確保我們的package.json中擁有正確的元數(shù)據(jù)。
- 確保
main屬性設(shè)置為打包的文件"main": "dist/index.js"。 - 為TypeScript用戶添加
"types": "dist/index.d.ts"。 - 因?yàn)槲覀兊膸鞎?huì)作為ES Module被使用,因此需要指定
"type": "module"。 name和description也應(yīng)填寫。
接著,我們應(yīng)該處理好我們希望發(fā)布的文件。我不覺得要發(fā)布任何配置文件,也不覺得要發(fā)布源文件和測(cè)試文件。
我們可以做的一件事是使用.npmignore,列出所有我們不想發(fā)布的文件。我更希望有一個(gè)"白名單",所以讓我們使用package.json中的files字段來指定我們想要包含的文件。
{
// ...
"files": ["dist", "LICENSE", "README.md", "package.json"],
// ...
}
終于,我們已經(jīng)準(zhǔn)備好發(fā)包了。
運(yùn)行以下命令:
npm publish --dry-run
并確保只包括所需的文件。當(dāng)一切準(zhǔn)備就緒時(shí),就可以運(yùn)行:
npm publish
測(cè)試一下
讓我們創(chuàng)建一個(gè)全新的項(xiàng)目并安裝我們的模塊。
npm install --save digx
現(xiàn)在,讓我們寫一個(gè)簡(jiǎn)單的程序來測(cè)試它。
import dg from "digx"
console.log(dg({ test: [1, 2, 3] }, "test[0]"))
結(jié)果非常棒!

然后運(yùn)行node index.js,你會(huì)看到屏幕上打印1。
總結(jié)
我們從頭開始創(chuàng)建并發(fā)布了一個(gè)簡(jiǎn)單的npm包。
我們的庫提供了一個(gè)ESM模塊,TypeScript的類型,使用jest覆蓋測(cè)試用例。
你可能會(huì)認(rèn)為,這其實(shí)一點(diǎn)都不難,的確如此。
原文鏈接:www.strictmode.io/articles/bu…
以上就是詳解如何發(fā)布TypeScript編寫的npm包的詳細(xì)內(nèi)容,更多關(guān)于TypeScript npm包發(fā)布的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
微信小程序 (三)tabBar底部導(dǎo)航詳細(xì)介紹
這篇文章主要介紹了微信小程序 (三)tabBar底部導(dǎo)航詳細(xì)介紹的相關(guān)資料,需要的朋友可以參考下2016-09-09
vue axios請(qǐng)求超時(shí)的正確處理方法
這篇文章主要介紹了vue axios請(qǐng)求超時(shí),設(shè)置重新請(qǐng)求的完美解決方法,一并給大家介紹了axios基本用法,需要的朋友可以參考下2018-04-04
詳解微信小程序Page中data數(shù)據(jù)操作和函數(shù)調(diào)用
這篇文章主要介紹了詳解微信小程序Page中data數(shù)據(jù)操作和函數(shù)調(diào)用的相關(guān)資料,希望通過本文能幫助到大家掌握這方法,需要的朋友可以參考下2017-09-09
詳解requestAnimationFrame和setInterval該如何選擇
這篇文章主要為大家介紹了requestAnimationFrame和setInterval該如何選擇示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪<BR>2023-03-03
詳解JavaScript中數(shù)組的相關(guān)知識(shí)
這篇文章主要介紹了JavaScript中中數(shù)組的相關(guān)知識(shí),是JS入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-07-07
vue框架通用化解決個(gè)性化文字換行問題實(shí)例詳解
這篇文章主要為大家介紹了通用化解決個(gè)性化文字換行問題實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09

