Prepare JS lib

This commit is contained in:
Maximilian Ammann 2022-09-08 18:08:47 +02:00
parent ea52e9c96d
commit 1f3423a70c
10 changed files with 183 additions and 153 deletions

View File

@ -1,15 +1,8 @@
[target.wasm32-unknown-unknown]
rustflags = [
# Enabled unstable APIs from web_sys
"--cfg=web_sys_unstable_apis",
# Enables features which are required for shared-memory
"-C", "target-feature=+atomics,+bulk-memory,+mutable-globals",
# Enables the possibility to import memory into wasm.
# Without --shared-memory it is not possible to use shared WebAssembly.Memory.
"-C", "link-args=--shared-memory --import-memory",
"--cfg=web_sys_unstable_apis"
]
runner = 'wasm-bindgen-test-runner'
[profile.wasm-dev]
inherits = "dev"

View File

@ -11,7 +11,7 @@ module it can resolve WebAssembly files or WebWorkers dynamically.
The following syntax is used to resolve referenced WebWorkers:
```ts
new Worker(new URL("./pool.worker.ts", import.meta.url), {
new Worker(new URL("./multithreaded-pool.worker.ts", import.meta.url), {
type: 'module'
});
```
@ -107,7 +107,7 @@ See config in `web/lib/build.mjs` for an example usage.
### Babel & TypeScript
Babel and TypeScript both can produce ESM modules, but they **fail with transforming references within the source code**
like `new URL("./pool.worker.ts", import.meta.url)`. There exist some Babel plugins, but none of them is stable.
like `new URL("./multithreaded-pool.worker.ts", import.meta.url)`. There exist some Babel plugins, but none of them is stable.
Therefore, we actually need a proper bundler which supports outputting ESM modules.
The only stable solution to this is Parcel. Parcel also has good documentation around the bundling of WebWorkers.
@ -277,4 +277,4 @@ Example config in `package.json:
### Rollup
Not yet evaluated
Not yet evaluated

View File

@ -1 +1,2 @@
declare const WEBGL: boolean
declare const MULTITHREADED: boolean

View File

@ -2,6 +2,7 @@ import {build} from 'esbuild';
import metaUrlPlugin from '@chialab/esbuild-plugin-meta-url';
import inlineWorker from 'esbuild-plugin-inline-worker';
import yargs from "yargs";
import process from "process";
import chokidar from "chokidar";
import {spawnSync} from "child_process"
import {dirname} from "path";
@ -9,32 +10,30 @@ import {fileURLToPath} from "url";
let argv = yargs(process.argv.slice(2))
.option('watch', {
alias: 'w',
type: 'boolean',
description: 'Enable watching'
})
.option('release', {
alias: 'r',
type: 'boolean',
description: 'Release mode'
})
.option('webgl', {
alias: 'g',
type: 'boolean',
description: 'Enable webgl'
})
.option('multithreaded', {
type: 'boolean',
description: 'Enable multithreaded support'
})
.option('esm', {
alias: 'e',
type: 'boolean',
description: 'Enable esm'
})
.option('cjs', {
alias: 'c',
type: 'boolean',
description: 'Enable cjs'
})
.option('iife', {
alias: 'i',
type: 'boolean',
description: 'Enable iife'
})
@ -44,6 +43,7 @@ let esm = argv.esm;
let iife = argv.iife;
let cjs = argv.cjs;
let release = argv.release;
let multithreaded = argv.multithreaded;
if (!esm && !iife && !cjs) {
console.warn("Enabling ESM bundling as no other bundle is enabled.")
@ -56,19 +56,29 @@ if (webgl) {
console.log("WebGL support enabled.")
}
let baseSettings = {
entryPoints: ['src/index.ts'],
bundle: true,
if (multithreaded) {
console.log("multithreaded support enabled.")
}
let baseConfig = {
platform: "browser",
bundle: true,
assetNames: "assets/[name]",
define: {"WEBGL": `${webgl}`},
define: {
WEBGL: `${webgl}`,
MULTITHREADED: `${multithreaded}`
},
}
let config = {
...baseConfig,
entryPoints: ['src/index.ts'],
incremental: argv.watch,
plugins: [
inlineWorker({
format: "cjs", platform: "browser",
...baseConfig,
format: "cjs",
target: 'es2022',
bundle: true,
assetNames: "assets/[name]",
}),
metaUrlPlugin()
],
@ -119,11 +129,22 @@ const wasmPack = () => {
let outDirectory = `${getLibDirectory()}/src/wasm`;
let profile = release ? "wasm-release" : "wasm-dev"
let cargo = spawnSync('cargo', ["build",
// language=toml
let multithreaded_config = `target.wasm32-unknown-unknown.rustflags = [
# Enables features which are required for shared-memory
"-C", "target-feature=+atomics,+bulk-memory,+mutable-globals",
# Enables the possibility to import memory into wasm.
# Without --shared-memory it is not possible to use shared WebAssembly.Memory.
"-C", "link-args=--shared-memory --import-memory",
]`
let cargo = spawnSync('cargo', [
...(multithreaded ? ["--config", multithreaded_config] : []),
"build",
"-p", "web", "--lib",
"--target", "wasm32-unknown-unknown",
"--profile", profile,
"--features", `${webgl ? "web-webgl" : ""}`,
"--features", `${webgl ? "web-webgl," : ""}`,
"-Z", "build-std=std,panic_abort"
], {
cwd: '.',
@ -206,7 +227,7 @@ const watchResult = async (result) => {
}
const esbuild = async (name, globalName = undefined) => {
let result = await build({...baseSettings, format: name, globalName, outfile: `dist/esbuild-${name}/module.js`,});
let result = await build({...config, format: name, globalName, outfile: `dist/esbuild-${name}/module.js`,});
if (argv.watch) {
console.log("Watching is enabled.")

73
web/lib/src/browser.ts Normal file
View File

@ -0,0 +1,73 @@
import {
bigInt,
bulkMemory,
exceptions,
multiValue,
mutableGlobals,
referenceTypes,
saturatedFloatToInt,
signExtensions,
simd,
tailCall,
threads
} from "wasm-feature-detect"
export const checkRequirements = () => {
if (MULTITHREADED) {
if (!isSecureContext) {
return "isSecureContext is false!"
}
if (!crossOriginIsolated) {
return "crossOriginIsolated is false! " +
"The Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy HTTP headers are required."
}
}
if (WEBGL) {
if (!isWebGLSupported()) {
return "WebGL is not supported in this Browser!"
}
} else {
if (!("gpu" in navigator)) {
return "WebGPU is not supported in this Browser!"
}
}
return null
}
export const isWebGLSupported = () => {
try {
const canvas = document.createElement('canvas')
canvas.getContext("webgl")
return true
} catch (x) {
return false
}
}
export const checkWasmFeatures = async () => {
const checkFeature = async function (featureName: string, feature: () => Promise<boolean>) {
let result = await feature();
let msg = `The feature ${featureName} returned: ${result}`;
if (result) {
console.log(msg);
} else {
console.warn(msg);
}
}
await checkFeature("bulkMemory", bulkMemory);
await checkFeature("exceptions", exceptions);
await checkFeature("multiValue", multiValue);
await checkFeature("mutableGlobals", mutableGlobals);
await checkFeature("referenceTypes", referenceTypes);
await checkFeature("saturatedFloatToInt", saturatedFloatToInt);
await checkFeature("signExtensions", signExtensions);
await checkFeature("simd", simd);
await checkFeature("tailCall", tailCall);
await checkFeature("threads", threads);
await checkFeature("bigInt", bigInt);
}

View File

@ -1,92 +1,12 @@
import init, {create_pool_scheduler, run} from "./wasm/maplibre"
import {create_pool_scheduler, run} from "./wasm/maplibre"
import {Spector} from "spectorjs"
import {WebWorkerMessageType} from "./worker-types"
import {
bigInt,
bulkMemory,
exceptions,
multiValue,
mutableGlobals,
referenceTypes,
saturatedFloatToInt,
signExtensions,
simd,
tailCall,
threads
} from "wasm-feature-detect"
// @ts-ignore
// @ts-ignore esbuild plugin is handling this
import MultithreadedPoolWorker from './multithreaded-pool.worker.js';
// @ts-ignore esbuild plugin is handling this
import PoolWorker from './pool.worker.js';
const isWebGLSupported = () => {
try {
const canvas = document.createElement('canvas')
canvas.getContext("webgl")
return true
} catch (x) {
return false
}
}
const checkWasmFeatures = async () => {
const checkFeature = async function (featureName: string, feature: () => Promise<boolean>) {
let result = await feature();
let msg = `The feature ${featureName} returned: ${result}`;
if (result) {
console.log(msg);
} else {
console.warn(msg);
}
}
await checkFeature("bulkMemory", bulkMemory);
await checkFeature("exceptions", exceptions);
await checkFeature("multiValue", multiValue);
await checkFeature("mutableGlobals", mutableGlobals);
await checkFeature("referenceTypes", referenceTypes);
await checkFeature("saturatedFloatToInt", saturatedFloatToInt);
await checkFeature("signExtensions", signExtensions);
await checkFeature("simd", simd);
await checkFeature("tailCall", tailCall);
await checkFeature("threads", threads);
await checkFeature("bigInt", bigInt);
}
const alertUser = (message: string) => {
console.error(message)
alert(message)
}
const checkRequirements = () => {
if (!isSecureContext) {
alertUser("isSecureContext is false!")
return false
}
if (!crossOriginIsolated) {
alertUser("crossOriginIsolated is false! " +
"The Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy HTTP headers are required.")
return false
}
if (WEBGL) {
if (!isWebGLSupported()) {
alertUser("WebGL is not supported in this Browser!")
return false
}
let spector = new Spector()
spector.displayUI()
} else {
if (!("gpu" in navigator)) {
let message = "WebGPU is not supported in this Browser!"
alertUser(message)
return false
}
}
return true
}
import {initialize} from "./module";
import {checkRequirements, checkWasmFeatures} from "./browser";
const preventDefaultTouchActions = () => {
document.body.querySelectorAll("canvas").forEach(canvas => {
@ -98,23 +18,28 @@ const preventDefaultTouchActions = () => {
export const startMapLibre = async (wasmPath: string | undefined, workerPath: string | undefined) => {
await checkWasmFeatures()
if (!checkRequirements()) {
let message = checkRequirements();
if (message) {
console.error(message)
alert(message)
return
}
if (WEBGL) {
let spector = new Spector()
spector.displayUI()
}
preventDefaultTouchActions();
let MEMORY_PAGES = 16 * 1024
const memory = new WebAssembly.Memory({initial: 1024, maximum: MEMORY_PAGES, shared: true})
await init(wasmPath, memory)
await initialize(wasmPath);
const schedulerPtr = create_pool_scheduler(() => {
let worker = workerPath ? new PoolWorker(workerPath, {
type: 'module'
}) : PoolWorker({name: "test"});
let CurrentWorker = MULTITHREADED ? MultithreadedPoolWorker : PoolWorker;
return worker;
return workerPath ? new Worker(workerPath, {
type: 'module'
}) : CurrentWorker();
})
await run(schedulerPtr)

28
web/lib/src/module.ts Normal file
View File

@ -0,0 +1,28 @@
import init from "./wasm/maplibre";
export const initialize = async (wasmPath: string) => {
if (MULTITHREADED) {
await initializeSharedModule(wasmPath)
} else {
// @ts-ignore
await init(wasmPath)
}
}
export const initializeExisting = async (module: string, memory?: string) => {
if (MULTITHREADED) {
// @ts-ignore
await init(module, memory)
} else {
// @ts-ignore
await init(module)
}
}
const initializeSharedModule = async (wasmPath) => {
let MEMORY_PAGES = 16 * 1024
const memory = new WebAssembly.Memory({initial: 1024, maximum: MEMORY_PAGES, shared: true})
// @ts-ignore
await init(wasmPath, memory)
}

View File

@ -0,0 +1,19 @@
import {worker_entry} from "./wasm/maplibre"
import {initializeExisting} from "./module";
onmessage = async message => {
const initialised = initializeExisting(message.data[0], message.data[1]).catch(err => {
// Propagate to main `onerror`:
setTimeout(() => {
throw err;
});
// Rethrow to keep promise rejected and prevent execution of further commands:
throw err;
});
self.onmessage = async message => {
// This will queue further commands up until the module is fully initialised:
await initialised;
await worker_entry(message.data);
};
}

View File

@ -1,18 +1,5 @@
import init, {worker_entry} from "./wasm/maplibre"
import {initializeExisting} from "./module";
onmessage = async message => {
const initialised = init(message.data[0], message.data[1]).catch(err => {
// Propagate to main `onerror`:
setTimeout(() => {
throw err;
});
// Rethrow to keep promise rejected and prevent execution of further commands:
throw err;
});
self.onmessage = async message => {
// This will queue further commands up until the module is fully initialised:
await initialised;
worker_entry(message.data);
};
}

View File

@ -1,17 +0,0 @@
/*
import {registerRoute} from 'workbox-routing';
import {CacheFirst} from 'workbox-strategies';
import {CacheableResponsePlugin} from 'workbox-cacheable-response';
registerRoute(
({url}) => url.pathname.endsWith('pbf'),
new CacheFirst({
cacheName: 'pbf-cache',
plugins: [
new CacheableResponsePlugin({
statuses: [0, 200],
})
]
})
);
*/