From fcc7b60a30e8e76f792ed483015139c2925a767a Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Tue, 23 Jun 2026 13:28:49 +0000 Subject: [PATCH 1/2] refactor(@angular/build): migrate to optimizeDeps.rolldownOptions in Vite config Vite now uses Rolldown as the underlying engine to optimize dependencies, rendering `optimizeDeps.esbuildOptions` deprecated. This commit refactors the Vite dev-server config in `@angular/build` to use `optimizeDeps.rolldownOptions` instead. It also updates the custom dependency optimization plugin from the esbuild-specific format to a native Rolldown plugin using the `load` hook, and aligns browser target lowering by pushing `es2016` when Zone.js is present (since Rolldown lacks esbuild-style feature-support flags). --- .../src/builders/dev-server/vite/index.ts | 23 ++++-- .../src/builders/dev-server/vite/server.ts | 44 ++++++------ .../angular/build/src/tools/esbuild/utils.ts | 1 - .../angular/build/src/tools/vite/utils.ts | 71 +++++++------------ 4 files changed, 67 insertions(+), 72 deletions(-) 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 a77b6fdfa211..7e50f7d89303 100644 --- a/packages/angular/build/src/builders/dev-server/vite/index.ts +++ b/packages/angular/build/src/builders/dev-server/vite/index.ts @@ -15,7 +15,7 @@ import type * as Vite from 'vite' with { }; import type { ComponentStyleRecord } from '../../../tools/vite/middlewares'; import { ServerSsrMode } from '../../../tools/vite/plugins'; -import { EsbuildLoaderOption, updateExternalMetadata } from '../../../tools/vite/utils'; +import { updateExternalMetadata } from '../../../tools/vite/utils'; import { normalizeSourceMaps } from '../../../utils'; import { useComponentStyleHmr, useComponentTemplateHmr } from '../../../utils/environment-options'; import { Result, ResultKind } from '../../application/results'; @@ -384,12 +384,17 @@ export async function* serveWithVite( const projectRoot = join(context.workspaceRoot, root as string); const browsers = getSupportedBrowsers(projectRoot, context.logger); - const target = transformSupportedBrowsersToTargets(browsers); // Needed for browser-esbuild as polyfills can be a string. const polyfills = Array.isArray((browserOptions.polyfills ??= [])) ? browserOptions.polyfills : [browserOptions.polyfills]; + const target = transformSupportedBrowsersToTargets(browsers); + if (!isZonelessApp(polyfills)) { + // Rolldown doesn't have an option to support Zone.js/async-await, so we need to support es2016. + target.push('es2016'); + } + let ssrMode: ServerSsrMode = ServerSsrMode.NoSsr; if ( browserOptions.outputMode && @@ -408,6 +413,16 @@ export async function* serveWithVite( }); } + // Copy the loader and modify the file extension to be asset + const loader = browserOptions.loader; + if (loader) { + for (const [key, value] of Object.entries(loader)) { + if (value === 'file') { + loader[key] = 'asset'; + } + } + } + // Setup server and start listening const serverConfiguration = await setupServer( serverOptions, @@ -418,12 +433,12 @@ export async function* serveWithVite( ssrMode, prebundleTransformer, target, - isZonelessApp(polyfills), componentStyles, templateUpdates, - browserOptions.loader as EsbuildLoaderOption | undefined, + loader, { ...browserOptions.define, + 'ngServerMode': 'false', 'ngJitMode': browserOptions.aot ? 'false' : 'true', 'ngHmrMode': browserOptions.templateUpdates ? 'true' : 'false', }, 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 8ff10e8e32b6..4f777b2f3622 100644 --- a/packages/angular/build/src/builders/dev-server/vite/server.ts +++ b/packages/angular/build/src/builders/dev-server/vite/server.ts @@ -20,7 +20,7 @@ import { createAngularSsrTransformPlugin, createRemoveIdPrefixPlugin, } from '../../../tools/vite/plugins'; -import { EsbuildLoaderOption, getDepOptimizationConfig } from '../../../tools/vite/utils'; +import { RolldownLoaderOption, getDepOptimizationConfig } from '../../../tools/vite/utils'; import { loadProxyConfiguration } from '../../../utils'; import { type ApplicationBuilderInternalOptions, JavaScriptTransformer } from '../internal'; import type { NormalizedDevServerOptions } from '../options'; @@ -82,13 +82,16 @@ async function createServerConfig( }, }; - if (serverOptions.ssl) { - if (serverOptions.sslCert && serverOptions.sslKey) { - server.https = { - cert: await readFile(serverOptions.sslCert), - key: await readFile(serverOptions.sslKey), - }; - } + if (serverOptions.ssl && serverOptions.sslCert && serverOptions.sslKey) { + const [cert, key] = await Promise.all([ + readFile(serverOptions.sslCert), + readFile(serverOptions.sslKey), + ]); + + server.https = { + cert, + key, + }; } return server; @@ -98,9 +101,7 @@ function createSsrConfig( externalMetadata: DevServerExternalResultMetadata, serverOptions: NormalizedDevServerOptions, prebundleTransformer: JavaScriptTransformer, - zoneless: boolean, - target: string[], - prebundleLoaderExtensions: EsbuildLoaderOption | undefined, + prebundleLoaderExtensions: RolldownLoaderOption | undefined, thirdPartySourcemaps: boolean, define: ApplicationBuilderInternalOptions['define'], ): Vite.SSROptions { @@ -116,13 +117,13 @@ function createSsrConfig( exclude: externalMetadata.explicitServer, // Include all implict dependencies from the external packages internal option include: externalMetadata.implicitServer, - ssr: true, prebundleTransformer, - zoneless, - target, loader: prebundleLoaderExtensions, thirdPartySourcemaps, - define, + define: { + ...define, + 'ngServerMode': 'true', + }, }), }; } @@ -136,10 +137,9 @@ export async function setupServer( ssrMode: ServerSsrMode, prebundleTransformer: JavaScriptTransformer, target: string[], - zoneless: boolean, componentStyles: Map, templateUpdates: Map, - prebundleLoaderExtensions: EsbuildLoaderOption | undefined, + prebundleLoaderExtensions: RolldownLoaderOption | undefined, define: ApplicationBuilderInternalOptions['define'], extensionMiddleware?: Vite.Connect.NextHandleFunction[], indexHtmlTransformer?: (content: string) => Promise, @@ -182,7 +182,7 @@ export async function setupServer( assetsInclude: prebundleLoaderExtensions && Object.entries(prebundleLoaderExtensions) - .filter(([, value]) => value === 'file') + .filter(([, value]) => value === 'asset') // Create a file extension glob for each key .map(([key]) => '*' + key), // Vite will normalize the `base` option by adding a leading slash. @@ -201,6 +201,9 @@ export async function setupServer( preTransformRequests, cacheDir, ), + build: { + target, + }, ssr: ssrMode === ServerSsrMode.NoSsr ? undefined @@ -208,8 +211,6 @@ export async function setupServer( externalMetadata, serverOptions, prebundleTransformer, - zoneless, - target, prebundleLoaderExtensions, thirdPartySourcemaps, define, @@ -244,10 +245,7 @@ export async function setupServer( exclude: externalMetadata.explicitBrowser, // Include all implict dependencies from the external packages internal option include: externalMetadata.implicitBrowser, - ssr: false, prebundleTransformer, - target, - zoneless, loader: prebundleLoaderExtensions, thirdPartySourcemaps, define, diff --git a/packages/angular/build/src/tools/esbuild/utils.ts b/packages/angular/build/src/tools/esbuild/utils.ts index 9024a981c3f7..6d76623f1493 100644 --- a/packages/angular/build/src/tools/esbuild/utils.ts +++ b/packages/angular/build/src/tools/esbuild/utils.ts @@ -9,7 +9,6 @@ import { BuilderContext } from '@angular-devkit/architect'; import { BuildOptions, Metafile, OutputFile, formatMessages } from 'esbuild'; import { Listr } from 'listr2'; -import { createHash } from 'node:crypto'; import { basename, join } from 'node:path'; import { pathToFileURL } from 'node:url'; import { brotliCompress } from 'node:zlib'; diff --git a/packages/angular/build/src/tools/vite/utils.ts b/packages/angular/build/src/tools/vite/utils.ts index 11cc94abee98..f2b54d403334 100644 --- a/packages/angular/build/src/tools/vite/utils.ts +++ b/packages/angular/build/src/tools/vite/utils.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.dev/license */ -import type { OnLoadArgs, PluginBuild } from 'esbuild'; import { lookup as lookupMimeType } from 'mrmime'; import { builtinModules, isBuiltin } from 'node:module'; import { extname } from 'node:path'; @@ -15,7 +14,6 @@ import type { DepOptimizationConfig } from 'vite' with { }; import type { ExternalResultMetadata } from '../esbuild/bundler-execution-result'; import { JavaScriptTransformer } from '../esbuild/javascript-transformer'; -import { getFeatureSupport } from '../esbuild/utils'; export type AngularMemoryOutputFiles = Map< string, @@ -44,23 +42,16 @@ export function lookupMimeTypeFromRequest(url: string): string | undefined { return extension && lookupMimeType(extension); } -type ViteEsBuildPlugin = NonNullable< - NonNullable['plugins'] ->[0]; - -export type EsbuildLoaderOption = Exclude< - DepOptimizationConfig['esbuildOptions'], +export type RolldownLoaderOption = Exclude< + DepOptimizationConfig['rolldownOptions'], undefined ->['loader']; +>['moduleTypes']; export function getDepOptimizationConfig({ disabled, exclude, include, - target, - zoneless, prebundleTransformer, - ssr, loader, thirdPartySourcemaps, define = {}, @@ -68,51 +59,43 @@ export function getDepOptimizationConfig({ disabled: boolean; exclude: string[]; include: string[]; - target: string[]; prebundleTransformer: JavaScriptTransformer; - ssr: boolean; - zoneless: boolean; - loader?: EsbuildLoaderOption; + loader?: RolldownLoaderOption; thirdPartySourcemaps: boolean; define: Record | undefined; }): DepOptimizationConfig { - const plugins: ViteEsBuildPlugin[] = [ - { - name: `angular-vite-optimize-deps${ssr ? '-ssr' : ''}${ - thirdPartySourcemaps ? '-vendor-sourcemap' : '' - }`, - setup(build: PluginBuild) { - build.onLoad({ filter: /\.[cm]?js$/ }, async (args: OnLoadArgs) => { - return { - contents: await prebundleTransformer.transformFile(args.path), - loader: 'js', - }; - }); - }, - }, - ]; - - return { + const config: DepOptimizationConfig = { // Exclude any explicitly defined dependencies (currently build defined externals) exclude, // NB: to disable the deps optimizer, set optimizeDeps.noDiscovery to true and optimizeDeps.include as undefined. // Include all implict dependencies from the external packages internal option include: disabled ? undefined : include, noDiscovery: disabled, - // Add an esbuild plugin to run the Angular linker on dependencies - esbuildOptions: { - // Set esbuild supported targets. - target, - supported: getFeatureSupport(zoneless), - plugins, - loader, - define: { - ...define, - 'ngServerMode': `${ssr}`, + rolldownOptions: { + transform: { + define, + }, + moduleTypes: loader, + resolve: { + extensions: ['.mjs', '.js', '.cjs'], }, - resolveExtensions: ['.mjs', '.js', '.cjs'], + plugins: [ + { + name: `angular-vite-optimize-deps${thirdPartySourcemaps ? '-vendor-sourcemap' : ''}`, + load: { + filter: { id: /\.[cm]?js$/ }, + async handler(id: string) { + const code = await prebundleTransformer.transformFile(id); + + return { code: Buffer.from(code).toString('utf-8') }; + }, + }, + }, + ], }, }; + + return config; } export interface DevServerExternalResultMetadata { From 712fb72d41627d71dbf22bf9afc680fe3abf5c25 Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Tue, 23 Jun 2026 14:19:27 +0000 Subject: [PATCH 2/2] fixup! refactor(@angular/build): migrate to optimizeDeps.rolldownOptions in Vite config --- packages/angular/build/src/builders/dev-server/vite/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 7e50f7d89303..94192ec41b8c 100644 --- a/packages/angular/build/src/builders/dev-server/vite/index.ts +++ b/packages/angular/build/src/builders/dev-server/vite/index.ts @@ -414,7 +414,7 @@ export async function* serveWithVite( } // Copy the loader and modify the file extension to be asset - const loader = browserOptions.loader; + const loader = browserOptions.loader ? { ...browserOptions.loader } : undefined; if (loader) { for (const [key, value] of Object.entries(loader)) { if (value === 'file') {