UNPKG

@paperbits/common

Version:
144 lines (112 loc) 5.93 kB
import * as Utils from "@paperbits/common"; import { MimeTypes } from "@paperbits/common"; import { Attributes } from "@paperbits/common/html"; import { ISettingsProvider } from "../configuration"; import { HtmlDocumentProvider } from "./htmlDocumentProvider"; import { HtmlPage } from "./htmlPage"; import { HtmlPageOptimizer } from "./htmlPageOptimizer"; import { HtmlPagePublisherPlugin } from "./htmlPagePublisherPlugin"; import { SourceLink } from "./sourceLink"; export class HtmlPagePublisher { private subresourceIntegrityEnabled: boolean; private staticAssetSuffix: string; constructor( private readonly htmlDocumentProvider: HtmlDocumentProvider, private readonly htmlPagePublisherPlugins: HtmlPagePublisherPlugin[], private readonly htmlPageOptimizer: HtmlPageOptimizer, private readonly settingsProvider: ISettingsProvider ) { } private initialize = Utils.debounce(this.loadSettings.bind(this)); private async loadSettings(): Promise<void> { this.subresourceIntegrityEnabled = !!await this.settingsProvider.getSetting<boolean>("features/subresourceIntegrity"); this.staticAssetSuffix = await this.settingsProvider.getSetting("staticAssetSuffix"); } private scaffoldPageDocument(document: Document): void { const charsetMetaElement: HTMLMetaElement = document.createElement("meta"); charsetMetaElement.setAttribute(Attributes.Charset, "utf-8"); document.head.appendChild(charsetMetaElement); this.appendMetaTag(document, "viewport", "width=device-width,minimum-scale=1,initial-scale=1"); this.appendStyleLink(document, { src: Utils.appendSuffixToFileName(`/styles/theme.css`, this.staticAssetSuffix) }); this.appendScriptLink(document, { src: Utils.appendSuffixToFileName(`/scripts/theme.js`, this.staticAssetSuffix) }); } private appendMetaTag(document: Document, name: string, content: string): void { const element: HTMLMetaElement = document.createElement("meta"); element.setAttribute("name", name); element.setAttribute("content", content); document.head.appendChild(element); } private appendStyleLink(document: Document, stylesheetLink: SourceLink): void { const element: HTMLStyleElement = document.createElement("link"); element.setAttribute(Attributes.Href, stylesheetLink.src); if (this.subresourceIntegrityEnabled && stylesheetLink.integrity) { element.setAttribute(Attributes.Integrity, stylesheetLink.integrity); } element.setAttribute(Attributes.Rel, "stylesheet"); element.setAttribute(Attributes.Type, MimeTypes.textCss); document.head.appendChild(element); } private appendScriptLink(document: Document, scriptLink: SourceLink): void { const element: HTMLScriptElement = document.createElement("script"); element.setAttribute(Attributes.Src, scriptLink.src); if (this.subresourceIntegrityEnabled && scriptLink.integrity) { element.setAttribute(Attributes.Integrity, scriptLink.integrity); } element.setAttribute(Attributes.Type, MimeTypes.textJs); document.head.appendChild(element); } private appendFaviconLink(document: Document, permalink: string): void { const faviconLinkElement = document.createElement("link"); faviconLinkElement.setAttribute(Attributes.Rel, "shortcut icon"); faviconLinkElement.setAttribute(Attributes.Href, permalink); document.head.insertAdjacentElement("afterbegin", faviconLinkElement); } public async renderHtml(page: HtmlPage, overridePlugins?: HtmlPagePublisherPlugin[]): Promise<string> { try { await this.initialize(); const document = this.htmlDocumentProvider.createDocument(); this.scaffoldPageDocument(document); document.title = page.title; if (page.faviconPermalink) { this.appendFaviconLink(document, page.faviconPermalink); } if (page.description) { this.appendMetaTag(document, "description", page.description); } if (page.keywords) { this.appendMetaTag(document, "keywords", page.keywords); } if (page.author) { this.appendMetaTag(document, "author", page.author); } if (page.locale) { document.documentElement.setAttribute(Attributes.Language, page.locale.code.split("-")[0]); if (page.locale.direction) { document.documentElement.setAttribute(Attributes.Direction, page.locale.direction); } } else { document.documentElement.setAttribute(Attributes.Language, "en"); // TODO: quick hack } page.styleReferences.forEach(reference => { this.appendStyleLink(document, reference); }); if (overridePlugins) { for (const plugin of overridePlugins) { await plugin.apply(document, page); } } else { for (const plugin of this.htmlPagePublisherPlugins) { await plugin.apply(document, page); } } const htmlContent = "<!DOCTYPE html>" + document.documentElement.outerHTML; document.defaultView.window.close(); const optimizedHtmlContent = await this.htmlPageOptimizer.optimize(htmlContent); return optimizedHtmlContent; } catch (error) { throw new Error(`Unable to render page: ${error.stack}`); } } }