Skip to content

Commit

Permalink
fix(@angular-devkit/build-angular): automatically include known packa…
Browse files Browse the repository at this point in the history
…ges in vite prebundling

When using the Vite-based development server, the application build step already contains the
list of known packages that would need to be prebundled. This information can be passed to Vite
directly to avoid Vite needing to perform discovery on every output file that will be requested.
This also avoids the Vite server behavior where Vite forces a reload of the page when it discovers
a new dependency. This behavior can result in lost state during lazy loading of a route.

(cherry picked from commit 9768c18)
  • Loading branch information
clydin authored and alan-agius4 committed Oct 25, 2023
1 parent 87425a7 commit e3c5b91
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,15 @@ export async function executeBuild(
return executionResult;
}

// Analyze external imports if external options are enabled
if (options.externalPackages || options.externalDependencies?.length) {
// TODO: Filter externalImports to generate second argument to support wildcard externalDependency values
executionResult.setExternalMetadata(
[...bundlingResult.externalImports],
options.externalDependencies,
);
}

const { metafile, initialFiles, outputFiles } = bundlingResult;

executionResult.outputFiles.push(...outputFiles);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ export async function* serveWithVite(
let hadError = false;
const generatedFiles = new Map<string, OutputFileRecord>();
const assetFiles = new Map<string, string>();
const externalMetadata: { implicit: string[]; explicit: string[] } = {
implicit: [],
explicit: [],
};
const build =
builderName === '@angular-devkit/build-angular:application'
? buildApplicationInternal
Expand Down Expand Up @@ -166,6 +170,15 @@ export async function* serveWithVite(
}
}

// To avoid disconnecting the array objects from the option, these arrays need to be mutated
// instead of replaced.
if (result.externalMetadata.explicit) {
externalMetadata.explicit.push(...result.externalMetadata.explicit);
}
if (result.externalMetadata.implicit) {
externalMetadata.implicit.push(...result.externalMetadata.implicit);
}

if (server) {
handleUpdate(generatedFiles, server, serverOptions, context.logger);
} else {
Expand All @@ -185,7 +198,7 @@ export async function* serveWithVite(
generatedFiles,
assetFiles,
browserOptions.preserveSymlinks,
browserOptions.externalDependencies,
externalMetadata,
!!browserOptions.ssr,
prebundleTransformer,
target,
Expand Down Expand Up @@ -336,7 +349,7 @@ export async function setupServer(
outputFiles: Map<string, OutputFileRecord>,
assets: Map<string, string>,
preserveSymlinks: boolean | undefined,
prebundleExclude: string[] | undefined,
externalMetadata: { implicit: string[]; explicit: string[] },
ssr: boolean,
prebundleTransformer: JavaScriptTransformer,
target: string[],
Expand Down Expand Up @@ -381,7 +394,7 @@ export async function setupServer(
},
ssr: {
// Exclude any provided dependencies (currently build defined externals)
external: prebundleExclude,
external: externalMetadata.implicit,
},
plugins: [
createAngularLocaleDataPlugin(),
Expand Down Expand Up @@ -598,8 +611,10 @@ export async function setupServer(
optimizeDeps: {
// Only enable with caching since it causes prebundle dependencies to be cached
disabled: !serverOptions.cacheOptions.enabled,
// Exclude any provided dependencies (currently build defined externals)
exclude: prebundleExclude,
// 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export type BundleContextResult =
metafile: Metafile;
outputFiles: BuildOutputFile[];
initialFiles: Map<string, InitialFileRecord>;
externalImports: Set<string>;
};

export interface InitialFileRecord {
Expand Down Expand Up @@ -105,6 +106,7 @@ export class BundlerContext {
const warnings: Message[] = [];
const metafile: Metafile = { inputs: {}, outputs: {} };
const initialFiles = new Map<string, InitialFileRecord>();
const externalImports = new Set<string>();
const outputFiles = [];
for (const result of individualResults) {
warnings.push(...result.warnings);
Expand All @@ -122,6 +124,7 @@ export class BundlerContext {

result.initialFiles.forEach((value, key) => initialFiles.set(key, value));
outputFiles.push(...result.outputFiles);
result.externalImports.forEach((value) => externalImports.add(value));
}

if (errors !== undefined) {
Expand All @@ -134,6 +137,7 @@ export class BundlerContext {
metafile,
initialFiles,
outputFiles,
externalImports,
};
}

Expand Down Expand Up @@ -284,6 +288,20 @@ export class BundlerContext {
}
}

// Collect all external package names
const externalImports = new Set<string>();
for (const { imports } of Object.values(result.metafile.outputs)) {
for (const importData of imports) {
if (
!importData.external ||
(importData.kind !== 'import-statement' && importData.kind !== 'dynamic-import')
) {
continue;
}
externalImports.add(importData.path);
}
}

const outputFiles = result.outputFiles.map((file) => {
let fileType: BuildOutputFileType;
if (dirname(file.path) === 'media') {
Expand All @@ -303,6 +321,7 @@ export class BundlerContext {
...result,
outputFiles,
initialFiles,
externalImports,
errors: undefined,
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export class ExecutionResult {
outputFiles: BuildOutputFile[] = [];
assetFiles: BuildOutputAsset[] = [];
errors: Message[] = [];
externalMetadata?: { implicit: string[]; explicit?: string[] };

constructor(
private rebuildContexts: BundlerContext[],
Expand All @@ -48,6 +49,16 @@ export class ExecutionResult {
this.errors.push(...errors);
}

/**
* Add external JavaScript import metadata to the result. This is currently used
* by the development server to optimize the prebundling process.
* @param implicit External dependencies due to the external packages option.
* @param explicit External dependencies due to explicit project configuration.
*/
setExternalMetadata(implicit: string[], explicit: string[] | undefined) {
this.externalMetadata = { implicit, explicit };
}

get output() {
return {
success: this.errors.length === 0,
Expand All @@ -60,6 +71,7 @@ export class ExecutionResult {
outputFiles: this.outputFiles,
assetFiles: this.assetFiles,
errors: this.errors,
externalMetadata: this.externalMetadata,
};
}

Expand Down

0 comments on commit e3c5b91

Please sign in to comment.