diff --git a/packages/angular/build/src/builders/application/options.ts b/packages/angular/build/src/builders/application/options.ts index 9c488e327a98..4a1d781f6680 100644 --- a/packages/angular/build/src/builders/application/options.ts +++ b/packages/angular/build/src/builders/application/options.ts @@ -327,24 +327,26 @@ export async function normalizeOptions( let indexOutput: string; // The output file will be created within the configured output path if (typeof options.index === 'string') { - /** - * If SSR is activated, create a distinct entry file for the `index.html`. - * This is necessary because numerous server/cloud providers automatically serve the `index.html` as a static file - * if it exists (handling SSG). - * - * For instance, accessing `foo.com/` would lead to `foo.com/index.html` being served instead of hitting the server. - * - * This approach can also be applied to service workers, where the `index.csr.html` is served instead of the prerendered `index.html`. - */ - const indexBaseName = path.basename(options.index); - indexOutput = - (ssrOptions || prerenderOptions) && indexBaseName === 'index.html' - ? INDEX_HTML_CSR - : indexBaseName; + indexOutput = options.index; } else { indexOutput = options.index.output || 'index.html'; } + /** + * If SSR is activated, create a distinct entry file for the `index.html`. + * This is necessary because numerous server/cloud providers automatically serve the `index.html` as a static file + * if it exists (handling SSG). + * + * For instance, accessing `foo.com/` would lead to `foo.com/index.html` being served instead of hitting the server. + * + * This approach can also be applied to service workers, where the `index.csr.html` is served instead of the prerendered `index.html`. + */ + const indexBaseName = path.basename(indexOutput); + indexOutput = + (ssrOptions || prerenderOptions) && indexBaseName === 'index.html' + ? INDEX_HTML_CSR + : indexBaseName; + indexHtmlOptions = { input: path.join( workspaceRoot, diff --git a/packages/angular/build/src/builders/application/tests/behavior/index-csr_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/index-csr_spec.ts new file mode 100644 index 000000000000..0f87dd78a289 --- /dev/null +++ b/packages/angular/build/src/builders/application/tests/behavior/index-csr_spec.ts @@ -0,0 +1,57 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { buildApplication } from '../../index'; +import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup'; + +describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { + describe('Behavior: "index.csr.html"', () => { + beforeEach(async () => { + await harness.modifyFile('src/tsconfig.app.json', (content) => { + const tsConfig = JSON.parse(content); + tsConfig.files ??= []; + tsConfig.files.push('main.server.ts'); + + return JSON.stringify(tsConfig); + }); + }); + + it(`should generate 'index.csr.html' instead of 'index.html' when ssr is enabled.`, async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + server: 'src/main.server.ts', + ssr: true, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + harness.expectDirectory('dist/server').toExist(); + harness.expectFile('dist/browser/index.csr.html').toExist(); + harness.expectFile('dist/browser/index.html').toNotExist(); + }); + + it(`should generate 'index.csr.html' instead of 'index.html' when 'output' is 'index.html' and ssr is enabled.`, async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + index: { + input: 'src/index.html', + output: 'index.html', + }, + server: 'src/main.server.ts', + ssr: true, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + harness.expectDirectory('dist/server').toExist(); + harness.expectFile('dist/browser/index.csr.html').toExist(); + harness.expectFile('dist/browser/index.html').toNotExist(); + }); + }); +}); diff --git a/packages/angular/build/src/builders/application/tests/options/index_spec.ts b/packages/angular/build/src/builders/application/tests/options/index_spec.ts index 83e3cc132fe5..d3a5fe9e57d3 100644 --- a/packages/angular/build/src/builders/application/tests/options/index_spec.ts +++ b/packages/angular/build/src/builders/application/tests/options/index_spec.ts @@ -12,7 +12,6 @@ import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setu describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { describe('Option: "index"', () => { beforeEach(async () => { - // Application code is not needed for index tests await harness.writeFile('src/main.ts', 'console.log("TEST");'); }); @@ -140,92 +139,72 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { }); }); - it('should generate initial preload link elements when preloadInitial is true', async () => { - harness.useTarget('build', { - ...BASE_OPTIONS, - index: { - input: 'src/index.html', - preloadInitial: true, - }, - }); - - // Setup an initial chunk usage for JS - await harness.writeFile('src/a.ts', 'console.log("TEST");'); - await harness.writeFile('src/b.ts', 'import "./a";'); - await harness.writeFile('src/main.ts', 'import "./a";\n(() => import("./b"))();'); + describe('preload', () => { + it('should generate initial preload link elements when preloadInitial is true', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + index: { + input: 'src/index.html', + preloadInitial: true, + }, + }); - const { result } = await harness.executeOnce(); + // Setup an initial chunk usage for JS + await harness.writeFile('src/a.ts', 'console.log("TEST");'); + await harness.writeFile('src/b.ts', 'import "./a";'); + await harness.writeFile('src/main.ts', 'import "./a";\n(() => import("./b"))();'); - expect(result?.success).toBe(true); - harness.expectFile('dist/browser/main.js').content.toContain('chunk-'); - harness.expectFile('dist/browser/index.html').content.toContain('modulepreload'); - harness.expectFile('dist/browser/index.html').content.toContain('chunk-'); - }); + const { result } = await harness.executeOnce(); - it('should generate initial preload link elements when preloadInitial is undefined', async () => { - harness.useTarget('build', { - ...BASE_OPTIONS, - index: { - input: 'src/index.html', - preloadInitial: undefined, - }, + expect(result?.success).toBe(true); + harness.expectFile('dist/browser/main.js').content.toContain('chunk-'); + harness.expectFile('dist/browser/index.html').content.toContain('modulepreload'); + harness.expectFile('dist/browser/index.html').content.toContain('chunk-'); }); - // Setup an initial chunk usage for JS - await harness.writeFile('src/a.ts', 'console.log("TEST");'); - await harness.writeFile('src/b.ts', 'import "./a";'); - await harness.writeFile('src/main.ts', 'import "./a";\n(() => import("./b"))();'); + it('should generate initial preload link elements when preloadInitial is undefined', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + index: { + input: 'src/index.html', + preloadInitial: undefined, + }, + }); - const { result } = await harness.executeOnce(); + // Setup an initial chunk usage for JS + await harness.writeFile('src/a.ts', 'console.log("TEST");'); + await harness.writeFile('src/b.ts', 'import "./a";'); + await harness.writeFile('src/main.ts', 'import "./a";\n(() => import("./b"))();'); - expect(result?.success).toBe(true); - harness.expectFile('dist/browser/main.js').content.toContain('chunk-'); - harness.expectFile('dist/browser/index.html').content.toContain('modulepreload'); - harness.expectFile('dist/browser/index.html').content.toContain('chunk-'); - }); + const { result } = await harness.executeOnce(); - it('should not generate initial preload link elements when preloadInitial is false', async () => { - harness.useTarget('build', { - ...BASE_OPTIONS, - index: { - input: 'src/index.html', - preloadInitial: false, - }, + expect(result?.success).toBe(true); + harness.expectFile('dist/browser/main.js').content.toContain('chunk-'); + harness.expectFile('dist/browser/index.html').content.toContain('modulepreload'); + harness.expectFile('dist/browser/index.html').content.toContain('chunk-'); }); - // Setup an initial chunk usage for JS - await harness.writeFile('src/a.ts', 'console.log("TEST");'); - await harness.writeFile('src/b.ts', 'import "./a";'); - await harness.writeFile('src/main.ts', 'import "./a";\n(() => import("./b"))();'); - - const { result } = await harness.executeOnce(); - - expect(result?.success).toBe(true); - harness.expectFile('dist/browser/main.js').content.toContain('chunk-'); - harness.expectFile('dist/browser/index.html').content.not.toContain('modulepreload'); - harness.expectFile('dist/browser/index.html').content.not.toContain('chunk-'); - }); + it('should not generate initial preload link elements when preloadInitial is false', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + index: { + input: 'src/index.html', + preloadInitial: false, + }, + }); - it(`should generate 'index.csr.html' instead of 'index.html' by default when ssr is enabled.`, async () => { - await harness.modifyFile('src/tsconfig.app.json', (content) => { - const tsConfig = JSON.parse(content); - tsConfig.files ??= []; - tsConfig.files.push('main.server.ts'); + // Setup an initial chunk usage for JS + await harness.writeFile('src/a.ts', 'console.log("TEST");'); + await harness.writeFile('src/b.ts', 'import "./a";'); + await harness.writeFile('src/main.ts', 'import "./a";\n(() => import("./b"))();'); - return JSON.stringify(tsConfig); - }); + const { result } = await harness.executeOnce(); - harness.useTarget('build', { - ...BASE_OPTIONS, - server: 'src/main.server.ts', - ssr: true, + expect(result?.success).toBe(true); + harness.expectFile('dist/browser/main.js').content.toContain('chunk-'); + harness.expectFile('dist/browser/index.html').content.not.toContain('modulepreload'); + harness.expectFile('dist/browser/index.html').content.not.toContain('chunk-'); }); - - const { result } = await harness.executeOnce(); - expect(result?.success).toBeTrue(); - harness.expectDirectory('dist/server').toExist(); - harness.expectFile('dist/browser/index.csr.html').toExist(); - harness.expectFile('dist/browser/index.html').toNotExist(); }); }); });