Added observable for web worker communication

This commit is contained in:
squidfunk 2019-12-17 15:59:13 +01:00
parent 862982a69d
commit 4e4e086af7
13 changed files with 208 additions and 61 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -197,7 +197,7 @@
{% endblock %} {% endblock %}
</div> </div>
{% block scripts %} {% block scripts %}
<script src="{{ 'assets/javascripts/app.js' | url }}"></script> <script src="{{ 'assets/javascripts/bundle.js' | url }}"></script>
{% if lang.t("search.language") != "en" %} {% if lang.t("search.language") != "en" %}
{% set languages = lang.t("search.language").split(",") %} {% set languages = lang.t("search.language").split(",") %}
{% if languages | length and languages[0] != "" %} {% if languages | length and languages[0] != "" %}
@ -235,7 +235,7 @@
{%- endfor -%} {%- endfor -%}
{{ translations | tojson }} {{ translations | tojson }}
</script> </script>
<script>app=initialize({base:"{{ base_url }}"})</script> <script>app=initialize({base:"{{ base_url }}",search:"{{ 'assets/javascripts/search.js' | url }}"})</script>
{% for path in config["extra_javascript"] %} {% for path in config["extra_javascript"] %}
<script src="{{ path | url }}"></script> <script src="{{ path | url }}"></script>
{% endfor %} {% endfor %}

View File

@ -68,8 +68,7 @@
"tslint-sonarts": "^1.9.0", "tslint-sonarts": "^1.9.0",
"typescript": "^3.6.3", "typescript": "^3.6.3",
"webpack": "^4.41.0", "webpack": "^4.41.0",
"webpack-cli": "^3.3.9", "webpack-cli": "^3.3.9"
"worker-loader": "^2.0.0"
}, },
"engines": { "engines": {
"node": ">= 10" "node": ">= 10"

View File

@ -29,6 +29,7 @@
*/ */
export interface Config { export interface Config {
base: string /* Base URL */ base: string /* Base URL */
search: string /* Web worker URL */
} }
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------

View File

@ -0,0 +1,96 @@
/*
* Copyright (c) 2016-2019 Martin Donath <martin.donath@squidfunk.com>
*
* 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 { Observable, fromEvent } from "rxjs"
import {
pluck,
shareReplay,
switchMap,
take,
tap,
throttle
} from "rxjs/operators"
/* ----------------------------------------------------------------------------
* Types
* ------------------------------------------------------------------------- */
/**
* Worker message
*/
export interface WorkerMessage {
type: unknown /* Message type */
data: unknown /* Message data */
}
/* ----------------------------------------------------------------------------
* Function types
* ------------------------------------------------------------------------- */
/**
* Options
*
* @template T - Worker message type
*/
interface Options<T extends WorkerMessage> {
message$: Observable<T> /* Message observable */
}
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
/**
* Watch a web worker
*
* This function returns an observable that will send all values emitted by the
* message observable to the web worker. Web worker communication is expected
* to be bidirectional (request-response) and synchronous. Messages that are
* emitted during a pending request are throttled, the last one is emitted.
*
* @param worker - Web worker
*
* @return Worker message observable
*/
export function watchWorker<T extends WorkerMessage>(
worker: Worker, { message$ }: Options<T>
): Observable<T> {
/* Receive messages from web worker */
const worker$ = fromEvent(worker, "message")
.pipe(
pluck<Event, T>("data")
)
/* Send request and wait for response */
return message$
.pipe(
throttle(() => worker$, { leading: true, trailing: true }),
tap(message => worker.postMessage(message)),
switchMap(() => worker$
.pipe(
take(1)
)
),
shareReplay(1)
)
}

View File

@ -395,7 +395,7 @@
<!-- Theme-related JavaScript --> <!-- Theme-related JavaScript -->
{% block scripts %} {% block scripts %}
<script src="{{ 'assets/javascripts/app.js' | url }}"></script> <script src="{{ 'assets/javascripts/bundle.js' | url }}"></script>
<!-- Load additional languages for search --> <!-- Load additional languages for search -->
{% if lang.t("search.language") != "en" %} {% if lang.t("search.language") != "en" %}
@ -442,7 +442,8 @@
<!-- Application initialization --> <!-- Application initialization -->
<script> <script>
app = initialize({ app = initialize({
base: "{{ base_url }}" base: "{{ base_url }}",
search: "{{ 'assets/javascripts/search.js' | url }}"
}); });
</script> </script>

View File

@ -8,7 +8,8 @@
"lib": [ "lib": [
"dom", "dom",
"es2017", "es2017",
"esnext" "esnext",
"webworker"
], ],
"module": "commonjs", "module": "commonjs",
"moduleResolution": "node", "moduleResolution": "node",

View File

@ -21,28 +21,23 @@
*/ */
import * as path from "path" import * as path from "path"
// import { Options } from "ts-loader"
import { Configuration } from "webpack" import { Configuration } from "webpack"
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Configuration * Helper functions
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
/** /**
* Webpack configuration * Webpack base configuration
* *
* @param env - Webpack environment arguments
* @param args - Command-line arguments * @param args - Command-line arguments
* *
* @return Webpack configuration * @return Webpack configuration
*/ */
export default (_env: never, args: Configuration) => { function config(args: Configuration): Configuration {
return { return {
mode: args.mode, mode: args.mode,
/* Entrypoint */
entry: ["src/assets/javascripts/index.ts"],
/* Loaders */ /* Loaders */
module: { module: {
rules: [ rules: [
@ -57,45 +52,16 @@ export default (_env: never, args: Configuration) => {
transpileOnly: true, transpileOnly: true,
compilerOptions: { compilerOptions: {
module: "esnext", module: "esnext",
noUnusedLocals: args.mode === "production",
noUnusedParameters: args.mode === "production",
removeComments: false removeComments: false
} }
} }
} }
], ],
exclude: /\/node_modules\// exclude: /\/node_modules\//
},
{
test: /\worker\/(.*?)\.ts$/,
use: [
{ loader: "worker-loader", options: {
inline: true, fallback: false } },
{
loader: "ts-loader",
options: {
transpileOnly: true,
compilerOptions: {
module: "esnext",
noUnusedLocals: args.mode === "production",
noUnusedParameters: args.mode === "production", // TODO: do not duplicate
removeComments: false
}
}
}
]
} }
] ]
}, },
/* Export class constructor as entrypoint */
output: {
path: path.resolve(__dirname, "material/assets/javascripts"),
filename: "app.js",
libraryTarget: "window"
},
/* Module resolver */ /* Module resolver */
resolve: { resolve: {
modules: [ modules: [
@ -109,3 +75,40 @@ export default (_env: never, args: Configuration) => {
devtool: "source-map" devtool: "source-map"
} }
} }
/* ----------------------------------------------------------------------------
* Configuration
* ------------------------------------------------------------------------- */
/**
* Webpack configuration
*
* @param env - Webpack environment arguments
* @param args - Command-line arguments
*
* @return Webpack configuration
*/
export default (_env: never, args: Configuration): Configuration[] => ([
/* Application */
{
...config(args),
entry: "src/assets/javascripts",
output: {
path: path.resolve(__dirname, "material/assets/javascripts"),
filename: "bundle.js",
libraryTarget: "window"
}
},
/* Search worker */
{
...config(args),
entry: "src/assets/javascripts/workers/search",
output: {
path: path.resolve(__dirname, "material/assets/javascripts"),
filename: "search.js",
libraryTarget: "var"
}
}
])