Skip to content

Commit dd94a83

Browse files
committed
perf(@angular/build): enable dependency prebundling for server dependencies
With this commit, we introduce Vite dependencies prebundling for server bundles. **Before:** ``` ng s Browser bundles Initial chunk files | Names | Raw size polyfills.js | polyfills | 90.87 kB main.js | main | 22.71 kB styles.css | styles | 95 bytes | Initial total | 113.67 kB Server bundles Initial chunk files | Names | Raw size chunk-4EGJGG5L.mjs | - | 1.80 MB polyfills.server.mjs | polyfills.server | 573.58 kB main.server.mjs | main.server | 224.03 kB chunk-QDHZVCWX.mjs | - | 2.57 kB render-utils.server.mjs | render-utils.server | 423 bytes Lazy chunk files | Names | Raw size chunk-XD2MYPRT.mjs | xhr2 | 40.04 kB Application bundle generation complete. [5.199 seconds] ``` **Now:** ``` ng s Browser bundles Initial chunk files | Names | Raw size polyfills.js | polyfills | 90.87 kB main.js | main | 22.71 kB styles.css | styles | 95 bytes | Initial total | 113.67 kB Server bundles Initial chunk files | Names | Raw size polyfills.server.mjs | polyfills.server | 573.58 kB main.server.mjs | main.server | 23.16 kB render-utils.server.mjs | render-utils.server | 472 bytes Application bundle generation complete. [2.880 seconds] ```
1 parent e5cf3c8 commit dd94a83

File tree

2 files changed

+44
-47
lines changed

2 files changed

+44
-47
lines changed

packages/angular/build/src/builders/application/setup-bundling.ts

+1-10
Original file line numberDiff line numberDiff line change
@@ -93,16 +93,7 @@ export function setupBundlerContexts(
9393
new BundlerContext(
9494
workspaceRoot,
9595
!!options.watch,
96-
createServerCodeBundleOptions(
97-
{
98-
...options,
99-
// Disable external deps for server bundles.
100-
// This is because it breaks Vite 'optimizeDeps' for SSR.
101-
externalPackages: false,
102-
},
103-
nodeTargets,
104-
codeBundleCache,
105-
),
96+
createServerCodeBundleOptions(options, nodeTargets, codeBundleCache),
10697
),
10798
);
10899

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

+43-37
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type { BuilderContext } from '@angular-devkit/architect';
1010
import type { Plugin } from 'esbuild';
1111
import assert from 'node:assert';
1212
import { readFile } from 'node:fs/promises';
13+
import { builtinModules } from 'node:module';
1314
import { basename, join } from 'node:path';
1415
import type { Connect, DepOptimizationConfig, InlineConfig, ViteDevServer } from 'vite';
1516
import { createAngularMemoryPlugin } from '../../tools/vite/angular-memory-plugin';
@@ -205,21 +206,31 @@ export async function* serveWithVite(
205206
}
206207

207208
// To avoid disconnecting the array objects from the option, these arrays need to be mutated instead of replaced.
209+
let requiresServerRestart = false;
208210
if (result.externalMetadata) {
209211
const { implicitBrowser, implicitServer, explicit } = result.externalMetadata;
212+
const implicitServerFiltered = (implicitServer as string[]).filter(
213+
(m) => removeNodeJsBuiltinModules(m) && removeAbsoluteUrls(m),
214+
);
215+
const implicitBrowserFiltered = (implicitBrowser as string[]).filter(removeAbsoluteUrls);
216+
217+
if (browserOptions.ssr && serverOptions.prebundle !== false) {
218+
const previousImplicitServer = new Set(externalMetadata.implicitServer);
219+
// Restart the server to force SSR dep re-optimization when a dependency has been added.
220+
// This is a workaround for: https://github.com/vitejs/vite/issues/14896
221+
requiresServerRestart = implicitServerFiltered.some(
222+
(dep) => !previousImplicitServer.has(dep),
223+
);
224+
}
225+
210226
// Empty Arrays to avoid growing unlimited with every re-build.
211227
externalMetadata.explicit.length = 0;
212228
externalMetadata.implicitServer.length = 0;
213229
externalMetadata.implicitBrowser.length = 0;
214230

215231
externalMetadata.explicit.push(...explicit);
216-
// Remove any absolute URLs (http://, https://, //) to avoid Vite's prebundling from processing them as files
217-
externalMetadata.implicitServer.push(
218-
...(implicitServer as string[]).filter((value) => !/^(?:https?:)?\/\//.test(value)),
219-
);
220-
externalMetadata.implicitBrowser.push(
221-
...(implicitBrowser as string[]).filter((value) => !/^(?:https?:)?\/\//.test(value)),
222-
);
232+
externalMetadata.implicitServer.push(...implicitServerFiltered);
233+
externalMetadata.implicitBrowser.push(...implicitBrowserFiltered);
223234

224235
// The below needs to be sorted as Vite uses these options are part of the hashing invalidation algorithm.
225236
// See: https://github.com/vitejs/vite/blob/0873bae0cfe0f0718ad2f5743dd34a17e4ab563d/packages/vite/src/node/optimizer/index.ts#L1203-L1239
@@ -234,7 +245,13 @@ export async function* serveWithVite(
234245
...new Set([...server.config.server.fs.allow, ...assetFiles.values()]),
235246
];
236247

237-
handleUpdate(normalizePath, generatedFiles, server, serverOptions, context.logger);
248+
await handleUpdate(normalizePath, generatedFiles, server, serverOptions, context.logger);
249+
250+
if (requiresServerRestart) {
251+
// Restart the server to force SSR dep re-optimization when a dependency has been added.
252+
// This is a workaround for: https://github.com/vitejs/vite/issues/14896
253+
await server.restart();
254+
}
238255
} else {
239256
const projectName = context.target?.project;
240257
if (!projectName) {
@@ -275,16 +292,10 @@ export async function* serveWithVite(
275292
server = await createServer(serverConfiguration);
276293
await server.listen();
277294

278-
if (serverConfiguration.ssr?.optimizeDeps?.disabled === false) {
279-
/**
280-
* Vite will only start dependency optimization of SSR modules when the first request comes in.
281-
* In some cases, this causes a long waiting time. To mitigate this, we call `ssrLoadModule` to
282-
* initiate this process before the first request.
283-
*
284-
* NOTE: This will intentionally fail from the unknown module, but currently there is no other way
285-
* to initiate the SSR dep optimizer.
286-
*/
287-
void server.ssrLoadModule('<deps-caller>').catch(() => {});
295+
if (browserOptions.ssr && serverOptions.prebundle !== false) {
296+
// Warm up the SSR request and begin optimizing dependencies.
297+
// Without this, Vite will only start optimizing SSR modules when the first request is made.
298+
void server.warmupRequest('./main.server.mjs', { ssr: true });
288299
}
289300

290301
const urls = server.resolvedUrls;
@@ -469,11 +480,6 @@ export async function setupServer(
469480
join(serverOptions.workspaceRoot, `.angular/vite-root`, serverOptions.buildTarget.project),
470481
);
471482

472-
const serverExplicitExternal = [
473-
...(await import('node:module')).builtinModules,
474-
...externalMetadata.explicit,
475-
];
476-
477483
const cacheDir = join(serverOptions.cacheOptions.path, 'vite');
478484
const configuration: InlineConfig = {
479485
configFile: false,
@@ -536,23 +542,12 @@ export async function setupServer(
536542
// Note: `true` and `/.*/` have different sematics. When true, the `external` option is ignored.
537543
noExternal: /.*/,
538544
// Exclude any Node.js built in module and provided dependencies (currently build defined externals)
539-
external: serverExplicitExternal,
545+
external: externalMetadata.explicit,
540546
optimizeDeps: getDepOptimizationConfig({
541-
/**
542-
* *********************************************
543-
* NOTE: Temporary disable 'optimizeDeps' for SSR.
544-
* *********************************************
545-
*
546-
* Currently this causes a number of issues.
547-
* - Deps are re-optimized everytime the server is started.
548-
* - Added deps after a rebuild are not optimized.
549-
* - Breaks RxJs (Unless it is added as external). See: https://github.com/angular/angular-cli/issues/26235
550-
*/
551-
552547
// Only enable with caching since it causes prebundle dependencies to be cached
553-
disabled: true, // serverOptions.prebundle === false,
548+
disabled: serverOptions.prebundle === false,
554549
// Exclude any explicitly defined dependencies (currently build defined externals and node.js built-ins)
555-
exclude: serverExplicitExternal,
550+
exclude: externalMetadata.explicit,
556551
// Include all implict dependencies from the external packages internal option
557552
include: externalMetadata.implicitServer,
558553
ssr: true,
@@ -678,3 +673,14 @@ function getDepOptimizationConfig({
678673
},
679674
};
680675
}
676+
677+
const nodeJsBuiltinModules = new Set(builtinModules);
678+
/** Remove any Node.js builtin modules to avoid Vite's prebundling from processing them as files. */
679+
function removeNodeJsBuiltinModules(value: string): boolean {
680+
return !nodeJsBuiltinModules.has(value);
681+
}
682+
683+
/** Remove any absolute URLs (http://, https://, //) to avoid Vite's prebundling from processing them as files. */
684+
function removeAbsoluteUrls(value: string): boolean {
685+
return !/^(?:https?:)?\/\//.test(value);
686+
}

0 commit comments

Comments
 (0)