Skip to content

Commit 77f99b5

Browse files
phenomnomnominalalexeagle
authored andcommitted
feat(@schematics/angular): add lazy module path fixer
1 parent 5fc1f24 commit 77f99b5

File tree

9 files changed

+198
-1
lines changed

9 files changed

+198
-1
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
]
6363
},
6464
"dependencies": {
65+
"@phenomnomnominal/tsquery": "3.0.0",
6566
"@types/debug": "^4.1.2",
6667
"@types/node-fetch": "^2.1.6",
6768
"@types/progress": "^2.0.3",

packages/schematics/angular/BUILD

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,10 @@ ts_library(
5050
"//packages/angular_devkit/schematics",
5151
"//packages/angular_devkit/schematics:tasks",
5252
"//packages/schematics/angular/third_party/github.com/Microsoft/TypeScript",
53+
"@npm//@phenomnomnominal/tsquery",
5354
"@npm//@types/node",
5455
"@npm//rxjs",
56+
"@npm//tslint",
5557
],
5658
)
5759

@@ -83,12 +85,15 @@ ts_library(
8385
deps = ALL_SCHEMA_DEPS + [
8486
":angular",
8587
"//packages/angular_devkit/core",
88+
"//packages/angular_devkit/core:node_testing",
8689
"//packages/angular_devkit/schematics",
8790
"//packages/angular_devkit/schematics:testing",
8891
"//packages/schematics/angular/third_party/github.com/Microsoft/TypeScript",
92+
"@npm//@phenomnomnominal/tsquery",
8993
"@npm//@types/node",
9094
"@npm//@types/jasmine",
9195
"@npm//rxjs",
96+
"@npm//tslint",
9297
],
9398
testonly = True,
9499
# @external_begin

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@
3434
"version": "8.0.0-beta.12",
3535
"factory": "./update-8",
3636
"description": "Update an Angular CLI project to version 8."
37+
},
38+
"migration-08": {
39+
"version": "8.0.0-beta.14",
40+
"factory": "./update-8/#updateLazyModulePaths",
41+
"description": "Update an Angular CLI project to version 8."
3742
}
3843
}
3944
}

packages/schematics/angular/migrations/update-8/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import { updatePackageJson, updateTsLintConfig } from './codelyzer-5';
1414
import { updateES5Projects } from './differential-loading';
1515
import { dropES2015Polyfills } from './drop-es6-polyfills';
1616

17+
export { updateLazyModulePaths } from './update-lazy-module-paths';
18+
1719
export default function(): Rule {
1820
return () => {
1921
return chain([
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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 { tsquery } from '@phenomnomnominal/tsquery';
10+
import {
11+
Replacement,
12+
RuleFailure,
13+
Rules,
14+
} from 'tslint'; // tslint:disable-line:no-implicit-dependencies
15+
import * as ts from '../../../third_party/github.com/Microsoft/TypeScript/lib/typescript';
16+
17+
// Constants:
18+
const LOAD_CHILDREN_SPLIT = '#';
19+
const NOT_CHILDREN_QUERY = `:not(:has(Identifier[name="children"]))`;
20+
const HAS_LOAD_CHILDREN_QUERY = `:has(Identifier[name="loadChildren"])`;
21+
const LAZY_VALUE_QUERY = `StringLiteral[value=/.*${LOAD_CHILDREN_SPLIT}.*/]`;
22+
const LOAD_CHILDREN_ASSIGNMENT_QUERY =
23+
`PropertyAssignment${NOT_CHILDREN_QUERY}${HAS_LOAD_CHILDREN_QUERY}:has(${LAZY_VALUE_QUERY})`;
24+
25+
const FAILURE_MESSAGE = 'Found magic `loadChildren` string. Use a function with `import` instead.';
26+
27+
export class Rule extends Rules.AbstractRule {
28+
public apply (ast: ts.SourceFile): Array<RuleFailure> {
29+
return tsquery(ast, LOAD_CHILDREN_ASSIGNMENT_QUERY).map(result => {
30+
const [valueNode] = tsquery(result, LAZY_VALUE_QUERY);
31+
let fix = this._promiseReplacement(valueNode.text);
32+
33+
// Try to fix indentation in replacement:
34+
const { character } = ast.getLineAndCharacterOfPosition(result.getStart());
35+
fix = fix.replace(/\n/g, `\n${' '.repeat(character)}`);
36+
37+
const replacement = new Replacement(valueNode.getStart(), valueNode.getWidth(), fix);
38+
const start = result.getStart();
39+
const end = result.getEnd();
40+
41+
return new RuleFailure(ast, start, end, FAILURE_MESSAGE, this.ruleName, replacement);
42+
});
43+
}
44+
45+
private _promiseReplacement (loadChildren: string): string {
46+
const [path, moduleName] = this._getChunks(loadChildren);
47+
48+
return `() => import('${path}').then(m => m.${moduleName})`;
49+
}
50+
51+
private _getChunks (loadChildren: string): Array<string> {
52+
return loadChildren.split(LOAD_CHILDREN_SPLIT);
53+
}
54+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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 {
9+
Rule,
10+
SchematicContext,
11+
Tree,
12+
} from '@angular-devkit/schematics';
13+
import {
14+
TslintFixTask,
15+
} from '@angular-devkit/schematics/tasks';
16+
import * as path from 'path';
17+
18+
export const updateLazyModulePaths = (): Rule => {
19+
return (_: Tree, context: SchematicContext) => {
20+
context.addTask(new TslintFixTask({
21+
rulesDirectory: path.join(__dirname, 'rules'),
22+
rules: {
23+
'no-lazy-module-paths': [true],
24+
},
25+
}, {
26+
includes: '**/*.ts',
27+
silent: false,
28+
}));
29+
};
30+
};
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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 { getSystemPath, normalize, virtualFs } from '@angular-devkit/core';
9+
import { TempScopedNodeJsSyncHost } from '@angular-devkit/core/node/testing';
10+
import { HostTree } from '@angular-devkit/schematics';
11+
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
12+
13+
describe('Migration to version 8', () => {
14+
const schematicRunner = new SchematicTestRunner(
15+
'migrations',
16+
require.resolve('../migration-collection.json'),
17+
);
18+
19+
let tree: UnitTestTree;
20+
let host: TempScopedNodeJsSyncHost;
21+
22+
const lazyRoutePath = normalize('src/lazy-route.ts');
23+
const lazyRoute = virtualFs.stringToFileBuffer(`
24+
import { Route } from '@angular/router';
25+
const routes: Array<Route> = [
26+
{
27+
path: '',
28+
loadChildren: './lazy/lazy.module#LazyModule'
29+
}
30+
];
31+
`);
32+
33+
const lazyChildRoute = virtualFs.stringToFileBuffer(`
34+
import { Route } from '@angular/router';
35+
const routes: Array<Route> = [
36+
{
37+
path: '',
38+
children: [{
39+
path: 'child',
40+
loadChildren: './lazy/lazy.module#LazyModule'
41+
}]
42+
}
43+
];
44+
`);
45+
46+
describe('Migration to import() style lazy routes', () => {
47+
beforeEach(async () => {
48+
host = new TempScopedNodeJsSyncHost();
49+
tree = new UnitTestTree(new HostTree(host));
50+
tree.create('/package.json', JSON.stringify({}));
51+
process.chdir(getSystemPath(host.root));
52+
});
53+
54+
it('should replace the module path string', async () => {
55+
await host.write(lazyRoutePath, lazyRoute).toPromise();
56+
57+
schematicRunner.runSchematic('migration-08', {}, tree);
58+
await schematicRunner.engine.executePostTasks().toPromise();
59+
60+
const routes = await host.read(lazyRoutePath)
61+
.toPromise()
62+
.then(virtualFs.fileBufferToString);
63+
64+
expect(routes).not.toContain('./lazy/lazy.module#LazyModule');
65+
expect(routes).toContain(
66+
`loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule)`);
67+
});
68+
69+
it('should replace the module path string in a child path', async () => {
70+
await host.write(lazyRoutePath, lazyChildRoute).toPromise();
71+
72+
schematicRunner.runSchematic('migration-08', {}, tree);
73+
await schematicRunner.engine.executePostTasks().toPromise();
74+
75+
const routes = await host.read(lazyRoutePath)
76+
.toPromise()
77+
.then(virtualFs.fileBufferToString);
78+
79+
expect(routes).not.toContain('./lazy/lazy.module#LazyModule');
80+
81+
expect(routes).toContain(
82+
`loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule)`);
83+
});
84+
});
85+
});

packages/schematics/angular/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
],
1010
"schematics": "./collection.json",
1111
"dependencies": {
12+
"@phenomnomnominal/tsquery": "3.0.0",
1213
"@angular-devkit/core": "0.0.0",
1314
"@angular-devkit/schematics": "0.0.0"
1415
}

yarn.lock

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,13 @@
221221
resolved "https://registry.yarnpkg.com/@ngtools/json-schema/-/json-schema-1.1.0.tgz#c3a0c544d62392acc2813a42c8a0dc6f58f86922"
222222
integrity sha1-w6DFRNYjkqzCgTpCyKDcb1j4aSI=
223223

224+
"@phenomnomnominal/[email protected]":
225+
version "3.0.0"
226+
resolved "https://registry.yarnpkg.com/@phenomnomnominal/tsquery/-/tsquery-3.0.0.tgz#6f2f4dbf6304ff52b12cc7a5b979f20c3794a22a"
227+
integrity sha512-SW8lKitBHWJ9fAYkJ9kJivuctwNYCh3BUxLdH0+XiR1GPBiu+7qiZzh8p8jqlj1LgVC1TbvfNFroaEsmYlL8Iw==
228+
dependencies:
229+
esquery "^1.0.1"
230+
224231
"@sindresorhus/is@^0.14.0":
225232
version "0.14.0"
226233
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
@@ -3341,6 +3348,13 @@ esprima@^4.0.0:
33413348
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
33423349
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
33433350

3351+
esquery@^1.0.1:
3352+
version "1.0.1"
3353+
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708"
3354+
integrity sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==
3355+
dependencies:
3356+
estraverse "^4.0.0"
3357+
33443358
esrecurse@^4.1.0:
33453359
version "4.2.1"
33463360
resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf"
@@ -3353,7 +3367,7 @@ estraverse@^1.9.1:
33533367
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44"
33543368
integrity sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=
33553369

3356-
estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0:
3370+
estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0:
33573371
version "4.2.0"
33583372
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
33593373
integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=

0 commit comments

Comments
 (0)