From 2adf03e54a554ad178009e5c63b0325c83f7e891 Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Mon, 26 Nov 2018 22:56:35 +0200 Subject: [PATCH] fix(docs-infra): inline SVG icons used in page load sensitive UI (#27250) These icons are part of the app shell and used on every load (on both desktop and mobile). Inlining them ensures they are rendered asap. PR Close #27250 --- aio/scripts/_payload-limits.json | 2 +- aio/src/app/app.component.html | 10 +++--- aio/src/app/app.module.ts | 35 +++++++++++++++++++ .../app/shared/custom-icon-registry.spec.ts | 21 +++++++++++ aio/src/app/shared/custom-icon-registry.ts | 25 +++++++++---- aio/src/assets/images/logos/github-icon.svg | 14 -------- aio/src/assets/images/logos/twitter-icon.svg | 13 ------- 7 files changed, 81 insertions(+), 39 deletions(-) delete mode 100644 aio/src/assets/images/logos/github-icon.svg delete mode 100644 aio/src/assets/images/logos/twitter-icon.svg diff --git a/aio/scripts/_payload-limits.json b/aio/scripts/_payload-limits.json index 8ea659c881..37ac38652b 100755 --- a/aio/scripts/_payload-limits.json +++ b/aio/scripts/_payload-limits.json @@ -3,7 +3,7 @@ "master": { "uncompressed": { "runtime": 3881, - "main": 499953, + "main": 502188, "polyfills": 53926 } } diff --git a/aio/src/app/app.component.html b/aio/src/app/app.component.html index b03d3c44cd..68402de045 100644 --- a/aio/src/app/app.component.html +++ b/aio/src/app/app.component.html @@ -29,10 +29,12 @@
- - - - + + + + + +
diff --git a/aio/src/app/app.module.ts b/aio/src/app/app.module.ts index 4ff6a8ff82..2aebb6f4e1 100644 --- a/aio/src/app/app.module.ts +++ b/aio/src/app/app.module.ts @@ -104,6 +104,41 @@ export const svgIconProviders = [ }, multi: true, }, + + // Namespace: logos + { + provide: SVG_ICONS, + useValue: { + namespace: 'logos', + name: 'github', + svgSource: + '' + + '' + + '', + }, + multi: true, + }, + { + provide: SVG_ICONS, + useValue: { + namespace: 'logos', + name: 'twitter', + svgSource: + '' + + '' + + '', + }, + multi: true, + }, ]; // tslint:enable: max-line-length diff --git a/aio/src/app/shared/custom-icon-registry.spec.ts b/aio/src/app/shared/custom-icon-registry.spec.ts index 6cfdbc4b4c..77e6233bb9 100644 --- a/aio/src/app/shared/custom-icon-registry.spec.ts +++ b/aio/src/app/shared/custom-icon-registry.spec.ts @@ -17,6 +17,27 @@ describe('CustomIconRegistry', () => { expect(svgElement).toEqual(createSvg(svgSrc)); }); + it('should support caching icons with a namespace', () => { + const mockHttp: any = {}; + const mockSanitizer: any = {}; + const mockDocument: any = {}; + + const svgSrc1 = ''; + const svgSrc2 = ''; + const svgSrc3 = ''; + const svgIcons: SvgIconInfo[] = [ + { name: 'test_icon', svgSource: svgSrc1 }, + { namespace: 'foo', name: 'test_icon', svgSource: svgSrc2 }, + { namespace: 'bar', name: 'test_icon', svgSource: svgSrc3 }, + ]; + + const registry = new CustomIconRegistry(mockHttp, mockSanitizer, mockDocument, svgIcons); + let svgElement: SVGElement|undefined; + registry.getNamedSvgIcon('test_icon', 'foo').subscribe(el => svgElement = el); + + expect(svgElement).toEqual(createSvg(svgSrc2)); + }); + it('should call through to the MdIconRegistry if the icon name is not in the preloaded cache', () => { const mockHttp: any = {}; const mockSanitizer: any = {}; diff --git a/aio/src/app/shared/custom-icon-registry.ts b/aio/src/app/shared/custom-icon-registry.ts index 2e3792b968..4ec9aa5e3a 100644 --- a/aio/src/app/shared/custom-icon-registry.ts +++ b/aio/src/app/shared/custom-icon-registry.ts @@ -20,21 +20,26 @@ import { DomSanitizer } from '@angular/platform-browser'; */ export const SVG_ICONS = new InjectionToken>('SvgIcons'); export interface SvgIconInfo { + namespace?: string; name: string; svgSource: string; } interface SvgIconMap { - [iconName: string]: SVGElement; + [namespace: string]: { + [iconName: string]: SVGElement; + }; } +const DEFAULT_NS = '$$default'; + /** * A custom replacement for Angular Material's `MdIconRegistry`, which allows * us to provide preloaded icon SVG sources. */ @Injectable() export class CustomIconRegistry extends MatIconRegistry { - private preloadedSvgElements: SvgIconMap = {}; + private preloadedSvgElements: SvgIconMap = {[DEFAULT_NS]: {}}; constructor(http: HttpClient, sanitizer: DomSanitizer, @Optional() @Inject(DOCUMENT) document, @Inject(SVG_ICONS) svgIcons: SvgIconInfo[]) { @@ -43,18 +48,24 @@ export class CustomIconRegistry extends MatIconRegistry { } getNamedSvgIcon(iconName: string, namespace?: string) { - if (this.preloadedSvgElements[iconName]) { - return of(this.preloadedSvgElements[iconName].cloneNode(true) as SVGElement); - } - return super.getNamedSvgIcon(iconName, namespace); + const nsIconMap = this.preloadedSvgElements[namespace || DEFAULT_NS]; + const preloadedElement = nsIconMap && nsIconMap[iconName]; + + return preloadedElement + ? of(preloadedElement.cloneNode(true) as SVGElement) + : super.getNamedSvgIcon(iconName, namespace); } private loadSvgElements(svgIcons: SvgIconInfo[]) { const div = document.createElement('DIV'); svgIcons.forEach(icon => { + const ns = icon.namespace || DEFAULT_NS; + const nsIconMap = this.preloadedSvgElements[ns] || (this.preloadedSvgElements[ns] = {}); + // SECURITY: the source for the SVG icons is provided in code by trusted developers div.innerHTML = icon.svgSource; - this.preloadedSvgElements[icon.name] = div.querySelector('svg')!; + + nsIconMap[icon.name] = div.querySelector('svg')!; }); } } diff --git a/aio/src/assets/images/logos/github-icon.svg b/aio/src/assets/images/logos/github-icon.svg deleted file mode 100644 index 6fe590f967..0000000000 --- a/aio/src/assets/images/logos/github-icon.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - diff --git a/aio/src/assets/images/logos/twitter-icon.svg b/aio/src/assets/images/logos/twitter-icon.svg deleted file mode 100644 index 4928d64d03..0000000000 --- a/aio/src/assets/images/logos/twitter-icon.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - -