Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
feat(aio): lazy-load embedded components
Fixes #16127
  • Loading branch information
gkalpak committed Dec 6, 2017
commit 0924132092891bb363c17486856169fc06394ce7
18 changes: 9 additions & 9 deletions aio/scripts/_payload-limits.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@
"aio": {
"master": {
"gzip7": {
"inline": 925,
"main": 119519,
"polyfills": 11863
"inline": 941,
"main": 116124,
"polyfills": 11860
},
"gzip9": {
"inline": 925,
"main": 119301,
"polyfills": 11861
"inline": 941,
"main": 115954,
"polyfills": 11858
},
"uncompressed": {
"inline": 1533,
"main": 486493,
"polyfills": 37068
"inline": 1558,
"main": 456432,
"polyfills": 37070
}
}
}
Expand Down
8 changes: 7 additions & 1 deletion aio/src/app/app.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { AppComponent } from './app.component';
import { AppModule } from './app.module';
import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component';
import { Deployment } from 'app/shared/deployment.service';
import { EmbedComponentsService } from 'app/embed-components/embed-components.service';
import { GaService } from 'app/shared/ga.service';
import { LocationService } from 'app/shared/location.service';
import { Logger } from 'app/shared/logger.service';
Expand All @@ -24,7 +25,7 @@ import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
import { SearchResultsComponent } from 'app/shared/search-results/search-results.component';
import { SearchService } from 'app/search/search.service';
import { SelectComponent } from 'app/shared/select/select.component';
import { TocComponent } from 'app/embedded/toc/toc.component';
import { TocComponent } from 'app/layout/toc/toc.component';
import { TocItem, TocService } from 'app/shared/toc.service';

const sideBySideBreakPoint = 992;
Expand Down Expand Up @@ -1033,6 +1034,7 @@ function createTestingModule(initialUrl: string, mode: string = 'stable') {
imports: [ AppModule ],
providers: [
{ provide: APP_BASE_HREF, useValue: '/' },
{ provide: EmbedComponentsService, useClass: TestEmbedComponentsService },
{ provide: GaService, useClass: TestGaService },
{ provide: HttpClient, useClass: TestHttpClient },
{ provide: LocationService, useFactory: () => mockLocationService },
Expand All @@ -1047,6 +1049,10 @@ function createTestingModule(initialUrl: string, mode: string = 'stable') {
});
}

class TestEmbedComponentsService {
embedInto = jasmine.createSpy('embedInto').and.returnValue(of([]));
}

class TestGaService {
locationChanged = jasmine.createSpy('locationChanged');
}
Expand Down
2 changes: 1 addition & 1 deletion aio/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Component, ElementRef, HostBinding, HostListener, OnInit,
QueryList, ViewChild, ViewChildren } from '@angular/core';
import { MatSidenav } from '@angular/material';
import { MatSidenav } from '@angular/material/sidenav';

import { CurrentNodes, NavigationService, NavigationNode, VersionInfo } from 'app/navigation/navigation.service';
import { DocumentService, DocumentContents } from 'app/documents/document.service';
Expand Down
48 changes: 48 additions & 0 deletions aio/src/app/app.module.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { TestBed } from '@angular/core/testing';
import { AppModule } from 'app/app.module';
import { ComponentsOrModulePath, EMBEDDED_COMPONENTS } from 'app/embed-components/embed-components.service';
import { embeddedComponents } from 'app/embedded/embedded.module';

describe('AppModule', () => {
let componentsMap: {[multiSelectorstring: string]: ComponentsOrModulePath};

beforeEach(() => {
TestBed.configureTestingModule({imports: [AppModule]});
componentsMap = TestBed.get(EMBEDDED_COMPONENTS);
});

it('should provide a map of selectors to embedded components (or module)', () => {
const allSelectors = Object.keys(componentsMap);

expect(allSelectors.length).toBeGreaterThan(1);
allSelectors.forEach(selector => {
const value = componentsMap[selector];
const isArrayOrString = Array.isArray(value) || (typeof value === 'string');
expect(isArrayOrString).toBe(true);
});
});

it('should provide a list of eagerly-loaded embedded components', () => {
const eagerSelector = Object.keys(componentsMap).find(selector => Array.isArray(componentsMap[selector]));
const selectorCount = eagerSelector.split(',').length;

expect(eagerSelector).not.toBeNull();
expect(selectorCount).toBe(componentsMap[eagerSelector].length);

// For example...
expect(eagerSelector).toContain('aio-toc');
});

it('should provide a list of lazy-loaded embedded components', () => {
const lazySelector = Object.keys(componentsMap).find(selector => selector.includes('code-example'));
const selectorCount = lazySelector.split(',').length;

expect(lazySelector).not.toBeNull();
expect(selectorCount).toBe(embeddedComponents.length);

// For example...
expect(lazySelector).toContain('code-example');
expect(lazySelector).toContain('code-tabs');
expect(lazySelector).toContain('live-example');
});
});
62 changes: 37 additions & 25 deletions aio/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,26 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

import { Location, LocationStrategy, PathLocationStrategy } from '@angular/common';

import {
MatButtonModule,
MatIconModule,
MatIconRegistry,
MatInputModule,
MatProgressBarModule,
MatSidenavModule,
MatTabsModule,
MatToolbarModule
} from '@angular/material';

import {
Platform
} from '@angular/cdk/platform';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule, MatIconRegistry } from '@angular/material/icon';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatToolbarModule } from '@angular/material/toolbar';

import { ROUTES } from '@angular/router';

// Temporary fix for MatSidenavModule issue:
// crashes with "missing first" operator when SideNav.mode is "over"
import 'rxjs/add/operator/first';

import { SwUpdatesModule } from 'app/sw-updates/sw-updates.module';

import { AppComponent } from 'app/app.component';
import { ApiService } from 'app/embedded/api/api.service';
import { EMBEDDED_COMPONENTS, EmbeddedComponentsMap } from 'app/embed-components/embed-components.service';
import { CustomIconRegistry, SVG_ICONS } from 'app/shared/custom-icon-registry';
import { Deployment } from 'app/shared/deployment.service';
import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component';
import { DtComponent } from 'app/layout/doc-viewer/dt.component';
import { ModeBannerComponent } from 'app/layout/mode-banner/mode-banner.component';
import { EmbeddedModule } from 'app/embedded/embedded.module';
import { GaService } from 'app/shared/ga.service';
import { Logger } from 'app/shared/logger.service';
import { LocationService } from 'app/shared/location.service';
Expand All @@ -47,11 +37,18 @@ import { NavMenuComponent } from 'app/layout/nav-menu/nav-menu.component';
import { NavItemComponent } from 'app/layout/nav-item/nav-item.component';
import { ScrollService } from 'app/shared/scroll.service';
import { ScrollSpyService } from 'app/shared/scroll-spy.service';
import { SearchBoxComponent } from './search/search-box/search-box.component';
import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
import { TocComponent } from 'app/layout/toc/toc.component';
import { TocService } from 'app/shared/toc.service';
import { WindowToken, windowProvider } from 'app/shared/window';

import { EmbedComponentsModule } from 'app/embed-components/embed-components.module';
import { SharedModule } from 'app/shared/shared.module';
import { SwUpdatesModule } from 'app/sw-updates/sw-updates.module';


// The path to the `EmbeddedModule`.
const embeddedModulePath = 'app/embedded/embedded.module#EmbeddedModule';

// These are the hardcoded inline svg sources to be used by the `<mat-icon>` component
export const svgIconProviders = [
Expand All @@ -78,15 +75,13 @@ export const svgIconProviders = [
@NgModule({
imports: [
BrowserModule,
EmbeddedModule,
HttpClientModule,
BrowserAnimationsModule,
EmbedComponentsModule,
HttpClientModule,
MatButtonModule,
MatIconModule,
MatInputModule,
MatProgressBarModule,
MatSidenavModule,
MatTabsModule,
MatToolbarModule,
SwUpdatesModule,
SharedModule
Expand All @@ -100,10 +95,10 @@ export const svgIconProviders = [
NavMenuComponent,
NavItemComponent,
SearchBoxComponent,
TocComponent,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So what you are saying is that TocComponent is not lazy loaded, but it needs to be made available as an embedded component?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly. Because we use it on the page (outside of DocViewer) for displaying the ToC on the right (for large screens), but also as embedded component to show it on narrow screens.

TopMenuComponent,
],
providers: [
ApiService,
Deployment,
DocumentService,
GaService,
Expand All @@ -113,15 +108,32 @@ export const svgIconProviders = [
LocationService,
{ provide: MatIconRegistry, useClass: CustomIconRegistry },
NavigationService,
Platform,
ScrollService,
ScrollSpyService,
SearchService,
svgIconProviders,
TocService,
{ provide: WindowToken, useFactory: windowProvider },

{
provide: EMBEDDED_COMPONENTS,
useValue: {
/* tslint:disable: max-line-length */
'aio-toc': [TocComponent],
'aio-api-list, aio-contributor-list, aio-file-not-found-search, aio-resource-list, code-example, code-tabs, current-location, live-example': embeddedModulePath,
/* tslint:enable: max-line-length */
} as EmbeddedComponentsMap,
},
{
// This is currently the only way to get `@angular/cli`
// to split `EmbeddedModule` into a separate chunk :(
provide: ROUTES,
useValue: [{ path: '/embedded', loadChildren: embeddedModulePath }],
multi: true,
},
],
bootstrap: [AppComponent]
entryComponents: [ TocComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule {
}
13 changes: 13 additions & 0 deletions aio/src/app/embed-components/embed-components.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { NgModule, NgModuleFactoryLoader, SystemJsNgModuleLoader } from '@angular/core';

import { EmbedComponentsService } from './embed-components.service';


@NgModule({
providers: [
EmbedComponentsService,
{ provide: NgModuleFactoryLoader, useClass: SystemJsNgModuleLoader },
],
})
export class EmbedComponentsModule {
}
Loading