Skip to content

Commit

Permalink
fix(@angular-devkit/build-angular): prebundle dependencies for SSR wh…
Browse files Browse the repository at this point in the history
…en using Vite

The commit introduces dependencies prebundling and optimisation for SSR dependencies. This is primarily needed for Angular linking and async/await downlevelling. To enable this, we need to use the undocumented `optimizeDeps` setting under the `ssr` option. This is because, the top level `optimizeDeps` vite config option  only controls browser dependencies.

For the above mentioned option to take effect and transform node packages, we also need to use `noExternal` and use a catch all `RegExp`. Note: setting this option to `true` has a different effect from a catch all `RegExp`, as the former will cause the `external` option to be ignored.

Additionally together with `externalMetadata.explicit` we add Node.js build-ins as `external`.

Closes: #26192
(cherry picked from commit 4c1265a)
  • Loading branch information
alan-agius4 committed Nov 2, 2023
1 parent fd2c4c3 commit 1f299ff
Showing 1 changed file with 81 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ import assert from 'node:assert';
import { randomUUID } from 'node:crypto';
import { readFile } from 'node:fs/promises';
import { ServerResponse } from 'node:http';
import type { AddressInfo } from 'node:net';
import path from 'node:path';
import type { Connect, InlineConfig, ViteDevServer } from 'vite';
import type { Connect, DepOptimizationConfig, InlineConfig, ViteDevServer } from 'vite';
import { BuildOutputFile, BuildOutputFileType } from '../../tools/esbuild/bundler-context';
import { JavaScriptTransformer } from '../../tools/esbuild/javascript-transformer';
import { createRxjsEsmResolutionPlugin } from '../../tools/esbuild/rxjs-esm-resolution-plugin';
import { getFeatureSupport, transformSupportedBrowsersToTargets } from '../../tools/esbuild/utils';
import { createAngularLocaleDataPlugin } from '../../tools/vite/i18n-locale-plugin';
import { renderPage } from '../../utils/server-rendering/render-page';
Expand Down Expand Up @@ -173,6 +173,8 @@ export async function* serveWithVite(

// To avoid disconnecting the array objects from the option, these arrays need to be mutated
// instead of replaced.
// TODO: split explicit imports by platform to avoid having Vite optimize server-only/browser-only
// dependencies twice when SSR is enabled.
if (result.externalMetadata) {
if (result.externalMetadata.explicit) {
externalMetadata.explicit.push(...result.externalMetadata.explicit);
Expand Down Expand Up @@ -379,6 +381,8 @@ export async function setupServer(
path.join(serverOptions.workspaceRoot, `.angular/vite-root/${randomUUID()}/`),
);

const { builtinModules } = await import('node:module');

const configuration: InlineConfig = {
configFile: false,
envFile: false,
Expand Down Expand Up @@ -412,8 +416,21 @@ export async function setupServer(
preTransformRequests: externalMetadata.explicit.length === 0,
},
ssr: {
// Exclude any provided dependencies (currently build defined externals)
external: externalMetadata.explicit,
// 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: [...builtinModules, ...externalMetadata.explicit],
optimizeDeps: getDepOptimizationConfig({
// Only enable with caching since it causes prebundle dependencies to be cached
disabled: !serverOptions.cacheOptions.enabled,
// Exclude any explicitly defined dependencies (currently build defined externals)
exclude: externalMetadata.explicit,
// Include all implict dependencies from the external packages internal option
include: externalMetadata.implicit,
ssr: true,
prebundleTransformer,
target,
}),
},
plugins: [
createAngularLocaleDataPlugin(),
Expand Down Expand Up @@ -645,35 +662,18 @@ export async function setupServer(
},
},
],
optimizeDeps: {
// Browser only optimizeDeps. (This does not run for SSR dependencies).
optimizeDeps: getDepOptimizationConfig({
// Only enable with caching since it causes prebundle dependencies to be cached
disabled: !serverOptions.cacheOptions.enabled,
// Exclude any explicitly defined dependencies (currently build defined externals)
exclude: externalMetadata.explicit,
// Include all implict dependencies from the external packages internal option
include: externalMetadata.implicit,
// Skip automatic file-based entry point discovery
entries: [],
// Add an esbuild plugin to run the Angular linker on dependencies
esbuildOptions: {
// Set esbuild supported targets.
target,
supported: getFeatureSupport(target),
plugins: [
{
name: 'angular-vite-optimize-deps',
setup(build) {
build.onLoad({ filter: /\.[cm]?js$/ }, async (args) => {
return {
contents: await prebundleTransformer.transformFile(args.path),
loader: 'js',
};
});
},
},
],
},
},
ssr: false,
prebundleTransformer,
target,
}),
};

if (serverOptions.ssl) {
Expand Down Expand Up @@ -728,3 +728,57 @@ function pathnameWithoutServePath(url: string, serverOptions: NormalizedDevServe

return pathname;
}

type ViteEsBuildPlugin = NonNullable<
NonNullable<DepOptimizationConfig['esbuildOptions']>['plugins']
>[0];

function getDepOptimizationConfig({
disabled,
exclude,
include,
target,
prebundleTransformer,
ssr,
}: {
disabled: boolean;
exclude: string[];
include: string[];
target: string[];
prebundleTransformer: JavaScriptTransformer;
ssr: boolean;
}): DepOptimizationConfig {
const plugins: ViteEsBuildPlugin[] = [
{
name: `angular-vite-optimize-deps${ssr ? '-ssr' : ''}`,
setup(build) {
build.onLoad({ filter: /\.[cm]?js$/ }, async (args) => {
return {
contents: await prebundleTransformer.transformFile(args.path),
loader: 'js',
};
});
},
},
];

if (ssr) {
plugins.unshift(createRxjsEsmResolutionPlugin() as ViteEsBuildPlugin);
}

return {
// Only enable with caching since it causes prebundle dependencies to be cached
disabled,
// Exclude any explicitly defined dependencies (currently build defined externals)
exclude,
// Include all implict dependencies from the external packages internal option
include,
// Add an esbuild plugin to run the Angular linker on dependencies
esbuildOptions: {
// Set esbuild supported targets.
target,
supported: getFeatureSupport(target),
plugins,
},
};
}

0 comments on commit 1f299ff

Please sign in to comment.