Add react-native support #63
5 changed files with 72 additions and 7 deletions
commit
2623959c10
|
|
@ -84,8 +84,8 @@ See the [example website source](examples/website/src/index.ts) for a more compl
|
||||||
|
|
||||||
React Native's default engine, Hermes, does not expose a runtime `WebAssembly`
|
React Native's default engine, Hermes, does not expose a runtime `WebAssembly`
|
||||||
global, so the WebAssembly build cannot run there. For React Native, the package
|
global, so the WebAssembly build cannot run there. For React Native, the package
|
||||||
ships a [`wasm2js`](https://github.com/WebAssembly/binaryen) (pure-JavaScript)
|
ships a pure-JavaScript build produced by [Binaryen's `wasm2js`](https://github.com/WebAssembly/binaryen)
|
||||||
build via its `react-native` entry point.
|
via its `react-native` entry point.
|
||||||
|
|
||||||
### Python
|
### Python
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node scripts/build-rn.mjs && webpack --mode production",
|
"build": "node scripts/build-rn.mjs && webpack --mode production",
|
||||||
"format": "prettier --write \"./**/*.(ts|scss|json|html)\"",
|
"format": "prettier --write \"./**/*.(ts|mjs|scss|json|html)\"",
|
||||||
"test": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest"
|
"test": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export interface WasmBackend {
|
||||||
ensureReady(): void;
|
ensureReady(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define the enum values as const arrays to avoid duplication
|
// Define the enum values as a const array to avoid duplication
|
||||||
const BUILTIN_TOKENIZERS = ['Character', 'Line', 'Markdown', 'Word'] as const;
|
const BUILTIN_TOKENIZERS = ['Character', 'Line', 'Markdown', 'Word'] as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -148,7 +148,8 @@ export interface ReconcileApi {
|
||||||
* @param left - The left version of the text (either string or TextWithCursors with cursor positions)
|
* @param left - The left version of the text (either string or TextWithCursors with cursor positions)
|
||||||
* @param right - The right version of the text (either string or TextWithCursors with cursor positions)
|
* @param right - The right version of the text (either string or TextWithCursors with cursor positions)
|
||||||
* @param tokenizer - The tokenisation strategy: "Word" (default, recommended for prose),
|
* @param tokenizer - The tokenisation strategy: "Word" (default, recommended for prose),
|
||||||
* "Character" (fine-grained), or "Line" (similar to git merge)
|
* "Character" (fine-grained), "Line" (similar to git merge), or
|
||||||
|
* "Markdown" (splits on Markdown structure)
|
||||||
* @returns The reconciled text with automatically repositioned cursor positions
|
* @returns The reconciled text with automatically repositioned cursor positions
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
|
|
@ -219,7 +220,8 @@ export interface ReconcileApi {
|
||||||
* @param left - The left version of the text (either string or TextWithCursors with cursor positions)
|
* @param left - The left version of the text (either string or TextWithCursors with cursor positions)
|
||||||
* @param right - The right version of the text (either string or TextWithCursors with cursor positions)
|
* @param right - The right version of the text (either string or TextWithCursors with cursor positions)
|
||||||
* @param tokenizer - The tokenisation strategy: "Word" (default, recommended for prose),
|
* @param tokenizer - The tokenisation strategy: "Word" (default, recommended for prose),
|
||||||
* "Character" (fine-grained), or "Line" (similar to git merge)
|
* "Character" (fine-grained), "Line" (similar to git merge), or
|
||||||
|
* "Markdown" (splits on Markdown structure)
|
||||||
* @returns The reconciled text with cursor positions and detailed change history
|
* @returns The reconciled text with cursor positions and detailed change history
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
|
|
@ -351,7 +353,15 @@ export function makeReconcileApi(backend: WasmBackend): ReconcileApi {
|
||||||
backend.ensureReady();
|
backend.ensureReady();
|
||||||
assertTokenizer(tokenizer);
|
assertTokenizer(tokenizer);
|
||||||
|
|
||||||
return backend.undiff(original, diffValue, tokenizer);
|
// The real-WebAssembly backend's `diff` emits BigInt spans, whereas the
|
||||||
|
// wasm2js (React Native) backend rejects BigInt outright. Normalise to
|
||||||
|
// plain numbers - exactly as `diff` does on the way out - so a `diff`
|
||||||
|
// result round-trips through `undiff` identically on every platform.
|
||||||
|
return backend.undiff(
|
||||||
|
original,
|
||||||
|
diffValue.map((item) => (typeof item === 'bigint' ? Number(item) : item)),
|
||||||
|
tokenizer
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function reconcileWithHistory(
|
function reconcileWithHistory(
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,46 @@ describe('reconcile (wasm2js / React Native build)', () => {
|
||||||
expect(result.text).toEqual('Hi world');
|
expect(result.text).toEqual('Hi world');
|
||||||
expect(result.history.length).toBeGreaterThan(0);
|
expect(result.history.length).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('undiff accepts bigint entries (per the Array<number | bigint | string> type)', () => {
|
||||||
|
const original = 'Hello world';
|
||||||
|
const changed = 'Hello cruel world';
|
||||||
|
|
||||||
|
// `diff` returns plain numbers; emulate a caller that supplies BigInt, which
|
||||||
|
// the public signature permits. The wasm2js (React Native) build rejects raw
|
||||||
|
// BigInt, so the wrapper must normalise it to keep both backends identical.
|
||||||
|
const withBigints = diff(original, changed).map((item) =>
|
||||||
|
typeof item === 'number' ? BigInt(item) : item
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(withBigints.some((item) => typeof item === 'bigint')).toBe(true);
|
||||||
|
expect(undiff(original, withBigints)).toEqual(changed);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('runs every operation with no WebAssembly global (Hermes parity)', () => {
|
||||||
|
// Hermes exposes no `WebAssembly` global - the entire reason the React
|
||||||
|
// Native entry point links a wasm2js (pure-JS) build instead of the real
|
||||||
|
// module. Remove the global to prove the operations never reach for it.
|
||||||
|
const descriptor = Object.getOwnPropertyDescriptor(globalThis, 'WebAssembly');
|
||||||
|
delete (globalThis as { WebAssembly?: unknown }).WebAssembly;
|
||||||
|
try {
|
||||||
|
expect((globalThis as { WebAssembly?: unknown }).WebAssembly).toBeUndefined();
|
||||||
|
|
||||||
|
expect(reconcile('Hello', 'Hello world', 'Hi world').text).toEqual('Hi world');
|
||||||
|
|
||||||
|
const changes = diff('Hello world', 'Hello cruel world');
|
||||||
|
expect(undiff('Hello world', changes)).toEqual('Hello cruel world');
|
||||||
|
|
||||||
|
expect(
|
||||||
|
reconcileWithHistory('Hello', 'Hello world', 'Hi world').history.length
|
||||||
|
).toBeGreaterThan(0);
|
||||||
|
} finally {
|
||||||
|
// Restore the global so the leak check and later suites are unaffected.
|
||||||
|
if (descriptor) {
|
||||||
|
Object.defineProperty(globalThis, 'WebAssembly', descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('test_diff_and_undiff_are_inverse (wasm2js / React Native build)', () => {
|
describe('test_diff_and_undiff_are_inverse (wasm2js / React Native build)', () => {
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,21 @@ describe('reconcile', () => {
|
||||||
expect(result.text).toEqual('Hi world');
|
expect(result.text).toEqual('Hi world');
|
||||||
expect(result.history.length).toBeGreaterThan(0);
|
expect(result.history.length).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('undiff accepts bigint entries (per the Array<number | bigint | string> type)', () => {
|
||||||
|
const original = 'Hello world';
|
||||||
|
const changed = 'Hello cruel world';
|
||||||
|
|
||||||
|
// `diff` returns plain numbers; emulate a caller that supplies BigInt, which
|
||||||
|
// the public signature permits. The wasm2js (React Native) build rejects raw
|
||||||
|
// BigInt, so the wrapper must normalise it to keep both backends identical.
|
||||||
|
const withBigints = diff(original, changed).map((item) =>
|
||||||
|
typeof item === 'number' ? BigInt(item) : item
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(withBigints.some((item) => typeof item === 'bigint')).toBe(true);
|
||||||
|
expect(undiff(original, withBigints)).toEqual(changed);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('test_diff_and_undiff_are_inverse', () => {
|
describe('test_diff_and_undiff_are_inverse', () => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue