关于npm:如何发布一个-TypeScript-编写的-npm-包

前言

在这篇文章中,咱们将应用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 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.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笼罩测试用例。

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

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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理