pages-cook
Version:
web-portals
169 lines (156 loc) • 5.3 kB
text/typescript
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
}