Skip to content

Commit 2fc8076

Browse files
committed
refactor(@angular-devkit/build-angular): directly configure internal babel plugins for application builder
The linker and build optimizer related babel plugins are now directly imported when needed within the JavaScript transformer worker code. This lowers the number of transitive modules that must be loaded for each worker instance. It also removes the use of `require` from the initialization code which provides support for full ESM output in the future.
1 parent 950a445 commit 2fc8076

File tree

2 files changed

+115
-32
lines changed

2 files changed

+115
-32
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
export { default as adjustStaticMembers } from './adjust-static-class-members';
10+
export { default as adjustTypeScriptEnums } from './adjust-typescript-enums';
11+
export { default as elideAngularMetadata } from './elide-angular-metadata';
12+
export { default as markTopLevelPure } from './pure-toplevel-functions';

packages/angular_devkit/build_angular/src/tools/esbuild/javascript-transformer-worker.ts

Lines changed: 103 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import { transformAsync } from '@babel/core';
9+
import { type PluginItem, transformAsync } from '@babel/core';
10+
import fs from 'node:fs';
11+
import path from 'node:path';
1012
import Piscina from 'piscina';
11-
import angularApplicationPreset, { requiresLinking } from '../../tools/babel/presets/application';
1213
import { loadEsmModule } from '../../utils/load-esm';
1314

1415
interface JavaScriptTransformRequest {
@@ -37,10 +38,18 @@ export default async function transformJavaScript(
3738
return Piscina.move(textEncoder.encode(transformedData));
3839
}
3940

41+
/**
42+
* Cached instance of the compiler-cli linker's createEs2015LinkerPlugin function.
43+
*/
4044
let linkerPluginCreator:
4145
| typeof import('@angular/compiler-cli/linker/babel').createEs2015LinkerPlugin
4246
| undefined;
4347

48+
/**
49+
* Cached instance of the compiler-cli linker's needsLinking function.
50+
*/
51+
let needsLinking: typeof import('@angular/compiler-cli/linker').needsLinking | undefined;
52+
4453
async function transformWithBabel(
4554
filename: string,
4655
data: string,
@@ -51,22 +60,36 @@ async function transformWithBabel(
5160
options.sourcemap &&
5261
(!!options.thirdPartySourcemaps || !/[\\/]node_modules[\\/]/.test(filename));
5362

54-
// If no additional transformations are needed, return the data directly
55-
if (!options.advancedOptimizations && !shouldLink) {
56-
// Strip sourcemaps if they should not be used
57-
return useInputSourcemap ? data : data.replace(/^\/\/# sourceMappingURL=[^\r\n]*/gm, '');
58-
}
59-
60-
const sideEffectFree = options.sideEffects === false;
61-
const safeAngularPackage = sideEffectFree && /[\\/]node_modules[\\/]@angular[\\/]/.test(filename);
63+
const plugins: PluginItem[] = [];
6264

6365
// Lazy load the linker plugin only when linking is required
6466
if (shouldLink) {
65-
linkerPluginCreator ??= (
66-
await loadEsmModule<typeof import('@angular/compiler-cli/linker/babel')>(
67-
'@angular/compiler-cli/linker/babel',
68-
)
69-
).createEs2015LinkerPlugin;
67+
const linkerPlugin = await createLinkerPlugin(options);
68+
plugins.push(linkerPlugin);
69+
}
70+
71+
if (options.advancedOptimizations) {
72+
const sideEffectFree = options.sideEffects === false;
73+
const safeAngularPackage =
74+
sideEffectFree && /[\\/]node_modules[\\/]@angular[\\/]/.test(filename);
75+
76+
const { adjustStaticMembers, adjustTypeScriptEnums, elideAngularMetadata, markTopLevelPure } =
77+
await import('../babel/plugins');
78+
79+
if (safeAngularPackage) {
80+
plugins.push(markTopLevelPure);
81+
}
82+
83+
plugins.push(elideAngularMetadata, adjustTypeScriptEnums, [
84+
adjustStaticMembers,
85+
{ wrapDecorators: sideEffectFree },
86+
]);
87+
}
88+
89+
// If no additional transformations are needed, return the data directly
90+
if (plugins.length === 0) {
91+
// Strip sourcemaps if they should not be used
92+
return useInputSourcemap ? data : data.replace(/^\/\/# sourceMappingURL=[^\r\n]*/gm, '');
7093
}
7194

7295
const result = await transformAsync(data, {
@@ -77,23 +100,7 @@ async function transformWithBabel(
77100
configFile: false,
78101
babelrc: false,
79102
browserslistConfigFile: false,
80-
plugins: [],
81-
presets: [
82-
[
83-
angularApplicationPreset,
84-
{
85-
angularLinker: linkerPluginCreator && {
86-
shouldLink,
87-
jitMode: options.jit,
88-
linkerPluginCreator,
89-
},
90-
optimize: options.advancedOptimizations && {
91-
pureTopLevel: safeAngularPackage,
92-
wrapDecorators: sideEffectFree,
93-
},
94-
},
95-
],
96-
],
103+
plugins,
97104
});
98105

99106
const outputCode = result?.code ?? data;
@@ -104,3 +111,67 @@ async function transformWithBabel(
104111
? outputCode
105112
: outputCode.replace(/^\/\/# sourceMappingURL=[^\r\n]*/gm, '');
106113
}
114+
115+
async function requiresLinking(path: string, source: string): Promise<boolean> {
116+
// @angular/core and @angular/compiler will cause false positives
117+
// Also, TypeScript files do not require linking
118+
if (/[\\/]@angular[\\/](?:compiler|core)|\.tsx?$/.test(path)) {
119+
return false;
120+
}
121+
122+
if (!needsLinking) {
123+
// Load ESM `@angular/compiler-cli/linker` using the TypeScript dynamic import workaround.
124+
// Once TypeScript provides support for keeping the dynamic import this workaround can be
125+
// changed to a direct dynamic import.
126+
const linkerModule = await loadEsmModule<typeof import('@angular/compiler-cli/linker')>(
127+
'@angular/compiler-cli/linker',
128+
);
129+
needsLinking = linkerModule.needsLinking;
130+
}
131+
132+
return needsLinking(path, source);
133+
}
134+
135+
async function createLinkerPlugin(options: Omit<JavaScriptTransformRequest, 'filename' | 'data'>) {
136+
linkerPluginCreator ??= (
137+
await loadEsmModule<typeof import('@angular/compiler-cli/linker/babel')>(
138+
'@angular/compiler-cli/linker/babel',
139+
)
140+
).createEs2015LinkerPlugin;
141+
142+
const linkerPlugin = linkerPluginCreator({
143+
linkerJitMode: options.jit,
144+
// This is a workaround until https://github.com/angular/angular/issues/42769 is fixed.
145+
sourceMapping: false,
146+
logger: {
147+
level: 1, // Info level
148+
debug(...args: string[]) {
149+
// eslint-disable-next-line no-console
150+
console.debug(args);
151+
},
152+
info(...args: string[]) {
153+
// eslint-disable-next-line no-console
154+
console.info(args);
155+
},
156+
warn(...args: string[]) {
157+
// eslint-disable-next-line no-console
158+
console.warn(args);
159+
},
160+
error(...args: string[]) {
161+
// eslint-disable-next-line no-console
162+
console.error(args);
163+
},
164+
},
165+
fileSystem: {
166+
resolve: path.resolve,
167+
exists: fs.existsSync,
168+
dirname: path.dirname,
169+
relative: path.relative,
170+
readFile: fs.readFileSync,
171+
// Node.JS types don't overlap the Compiler types.
172+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
173+
} as any,
174+
});
175+
176+
return linkerPlugin;
177+
}

0 commit comments

Comments
 (0)