Skip to content

Commit 94496f2

Browse files
committed
fix(@angular/build): exclude all entrypoints of a library from prebundling
The configuration now ensures that when a package is listed for exclusion, all paths within that package including sub-paths like `@foo/bar/baz` are marked as external and not prebundled by the development server. For example, specifying `@foo/bar` in the exclude list will prevent the development server from bundling any files from the `@foo/bar` package, including its sub-paths such as `@foo/bar/baz`. This aligns with esbuild external option behaviour https://esbuild.github.io/api/#external Closes angular#29170
1 parent c2bef38 commit 94496f2

File tree

4 files changed

+23
-8
lines changed

4 files changed

+23
-8
lines changed

packages/angular/build/src/builders/application/schema.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@
196196
"additionalProperties": false
197197
},
198198
"externalDependencies": {
199-
"description": "Exclude the listed external dependencies from being bundled into the bundle. Instead, the created bundle relies on these dependencies to be available during runtime.",
199+
"description": "Exclude the listed external dependencies from being bundled into the bundle. Instead, the created bundle relies on these dependencies to be available during runtime. Note: `@foo/bar` marks all paths within the `@foo/bar` package as external, including sub-paths like `@foo/bar/baz`.",
200200
"type": "array",
201201
"items": {
202202
"type": "string"

packages/angular/build/src/builders/dev-server/schema.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@
115115
"type": "object",
116116
"properties": {
117117
"exclude": {
118-
"description": "List of package imports that should not be prebundled by the development server. The packages will be bundled into the application code itself.",
118+
"description": "List of package imports that should not be prebundled by the development server. The packages will be bundled into the application code itself. Note: specifying `@foo/bar` marks all paths within the `@foo/bar` package as excluded, including sub-paths like `@foo/bar/baz`.",
119119
"type": "array",
120120
"items": { "type": "string" }
121121
}

packages/angular/build/src/builders/dev-server/tests/behavior/build-external-dependencies_spec.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupT
1515
describe('Behavior: "browser builder external dependencies"', () => {
1616
beforeEach(async () => {
1717
setupTarget(harness, {
18-
externalDependencies: ['rxjs', 'rxjs/operators'],
18+
externalDependencies: ['rxjs'],
1919
});
2020

2121
await harness.writeFile(
@@ -48,7 +48,7 @@ describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupT
4848

4949
it('respects import specifiers when using baseHref with trailing slash', async () => {
5050
setupTarget(harness, {
51-
externalDependencies: ['rxjs', 'rxjs/operators'],
51+
externalDependencies: ['rxjs'],
5252
baseHref: '/test/',
5353
});
5454

@@ -67,7 +67,7 @@ describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupT
6767

6868
it('respects import specifiers when using baseHref without trailing slash', async () => {
6969
setupTarget(harness, {
70-
externalDependencies: ['rxjs', 'rxjs/operators'],
70+
externalDependencies: ['rxjs/*'],
7171
baseHref: '/test',
7272
});
7373

packages/angular/build/src/tools/esbuild/external-packages-plugin.ts

+18-3
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ const EXTERNAL_PACKAGE_RESOLUTION = Symbol('EXTERNAL_PACKAGE_RESOLUTION');
1919
* @returns An esbuild plugin.
2020
*/
2121
export function createExternalPackagesPlugin(options?: { exclude?: string[] }): Plugin {
22-
const exclusions = options?.exclude?.length ? new Set(options.exclude) : undefined;
22+
const exclusions = options?.exclude?.map((e) => (e.endsWith('/*') ? e.slice(0, -2) : e));
23+
const seenExclusion: Set<string> = new Set();
24+
const seenNonExclusions: Set<string> = new Set();
2325

2426
return {
2527
name: 'angular-external-packages',
@@ -33,7 +35,7 @@ export function createExternalPackagesPlugin(options?: { exclude?: string[] }):
3335
.map(([key]) => key);
3436

3537
// Safe to use native packages external option if no loader options or exclusions present
36-
if (!exclusions && !loaderOptionKeys?.length) {
38+
if (!exclusions?.length && !loaderOptionKeys?.length) {
3739
build.initialOptions.packages = 'external';
3840

3941
return;
@@ -47,10 +49,23 @@ export function createExternalPackagesPlugin(options?: { exclude?: string[] }):
4749
return null;
4850
}
4951

50-
if (exclusions?.has(args.path)) {
52+
if (seenExclusion.has(args.path)) {
5153
return null;
5254
}
5355

56+
if (!seenNonExclusions.has(args.path)) {
57+
if (exclusions?.some((e) => e.startsWith(args.path))) {
58+
// Similar to esbuild, --external:@foo/bar automatically implies --external:@foo/bar/*,
59+
// which matches import paths like @foo/bar/baz.
60+
// This means all paths within the @foo/bar package are also marked as external.
61+
seenExclusion.add(args.path);
62+
63+
return null;
64+
}
65+
66+
seenNonExclusions.add(args.path);
67+
}
68+
5469
const { importer, kind, resolveDir, namespace, pluginData = {} } = args;
5570
pluginData[EXTERNAL_PACKAGE_RESOLUTION] = true;
5671

0 commit comments

Comments
 (0)