Skip to content

Commit f83a485

Browse files
clydinalan-agius4
authored andcommitted
fix(@angular-devkit/build-angular): allow package file loader option with Vite prebundling
Previously, the `application` builder would consider all imports originating from a package to be considered external when caching was enabled. This allows Vite's prebundling to function and optimize the build/rebuild experience for the development server. However, when using the newly introduced `loader` option, this also inadvertently caused files that should be affected by the option that originate from a package to also be considered external. This behavior would then prevent the loader customization from being performed. To rectify this situation, all files that would be affected by a loader customization will not be marked as external for the purposes of prebundling unless explicitly configured by the `externalDependencies` option.
1 parent 0b0df5a commit f83a485

File tree

2 files changed

+89
-4
lines changed

2 files changed

+89
-4
lines changed

packages/angular_devkit/build_angular/src/tools/esbuild/application-code-bundle.ts

+12-4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { createCompilerPlugin } from './angular/compiler-plugin';
1717
import { SourceFileCache } from './angular/source-file-cache';
1818
import { BundlerOptionsFactory } from './bundler-context';
1919
import { createCompilerPluginOptions } from './compiler-plugin-options';
20+
import { createExternalPackagesPlugin } from './external-packages-plugin';
2021
import { createAngularLocaleDataPlugin } from './i18n-locale-plugin';
2122
import { createRxjsEsmResolutionPlugin } from './rxjs-esm-resolution-plugin';
2223
import { createSourcemapIgnorelistPlugin } from './sourcemap-ignorelist-plugin';
@@ -59,14 +60,21 @@ export function createBrowserCodeBundleOptions(
5960
],
6061
};
6162

62-
if (options.externalPackages) {
63-
buildOptions.packages = 'external';
64-
}
65-
6663
if (options.plugins) {
6764
buildOptions.plugins?.push(...options.plugins);
6865
}
6966

67+
if (options.externalPackages) {
68+
// Package files affected by a customized loader should not be implicitly marked as external
69+
if (options.loaderExtensions || options.plugins) {
70+
// Plugin must be added after custom plugins to ensure any added loader options are considered
71+
buildOptions.plugins?.push(createExternalPackagesPlugin());
72+
} else {
73+
// Safe to use the packages external option directly
74+
buildOptions.packages = 'external';
75+
}
76+
}
77+
7078
return buildOptions;
7179
}
7280

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import type { Plugin } from 'esbuild';
10+
import { extname } from 'node:path';
11+
12+
const EXTERNAL_PACKAGE_RESOLUTION = Symbol('EXTERNAL_PACKAGE_RESOLUTION');
13+
14+
/**
15+
* Creates a plugin that marks any resolved path as external if it is within a node modules directory.
16+
* This is used instead of the esbuild `packages` option to avoid marking files that should be loaded
17+
* via customized loaders. This is necessary to prevent Vite development server pre-bundling errors.
18+
*
19+
* @returns An esbuild plugin.
20+
*/
21+
export function createExternalPackagesPlugin(): Plugin {
22+
return {
23+
name: 'angular-external-packages',
24+
setup(build) {
25+
// Safe to use native packages external option if no loader options present
26+
if (
27+
build.initialOptions.loader === undefined ||
28+
Object.keys(build.initialOptions.loader).length === 0
29+
) {
30+
build.initialOptions.packages = 'external';
31+
32+
return;
33+
}
34+
35+
const loaderFileExtensions = new Set(Object.keys(build.initialOptions.loader));
36+
37+
// Only attempt resolve of non-relative and non-absolute paths
38+
build.onResolve({ filter: /^[^./]/ }, async (args) => {
39+
if (args.pluginData?.[EXTERNAL_PACKAGE_RESOLUTION]) {
40+
return null;
41+
}
42+
43+
const { importer, kind, resolveDir, namespace, pluginData = {} } = args;
44+
pluginData[EXTERNAL_PACKAGE_RESOLUTION] = true;
45+
46+
const result = await build.resolve(args.path, {
47+
importer,
48+
kind,
49+
namespace,
50+
pluginData,
51+
resolveDir,
52+
});
53+
54+
// Return result if unable to resolve or explicitly marked external (externalDependencies option)
55+
if (!result.path || result.external) {
56+
return result;
57+
}
58+
59+
// Allow customized loaders to run against configured paths regardless of location
60+
if (loaderFileExtensions.has(extname(result.path))) {
61+
return result;
62+
}
63+
64+
// Mark paths from a node modules directory as external
65+
if (/[\\/]node_modules[\\/]/.test(result.path)) {
66+
return {
67+
path: args.path,
68+
external: true,
69+
};
70+
}
71+
72+
// Otherwise return original result
73+
return result;
74+
});
75+
},
76+
};
77+
}

0 commit comments

Comments
 (0)