From e406cb22cba7fb276acd2cfe4c2f91a0e05fa21b Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Wed, 20 Dec 2023 09:57:18 +0000 Subject: [PATCH] feat(@angular-devkit/build-angular): emit external sourcemaps for component styles This commits, changes the behaviour in the esbuild based builders by emitting component sourcemaps in external files instead of inlining them. Closes #24049 and closes #26676 --- .../tests/options/sourcemap_spec.ts | 36 ++++++++ .../tools/esbuild/angular/compiler-plugin.ts | 7 +- .../esbuild/angular/component-stylesheets.ts | 89 +++++++++---------- .../esbuild/angular/jit-plugin-callbacks.ts | 4 +- .../tools/esbuild/compiler-plugin-options.ts | 2 +- .../esbuild/stylesheets/bundle-options.ts | 2 +- 6 files changed, 88 insertions(+), 52 deletions(-) diff --git a/packages/angular_devkit/build_angular/src/builders/application/tests/options/sourcemap_spec.ts b/packages/angular_devkit/build_angular/src/builders/application/tests/options/sourcemap_spec.ts index aa82ad7c2d6b..9dfc7a57534d 100644 --- a/packages/angular_devkit/build_angular/src/builders/application/tests/options/sourcemap_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/application/tests/options/sourcemap_spec.ts @@ -136,5 +136,41 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { harness.expectFile('dist/browser/main.js.map').content.toContain('"x_google_ignoreList"'); }); + + it('should generate component sourcemaps when sourcemaps when true', async () => { + await harness.writeFile('src/app/app.component.css', `* { color: red}`); + + harness.useTarget('build', { + ...BASE_OPTIONS, + sourceMap: true, + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBeTrue(); + + harness + .expectFile('dist/browser/main.js') + .content.toContain('sourceMappingURL=app.component.css.map'); + harness.expectFile('dist/browser/app.component.css.map').toExist(); + }); + + it('should not generate component sourcemaps when sourcemaps when false', async () => { + await harness.writeFile('src/app/app.component.css', `* { color: red}`); + + harness.useTarget('build', { + ...BASE_OPTIONS, + sourceMap: false, + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBeTrue(); + + harness + .expectFile('dist/browser/main.js') + .content.not.toContain('sourceMappingURL=app.component.css.map'); + harness.expectFile('dist/browser/app.component.css.map').toNotExist(); + }); }); }); diff --git a/packages/angular_devkit/build_angular/src/tools/esbuild/angular/compiler-plugin.ts b/packages/angular_devkit/build_angular/src/tools/esbuild/angular/compiler-plugin.ts index 936cd3537b87..1d85e2554053 100644 --- a/packages/angular_devkit/build_angular/src/tools/esbuild/angular/compiler-plugin.ts +++ b/packages/angular_devkit/build_angular/src/tools/esbuild/angular/compiler-plugin.ts @@ -164,14 +164,15 @@ export function createCompilerPlugin( ); } - const { contents, resourceFiles, referencedFiles, errors, warnings } = stylesheetResult; + const { contents, outputFiles, metafile, referencedFiles, errors, warnings } = + stylesheetResult; if (errors) { (result.errors ??= []).push(...errors); } (result.warnings ??= []).push(...warnings); additionalResults.set(stylesheetFile ?? containingFile, { - outputFiles: resourceFiles, - metafile: stylesheetResult.metafile, + outputFiles, + metafile, }); if (referencedFiles) { diff --git a/packages/angular_devkit/build_angular/src/tools/esbuild/angular/component-stylesheets.ts b/packages/angular_devkit/build_angular/src/tools/esbuild/angular/component-stylesheets.ts index ce1d68ea9655..0a3d0d38300e 100644 --- a/packages/angular_devkit/build_angular/src/tools/esbuild/angular/component-stylesheets.ts +++ b/packages/angular_devkit/build_angular/src/tools/esbuild/angular/component-stylesheets.ts @@ -57,7 +57,7 @@ export class ComponentStylesheetBundler { }); }); - return extractResult(await bundlerContext.bundle(), bundlerContext.watchFiles); + return this.extractResult(await bundlerContext.bundle(), bundlerContext.watchFiles); } async bundleInline(data: string, filename: string, language: string) { @@ -103,7 +103,7 @@ export class ComponentStylesheetBundler { }); // Extract the result of the bundling from the output files - return extractResult(await bundlerContext.bundle(), bundlerContext.watchFiles); + return this.extractResult(await bundlerContext.bundle(), bundlerContext.watchFiles); } invalidate(files: Iterable) { @@ -128,52 +128,51 @@ export class ComponentStylesheetBundler { await Promise.allSettled(contexts.map((context) => context.dispose())); } -} -function extractResult(result: BundleContextResult, referencedFiles?: Set) { - let contents = ''; - let map; - let outputPath; - const resourceFiles: OutputFile[] = []; - if (!result.errors) { - for (const outputFile of result.outputFiles) { - const filename = path.basename(outputFile.path); - if (outputFile.type === BuildOutputFileType.Media) { - // The output files could also contain resources (images/fonts/etc.) that were referenced - resourceFiles.push(outputFile); - } else if (filename.endsWith('.css')) { - outputPath = outputFile.path; - contents = outputFile.text; - } else if (filename.endsWith('.css.map')) { - map = outputFile.text; - } else { - throw new Error( - `Unexpected non CSS/Media file "${filename}" outputted during component stylesheet processing.`, - ); + private extractResult(result: BundleContextResult, referencedFiles?: Set) { + let contents = ''; + const outputFiles: OutputFile[] = []; + if (!result.errors) { + for (const outputFile of result.outputFiles) { + const filename = path.basename(outputFile.path); + + // Needed for Bazel as otherwise the files will not be written in the correct place. + outputFile.path = path.join(this.options.workspaceRoot, outputFile.path); + + if (outputFile.type === BuildOutputFileType.Media) { + // The output files could also contain resources (images/fonts/etc.) that were referenced + outputFiles.push(outputFile); + } else if (filename.endsWith('.css')) { + contents = outputFile.text; + } else if (filename.endsWith('.css.map')) { + outputFiles.push(outputFile); + } else { + throw new Error( + `Unexpected non CSS/Media file "${filename}" outputted during component stylesheet processing.`, + ); + } } } - } - let metafile; - if (!result.errors) { - metafile = result.metafile; - // Remove entryPoint fields from outputs to prevent the internal component styles from being - // treated as initial files. Also mark the entry as a component resource for stat reporting. - Object.values(metafile.outputs).forEach((output) => { - delete output.entryPoint; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (output as any)['ng-component'] = true; - }); - } + let metafile; + if (!result.errors) { + metafile = result.metafile; + // Remove entryPoint fields from outputs to prevent the internal component styles from being + // treated as initial files. Also mark the entry as a component resource for stat reporting. + Object.values(metafile.outputs).forEach((output) => { + delete output.entryPoint; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (output as any)['ng-component'] = true; + }); + } - return { - errors: result.errors, - warnings: result.warnings, - contents, - map, - path: outputPath, - resourceFiles, - metafile, - referencedFiles, - }; + return { + errors: result.errors, + warnings: result.warnings, + contents, + outputFiles, + metafile, + referencedFiles, + }; + } } diff --git a/packages/angular_devkit/build_angular/src/tools/esbuild/angular/jit-plugin-callbacks.ts b/packages/angular_devkit/build_angular/src/tools/esbuild/angular/jit-plugin-callbacks.ts index b6bc41bd5e67..2d0bb89b6d2e 100644 --- a/packages/angular_devkit/build_angular/src/tools/esbuild/angular/jit-plugin-callbacks.ts +++ b/packages/angular_devkit/build_angular/src/tools/esbuild/angular/jit-plugin-callbacks.ts @@ -121,10 +121,10 @@ export function setupJitPluginCallbacks( ); } - const { contents, resourceFiles, errors, warnings, metafile, referencedFiles } = + const { contents, outputFiles, errors, warnings, metafile, referencedFiles } = stylesheetResult; - additionalResultFiles.set(entry.path, { outputFiles: resourceFiles, metafile }); + additionalResultFiles.set(entry.path, { outputFiles, metafile }); return { errors, diff --git a/packages/angular_devkit/build_angular/src/tools/esbuild/compiler-plugin-options.ts b/packages/angular_devkit/build_angular/src/tools/esbuild/compiler-plugin-options.ts index 78035e9e0349..b531bf263a13 100644 --- a/packages/angular_devkit/build_angular/src/tools/esbuild/compiler-plugin-options.ts +++ b/packages/angular_devkit/build_angular/src/tools/esbuild/compiler-plugin-options.ts @@ -60,7 +60,7 @@ export function createCompilerPluginOptions( // Hidden component stylesheet sourcemaps are inaccessible which is effectively // the same as being disabled. Disabling has the advantage of avoiding the overhead // of sourcemap processing. - !!sourcemapOptions.styles && (sourcemapOptions.hidden ? false : 'inline'), + sourcemapOptions.styles && !sourcemapOptions.hidden ? 'linked' : false, outputNames, includePaths: stylePreprocessorOptions?.includePaths, externalDependencies, diff --git a/packages/angular_devkit/build_angular/src/tools/esbuild/stylesheets/bundle-options.ts b/packages/angular_devkit/build_angular/src/tools/esbuild/stylesheets/bundle-options.ts index e63461bd78fd..554e1a9c7180 100644 --- a/packages/angular_devkit/build_angular/src/tools/esbuild/stylesheets/bundle-options.ts +++ b/packages/angular_devkit/build_angular/src/tools/esbuild/stylesheets/bundle-options.ts @@ -22,7 +22,7 @@ export interface BundleStylesheetOptions { optimization: boolean; inlineFonts: boolean; preserveSymlinks?: boolean; - sourcemap: boolean | 'external' | 'inline'; + sourcemap: boolean | 'external' | 'inline' | 'linked'; outputNames: { bundles: string; media: string }; includePaths?: string[]; externalDependencies?: string[];