Consolidate JavaScript Flavors #1433

Closed
NickGerleman wants to merge 14 commits from consolidate-js-flavors into main
68 changed files with 851 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: {
value: function () {
this.push('import {Yoga} from "../tools/globals";');
this.push("import Yoga from 'yoga-layout';");
this.push('import {');
this.pushIndent();
this.push('Align,');

View File

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

View File

@@ -1,24 +1,11 @@
# 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
The default entrypoint provides an asynchronous loader function to return a Yoga instance.
```ts
import {loadYoga, 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';
import {Yoga, Align} from 'yoga-layout';
const node = Yoga.Node.create();
node.setAlignContent(Align.Center);
@@ -37,20 +24,14 @@ node.freeRecursive();
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
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
### 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
*/
module.exports = {
presets: [
['@babel/preset-env', {targets: 'defaults'}],
'@babel/preset-typescript',
],
export default {
setupFiles: ['./jest.setup.js'],
testRegex: '/tests/.*\\.test\\.ts$',
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
*/
module.exports = async () => {
const {loadYoga, default: Yoga} = require('yoga-layout');
globalThis.Yoga = Yoga ?? (await loadYoga());
};
Object.defineProperty(globalThis, 'YGBENCHMARK', {
get: () => globalThis.test,
});

View File

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

View File

@@ -9,69 +9,38 @@
"type": "git",
"url": "git@github.com:facebook/yoga.git"
},
"exports": {
".": {
"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"
}
},
"type": "module",
"main": "./src/index.ts",
"files": [
"binaries/**",
"src/**"
],
"scripts": {
"benchmark": "just benchmark",
"build": "just build",
"clang-format": "just clang-format",
"clang-format:fix": "just clang-format --fix",
"clean": "just clean",
"benchmark": "just benchmark --config just.config.cjs",
"build": "just build --config just.config.cjs",
"clang-format": "just clang-format --config just.config.cjs",
"clang-format:fix": "just clang-format --fix --config just.config.cjs",
"clean": "just clean --config just.config.cjs",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"prepack": "just prepack",
"test": "just test",
"prepack": "just prepack --config just.config.cjs",
"test": "just test --config just.config.cjs",
"tsc": "tsc --noEmit"
},
"devDependencies": {
"@babel/cli": "^7.21.4",
"@babel/core": "^7.21.4",
"@babel/node": "^7.21.4",
"@babel/preset-env": "^7.21.4",
"@babel/preset-typescript": "^7.21.4",
"@babel/cli": "^7.23.0",
"@babel/core": "^7.23.0",
"@babel/preset-env": "^7.23.0",
"@babel/preset-typescript": "^7.23.0",
"@types/glob": "^8.1.0",
"@types/jest": "^29.5.1",
"@types/node": "^16.18.25",
"@types/which": "^3.0.0",
"babel-register-esm": "^1.2.5",
"clang-format": "^1.8.0",
"glob": "^8.0.3",
"jest": "^29.3.1",
"just-scripts": "^2.1.0",
"ts-node": "^10.9.1",
"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
*/
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 {
Config,
DirtiedFunction,
MeasureFunction,
Node,
Yoga,
} from '../wrapAssembly';
} from './wrapAssembly.ts';
const loadAssembly = require('../../binaries/wasm-sync-web');
const Yoga = wrapAssembly(loadAssembly());
const Yoga = wrapAssembly(await loadYoga());
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

@@ -6,10 +6,13 @@
*
* @format
*/
// @ts-nocheck
import {Unit, Direction} from './generated/YGEnums.ts';
import YGEnums from './generated/YGEnums.ts';
import type {
Align,
Direction,
Display,
Edge,
Errata,
@@ -20,11 +23,8 @@ import type {
MeasureMode,
Overflow,
PositionType,
Unit,
Wrap,
} from './generated/YGEnums';
import YGEnums from './generated/YGEnums';
} from './generated/YGEnums.ts';
type Layout = {
left: number;
@@ -179,5 +179,138 @@ export type Yoga = {
};
} & typeof YGEnums;
declare const wrapAsm: (assembly: unknown) => Yoga;
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.
*/
import {getMeasureCounter} from '../tools/MeasureCounter';
import {Yoga, YGBENCHMARK} from '../tools/globals';
import {getMeasureCounter} from '../tools/MeasureCounter.ts';
import {YGBENCHMARK} from '../tools/globals.ts';
import Yoga from 'yoga-layout';
const ITERATIONS = 2000;

View File

@@ -5,7 +5,7 @@
* 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', () => {
const config = Yoga.Config.create();

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,7 @@
* 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', () => {
const config = Yoga.Config.create();

View File

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

View File

@@ -5,9 +5,9 @@
* 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', () => {
const root = Yoga.Node.create();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,7 +8,7 @@
*/
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 './YogaEnumSelect.css';
const RadioButton = Radio.Button;

View File

@@ -8,12 +8,12 @@
*/
import React, {Component} from 'react';
import Yoga from 'yoga-layout/sync';
import Yoga from 'yoga-layout';
import PositionGuide from './PositionGuide';
import PositionRecord from './PositionRecord';
import LayoutRecord 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';

View File

@@ -5,19 +5,14 @@
* LICENSE file in the root directory of this source tree.
*/
.PlaygroundContainer {
.PlaygroundContainer {
display: flex;
flex-direction: row;
flex-grow: 1;
width: 100%;
}
.Playground {
display: flex;
flex-grow: 1;
position: relative;
width: 100%;
overflow: hidden;
.playground-background {
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),
@@ -41,6 +36,15 @@
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 {
margin: auto;
position: static;
@@ -72,3 +76,12 @@
.ant-modal-content {
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 {Direction} from 'yoga-layout/sync';
import {Direction} from 'yoga-layout';
import YogaNode from './YogaNode';
import Editor from './Editor';
import {List, setIn} from 'immutable';
@@ -235,52 +235,58 @@ export default class Playground extends Component<Props, State> {
: null;
const playground = (
<div
className={`Playground ${this.props.renderSidebar ? '' : 'standalone'}`}
onMouseDown={this.onMouseDown}
style={{height, maxHeight: height}}
ref={ref => {
this._containerRef = ref;
}}>
<YogaNode
layoutDefinition={layoutDefinition}
selectedNodePath={selectedNodePath}
onClick={selectedNodePath => this.setState({selectedNodePath})}
onDoubleClick={this.onAdd}
direction={direction}
showGuides={this.props.showGuides}
/>
{!this.props.renderSidebar && (
<Sidebar>
{this.state.selectedNodePath ? (
<Editor
node={selectedNode}
selectedNodeIsRoot={
selectedNodePath ? selectedNodePath.length === 0 : false
}
onChangeLayout={this.onChangeLayout}
// @ts-ignore
onChangeSetting={(key, value) => this.setState({[key]: value})}
direction={direction}
onRemove={
selectedNodePath && selectedNodePath.length > 0
? this.onRemove
: undefined
}
onAdd={
selectedNodePath &&
selectedNodePath.length < this.props.maxDepth
? this.onAdd
: undefined
}
/>
) : (
<div className="NoContent">
Select a node to edit its properties
</div>
)}
</Sidebar>
)}
<div className="playground-background">
<div
className={`Playground ${
this.props.renderSidebar ? '' : 'standalone'
}`}
onMouseDown={this.onMouseDown}
style={{height, maxHeight: height}}
ref={ref => {
this._containerRef = ref;
}}>
<YogaNode
layoutDefinition={layoutDefinition}
selectedNodePath={selectedNodePath}
onClick={selectedNodePath => this.setState({selectedNodePath})}
onDoubleClick={this.onAdd}
direction={direction}
showGuides={this.props.showGuides}
/>
{!this.props.renderSidebar && (
<Sidebar>
{this.state.selectedNodePath ? (
<Editor
node={selectedNode}
selectedNodeIsRoot={
selectedNodePath ? selectedNodePath.length === 0 : false
}
onChangeLayout={this.onChangeLayout}
// @ts-ignore
onChangeSetting={(key, value) =>
this.setState({[key]: value})
}
direction={direction}
onRemove={
selectedNodePath && selectedNodePath.length > 0
? this.onRemove
: undefined
}
onAdd={
selectedNodePath &&
selectedNodePath.length < this.props.maxDepth
? this.onAdd
: undefined
}
/>
) : (
<div className="NoContent">
Select a node to edit its properties
</div>
)}
</Sidebar>
)}
</div>
</div>
);

View File

@@ -28,3 +28,29 @@
align-items: 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.
*/
import React from 'react';
import React, {Suspense} from 'react';
import clsx from 'clsx';
import Link from '@docusaurus/Link';
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 {
const {siteConfig} = useDocusaurusContext();
return (
@@ -43,12 +59,7 @@ export default function Home(): JSX.Element {
<HomepageHeader />
<main>
<HomepageFeatures />
<BrowserOnly fallback={null}>
{() => {
const Playground = require('../components/Playground');
return <Playground />;
}}
</BrowserOnly>
<ClientPlayground />
</main>
</Layout>
);

View File

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

1961
yarn.lock

File diff suppressed because it is too large Load Diff