Skip to content
This repository has been archived by the owner on Nov 22, 2024. It is now read-only.

Commit

Permalink
feat(ls-routes): introduce ls-routes
Browse files Browse the repository at this point in the history
  • Loading branch information
Fabian Wiles committed Aug 15, 2017
1 parent f7a4a49 commit c06886b
Show file tree
Hide file tree
Showing 13 changed files with 353 additions and 2 deletions.
5 changes: 5 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,8 @@ npm run build:ng-module-map-ngfactory-loader

cp modules/ng-module-map-ngfactory-loader/package.json dist/ng-module-map-ngfactory-loader/package.json
cp modules/ng-module-map-ngfactory-loader/README.md dist/ng-module-map-ngfactory-loader/README.md

npm run build:ng-ls-routes

cp modules/ng-ls-routes/package.json dist/ng-ls-routes/package.json
cp modules/ng-ls-routes/README.md dist/ng-ls-routes/README.md
41 changes: 41 additions & 0 deletions karma.conf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html

module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', 'karma-typescript'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('karma-typescript'),
],
client:{
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
files: [
'test.ts',
'./modules/**/*.spec.ts',
'./modules/**/*.ts',
],
preprocessors: {
'**/*.ts': ['karma-typescript'],
},
karmaTypescriptConfig: {
tsconfig: './tsconfig.spec.json',
},
coverageIstanbulReporter: {
reports: [ 'html', 'lcovonly' ],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false
});
};
24 changes: 24 additions & 0 deletions modules/ng-ls-routes/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
This is a tool which will gather the routes from a build factory bundle and return them ready to be used with stamping out prerendered index.html files

```js
import { lsRoutes } from '@nguniversal/ls-routes';

const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = require('./main.a5d2e81ce51e0c3ba3c8.bundle.js')

lsRoutes(
'flatPaths',
AppServerModuleNgFactory,
LAZY_MODULE_MAP
).then(paths => {
paths.filter(path => !path.includes(':'))
.forEach(path => {
renderModuleFactory(AppServerModuleNgFactory, {
document: index,
url: path,
extraProviders: [
provideModuleMap(LAZY_MODULE_MAP)
]
})
.then(html => fs.writeFileSync(`dist/${path.replace(/\//g, '-')}.index.html`, html))
})
})
1 change: 1 addition & 0 deletions modules/ng-ls-routes/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './src';
27 changes: 27 additions & 0 deletions modules/ng-ls-routes/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "@nguniversal/ls-routes",
"main": "index.js",
"types": "index.d.ts",
"version": "1.0.0-beta.0",
"description": "A tool for retrieving routes from a factory",
"homepage": "https://github.com/angular/universal",
"license": "MIT",
"contributors": [
"Toxicable"
],
"repository": {
"type": "git",
"url": "https://github.com/angular/universal"
},
"bugs": {
"url": "https://github.com/angular/universal/issues"
},
"peerDependencies": {
"@angular/core": "^4.0.0",
"@angular/platform-server": "^4.0.0",
"@angular/router": "^4.0.0",
"@nguniversal/module-map-ngfactory-loader": "^1.0.0",
"rxjs": "^5.0.0",
"zone.js": "^0.8.4"
}
}
1 change: 1 addition & 0 deletions modules/ng-ls-routes/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './ls-routes';
92 changes: 92 additions & 0 deletions modules/ng-ls-routes/src/ls-routes.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { lsRoutes } from './ls-routes';
import * as fs from 'fs';
import { ReflectiveInjector, enableProdMode, NgModule, Component } from '@angular/core';
import { COMPILER_PROVIDERS, JitCompiler, ResourceLoader } from '@angular/compiler';
import { RouterModule, Route } from '@angular/router';
import { BrowserModule } from "@angular/platform-browser";
class FileLoader implements ResourceLoader {
get(url: string): Promise<string> {
return new Promise((resolve) => {
resolve(fs.readFileSync(url).toString());
});
}
}
let _jitCompiler: JitCompiler;
export function jitCompiler(): JitCompiler {
if (!_jitCompiler) {
enableProdMode();
const injector = ReflectiveInjector.resolveAndCreate([
COMPILER_PROVIDERS,
{ provide: ResourceLoader, useValue: new FileLoader() }
]);
_jitCompiler = injector.get(JitCompiler);
}
return _jitCompiler;
}

function assignComponent(route: Route, comp: any){
route.component = comp;
if(route.children){
route.children = route.children.map(route => assignComponent(route, comp));
}
return route;
}

function createTestingFactory(routeConfig: Route[]) {
@Component({selector: 'a', template: 'a' })
class MockComponent { }

@NgModule({
imports: [
BrowserModule,
RouterModule.forRoot(routeConfig.map(r => assignComponent(r, MockComponent)))
],
declarations: [MockComponent]
})
class TestModule { }
return jitCompiler().compileModuleAsync(TestModule);
}
function createFactoryAndGetRotues(routeConfig: Route[]) {
//make it as easy as possible
return createTestingFactory(routeConfig)
.then(factory => lsRoutes('flatPaths', factory, {}))
}

describe('ls-routes', () => {
it('should resolve a single path', (done) => {
createFactoryAndGetRotues([
{ path: 'a' }
])
.then(routes => {
expect(routes).toContain('/a')
done()
})
})
it('should resolve a multiple paths', (done) => {
createFactoryAndGetRotues([
{ path: 'a' },
{ path: 'b' },
{ path: 'c' },
])
.then(routes => {
expect(routes).toContain('/a')
expect(routes).toContain('/b')
expect(routes).toContain('/c')
done()
})
})
it('should resolve nested paths', (done) => {
createFactoryAndGetRotues([
{
path: 'a',
children: [
{ path: 'a-a' }
]
},
])
.then(routes => {
expect(routes).toContain('/a/a-a')
done()
})
})
})
109 changes: 109 additions & 0 deletions modules/ng-ls-routes/src/ls-routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import 'zone.js/dist/zone-node';
import { ReflectiveInjector, NgModuleFactoryLoader, NgModuleFactory, Injector, NgZone } from '@angular/core';
import { platformServer } from '@angular/platform-server';
import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';
import { ROUTES, Route } from '@angular/router';
import { Observable } from "rxjs/Observable";
import 'rxjs/add/operator/toPromise';

let loader: NgModuleFactoryLoader;

export function lsRoutes<T>(
returnType: 'flatPaths' | 'nestedPaths' | 'full',
serverFactory: NgModuleFactory<T>,
lazyModuleMap?: any
) {
const ngZone = new NgZone({ enableLongStackTrace: false });
const rootInjector = ReflectiveInjector.resolveAndCreate(
[
{ provide: NgZone, useValue: ngZone },
provideModuleMap(lazyModuleMap)
],
platformServer().injector
);
const moduleRef = serverFactory.create(rootInjector);
loader = moduleRef.injector.get(NgModuleFactoryLoader);
return Promise.all(createModule(serverFactory, rootInjector))
.then(routes => {
if (returnType === 'full') {
return routes;
}
if (returnType === 'nestedPaths') {
return flattenRouteToPath(routes);
}
if (returnType === 'flatPaths') {
return flattenArray(flattenRouteToPath(routes));
}
throw new Error('you must provide a supported returnType');
});
}

function flattenArray<T, V>(array: T[] | T): V[] {
return !Array.isArray(array) ? array : [].concat.apply([], array.map(r => flattenArray(r)));
}

function flattenRouteToPath(routes: Route[]): (string[] | string)[] {
return routes.map(route => {
if (!route.children) {
return route.path ? '/' + route.path : '/';
} else {
return flattenRouteToPath(route.children)
.map((childRoute: string) => (!route.path ? '' : '/' + route.path) + childRoute);
}
});
}

function cocerceIntoPromise<T>(mightBePromise: Observable<T> | Promise<T> | T): Promise<T> {
if (mightBePromise instanceof Observable) {
return mightBePromise.toPromise();
}
return Promise.resolve(mightBePromise);
}

function extractRoute(route: Route, injector: Injector): Promise<Route> {
if (route.loadChildren) {
return resolveLazyChildren(route, injector);
}
if (route.children) {
return Promise.all(route.children.map(r => extractRoute(r, injector)))
.then(routes => {
route.children = routes;
return route;
});
}
return Promise.resolve(route);
}

function resolveLazyChildren(route: Route, injector: Injector): Promise<Route> {
let nextFactory: Promise<NgModuleFactory<any>>;
if (typeof route.loadChildren === 'function') {
nextFactory = cocerceIntoPromise<NgModuleFactory<any>>(
< NgModuleFactory<any> | Promise<NgModuleFactory<any>> >route.loadChildren()
)
} else {
nextFactory = loader.load(<string>route.loadChildren)
}
return nextFactory
.then(factory => Promise.all(createModule(factory, injector)))
.then(children => {
route.children = children;
delete route.loadChildren;
return route;
});
}

function createModule<T>(factory: NgModuleFactory<T>, parentInjector: Injector): Promise<Route>[] {

const moduleRef = factory.create(parentInjector);
const routes = moduleRef.injector.get(ROUTES);

return flattenArray<Route[][], Route>(routes)
.map(route => {
if (!route.loadChildren) {
//no lazy loaded paths so we can return the routes directly
return extractRoute(route, parentInjector);
} else {
return resolveLazyChildren(route, moduleRef.injector);
}
});
}
12 changes: 12 additions & 0 deletions modules/ng-ls-routes/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/ng-ls-routes"
},
"angularCompilerOptions": {
"genDir": "ngfactory"
},
"files": [
"index.ts"
]
}
12 changes: 11 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,9 @@
"build:ng-express-engine": "ngc -p modules/ng-express-engine/tsconfig.json",
"build:ng-aspnetcore-engine": "ngc -p modules/ng-aspnetcore-engine/tsconfig.json",
"build:ng-module-map-ngfactory-loader": "ngc -p modules/ng-module-map-ngfactory-loader/tsconfig.json",
"build:ng-ls-routes": "ngc -p modules/ng-ls-routes/tsconfig.json",
"build": "./build.sh",
"test": "exit 0"
"test": "karma start ./karma.conf.js"
},
"devDependencies": {
"@angular/animations": "^4.0.0",
Expand All @@ -75,8 +76,17 @@
"@angular/http": "^4.0.0",
"@angular/platform-browser": "^4.0.0",
"@angular/platform-server": "^4.0.0",
"@angular/router": "^4.0.0",
"@types/express": "^4.0.35",
"@types/jasmine": "^2.5.53",
"express": "^4.15.2",
"jasmine": "^2.7.0",
"karma": "^1.7.0",
"karma-chrome-launcher": "^2.2.0",
"karma-coverage-istanbul-reporter": "^1.3.0",
"karma-jasmine": "^1.1.0",
"karma-jasmine-html-reporter": "^0.2.2",
"karma-typescript": "^3.0.5",
"rimraf": "^2.6.1",
"rxjs": "^5.2.0",
"typescript": "^2.2.1",
Expand Down
10 changes: 10 additions & 0 deletions test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/zone-node';
import 'zone.js/dist/long-stack-trace-zone';
import 'zone.js/dist/proxy.js';
import 'zone.js/dist/sync-test';
import 'zone.js/dist/jasmine-patch';
import 'zone.js/dist/async-test';
import 'zone.js/dist/fake-async-test';
import 'reflect-metadata';

5 changes: 4 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true,
"skipLibCheck": true,
"lib": [
"dom",
"es6"
],
"types": [
"node"
"node",
"jasmine",
"karma"
]
},
"compileOnSave": false,
Expand Down
16 changes: 16 additions & 0 deletions tsconfig.spec.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [
"jasmine",
"node"
],
"skipLibCheck": true
},
"include": [
"test.ts",
"./modules/**/*.spec.ts",
"./modules/**/*.ts"
]
}

0 comments on commit c06886b

Please sign in to comment.