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

docs: allowNamespaces in typescript now defaults to true #2449

Merged
merged 3 commits into from Feb 22, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
226 changes: 113 additions & 113 deletions docs/plugin-transform-typescript.md
Expand Up @@ -46,109 +46,10 @@ babel --plugins @babel/plugin-transform-typescript script.js

```javascript
require("@babel/core").transformSync("code", {
plugins: ["@babel/plugin-transform-typescript"]
plugins: ["@babel/plugin-transform-typescript"],
});
```


## Caveats

Because there are features of the TypeScript language which rely on the full type-system to be available to make changes at runtime. This section of caveats is quite long, however, it's worth noting that a few of these features are only found in older TypeScript codebases and have modern JavaScript equivalents which you are probably already using.

1. Since Babel does not type-check, code which is syntactically correct, but would fail the TypeScript type-checking may successfully get transformed, and often in unexpected or invalid ways.

1. This plugin does not support [`const enum`][const_enum]s because those require type information to compile.

**Workarounds**:
- Use the plugin [babel-plugin-const-enum](https://www.npmjs.com/package/babel-plugin-const-enum).
- Remove the `const`, which makes it available at runtime.

1. This plugin does not support [`export =`][exin] and [`import =`][exin], because those cannot be compiled to ES.next. These are a TypeScript only form of `import`/`export`.

**Workarounds**:
- Use the plugin [babel-plugin-replace-ts-export-assignment](https://www.npmjs.com/package/babel-plugin-replace-ts-export-assignment) to transform `export =`.
- Convert to using `export default` and `export const`, and `import x, {y} from "z"`.

1. Changes to your `tsconfig.json` are not reflected in babel. The build process will always behave as though [`isolatedModules`][iso-mods] is turned on, there are Babel-native alternative ways to set a lot of the [`tsconfig.json` options](#typescript-compiler-options) however.

1. **Q**: Why doesn't Babel allow export of a `var` or `let`?

**A**: The TypeScript compiler dynamically changes how these variables are used depending on whether or not the value is mutated. Ultimately, this depends on a type-model and is outside the scope of Babel. A best-effort implementation would transform context-dependent usages of the variable to always use the `Namespace.Value` version instead of `Value`, in case it was mutated outside of the current file. Allowing `var` or `let` from Babel (as the transform is not-yet-written) is therefore is more likely than not to present itself as a bug when used as-if it was not `const`.


### Impartial Namespace Support

If you have existing code which uses the TypeScript-only [namespace][namespace] features. Babel supports a subset of TypeScript's namespace features. If you are considering writing new code which uses namespace, using the ES2015 `import`/`export` is recommended instead. It's [not going away][not-disappearing], but there are modern alternatives.

* Type-only `namespace`s should be marked with `declare` and will subsequently be safely removed.

* `namespaces`s not marked with `declare` are experimental and disabled by default. Not enabling will result in an error: *"Namespace not marked type-only declare. Non-declarative namespaces are only supported experimentally in Babel."*

**Workaround**: Enable the `allowNamespaces` option.

* `export`ing a variable using `var` or `let` in a `namespace` will result in an error: *"Namespaces exporting non-const are not supported by Babel. Change to const or ..."*

**Workaround**: Use `const`. If some form of mutation is required, explicitly use an object with internal mutability.

* `namespace`s will not share their scope. In TypeScript, it is valid to refer to contextual items that a `namespace` extends without qualifying them, and the compiler will add the qualifier. In Babel, there is no type-model, and it is impossible to dynamically change references to match the established type of the parent object.

Consider this code:

```typescript
namespace N {
export const V = 1;
}
namespace N {
export const W = V;
}
```

The TypeScript compiler compiles it to something like this:

```javascript
var N = {};
(function (N) {
N.V = 1;
})(N);
(function (N) {
N.W = N.V;
})(N);
```

While Babel will transform it to something like this:

```javascript
var N;
(function (_N) {
const V = _N = 1;
})(N || (N = {}));
(function (_N) {
const W = V;
})(N || (N = {}));
```

As Babel doesn't understand the type of `N`, the reference to `V` will be `undefined` resulting in an error.

**Workaround**: Explicitly refer to values not in the same namespace definition, even if they would be in the scope according to TypeScript. Examples:

```typescript
namespace N {
export const V = 1;
}
namespace N {
export const W = N.V;
}
```

Or:

```typescript
namespace N {
export const V = 1;
export const W = V;
}
```

## Options

### `allowDeclareFields`
Expand All @@ -170,9 +71,15 @@ class A {

### `allowNamespaces`

`boolean`, defaults to `false` but will default to `true` in the [future](https://github.com/babel/notes/blob/master/2019/05/21.md#prs).
`boolean`, defaults to `true` but will default to `true` in the [future](https://github.com/babel/notes/blob/master/2019/05/21.md#prs).

Added in: `v7.5.0`
<details>
<summary>History</summary>
| Version | Changes |
| --- | --- |
| `v7.5.0` | Added `allowNamespaces`, defaults to `false` |
| `v7.13.0` | defaults to `true` |
</details>

Enables compilation of TypeScript namespaces.

Expand Down Expand Up @@ -235,9 +142,7 @@ equivalents in Babel can be enabled by some configuration options or plugins.
This option enables support for the "legacy" decorator proposal. You can enable it in Babel using the `@babel/plugin-proposal-decorators` plugin, but please be aware, there are some minor differences.
```js
module.exports = {
plugins: [
["@babel/plugin-proposal-decorators", { legacy: true }]
]
plugins: [["@babel/plugin-proposal-decorators", { legacy: true }]],
};
```
- `--importHelpers`
Expand All @@ -257,14 +162,14 @@ equivalents in Babel can be enabled by some configuration options or plugins.
If you are using a bundler (Webpack or Rollup), this option is set automatically.
If you are using `@babel/preset-env`, you can use the [`modules` option](https://babeljs.io/docs/en/babel-preset-env#modules); otherwise you can load the specific plugin.

| **`--module` value** | **`@babel/preset-env`'s `modules`** | **Single plugin** |
|:--------------------:|:-----------------------------------:|:-----------------:|
| `None` | `false` | / |
| `CommonJS` | `"commonjs"` or `"cjs"` | `@babel/plugin-transform-modules-commonjs` |
| `AMD` | `"amd"` | `@babel/plugin-transform-modules-amd` |
| `System` | `"systemjs"` | `@babel/plugin-transform-modules-systemjs` |
| `UMD` | `"umd"` | `@babel/plugin-transform-modules-umd` |
| `ES6` or `ES2015` | `false` | / |
| **`--module` value** | **`@babel/preset-env`'s `modules`** | **Single plugin** |
| :------------------: | :---------------------------------: | :----------------------------------------: |
| `None` | `false` | / |
| `CommonJS` | `"commonjs"` or `"cjs"` | `@babel/plugin-transform-modules-commonjs` |
| `AMD` | `"amd"` | `@babel/plugin-transform-modules-amd` |
| `System` | `"systemjs"` | `@babel/plugin-transform-modules-systemjs` |
| `UMD` | `"umd"` | `@babel/plugin-transform-modules-umd` |
| `ES6` or `ES2015` | `false` | / |

- `--outDir`
When using `@babel/cli`, you can set the [`--out-dir` option](https://babeljs.io/docs/en/babel-cli#compile-directories).
Expand All @@ -281,6 +186,101 @@ equivalents in Babel can be enabled by some configuration options or plugins.
- `--watch`, `-w`
When using `@babel/cli`, you can specify the [`--watch` option](https://babeljs.io/docs/en/babel-cli#compile-files).

## Caveats

Because there are features of the TypeScript language which rely on the full type-system to be available to make changes at runtime. This section of caveats is quite long, however, it's worth noting that a few of these features are only found in older TypeScript codebases and have modern JavaScript equivalents which you are probably already using.

1. Since Babel does not type-check, code which is syntactically correct, but would fail the TypeScript type-checking may successfully get transformed, and often in unexpected or invalid ways.

1. This plugin does not support [`const enum`][const_enum]s because those require type information to compile.

**Workarounds**:

- Use the plugin [babel-plugin-const-enum](https://www.npmjs.com/package/babel-plugin-const-enum).
- Remove the `const`, which makes it available at runtime.

1. This plugin does not support [`export =`][exin] and [`import =`][exin], because those cannot be compiled to ES.next. These are a TypeScript only form of `import`/`export`.

**Workarounds**:

- Use the plugin [babel-plugin-replace-ts-export-assignment](https://www.npmjs.com/package/babel-plugin-replace-ts-export-assignment) to transform `export =`.
- Convert to using `export default` and `export const`, and `import x, {y} from "z"`.

1. Changes to your `tsconfig.json` are not reflected in babel. The build process will always behave as though [`isolatedModules`][iso-mods] is turned on, there are Babel-native alternative ways to set a lot of the [`tsconfig.json` options](#typescript-compiler-options) however.

1. **Q**: Why doesn't Babel allow export of a `var` or `let`?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not introduced by this PR, but I don't understand this question.

Babel allows exporting anything 🤔


**A**: The TypeScript compiler dynamically changes how these variables are used depending on whether or not the value is mutated. Ultimately, this depends on a type-model and is outside the scope of Babel. A best-effort implementation would transform context-dependent usages of the variable to always use the `Namespace.Value` version instead of `Value`, in case it was mutated outside of the current file. Allowing `var` or `let` from Babel (as the transform is not-yet-written) is therefore is more likely than not to present itself as a bug when used as-if it was not `const`.

### Impartial Namespace Support

If you have existing code which uses the TypeScript-only [namespace][namespace] features. Babel supports a subset of TypeScript's namespace features. If you are considering writing new code which uses namespace, using the ES2015 `import`/`export` is recommended instead. It's [not going away][not-disappearing], but there are modern alternatives.

- Type-only `namespace`s should be marked with `declare` and will subsequently be safely removed.

- `export`ing a variable using `var` or `let` in a `namespace` will result in an error: _"Namespaces exporting non-const are not supported by Babel. Change to const or ..."_

**Workaround**: Use `const`. If some form of mutation is required, explicitly use an object with internal mutability.

- `namespace`s will not share their scope. In TypeScript, it is valid to refer to contextual items that a `namespace` extends without qualifying them, and the compiler will add the qualifier. In Babel, there is no type-model, and it is impossible to dynamically change references to match the established type of the parent object.

Consider this code:

```typescript
namespace N {
export const V = 1;
}
namespace N {
export const W = V;
}
```

The TypeScript compiler compiles it to something like this:

```javascript
var N = {};
(function(N) {
N.V = 1;
})(N);
(function(N) {
N.W = N.V;
})(N);
```

While Babel will transform it to something like this:

```javascript
var N;
(function(_N) {
const V = (_N = 1);
})(N || (N = {}));
(function(_N) {
const W = V;
})(N || (N = {}));
```

As Babel doesn't understand the type of `N`, the reference to `V` will be `undefined` resulting in an error.

**Workaround**: Explicitly refer to values not in the same namespace definition, even if they would be in the scope according to TypeScript. Examples:

```typescript
namespace N {
export const V = 1;
}
namespace N {
export const W = N.V;
}
```

Or:

```typescript
namespace N {
export const V = 1;
export const W = V;
}
```

[const_enum]: https://www.typescriptlang.org/docs/handbook/enums.html#const-enums
[exin]: https://www.typescriptlang.org/docs/handbook/modules.html#export--and-import--require
[iso-mods]: https://www.typescriptlang.org/docs/handbook/compiler-options.html
Expand Down