Skip to content
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
6 changes: 6 additions & 0 deletions .changeset/legacy-chrome-apis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@plextv/react-lightning': patch
'@plextv/react-native-lightning': patch
---

Run on Chrome 57-73 (webOS 5, Tizen 5.x): the ResizeObserver shim no longer crashes `render()` when the browser has no ResizeObserver or DOMRectReadOnly, microtasks fall back to a promise where `queueMicrotask` is missing, and `Array.prototype.at` / `flat` are no longer used on the render, focus and key paths.
6 changes: 6 additions & 0 deletions .changeset/reanimated-legacy-chrome.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@plextv/react-lightning': patch
'@plextv/react-lightning-plugin-reanimated': patch
---

Animated styles and `:focus-within` no longer need Chrome 71+ / 92+: they schedule through the exported `scheduleMicrotask` (a promise where `queueMicrotask` is missing) and read the focus leaf by index instead of `Array.prototype.at`.
5 changes: 5 additions & 0 deletions .changeset/virtuallist-no-globalthis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@plextv/react-lightning-components': patch
---

VirtualList reads the pointer focus-scroll hook off `window` (or `self`) instead of `globalThis`, which needs Chrome 71.
5 changes: 5 additions & 0 deletions .changeset/yoga-mvp-wasm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@plextv/react-lightning-plugin-flexbox': patch
---

ship yoga with its wasm lowered to mvp so layout runs on chrome 57-73 tvs, and build the inline worker for chrome 56 like the rest of the package
7 changes: 6 additions & 1 deletion .oxfmtrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,10 @@
"internalPattern": ["@plextv/", "@repo/"],
"partitionByNewline": false
},
"ignorePatterns": [".changeset", "**/public", "**/dist"]
"ignorePatterns": [
".changeset",
"**/public",
"**/dist",
"packages/plugin-flexbox/src/yoga/vendor"
]
}
2 changes: 1 addition & 1 deletion .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@
"jsx-a11y/mouse-events-have-key-events": "off",
"jsx-a11y/click-events-have-key-events": "off"
},
"ignorePatterns": ["dist", ".changeset", "**/public"]
"ignorePatterns": ["dist", ".changeset", "**/public", "packages/plugin-flexbox/src/yoga/vendor"]
}
3 changes: 2 additions & 1 deletion packages/plugin-flexbox/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@
"test:unit": "vitest run --passWithNoTests",
"bench": "tsx bench/cli.ts",
"bench:update": "tsx bench/cli.ts --update",
"bench:save": "tsx bench/cli.ts save"
"bench:save": "tsx bench/cli.ts save",
"vendor:yoga": "tsx scripts/vendorYoga.ts"
},
"dependencies": {
"tseep": "catalog:",
Expand Down
82 changes: 82 additions & 0 deletions packages/plugin-flexbox/scripts/vendorYoga.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { execFileSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

// Rebuilds src/yoga/vendor from the installed yoga-layout with its wasm lowered
// to WebAssembly MVP, so Yoga instantiates on Chrome 57-73 (sign-ext needs 74).
// Run `pnpm vendor:yoga` after bumping yoga-layout; yogaVendor.spec.ts fails
// until you do.

const BINARYEN = 'binaryen@132.0.0';
const DATA_URI_PREFIX = 'data:application/octet-stream;base64,';
const SCRIPT_DIR_LINE = 'var _scriptDir = import.meta.url;';

const pkgDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const outDir = path.join(pkgDir, 'src/yoga/vendor');
const yogaDist = path.resolve(fileURLToPath(import.meta.resolve('yoga-layout/load')), '../..');
const yogaPkgJson = path.join(yogaDist, '../package.json');
const yogaVersion: string = JSON.parse(readFileSync(yogaPkgJson, 'utf8')).version;

const readUpstream = (file: string) =>
readFileSync(path.join(yogaDist, file), 'utf8').replace(/\n\/\/# sourceMappingURL=.*$/m, '\n');

const wasmOpt = (args: string[]) =>
execFileSync('pnpm', ['--silent', `--package=${BINARYEN}`, 'dlx', 'wasm-opt', ...args], {
stdio: ['ignore', 'inherit', 'inherit'],
env: { ...process.env, NODE_NO_WARNINGS: '1' },
});

const glue = readUpstream('binaries/yoga-wasm-base64-esm.js');
const match = glue.match(/data:application\/octet-stream;base64,([A-Za-z0-9+/=]+)/);

if (!match?.[1]) {
throw new Error('No embedded wasm found in yoga-wasm-base64-esm.js');
}

if (!glue.includes(SCRIPT_DIR_LINE)) {
throw new Error(`Expected "${SCRIPT_DIR_LINE}" in the yoga glue, check the upstream build`);
}

const original = Buffer.from(match[1], 'base64');
const tmp = mkdtempSync(path.join(tmpdir(), 'yoga-mvp-'));

try {
const inFile = path.join(tmp, 'yoga.wasm');
const outFile = path.join(tmp, 'yoga-mvp.wasm');

writeFileSync(inFile, original);
wasmOpt([inFile, '--enable-sign-ext', '--signext-lowering', '-o', outFile]);
// Fails (non-zero exit) if anything beyond MVP is left.
wasmOpt([outFile, '--mvp-features', '--quiet', '-o', path.join(tmp, 'validated.wasm')]);

const lowered = readFileSync(outFile);
const sha = createHash('sha256').update(original).digest('hex');
const header = [
`// Generated by scripts/vendorYoga.ts from yoga-layout@${yogaVersion} (MIT, Meta Platforms, Inc.). Do not edit.`,
`// The wasm is lowered to WebAssembly MVP with ${BINARYEN} (--signext-lowering).`,
`// source-wasm-sha256: ${sha}`,
'',
].join('\n');

// import.meta is a syntax error before Chrome 64, and the wasm is a data URI so
// the script dir is never used.
const vendoredGlue = glue
.replace(match[0], DATA_URI_PREFIX + lowered.toString('base64'))
.replace(SCRIPT_DIR_LINE, 'var _scriptDir = "";');

writeFileSync(path.join(outDir, 'yoga-wasm-mvp.js'), header + vendoredGlue);
writeFileSync(
path.join(outDir, 'wrapAssembly.js'),
readUpstream('src/wrapAssembly.js').replaceAll('./generated/YGEnums.js', './YGEnums.js'),
);
writeFileSync(path.join(outDir, 'YGEnums.js'), readUpstream('src/generated/YGEnums.js'));

console.log(
`Vendored yoga-layout@${yogaVersion}: ${original.length} -> ${lowered.length} bytes of wasm`,
);
} finally {
rmSync(tmp, { recursive: true, force: true });
}
4 changes: 2 additions & 2 deletions packages/plugin-flexbox/src/YogaManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { YogaOptions } from './types/YogaOptions';
import { SimpleDataView } from './util/SimpleDataView';
import { YogaManager } from './YogaManager';

// Mock the yoga-layout/load module
// Mock the vendored yoga loader
const mockNode = {
create: vi.fn(),
free: vi.fn(),
Expand Down Expand Up @@ -63,7 +63,7 @@ const mockYoga = {
DISPLAY_NONE: 1,
};

vi.mock('yoga-layout/load', () => ({
vi.mock('./yoga/loadYoga', () => ({
loadYoga: vi.fn(() => Promise.resolve(mockYoga)),
}));

Expand Down
8 changes: 5 additions & 3 deletions packages/plugin-flexbox/src/YogaManager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { EventEmitter } from 'tseep';
import { type Config, loadYoga, type Yoga } from 'yoga-layout/load';
import type { Config, Yoga } from 'yoga-layout/load';

import type { LightningElementStyle, Rect } from '@plextv/react-lightning';

Expand All @@ -10,6 +10,7 @@ import type { YogaOptions } from './types/YogaOptions';
import applyReactPropsToYoga, { applyFlexPropToYoga } from './util/applyReactPropsToYoga';
import { resolveHorizontalTranslate, resolveVerticalTranslate } from './util/resolveTranslateInset';
import { SimpleDataView } from './util/SimpleDataView';
import { loadYoga } from './yoga/loadYoga';

export type BatchedUpdate = Record<number, Partial<Rect>>;

Expand Down Expand Up @@ -361,8 +362,9 @@ export class YogaManager {

// Microtask runs AFTER the current synchronous batch of style/node
// ops (arriving from postMessage handlers); setTimeout's 1ms+ minimum
// would fragment a batch into many render passes.
queueMicrotask(() => {
// would fragment a batch into many render passes. Not queueMicrotask: it
// needs Chrome 71, and nothing polyfills the inline worker.
void Promise.resolve().then(() => {
this._isRenderQueued = false;

if (this._independentRoots.size === 0) {
Expand Down
5 changes: 3 additions & 2 deletions packages/plugin-flexbox/src/YogaManagerWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ function debounceMicrotask<T extends (...args: unknown[]) => void | Promise<void
}

scheduled = true;
queueMicrotask(() => {
// Not queueMicrotask, which needs Chrome 71.
void Promise.resolve().then(() => {
scheduled = false;
fn.apply(this, latestArgs);
});
Expand Down Expand Up @@ -396,7 +397,7 @@ function resolveFontUrls(yogaOptions?: YogaOptions): YogaOptions | undefined {
return yogaOptions;
}

const baseHref = globalThis.location?.href;
const baseHref = typeof location === 'undefined' ? undefined : location.href;

return {
...yogaOptions,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { beforeAll, describe, expect, it } from 'vitest';
import type { Node } from 'yoga-layout';
import { loadYoga, type Yoga } from 'yoga-layout/load';
import type { Yoga } from 'yoga-layout/load';

import type { LightningViewElementStyle } from '@plextv/react-lightning';

import type { ManagerNode } from '../types/ManagerNode';
import type { YogaOptions } from '../types/YogaOptions';
import { loadYoga } from '../yoga/loadYoga';
import applyReactPropsToYoga, { applyFlexPropToYoga } from './applyReactPropsToYoga';

// Yoga resolves `alignSelf: auto` to the parent's alignItems, and treats
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { beforeAll, describe, expect, it } from 'vitest';
import type { Node } from 'yoga-layout';
import { loadYoga, type Yoga } from 'yoga-layout/load';
import type { Yoga } from 'yoga-layout/load';

import type { LightningViewElementStyle } from '@plextv/react-lightning';

import type { YogaOptions } from '../types/YogaOptions';
import { loadYoga } from '../yoga/loadYoga';
import { applyFlexPropToYoga } from './applyReactPropsToYoga';

// react-native feeds borderWidth into Yoga (border-box), so a border reserves
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest';
import { loadYoga, type Yoga } from 'yoga-layout/load';
import type { Yoga } from 'yoga-layout/load';

import type { YogaOptions } from '../types/YogaOptions';
import { loadYoga } from '../yoga/loadYoga';
import { applyFlexPropToYoga } from './applyReactPropsToYoga';

const options = { expandToAutoFlexBasis: false } as YogaOptions;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { beforeAll, describe, expect, it } from 'vitest';
import type { Node } from 'yoga-layout';
import { loadYoga, type Yoga } from 'yoga-layout/load';
import type { Yoga } from 'yoga-layout/load';

import type { LightningViewElementStyle } from '@plextv/react-lightning';

import type { YogaOptions } from '../types/YogaOptions';
import { loadYoga } from '../yoga/loadYoga';
import { applyFlexPropToYoga } from './applyReactPropsToYoga';

// RN ships logical start/end insets (LTR: start=left, end=right). react-lightning
Expand Down
12 changes: 12 additions & 0 deletions packages/plugin-flexbox/src/yoga/loadYoga.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { Yoga } from 'yoga-layout/load';

import wrapAssembly from './vendor/wrapAssembly.js';
import loadYogaImpl from './vendor/yoga-wasm-mvp.js';

/**
* Same as `loadYoga` from `yoga-layout/load`, but with the wasm lowered to
* WebAssembly MVP so it runs on Chrome 57+ (the upstream build needs 74).
*/
export async function loadYoga(): Promise<Yoga> {
return wrapAssembly(await loadYogaImpl());
}
Loading
Loading