Closed ahejlsberg closed 9 years ago
Babel handles mangling default exports with named exports just fine in both AMD and CommonJS. This (amongst other thigs) allows for some nice ways to create default instances of classes, like
export class Logger {
// stuff
}
export default new Logger(defaultArgs);
which results in the following CommonJS code:
var Logger = exports.Logger = function Logger() {
_classCallCheck(this, Logger);
};
exports["default"] = new Logger(defaultArgs);
exports.__esModule = true;
When importing this as
import defaultLogger, {Logger} from './log';
it generates the following:
var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; };
var _log = require("./log");
var defaultLogger = _interopRequire(_log);
var Logger = _log.Logger;
The trick here is the _interopRequire
call on default imports, that allows it to work with both es6 modules compiled with the same transpiler, as well as regular AMD/CommonJS modules that use the default export paradigm.
Oh, and as a side-note, given that typescript typically has metadata about everything, the _interopRequire
could be skipped as long as the source is in typescript, or there is a .d.ts
file, as it would be known at compile-time the shape of the module in question.
@ahejlsberg,
:+1:
It is recommended that TypeScript libraries and applications be updated to use the new syntax
I suggest the addendum: "TypeScript's original internal and external module constructs are deprecated and may not be supported in future versions".
(With the aim of discouraging multiple ways of doing the same thing.)
Also I couldn't find anything that says what happens when a module is imported and used in a type-only position. I would expect the down-level emit to omit the require
as it does now.
@Alxandr It's a nifty scheme, but one issue is that if export default
always creates an exports.default
property we would have to keep the old export =
syntax around for creating modules that want to assign to module.exports
and remain consumable by down-level clients that aren't aware of the trick. We would much prefer to retire the old syntax and have everyone move to ES6 syntax.
@ahejlsberg babel actually deals with this by special casing files that only export a default to use module.exports
. Given that this is a new syntax etc, it would not break compatibility, while still allowing for downstream compatible libraries.
It's (IMHO at least) better than disallowing default and named exports at the same time.
@ahejlsberg I hear what you are saying about wanting to get everyone on board with the One True Module Format. I have that desire as well. However, I think I also share some of @Alxandr’s concern that if TypeScript isn’t following the rules of that module format more closely that it’s going to cause problems as the “standard” ES6 module format does lots of non-standard things in TypeScript emitting to ES5.
In particular I definitely want people to be to experience the benefits of being able to have circular dependencies on modules with default values, which is currently not possible with the way AMD (and, in some ways, CJS) modules work. This is a fairly important feature when doing things like creating data models that have circular relations to other types, without either using an intermediate registry to retrieve types, or hanging values that should be defaults off of properties (the var Foo = require('Foo').Foo
anti-pattern, which TS would have to do, but at least it would be more hidden from developer eyes).
I also understand and share the concern about emitting TS modules for down-level consumers that won’t know this One Weird Trick from Babel to support ES6 modules. I feel like continuing to support export =
syntax for this case might be OK since it’s basically an opt-in for the more restrictive default behaviour of legacy module formats. (Of course I don’t do much maintenance of the compiler so YMMV. :))
Please let me know your thoughts on this if you have a moment, I’d like to have some holes poked in my thinking here. Thanks!
@Alxandr I think your suggestion has a lot of merit. Let me summarize what I think we would do.
If a module has only a default export, emit an assignment to module.exports
:
// TypeScript code
export default function foo() { }
// Code emitted for CommonJS
function foo() { }
module.exports = foo;
Otherwise, emit everything as assignments to exports.xxx
and emit an exports.__esmodule
marker:
// TypeScript code
export function foo() { }
export function bar() { }
export default { foo, bar };
// Code emitted for CommonJS
function foo() { }
exports.foo = foo;
function bar() { }
exports.bar = bar;
exports.default = { foo: foo, bar: bar };
exports.__esmodule = true;
On the import side, include an __esmodule
check on all default imports:
// TypeScript code
import d, { foo } from "./foobar";
d.foo();
foo();
// Code emitted for CommonJS
var _a = require("./foobar"), d = _a && _a.__esmodule ? _a.default : _a;
d.foo();
_a.foo();
It's not quite as pretty as what is emitted now, but I think it is worth it to get support for full ES6 module semantics down-level (as well as interop with modules emitted by Babel).
For an original import-equals declaration, we would give an error if the imported module has both regular exports and a default export (i.e. if it is an ES6 module). Such modules would only be importable using the new ES6 syntax.
@csnover With this proposal you'd be able to have circular dependencies between modules with default exports as long as the modules have at least one regular export as well (which could just be a dummy member).
@ahejlsberg wouldn't it be possible to skip the fancy emit given metadata? I mean, typescript has typeinformation about everything (which is sort of the idea, right)? So if we know that the module being imported, we should know the format it exports at, right?
Is it better to give an error for using import-equals to import an es6 module? Or is it better to emit an import-equals declaration in the same way as a default import? We could emit:
import d = require("./foobar");
as
var _a = require("./foobar"), d = _a && _a.__esModule ? _a.default : _a;
Also, to make circular references work, don't you have to access the default member late? So instead of assigning the default to d eagerly, a call to d.foo()
would emit as _a.default.foo()
. Why is this not the case, but for named exports it is?
@Alxandr Yes, I think it would work to have the following rules:
module.exports
assignment when a module exports only a default, and emit everything as assignments to exports.xxx
otherwise.require
returns the default export object itself when importing a module that exports only a default, and assume everything is a property on the returned object otherwise.We would lose the ability to dynamically adapt on import based on the __esModule
marker, but that would be ok as long as everyone else plays by the same rules.
I suppose we'd still want to emit the __esModule
marker such that Babel and other systems not guided by static type information can do the right thing.
@JsonFreeman I think we have two choices for import-equals with an ES6 (mixed) module. Either say it is an error (there's no backwards compatibility to worry about) or say that you get the module object with a set of properties including one named default
. The odd thing about the latter is that adding a regular export to a module that previously had only a default export would cause everything to "pop out" one level on the import-equals side. My personal inclination is to make import-equals an error with mixed modules.
Regarding circular references, you're right, we'd want to rewrite references to the default import in the same way we'd do with any other import. Which in turn means we don't want the dynamic _esModule
import check. One more reason not to do it.
In terms of backward compatibility, importing code that previously did not error, would now error if the exporting module suddenly starts exporting other stuff besides its default export. But I guess the argument is, in that case it's better to get an error than to suddenly get different semantics. So I guess in that sense, there is no real break of backward compatibility.
@ahejlsberg, you mentioned skipping the dynamic check on the import side. I agree it's nicer to not have it, but I have one question. Does this mean that the following assigns directly to module.exports?
class C { }
export { C as default };
What about this? Would this assign directly to module.exports?:
// In a file A.ts
export default class { };
// In a file B.ts
export * from "A"; // Does this assign directly to module.exports? Or just an empty namespace?
@JsonFreeman Yes, your class first example would assign directly to module.exports
. Writing
export { C as default };
is precisely equivalent to writing
export default C;
Regarding your second example, an export *
never re-exports default exports, so it would never assign to module.exports
.
Great, thanks. I believe this design is consistent and reasonable.
Yes, the spec specifically allows the identifier following as
in an export clause to be a reserved word.
https://people.mozilla.org/~jorendorff/es6-draft.html#sec-exports
@jbondc I don't think the first export there is legal. At least babel throws on the .
.
@jbondc From using ES6 with babel for a good while, I've almost never used export {..}
. In general you just export values as you create them.
Another point that pooped up from your question though is exports inside of internal modules. How will that be handled? Do I do the following?
export module Foo {
export class Bar {}
}
or is the following enough
module Foo {
export class Bar {}
}
And how do I import it?
import { Foo } from './file';
new Foo.Bar();
Or will internal modules get removed down the line, as they were from the module draft for ES6?
@Alxandr The TypeScript team is considering renaming 'module' to 'namespace' #2159 You'd have to 'export module' or 'export namespace' for it to be importable.
@Alxandr @jbondc A TypeScript internal module is really no different than other declarable entities such as classes, functions, and enums when it comes external modules. For example, given this external module that exports an internal module
export module Foo {
export class Bar { }
}
you can import as follows
import { Foo } from "./mod";
new Foo.Bar();
However, as you've observed, ES6 import and export declarations don't allow you to "dot into" the substructure of internal modules (understandable, as they aren't part of ES6). So, for example, the following is not allowed:
import { Foo.Bar as Bar } from "./mod"; // Error, qualified name not allowed
You would have to do it in two steps by adding a TypeScript import-equals:
import { Foo } from "./mod";
import Bar = Foo.Bar;
In general I don't think it will be common to mix the two, nor is it clear that we want to encourage it.
Having spent a total of several hours trying to come up with a new ES6 style syntax to apply typeof
on an ambient external module (#2357), and closely evaluating the ES6 import syntax, there is one thing I found truly counter-intuitive, almost to the point of doubting my own understanding of it:
The from
keyword intuitively seems to imply that something is "chosen" from the module, yet, ES6 designers use it in quite a bizarre way, for example:
import {FileReader} from "FileSystem";
as expected, would import the export (let's say in this case a class) FileReader
from the module, yet
import FileReader from "FileSystem";
would unexpectedly import the default export from the module and assign it the identifier FileReader
.
Apart from being a huge pitfall for human error and confusion, I also think it "abuses" the from
keyword in an unappealing way. As I see it: import
.. from
means "choose something from", not "apply alias to default export". A more meaningful syntax would have been:
import "FileSystem" as FS;
where the as
keyword is interpreted with the natural semantics most people would assign to it.
@rotemdan It's popped up on es and possibly other places: https://esdiscuss.org/topic/import-default-syntax
Another part that's been mentioned is: a) export default Ts.Is.Cool.bar vs. b) export default = Ts.Is.Cool.bar
There seemed to be a preference for (b) Good read but unclear how "final" the syntax is: http://www.2ality.com/2014/09/es6-modules-final.html
@Alxandr @JsonFreeman Having given some more thought to whether metadata should guide the code generation for ES6 import declarations, I now think that it shouldn't.
The problem with the metadata guided approach is that it only works when modules are compiled together. For example, say that module "a" is a default export only module and that "b" imports the default export of "a". Now say that "a" adds a regular export, thus becoming a mixed module. "b" now needs to be recompiled because the code to import the default export of "a" is different. For "b" to be unaffected by such changes in "a" we need to include dynamic __esModule
checks in imports of default exports.
Also, only by including __esModule
checks is it possible to emit code for a given module without resolving its dependencies, and we definitely want that for features such as compile-on-save.
@csnover The upshot of this is that imports of default exports will always be evaluated eagerly (as they are now), and I don't see any way in which we could make circular references between default exports work in CommonJS or AMD.
@ahejlsberg Ah, yeah, I completely agree with this.
I think that is reasonable, given that we want isolated, single file emit as a goal. If this is important, we will also need to visit other places in the language to make sure that the whole language is single-file-emit safe.
@csnover The upshot of this is that imports of default exports will always be evaluated eagerly (as they are now), and I don't see any way in which we could make circular references between default exports work in CommonJS or AMD.
If you are saying that using __esModule runtime flag is the solution, doing the check at the usage site and not at the import reduces efficiency and adds a little ugliness but would ensure that the circular dependency handling works, I think:
define([ 'exports', 'a' ], function (exports, a) {
function b() {
(a && a.__esModule ? a.default : a)();
}
exports.default = b;
});
or you could use a function to focus the ugly check to one place but is slower:
define([ 'exports', 'a' ], function (exports, a) {
function __default(obj) { return obj.__esModule ? obj['default'] : obj; }
function b() {
__default(a)();
}
exports.default = b;
});
The only reason I push on this is because the default import/export sugar is one of the only real benefits of the ES module format, so it would be a shame to be deprived of its use. Even an opt-in flag like Esperanto’s strict mode flag would be fine, so people that want to write ES modules with ES semantics can get that, and people that want to write ES modules with legacy semantics can avoid the overhead.
@mhegazy and I spent some time over the past few days thinking about the feedback we've gotten here and elsewhere. We've come to the consensus that export default
should consistently have ES module semantics and not be conflated with export =
. Specifically:
export default
declaration always declares an exported member named default
and is always emitted as an assignment to exports.default
. In other words, export default
consistently has ES module semantics. For compatibility with Babel we could optionally emit an __esModule
marker when a module has a default
export, but we wouldn't actually use that marker for anything.export =
declaration, which substitutes a different entity to be exported in place of the module itself, is always emitted as an assignment to module.exports
. It is an error to have other exports in a module that uses export =
. This is the existing TypeScript behavior.export =
to export another module (be that an internal or external module) can be imported using the new ES6 constructs. In particular, the convenient destructuring imports can be used with such modules. The pattern of using export =
to export another module is common in .d.ts files that provide a CommonJS/AMD view of an internal module (e.g. angular.d.ts).export =
to export a non-module entity in place of the module itself must be imported using the existing import x = require("foo")
syntax as is the case today.This approach has several advantages:
An example of circularly dependent ES6 modules:
// ------ ping.ts ------
import pong from "./pong";
export default function (count: number) {
if (count > 0) {
console.log("ping");
pong(count - 1);
}
}
// ------ pong.ts ------
import ping from "./ping";
export default function (count: number) {
if (count > 0) {
console.log("pong");
ping(count - 1);
}
}
// ------ main.ts ------
import ping from "./ping";
ping(10);
The emitted code for CommonJS:
// ------ ping.js ------
var pong = require("./pong");
function _default(count) {
if (count > 0) {
console.log("ping");
pong.default(count - 1);
}
}
exports.default = _default;
// ------ pong.js ------
var ping = require("./ping");
function _default(count) {
if (count > 0) {
console.log("pong");
ping.default(count - 1);
}
}
exports.default = _default;
// ------ main.js ------
var ping = require("./ping");
ping.default(10);
An example of interop with existing .d.ts files:
// ------ angular.d.ts ------
declare module angular {
export function bind(context: any, fn: Function, ...args: any[]): Function;
export function bootstrap(element: string, modules?: string, config?: any): any;
// more
}
declare module "angular" {
export = angular;
}
// ------ client.ts ------
/// <reference path="angular.d.ts"/>
import { bind, bootstrap } from "angular";
bind;
bootstrap;
The emitted code for AMD:
// ------ client.js ------
define(["require", "exports", "angular"], function (require, exports, _angular) {
_angular.bind;
_angular.bootstrap;
});
So now everything has a distinct meaning, nothing is synonymous with anything, correct?
So now everything has a distinct meaning
I think var foo; export foo
etc. is still synonymous with external modules.
just to make sure, doing this:
module Base {
export interface ISomeInterface { /* .. */ }
export var something: ISomeInterface = /*...*/;
}
export = Base;
isn't encouraged anymore? or the module [[Name]] { }
will still work?
module [[Name]] { } will still work
Yes. There is however talk of renaming module
to namespace
in this case.
About:
module Base {
export interface ISomeInterface { /* .. */ }
export var something: ISomeInterface = /*...*/;
}
export = Base;
I would do:
module Base {
export interface ISomeInterface { /* .. */ }
export var something: ISomeInterface = /*...*/;
}
export default Base;
@pocesar Doing this:
module Base {
export interface ISomeInterface { /* .. */ }
export var something: ISomeInterface = /*...*/;
}
export = Base;
is basically the same as doing this:
export interface ISomeInterface { /* .. */ }
export var something: ISomeInterface = /*...*/;
The latter is much simpler and identical in ES6.
A module that uses export = to export a non-module entity in place of the module itself must be imported using the existing import x = require("foo") syntax as is the case today.
Can't they just use import * as foo from "foo"
?
@basarat We disallow the import * as foo from "foo"
form when the imported entity is a non-module (such as a class or function exported with export =
). If we allowed it, you'd have to write this
import * as Foo from "foo";
var foo = new Foo();
Would be pretty odd as * as Foo
strongly indicates that Foo
is an alias for a module and not something you can apply new
to.
Would be pretty odd as * as Foo strongly indicates that Foo is an alias for a module and not something you can apply new to
Seems a bit arbitrary. Consider:
declare module "exportEqual" {
class Foo {
}
export = Foo;
}
declare module "exportEqual2" {
interface FooInstance {
}
interface FooConstructor {
new (): FooInstance
}
var Foo: FooConstructor;
export = Foo;
}
With a test
import * as First from "exportEqual"; // Error
import * as Second from "exportEqual2"; // Okay
var foo = new Second(); // Okay
I would expect import * as foo from "foo"
to be semantically equivalent and allowed like import foo = require('foo')
in all cases.
I'd rather just consistently use import * as
in all places instead of sometimes doing import foo =
and other times not.
@basarat it’s not arbitrary, ES6 modules are different from AMD/CJS modules and the import/export semantics are not the same. Using import * as foo
in ES and expecting to get the default value at foo
is nonsense in ES module semantics since the default value of an ES module is on the default
property. 15.2.2.4 describes what is returned from an import * as foo
statement and it does not alias the default property to foo
.
Using import * as foo in ES and expecting to get the default value at foo is nonsense in ES module semantics
So foo
must be used only for dereferencing i.e. foo.
Okay I get that, and I get that its per ES6 module semantics. Perhaps if someone does import * as foo
and uses foo
in anything other than foo.
it should be an error?
I would like to point out these facts :
jquery
or momentjs
is going to change how they work. I hate inconsistency :). I wish that ES6 modules catered for existing very popular commonjs modules out there but I guess it doesn't. And I know its not TypeScript's fault :heart:
At this point I feel like recommending people to just use import / require
for as long as they can? Is this wise of me?
Perhaps if someone does import * as foo and uses foo in anything other than foo. it should be an error?
foo
is defined by the spec to be an object of List type, like the arguments
object. TS is correct in its current operation.
We now have a module system that works fundamentally different in a breaking way from a majority of JS out there. Even @teppeis typescript simple or your dts-generator. And I doubt jquery or momentjs is going to change how they work.
Barring some sort of catastrophe, eventually all modular ES code will be using ES modules, this is a transitional period. There is nothing wrong with dts-generator with TS1.5, everything works just fine whether you are using legacy modules or ES modules or a mix of module types.
whether you are using legacy modules
Calling it legacy
is the issue I have. I don't think momentjs
or jquery
will change how they work, so we will end up with two module systems in the long run:
And I feel like saying that if import / require
works all the time. I'm just going to use that and not bother with import / from
.
An external module that uses export = to export another module or a "module like" entity can also be imported using the new ES6 constructs. In particular, the convenient destructuring imports can be used with such modules. The pattern of using export = to export another module is common in .d.ts files that provide a CommonJS/AMD view of an internal module
I would recommend that export =
not be supported in any form by ES6 import. Based on the spec if the source does export =
(e.g module.export =
) there is no synonym in ES6 spec and the module cannot be effectively used by the ES6 module system.
Correct ? /cc @csnover
That way there is no risk of mistakes and inconsistency in usage. e.g. moment : https://github.com/borisyankov/DefinitelyTyped/blob/master/moment/moment.d.ts#L471 Shouldn't be allowed to used (as we have discussed) yet import * as moment from "moment"
compiles.
FWIW moment is pretty popular : https://www.npmjs.com/package/moment 54,000 downloads yesterday.
It's tough because there really is no simple answer here--only compromises. We really want to permit existing .d.ts files to be imported using ES6 syntax, but a large number of the .d.ts files on DefinitelyTyped actually use export =
to export an internal module (so the .d.ts file can be used both with and without a module loader). It seems perfectly meaningful to allow those to be imported using import * as foo from "foo"
. However, it starts to get muddled when the entity exported with export =
also has call or construct signatures (such as the "moment" library). And it gets even more muddled when the entity is actually a function or a class. We currently try to only permit "module-like" entities to be imported this way (i.e. we rule out functions and classes unless they're merged with a module), but maybe we should just stop policing and say that any export =
can be imported using import * as foo from "foo"
?
Based on the spec if the source does export = (e.g module.export =) there is no synonym in ES6 spec and the module cannot be effectively used by the ES6 module system. Correct ? /cc @csnover
If you export some thing using export =
and it has some properties on it then technically there is no reason you should be unable to import those properties using ES import, it’s just sugar. The only reason to restrict this behaviour would be to block users thinking that import * from foo
is a synonym for import foo = require(…)
.
but maybe we should just stop policing and say that any export = can be imported using import * as foo from "foo"?
No, the ES spec is explicit about the type foo
in import * as foo
so I don’t think it makes sense to break language compatibility. I would rather prevent import * as foo
entirely on legacy modules than move in this direction since, again, this would be a ES spec violation (import * from foo
does not give you foo.default
as foo
in ES modules!)
Having actually worked in an environment with mixed modules I think the current behaviour of the TS compiler is just fine.
To the concern about legacy modules there is only one thing that might be worth revisiting in the future:
interface foo {
(): void;
default: () => void;
lol: boolean;
}
var foo = <foo> function () {};
foo.default = foo;
foo.lol = true;
export = foo;
The TS compiler will say “error TS1192: External module '"foo"' has no default export.” but per my reading of the spec (ES6 §15.2.2.4) it should be trying to get the [[ImportName]]
'default' which does exist on the exported module, so should not be an error (or should not be an error if the module also has the __esModule indicator to demonstrate that default
is not an unrelated property). However, I don’t think this is important to address right now, as I said, the current behaviour of the compiler is fine in my practical experience.
This seems to be an entirely acceptable compromise. Multiple import syntaxes are not ideal, but those different syntaxes are semantically meaningful in this scenario. IMO it would cause more developer confusion to have the same ES6 syntax mean different things in different contexts.
@basarat "I hate inconsistency :)" -- It's consistent with the long, fractured, and crazy ES6 spec development :)
Since this time I've been (ab)using es6 module syntax (doing import *
where I know it might not be semantically valid and not doing any export =
myself to be semantically valid in my code) and I sort of like it. I'll see where we end up :bus:
I'm guessing from this issue that typescript@1.5.1 should add the ability to compile .ts
files to .js
files that contain ES6 modules. Is that correct?
@aexmachina this is already available in TypeScript 1.5-alpha, which was released last month.
@mhegazy @aexmachina , I just updated my project (WIP) to use ES6 Modules and I'm getting empty .js and .d.ts files:
This was working with the old module syntax
@niemyjski the issue is you are using --out with --module. --out only controls the output of "global" code that is not a module, i.e. files that do not have a top level import or export. modules are emitted to js files that have 1-1 mapping with their .ts files.
This is a source of confusion, and issue #1544 tracks making it an error.
Also for references, issue #17 tracks bundling (i.e. creating a single output from multiple modules).
@niemyjski to follow up, your files will be generated in src\configuration.js for instance. use --outDir to control the output location.
This issue describes TypeScript's support for ECMAScript 6 modules as implemented in #1983, #2197, and #2460.
TypeScript 1.5 supports ECMAScript 6 (ES6) modules. ES6 modules are effectively TypeScript external modules with a new syntax: ES6 modules are separately loaded source files that possibly import other modules and provide a number of externally accessible exports. ES6 modules feature several new export and import declarations. It is recommended that TypeScript libraries and applications be updated to use the new syntax, but this is not a requirement. The new ES6 module syntax coexists with TypeScript's original internal and external module constructs and the constructs can be mixed and matched at will.
In TypeScript 1.5, a source file is considered an external module if it contains at least one of the following:
export
modifier.export = Point
.import Math = require("math")
.An external module has a set of exports that are specified using various forms of export declarations. Those exports can be imported into local name bindings in other modules using various forms of import declarations.
An external module may designate a default export, which is an export with the reserved name
default
. A number of short-hand export and import declaration constructs exist to facilitate easy export and import of the default entity.For backwards compatibility with CommonJS and AMD style modules, TypeScript also supports export-equals declarations of the form
export = Point
. Unlike default export declarations, which are just shorthand for an export nameddefault
, export-equals declarations designate an entity to be exported in place of the actual module.As ES6 modules gain adoption, TypeScript's original export-equals and import-equals declarations are expected to become legacy.
Export Declarations
When a declaration specifies an
export
modifier, each declared name is exported from the containing module exactly as is the case with original TypeScript external modules. For example:Module members can also be exported using separate export declarations, and such declarations can specify different names for exports using
as
clauses. For example:An export declaration exports all meanings of a name. For example:
Re-exporting
An export declaration that specifies a
from
clause is a re-export. A re-export copies the exports of a given module to the current module without introducing local names.An
export *
declaration can be used to re-export all exports of another module. This is useful for creating modules that aggregate the exports of several other modules.An
export *
doesn't re-export default exports or exports with names that are already exported from the current module. For example, thetransform
export in the module above hides anytransform
export in the re-exported modules.Default Export
An export default declaration specifies an expression that becomes the default export of a module:
An export default declaration is just a short-hand way of exporting an entity with the name
default
. For example, the module above could instead be written:When an export default specifies a single identifier, all meanings of that identifier are exported:
An export default declaration can directly declare and export a function or class. The function or class can optionally be named so it can be referenced in the implementing module, but the exported name is always
default
.The following exports an unnamed function with the exported name
default
:The following exports a class with the local name
Greeter
and the exported namedefault
:Import Declarations
The exports of a module are imported using import declarations. Import declarations can optionally use
as
clauses to specify different local names for the imports. For example:As an alternative to individual imports, a namespace import can be used to import an entire module:
Default Import
The default export of a module is particularly easy to import:
The above is exactly equivalent to importing the export named
default
:It is possible to import both the default export and named exports in a single import declaration:
Bare Import
A "bare import" can be used to import a module only for its side-effects. Such an import creates no local name bindings.
CommonJS and AMD Code Generation
TypeScript supports down-level compilation of external modules using the new ES6 syntax.
-t ES3
or-t ES5
a module format must be chosen using-m CommonJS
or-m AMD
.-t ES6
the module format is implicitly assumed to be ECMAScript 6 and the compiler simply emits the original code with type annotations removed.When compiling down-level for CommonJS or AMD, named exports are emitted as properties on the loader supplied
exports
instance. This includes default exports which are emitted as assignments toexports.default
.Below are some examples of external modules and the code emitted for CommonJS and AMD.
A module with named exports:
A module with a default export:
A module with re-exports:
Importing a module:
Note that destructuring import declarations are rewritten to property accesses on the imported module object. This ensures that exported members can circularly reference each other. For example:
This generates the following code when compiled for CommonJS:
Interoperabitility
An existing external module that doesn't use
export =
is already ES6 compliant and can be imported using the new ES6 constructs with no additional work.An external module that uses
export =
to export another module or a "module like" entity can also be imported using the new ES6 constructs. In particular, the convenient destructuring imports can be used with such modules. The pattern of usingexport =
to export another module is common in .d.ts files that provide a CommonJS/AMD view of an internal module (e.g. angular.d.ts).A module that uses
export =
to export a non-module entity in place of the module itself must be imported using the existingimport x = require("foo")
syntax as is the case today.