|
6 | 6 | * found in the LICENSE file at https://angular.dev/license
|
7 | 7 | */
|
8 | 8 |
|
9 |
| -import { type PluginItem, transformAsync } from '@babel/core'; |
10 |
| -import fs from 'node:fs'; |
11 |
| -import path from 'node:path'; |
12 | 9 | import Piscina from 'piscina';
|
13 |
| -import { loadEsmModule } from '../../utils/load-esm'; |
| 10 | +import { BabelTransformOptions, transformWithBabel } from '../babel/transform'; |
14 | 11 |
|
15 |
| -interface JavaScriptTransformRequest { |
| 12 | +interface JavaScriptTransformRequest extends BabelTransformOptions { |
16 | 13 | filename: string;
|
17 | 14 | data: string | Uint8Array;
|
18 |
| - sourcemap: boolean; |
19 |
| - thirdPartySourcemaps: boolean; |
20 |
| - advancedOptimizations: boolean; |
21 |
| - skipLinker?: boolean; |
22 |
| - sideEffects?: boolean; |
23 |
| - jit: boolean; |
24 |
| - instrumentForCoverage?: boolean; |
25 | 15 | }
|
26 | 16 |
|
27 |
| -const textDecoder = new TextDecoder(); |
28 |
| -const textEncoder = new TextEncoder(); |
29 |
| - |
30 | 17 | export default async function transformJavaScript(
|
31 | 18 | request: JavaScriptTransformRequest,
|
32 | 19 | ): Promise<unknown> {
|
33 | 20 | const { filename, data, ...options } = request;
|
34 |
| - const textData = typeof data === 'string' ? data : textDecoder.decode(data); |
35 | 21 |
|
36 |
| - const transformedData = await transformWithBabel(filename, textData, options); |
| 22 | + const transformedData = await transformWithBabel(filename, data, options); |
37 | 23 |
|
38 | 24 | // Transfer the data via `move` instead of cloning
|
39 |
| - return Piscina.move(textEncoder.encode(transformedData)); |
40 |
| -} |
41 |
| - |
42 |
| -/** |
43 |
| - * Cached instance of the compiler-cli linker's createEs2015LinkerPlugin function. |
44 |
| - */ |
45 |
| -let linkerPluginCreator: |
46 |
| - | typeof import('@angular/compiler-cli/linker/babel').createEs2015LinkerPlugin |
47 |
| - | undefined; |
48 |
| - |
49 |
| -/** |
50 |
| - * Cached instance of the compiler-cli linker's needsLinking function. |
51 |
| - */ |
52 |
| -let needsLinking: typeof import('@angular/compiler-cli/linker').needsLinking | undefined; |
53 |
| - |
54 |
| -async function transformWithBabel( |
55 |
| - filename: string, |
56 |
| - data: string, |
57 |
| - options: Omit<JavaScriptTransformRequest, 'filename' | 'data'>, |
58 |
| -): Promise<string> { |
59 |
| - const shouldLink = !options.skipLinker && (await requiresLinking(filename, data)); |
60 |
| - const useInputSourcemap = |
61 |
| - options.sourcemap && |
62 |
| - (!!options.thirdPartySourcemaps || !/[\\/]node_modules[\\/]/.test(filename)); |
63 |
| - |
64 |
| - // @ts-expect-error Import attribute syntax plugin does not currently have type definitions |
65 |
| - const { default: importAttributePlugin } = await import('@babel/plugin-syntax-import-attributes'); |
66 |
| - const plugins: PluginItem[] = [importAttributePlugin]; |
67 |
| - |
68 |
| - if (options.instrumentForCoverage) { |
69 |
| - const { default: coveragePlugin } = await import('../babel/plugins/add-code-coverage.js'); |
70 |
| - plugins.push(coveragePlugin); |
71 |
| - } |
72 |
| - |
73 |
| - if (shouldLink) { |
74 |
| - // Lazy load the linker plugin only when linking is required |
75 |
| - const linkerPlugin = await createLinkerPlugin(options); |
76 |
| - plugins.push(linkerPlugin); |
77 |
| - } |
78 |
| - |
79 |
| - if (options.advancedOptimizations) { |
80 |
| - const sideEffectFree = options.sideEffects === false; |
81 |
| - const safeAngularPackage = |
82 |
| - sideEffectFree && /[\\/]node_modules[\\/]@angular[\\/]/.test(filename); |
83 |
| - |
84 |
| - const { adjustStaticMembers, adjustTypeScriptEnums, elideAngularMetadata, markTopLevelPure } = |
85 |
| - await import('../babel/plugins'); |
86 |
| - |
87 |
| - if (safeAngularPackage) { |
88 |
| - plugins.push(markTopLevelPure); |
89 |
| - } |
90 |
| - |
91 |
| - plugins.push(elideAngularMetadata, adjustTypeScriptEnums, [ |
92 |
| - adjustStaticMembers, |
93 |
| - { wrapDecorators: sideEffectFree }, |
94 |
| - ]); |
95 |
| - } |
96 |
| - |
97 |
| - // If no additional transformations are needed, return the data directly |
98 |
| - if (plugins.length === 0) { |
99 |
| - // Strip sourcemaps if they should not be used |
100 |
| - return useInputSourcemap ? data : data.replace(/^\/\/# sourceMappingURL=[^\r\n]*/gm, ''); |
101 |
| - } |
102 |
| - |
103 |
| - const result = await transformAsync(data, { |
104 |
| - filename, |
105 |
| - inputSourceMap: (useInputSourcemap ? undefined : false) as undefined, |
106 |
| - sourceMaps: useInputSourcemap ? 'inline' : false, |
107 |
| - compact: false, |
108 |
| - configFile: false, |
109 |
| - babelrc: false, |
110 |
| - browserslistConfigFile: false, |
111 |
| - plugins, |
112 |
| - }); |
113 |
| - |
114 |
| - const outputCode = result?.code ?? data; |
115 |
| - |
116 |
| - // Strip sourcemaps if they should not be used. |
117 |
| - // Babel will keep the original comments even if sourcemaps are disabled. |
118 |
| - return useInputSourcemap |
119 |
| - ? outputCode |
120 |
| - : outputCode.replace(/^\/\/# sourceMappingURL=[^\r\n]*/gm, ''); |
121 |
| -} |
122 |
| - |
123 |
| -async function requiresLinking(path: string, source: string): Promise<boolean> { |
124 |
| - // @angular/core and @angular/compiler will cause false positives |
125 |
| - // Also, TypeScript files do not require linking |
126 |
| - if (/[\\/]@angular[\\/](?:compiler|core)|\.tsx?$/.test(path)) { |
127 |
| - return false; |
128 |
| - } |
129 |
| - |
130 |
| - if (!needsLinking) { |
131 |
| - // Load ESM `@angular/compiler-cli/linker` using the TypeScript dynamic import workaround. |
132 |
| - // Once TypeScript provides support for keeping the dynamic import this workaround can be |
133 |
| - // changed to a direct dynamic import. |
134 |
| - const linkerModule = await loadEsmModule<typeof import('@angular/compiler-cli/linker')>( |
135 |
| - '@angular/compiler-cli/linker', |
136 |
| - ); |
137 |
| - needsLinking = linkerModule.needsLinking; |
138 |
| - } |
139 |
| - |
140 |
| - return needsLinking(path, source); |
141 |
| -} |
142 |
| - |
143 |
| -async function createLinkerPlugin(options: Omit<JavaScriptTransformRequest, 'filename' | 'data'>) { |
144 |
| - linkerPluginCreator ??= ( |
145 |
| - await loadEsmModule<typeof import('@angular/compiler-cli/linker/babel')>( |
146 |
| - '@angular/compiler-cli/linker/babel', |
147 |
| - ) |
148 |
| - ).createEs2015LinkerPlugin; |
149 |
| - |
150 |
| - const linkerPlugin = linkerPluginCreator({ |
151 |
| - linkerJitMode: options.jit, |
152 |
| - // This is a workaround until https://github.com/angular/angular/issues/42769 is fixed. |
153 |
| - sourceMapping: false, |
154 |
| - logger: { |
155 |
| - level: 1, // Info level |
156 |
| - debug(...args: string[]) { |
157 |
| - // eslint-disable-next-line no-console |
158 |
| - console.debug(args); |
159 |
| - }, |
160 |
| - info(...args: string[]) { |
161 |
| - // eslint-disable-next-line no-console |
162 |
| - console.info(args); |
163 |
| - }, |
164 |
| - warn(...args: string[]) { |
165 |
| - // eslint-disable-next-line no-console |
166 |
| - console.warn(args); |
167 |
| - }, |
168 |
| - error(...args: string[]) { |
169 |
| - // eslint-disable-next-line no-console |
170 |
| - console.error(args); |
171 |
| - }, |
172 |
| - }, |
173 |
| - fileSystem: { |
174 |
| - resolve: path.resolve, |
175 |
| - exists: fs.existsSync, |
176 |
| - dirname: path.dirname, |
177 |
| - relative: path.relative, |
178 |
| - readFile: fs.readFileSync, |
179 |
| - // Node.JS types don't overlap the Compiler types. |
180 |
| - // eslint-disable-next-line @typescript-eslint/no-explicit-any |
181 |
| - } as any, |
182 |
| - }); |
183 |
| - |
184 |
| - return linkerPlugin; |
| 25 | + return Piscina.move(transformedData); |
185 | 26 | }
|
0 commit comments