Skip to content

Commit 84d12aa

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 84d12aa

4 files changed

Lines changed: 86 additions & 66 deletions

File tree

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

Lines changed: 21 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,12 +435,12 @@ 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,
443+
'ngServerMode': 'false',
427444
'ngJitMode': browserOptions.aot ? 'false' : 'true',
428445
'ngHmrMode': browserOptions.templateUpdates ? 'true' : 'false',
429446
},

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

Lines changed: 18 additions & 19 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';
@@ -82,13 +82,16 @@ async function createServerConfig(
8282
},
8383
};
8484

85-
if (serverOptions.ssl) {
86-
if (serverOptions.sslCert && serverOptions.sslKey) {
87-
server.https = {
88-
cert: await readFile(serverOptions.sslCert),
89-
key: await readFile(serverOptions.sslKey),
90-
};
91-
}
85+
if (serverOptions.ssl && serverOptions.sslCert && serverOptions.sslKey) {
86+
const [cert, key] = await Promise.all([
87+
readFile(serverOptions.sslCert),
88+
readFile(serverOptions.sslKey),
89+
]);
90+
91+
server.https = {
92+
cert,
93+
key,
94+
};
9295
}
9396

9497
return server;
@@ -98,9 +101,8 @@ function createSsrConfig(
98101
externalMetadata: DevServerExternalResultMetadata,
99102
serverOptions: NormalizedDevServerOptions,
100103
prebundleTransformer: JavaScriptTransformer,
101-
zoneless: boolean,
102104
target: string[],
103-
prebundleLoaderExtensions: EsbuildLoaderOption | undefined,
105+
prebundleLoaderExtensions: RolldownLoaderOption | undefined,
104106
thirdPartySourcemaps: boolean,
105107
define: ApplicationBuilderInternalOptions['define'],
106108
): Vite.SSROptions {
@@ -116,13 +118,14 @@ function createSsrConfig(
116118
exclude: externalMetadata.explicitServer,
117119
// Include all implict dependencies from the external packages internal option
118120
include: externalMetadata.implicitServer,
119-
ssr: true,
120121
prebundleTransformer,
121-
zoneless,
122122
target,
123123
loader: prebundleLoaderExtensions,
124124
thirdPartySourcemaps,
125-
define,
125+
define: {
126+
...define,
127+
'ngServerMode': 'true',
128+
},
126129
}),
127130
};
128131
}
@@ -136,10 +139,9 @@ export async function setupServer(
136139
ssrMode: ServerSsrMode,
137140
prebundleTransformer: JavaScriptTransformer,
138141
target: string[],
139-
zoneless: boolean,
140142
componentStyles: Map<string, ComponentStyleRecord>,
141143
templateUpdates: Map<string, string>,
142-
prebundleLoaderExtensions: EsbuildLoaderOption | undefined,
144+
prebundleLoaderExtensions: RolldownLoaderOption | undefined,
143145
define: ApplicationBuilderInternalOptions['define'],
144146
extensionMiddleware?: Vite.Connect.NextHandleFunction[],
145147
indexHtmlTransformer?: (content: string) => Promise<string>,
@@ -182,7 +184,7 @@ export async function setupServer(
182184
assetsInclude:
183185
prebundleLoaderExtensions &&
184186
Object.entries(prebundleLoaderExtensions)
185-
.filter(([, value]) => value === 'file')
187+
.filter(([, value]) => value === 'asset')
186188
// Create a file extension glob for each key
187189
.map(([key]) => '*' + key),
188190
// Vite will normalize the `base` option by adding a leading slash.
@@ -208,7 +210,6 @@ export async function setupServer(
208210
externalMetadata,
209211
serverOptions,
210212
prebundleTransformer,
211-
zoneless,
212213
target,
213214
prebundleLoaderExtensions,
214215
thirdPartySourcemaps,
@@ -244,10 +245,8 @@ export async function setupServer(
244245
exclude: externalMetadata.explicitBrowser,
245246
// Include all implict dependencies from the external packages internal option
246247
include: externalMetadata.implicitBrowser,
247-
ssr: false,
248248
prebundleTransformer,
249249
target,
250-
zoneless,
251250
loader: prebundleLoaderExtensions,
252251
thirdPartySourcemaps,
253252
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: 47 additions & 42 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,23 +42,17 @@ 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,
63-
ssr,
6456
loader,
6557
thirdPartySourcemaps,
6658
define = {},
@@ -70,49 +62,62 @@ export function getDepOptimizationConfig({
7062
include: string[];
7163
target: string[];
7264
prebundleTransformer: JavaScriptTransformer;
73-
ssr: boolean;
74-
zoneless: boolean;
75-
loader?: EsbuildLoaderOption;
65+
loader?: RolldownLoaderOption;
7666
thirdPartySourcemaps: boolean;
7767
define: Record<string, string> | undefined;
7868
}): 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 {
69+
const config: DepOptimizationConfig = {
9670
// Exclude any explicitly defined dependencies (currently build defined externals)
9771
exclude,
9872
// NB: to disable the deps optimizer, set optimizeDeps.noDiscovery to true and optimizeDeps.include as undefined.
9973
// Include all implict dependencies from the external packages internal option
10074
include: disabled ? undefined : include,
10175
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}`,
76+
rolldownOptions: {
77+
transform: {
78+
target,
79+
define,
80+
},
81+
plugins: [
82+
{
83+
name: `angular-vite-optimize-deps${thirdPartySourcemaps ? '-vendor-sourcemap' : ''}`,
84+
transform: {
85+
filter: { id: /\.[cm]?js$/ },
86+
async handler(code: string, id: string) {
87+
const result = await prebundleTransformer.transformData(
88+
id,
89+
code,
90+
/** skipLinker */ false,
91+
);
92+
93+
const transformedCode = Buffer.from(result).toString('utf-8');
94+
const mapCommentRegex =
95+
/\/\/# sourceMappingURL=data:application\/json;(?:charset=[^;]+;)?base64,(.*)$/m;
96+
const match = mapCommentRegex.exec(transformedCode);
97+
if (match) {
98+
try {
99+
const mapJson = Buffer.from(match[1], 'base64').toString('utf-8');
100+
101+
return {
102+
code: transformedCode.replace(mapCommentRegex, ''),
103+
map: JSON.parse(mapJson),
104+
};
105+
} catch {}
106+
}
107+
108+
return { transformedCode, map: null };
109+
},
110+
},
111+
},
112+
],
113+
moduleTypes: loader,
114+
resolve: {
115+
extensions: ['.mjs', '.js', '.cjs'],
112116
},
113-
resolveExtensions: ['.mjs', '.js', '.cjs'],
114117
},
115118
};
119+
120+
return config;
116121
}
117122

118123
export interface DevServerExternalResultMetadata {

0 commit comments

Comments
 (0)