From aa093a10ef884cd400aae7c33782e35a6ecf793b Mon Sep 17 00:00:00 2001 From: Andrew Mitchell Date: Thu, 4 May 2017 16:03:14 -0500 Subject: [PATCH] feat(util): add webcomponentsReady function to help bootstrap apps --- README.md | 31 +++++---------- bower.json | 3 +- demo/bower.json | 1 + demo/src/index.html | 6 --- demo/src/main.ts | 15 +++----- docs/importing-elements.md | 12 ------ docs/polymer-templates.md | 6 --- docs/production-build.md | 6 --- src/origami.ts | 1 + src/util/webcomponents.spec.ts | 20 ++++++++++ src/util/webcomponents.ts | 70 ++++++++++++++++++++++++++++++++++ 11 files changed, 108 insertions(+), 63 deletions(-) create mode 100644 src/util/webcomponents.spec.ts create mode 100644 src/util/webcomponents.ts diff --git a/README.md b/README.md index 4ae1ce4..ef0686e 100644 --- a/README.md +++ b/README.md @@ -79,14 +79,15 @@ Make sure bower components are installed to a directory that is included in the Next install Polymer and any other custom elements. ``` -$ bower install --save Polymer/polymer#2.0.0-rc.5 +$ bower install --save Polymer/polymer#^2.0.0-rc.7 +$ bower install --save webcomponents/webcomponentsjs#^1.0.0-rc.11 ``` Projects should add the `bower_components/` directory to their `.gitignore` file. ### Polyfills -When targeting browsers that do not natively support WebComponents, polyfills are required. The app must wait for the `WebComponentsReady` event before bootstrapping. +When targeting browsers that do not natively support WebComponents, polyfills are required. The app must wait for the polyfills before bootstrapping. Origami recommends using the `webcomponents-loader.js` polyfill. This script will check for native browser support before loading the required polyfills. @@ -96,20 +97,10 @@ index.html Paper Crane - Loading... - ``` @@ -117,18 +108,14 @@ index.html main.ts ```ts import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { webcomponentsReady } from '@codebakery/origami'; -function bootstrap() { +webcomponentsReady().then(() => { platformBrowserDynamic().bootstrapModule(AppModule); -} - -if ((window).webComponentsReady) { - // Polyfills not needed - bootstrap(); -} else { - // Wait for polyfills before bootstrapping - window.addEventListener('WebComponentsReady', bootstrap); -} +}).catch(error => { + // No WebComponent support and webcomponentsjs is not loaded + console.error(error); +}); ``` ### Templates diff --git a/bower.json b/bower.json index a1d626d..cd0959e 100644 --- a/bower.json +++ b/bower.json @@ -8,7 +8,8 @@ "homepage": "https://github.com/hotforfeature/origami", "private": true, "dependencies": { - "polymer": "Polymer/polymer#^2.0.0-rc.6", + "polymer": "Polymer/polymer#^2.0.0-rc.7", + "webcomponentsjs": "webcomponents/webcomponentsjs#^1.0.0-rc.11", "paper-input": "PolymerElements/paper-input#2.0-preview", "paper-checkbox": "PolymerElements/paper-checkbox#2.0-preview", "iron-selector": "PolymerElements/iron-selector#2.0-preview" diff --git a/demo/bower.json b/demo/bower.json index be1d008..3418f2e 100644 --- a/demo/bower.json +++ b/demo/bower.json @@ -2,6 +2,7 @@ "name": "origami-demo", "dependencies": { "polymer": "Polymer/polymer#^2.0.0-rc.5", + "webcomponentsjs": "webcomponents/webcomponentsjs#^1.0.0-rc.11", "paper-tabs": "PolymerElements/paper-tabs#2.0-preview", "app-layout": "PolymerElements/app-layout#2.0-preview", "paper-styles": "PolymerElements/paper-styles#2.0-preview", diff --git a/demo/src/index.html b/demo/src/index.html index 74538a5..8d1c4df 100644 --- a/demo/src/index.html +++ b/demo/src/index.html @@ -15,11 +15,5 @@ Loading... - diff --git a/demo/src/main.ts b/demo/src/main.ts index b16ef0a..c609cfa 100644 --- a/demo/src/main.ts +++ b/demo/src/main.ts @@ -1,5 +1,6 @@ import { enableProdMode } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { webcomponentsReady } from './origami/origami'; import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; @@ -8,14 +9,8 @@ if (environment.production) { enableProdMode(); } -function bootstrap() { +webcomponentsReady().then(() => { platformBrowserDynamic().bootstrapModule(AppModule); -} - -if ((window).webComponentsReady) { - // Polyfills not needed - bootstrap(); -} else { - // Wait for polyfills before bootstrapping - window.addEventListener('WebComponentsReady', bootstrap); -} +}).catch(error => { + console.error(error); +}); diff --git a/docs/importing-elements.md b/docs/importing-elements.md index 9f2c8de..5c892dc 100644 --- a/docs/importing-elements.md +++ b/docs/importing-elements.md @@ -17,12 +17,6 @@ index.html Loading... - ``` @@ -55,12 +49,6 @@ index.html Loading... - ``` diff --git a/docs/polymer-templates.md b/docs/polymer-templates.md index fe6ddb4..677bbb2 100644 --- a/docs/polymer-templates.md +++ b/docs/polymer-templates.md @@ -81,12 +81,6 @@ index.html Loading... - ``` diff --git a/docs/production-build.md b/docs/production-build.md index ae882fe..5a7c99c 100644 --- a/docs/production-build.md +++ b/docs/production-build.md @@ -61,12 +61,6 @@ index.html Loading... - ``` diff --git a/src/origami.ts b/src/origami.ts index 76195d2..bb9aca3 100644 --- a/src/origami.ts +++ b/src/origami.ts @@ -7,3 +7,4 @@ export * from './style/custom-style.service'; export * from './templates/polymer-template.directive'; export * from './util/customElements'; export * from './util/Polymer'; +export * from './util/webcomponents'; diff --git a/src/util/webcomponents.spec.ts b/src/util/webcomponents.spec.ts new file mode 100644 index 0000000..e8b2769 --- /dev/null +++ b/src/util/webcomponents.spec.ts @@ -0,0 +1,20 @@ +import {} from 'jasmine'; + +import { webcomponentsReady, webcomponentsSupported } from './webcomponents'; + +describe('webcomponentsReady', () => { + it('should return a Promise', () => { + expect(webcomponentsReady()).toEqual(jasmine.any(Promise)); + }); + + it('should not create multiple Promises', () => { + const result = webcomponentsReady(); + expect(webcomponentsReady()).toBe(result); + }); +}); + +describe('webcomponentsSupported', () => { + it('should return true in test environment', () => { + expect(webcomponentsSupported()).toBe(true); + }); +}); diff --git a/src/util/webcomponents.ts b/src/util/webcomponents.ts new file mode 100644 index 0000000..6d4b1da --- /dev/null +++ b/src/util/webcomponents.ts @@ -0,0 +1,70 @@ +declare global { + interface Window { + Promise: PromiseConstructor; + WebComponents: { + ready: boolean; + }; + } +} + +export function webcomponentsSupported(): boolean { + // HTML imports + /* istanbul ignore if */ + if (!('import' in document.createElement('link'))) { + return false; + } + + // Shadow DOM + /* istanbul ignore if */ + if (!('attachShadow' in Element.prototype && 'getRootNode' in Element.prototype)) { + return false; + } + + // Custom elements + /* istanbul ignore if */ + if (!window.customElements) { + return false; + } + + // Templates + /* istanbul ignore if */ + if (!('content' in document.createElement('template')) || + // Edge has broken fragment cloning which means you cannot clone template.content + !(document.createDocumentFragment().cloneNode() instanceof DocumentFragment)) { + return false; + } + + return true; +} + +// webcomponents-lite.js will override Promise. Angular provides its own Promise polyfill, so we +// want to make sure we keep that. +const OriginalPromise = window.Promise; // tslint:disable-line:variable-name +let readyPromise: Promise; + +export function webcomponentsReady(): Promise { + if (!readyPromise) { + /* istanbul ignore next */ + readyPromise = new Promise((resolve, reject) => { + if (window.WebComponents) { + if (window.WebComponents.ready) { + window.Promise = OriginalPromise; + resolve(); + } else { + // tslint:disable-next-line:only-arrow-functions + document.addEventListener('WebComponentsReady', function onready() { + window.Promise = OriginalPromise; + resolve(); + document.removeEventListener('WebComponentsReady', onready); + }); + } + } else if (webcomponentsSupported()) { + resolve(); + } else { + reject(new Error('WebComponent support or polyfills are not present')); + } + }); + } + + return readyPromise; +}