From 97a6f2f82e4cb4e36332e6777a1915a39c4acded Mon Sep 17 00:00:00 2001 From: Dan Bucholtz Date: Thu, 9 Mar 2017 23:36:47 -0600 Subject: [PATCH] refactor(deep-linking): add support for ionic serve/watch builds add support for ionic serve/watch builds --- src/deep-linking.ts | 42 +++++++++++++------- src/deep-linking/util.ts | 52 ++++++++++++++++--------- src/webpack/ionic-environment-plugin.ts | 2 + 3 files changed, 63 insertions(+), 33 deletions(-) diff --git a/src/deep-linking.ts b/src/deep-linking.ts index f21e0abb..93ef55d7 100644 --- a/src/deep-linking.ts +++ b/src/deep-linking.ts @@ -7,9 +7,18 @@ import { BuildContext, ChangedFile, DeepLinkConfigEntry } from './util/interface import { convertDeepLinkConfigEntriesToString, getDeepLinkData, hasExistingDeepLinkConfig, updateAppNgModuleAndFactoryWithDeepLinkConfig } from './deep-linking/util'; +/* + * We want to cache a local, in-memory copy of the App's main NgModule file content. + * Each time we do a build, a new DeepLinkConfig is generated and inserted into the + * app's main NgModule. By keeping a copy of the original and using it to determine + * if the developer had an existing config, we will get an accurate answer where + * as the cached version of the App's main NgModule content will basically always + * have a generated deep likn config in it. +*/ +let cachedUnmodifiedAppNgModuleFileContent: string = null; export function deepLinking(context: BuildContext) { - const logger = new Logger(`deep links`); + const logger = new Logger(`deeplinks`); return deepLinkingWorker(context).then((deepLinkConfigEntries: DeepLinkConfigEntry[]) => { setParsedDeepLinkConfig(deepLinkConfigEntries); logger.finish(); @@ -23,31 +32,36 @@ export function deepLinking(context: BuildContext) { function deepLinkingWorker(context: BuildContext) { + return deepLinkingWorkerImpl(context, null); +} + +function deepLinkingWorkerImpl(context: BuildContext, changedFiles: ChangedFile[]) { return Promise.resolve().then(() => { const appNgModulePath = getStringPropertyValue(Constants.ENV_APP_NG_MODULE_PATH); const appNgModuleFile = context.fileCache.get(appNgModulePath); + if (!cachedUnmodifiedAppNgModuleFileContent) { + cachedUnmodifiedAppNgModuleFileContent = appNgModuleFile.content; + } const deepLinkConfigEntries = getDeepLinkData(appNgModulePath, context.fileCache, context.runAot); - const hasExisting = hasExistingDeepLinkConfig(appNgModulePath, appNgModuleFile.content); + const hasExisting = hasExistingDeepLinkConfig(appNgModulePath, cachedUnmodifiedAppNgModuleFileContent); if (!hasExisting) { // only update the app's main ngModule if there isn't an existing config const deepLinkString = convertDeepLinkConfigEntriesToString(deepLinkConfigEntries); - updateAppNgModuleAndFactoryWithDeepLinkConfig(context, deepLinkString); + updateAppNgModuleAndFactoryWithDeepLinkConfig(context, deepLinkString, changedFiles, context.runAot); } return deepLinkConfigEntries; }); } - - export function deepLinkingUpdate(changedFiles: ChangedFile[], context: BuildContext) { - /*const appNgModuleChangedFiles = changedFiles.filter(changedFile => changedFile.filePath === process.env[Constants.ENV_APP_NG_MODULE_PATH]); - if (appNgModuleChangedFiles.length) { - const fileContent = context.fileCache.get(appNgModuleChangedFiles[0].filePath).content; - const hydratedDeepLinkEntries = extractDeepLinkData(appNgModuleChangedFiles[0].filePath, fileContent, context.runAot); - setParsedDeepLinkConfig(hydratedDeepLinkEntries); - } - return Promise.resolve(); - */ - return Promise.resolve(); + // TODO, consider optimizing later + const logger = new Logger('deeplinks update'); + return deepLinkingWorkerImpl(context, changedFiles).then((deepLinkConfigEntries: DeepLinkConfigEntry[]) => { + setParsedDeepLinkConfig(deepLinkConfigEntries); + logger.finish(); + }).catch((err: Error) => { + const error = new BuildError(err.message); + throw logger.fail(error); + }); } diff --git a/src/deep-linking/util.ts b/src/deep-linking/util.ts index e6917075..083556ff 100644 --- a/src/deep-linking/util.ts +++ b/src/deep-linking/util.ts @@ -18,7 +18,7 @@ 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 { BuildContext, DeepLinkConfigEntry, DeepLinkDecoratorAndClass, DeepLinkPathInfo } from '../util/interfaces'; +import { BuildContext, ChangedFile, DeepLinkConfigEntry, DeepLinkDecoratorAndClass, DeepLinkPathInfo } from '../util/interfaces'; import { appendAfter, getClassDeclarations, getTypescriptSourceFile, getNodeStringContent, replaceNode } from '../util/typescript-utils'; import { transpileTsString } from '../transpile'; @@ -299,41 +299,55 @@ export function convertDeepLinkEntryToJsObjectString(entry: DeepLinkConfigEntry) return `{ loadChildren: '${entry.userlandModulePath}${LOAD_CHILDREN_SEPARATOR}${entry.className}', name: '${entry.name}', segment: ${segmentString}, priority: '${entry.priority}', defaultHistory: [${defaultHistoryWithQuotes.join(', ')}] }`; } -export function updateAppNgModuleAndFactoryWithDeepLinkConfig(context: BuildContext, deepLinkString: string) { +export function updateAppNgModuleAndFactoryWithDeepLinkConfig(context: BuildContext, deepLinkString: string, changedFiles: ChangedFile[], isAot: boolean) { const appNgModulePath = getStringPropertyValue(Constants.ENV_APP_NG_MODULE_PATH); - const appNgModuleFactoryPath = changeExtension(appNgModulePath, '.ngfactory.ts'); const appNgModuleFile = context.fileCache.get(appNgModulePath); - const appNgModuleFactoryFile = context.fileCache.get(appNgModuleFactoryPath); + if (!appNgModuleFile) { throw new Error(`App NgModule ${appNgModulePath} not found in cache`); } - if (!appNgModuleFactoryFile) { - throw new Error(`App NgModule Factory ${appNgModuleFactoryPath} not found in cache`); - } const updatedAppNgModuleContent = getUpdatedAppNgModuleContentWithDeepLinkConfig(appNgModulePath, appNgModuleFile.content, deepLinkString); - const updatedAppNgModuleFactoryContent = getUpdatedAppNgModuleFactoryContentWithDeepLinksConfig(appNgModuleFactoryFile.content, deepLinkString); - // update the typescript files in cache context.fileCache.set(appNgModulePath, { path: appNgModulePath, content: updatedAppNgModuleContent}); - context.fileCache.set(appNgModuleFactoryPath, { path: appNgModuleFactoryPath, content: updatedAppNgModuleFactoryContent}); - // transpile the ts to js, and then update the cache const appNgModuleOutput = transpileTsString(context, appNgModulePath, updatedAppNgModuleContent); - const appNgModuleFactoryOutput = transpileTsString(context, appNgModuleFactoryPath, updatedAppNgModuleFactoryContent); - const appNgModuleSourceMapPath = changeExtension(appNgModulePath, '.js.map'); const appNgModulePathJsFile = changeExtension(appNgModulePath, '.js'); context.fileCache.set(appNgModuleSourceMapPath, { path: appNgModuleSourceMapPath, content: appNgModuleOutput.sourceMapText}); context.fileCache.set(appNgModulePathJsFile, { path: appNgModulePathJsFile, content: appNgModuleOutput.outputText}); - const appNgModuleFactorySourceMapPath = changeExtension(appNgModuleFactoryPath, '.js.map'); - const appNgModuleFactoryPathJsFile = changeExtension(appNgModuleFactoryPath, '.js'); - context.fileCache.set(appNgModuleFactorySourceMapPath, { path: appNgModuleFactorySourceMapPath, content: appNgModuleFactoryOutput.sourceMapText}); - context.fileCache.set(appNgModuleFactoryPathJsFile, { path: appNgModuleFactoryPathJsFile, content: appNgModuleFactoryOutput.outputText}); + if (changedFiles) { + changedFiles.push({ + event: 'change', + filePath: appNgModulePath, + ext: extname(appNgModulePath).toLowerCase() + }); + } - const appNgModuleFileDan = context.fileCache.get(appNgModulePath); - const appNgModuleFactoryFileDan = context.fileCache.get(appNgModuleFactoryPath); + if (isAot) { + const appNgModuleFactoryPath = changeExtension(appNgModulePath, '.ngfactory.ts'); + const appNgModuleFactoryFile = context.fileCache.get(appNgModuleFactoryPath); + if (!appNgModuleFactoryFile) { + throw new Error(`App NgModule Factory ${appNgModuleFactoryPath} not found in cache`); + } + const updatedAppNgModuleFactoryContent = getUpdatedAppNgModuleFactoryContentWithDeepLinksConfig(appNgModuleFactoryFile.content, deepLinkString); + context.fileCache.set(appNgModuleFactoryPath, { path: appNgModuleFactoryPath, content: updatedAppNgModuleFactoryContent}); + const appNgModuleFactoryOutput = transpileTsString(context, appNgModuleFactoryPath, updatedAppNgModuleFactoryContent); + + const appNgModuleFactorySourceMapPath = changeExtension(appNgModuleFactoryPath, '.js.map'); + const appNgModuleFactoryPathJsFile = changeExtension(appNgModuleFactoryPath, '.js'); + context.fileCache.set(appNgModuleFactorySourceMapPath, { path: appNgModuleFactorySourceMapPath, content: appNgModuleFactoryOutput.sourceMapText}); + context.fileCache.set(appNgModuleFactoryPathJsFile, { path: appNgModuleFactoryPathJsFile, content: appNgModuleFactoryOutput.outputText}); + + if (changedFiles) { + changedFiles.push({ + event: 'change', + filePath: appNgModuleFactoryPath, + ext: extname(appNgModuleFactoryPath).toLowerCase() + }); + } + } } export function getUpdatedAppNgModuleContentWithDeepLinkConfig(appNgModuleFilePath: string, appNgModuleFileContent: string, deepLinkStringContent: string) { diff --git a/src/webpack/ionic-environment-plugin.ts b/src/webpack/ionic-environment-plugin.ts index 4448a895..a95ef8fd 100644 --- a/src/webpack/ionic-environment-plugin.ts +++ b/src/webpack/ionic-environment-plugin.ts @@ -3,6 +3,7 @@ import { BuildContext , DeepLinkConfigEntry} from '../util/interfaces'; import { Logger } from '../logger/logger'; import { getInstance } from '../util/hybrid-file-system-factory'; import { createResolveDependenciesFromContextMap } from './util'; +import { WatchMemorySystem } from './watch-memory-system'; export class IonicEnvironmentPlugin { constructor(private context: BuildContext) { @@ -30,6 +31,7 @@ export class IonicEnvironmentPlugin { const hybridFileSystem = getInstance(); hybridFileSystem.setFileSystem(compiler.inputFileSystem); compiler.inputFileSystem = hybridFileSystem; + compiler.watchFileSystem = new WatchMemorySystem(this.context.fileCache); // do a bunch of webpack specific stuff here, so cast to an any // populate the content of the file system with any virtual files