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

WIP: feat(common): introduce ls-routes #1002

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions angular.tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
],
"exclude": [
"node_modules/@angular/bazel/**",
"node_modules/@angular/router/upgrade/**",
"node_modules/@angular/compiler-cli/**",
"node_modules/@angular/tsc-wrapped/**",
"node_modules/@angular/platform-browser/testing/**",
Expand Down
2 changes: 2 additions & 0 deletions modules/common/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ ng_module(
deps = [
"//modules/common/engine",
"//modules/common/tokens",
"//modules/common/ls-routes",
],
)

Expand All @@ -25,6 +26,7 @@ ng_package(
":common",
"//modules/common/engine",
"//modules/common/tokens",
"//modules/common/ls-routes",
],
)

Expand Down
33 changes: 33 additions & 0 deletions modules/common/ls-routes/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package(default_visibility = ["//visibility:public"])

load("//tools:defaults.bzl", "ts_library", "ng_module")
load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test")

ng_module(
name = "ls-routes",
srcs = glob([
"src/*.ts",
"*.ts",
]),
deps = [
"//modules/module-map-ngfactory-loader",
],
module_name = "@nguniversal/common/ls-routes",
)

ts_library(
name = "unit_test_lib",
testonly = True,
srcs = glob([
"spec/**/*.spec.ts",
]),
deps = [
"//modules/module-map-ngfactory-loader",
":ls-routes",
],
)

jasmine_node_test(
name = "unit_test",
srcs = [":unit_test_lib"],
)
8 changes: 8 additions & 0 deletions modules/common/ls-routes/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export * from './public_api';
8 changes: 8 additions & 0 deletions modules/common/ls-routes/public_api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export { lsRoutes } from './src/ls-routes';
151 changes: 151 additions & 0 deletions modules/common/ls-routes/spec/ls-routes.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { lsRoutes } from '@nguniversal/common/ls-routes';
import { enableProdMode, NgModule, Component, CompilerFactory, Compiler } from '@angular/core';
import { RouterModule, Route } from '@angular/router';
import { BrowserModule } from '@angular/platform-browser';
import { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';
import { ServerModule, platformDynamicServer } from '@angular/platform-server';

@Component({selector: 'lazy', template: 'lazy'})
export class LazyComponent {}

@NgModule({
imports: [RouterModule.forChild([
{path: 'lazy-a', component: LazyComponent}
])],
declarations: [ LazyComponent ]
})
export class LazyModule {}

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


async function createFactoryAndGetRoutes(
routeConfig: Route[],
compiler: Compiler,
moduleMap: {[key: string]: any} = {} ) {

@Component({ selector: 'a', template: 'a' })
class MockComponent { }

@NgModule({
imports: [
BrowserModule,
RouterModule.forRoot(routeConfig.map(r => assignComponent(r, MockComponent))),
],
declarations: [MockComponent]
})
class MockModule { }
@NgModule({
imports: [
ServerModule,
MockModule,
ModuleMapLoaderModule
]
})
class MockServerModule {}
const factory = await compiler.compileModuleAsync(MockServerModule);

return lsRoutes(factory, moduleMap);
}

describe('ls-routes', () => {
let compiler: Compiler;
beforeAll(() => {
enableProdMode();
const compilerFactory = platformDynamicServer()
.injector.get(CompilerFactory) as CompilerFactory;

compiler = compilerFactory.createCompiler();
});

it('should resolve a single path', async(done) => {
const routes = await createFactoryAndGetRoutes([
{ path: 'a' }
], compiler);
expect(routes).toContain('/a');
done();
});
xit('should resolve a multiple paths', async(done) => {
const routes = await createFactoryAndGetRoutes([
{ path: 'a' },
{ path: 'b' },
{ path: 'c' },
], compiler);
expect(routes).toContain('/a');
expect(routes).toContain('/b');
expect(routes).toContain('/c');
done();

});
xit('should resolve nested paths', async(done) => {
const routes = await createFactoryAndGetRoutes([
{
path: 'a',
children: [
{ path: 'a-a' },
{ path: 'a-b' }
]
},
], compiler);
expect(routes).toContain('/a/a-a');
expect(routes).toContain('/a/a-b');
done();
});
xit('should resolve a string loaded loadChildren', async(done) => {
const moduleMap = { './ls-routes.spec.ts#LazyModule': LazyModule };
const routes = await createFactoryAndGetRoutes([
{
path: 'a',
loadChildren: './ls-routes.spec.ts#LazyModule'
}
], compiler, moduleMap);
expect(routes).toContain('/a/lazy-a');
done();
});
xit('should resolve a function loaded loadChildren', async(done) => {
const routes = await createFactoryAndGetRoutes([
{
path: 'a',
loadChildren: () => compiler.compileModuleSync(LazyModule)
}
], compiler);
expect(routes).toContain('/a/lazy-a');
done();
});
xit('should resolve a function loaded promise loadChildren', async(done) => {
const routes = await createFactoryAndGetRoutes([
{
path: 'a',
loadChildren: () => compiler.compileModuleAsync(LazyModule) as any
}
], compiler);
expect(routes).toContain('/a/lazy-a');
done();

});
xit('should correctly merge nested routes with empty string ', async(done) => {
const routes = await createFactoryAndGetRoutes([
{
path: '',
children: [
{
path: '',
children: [
{ path: '' },
{ path: 'level3'}
]
}
]
}
], compiler);
expect(routes).toContain('/');
expect(routes).toContain('/level3');
done();
});
});
107 changes: 107 additions & 0 deletions modules/common/ls-routes/src/ls-routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import { NgModuleFactoryLoader,
NgModuleFactory, Injector, NgZone } from '@angular/core';
import { platformServer } from '@angular/platform-server';
import { ROUTES, Route } from '@angular/router';
import { Observable } from 'rxjs';
import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';

export let loader: NgModuleFactoryLoader;

export function lsRoutes<T>(
serverFactory: NgModuleFactory<T>,
lazyModuleMap?: any
) {
const ngZone = new NgZone({ enableLongStackTrace: false });
const rootInjector = Injector.create(
[
{ 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 => {
return flattenArray(flattenRouteToPath(routes));
});
}

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 {
// extra flatten here for nested routes
return flattenArray(flattenRouteToPath(route.children))
.map(childRoute => (!route.path ? '' : '/' + route.path) + childRoute);
}
});
}

function coerceIntoPromise<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 = coerceIntoPromise<NgModuleFactory<any>>(
route.loadChildren() as NgModuleFactory<any> | Promise<NgModuleFactory<any>>
);
} else {
nextFactory = loader.load(route.loadChildren as string);
}
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);
}
});
}
4 changes: 3 additions & 1 deletion modules/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
],
"peerDependencies": {
"@angular/common": "NG_VERSION",
"@angular/core": "NG_VERSION"
"@angular/core": "NG_VERSION",
"@angular/router": "NG_VERSION",
"@nguniversal/module-map-ngfactory-loader": "0.0.0-PLACEHOLDER"
},
"ng-update": {
"packageGroup": "NG_UPDATE_PACKAGE_GROUP"
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@
"@angular/platform-browser": "^6.0.4",
"@angular/platform-browser-dynamic": "^6.0.4",
"@angular/platform-server": "^6.0.4",
"@angular/upgrade": "^6.0.4",
"@angular/router": "^6.0.4",
"@bazel/ibazel": "^0.3.1",
"@types/express": "^4.0.39",
"@types/fs-extra": "^4.0.5",
Expand Down
12 changes: 12 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,18 @@
tslib "^1.9.0"
xhr2 "^0.1.4"

"@angular/router@^6.0.4":
version "6.0.4"
resolved "https://registry.yarnpkg.com/@angular/router/-/router-6.0.4.tgz#81c96dfa42a8c4a218cbd450b1e8959a76ea9595"
dependencies:
tslib "^1.9.0"

"@angular/upgrade@^6.0.4":
version "6.0.4"
resolved "https://registry.yarnpkg.com/@angular/upgrade/-/upgrade-6.0.4.tgz#b12a016bab98db77bacbe9b1a83c4c2a47e5baf5"
dependencies:
tslib "^1.9.0"

"@bazel/ibazel@^0.3.1":
version "0.3.1"
resolved "https://registry.yarnpkg.com/@bazel/ibazel/-/ibazel-0.3.1.tgz#5f02f208f138e581bbdb1534d5c013d7a0ac9799"
Expand Down