Динамически загружать модуль ESM в пакете CJS в TypeScript

Я пытаюсь загрузить один модуль ESM в свой проект TypeScript CJS. Все примеры, которые я нахожу, предназначены для JavaScript.

// example.ts
export const example = async () => {
  const module = await import("esm-module");
  return module.func
}

Моя проблема в том, что TSC переносит функцию import в требование, которое все ломает.

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.example = void 0;
const example = async () => {
    const module = await Promise.resolve().then(() => require("esm-module")); // Not desired
    return module.func;
};
exports.example = example
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
0
0
166
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Я наткнулся на этот комментарий - Не уверен, что сейчас есть лучший способ. По крайней мере, eval используется с постоянной строкой, поэтому я не вижу проблем с линтингом.

// example.ts
type EsmModule = typeof import("esm-module");

export const example = async () => {
  const module = await (eval('import("esm-module")') as Promise<EsmModule>);
  return module.func
}

Большинство других решений усложняют основную предпосылку и либо пытаются испортить tsconfig.json или package.json, либо неправильно учитывают транспиляцию TypeScript.

Вы предпочитаете использовать eval, чем правильно настроенный проект TS? В вашем связанном комментарии этот подход называется quick and dirty.

morganney 12.05.2024 16:02

Этот проект является устаревшим и имеет moduleResolution как Node10, но не может использовать Node 22 до тех пор, пока не перейдет в статус LTS в октябре. Я уже пробовал комбинацию настроек ts-config, но все равно сталкиваюсь с ошибкой ESM глубже внутри самого пакета ESM. Это единственное решение, которое работает в нашей текущей настройке. Однако в идеале да, я хотел бы перенести проект на ESNext и, в конечном итоге, на ESM, как только экосистема станет немного более стабильной.

myol 13.05.2024 11:54

Вам следует обновить свой вопрос, указав, какую версию модуля и/или узла вы должны использовать. Если вы можете использовать узел 20, который является LTS, вы сможете обновить разрешение модуля, чтобы избежать eval. Конечно, это зависит от того, какую ошибку вы увидели при загрузке пакета, о которой также следовало упомянуть.

morganney 13.05.2024 15:18

Канонический подход

Скорее всего, вам придется использовать module: nodenext в своем tsconfig.json.

tsconfig.json

{
    "compilerOptions": {
        "target": "ESNext",
        "module": "NodeNext",
        "moduleResolution": "NodeNext",
        "outDir": "dist"
    },
    "include": ["src"]
}

пакет.json

"type": "commonjs"

Теперь примеры файлов:

источник/file.ts

export const example = async () => {
    // find-up is a popular ES module on NPM
    const module = await import("find-up");
    return module.findUp
}

src/other.ts

import { example } from './file.js'

async function run() {
    console.info('example', await example())
}

run()

Теперь бегите tsc. Вывод в dist будет выглядеть так:

dist/file.js

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.example = void 0;
const example = async () => {
    const module = await import("find-up");
    return module.findUp;
};
exports.example = example;

dist/other.js

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const file_js_1 = require("./file.js");
async function run() {
    console.info('example', await (0, file_js_1.example)());
}
run();

Альтернативный подход

Если вы можете использовать Node.js >= 22.0.0 и если модуль ES, который вы пытаетесь загрузить в CJS, соответствует этому критерию:

  • Модуль полностью синхронный (не содержит верхнего уровня await); и
  • Одно из этих условий соблюдено:
    • Файл имеет расширение .mjs.
    • Файл имеет расширение .js, а ближайший package.json содержит "type": "module".
    • Файл имеет расширение .js, ближайший package.json не содержит "type": "commonjs" и --experimental-detect-module включен.

Вы можете использовать --experimental-require-module.

tsconfig.json

{
    "compilerOptions": {
        "target": "ESNext",
        "module": "CommonJS",
        "moduleResolution": "Node",
        "outDir": "dist"
    },
    "include": ["src"]
}

пакет.json

"type": "commonjs"

источник/file.ts

export const example = () => {
    const module = require("find-up")
    
    return module.findUp
}

src/other.ts

import { example } from './file.js'

function run() {
    console.info('example', example())
}

run()

Теперь запустите tsc, чтобы получить обновленный результат в dist.

Теперь запустите node --experimental-require-module dist/other.js:

example [AsyncFunction: findUp]
(node:37823) ExperimentalWarning: Support for loading ES Module in require() is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)

Другие вопросы по теме