Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions goldens/public-api/angular_devkit/build_angular/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ export interface DevServerBuilderOptions {
open?: boolean;
poll?: number;
port?: number;
prebundle?: PrebundleUnion;
proxyConfig?: string;
publicHost?: string;
servePath?: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,27 @@ export function execute(
);
}

// Warn if the initial options provided by the user enable prebundling but caching is disabled
if (options.prebundle && !normalizedOptions.cacheOptions.enabled) {
context.logger.warn(
`Prebundling has been configured but will not be used because caching has been disabled.`,
);
}

return defer(() => import('./vite-server')).pipe(
switchMap(({ serveWithVite }) =>
serveWithVite(normalizedOptions, builderName, context, transforms, extensions),
),
);
}

// Warn if the initial options provided by the user enable prebundling with Webpack-based builders
if (options.prebundle) {
context.logger.warn(
`Prebundling has been configured but will not be used because it is not supported by the "${builderName}" builder.`,
);
}

if (extensions?.buildPlugins?.length) {
throw new Error('Only the `application` and `browser-esbuild` builders support plugins.');
}
Expand Down Expand Up @@ -105,7 +119,13 @@ async function initialize(
await purgeStaleBuildCache(context);

const normalizedOptions = await normalizeOptions(context, projectName, initialOptions);
const builderName = await context.getBuilderNameForTarget(normalizedOptions.buildTarget);
const builderName = builderSelector(
{
builderName: await context.getBuilderNameForTarget(normalizedOptions.buildTarget),
forceEsbuild: !!normalizedOptions.forceEsbuild,
},
context.logger,
);

if (
!normalizedOptions.disableHostCheck &&
Expand Down Expand Up @@ -133,10 +153,7 @@ case.
normalizedOptions.port = await checkPort(normalizedOptions.port, normalizedOptions.host);

return {
builderName: builderSelector(
{ builderName, forceEsbuild: !!normalizedOptions.forceEsbuild },
context.logger,
),
builderName,
normalizedOptions,
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export async function normalizeOptions(
sslCert,
sslKey,
forceEsbuild,
prebundle,
} = options;

// Return all the normalized options
Expand All @@ -84,5 +85,7 @@ export async function normalizeOptions(
sslCert,
sslKey,
forceEsbuild,
// Prebundling defaults to true but requires caching to function
prebundle: cacheOptions.enabled && (prebundle ?? true),
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,24 @@
"type": "boolean",
"description": "Force the development server to use the 'browser-esbuild' builder when building. This is a developer preview option for the esbuild-based build system.",
"default": false
},
"prebundle": {
Comment thread
clydin marked this conversation as resolved.
"description": "Enable and control the Vite-based development server's prebundling capabilities. To enable prebundling, the Angular CLI cache must also be enabled. This option has no effect when using the 'browser' or other Webpack-based builders.",
"oneOf": [
{ "type": "boolean" },
{
"type": "object",
"properties": {
"exclude": {
"description": "List of package imports that should not be prebundled by the development server. The packages will be bundled into the application code itself.",
"type": "array",
"items": { "type": "string" }
}
},
"additionalProperties": false,
"required": ["exclude"]
}
]
}
},
"additionalProperties": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,24 @@ export async function executeOnceAndFetch<T>(
harness: BuilderHarness<T>,
url: string,
options?: Partial<BuilderHarnessExecutionOptions> & { request?: RequestInit },
): Promise<BuilderHarnessExecutionResult & { response?: Response }> {
): Promise<BuilderHarnessExecutionResult & { response?: Response; content?: string }> {
return lastValueFrom(
harness.execute().pipe(
timeout(30000),
mergeMap(async (executionResult) => {
let response = undefined;
let content = undefined;
if (executionResult.result?.success) {
let baseUrl = `${executionResult.result.baseUrl}`;
baseUrl = baseUrl[baseUrl.length - 1] === '/' ? baseUrl : `${baseUrl}/`;
const resolvedUrl = new URL(url, baseUrl);
response = await fetch(resolvedUrl, options?.request);
const originalResponse = await fetch(resolvedUrl, options?.request);
response = originalResponse.clone();
// Ensure all data is available before stopping server
content = await originalResponse.text();
}

return { ...executionResult, response };
return { ...executionResult, response, content };
}),
take(1),
),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/**
* @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 { executeDevServer } from '../../index';
import { executeOnceAndFetch } from '../execute-fetch';
import { describeServeBuilder } from '../jasmine-helpers';
import { BASE_OPTIONS, DEV_SERVER_BUILDER_INFO } from '../setup';

// TODO: Temporarily disabled pending investigation into test-only Vite not stopping when caching is enabled
describeServeBuilder(
executeDevServer,
DEV_SERVER_BUILDER_INFO,
(harness, setupTarget, isViteRun) => {
// prebundling is not available in webpack
(isViteRun ? xdescribe : xdescribe)('option: "prebundle"', () => {
beforeEach(async () => {
setupTarget(harness);

harness.useProject('test', {
cli: {
cache: {
enabled: true,
},
},
});

// Application code is not needed for these tests
await harness.writeFile(
'src/main.ts',
`
import { VERSION as coreVersion } from '@angular/core';
import { VERSION as platformVersion } from '@angular/platform-browser';

console.log(coreVersion);
console.log(platformVersion);
`,
);
});

it('should prebundle dependencies when option is not present', async () => {
harness.useTarget('serve', {
...BASE_OPTIONS,
});

const { result, content } = await executeOnceAndFetch(harness, '/main.js');

expect(result?.success).toBeTrue();
expect(content).toContain('vite/deps/@angular_core.js');
expect(content).not.toContain('node_modules/@angular/core/');
});

it('should prebundle dependencies when option is set to true', async () => {
harness.useTarget('serve', {
...BASE_OPTIONS,
prebundle: true,
});

const { result, content } = await executeOnceAndFetch(harness, '/main.js');

expect(result?.success).toBeTrue();
expect(content).toContain('vite/deps/@angular_core.js');
expect(content).not.toContain('node_modules/@angular/core/');
});

it('should not prebundle dependencies when option is set to false', async () => {
harness.useTarget('serve', {
...BASE_OPTIONS,
prebundle: false,
});

const { result, content } = await executeOnceAndFetch(harness, '/main.js');

expect(result?.success).toBeTrue();
expect(content).not.toContain('vite/deps/@angular_core.js');
expect(content).toContain('node_modules/@angular/core/');
});

it('should not prebundle specified dependency if added to exclude list', async () => {
harness.useTarget('serve', {
...BASE_OPTIONS,
prebundle: { exclude: ['@angular/platform-browser'] },
});

const { result, content } = await executeOnceAndFetch(harness, '/main.js');

expect(result?.success).toBeTrue();
expect(content).toContain('vite/deps/@angular_core.js');
expect(content).not.toContain('node_modules/@angular/core/');
expect(content).not.toContain('vite/deps/@angular_platform-browser.js');
expect(content).toContain('node_modules/@angular/platform-browser/');
});
});
},
);
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export async function* serveWithVite(
}

// Set all packages as external to support Vite's prebundle caching
browserOptions.externalPackages = serverOptions.cacheOptions.enabled;
browserOptions.externalPackages = serverOptions.prebundle as json.JsonValue;

const baseHref = browserOptions.baseHref;
if (serverOptions.servePath === undefined && baseHref !== undefined) {
Expand Down Expand Up @@ -513,7 +513,7 @@ export async function setupServer(
*/

// Only enable with caching since it causes prebundle dependencies to be cached
disabled: true, // !serverOptions.cacheOptions.enabled,
disabled: true, // serverOptions.prebundle === false,
// Exclude any explicitly defined dependencies (currently build defined externals and node.js built-ins)
exclude: serverExplicitExternal,
// Include all implict dependencies from the external packages internal option
Expand Down Expand Up @@ -543,7 +543,7 @@ export async function setupServer(
// Browser only optimizeDeps. (This does not run for SSR dependencies).
optimizeDeps: getDepOptimizationConfig({
// Only enable with caching since it causes prebundle dependencies to be cached
disabled: !serverOptions.cacheOptions.enabled,
disabled: serverOptions.prebundle === false,
// Exclude any explicitly defined dependencies (currently build defined externals)
exclude: externalMetadata.explicit,
// Include all implict dependencies from the external packages internal option
Expand Down