前言
在这篇文章中,咱们将应用 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
}
}
最重要的设置是这些:
- 库的主文件会位于
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
笼罩测试用例。
你可能会认为,这其实一点都不难,的确如此。
以上就是本文的所有内容,如果对你有所帮忙,欢送珍藏、点赞、转发~