diff --git a/packages/angular/build/src/builders/dev-server/tests/behavior/build-source-map_spec.ts b/packages/angular/build/src/builders/dev-server/tests/behavior/build-source-map_spec.ts new file mode 100644 index 000000000000..232b1fed88a8 --- /dev/null +++ b/packages/angular/build/src/builders/dev-server/tests/behavior/build-source-map_spec.ts @@ -0,0 +1,49 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { executeDevServer } from '../../index'; +import { executeOnceAndFetch } from '../execute-fetch'; +import { describeServeBuilder } from '../jasmine-helpers'; +import { BASE_OPTIONS, DEV_SERVER_BUILDER_INFO } from '../setup'; + +describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupTarget) => { + describe('Behavior: "buildTarget sourceMap"', () => { + beforeEach(async () => { + // Application code is not needed for these tests + await harness.writeFile('src/main.ts', 'console.log("foo");'); + }); + + it('should not include sourcemaps when disabled', async () => { + setupTarget(harness, { + sourceMap: false, + }); + + harness.useTarget('serve', { + ...BASE_OPTIONS, + }); + + const { result, response } = await executeOnceAndFetch(harness, '/main.js'); + expect(result?.success).toBeTrue(); + expect(await response?.text()).not.toContain('//# sourceMappingURL='); + }); + + it('should include sourcemaps when enabled', async () => { + setupTarget(harness, { + sourceMap: true, + }); + + harness.useTarget('serve', { + ...BASE_OPTIONS, + }); + + const { result, response } = await executeOnceAndFetch(harness, '/main.js'); + expect(result?.success).toBeTrue(); + expect(await response?.text()).toContain('//# sourceMappingURL='); + }); + }); +}); diff --git a/packages/angular/build/src/builders/dev-server/vite/index.ts b/packages/angular/build/src/builders/dev-server/vite/index.ts index 27cb6d15adbb..8795052e16e4 100644 --- a/packages/angular/build/src/builders/dev-server/vite/index.ts +++ b/packages/angular/build/src/builders/dev-server/vite/index.ts @@ -130,9 +130,11 @@ export async function* serveWithVite( browserOptions.forceI18nFlatOutput = true; } - const { vendor: thirdPartySourcemaps, scripts: scriptsSourcemaps } = normalizeSourceMaps( - browserOptions.sourceMap ?? false, - ); + const { + vendor: thirdPartySourcemaps, + scripts: scriptsSourcemaps, + styles: stylesSourceMap, + } = normalizeSourceMaps(browserOptions.sourceMap ?? false); if (scriptsSourcemaps && browserOptions.server) { // https://nodejs.org/api/process.html#processsetsourcemapsenabledval @@ -441,6 +443,7 @@ export async function* serveWithVite( }, extensions?.middleware, transformers?.indexHtml, + scriptsSourcemaps || stylesSourceMap, thirdPartySourcemaps, ); diff --git a/packages/angular/build/src/builders/dev-server/vite/server.ts b/packages/angular/build/src/builders/dev-server/vite/server.ts index 0d48ce5325e2..624477853031 100644 --- a/packages/angular/build/src/builders/dev-server/vite/server.ts +++ b/packages/angular/build/src/builders/dev-server/vite/server.ts @@ -8,7 +8,7 @@ import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; -import type { Connect, InlineConfig, SSROptions, ServerOptions } from 'vite'; +import type { Connect, InlineConfig, Plugin, SSROptions, ServerOptions } from 'vite'; import type { ComponentStyleRecord } from '../../../tools/vite/middlewares'; import { ServerSsrMode, @@ -16,6 +16,7 @@ import { createAngularSetupMiddlewaresPlugin, createAngularSsrTransformPlugin, createRemoveIdPrefixPlugin, + removeSourceMapsPlugin, } from '../../../tools/vite/plugins'; import { EsbuildLoaderOption, getDepOptimizationConfig } from '../../../tools/vite/utils'; import { loadProxyConfiguration } from '../../../utils'; @@ -150,6 +151,7 @@ export async function setupServer( define: ApplicationBuilderInternalOptions['define'], extensionMiddleware?: Connect.NextHandleFunction[], indexHtmlTransformer?: (content: string) => Promise, + sourceMaps = true, thirdPartySourcemaps = false, ): Promise { // dynamically import Vite for ESM compatibility @@ -171,6 +173,33 @@ export async function setupServer( externalMetadata.explicitBrowser.length === 0 && ssrMode === ServerSsrMode.NoSsr; const cacheDir = join(serverOptions.cacheOptions.path, serverOptions.buildTarget.project, 'vite'); + const plugins: Plugin[] = [ + createAngularSetupMiddlewaresPlugin({ + outputFiles, + assets, + indexHtmlTransformer, + extensionMiddleware, + componentStyles, + templateUpdates, + ssrMode, + resetComponentUpdates: () => templateUpdates.clear(), + projectRoot: serverOptions.projectRoot, + }), + createRemoveIdPrefixPlugin(externalMetadata.explicitBrowser), + await createAngularSsrTransformPlugin(serverOptions.workspaceRoot), + await createAngularMemoryPlugin({ + virtualProjectRoot, + outputFiles, + templateUpdates, + external: externalMetadata.explicitBrowser, + disableViteTransport: !serverOptions.liveReload, + }), + ]; + + if (!sourceMaps) { + plugins.push(removeSourceMapsPlugin); + } + const configuration: InlineConfig = { configFile: false, envFile: false, @@ -182,7 +211,7 @@ export async function setupServer( // We use custom as we do not rely on Vite's htmlFallbackMiddleware and indexHtmlMiddleware. appType: 'custom', css: { - devSourcemap: true, + devSourcemap: sourceMaps, }, // Ensure custom 'file' loader build option entries are handled by Vite in application code that // reference third-party libraries. Relative usage is handled directly by the build and not Vite. @@ -219,28 +248,7 @@ export async function setupServer( thirdPartySourcemaps, define, ), - plugins: [ - createAngularSetupMiddlewaresPlugin({ - outputFiles, - assets, - indexHtmlTransformer, - extensionMiddleware, - componentStyles, - templateUpdates, - ssrMode, - resetComponentUpdates: () => templateUpdates.clear(), - projectRoot: serverOptions.projectRoot, - }), - createRemoveIdPrefixPlugin(externalMetadata.explicitBrowser), - await createAngularSsrTransformPlugin(serverOptions.workspaceRoot), - await createAngularMemoryPlugin({ - virtualProjectRoot, - outputFiles, - templateUpdates, - external: externalMetadata.explicitBrowser, - disableViteTransport: !serverOptions.liveReload, - }), - ], + plugins, // Browser only optimizeDeps. (This does not run for SSR dependencies). optimizeDeps: getDepOptimizationConfig({ // Only enable with caching since it causes prebundle dependencies to be cached diff --git a/packages/angular/build/src/tools/vite/plugins/index.ts b/packages/angular/build/src/tools/vite/plugins/index.ts index ef697aa7395a..4d21e856ab68 100644 --- a/packages/angular/build/src/tools/vite/plugins/index.ts +++ b/packages/angular/build/src/tools/vite/plugins/index.ts @@ -10,3 +10,4 @@ export { createAngularMemoryPlugin } from './angular-memory-plugin'; export { createRemoveIdPrefixPlugin } from './id-prefix-plugin'; export { createAngularSetupMiddlewaresPlugin, ServerSsrMode } from './setup-middlewares-plugin'; export { createAngularSsrTransformPlugin } from './ssr-transform-plugin'; +export { removeSourceMapsPlugin } from './remove-sourcemaps'; diff --git a/packages/angular/build/src/tools/vite/plugins/remove-sourcemaps.ts b/packages/angular/build/src/tools/vite/plugins/remove-sourcemaps.ts new file mode 100644 index 000000000000..5942e8dbe98d --- /dev/null +++ b/packages/angular/build/src/tools/vite/plugins/remove-sourcemaps.ts @@ -0,0 +1,19 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import type { Plugin } from 'vite'; + +export const removeSourceMapsPlugin: Plugin = { + name: 'vite:angular-remove-sourcemaps', + transform(code) { + return { + code, + map: { mappings: '' }, + }; + }, +};