35

TypeScripts 以某种“声明性”的方式抽象出模块导入/导出。

但是如果我想根据一些运行时计算的条件导入或导出一些东西怎么办?

最常见的用例是在 Node.js 和 Windows Script Host 等平台之间共享代码。

TypeScript 自己的 io.ts 在 TSC 编译器中抽象输入/输出,手动破解内置的 TypeScript 自己的模块语法。这是唯一的方法吗?

PS 将import fs = module("fs")加入if语句的问题在于 TypeScript 只允许在顶层使用 import 语句。这意味着在 WSH 中require("fs")将被执行并且显然失败,因为require是未定义的。

4

6 回答 6

26

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);
于 2018-12-17T04:42:05.990 回答
19

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

具有始终发出的导出,但使内容根据运行时值而变化。例如:

// 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. 导入文件将期望定义这些值。因此,您必须确保导入文件确实需要模块(如果您正在处理仅在测试期间运行的文件,这很好),或者您必须为它们实际上没有被导出的情况定义替代值。

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

于 2016-09-12T18:25:45.187 回答
10

我同意他们只能拥有顶级范围的事实充其量是次优的。除了您所说的问题外,这还意味着软件的初始加载时间较慢。例如,在 nodejs 中,如果该函数很少使用,我现在有时会在函数中加载一个模块。所以我的应用程序启动得更快,因为它还没有加载那个模块。

当然,你可以直接使用 require 或 AMD,但是你会错过一些打字的好处。

然而,我认为真正的问题在于 Harmony/es6 将模块定义为顶层,而 TS 似乎遵循该提议。所以不确定TS团队在不偏离标准的情况下能做多少。

于 2012-11-19T17:29:56.243 回答
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());
    }
});
于 2012-11-19T09:34:36.883 回答
1
import { something } from '../here';
import { anything } from '../there';

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

灵感来自安德鲁的回答

于 2021-06-16T14:20:02.170 回答
0

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

  1. 遗憾的是,您必须将导入设置为“任何”以使编译器满意。

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

  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.

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

于 2021-01-04T14:36:57.220 回答