Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 7 commits into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
11 changes: 2 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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 && \
Expand Down
27 changes: 18 additions & 9 deletions bindings/wysiwyg-wasm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ WASM/JavaScript bindings for wysiwyg-rust.

```sh
cd bindings/wysiwyg-wasm
npm install
npm run build
#npm run test (no tests yet)
yarn
yarn build
#yarn test (no tests yet)
```

This will generate:
Expand All @@ -26,15 +26,24 @@ pkg/matrix_sdk_wysiwyg.js
... plus other files
```

These files should be copied into a web project and imported with code like:
You can then consume these files in your project by linking the package in your package.json:

```json
{
"dependencies": {
"@matrix-org/matrix-sdk-wysiwyg-wasm": "link:../../bindings/wysiwyg-wasm"
}
}
```

And consume with code like this:

```html
<script type="module">
import init, { some_method_from_rust }
from './generated/matrix_sdk_wysiwyg.js';
import { initAsync, some_method_from_rust } from '@matrix-org/matrix-sdk-wysiwyg-wasm';

async function run() {
await init();
await initAsync();
some_method_from_rust();
}

Expand All @@ -45,8 +54,8 @@ run();
## Profiling

To generate a debugging/profiling Wasm module, use the following command
instead of `npm run build`:
instead of `yarn build`:

```sh
$ npm run dev-build
$ yarn dev-build
```
64 changes: 64 additions & 0 deletions bindings/wysiwyg-wasm/index-wasm-esm.js
Original file line number Diff line number Diff line change
@@ -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 async function initAsync() {
if (!modPromise) modPromise = loadModule();
await modPromise;
}

// Re-export everything from the generated javascript wrappers
export * from './pkg/wysiwyg_bg.js';
85 changes: 85 additions & 0 deletions bindings/wysiwyg-wasm/index.cjs
Original file line number Diff line number Diff line change
@@ -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 changes: 17 additions & 0 deletions bindings/wysiwyg-wasm/index.d.ts
Original file line number Diff line number Diff line change
@@ -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>;
85 changes: 85 additions & 0 deletions bindings/wysiwyg-wasm/index.js
Original file line number Diff line number Diff line change
@@ -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';
Loading
Loading