From beabe32cf5bf031e08c090b81ed71d3bc00cb716 Mon Sep 17 00:00:00 2001 From: Dan Bucholtz Date: Thu, 2 Mar 2017 15:10:27 -0600 Subject: [PATCH] feat(util): system.js ng-module loader system.js ng-module loader --- src/util/module-loader.ts | 49 +++++++++++++++++++++++ src/util/ng-module-loader.ts | 77 ++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 src/util/module-loader.ts create mode 100644 src/util/ng-module-loader.ts diff --git a/src/util/module-loader.ts b/src/util/module-loader.ts new file mode 100644 index 00000000000..e9857618e3b --- /dev/null +++ b/src/util/module-loader.ts @@ -0,0 +1,49 @@ +import { ComponentFactoryResolver, Injectable, Injector, OpaqueToken, Type } from '@angular/core'; +import { NgModuleLoader } from './ng-module-loader'; + +export const LAZY_LOADED_TOKEN = new OpaqueToken('LZYCMP'); + +/** + * @private + */ +@Injectable() +export class ModuleLoader { + + constructor( + private _ngModuleLoader: NgModuleLoader, + private _injector: Injector) {} + + + load(modulePath: string): Promise { + console.time(`ModuleLoader, load: ${modulePath}'`); + + const splitString = modulePath.split(SPLITTER); + + return this._ngModuleLoader.load(splitString[0], splitString[1]) + .then(loadedModule => { + console.timeEnd(`ModuleLoader, load: ${modulePath}'`); + const ref = loadedModule.create(this._injector); + + return { + componentFactoryResolver: ref.componentFactoryResolver, + component: ref.injector.get(LAZY_LOADED_TOKEN) + }; + }); + } +} + +const SPLITTER = '#'; + + +/** + * @private + */ +export function provideModuleLoader(ngModuleLoader: NgModuleLoader, injector: Injector) { + return new ModuleLoader(ngModuleLoader, injector); +} + + +export interface LoadedModule { + componentFactoryResolver: ComponentFactoryResolver; + component: Type; +}; diff --git a/src/util/ng-module-loader.ts b/src/util/ng-module-loader.ts new file mode 100644 index 00000000000..74b95392404 --- /dev/null +++ b/src/util/ng-module-loader.ts @@ -0,0 +1,77 @@ +import { Compiler, Injectable, NgModuleFactory, Optional } from '@angular/core'; + +const FACTORY_CLASS_SUFFIX = 'NgFactory'; + +/** + * Configuration for NgModuleLoader. + * token. + * + * @experimental + */ +export abstract class NgModuleLoaderConfig { + /** + * Prefix to add when computing the name of the factory module for a given module name. + */ + factoryPathPrefix: string; + + /** + * Suffix to add when computing the name of the factory module for a given module name. + */ + factoryPathSuffix: string; +} + +const DEFAULT_CONFIG: NgModuleLoaderConfig = { + factoryPathPrefix: '', + factoryPathSuffix: '.ngfactory', +}; + +/** + * NgModuleFactoryLoader that uses SystemJS to load NgModuleFactory + */ +@Injectable() +export class NgModuleLoader { + private _config: NgModuleLoaderConfig; + + constructor(private _compiler: Compiler, @Optional() config?: NgModuleLoaderConfig) { + this._config = config || DEFAULT_CONFIG; + } + + load(modulePath: string, ngModuleExport: string) { + const offlineMode = this._compiler instanceof Compiler; + return offlineMode ? loadPrecompiledFactory(this._config, modulePath, ngModuleExport) : loadAndCompile(this._compiler, modulePath, ngModuleExport); + } +} + + +function loadAndCompile(compiler: Compiler, modulePath: string, ngModuleExport: string): Promise> { + if (!ngModuleExport) { + ngModuleExport = 'default'; + } + + return System.import(modulePath) + .then((rawModule: any) => { + const module = rawModule[ngModuleExport]; + if (!module) { + throw new Error(`Module ${modulePath} does not export ${ngModuleExport}`); + } + return compiler.compileModuleAsync(module); + }); +} + + +function loadPrecompiledFactory(config: NgModuleLoaderConfig, modulePath: string, ngModuleExport: string): Promise> { + let factoryClassSuffix = FACTORY_CLASS_SUFFIX; + if (ngModuleExport === undefined) { + ngModuleExport = 'default'; + factoryClassSuffix = ''; + } + + return System.import(config.factoryPathPrefix + modulePath + config.factoryPathSuffix) + .then((rawModule: any) => { + const ngModuleFactory = rawModule[ngModuleExport + factoryClassSuffix]; + if (!ngModuleFactory) { + throw new Error(`Module ${modulePath} does not export ${ngModuleExport}`); + } + return ngModuleFactory; + }); +}