diff --git a/packages/angular_devkit/build_angular/src/builders/application/execute-build.ts b/packages/angular_devkit/build_angular/src/builders/application/execute-build.ts index a5884a45af12..d039c51724f0 100644 --- a/packages/angular_devkit/build_angular/src/builders/application/execute-build.ts +++ b/packages/angular_devkit/build_angular/src/builders/application/execute-build.ts @@ -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); diff --git a/packages/angular_devkit/build_angular/src/builders/dev-server/vite-server.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/vite-server.ts index adc28d991f7a..59d8f42292a3 100644 --- a/packages/angular_devkit/build_angular/src/builders/dev-server/vite-server.ts +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/vite-server.ts @@ -115,6 +115,10 @@ export async function* serveWithVite( let hadError = false; const generatedFiles = new Map(); const assetFiles = new Map(); + const externalMetadata: { implicit: string[]; explicit: string[] } = { + implicit: [], + explicit: [], + }; const build = builderName === '@angular-devkit/build-angular:application' ? buildApplicationInternal @@ -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 { @@ -185,7 +198,7 @@ export async function* serveWithVite( generatedFiles, assetFiles, browserOptions.preserveSymlinks, - browserOptions.externalDependencies, + externalMetadata, !!browserOptions.ssr, prebundleTransformer, target, @@ -336,7 +349,7 @@ export async function setupServer( outputFiles: Map, assets: Map, preserveSymlinks: boolean | undefined, - prebundleExclude: string[] | undefined, + externalMetadata: { implicit: string[]; explicit: string[] }, ssr: boolean, prebundleTransformer: JavaScriptTransformer, target: string[], @@ -381,7 +394,7 @@ export async function setupServer( }, ssr: { // Exclude any provided dependencies (currently build defined externals) - external: prebundleExclude, + external: externalMetadata.implicit, }, plugins: [ createAngularLocaleDataPlugin(), @@ -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 diff --git a/packages/angular_devkit/build_angular/src/tools/esbuild/bundler-context.ts b/packages/angular_devkit/build_angular/src/tools/esbuild/bundler-context.ts index ef0d8f76aaf2..dcdaa260ad65 100644 --- a/packages/angular_devkit/build_angular/src/tools/esbuild/bundler-context.ts +++ b/packages/angular_devkit/build_angular/src/tools/esbuild/bundler-context.ts @@ -28,6 +28,7 @@ export type BundleContextResult = metafile: Metafile; outputFiles: BuildOutputFile[]; initialFiles: Map; + externalImports: Set; }; export interface InitialFileRecord { @@ -100,6 +101,7 @@ export class BundlerContext { const warnings: Message[] = []; const metafile: Metafile = { inputs: {}, outputs: {} }; const initialFiles = new Map(); + const externalImports = new Set(); const outputFiles = []; for (const result of individualResults) { warnings.push(...result.warnings); @@ -117,6 +119,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) { @@ -129,6 +132,7 @@ export class BundlerContext { metafile, initialFiles, outputFiles, + externalImports, }; } @@ -258,6 +262,20 @@ export class BundlerContext { } } + // Collect all external package names + const externalImports = new Set(); + 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') { @@ -277,6 +295,7 @@ export class BundlerContext { ...result, outputFiles, initialFiles, + externalImports, errors: undefined, }; } diff --git a/packages/angular_devkit/build_angular/src/tools/esbuild/bundler-execution-result.ts b/packages/angular_devkit/build_angular/src/tools/esbuild/bundler-execution-result.ts index 55840967d48d..4626f723c7eb 100644 --- a/packages/angular_devkit/build_angular/src/tools/esbuild/bundler-execution-result.ts +++ b/packages/angular_devkit/build_angular/src/tools/esbuild/bundler-execution-result.ts @@ -30,6 +30,7 @@ export class ExecutionResult { outputFiles: BuildOutputFile[] = []; assetFiles: BuildOutputAsset[] = []; errors: Message[] = []; + externalMetadata?: { implicit: string[]; explicit?: string[] }; constructor( private rebuildContexts: BundlerContext[], @@ -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, @@ -60,6 +71,7 @@ export class ExecutionResult { outputFiles: this.outputFiles, assetFiles: this.assetFiles, errors: this.errors, + externalMetadata: this.externalMetadata, }; }