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

Re-do the esmoduleinterop docs #873

Merged
merged 2 commits into from
Aug 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,42 @@ instead of:
import * as React from "react";
```

When the module **does not** specify a default export.
When the module **does not** explicitly specify a default export.

This does not affect the JavaScript emitted by TypeScript, it only for the type checking.
For example, without `allowSyntheticDefaultImports` as true:

```ts twoslash
// @errors: 1259
// @checkJs
// @allowJs
// @esModuleInterop: false
// @filename: utilFunctions.js
// @noImplicitAny: false
const getStringLength = (str) => str.length;

module.exports = {
getStringLength,
};

// @filename: index.ts
import utils from "./utilFunctions";

const count = utils.getStringLength("Check JS");
```

This code raises an error because there isn't a `default` object which you can import. Even though it feels like it should.
For convenience, transpilers like Babel will automatically create a default if one isn't created. Making the module look a bit more like:

```js
// @filename: utilFunctions.js
const getStringLength = (str) => str.length;
const allFunctions = {
getStringLength,
};

module.exports = allFunctions;
module.exports.default = allFunctions;
```

This flag does not affect the JavaScript emitted by TypeScript, it only for the type checking.
This option brings the behavior of TypeScript in-line with Babel, where extra code is emitted to make using a default export of a module more ergonomic.
81 changes: 55 additions & 26 deletions packages/tsconfig-reference/copy/en/options/esModuleInterop.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,70 @@ display: "ES Module Interop"
oneline: "Emit additional JS to ease support for importing commonjs modules"
---

Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports.
By default (with `esModuleInterop` false or not set) TypeScript treats CommonJS/AMD/UMD modules similar to ES6 modules. In doing this, there are two parts in particular which turned out to be flawed assumptions:

TypeScript adheres to the EcmaScript standard for modules, which means that a file with exports would have to specifically
include a `default` export in order to support syntax like `import React from "react"`.
This export pattern is rare in modules for CommonJS. For example, without `esModuleInterop` as true:
- a namespace import like `import * as moment from "moment"` acts the same as `const moment = require("moment")`

- a default import like `import moment as "moment"` acts the same as `const moment = require("moment").default`

This mis-match causes these two issues:

- the ES6 modules spec states that a namespace import (`import * as x`) can only be an object, by having TypeScript
treating it the same as `= require("x")` then TypeScript allowed for the import to be treated as a function and be callable. This breaks the spec's recommendations.

- while accurate to the ES6 modules spec, most libraries with CommonJS/AMD/UMD modules didn't conform as strictly as TypeScript's implementation.

Turning on `esModuleInterop` will fix both of these problems in the code transpiled by TypeScript. The first changes the behavior in the compiler,the second is fixed by two new helper functions which provide a shim to ensure compatibility in the emitted JavaScript:

```ts
import * as fs from "fs";
import _ from "lodash";

fs.readFileSync("file.txt", "utf8");
_.chunk(["a", "b", "c", "d"], 2);
```

With `esModuleInterop` disabled:

```ts twoslash
// @checkJs
// @allowJs
// @allowSyntheticDefaultImports
// @filename: utilFunctions.js
// @noImplicitAny: false
const getStringLength = str => str.length;
// @noErrors
// @showEmit
// @esModuleInterop: false
// @module: commonjs
import * as fs from "fs";
import _ from "lodash";

fs.readFileSync("file.txt", "utf8");
_.chunk(["a", "b", "c", "d"], 2);
```

module.exports = {
getStringLength
};
With `esModuleInterop` set to `true`:

// @filename: index.ts
import utils from "./utilFunctions";
```ts twoslash
// @noErrors
// @showEmit
// @esModuleInterop
// @module: commonjs
import * as fs from "fs";
import _ from "lodash";

const count = utils.getStringLength("Check JS");
fs.readFileSync("file.txt", "utf8");
_.chunk(["a", "b", "c", "d"], 2);
```

This won't work because there isn't a `default` object which you can import. Even though it feels like it should.
For convenience, transpilers like Babel will automatically create a default if one isn't created. Making the module look a bit more like:
_Note_: You can make JS emit terser by enabling [`importHelpers`](#importHelpers):

```js
// @filename: utilFunctions.js
const getStringLength = str => str.length;
const allFunctions = {
getStringLength
};
```ts twoslash
// @noErrors
// @showEmit
// @esModuleInterop
// @importHelpers
// @module: commonjs
import * as fs from "fs";
import _ from "lodash";

module.exports = allFunctions;
fs.readFileSync("file.txt", "utf8");
_.chunk(["a", "b", "c", "d"], 2);
```

Turning on this compiler flag will also enable [`allowSyntheticDefaultImports`](#allowSyntheticDefaultImports).
Enabling `esModuleInterop` will also enable [`allowSyntheticDefaultImports`](#allowSyntheticDefaultImports).
1 change: 1 addition & 0 deletions packages/tsconfig-reference/scripts/tsconfigRules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const recommended: CompilerOptionName[] = [
"strictFunctionTypes",
"noImplicitThis",
"noImplicitAny",
"esModuleInterop",
];

type RootProperties = "files" | "extends" | "include" | "exclude";
Expand Down