专栏首页前端全栈开发者使用Typescript和ES模块发布Node模块

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

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

但是,当你想用TypeScript编写一个库或包,同时又想用JavaScript来发布,这样你的最终用户就不必手动编译你的代码,会发生什么?我们如何使用现代的JavaScript功能(如ES模块)来编写,同时又能获得TypeScript的所有好处?

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

本文完整代码:https://github.com/dunizb/CodeTest/tree/master/Typescript/typescript-es-modules


入门

我们要做的第一件事是建立一个新项目。在本教程中,我们将创建一个基本的数学程序包——不是一个服务于任何实际目的的程序包——因为它将让我们演示所有我们需要的TypeScript,而不会偏离程序包的实际功能。

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

$ mkdir maths-package
$ cd maths-package
$ npm init -y

现在,我们可以添加第一个也是最重要的依赖项:TypeScript!

$ npm install --save-dev typescript

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

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

$ npx tsc --init

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

{
  "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输出你可能需要的多种格式,而不需要太多麻烦。


原文:https://blog.logrocket.com,作者:Jack Franklin ,翻译:公众号《前端外文精选》

本文首发于公众号《前端外文精选》,关注后私信回复:大礼包,送某网精品视频课程网盘资料,准能为你节省不少钱!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

相关文章

  • 【译】编写高质量JavaScript模块的4个最佳实践

    使用ES2015模块,您可以将应用程序代码分成可重用的、封装的、专注于单一任务的模块。

    Dunizb
  • 测量JavaScript函数的性能的简单方法及与其他方式对比

    测量执行一个函数所需的时间总是一个很好的办法,证明某些实现比另一个实现的性能更好。这也是一个很好的方法,可以确保性能没有在某些改变后受到影响,也可以追踪瓶颈。

    Dunizb
  • 使用POI组件导出数据为Excel文件

    Dunizb
  • 网站域名到底加不加 WWW

    总的来说对于大访问量或多子域名的网站来说,不建议使用裸域。小流量或子域名少的网站的话就看个人爱好了。我挺喜欢裸域的。最近几年流行起来的「单页网页应用」 ( Si...

    ZONGLYN
  • 2018年AI商业价值可达1.2万亿美元,同比大涨7成

    市场研究与咨询顾问公司Gartner预测,2018年全球和人工智能(AI)相关的商业价值总计将达1.2万亿美元,较2017年增加70%。到了2022年,人工智能...

    机器人网
  • Java工具集-字符串(StringUtils)

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    cwl_java
  • D妹上新:域名共享你用上了吗

    DNSPod域名共享,一个域名可以由多个DNSPod账户来进行共同管理。 那么DNSPod域名共享功能在什么情况下可以使用呢? 第一种情况,如果阿D的账号,有...

    腾讯云DNSPod团队
  • Java工具集-文字(WordUtils)

    cwl_java
  • CPS推广:为什么我的佣金还没有到账呢

    CPS推广奖励的佣金,目前无法直接后台提现,需要在次月月结之后,由财务系统统一打款到银行,即推广者后台所填写的银行账号,一般上月佣金,次月月末到账,具体时间以银...

    腾讯云-推广奖励
  • 20. 企业级开发基础1:自定义模块

    之前的课程中,我们已经了解了python程序设计开发的基础部分内容 包含了数据类型、变量、运算符、程序选择结构、循环结构、函数处理、字符串处理等等内容。 本...

    大牧莫邪

扫码关注云+社区

领取腾讯云代金券