GeezerVison is showing github.com — feat(aio): lazy-load embedded components (#18428) · angular/angular@7d81309
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: aio/scripts/_payload-limits.json +9-9Lines changed: 9 additions & 9 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 2 | 2 | "aio": { | 3 | 3 | "master": { | 4 | 4 | "gzip7": { | 5 | | - "inline": 925, | 6 | | - "main": 119519, | 7 | | - "polyfills": 11863 | | 5 | + "inline": 941, | | 6 | + "main": 116124, | | 7 | + "polyfills": 11860 | 8 | 8 | }, | 9 | 9 | "gzip9": { | 10 | | - "inline": 925, | 11 | | - "main": 119301, | 12 | | - "polyfills": 11861 | | 10 | + "inline": 941, | | 11 | + "main": 115954, | | 12 | + "polyfills": 11858 | 13 | 13 | }, | 14 | 14 | "uncompressed": { | 15 | | - "inline": 1533, | 16 | | - "main": 486493, | 17 | | - "polyfills": 37068 | | 15 | + "inline": 1558, | | 16 | + "main": 456432, | | 17 | + "polyfills": 37070 | 18 | 18 | } | 19 | 19 | } | 20 | 20 | } | |
Collapse file Copy file name to clipboardExpand all lines: aio/src/app/app.component.spec.ts +7-1Lines changed: 7 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -12,6 +12,7 @@ import { AppComponent } from './app.component'; | 12 | 12 | import { AppModule } from './app.module'; | 13 | 13 | import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component'; | 14 | 14 | import { Deployment } from 'app/shared/deployment.service'; | | 15 | +import { EmbedComponentsService } from 'app/embed-components/embed-components.service'; | 15 | 16 | import { GaService } from 'app/shared/ga.service'; | 16 | 17 | import { LocationService } from 'app/shared/location.service'; | 17 | 18 | import { Logger } from 'app/shared/logger.service'; | @@ -24,7 +25,7 @@ import { SearchBoxComponent } from 'app/search/search-box/search-box.component'; | 24 | 25 | import { SearchResultsComponent } from 'app/shared/search-results/search-results.component'; | 25 | 26 | import { SearchService } from 'app/search/search.service'; | 26 | 27 | import { SelectComponent } from 'app/shared/select/select.component'; | 27 | | -import { TocComponent } from 'app/embedded/toc/toc.component'; | | 28 | +import { TocComponent } from 'app/layout/toc/toc.component'; | 28 | 29 | import { TocItem, TocService } from 'app/shared/toc.service'; | 29 | 30 |
| 30 | 31 | const sideBySideBreakPoint = 992; | @@ -1033,6 +1034,7 @@ function createTestingModule(initialUrl: string, mode: string = 'stable') { | 1033 | 1034 | imports: [ AppModule ], | 1034 | 1035 | providers: [ | 1035 | 1036 | { provide: APP_BASE_HREF, useValue: '/' }, | | 1037 | + { provide: EmbedComponentsService, useClass: TestEmbedComponentsService }, | 1036 | 1038 | { provide: GaService, useClass: TestGaService }, | 1037 | 1039 | { provide: HttpClient, useClass: TestHttpClient }, | 1038 | 1040 | { provide: LocationService, useFactory: () => mockLocationService }, | @@ -1047,6 +1049,10 @@ function createTestingModule(initialUrl: string, mode: string = 'stable') { | 1047 | 1049 | }); | 1048 | 1050 | } | 1049 | 1051 |
| | 1052 | +class TestEmbedComponentsService { | | 1053 | + embedInto = jasmine.createSpy('embedInto').and.returnValue(of([])); | | 1054 | +} | | 1055 | + | 1050 | 1056 | class TestGaService { | 1051 | 1057 | locationChanged = jasmine.createSpy('locationChanged'); | 1052 | 1058 | } | |
Collapse file Copy file name to clipboardExpand all lines: aio/src/app/app.component.ts +1-1Lines changed: 1 addition & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
| 1 | 1 | import { Component, ElementRef, HostBinding, HostListener, OnInit, | 2 | 2 | QueryList, ViewChild, ViewChildren } from '@angular/core'; | 3 | | -import { MatSidenav } from '@angular/material'; | | 3 | +import { MatSidenav } from '@angular/material/sidenav'; | 4 | 4 |
| 5 | 5 | import { CurrentNodes, NavigationService, NavigationNode, VersionInfo } from 'app/navigation/navigation.service'; | 6 | 6 | import { DocumentService, DocumentContents } from 'app/documents/document.service'; | |
Collapse file Copy file name to clipboard +48Lines changed: 48 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +import { TestBed } from '@angular/core/testing'; | | 2 | +import { AppModule } from 'app/app.module'; | | 3 | +import { ComponentsOrModulePath, EMBEDDED_COMPONENTS } from 'app/embed-components/embed-components.service'; | | 4 | +import { embeddedComponents } from 'app/embedded/embedded.module'; | | 5 | + | | 6 | +describe('AppModule', () => { | | 7 | + let componentsMap: {[multiSelectorstring: string]: ComponentsOrModulePath}; | | 8 | + | | 9 | + beforeEach(() => { | | 10 | + TestBed.configureTestingModule({imports: [AppModule]}); | | 11 | + componentsMap = TestBed.get(EMBEDDED_COMPONENTS); | | 12 | + }); | | 13 | + | | 14 | + it('should provide a map of selectors to embedded components (or module)', () => { | | 15 | + const allSelectors = Object.keys(componentsMap); | | 16 | + | | 17 | + expect(allSelectors.length).toBeGreaterThan(1); | | 18 | + allSelectors.forEach(selector => { | | 19 | + const value = componentsMap[selector]; | | 20 | + const isArrayOrString = Array.isArray(value) || (typeof value === 'string'); | | 21 | + expect(isArrayOrString).toBe(true); | | 22 | + }); | | 23 | + }); | | 24 | + | | 25 | + it('should provide a list of eagerly-loaded embedded components', () => { | | 26 | + const eagerSelector = Object.keys(componentsMap).find(selector => Array.isArray(componentsMap[selector])); | | 27 | + const selectorCount = eagerSelector.split(',').length; | | 28 | + | | 29 | + expect(eagerSelector).not.toBeNull(); | | 30 | + expect(selectorCount).toBe(componentsMap[eagerSelector].length); | | 31 | + | | 32 | + // For example... | | 33 | + expect(eagerSelector).toContain('aio-toc'); | | 34 | + }); | | 35 | + | | 36 | + it('should provide a list of lazy-loaded embedded components', () => { | | 37 | + const lazySelector = Object.keys(componentsMap).find(selector => selector.includes('code-example')); | | 38 | + const selectorCount = lazySelector.split(',').length; | | 39 | + | | 40 | + expect(lazySelector).not.toBeNull(); | | 41 | + expect(selectorCount).toBe(embeddedComponents.length); | | 42 | + | | 43 | + // For example... | | 44 | + expect(lazySelector).toContain('code-example'); | | 45 | + expect(lazySelector).toContain('code-tabs'); | | 46 | + expect(lazySelector).toContain('live-example'); | | 47 | + }); | | 48 | +}); |
Collapse file Copy file name to clipboardExpand all lines: aio/src/app/app.module.ts +37-25Lines changed: 37 additions & 25 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -5,36 +5,26 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; | 5 | 5 |
| 6 | 6 | import { Location, LocationStrategy, PathLocationStrategy } from '@angular/common'; | 7 | 7 |
| 8 | | -import { | 9 | | - MatButtonModule, | 10 | | - MatIconModule, | 11 | | - MatIconRegistry, | 12 | | - MatInputModule, | 13 | | - MatProgressBarModule, | 14 | | - MatSidenavModule, | 15 | | - MatTabsModule, | 16 | | - MatToolbarModule | 17 | | -} from '@angular/material'; | 18 | | - | 19 | | -import { | 20 | | - Platform | 21 | | -} from '@angular/cdk/platform'; | | 8 | +import { MatButtonModule } from '@angular/material/button'; | | 9 | +import { MatIconModule, MatIconRegistry } from '@angular/material/icon'; | | 10 | +import { MatProgressBarModule } from '@angular/material/progress-bar'; | | 11 | +import { MatSidenavModule } from '@angular/material/sidenav'; | | 12 | +import { MatToolbarModule } from '@angular/material/toolbar'; | 22 | 13 |
| | 14 | +import { ROUTES } from '@angular/router'; | 23 | 15 |
| 24 | 16 | // Temporary fix for MatSidenavModule issue: | 25 | 17 | // crashes with "missing first" operator when SideNav.mode is "over" | 26 | 18 | import 'rxjs/add/operator/first'; | 27 | 19 |
| 28 | | -import { SwUpdatesModule } from 'app/sw-updates/sw-updates.module'; | 29 | 20 |
| 30 | 21 | import { AppComponent } from 'app/app.component'; | 31 | | -import { ApiService } from 'app/embedded/api/api.service'; | | 22 | +import { EMBEDDED_COMPONENTS, EmbeddedComponentsMap } from 'app/embed-components/embed-components.service'; | 32 | 23 | import { CustomIconRegistry, SVG_ICONS } from 'app/shared/custom-icon-registry'; | 33 | 24 | import { Deployment } from 'app/shared/deployment.service'; | 34 | 25 | import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component'; | 35 | 26 | import { DtComponent } from 'app/layout/doc-viewer/dt.component'; | 36 | 27 | import { ModeBannerComponent } from 'app/layout/mode-banner/mode-banner.component'; | 37 | | -import { EmbeddedModule } from 'app/embedded/embedded.module'; | 38 | 28 | import { GaService } from 'app/shared/ga.service'; | 39 | 29 | import { Logger } from 'app/shared/logger.service'; | 40 | 30 | import { LocationService } from 'app/shared/location.service'; | @@ -47,11 +37,18 @@ import { NavMenuComponent } from 'app/layout/nav-menu/nav-menu.component'; | 47 | 37 | import { NavItemComponent } from 'app/layout/nav-item/nav-item.component'; | 48 | 38 | import { ScrollService } from 'app/shared/scroll.service'; | 49 | 39 | import { ScrollSpyService } from 'app/shared/scroll-spy.service'; | 50 | | -import { SearchBoxComponent } from './search/search-box/search-box.component'; | | 40 | +import { SearchBoxComponent } from 'app/search/search-box/search-box.component'; | | 41 | +import { TocComponent } from 'app/layout/toc/toc.component'; | 51 | 42 | import { TocService } from 'app/shared/toc.service'; | 52 | 43 | import { WindowToken, windowProvider } from 'app/shared/window'; | 53 | 44 |
| | 45 | +import { EmbedComponentsModule } from 'app/embed-components/embed-components.module'; | 54 | 46 | import { SharedModule } from 'app/shared/shared.module'; | | 47 | +import { SwUpdatesModule } from 'app/sw-updates/sw-updates.module'; | | 48 | + | | 49 | + | | 50 | +// The path to the `EmbeddedModule`. | | 51 | +const embeddedModulePath = 'app/embedded/embedded.module#EmbeddedModule'; | 55 | 52 |
| 56 | 53 | // These are the hardcoded inline svg sources to be used by the `<mat-icon>` component | 57 | 54 | export const svgIconProviders = [ | @@ -78,15 +75,13 @@ export const svgIconProviders = [ | 78 | 75 | @NgModule({ | 79 | 76 | imports: [ | 80 | 77 | BrowserModule, | 81 | | - EmbeddedModule, | 82 | | - HttpClientModule, | 83 | 78 | BrowserAnimationsModule, | | 79 | + EmbedComponentsModule, | | 80 | + HttpClientModule, | 84 | 81 | MatButtonModule, | 85 | 82 | MatIconModule, | 86 | | - MatInputModule, | 87 | 83 | MatProgressBarModule, | 88 | 84 | MatSidenavModule, | 89 | | - MatTabsModule, | 90 | 85 | MatToolbarModule, | 91 | 86 | SwUpdatesModule, | 92 | 87 | SharedModule | @@ -100,10 +95,10 @@ export const svgIconProviders = [ | 100 | 95 | NavMenuComponent, | 101 | 96 | NavItemComponent, | 102 | 97 | SearchBoxComponent, | | 98 | + TocComponent, | 103 | 99 | TopMenuComponent, | 104 | 100 | ], | 105 | 101 | providers: [ | 106 | | - ApiService, | 107 | 102 | Deployment, | 108 | 103 | DocumentService, | 109 | 104 | GaService, | @@ -113,15 +108,32 @@ export const svgIconProviders = [ | 113 | 108 | LocationService, | 114 | 109 | { provide: MatIconRegistry, useClass: CustomIconRegistry }, | 115 | 110 | NavigationService, | 116 | | - Platform, | 117 | 111 | ScrollService, | 118 | 112 | ScrollSpyService, | 119 | 113 | SearchService, | 120 | 114 | svgIconProviders, | 121 | 115 | TocService, | 122 | 116 | { provide: WindowToken, useFactory: windowProvider }, | | 117 | + | | 118 | + { | | 119 | + provide: EMBEDDED_COMPONENTS, | | 120 | + useValue: { | | 121 | + /* tslint:disable: max-line-length */ | | 122 | + 'aio-toc': [TocComponent], | | 123 | + 'aio-api-list, aio-contributor-list, aio-file-not-found-search, aio-resource-list, code-example, code-tabs, current-location, live-example': embeddedModulePath, | | 124 | + /* tslint:enable: max-line-length */ | | 125 | + } as EmbeddedComponentsMap, | | 126 | + }, | | 127 | + { | | 128 | + // This is currently the only way to get `@angular/cli` | | 129 | + // to split `EmbeddedModule` into a separate chunk :( | | 130 | + provide: ROUTES, | | 131 | + useValue: [{ path: '/embedded', loadChildren: embeddedModulePath }], | | 132 | + multi: true, | | 133 | + }, | 123 | 134 | ], | 124 | | - bootstrap: [AppComponent] | | 135 | + entryComponents: [ TocComponent ], | | 136 | + bootstrap: [ AppComponent ] | 125 | 137 | }) | 126 | 138 | export class AppModule { | 127 | 139 | } |
Collapse file Copy file name to clipboard +13Lines changed: 13 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +import { NgModule, NgModuleFactoryLoader, SystemJsNgModuleLoader } from '@angular/core'; | | 2 | + | | 3 | +import { EmbedComponentsService } from './embed-components.service'; | | 4 | + | | 5 | + | | 6 | +@NgModule({ | | 7 | + providers: [ | | 8 | + EmbedComponentsService, | | 9 | + { provide: NgModuleFactoryLoader, useClass: SystemJsNgModuleLoader }, | | 10 | + ], | | 11 | +}) | | 12 | +export class EmbedComponentsModule { | | 13 | +} |
|