@rolldown/plugin-babel 0.1.2 → 0.1.4

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/README.md CHANGED
@@ -5,18 +5,18 @@ Rolldown plugin for transforming code with [Babel](https://babeljs.io/).
5
5
  ## Install
6
6
 
7
7
  ```bash
8
- npm install @rolldown/plugin-babel @babel/core
8
+ pnpm install @rolldown/plugin-babel @babel/core
9
9
  ```
10
10
 
11
11
  ## Usage
12
12
 
13
13
  ```js
14
- import babelPlugin from '@rolldown/plugin-babel'
14
+ import babel from '@rolldown/plugin-babel'
15
15
 
16
16
  export default {
17
17
  plugins: [
18
- babelPlugin({
19
- presets: [['@babel/preset-env', { targets: { chrome: '100' } }]],
18
+ babel({
19
+ plugins: ['@babel/plugin-proposal-throw-expressions'],
20
20
  }),
21
21
  ],
22
22
  }
@@ -31,8 +31,9 @@ The plugin automatically configures Babel's parser for `.jsx`, `.ts`, and `.tsx`
31
31
  ### `include`
32
32
 
33
33
  - **Type:** `string | RegExp | (string | RegExp)[]`
34
+ - **Default:** `/\.(?:[jt]sx?|[cm][jt]s)(?:$|\?)/`
34
35
 
35
- If specified, only files matching the pattern will be processed.
36
+ Only files matching the pattern will be processed.
36
37
 
37
38
  ### `exclude`
38
39
 
@@ -0,0 +1,63 @@
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
+ * @default `/\.(?:[jt]sx?|[cm][jt]s)(?:$|\?)/`
35
+ *
36
+ * Note that this option receives the syntax supported by babel instead of picomatch.
37
+ * @see https://babeljs.io/docs/options#matchpattern
38
+ */
39
+ include?: InnerTransformOptions['include'];
40
+ /**
41
+ * If any of patterns match, babel will not process the file.
42
+ * @default `/[\/\\]node_modules[\/\\]/`
43
+ *
44
+ * Note that this option receives the syntax supported by babel instead of picomatch.
45
+ * @see https://babeljs.io/docs/options#matchpattern
46
+ */
47
+ exclude?: InnerTransformOptions['exclude'];
48
+ /**
49
+ * If false, skips source map generation. This will improve performance.
50
+ * @default true
51
+ */
52
+ sourceMap?: boolean;
53
+ /**
54
+ * Allows users to provide an array of options that will be merged into the current configuration one at a time.
55
+ * This feature is best used alongside the "test"/"include"/"exclude" options to provide conditions for which an override should apply
56
+ */
57
+ overrides?: InnerTransformOptions[] | undefined;
58
+ }
59
+ //#endregion
60
+ //#region src/index.d.ts
61
+ declare function babelPlugin(rawOptions: PluginOptions): Promise<Plugin>;
62
+ //#endregion
63
+ export { type RolldownBabelPreset, babelPlugin as default, defineRolldownBabelPreset };
package/dist/index.mjs ADDED
@@ -0,0 +1,374 @@
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
+ const DEFAULT_INCLUDE = [/\.(?:[jt]sx?|[cm][jt]s)(?:$|\?)/];
79
+ const DEFAULT_EXCLUDE = [/[/\\]node_modules[/\\]/];
80
+ function resolveOptions(options) {
81
+ return {
82
+ ...options,
83
+ include: options.include ?? DEFAULT_INCLUDE,
84
+ exclude: options.exclude ?? DEFAULT_EXCLUDE,
85
+ sourceMap: options.sourceMap ?? true
86
+ };
87
+ }
88
+ function compilePresetFilters(presets) {
89
+ return presets.map((preset) => typeof preset === "object" && "rolldown" in preset ? compilePresetFilter(preset.rolldown.filter) : void 0);
90
+ }
91
+ function filterPresetArrayWithEnvironment(presets, environment) {
92
+ return presets.filter((preset) => {
93
+ if (typeof preset !== "object" || !("rolldown" in preset)) return true;
94
+ if (!preset.rolldown.applyToEnvironmentHook) return true;
95
+ return preset.rolldown.applyToEnvironmentHook(environment);
96
+ });
97
+ }
98
+ function filterPresetsWithEnvironment(options, environment) {
99
+ return {
100
+ ...options,
101
+ presets: options.presets ? filterPresetArrayWithEnvironment(options.presets, environment) : void 0,
102
+ overrides: options.overrides?.map((override) => override.presets ? {
103
+ ...override,
104
+ presets: filterPresetArrayWithEnvironment(override.presets, environment)
105
+ } : override)
106
+ };
107
+ }
108
+ function filterPresetArray(presets, config) {
109
+ return presets.filter((preset) => {
110
+ if (typeof preset !== "object" || !("rolldown" in preset)) return true;
111
+ if (!preset.rolldown.configResolvedHook) return true;
112
+ return preset.rolldown.configResolvedHook(config);
113
+ });
114
+ }
115
+ function filterPresetsWithConfigResolved(options, config) {
116
+ return {
117
+ ...options,
118
+ presets: options.presets ? filterPresetArray(options.presets, config) : void 0,
119
+ overrides: options.overrides?.map((override) => override.presets ? {
120
+ ...override,
121
+ presets: filterPresetArray(override.presets, config)
122
+ } : override)
123
+ };
124
+ }
125
+ /**
126
+ * Pre-compile all preset filters and return a function that
127
+ * converts options to babel options for a given context.
128
+ */
129
+ function createBabelOptionsConverter(options) {
130
+ const presetFilters = options.presets ? compilePresetFilters(options.presets) : void 0;
131
+ const overridePresetFilters = options.overrides?.map((override) => override.presets ? compilePresetFilters(override.presets) : void 0);
132
+ return function(ctx) {
133
+ return {
134
+ ...options,
135
+ presets: options.presets ? filterMap(options.presets, (preset, i) => convertToBabelPresetItem(ctx, preset, presetFilters[i])) : void 0,
136
+ overrides: options.overrides?.map((override, i) => override.presets ? {
137
+ ...override,
138
+ presets: filterMap(override.presets, (preset, j) => convertToBabelPresetItem(ctx, preset, overridePresetFilters[i][j]))
139
+ } : override)
140
+ };
141
+ };
142
+ }
143
+
144
+ //#endregion
145
+ //#region src/filter.ts
146
+ /**
147
+ * Extract string/RegExp values from babel's ConfigApplicableTest,
148
+ * filtering out function entries which HookFilter can't represent.
149
+ * If any function entry is present, returns undefined because the
150
+ * function could match anything we can't predict at the HookFilter level.
151
+ */
152
+ function extractStringOrRegExp(test) {
153
+ if (test === void 0) return void 0;
154
+ const items = arrayify(test);
155
+ const result = [];
156
+ for (const item of items) {
157
+ if (typeof item === "function") return;
158
+ result.push(item);
159
+ }
160
+ return result.length > 0 ? result : void 0;
161
+ }
162
+ /**
163
+ * Normalize a GeneralHookFilter into { include?, exclude? } form.
164
+ */
165
+ function normalizeGeneralHookFilter(filter) {
166
+ if (filter == null) return {};
167
+ if (typeof filter === "string" || filter instanceof RegExp) return { include: [filter] };
168
+ if (Array.isArray(filter)) return { include: filter };
169
+ return {
170
+ include: filter.include != null ? arrayify(filter.include) : void 0,
171
+ exclude: filter.exclude != null ? arrayify(filter.exclude) : void 0
172
+ };
173
+ }
174
+ function isRolldownBabelPreset(preset) {
175
+ return typeof preset === "object" && preset !== null && "rolldown" in preset;
176
+ }
177
+ function normalizeModuleTypeFilter(filter) {
178
+ if (Array.isArray(filter)) return filter;
179
+ return filter.include ?? [];
180
+ }
181
+ function patternKey(pattern) {
182
+ return typeof pattern === "string" ? `s:${pattern}` : `r:${pattern.source}:${pattern.flags}`;
183
+ }
184
+ /**
185
+ * Compute the intersection of arrays by key.
186
+ * An item is kept only if it appears in every array.
187
+ * If any array is undefined, the intersection is empty.
188
+ */
189
+ function intersectArrays(arrays, keyFn) {
190
+ if (arrays.length === 0) return [];
191
+ const defined = arrays.filter((a) => a != null);
192
+ if (defined.length < arrays.length) return [];
193
+ let result = new Map(defined[0].map((p) => [keyFn(p), p]));
194
+ for (let i = 1; i < defined.length; i++) {
195
+ const keys = new Set(defined[i].map((p) => keyFn(p)));
196
+ for (const key of result.keys()) if (!keys.has(key)) result.delete(key);
197
+ }
198
+ return [...result.values()];
199
+ }
200
+ function concatArrays(a, b) {
201
+ if (!a) return b;
202
+ if (!b) return a;
203
+ return [...a, ...b];
204
+ }
205
+ /**
206
+ * Union filter values from multiple presets for a single dimension.
207
+ * Includes are unioned (OR). Excludes are intersected (only items in ALL presets are kept).
208
+ *
209
+ * @param rawFilters Per-preset filter values. undefined = no filter (matches everything).
210
+ * @param normalize Converts a raw filter into { include?, exclude? } arrays.
211
+ * @param keyFn Serializes an item for intersection comparison.
212
+ */
213
+ function unionFilters(rawFilters, normalize, keyFn) {
214
+ let matchAll = false;
215
+ const includes = [];
216
+ const excludeArrays = [];
217
+ for (const raw of rawFilters) {
218
+ if (raw === void 0) {
219
+ matchAll = true;
220
+ excludeArrays.push(void 0);
221
+ continue;
222
+ }
223
+ const n = normalize(raw);
224
+ if (!matchAll) if (n.include) includes.push(...n.include);
225
+ else matchAll = true;
226
+ excludeArrays.push(n.exclude);
227
+ }
228
+ return {
229
+ includes: matchAll ? void 0 : includes.length > 0 ? includes : void 0,
230
+ excludes: intersectArrays(excludeArrays, keyFn)
231
+ };
232
+ }
233
+ /**
234
+ * Build the transform hook filter by intersecting a baseFilter (from user
235
+ * include/exclude options) with a presetFilter (union of all RolldownBabelPreset
236
+ * filters).
237
+ *
238
+ * - baseFilter constrains by id only (include/exclude from user options).
239
+ * - presetFilter constrains by id, moduleType, and code. Includes are unioned
240
+ * across presets (OR), excludes are intersected (only patterns in ALL presets).
241
+ * - The result uses user includes when present, otherwise falls back to preset
242
+ * includes. Excludes are combined from both (excluded by either → excluded).
243
+ * - If the user has explicit plugins, presetFilter is skipped (plugins can match
244
+ * any file). Same if any preset is a plain babel preset without rolldown filters.
245
+ */
246
+ function calculateTransformFilter(options) {
247
+ const userInclude = extractStringOrRegExp(options.include);
248
+ const userExclude = extractStringOrRegExp(options.exclude);
249
+ const baseFilter = { id: {
250
+ include: userInclude,
251
+ exclude: userExclude
252
+ } };
253
+ if (options.plugins && options.plugins.length > 0) return baseFilter;
254
+ const presets = options.presets;
255
+ if (!presets || presets.length === 0) return baseFilter;
256
+ for (const preset of presets) if (!isRolldownBabelPreset(preset) || !preset.rolldown.filter) return baseFilter;
257
+ const filters = presets.map((p) => p.rolldown.filter);
258
+ const idUnion = unionFilters(filters.map((f) => f.id), normalizeGeneralHookFilter, patternKey);
259
+ const moduleTypeUnion = unionFilters(filters.map((f) => f.moduleType), (f) => {
260
+ const types = normalizeModuleTypeFilter(f);
261
+ return { include: types.length > 0 ? types : void 0 };
262
+ }, (s) => s);
263
+ const codeUnion = unionFilters(filters.map((f) => f.code), normalizeGeneralHookFilter, patternKey);
264
+ const finalFilter = {};
265
+ const finalFilterIdInclude = userInclude ?? idUnion.includes;
266
+ const finalFilterIdExclude = concatArrays(userExclude, idUnion.excludes.length > 0 ? idUnion.excludes : void 0);
267
+ if (finalFilterIdInclude || finalFilterIdExclude) finalFilter.id = {
268
+ include: finalFilterIdInclude,
269
+ exclude: finalFilterIdExclude
270
+ };
271
+ if (moduleTypeUnion.includes) finalFilter.moduleType = moduleTypeUnion.includes;
272
+ if (codeUnion.includes) {
273
+ const finalFilterCodeExclude = codeUnion.excludes.length > 0 ? codeUnion.excludes : void 0;
274
+ if (finalFilterCodeExclude) finalFilter.code = {
275
+ include: codeUnion.includes,
276
+ exclude: finalFilterCodeExclude
277
+ };
278
+ else finalFilter.code = codeUnion.includes;
279
+ }
280
+ return finalFilter;
281
+ }
282
+ /**
283
+ * Calculate the filters to apply to the plugin
284
+ */
285
+ function calculatePluginFilters(options) {
286
+ return { transformFilter: calculateTransformFilter(options) };
287
+ }
288
+
289
+ //#endregion
290
+ //#region src/index.ts
291
+ async function babelPlugin(rawOptions) {
292
+ let configFilteredOptions;
293
+ const envState = /* @__PURE__ */ new Map();
294
+ const plugin = {
295
+ name: "@rolldown/plugin-babel",
296
+ configResolved(config) {
297
+ configFilteredOptions = filterPresetsWithConfigResolved(rawOptions, config);
298
+ const resolved = resolveOptions(configFilteredOptions);
299
+ plugin.transform.filter = calculatePluginFilters(resolved).transformFilter;
300
+ },
301
+ applyToEnvironment(environment) {
302
+ const envOptions = filterPresetsWithEnvironment(configFilteredOptions, environment);
303
+ if (!envOptions.presets?.length && !envOptions.plugins?.length && !envOptions.overrides?.some((o) => o.presets?.length || o.plugins?.length)) return false;
304
+ const resolved = resolveOptions(envOptions);
305
+ envState.set(environment.name, createBabelOptionsConverter(resolved));
306
+ return true;
307
+ },
308
+ outputOptions() {
309
+ if (this.meta.viteVersion) return;
310
+ const resolved = resolveOptions(rawOptions);
311
+ envState.set(void 0, createBabelOptionsConverter(resolved));
312
+ plugin.transform.filter = calculatePluginFilters(resolved).transformFilter;
313
+ },
314
+ transform: {
315
+ filter: void 0,
316
+ async handler(code, id, opts) {
317
+ const convertToBabelOptions = envState.get(this.environment?.name);
318
+ if (!convertToBabelOptions) return;
319
+ const babelOptions = convertToBabelOptions({
320
+ id,
321
+ moduleType: opts?.moduleType ?? "js",
322
+ code
323
+ });
324
+ const loadedOptions = await babel.loadOptionsAsync({
325
+ ...babelOptions,
326
+ babelrc: false,
327
+ configFile: false,
328
+ parserOpts: {
329
+ sourceType: "module",
330
+ allowAwaitOutsideFunction: true,
331
+ ...babelOptions.parserOpts
332
+ },
333
+ overrides: [
334
+ {
335
+ test: "**/*.jsx",
336
+ parserOpts: { plugins: ["jsx"] }
337
+ },
338
+ {
339
+ test: "**/*.ts",
340
+ parserOpts: { plugins: ["typescript"] }
341
+ },
342
+ {
343
+ test: "**/*.tsx",
344
+ parserOpts: { plugins: ["typescript", "jsx"] }
345
+ },
346
+ ...babelOptions.overrides ?? []
347
+ ],
348
+ filename: id
349
+ });
350
+ if (!loadedOptions || loadedOptions.plugins.length === 0) return;
351
+ let result;
352
+ try {
353
+ result = await babel.transformAsync(code, loadedOptions);
354
+ } catch (err) {
355
+ this.error({
356
+ message: `[BabelError] ${err.message}`,
357
+ loc: err.loc,
358
+ pos: err.pos,
359
+ cause: err,
360
+ pluginCode: `${err.code}:${err.reasonCode}`
361
+ });
362
+ }
363
+ if (result) return {
364
+ code: result.code ?? void 0,
365
+ map: result.map
366
+ };
367
+ }
368
+ }
369
+ };
370
+ return plugin;
371
+ }
372
+
373
+ //#endregion
374
+ export { babelPlugin as default, defineRolldownBabelPreset };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rolldown/plugin-babel",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Rolldown plugin for Babel",
5
5
  "keywords": [
6
6
  "babel",