Skip to content

Commit e545ec4

Browse files
Move ECMAScript plugins to @babel/core
1 parent b4eb5de commit e545ec4

File tree

1 file changed

+158
-0
lines changed

1 file changed

+158
-0
lines changed
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
- Repo: `babel/babel`
2+
- Start Date: 2021-02-12
3+
- RFC PR: <!-- leave this empty, to be filled in later -->
4+
- Related Issues: <!-- if relevant -->
5+
- Authors: [Nicolò Ribaudo](https://github.com/nicolo-ribaudo)
6+
- Champion: [Nicolò Ribaudo](https://github.com/nicolo-ribaudo)
7+
- Implementors: <!-- the names of everyone who will work on the PR. you can leave this empty if you would like someone else to work on it -->
8+
9+
# Summary
10+
11+
This RFC proposes moving all the existing plugins under the `@babel/` namespace that transform standard ECMAScript syntax into the `@babel/core` package.
12+
13+
From a user perspective this change should be almost invisible, but it gives us better stability and great implementation advantages by drastically reducing the ways in which packages at different versions can be mixed together.
14+
15+
# Motivation
16+
17+
One of the biggest problems that our architecture leads to is that users could mix any version of the "core" packages (`@babel/core`, `@babel/traverse`, `@babel/parser`, etc) with any version of the transform plugins (`@babel/plugin-transform-classes`, `@babel/plugin-proposal-object-rest-spread`, etc).
18+
19+
This is particularly problematic when a plugin version is _ahead_ of the core packages versions.
20+
21+
1. When a proposal moves to Stage 4 and is enabled by default in `@babel/parser`, we still need to defensively enable the syntax plugins until the next major release. For example, the `objectRestSpread` plugin was enabled in `@babel/[email protected]` but `@babel/plugin-proposal-object-rest-spread` still needs to inherit from `@babel/plugin-syntax-object-rest-spread` ([source](https://github.com/babel/babel/blob/c22e72eb24b9dd83d43e693067d9c856acf9977d/packages/babel-plugin-proposal-object-rest-spread/src/index.js#L214)) in case someone is using the plugin with `@babel/[email protected]`.
22+
2. When we introduce a new helper in `@babel/helpers`, we cannot use it safely in any of the transform plugins because users could still have an old `@babel/helpers` dependency. We always need to first check if the helper is defined in the used `@babel/core`/`@babel/helper` version, and fallback to a completely different transformation if it's not ([source](https://github.com/babel/babel/blob/58d2f41930859b5d46001e3ae365ffc09e5a8463/packages/babel-plugin-transform-for-of/src/index.js#L180-L184))
23+
3. When we introduce a new utility function in `@babel/traverse`, we cannot safely rely on it in the transform plugins and we must defensively handle old `@babel/traverse` versions, either by working around fixed `@babel/traverse` bugs ([source](https://github.com/babel/babel/blob/c22e72eb24b9dd83d43e693067d9c856acf9977d/packages/babel-plugin-proposal-object-rest-spread/src/index.js#L6-L15)) or by inlining the new utility in the plugin itself or in a new `@babel/helper-*` package.
24+
25+
# Detailed design
26+
27+
## API
28+
29+
Currently plugin can be specified in one of the following ways:
30+
31+
- `@babel/plugin-[feature]` or `@babel/[feature]`
32+
- `@org/babel-plugin-[feature]` or `@org/[feature]`
33+
- `babel-plugin-[feature]` or `[feature]`
34+
- `module:[package name]`
35+
36+
We can introduce a new protocol similar to `module:`:
37+
38+
- `internal:[feature]`
39+
40+
A configuration to transform classes will thus look like this:
41+
42+
```json
43+
{
44+
"plugins": ["internal:transform-classes"]
45+
}
46+
```
47+
48+
We also need to export a new function from `@babel/core` that can be used to query the list of available internal plugins:
49+
50+
```ts
51+
export function internalPluginAvailable(name: string): boolean;
52+
```
53+
54+
## Monorepo structure
55+
56+
All the plugins for stable ECMAScript features should be moved from the `./packages/babel-plugin-*` folders into `./packages/babel-core/src/transform-plugins/*`.
57+
58+
To make it easier to run tests for an individual plugin (currently you can use `yarn jest transform-classes`), we can still keep tests in separate internal workspaces, for example in `/test/core-plugins/[plugin name]`.
59+
60+
## New Stage 4 proposals
61+
62+
When an ECMAScript proposal is promoted from Stage 3 to Stage 4, we should:
63+
1. Enable it by default in `@babel/parser`
64+
2. Copy the transform plugin into `@babel/core`
65+
3. Enable it by default in `@babel/preset-env`
66+
4. Archive the `@babel/plugin-syntax-*` syntax plugin
67+
5. Archive the `@babel/plugin-proposal-*` transform plugin
68+
69+
`@babel/preset-env` should still depend on the archived proposal plugin: since it's a separate package, it could still be used with an older `@babel/core` version that doesn't support the new `internal:*` plugin.
70+
71+
It can call, for example, `babel.internalPluginAvailable("internal:transform-classes")` to decide whether it should enable the internal plugin or fallback to the old proposal implementation.
72+
73+
# Drawbacks
74+
75+
This proposal significantly increases the size of the `@babel/core` package.
76+
However, most of the projects depending on `@babel/core` also depend on `@babel/preset-env` and would already download all the plugins from npm anyway.
77+
78+
If we discover that the additional bundle size causes problems for other tools depending on `@babel/core` but not on `@babel/preset-env`, we could extract and publish two new packages from `@babel/core`:
79+
- `@babel/config-loader`, which handles config loading and plugin/preset resolution;
80+
- `@babel/transform`, which exposes the `transform`, `transformFromAst` and `parse` methods that take a resolved config (ideally that doesn't need FS access) and parse/transform the input.
81+
82+
# Alternatives
83+
84+
## To the whole RFC
85+
86+
- Keep the current status.
87+
- Drop SemVer for the `@babel/core` `peerDependency` of plugins, so that we can require an higher `@babel/core` version when needed.
88+
89+
## To specific parts of the RFC
90+
91+
Instead of the `internalPluginAvailable` function, we could export an `internalPluginName` function with the following signature:
92+
93+
```js
94+
export function internalPluginName(name: string): string | undefined;
95+
```
96+
97+
This function takes a plugin name (either internal or external) as the input, and returns the internal plugin name when it's available:
98+
99+
```js
100+
internalPluginName("internal:transform-classes"); // "internal:transform-classes"
101+
internalPluginName("@babel/plugin-transform-classes"); // "internal:transform-classes"
102+
internalPluginName("@babel/plugin-unknown"); // undefined
103+
```
104+
105+
`@babel/core` can then conditionally enable plugins like this:
106+
107+
```js
108+
import transformClasses from "@babel/plugin-transform-classes";
109+
110+
// ...
111+
112+
plugins: [
113+
babel.internalPluginName("@babel/plugin-transform-classes") ?? transformClasses
114+
]
115+
```
116+
117+
by doing so, we could make old `@babel/preset-env` versions aware of new `@babel/core` internal plugins, so that it doesn't accidentally enable an older proposal implementation after that it becomes Stage 4 and is moved to stage 4.
118+
119+
Is it worth the additional complexity? (we would need a list of plugin names mappings in `@babel/core`).
120+
121+
# Adoption strategy
122+
123+
For most Babel users, this RFC doesn't require any direct action.
124+
125+
If they depend on one of the packages that will be moved to `@babel/core`, they can remove that dependency and use the `internal:` plugin in their configuraiton.
126+
127+
# How we teach this
128+
129+
Since this RFC doesn't have a big impact on how to use Babel, documenting the new `internal:` plugin is enough.
130+
131+
In the next major release, we can publish an empty placeholder with a deprecation error for all the plugins moved to `@babel/core`, similarly to what we did with `@babel/[email protected]`.
132+
133+
# Open questions
134+
135+
1. If Babel recognizes a plugin for a proposal which has then been moved to `@babel/core`, should it warn? Should it silently replace it with the `internal:` plugin?
136+
137+
2. Should `@babel/core` export these plugins? (e.g. `@babel/core/internal-plugins/transform-classes`)
138+
139+
## Frequently Asked Questions
140+
141+
**Why don't we move proposal plugins to `@babel/core`?**
142+
143+
Proposals are inherently unstable, and can have breaking changes. People shouldn't use them in their applications unless they are willing to keep up to date with the proposal evolution, and we shouldn't make it easier to enable those plugins.
144+
145+
**Why don't we move Flow, TypeScript and JSX plugins to `@babel/core`?**
146+
147+
Many users won't need these plugins (and no one will need both the Flow and the TypeScript plugin at the same time), so we can avoid downloading unnecessary code to their `node_modules`.
148+
149+
Additionally, the Flow and TypeScript plugin aren't usually affected by version problems because their transforms are self-contained (they mostly just delete nodes).
150+
151+
## Related Discussions
152+
153+
<!--
154+
This section is optional but suggested.
155+
156+
If there is an issue, pull request, or other URL that provides useful
157+
context for this proposal, please include those links here.
158+
-->

0 commit comments

Comments
 (0)