UNPKG

pages-cook

Version:

web-portals

169 lines (156 loc) 5.3 kB
import { ModulePrefetch } from './prefetch' import { ModuleManifest, ModuleConfig, Application } from '../types' import inject from './inject' class ModuleView extends ModulePrefetch { constructor (id: string, model: ModuleManifest, application: Application) { super(id, model, application) } public rigesterElement (name: string, element: HTMLElement) { this.elements[name] = element if (name === 'container' && element) { this.status.init = true } } private createSandbox (uri?: string, config?: ModuleConfig['sandbox']): HTMLIFrameElement { const sandbox = document.createElement('iframe') if (config !== undefined) sandbox.setAttribute('sandbox', config) if (uri) sandbox.src = uri return sandbox } private writeSandbox (sandbox: HTMLIFrameElement, container: HTMLElement) { sandbox.src = 'about:blank' sandbox.setAttribute('seamless', 'seamless') container.appendChild(sandbox) const contentDocument = sandbox.contentDocument const contentWindow = sandbox.contentWindow if (contentWindow && contentDocument) { contentDocument.open() contentDocument.write(this.config?.source?.html || '<head><meta charset="utf-8"></head>') contentDocument.close() inject(contentWindow?.window, this.config, this.application) } } private createShadowbox (render: (target: HTMLElement) => void): HTMLElement { const shadowbox = document.createElement('div') const shadowboxInner = document.createElement('div') shadowbox?.attachShadow({ mode: 'open' }) const shadowRoot = shadowbox.shadowRoot ? shadowbox.shadowRoot : shadowbox shadowRoot.appendChild(shadowboxInner) render(shadowboxInner) this.elements.container.appendChild(shadowbox) return shadowbox } private createPortals (uri: string): HTMLPortalElement { const portal = document.createElement('portal') as HTMLPortalElement portal.src = uri return portal } private createShadowView (): HTMLElement | HTMLPortalElement | HTMLIFrameElement | undefined { if (this.shadowView) return this.shadowView if (this.config.render) { return this.shadowView = this.createShadowbox(this.config.render) } if (this.rel !== 'module') return return this.shadowView = this.viewType === 'portal' ? this.createPortals(this.uri) : this.createSandbox(this.uri, this.config.sandbox) } private loadShadowView () { const container = this.elements.container const shadowView = this.shadowView if (!shadowView) return switch (this.viewType) { case 'portal': container.appendChild(shadowView as HTMLPortalElement) break case 'iframe': default: if (this.uri) { container.appendChild(shadowView) const contentWindow = (shadowView as HTMLIFrameElement).contentWindow if (contentWindow && this.sameOrigin) { inject(contentWindow?.window, this.config, this.application) contentWindow?.window.postMessage('container-ready', '*') } } else { this.writeSandbox(shadowView as HTMLIFrameElement, container) } break } } public create (prepare = true): Promise<Event | string> { this.unfate() this.timeTick() if (this.status.prerender) { return Promise.resolve('prerender') } return new Promise((resolve, reject) => { this.createTime = Date.now() const shadowView = this.createShadowView() if (!shadowView) { return resolve('null') } if (this.viewType === 'shadow') { return resolve('shadow') } shadowView.style.cssText = ` position: absolute; z-index: 0; width: 100%; height: 100%; border: 0; transform: translate3d(0, 0, 0); ` shadowView.onload = (e) => { this.status.prerender = true this.events?.load.bind(this)() resolve(e) } shadowView.onerror = (e) => { this.events?.loadError.bind(this)() reject(e) } this.loadShadowView() if (prepare === false || this.config.animation === false) { return resolve('instant') } setTimeout(() => { resolve('timeout') }, this.status.preload ? 800 : 200) }) } public preload (): Promise<Event> { return new Promise((resolve, reject) => { const uri = this.uri const head = document.head const sandbox = this.createSandbox('', '') sandbox.style.display = 'none' sandbox.onload = (e) => { this.status.preload = true head.removeChild(sandbox) this.events?.preload.bind(this)() resolve(e) } sandbox.onerror = (e) => { head.removeChild(sandbox) reject(e) } if (uri) { sandbox.src = this.uri head.appendChild(sandbox) } else { this.writeSandbox(sandbox, head) } }) } public prerender (): Promise<Event | void> { if (this.status.preload || this.status.prerender) { return Promise.resolve() } return new Promise((resolve, reject) => { this.preload().then(resolve).catch(reject) this.beforehandLink(this.uri, 'prerender', 'document') }) } } export { ModuleView }