Consolidate JavaScript Flavors (#1433)

Summary:
Fixes https://github.com/facebook/yoga/issues/1417

This dramatically simplifies the matrix of Node vs web, ASM vs WASM, sync vs async compilation, or CommonJS vs ES Modules. We have one variant, using wasm, with ESModule top-level await to do async compilation. Web/node share the same binary, and we base64 encode the WASM into a wrapper JS file for compatibility with Node and bundlers.

This has some downsides, like requiring an environment with top level await, but also has upsides, like a consistent, sync looking API compatible with older Yoga, and mitigating TypeScript issues with package exports and typings resolution.

As part of this work I also removed `ts-node` from the toolchain (at the cost of a couple of config files needing to be vanilla JS).

Pull Request resolved: https://github.com/facebook/yoga/pull/1433

Test Plan:
1. `yarn test`
2. `yarn lint`
3. `yarn tsc`
4. `yarn benchmark`
5. `yarn build` website-next
6. `yarn lint` website-next
7. Locally test website-next
8. Examine package artifact created by GitHub
9. All Automation passes

Reviewed By: yungsters

Differential Revision: D50453324

Pulled By: NickGerleman

fbshipit-source-id: fe1192acc69e57fa69a1ff056dd7b5844d2198d5
This commit is contained in:
Nick Gerleman
2023-10-31 20:41:38 -07:00
committed by Facebook GitHub Bot
parent b19a07cff9
commit ef1d772447
68 changed files with 853 additions and 2394 deletions

88
.eslintrc.cjs Normal file
View File

@@ -0,0 +1,88 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
module.exports = {
root: true,
ignorePatterns: [
'/website',
'**/binaries/**',
'**/build/**',
'**/generated/**',
],
overrides: [
// Catch-all
{
files: ['**/*.?(m|c){j,t}s?(x)'],
extends: ['eslint:recommended', 'plugin:prettier/recommended'],
plugins: ['prettier'],
rules: {
'no-unused-vars': ['error', {argsIgnorePattern: '^_'}],
'no-var': 'error',
'prefer-arrow-callback': 'error',
'prefer-const': 'error',
'prefer-object-spread': 'error',
'prefer-spread': 'error',
'require-await': 'error',
},
env: {
es2020: true,
},
parserOptions: {
ecmaVersion: 'latest',
},
},
// ES Modules
{
files: ['**/*.?(m){j,t}s?(x)'],
parserOptions: {
sourceType: 'module',
},
},
// CommonJS Modules
{
files: ['**/*.c{j,t}s?(x)'],
env: {
commonjs: true,
},
parserOptions: {
sourceType: 'commonjs',
},
},
// TypeScript
{
files: ['**/*.?(m|c)ts?(x)'],
extends: ['plugin:@typescript-eslint/recommended'],
parser: '@typescript-eslint/parser',
parserOptions: {
project: true,
},
plugins: ['@typescript-eslint'],
rules: {
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{argsIgnorePattern: '^_'},
],
'@typescript-eslint/no-var-requires': 'off',
},
},
// Node
{
files: ['**/.*rc.(c){j,t}s', '**/*.config.?(c){j,t}s'],
env: {
node: true,
},
},
// Jest
{
files: ['**/tests/**'],
extends: ['plugin:jest/recommended'],
},
],
};

View File

@@ -1,76 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
module.exports = {
root: true,
ignorePatterns: [
'/website',
'**/binaries/**',
'**/build/**',
'**/generated/**',
],
extends: ['eslint:recommended', 'plugin:prettier/recommended'],
plugins: ['prettier'],
rules: {
'no-unused-vars': ['error', {argsIgnorePattern: '^_'}],
'no-var': 'error',
'prefer-arrow-callback': 'error',
'prefer-const': 'error',
'prefer-object-spread': 'error',
'prefer-spread': 'error',
'require-await': 'error',
},
env: {
commonjs: true,
es2018: true,
},
parserOptions: {
sourceType: 'module',
ecmaVersion: 'latest',
},
overrides: [
{
files: ['**/*.ts', '**/*.tsx'],
extends: ['plugin:@typescript-eslint/recommended'],
parser: '@typescript-eslint/parser',
parserOptions: {
project: true,
},
plugins: ['@typescript-eslint'],
rules: {
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{argsIgnorePattern: '^_'},
],
'@typescript-eslint/no-var-requires': 'off',
},
},
{
files: ['**/.eslintrc.js', '**/just.config.js'],
env: {
node: true,
},
},
{
files: ['jest.*', '**/tests/**'],
env: {
node: true,
},
extends: ['plugin:jest/recommended'],
globals: {
getMeasureCounter: 'writable',
getMeasureCounterMax: 'writable',
getMeasureCounterMin: 'writable',
Yoga: 'writable',
YGBENCHMARK: 'writable',
},
},
],
};

View File

@@ -23,7 +23,7 @@ JavascriptEmitter.prototype = Object.create(Emitter.prototype, {
emitPrologue: { emitPrologue: {
value: function () { value: function () {
this.push('import {Yoga} from "../tools/globals";'); this.push("import Yoga from 'yoga-layout';");
this.push('import {'); this.push('import {');
this.pushIndent(); this.pushIndent();
this.push('Align,'); this.push('Align,');

View File

@@ -31,9 +31,9 @@ add_compile_options(${COMPILE_OPTIONS})
add_link_options( add_link_options(
${COMPILE_OPTIONS} ${COMPILE_OPTIONS}
--closure 1 "SHELL:--closure 1"
--memory-init-file 0 "SHELL:--memory-init-file 0"
--no-entry "SHELL:--no-entry"
"SHELL:-s ALLOW_MEMORY_GROWTH=1" "SHELL:-s ALLOW_MEMORY_GROWTH=1"
"SHELL:-s ASSERTIONS=0" "SHELL:-s ASSERTIONS=0"
"SHELL:-s DYNAMIC_EXECUTION=0" "SHELL:-s DYNAMIC_EXECUTION=0"
@@ -42,8 +42,9 @@ add_link_options(
"SHELL:-s FILESYSTEM=0" "SHELL:-s FILESYSTEM=0"
"SHELL:-s MALLOC='emmalloc'" "SHELL:-s MALLOC='emmalloc'"
"SHELL:-s MODULARIZE=1" "SHELL:-s MODULARIZE=1"
"SHELL:-s TEXTDECODER=0" "SHELL:-s EXPORT_ES6=1"
"SHELL:-s SINGLE_FILE=1") "SHELL:-s WASM=1"
"SHELL:-s TEXTDECODER=0")
link_libraries(embind) link_libraries(embind)
@@ -51,62 +52,9 @@ add_library(yogaObjLib OBJECT ${SOURCES})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/binaries) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/binaries)
add_executable(asmjs-sync-node $<TARGET_OBJECTS:yogaObjLib>) add_executable(web $<TARGET_OBJECTS:yogaObjLib>)
target_link_options(asmjs-sync-node PUBLIC target_link_options(web PRIVATE
"SHELL:-s ENVIRONMENT='node'" # SINGLE_FILE=1 combined with ENVIRONMENT='web' creates code that works on
"SHELL:-s WASM=0" # both bundlders and Node.
"SHELL:-s WASM_ASYNC_COMPILATION=0") "SHELL:-s SINGLE_FILE=1"
"SHELL:-s ENVIRONMENT='web'")
add_executable(asmjs-async-node $<TARGET_OBJECTS:yogaObjLib>)
target_link_options(asmjs-async-node PUBLIC
"SHELL:-s ENVIRONMENT='node'"
"SHELL:-s WASM=0"
"SHELL:-s WASM_ASYNC_COMPILATION=1")
add_executable(asmjs-sync-web $<TARGET_OBJECTS:yogaObjLib>)
target_link_options(asmjs-sync-web PUBLIC
"SHELL:-s ENVIRONMENT='web'"
"SHELL:-s WASM=0"
"SHELL:-s WASM_ASYNC_COMPILATION=0")
add_executable(asmjs-async-web $<TARGET_OBJECTS:yogaObjLib>)
target_link_options(asmjs-async-web PUBLIC
"SHELL:-s ENVIRONMENT='web'"
"SHELL:-s WASM=0"
"SHELL:-s WASM_ASYNC_COMPILATION=1")
add_executable(asmjs-sync $<TARGET_OBJECTS:yogaObjLib>)
target_link_options(asmjs-sync PUBLIC
"SHELL:-s ENVIRONMENT='web,node'"
"SHELL:-s WASM=0"
"SHELL:-s WASM_ASYNC_COMPILATION=0")
add_executable(asmjs-async $<TARGET_OBJECTS:yogaObjLib>)
target_link_options(asmjs-async PUBLIC
"SHELL:-s ENVIRONMENT='web,node'"
"SHELL:-s WASM=0"
"SHELL:-s WASM_ASYNC_COMPILATION=1")
add_executable(wasm-sync-node $<TARGET_OBJECTS:yogaObjLib>)
target_link_options(wasm-sync-node PUBLIC
"SHELL:-s ENVIRONMENT='node'"
"SHELL:-s WASM=1"
"SHELL:-s WASM_ASYNC_COMPILATION=0")
add_executable(wasm-async-node $<TARGET_OBJECTS:yogaObjLib>)
target_link_options(wasm-async-node PUBLIC
"SHELL:-s ENVIRONMENT='node'"
"SHELL:-s WASM=1"
"SHELL:-s WASM_ASYNC_COMPILATION=1")
add_executable(wasm-sync-web $<TARGET_OBJECTS:yogaObjLib>)
target_link_options(wasm-sync-web PUBLIC
"SHELL:-s ENVIRONMENT='web'"
"SHELL:-s WASM=1"
"SHELL:-s WASM_ASYNC_COMPILATION=0")
add_executable(wasm-async-web $<TARGET_OBJECTS:yogaObjLib>)
target_link_options(wasm-async-web PUBLIC
"SHELL:-s ENVIRONMENT='web'"
"SHELL:-s WASM=1"
"SHELL:-s WASM_ASYNC_COMPILATION=1")

View File

@@ -1,24 +1,11 @@
# yoga-layout # yoga-layout
This package provides prebuilt JavaScript bindings for the Yoga layout engine. Both WebAssembly and asm.js variants are packaged, with the optimal loaded based on platform. This package provides prebuilt WebAssembly bindings for the Yoga layout engine.
## Usage ## Usage
The default entrypoint provides an asynchronous loader function to return a Yoga instance.
```ts ```ts
import {loadYoga, Align} from 'yoga-layout'; import {Yoga, Align} from 'yoga-layout';
const Yoga = await loadYoga();
const node = Yoga.Node.create();
node.setAlignContent(Align.Center);
```
An alternative synchronous API is provided for compatibility, but requires using asm.js in browsers instead of WebAssembly, leading to worse performance and larger assets.
```ts
import Yoga, {Align} from 'yoga-layout/sync';
const node = Yoga.Node.create(); const node = Yoga.Node.create();
node.setAlignContent(Align.Center); node.setAlignContent(Align.Center);
@@ -37,20 +24,14 @@ node.freeRecursive();
node.free(); node.free();
``` ```
## Selecting WebAssembly or asm.js
For better performance and smaller packages, WebAssembly is preferred to asm.js where available. `yoga-layout` tries to provide the right default using [export maps](https://webpack.js.org/guides/package-exports/#conditional-syntax) so that platforms which can take advantage of WebAssembly use it by default.
Different entrypoints are exposed to choose a flavor explicitly.
```ts
import {loadYoga} from 'yoga-layout/wasm-async';
```
## Using TypeScript ## Using TypeScript
This package provides out-of-the-box TypeScript typings so long as `tsc` is configured to support ESM resolution. It is recommended to set `moduleResolution: 'bundler'` or `moduleResolution: node16` in your `tsconfig.json` according to your environment. This package provides out-of-the-box TypeScript typings so long as `tsc` is configured to support ESM resolution. It is recommended to set `moduleResolution: 'bundler'` or `moduleResolution: node16` in your `tsconfig.json` according to your environment.
## ES Modules
`yoga-layout` is only provided as an ES Module, relying on top-level await. This allows providing a synchronous API, while still allowing async WebAssembly compilation in browsers, and will allow eventual usage of ESM/WASM interop.
## Contributing ## Contributing
### Requirements ### Requirements

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
module.exports = api => ({
presets: [
[
'@babel/preset-env',
{
targets: [
'maintained node versions',
'> 0.5%, last 2 versions, Firefox ESR, not dead',
],
// Do not transform to another module system
modules: false,
},
],
[
'@babel/preset-typescript',
{
rewriteImportExtensions: api.env('dist'),
},
],
],
});

View File

@@ -7,9 +7,8 @@
* @format * @format
*/ */
module.exports = { export default {
presets: [ setupFiles: ['./jest.setup.js'],
['@babel/preset-env', {targets: 'defaults'}], testRegex: '/tests/.*\\.test\\.ts$',
'@babel/preset-typescript', extensionsToTreatAsEsm: ['.ts'],
],
}; };

View File

@@ -1,27 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import type {Config} from 'jest';
const config: Config = {
setupFiles: ['./jest.setup.ts'],
testRegex: '/tests/.*\\.test\\.[jt]s$',
moduleNameMapper: {
'yoga-layout':
process.env['SYNC'] === '1' && process.env['WASM'] === '1'
? 'yoga-layout/wasm-sync'
: process.env['SYNC'] === '1'
? 'yoga-layout/asmjs-sync'
: process.env['WASM'] === '1'
? 'yoga-layout/wasm-async'
: 'yoga-layout/asmjs-async',
},
};
export default config;

View File

@@ -7,11 +7,6 @@
* @format * @format
*/ */
module.exports = async () => {
const {loadYoga, default: Yoga} = require('yoga-layout');
globalThis.Yoga = Yoga ?? (await loadYoga());
};
Object.defineProperty(globalThis, 'YGBENCHMARK', { Object.defineProperty(globalThis, 'YGBENCHMARK', {
get: () => globalThis.test, get: () => globalThis.test,
}); });

View File

@@ -7,7 +7,7 @@
* @format * @format
*/ */
import { const {
argv, argv,
cleanTask, cleanTask,
logger, logger,
@@ -18,13 +18,13 @@ import {
spawn, spawn,
task, task,
tscTask, tscTask,
} from 'just-scripts'; } = require('just-scripts');
import {readFile, writeFile} from 'fs/promises'; const {readFile, writeFile} = require('fs/promises');
import glob from 'glob'; const glob = require('glob');
import path from 'path'; const path = require('path');
import which from 'which'; const which = require('which');
const node = process.execPath; const node = process.execPath;
@@ -32,11 +32,15 @@ option('fix');
task('clean', cleanTask({paths: ['build', 'dist']})); task('clean', cleanTask({paths: ['build', 'dist']}));
function defineFlavor(flavor: string, env: NodeJS.ProcessEnv) { function defineFlavor(flavor, env) {
task(`cmake-build:${flavor}`, cmakeBuildTask({targets: [flavor]})); task(`cmake-build:${flavor}`, cmakeBuildTask({targets: [flavor]}));
task( task(
`jest:${flavor}`, `jest:${flavor}`,
jestTask({config: path.join(__dirname, 'jest.config.ts'), env}), jestTask({
config: path.join(__dirname, 'jest.config.js'),
nodeArgs: ['--experimental-vm-modules'],
env,
}),
); );
task( task(
`test:${flavor}`, `test:${flavor}`,
@@ -44,33 +48,17 @@ function defineFlavor(flavor: string, env: NodeJS.ProcessEnv) {
); );
} }
defineFlavor('asmjs-async-node', {WASM: '0', SYNC: '0'}); defineFlavor('web');
defineFlavor('asmjs-sync-node', {WASM: '0', SYNC: '1'});
defineFlavor('asmjs-async-web', {WASM: '0', SYNC: '0'});
defineFlavor('asmjs-sync-web', {WASM: '0', SYNC: '1'});
defineFlavor('wasm-async-node', {WASM: '1', SYNC: '0'});
defineFlavor('wasm-sync-node', {WASM: '1', SYNC: '1'});
defineFlavor('wasm-async-web', {WASM: '1', SYNC: '0'});
defineFlavor('wasm-sync-web', {WASM: '1', SYNC: '1'});
task('build', series(emcmakeGenerateTask(), cmakeBuildTask())); task('build', series(emcmakeGenerateTask(), cmakeBuildTask()));
task( task('test', series(emcmakeGenerateTask(), 'cmake-build:web', 'jest:web'));
'test',
series(
emcmakeGenerateTask(),
series('cmake-build:asmjs-async-node', 'jest:asmjs-async-node'),
series('cmake-build:asmjs-sync-node', 'jest:asmjs-sync-node'),
series('cmake-build:wasm-async-node', 'jest:wasm-async-node'),
series('cmake-build:wasm-sync-node', 'jest:wasm-sync-node'),
),
);
task( task(
'benchmark', 'benchmark',
series( series(
emcmakeGenerateTask(), emcmakeGenerateTask(),
cmakeBuildTask({targets: ['asmjs-sync-node', 'wasm-sync-node']}), cmakeBuildTask({targets: ['web']}),
runBenchTask(), runBenchTask(),
), ),
); );
@@ -95,21 +83,17 @@ task(
), ),
); );
function recursiveReplace( function recursiveReplace(obj, pattern, replacement) {
obj: Record<string, unknown>,
pattern: RegExp,
replacement: string,
) {
for (const [key, value] of Object.entries(obj)) { for (const [key, value] of Object.entries(obj)) {
if (typeof value === 'string') { if (typeof value === 'string') {
obj[key] = value.replace(pattern, replacement); obj[key] = value.replace(pattern, replacement);
} else if (typeof value === 'object' && value != null) { } else if (typeof value === 'object' && value != null) {
recursiveReplace(value as Record<string, unknown>, pattern, replacement); recursiveReplace(value, pattern, replacement);
} }
} }
} }
function babelTransformTask(opts: {dir: string}) { function babelTransformTask(opts) {
return () => { return () => {
const args = [ const args = [
opts.dir, opts.dir,
@@ -117,12 +101,16 @@ function babelTransformTask(opts: {dir: string}) {
'--out-dir', '--out-dir',
opts.dir, opts.dir,
'--extensions', '--extensions',
'.js,.ts', '.js,.cjs,.mjs,.ts,.cts,.mts',
]; ];
logger.info(`Transforming "${path.resolve(opts.dir)}"`); logger.info(`Transforming "${path.resolve(opts.dir)}"`);
return spawn(node, [require.resolve('@babel/cli/bin/babel'), ...args], { return spawn(node, [require.resolve('@babel/cli/bin/babel'), ...args], {
cwd: __dirname, cwd: __dirname,
env: {
// Trigger distribution-specific Babel transforms
NODE_ENV: 'dist',
},
}); });
}; };
} }
@@ -132,23 +120,15 @@ function runBenchTask() {
const files = glob.sync('./tests/Benchmarks/**/*'); const files = glob.sync('./tests/Benchmarks/**/*');
const args = [ const args = [
'--extensions', '--loader=babel-register-esm',
'.js,.ts',
'--config-file',
path.join(__dirname, '.babelrc.js'),
'--',
'./tests/bin/run-bench.ts', './tests/bin/run-bench.ts',
...files, ...files,
]; ];
logger.info(['babel-node', ...args].join(' ')); logger.info(['node', ...args].join(' '));
return spawn( return spawn(node, args, {
node, stdio: 'inherit',
[require.resolve('@babel/node/bin/babel-node'), ...args], });
{
stdio: 'inherit',
},
);
}; };
} }
@@ -170,7 +150,7 @@ function emcmakeGenerateTask() {
}; };
} }
function cmakeBuildTask(opts?: {targets?: ReadonlyArray<string>}) { function cmakeBuildTask(opts) {
return () => { return () => {
const cmake = which.sync('cmake'); const cmake = which.sync('cmake');
const args = [ const args = [
@@ -184,7 +164,7 @@ function cmakeBuildTask(opts?: {targets?: ReadonlyArray<string>}) {
}; };
} }
function clangFormatTask(opts?: {fix?: boolean}) { function clangFormatTask(opts) {
return () => { return () => {
const args = [ const args = [
...(opts?.fix ? ['-i'] : ['--dry-run', '--Werror']), ...(opts?.fix ? ['-i'] : ['--dry-run', '--Werror']),

View File

@@ -9,69 +9,38 @@
"type": "git", "type": "git",
"url": "git@github.com:facebook/yoga.git" "url": "git@github.com:facebook/yoga.git"
}, },
"exports": { "type": "module",
".": { "main": "./src/index.ts",
"browser": "./src/entrypoint/wasm-async-web.ts",
"node": "./src/entrypoint/wasm-async-node.ts",
"default": "./src/entrypoint/asmjs-async-web.ts"
},
"./sync": {
"browser": "./src/entrypoint/asmjs-sync-web.ts",
"node": "./src/entrypoint/wasm-sync-node.ts",
"default": "./src/entrypoint/asmjs-sync-web.ts"
},
"./asmjs-async": {
"browser": "./src/entrypoint/asmjs-async-web.ts",
"node": "./src/entrypoint/asmjs-async-node.ts",
"default": "./src/entrypoint/asmjs-async-web.ts"
},
"./asmjs-sync": {
"browser": "./src/entrypoint/asmjs-sync-web.ts",
"node": "./src/entrypoint/asmjs-sync-node.ts",
"default": "./src/entrypoint/asmjs-sync-web.ts"
},
"./wasm-async": {
"browser": "./src/entrypoint/wasm-async-web.ts",
"node": "./src/entrypoint/wasm-async-node.ts",
"default": "./src/entrypoint/wasm-async-web.ts"
},
"./wasm-sync": {
"browser": "./src/entrypoint/wasm-sync-web.ts",
"node": "./src/entrypoint/wasm-sync-node.ts",
"default": "./src/entrypoint/wasm-async-web.ts"
}
},
"files": [ "files": [
"binaries/**", "binaries/**",
"src/**" "src/**"
], ],
"scripts": { "scripts": {
"benchmark": "just benchmark", "benchmark": "just benchmark --config just.config.cjs",
"build": "just build", "build": "just build --config just.config.cjs",
"clang-format": "just clang-format", "clang-format": "just clang-format --config just.config.cjs",
"clang-format:fix": "just clang-format --fix", "clang-format:fix": "just clang-format --fix --config just.config.cjs",
"clean": "just clean", "clean": "just clean --config just.config.cjs",
"lint": "eslint .", "lint": "eslint .",
"lint:fix": "eslint . --fix", "lint:fix": "eslint . --fix",
"prepack": "just prepack", "prepack": "just prepack --config just.config.cjs",
"test": "just test", "test": "just test --config just.config.cjs",
"tsc": "tsc --noEmit" "tsc": "tsc --noEmit"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.21.4", "@babel/cli": "^7.23.0",
"@babel/core": "^7.21.4", "@babel/core": "^7.23.0",
"@babel/node": "^7.21.4", "@babel/preset-env": "^7.23.0",
"@babel/preset-env": "^7.21.4", "@babel/preset-typescript": "^7.23.0",
"@babel/preset-typescript": "^7.21.4",
"@types/glob": "^8.1.0", "@types/glob": "^8.1.0",
"@types/jest": "^29.5.1", "@types/jest": "^29.5.1",
"@types/node": "^16.18.25", "@types/node": "^16.18.25",
"@types/which": "^3.0.0", "@types/which": "^3.0.0",
"babel-register-esm": "^1.2.5",
"clang-format": "^1.8.0", "clang-format": "^1.8.0",
"glob": "^8.0.3", "glob": "^8.0.3",
"jest": "^29.3.1", "jest": "^29.3.1",
"just-scripts": "^2.1.0", "just-scripts": "^2.1.0",
"ts-node": "^10.9.1",
"which": "^3.0.0" "which": "^3.0.0"
} }
} }

View File

@@ -1,26 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import wrapAssembly from '../wrapAssembly';
import type {Yoga} from '../wrapAssembly';
export * from '../generated/YGEnums';
export type {
Config,
DirtiedFunction,
MeasureFunction,
Node,
Yoga,
} from '../wrapAssembly';
const loadAssembly = require('../../binaries/asmjs-async-node');
export async function loadYoga(): Promise<Yoga> {
return wrapAssembly(await loadAssembly());
}

View File

@@ -1,26 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import wrapAssembly from '../wrapAssembly';
import type {Yoga} from '../wrapAssembly';
export * from '../generated/YGEnums';
export type {
Config,
DirtiedFunction,
MeasureFunction,
Node,
Yoga,
} from '../wrapAssembly';
const loadAssembly = require('../../binaries/asmjs-async-web');
export async function loadYoga(): Promise<Yoga> {
return wrapAssembly(await loadAssembly());
}

View File

@@ -1,23 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import wrapAssembly from '../wrapAssembly';
export * from '../generated/YGEnums';
export type {
Config,
DirtiedFunction,
MeasureFunction,
Node,
Yoga,
} from '../wrapAssembly';
const loadAssembly = require('../../binaries/asmjs-sync-node');
const Yoga = wrapAssembly(loadAssembly());
export default Yoga;

View File

@@ -1,23 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import wrapAssembly from '../wrapAssembly';
export * from '../generated/YGEnums';
export type {
Config,
DirtiedFunction,
MeasureFunction,
Node,
Yoga,
} from '../wrapAssembly';
const loadAssembly = require('../../binaries/asmjs-sync-web');
const Yoga = wrapAssembly(loadAssembly());
export default Yoga;

View File

@@ -1,26 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import wrapAssembly from '../wrapAssembly';
import type {Yoga} from '../wrapAssembly';
export * from '../generated/YGEnums';
export type {
Config,
DirtiedFunction,
MeasureFunction,
Node,
Yoga,
} from '../wrapAssembly';
const loadAssembly = require('../../binaries/wasm-async-node');
export async function loadYoga(): Promise<Yoga> {
return wrapAssembly(await loadAssembly());
}

View File

@@ -1,26 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import wrapAssembly from '../wrapAssembly';
import type {Yoga} from '../wrapAssembly';
export * from '../generated/YGEnums';
export type {
Config,
DirtiedFunction,
MeasureFunction,
Node,
Yoga,
} from '../wrapAssembly';
const loadAssembly = require('../../binaries/wasm-async-web');
export async function loadYoga(): Promise<Yoga> {
return wrapAssembly(await loadAssembly());
}

View File

@@ -1,23 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import wrapAssembly from '../wrapAssembly';
export * from '../generated/YGEnums';
export type {
Config,
DirtiedFunction,
MeasureFunction,
Node,
Yoga,
} from '../wrapAssembly';
const loadAssembly = require('../../binaries/wasm-sync-node');
const Yoga = wrapAssembly(loadAssembly());
export default Yoga;

View File

@@ -7,17 +7,17 @@
* @format * @format
*/ */
import wrapAssembly from '../wrapAssembly'; // @ts-ignore untyped from Emscripten
import loadYoga from '../binaries/web.js';
import wrapAssembly from './wrapAssembly.ts';
export * from '../generated/YGEnums';
export type { export type {
Config, Config,
DirtiedFunction, DirtiedFunction,
MeasureFunction, MeasureFunction,
Node, Node,
Yoga, } from './wrapAssembly.ts';
} from '../wrapAssembly';
const loadAssembly = require('../../binaries/wasm-sync-web'); const Yoga = wrapAssembly(await loadYoga());
const Yoga = wrapAssembly(loadAssembly());
export default Yoga; export default Yoga;
export * from './generated/YGEnums.ts';

View File

@@ -1,146 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import YGEnums, {Unit, Direction} from './generated/YGEnums';
export default function wrapAssembly(lib) {
function patch(prototype, name, fn) {
const original = prototype[name];
prototype[name] = function (...args) {
return fn.call(this, original, ...args);
};
}
for (const fnName of [
'setPosition',
'setMargin',
'setFlexBasis',
'setWidth',
'setHeight',
'setMinWidth',
'setMinHeight',
'setMaxWidth',
'setMaxHeight',
'setPadding',
]) {
const methods = {
[Unit.Point]: lib.Node.prototype[fnName],
[Unit.Percent]: lib.Node.prototype[`${fnName}Percent`],
[Unit.Auto]: lib.Node.prototype[`${fnName}Auto`],
};
patch(lib.Node.prototype, fnName, function (original, ...args) {
// We patch all these functions to add support for the following calls:
// .setWidth(100) / .setWidth("100%") / .setWidth(.getWidth()) / .setWidth("auto")
const value = args.pop();
let unit, asNumber;
if (value === 'auto') {
unit = Unit.Auto;
asNumber = undefined;
} else if (typeof value === 'object') {
unit = value.unit;
asNumber = value.valueOf();
} else {
unit =
typeof value === 'string' && value.endsWith('%')
? Unit.Percent
: Unit.Point;
asNumber = parseFloat(value);
if (!Number.isNaN(value) && Number.isNaN(asNumber)) {
throw new Error(`Invalid value ${value} for ${fnName}`);
}
}
if (!methods[unit])
throw new Error(
`Failed to execute "${fnName}": Unsupported unit '${value}'`,
);
if (asNumber !== undefined) {
return methods[unit].call(this, ...args, asNumber);
} else {
return methods[unit].call(this, ...args);
}
});
}
function wrapMeasureFunction(measureFunction) {
return lib.MeasureCallback.implement({
measure: (...args) => {
const {width, height} = measureFunction(...args);
return {
width: width ?? NaN,
height: height ?? NaN,
};
},
});
}
patch(lib.Node.prototype, 'setMeasureFunc', function (original, measureFunc) {
// This patch is just a convenience patch, since it helps write more
// idiomatic source code (such as .setMeasureFunc(null))
if (measureFunc) {
return original.call(this, wrapMeasureFunction(measureFunc));
} else {
return this.unsetMeasureFunc();
}
});
function wrapDirtiedFunc(dirtiedFunction) {
return lib.DirtiedCallback.implement({dirtied: dirtiedFunction});
}
patch(lib.Node.prototype, 'setDirtiedFunc', function (original, dirtiedFunc) {
original.call(this, wrapDirtiedFunc(dirtiedFunc));
});
patch(lib.Config.prototype, 'free', function () {
// Since we handle the memory allocation ourselves (via lib.Config.create),
// we also need to handle the deallocation
lib.Config.destroy(this);
});
patch(lib.Node, 'create', (_, config) => {
// We decide the constructor we want to call depending on the parameters
return config
? lib.Node.createWithConfig(config)
: lib.Node.createDefault();
});
patch(lib.Node.prototype, 'free', function () {
// Since we handle the memory allocation ourselves (via lib.Node.create),
// we also need to handle the deallocation
lib.Node.destroy(this);
});
patch(lib.Node.prototype, 'freeRecursive', function () {
for (let t = 0, T = this.getChildCount(); t < T; ++t) {
this.getChild(0).freeRecursive();
}
this.free();
});
patch(
lib.Node.prototype,
'calculateLayout',
function (original, width = NaN, height = NaN, direction = Direction.LTR) {
// Just a small patch to add support for the function default parameters
return original.call(this, width, height, direction);
},
);
return {
Config: lib.Config,
Node: lib.Node,
...YGEnums,
};
}

View File

@@ -7,9 +7,13 @@
* @format * @format
*/ */
// @ts-nocheck
import {Unit, Direction} from './generated/YGEnums.ts';
import YGEnums from './generated/YGEnums.ts';
import type { import type {
Align, Align,
Direction,
Display, Display,
Edge, Edge,
Errata, Errata,
@@ -20,11 +24,8 @@ import type {
MeasureMode, MeasureMode,
Overflow, Overflow,
PositionType, PositionType,
Unit,
Wrap, Wrap,
} from './generated/YGEnums'; } from './generated/YGEnums.ts';
import YGEnums from './generated/YGEnums';
type Layout = { type Layout = {
left: number; left: number;
@@ -179,5 +180,139 @@ export type Yoga = {
}; };
} & typeof YGEnums; } & typeof YGEnums;
declare const wrapAsm: (assembly: unknown) => Yoga; // eslint-disable-next-line @typescript-eslint/no-explicit-any
export default wrapAsm; export default function wrapAssembly(lib: any): Yoga {
function patch(prototype, name, fn) {
const original = prototype[name];
prototype[name] = function (...args) {
return fn.call(this, original, ...args);
};
}
for (const fnName of [
'setPosition',
'setMargin',
'setFlexBasis',
'setWidth',
'setHeight',
'setMinWidth',
'setMinHeight',
'setMaxWidth',
'setMaxHeight',
'setPadding',
]) {
const methods = {
[Unit.Point]: lib.Node.prototype[fnName],
[Unit.Percent]: lib.Node.prototype[`${fnName}Percent`],
[Unit.Auto]: lib.Node.prototype[`${fnName}Auto`],
};
patch(lib.Node.prototype, fnName, function (original, ...args) {
// We patch all these functions to add support for the following calls:
// .setWidth(100) / .setWidth("100%") / .setWidth(.getWidth()) / .setWidth("auto")
const value = args.pop();
let unit, asNumber;
if (value === 'auto') {
unit = Unit.Auto;
asNumber = undefined;
} else if (typeof value === 'object') {
unit = value.unit;
asNumber = value.valueOf();
} else {
unit =
typeof value === 'string' && value.endsWith('%')
? Unit.Percent
: Unit.Point;
asNumber = parseFloat(value);
if (!Number.isNaN(value) && Number.isNaN(asNumber)) {
throw new Error(`Invalid value ${value} for ${fnName}`);
}
}
if (!methods[unit])
throw new Error(
`Failed to execute "${fnName}": Unsupported unit '${value}'`,
);
if (asNumber !== undefined) {
return methods[unit].call(this, ...args, asNumber);
} else {
return methods[unit].call(this, ...args);
}
});
}
function wrapMeasureFunction(measureFunction) {
return lib.MeasureCallback.implement({
measure: (...args) => {
const {width, height} = measureFunction(...args);
return {
width: width ?? NaN,
height: height ?? NaN,
};
},
});
}
patch(lib.Node.prototype, 'setMeasureFunc', function (original, measureFunc) {
// This patch is just a convenience patch, since it helps write more
// idiomatic source code (such as .setMeasureFunc(null))
if (measureFunc) {
return original.call(this, wrapMeasureFunction(measureFunc));
} else {
return this.unsetMeasureFunc();
}
});
function wrapDirtiedFunc(dirtiedFunction) {
return lib.DirtiedCallback.implement({dirtied: dirtiedFunction});
}
patch(lib.Node.prototype, 'setDirtiedFunc', function (original, dirtiedFunc) {
original.call(this, wrapDirtiedFunc(dirtiedFunc));
});
patch(lib.Config.prototype, 'free', function () {
// Since we handle the memory allocation ourselves (via lib.Config.create),
// we also need to handle the deallocation
lib.Config.destroy(this);
});
patch(lib.Node, 'create', (_, config) => {
// We decide the constructor we want to call depending on the parameters
return config
? lib.Node.createWithConfig(config)
: lib.Node.createDefault();
});
patch(lib.Node.prototype, 'free', function () {
// Since we handle the memory allocation ourselves (via lib.Node.create),
// we also need to handle the deallocation
lib.Node.destroy(this);
});
patch(lib.Node.prototype, 'freeRecursive', function () {
for (let t = 0, T = this.getChildCount(); t < T; ++t) {
this.getChild(0).freeRecursive();
}
this.free();
});
patch(
lib.Node.prototype,
'calculateLayout',
function (original, width = NaN, height = NaN, direction = Direction.LTR) {
// Just a small patch to add support for the function default parameters
return original.call(this, width, height, direction);
},
);
return {
Config: lib.Config,
Node: lib.Node,
...YGEnums,
};
}

View File

@@ -5,8 +5,10 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import {getMeasureCounter} from '../tools/MeasureCounter'; import {getMeasureCounter} from '../tools/MeasureCounter.ts';
import {Yoga, YGBENCHMARK} from '../tools/globals'; import {YGBENCHMARK} from '../tools/globals.ts';
import Yoga from 'yoga-layout';
const ITERATIONS = 2000; const ITERATIONS = 2000;

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import {Yoga} from './tools/globals'; import Yoga from 'yoga-layout';
test('align_baseline_parent_using_child_in_column_as_reference', () => { test('align_baseline_parent_using_child_in_column_as_reference', () => {
const config = Yoga.Config.create(); const config = Yoga.Config.create();

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import {Yoga} from './tools/globals'; import Yoga from 'yoga-layout';
test('border_start', () => { test('border_start', () => {
const root = Yoga.Node.create(); const root = Yoga.Node.create();

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import {Yoga} from './tools/globals'; import Yoga from 'yoga-layout';
test('margin_start', () => { test('margin_start', () => {
const root = Yoga.Node.create(); const root = Yoga.Node.create();

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import {Yoga} from './tools/globals'; import Yoga from 'yoga-layout';
test('padding_start', () => { test('padding_start', () => {
const root = Yoga.Node.create(); const root = Yoga.Node.create();

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import {Yoga} from './tools/globals'; import Yoga from 'yoga-layout';
test('dirtied', () => { test('dirtied', () => {
const root = Yoga.Node.create(); const root = Yoga.Node.create();

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import {Yoga} from './tools/globals'; import Yoga from 'yoga-layout';
test('errata_all_contains_example_errata', () => { test('errata_all_contains_example_errata', () => {
const config = Yoga.Config.create(); const config = Yoga.Config.create();

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import {Yoga} from './tools/globals'; import Yoga from 'yoga-layout';
test('flex_basis_auto', () => { test('flex_basis_auto', () => {
const root = Yoga.Node.create(); const root = Yoga.Node.create();

View File

@@ -5,9 +5,9 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import {Yoga} from './tools/globals'; import Yoga from 'yoga-layout';
import {getMeasureCounterMax} from './tools/MeasureCounter'; import {getMeasureCounterMax} from './tools/MeasureCounter.ts';
test('measure_once_single_flexible_child', () => { test('measure_once_single_flexible_child', () => {
const root = Yoga.Node.create(); const root = Yoga.Node.create();

View File

@@ -5,8 +5,8 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import {Yoga} from './tools/globals'; import Yoga from 'yoga-layout';
import {getMeasureCounter} from './tools/MeasureCounter'; import {getMeasureCounter} from './tools/MeasureCounter.ts';
test('dont_measure_single_grow_shrink_child', () => { test('dont_measure_single_grow_shrink_child', () => {
const root = Yoga.Node.create(); const root = Yoga.Node.create();

View File

@@ -10,9 +10,6 @@
import path from 'path'; import path from 'path';
import YogaAsmjs from 'yoga-layout/asmjs-sync';
import YogaWasm from 'yoga-layout/wasm-sync';
const WARMUP_ITERATIONS = 3; const WARMUP_ITERATIONS = 3;
const BENCHMARK_ITERATIONS = 10; const BENCHMARK_ITERATIONS = 10;
@@ -20,9 +17,7 @@ const testFiles = process.argv.slice(2);
const testResults = new Map<string, Map<string, number>>(); const testResults = new Map<string, Map<string, number>>();
for (const type of ['asmjs', 'wasm']) { for (const type of ['wasm']) {
globalThis.Yoga = type === 'asmjs' ? YogaAsmjs : YogaWasm;
for (const file of testFiles) { for (const file of testFiles) {
globalThis.YGBENCHMARK = (name: string, fn: () => void) => { globalThis.YGBENCHMARK = (name: string, fn: () => void) => {
let testEntry = testResults.get(name); let testEntry = testResults.get(name);
@@ -42,10 +37,7 @@ for (const type of ['asmjs', 'wasm']) {
}; };
const modulePath = path.resolve(file); const modulePath = path.resolve(file);
await import(modulePath);
delete require.cache[require.resolve('../tools/globals')];
delete require.cache[modulePath];
require(modulePath);
} }
} }

View File

@@ -7,7 +7,7 @@
// @generated by gentest/gentest.rb from gentest/fixtures/YGAbsolutePositionTest.html // @generated by gentest/gentest.rb from gentest/fixtures/YGAbsolutePositionTest.html
import {Yoga} from "../tools/globals"; import Yoga from 'yoga-layout';
import { import {
Align, Align,
Direction, Direction,

View File

@@ -7,7 +7,7 @@
// @generated by gentest/gentest.rb from gentest/fixtures/YGAlignContentTest.html // @generated by gentest/gentest.rb from gentest/fixtures/YGAlignContentTest.html
import {Yoga} from "../tools/globals"; import Yoga from 'yoga-layout';
import { import {
Align, Align,
Direction, Direction,

View File

@@ -7,7 +7,7 @@
// @generated by gentest/gentest.rb from gentest/fixtures/YGAlignItemsTest.html // @generated by gentest/gentest.rb from gentest/fixtures/YGAlignItemsTest.html
import {Yoga} from "../tools/globals"; import Yoga from 'yoga-layout';
import { import {
Align, Align,
Direction, Direction,

View File

@@ -7,7 +7,7 @@
// @generated by gentest/gentest.rb from gentest/fixtures/YGAlignSelfTest.html // @generated by gentest/gentest.rb from gentest/fixtures/YGAlignSelfTest.html
import {Yoga} from "../tools/globals"; import Yoga from 'yoga-layout';
import { import {
Align, Align,
Direction, Direction,

View File

@@ -7,7 +7,7 @@
// @generated by gentest/gentest.rb from gentest/fixtures/YGAndroidNewsFeed.html // @generated by gentest/gentest.rb from gentest/fixtures/YGAndroidNewsFeed.html
import {Yoga} from "../tools/globals"; import Yoga from 'yoga-layout';
import { import {
Align, Align,
Direction, Direction,

View File

@@ -7,7 +7,7 @@
// @generated by gentest/gentest.rb from gentest/fixtures/YGAspectRatioTest.html // @generated by gentest/gentest.rb from gentest/fixtures/YGAspectRatioTest.html
import {Yoga} from "../tools/globals"; import Yoga from 'yoga-layout';
import { import {
Align, Align,
Direction, Direction,

View File

@@ -7,7 +7,7 @@
// @generated by gentest/gentest.rb from gentest/fixtures/YGBorderTest.html // @generated by gentest/gentest.rb from gentest/fixtures/YGBorderTest.html
import {Yoga} from "../tools/globals"; import Yoga from 'yoga-layout';
import { import {
Align, Align,
Direction, Direction,

View File

@@ -7,7 +7,7 @@
// @generated by gentest/gentest.rb from gentest/fixtures/YGDimensionTest.html // @generated by gentest/gentest.rb from gentest/fixtures/YGDimensionTest.html
import {Yoga} from "../tools/globals"; import Yoga from 'yoga-layout';
import { import {
Align, Align,
Direction, Direction,

View File

@@ -7,7 +7,7 @@
// @generated by gentest/gentest.rb from gentest/fixtures/YGDisplayTest.html // @generated by gentest/gentest.rb from gentest/fixtures/YGDisplayTest.html
import {Yoga} from "../tools/globals"; import Yoga from 'yoga-layout';
import { import {
Align, Align,
Direction, Direction,

View File

@@ -7,7 +7,7 @@
// @generated by gentest/gentest.rb from gentest/fixtures/YGFlexDirectionTest.html // @generated by gentest/gentest.rb from gentest/fixtures/YGFlexDirectionTest.html
import {Yoga} from "../tools/globals"; import Yoga from 'yoga-layout';
import { import {
Align, Align,
Direction, Direction,

View File

@@ -7,7 +7,7 @@
// @generated by gentest/gentest.rb from gentest/fixtures/YGFlexTest.html // @generated by gentest/gentest.rb from gentest/fixtures/YGFlexTest.html
import {Yoga} from "../tools/globals"; import Yoga from 'yoga-layout';
import { import {
Align, Align,
Direction, Direction,

View File

@@ -7,7 +7,7 @@
// @generated by gentest/gentest.rb from gentest/fixtures/YGFlexWrapTest.html // @generated by gentest/gentest.rb from gentest/fixtures/YGFlexWrapTest.html
import {Yoga} from "../tools/globals"; import Yoga from 'yoga-layout';
import { import {
Align, Align,
Direction, Direction,

View File

@@ -7,7 +7,7 @@
// @generated by gentest/gentest.rb from gentest/fixtures/YGGapTest.html // @generated by gentest/gentest.rb from gentest/fixtures/YGGapTest.html
import {Yoga} from "../tools/globals"; import Yoga from 'yoga-layout';
import { import {
Align, Align,
Direction, Direction,

View File

@@ -7,7 +7,7 @@
// @generated by gentest/gentest.rb from gentest/fixtures/YGJustifyContentTest.html // @generated by gentest/gentest.rb from gentest/fixtures/YGJustifyContentTest.html
import {Yoga} from "../tools/globals"; import Yoga from 'yoga-layout';
import { import {
Align, Align,
Direction, Direction,

View File

@@ -7,7 +7,7 @@
// @generated by gentest/gentest.rb from gentest/fixtures/YGMarginTest.html // @generated by gentest/gentest.rb from gentest/fixtures/YGMarginTest.html
import {Yoga} from "../tools/globals"; import Yoga from 'yoga-layout';
import { import {
Align, Align,
Direction, Direction,

View File

@@ -7,7 +7,7 @@
// @generated by gentest/gentest.rb from gentest/fixtures/YGMinMaxDimensionTest.html // @generated by gentest/gentest.rb from gentest/fixtures/YGMinMaxDimensionTest.html
import {Yoga} from "../tools/globals"; import Yoga from 'yoga-layout';
import { import {
Align, Align,
Direction, Direction,

View File

@@ -7,7 +7,7 @@
// @generated by gentest/gentest.rb from gentest/fixtures/YGPaddingTest.html // @generated by gentest/gentest.rb from gentest/fixtures/YGPaddingTest.html
import {Yoga} from "../tools/globals"; import Yoga from 'yoga-layout';
import { import {
Align, Align,
Direction, Direction,

View File

@@ -7,7 +7,7 @@
// @generated by gentest/gentest.rb from gentest/fixtures/YGPercentageTest.html // @generated by gentest/gentest.rb from gentest/fixtures/YGPercentageTest.html
import {Yoga} from "../tools/globals"; import Yoga from 'yoga-layout';
import { import {
Align, Align,
Direction, Direction,

View File

@@ -7,7 +7,7 @@
// @generated by gentest/gentest.rb from gentest/fixtures/YGRoundingTest.html // @generated by gentest/gentest.rb from gentest/fixtures/YGRoundingTest.html
import {Yoga} from "../tools/globals"; import Yoga from 'yoga-layout';
import { import {
Align, Align,
Direction, Direction,

View File

@@ -7,7 +7,7 @@
// @generated by gentest/gentest.rb from gentest/fixtures/YGSizeOverflowTest.html // @generated by gentest/gentest.rb from gentest/fixtures/YGSizeOverflowTest.html
import {Yoga} from "../tools/globals"; import Yoga from 'yoga-layout';
import { import {
Align, Align,
Direction, Direction,

View File

@@ -8,7 +8,7 @@
*/ */
import type {MeasureFunction} from 'yoga-layout'; import type {MeasureFunction} from 'yoga-layout';
import {Yoga} from './globals'; import Yoga from 'yoga-layout';
export type MeasureCounter = { export type MeasureCounter = {
inc: MeasureFunction; inc: MeasureFunction;

View File

@@ -5,23 +5,15 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import type {Yoga} from 'yoga-layout';
declare global { declare global {
// eslint-disable-next-line no-var
var Yoga: Yoga | undefined;
// eslint-disable-next-line no-var // eslint-disable-next-line no-var
var YGBENCHMARK: (title: string, fn: () => void) => void; var YGBENCHMARK: (title: string, fn: () => void) => void;
} }
if (globalThis.Yoga === undefined) {
throw new Error('Expected "Yoga" global to be set');
}
if (globalThis.YGBENCHMARK === undefined) { if (globalThis.YGBENCHMARK === undefined) {
throw new Error('Expected "YGBENCHMARK" global to be set'); throw new Error('Expected "YGBENCHMARK" global to be set');
} }
const yoga = globalThis.Yoga;
const benchmark = globalThis.YGBENCHMARK; const benchmark = globalThis.YGBENCHMARK;
export {yoga as Yoga, benchmark as YGBENCHMARK}; export {benchmark as YGBENCHMARK};

View File

@@ -1,7 +1,7 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "es2018", "target": "es2020",
"module": "commonjs", "module": "esnext",
"strict": true, "strict": true,
"declaration": true, "declaration": true,
"esModuleInterop": true, "esModuleInterop": true,
@@ -11,15 +11,13 @@
"forceConsistentCasingInFileNames": false, "forceConsistentCasingInFileNames": false,
"baseUrl": ".", "baseUrl": ".",
"moduleResolution": "nodenext", "moduleResolution": "nodenext",
"allowImportingTsExtensions": true,
"paths": { "paths": {
"yoga-layout": ["src"] "yoga-layout": ["src"]
} }
}, },
"ts-node": { "include": [
"transpileOnly": true "src/**/*",
}, "tests/**/*"
"exclude": [
"binaries/**/*",
"build/**/*"
] ]
} }

View File

@@ -37,7 +37,7 @@ const config = {
/** @type {import('@docusaurus/preset-classic').Options} */ /** @type {import('@docusaurus/preset-classic').Options} */
({ ({
docs: { docs: {
sidebarPath: require.resolve('./sidebars.js'), sidebarPath: require.resolve('./sidebars.cjs'),
editUrl: 'https://github.com/facebook/yoga/tree/main/website', editUrl: 'https://github.com/facebook/yoga/tree/main/website',
}, },
blog: { blog: {

View File

@@ -11,7 +11,7 @@ import React, {Component} from 'react';
import {Row, Col, Button, Tabs} from 'antd'; import {Row, Col, Button, Tabs} from 'antd';
import EditValue from './EditValue'; import EditValue from './EditValue';
import type {LayoutRecordType} from './LayoutRecord'; import type {LayoutRecordType} from './LayoutRecord';
import type {Direction} from 'yoga-layout/sync'; import type {Direction} from 'yoga-layout';
import InfoText from './InfoText'; import InfoText from './InfoText';
import './Editor.css'; import './Editor.css';
const TabPane = Tabs.TabPane; const TabPane = Tabs.TabPane;

View File

@@ -10,15 +10,8 @@
import {Record, List} from 'immutable'; import {Record, List} from 'immutable';
import PositionRecord from './PositionRecord'; import PositionRecord from './PositionRecord';
import type {PositionRecordType} from './PositionRecord'; import type {PositionRecordType} from './PositionRecord';
import yoga from 'yoga-layout/sync';
import type { import {Align, Justify, FlexDirection, Wrap, PositionType} from 'yoga-layout';
Align,
Justify,
FlexDirection,
Wrap,
PositionType,
} from 'yoga-layout/sync';
export type LayoutRecordType = ReturnType<LayoutRecordFactory>; export type LayoutRecordType = ReturnType<LayoutRecordFactory>;
@@ -50,11 +43,11 @@ export type LayoutRecordFactory = Record.Factory<{
const r: LayoutRecordFactory = Record({ const r: LayoutRecordFactory = Record({
width: 'auto', width: 'auto',
height: 'auto', height: 'auto',
justifyContent: yoga.JUSTIFY_FLEX_START, justifyContent: Justify.FlexStart,
alignItems: yoga.ALIGN_STRETCH, alignItems: Align.Stretch,
alignSelf: yoga.ALIGN_AUTO, alignSelf: Align.Auto,
alignContent: yoga.ALIGN_STRETCH, alignContent: Align.Stretch,
flexDirection: yoga.FLEX_DIRECTION_ROW, flexDirection: FlexDirection.Row,
padding: PositionRecord(), padding: PositionRecord(),
margin: PositionRecord(), margin: PositionRecord(),
border: PositionRecord(), border: PositionRecord(),
@@ -64,8 +57,8 @@ const r: LayoutRecordFactory = Record({
right: NaN, right: NaN,
bottom: NaN, bottom: NaN,
}), }),
positionType: yoga.POSITION_TYPE_RELATIVE, positionType: PositionType.Relative,
flexWrap: yoga.WRAP_NO_WRAP, flexWrap: Wrap.NoWrap,
flexBasis: 'auto', flexBasis: 'auto',
flexGrow: 0, flexGrow: 0,
flexShrink: 1, flexShrink: 1,

View File

@@ -8,7 +8,7 @@
*/ */
import React, {Component} from 'react'; import React, {Component} from 'react';
import Yoga from 'yoga-layout/sync'; import Yoga from 'yoga-layout';
import {Radio, Menu, Dropdown, Button, Icon} from 'antd'; import {Radio, Menu, Dropdown, Button, Icon} from 'antd';
import './YogaEnumSelect.css'; import './YogaEnumSelect.css';
const RadioButton = Radio.Button; const RadioButton = Radio.Button;

View File

@@ -8,12 +8,12 @@
*/ */
import React, {Component} from 'react'; import React, {Component} from 'react';
import Yoga from 'yoga-layout/sync'; import Yoga from 'yoga-layout';
import PositionGuide from './PositionGuide'; import PositionGuide from './PositionGuide';
import PositionRecord from './PositionRecord'; import PositionRecord from './PositionRecord';
import LayoutRecord from './LayoutRecord'; import LayoutRecord from './LayoutRecord';
import type {LayoutRecordType} from './LayoutRecord'; import type {LayoutRecordType} from './LayoutRecord';
import {Direction, Display, Edge, Node, Wrap} from 'yoga-layout/sync'; import {Direction, Display, Edge, Node, Wrap} from 'yoga-layout';
import './YogaNode.css'; import './YogaNode.css';

View File

@@ -5,19 +5,14 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
.PlaygroundContainer { .PlaygroundContainer {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
flex-grow: 1; flex-grow: 1;
width: 100%; width: 100%;
} }
.Playground { .playground-background {
display: flex;
flex-grow: 1;
position: relative;
width: 100%;
overflow: hidden;
background: linear-gradient(-90deg, rgba(0, 0, 0, 0.02) 1px, transparent 1px), background: linear-gradient(-90deg, rgba(0, 0, 0, 0.02) 1px, transparent 1px),
linear-gradient(rgba(0, 0, 0, 0.02) 1px, transparent 1px), linear-gradient(rgba(0, 0, 0, 0.02) 1px, transparent 1px),
linear-gradient(-90deg, rgba(0, 0, 0, 0.03) 1px, transparent 1px), linear-gradient(-90deg, rgba(0, 0, 0, 0.03) 1px, transparent 1px),
@@ -41,6 +36,15 @@
100px 100px, 100px 100px, 100px 100px; 100px 100px, 100px 100px, 100px 100px;
} }
.Playground {
display: flex;
flex-grow: 1;
position: relative;
width: 100%;
overflow: hidden;
animation: playground-content-fade-in-frames 50ms ease-in;
}
.Playground > .YogaNode { .Playground > .YogaNode {
margin: auto; margin: auto;
position: static; position: static;
@@ -72,3 +76,12 @@
.ant-modal-content { .ant-modal-content {
overflow: hidden; overflow: hidden;
} }
@keyframes playground-content-fade-in-frames {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}

View File

@@ -8,7 +8,7 @@
*/ */
import React, {Component} from 'react'; import React, {Component} from 'react';
import {Direction} from 'yoga-layout/sync'; import {Direction} from 'yoga-layout';
import YogaNode from './YogaNode'; import YogaNode from './YogaNode';
import Editor from './Editor'; import Editor from './Editor';
import {List, setIn} from 'immutable'; import {List, setIn} from 'immutable';
@@ -235,52 +235,58 @@ export default class Playground extends Component<Props, State> {
: null; : null;
const playground = ( const playground = (
<div <div className="playground-background">
className={`Playground ${this.props.renderSidebar ? '' : 'standalone'}`} <div
onMouseDown={this.onMouseDown} className={`Playground ${
style={{height, maxHeight: height}} this.props.renderSidebar ? '' : 'standalone'
ref={ref => { }`}
this._containerRef = ref; onMouseDown={this.onMouseDown}
}}> style={{height, maxHeight: height}}
<YogaNode ref={ref => {
layoutDefinition={layoutDefinition} this._containerRef = ref;
selectedNodePath={selectedNodePath} }}>
onClick={selectedNodePath => this.setState({selectedNodePath})} <YogaNode
onDoubleClick={this.onAdd} layoutDefinition={layoutDefinition}
direction={direction} selectedNodePath={selectedNodePath}
showGuides={this.props.showGuides} onClick={selectedNodePath => this.setState({selectedNodePath})}
/> onDoubleClick={this.onAdd}
{!this.props.renderSidebar && ( direction={direction}
<Sidebar> showGuides={this.props.showGuides}
{this.state.selectedNodePath ? ( />
<Editor {!this.props.renderSidebar && (
node={selectedNode} <Sidebar>
selectedNodeIsRoot={ {this.state.selectedNodePath ? (
selectedNodePath ? selectedNodePath.length === 0 : false <Editor
} node={selectedNode}
onChangeLayout={this.onChangeLayout} selectedNodeIsRoot={
// @ts-ignore selectedNodePath ? selectedNodePath.length === 0 : false
onChangeSetting={(key, value) => this.setState({[key]: value})} }
direction={direction} onChangeLayout={this.onChangeLayout}
onRemove={ // @ts-ignore
selectedNodePath && selectedNodePath.length > 0 onChangeSetting={(key, value) =>
? this.onRemove this.setState({[key]: value})
: undefined }
} direction={direction}
onAdd={ onRemove={
selectedNodePath && selectedNodePath && selectedNodePath.length > 0
selectedNodePath.length < this.props.maxDepth ? this.onRemove
? this.onAdd : undefined
: undefined }
} onAdd={
/> selectedNodePath &&
) : ( selectedNodePath.length < this.props.maxDepth
<div className="NoContent"> ? this.onAdd
Select a node to edit its properties : undefined
</div> }
)} />
</Sidebar> ) : (
)} <div className="NoContent">
Select a node to edit its properties
</div>
)}
</Sidebar>
)}
</div>
</div> </div>
); );

View File

@@ -28,3 +28,29 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.playgroundFallback {
height: 500px;
width: 100%;
background: linear-gradient(-90deg, rgba(0, 0, 0, 0.02) 1px, transparent 1px),
linear-gradient(rgba(0, 0, 0, 0.02) 1px, transparent 1px),
linear-gradient(-90deg, rgba(0, 0, 0, 0.03) 1px, transparent 1px),
linear-gradient(rgba(0, 0, 0, 0.03) 1px, transparent 1px),
linear-gradient(
transparent 4px,
#f5f5f5 4px,
#f5f5f5 97px,
transparent 97px
),
linear-gradient(-90deg, #e5e5e5 1px, transparent 1px),
linear-gradient(
-90deg,
transparent 4px,
#f5f5f5 4px,
#f5f5f5 97px,
transparent 97px
),
linear-gradient(#e5e5e5 1px, transparent 1px), #f5f5f5;
background-size: 10px 10px, 10px 10px, 100px 100px, 100px 100px, 100px 100px,
100px 100px, 100px 100px, 100px 100px;
}

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import React from 'react'; import React, {Suspense} from 'react';
import clsx from 'clsx'; import clsx from 'clsx';
import Link from '@docusaurus/Link'; import Link from '@docusaurus/Link';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
@@ -34,6 +34,22 @@ function HomepageHeader() {
); );
} }
const LazyPlayground = React.lazy(() => import('../components/Playground'));
function ClientPlayground() {
const fallback = <div className={styles.playgroundFallback} />;
return (
<BrowserOnly fallback={fallback}>
{() => (
<Suspense fallback={fallback}>
<LazyPlayground />
</Suspense>
)}
</BrowserOnly>
);
}
export default function Home(): JSX.Element { export default function Home(): JSX.Element {
const {siteConfig} = useDocusaurusContext(); const {siteConfig} = useDocusaurusContext();
return ( return (
@@ -43,12 +59,7 @@ export default function Home(): JSX.Element {
<HomepageHeader /> <HomepageHeader />
<main> <main>
<HomepageFeatures /> <HomepageFeatures />
<BrowserOnly fallback={null}> <ClientPlayground />
{() => {
const Playground = require('../components/Playground');
return <Playground />;
}}
</BrowserOnly>
</main> </main>
</Layout> </Layout>
); );

View File

@@ -2,6 +2,10 @@
// This file is not used in compilation. It is here just for a nice editor experience. // This file is not used in compilation. It is here just for a nice editor experience.
"extends": "@tsconfig/docusaurus/tsconfig.json", "extends": "@tsconfig/docusaurus/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"baseUrl": "." "baseUrl": ".",
"target": "esnext",
"module": "esnext",
"moduleResolution": "bundler",
"allowImportingTsExtensions": true
} }
} }

1961
yarn.lock

File diff suppressed because it is too large Load Diff