Skip to content
This repository was archived by the owner on May 1, 2020. It is now read-only.

Commit

Permalink
feat(deep-linking): generate default NgModule when missing by default
Browse files Browse the repository at this point in the history
generate default NgModule when missing by default
  • Loading branch information
danbucholtz committed Mar 16, 2017
1 parent 78b60d1 commit 90138fa
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 13 deletions.
62 changes: 58 additions & 4 deletions src/deep-linking/util.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import * as fs from 'fs';
import { join } from 'path';

import * as util from './util';

import * as Constants from '../util/constants';
Expand Down Expand Up @@ -558,21 +560,41 @@ export function removeDecorators(fileName: string, source: string): string {
});

describe('getNgModuleDataFromPage', () => {
it('should throw when NgModule is not in cache', () => {
it('should throw when NgModule is not in cache and create default ngModule flag is off', () => {
const prefix = join('Users', 'noone', 'myApp', 'src');
const appNgModulePath = join(prefix, 'app', 'app.module.ts');
const pagePath = join(prefix, 'pages', 'page-one', 'page-one.ts');
const knownClassName = 'PageOne';
const fileCache = new FileCache();
spyOn(helpers, helpers.getStringPropertyValue.name).and.returnValue('.module.ts');
spyOn(helpers, helpers.getBooleanPropertyValue.name).and.returnValue(false);

const knownErrorMsg = 'Should never happen';
try {
util.getNgModuleDataFromPage(appNgModulePath, pagePath, fileCache, false);
util.getNgModuleDataFromPage(appNgModulePath, pagePath, knownClassName, fileCache, false);
throw new Error(knownErrorMsg);
} catch (ex) {
expect(ex.message).not.toEqual(knownErrorMsg);
expect(helpers.getBooleanPropertyValue).toHaveBeenCalledWith(Constants.ENV_CREATE_DEFAULT_NG_MODULE_WHEN_MISSING);
}
});

it('should create a default ngModule and write it to disk when create default ngModule flag is on', () => {
const prefix = join('Users', 'noone', 'myApp', 'src');
const appNgModulePath = join(prefix, 'app', 'app.module.ts');
const pagePath = join(prefix, 'pages', 'page-one', 'page-one.ts');
const knownClassName = 'PageOne';
const fileCache = new FileCache();
spyOn(helpers, helpers.getStringPropertyValue.name).and.returnValue('.module.ts');
spyOn(helpers, helpers.getBooleanPropertyValue.name).and.returnValue(true);
spyOn(fs, 'writeFileSync');
const result = util.getNgModuleDataFromPage(appNgModulePath, pagePath, knownClassName, fileCache, false);
expect(result.absolutePath).toEqual('Users/noone/myApp/src/pages/page-one/page-one.module.ts');
expect(result.userlandModulePath).toEqual('../pages/page-one/page-one.module');
expect(result.className).toEqual('PageOneModule');
expect(fs.writeFileSync).toHaveBeenCalled();
});

it('should return non-aot adjusted paths when not in AoT', () => {
const pageNgModuleContent = `
import { NgModule } from '@angular/core';
Expand All @@ -594,15 +616,18 @@ export class HomePageModule {}
const appNgModulePath = join(prefix, 'app', 'app.module.ts');
const pageNgModulePath = join(prefix, 'pages', 'page-one', 'page-one.module.ts');
const pagePath = join(prefix, 'pages', 'page-one', 'page-one.ts');
const knownClassName = 'PageOne';
const fileCache = new FileCache();
fileCache.set(pageNgModulePath, { path: pageNgModulePath, content: pageNgModuleContent});
spyOn(helpers, helpers.getStringPropertyValue.name).and.returnValue('.module.ts');
spyOn(helpers, helpers.getBooleanPropertyValue.name);

const result = util.getNgModuleDataFromPage(appNgModulePath, pagePath, fileCache, false);
const result = util.getNgModuleDataFromPage(appNgModulePath, pagePath, knownClassName, fileCache, false);

expect(result.absolutePath).toEqual(pageNgModulePath);
expect(result.userlandModulePath).toEqual('../pages/page-one/page-one.module');
expect(result.className).toEqual('HomePageModule');
expect(helpers.getBooleanPropertyValue).not.toHaveBeenCalled();
});

it('should return adjusted paths to account for AoT', () => {
Expand All @@ -626,14 +651,17 @@ export class HomePageModule {}
const appNgModulePath = join(prefix, 'app', 'app.module.ts');
const pageNgModulePath = join(prefix, 'pages', 'page-one', 'page-one.module.ts');
const pagePath = join(prefix, 'pages', 'page-one', 'page-one.ts');
const knownClassName = 'PageOne';
const fileCache = new FileCache();
fileCache.set(pageNgModulePath, { path: pageNgModulePath, content: pageNgModuleContent});
spyOn(helpers, helpers.getStringPropertyValue.name).and.returnValue('.module.ts');
spyOn(helpers, helpers.getBooleanPropertyValue.name);

const result = util.getNgModuleDataFromPage(appNgModulePath, pagePath, fileCache, true);
const result = util.getNgModuleDataFromPage(appNgModulePath, pagePath, knownClassName, fileCache, true);
expect(result.absolutePath).toEqual(helpers.changeExtension(pageNgModulePath, '.ngfactory.ts'));
expect(result.userlandModulePath).toEqual('../pages/page-one/page-one.module.ngfactory');
expect(result.className).toEqual('HomePageModuleNgFactory');
expect(helpers.getBooleanPropertyValue).not.toHaveBeenCalled();
});
});

Expand Down Expand Up @@ -2320,4 +2348,30 @@ export const AppModuleNgFactory:import0.NgModuleFactory<import1.AppModule> = new
expect(result.indexOf(expectedDeepLinkString)).toBeGreaterThanOrEqual(0);
});
});

describe('generateDefaultDeepLinkNgModuleContent', () => {
it('should generate a default NgModule for a DeepLinked component', () => {
const knownFileContent = `
import { NgModule } from '@angular/core';
import { DeepLinkModule } from 'ionic-angular';
import { PageOne } from './page-one';
@NgModule({
declarations: [
PageOne,
],
imports: [
DeepLinkModule.forChild(PageOne)
]
})
export class PageOneModule {}
`;
const knownFilePath = '/someFakePath/myApp/src/pages/page-one/page-one.ts';
const knownClassName = 'PageOne';
const fileContent = util.generateDefaultDeepLinkNgModuleContent(knownFilePath, knownClassName);
expect(fileContent).toEqual(knownFileContent);
});
});
});
51 changes: 44 additions & 7 deletions src/deep-linking/util.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { dirname, extname, relative } from 'path';
import { writeFileSync } from 'fs';

import { basename, dirname, extname, relative } from 'path';

import {
ArrayLiteralExpression,
Expand All @@ -17,7 +19,7 @@ import {
import { Logger } from '../logger/logger';
import * as Constants from '../util/constants';
import { FileCache } from '../util/file-cache';
import { changeExtension, getStringPropertyValue, replaceAll } from '../util/helpers';
import { changeExtension, getBooleanPropertyValue, getStringPropertyValue, replaceAll } from '../util/helpers';
import { BuildContext, ChangedFile, DeepLinkConfigEntry, DeepLinkDecoratorAndClass, DeepLinkPathInfo, File } from '../util/interfaces';
import {
appendAfter,
Expand All @@ -42,7 +44,7 @@ export function getDeepLinkData(appNgModuleFilePath: string, fileCache: FileCach

if (deepLinkDecoratorData) {
// sweet, the page has a DeepLinkDecorator, which means it meets the criteria to process that bad boy
const pathInfo = getNgModuleDataFromPage(appNgModuleFilePath, file.path, fileCache, isAot);
const pathInfo = getNgModuleDataFromPage(appNgModuleFilePath, file.path, deepLinkDecoratorData.className, fileCache, isAot);
const deepLinkConfigEntry = Object.assign({}, deepLinkDecoratorData, pathInfo);
deepLinkConfigEntries.push(deepLinkConfigEntry);
}
Expand All @@ -65,11 +67,23 @@ export function getRelativePathToPageNgModuleFromAppNgModule(pathToAppNgModule:
return relative(dirname(pathToAppNgModule), pathToPageNgModule);
}

export function getNgModuleDataFromPage(appNgModuleFilePath: string, filePath: string, fileCache: FileCache, isAot: boolean): DeepLinkPathInfo {
export function getNgModuleDataFromPage(appNgModuleFilePath: string, filePath: string, className: string, fileCache: FileCache, isAot: boolean): DeepLinkPathInfo {
const ngModulePath = getNgModulePathFromCorrespondingPage(filePath);
const ngModuleFile = fileCache.get(ngModulePath);
let ngModuleFile = fileCache.get(ngModulePath);
if (!ngModuleFile) {
throw new Error(`${filePath} has a @DeepLink decorator, but it does not have a corresponding "NgModule" at ${ngModulePath}`);
// the NgModule file does not exists, check if we are going to make it easy for the userlandModulePath
// and automagically generate an NgModule for them
// /gif magic
if (getBooleanPropertyValue(Constants.ENV_CREATE_DEFAULT_NG_MODULE_WHEN_MISSING)) {
const defaultNgModuleContent = generateDefaultDeepLinkNgModuleContent(filePath, className);
// cache it and write it to disk to avoid this connodrum in the future
ngModuleFile = { path: ngModulePath, content: defaultNgModuleContent};
fileCache.set(ngModulePath, ngModuleFile);
writeFileSync(ngModulePath, defaultNgModuleContent);
} else {
// the flag is not set, so throw an error
throw new Error(`${filePath} has a @DeepLink decorator, but it does not have a corresponding "NgModule" at ${ngModulePath}`);
}
}
// get the class declaration out of NgModule class content
const exportedClassName = getNgModuleClassName(ngModuleFile.path, ngModuleFile.content);
Expand Down Expand Up @@ -116,7 +130,8 @@ export function getDeepLinkDecoratorContentForSourceFile(sourceFile: SourceFile)
segment: deepLinkSegment,
priority: deepLinkPriority,
defaultHistory: deepLinkDefaultHistory,
rawString: rawStringContent
rawString: rawStringContent,
className: className
});
}
});
Expand Down Expand Up @@ -352,6 +367,28 @@ export function addDeepLinkArgumentToAppNgModule(appNgModuleFileContent: string,
return updatedFileContent;
}

export function generateDefaultDeepLinkNgModuleContent(filePath: string, className: string) {
const importFrom = basename(filePath, '.ts');

return `
import { NgModule } from '@angular/core';
import { DeepLinkModule } from 'ionic-angular';
import { ${className} } from './${importFrom}';
@NgModule({
declarations: [
${className},
],
imports: [
DeepLinkModule.forChild(${className})
]
})
export class ${className}Module {}
`;
}



const DEEPLINK_DECORATOR_TEXT = 'DeepLink';
Expand Down
6 changes: 4 additions & 2 deletions src/util/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,6 @@ export function generateContext(context?: BuildContext): BuildContext {
setProcessEnvVar(Constants.ENV_OPTIMIZATION_LOADER, optimizationLoaderPath);
Logger.debug(`optimizationLoaderPath set to ${optimizationLoaderPath}`);


const aotWriteToDisk = getConfigValue(context, '--aotWriteToDisk', null, Constants.ENV_AOT_WRITE_TO_DISK, Constants.ENV_AOT_WRITE_TO_DISK.toLowerCase(), null);
setProcessEnvVar(Constants.ENV_AOT_WRITE_TO_DISK, aotWriteToDisk);
Logger.debug(`aotWriteToDisk set to ${aotWriteToDisk}`);
Expand All @@ -212,7 +211,6 @@ export function generateContext(context?: BuildContext): BuildContext {
setProcessEnvVar(Constants.ENV_PRINT_WEBPACK_DEPENDENCY_TREE, printWebpackDependencyTree);
Logger.debug(`printWebpackDependencyTree set to ${printWebpackDependencyTree}`);


const bailOnLintError = getConfigValue(context, '--bailOnLintError', null, Constants.ENV_BAIL_ON_LINT_ERROR, Constants.ENV_BAIL_ON_LINT_ERROR.toLowerCase(), null);
setProcessEnvVar(Constants.ENV_BAIL_ON_LINT_ERROR, bailOnLintError);
Logger.debug(`bailOnLintError set to ${bailOnLintError}`);
Expand All @@ -233,6 +231,10 @@ export function generateContext(context?: BuildContext): BuildContext {
setProcessEnvVar(Constants.ENV_NG_MODULE_FILE_NAME_SUFFIX, ngModuleFileNameSuffix);
Logger.debug(`ngModuleFileNameSuffix set to ${ngModuleFileNameSuffix}`);

const createDefaultNgModuleWhenMissing = getConfigValue(context, '--createDefaultNgModuleWhenMissing', null, Constants.ENV_CREATE_DEFAULT_NG_MODULE_WHEN_MISSING, Constants.ENV_CREATE_DEFAULT_NG_MODULE_WHEN_MISSING.toLowerCase(), 'true');
setProcessEnvVar(Constants.ENV_CREATE_DEFAULT_NG_MODULE_WHEN_MISSING, createDefaultNgModuleWhenMissing);
Logger.debug(`createDefaultNgModuleWhenMissing set to ${createDefaultNgModuleWhenMissing}`);

/* Provider Path Stuff */
setProcessEnvVar(Constants.ENV_ACTION_SHEET_CONTROLLER_CLASSNAME, 'ActionSheetController');
setProcessEnvVar(Constants.ENV_ACTION_SHEET_CONTROLLER_PATH, join(context.ionicAngularDir, 'components', 'action-sheet', 'action-sheet-controller.js'));
Expand Down
1 change: 1 addition & 0 deletions src/util/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export const ENV_ENABLE_LINT = 'IONIC_ENABLE_LINT';
export const ENV_DISABLE_LOGGING = 'IONIC_DISABLE_LOGGING';
export const ENV_START_WATCH_TIMEOUT = 'IONIC_START_WATCH_TIMEOUT';
export const ENV_NG_MODULE_FILE_NAME_SUFFIX = 'IONIC_NG_MODULE_FILENAME_SUFFIX';
export const ENV_CREATE_DEFAULT_NG_MODULE_WHEN_MISSING = 'IONIC_CREATE_DEFAULT_NG_MODULE_WHEN_MISSING';

export const ENV_PRINT_ORIGINAL_DEPENDENCY_TREE = 'IONIC_PRINT_ORIGINAL_DEPENDENCY_TREE';
export const ENV_PRINT_MODIFIED_DEPENDENCY_TREE = 'IONIC_PRINT_MODIFIED_DEPENDENCY_TREE';
Expand Down
1 change: 1 addition & 0 deletions src/util/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ export interface DeepLinkDecoratorAndClass {
defaultHistory: string[];
priority: string;
rawString: string;
className: string;
};

export interface DeepLinkPathInfo {
Expand Down

0 comments on commit 90138fa

Please sign in to comment.