TypeScript - 条件模块导入/导出

49

在TypeScript中,模块的导入和导出以某种“声明式”的方式隐藏起来。

但是,如果我想基于某些运行时计算的条件来导入或导出一些内容怎么办呢?

最常见的用例是在像Node.js和Windows脚本宿主这样的平台之间共享代码。

TypeScript自己的io.ts在TSC编译器中手动绕过了内置的TypeScript模块语法。那么这是唯一的方法吗?

注:将import fs = module("fs")直接放入if语句中的问题在于,TypeScript只允许在顶层使用import语句。这意味着在WSH中执行require("fs")会失败,因为require未定义。

6个回答

38
TypeScript v2.4开始,您可以使用动态导入来实现条件导入。
一个异步示例:
async function importModule(moduleName: string):Promise<any>{
    console.log("importing ", moduleName);
    const importedModule = await import(moduleName);
    console.log("\timported ...");
    return importedModule;
}

let moduleName:string = "module-a";
let importedModule = await importModule(moduleName);
console.log("importedModule", importedModule);

1
动态导入文档在MDN上,链接为https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/import#Dynamic_Imports。 - kaznovac

22

我有一个稍微有些笨重但非常有效的解决方案,特别是如果你在使用条件导入/导出进行单元测试。

设置一个总是被输出的导出项,但其内容基于运行时值而变化。例如:

// outputModule.ts
export const priv = (process.env.BUILD_MODE === 'test')
  ? { hydrateRecords, fillBlanks, extractHeaders }
  : null

然后在使用该模块的文件中,导入该模块,检查已导入的值是否存在,如果存在,则将所有原本独立导入的值分配给一组变量:

然后在消耗文件中导入输出,检查导入的值是否存在,如果存在,将除了你原本独立导入的所有值都分配给一组变量:

// importingModule.spec.ts
import { priv } from './outputModule';

const { hydrateRecords, fillBlanks, extractHeaders } = priv as any;
// these will exist if environment var BUILD_MODE==='test'

限制:

  1. 不幸的是,您必须将导入设置为“任何”才能让编译器满意。
  2. 您需要检查特定的导入是否已定义(但这是必要的)。
  3. 导入文件将期望这些值被定义。因此,您需要确保导入文件实际上需要这些模块(如果您只在测试期间处理文件,则问题不大),或者您需要为它们未实际导出情况下定义替代值。

尽管如此,它对我的目的非常有效,希望对您也有用。它特别适用于单元测试私有方法。


11

我认为它们只能具有顶层范围这一事实在最好的情况下是次优的。除了你所说的问题之外,它还意味着软件的初始加载时间更慢。例如,在nodejs中,如果某个函数很少使用,我现在有时会在函数中加载一个模块。因此,我的应用程序启动更快,因为它尚未加载该模块。

当然,您也可以直接使用require或AMD,但那样将会错过一些类型化的好处。

然而,我认为真正的问题在于harmony / es6将模块定义为顶级模块,TS似乎正在遵循这个提案。因此,不确定TS团队在不背离标准的情况下能做多少。


2

在TypeScript中有一种动态导入机制,尽管实现方式因模块类型而异。

下面的示例(针对AMD)将有条件地加载模块:

declare function require(moduleNames: string[], onLoad: (...args: any[]) => void): void;

import * as ModuleAlias from './mod';

const someCondition = true;

if (someCondition) {
    require(["./mod"], (module: typeof ModuleAlias) => {
        console.log(module.go());
    });
}

文件顶部的import语句是惰性的,只有当条件语句if (someCondition)为真时,才会实际加载模块。

你可以通过更改someCondition来测试它,并观察网络选项卡上的影响,或查看生成的代码...在动态版本中,"./mod"不会出现在define调用中。而在非动态版本中,则会出现。

使用动态加载

define(["require", "exports"], function (require, exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    const someCondition = true;
    if (someCondition) {
        require(["./mod"], (module) => {
            console.log(module.go());
        });
    }
});

没有动态加载

define(["require", "exports", "./mod"], function (require, exports, ModuleAlias) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    const someCondition = true;
    if (someCondition) {
        console.log(ModuleAlias.go());
    }
});

2
import { something } from '../here';
import { anything } from '../there';

export const conditionalExport =
  process.env.NODE_ENV === 'production' ? something : anything;

灵感来自安德鲁的回答


那不是有条件的进口。 - undefined
你会怎么命名它? - undefined

0

找不到直接实现条件导出作为条件导入的方法。但是我发现Andrew Faulkner的答案很有用,但我对其限制不满意。

  1. 不幸的是,您必须将导入设置为“any”才能使编译器满意。

我想出了一个解决上述限制的方法。以下是我的步骤。

  1. 将条件导出写成一个对象,就像Andrew的答案一样。
  2. 在另一个模块中导入导出的对象。
  3. 解构它。
  4. 通过分配所有解构项并给出适当的类型来定义新常量。

这是一个例子。

//CryptoUtil.ts

function encryptData(data : Buffer, key : Buffer) : Buffer {
    // My encryption mechanism.
    // I return a Buffer here.
}

function decryptData(data : Buffer, key : Buffer) : Buffer {
    // My decryption mechanism.
    // I return a Buffer here.
}

// Step 1
// Exporting things conditionally

export const _private = (process.env.NODE_ENV === "test") ? {
    __encryptData : encryptData,
    __decryptData : decryptData,
} : null;

请注意,我将encryptData导出为__encryptData而不是直接导出为encryptData。我这样做只是因为在导入模块时,可以给人一种__encryptData是私有函数的想法。这完全是我的个人喜好。
然后,在导入东西时...
// CryptoUtil.test.ts

// Step 2
// import the exported object.
import { _private } from "./CryptoUtil";

// Step 3. De-structuring.
const {
    __encryptData,
    __decryptData,
} = _private as any;
 
// Step 4. Define new constants giving proper type.

const _encryptData : (data : string, password : Buffer) => Buffer = __encryptData;
const _decryptData : (encryptedData : Buffer, password : Buffer) => Buffer = __decryptData;

// Now I can use _encryptData, _decryptData having the proper type.

尽管我提出了这种方法来解决安德鲁的第一个限制,但我的方法引入了一个新的限制。也就是说,您必须在两个地方定义类型。当您更改导出函数的类型时,它不会自动更改导入函数的类型。您必须手动更改。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接