Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

It is very difficult to call the import function from a CommonJS module. #52775

Closed
Jason3S opened this issue Feb 15, 2023 · 9 comments
Closed
Assignees
Labels
Question An issue which isn't directly actionable in code

Comments

@Jason3S
Copy link

Jason3S commented Feb 15, 2023

Bug Report/Feature Request

tldr;

The fix was to use:
tsconfig.json

{
  // ...
  "module": "CommonJS",          // Forces CommonJS
  "moduleResolution": "node16",  // `node16` allows the `import()`
  // ...
}

Description

I did search for possible duplicates of this issue, but I wasn't able to filter the issues enough to find them.

Feature Request: flag to enable the import() function.

Bug: the transpiler converts import() function into require() in .cjs files.

It seems impossible to call the import() function from a TypeScript file compiling to CommonJS. It always gets converted into an inline require().

It is going to take a long time for code bases to migrate from CommonJS to ESM. In the mean time, how can we make it possible to easily asynchronously import an ESM modules on targets that support it? I don't want to get into the ESM/CommonJS debacle. My goal here is to just to async import an ESM Module from CommonJS TypeScript.

Even if we cannot get TypeScript to work, a bit of JavaScript should work, but it does not.

dynamicImport.cjs

'use strict';

/**
 * Thunk Layer for `import` to allow dynamic imports.
 * @param {string | URL} module - name of module to load
 * @returns
 */
function dynamicImport(module) {
    return import(module.toString());
}

exports.dynamicImport = dynamicImport;

dynamicImport.d.cts

/**
 * Thunk Layer for `import` to allow dynamic imports.
 * @param {string | URL} module - name of module to load
 * @returns
 */
export function dynamicImport(module: string): Promise<any>;

If I add that code into my CommomJS TypeScript project with allowJs, import gets converted into require, even if the target is ES2020.

dist/dynamicImport.cjs

/**
 * Thunk Layer for `import` to allow dynamic imports.
 * @param {string | URL} module - name of module to load
 * @returns
 */
function dynamicImport(module) {
    var _a;
    return _a = module.toString(), Promise.resolve().then(() => __importStar(require(_a)));
}
exports.dynamicImport = dynamicImport;

I realize that it is possible through some creative uses of tsconfig and "composite": true to get this to transpile correctly. But, that is missing the point. How to make dynamic imports easy?

🔎 Search Terms

import, function, commonjs, allowjs, javascript

🕗 Version & Regression Information

  • Version 4.9.5

⏯ Playground Link

Playground link with relevant code

💻 Code

// We can quickly address your report if:
//  - The code sample is short. Nearly all TypeScript bugs can be demonstrated in 20-30 lines of code!
//  - It doesn't use external libraries. These are often issues with the type definitions rather than TypeScript bugs.
//  - The incorrectness of the behavior is readily apparent from reading the sample.
// Reports are slower to investigate if:
//  - We have to pare too much extraneous code.
//  - We have to clone a large repo and validate that the problem isn't elsewhere.
//  - The sample is confusing or doesn't clearly demonstrate what's wrong.

🙁 Actual behavior

🙂 Expected behavior

@ajafff
Copy link
Contributor

ajafff commented Feb 15, 2023

#45125

@Jason3S
Copy link
Author

Jason3S commented Feb 15, 2023

@ajafff,

Thank you. Do you know the magic combination of "module" and "moduleResolution". #45125 refers to node12 which no longer exists and the documentation at TSConfig Reference is out of date or poor. It gives the options, but not the expected output. Related to #52593.

The closest I came was with:

  "module": "CommonJS",
  "moduleResolution": "node16",

But that doesn't work correctly with package.json.exports (import, require, types). It sees "type": "module" in the package.json and gives up without looking at exports or main.

 error TS1479: The current file is a CommonJS module whose imports will produce 'require' calls; 
however, the referenced file is an ECMAScript module and cannot be imported with 'require'. 
Consider writing a dynamic 'import("<module>")' call instead.

@RyanCavanaugh RyanCavanaugh added the Needs Investigation This issue needs a team member to investigate its status. label Feb 15, 2023
@andrewbranch
Copy link
Member

@Jason3S node12 was renamed to node16.

@andrewbranch andrewbranch added Question An issue which isn't directly actionable in code and removed Needs Investigation This issue needs a team member to investigate its status. labels Feb 15, 2023
@Jason3S
Copy link
Author

Jason3S commented Feb 16, 2023

@andrewbranch,

Thank you, that is clear. It is really just a short coming with node16 moduleResolution.

It seems like tsc is just giving a false positive with Error TS1479 when a package has type=module and exports main and require endpoints.

It works with importing modules that are type=CommonJS that also happen to export ESM endpoints.

project:

- package.json (CommonJS)
- tsconfig.json
- src/code.ts
- node_modules
  - <pkg>
    - package.json (Exports both CommonJS and ESM)
    - index.cjs
    - index.mjs
p.type p.main p.module p.exports.require p.exports.import t.module t.modRes works
CommonJS CommonJS node
CommonJS CommonJS node16
module CommonJS node
module CommonJS node16
module module node
module module node16

p. - is the imported node_modles/<pkg>/package.json
t. - is my local tsconfig.json.compilerOptions
- is Error TS1479

@fatcerberus
Copy link

I believe in general, if you're using moduleResolution: "node16" then module should also be node16. Then, since you have "type": "module" in package.json, use .cts instead of .ts for CommonJS modules.

@Jason3S
Copy link
Author

Jason3S commented Feb 16, 2023

@fatcerberus,

Sorry for the confusion. There are two package.json files involved.

  • package.json (CommonJS)
  • node_modules//package.json (exports both CommondJS and ESM)

@andrewbranch
Copy link
Member

@Jason3S it’s impossible to say without an actual example here, but chances are that the package you’re trying to import is configured incorrectly, such that TypeScript is directed to ESM types even while Node is directed to a CJS module. You can check with https://arethetypeswrong.github.io.

@typescript-bot
Copy link
Collaborator

This issue has been marked as 'Question' and has seen no recent activity. It has been automatically closed for house-keeping purposes. If you're still waiting on a response, questions are usually better suited to stackoverflow or the TypeScript Discord community.

@Jason3S
Copy link
Author

Jason3S commented Feb 20, 2023

@andrewbranch,

Thank you for the link, I didn't realize it existed.

I investigated it over the weekend and discovered that it is NOT possible using only TypeScript to create dual output ESM / CJS library modules (if it is, I would love to see an example). A tool to correctly rename the ESM .js files to .mjs is needed. I didn't find a tool that correctly renamed the files, so I made one ts2mjs.

TypeScript has help lift the entire JavaScript ecosystem. It is a very nice language and a joy to work with. Thank you for helping with that.

When working on #52593, some good examples how to correctly export types for both CJS and ESM would be very useful. Like what VS Code does for extensions (vscode-extension-samples).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

6 participants