使用Typescript和ES模块发布Node模块

TypeScript已经成为一种很是流行的JavaScript语言,这是有缘由的。它的类型系统和编译器可以在您的软件运行以前的编译时捕获各类bug,而且附加的代码编辑器功能使它成为一个很是适合开发人员的高效环境。javascript

可是,当你想用TypeScript编写一个库或包,同时又想用JavaScript来发布,这样你的最终用户就没必要手动编译你的代码,会发生什么?咱们如何使用现代的JavaScript功能(如ES模块)来编写,同时又能得到TypeScript的全部好处?前端

本文旨在解决全部这些问题,并为你提供一个设置,使你能够放心地编写和共享TypeScript库,并为包装的使用者提供轻松的体验。java

本文完整代码:github.com/dunizb/Code…node


入门

咱们要作的第一件事是创建一个新项目。在本教程中,咱们将建立一个基本的数学程序包——不是一个服务于任何实际目的的程序包——由于它将让咱们演示全部咱们须要的TypeScript,而不会偏离程序包的实际功能。webpack

首先,建立一个空目录并运行 npm init -y 建立一个新项目。这将建立你的 package.json 并为你提供一个空项目以供处理:git

$ mkdir maths-package
$ cd maths-package
$ npm init -y
复制代码

如今,咱们能够添加第一个也是最重要的依赖项:TypeScript!github

$ npm install --save-dev typescript
复制代码

安装TypeScript后,能够经过运行 tsc --init 初始化TypeScript项目。tsc 是“ TypeScript编译器”的缩写,是TypeScript的命令行工具。web

为确保你运行咱们刚刚在本地安装的TypeScript编译器,应在命令前加上 npx。npx是个很棒的工具,它将在node_modules 文件夹中查找你提供的命令,所以,经过在命令前面加上前缀,能够确保咱们使用的是本地版本,而不是你可能已安装的TypeScript的任何其余全局版本。typescript

$ npx tsc --init
复制代码

这将建立一个 tsconfig.json 文件,该文件负责配置咱们的TypeScript项目。您会看到该文件具备数百个选项,其中大多数选项已被注释掉(TypeScript支持 tsconfig.json 文件中的注释)。我已将文件缩减为仅启用的设置,以下所示:shell

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true
  }
}
复制代码

咱们须要对此配置进行一些更改,以使咱们可以使用ES模块发布程序包,所以,让咱们如今来看一下这些选项。

配置tsconfig.json 选项

若是您正在寻找全部可能的 tsconfig 选项的完整列表,能够在TypeScript网站上找到此方便的参考

让咱们从 target 开始,这定义了你将在浏览器中提供代码的JavaScript支持级别。若是您必须使用一组较旧的浏览器,这些浏览器可能不具备全部最新和最强大的功能,则能够将其设置为 ES2015。若是您确实须要最大的浏览器覆盖范围,TypeScript甚至将支持 ES3

咱们将在此处针对该模块使用 ES2015,但能够随时进行相应更改。例如,若是我为本身创建一个快速的辅助项目,而且只关心尖端的浏览器,那么我很高兴将其设置为 ES2020

选择模块系统

接下来,咱们必须决定将用于该项目的模块系统。请注意,这不是咱们要编写的模块系统,而是TypeScript的编译器在输出代码时将使用的模块系统。

发布模块时我喜欢作的事情是发布两个版本:

  • 带有ES模块的现代版本,以便捆绑工具能够巧妙地将未使用的代码treeshake ,所以支持ES模块的浏览器只需导入文件
  • 使用CommonJS模块的版本(若是在Node中工做,你将习惯使用 require 代码),所以较早的构建工具和Node.js环境能够轻松运行该代码

稍后咱们将介绍如何使用不一样的选项捆绑两次,可是如今,让咱们将TypeScript配置为输出ES模块。咱们能够经过将 module 设置设置为 ES2020 来实现。

如今,你的 tsconfig.json 文件应以下所示:

{
  "compilerOptions": {
    "target": "ES2015",
    "module": "ES2020",
    "strict": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true
  }
}
复制代码

编写一些代码

在讨论捆绑代码以前,咱们须要写一些代码!让咱们建立两个小模块,它们既导出函数,又为导出全部代码的模块提供一个主 entry 文件。

我喜欢将全部TypeScript代码放在 src 目录中,由于这意味着咱们能够直接将TypeScript编译器指向它,所以,我将使用如下代码建立 src/add.ts

export const add = (x: number, y:number):number => {
  return x + y;
}
复制代码

我也将建立 src/subtract.ts

export const subtract = (x: number, y:number):number => {
  return x - y;
}
复制代码

最后,src/index.ts 将导入咱们全部的API方法并再次导出它们:

import { add } from './add.js'
import { subtract } from './subtract.js'
export {
  add,
  subtract
}
复制代码

这意味着,用户能够经过导入只须要的东西来获取咱们的功能,也能够经过获取全部的东西来获取。

import { add } from 'maths-package';

import * as MathsPackage from 'maths-package';
复制代码

请注意,在 src/index.ts 中,个人导入包含文件扩展名。若是只想支持Node.js和构建工具(例如webpack),则不须要这样作,可是若是要支持支持ES模块的浏览器,则须要文件扩展名。

使用TypeScript进行编译

让咱们看看是否可让TypeScript编译咱们的代码。咱们须要先对 tsconfig.json 文件进行一些调整,而后才能执行如下操做:

{
  "compilerOptions": {
    "target": "ES2015",
    "module": "ES2020",
    "strict": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "./lib",
  },
  "include": [
    "./src"
  ]
}
复制代码

咱们进行了两项更改:

  • compilerOptions.outDir ——这告诉TypeScript将咱们的代码编译到一个目录中。在这种状况下,我已经告诉它命名该目录 lib,可是您能够根据须要命名它。
  • include ——告诉TypeScript咱们但愿在编译过程当中包含哪些文件。在咱们的例子中,咱们全部的代码都位于src 目录中,所以我将其传入。这就是为何我喜欢将全部TS源文件保存在一个文件夹中的缘由,这使配置变得很是容易

让咱们来试一试,看看会发生什么吧! 我发如今调整个人TypeScript配置时,最适合个人方法是调整、编译、检查输出,而后再调整。不要惧怕尝试这些设置,看看它们如何影响最终结果。

要编译TypeScript,咱们将运行 tsc 并使用 -p 标志(“project”的缩写)告诉它 tsconfig.json 的位置:

npx tsc -p tsconfig.json
复制代码

若是你有任何类型错误或配置问题,将在此处显示。若是没有,您应该什么也看不到——可是请注意,你有一个新的 lib 目录,其中有文件!TypeScript编译时不会将任何文件合并在一块儿,而是将每一个模块转换成对应的JavaScript。

让咱们看一下输出的三个文件:

// lib/add.js
export const add = (x, y) => {
    return x + y;
};

// lib/subtract.js
export const subtract = (x, y) => {
    return x - y;
};

// lib/index.js
import { add } from './add.js';
import { subtract } from './subtract.js';
export { add, subtract };
复制代码

它们看起来和咱们的输入很是类似,但没有咱们添加的类型注释。这是能够预期的:咱们在ES模块中编写了咱们的代码,并告诉TypeScript也要以这种形式输出。若是咱们使用了比ES2015更新的任何JavaScript功能,TypeScript会将它们转换为ES2015友好的语法,可是在咱们的案例中,咱们没有使用它,所以TypeScript在很大程度上仅保留了全部内容。

该模块如今能够发布到npm上供其余用户使用,可是咱们有两个问题须要解决:

  • 咱们不会在代码中发布任何类型信息。这不会对咱们的用户形成破坏,但这是一个错过的机会:若是咱们也发布咱们的类型信息,那么使用支持TypeScript的编辑器的人或用TypeScript编写应用程序的人将得到更好的体验。
  • Node还不支持开箱即用的ES模块。发布CommonJS版本也很好,因此Node不须要额外的工做。ES模块支持将出如今Node 13和更高的版本中,可是要遇上生态系统还须要一段时间。

发布类型定义

咱们能够经过要求TypeScript在写代码的同时发出一个声明文件来解决类型信息问题。这个文件的结尾是 .d.ts,它将包含关于咱们代码的类型信息。将它看做源代码,除了不包含类型和实现以外,它只包含类型。

让咱们在 tsconfig.json 中添加 "declaration": true(在 "compilerOptions" 部分中),而后再次运行 npx tsc -p tsconfig.json

提示:我想在个人 package.json 文件中添加一个脚原本进行编译,所以无需输入如下内容:

"scripts": {
    "tsc": "tsc -p tsconfig.json"
  }
复制代码

而后我能够运行 npm run tsc 来编译个人代码。

如今,您将看到每一个JavaScript文件(例如 add.js )旁边都有一个等效的 add.d.ts 文件,以下所示:

// lib/add.d.ts
export declare const add: (x: number, y: number) => number;
复制代码

所以,如今当用户使用咱们的模块时,TypeScript编译器将可以选择全部这些类型。

发布到CommonJS

难题的最后一部分是还将TypeScript配置为输出使用CommonJS的代码版本。为此,咱们能够制做两个 tsconfig.json 文件,一个针对ES模块,另外一个针对CommonJS。不过,咱们可让CommonJS配置扩展咱们的默认设置并覆盖 modules 设置,而不是复制全部配置。

让咱们建立 tsconfig-cjs.json

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "module": "CommonJS",
    "outDir": "./lib/cjs"
  },
}
复制代码

重要的是第一行,这意味着此配置默认状况下会继承 tsconfig.json 的全部设置。这很重要,由于你不须要在多个JSON文件之间同步设置。

而后覆盖须要更改的设置。我相应地更新模块,而后将 outDir 设置更新到 lib/cjs ,这样咱们就能够输出到lib 中的子文件夹。

此时,我还更新了 package.json 中的 tsc 脚本:

"scripts": {
  "tsc": "tsc -p tsconfig.json && tsc -p tsconfig-cjs.json"
}
复制代码

如今,当咱们运行 npm run tsc 时,咱们将编译两次,而且咱们的lib目录将以下所示:

这个有点乱,让咱们经过更新 tsconfig 中的 outDir 选项来将ESM输出更新到 lib/esm

接下来,咱们将设置 module 属性。这是应该连接到咱们软件包的ES模块版本的属性。支持此功能的工具将可以使用此版本的软件包。所以,应将其设置为 ./lib/esm/index.js

接下来,咱们将 files entry 添加到 package.json 中。在这里,咱们定义了发布模块时应包括的全部文件。我喜欢使用这种方法来明肯定义要在最终模块中推送到npm的文件。

这样咱们就能够减少模块的大小。例如,咱们不会发布 src 文件,而是发布 lib 目录。若是你在 files entry 中提供目录,则默认状况下会包含其全部文件和子目录,所以你没必要所有列出。

提示:若是要查看模块中将包含哪些文件,请运行 npx pkgfiles 以得到列表。

如今,咱们的 package.json 中包含如下三个附加字段:

"main": "./lib/cjs/index.js",
  "module": "./lib/esm/index.js",
  "files": [
    "lib/"
  ],
复制代码

还有最后一步。由于咱们要发布 lib 目录,因此须要确保在运行 npm publishlib 目录是最新的。npm文档中有一节是关于如何作到这一点的——咱们可使用 prepublishOnly 脚本。当咱们运行 npm publish 时,该脚本将自动为咱们运行:

"scripts": {
  "tsc": "tsc -p tsconfig.json && tsc -p tsconfig-cjs.json",
  "prepublish": "npm run tsc"
},
复制代码

注意,还有一个名为 prepublish 的脚本,这使选择哪一个稍微有些混乱。npm文档提到了这一点:不推荐使用prepublish ,若是只想在发布时运行代码,则应使用prepublishOnly

这样,运行 npm publish 将运行咱们的TypeScript编译器并在线发布模块!我将该软件包发布在 @ jackfranklin/maths-package-for-blog-post 下,虽然我不建议你使用它,可是你能够浏览文件并查看。

结束

就是这样!我但愿这篇教程已经告诉你,使用TypeScript上手和运行TypeScript并不像最初看起来那么困难,只要稍加调整,就可让TypeScript输出你可能须要的多种格式,而不须要太多麻烦。


本文首发于微信公众号《前端外文精选》,关注即送大礼包,准能为你节省很多钱!

相关文章
相关标签/搜索