Skip to content

Commit

Permalink
fix(deps): dynamic import support for path and pathMatch routes defin…
Browse files Browse the repository at this point in the history
…ition

fix #216
  • Loading branch information
vogloblinsky committed Nov 17, 2017
1 parent 6d95fd1 commit 633ea2f
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 12 deletions.
26 changes: 15 additions & 11 deletions src/app/compiler/dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -866,23 +866,27 @@ export class Dependencies {
return result;
}

private visitEnumDeclarationForRoutes(fileName, node) {
private visitEnumDeclarationForRoutes(fileName, node, sourceFile) {
if (node.declarationList.declarations) {
let i = 0;
let len = node.declarationList.declarations.length;
for (i; i < len; i++) {
if (node.declarationList.declarations[i].type) {
if (node.declarationList.declarations[i].type.typeName &&
node.declarationList.declarations[i].type.typeName.text === 'Routes') {
let data = new CodeGenerator().generate(node.declarationList.declarations[i].initializer);
this.routerParser.addRoute({
name: node.declarationList.declarations[i].name.text,
data: this.routerParser.cleanRawRoute(data),
filename: fileName
});
return [{
routes: data
}];
let routesInitializer = node.declarationList.declarations[i].initializer;
if (ts.isArrayLiteralExpression(routesInitializer)) {
routesInitializer = this.routerParser.cleanRoutesDefinitionWithImport(routesInitializer, node, sourceFile);
}
let data = new CodeGenerator().generate(routesInitializer);
this.routerParser.addRoute({
name: node.declarationList.declarations[i].name.text,
data: this.routerParser.cleanRawRoute(data),
filename: fileName
});
return [{
routes: data
}];
}
}
}
Expand All @@ -899,7 +903,7 @@ export class Dependencies {
res = sourceFile.statements.reduce((directive, statement) => {

if (ts.isVariableStatement(statement)) {
return directive.concat(this.visitEnumDeclarationForRoutes(filename, statement));
return directive.concat(this.visitEnumDeclarationForRoutes(filename, statement, sourceFile));
}

return directive;
Expand Down
103 changes: 103 additions & 0 deletions src/utils/imports.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,107 @@ export class ImportsUtil {

return [];
}

/**
* Find in imports something like VAR.AVAR.BVAR.thestring
* @param {string} variableName like VAR.AVAR.BVAR.thestring
* @return {[type]} thestring value
*/
public findPropertyValueInImport(variableName, sourceFile: ts.SourceFile) {
let variablesAttributes = variableName.split('.'),
metadataVariableName = variablesAttributes[0],
searchedImport,
aliasOriginalName = '',
foundWithAlias = false;

const file = (typeof ast.getSourceFile(sourceFile.fileName) !== 'undefined') ? ast.getSourceFile(sourceFile.fileName) : ast.addSourceFileFromText(sourceFile.fileName, sourceFile.getText());
const imports = file.getImports();

/**
* Loop through all imports, and find one matching variableName
*/
imports.forEach((i) => {
let namedImports = i.getNamedImports(),
namedImportsLength = namedImports.length,
j = 0;

if (namedImportsLength > 0) {
for (j; j<namedImportsLength; j++) {
let importName = namedImports[j].getNameIdentifier().getText() as string,
importAlias;

if (namedImports[j].getAliasIdentifier()) {
importAlias = namedImports[j].getAliasIdentifier().getText();
}
if (importName === metadataVariableName) {
searchedImport = i;
break;
}
if (importAlias === metadataVariableName) {
foundWithAlias = true;
aliasOriginalName = importName;
searchedImport = i;
break;
}
}
}
});

let findInVariableDeclaration = (variableDeclaration) => {
let variableKind = variableDeclaration.getKind();
if (variableKind && variableKind === ts.SyntaxKind.VariableDeclaration) {
let initializer = variableDeclaration.getInitializer();
if (initializer) {
let initializerKind = initializer.getKind();
if (initializerKind && initializerKind === ts.SyntaxKind.ObjectLiteralExpression) {
let compilerNode = initializer.compilerNode as ts.ObjectLiteralExpression,
finalValue = '';
// Find thestring from AVAR.BVAR.thestring inside properties
let depth = 0;
let loopProperties = (properties) => {
properties.forEach(prop => {
if (prop.name) {
if (variablesAttributes[depth + 1]) {
if (prop.name.getText() === variablesAttributes[depth + 1]) {
if (prop.initializer) {
if (prop.initializer.properties) {
depth += 1;
loopProperties(prop.initializer.properties);
} else {
finalValue = prop.initializer.text;
}
} else {
finalValue = prop.initializer.text;
}
}
}
}
})
};
loopProperties(compilerNode.properties);
return finalValue;
}
}
}
};

if (typeof searchedImport !== 'undefined') {
let imporPath = path.resolve(path.dirname(sourceFile.fileName) + '/' + searchedImport.getModuleSpecifier() + '.ts');
const sourceFileImport = ast.getOrAddSourceFile(imporPath);
if (sourceFileImport) {
let variableName = (foundWithAlias) ? aliasOriginalName : metadataVariableName;
let variableDeclaration = sourceFileImport.getVariableDeclaration(variableName);
if (variableDeclaration) {
return findInVariableDeclaration(variableDeclaration);
}
}
} else {
// Find in local variables of the file
const variableStatements = file.getVariableStatements();
const variableDeclaration = file.getVariableDeclaration(metadataVariableName);
if (variableDeclaration) {
return findInVariableDeclaration(variableDeclaration);
}
}
}
}
62 changes: 62 additions & 0 deletions src/utils/router-parser.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as JSON5 from 'json5';
import { logger } from '../logger';
import { FileEngine } from '../app/engines/file.engine';
import { RoutingGraphNode } from '../app/nodes/routing-graph-node';
import { ImportsUtil } from './imports.util';

export class RouterParserUtil {
private routes: any[] = [];
Expand All @@ -20,6 +21,7 @@ export class RouterParserUtil {
private modulesWithRoutes = [];

private fileEngine = new FileEngine();
private importsUtil = new ImportsUtil();

public addRoute(route): void {
this.routes.push(route);
Expand Down Expand Up @@ -392,4 +394,64 @@ export class RouterParserUtil {
console.log('printModulesRoutes: ');
console.log(this.modulesWithRoutes);
}

/**
* Clean routes definition with imported data, for example path, children, or dynamic stuff inside data
*
* const MY_ROUTES: Routes = [
* {
* path: 'home',
* component: HomeComponent
* },
* {
* path: PATHS.home,
* component: HomeComponent
* }
* ];
*
* The initializer is an array (ArrayLiteralExpression - 177 ), it has elements, objects (ObjectLiteralExpression - 178)
* with properties (PropertyAssignment - 261)
*
* For each know property (https://angular.io/api/router/Routes#description), we try to see if we have what we want
*
* Ex: path and pathMatch want a string, component a component reference.
*
* It is an imperative approach, not a generic way, parsing all the tree
* and find something like this which willl break JSON.stringify : MYIMPORT.path
*
* @param {ts.Node} initializer The node of routes definition
* @return {ts.Node} The edited node
*/
public cleanRoutesDefinitionWithImport(initializer: ts.ArrayLiteralExpression, node: ts.Node, sourceFile: ts.SourceFile): ts.Node {
initializer.elements.forEach((element: ts.ObjectLiteralExpression) => {
element.properties.forEach((property: ts.PropertyAssignment) => {
let propertyName = property.name.getText(),
propertyInitializer = property.initializer;
switch (propertyName) {
case 'path':
case 'pathMatch':
if (propertyInitializer) {
if (propertyInitializer.kind !== ts.SyntaxKind.StringLiteral) {
// Identifier(71) won't break parsing, but it will be better to retrive them
// PropertyAccessExpression(179) ex: MYIMPORT.path will break it, find it in import
if (propertyInitializer.kind === ts.SyntaxKind.PropertyAccessExpression) {
let lastObjectLiteralAttributeName = propertyInitializer.name.getText(),
firstObjectLiteralAttributeName;
if (propertyInitializer.expression) {
firstObjectLiteralAttributeName = propertyInitializer.expression.getText();
let result = this.importsUtil.findPropertyValueInImport(firstObjectLiteralAttributeName + '.' + lastObjectLiteralAttributeName, sourceFile)
if (result !== '') {
propertyInitializer.kind = 9;
propertyInitializer.text = result;
}
}
}
}
}
break;
}
});
});
return initializer;
}
}
7 changes: 7 additions & 0 deletions test/src/cli/cli-generation-big-app.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -433,4 +433,11 @@ describe('CLI simple generation - big app', () => {
expect(file).to.contain('EntryComponents');
expect(file).to.contain('href="../components/AboutComponent.html"');
});

it('should support dynamic path for routes', () => {
let file = exists('documentation/modules/HomeRoutingModule.html');
expect(file).to.be.true;
let routesFile = read('documentation/js/routes/routes_index.js');
expect(routesFile).to.contain('homeimported');
});
});
4 changes: 3 additions & 1 deletion test/src/todomvc-ng2/src/app/home/home-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import { Routes, RouterModule } from '@angular/router';

import { HomeComponent } from './home.component';

import { PATHS } from './paths';

const HOME_ROUTES: Routes = [
{ path: 'home', component: HomeComponent }
{ path: PATHS.home.url, component: HomeComponent }
];

@NgModule({
Expand Down
5 changes: 5 additions & 0 deletions test/src/todomvc-ng2/src/app/home/paths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const PATHS = {
home: {
url: 'homeimported'
}
};

0 comments on commit 633ea2f

Please sign in to comment.