Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 3 additions & 13 deletions packages/angular/build/src/tools/angular/linker/oxc-linker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
*/

import type { DecodedSourceMap } from '@ampproject/remapping';
import remapping from '@ampproject/remapping';
import { ConsoleLogger, LogLevel } from '@angular/compiler-cli';
import type { DeclarationScope } from '@angular/compiler-cli/linker';
import { FileLinker, LinkerEnvironment, needsLinking } from '@angular/compiler-cli/linker';
Expand All @@ -18,7 +17,6 @@ import type {
import type { CallExpression, Node } from '@oxc-project/types';
import MagicString from 'magic-string';
import { parseSync, visitorKeys } from 'oxc-parser';
import { loadInputSourceMap } from '../../../utils/source-map';
import { OxcAstHost } from './oxc-ast-host';
import { StringAstFactory } from './string-ast-factory';

Expand Down Expand Up @@ -168,18 +166,10 @@ export function linkWithOxc(filename: string, code: string, options: OxcLinkerOp
return { code, map: undefined };
}

let map: string | undefined;
let map: DecodedSourceMap | undefined;
if (options.sourcemap) {
const inputMap = loadInputSourceMap(filename, code);
if (inputMap) {
const rawMap = s.generateDecodedMap({ hires: true, source: filename });
map = remapping(
[{ ...rawMap, version: 3 } satisfies DecodedSourceMap, inputMap],
() => null,
).toString();
} else {
map = s.generateMap({ hires: true, source: filename }).toString();
}
const rawMap = s.generateDecodedMap({ hires: true, source: filename });
map = { ...rawMap, version: 3 };
}

return {
Expand Down
36 changes: 3 additions & 33 deletions packages/angular/build/src/tools/angular/linker/oxc-linker_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ describe('linkWithOxc', () => {
expect(result.code).not.toContain('i0.ɵɵngDeclareComponent');
});

it('should generate a sourcemap when sourcemap option is enabled', () => {
it('should generate a decoded sourcemap when sourcemap option is enabled', () => {
const input = `
import * as i0 from "@angular/core";
export class MyDirective {}
Expand All @@ -69,37 +69,7 @@ describe('linkWithOxc', () => {

const result = linkWithOxc('test.js', input, { sourcemap: true });
expect(result.map).toBeDefined();
const parsedMap = JSON.parse(result.map as string);
expect(parsedMap.version).toBe(3);
expect(parsedMap.sources).toContain('test.js');
});

it('should remap with input sourcemap when sourcemap option is enabled and inputMap is present', () => {
const inputMap = {
version: 3,
sources: ['original.ts'],
sourcesContent: ['// original content'],
mappings: 'AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA',
names: [],
};
const base64Map = Buffer.from(JSON.stringify(inputMap)).toString('base64');
const input = `
import * as i0 from "@angular/core";
export class MyDirective {}
MyDirective.ɵdir = i0.ɵɵngDeclareDirective({
minVersion: "12.0.0",
version: "14.0.0",
ngImport: i0,
type: MyDirective,
selector: "[my-dir]"
});
//# sourceMappingURL=data:application/json;base64,${base64Map}
`;

const result = linkWithOxc('test.js', input, { sourcemap: true });
expect(result.map).toBeDefined();
const parsedMap = JSON.parse(result.map as string);
expect(parsedMap.version).toBe(3);
expect(parsedMap.sources).toContain('original.ts');
expect(result.map?.version).toBe(3);
expect(result.map?.sources).toContain('test.js');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.dev/license
*/

import remapping, { type DecodedSourceMap, type EncodedSourceMap } from '@ampproject/remapping';
import { type PluginItem, transformAsync } from '@babel/core';
import { createRequire } from 'node:module';
import Piscina from 'piscina';
Expand Down Expand Up @@ -39,7 +40,7 @@ async function instrumentCoverage(
filename: string,
data: string,
useInputSourcemap: boolean,
): Promise<string> {
): Promise<{ code: string; map?: EncodedSourceMap }> {
try {
let resolvedPath = 'istanbul-lib-instrument';
try {
Expand All @@ -63,15 +64,14 @@ async function instrumentCoverage(
filename,
inputSourceMap as Parameters<typeof instrumenter.instrumentSync>[2],
);
const lastMap = instrumenter.lastSourceMap();

if (useInputSourcemap && lastMap) {
const inlineMap = Buffer.from(JSON.stringify(lastMap)).toString('base64');

return instrumentedCode + `\n//# sourceMappingURL=data:application/json;base64,${inlineMap}`;
}

return removeSourceMappingURL(instrumentedCode);
const lastMap = useInputSourcemap
? (instrumenter.lastSourceMap() as EncodedSourceMap)
: undefined;

return {
code: instrumentedCode,
map: lastMap ?? undefined,
};
} catch (error) {
throw new Error(
`The 'istanbul-lib-instrument' package is required for code coverage but was not found. Please install the package.`,
Expand All @@ -97,6 +97,11 @@ export default async function transformJavaScript(
*/
let oxcLinkerModule: typeof import('../angular/linker/oxc-linker.js') | undefined;

/**
* Cached instance of the OXC transform module.
*/
let oxcTransformModule: typeof import('../oxc/oxc-transform.js') | undefined;

async function transformJavaScriptImpl(
filename: string,
data: string,
Expand All @@ -108,9 +113,13 @@ async function transformJavaScriptImpl(
(!!options.thirdPartySourcemaps || !/[\\/]node_modules[\\/]/.test(filename));

let code = data;
const maps: (DecodedSourceMap | EncodedSourceMap)[] = [];
let coverageMap: EncodedSourceMap | undefined;

if (options.instrumentForCoverage) {
code = await instrumentCoverage(filename, code, useInputSourcemap);
const result = await instrumentCoverage(filename, code, useInputSourcemap);
code = result.code;
coverageMap = result.map;
}

if (shouldLink) {
Expand All @@ -120,8 +129,8 @@ async function transformJavaScriptImpl(

const result = await transformAsync(code, {
filename,
inputSourceMap: (useInputSourcemap ? undefined : false) as undefined,
sourceMaps: useInputSourcemap ? 'inline' : false,
inputSourceMap: false,
sourceMaps: !!useInputSourcemap,
compact: false,
configFile: false,
babelrc: false,
Expand All @@ -144,6 +153,9 @@ async function transformJavaScriptImpl(
});

code = result?.code ?? code;
if (result?.map) {
maps.push(result.map as EncodedSourceMap);
}
} else {
oxcLinkerModule ??= await import('../angular/linker/oxc-linker.js');
const result = oxcLinkerModule.linkWithOxc(filename, code, {
Expand All @@ -152,39 +164,52 @@ async function transformJavaScriptImpl(
skipCheck: true,
});
code = result.code;
if (useInputSourcemap && result.map) {
code = removeSourceMappingURL(code);
const base64Map = Buffer.from(result.map).toString('base64');
code += `\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,${base64Map}`;
if (result.map) {
maps.push(result.map);
}
}
}

// Run advanced optimizations using our fast oxc-transform
if (options.advancedOptimizations) {
const { transform } = await import('../oxc/oxc-transform.js');
oxcTransformModule ??= await import('../oxc/oxc-transform.js');
const sideEffectFree = options.sideEffects === false;
const safeAngularPackage =
sideEffectFree && /[\\/]node_modules[\\/]@angular[\\/]/.test(filename);
const topLevelSafeMode = !safeAngularPackage;

const result = transform(filename, code, {
const result = oxcTransformModule.transform(filename, code, {
sourcemap: useInputSourcemap,
sideEffects: options.sideEffects,
topLevelSafeMode,
});
code = result.code;
if (result.map) {
maps.push(result.map);
}
}

if (useInputSourcemap && result.map) {
// Strip old source map comment if Babel added one
if (useInputSourcemap) {
const baseMap = coverageMap ?? loadInputSourceMap(filename, data);
if (maps.length > 0 || coverageMap) {
code = removeSourceMappingURL(code);
const base64Map = Buffer.from(result.map).toString('base64');
code += `\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,${base64Map}`;
const remappingChain: (DecodedSourceMap | EncodedSourceMap)[] = maps.reverse();
if (baseMap) {
remappingChain.push(baseMap);
}

if (remappingChain.length > 0) {
const finalMap = remapping(remappingChain, () => null).toString();
const base64Map = Buffer.from(finalMap).toString('base64');
code += `\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,${base64Map}`;
}
}

return code;
}

// Strip sourcemaps if they should not be used
return useInputSourcemap ? code : removeSourceMappingURL(code);
return removeSourceMappingURL(code);
}

function requiresLinking(path: string, source: string): boolean {
Expand Down
Loading