@rolldown/plugin-babel 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +203 -0
- package/dist/index.d.mts +56 -0
- package/dist/index.mjs +360 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026-present, rolldown/plugins repository contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# @rolldown/plugin-babel
|
|
2
|
+
|
|
3
|
+
Rolldown plugin for transforming code with [Babel](https://babeljs.io/).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @rolldown/plugin-babel @babel/core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
import babelPlugin from '@rolldown/plugin-babel'
|
|
15
|
+
|
|
16
|
+
export default {
|
|
17
|
+
plugins: [
|
|
18
|
+
babelPlugin({
|
|
19
|
+
presets: [['@babel/preset-env', { targets: { chrome: '100' } }]],
|
|
20
|
+
}),
|
|
21
|
+
],
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The plugin automatically configures Babel's parser for `.jsx`, `.ts`, and `.tsx` files.
|
|
26
|
+
|
|
27
|
+
> **Note:** This plugin does not load Babel configuration files (e.g., `babel.config.js`, `.babelrc`). All Babel options must be passed directly through the plugin options.
|
|
28
|
+
|
|
29
|
+
## Options
|
|
30
|
+
|
|
31
|
+
### `include`
|
|
32
|
+
|
|
33
|
+
- **Type:** `string | RegExp | (string | RegExp)[]`
|
|
34
|
+
|
|
35
|
+
If specified, only files matching the pattern will be processed.
|
|
36
|
+
|
|
37
|
+
### `exclude`
|
|
38
|
+
|
|
39
|
+
- **Type:** `string | RegExp | (string | RegExp)[]`
|
|
40
|
+
- **Default:** `/[\/\\]node_modules[\/\\]/`
|
|
41
|
+
|
|
42
|
+
Files matching the pattern will be skipped.
|
|
43
|
+
|
|
44
|
+
### `sourceMap`
|
|
45
|
+
|
|
46
|
+
- **Type:** `boolean`
|
|
47
|
+
- **Default:** `true`
|
|
48
|
+
|
|
49
|
+
Set to `false` to skip source map generation for better performance.
|
|
50
|
+
|
|
51
|
+
### `presets`
|
|
52
|
+
|
|
53
|
+
- **Type:** `(babel.PresetItem | RolldownBabelPreset)[]`
|
|
54
|
+
|
|
55
|
+
List of Babel presets to apply. Supports both standard Babel presets and Rolldown-enhanced presets with per-file filtering (see [Rolldown Babel Presets](#rolldown-babel-presets)).
|
|
56
|
+
|
|
57
|
+
### `plugins`
|
|
58
|
+
|
|
59
|
+
- **Type:** `babel.PluginItem[]`
|
|
60
|
+
|
|
61
|
+
List of Babel plugins to apply.
|
|
62
|
+
|
|
63
|
+
### `overrides`
|
|
64
|
+
|
|
65
|
+
- **Type:** `InnerTransformOptions[]`
|
|
66
|
+
|
|
67
|
+
Array of additional configurations that are merged into the current configuration. Use with Babel's `test`/`include`/`exclude` options to conditionally apply overrides.
|
|
68
|
+
|
|
69
|
+
### Other Babel options
|
|
70
|
+
|
|
71
|
+
The following [Babel options](https://babeljs.io/docs/options) are forwarded directly:
|
|
72
|
+
|
|
73
|
+
`assumptions`, `auxiliaryCommentAfter`, `auxiliaryCommentBefore`, `comments`, `compact`, `cwd`, `generatorOpts`, `parserOpts`, `retainLines`, `shouldPrintComment`, `targets`, `wrapPluginVisitorMethod`
|
|
74
|
+
|
|
75
|
+
## Rolldown Babel Presets
|
|
76
|
+
|
|
77
|
+
Standard Babel presets are applied to every file. When a preset should only apply to certain files, wrap it in a `RolldownBabelPreset` with a `filter`. Use the `defineRolldownBabelPreset` helper for type checking:
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
import babelPlugin, { defineRolldownBabelPreset } from '@rolldown/plugin-babel'
|
|
81
|
+
|
|
82
|
+
const myReactPreset = defineRolldownBabelPreset({
|
|
83
|
+
preset: ['@babel/preset-react'],
|
|
84
|
+
rolldown: {
|
|
85
|
+
filter: {
|
|
86
|
+
id: /\.tsx?$/,
|
|
87
|
+
moduleType: ['tsx', 'jsx'],
|
|
88
|
+
code: /from ['"]react['"]/,
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
export default {
|
|
94
|
+
plugins: [
|
|
95
|
+
babelPlugin({
|
|
96
|
+
presets: [myReactPreset],
|
|
97
|
+
}),
|
|
98
|
+
],
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Filter dimensions
|
|
103
|
+
|
|
104
|
+
All filter dimensions are optional. When multiple dimensions are specified, all must match for the preset to apply.
|
|
105
|
+
|
|
106
|
+
#### `id`
|
|
107
|
+
|
|
108
|
+
Match files by path. Accepts a string glob, RegExp, array, or `{ include, exclude }` object.
|
|
109
|
+
|
|
110
|
+
```js
|
|
111
|
+
{ id: /\.tsx$/ }
|
|
112
|
+
{ id: '**/*.tsx' }
|
|
113
|
+
{ id: [/\.tsx$/, /\.jsx$/] }
|
|
114
|
+
{ id: { include: [/\.tsx$/], exclude: [/test\.tsx$/] } }
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
#### `moduleType`
|
|
118
|
+
|
|
119
|
+
Match by Rolldown module type. Accepts a string array or `{ include }` object.
|
|
120
|
+
|
|
121
|
+
```js
|
|
122
|
+
{
|
|
123
|
+
moduleType: ['tsx', 'jsx']
|
|
124
|
+
}
|
|
125
|
+
{
|
|
126
|
+
moduleType: {
|
|
127
|
+
include: ['tsx']
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
#### `code`
|
|
133
|
+
|
|
134
|
+
Match by file content. Accepts a RegExp, array, or `{ include, exclude }` object.
|
|
135
|
+
|
|
136
|
+
```js
|
|
137
|
+
{ code: /import React/ }
|
|
138
|
+
{ code: { include: [/import React/], exclude: [/\/\/ @no-transform/] } }
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### `configResolvedHook`
|
|
142
|
+
|
|
143
|
+
When used with Vite, a preset can define a `configResolvedHook` callback to conditionally enable or disable itself based on the resolved Vite config. The callback receives the `ResolvedConfig` and should return `false` to remove the preset.
|
|
144
|
+
|
|
145
|
+
```js
|
|
146
|
+
defineRolldownBabelPreset({
|
|
147
|
+
preset: ['@babel/preset-react'],
|
|
148
|
+
rolldown: {
|
|
149
|
+
filter: { id: /\.[jt]sx$/ },
|
|
150
|
+
configResolvedHook(config) {
|
|
151
|
+
// Only apply during production builds
|
|
152
|
+
return config.command === 'build'
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
})
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
When running without Vite (pure Rolldown), `configResolvedHook` is ignored.
|
|
159
|
+
|
|
160
|
+
### `applyToEnvironmentHook`
|
|
161
|
+
|
|
162
|
+
When used with Vite, a preset can define an `applyToEnvironmentHook` callback to conditionally enable or disable itself based on the Vite environment. The callback receives the `PartialEnvironment` and should return `false` to remove the preset for that environment.
|
|
163
|
+
|
|
164
|
+
```js
|
|
165
|
+
defineRolldownBabelPreset({
|
|
166
|
+
preset: ['@babel/preset-react'],
|
|
167
|
+
rolldown: {
|
|
168
|
+
filter: { id: /\.[jt]sx$/ },
|
|
169
|
+
applyToEnvironmentHook(environment) {
|
|
170
|
+
// Only apply in the client environment
|
|
171
|
+
return environment.name === 'client'
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
})
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
When running without Vite (pure Rolldown), `applyToEnvironmentHook` is ignored.
|
|
178
|
+
|
|
179
|
+
### How preset filters work
|
|
180
|
+
|
|
181
|
+
Preset filters operate at two levels:
|
|
182
|
+
|
|
183
|
+
1. **Per-file filtering** — When Rolldown calls the transform hook, each preset's filter is checked against the current file. Presets whose filter doesn't match are skipped for that file.
|
|
184
|
+
|
|
185
|
+
2. **Transform hook pre-filtering** — The plugin computes a union of all preset filters to tell Rolldown which files to send to the transform hook in the first place. This avoids calling into the plugin for files that no preset would match.
|
|
186
|
+
|
|
187
|
+
You can mix standard Babel presets and Rolldown presets freely:
|
|
188
|
+
|
|
189
|
+
```js
|
|
190
|
+
babelPlugin({
|
|
191
|
+
presets: [
|
|
192
|
+
'@babel/preset-env', // applied to all files
|
|
193
|
+
{
|
|
194
|
+
preset: ['@babel/preset-react'],
|
|
195
|
+
rolldown: { filter: { id: /\.[jt]sx$/ } },
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
})
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## License
|
|
202
|
+
|
|
203
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as babel from "@babel/core";
|
|
2
|
+
import { GeneralHookFilter, ModuleTypeFilter, Plugin } from "rolldown";
|
|
3
|
+
import { Plugin as Plugin$1, ResolvedConfig } from "vite";
|
|
4
|
+
|
|
5
|
+
//#region src/rolldownPreset.d.ts
|
|
6
|
+
type PartialEnvironment = Parameters<NonNullable<Plugin$1['applyToEnvironment']>>[0];
|
|
7
|
+
type RolldownBabelPreset = {
|
|
8
|
+
preset: babel.PresetItem;
|
|
9
|
+
rolldown: {
|
|
10
|
+
filter?: {
|
|
11
|
+
id?: GeneralHookFilter;
|
|
12
|
+
moduleType?: ModuleTypeFilter;
|
|
13
|
+
code?: GeneralHookFilter;
|
|
14
|
+
};
|
|
15
|
+
applyToEnvironmentHook?: (environment: PartialEnvironment) => boolean;
|
|
16
|
+
configResolvedHook?: (config: ResolvedConfig) => boolean;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
type RolldownBabelPresetItem = babel.PresetItem | RolldownBabelPreset;
|
|
20
|
+
declare function defineRolldownBabelPreset(preset: RolldownBabelPreset): RolldownBabelPreset;
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/options.d.ts
|
|
23
|
+
interface InnerTransformOptions extends Pick<babel.InputOptions, 'assumptions' | 'auxiliaryCommentAfter' | 'auxiliaryCommentBefore' | 'exclude' | 'comments' | 'compact' | 'cwd' | 'generatorOpts' | 'include' | 'parserOpts' | 'plugins' | 'retainLines' | 'shouldPrintComment' | 'targets' | 'wrapPluginVisitorMethod'> {
|
|
24
|
+
/**
|
|
25
|
+
* List of presets (a set of plugins) to load and use
|
|
26
|
+
*
|
|
27
|
+
* Default: `[]`
|
|
28
|
+
*/
|
|
29
|
+
presets?: RolldownBabelPresetItem[] | undefined;
|
|
30
|
+
}
|
|
31
|
+
interface PluginOptions extends Omit<InnerTransformOptions, 'include' | 'exclude'> {
|
|
32
|
+
/**
|
|
33
|
+
* If specified, only files matching the pattern will be processed by babel.
|
|
34
|
+
*/
|
|
35
|
+
include?: InnerTransformOptions['include'];
|
|
36
|
+
/**
|
|
37
|
+
* If any of patterns match, babel will not process the file.
|
|
38
|
+
* @default `/[\/\\]node_modules[\/\\]/`
|
|
39
|
+
*/
|
|
40
|
+
exclude?: InnerTransformOptions['exclude'];
|
|
41
|
+
/**
|
|
42
|
+
* If false, skips source map generation. This will improve performance.
|
|
43
|
+
* @default true
|
|
44
|
+
*/
|
|
45
|
+
sourceMap?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Allows users to provide an array of options that will be merged into the current configuration one at a time.
|
|
48
|
+
* This feature is best used alongside the "test"/"include"/"exclude" options to provide conditions for which an override should apply
|
|
49
|
+
*/
|
|
50
|
+
overrides?: InnerTransformOptions[] | undefined;
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/index.d.ts
|
|
54
|
+
declare function babelPlugin(rawOptions: PluginOptions): Promise<Plugin>;
|
|
55
|
+
//#endregion
|
|
56
|
+
export { type RolldownBabelPreset, babelPlugin as default, defineRolldownBabelPreset };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import picomatch from "picomatch";
|
|
2
|
+
import * as babel from "@babel/core";
|
|
3
|
+
|
|
4
|
+
//#region src/utils.ts
|
|
5
|
+
function arrayify(value) {
|
|
6
|
+
return Array.isArray(value) ? value : [value];
|
|
7
|
+
}
|
|
8
|
+
function filterMap(array, predicate) {
|
|
9
|
+
const newArray = [];
|
|
10
|
+
for (const [index, item] of array.entries()) {
|
|
11
|
+
const result = predicate(item, index);
|
|
12
|
+
if (result) newArray.push(result.value);
|
|
13
|
+
}
|
|
14
|
+
return newArray;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/rolldownPreset.ts
|
|
19
|
+
function compilePattern(pattern) {
|
|
20
|
+
if (pattern instanceof RegExp) return (value) => pattern.test(value);
|
|
21
|
+
return picomatch(pattern);
|
|
22
|
+
}
|
|
23
|
+
function compilePatterns(patterns) {
|
|
24
|
+
const matchers = patterns.map(compilePattern);
|
|
25
|
+
return (value) => matchers.some((m) => m(value));
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Pre-compile a GeneralHookFilter into a single matcher function.
|
|
29
|
+
* Returns undefined when the filter matches everything.
|
|
30
|
+
*/
|
|
31
|
+
function compileGeneralHookFilter(filter) {
|
|
32
|
+
if (filter == null) return void 0;
|
|
33
|
+
let include;
|
|
34
|
+
let exclude;
|
|
35
|
+
if (typeof filter === "string" || filter instanceof RegExp) include = [filter];
|
|
36
|
+
else if (Array.isArray(filter)) include = filter;
|
|
37
|
+
else {
|
|
38
|
+
include = filter.include != null ? arrayify(filter.include) : void 0;
|
|
39
|
+
exclude = filter.exclude != null ? arrayify(filter.exclude) : void 0;
|
|
40
|
+
}
|
|
41
|
+
const includeMatcher = include ? compilePatterns(include) : void 0;
|
|
42
|
+
const excludeMatcher = exclude ? compilePatterns(exclude) : void 0;
|
|
43
|
+
if (includeMatcher && excludeMatcher) return (value) => !excludeMatcher(value) && includeMatcher(value);
|
|
44
|
+
if (excludeMatcher) return (value) => !excludeMatcher(value);
|
|
45
|
+
return includeMatcher;
|
|
46
|
+
}
|
|
47
|
+
function compileModuleTypeFilter(filter) {
|
|
48
|
+
if (filter == null) return void 0;
|
|
49
|
+
const types = Array.isArray(filter) ? filter : filter.include ?? [];
|
|
50
|
+
if (types.length === 0) return void 0;
|
|
51
|
+
const typeSet = new Set(types);
|
|
52
|
+
return (value) => typeSet.has(value);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Pre-compile a preset's filter into a single matcher function
|
|
56
|
+
* that checks all dimensions (id, moduleType, code) at once.
|
|
57
|
+
* Returns undefined when the filter matches everything.
|
|
58
|
+
*/
|
|
59
|
+
function compilePresetFilter(filter) {
|
|
60
|
+
if (!filter) return void 0;
|
|
61
|
+
const matchId = compileGeneralHookFilter(filter.id);
|
|
62
|
+
const matchModuleType = compileModuleTypeFilter(filter.moduleType);
|
|
63
|
+
const matchCode = compileGeneralHookFilter(filter.code);
|
|
64
|
+
if (!matchId && !matchModuleType && !matchCode) return void 0;
|
|
65
|
+
return (ctx) => (!matchId || matchId(ctx.id)) && (!matchModuleType || matchModuleType(ctx.moduleType)) && (!matchCode || matchCode(ctx.code));
|
|
66
|
+
}
|
|
67
|
+
function defineRolldownBabelPreset(preset) {
|
|
68
|
+
return preset;
|
|
69
|
+
}
|
|
70
|
+
function convertToBabelPresetItem(ctx, preset, compiledFilter) {
|
|
71
|
+
if (typeof preset !== "object" || !("rolldown" in preset)) return { value: preset };
|
|
72
|
+
if (compiledFilter && !compiledFilter(ctx)) return void 0;
|
|
73
|
+
return { value: preset.preset };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/options.ts
|
|
78
|
+
function resolveOptions(options) {
|
|
79
|
+
return {
|
|
80
|
+
...options,
|
|
81
|
+
exclude: options.exclude ?? [/[/\\]node_modules[/\\]/],
|
|
82
|
+
sourceMap: options.sourceMap ?? true
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function compilePresetFilters(presets) {
|
|
86
|
+
return presets.map((preset) => typeof preset === "object" && "rolldown" in preset ? compilePresetFilter(preset.rolldown.filter) : void 0);
|
|
87
|
+
}
|
|
88
|
+
function filterPresetArrayWithEnvironment(presets, environment) {
|
|
89
|
+
return presets.filter((preset) => {
|
|
90
|
+
if (typeof preset !== "object" || !("rolldown" in preset)) return true;
|
|
91
|
+
if (!preset.rolldown.applyToEnvironmentHook) return true;
|
|
92
|
+
return preset.rolldown.applyToEnvironmentHook(environment);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
function filterPresetsWithEnvironment(options, environment) {
|
|
96
|
+
return {
|
|
97
|
+
...options,
|
|
98
|
+
presets: options.presets ? filterPresetArrayWithEnvironment(options.presets, environment) : void 0,
|
|
99
|
+
overrides: options.overrides?.map((override) => override.presets ? {
|
|
100
|
+
...override,
|
|
101
|
+
presets: filterPresetArrayWithEnvironment(override.presets, environment)
|
|
102
|
+
} : override)
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function filterPresetArray(presets, config) {
|
|
106
|
+
return presets.filter((preset) => {
|
|
107
|
+
if (typeof preset !== "object" || !("rolldown" in preset)) return true;
|
|
108
|
+
if (!preset.rolldown.configResolvedHook) return true;
|
|
109
|
+
return preset.rolldown.configResolvedHook(config);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
function filterPresetsWithConfigResolved(options, config) {
|
|
113
|
+
return {
|
|
114
|
+
...options,
|
|
115
|
+
presets: options.presets ? filterPresetArray(options.presets, config) : void 0,
|
|
116
|
+
overrides: options.overrides?.map((override) => override.presets ? {
|
|
117
|
+
...override,
|
|
118
|
+
presets: filterPresetArray(override.presets, config)
|
|
119
|
+
} : override)
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Pre-compile all preset filters and return a function that
|
|
124
|
+
* converts options to babel options for a given context.
|
|
125
|
+
*/
|
|
126
|
+
function createBabelOptionsConverter(options) {
|
|
127
|
+
const presetFilters = options.presets ? compilePresetFilters(options.presets) : void 0;
|
|
128
|
+
const overridePresetFilters = options.overrides?.map((override) => override.presets ? compilePresetFilters(override.presets) : void 0);
|
|
129
|
+
return function(ctx) {
|
|
130
|
+
return {
|
|
131
|
+
...options,
|
|
132
|
+
presets: options.presets ? filterMap(options.presets, (preset, i) => convertToBabelPresetItem(ctx, preset, presetFilters[i])) : void 0,
|
|
133
|
+
overrides: options.overrides?.map((override, i) => override.presets ? {
|
|
134
|
+
...override,
|
|
135
|
+
presets: filterMap(override.presets, (preset, j) => convertToBabelPresetItem(ctx, preset, overridePresetFilters[i][j]))
|
|
136
|
+
} : override)
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
//#endregion
|
|
142
|
+
//#region src/filter.ts
|
|
143
|
+
/**
|
|
144
|
+
* Extract string/RegExp values from babel's ConfigApplicableTest,
|
|
145
|
+
* filtering out function entries which HookFilter can't represent.
|
|
146
|
+
* If any function entry is present, returns undefined because the
|
|
147
|
+
* function could match anything we can't predict at the HookFilter level.
|
|
148
|
+
*/
|
|
149
|
+
function extractStringOrRegExp(test) {
|
|
150
|
+
if (test === void 0) return void 0;
|
|
151
|
+
const items = arrayify(test);
|
|
152
|
+
const result = [];
|
|
153
|
+
for (const item of items) {
|
|
154
|
+
if (typeof item === "function") return;
|
|
155
|
+
result.push(item);
|
|
156
|
+
}
|
|
157
|
+
return result.length > 0 ? result : void 0;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Normalize a GeneralHookFilter into { include?, exclude? } form.
|
|
161
|
+
*/
|
|
162
|
+
function normalizeGeneralHookFilter(filter) {
|
|
163
|
+
if (filter == null) return {};
|
|
164
|
+
if (typeof filter === "string" || filter instanceof RegExp) return { include: [filter] };
|
|
165
|
+
if (Array.isArray(filter)) return { include: filter };
|
|
166
|
+
return {
|
|
167
|
+
include: filter.include != null ? arrayify(filter.include) : void 0,
|
|
168
|
+
exclude: filter.exclude != null ? arrayify(filter.exclude) : void 0
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function isRolldownBabelPreset(preset) {
|
|
172
|
+
return typeof preset === "object" && preset !== null && "rolldown" in preset;
|
|
173
|
+
}
|
|
174
|
+
function normalizeModuleTypeFilter(filter) {
|
|
175
|
+
if (Array.isArray(filter)) return filter;
|
|
176
|
+
return filter.include ?? [];
|
|
177
|
+
}
|
|
178
|
+
function patternKey(pattern) {
|
|
179
|
+
return typeof pattern === "string" ? `s:${pattern}` : `r:${pattern.source}:${pattern.flags}`;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Compute the intersection of arrays by key.
|
|
183
|
+
* An item is kept only if it appears in every array.
|
|
184
|
+
* If any array is undefined, the intersection is empty.
|
|
185
|
+
*/
|
|
186
|
+
function intersectArrays(arrays, keyFn) {
|
|
187
|
+
if (arrays.length === 0) return [];
|
|
188
|
+
const defined = arrays.filter((a) => a != null);
|
|
189
|
+
if (defined.length < arrays.length) return [];
|
|
190
|
+
let result = new Map(defined[0].map((p) => [keyFn(p), p]));
|
|
191
|
+
for (let i = 1; i < defined.length; i++) {
|
|
192
|
+
const keys = new Set(defined[i].map((p) => keyFn(p)));
|
|
193
|
+
for (const key of result.keys()) if (!keys.has(key)) result.delete(key);
|
|
194
|
+
}
|
|
195
|
+
return [...result.values()];
|
|
196
|
+
}
|
|
197
|
+
function concatArrays(a, b) {
|
|
198
|
+
if (!a) return b;
|
|
199
|
+
if (!b) return a;
|
|
200
|
+
return [...a, ...b];
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Union filter values from multiple presets for a single dimension.
|
|
204
|
+
* Includes are unioned (OR). Excludes are intersected (only items in ALL presets are kept).
|
|
205
|
+
*
|
|
206
|
+
* @param rawFilters Per-preset filter values. undefined = no filter (matches everything).
|
|
207
|
+
* @param normalize Converts a raw filter into { include?, exclude? } arrays.
|
|
208
|
+
* @param keyFn Serializes an item for intersection comparison.
|
|
209
|
+
*/
|
|
210
|
+
function unionFilters(rawFilters, normalize, keyFn) {
|
|
211
|
+
let matchAll = false;
|
|
212
|
+
const includes = [];
|
|
213
|
+
const excludeArrays = [];
|
|
214
|
+
for (const raw of rawFilters) {
|
|
215
|
+
if (raw === void 0) {
|
|
216
|
+
matchAll = true;
|
|
217
|
+
excludeArrays.push(void 0);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
const n = normalize(raw);
|
|
221
|
+
if (!matchAll) if (n.include) includes.push(...n.include);
|
|
222
|
+
else matchAll = true;
|
|
223
|
+
excludeArrays.push(n.exclude);
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
includes: matchAll ? void 0 : includes.length > 0 ? includes : void 0,
|
|
227
|
+
excludes: intersectArrays(excludeArrays, keyFn)
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Build the transform hook filter by intersecting a baseFilter (from user
|
|
232
|
+
* include/exclude options) with a presetFilter (union of all RolldownBabelPreset
|
|
233
|
+
* filters).
|
|
234
|
+
*
|
|
235
|
+
* - baseFilter constrains by id only (include/exclude from user options).
|
|
236
|
+
* - presetFilter constrains by id, moduleType, and code. Includes are unioned
|
|
237
|
+
* across presets (OR), excludes are intersected (only patterns in ALL presets).
|
|
238
|
+
* - The result uses user includes when present, otherwise falls back to preset
|
|
239
|
+
* includes. Excludes are combined from both (excluded by either → excluded).
|
|
240
|
+
* - If the user has explicit plugins, presetFilter is skipped (plugins can match
|
|
241
|
+
* any file). Same if any preset is a plain babel preset without rolldown filters.
|
|
242
|
+
*/
|
|
243
|
+
function calculateTransformFilter(options) {
|
|
244
|
+
const userInclude = extractStringOrRegExp(options.include);
|
|
245
|
+
const userExclude = extractStringOrRegExp(options.exclude);
|
|
246
|
+
const baseFilter = { id: {
|
|
247
|
+
include: userInclude,
|
|
248
|
+
exclude: userExclude
|
|
249
|
+
} };
|
|
250
|
+
if (options.plugins && options.plugins.length > 0) return baseFilter;
|
|
251
|
+
const presets = options.presets;
|
|
252
|
+
if (!presets || presets.length === 0) return baseFilter;
|
|
253
|
+
for (const preset of presets) if (!isRolldownBabelPreset(preset) || !preset.rolldown.filter) return baseFilter;
|
|
254
|
+
const filters = presets.map((p) => p.rolldown.filter);
|
|
255
|
+
const idUnion = unionFilters(filters.map((f) => f.id), normalizeGeneralHookFilter, patternKey);
|
|
256
|
+
const moduleTypeUnion = unionFilters(filters.map((f) => f.moduleType), (f) => {
|
|
257
|
+
const types = normalizeModuleTypeFilter(f);
|
|
258
|
+
return { include: types.length > 0 ? types : void 0 };
|
|
259
|
+
}, (s) => s);
|
|
260
|
+
const codeUnion = unionFilters(filters.map((f) => f.code), normalizeGeneralHookFilter, patternKey);
|
|
261
|
+
const finalFilter = {};
|
|
262
|
+
const finalFilterIdInclude = userInclude ?? idUnion.includes;
|
|
263
|
+
const finalFilterIdExclude = concatArrays(userExclude, idUnion.excludes.length > 0 ? idUnion.excludes : void 0);
|
|
264
|
+
if (finalFilterIdInclude || finalFilterIdExclude) finalFilter.id = {
|
|
265
|
+
include: finalFilterIdInclude,
|
|
266
|
+
exclude: finalFilterIdExclude
|
|
267
|
+
};
|
|
268
|
+
if (moduleTypeUnion.includes) finalFilter.moduleType = moduleTypeUnion.includes;
|
|
269
|
+
if (codeUnion.includes) {
|
|
270
|
+
const finalFilterCodeExclude = codeUnion.excludes.length > 0 ? codeUnion.excludes : void 0;
|
|
271
|
+
if (finalFilterCodeExclude) finalFilter.code = {
|
|
272
|
+
include: codeUnion.includes,
|
|
273
|
+
exclude: finalFilterCodeExclude
|
|
274
|
+
};
|
|
275
|
+
else finalFilter.code = codeUnion.includes;
|
|
276
|
+
}
|
|
277
|
+
return finalFilter;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Calculate the filters to apply to the plugin
|
|
281
|
+
*/
|
|
282
|
+
function calculatePluginFilters(options) {
|
|
283
|
+
return { transformFilter: calculateTransformFilter(options) };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
//#endregion
|
|
287
|
+
//#region src/index.ts
|
|
288
|
+
async function babelPlugin(rawOptions) {
|
|
289
|
+
let configFilteredOptions;
|
|
290
|
+
const envState = /* @__PURE__ */ new Map();
|
|
291
|
+
const plugin = {
|
|
292
|
+
name: "@rolldown/plugin-babel",
|
|
293
|
+
configResolved(config) {
|
|
294
|
+
configFilteredOptions = filterPresetsWithConfigResolved(rawOptions, config);
|
|
295
|
+
const resolved = resolveOptions(configFilteredOptions);
|
|
296
|
+
plugin.transform.filter = calculatePluginFilters(resolved).transformFilter;
|
|
297
|
+
},
|
|
298
|
+
applyToEnvironment(environment) {
|
|
299
|
+
const envOptions = filterPresetsWithEnvironment(configFilteredOptions, environment);
|
|
300
|
+
if (!envOptions.presets?.length && !envOptions.plugins?.length && !envOptions.overrides?.some((o) => o.presets?.length || o.plugins?.length)) return false;
|
|
301
|
+
const resolved = resolveOptions(envOptions);
|
|
302
|
+
envState.set(environment.name, createBabelOptionsConverter(resolved));
|
|
303
|
+
return true;
|
|
304
|
+
},
|
|
305
|
+
outputOptions() {
|
|
306
|
+
if (this.meta.viteVersion) return;
|
|
307
|
+
const resolved = resolveOptions(rawOptions);
|
|
308
|
+
envState.set(void 0, createBabelOptionsConverter(resolved));
|
|
309
|
+
plugin.transform.filter = calculatePluginFilters(resolved).transformFilter;
|
|
310
|
+
},
|
|
311
|
+
transform: {
|
|
312
|
+
filter: void 0,
|
|
313
|
+
async handler(code, id, opts) {
|
|
314
|
+
const convertToBabelOptions = envState.get(this.environment?.name);
|
|
315
|
+
if (!convertToBabelOptions) return;
|
|
316
|
+
const babelOptions = convertToBabelOptions({
|
|
317
|
+
id,
|
|
318
|
+
moduleType: opts?.moduleType ?? "js",
|
|
319
|
+
code
|
|
320
|
+
});
|
|
321
|
+
const loadedOptions = await babel.loadOptionsAsync({
|
|
322
|
+
...babelOptions,
|
|
323
|
+
babelrc: false,
|
|
324
|
+
configFile: false,
|
|
325
|
+
parserOpts: {
|
|
326
|
+
sourceType: "module",
|
|
327
|
+
allowAwaitOutsideFunction: true,
|
|
328
|
+
...babelOptions.parserOpts
|
|
329
|
+
},
|
|
330
|
+
overrides: [
|
|
331
|
+
{
|
|
332
|
+
test: "**/*.jsx",
|
|
333
|
+
parserOpts: { plugins: ["jsx"] }
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
test: "**/*.ts",
|
|
337
|
+
parserOpts: { plugins: ["typescript"] }
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
test: "**/*.tsx",
|
|
341
|
+
parserOpts: { plugins: ["typescript", "jsx"] }
|
|
342
|
+
},
|
|
343
|
+
...babelOptions.overrides ?? []
|
|
344
|
+
],
|
|
345
|
+
filename: id
|
|
346
|
+
});
|
|
347
|
+
if (!loadedOptions || loadedOptions.plugins.length === 0) return;
|
|
348
|
+
const result = await babel.transformAsync(code, loadedOptions);
|
|
349
|
+
if (result) return {
|
|
350
|
+
code: result.code ?? void 0,
|
|
351
|
+
map: result.map
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
return plugin;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
//#endregion
|
|
360
|
+
export { babelPlugin as default, defineRolldownBabelPreset };
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rolldown/plugin-babel",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Rolldown plugin for Babel",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"babel",
|
|
7
|
+
"plugin",
|
|
8
|
+
"rolldown",
|
|
9
|
+
"rolldown-plugin"
|
|
10
|
+
],
|
|
11
|
+
"homepage": "https://github.com/rolldown/plugins/tree/main/packages/babel#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/rolldown/plugins/issues"
|
|
14
|
+
},
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/rolldown/plugins.git",
|
|
19
|
+
"directory": "packages/babel"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"type": "module",
|
|
25
|
+
"exports": "./dist/index.mjs",
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"picomatch": "^4.0.3"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@babel/core": "^8.0.0-rc.1",
|
|
31
|
+
"@types/node": "^22.19.11",
|
|
32
|
+
"@types/picomatch": "^4.0.2",
|
|
33
|
+
"rolldown": "1.0.0-rc.5",
|
|
34
|
+
"vite": "^8.0.0-beta.15"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@babel/core": "^7.29.0 || ^8.0.0-rc.1",
|
|
38
|
+
"rolldown": "^1.0.0-rc.5",
|
|
39
|
+
"vite": "^8.0.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependenciesMeta": {
|
|
42
|
+
"vite": {
|
|
43
|
+
"optional": true
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=22.12.0 || ^24.0.0"
|
|
48
|
+
},
|
|
49
|
+
"compatiblePackages": {
|
|
50
|
+
"schemaVersion": 1,
|
|
51
|
+
"rollup": {
|
|
52
|
+
"type": "incompatible",
|
|
53
|
+
"reason": "Uses Rolldown-specific APIs"
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"scripts": {
|
|
57
|
+
"dev": "tsdown --watch",
|
|
58
|
+
"build": "tsdown",
|
|
59
|
+
"test": "vitest --project babel"
|
|
60
|
+
}
|
|
61
|
+
}
|