10000 feat: add utility functions for route generation · angular/angular-cli@deda112 · GitHub
[go: up one dir, main page]

Skip to content

Commit deda112

Browse files
committed
feat: add utility functions for route generation
'route-utils.ts' provides utility functions to be used in generating routes 'blueprints/routes/*' creates a 'routes.ts' file when the newroutes command is run and 'route.ts' doesn't exit
1 parent 46c965c commit deda112

File tree

5 files changed

+430
-1
lines changed

5 files changed

+430
-1
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default [];

addon/ng2/blueprints/routes/index.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
const Blueprint = require('ember-cli/lib/models/blueprint');
2+
const getFiles = Blueprint.prototype.files;
3+
const path = require('path');
4+
const dynamicPathParser = require('../../utilities/dynamic-path-parser');
5+
const util = require('../../utilities/route-utils');
6+
const _ = require('lodash');
7+
const fs = require('fs');
8+
const SilentError = require('silent-error');
9+
10+
module.exports = {
11+
description: 'Generates a route/guard and template',
12+
13+
files: function() {
14+
var fileList = getFiles.call(this);
15+
if (this.project && fs.existsSync(path.join(this.project.root, 'src/routes.ts'))) {
16+
return [];
17+
}
18+
return fileList;
19+
},
20+
21+
fileMapTokens: function() {
22+
return {
23+
__path__: () => 'src'
24+
};
25+
},
26+
27+
normalizeEntityName: function(entityName) {
28+
var parsedPath = dynamicPathParser(this.project, entityName);
29+
this.dynamicPath = parsedPath;
30+
return entityName;
31+
},
32+
33+
afterInstall: function(options) {
34+
const mainFile = path.join(this.project.root, 'src/main.ts');
35+
const routesFile = path.join(this.project.root, 'src/routes.ts');
36+
return util.configureMain(mainFile, 'routes', './routes').then(() => {
37+
return this._locals(options);
38+
}).then(names => {
39+
40+
if (process.env.PWD.indexOf('src/app') === -1) {
41+
return Promise.reject(new SilentError('New route must be within app'));
42+
}
43+
// setup options needed for adding path to routes.ts
44+
var pathOptions = {}
45+
pathOptions.isDefault = options.default;
46+
pathOptions.route = options.path;
47+
pathOptions.component = `${names.classifiedModuleName}Component`;
48+
pathOptions.dasherizedName = names.dasherizedModuleName;
49+
pathOptions = _.merge(pathOptions, this.dynamicPath);
50+
51+
var newRoutePath = options.taskOptions.args[1];
52+
if (!newRoutePath) {
53+
throw new SilentError('Please provide new route\'s name');
54+
}
55+
var file = util.resolveComponentPath(this.project.root, process.env.PWD, newRoutePath);
56+
var component = pathOptions.component;
57+
// confirm that there is an export of the component in componentFile
58+
if (!util.confirmComponentExport(file, component)) {
59+
throw new SilentError(`Please add export for '${component}' to '${file}'`)
60+
}
61+
62+
pathOptions.component = util.resolveImportName(component, path.join(this.project.root, 'src/routes.ts'));
63+
64+
return util.addPathToRoutes(routesFile, pathOptions);
65+
});
66+
}
67+
}

addon/ng2/utilities/dynamic-path-parser.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,3 @@ module.exports = function dynamicPathParser(project, entityName) {
5656

5757
return parsedPath;
5858
};
59-

addon/ng2/utilities/route-utils.ts

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import * as ts from 'typescript';
2+
import * as fs from 'fs';
3+
import {findNodes, insertAfterLastOccurence} from './ast-utils';
4+
5+
/**
6+
* Adds provideRouter configuration to the main file (import and bootstrap) if
7+
* main file hasn't been already configured, else it has no effect.
8+
*
9+
* @param (mainFile) path to main.ts in ng project
10+
* @param (routesName) exported name for the routes array from routesFile
11+
* @param (routesFile)
12+
*/
13+
export function configureMain(mainFile: string, routesName: string,
14+
routesFile: string): Promise<void> {
15+
return insertImport(mainFile, 'provideRouter', '@angular/router')
16+
.then(() => {
17+
return insertImport(mainFile, routesName, routesFile, true);
18+
}).then(() => {
19+
let rootNode = ts.createSourceFile(mainFile, fs.readFileSync(mainFile).toString(),
20+
ts.ScriptTarget.ES6, true);
21+
// get ExpressionStatements from the top level syntaxList of the sourceFile
22+
let bootstrapNodes = rootNode.getChildAt(0).getChildren().filter(node => {
23+
// get bootstrap expressions
24+
return node.kind === ts.SyntaxKind.ExpressionStatement &&
25+
node.getChildAt(0).getChildAt(0).text.toLowerCase() === 'bootstrap';
26+
});
27+
// printAll(bootstrapNodes[0].getChildAt(0).getChildAt(2).getChildAt(2));
28+
if (bootstrapNodes.length !== 1) {
29+
return Promise.reject(new Error(`Did not bootstrap provideRouter in ${mainFile}` +
30+
' because of multiple or no bootstrap calls'));
31+
}
32+
let bootstrapNode = bootstrapNodes[0].getChildAt(0);
33+
let isBootstraped = findNodes(bootstrapNode, ts.SyntaxKind.Identifier)
34+
.map(_ => _.text)
35+
.indexOf('provideRouter') !== -1;
36+
37+
if (isBootstraped) {
38+
return Promise.resolve();
39+
}
40+
// if bracket exitst already, add configuration template,
41+
// otherwise, insert into bootstrap parens
42+
var fallBackPos: number, configurePathsTemplate: string, separator: string,
43+
var syntaxListNodes: any;
44+
let bootstrapProviders = bootstrapNode.getChildAt(2).getChildAt(2); // array of providers
45+
46+
if ( bootstrapProviders ) {
47+
syntaxListNodes = bootstrapProviders.getChildAt(1).getChildren();
48+
fallBackPos = bootstrapProviders.getChildAt(2).pos; // closeBracketLiteral
49+
separator = syntaxListNodes.length === 0 ? '' : ', ';
50+
configurePathsTemplate = `${separator}provideRouter(${routesName})`;
51+
} else {
52+
fallBackPos = bootstrapNode.getChildAt(3).pos; // closeParenLiteral
53+
syntaxListNodes = bootstrapNode.getChildAt(2).getChildren();
54+
configurePathsTemplate = `, [provideRouter(${routesName})]`;
55+
}
56+
57+
return insertAfterLastOccurence(syntaxListNodes, configurePathsTemplate,
58+
mainFile, fallBackPos).apply();
59+
});
60+
}
61+
62+
/**
63+
* Add Import `import { symbolName } from fileName` if the import doesn't exit
64+
* already. Assumes fileToEdit can be resolved and accessed.
65+
* @param fileToEdit (file we want to add import to)
66+
* @param symbolName (item to import)
67+
* @param fileName (path to the file)
68+
* @param isDefault (if true, import follows style for importing default exports)
69+
*/
70+
71+
export function insertImport(fileToEdit: string, symbolName: string,
72+
fileName: string, isDefault = false): Promise<void> {
73+
let rootNode = ts.createSourceFile(fileToEdit, fs.readFileSync(fileToEdit).toString(),
74+
ts.ScriptTarget.ES6, true);
75+
let allImports = findNodes(rootNode, ts.SyntaxKind.ImportDeclaration);
76+
77+
// get nodes that map to import statements from the file fileName
78+
let relevantImports = allImports.filter(node => {
79+
// StringLiteral of the ImportDeclaration is the import file (fileName in this case).
80+
let importFiles = node.getChildren().filter(child => child.kind === ts.SyntaxKind.StringLiteral)
81+
.map(n => (<ts.StringLiteralTypeNode>n).text);
82+
return importFiles.filter(file => file === fileName).length === 1;
83+
});
84+
85+
if (relevantImports.length > 0) {
86+
87+
var importsAsterisk = false;
88+
// imports from import file
89+
let imports: ts.Node[] = [];
90+
relevantImports.forEach(n => {
91+
Array.prototype.push.apply(imports, findNodes(n, ts.SyntaxKind.Identifier));
92+
if (findNodes(n, ts.SyntaxKind.AsteriskToken).length > 0) {
93+
importsAsterisk = true;
94+
}
95+
});
96+
97+
// if imports * from fileName, don't add symbolName
98+
if (importsAsterisk) {
99+
return Promise.resolve();
100+
}
101+
102+
let importTextNodes = imports.filter(n => (<ts.Identifier>n).text === symbolName);
103+
104+
// insert import if it's not there
105+
if (importTextNodes.length === 0) {
106+
let fallbackPos = findNodes(relevantImports[0], ts.SyntaxKind.CloseBraceToken)[0].pos ||
107+
findNodes(relevantImports[0], ts.SyntaxKind.FromKeyword)[0].pos;
108+
return insertAfterLastOccurence(imports, `, ${symbolName}`, fileToEdit, fallbackPos).apply();
109+
}
110+
return Promise.resolve();
111+
}
112+
113+
// no such import declaration exists
114+
let useStrict = findNodes(rootNode, ts.SyntaxKind.StringLiteral)
115+
.filter(n => n.text === 'use strict');
116+
let fallbackPos = 0;
117+
if (useStrict.length > 0) {
118+
fallbackPos = useStrict[0].end;
119+
}
120+
let open = isDefault ? '' : '{ ';
121+
let close = isDefault ? '' : ' }';
122+
// if there are no imports or 'use strict' statement, insert import at beginning of file
123+
let insertAtBeginning = allImports.length === 0 && useStrict.length === 0;
124+
let separator = insertAtBeginning ? '' : ';\n';
125+
return insertAfterLastOccurence(allImports, `${separator}import ${open}${symbolName}${close}` +
126+
` from '${fileName}'${insertAtBeginning ? ';\n' : ''}`,
127+
fileToEdit, fallbackPos, ts.SyntaxKind.StringLiteral).apply();
128+
};

0 commit comments

Comments
 (0)
0