From cdcafe9e6f5e025288bb1a57d92ada1a699625d4 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 24 Aug 2019 12:48:57 -0700 Subject: [PATCH] Run markdown in web worker (#3524) * Run markdown in web worker * Set global object --- build-scripts/webpack.js | 6 ++ package.json | 3 +- src/components/ha-markdown.js | 96 -------------------------------- src/components/ha-markdown.ts | 64 +++++++++++++++++++++ src/resources/load_markdown.js | 5 -- src/resources/markdown_worker.ts | 14 +++++ yarn.lock | 9 ++- 7 files changed, 94 insertions(+), 103 deletions(-) delete mode 100644 src/components/ha-markdown.js create mode 100644 src/components/ha-markdown.ts delete mode 100644 src/resources/load_markdown.js create mode 100644 src/resources/markdown_worker.ts diff --git a/build-scripts/webpack.js b/build-scripts/webpack.js index f41b318f2c..fa9bfb9edd 100644 --- a/build-scripts/webpack.js +++ b/build-scripts/webpack.js @@ -170,6 +170,8 @@ const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => { chunkFilename: genChunkFilename(isProdBuild, isStatsBuild), path: latestBuild ? paths.output : paths.output_es5, publicPath: latestBuild ? "/frontend_latest/" : "/frontend_es5/", + // For workerize loader + globalObject: "self", }, resolve, }; @@ -210,6 +212,8 @@ const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => { latestBuild ? "frontend_latest" : "frontend_es5" ), publicPath: latestBuild ? "/frontend_latest/" : "/frontend_es5/", + // For workerize loader + globalObject: "self", }, }; }; @@ -255,6 +259,8 @@ const createCastConfig = ({ isProdBuild, latestBuild }) => { latestBuild ? "frontend_latest" : "frontend_es5" ), publicPath: latestBuild ? "/frontend_latest/" : "/frontend_es5/", + // For workerize loader + globalObject: "self", }, }; }; diff --git a/package.json b/package.json index e71313cfcf..3dc0486a28 100644 --- a/package.json +++ b/package.json @@ -170,7 +170,8 @@ "webpack-cli": "^3.3.0", "webpack-dev-server": "^3.2.1", "webpack-manifest-plugin": "^2.0.4", - "workbox-webpack-plugin": "^4.1.1" + "workbox-webpack-plugin": "^4.1.1", + "workerize-loader": "^1.1.0" }, "_comment": "Polymer fixed to 3.1 because 3.2 throws on logbook page", "_comment_2": "Fix in https://github.com/Polymer/polymer/pull/5569", diff --git a/src/components/ha-markdown.js b/src/components/ha-markdown.js deleted file mode 100644 index 02c31e84d9..0000000000 --- a/src/components/ha-markdown.js +++ /dev/null @@ -1,96 +0,0 @@ -import { PolymerElement } from "@polymer/polymer/polymer-element"; -import { EventsMixin } from "../mixins/events-mixin"; - -let loaded = null; - -const tagWhiteList = ["svg", "path", "ha-icon"]; - -/* - * @appliesMixin EventsMixin - */ -class HaMarkdown extends EventsMixin(PolymerElement) { - static get properties() { - return { - content: { - observer: "_render", - type: String, - }, - }; - } - - connectedCallback() { - super.connectedCallback(); - // 0 = not loaded, 1 = success, 2 = error - this._scriptLoaded = 0; - this._renderScheduled = false; - this._resize = () => this.fire("iron-resize"); - - if (!loaded) { - loaded = import(/* webpackChunkName: "load_markdown" */ "../resources/load_markdown"); - } - loaded - .then( - ({ marked, filterXSS }) => { - this.marked = marked; - this.filterXSS = filterXSS; - this._scriptLoaded = 1; - }, - () => { - this._scriptLoaded = 2; - } - ) - .then(() => this._render()); - } - - _render() { - if (this._scriptLoaded === 0 || this._renderScheduled) { - return; - } - - this._renderScheduled = true; - - // debounce it to next microtask. - Promise.resolve().then(() => { - this._renderScheduled = false; - - if (this._scriptLoaded === 1) { - this.innerHTML = this.filterXSS( - this.marked(this.content, { - breaks: true, - gfm: true, - tables: true, - }), - { - onIgnoreTag: (tag, html) => - tagWhiteList.indexOf(tag) >= 0 ? html : null, - } - ); - this._resize(); - - const walker = document.createTreeWalker( - this, - 1 /* SHOW_ELEMENT */, - null, - false - ); - - while (walker.nextNode()) { - const node = walker.currentNode; - - // Open external links in a new window - if (node.tagName === "A" && node.host !== document.location.host) { - node.target = "_blank"; - - // Fire a resize event when images loaded to notify content resized - } else if (node.tagName === "IMG") { - node.addEventListener("load", this._resize); - } - } - } else if (this._scriptLoaded === 2) { - this.innerText = this.content; - } - }); - } -} - -customElements.define("ha-markdown", HaMarkdown); diff --git a/src/components/ha-markdown.ts b/src/components/ha-markdown.ts new file mode 100644 index 0000000000..41d5e08b90 --- /dev/null +++ b/src/components/ha-markdown.ts @@ -0,0 +1,64 @@ +import { UpdatingElement, property, customElement } from "lit-element"; +// eslint-disable-next-line import/no-webpack-loader-syntax +// @ts-ignore +// tslint:disable-next-line: no-implicit-dependencies +import markdownWorker from "workerize-loader!../resources/markdown_worker"; +import { fireEvent } from "../common/dom/fire_event"; + +let worker: any | undefined; + +@customElement("ha-markdown") +class HaMarkdown extends UpdatingElement { + @property() public content = ""; + + protected update(changedProps) { + super.update(changedProps); + + if (!worker) { + worker = markdownWorker(); + } + + this._render(); + } + + private async _render() { + this.innerHTML = await worker.renderMarkdown(this.content, { + breaks: true, + gfm: true, + tables: true, + }); + + this._resize(); + + const walker = document.createTreeWalker( + this, + 1 /* SHOW_ELEMENT */, + null, + false + ); + + while (walker.nextNode()) { + const node = walker.currentNode; + + // Open external links in a new window + if ( + node.nodeName === "A" && + (node as HTMLAnchorElement).host !== document.location.host + ) { + (node as HTMLAnchorElement).target = "_blank"; + + // Fire a resize event when images loaded to notify content resized + } else if (node.nodeName === "IMG") { + node.addEventListener("load", this._resize); + } + } + } + + private _resize = () => fireEvent(this, "iron-resize"); +} + +declare global { + interface HTMLElementTagNameMap { + "ha-markdown": HaMarkdown; + } +} diff --git a/src/resources/load_markdown.js b/src/resources/load_markdown.js deleted file mode 100644 index 57a0a8b4ba..0000000000 --- a/src/resources/load_markdown.js +++ /dev/null @@ -1,5 +0,0 @@ -import marked_ from "marked"; -import filterXSS_ from "xss"; - -export const marked = marked_; -export const filterXSS = filterXSS_; diff --git a/src/resources/markdown_worker.ts b/src/resources/markdown_worker.ts new file mode 100644 index 0000000000..151efbe79f --- /dev/null +++ b/src/resources/markdown_worker.ts @@ -0,0 +1,14 @@ +import marked from "marked"; +// @ts-ignore +import filterXSS from "xss"; + +export const renderMarkdown = async ( + content: string, + markedOptions: object +) => { + return filterXSS(marked(content, markedOptions), { + onIgnoreTag(tag, html) { + return ["svg", "path", "ha-icon"].indexOf(tag) !== -1 ? html : null; + }, + }); +}; diff --git a/yarn.lock b/yarn.lock index efc1eb942f..1e048b582a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8639,7 +8639,7 @@ loader-utils@^0.2.16: json5 "^0.5.0" object-assign "^4.0.1" -loader-utils@^1.0.2, loader-utils@^1.1.0: +loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA== @@ -14362,6 +14362,13 @@ worker-farm@^1.5.2: dependencies: errno "~0.1.7" +workerize-loader@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/workerize-loader/-/workerize-loader-1.1.0.tgz#d3a634390dcb685cc1ee292cd1fffeef0a646044" + integrity sha512-cU2jPVE3AzzVxOonBe9lCCO//qwE9s/K4a9njFVRLueznzNDNND5vGHVorGuzK6xvamdDOZ9+g7CPIc7QKzucQ== + dependencies: + loader-utils "^1.2.3" + wrap-ansi@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"