mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-19 04:26:01 +08:00
feat(@angular-devkit/build-angular): allowing control of index HTML initial preload generation
The long-form variant of the `index` option for the `application` builder now supports an addition sub-option named `preloadInitial`. This new option is a boolean option that controls the generation of initial preload related link elements in the generated index HTML file for the application. Preload related link elements include `preload`, `modulepreload`, and `preconnect` link rels for initial JavaScript and stylesheet application files.
This commit is contained in:
parent
f7d538903a
commit
15a669c1ef
@ -220,6 +220,8 @@ export async function normalizeOptions(
|
||||
styles: options.styles ?? [],
|
||||
}),
|
||||
transformer: extensions?.indexHtmlTransformer,
|
||||
// Preload initial defaults to true
|
||||
preloadInitial: typeof options.index !== 'object' || (options.index.preloadInitial ?? true),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -416,6 +416,11 @@
|
||||
"minLength": 1,
|
||||
"default": "index.html",
|
||||
"description": "The output path of the application's generated HTML index file. The full provided path will be used and will be considered relative to the application's configured output path."
|
||||
},
|
||||
"preloadInitial": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Generates 'preload', `modulepreload', and 'preconnect' link elements for initial application files and resources."
|
||||
}
|
||||
},
|
||||
"required": ["input"]
|
||||
|
@ -0,0 +1,209 @@
|
||||
/**
|
||||
* @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.io/license
|
||||
*/
|
||||
|
||||
import { buildApplication } from '../../index';
|
||||
import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup';
|
||||
|
||||
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");');
|
||||
});
|
||||
|
||||
describe('short form syntax', () => {
|
||||
it('should not generate an output file when false', async () => {
|
||||
harness.useTarget('build', {
|
||||
...BASE_OPTIONS,
|
||||
index: false,
|
||||
});
|
||||
|
||||
const { result } = await harness.executeOnce();
|
||||
|
||||
expect(result?.success).toBe(true);
|
||||
|
||||
harness.expectFile('dist/browser/index.html').toNotExist();
|
||||
});
|
||||
|
||||
// TODO: This fails option validation when used in the CLI but not when used directly
|
||||
xit('should fail build when true', async () => {
|
||||
harness.useTarget('build', {
|
||||
...BASE_OPTIONS,
|
||||
index: true,
|
||||
});
|
||||
|
||||
const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false });
|
||||
|
||||
expect(result?.success).toBe(false);
|
||||
harness.expectFile('dist/browser/index.html').toNotExist();
|
||||
expect(logs).toContain(
|
||||
jasmine.objectContaining({ message: jasmine.stringMatching('Schema validation failed') }),
|
||||
);
|
||||
});
|
||||
|
||||
it('should use the provided file path to generate the output file when a string path', async () => {
|
||||
harness.useTarget('build', {
|
||||
...BASE_OPTIONS,
|
||||
index: 'src/index.html',
|
||||
});
|
||||
|
||||
await harness.writeFile(
|
||||
'src/index.html',
|
||||
'<html><head><title>TEST_123</title></head><body></body>',
|
||||
);
|
||||
|
||||
const { result } = await harness.executeOnce();
|
||||
|
||||
expect(result?.success).toBe(true);
|
||||
harness.expectFile('dist/browser/index.html').content.toContain('TEST_123');
|
||||
});
|
||||
|
||||
// TODO: Build needs to be fixed to not throw an unhandled exception for this case
|
||||
xit('should fail build when a string path to non-existent file', async () => {
|
||||
harness.useTarget('build', {
|
||||
...BASE_OPTIONS,
|
||||
index: 'src/not-here.html',
|
||||
});
|
||||
|
||||
const { result } = await harness.executeOnce({ outputLogsOnFailure: false });
|
||||
|
||||
expect(result?.success).toBe(false);
|
||||
harness.expectFile('dist/browser/index.html').toNotExist();
|
||||
});
|
||||
|
||||
it('should generate initial preload link elements', 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"))();');
|
||||
|
||||
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.toContain('modulepreload');
|
||||
harness.expectFile('dist/browser/index.html').content.toContain('chunk-');
|
||||
});
|
||||
});
|
||||
|
||||
describe('long form syntax', () => {
|
||||
it('should use the provided input path to generate the output file when present', async () => {
|
||||
harness.useTarget('build', {
|
||||
...BASE_OPTIONS,
|
||||
index: {
|
||||
input: 'src/index.html',
|
||||
},
|
||||
});
|
||||
|
||||
await harness.writeFile(
|
||||
'src/index.html',
|
||||
'<html><head><title>TEST_123</title></head><body></body>',
|
||||
);
|
||||
|
||||
const { result } = await harness.executeOnce();
|
||||
|
||||
expect(result?.success).toBe(true);
|
||||
harness.expectFile('dist/browser/index.html').content.toContain('TEST_123');
|
||||
});
|
||||
|
||||
it('should use the provided output path to generate the output file when present', async () => {
|
||||
harness.useTarget('build', {
|
||||
...BASE_OPTIONS,
|
||||
index: {
|
||||
input: 'src/index.html',
|
||||
output: 'output.html',
|
||||
},
|
||||
});
|
||||
|
||||
await harness.writeFile(
|
||||
'src/index.html',
|
||||
'<html><head><title>TEST_123</title></head><body></body>',
|
||||
);
|
||||
|
||||
const { result } = await harness.executeOnce();
|
||||
|
||||
expect(result?.success).toBe(true);
|
||||
harness.expectFile('dist/browser/output.html').content.toContain('TEST_123');
|
||||
});
|
||||
});
|
||||
|
||||
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"))();');
|
||||
|
||||
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.toContain('modulepreload');
|
||||
harness.expectFile('dist/browser/index.html').content.toContain('chunk-');
|
||||
});
|
||||
|
||||
it('should generate initial preload link elements when preloadInitial is undefined', async () => {
|
||||
harness.useTarget('build', {
|
||||
...BASE_OPTIONS,
|
||||
index: {
|
||||
input: 'src/index.html',
|
||||
preloadInitial: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
// 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.toContain('modulepreload');
|
||||
harness.expectFile('dist/browser/index.html').content.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,
|
||||
},
|
||||
});
|
||||
|
||||
// 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-');
|
||||
});
|
||||
});
|
||||
});
|
@ -38,7 +38,7 @@ export async function generateIndexHtml(
|
||||
|
||||
assert(indexHtmlOptions, 'indexHtmlOptions cannot be undefined.');
|
||||
|
||||
if (!externalPackages) {
|
||||
if (!externalPackages && indexHtmlOptions.preloadInitial) {
|
||||
for (const [key, value] of initialFiles) {
|
||||
if (value.entrypoint) {
|
||||
// Entry points are already referenced in the HTML
|
||||
|
Loading…
x
Reference in New Issue
Block a user