66 * found in the LICENSE file at https://angular.dev/license
77 */
88
9- import remapping , { SourceMapInput } from '@ampproject/remapping' ;
10- import { NodePath , PluginItem , parseSync , transformFromAstAsync , types } from '@babel/core ' ;
9+ import remapping , { type EncodedSourceMap , type SourceMapInput } from '@ampproject/remapping' ;
10+ import MagicString from 'magic-string ' ;
1111import assert from 'node:assert' ;
1212import { workerData } from 'node:worker_threads' ;
13- import { assertIsError } from '../../utils/error ' ;
13+ import { Visitor , parseSync } from 'oxc-parser ' ;
1414
1515/**
1616 * The options passed to the inliner for each file request
@@ -80,11 +80,7 @@ export default async function inlineFile(request: InlineFileRequest) {
8080
8181 const code = await data . text ( ) ;
8282 const map = await files . get ( request . filename + '.map' ) ?. text ( ) ;
83- const result = await transformWithBabel (
84- code ,
85- map && ( JSON . parse ( map ) as SourceMapInput ) ,
86- request ,
87- ) ;
83+ const result = await transformWithOxc ( code , map && ( JSON . parse ( map ) as SourceMapInput ) , request ) ;
8884
8985 return {
9086 file : request . filename ,
@@ -102,7 +98,7 @@ export default async function inlineFile(request: InlineFileRequest) {
10298 * @returns An object containing the inlined code.
10399 */
104100export async function inlineCode ( request : InlineCodeRequest ) {
105- const result = await transformWithBabel ( request . code , undefined , request ) ;
101+ const result = await transformWithOxc ( request . code , undefined , request ) ;
106102
107103 return {
108104 output : result . code ,
@@ -136,95 +132,93 @@ async function loadLocalizeTools(): Promise<LocalizeUtilityModule> {
136132}
137133
138134/**
139- * Creates the needed Babel plugins to inline a given locale and translation for a JavaScript file.
140- * @param locale A string containing the locale specifier to use.
141- * @param translation A object record containing locale specific messages to use.
142- * @returns An array of Babel plugins.
143- */
144- async function createI18nPlugins ( locale : string , translation : Record < string , unknown > | undefined ) {
145- const { Diagnostics, makeEs2015TranslatePlugin } = await loadLocalizeTools ( ) ;
146-
147- const plugins : PluginItem [ ] = [ ] ;
148- const diagnostics = new Diagnostics ( ) ;
149-
150- plugins . push (
151- makeEs2015TranslatePlugin ( diagnostics , translation || { } , {
152- missingTranslation : translation === undefined ? 'ignore' : missingTranslation ,
153- } ) as unknown as PluginItem ,
154- ) ;
155-
156- // Create a plugin to replace the locale specifier constant inject by the build system with the actual specifier
157- plugins . push ( ( ) => ( {
158- visitor : {
159- StringLiteral ( path : NodePath < types . StringLiteral > ) {
160- if ( path . node . value === '___NG_LOCALE_INSERT___' ) {
161- path . replaceWith ( types . stringLiteral ( locale ) ) ;
162- }
163- } ,
164- } ,
165- } ) ) ;
166-
167- return { diagnostics, plugins } ;
168- }
169-
170- /**
171- * Transforms a JavaScript file using Babel to inline the request locale and translation.
135+ * Transforms a JavaScript file using OXC and Magic-String to inline the request locale and translation.
172136 * @param code A string containing the JavaScript code to transform.
173137 * @param map A sourcemap object for the provided JavaScript code.
174138 * @param options The inline request options to use.
175139 * @returns An object containing the code, map, and diagnostics from the transformation.
176140 */
177- async function transformWithBabel (
141+ async function transformWithOxc (
178142 code : string ,
179143 map : SourceMapInput | undefined ,
180144 options : InlineFileRequest ,
181145) {
182- let ast ;
183- try {
184- ast = parseSync ( code , {
185- babelrc : false ,
186- configFile : false ,
187- sourceType : 'unambiguous' ,
188- filename : options . filename ,
189- } ) ;
190- } catch ( error ) {
191- assertIsError ( error ) ;
192-
193- // Make the error more readable.
194- // Same errors will contain the full content of the file as the error message
195- // Which makes it hard to find the actual error message.
196- const index = error . message . indexOf ( ')\n' ) ;
197- const msg = index !== - 1 ? error . message . slice ( 0 , index + 1 ) : error . message ;
198- throw new Error ( `${ msg } \nAn error occurred inlining file "${ options . filename } "` , {
199- cause : error ,
200- } ) ;
201- }
146+ const { program } = parseSync ( options . filename , code , {
147+ sourceType : 'unambiguous' ,
148+ } ) ;
202149
203- if ( ! ast ) {
204- throw new Error ( `Unknown error occurred inlining file "${ options . filename } "` ) ;
150+ if ( ! program ) {
151+ throw new Error ( `Unknown error occurred parsing file "${ options . filename } " with OXC. ` ) ;
205152 }
206153
207- const { diagnostics, plugins } = await createI18nPlugins ( options . locale , options . translation ) ;
208- const transformResult = await transformFromAstAsync ( ast , code , {
209- filename : options . filename ,
210- // false is a valid value but not included in the type definition
211- inputSourceMap : false as unknown as undefined ,
212- sourceMaps : ! ! map ,
213- compact : shouldOptimize ,
214- configFile : false ,
215- babelrc : false ,
216- browserslistConfigFile : false ,
217- plugins,
154+ const magicString = new MagicString ( code ) ;
155+ const { Diagnostics, translate } = await loadLocalizeTools ( ) ;
156+ const diagnostics = new Diagnostics ( ) ;
157+
158+ const visitor = new Visitor ( {
159+ Literal ( node ) {
160+ if ( typeof node . value === 'string' && node . value === '___NG_LOCALE_INSERT___' ) {
161+ magicString . overwrite ( node . start , node . end , JSON . stringify ( options . locale ) ) ;
162+ }
163+ } ,
164+ 'TaggedTemplateExpression:exit' ( node ) {
165+ if ( node . tag . type === 'Identifier' && node . tag . name === '$localize' ) {
166+ const cooked = node . quasi . quasis . map ( ( q ) => q . value . cooked ) ;
167+ const raw = node . quasi . quasis . map ( ( q ) => q . value . raw ) ;
168+ const messageParts = Object . assign ( cooked , { raw } ) as unknown as TemplateStringsArray ;
169+
170+ const [ translatedParts , translatedSubstitutions ] = translate (
171+ diagnostics ,
172+ options . translation || { } ,
173+ messageParts ,
174+ node . quasi . expressions . map ( ( _ , index ) => index ) ,
175+ options . translation === undefined ? 'ignore' : missingTranslation ,
176+ ) ;
177+
178+ // Reconstruct the new template/string literal replacement
179+ let replacement : string ;
180+ if ( translatedSubstitutions . length === 0 ) {
181+ replacement = JSON . stringify ( translatedParts [ 0 ] ) ;
182+ } else {
183+ replacement = '`' ;
184+ for ( let i = 0 ; i < translatedParts . length ; i ++ ) {
185+ const escapedPart = translatedParts [ i ]
186+ . replace ( / \\ / g, '\\\\' )
187+ . replace ( / ` / g, '\\`' )
188+ . replace ( / \$ \{ / g, '\\${' ) ;
189+ replacement += escapedPart ;
190+
191+ if ( i < translatedSubstitutions . length ) {
192+ const originalIndex = translatedSubstitutions [ i ] ;
193+ const exprNode = node . quasi . expressions [ originalIndex ] ;
194+ const exprCode = magicString . slice ( exprNode . start , exprNode . end ) ;
195+ replacement += '${' + exprCode + '}' ;
196+ }
197+ }
198+ replacement += '`' ;
199+ }
200+
201+ magicString . overwrite ( node . start , node . end , replacement ) ;
202+ }
203+ } ,
218204 } ) ;
219205
220- if ( ! transformResult || ! transformResult . code ) {
221- throw new Error ( `Unknown error occurred processing bundle for "${ options . filename } ".` ) ;
222- }
206+ visitor . visit ( program ) ;
223207
208+ const outputCode = magicString . toString ( ) ;
224209 let outputMap ;
225- if ( map && transformResult . map ) {
226- outputMap = remapping ( [ transformResult . map as SourceMapInput , map ] , ( ) => null ) ;
210+ if ( map && magicString . hasChanged ( ) ) {
211+ const rawMap = magicString . generateMap ( {
212+ source : options . filename ,
213+ includeContent : true ,
214+ hires : 'boundary' ,
215+ } ) ;
216+ outputMap = remapping ( [ rawMap as EncodedSourceMap , map ] , ( ) => null ) ;
227217 }
228218
229- return { code : transformResult . code , map : outputMap && JSON . stringify ( outputMap ) , diagnostics } ;
219+ return {
220+ code : outputCode ,
221+ map : outputMap && JSON . stringify ( outputMap ) ,
222+ diagnostics,
223+ } ;
230224}
0 commit comments