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

Commit 90138fa

Browse files
committed
feat(deep-linking): generate default NgModule when missing by default
generate default NgModule when missing by default
1 parent 78b60d1 commit 90138fa

File tree

5 files changed

+108
-13
lines changed

5 files changed

+108
-13
lines changed

src/deep-linking/util.spec.ts

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import * as fs from 'fs';
12
import { join } from 'path';
3+
24
import * as util from './util';
35

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

560562
describe('getNgModuleDataFromPage', () => {
561-
it('should throw when NgModule is not in cache', () => {
563+
it('should throw when NgModule is not in cache and create default ngModule flag is off', () => {
562564
const prefix = join('Users', 'noone', 'myApp', 'src');
563565
const appNgModulePath = join(prefix, 'app', 'app.module.ts');
564566
const pagePath = join(prefix, 'pages', 'page-one', 'page-one.ts');
567+
const knownClassName = 'PageOne';
565568
const fileCache = new FileCache();
566569
spyOn(helpers, helpers.getStringPropertyValue.name).and.returnValue('.module.ts');
570+
spyOn(helpers, helpers.getBooleanPropertyValue.name).and.returnValue(false);
571+
567572
const knownErrorMsg = 'Should never happen';
568573
try {
569-
util.getNgModuleDataFromPage(appNgModulePath, pagePath, fileCache, false);
574+
util.getNgModuleDataFromPage(appNgModulePath, pagePath, knownClassName, fileCache, false);
570575
throw new Error(knownErrorMsg);
571576
} catch (ex) {
572577
expect(ex.message).not.toEqual(knownErrorMsg);
578+
expect(helpers.getBooleanPropertyValue).toHaveBeenCalledWith(Constants.ENV_CREATE_DEFAULT_NG_MODULE_WHEN_MISSING);
573579
}
574580
});
575581

582+
it('should create a default ngModule and write it to disk when create default ngModule flag is on', () => {
583+
const prefix = join('Users', 'noone', 'myApp', 'src');
584+
const appNgModulePath = join(prefix, 'app', 'app.module.ts');
585+
const pagePath = join(prefix, 'pages', 'page-one', 'page-one.ts');
586+
const knownClassName = 'PageOne';
587+
const fileCache = new FileCache();
588+
spyOn(helpers, helpers.getStringPropertyValue.name).and.returnValue('.module.ts');
589+
spyOn(helpers, helpers.getBooleanPropertyValue.name).and.returnValue(true);
590+
spyOn(fs, 'writeFileSync');
591+
const result = util.getNgModuleDataFromPage(appNgModulePath, pagePath, knownClassName, fileCache, false);
592+
expect(result.absolutePath).toEqual('Users/noone/myApp/src/pages/page-one/page-one.module.ts');
593+
expect(result.userlandModulePath).toEqual('../pages/page-one/page-one.module');
594+
expect(result.className).toEqual('PageOneModule');
595+
expect(fs.writeFileSync).toHaveBeenCalled();
596+
});
597+
576598
it('should return non-aot adjusted paths when not in AoT', () => {
577599
const pageNgModuleContent = `
578600
import { NgModule } from '@angular/core';
@@ -594,15 +616,18 @@ export class HomePageModule {}
594616
const appNgModulePath = join(prefix, 'app', 'app.module.ts');
595617
const pageNgModulePath = join(prefix, 'pages', 'page-one', 'page-one.module.ts');
596618
const pagePath = join(prefix, 'pages', 'page-one', 'page-one.ts');
619+
const knownClassName = 'PageOne';
597620
const fileCache = new FileCache();
598621
fileCache.set(pageNgModulePath, { path: pageNgModulePath, content: pageNgModuleContent});
599622
spyOn(helpers, helpers.getStringPropertyValue.name).and.returnValue('.module.ts');
623+
spyOn(helpers, helpers.getBooleanPropertyValue.name);
600624

601-
const result = util.getNgModuleDataFromPage(appNgModulePath, pagePath, fileCache, false);
625+
const result = util.getNgModuleDataFromPage(appNgModulePath, pagePath, knownClassName, fileCache, false);
602626

603627
expect(result.absolutePath).toEqual(pageNgModulePath);
604628
expect(result.userlandModulePath).toEqual('../pages/page-one/page-one.module');
605629
expect(result.className).toEqual('HomePageModule');
630+
expect(helpers.getBooleanPropertyValue).not.toHaveBeenCalled();
606631
});
607632

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

633-
const result = util.getNgModuleDataFromPage(appNgModulePath, pagePath, fileCache, true);
660+
const result = util.getNgModuleDataFromPage(appNgModulePath, pagePath, knownClassName, fileCache, true);
634661
expect(result.absolutePath).toEqual(helpers.changeExtension(pageNgModulePath, '.ngfactory.ts'));
635662
expect(result.userlandModulePath).toEqual('../pages/page-one/page-one.module.ngfactory');
636663
expect(result.className).toEqual('HomePageModuleNgFactory');
664+
expect(helpers.getBooleanPropertyValue).not.toHaveBeenCalled();
637665
});
638666
});
639667

@@ -2320,4 +2348,30 @@ export const AppModuleNgFactory:import0.NgModuleFactory<import1.AppModule> = new
23202348
expect(result.indexOf(expectedDeepLinkString)).toBeGreaterThanOrEqual(0);
23212349
});
23222350
});
2351+
2352+
describe('generateDefaultDeepLinkNgModuleContent', () => {
2353+
it('should generate a default NgModule for a DeepLinked component', () => {
2354+
const knownFileContent = `
2355+
import { NgModule } from '@angular/core';
2356+
import { DeepLinkModule } from 'ionic-angular';
2357+
import { PageOne } from './page-one';
2358+
2359+
2360+
@NgModule({
2361+
declarations: [
2362+
PageOne,
2363+
],
2364+
imports: [
2365+
DeepLinkModule.forChild(PageOne)
2366+
]
2367+
})
2368+
export class PageOneModule {}
2369+
2370+
`;
2371+
const knownFilePath = '/someFakePath/myApp/src/pages/page-one/page-one.ts';
2372+
const knownClassName = 'PageOne';
2373+
const fileContent = util.generateDefaultDeepLinkNgModuleContent(knownFilePath, knownClassName);
2374+
expect(fileContent).toEqual(knownFileContent);
2375+
});
2376+
});
23232377
});

src/deep-linking/util.ts

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { dirname, extname, relative } from 'path';
1+
import { writeFileSync } from 'fs';
2+
3+
import { basename, dirname, extname, relative } from 'path';
24

35
import {
46
ArrayLiteralExpression,
@@ -17,7 +19,7 @@ import {
1719
import { Logger } from '../logger/logger';
1820
import * as Constants from '../util/constants';
1921
import { FileCache } from '../util/file-cache';
20-
import { changeExtension, getStringPropertyValue, replaceAll } from '../util/helpers';
22+
import { changeExtension, getBooleanPropertyValue, getStringPropertyValue, replaceAll } from '../util/helpers';
2123
import { BuildContext, ChangedFile, DeepLinkConfigEntry, DeepLinkDecoratorAndClass, DeepLinkPathInfo, File } from '../util/interfaces';
2224
import {
2325
appendAfter,
@@ -42,7 +44,7 @@ export function getDeepLinkData(appNgModuleFilePath: string, fileCache: FileCach
4244

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

68-
export function getNgModuleDataFromPage(appNgModuleFilePath: string, filePath: string, fileCache: FileCache, isAot: boolean): DeepLinkPathInfo {
70+
export function getNgModuleDataFromPage(appNgModuleFilePath: string, filePath: string, className: string, fileCache: FileCache, isAot: boolean): DeepLinkPathInfo {
6971
const ngModulePath = getNgModulePathFromCorrespondingPage(filePath);
70-
const ngModuleFile = fileCache.get(ngModulePath);
72+
let ngModuleFile = fileCache.get(ngModulePath);
7173
if (!ngModuleFile) {
72-
throw new Error(`${filePath} has a @DeepLink decorator, but it does not have a corresponding "NgModule" at ${ngModulePath}`);
74+
// the NgModule file does not exists, check if we are going to make it easy for the userlandModulePath
75+
// and automagically generate an NgModule for them
76+
// /gif magic
77+
if (getBooleanPropertyValue(Constants.ENV_CREATE_DEFAULT_NG_MODULE_WHEN_MISSING)) {
78+
const defaultNgModuleContent = generateDefaultDeepLinkNgModuleContent(filePath, className);
79+
// cache it and write it to disk to avoid this connodrum in the future
80+
ngModuleFile = { path: ngModulePath, content: defaultNgModuleContent};
81+
fileCache.set(ngModulePath, ngModuleFile);
82+
writeFileSync(ngModulePath, defaultNgModuleContent);
83+
} else {
84+
// the flag is not set, so throw an error
85+
throw new Error(`${filePath} has a @DeepLink decorator, but it does not have a corresponding "NgModule" at ${ngModulePath}`);
86+
}
7387
}
7488
// get the class declaration out of NgModule class content
7589
const exportedClassName = getNgModuleClassName(ngModuleFile.path, ngModuleFile.content);
@@ -116,7 +130,8 @@ export function getDeepLinkDecoratorContentForSourceFile(sourceFile: SourceFile)
116130
segment: deepLinkSegment,
117131
priority: deepLinkPriority,
118132
defaultHistory: deepLinkDefaultHistory,
119-
rawString: rawStringContent
133+
rawString: rawStringContent,
134+
className: className
120135
});
121136
}
122137
});
@@ -352,6 +367,28 @@ export function addDeepLinkArgumentToAppNgModule(appNgModuleFileContent: string,
352367
return updatedFileContent;
353368
}
354369

370+
export function generateDefaultDeepLinkNgModuleContent(filePath: string, className: string) {
371+
const importFrom = basename(filePath, '.ts');
372+
373+
return `
374+
import { NgModule } from '@angular/core';
375+
import { DeepLinkModule } from 'ionic-angular';
376+
import { ${className} } from './${importFrom}';
377+
378+
379+
@NgModule({
380+
declarations: [
381+
${className},
382+
],
383+
imports: [
384+
DeepLinkModule.forChild(${className})
385+
]
386+
})
387+
export class ${className}Module {}
388+
389+
`;
390+
}
391+
355392

356393

357394
const DEEPLINK_DECORATOR_TEXT = 'DeepLink';

src/util/config.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,6 @@ export function generateContext(context?: BuildContext): BuildContext {
195195
setProcessEnvVar(Constants.ENV_OPTIMIZATION_LOADER, optimizationLoaderPath);
196196
Logger.debug(`optimizationLoaderPath set to ${optimizationLoaderPath}`);
197197

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

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

234+
const createDefaultNgModuleWhenMissing = getConfigValue(context, '--createDefaultNgModuleWhenMissing', null, Constants.ENV_CREATE_DEFAULT_NG_MODULE_WHEN_MISSING, Constants.ENV_CREATE_DEFAULT_NG_MODULE_WHEN_MISSING.toLowerCase(), 'true');
235+
setProcessEnvVar(Constants.ENV_CREATE_DEFAULT_NG_MODULE_WHEN_MISSING, createDefaultNgModuleWhenMissing);
236+
Logger.debug(`createDefaultNgModuleWhenMissing set to ${createDefaultNgModuleWhenMissing}`);
237+
236238
/* Provider Path Stuff */
237239
setProcessEnvVar(Constants.ENV_ACTION_SHEET_CONTROLLER_CLASSNAME, 'ActionSheetController');
238240
setProcessEnvVar(Constants.ENV_ACTION_SHEET_CONTROLLER_PATH, join(context.ionicAngularDir, 'components', 'action-sheet', 'action-sheet-controller.js'));

src/util/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export const ENV_ENABLE_LINT = 'IONIC_ENABLE_LINT';
6565
export const ENV_DISABLE_LOGGING = 'IONIC_DISABLE_LOGGING';
6666
export const ENV_START_WATCH_TIMEOUT = 'IONIC_START_WATCH_TIMEOUT';
6767
export const ENV_NG_MODULE_FILE_NAME_SUFFIX = 'IONIC_NG_MODULE_FILENAME_SUFFIX';
68+
export const ENV_CREATE_DEFAULT_NG_MODULE_WHEN_MISSING = 'IONIC_CREATE_DEFAULT_NG_MODULE_WHEN_MISSING';
6869

6970
export const ENV_PRINT_ORIGINAL_DEPENDENCY_TREE = 'IONIC_PRINT_ORIGINAL_DEPENDENCY_TREE';
7071
export const ENV_PRINT_MODIFIED_DEPENDENCY_TREE = 'IONIC_PRINT_MODIFIED_DEPENDENCY_TREE';

src/util/interfaces.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ export interface DeepLinkDecoratorAndClass {
156156
defaultHistory: string[];
157157
priority: string;
158158
rawString: string;
159+
className: string;
159160
};
160161

161162
export interface DeepLinkPathInfo {

0 commit comments

Comments
 (0)