Skip to content

Commit cbf0feb

Browse files
alan-agius4dgp1130
authored andcommitted
feat(@schematics/angular): enable stricter type checking and optimization effective coding rules
With this change we enable stricter type checking and optimization effective coding rules when using the `--strict` option. Changes in schematics - `ng-new`: A prompt for the `--strict` option was added. This option is a proxy and will be passed to the application and workspace schematics. - `application`: A `package.json` was added in the `app` folder, to tell the bundlers whether the application is free from side-effect code. When `strict` is `true`. the `sideEffects` will be set `false`. - `workspace` When `strict` is true, we add stricter TypeScript and Angular type-checking options. Note: AIO is already using these strict TypeScript compiler settings. PR to enable `strictTemplates` angular/angular#36391 Reference: TOOL-1366
1 parent 2d48ab3 commit cbf0feb

22 files changed

+192
-27
lines changed

packages/schematics/angular/application/index_spec.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,24 @@ describe('Application Schematic', () => {
280280
});
281281
});
282282

283+
it('sideEffects property should be true when strict mode', async () => {
284+
const options = { ...defaultOptions, projectRoot: '', strict: true };
285+
286+
const tree = await schematicRunner.runSchematicAsync('application', options, workspaceTree)
287+
.toPromise();
288+
const content = JSON.parse(tree.readContent('/src/app/package.json'));
289+
expect(content.sideEffects).toBe(false);
290+
});
291+
292+
it('sideEffects property should be false when not in strict mode', async () => {
293+
const options = { ...defaultOptions, projectRoot: '', strict: false };
294+
295+
const tree = await schematicRunner.runSchematicAsync('application', options, workspaceTree)
296+
.toPromise();
297+
const content = JSON.parse(tree.readContent('/src/app/package.json'));
298+
expect(content.sideEffects).toBe(true);
299+
});
300+
283301
describe('custom projectRoot', () => {
284302
it('should put app files in the right spot', async () => {
285303
const options = { ...defaultOptions, projectRoot: '' };
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "<%= utils.dasherize(name) %>",
3+
"private": true,
4+
"description": "This is a special package.json file that is not used by package managers. It is however used to tell the tools and bundlers whether the code under this directory is free of code with non-local side-effect. Any code that does have non-local side-effects can't be well optimized (tree-shaken) and will result in unnecessary increased payload size. It should be safe to set this option to 'false' for new applications, but existing code bases could be broken when built with the production config if the application code does contain non-local side-effects that the application depends on.",
5+
"sideEffects": <%= !strict %>
6+
}

packages/schematics/angular/application/schema.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,11 @@
106106
"description": "When true, applies lint fixes after generating the application.",
107107
"x-user-analytics": 15
108108
},
109+
"strict": {
110+
"description": "Creates an application with stricter build optimization options.",
111+
"type": "boolean",
112+
"default": false
113+
},
109114
"legacyBrowsers": {
110115
"type": "boolean",
111116
"description": "Add support for legacy browsers like Internet Explorer using differential loading.",

packages/schematics/angular/migrations/migration-collection.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@
8484
"version": "10.0.0-beta.3",
8585
"factory": "./update-10/update-angular-config",
8686
"description": "Remove various deprecated builders options from 'angular.json'."
87+
},
88+
"side-effects-package-json": {
89+
"version": "10.0.0-beta.3",
90+
"factory": "./update-10/side-effects-package-json",
91+
"description": "Create a special 'package.json' file that is used to tell the tools and bundlers whether the code under the app directory is free of code with non-local side-effect."
8792
}
8893
}
8994
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import { join, normalize, strings } from '@angular-devkit/core';
10+
import { Rule } from '@angular-devkit/schematics';
11+
import { getWorkspace } from '../../utility/workspace';
12+
import { ProjectType } from '../../utility/workspace-models';
13+
14+
export default function (): Rule {
15+
return async (host, context) => {
16+
const workspace = await getWorkspace(host);
17+
const logger = context.logger;
18+
19+
for (const [projectName, project] of workspace.projects) {
20+
if (project.extensions.projectType !== ProjectType.Application) {
21+
// Only interested in application projects
22+
continue;
23+
}
24+
25+
const appDir = join(normalize(project.sourceRoot || ''), 'app');
26+
const { subdirs, subfiles } = host.getDir(appDir);
27+
if (!subdirs.length && !subfiles.length) {
28+
logger.error(`Application directory '${appDir}' for project '${projectName}' doesn't exist.`);
29+
continue;
30+
}
31+
32+
const pkgJson = join(appDir, 'package.json');
33+
if (!host.exists(pkgJson)) {
34+
const pkgJsonContent = {
35+
name: strings.dasherize(projectName),
36+
private: true,
37+
description: `This is a special package.json file that is not used by package managers. It is however used to tell the tools and bundlers whether the code under this directory is free of code with non-local side-effect. Any code that does have non-local side-effects can't be well optimized (tree-shaken) and will result in unnecessary increased payload size. It should be safe to set this option to 'false' for new applications, but existing code bases could be broken when built with the production config if the application code does contain non-local side-effects that the application depends on.`,
38+
sideEffects: true,
39+
};
40+
41+
host.create(pkgJson, JSON.stringify(pkgJsonContent, undefined, 2));
42+
}
43+
}
44+
};
45+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
import { EmptyTree } from '@angular-devkit/schematics';
9+
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
10+
import { Builders, ProjectType, WorkspaceSchema } from '../../utility/workspace-models';
11+
12+
function createWorkSpaceConfig(tree: UnitTestTree) {
13+
const angularConfig: WorkspaceSchema = {
14+
version: 1,
15+
projects: {
16+
demo: {
17+
root: '',
18+
sourceRoot: 'src',
19+
projectType: ProjectType.Application,
20+
prefix: 'app',
21+
architect: {
22+
build: {
23+
builder: Builders.Browser,
24+
options: {
25+
tsConfig: '',
26+
main: '',
27+
polyfills: '',
28+
},
29+
},
30+
},
31+
},
32+
},
33+
};
34+
35+
tree.create('/angular.json', JSON.stringify(angularConfig, undefined, 2));
36+
}
37+
38+
describe(`Migration to add sideEffects package.json`, () => {
39+
const schematicName = 'side-effects-package-json';
40+
41+
const schematicRunner = new SchematicTestRunner(
42+
'migrations',
43+
require.resolve('../migration-collection.json'),
44+
);
45+
46+
let tree: UnitTestTree;
47+
beforeEach(() => {
48+
tree = new UnitTestTree(new EmptyTree());
49+
createWorkSpaceConfig(tree);
50+
tree.create('src/app/main.ts', '');
51+
});
52+
53+
it(`should create a package.json with sideEffects true under app folder.`, async () => {
54+
const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
55+
const { name, sideEffects } = JSON.parse(newTree.readContent('src/app/package.json'));
56+
expect(name).toBe('demo');
57+
expect(sideEffects).toBeTrue();
58+
});
59+
});

packages/schematics/angular/ng-new/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export default function(options: NgNewOptions): Rule {
5858
skipPackageJson: false,
5959
// always 'skipInstall' here, so that we do it after the move
6060
skipInstall: true,
61+
strict: options.strict,
6162
minimal: options.minimal,
6263
legacyBrowsers: options.legacyBrowsers,
6364
};

packages/schematics/angular/ng-new/schema.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,20 +118,21 @@
118118
"x-user-analytics": 12
119119
},
120120
"createApplication": {
121-
"description": "When true (the default), creates a new initial app project in the src folder of the new workspace. When false, creates an empty workspace with no initial app. You can then use the generate application command so that all apps are created in the projects folder.",
121+
"description": "When true (the default), creates a new initial application project in the src folder of the new workspace. When false, creates an empty workspace with no initial app. You can then use the generate application command so that all apps are created in the projects folder.",
122122
"type": "boolean",
123123
"default": true
124124
},
125125
"minimal": {
126-
"description": "When true, creates a project without any testing frameworks. (Use for learning purposes only.)",
126+
"description": "When true, creates a workspace without any testing frameworks. (Use for learning purposes only.)",
127127
"type": "boolean",
128128
"default": false,
129129
"x-user-analytics": 14
130130
},
131131
"strict": {
132-
"description": "Creates a workspace with stricter TypeScript compiler options.",
132+
"description": "Creates a workspace with stricter type checking and build optimization options.",
133133
"type": "boolean",
134-
"default": false
134+
"default": false,
135+
"x-prompt": "Create a workspace with stricter type checking and more efficient production optimizations?"
135136
},
136137
"legacyBrowsers": {
137138
"type": "boolean",

packages/schematics/angular/workspace/files/tsconfig.json.template

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@
33
"compilerOptions": {
44
"baseUrl": "./",
55
"outDir": "./dist/out-tsc",<% if (strict) { %>
6-
"noImplicitAny": true,
6+
"forceConsistentCasingInFileNames": true,
7+
"strict": true,
78
"noImplicitReturns": true,
8-
"noImplicitThis": true,
9-
"noFallthroughCasesInSwitch": true,
10-
"strictNullChecks": true,<% } %>
9+
"noFallthroughCasesInSwitch": true,<% } %>
1110
"sourceMap": true,
1211
"declaration": false,
1312
"downlevelIteration": true,
@@ -20,9 +19,9 @@
2019
"es2018",
2120
"dom"
2221
]
23-
},
22+
}<% if (strict) { %>,
2423
"angularCompilerOptions": {
25-
"fullTemplateTypeCheck": true,
26-
"strictInjectionParameters": true
27-
}
24+
"strictInjectionParameters": true,
25+
"strictTemplates": true
26+
}<% } %>
2827
}

packages/schematics/angular/workspace/index_spec.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,15 @@ describe('Workspace Schematic', () => {
7474

7575
it('should not add strict compiler options when false', async () => {
7676
const tree = await schematicRunner.runSchematicAsync('workspace', { ...defaultOptions, strict: false }).toPromise();
77-
const { compilerOptions } = JSON.parse(tree.readContent('/tsconfig.json'));
78-
expect(compilerOptions.strictNullChecks).not.toBeDefined();
77+
const { compilerOptions, angularCompilerOptions } = JSON.parse(tree.readContent('/tsconfig.json'));
78+
expect(compilerOptions.strict).toBeUndefined();
79+
expect(angularCompilerOptions).toBeUndefined();
7980
});
8081

8182
it('should not add strict compiler options when true', async () => {
8283
const tree = await schematicRunner.runSchematicAsync('workspace', { ...defaultOptions, strict: true }).toPromise();
83-
const { compilerOptions } = JSON.parse(tree.readContent('/tsconfig.json'));
84-
expect(compilerOptions.strictNullChecks).toBe(true);
84+
const { compilerOptions, angularCompilerOptions } = JSON.parse(tree.readContent('/tsconfig.json'));
85+
expect(compilerOptions.strict).toBe(true);
86+
expect(angularCompilerOptions.strictTemplates).toBe(true);
8587
});
8688
});

0 commit comments

Comments
 (0)