Node.js上的TypeScript和本地ESM

391 阅读3分钟

在这篇博文中,我将解释你需要知道的一切,以便在Node.js上使用和制作本地ECMAScript模块。


基础知识

让我们假设我们正在通过TypeScript实现一个npm包my-package 。该包有以下文件结构:

my-package/
  ts/
    src/
      main.ts
      util/
        errors.ts
  dist/

tsconfig.json

  • A线("module"):我们正在告诉TypeScript生成ECMAScript模块。
    • "ES6","ES2015" :支持基本的ESM功能
    • "2020": 另外,支持动态导入和import.meta
  • B行("moduleResolution"):这个值是Node.js需要的。
  • C行("allowSyntheticDefaultImports"):我需要这个设置,以便导入一个传统的CommonJS模块。在这种情况下,module.exports 是默认的出口。

package.json

package.json 中需要以下条目:

"type": "module"

指定一个"type" ,无论如何都是推荐的,但在这里是必须的,因为TypeScript还不支持.mjs ,只支持.js

如果这个包被npm安装在另一个包中,这就是该包导入函数的方式start():

import {start} from 'my-package/dist/src/main.js';

这里的关键点是,在默认情况下,我们需要提供文件名扩展,特别是对于同一包内的模块。

Visual Studio代码

默认情况下,VS Code在为我们添加导入时不会添加文件名扩展。这可以通过以下两个设置来改变:

"javascript.preferences.importModuleSpecifierEnding": "js"
"typescript.preferences.importModuleSpecifierEnding": "js"

对于一个包内的导入,你可以按以下方式添加文件名扩展名。

  • 搜索。^(import [^';]* from '(\./|(\.\./)+)[^';.]*)';
  • 替换。$1.js';

包出口:更好的模块指定器

包出口包的入口点让我们在导入时定义更好的模块指定符。回想一下,这就是包的文件结构:

my-package/
  ts/
    src/
      main.ts
      util/
        errors.ts
  dist/

包本身的一个入口点

package.json:

{
  "main": "./dist/src/main.js",
  "exports": {
    ".": "./dist/src/main.js"
  },
  "typesVersions": {
    "*": {
      "main.d.ts": ["dist/src/main.d.ts"]
    }
  }
}

我们只提供"main" ,以便向后兼容。

"typesVersions"执行与"exports" 相同的映射,但针对TypeScript的类型定义。

这就是TypeScript中的导入语句:

import {start} from 'my-package';

子树的更好的模块指定器

package.json:

{
  "exports": {
    "./*": "./dist/src/*"
  },
  "typesVersions": {
    "*": {
      "*": ["dist/src/*"]
    }
  }
}

在这里,我们缩短了整个子树的模块说明,在my-package/dist/src:

import {InternalError} from 'my-package/util/errors.js';

如果没有导出,导入语句将是:

import {InternalError} from 'my-package/dist/src/util/errors.js';

将一个目录的直接子目录导出为裸模块指定符

package.json:

{
  "exports": {
    "./util/*": "./dist/src/util/*.js"
  },
  "typesVersions": {
    "*": {
      "util/*": ["dist/src/util/*"]
    }
  }
}

只有./dist/src/util/ 的直接子目录被导出,所有的模块都可以作为裸模块指定符(无扩展)。

import {InternalError} from 'my-package/util/errors';

将一个目录映射到一个文件

package.json:

{
  "exports": {
    "./util": "./dist/src/util/errors.js"
  },
  "typesVersions": {
    "*": {
      "util": ["dist/src/util/errors.d.ts"]
    }
  }
}

在这里,我们想导出./dist/src/util/errors.js ,就像它所在的目录一样。

import {InternalError} from 'my-package/util';

高级软件包导出功能

Node.js支持更多的导出功能,例如 条件性导出:我们可以根据我们的包被导入的方式来改变我们的映射--例如,通过import 或通过require() (例子取自Node.js文档)。

{
  "main": "./main-require.cjs",
  "exports": {
    "import": "./main-module.js",
    "require": "./main-require.cjs"
  },
  "type": "module"
}

我们还可以区分浏览器和Node.js,以及更多。

浏览器呢?

到目前为止,我只在Node.js上测试了这个设置,但我认为捆绑器也会支持这些功能。如果还没有,那么最终会有。