Fixup TypeScript with export maps (#1284)

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

This makes TypeScript resolution play nicely with export maps, and converts the entrypoints to TypeScript.

We remove the non-export-map fallbacks as well, so the export maps are always followed.

Tests are moved to load yoga from its external export, testing the entrypoints.

This moves the only untyped bit to the binary wrapper, which another diff will move.

Reviewed By: yungsters

Differential Revision: D45713689

fbshipit-source-id: 228e6f2c9d53520230707a81e76c3c35fcd46de5
This commit is contained in:
Nick Gerleman
2023-05-09 22:21:01 -07:00
committed by Facebook GitHub Bot
parent 104646d8ca
commit af89315fd4
25 changed files with 183 additions and 176 deletions

View File

@@ -7,20 +7,20 @@
* @format * @format
*/ */
const path = require("path"); const path = require('path');
module.exports = { module.exports = {
root: true, root: true,
ignorePatterns: ["dist/**", "tests/generated/**"], ignorePatterns: ['dist/**', 'tests/generated/**'],
extends: ["eslint:recommended", "plugin:prettier/recommended"], extends: ['eslint:recommended', 'plugin:prettier/recommended'],
plugins: ["prettier"], plugins: ['prettier'],
rules: { rules: {
"no-var": "error", 'no-var': 'error',
"prefer-arrow-callback": "error", 'prefer-arrow-callback': 'error',
"prefer-const": "error", 'prefer-const': 'error',
"prefer-object-spread": "error", 'prefer-object-spread': 'error',
"prefer-spread": "error", 'prefer-spread': 'error',
"require-await": "error", 'require-await': 'error',
}, },
env: { env: {
commonjs: true, commonjs: true,
@@ -28,44 +28,44 @@ module.exports = {
}, },
overrides: [ overrides: [
{ {
files: ["**/*.js"], files: ['**/*.js'],
parser: "@babel/eslint-parser", parser: '@babel/eslint-parser',
parserOptions: { parserOptions: {
babelOptions: { babelOptions: {
configFile: path.join(__dirname, ".babelrc.js"), configFile: path.join(__dirname, '.babelrc.js'),
}, },
}, },
}, },
{ {
files: ["**/*.ts"], files: ['**/*.ts'],
extends: ["plugin:@typescript-eslint/recommended"], extends: ['plugin:@typescript-eslint/recommended'],
parser: "@typescript-eslint/parser", parser: '@typescript-eslint/parser',
parserOptions: { parserOptions: {
project: path.join(__dirname, "tsconfig.json"), project: path.join(__dirname, 'tsconfig.json'),
}, },
plugins: ["@typescript-eslint"], plugins: ['@typescript-eslint'],
rules: { rules: {
"@typescript-eslint/no-var-requires": "off", '@typescript-eslint/no-var-requires': 'off',
}, },
}, },
{ {
files: ["**/.eslintrc.js", "**/just.config.js"], files: ['**/.eslintrc.js', '**/just.config.js'],
env: { env: {
node: true, node: true,
}, },
}, },
{ {
files: ["jest.*", "tests/**"], files: ['jest.*', 'tests/**'],
env: { env: {
node: true, node: true,
}, },
extends: ["plugin:jest/recommended"], extends: ['plugin:jest/recommended'],
globals: { globals: {
getMeasureCounter: "writable", getMeasureCounter: 'writable',
getMeasureCounterMax: "writable", getMeasureCounterMax: 'writable',
getMeasureCounterMin: "writable", getMeasureCounterMin: 'writable',
Yoga: "writable", Yoga: 'writable',
YGBENCHMARK: "writable", YGBENCHMARK: 'writable',
}, },
}, },
], ],

View File

@@ -1,3 +1,4 @@
/binaries
/build /build
/dist /dist
/node_modules /node_modules

View File

@@ -49,7 +49,7 @@ link_libraries(embind)
add_library(yogaObjLib OBJECT ${SOURCES}) add_library(yogaObjLib OBJECT ${SOURCES})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/dist/build) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/binaries)
add_executable(asmjs-sync $<TARGET_OBJECTS:yogaObjLib>) add_executable(asmjs-sync $<TARGET_OBJECTS:yogaObjLib>)
target_link_options(asmjs-sync PUBLIC target_link_options(asmjs-sync PUBLIC

View File

@@ -44,7 +44,7 @@ For better performance and smaller packages, WebAssembly is preferred to asm.js
A specific entrypoint may be specified on platforms which do not understand export conditions. A specific entrypoint may be specified on platforms which do not understand export conditions.
```ts ```ts
import {loadYoga} from 'yoga-layout/dist/entrypoint/wasm-async'; import {loadYoga} from 'yoga-layout/wasm-async';
``` ```

View File

@@ -9,13 +9,13 @@
module.exports = async () => { module.exports = async () => {
if (process.env['SYNC'] === '1' && process.env['WASM'] === '1') { if (process.env['SYNC'] === '1' && process.env['WASM'] === '1') {
globalThis.Yoga = require('./dist/entrypoint/wasm-sync'); globalThis.Yoga = require('yoga-layout/wasm-sync').default;
} else if (process.env['SYNC'] === '1') { } else if (process.env['SYNC'] === '1') {
globalThis.Yoga = require('./dist/entrypoint/asmjs-sync'); globalThis.Yoga = require('yoga-layout/asmjs-sync').default;
} else if (process.env['WASM'] === '1') { } else if (process.env['WASM'] === '1') {
globalThis.Yoga = await require('./dist/entrypoint/wasm-async').loadYoga(); globalThis.Yoga = await require('yoga-layout/wasm-async').loadYoga();
} else { } else {
globalThis.Yoga = await require('./dist/entrypoint/asmjs-async').loadYoga(); globalThis.Yoga = await require('yoga-layout/asmjs-async').loadYoga();
} }
}; };

View File

@@ -21,6 +21,8 @@ import {
tscTask, tscTask,
} from 'just-scripts'; } from 'just-scripts';
import {readFile, writeFile} from 'fs/promises';
import glob from 'glob'; import glob from 'glob';
import path from 'path'; import path from 'path';
import which from 'which'; import which from 'which';
@@ -84,15 +86,38 @@ task(
), ),
); );
task('prepack-package-json', async () => {
const packageJsonPath = path.join(__dirname, 'package.json');
const packageJsonContents = await readFile(packageJsonPath);
const packageJson = JSON.parse(packageJsonContents.toString('utf-8'));
recursiveReplace(packageJson, /(.\/src\/.*)\.ts/, '$1.js');
await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
});
task( task(
'prepublish', 'prepack',
parallel( series(
'build', parallel('build', tscTask({emitDeclarationOnly: true})),
tscTask({emitDeclarationOnly: true}),
babelTransformTask({dir: 'src'}), babelTransformTask({dir: 'src'}),
'prepack-package-json',
), ),
); );
function recursiveReplace(
obj: Record<string, unknown>,
pattern: RegExp,
replacement: string,
) {
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);
}
}
}
function babelTransformTask(opts: {dir: string}) { function babelTransformTask(opts: {dir: string}) {
return () => { return () => {
const args = [ const args = [

View File

@@ -7,22 +7,24 @@
"type": "git", "type": "git",
"url": "git@github.com:facebook/yoga.git" "url": "git@github.com:facebook/yoga.git"
}, },
"main": "./src/index.js",
"types": "./src/index.d.ts",
"exports": { "exports": {
".": { ".": {
"browser": "./src/entrypoint/wasm-async.js", "browser": "./src/entrypoint/wasm-async.ts",
"node": "./src/entrypoint/wasm-async.js", "node": "./src/entrypoint/wasm-async.ts",
"default": "./src/entrypoint/asmjs-async.js" "default": "./src/entrypoint/asmjs-async.ts"
}, },
"./sync": { "./sync": {
"browser": "./src/entrypoint/asmjs-sync.js", "browser": "./src/entrypoint/asmjs-sync.ts",
"node": "./src/entrypoint/wasm-sync.js", "node": "./src/entrypoint/wasm-sync.ts",
"default": "./src/entrypoint/asmjs-sync.js" "default": "./src/entrypoint/asmjs-sync.ts"
} },
"./asmjs-async": "./src/entrypoint/asmjs-async.ts",
"./asmjs-sync": "./src/entrypoint/asmjs-sync.ts",
"./wasm-async": "./src/entrypoint/wasm-async.ts",
"./wasm-sync": "./src/entrypoint/wasm-sync.ts"
}, },
"files": [ "files": [
"dist/**", "binaries/**",
"src/**" "src/**"
], ],
"scripts": { "scripts": {
@@ -31,7 +33,7 @@
"clean": "just clean", "clean": "just clean",
"lint": "just lint", "lint": "just lint",
"lint:fix": "just lint --fix", "lint:fix": "just lint --fix",
"prepublish": "just prepublish", "prepack": "just prepack",
"test": "just test", "test": "just test",
"test:asmjs-async": "just test:asmjs-async", "test:asmjs-async": "just test:asmjs-async",
"test:asmjs-sync": "just test:asmjs-sync", "test:asmjs-sync": "just test:asmjs-sync",

View File

@@ -1,15 +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
*/
const wrapAsm = require('../wrapAsm');
module.exports = loadAsm => ({
loadYoga: () => loadAsm().then(wrapAsm),
...require('../generated/YGEnums'),
});

View File

@@ -1,11 +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
*/
const wrapAsm = require('../wrapAsm');
module.exports = asm => wrapAsm(asm());

View File

@@ -1,11 +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
*/
const asm = require('../build/asmjs-async');
module.exports = require('./_entryAsync')(asm);

View File

@@ -0,0 +1,26 @@
/**
* 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');
export async function loadYoga(): Promise<Yoga> {
return wrapAssembly(await loadAssembly());
}

View File

@@ -1,11 +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
*/
const asm = require('../build/asmjs-sync');
module.exports = require('./_entrySync')(asm);

View File

@@ -0,0 +1,23 @@
/**
* 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');
const Yoga = wrapAssembly(loadAssembly());
export default Yoga;

View File

@@ -1,11 +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
*/
const asm = require('../build/wasm-async');
module.exports = require('./_entryAsync')(asm);

View File

@@ -0,0 +1,26 @@
/**
* 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');
export async function loadYoga(): Promise<Yoga> {
return wrapAssembly(await loadAssembly());
}

View File

@@ -1,11 +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
*/
const asm = require('../build/wasm-sync');
module.exports = require('./_entrySync')(asm);

View File

@@ -0,0 +1,23 @@
/**
* 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');
const Yoga = wrapAssembly(loadAssembly());
export default Yoga;

View File

@@ -1,15 +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 {Yoga} from './wrapAsm';
export * from './generated/YGEnums';
export * from './wrapAsm';
export function loadYoga(): Promise<Yoga>;

View File

@@ -1,11 +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
*/
// Fallback for when the export map is not followed
module.exports = require('./entrypoint/asmjs-async');

View File

@@ -1,16 +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 {Yoga} from './wrapAsm';
export * from './generated/YGEnums';
export * from './wrapAsm';
declare const yoga: Yoga;
export default yoga;

View File

@@ -1,11 +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
*/
// Fallback for when the export map is not followed
module.exports = require('./entrypoint/asmjs-sync');

View File

@@ -187,5 +187,5 @@ export type Yoga = {
}; };
} & typeof YGEnums; } & typeof YGEnums;
declare const wrapAsm: () => Yoga; declare const wrapAsm: (assembly: unknown) => Yoga;
export default wrapAsm; export default wrapAsm;

View File

@@ -10,6 +10,9 @@
import path from 'path'; import path from 'path';
import YogaAsmjs from 'yoga-layout/asmjs-sync';
import YogaWasm from 'yoga-layout/wasm-sync';
const WARMUP_ITERATIONS = 3; const WARMUP_ITERATIONS = 3;
const BENCHMARK_ITERATIONS = 10; const BENCHMARK_ITERATIONS = 10;
@@ -18,9 +21,7 @@ const testFiles = process.argv.slice(2);
const testResults = new Map<string, Map<string, number>>(); const testResults = new Map<string, Map<string, number>>();
for (const type of ['asmjs', 'wasm']) { for (const type of ['asmjs', 'wasm']) {
globalThis.Yoga = require(type === 'asmjs' globalThis.Yoga = type === 'asmjs' ? YogaAsmjs : YogaWasm;
? '../../dist/entrypoint/asmjs-sync'
: '../../dist/entrypoint/wasm-sync');
for (const file of testFiles) { for (const file of testFiles) {
globalThis.YGBENCHMARK = (name: string, fn: () => void) => { globalThis.YGBENCHMARK = (name: string, fn: () => void) => {

View File

@@ -6,8 +6,11 @@
"declaration": true, "declaration": true,
"esModuleInterop": true, "esModuleInterop": true,
"skipLibCheck": true, "skipLibCheck": true,
"forceConsistentCasingInFileNames": true, // TODO: moduleResolution: "nodenext" is buggy with this if the absolute
// path contains any capital letters
"forceConsistentCasingInFileNames": false,
"baseUrl": ".", "baseUrl": ".",
"moduleResolution": "nodenext",
"paths": { "paths": {
"yoga-layout": ["src"] "yoga-layout": ["src"]
} }