Skip to content

Commit

Permalink
feat(@angular-devkit/build-angular): remove bundleDependencies from…
Browse files Browse the repository at this point in the history
… server builder

This commit removes the usages of `bundleDependencies` which does not correctly work as webpack will use `require` to import ESM module since we configure the server bundle to be outputted in CJS. Migrating fully to ESM is also currently not viable due to the lack of support from Domino.
Even if full ESM was possible, using this option would have resulted in a runtime overhead as Angular libraries would be linked during runtime instead of compile time.

BREAKING CHANGE:
The server builder `bundleDependencies` option has been removed. This option was used pre Ivy. Currently, using this option is unlikely to produce working server bundles.

The `externalDependencies` option can be used instead to exclude specific node_module packages from the final bundle.

Closes #23905
  • Loading branch information
alan-agius4 authored and clydin committed Sep 23, 2022
1 parent 4ead45c commit 9c13fce
Show file tree
Hide file tree
Showing 10 changed files with 121 additions and 111 deletions.
1 change: 0 additions & 1 deletion goldens/public-api/angular_devkit/build_angular/index.md
Expand Up @@ -244,7 +244,6 @@ export interface ProtractorBuilderOptions {

// @public (undocumented)
export interface ServerBuilderOptions {
bundleDependencies?: boolean;
deleteOutputPath?: boolean;
// @deprecated
deployUrl?: string;
Expand Down
Expand Up @@ -181,11 +181,6 @@
"description": "Use file name for lazy loaded chunks.",
"default": false
},
"bundleDependencies": {
"description": "Which external dependencies to bundle into the bundle. By default, all of node_modules will be bundled.",
"default": true,
"type": "boolean"
},
"externalDependencies": {
"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.",
"type": "array",
Expand Down
Expand Up @@ -41,7 +41,6 @@ export interface BuildOptions {
progress?: boolean;
localize?: Localize;
i18nMissingTranslation?: I18NTranslation;
bundleDependencies?: boolean;
externalDependencies?: string[];
watch?: boolean;
outputHashing?: OutputHashing;
Expand Down
Expand Up @@ -9,7 +9,6 @@
import { AngularWebpackLoaderPath } from '@ngtools/webpack';
import CopyWebpackPlugin from 'copy-webpack-plugin';
import * as path from 'path';
import { ScriptTarget } from 'typescript';
import {
Compiler,
Configuration,
Expand Down Expand Up @@ -37,7 +36,6 @@ import { createIvyPlugin } from '../plugins/typescript';
import { WatchFilesLogsPlugin } from '../plugins/watch-files-logs-plugin';
import {
assetPatterns,
externalizePackages,
getCacheSettings,
getInstrumentationExcludedPaths,
getOutputHashFormat,
Expand Down Expand Up @@ -74,7 +72,6 @@ export async function getCommonConfig(wco: WebpackConfigOptions): Promise<Config
webWorkerTsConfig,
externalDependencies = [],
allowedCommonJsDependencies,
bundleDependencies,
} = buildOptions;

const isPlatformServer = buildOptions.platform === 'server';
Expand Down Expand Up @@ -273,13 +270,6 @@ export async function getCommonConfig(wco: WebpackConfigOptions): Promise<Config
extraMinimizers.push(new TransferSizePlugin());
}

const externals: Configuration['externals'] = [...externalDependencies];
if (isPlatformServer && !bundleDependencies) {
externals.push(({ context, request }, callback) =>
externalizePackages(context ?? wco.projectRoot, request, callback),
);
}

let crossOriginLoading: NonNullable<Configuration['output']>['crossOriginLoading'] = false;
if (subresourceIntegrity && crossOrigin === 'none') {
crossOriginLoading = 'anonymous';
Expand Down Expand Up @@ -307,7 +297,7 @@ export async function getCommonConfig(wco: WebpackConfigOptions): Promise<Config
},
context: root,
entry: entryPoints,
externals,
externals: externalDependencies,
output: {
uniqueName: projectName,
hashFunction: 'xxhash64', // todo: remove in webpack 6. This is part of `futureDefaults`.
Expand Down
25 changes: 0 additions & 25 deletions packages/angular_devkit/build_angular/src/webpack/utils/helpers.ts
Expand Up @@ -252,31 +252,6 @@ export function assetPatterns(root: string, assets: AssetPatternClass[]) {
});
}

export function externalizePackages(
context: string,
request: string | undefined,
callback: (error?: Error, result?: string) => void,
): void {
if (!request) {
return;
}

// Absolute & Relative paths are not externals
if (request.startsWith('.') || path.isAbsolute(request)) {
callback();

return;
}

try {
require.resolve(request, { paths: [context] });
callback(undefined, request);
} catch {
// Node couldn't find it, so it must be user-aliased
callback();
}
}

export function getStatsOptions(verbose = false): WebpackStatsOptions {
const webpackOutputOptions: WebpackStatsOptions = {
all: false, // Fallback value for stats options when an option is not defined. It has precedence over local webpack defaults.
Expand Down
Expand Up @@ -14,6 +14,11 @@
"version": "15.0.0",
"factory": "./update-15/update-typescript-target",
"description": "Update TypeScript compiler `target` and set `useDefineForClassFields`. These changes are for IDE purposes as TypeScript compiler options `target` and `useDefineForClassFields` are set to `ES2022` and `false` respectively by the Angular CLI. To control ECMA version and features use the Browerslist configuration."
},
"update-workspace-config": {
"version": "15.0.0",
"factory": "./update-15/update-workspace-config",
"description": "Remove options from 'angular.json' that are no longer supported by the official builders."
}
}
}
@@ -0,0 +1,27 @@
/**
* @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 { Rule } from '@angular-devkit/schematics';
import { allTargetOptions, updateWorkspace } from '../../utility/workspace';
import { Builders } from '../../utility/workspace-models';

export default function (): Rule {
return updateWorkspace((workspace) => {
for (const project of workspace.projects.values()) {
for (const target of project.targets.values()) {
if (target.builder !== Builders.Server) {
continue;
}

for (const [, options] of allTargetOptions(target)) {
delete options.bundleDependencies;
}
}
}
});
}
@@ -0,0 +1,86 @@
/**
* @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 { JsonObject } from '@angular-devkit/core';
import { EmptyTree } from '@angular-devkit/schematics';
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
import {
BuilderTarget,
Builders,
ProjectType,
WorkspaceSchema,
} from '../../utility/workspace-models';

function getServerTarget(tree: UnitTestTree): BuilderTarget<Builders.Server, JsonObject> {
const target = (tree.readJson('/angular.json') as unknown as WorkspaceSchema).projects.app
.architect?.server;

return target as unknown as BuilderTarget<Builders.Server, JsonObject>;
}

function createWorkSpaceConfig(tree: UnitTestTree) {
const angularConfig: WorkspaceSchema = {
version: 1,
projects: {
app: {
root: '',
sourceRoot: 'src',
projectType: ProjectType.Application,
prefix: 'app',
architect: {
server: {
builder: Builders.Server,
options: {
main: './server.ts',
bundleDependencies: false,
sourceMaps: true,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any,
configurations: {
one: {
aot: true,
},
two: {
bundleDependencies: true,
aot: true,
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any,
},
},
},
},
};

tree.create('/angular.json', JSON.stringify(angularConfig, undefined, 2));
}

const schematicName = 'update-workspace-config';

describe(`Migration to update 'angular.json'. ${schematicName}`, () => {
const schematicRunner = new SchematicTestRunner(
'migrations',
require.resolve('../migration-collection.json'),
);

let tree: UnitTestTree;
beforeEach(() => {
tree = new UnitTestTree(new EmptyTree());
createWorkSpaceConfig(tree);
});

it(`should remove 'bundleDependencies'`, async () => {
const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
const { options, configurations } = getServerTarget(newTree);

expect(options.bundleDependencies).toBeUndefined();
expect(configurations).toBeDefined();
expect(configurations?.one.bundleDependencies).toBeUndefined();
expect(configurations?.two.bundleDependencies).toBeUndefined();
});
});
4 changes: 2 additions & 2 deletions tests/legacy-cli/e2e/tests/build/platform-server.ts
Expand Up @@ -59,8 +59,8 @@ export default async function () {
/<p.*>Here are some links to help you get started:<\/p>/,
);

// works with optimization and bundleDependencies enabled
await ng('run', 'test-project:server', '--optimization', '--bundle-dependencies');
// works with optimization
await ng('run', 'test-project:server', '--optimization');
await exec(normalize('node'), 'dist/test-project/server/main.js');
await expectFileToMatch(
'dist/test-project/server/index.html',
Expand Down
66 changes: 0 additions & 66 deletions tests/legacy-cli/e2e/tests/misc/universal-bundle-dependencies.ts

This file was deleted.

0 comments on commit 9c13fce

Please sign in to comment.