From 6c7db82ccdf90f749a8d9bb5340b1ac2e1674525 Mon Sep 17 00:00:00 2001 From: squidfunk Date: Wed, 18 Dec 2019 11:47:52 +0100 Subject: [PATCH] Moved compression to separate worker + added JSX factory --- .../javascripts/extensions/jsx/index.ts | 84 +++++++++++ .../javascripts/modules/search/_/index.ts | 43 +++--- src/assets/javascripts/workers/index.ts | 24 ++++ .../javascripts/workers/packer/index.ts | 101 +++++++++++++ .../javascripts/workers/search/index.ts | 134 ++++++++++++++++++ tsconfig.json | 2 + webpack.config.ts | 20 ++- 7 files changed, 388 insertions(+), 20 deletions(-) create mode 100644 src/assets/javascripts/extensions/jsx/index.ts create mode 100644 src/assets/javascripts/workers/index.ts create mode 100644 src/assets/javascripts/workers/packer/index.ts create mode 100644 src/assets/javascripts/workers/search/index.ts diff --git a/src/assets/javascripts/extensions/jsx/index.ts b/src/assets/javascripts/extensions/jsx/index.ts new file mode 100644 index 000000000..df238d244 --- /dev/null +++ b/src/assets/javascripts/extensions/jsx/index.ts @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2016-2019 Martin Donath + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +/* ---------------------------------------------------------------------------- + * Types + * ------------------------------------------------------------------------- */ + + + +/* ---------------------------------------------------------------------------- + * Functions + * ------------------------------------------------------------------------- */ + +/** + * JSX factory + * + * @param tag - Tag name + * @param attributes - Properties + * @param children - Child elements + * + * @return Element + */ +export function h( + tag: string, + attributes: Record | null, + ...children: Array +) { + console.log(tag, attributes, children) + // const el = document.createElement(tag) + + // /* Set all properties */ + // if (attributes) + // Array.prototype.forEach.call(Object.keys(attributes), attr => { + // el.setAttribute(attr, attributes[attr]) + // }) + + // /* Iterate child nodes */ + // const iterateChildNodes = nodes => { + // Array.prototype.forEach.call(nodes, node => { + + // /* Directly append text content */ + // if (typeof node === "string" || + // typeof node === "number") { + // el.textContent += node + + // /* Recurse, if we got an array */ + // } else if (Array.isArray(node)) { + // iterateChildNodes(node) + + // /* Append raw HTML */ + // } else if (typeof node.__html !== "undefined") { + // el.innerHTML += node.__html + + // /* Append regular nodes */ + // } else if (node instanceof Node) { + // el.appendChild(node) + // } + // }) + // } + + // /* Iterate child nodes and return element */ + // iterateChildNodes(children) + // return el + return { tag } +} diff --git a/src/assets/javascripts/modules/search/_/index.ts b/src/assets/javascripts/modules/search/_/index.ts index 9a656bafa..7681fd3db 100644 --- a/src/assets/javascripts/modules/search/_/index.ts +++ b/src/assets/javascripts/modules/search/_/index.ts @@ -21,7 +21,6 @@ */ import * as lunr from "lunr" -import { compress, decompress } from "lz-string" import { SearchArticle, @@ -51,6 +50,16 @@ export interface SearchIndexDocument { text: string /* Document text */ } +/** + * Search index options + */ +export interface SearchIndexOptions { + pipeline: { + trimmer: boolean /* Add trimmer to pipeline */ + stopwords: boolean /* Add stopword filter to pipeline */ + } +} + /** * Search index * @@ -60,7 +69,8 @@ export interface SearchIndexDocument { export interface SearchIndex { config: SearchIndexConfig /* Search index configuration */ docs: SearchIndexDocument[] /* Search index documents */ - index?: object | string /* Pre-built or serialized index */ + options?: SearchIndexOptions /* Search index options */ + index?: object /* Prebuilt index */ } /* ------------------------------------------------------------------------- */ @@ -74,16 +84,16 @@ export interface SearchResult { } /* ---------------------------------------------------------------------------- - * Function types + * Data * ------------------------------------------------------------------------- */ /** - * Options + * Default options */ -interface Options { +const defaultOptions: SearchIndexOptions = { pipeline: { - trimmer: boolean /* Add trimmer to pipeline */ - stopwords: boolean /* Add stopword filter to pipeline */ + trimmer: true, + stopwords: true } } @@ -114,18 +124,19 @@ export class Search { * @param index - Search index * @param options - Options */ - public constructor({ docs, index }: SearchIndex, options: Options) { + public constructor({ docs, options, index }: SearchIndex) { this.documents = setupSearchDocumentMap(docs) /* If no index was given, create it */ if (typeof index === "undefined") { this.index = lunr(function() { + const { pipeline } = options || defaultOptions /* Remove stemmer, as it cripples search experience */ this.pipeline.reset() - if (options.pipeline.trimmer) + if (pipeline.trimmer) this.pipeline.add(lunr.trimmer) - if (options.pipeline.stopwords) + if (pipeline.stopwords) this.pipeline.add(lunr.stopWordFilter) /* Setup fields and reference */ @@ -138,10 +149,6 @@ export class Search { this.add(doc) }) - /* Serialized and compressed index */ - } else if (typeof index === "string") { - this.index = lunr.Index.load(JSON.parse(decompress(index))) - /* Prebuilt index */ } else { this.index = lunr.Index.load(index) @@ -192,11 +199,11 @@ export class Search { } /** - * Serialize and compress the index + * Serialize index * - * @return Serialized and compressed index + * @return JSON representation */ - public toString(): string { - return compress(JSON.stringify(this.index)) + public toJSON(): object { + return this.index.toJSON() } } diff --git a/src/assets/javascripts/workers/index.ts b/src/assets/javascripts/workers/index.ts new file mode 100644 index 000000000..22bd0bc2c --- /dev/null +++ b/src/assets/javascripts/workers/index.ts @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2016-2019 Martin Donath + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +export { PackerMessage, PackerMessageType } from "./packer" +export { SearchMessage, SearchMessageType } from "./search" diff --git a/src/assets/javascripts/workers/packer/index.ts b/src/assets/javascripts/workers/packer/index.ts new file mode 100644 index 000000000..81dd14937 --- /dev/null +++ b/src/assets/javascripts/workers/packer/index.ts @@ -0,0 +1,101 @@ + +/* + * Copyright (c) 2016-2019 Martin Donath + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +import { compress, decompress } from "lz-string" + +/* ---------------------------------------------------------------------------- + * Types + * ------------------------------------------------------------------------- */ + +/** + * Packer message type + */ +export const enum PackerMessageType { + STRING, /* String data */ + PACKED /* Packed data */ +} + +/* ------------------------------------------------------------------------- */ + +/** + * A message containing an unpacked string + */ +interface StringMessage { + type: PackerMessageType.STRING /* Message type */ + data: string /* Message data */ +} + +/** + * A message containing a packed string + */ +interface PackedMessage { + type: PackerMessageType.PACKED /* Message type */ + data: string /* Message data */ +} + +/* ------------------------------------------------------------------------- */ + +/** + * A message exchanged with the packer worker + */ +export type PackerMessage = + | StringMessage + | PackedMessage + +/* ---------------------------------------------------------------------------- + * Functions + * ------------------------------------------------------------------------- */ + +/** + * Message handler + * + * @param message - Source message + * + * @return Target message + */ +export function handler(message: PackerMessage): PackerMessage { + switch (message.type) { + + /* Pack an unpacked string */ + case PackerMessageType.STRING: + return { + type: PackerMessageType.PACKED, + data: compress(message.data) + } + + /* Unpack a packed string */ + case PackerMessageType.PACKED: + return { + type: PackerMessageType.STRING, + data: decompress(message.data) + } + } +} + +/* ---------------------------------------------------------------------------- + * Worker + * ------------------------------------------------------------------------- */ + +addEventListener("message", ev => { + postMessage(handler(ev.data)) +}) diff --git a/src/assets/javascripts/workers/search/index.ts b/src/assets/javascripts/workers/search/index.ts new file mode 100644 index 000000000..d6a56d5f7 --- /dev/null +++ b/src/assets/javascripts/workers/search/index.ts @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2016-2019 Martin Donath + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +import { Search, SearchIndex, SearchResult } from "../../modules" + +/* ---------------------------------------------------------------------------- + * Types + * ------------------------------------------------------------------------- */ + +/** + * Search message type + */ +export const enum SearchMessageType { + SETUP, /* Search index setup */ + DUMP, /* Search index dump */ + QUERY, /* Search query */ + RESULT /* Search results */ +} + +/* ------------------------------------------------------------------------- */ + +/** + * A message containing the data necessary to setup the search index + */ +interface SetupMessage { + type: SearchMessageType.SETUP /* Message type */ + data: SearchIndex /* Message data */ +} + +/** + * A message containing the a dump of the search index + */ +interface DumpMessage { + type: SearchMessageType.DUMP /* Message type */ + data: object /* Message data */ +} + +/** + * A message containing a search query + */ +interface QueryMessage { + type: SearchMessageType.QUERY /* Message type */ + data: string /* Message data */ +} + +/** + * A message containing results for a search query + */ +interface ResultMessage { + type: SearchMessageType.RESULT /* Message type */ + data: SearchResult[] /* Message data */ +} + +/* ------------------------------------------------------------------------- */ + +/** + * A message exchanged with the search worker + */ +export type SearchMessage = + | SetupMessage + | DumpMessage + | QueryMessage + | ResultMessage + +/* ---------------------------------------------------------------------------- + * Data + * ------------------------------------------------------------------------- */ + +/** + * Search index + */ +let index: Search + +/* ---------------------------------------------------------------------------- + * Functions + * ------------------------------------------------------------------------- */ + +/** + * Message handler + * + * @param message - Source message + * + * @return Target message + */ +export function handler(message: SearchMessage): SearchMessage { + switch (message.type) { + + /* Setup search index */ + case SearchMessageType.SETUP: + index = new Search(message.data) + return { + type: SearchMessageType.DUMP, + data: index.toJSON() + } + + /* Query search index */ + case SearchMessageType.QUERY: + return { + type: SearchMessageType.RESULT, + data: index.search(message.data) + } + + /* All other messages */ + default: + throw new TypeError("Invalid message type") + } +} + +/* ---------------------------------------------------------------------------- + * Worker + * ------------------------------------------------------------------------- */ + +addEventListener("message", ev => { + postMessage(handler(ev.data)) +}) diff --git a/tsconfig.json b/tsconfig.json index 7a076a1a4..78943fa0b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,8 @@ "declaration": false, "declarationMap": false, "downlevelIteration": true, + "jsx": "react", + "jsxFactory": "jsx.h", "lib": [ "dom", "es2017", diff --git a/webpack.config.ts b/webpack.config.ts index 92edd36e6..8c7b69937 100644 --- a/webpack.config.ts +++ b/webpack.config.ts @@ -21,7 +21,7 @@ */ import * as path from "path" -import { Configuration } from "webpack" +import { Configuration, ProvidePlugin } from "webpack" /* ---------------------------------------------------------------------------- * Helper functions @@ -98,7 +98,12 @@ export default (_env: never, args: Configuration): Configuration[] => ([ path: path.resolve(__dirname, "material/assets/javascripts"), filename: "bundle.js", libraryTarget: "window" - } + }, + plugins: [ + new ProvidePlugin({ + jsx: "src/assets/javascripts/extensions/jsx" + }) + ] }, /* Search worker */ @@ -110,5 +115,16 @@ export default (_env: never, args: Configuration): Configuration[] => ([ filename: "search.js", libraryTarget: "var" } + }, + + /* Packer worker */ + { + ...config(args), + entry: "src/assets/javascripts/workers/packer", + output: { + path: path.resolve(__dirname, "material/assets/javascripts"), + filename: "packer.js", + libraryTarget: "var" + } } ])