Publish a dual CJS/ESM package with platform-specific loaders

This commit is contained in:
Michael Telatynski 2024-12-04 14:12:08 +00:00
parent b94addf130
commit b2694a78bf
No known key found for this signature in database
GPG key ID: A2B008A5F49F5D0D
36 changed files with 738 additions and 272 deletions

View file

@ -7,6 +7,7 @@
- [Web] Add typescript checking to bindings/wysiwyg-wasm.
- [Web] Update vite and related packages.
- [Web] Simplify build scripts.
- [Web] Publish a dual CJS/ESM package with platform-specific loaders.
# [2.37.14]
- [Android] Have separate modes for parsing HTML for 'editor mode' and 'message mode' using `isEditor: Boolean` parameter.

View file

@ -27,15 +27,8 @@ ios: targets-ios
web:
cd bindings/wysiwyg-wasm && \
yarn && \
yarn build && \
mkdir -p ../../platforms/web/generated && \
cp \
pkg/wysiwyg_bg.wasm \
pkg/wysiwyg_bg.wasm.d.ts \
pkg/wysiwyg.d.ts \
pkg/wysiwyg.js \
../../platforms/web/generated/
cd platforms/web && yarn install && yarn build
yarn build
cd platforms/web && yarn && yarn build
web-format:
cd platforms/web && \

View file

@ -30,8 +30,9 @@ These files should be copied into a web project and imported with code like:
```html
<script type="module">
import init, { some_method_from_rust }
from './generated/matrix_sdk_wysiwyg.js';
TODO this is all wrong
import { initAsync, some_method_from_rust }
from './pkg/matrix_sdk_wysiwyg.js';
async function run() {
await init();

View file

@ -0,0 +1,64 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
// @ts-check
/**
* This is the entrypoint on non-node ESM environments which support the ES Module Integration Proposal for WebAssembly [1]
* (such as Element Web).
*
* [1]: https://github.com/webassembly/esm-integration
*/
import * as bindings from './pkg/wysiwyg_bg.js';
// We want to throw an error if the user tries to use the bindings before
// calling `initAsync`.
bindings.__wbg_set_wasm(
new Proxy(
{},
{
get() {
throw new Error(
'@element-hq/matrix-wysiwyg was used before it was initialized. Call `initAsync` first.',
);
},
},
),
);
/**
* Stores a promise of the `loadModule` call
* @type {Promise<void> | null}
*/
let modPromise = null;
/**
* Loads the WASM module asynchronously
*
* @returns {Promise<void>}
*/
async function loadModule() {
const wasm = await import('./pkg/wysiwyg_bg.wasm');
bindings.__wbg_set_wasm(wasm);
wasm.__wbindgen_start();
}
/**
* Load the WebAssembly module in the background, if it has not already been loaded.
*
* Returns a promise which will resolve once the other methods are ready.
*
* @returns {Promise<void>}
*/
export default async function initAsync() {
if (!modPromise) modPromise = loadModule();
await modPromise;
}
// Re-export everything from the generated javascript wrappers
export * from './pkg/wysiwyg_bg.js';

View file

@ -0,0 +1,85 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
// @ts-check
/**
* This is the entrypoint on non-node CommonJS environments.
* `initAsync` will load the WASM module using a `fetch` call.
*/
const bindings = require("./pkg/wysiwyg_bg.cjs");
const moduleUrl = require.resolve("./pkg/wysiwyg_bg.wasm");
// We want to throw an error if the user tries to use the bindings before
// calling `initAsync`.
bindings.__wbg_set_wasm(
new Proxy(
{},
{
get() {
throw new Error(
'@element-hq/matrix-wysiwyg was used before it was initialized. Call `initAsync` first.',
);
},
},
),
);
/**
* Stores a promise of the `loadModule` call
* @type {Promise<void> | null}
*/
let modPromise = null;
/**
* Loads the WASM module asynchronously
*
* @returns {Promise<void>}
*/
async function loadModule() {
let mod;
if (typeof WebAssembly.compileStreaming === 'function') {
mod = await WebAssembly.compileStreaming(fetch(moduleUrl));
} else {
// Fallback to fetch and compile
const response = await fetch(moduleUrl);
if (!response.ok) {
throw new Error(`Failed to fetch wasm module: ${moduleUrl}`);
}
const bytes = await response.arrayBuffer();
mod = await WebAssembly.compile(bytes);
}
/** @type {{exports: typeof import("./pkg/wysiwyg_bg.wasm.d.ts")}} */
// @ts-expect-error: Typescript doesn't know what the instance exports exactly
const instance = await WebAssembly.instantiate(mod, {
'./wysiwyg_bg.js': bindings,
});
bindings.__wbg_set_wasm(instance.exports);
instance.exports.__wbindgen_start();
}
/**
* Load the WebAssembly module in the background, if it has not already been loaded.
*
* Returns a promise which will resolve once the other methods are ready.
*
* @returns {Promise<void>}
*/
async function initAsync() {
if (!modPromise) modPromise = loadModule();
await modPromise;
}
module.exports = {
// Re-export everything from the generated javascript wrappers
...bindings,
initAsync,
};

17
bindings/wysiwyg-wasm/index.d.ts vendored Normal file
View file

@ -0,0 +1,17 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
export * from './pkg/wysiwyg.d';
/**
* Load the WebAssembly module in the background, if it has not already been loaded.
*
* Returns a promise which will resolve once the other methods are ready.
*
* @returns {Promise<void>}
*/
export function initAsync(): Promise<void>;

View file

@ -0,0 +1,85 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
// @ts-check
/**
* This is the entrypoint on non-node ESM environments.
* `initAsync` will load the WASM module using a `fetch` call.
*/
import * as bindings from './pkg/wysiwyg_bg.js';
const moduleUrl = new URL(
'./pkg/wysiwyg_bg.wasm?url',
import.meta.url,
);
// We want to throw an error if the user tries to use the bindings before
// calling `initAsync`.
bindings.__wbg_set_wasm(
new Proxy(
{},
{
get() {
throw new Error(
'@element-hq/matrix-wysiwyg was used before it was initialized. Call `initAsync` first.',
);
},
},
),
);
/**
* Stores a promise of the `loadModule` call
* @type {Promise<void> | null}
*/
let modPromise = null;
/**
* Loads the WASM module asynchronously
*
* @returns {Promise<void>}
*/
async function loadModule() {
let mod;
if (typeof WebAssembly.compileStreaming === 'function') {
mod = await WebAssembly.compileStreaming(fetch(moduleUrl));
} else {
// Fallback to fetch and compile
const response = await fetch(moduleUrl);
if (!response.ok) {
throw new Error(`Failed to fetch wasm module: ${moduleUrl}`);
}
const bytes = await response.arrayBuffer();
mod = await WebAssembly.compile(bytes);
}
/** @type {{exports: typeof import("./pkg/wysiwyg_bg.wasm.d.ts")}} */
// @ts-expect-error: Typescript doesn't know what the instance exports exactly
const instance = await WebAssembly.instantiate(mod, {
'./wysiwyg_bg.js': bindings,
});
bindings.__wbg_set_wasm(instance.exports);
instance.exports.__wbindgen_start();
}
/**
* Load the WebAssembly module in the background, if it has not already been loaded.
*
* Returns a promise which will resolve once the other methods are ready.
*
* @returns {Promise<void>}
*/
export async function initAsync() {
if (!modPromise) modPromise = loadModule();
await modPromise;
}
// Re-export everything from the generated javascript wrappers
export * from './pkg/wysiwyg_bg.js';

View file

@ -0,0 +1,120 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
// @ts-check
/**
* This is the entrypoint on node-compatible CommonJS environments.
* `asyncLoad` will use `fs.readFile` to load the WASM module.
*/
const { readFileSync } = require("node:fs");
const { readFile } = require("node:fs/promises");
const path = require("node:path");
const bindings = require("./pkg/wysiwyg_bg.cjs");
const filename = path.join(__dirname, "pkg/wysiwyg_bg.wasm");
// In node environments, we want to automatically load the WASM module
// synchronously if the consumer did not call `initAsync`. To do so, we install
// a `Proxy` that will intercept calls to the WASM module.
bindings.__wbg_set_wasm(
new Proxy(
{},
{
get(_target, prop) {
const instance = loadModuleSync();
return instance[prop];
},
},
),
);
/**
* Stores a promise of the `loadModule` call
* @type {Promise<void> | null}
*/
let modPromise = null;
/**
* Tracks whether the module has been instantiated or not
* @type {boolean}
*/
let initialised = false;
/**
* Loads and instantiates the WASM module synchronously
*
* It will throw if there is an attempt to load the module asynchronously running
*
* @returns {typeof import("./pkg/wysiwyg_bg.wasm.d")}
*/
function loadModuleSync() {
if (modPromise) throw new Error("The WASM module is being loaded asynchronously but hasn't finished");
const bytes = readFileSync(filename);
const mod = new WebAssembly.Module(bytes);
const instance = new WebAssembly.Instance(mod, {
// @ts-expect-error: The bindings don't exactly match the 'ExportValue' type
"./wysiwyg_bg.js": bindings,
});
initInstance(instance);
// @ts-expect-error: Typescript doesn't know what the instance exports exactly
return instance.exports;
}
/**
* Loads the WASM module asynchronously
*
* @returns {Promise<void>}
*/
async function loadModuleAsync() {
const bytes = await readFile(filename);
const { instance } = await WebAssembly.instantiate(bytes, {
// @ts-expect-error: The bindings don't exactly match the 'ExportValue' type
"./wysiwyg_bg.js": bindings,
});
initInstance(instance);
// @ts-expect-error: Typescript doesn't know what the instance exports exactly
return instance.exports;
}
/**
* Initializes the WASM module and returns the exports from the WASM module.
*
* @param {WebAssembly.Instance} instance
*/
function initInstance(instance) {
if (initialised) throw new Error("initInstance called twice");
bindings.__wbg_set_wasm(instance.exports);
// @ts-expect-error: Typescript doesn't know what the instance exports exactly
instance.exports.__wbindgen_start();
initialised = true;
}
/**
* Load the WebAssembly module in the background, if it has not already been loaded.
*
* Returns a promise which will resolve once the other methods are ready.
*
* @returns {Promise<void>}
*/
async function initAsync() {
if (initialised) return;
if (!modPromise) modPromise = loadModuleAsync();
await modPromise;
}
module.exports = {
// Re-export everything from the generated javascript wrappers
...bindings,
initAsync,
};

View file

@ -0,0 +1,117 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
// @ts-check
/**
* This is the entrypoint on node-compatible ESM environments.
* `asyncLoad` will use `fs.readFile` to load the WASM module.
*/
import { fileURLToPath } from "node:url";
import { join, dirname } from "node:path";
import { readFileSync } from "node:fs";
import { readFile } from "node:fs/promises";
import * as bindings from "./pkg/wysiwyg_bg.js";
const filename = join(dirname(fileURLToPath(import.meta.url)), "pkg", "wysiwyg_bg.wasm");
// In node environments, we want to automatically load the WASM module
// synchronously if the consumer did not call `initAsync`. To do so, we install
// a `Proxy` that will intercept calls to the WASM module.
bindings.__wbg_set_wasm(
new Proxy(
{},
{
get(_target, prop) {
const instance = loadModuleSync();
return instance[prop];
},
},
),
);
/**
* Stores a promise of the `loadModule` call
* @type {Promise<void> | null}
*/
let modPromise = null;
/**
* Tracks whether the module has been instantiated or not
* @type {boolean}
*/
let initialised = false;
/**
* Loads and instantiates the WASM module synchronously
*
* It will throw if there is an attempt to load the module asynchronously running
*
* @returns {typeof import("./pkg/wysiwyg_bg.wasm.d")}
*/
function loadModuleSync() {
if (modPromise) throw new Error("The WASM module is being loaded asynchronously but hasn't finished");
const bytes = readFileSync(filename);
const mod = new WebAssembly.Module(bytes);
const instance = new WebAssembly.Instance(mod, {
// @ts-expect-error: The bindings don't exactly match the 'ExportValue' type
"./wysiwyg_bg.js": bindings,
});
initInstance(instance);
// @ts-expect-error: Typescript doesn't know what the instance exports exactly
return instance.exports;
}
/**
* Loads the WASM module asynchronously
*
* @returns {Promise<void>}
*/
async function loadModuleAsync() {
const bytes = await readFile(filename);
const { instance } = await WebAssembly.instantiate(bytes, {
// @ts-expect-error: The bindings don't exactly match the 'ExportValue' type
"./wysiwyg_bg.js": bindings,
});
initInstance(instance);
// @ts-expect-error: Typescript doesn't know what the instance exports exactly
return instance.exports;
}
/**
* Initializes the WASM module and returns the exports from the WASM module.
*
* @param {WebAssembly.Instance} instance
*/
function initInstance(instance) {
if (initialised) throw new Error("initInstance called twice");
bindings.__wbg_set_wasm(instance.exports);
// @ts-expect-error: Typescript doesn't know what the instance exports exactly
instance.exports.__wbindgen_start();
initialised = true;
}
/**
* Load the WebAssembly module in the background, if it has not already been loaded.
*
* Returns a promise which will resolve once the other methods are ready.
*
* @returns {Promise<void>}
*/
export async function initAsync() {
if (initialised) return;
if (!modPromise) modPromise = loadModuleAsync();
await modPromise;
}
export * from "./pkg/wysiwyg_bg.js";

View file

@ -1,9 +1,10 @@
{
"name": "wysiwyg-wasm",
"name": "@vector-im/matrix-wysiwyg-wasm",
"version": "2.37.14",
"homepage": "https://gitlab.com/andybalaam/wysiwyg-rust",
"description": "WASM bindings for wysiwyg-rust",
"license": "AGPL-3.0",
"type": "module",
"collaborators": [
"Andy Balaam <andy.balaam@matrix.org>"
],
@ -17,15 +18,40 @@
"messaging",
"wysiwyg"
],
"main": "wysiwyg.js",
"types": "pkg/wysiwyg.d.ts",
"exports": {
".": {
"matrix-org:wasm-esm": {
"types": "./index.d.ts",
"default": "./index-wasm-esm.js"
},
"require": {
"types": "./index.d.ts",
"node": "./node.cjs",
"default": "./index.cjs"
},
"import": {
"types": "./index.d.ts",
"node": "./node.js",
"default": "./index.js"
}
}
},
"files": [
"pkg/wysiwyg_bg.js",
"pkg/wysiwyg_bg.cjs",
"pkg/wysiwyg_bg.wasm",
"pkg/wysiwyg_bg.wasm.d.ts",
"pkg/wysiwyg.js",
"pkg/wysiwyg.d.ts"
"pkg/wysiwyg.d.ts",
"index.d.ts",
"index.js",
"index.cjs",
"index-wasm-esm.js",
"node.js",
"node.cjs"
],
"devDependencies": {
"@babel/cli": "^7.23.5",
"@babel/plugin-transform-modules-commonjs": "^7.25.9",
"@types/node": "^22.10.2",
"jest": "^28.1.0",
"typedoc": "^0.26.0",
@ -36,9 +62,10 @@
"node": ">= 10"
},
"scripts": {
"dev-build": "WASM_BINDGEN_WEAKREF=1 wasm-pack build --profiling --target web --out-name wysiwyg --out-dir ./pkg",
"build": "yarn build:cjs && yarn lint",
"build:cjs": "RUSTFLAGS='-C opt-level=s' WASM_BINDGEN_WEAKREF=1 wasm-pack build --release --target web --out-name wysiwyg --out-dir ./pkg",
"dev-build": "WASM_BINDGEN_WEAKREF=1 wasm-pack build --profiling --target bundler --out-name wysiwyg --out-dir ./pkg",
"build": "yarn build:esm && yarn build:cjs && yarn lint",
"build:esm": "RUSTFLAGS='-C opt-level=s' WASM_BINDGEN_WEAKREF=1 wasm-pack build --release --target bundler --out-name wysiwyg --out-dir ./pkg",
"build:cjs": "babel pkg/wysiwyg_bg.js --out-dir pkg --out-file-extension .cjs --plugins @babel/plugin-transform-modules-commonjs",
"lint": "tsc --noEmit",
"test": "jest --verbose",
"doc": "typedoc --tsconfig ."

View file

@ -11,5 +11,5 @@
"noUnusedLocals": false,
"noImplicitThis": false,
},
"include": ["pkg/wysiwyg.d.ts", "pkg/wysiwyg_bg.wasm.d.ts"]
"include": ["index.*", "node.*", "pkg/wysiwyg.d.ts", "pkg/wysiwyg_bg.wasm.d.ts"]
}

View file

@ -10,6 +10,22 @@
"@jridgewell/gen-mapping" "^0.3.5"
"@jridgewell/trace-mapping" "^0.3.24"
"@babel/cli@^7.23.5":
version "7.26.4"
resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.26.4.tgz#4101ff8ee5de8447a6c395397a97921056411d20"
integrity sha512-+mORf3ezU3p3qr+82WvJSnQNE1GAYeoCfEv4fik6B5/2cvKZ75AX8oawWQdXtM9MmndooQj15Jr9kelRFWsuRw==
dependencies:
"@jridgewell/trace-mapping" "^0.3.25"
commander "^6.2.0"
convert-source-map "^2.0.0"
fs-readdir-recursive "^1.1.0"
glob "^7.2.0"
make-dir "^2.1.0"
slash "^2.0.0"
optionalDependencies:
"@nicolo-ribaudo/chokidar-2" "2.1.8-no-fsevents.3"
chokidar "^3.6.0"
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0", "@babel/code-frame@^7.26.2":
version "7.26.2"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85"
@ -231,6 +247,14 @@
dependencies:
"@babel/helper-plugin-utils" "^7.25.9"
"@babel/plugin-transform-modules-commonjs@^7.25.9":
version "7.26.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz#8f011d44b20d02c3de44d8850d971d8497f981fb"
integrity sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==
dependencies:
"@babel/helper-module-transforms" "^7.26.0"
"@babel/helper-plugin-utils" "^7.25.9"
"@babel/template@^7.25.9", "@babel/template@^7.3.3":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.9.tgz#ecb62d81a8a6f5dc5fe8abfc3901fc52ddf15016"
@ -507,6 +531,11 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
"@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3":
version "2.1.8-no-fsevents.3"
resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz#323d72dd25103d0c4fbdce89dadf574a787b1f9b"
integrity sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==
"@shikijs/core@1.24.4":
version "1.24.4"
resolved "https://registry.yarnpkg.com/@shikijs/core/-/core-1.24.4.tgz#de1c454a4e2dbcfaee2dde51d3fac6041e6171f7"
@ -704,7 +733,7 @@ ansi-styles@^5.0.0:
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
anymatch@^3.0.3:
anymatch@^3.0.3, anymatch@~3.1.2:
version "3.1.3"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"
integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==
@ -799,6 +828,11 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
binary-extensions@^2.0.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
binary-install@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/binary-install/-/binary-install-1.1.0.tgz#61195349acabf5a043f3805b03f96e506cc96d6e"
@ -823,7 +857,7 @@ brace-expansion@^2.0.1:
dependencies:
balanced-match "^1.0.0"
braces@^3.0.3:
braces@^3.0.3, braces@~3.0.2:
version "3.0.3"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
@ -900,6 +934,21 @@ character-entities-legacy@^3.0.0:
resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz#76bc83a90738901d7bc223a9e93759fdd560125b"
integrity sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==
chokidar@^3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==
dependencies:
anymatch "~3.1.2"
braces "~3.0.2"
glob-parent "~5.1.2"
is-binary-path "~2.1.0"
is-glob "~4.0.1"
normalize-path "~3.0.0"
readdirp "~3.6.0"
optionalDependencies:
fsevents "~2.3.2"
chownr@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
@ -951,6 +1000,11 @@ comma-separated-tokens@^2.0.0:
resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee"
integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==
commander@^6.2.0:
version "6.2.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@ -1131,12 +1185,17 @@ fs-minipass@^2.0.0:
dependencies:
minipass "^3.0.0"
fs-readdir-recursive@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27"
integrity sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
fsevents@^2.3.2:
fsevents@^2.3.2, fsevents@~2.3.2:
version "2.3.3"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
@ -1166,7 +1225,14 @@ get-stream@^6.0.0:
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
glob@^7.1.3, glob@^7.1.4:
glob-parent@~5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
dependencies:
is-glob "^4.0.1"
glob@^7.1.3, glob@^7.1.4, glob@^7.2.0:
version "7.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
@ -1270,6 +1336,13 @@ is-arrayish@^0.2.1:
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==
is-binary-path@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
dependencies:
binary-extensions "^2.0.0"
is-core-module@^2.16.0:
version "2.16.1"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4"
@ -1277,6 +1350,11 @@ is-core-module@^2.16.0:
dependencies:
hasown "^2.0.2"
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
is-fullwidth-code-point@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
@ -1287,6 +1365,13 @@ is-generator-fn@^2.0.0:
resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118"
integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==
is-glob@^4.0.1, is-glob@~4.0.1:
version "4.0.3"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
dependencies:
is-extglob "^2.1.1"
is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
@ -1771,6 +1856,14 @@ lunr@^2.3.9:
resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1"
integrity sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==
make-dir@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==
dependencies:
pify "^4.0.1"
semver "^5.6.0"
make-dir@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e"
@ -1926,7 +2019,7 @@ node-releases@^2.0.19:
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314"
integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==
normalize-path@^3.0.0:
normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
@ -2022,11 +2115,16 @@ picocolors@^1.0.0, picocolors@^1.1.0:
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1:
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
pify@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231"
integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==
pirates@^4.0.4:
version "4.0.6"
resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9"
@ -2072,6 +2170,13 @@ react-is@^18.0.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e"
integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
readdirp@~3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
dependencies:
picomatch "^2.2.1"
regex-recursion@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/regex-recursion/-/regex-recursion-5.0.0.tgz#330c14e9e448394210dfd063c6a757d7a293e9bb"
@ -2129,6 +2234,11 @@ rimraf@^3.0.0, rimraf@^3.0.2:
dependencies:
glob "^7.1.3"
semver@^5.6.0:
version "5.7.2"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8"
integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==
semver@^6.3.0, semver@^6.3.1:
version "6.3.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
@ -2173,6 +2283,11 @@ sisteransi@^1.0.5:
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==
slash@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44"
integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==
slash@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"

10
package-lock.json generated
View file

@ -1,10 +0,0 @@
{
"name": "matrix-rich-text-editor",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "matrix-rich-text-editor"
}
}
}

View file

@ -40,6 +40,13 @@ Requirements:
yarn install
```
If your environment supports the experimental [ES Module Integration Proposal for WebAssembly](https://github.com/WebAssembly/esm-integration),
you can instead use that, by setting the `matrix-org:wasm-esm` custom [export condition](https://nodejs.org/api/packages.html#conditional-exports).
This is only supported when the library is imported as an ES Module. For example:
- In Webpack, set [`experiments.asyncWebAssembly`](https://webpack.js.org/configuration/experiments/#experiments)
to `true` and [`resolve.conditionNames`](https://webpack.js.org/configuration/resolve/#resolveconditionnames)
to `["matrix-org:wasm-esm", "..."]` (the `"..."` preserves default condition names).
### Dev
#### Folder structure

View file

@ -6,7 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import { ComposerModel } from '../generated/wysiwyg';
import { ComposerModel } from '@vector-im/matrix-wysiwyg-wasm';
import { processInput } from './composer';
import { FormattingFunctions } from './types';

View file

@ -10,7 +10,8 @@ import {
ComposerModel,
ComposerUpdate,
SuggestionPattern,
} from '../generated/wysiwyg';
} from '@vector-im/matrix-wysiwyg-wasm';
import {
WysiwygInputEvent,
InputEventProcessor,

View file

@ -10,7 +10,8 @@ Please see LICENSE in the repository root for full details.
import {
// eslint-disable-next-line camelcase
new_composer_model,
} from '../generated/wysiwyg.js';
} from '@vector-im/matrix-wysiwyg-wasm';
import { initOnce } from './useComposerModel.js';
const NEWLINE_CHAR = '\n';

View file

@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import { ComposerModel, DomHandle } from '../generated/wysiwyg';
import { ComposerModel, DomHandle } from '@vector-im/matrix-wysiwyg-wasm';
export function refreshComposerView(
node: HTMLElement,

View file

@ -6,11 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import init, {
import {
initAsync,
// eslint-disable-next-line camelcase
new_composer_model,
SuggestionPattern,
} from '../generated/wysiwyg';
} from '@vector-im/matrix-wysiwyg-wasm';
import { SUGGESTIONS } from './constants';
import {
getSuggestionChar,
@ -18,9 +20,7 @@ import {
mapSuggestion,
} from './suggestion';
beforeAll(async () => {
await init();
});
beforeAll(initAsync);
describe('getSuggestionChar', () => {
it('returns the expected character', () => {

View file

@ -6,7 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import { SuggestionPattern } from '../generated/wysiwyg';
import { SuggestionPattern } from '@vector-im/matrix-wysiwyg-wasm';
import { SUGGESTIONS } from './constants';
import { MappedSuggestion, SuggestionChar, SuggestionType } from './types';

View file

@ -6,7 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import { ComposerUpdate } from '../generated/wysiwyg';
import { ComposerUpdate } from '@vector-im/matrix-wysiwyg-wasm';
import { ACTION_TYPES, SUGGESTIONS } from './constants';
import { AllowedMentionAttributes, LinkEvent } from './useListeners/types';

View file

@ -8,8 +8,8 @@ Please see LICENSE in the repository root for full details.
import { act, RefObject } from 'react';
import { renderHook, waitFor } from '@testing-library/react';
import * as mockRustModel from '@vector-im/matrix-wysiwyg-wasm';
import * as mockRustModel from '../generated/wysiwyg';
import { useComposerModel } from './useComposerModel';
describe('useComposerModel', () => {

View file

@ -7,15 +7,16 @@ Please see LICENSE in the repository root for full details.
*/
import { RefObject, useCallback, useEffect, useState } from 'react';
// rust generated bindings
import init, {
import {
initAsync,
ComposerModel,
// eslint-disable-next-line camelcase
new_composer_model,
// eslint-disable-next-line camelcase
new_composer_model_from_html,
} from '../generated/wysiwyg.js';
} from '@vector-im/matrix-wysiwyg-wasm';
import { replaceEditor } from './dom';
let initStarted = false;
@ -42,7 +43,7 @@ export async function initOnce(): Promise<void> {
}
initStarted = true;
await init();
await initAsync();
initFinished = true;
}

View file

@ -7,6 +7,7 @@ Please see LICENSE in the repository root for full details.
*/
import { RefObject, useMemo } from 'react';
import { ComposerModel } from '@vector-im/matrix-wysiwyg-wasm';
import { BlockType, FormattingFunctions } from './types';
import { sendWysiwygInputEvent } from './useListeners';
@ -16,7 +17,6 @@ import {
LinkEvent,
SuggestionEvent,
} from './useListeners/types';
import { ComposerModel } from '../generated/wysiwyg';
export function useFormattingFunctions(
editorRef: RefObject<HTMLElement | null>,

View file

@ -7,15 +7,14 @@ Please see LICENSE in the repository root for full details.
*/
// eslint-disable-next-line camelcase
import init, { new_composer_model } from '../../generated/wysiwyg';
import { initAsync, new_composer_model } from '@vector-im/matrix-wysiwyg-wasm';
import { extractActionStates, handleKeyDown } from './event';
import { FormatBlockEvent } from './types';
import { FormattingFunctions } from '../types';
import { WINDOWS_UA, mockUserAgent } from '../utils.test';
beforeAll(async () => {
await init();
});
beforeAll(initAsync);
describe('getFormattingState', () => {
it('Should be a map of action to state', () => {

View file

@ -7,12 +7,12 @@ Please see LICENSE in the repository root for full details.
*/
import { MouseEvent as ReactMouseEvent } from 'react';
import {
ComposerModel,
MenuStateUpdate,
SuggestionPattern,
} from '../../generated/wysiwyg';
} from '@vector-im/matrix-wysiwyg-wasm';
import { processEvent, processInput } from '../composer';
import {
getCurrentSelection,

View file

@ -7,8 +7,11 @@ Please see LICENSE in the repository root for full details.
*/
import { RefObject, useEffect, useRef, useState } from 'react';
import {
ComposerModel,
SuggestionPattern,
} from '@vector-im/matrix-wysiwyg-wasm';
import { ComposerModel, SuggestionPattern } from '../../generated/wysiwyg';
import { isClipboardEvent, isInputEvent } from './assert';
import { handleInput, handleKeyDown, handleSelectionChange } from './event';
import {

View file

@ -7,8 +7,8 @@ Please see LICENSE in the repository root for full details.
*/
import { RefObject, useCallback, useMemo, useRef, useState } from 'react';
import { ComposerModel } from '@vector-im/matrix-wysiwyg-wasm';
import { ComposerModel } from '../../generated/wysiwyg';
import { Actions } from './types';
import {
getSelectionAccordingToActions,

View file

@ -6,7 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import init from '../../generated/wysiwyg';
import { initAsync } from '@vector-im/matrix-wysiwyg-wasm';
import { Actions } from './types';
import {
escapeHtml,
@ -14,9 +15,7 @@ import {
getSelectionAccordingToActions,
} from './utils';
beforeAll(async () => {
await init();
});
beforeAll(initAsync);
describe('getSelectionAccordingToActions', () => {
it('Should return -1, -1 for selection when there are no actions', () => {

View file

@ -12,7 +12,8 @@ import {
ComposerUpdate,
// eslint-disable-next-line camelcase
new_composer_model_from_html,
} from '../../generated/wysiwyg';
} from '@vector-im/matrix-wysiwyg-wasm';
import { getCurrentSelection } from '../dom';
import { TraceAction } from '../types';
import { isSelectTuple } from './assert';

View file

@ -133,3 +133,5 @@ export function useWysiwyg(wysiwygProps?: WysiwygProps): UseWysiwyg {
messageContent: composerModel?.get_content_as_message_html() ?? null,
};
}
export { initOnce } from './useComposerModel.js';

View file

@ -9,7 +9,7 @@
"url": "https://github.com/element-hq/matrix-rich-text-editor"
},
"license": "AGPL-3.0",
"main": "./dist/matrix-wysiwyg.umd.cjs",
"main": "./dist/matrix-wysiwyg.js",
"module": "./dist/matrix-wysiwyg.js",
"exports": {
".": {
@ -26,7 +26,7 @@
"types": "./dist/index.d.ts",
"scripts": {
"dev": "vite",
"build": "vite build && node scripts/hack_myfetch.js",
"build": "vite build",
"build:demo": "vite build -c vite.demo.config.ts",
"preview": "vite preview",
"typecheck": "tsc --noEmit",
@ -37,7 +37,7 @@
"test:watch": "vitest --pool=forks",
"test:ui": "vitest --ui",
"coverage": "vitest run --coverage --pool=forks",
"update": "cd ../../bindings/wysiwyg-wasm && npm run build && cp -f ./pkg/*wysiwyg* ../../platforms/web/generated && cd ../../platforms/web && yarn build"
"update": "cd ../../bindings/wysiwyg-wasm && npm run build && cd ../../platforms/web && yarn build"
},
"peerDependencies": {
"react": "^18.2.0"
@ -71,11 +71,16 @@
"prettier": "2.8.8",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"replace": "^1.2.1",
"typescript": "^4.7.4",
"vite": "5.3.3",
"vite-plugin-dts": "^4.3.0",
"vitest": "^2.1.8",
"vitest-sonar-reporter": "^0.5.0"
}
},
"dependencies": {
"@vector-im/matrix-wysiwyg-wasm": "link:../../bindings/wysiwyg-wasm"
},
"bundledDependencies": [
"@vector-im/matrix-wysiwyg-wasm"
]
}

View file

@ -1,100 +0,0 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2022 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
/*
* The base64 utils here are taken from:
* https://developer.mozilla.org/en-US/docs/Glossary/Base64
* (Code samples from MDN are released under CC0.)
*/
/*
* Modify our generated code to replace the use of the fetch function with a
* custom implementation that does not actually call fetch, because that
* can fall foul of a Content Security Policy.
*
* Our replacement will only work with a data: URL, but fortunately that is
* what we are using here.
*
* It simply interprets the data: URL and creates a Response object without
* actually fetching anything.
*/
import replace from 'replace';
console.log('[hack_myfetch] Hacking generated files to avoid using fetch()');
const code = `(function(){
function myfetch(dataurl) {
const cruftLength = 'data:application/wasm;base64,'.length;
let body = dataurl.toString();
body = body.substring(cruftLength);
const response = new Response(base64DecToArr(body));
return response;
}
function b64ToUint6(nChr) {
return nChr > 64 && nChr < 91
? nChr - 65
: nChr > 96 && nChr < 123
? nChr - 71
: nChr > 47 && nChr < 58
? nChr + 4
: nChr === 43
? 62
: nChr === 47
? 63
: 0;
}
function base64DecToArr(sBase64, nBlocksSize) {
const sB64Enc = sBase64.replace(/[^A-Za-z0-9+/]/g, "");
const nInLen = sB64Enc.length;
const nOutLen = nBlocksSize
? Math.ceil(((nInLen * 3 + 1) >> 2) / nBlocksSize) * nBlocksSize
: (nInLen * 3 + 1) >> 2;
const taBytes = new Uint8Array(nOutLen);
let nMod3;
let nMod4;
let nUint24 = 0;
let nOutIdx = 0;
for (let nInIdx = 0; nInIdx < nInLen; nInIdx++) {
nMod4 = nInIdx & 3;
nUint24 |= (
b64ToUint6(sB64Enc.charCodeAt(nInIdx)) <<
(6 * (3 - nMod4))
);
if (nMod4 === 3 || nInLen - nInIdx === 1) {
nMod3 = 0;
while (nMod3 < 3 && nOutIdx < nOutLen) {
taBytes[nOutIdx] = (
nUint24 >>> ((16 >>> nMod3) & 24)
) & 255;
nMod3++;
nOutIdx++;
}
nUint24 = 0;
}
}
return taBytes;
}
return myfetch($1)
}())`;
// Note: we do very simple search+replace here, so if the code contains other
// fetch calls, they will be messed up.
replace({
regex: /fetch\((\w+)\)/,
replacement: code.replace(/\n\s*/g, ''),
paths: ['./dist/matrix-wysiwyg.umd.cjs', './dist/matrix-wysiwyg.js'],
recursive: false,
silent: false,
});

View file

@ -6,21 +6,27 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
// We are using node api here
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
import '@testing-library/jest-dom';
import { cleanup } from '@testing-library/react';
import fs from 'node:fs/promises';
import path from 'path';
import path from 'node:path';
globalThis.fetch = (url): Promise<Buffer> => {
globalThis.fetch = async (url): Promise<Response> => {
// wysiwyg.js binding uses fetch to get the wasm file
// we return manually here the wasm file
if (url instanceof URL && url.href.includes('wysiwyg_bg.wasm')) {
const wasmPath = path.resolve(__dirname, 'generated/wysiwyg_bg.wasm');
return fs.readFile(wasmPath);
const wasmPath = path.resolve(
__dirname,
'..',
'..',
'bindings',
'wysiwyg-wasm',
'pkg',
'wysiwyg_bg.wasm',
);
return new Response(await fs.readFile(wasmPath), {
headers: { 'Content-Type': 'application/wasm' },
});
} else {
throw new Error('fetch is not defined');
}
@ -28,7 +34,9 @@ globalThis.fetch = (url): Promise<Buffer> => {
// Work around missing ClipboardEvent type
class MyClipboardEvent {}
globalThis.ClipboardEvent = MyClipboardEvent as ClipboardEvent;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
globalThis.ClipboardEvent = MyClipboardEvent as unknown as ClipboardEvent;
afterEach(() => {
cleanup();

View file

@ -33,6 +33,12 @@ export default defineConfig({
},
}),
],
server: {
fs: {
// Allow serving files from the git root to access the wasm in bindings dir
allow: ['../..'],
},
},
test: {
globals: true,
environment: 'jsdom',
@ -57,6 +63,7 @@ export default defineConfig({
},
},
build: {
minify: false, // TODO remove
lib: {
entry: resolve(__dirname, 'lib/useWysiwyg.ts'),
name: 'Matrix wysiwyg',
@ -66,7 +73,7 @@ export default defineConfig({
rollupOptions: {
// make sure to externalize deps that shouldn't be bundled
// into your library
external: ['react', 'react-dom'],
external: ['react', 'react-dom', '@vector-im/matrix-wysiwyg-wasm'],
output: {
// Provide global variables to use in the UMD build
// for externalized deps

View file

@ -1178,6 +1178,10 @@
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==
"@vector-im/matrix-wysiwyg-wasm@link:../../bindings/wysiwyg-wasm":
version "0.0.0"
uid ""
"@vitejs/plugin-react@^4.2.1":
version "4.3.1"
resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.3.1.tgz#d0be6594051ded8957df555ff07a991fb618b48e"
@ -1807,11 +1811,6 @@ callsites@^3.0.0:
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
camelcase@^5.0.0:
version "5.3.1"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
caniuse-lite@^1.0.30001629:
version "1.0.30001640"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001640.tgz#32c467d4bf1f1a0faa63fc793c2ba81169e7652f"
@ -1833,7 +1832,7 @@ chai@^5.1.2:
loupe "^3.1.0"
pathval "^2.0.0"
chalk@2.4.2, chalk@^2.4.2:
chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@ -1914,15 +1913,6 @@ cli-truncate@^2.1.0:
slice-ansi "^3.0.0"
string-width "^4.2.0"
cliui@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1"
integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==
dependencies:
string-width "^4.2.0"
strip-ansi "^6.0.0"
wrap-ansi "^6.2.0"
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
@ -2171,11 +2161,6 @@ debug@^4.3.7, debug@^4.4.0:
dependencies:
ms "^2.1.3"
decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
decimal.js@^10.4.2:
version "10.4.3"
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23"
@ -3066,11 +3051,6 @@ gensync@^1.0.0-beta.2:
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
get-caller-file@^2.0.1:
version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
get-func-name@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41"
@ -4150,13 +4130,6 @@ min-indent@^1.0.0:
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
minimatch@3.0.5:
version "3.0.5"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.5.tgz#4da8f1290ee0f0f8e83d60ca69f8f134068604a3"
integrity sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==
dependencies:
brace-expansion "^1.1.7"
minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
@ -4725,15 +4698,6 @@ regjsparser@^0.10.0:
dependencies:
jsesc "~0.5.0"
replace@^1.2.1:
version "1.2.2"
resolved "https://registry.yarnpkg.com/replace/-/replace-1.2.2.tgz#880247113a950afa749a297e6d10d4d7bcd27acf"
integrity sha512-C4EDifm22XZM2b2JOYe6Mhn+lBsLBAvLbK8drfUQLTfD1KYl/n3VaW/CDju0Ny4w3xTtegBpg8YNSpFJPUDSjA==
dependencies:
chalk "2.4.2"
minimatch "3.0.5"
yargs "^15.3.1"
request-progress@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-3.0.0.tgz#4ca754081c7fec63f505e4faa825aa06cd669dbe"
@ -4741,21 +4705,11 @@ request-progress@^3.0.0:
dependencies:
throttleit "^1.0.0"
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
require-from-string@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
require-main-filename@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
requires-port@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
@ -4925,11 +4879,6 @@ semver@~7.5.4:
dependencies:
lru-cache "^6.0.0"
set-blocking@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==
set-function-length@^1.2.1:
version "1.2.2"
resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449"
@ -5721,11 +5670,6 @@ which-collection@^1.0.1:
is-weakmap "^2.0.2"
is-weakset "^2.0.3"
which-module@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409"
integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==
which-typed-array@^1.1.13, which-typed-array@^1.1.14, which-typed-array@^1.1.15, which-typed-array@^1.1.9:
version "1.1.15"
resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d"
@ -5813,11 +5757,6 @@ xmlchars@^2.2.0:
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
y18n@^4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==
yallist@^3.0.2:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
@ -5828,31 +5767,6 @@ yallist@^4.0.0:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yargs-parser@^18.1.2:
version "18.1.3"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==
dependencies:
camelcase "^5.0.0"
decamelize "^1.2.0"
yargs@^15.3.1:
version "15.4.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==
dependencies:
cliui "^6.0.0"
decamelize "^1.2.0"
find-up "^4.1.0"
get-caller-file "^2.0.1"
require-directory "^2.1.1"
require-main-filename "^2.0.0"
set-blocking "^2.0.0"
string-width "^4.2.0"
which-module "^2.0.0"
y18n "^4.0.0"
yargs-parser "^18.1.2"
yauzl@^2.10.0:
version "2.10.0"
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"