Skip to content

Commit 7f27908

Browse files
committed
refactor(@angular-devkit/build-angular): centralize babel transformation in esbuild-based builder
The Babel transformation code was mostly duplicated in two places within the esbuild-based browser application builder. The code has now been extracted into a helper function and used where needed.
1 parent 3d94ca2 commit 7f27908

File tree

1 file changed

+76
-102
lines changed

1 file changed

+76
-102
lines changed

packages/angular_devkit/build_angular/src/builders/browser-esbuild/compiler-plugin.ts

Lines changed: 76 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ type FileEmitter = (file: string) => Promise<EmitFileResult | undefined>;
3737
* Converts TypeScript Diagnostic related information into an esbuild compatible note object.
3838
* Related information is a subset of a full TypeScript Diagnostic and also used for diagnostic
3939
* notes associated with the main Diagnostic.
40-
* @param diagnostic The TypeScript diagnostic relative information to convert.
40+
* @param info The TypeScript diagnostic relative information to convert.
4141
* @param host A TypeScript FormatDiagnosticsHost instance to use during conversion.
4242
* @returns An esbuild diagnostic message as a PartialMessage object
4343
*/
@@ -120,16 +120,18 @@ function convertTypeScriptDiagnostic(
120120
return message;
121121
}
122122

123+
export interface CompilerPluginOptions {
124+
sourcemap: boolean;
125+
tsconfig: string;
126+
advancedOptimizations?: boolean;
127+
thirdPartySourcemaps?: boolean;
128+
fileReplacements?: Record<string, string>;
129+
}
130+
123131
// This is a non-watch version of the compiler code from `@ngtools/webpack` augmented for esbuild
124132
// eslint-disable-next-line max-lines-per-function
125133
export function createCompilerPlugin(
126-
pluginOptions: {
127-
sourcemap: boolean;
128-
tsconfig: string;
129-
advancedOptimizations?: boolean;
130-
thirdPartySourcemaps?: boolean;
131-
fileReplacements?: Record<string, string>;
132-
},
134+
pluginOptions: CompilerPluginOptions,
133135
styleOptions: BundleStylesheetOptions,
134136
): Plugin {
135137
return {
@@ -335,8 +337,7 @@ export function createCompilerPlugin(
335337
return {
336338
errors: [
337339
{
338-
text: 'File is missing from the TypeScript compilation.',
339-
location: { file: args.path },
340+
text: `File '${args.path}' is missing from the TypeScript compilation.`,
340341
notes: [
341342
{
342343
text: `Ensure the file is part of the TypeScript program via the 'files' or 'include' property.`,
@@ -347,108 +348,22 @@ export function createCompilerPlugin(
347348
};
348349
}
349350

350-
const data = typescriptResult.content ?? '';
351-
const forceAsyncTransformation = /async\s+function\s*\*/.test(data);
352-
const useInputSourcemap =
353-
pluginOptions.sourcemap &&
354-
(!!pluginOptions.thirdPartySourcemaps || !/[\\/]node_modules[\\/]/.test(args.path));
355-
356-
// If no additional transformations are needed, return the TypeScript output directly
357-
if (!forceAsyncTransformation && !pluginOptions.advancedOptimizations) {
358-
return {
359-
// Strip sourcemaps if they should not be used
360-
contents: useInputSourcemap
361-
? data
362-
: data.replace(/^\/\/# sourceMappingURL=[^\r\n]*/gm, ''),
363-
loader: 'js',
364-
};
365-
}
366-
367-
const babelResult = await transformAsync(data, {
368-
filename: args.path,
369-
inputSourceMap: (useInputSourcemap ? undefined : false) as undefined,
370-
sourceMaps: pluginOptions.sourcemap ? 'inline' : false,
371-
compact: false,
372-
configFile: false,
373-
babelrc: false,
374-
browserslistConfigFile: false,
375-
plugins: [],
376-
presets: [
377-
[
378-
angularApplicationPreset,
379-
{
380-
forceAsyncTransformation,
381-
optimize: pluginOptions.advancedOptimizations && {},
382-
},
383-
],
384-
],
385-
});
386-
387351
return {
388-
contents: babelResult?.code ?? '',
352+
contents: await transformWithBabel(
353+
args.path,
354+
typescriptResult.content ?? '',
355+
pluginOptions,
356+
),
389357
loader: 'js',
390358
};
391359
},
392360
);
393361

394362
build.onLoad({ filter: /\.[cm]?js$/ }, async (args) => {
395363
const data = await fs.readFile(args.path, 'utf-8');
396-
const forceAsyncTransformation =
397-
!/[\\/][_f]?esm2015[\\/]/.test(args.path) && /async\s+function\s*\*/.test(data);
398-
const shouldLink = await requiresLinking(args.path, data);
399-
const useInputSourcemap =
400-
pluginOptions.sourcemap &&
401-
(!!pluginOptions.thirdPartySourcemaps || !/[\\/]node_modules[\\/]/.test(args.path));
402-
403-
// If no additional transformations are needed, return the TypeScript output directly
404-
if (!forceAsyncTransformation && !pluginOptions.advancedOptimizations && !shouldLink) {
405-
return {
406-
// Strip sourcemaps if they should not be used
407-
contents: useInputSourcemap
408-
? data
409-
: data.replace(/^\/\/# sourceMappingURL=[^\r\n]*/gm, ''),
410-
loader: 'js',
411-
};
412-
}
413-
414-
const angularPackage = /[\\/]node_modules[\\/]@angular[\\/]/.test(args.path);
415-
416-
const linkerPluginCreator = (
417-
await loadEsmModule<typeof import('@angular/compiler-cli/linker/babel')>(
418-
'@angular/compiler-cli/linker/babel',
419-
)
420-
).createEs2015LinkerPlugin;
421-
422-
const result = await transformAsync(data, {
423-
filename: args.path,
424-
inputSourceMap: (useInputSourcemap ? undefined : false) as undefined,
425-
sourceMaps: pluginOptions.sourcemap ? 'inline' : false,
426-
compact: false,
427-
configFile: false,
428-
babelrc: false,
429-
browserslistConfigFile: false,
430-
plugins: [],
431-
presets: [
432-
[
433-
angularApplicationPreset,
434-
{
435-
angularLinker: {
436-
shouldLink,
437-
jitMode: false,
438-
linkerPluginCreator,
439-
},
440-
forceAsyncTransformation,
441-
optimize: pluginOptions.advancedOptimizations && {
442-
looseEnums: angularPackage,
443-
pureTopLevel: angularPackage,
444-
},
445-
},
446-
],
447-
],
448-
});
449364

450365
return {
451-
contents: result?.code ?? data,
366+
contents: await transformWithBabel(args.path, data, pluginOptions),
452367
loader: 'js',
453368
};
454369
});
@@ -491,3 +406,62 @@ function createFileEmitter(
491406
return { content, dependencies: [] };
492407
};
493408
}
409+
410+
async function transformWithBabel(
411+
filename: string,
412+
data: string,
413+
pluginOptions: CompilerPluginOptions,
414+
): Promise<string> {
415+
const forceAsyncTransformation =
416+
!/[\\/][_f]?esm2015[\\/]/.test(filename) && /async\s+function\s*\*/.test(data);
417+
const shouldLink = await requiresLinking(filename, data);
418+
const useInputSourcemap =
419+
pluginOptions.sourcemap &&
420+
(!!pluginOptions.thirdPartySourcemaps || !/[\\/]node_modules[\\/]/.test(filename));
421+
422+
// If no additional transformations are needed, return the data directly
423+
if (!forceAsyncTransformation && !pluginOptions.advancedOptimizations && !shouldLink) {
424+
// Strip sourcemaps if they should not be used
425+
return useInputSourcemap ? data : data.replace(/^\/\/# sourceMappingURL=[^\r\n]*/gm, '');
426+
}
427+
428+
const angularPackage = /[\\/]node_modules[\\/]@angular[\\/]/.test(filename);
429+
430+
const linkerPluginCreator = shouldLink
431+
? (
432+
await loadEsmModule<typeof import('@angular/compiler-cli/linker/babel')>(
433+
'@angular/compiler-cli/linker/babel',
434+
)
435+
).createEs2015LinkerPlugin
436+
: undefined;
437+
438+
const result = await transformAsync(data, {
439+
filename,
440+
inputSourceMap: (useInputSourcemap ? undefined : false) as undefined,
441+
sourceMaps: pluginOptions.sourcemap ? 'inline' : false,
442+
compact: false,
443+
configFile: false,
444+
babelrc: false,
445+
browserslistConfigFile: false,
446+
plugins: [],
447+
presets: [
448+
[
449+
angularApplicationPreset,
450+
{
451+
angularLinker: {
452+
shouldLink,
453+
jitMode: false,
454+
linkerPluginCreator,
455+
},
456+
forceAsyncTransformation,
457+
optimize: pluginOptions.advancedOptimizations && {
458+
looseEnums: angularPackage,
459+
pureTopLevel: angularPackage,
460+
},
461+
},
462+
],
463+
],
464+
});
465+
466+
return result?.code ?? data;
467+
}

0 commit comments

Comments
 (0)