Skip to content

Commit 2138d1d

Browse files
committed
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).
1 parent 7ef9ed2 commit 2138d1d

4 files changed

Lines changed: 77 additions & 54 deletions

File tree

packages/angular/build/src/builders/dev-server/vite/index.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import type * as Vite from 'vite' with {
1515
};
1616
import type { ComponentStyleRecord } from '../../../tools/vite/middlewares';
1717
import { ServerSsrMode } from '../../../tools/vite/plugins';
18-
import { EsbuildLoaderOption, updateExternalMetadata } from '../../../tools/vite/utils';
18+
import { updateExternalMetadata } from '../../../tools/vite/utils';
1919
import { normalizeSourceMaps } from '../../../utils';
2020
import { useComponentStyleHmr, useComponentTemplateHmr } from '../../../utils/environment-options';
2121
import { Result, ResultKind } from '../../application/results';
@@ -384,12 +384,19 @@ export async function* serveWithVite(
384384
const projectRoot = join(context.workspaceRoot, root as string);
385385
const browsers = getSupportedBrowsers(projectRoot, context.logger);
386386

387-
const target = transformSupportedBrowsersToTargets(browsers);
388387
// Needed for browser-esbuild as polyfills can be a string.
389388
const polyfills = Array.isArray((browserOptions.polyfills ??= []))
390389
? browserOptions.polyfills
391390
: [browserOptions.polyfills];
392391

392+
let target: string[];
393+
if (isZonelessApp(polyfills)) {
394+
target = transformSupportedBrowsersToTargets(browsers);
395+
} else {
396+
// Rolldown doesn't have an option to support Zone.js/async-await, so we need to support es2016.
397+
target = ['es2016'];
398+
}
399+
393400
let ssrMode: ServerSsrMode = ServerSsrMode.NoSsr;
394401
if (
395402
browserOptions.outputMode &&
@@ -408,6 +415,16 @@ export async function* serveWithVite(
408415
});
409416
}
410417

418+
// Copy the loader and modify the file extension to be asset
419+
const loader = browserOptions.loader;
420+
if (loader) {
421+
for (const [key, value] of Object.entries(loader)) {
422+
if (value === 'file') {
423+
loader[key] = 'asset';
424+
}
425+
}
426+
}
427+
411428
// Setup server and start listening
412429
const serverConfiguration = await setupServer(
413430
serverOptions,
@@ -418,10 +435,9 @@ export async function* serveWithVite(
418435
ssrMode,
419436
prebundleTransformer,
420437
target,
421-
isZonelessApp(polyfills),
422438
componentStyles,
423439
templateUpdates,
424-
browserOptions.loader as EsbuildLoaderOption | undefined,
440+
loader,
425441
{
426442
...browserOptions.define,
427443
'ngJitMode': browserOptions.aot ? 'false' : 'true',

packages/angular/build/src/builders/dev-server/vite/server.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
createAngularSsrTransformPlugin,
2121
createRemoveIdPrefixPlugin,
2222
} from '../../../tools/vite/plugins';
23-
import { EsbuildLoaderOption, getDepOptimizationConfig } from '../../../tools/vite/utils';
23+
import { RolldownLoaderOption, getDepOptimizationConfig } from '../../../tools/vite/utils';
2424
import { loadProxyConfiguration } from '../../../utils';
2525
import { type ApplicationBuilderInternalOptions, JavaScriptTransformer } from '../internal';
2626
import type { NormalizedDevServerOptions } from '../options';
@@ -98,9 +98,8 @@ function createSsrConfig(
9898
externalMetadata: DevServerExternalResultMetadata,
9999
serverOptions: NormalizedDevServerOptions,
100100
prebundleTransformer: JavaScriptTransformer,
101-
zoneless: boolean,
102101
target: string[],
103-
prebundleLoaderExtensions: EsbuildLoaderOption | undefined,
102+
prebundleLoaderExtensions: RolldownLoaderOption | undefined,
104103
thirdPartySourcemaps: boolean,
105104
define: ApplicationBuilderInternalOptions['define'],
106105
): Vite.SSROptions {
@@ -118,7 +117,6 @@ function createSsrConfig(
118117
include: externalMetadata.implicitServer,
119118
ssr: true,
120119
prebundleTransformer,
121-
zoneless,
122120
target,
123121
loader: prebundleLoaderExtensions,
124122
thirdPartySourcemaps,
@@ -136,10 +134,9 @@ export async function setupServer(
136134
ssrMode: ServerSsrMode,
137135
prebundleTransformer: JavaScriptTransformer,
138136
target: string[],
139-
zoneless: boolean,
140137
componentStyles: Map<string, ComponentStyleRecord>,
141138
templateUpdates: Map<string, string>,
142-
prebundleLoaderExtensions: EsbuildLoaderOption | undefined,
139+
prebundleLoaderExtensions: RolldownLoaderOption | undefined,
143140
define: ApplicationBuilderInternalOptions['define'],
144141
extensionMiddleware?: Vite.Connect.NextHandleFunction[],
145142
indexHtmlTransformer?: (content: string) => Promise<string>,
@@ -182,7 +179,7 @@ export async function setupServer(
182179
assetsInclude:
183180
prebundleLoaderExtensions &&
184181
Object.entries(prebundleLoaderExtensions)
185-
.filter(([, value]) => value === 'file')
182+
.filter(([, value]) => value === 'asset')
186183
// Create a file extension glob for each key
187184
.map(([key]) => '*' + key),
188185
// Vite will normalize the `base` option by adding a leading slash.
@@ -208,7 +205,6 @@ export async function setupServer(
208205
externalMetadata,
209206
serverOptions,
210207
prebundleTransformer,
211-
zoneless,
212208
target,
213209
prebundleLoaderExtensions,
214210
thirdPartySourcemaps,
@@ -247,7 +243,6 @@ export async function setupServer(
247243
ssr: false,
248244
prebundleTransformer,
249245
target,
250-
zoneless,
251246
loader: prebundleLoaderExtensions,
252247
thirdPartySourcemaps,
253248
define,

packages/angular/build/src/tools/esbuild/utils.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import { BuilderContext } from '@angular-devkit/architect';
1010
import { BuildOptions, Metafile, OutputFile, formatMessages } from 'esbuild';
1111
import { Listr } from 'listr2';
12-
import { createHash } from 'node:crypto';
1312
import { basename, join } from 'node:path';
1413
import { pathToFileURL } from 'node:url';
1514
import { brotliCompress } from 'node:zlib';

packages/angular/build/src/tools/vite/utils.ts

Lines changed: 53 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import type { OnLoadArgs, PluginBuild } from 'esbuild';
109
import { lookup as lookupMimeType } from 'mrmime';
1110
import { builtinModules, isBuiltin } from 'node:module';
1211
import { extname } from 'node:path';
@@ -15,7 +14,6 @@ import type { DepOptimizationConfig } from 'vite' with {
1514
};
1615
import type { ExternalResultMetadata } from '../esbuild/bundler-execution-result';
1716
import { JavaScriptTransformer } from '../esbuild/javascript-transformer';
18-
import { getFeatureSupport } from '../esbuild/utils';
1917

2018
export type AngularMemoryOutputFiles = Map<
2119
string,
@@ -44,21 +42,16 @@ export function lookupMimeTypeFromRequest(url: string): string | undefined {
4442
return extension && lookupMimeType(extension);
4543
}
4644

47-
type ViteEsBuildPlugin = NonNullable<
48-
NonNullable<DepOptimizationConfig['esbuildOptions']>['plugins']
49-
>[0];
50-
51-
export type EsbuildLoaderOption = Exclude<
52-
DepOptimizationConfig['esbuildOptions'],
45+
export type RolldownLoaderOption = Exclude<
46+
DepOptimizationConfig['rolldownOptions'],
5347
undefined
54-
>['loader'];
48+
>['moduleTypes'];
5549

5650
export function getDepOptimizationConfig({
5751
disabled,
5852
exclude,
5953
include,
6054
target,
61-
zoneless,
6255
prebundleTransformer,
6356
ssr,
6457
loader,
@@ -71,48 +64,68 @@ export function getDepOptimizationConfig({
7164
target: string[];
7265
prebundleTransformer: JavaScriptTransformer;
7366
ssr: boolean;
74-
zoneless: boolean;
75-
loader?: EsbuildLoaderOption;
67+
loader?: RolldownLoaderOption;
7668
thirdPartySourcemaps: boolean;
7769
define: Record<string, string> | undefined;
7870
}): DepOptimizationConfig {
79-
const plugins: ViteEsBuildPlugin[] = [
80-
{
81-
name: `angular-vite-optimize-deps${ssr ? '-ssr' : ''}${
82-
thirdPartySourcemaps ? '-vendor-sourcemap' : ''
83-
}`,
84-
setup(build: PluginBuild) {
85-
build.onLoad({ filter: /\.[cm]?js$/ }, async (args: OnLoadArgs) => {
86-
return {
87-
contents: await prebundleTransformer.transformFile(args.path),
88-
loader: 'js',
89-
};
90-
});
91-
},
92-
},
93-
];
94-
95-
return {
71+
const config: DepOptimizationConfig = {
9672
// Exclude any explicitly defined dependencies (currently build defined externals)
9773
exclude,
9874
// NB: to disable the deps optimizer, set optimizeDeps.noDiscovery to true and optimizeDeps.include as undefined.
9975
// Include all implict dependencies from the external packages internal option
10076
include: disabled ? undefined : include,
10177
noDiscovery: disabled,
102-
// Add an esbuild plugin to run the Angular linker on dependencies
103-
esbuildOptions: {
104-
// Set esbuild supported targets.
105-
target,
106-
supported: getFeatureSupport(zoneless),
107-
plugins,
108-
loader,
109-
define: {
110-
...define,
111-
'ngServerMode': `${ssr}`,
78+
rolldownOptions: {
79+
transform: {
80+
// Set target.
81+
target,
82+
define: {
83+
...define,
84+
'ngServerMode': `${ssr}`,
85+
},
86+
},
87+
plugins: [
88+
{
89+
name: `angular-vite-optimize-deps${ssr ? '-ssr' : ''}${
90+
thirdPartySourcemaps ? '-vendor-sourcemap' : ''
91+
}`,
92+
transform: {
93+
filter: { id: /\.[cm]?js$/ },
94+
async handler(code: string, id: string) {
95+
const result = await prebundleTransformer.transformData(
96+
id,
97+
code,
98+
/** skipLinker */ false,
99+
);
100+
101+
const transformedCode = Buffer.from(result).toString('utf-8');
102+
const mapCommentRegex =
103+
/\/\/# sourceMappingURL=data:application\/json;(?:charset=[^;]+;)?base64,(.*)$/m;
104+
const match = mapCommentRegex.exec(transformedCode);
105+
if (match) {
106+
try {
107+
const mapJson = Buffer.from(match[1], 'base64').toString('utf-8');
108+
109+
return {
110+
code: transformedCode.replace(mapCommentRegex, ''),
111+
map: JSON.parse(mapJson),
112+
};
113+
} catch {}
114+
}
115+
116+
return { transformedCode, map: null };
117+
},
118+
},
119+
},
120+
],
121+
moduleTypes: loader,
122+
resolve: {
123+
extensions: ['.mjs', '.js', '.cjs'],
112124
},
113-
resolveExtensions: ['.mjs', '.js', '.cjs'],
114125
},
115126
};
127+
128+
return config;
116129
}
117130

118131
export interface DevServerExternalResultMetadata {

0 commit comments

Comments
 (0)