6
6
* found in the LICENSE file at https://angular.io/license
7
7
*/
8
8
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' ;
10
12
import Piscina from 'piscina' ;
11
- import angularApplicationPreset , { requiresLinking } from '../../tools/babel/presets/application' ;
12
13
import { loadEsmModule } from '../../utils/load-esm' ;
13
14
14
15
interface JavaScriptTransformRequest {
@@ -37,10 +38,18 @@ export default async function transformJavaScript(
37
38
return Piscina . move ( textEncoder . encode ( transformedData ) ) ;
38
39
}
39
40
41
+ /**
42
+ * Cached instance of the compiler-cli linker's createEs2015LinkerPlugin function.
43
+ */
40
44
let linkerPluginCreator :
41
45
| typeof import ( '@angular/compiler-cli/linker/babel' ) . createEs2015LinkerPlugin
42
46
| undefined ;
43
47
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
+
44
53
async function transformWithBabel (
45
54
filename : string ,
46
55
data : string ,
@@ -51,22 +60,36 @@ async function transformWithBabel(
51
60
options . sourcemap &&
52
61
( ! ! options . thirdPartySourcemaps || ! / [ \\ / ] n o d e _ m o d u l e s [ \\ / ] / . test ( filename ) ) ;
53
62
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 ( / ^ \/ \/ # s o u r c e M a p p i n g U R L = [ ^ \r \n ] * / gm, '' ) ;
58
- }
59
-
60
- const sideEffectFree = options . sideEffects === false ;
61
- const safeAngularPackage = sideEffectFree && / [ \\ / ] n o d e _ m o d u l e s [ \\ / ] @ a n g u l a r [ \\ / ] / . test ( filename ) ;
63
+ const plugins : PluginItem [ ] = [ ] ;
62
64
63
65
// Lazy load the linker plugin only when linking is required
64
66
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 && / [ \\ / ] n o d e _ m o d u l e s [ \\ / ] @ a n g u l a r [ \\ / ] / . 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 ( / ^ \/ \/ # s o u r c e M a p p i n g U R L = [ ^ \r \n ] * / gm, '' ) ;
70
93
}
71
94
72
95
const result = await transformAsync ( data , {
@@ -77,23 +100,7 @@ async function transformWithBabel(
77
100
configFile : false ,
78
101
babelrc : false ,
79
102
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,
97
104
} ) ;
98
105
99
106
const outputCode = result ?. code ?? data ;
@@ -104,3 +111,67 @@ async function transformWithBabel(
104
111
? outputCode
105
112
: outputCode . replace ( / ^ \/ \/ # s o u r c e M a p p i n g U R L = [ ^ \r \n ] * / gm, '' ) ;
106
113
}
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 ( / [ \\ / ] @ a n g u l a r [ \\ / ] (?: c o m p i l e r | c o r e ) | \. t s x ? $ / . 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