From e7756544be7647f5fef90c5c0a0c908315c10544 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Sun, 4 May 2025 19:22:52 -0400 Subject: [PATCH] fix(@angular/build): allow a default application `browser` option The application build system's `browser` option is now considered optional. If not present, the value will be an `main.ts` file within the configured project source root (`sourceRoot`). This change allows the removal of the `browser` option from any project that uses the default generated value. --- goldens/public-api/angular/build/index.api.md | 2 +- .../build/src/builders/application/options.ts | 20 +++++---- .../src/builders/application/schema.json | 2 +- .../application/tests/options/browser_spec.ts | 43 +++++++++++++++++++ 4 files changed, 57 insertions(+), 10 deletions(-) diff --git a/goldens/public-api/angular/build/index.api.md b/goldens/public-api/angular/build/index.api.md index 6c0c2f19145b..31576f664e36 100644 --- a/goldens/public-api/angular/build/index.api.md +++ b/goldens/public-api/angular/build/index.api.md @@ -25,7 +25,7 @@ export type ApplicationBuilderOptions = { appShell?: boolean; assets?: AssetPattern[]; baseHref?: string; - browser: string; + browser?: string; budgets?: Budget[]; clearScreen?: boolean; conditions?: string[]; diff --git a/packages/angular/build/src/builders/application/options.ts b/packages/angular/build/src/builders/application/options.ts index 60b240eb9d74..c3b13699a06e 100644 --- a/packages/angular/build/src/builders/application/options.ts +++ b/packages/angular/build/src/builders/application/options.ts @@ -177,7 +177,12 @@ export async function normalizeOptions( i18nOptions.flatOutput = true; } - const entryPoints = normalizeEntryPoints(workspaceRoot, options.browser, options.entryPoints); + const entryPoints = normalizeEntryPoints( + workspaceRoot, + projectSourceRoot, + options.browser, + options.entryPoints, + ); const tsconfig = path.join(workspaceRoot, options.tsConfig); const optimizationOptions = normalizeOptimization(options.optimization); const sourcemapOptions = normalizeSourceMaps(options.sourceMap ?? false); @@ -545,26 +550,25 @@ async function getTailwindConfig( */ function normalizeEntryPoints( workspaceRoot: string, + projectSourceRoot: string, browser: string | undefined, - entryPoints: Set | Map = new Set(), + entryPoints: Set | Map | undefined, ): Record { if (browser === '') { throw new Error('`browser` option cannot be an empty string.'); } // `browser` and `entryPoints` are mutually exclusive. - if (browser && entryPoints.size > 0) { + if (browser && entryPoints) { throw new Error('Only one of `browser` or `entryPoints` may be provided.'); } - if (!browser && entryPoints.size === 0) { - // Schema should normally reject this case, but programmatic usages of the builder might make this mistake. - throw new Error('Either `browser` or at least one `entryPoints` value must be provided.'); - } - // Schema types force `browser` to always be provided, but it may be omitted when the builder is invoked programmatically. if (browser) { // Use `browser` alone. return { 'main': path.join(workspaceRoot, browser) }; + } else if (!entryPoints) { + // Default browser entry if no explicit entry points + return { 'main': path.join(projectSourceRoot, 'main.ts') }; } else if (entryPoints instanceof Map) { return Object.fromEntries( Array.from(entryPoints.entries(), ([name, entryPoint]) => { diff --git a/packages/angular/build/src/builders/application/schema.json b/packages/angular/build/src/builders/application/schema.json index 934bfe9390f4..ef4cbb75b82a 100644 --- a/packages/angular/build/src/builders/application/schema.json +++ b/packages/angular/build/src/builders/application/schema.json @@ -616,7 +616,7 @@ } }, "additionalProperties": false, - "required": ["browser", "tsConfig"], + "required": ["tsConfig"], "definitions": { "assetPattern": { "oneOf": [ diff --git a/packages/angular/build/src/builders/application/tests/options/browser_spec.ts b/packages/angular/build/src/builders/application/tests/options/browser_spec.ts index 7a795fdb0883..9fe3cd00536a 100644 --- a/packages/angular/build/src/builders/application/tests/options/browser_spec.ts +++ b/packages/angular/build/src/builders/application/tests/options/browser_spec.ts @@ -42,6 +42,49 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { harness.expectFile('dist/browser/main.js').content.toContain('console.log("main")'); }); + it('defaults to use `src/main.ts` if not present', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + browser: undefined, + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/browser/main.js').toExist(); + harness.expectFile('dist/browser/index.html').toExist(); + }); + + it('uses project source root in default if `browser` not present', async () => { + harness.useProject('test', { + root: '.', + sourceRoot: 'source', + cli: { + cache: { + enabled: false, + }, + }, + }); + harness.useTarget('build', { + ...BASE_OPTIONS, + browser: undefined, + index: false, + }); + + // Update app for a `source/main.ts` file based on the above changed `sourceRoot` + await harness.writeFile('source/main.ts', `console.log('main');`); + await harness.modifyFile('src/tsconfig.app.json', (content) => + content.replace('main.ts', '../source/main.ts'), + ); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/browser/main.js').content.toContain('console.log("main")'); + }); + it('fails and shows an error when file does not exist', async () => { harness.useTarget('build', { ...BASE_OPTIONS,