Description
TypeScript Version: 2.7.2
Search Terms: import assignment modules import = require types
Code
When targeting CommonJS, I can pull in values and types from a CommonJS module like so:
import Koa = require('koa');
Which generates a normal require statement:
const Koa = require('koa');
Actual behavior:
When targeting MS modules, import assignment triggers the warning:
[ts] Import assignment cannot be used when targeting ECMAScript modules. Consider using 'import * as ns from "mod"', 'import {a} from "mod"', 'import d from "mod"', or another module format instead.
The suggestions aren't available though, because
- This CJS module isn't ES module compatible, it uses an
module.exports =
style export. - An ES
import
statement will emit andimport
statement, and I need it to emitrequire()
to use with@std/esm
(more on this latter).
The only way to generate a require()
is to use one without import assignment, but then we have to jump through major hoops to import the types:
// Import just the type under a different name so there's not a clash in the value namespace
// Make sure you import only types so this isn't emitted!
import _Koa from 'koa';
// Alias just the type back to the class name
type Koa = _Koa;
// Re-declare the static interface because there's no way I know of to extract the
// static interface of a class in this situation.
interface KoaConstructor {
new(): Koa;
}
// Normal require(), and cast to the static type
const Koa = require('koa') as KoaConstructor;
This is obviously pretty cumbersome.
Expected behavior:
I think supporting import assignment when emitting ES modules is the easiest solution. Just continue to emit:
const Koa = require('koa');
Obviously, we don't know how CJS/ESM interop is going to behave quite yet. The possibilities range from:
- Not supporting CJS in
import
statements (probably unlikely due to Node.js team preferences) - CJS modules only having a default export with the value of
module.exports
(most likely) - Somehow supporting named exports (seems difficult to impossible).
Regardless of the CJS interop support, it will at least possible to still use require()
(maybe still a global, maybe via import.meta.require
).
But even with interop, using require()
is necessary for type-checking because existing typings describe what's returned by require()
, not what would be returned from the CJS interop proposals. That is, existing typings do not describe an ES module with a default export. TypeScript may also need a way to transform typings under various interop schemes, but that's a separate issue.
So, currently at least, we need to emit require()
instead import
and also bring in the types. Import assignment does exactly this.
Another option could be to offer an easier way to import just the type of a module and cast the require()
result, but that seems to only more verbosely describe what import assignment already does. Essentially it would be allowing this:
import * as _Koa from 'koa';
const Koa = require('koa') as _Koa;
Without the error that's generated from the import
:
[ts] A namespace-style import cannot be called or constructed, and will cause a failure at runtime.
Playground Link: BTW, there's no option in the playground to set the "module" compiler option.
Related Issues:
#19500