mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-16 02:24:10 +08:00
feat(@schematics/angular): add production by default optional migration
With this change we add an optional migration to update Angular CLI workspace configurations to 'production' mode by default. To run this migration use the below commands ``` ng update @angular/cli ng update @angular/cli --migrate-only production-by-default ```
This commit is contained in:
parent
decb05b2fe
commit
c7e126609f
@ -144,6 +144,11 @@
|
|||||||
"version": "12.0.0-next.4",
|
"version": "12.0.0-next.4",
|
||||||
"factory": "./update-8/#updateLazyModulePaths",
|
"factory": "./update-8/#updateLazyModulePaths",
|
||||||
"description": "Lazy loading syntax migration. Update lazy loading string syntax to use dynamic imports."
|
"description": "Lazy loading syntax migration. Update lazy loading string syntax to use dynamic imports."
|
||||||
|
},
|
||||||
|
"production-by-default": {
|
||||||
|
"version": "9999.0.0",
|
||||||
|
"factory": "./update-12/production-default-config",
|
||||||
|
"description": "Optional migration to update Angular CLI workspace configurations to 'production' mode by default."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,132 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. 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 { JsonValue, logging, tags, workspaces } from '@angular-devkit/core';
|
||||||
|
import { Rule } from '@angular-devkit/schematics';
|
||||||
|
import { allTargetOptions, allWorkspaceTargets, updateWorkspace } from '../../utility/workspace';
|
||||||
|
import { Builders } from '../../utility/workspace-models';
|
||||||
|
|
||||||
|
export default function (): Rule {
|
||||||
|
return async (_host, context) => updateWorkspace(workspace => {
|
||||||
|
for (const [name, target] of allWorkspaceTargets(workspace)) {
|
||||||
|
let defaultConfiguration: string | undefined;
|
||||||
|
|
||||||
|
// Only interested in 1st party builders
|
||||||
|
switch (target.builder) {
|
||||||
|
case Builders.AppShell:
|
||||||
|
case Builders.Browser:
|
||||||
|
case Builders.Server:
|
||||||
|
case Builders.NgPackagr:
|
||||||
|
defaultConfiguration = 'production';
|
||||||
|
break;
|
||||||
|
case Builders.DevServer:
|
||||||
|
case Builders.Protractor:
|
||||||
|
case '@nguniversal/builders:ssr-dev-server':
|
||||||
|
defaultConfiguration = 'development';
|
||||||
|
break;
|
||||||
|
case Builders.TsLint:
|
||||||
|
case Builders.ExtractI18n:
|
||||||
|
case Builders.Karma:
|
||||||
|
// Nothing to update
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
context.logger.warn(tags.stripIndents`Cannot update "${name}" target configuration as it's using "${target.builder}"
|
||||||
|
which is a third-party builder. This target configuration will require manual review.`);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!defaultConfiguration) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTarget(name, target, context.logger, defaultConfiguration);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getArchitectTargetWithConfig(currentTarget: string, overrideConfig?: string): string {
|
||||||
|
const [project, target, config = 'development'] = currentTarget.split(':');
|
||||||
|
|
||||||
|
return `${project}:${target}:${overrideConfig || config}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTarget(
|
||||||
|
targetName: string,
|
||||||
|
target: workspaces.TargetDefinition,
|
||||||
|
logger: logging.LoggerApi,
|
||||||
|
defaultConfiguration: string,
|
||||||
|
): void {
|
||||||
|
if (!target.configurations) {
|
||||||
|
target.configurations = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target.configurations?.development) {
|
||||||
|
logger.info(tags.stripIndents`Skipping updating "${targetName}" target configuration as a "development" configuration is already defined.`);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!target.configurations?.production) {
|
||||||
|
logger.info(tags.stripIndents`Skipping updating "${targetName}" target configuration as a "production" configuration is not defined.`);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const developmentOptions: Record<string, JsonValue | undefined> = {};
|
||||||
|
let serverTarget = true;
|
||||||
|
let browserTarget = true;
|
||||||
|
let devServerTarget = true;
|
||||||
|
|
||||||
|
for (const [, options] of allTargetOptions(target)) {
|
||||||
|
if (typeof options.serverTarget === 'string') {
|
||||||
|
options.serverTarget = getArchitectTargetWithConfig(options.serverTarget);
|
||||||
|
if (!developmentOptions.serverTarget) {
|
||||||
|
developmentOptions.serverTarget = getArchitectTargetWithConfig(options.serverTarget, 'development');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
serverTarget = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof options.browserTarget === 'string') {
|
||||||
|
options.browserTarget = getArchitectTargetWithConfig(options.browserTarget);
|
||||||
|
if (!developmentOptions.browserTarget) {
|
||||||
|
developmentOptions.browserTarget = getArchitectTargetWithConfig(options.browserTarget, 'development');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
browserTarget = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof options.devServerTarget === 'string') {
|
||||||
|
options.devServerTarget = getArchitectTargetWithConfig(options.devServerTarget);
|
||||||
|
if (!developmentOptions.devServerTarget) {
|
||||||
|
developmentOptions.devServerTarget = getArchitectTargetWithConfig(options.devServerTarget, 'development');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
devServerTarget = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If all configurastions have a target defined delete the one in options.
|
||||||
|
if (target.options) {
|
||||||
|
if (serverTarget) {
|
||||||
|
delete target.options.serverTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (browserTarget) {
|
||||||
|
delete target.options.browserTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (devServerTarget) {
|
||||||
|
delete target.options.devServerTarget;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
target.defaultConfiguration = defaultConfiguration;
|
||||||
|
target.configurations.development = developmentOptions;
|
||||||
|
}
|
@ -0,0 +1,310 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. 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 { Builders, ProjectType, WorkspaceSchema } from '../../utility/workspace-models';
|
||||||
|
|
||||||
|
function getArchitect(tree: UnitTestTree): JsonObject {
|
||||||
|
return JSON.parse(tree.readContent('/angular.json')).projects.app.architect;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createWorkSpaceConfig(tree: UnitTestTree) {
|
||||||
|
const angularConfig: WorkspaceSchema = {
|
||||||
|
version: 1,
|
||||||
|
projects: {
|
||||||
|
app: {
|
||||||
|
root: '',
|
||||||
|
sourceRoot: 'src',
|
||||||
|
projectType: ProjectType.Application,
|
||||||
|
prefix: 'app',
|
||||||
|
architect: {
|
||||||
|
browser: {
|
||||||
|
builder: Builders.Browser,
|
||||||
|
options: {
|
||||||
|
outputPath: 'dist/integration-project',
|
||||||
|
index: 'src/index.html',
|
||||||
|
main: 'src/main.ts',
|
||||||
|
polyfills: 'src/polyfills.ts',
|
||||||
|
tsConfig: 'tsconfig.app.json',
|
||||||
|
aot: true,
|
||||||
|
sourceMap: true,
|
||||||
|
assets: [
|
||||||
|
'src/favicon.ico',
|
||||||
|
'src/assets',
|
||||||
|
],
|
||||||
|
styles: [
|
||||||
|
'src/styles.css',
|
||||||
|
],
|
||||||
|
scripts: [],
|
||||||
|
},
|
||||||
|
configurations: {
|
||||||
|
production: {
|
||||||
|
deployUrl: 'http://cdn.com',
|
||||||
|
fileReplacements: [{
|
||||||
|
replace: 'src/environments/environment.ts',
|
||||||
|
with: 'src/environments/environment.prod.ts',
|
||||||
|
}],
|
||||||
|
optimization: true,
|
||||||
|
outputHashing: 'all',
|
||||||
|
sourceMap: false,
|
||||||
|
namedChunks: false,
|
||||||
|
extractLicenses: true,
|
||||||
|
vendorChunk: false,
|
||||||
|
buildOptimizer: true,
|
||||||
|
watch: true,
|
||||||
|
budgets: [{
|
||||||
|
type: 'initial',
|
||||||
|
maximumWarning: '2mb',
|
||||||
|
maximumError: '5mb',
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
optimization_sm: {
|
||||||
|
sourceMap: true,
|
||||||
|
optimization: true,
|
||||||
|
namedChunks: false,
|
||||||
|
vendorChunk: true,
|
||||||
|
buildOptimizer: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ng_packagr: {
|
||||||
|
builder: Builders.NgPackagr,
|
||||||
|
options: {
|
||||||
|
watch: true,
|
||||||
|
tsConfig: 'projects/lib/tsconfig.lib.json',
|
||||||
|
},
|
||||||
|
configurations: {
|
||||||
|
production: {
|
||||||
|
watch: false,
|
||||||
|
tsConfig: 'projects/lib/tsconfig.lib.prod.json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dev_server: {
|
||||||
|
builder: Builders.DevServer,
|
||||||
|
options: {
|
||||||
|
browserTarget: 'app:build',
|
||||||
|
watch: false,
|
||||||
|
},
|
||||||
|
configurations: {
|
||||||
|
production: {
|
||||||
|
browserTarget: 'app:build:production',
|
||||||
|
},
|
||||||
|
optimization_sm: {
|
||||||
|
browserTarget: 'app:build:optimization_sm',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
app_shell: {
|
||||||
|
builder: Builders.AppShell,
|
||||||
|
options: {
|
||||||
|
browserTarget: 'app:build',
|
||||||
|
serverTarget: 'app:server',
|
||||||
|
},
|
||||||
|
configurations: {
|
||||||
|
optimization_sm: {
|
||||||
|
browserTarget: 'app:build:optimization_sm',
|
||||||
|
serverTarget: 'app:server:optimization_sm',
|
||||||
|
},
|
||||||
|
production: {
|
||||||
|
browserTarget: 'app:build:production',
|
||||||
|
serverTarget: 'app:server:optimization_sm',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
builder: Builders.Server,
|
||||||
|
options: {
|
||||||
|
outputPath: 'dist/server',
|
||||||
|
main: 'server.ts',
|
||||||
|
tsConfig: 'tsconfig.server.json',
|
||||||
|
optimization: false,
|
||||||
|
sourceMap: true,
|
||||||
|
},
|
||||||
|
configurations: {
|
||||||
|
optimization_sm: {
|
||||||
|
sourceMap: true,
|
||||||
|
optimization: true,
|
||||||
|
},
|
||||||
|
production: {
|
||||||
|
fileReplacements: [
|
||||||
|
{
|
||||||
|
replace: 'src/environments/environment.ts',
|
||||||
|
with: 'src/environments/environment.prod.ts',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sourceMap: false,
|
||||||
|
optimization: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
tree.create('/angular.json', JSON.stringify(angularConfig, undefined, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
const schematicName = 'production-by-default';
|
||||||
|
describe(`Migration to update 'angular.json' configurations to production by default. ${schematicName}`, () => {
|
||||||
|
const schematicRunner = new SchematicTestRunner(
|
||||||
|
'migrations',
|
||||||
|
require.resolve('../migration-collection.json'),
|
||||||
|
);
|
||||||
|
|
||||||
|
let tree: UnitTestTree;
|
||||||
|
beforeEach(() => {
|
||||||
|
tree = new UnitTestTree(new EmptyTree());
|
||||||
|
createWorkSpaceConfig(tree);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('update browser builder configurations', async () => {
|
||||||
|
const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
|
||||||
|
const { browser } = getArchitect(newTree);
|
||||||
|
const output = {
|
||||||
|
builder: '@angular-devkit/build-angular:browser',
|
||||||
|
options: {
|
||||||
|
outputPath: 'dist/integration-project',
|
||||||
|
index: 'src/index.html',
|
||||||
|
main: 'src/main.ts',
|
||||||
|
polyfills: 'src/polyfills.ts',
|
||||||
|
tsConfig: 'tsconfig.app.json',
|
||||||
|
aot: true,
|
||||||
|
sourceMap: true,
|
||||||
|
assets: ['src/favicon.ico', 'src/assets'],
|
||||||
|
styles: ['src/styles.css'],
|
||||||
|
scripts: [],
|
||||||
|
},
|
||||||
|
configurations: {
|
||||||
|
production: {
|
||||||
|
deployUrl: 'http://cdn.com',
|
||||||
|
optimization: true,
|
||||||
|
outputHashing: 'all',
|
||||||
|
sourceMap: false,
|
||||||
|
namedChunks: false,
|
||||||
|
extractLicenses: true,
|
||||||
|
vendorChunk: false,
|
||||||
|
buildOptimizer: true,
|
||||||
|
watch: true,
|
||||||
|
fileReplacements: [{
|
||||||
|
replace: 'src/environments/environment.ts',
|
||||||
|
with: 'src/environments/environment.prod.ts',
|
||||||
|
}],
|
||||||
|
budgets: [{
|
||||||
|
type: 'initial',
|
||||||
|
maximumWarning: '2mb',
|
||||||
|
maximumError: '5mb',
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
optimization_sm: {
|
||||||
|
sourceMap: true,
|
||||||
|
optimization: true,
|
||||||
|
namedChunks: false,
|
||||||
|
vendorChunk: true,
|
||||||
|
buildOptimizer: true,
|
||||||
|
},
|
||||||
|
development: {},
|
||||||
|
},
|
||||||
|
defaultConfiguration: 'production',
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(browser).toEqual(output);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('update ng-packagr builder configurations', async () => {
|
||||||
|
const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
|
||||||
|
const { ng_packagr } = getArchitect(newTree);
|
||||||
|
const output = {
|
||||||
|
builder: '@angular-devkit/build-angular:ng-packagr',
|
||||||
|
options: { watch: true, tsConfig: 'projects/lib/tsconfig.lib.json' },
|
||||||
|
configurations: {
|
||||||
|
production: { watch: false, tsConfig: 'projects/lib/tsconfig.lib.prod.json' },
|
||||||
|
development: {},
|
||||||
|
},
|
||||||
|
defaultConfiguration: 'production',
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(ng_packagr).toEqual(output);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('update dev-server builder configurations', async () => {
|
||||||
|
const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
|
||||||
|
const { dev_server } = getArchitect(newTree);
|
||||||
|
const output = {
|
||||||
|
builder: '@angular-devkit/build-angular:dev-server',
|
||||||
|
options: { watch: false },
|
||||||
|
configurations: {
|
||||||
|
production: { browserTarget: 'app:build:production' },
|
||||||
|
optimization_sm: { browserTarget: 'app:build:optimization_sm' },
|
||||||
|
development: { browserTarget: 'app:build:development' },
|
||||||
|
},
|
||||||
|
defaultConfiguration: 'development',
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(dev_server).toEqual(output);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('update server builder configurations', async () => {
|
||||||
|
const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
|
||||||
|
const { server } = getArchitect(newTree);
|
||||||
|
const output = {
|
||||||
|
builder: '@angular-devkit/build-angular:server',
|
||||||
|
options: {
|
||||||
|
outputPath: 'dist/server',
|
||||||
|
main: 'server.ts',
|
||||||
|
tsConfig: 'tsconfig.server.json',
|
||||||
|
optimization: false,
|
||||||
|
sourceMap: true,
|
||||||
|
},
|
||||||
|
configurations: {
|
||||||
|
optimization_sm: { sourceMap: true, optimization: true },
|
||||||
|
production: {
|
||||||
|
fileReplacements: [{
|
||||||
|
replace: 'src/environments/environment.ts',
|
||||||
|
with: 'src/environments/environment.prod.ts',
|
||||||
|
}],
|
||||||
|
sourceMap: false,
|
||||||
|
optimization: true,
|
||||||
|
},
|
||||||
|
development: {},
|
||||||
|
},
|
||||||
|
defaultConfiguration: 'production',
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(server).toEqual(output);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('update app-shell builder configurations', async () => {
|
||||||
|
const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
|
||||||
|
const { app_shell } = getArchitect(newTree);
|
||||||
|
|
||||||
|
const output = {
|
||||||
|
builder: '@angular-devkit/build-angular:app-shell',
|
||||||
|
options: {},
|
||||||
|
configurations: {
|
||||||
|
optimization_sm: {
|
||||||
|
browserTarget: 'app:build:optimization_sm',
|
||||||
|
serverTarget: 'app:server:optimization_sm',
|
||||||
|
},
|
||||||
|
production: {
|
||||||
|
browserTarget: 'app:build:production',
|
||||||
|
serverTarget: 'app:server:optimization_sm',
|
||||||
|
},
|
||||||
|
development: {
|
||||||
|
serverTarget: 'app:server:development',
|
||||||
|
browserTarget: 'app:build:development',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultConfiguration: 'production',
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(app_shell).toEqual(output);
|
||||||
|
});
|
||||||
|
});
|
@ -76,7 +76,7 @@ export interface ServerBuilderOptions {
|
|||||||
tsConfig: string;
|
tsConfig: string;
|
||||||
main: string;
|
main: string;
|
||||||
fileReplacements?: FileReplacements[];
|
fileReplacements?: FileReplacements[];
|
||||||
optimization?: {
|
optimization?: boolean | {
|
||||||
scripts?: boolean;
|
scripts?: boolean;
|
||||||
styles?: boolean;
|
styles?: boolean;
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user