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: {
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],
{
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

@@ -7,9 +7,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 +24,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 +180,139 @@ export type Yoga = {
};
} & typeof YGEnums;
declare const wrapAsm: (assembly: unknown) => Yoga;
export default wrapAsm;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
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,8 +235,11 @@ export default class Playground extends Component<Props, State> {
: null;
const playground = (
<div className="playground-background">
<div
className={`Playground ${this.props.renderSidebar ? '' : 'standalone'}`}
className={`Playground ${
this.props.renderSidebar ? '' : 'standalone'
}`}
onMouseDown={this.onMouseDown}
style={{height, maxHeight: height}}
ref={ref => {
@@ -260,7 +263,9 @@ export default class Playground extends Component<Props, State> {
}
onChangeLayout={this.onChangeLayout}
// @ts-ignore
onChangeSetting={(key, value) => this.setState({[key]: value})}
onChangeSetting={(key, value) =>
this.setState({[key]: value})
}
direction={direction}
onRemove={
selectedNodePath && selectedNodePath.length > 0
@@ -282,6 +287,7 @@ export default class Playground extends Component<Props, State> {
</Sidebar>
)}
</div>
</div>
);
if (this.props.renderSidebar) {

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