为 TypeScript、Webpack 和 Jasmine 导入 ES 模块

Importing ES modules for TypeScript, Webpack, and Jasmine

背景

我有一组包,我使用类似的结构在我的个人项目中重复使用。

出于生产目的,它们是用 TypeScript 编写的,并在发布到 npm 之前通过 tsc 编译成 JavaScript,因此我可以安装它们在我的其他项目中。我还使用 Jasmine.

对它们进行了 运行 测试

在开发中,我使用 Webpack 及其 ts-loader 以允许我在示例脚本中使用它的方式编译 TypeScript 运行ning一个浏览器。我也通常在我使用这些包的项目中使用 Webpack,尽管因为它们是在发布之前编译的,所以这些项目只会将它们视为 JavaScript 而不需要使用 ts-loader.

我到处都使用 ES 模块语法,这样我就可以在每个上下文中轻松地使用相同的语法,而不用担心在用于 Node 的 CommonJS 和用于浏览器的 ES 模块语法之间切换。为了在 Jasmine 中允许这样做,在我的 jasmine.json 配置文件中我设置了 "jsLoader": "import".

我对如何编写 import 语句有疑问。特别是,如何以适用于我开发这些包时出现的每个用例的方式指定文件扩展名。


没有文件扩展名

TypeScript's Node module resolution 对文件扩展名做了一些有用的假设:

TypeScript will mimic the Node.js run-time resolution strategy in order to locate definition files for modules at compile-time. To accomplish this, TypeScript overlays the TypeScript source file extensions (.ts, .tsx, and .d.ts) over Node’s resolution logic.

这意味着如果我使用 import { foo } from './foo'; 之类的代码,TypeScript 将理解查找名为 foo.ts.

的文件

类似地,Webpack's resolve configuration object 允许在 extensions 属性 中指定一组文件扩展名,以告诉 Webpack 如何尝试解析没有文件扩展名的导入。因此,根据我的 extensions: ['.js', '.ts'] 配置,它将找到与 TypeScript 相同的 foo.ts 文件。

但是,当我尝试 运行 我在 Jasmine 中使用 import 没有文件扩展名的测试时,它无法解析。

C:\Users\mark.hanna\Documents\Code Playground\analyser>npm test

> analyser@0.1.0 test C:\Users\mark.hanna\Documents\Code Playground\analyser
> tsc && jasmine

Error: While loading C:/Users/mark.hanna/Documents/Code Playground/analyser/spec/analyser.spec.js: NodeError: Cannot find module 'C:\Users\mark.hanna\Documents\Code Playground\analyser\dist\file-processing' imported from C:\Users\mark.hanna\Documents\Code Playground\analyser\dist\analyser.js
    at C:\Users\mark.hanna\Documents\Code Playground\analyser\node_modules\jasmine\lib\loader.js:22:30
    at async Jasmine._loadFiles (C:\Users\mark.hanna\Documents\Code Playground\analyser\node_modules\jasmine\lib\jasmine.js:182:5)
    at async Jasmine.loadSpecs (C:\Users\mark.hanna\Documents\Code Playground\analyser\node_modules\jasmine\lib\jasmine.js:173:3)
    at async Jasmine.execute (C:\Users\mark.hanna\Documents\Code Playground\analyser\node_modules\jasmine\lib\jasmine.js:479:3)
npm ERR! Test failed.  See above for more details.

.js 扩展

我的另一个选择是在我的导入中指定 .js 文件扩展名,例如import { foo } from './foo.js.

虽然我没有找到明确的文档,但 TypeScript 的模块解析也清楚地理解 ./foo.js 实际上可能意味着 ./foo.ts。我发现对此的引用是 supported since TypeScript 2

这意味着我可以指定 .js 文件扩展名,并且仍然可以使用 tsc 正确编译我的代码。然后我还能够正确地 运行 我的 Jasmine 测试,因为它了解在包含文件扩展名时如何进行模块解析。

然而,Webpack 不理解 ./foo.js 实际上可能意味着 ./foo.ts。因此,使用这种方法,在将代码导入脚本以在浏览器中显示示例时,我无法在包中构建代码。

ERROR in ./src/file-processing.ts 2:0-49
Module not found: Error: Can't resolve './AnalyserRows.js' in 'C:\Users\mark.hanna\Documents\Code Playground\analyser\src'
resolve './AnalyserRows.js' in 'C:\Users\mark.hanna\Documents\Code Playground\analyser\src'
  using description file: C:\Users\mark.hanna\Documents\Code Playground\analyser\package.json (relative path: ./src)
    Field 'browser' doesn't contain a valid alias configuration
    using description file: C:\Users\mark.hanna\Documents\Code Playground\analyser\package.json (relative path: ./src/AnalyserRows.js)
      no extension
        Field 'browser' doesn't contain a valid alias configuration
        C:\Users\mark.hanna\Documents\Code Playground\analyser\src\AnalyserRows.js doesn't exist
      .js
        Field 'browser' doesn't contain a valid alias configuration
        C:\Users\mark.hanna\Documents\Code Playground\analyser\src\AnalyserRows.js.js doesn't exist
      .ts
        Field 'browser' doesn't contain a valid alias configuration
        C:\Users\mark.hanna\Documents\Code Playground\analyser\src\AnalyserRows.js.ts doesn't exist
      as directory
        C:\Users\mark.hanna\Documents\Code Playground\analyser\src\AnalyserRows.js doesn't exist
 @ ./src/analyser.ts 4:0-48 69:0-50
 @ ./docs/assets/js/src/docs-script.ts 2:0-53 7:14-36 34:14-36 43:14-36 48:51-68 49:28-45

.js 扩展与 Webpack 别名

到目前为止我找到的唯一解决方案是一个我真的不喜欢的解决方案,即使用 Webpack 的 resolve.alias 配置明确告诉它当我说 ./foo.js 我的意思是 ./foo.ts.

例如,我目前在我的 @cipscis/csv 项目中使用了这种方法:

    resolve: {
        extensions: ['.js', '.ts'],
        alias: {
            '@cipscis/csv': `${srcPath}/csv.ts`,
            './stringify.js': './stringify.ts',
            './parse.js': './parse.ts',
        },
    },

webpack.config.js

虽然这感觉很笨拙,而且我必须为我导入的每个文件指定一个类似的别名,所以它根本感觉不是很好的可扩展性。感觉 一定有一个“正确”的方法来做这件事吗?

感谢,我现在对这个问题有了答案。

Jasmine 在这里有点转移注意力,真正相关的只是它试图对我的设置进行的 ES 模块解析。问题的核心是 Webpack 的模块解析无法重现 TypeScript 的模块解析规则的功能,它将 *.js 路径视为可能指向 *.ts 文件。

感谢 amosq 的评论,我已经能够使用 Webpack 解析器插件解决此问题:resolve-typescript-plugin

它的说明只展示了如何在 CommonJS 语法中使用它,尽管正如我在问题中提到的那样,我尽可能地使用 ES 模块语法。要在我的 Webpack 配置中使用它,我这样做了:

import resolveTypeScriptPluginModule from 'resolve-typescript-plugin';
const ResolveTypeScriptPlugin = resolveTypeScriptPluginModule.default;

const config = {
    // ...
    resolve: {
        fullySpecified: true,
        plugins: [new ResolveTypeScriptPlugin()],
        // ...
    },
    // ...
};

此设置允许我使用带有 ts-loader 的 Webpack 来编译在导入语句中使用 *.js 的 TypeScript 文件,作为我也使用 [=15] 的相同设置的一部分=] 来编译这些文件。我不再需要使用我在问题末尾提到的 Webpack 别名。