Skip to content

Commit

Permalink
perf(@angular/build): enable dependency prebundling for server depend…
Browse files Browse the repository at this point in the history
…encies

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]
```
  • Loading branch information
alan-agius4 committed May 30, 2024
1 parent e5cf3c8 commit dd94a83
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -93,16 +93,7 @@ export function setupBundlerContexts(
new BundlerContext(
workspaceRoot,
!!options.watch,
createServerCodeBundleOptions(
{
...options,
// Disable external deps for server bundles.
// This is because it breaks Vite 'optimizeDeps' for SSR.
externalPackages: false,
},
nodeTargets,
codeBundleCache,
),
createServerCodeBundleOptions(options, nodeTargets, codeBundleCache),
),
);

Expand Down
80 changes: 43 additions & 37 deletions packages/angular/build/src/builders/dev-server/vite-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { BuilderContext } from '@angular-devkit/architect';
import type { Plugin } from 'esbuild';
import assert from 'node:assert';
import { readFile } from 'node:fs/promises';
import { builtinModules } from 'node:module';
import { basename, join } from 'node:path';
import type { Connect, DepOptimizationConfig, InlineConfig, ViteDevServer } from 'vite';
import { createAngularMemoryPlugin } from '../../tools/vite/angular-memory-plugin';
Expand Down Expand Up @@ -205,21 +206,31 @@ export async function* serveWithVite(
}

// To avoid disconnecting the array objects from the option, these arrays need to be mutated instead of replaced.
let requiresServerRestart = false;
if (result.externalMetadata) {
const { implicitBrowser, implicitServer, explicit } = result.externalMetadata;
const implicitServerFiltered = (implicitServer as string[]).filter(
(m) => removeNodeJsBuiltinModules(m) && removeAbsoluteUrls(m),
);
const implicitBrowserFiltered = (implicitBrowser as string[]).filter(removeAbsoluteUrls);

if (browserOptions.ssr && serverOptions.prebundle !== false) {
const previousImplicitServer = new Set(externalMetadata.implicitServer);
// Restart the server to force SSR dep re-optimization when a dependency has been added.
// This is a workaround for: https://github.com/vitejs/vite/issues/14896
requiresServerRestart = implicitServerFiltered.some(
(dep) => !previousImplicitServer.has(dep),
);
}

// Empty Arrays to avoid growing unlimited with every re-build.
externalMetadata.explicit.length = 0;
externalMetadata.implicitServer.length = 0;
externalMetadata.implicitBrowser.length = 0;

externalMetadata.explicit.push(...explicit);
// Remove any absolute URLs (http://, https://, //) to avoid Vite's prebundling from processing them as files
externalMetadata.implicitServer.push(
...(implicitServer as string[]).filter((value) => !/^(?:https?:)?\/\//.test(value)),
);
externalMetadata.implicitBrowser.push(
...(implicitBrowser as string[]).filter((value) => !/^(?:https?:)?\/\//.test(value)),
);
externalMetadata.implicitServer.push(...implicitServerFiltered);
externalMetadata.implicitBrowser.push(...implicitBrowserFiltered);

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

handleUpdate(normalizePath, generatedFiles, server, serverOptions, context.logger);
await handleUpdate(normalizePath, generatedFiles, server, serverOptions, context.logger);

if (requiresServerRestart) {
// Restart the server to force SSR dep re-optimization when a dependency has been added.
// This is a workaround for: https://github.com/vitejs/vite/issues/14896
await server.restart();
}
} else {
const projectName = context.target?.project;
if (!projectName) {
Expand Down Expand Up @@ -275,16 +292,10 @@ export async function* serveWithVite(
server = await createServer(serverConfiguration);
await server.listen();

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

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

const serverExplicitExternal = [
...(await import('node:module')).builtinModules,
...externalMetadata.explicit,
];

const cacheDir = join(serverOptions.cacheOptions.path, 'vite');
const configuration: InlineConfig = {
configFile: false,
Expand Down Expand Up @@ -536,23 +542,12 @@ export async function setupServer(
// Note: `true` and `/.*/` have different sematics. When true, the `external` option is ignored.
noExternal: /.*/,
// Exclude any Node.js built in module and provided dependencies (currently build defined externals)
external: serverExplicitExternal,
external: externalMetadata.explicit,
optimizeDeps: getDepOptimizationConfig({
/**
* *********************************************
* NOTE: Temporary disable 'optimizeDeps' for SSR.
* *********************************************
*
* Currently this causes a number of issues.
* - Deps are re-optimized everytime the server is started.
* - Added deps after a rebuild are not optimized.
* - Breaks RxJs (Unless it is added as external). See: https://github.com/angular/angular-cli/issues/26235
*/

// Only enable with caching since it causes prebundle dependencies to be cached
disabled: true, // serverOptions.prebundle === false,
disabled: serverOptions.prebundle === false,
// Exclude any explicitly defined dependencies (currently build defined externals and node.js built-ins)
exclude: serverExplicitExternal,
exclude: externalMetadata.explicit,
// Include all implict dependencies from the external packages internal option
include: externalMetadata.implicitServer,
ssr: true,
Expand Down Expand Up @@ -678,3 +673,14 @@ function getDepOptimizationConfig({
},
};
}

const nodeJsBuiltinModules = new Set(builtinModules);
/** Remove any Node.js builtin modules to avoid Vite's prebundling from processing them as files. */
function removeNodeJsBuiltinModules(value: string): boolean {
return !nodeJsBuiltinModules.has(value);
}

/** Remove any absolute URLs (http://, https://, //) to avoid Vite's prebundling from processing them as files. */
function removeAbsoluteUrls(value: string): boolean {
return !/^(?:https?:)?\/\//.test(value);
}

0 comments on commit dd94a83

Please sign in to comment.