Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb9505c980 |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"packages": ["packages/react", "packages/react-dom", "packages/react-server-dom-webpack", "packages/scheduler"],
|
||||
"buildCommand": "download-build-in-codesandbox-ci",
|
||||
"node": "20",
|
||||
"node": "18",
|
||||
"publishDirectory": {
|
||||
"react": "build/oss-experimental/react",
|
||||
"react-dom": "build/oss-experimental/react-dom",
|
||||
|
||||
@@ -6,51 +6,52 @@
|
||||
*/
|
||||
|
||||
import MonacoEditor, {loader, type Monaco} from '@monaco-editor/react';
|
||||
import {parseConfigPragmaAsString} from 'babel-plugin-react-compiler';
|
||||
import type {editor} from 'monaco-editor';
|
||||
import * as monaco from 'monaco-editor';
|
||||
import {useState} from 'react';
|
||||
import parserBabel from 'prettier/plugins/babel';
|
||||
import * as prettierPluginEstree from 'prettier/plugins/estree';
|
||||
import * as prettier from 'prettier/standalone';
|
||||
import {useState, useEffect} from 'react';
|
||||
import {Resizable} from 're-resizable';
|
||||
import {useStore, useStoreDispatch} from '../StoreContext';
|
||||
import {useStore} from '../StoreContext';
|
||||
import {monacoOptions} from './monacoOptions';
|
||||
import {
|
||||
generateOverridePragmaFromConfig,
|
||||
updateSourceWithOverridePragma,
|
||||
} from '../../lib/configUtils';
|
||||
|
||||
loader.config({monaco});
|
||||
|
||||
export default function ConfigEditor(): JSX.Element {
|
||||
const [, setMonaco] = useState<Monaco | null>(null);
|
||||
const store = useStore();
|
||||
const dispatchStore = useStoreDispatch();
|
||||
|
||||
const handleChange: (value: string | undefined) => void = async value => {
|
||||
if (value === undefined) return;
|
||||
// Parse string-based override config from pragma comment and format it
|
||||
const [configJavaScript, setConfigJavaScript] = useState('');
|
||||
|
||||
try {
|
||||
const newPragma = await generateOverridePragmaFromConfig(value);
|
||||
const updatedSource = updateSourceWithOverridePragma(
|
||||
store.source,
|
||||
newPragma,
|
||||
);
|
||||
useEffect(() => {
|
||||
const pragma = store.source.substring(0, store.source.indexOf('\n'));
|
||||
const configString = `(${parseConfigPragmaAsString(pragma)})`;
|
||||
|
||||
// Update the store with both the new config and updated source
|
||||
dispatchStore({
|
||||
type: 'updateFile',
|
||||
payload: {
|
||||
source: updatedSource,
|
||||
config: value,
|
||||
},
|
||||
prettier
|
||||
.format(configString, {
|
||||
semi: true,
|
||||
parser: 'babel-ts',
|
||||
plugins: [parserBabel, prettierPluginEstree],
|
||||
})
|
||||
.then(formatted => {
|
||||
setConfigJavaScript(formatted);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error formatting config:', error);
|
||||
setConfigJavaScript('({})'); // Return empty object if not valid for now
|
||||
//TODO: Add validation and error handling for config
|
||||
});
|
||||
} catch (_) {
|
||||
dispatchStore({
|
||||
type: 'updateFile',
|
||||
payload: {
|
||||
source: store.source,
|
||||
config: value,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log('Config:', configString);
|
||||
}, [store.source]);
|
||||
|
||||
const handleChange: (value: string | undefined) => void = value => {
|
||||
if (!value) return;
|
||||
|
||||
// TODO: Implement sync logic to update pragma comments in the source
|
||||
console.log('Config changed:', value);
|
||||
};
|
||||
|
||||
const handleMount: (
|
||||
@@ -80,11 +81,12 @@ export default function ConfigEditor(): JSX.Element {
|
||||
<MonacoEditor
|
||||
path={'config.js'}
|
||||
language={'javascript'}
|
||||
value={store.config}
|
||||
value={configJavaScript}
|
||||
onMount={handleMount}
|
||||
onChange={handleChange}
|
||||
options={{
|
||||
...monacoOptions,
|
||||
readOnly: true,
|
||||
lineNumbers: 'off',
|
||||
folding: false,
|
||||
renderLineHighlight: 'none',
|
||||
|
||||
@@ -48,7 +48,6 @@ import {
|
||||
import {transformFromAstSync} from '@babel/core';
|
||||
import {LoggerEvent} from 'babel-plugin-react-compiler/dist/Entrypoint';
|
||||
import {useSearchParams} from 'next/navigation';
|
||||
import {parseAndFormatConfig} from '../../lib/configUtils';
|
||||
|
||||
function parseInput(
|
||||
input: string,
|
||||
@@ -316,17 +315,9 @@ export default function Editor(): JSX.Element {
|
||||
});
|
||||
mountStore = defaultStore;
|
||||
}
|
||||
|
||||
parseAndFormatConfig(mountStore.source).then(config => {
|
||||
dispatchStore({
|
||||
type: 'setStore',
|
||||
payload: {
|
||||
store: {
|
||||
...mountStore,
|
||||
config,
|
||||
},
|
||||
},
|
||||
});
|
||||
dispatchStore({
|
||||
type: 'setStore',
|
||||
payload: {store: mountStore},
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ import {useStore, useStoreDispatch} from '../StoreContext';
|
||||
import {monacoOptions} from './monacoOptions';
|
||||
// @ts-expect-error TODO: Make TS recognize .d.ts files, in addition to loading them with webpack.
|
||||
import React$Types from '../../node_modules/@types/react/index.d.ts';
|
||||
import {parseAndFormatConfig} from '../../lib/configUtils.ts';
|
||||
|
||||
loader.config({monaco});
|
||||
|
||||
@@ -80,17 +79,13 @@ export default function Input({errors, language}: Props): JSX.Element {
|
||||
});
|
||||
}, [monaco, language]);
|
||||
|
||||
const handleChange: (value: string | undefined) => void = async value => {
|
||||
const handleChange: (value: string | undefined) => void = value => {
|
||||
if (!value) return;
|
||||
|
||||
// Parse and format the config
|
||||
const config = await parseAndFormatConfig(value);
|
||||
|
||||
dispatchStore({
|
||||
type: 'updateFile',
|
||||
payload: {
|
||||
source: value,
|
||||
config,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -56,7 +56,6 @@ type ReducerAction =
|
||||
type: 'updateFile';
|
||||
payload: {
|
||||
source: string;
|
||||
config?: string;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -67,11 +66,10 @@ function storeReducer(store: Store, action: ReducerAction): Store {
|
||||
return newStore;
|
||||
}
|
||||
case 'updateFile': {
|
||||
const {source, config} = action.payload;
|
||||
const {source} = action.payload;
|
||||
const newStore = {
|
||||
...store,
|
||||
source,
|
||||
config,
|
||||
};
|
||||
return newStore;
|
||||
}
|
||||
|
||||
@@ -1,87 +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.
|
||||
*/
|
||||
|
||||
import parserBabel from 'prettier/plugins/babel';
|
||||
import prettierPluginEstree from 'prettier/plugins/estree';
|
||||
import * as prettier from 'prettier/standalone';
|
||||
import {parseConfigPragmaAsString} from '../../../packages/babel-plugin-react-compiler/src/Utils/TestUtils';
|
||||
|
||||
/**
|
||||
* Parse config from pragma and format it with prettier
|
||||
*/
|
||||
export async function parseAndFormatConfig(source: string): Promise<string> {
|
||||
const pragma = source.substring(0, source.indexOf('\n'));
|
||||
let configString = parseConfigPragmaAsString(pragma);
|
||||
if (configString !== '') {
|
||||
configString = `(${configString})`;
|
||||
}
|
||||
|
||||
try {
|
||||
const formatted = await prettier.format(configString, {
|
||||
semi: true,
|
||||
parser: 'babel-ts',
|
||||
plugins: [parserBabel, prettierPluginEstree],
|
||||
});
|
||||
return formatted;
|
||||
} catch (error) {
|
||||
console.error('Error formatting config:', error);
|
||||
return ''; // Return empty string if not valid for now
|
||||
}
|
||||
}
|
||||
|
||||
function extractCurlyBracesContent(input: string): string {
|
||||
const startIndex = input.indexOf('{');
|
||||
const endIndex = input.lastIndexOf('}');
|
||||
if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) {
|
||||
throw new Error('No outer curly braces found in input');
|
||||
}
|
||||
return input.slice(startIndex, endIndex + 1);
|
||||
}
|
||||
|
||||
function cleanContent(content: string): string {
|
||||
return content
|
||||
.replace(/[\r\n]+/g, ' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a the override pragma comment from a formatted config object string
|
||||
*/
|
||||
export async function generateOverridePragmaFromConfig(
|
||||
formattedConfigString: string,
|
||||
): Promise<string> {
|
||||
const content = extractCurlyBracesContent(formattedConfigString);
|
||||
const cleanConfig = cleanContent(content);
|
||||
|
||||
// Format the config to ensure it's valid
|
||||
await prettier.format(`(${cleanConfig})`, {
|
||||
semi: false,
|
||||
parser: 'babel-ts',
|
||||
plugins: [parserBabel, prettierPluginEstree],
|
||||
});
|
||||
|
||||
return `// @OVERRIDE:${cleanConfig}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the override pragma comment in source code.
|
||||
*/
|
||||
export function updateSourceWithOverridePragma(
|
||||
source: string,
|
||||
newPragma: string,
|
||||
): string {
|
||||
const firstLineEnd = source.indexOf('\n');
|
||||
const firstLine = source.substring(0, firstLineEnd);
|
||||
|
||||
const pragmaRegex = /^\/\/\s*@/;
|
||||
if (firstLineEnd !== -1 && pragmaRegex.test(firstLine.trim())) {
|
||||
return newPragma + source.substring(firstLineEnd);
|
||||
} else {
|
||||
return newPragma + '\n' + source;
|
||||
}
|
||||
}
|
||||
@@ -15,10 +15,8 @@ export default function MyApp() {
|
||||
|
||||
export const defaultStore: Store = {
|
||||
source: index,
|
||||
config: '',
|
||||
};
|
||||
|
||||
export const emptyStore: Store = {
|
||||
source: '',
|
||||
config: '',
|
||||
};
|
||||
|
||||
@@ -17,7 +17,6 @@ import {defaultStore} from '../defaultStore';
|
||||
*/
|
||||
export interface Store {
|
||||
source: string;
|
||||
config?: string;
|
||||
}
|
||||
export function encodeStore(store: Store): string {
|
||||
return compressToEncodedURIComponent(JSON.stringify(store));
|
||||
@@ -66,14 +65,5 @@ export function initStoreFromUrlOrLocalStorage(): Store {
|
||||
const raw = decodeStore(encodedSource);
|
||||
|
||||
invariant(isValidStore(raw), 'Invalid Store');
|
||||
|
||||
// Add config property if missing for backwards compatibility
|
||||
if (!('config' in raw)) {
|
||||
return {
|
||||
...raw,
|
||||
config: '',
|
||||
};
|
||||
}
|
||||
|
||||
return raw;
|
||||
}
|
||||
|
||||
1
compiler/apps/playground/next-env.d.ts
vendored
1
compiler/apps/playground/next-env.d.ts
vendored
@@ -1,6 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
/// <reference path="./.next/types/routes.d.ts" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
@@ -34,30 +34,26 @@
|
||||
"invariant": "^2.2.4",
|
||||
"lz-string": "^1.5.0",
|
||||
"monaco-editor": "^0.52.0",
|
||||
"next": "15.5.2",
|
||||
"next": "^15.2.0-canary.64",
|
||||
"notistack": "^3.0.0-alpha.7",
|
||||
"prettier": "^3.3.3",
|
||||
"pretty-format": "^29.3.1",
|
||||
"re-resizable": "^6.9.16",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1"
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "18.11.9",
|
||||
"@types/react": "19.1.12",
|
||||
"@types/react-dom": "19.1.9",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"clsx": "^1.2.1",
|
||||
"concurrently": "^7.4.0",
|
||||
"eslint": "^8.28.0",
|
||||
"eslint-config-next": "15.5.2",
|
||||
"eslint-config-next": "^15.0.1",
|
||||
"monaco-editor-webpack-plugin": "^7.1.0",
|
||||
"postcss": "^8.4.31",
|
||||
"tailwindcss": "^3.2.4",
|
||||
"wait-on": "^7.2.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "19.1.12",
|
||||
"@types/react-dom": "19.1.9"
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2089,7 +2089,7 @@ function computeSignatureForInstruction(
|
||||
effects.push({
|
||||
kind: 'Freeze',
|
||||
value: operand,
|
||||
reason: ValueReason.HookCaptured,
|
||||
reason: ValueReason.Other,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,41 +175,6 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
|
||||
if (node != null) {
|
||||
valueBlockNodes.set(fallthrough, node);
|
||||
}
|
||||
} else if (terminal.kind === 'goto') {
|
||||
/**
|
||||
* If we encounter a goto that is not to the natural fallthrough of the current
|
||||
* block (not the topmost fallthrough on the stack), then this is a goto to a
|
||||
* label. Any scopes that extend beyond the goto must be extended to include
|
||||
* the labeled range, so that the break statement doesn't accidentally jump
|
||||
* out of the scope. We do this by extending the start and end of the scope's
|
||||
* range to the label and its fallthrough respectively.
|
||||
*/
|
||||
const start = activeBlockFallthroughRanges.find(
|
||||
range => range.fallthrough === terminal.block,
|
||||
);
|
||||
if (start != null && start !== activeBlockFallthroughRanges.at(-1)) {
|
||||
const fallthroughBlock = fn.body.blocks.get(start.fallthrough)!;
|
||||
const firstId =
|
||||
fallthroughBlock.instructions[0]?.id ?? fallthroughBlock.terminal.id;
|
||||
for (const scope of activeScopes) {
|
||||
/**
|
||||
* activeScopes is only filtered at block start points, so some of the
|
||||
* scopes may not actually be active anymore, ie we've past their end
|
||||
* instruction. Only extend ranges for scopes that are actually active.
|
||||
*
|
||||
* TODO: consider pruning activeScopes per instruction
|
||||
*/
|
||||
if (scope.range.end <= terminal.id) {
|
||||
continue;
|
||||
}
|
||||
scope.range.start = makeInstructionId(
|
||||
Math.min(start.range.start, scope.range.start),
|
||||
);
|
||||
scope.range.end = makeInstructionId(
|
||||
Math.max(firstId, scope.range.end),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -411,9 +411,7 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<
|
||||
this.state = state;
|
||||
this.options = {
|
||||
memoizeJsxElements: !this.env.config.enableForest,
|
||||
forceMemoizePrimitives:
|
||||
this.env.config.enableForest ||
|
||||
this.env.config.enablePreserveExistingMemoizationGuarantees,
|
||||
forceMemoizePrimitives: this.env.config.enableForest,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -536,23 +534,9 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<
|
||||
case 'JSXText':
|
||||
case 'BinaryExpression':
|
||||
case 'UnaryExpression': {
|
||||
if (options.forceMemoizePrimitives) {
|
||||
/**
|
||||
* Because these instructions produce primitives we usually don't consider
|
||||
* them as escape points: they are known to copy, not return references.
|
||||
* However if we're forcing memoization of primitives then we mark these
|
||||
* instructions as needing memoization and walk their rvalues to ensure
|
||||
* any scopes transitively reachable from the rvalues are considered for
|
||||
* memoization. Note: we may still prune primitive-producing scopes if
|
||||
* they don't ultimately escape at all.
|
||||
*/
|
||||
const level = MemoizationLevel.Conditional;
|
||||
return {
|
||||
lvalues: lvalue !== null ? [{place: lvalue, level}] : [],
|
||||
rvalues: [...eachReactiveValueOperand(value)],
|
||||
};
|
||||
}
|
||||
const level = MemoizationLevel.Never;
|
||||
const level = options.forceMemoizePrimitives
|
||||
? MemoizationLevel.Memoized
|
||||
: MemoizationLevel.Never;
|
||||
return {
|
||||
// All of these instructions return a primitive value and never need to be memoized
|
||||
lvalues: lvalue !== null ? [{place: lvalue, level}] : [],
|
||||
@@ -701,7 +685,9 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<
|
||||
}
|
||||
case 'ComputedLoad':
|
||||
case 'PropertyLoad': {
|
||||
const level = MemoizationLevel.Conditional;
|
||||
const level = options.forceMemoizePrimitives
|
||||
? MemoizationLevel.Memoized
|
||||
: MemoizationLevel.Conditional;
|
||||
return {
|
||||
// Indirection for the inner value, memoized if the value is
|
||||
lvalues: lvalue !== null ? [{place: lvalue, level}] : [],
|
||||
|
||||
@@ -255,16 +255,11 @@ function parseConfigStringAsJS(
|
||||
|
||||
console.log('OVERRIDE:', parsedConfig);
|
||||
|
||||
const environment = parseConfigPragmaEnvironmentForTest(
|
||||
'',
|
||||
defaults.environment ?? {},
|
||||
);
|
||||
|
||||
const options: Record<keyof PluginOptions, unknown> = {
|
||||
...defaultOptions,
|
||||
panicThreshold: 'all_errors',
|
||||
compilationMode: defaults.compilationMode,
|
||||
environment,
|
||||
environment: defaults.environment ?? defaultOptions.environment,
|
||||
};
|
||||
|
||||
// Apply parsed config, merging environment if it exists
|
||||
@@ -274,9 +269,22 @@ function parseConfigStringAsJS(
|
||||
...parsedConfig.environment,
|
||||
};
|
||||
|
||||
// Apply complex defaults for environment flags that are set to true
|
||||
const environmentConfig: Partial<Record<keyof EnvironmentConfig, unknown>> =
|
||||
{};
|
||||
for (const [key, value] of Object.entries(mergedEnvironment)) {
|
||||
if (hasOwnProperty(EnvironmentConfigSchema.shape, key)) {
|
||||
if (value === true && key in testComplexConfigDefaults) {
|
||||
environmentConfig[key] = testComplexConfigDefaults[key];
|
||||
} else {
|
||||
environmentConfig[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate environment config
|
||||
const validatedEnvironment =
|
||||
EnvironmentConfigSchema.safeParse(mergedEnvironment);
|
||||
EnvironmentConfigSchema.safeParse(environmentConfig);
|
||||
if (!validatedEnvironment.success) {
|
||||
CompilerError.invariant(false, {
|
||||
reason: 'Invalid environment configuration in config pragma',
|
||||
@@ -286,6 +294,10 @@ function parseConfigStringAsJS(
|
||||
});
|
||||
}
|
||||
|
||||
if (validatedEnvironment.data.enableResetCacheOnSourceFileChanges == null) {
|
||||
validatedEnvironment.data.enableResetCacheOnSourceFileChanges = false;
|
||||
}
|
||||
|
||||
options.environment = validatedEnvironment.data;
|
||||
}
|
||||
|
||||
@@ -296,7 +308,9 @@ function parseConfigStringAsJS(
|
||||
}
|
||||
|
||||
if (hasOwnProperty(defaultOptions, key)) {
|
||||
if (key === 'target' && value === 'donotuse_meta_internal') {
|
||||
if (value === true && key in testComplexPluginOptionDefaults) {
|
||||
options[key] = testComplexPluginOptionDefaults[key];
|
||||
} else if (key === 'target' && value === 'donotuse_meta_internal') {
|
||||
options[key] = {
|
||||
kind: value,
|
||||
runtimeModule: 'react',
|
||||
|
||||
@@ -46,16 +46,14 @@ function useFoo(t0) {
|
||||
t1 = $[0];
|
||||
}
|
||||
let items = t1;
|
||||
if ($[1] !== cond) {
|
||||
bb0: {
|
||||
if (cond) {
|
||||
items = [];
|
||||
} else {
|
||||
break bb0;
|
||||
}
|
||||
|
||||
items.push(2);
|
||||
bb0: if ($[1] !== cond) {
|
||||
if (cond) {
|
||||
items = [];
|
||||
} else {
|
||||
break bb0;
|
||||
}
|
||||
|
||||
items.push(2);
|
||||
$[1] = cond;
|
||||
$[2] = items;
|
||||
} else {
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
|
||||
import {useMemo} from 'react';
|
||||
import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
function Component(props) {
|
||||
const result = useMemo(
|
||||
() => makeObject(props.value).value + 1,
|
||||
[props.value]
|
||||
);
|
||||
console.log(result);
|
||||
return 'ok';
|
||||
}
|
||||
|
||||
function makeObject(value) {
|
||||
console.log(value);
|
||||
return {value};
|
||||
}
|
||||
|
||||
export const TODO_FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: 42}],
|
||||
sequentialRenders: [
|
||||
{value: 42},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
{value: 3.14},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo } from "react";
|
||||
import { makeObject_Primitives, ValidateMemoization } from "shared-runtime";
|
||||
|
||||
function Component(props) {
|
||||
const result = makeObject(props.value).value + 1;
|
||||
|
||||
console.log(result);
|
||||
return "ok";
|
||||
}
|
||||
|
||||
function makeObject(value) {
|
||||
console.log(value);
|
||||
return { value };
|
||||
}
|
||||
|
||||
export const TODO_FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ value: 42 }],
|
||||
sequentialRenders: [
|
||||
{ value: 42 },
|
||||
{ value: 42 },
|
||||
{ value: 3.14 },
|
||||
{ value: 3.14 },
|
||||
{ value: 42 },
|
||||
{ value: 3.14 },
|
||||
{ value: 42 },
|
||||
{ value: 3.14 },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
@@ -1,32 +0,0 @@
|
||||
// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
|
||||
import {useMemo} from 'react';
|
||||
import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
function Component(props) {
|
||||
const result = useMemo(
|
||||
() => makeObject(props.value).value + 1,
|
||||
[props.value]
|
||||
);
|
||||
console.log(result);
|
||||
return 'ok';
|
||||
}
|
||||
|
||||
function makeObject(value) {
|
||||
console.log(value);
|
||||
return {value};
|
||||
}
|
||||
|
||||
export const TODO_FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: 42}],
|
||||
sequentialRenders: [
|
||||
{value: 42},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
{value: 3.14},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
],
|
||||
};
|
||||
@@ -1,81 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
|
||||
import {useMemo} from 'react';
|
||||
import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
function Component(props) {
|
||||
const result = makeObject(props.value).value + 1;
|
||||
console.log(result);
|
||||
return 'ok';
|
||||
}
|
||||
|
||||
function makeObject(value) {
|
||||
console.log(value);
|
||||
return {value};
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: 42}],
|
||||
sequentialRenders: [
|
||||
{value: 42},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
{value: 3.14},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo } from "react";
|
||||
import { makeObject_Primitives, ValidateMemoization } from "shared-runtime";
|
||||
|
||||
function Component(props) {
|
||||
const result = makeObject(props.value).value + 1;
|
||||
console.log(result);
|
||||
return "ok";
|
||||
}
|
||||
|
||||
function makeObject(value) {
|
||||
console.log(value);
|
||||
return { value };
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ value: 42 }],
|
||||
sequentialRenders: [
|
||||
{ value: 42 },
|
||||
{ value: 42 },
|
||||
{ value: 3.14 },
|
||||
{ value: 3.14 },
|
||||
{ value: 42 },
|
||||
{ value: 3.14 },
|
||||
{ value: 42 },
|
||||
{ value: 3.14 },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) "ok"
|
||||
"ok"
|
||||
"ok"
|
||||
"ok"
|
||||
"ok"
|
||||
"ok"
|
||||
"ok"
|
||||
"ok"
|
||||
logs: [42,43,42,43,3.14,4.140000000000001,3.14,4.140000000000001,42,43,3.14,4.140000000000001,42,43,3.14,4.140000000000001]
|
||||
@@ -1,29 +0,0 @@
|
||||
// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
|
||||
import {useMemo} from 'react';
|
||||
import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
function Component(props) {
|
||||
const result = makeObject(props.value).value + 1;
|
||||
console.log(result);
|
||||
return 'ok';
|
||||
}
|
||||
|
||||
function makeObject(value) {
|
||||
console.log(value);
|
||||
return {value};
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: 42}],
|
||||
sequentialRenders: [
|
||||
{value: 42},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
{value: 3.14},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
],
|
||||
};
|
||||
@@ -1,107 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
|
||||
import {useMemo} from 'react';
|
||||
import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
function Component(props) {
|
||||
const result = useMemo(() => {
|
||||
return makeObject(props.value).value + 1;
|
||||
}, [props.value]);
|
||||
return <ValidateMemoization inputs={[props.value]} output={result} />;
|
||||
}
|
||||
|
||||
function makeObject(value) {
|
||||
console.log(value);
|
||||
return {value};
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: 42}],
|
||||
sequentialRenders: [
|
||||
{value: 42},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
{value: 3.14},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo } from "react";
|
||||
import { makeObject_Primitives, ValidateMemoization } from "shared-runtime";
|
||||
|
||||
function Component(props) {
|
||||
const $ = _c(7);
|
||||
let t0;
|
||||
if ($[0] !== props.value) {
|
||||
t0 = makeObject(props.value);
|
||||
$[0] = props.value;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
const result = t0.value + 1;
|
||||
let t1;
|
||||
if ($[2] !== props.value) {
|
||||
t1 = [props.value];
|
||||
$[2] = props.value;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
}
|
||||
let t2;
|
||||
if ($[4] !== result || $[5] !== t1) {
|
||||
t2 = <ValidateMemoization inputs={t1} output={result} />;
|
||||
$[4] = result;
|
||||
$[5] = t1;
|
||||
$[6] = t2;
|
||||
} else {
|
||||
t2 = $[6];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
function makeObject(value) {
|
||||
console.log(value);
|
||||
return { value };
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ value: 42 }],
|
||||
sequentialRenders: [
|
||||
{ value: 42 },
|
||||
{ value: 42 },
|
||||
{ value: 3.14 },
|
||||
{ value: 3.14 },
|
||||
{ value: 42 },
|
||||
{ value: 3.14 },
|
||||
{ value: 42 },
|
||||
{ value: 3.14 },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"inputs":[42],"output":43}</div>
|
||||
<div>{"inputs":[42],"output":43}</div>
|
||||
<div>{"inputs":[3.14],"output":4.140000000000001}</div>
|
||||
<div>{"inputs":[3.14],"output":4.140000000000001}</div>
|
||||
<div>{"inputs":[42],"output":43}</div>
|
||||
<div>{"inputs":[3.14],"output":4.140000000000001}</div>
|
||||
<div>{"inputs":[42],"output":43}</div>
|
||||
<div>{"inputs":[3.14],"output":4.140000000000001}</div>
|
||||
logs: [42,3.14,42,3.14,42,3.14]
|
||||
@@ -1,30 +0,0 @@
|
||||
// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
|
||||
import {useMemo} from 'react';
|
||||
import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
function Component(props) {
|
||||
const result = useMemo(() => {
|
||||
return makeObject(props.value).value + 1;
|
||||
}, [props.value]);
|
||||
return <ValidateMemoization inputs={[props.value]} output={result} />;
|
||||
}
|
||||
|
||||
function makeObject(value) {
|
||||
console.log(value);
|
||||
return {value};
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: 42}],
|
||||
sequentialRenders: [
|
||||
{value: 42},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
{value: 3.14},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
],
|
||||
};
|
||||
@@ -2,7 +2,6 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @compilationMode:"infer"
|
||||
import {makeArray} from 'shared-runtime';
|
||||
|
||||
function Component() {
|
||||
@@ -31,7 +30,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer"
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { makeArray } from "shared-runtime";
|
||||
|
||||
function Component() {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// @compilationMode:"infer"
|
||||
import {makeArray} from 'shared-runtime';
|
||||
|
||||
function Component() {
|
||||
|
||||
@@ -49,12 +49,12 @@ import {
|
||||
} from "shared-runtime";
|
||||
|
||||
function useFoo(t0) {
|
||||
const $ = _c(4);
|
||||
const $ = _c(3);
|
||||
const { data } = t0;
|
||||
let obj;
|
||||
let myDiv = null;
|
||||
if ($[0] !== data.cond || $[1] !== data.cond1) {
|
||||
bb0: if (data.cond) {
|
||||
bb0: if (data.cond) {
|
||||
if ($[0] !== data.cond1) {
|
||||
obj = makeObject_Primitives();
|
||||
if (data.cond1) {
|
||||
myDiv = <Stringify value={mutateAndReturn(obj)} />;
|
||||
@@ -62,14 +62,13 @@ function useFoo(t0) {
|
||||
}
|
||||
|
||||
mutate(obj);
|
||||
$[0] = data.cond1;
|
||||
$[1] = obj;
|
||||
$[2] = myDiv;
|
||||
} else {
|
||||
obj = $[1];
|
||||
myDiv = $[2];
|
||||
}
|
||||
$[0] = data.cond;
|
||||
$[1] = data.cond1;
|
||||
$[2] = obj;
|
||||
$[3] = myDiv;
|
||||
} else {
|
||||
obj = $[2];
|
||||
myDiv = $[3];
|
||||
}
|
||||
return myDiv;
|
||||
}
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePreserveExistingMemoizationGuarantees
|
||||
import {fbt} from 'fbt';
|
||||
|
||||
function Component() {
|
||||
const buttonLabel = () => {
|
||||
if (!someCondition) {
|
||||
return <fbt desc="My label">{'Purchase as a gift'}</fbt>;
|
||||
} else if (
|
||||
!iconOnly &&
|
||||
showPrice &&
|
||||
item?.current_gift_offer?.price?.formatted != null
|
||||
) {
|
||||
return (
|
||||
<fbt desc="Gift button's label">
|
||||
{'Gift | '}
|
||||
<fbt:param name="price">
|
||||
{item?.current_gift_offer?.price?.formatted}
|
||||
</fbt:param>
|
||||
</fbt>
|
||||
);
|
||||
} else if (!iconOnly && !showPrice) {
|
||||
return <fbt desc="Gift button's label">{'Gift'}</fbt>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Button text={buttonLabel()} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees
|
||||
import { fbt } from "fbt";
|
||||
|
||||
function Component() {
|
||||
const $ = _c(1);
|
||||
const buttonLabel = _temp;
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = (
|
||||
<View>
|
||||
<Button text={buttonLabel()} />
|
||||
</View>
|
||||
);
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
function _temp() {
|
||||
if (!someCondition) {
|
||||
return fbt._("Purchase as a gift", null, { hk: "1gHj4g" });
|
||||
} else {
|
||||
if (
|
||||
!iconOnly &&
|
||||
showPrice &&
|
||||
item?.current_gift_offer?.price?.formatted != null
|
||||
) {
|
||||
return fbt._(
|
||||
"Gift | {price}",
|
||||
[fbt._param("price", item?.current_gift_offer?.price?.formatted)],
|
||||
{ hk: "3GTnGE" },
|
||||
);
|
||||
} else {
|
||||
if (!iconOnly && !showPrice) {
|
||||
return fbt._("Gift", null, { hk: "3fqfrk" });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
@@ -1,31 +0,0 @@
|
||||
// @enablePreserveExistingMemoizationGuarantees
|
||||
import {fbt} from 'fbt';
|
||||
|
||||
function Component() {
|
||||
const buttonLabel = () => {
|
||||
if (!someCondition) {
|
||||
return <fbt desc="My label">{'Purchase as a gift'}</fbt>;
|
||||
} else if (
|
||||
!iconOnly &&
|
||||
showPrice &&
|
||||
item?.current_gift_offer?.price?.formatted != null
|
||||
) {
|
||||
return (
|
||||
<fbt desc="Gift button's label">
|
||||
{'Gift | '}
|
||||
<fbt:param name="price">
|
||||
{item?.current_gift_offer?.price?.formatted}
|
||||
</fbt:param>
|
||||
</fbt>
|
||||
);
|
||||
} else if (!iconOnly && !showPrice) {
|
||||
return <fbt desc="Gift button's label">{'Gift'}</fbt>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Button text={buttonLabel()} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePreserveExistingMemoizationGuarantees
|
||||
// @enableForest
|
||||
function Component({base, start, increment, test}) {
|
||||
let value = base;
|
||||
for (let i = start; i < test; i += increment) {
|
||||
@@ -27,23 +27,25 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees
|
||||
import { c as _c } from "react/compiler-runtime"; // @enableForest
|
||||
function Component(t0) {
|
||||
const $ = _c(2);
|
||||
const $ = _c(5);
|
||||
const { base, start, increment, test } = t0;
|
||||
let value = base;
|
||||
for (let i = start; i < test; i = i + increment, i) {
|
||||
value = value + i;
|
||||
}
|
||||
let t1;
|
||||
if ($[0] !== value) {
|
||||
t1 = <div>{value}</div>;
|
||||
$[0] = value;
|
||||
$[1] = t1;
|
||||
let value;
|
||||
if ($[0] !== base || $[1] !== increment || $[2] !== start || $[3] !== test) {
|
||||
value = base;
|
||||
for (let i = start; i < test; i = i + increment, i) {
|
||||
value = value + i;
|
||||
}
|
||||
$[0] = base;
|
||||
$[1] = increment;
|
||||
$[2] = start;
|
||||
$[3] = test;
|
||||
$[4] = value;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
value = $[4];
|
||||
}
|
||||
return t1;
|
||||
return <div>{value}</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @enablePreserveExistingMemoizationGuarantees
|
||||
// @enableForest
|
||||
function Component({base, start, increment, test}) {
|
||||
let value = base;
|
||||
for (let i = start; i < test; i += increment) {
|
||||
|
||||
@@ -34,16 +34,17 @@ import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
|
||||
import { useMemo } from "react";
|
||||
|
||||
function Component(props) {
|
||||
const $ = _c(5);
|
||||
const $ = _c(6);
|
||||
let t0;
|
||||
if (
|
||||
$[0] !== props.a ||
|
||||
$[1] !== props.b ||
|
||||
$[2] !== props.cond ||
|
||||
$[3] !== props.cond2
|
||||
) {
|
||||
bb0: {
|
||||
const y = [];
|
||||
bb0: {
|
||||
let y;
|
||||
if (
|
||||
$[0] !== props.a ||
|
||||
$[1] !== props.b ||
|
||||
$[2] !== props.cond ||
|
||||
$[3] !== props.cond2
|
||||
) {
|
||||
y = [];
|
||||
if (props.cond) {
|
||||
y.push(props.a);
|
||||
}
|
||||
@@ -53,15 +54,17 @@ function Component(props) {
|
||||
}
|
||||
|
||||
y.push(props.b);
|
||||
t0 = y;
|
||||
$[0] = props.a;
|
||||
$[1] = props.b;
|
||||
$[2] = props.cond;
|
||||
$[3] = props.cond2;
|
||||
$[4] = y;
|
||||
$[5] = t0;
|
||||
} else {
|
||||
y = $[4];
|
||||
t0 = $[5];
|
||||
}
|
||||
$[0] = props.a;
|
||||
$[1] = props.b;
|
||||
$[2] = props.cond;
|
||||
$[3] = props.cond2;
|
||||
$[4] = t0;
|
||||
} else {
|
||||
t0 = $[4];
|
||||
t0 = y;
|
||||
}
|
||||
const x = t0;
|
||||
return x;
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {useMemo} from 'react';
|
||||
import {
|
||||
makeObject_Primitives,
|
||||
mutate,
|
||||
Stringify,
|
||||
ValidateMemoization,
|
||||
} from 'shared-runtime';
|
||||
|
||||
function Component({cond}) {
|
||||
const memoized = useMemo(() => {
|
||||
const value = makeObject_Primitives();
|
||||
if (cond) {
|
||||
return value;
|
||||
} else {
|
||||
mutate(value);
|
||||
return value;
|
||||
}
|
||||
}, [cond]);
|
||||
return <ValidateMemoization inputs={[cond]} output={memoized} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{cond: false}],
|
||||
sequentialRenders: [
|
||||
{cond: false},
|
||||
{cond: false},
|
||||
{cond: true},
|
||||
{cond: true},
|
||||
{cond: false},
|
||||
{cond: true},
|
||||
{cond: false},
|
||||
{cond: true},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
makeObject_Primitives,
|
||||
mutate,
|
||||
Stringify,
|
||||
ValidateMemoization,
|
||||
} from "shared-runtime";
|
||||
|
||||
function Component(t0) {
|
||||
const $ = _c(7);
|
||||
const { cond } = t0;
|
||||
let t1;
|
||||
if ($[0] !== cond) {
|
||||
const value = makeObject_Primitives();
|
||||
if (cond) {
|
||||
t1 = value;
|
||||
} else {
|
||||
mutate(value);
|
||||
t1 = value;
|
||||
}
|
||||
$[0] = cond;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
const memoized = t1;
|
||||
let t2;
|
||||
if ($[2] !== cond) {
|
||||
t2 = [cond];
|
||||
$[2] = cond;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
}
|
||||
let t3;
|
||||
if ($[4] !== memoized || $[5] !== t2) {
|
||||
t3 = <ValidateMemoization inputs={t2} output={memoized} />;
|
||||
$[4] = memoized;
|
||||
$[5] = t2;
|
||||
$[6] = t3;
|
||||
} else {
|
||||
t3 = $[6];
|
||||
}
|
||||
return t3;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ cond: false }],
|
||||
sequentialRenders: [
|
||||
{ cond: false },
|
||||
{ cond: false },
|
||||
{ cond: true },
|
||||
{ cond: true },
|
||||
{ cond: false },
|
||||
{ cond: true },
|
||||
{ cond: false },
|
||||
{ cond: true },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"inputs":[false],"output":{"a":0,"b":"value1","c":true,"wat0":"joe"}}</div>
|
||||
<div>{"inputs":[false],"output":{"a":0,"b":"value1","c":true,"wat0":"joe"}}</div>
|
||||
<div>{"inputs":[true],"output":{"a":0,"b":"value1","c":true}}</div>
|
||||
<div>{"inputs":[true],"output":{"a":0,"b":"value1","c":true}}</div>
|
||||
<div>{"inputs":[false],"output":{"a":0,"b":"value1","c":true,"wat0":"joe"}}</div>
|
||||
<div>{"inputs":[true],"output":{"a":0,"b":"value1","c":true}}</div>
|
||||
<div>{"inputs":[false],"output":{"a":0,"b":"value1","c":true,"wat0":"joe"}}</div>
|
||||
<div>{"inputs":[true],"output":{"a":0,"b":"value1","c":true}}</div>
|
||||
@@ -1,35 +0,0 @@
|
||||
import {useMemo} from 'react';
|
||||
import {
|
||||
makeObject_Primitives,
|
||||
mutate,
|
||||
Stringify,
|
||||
ValidateMemoization,
|
||||
} from 'shared-runtime';
|
||||
|
||||
function Component({cond}) {
|
||||
const memoized = useMemo(() => {
|
||||
const value = makeObject_Primitives();
|
||||
if (cond) {
|
||||
return value;
|
||||
} else {
|
||||
mutate(value);
|
||||
return value;
|
||||
}
|
||||
}, [cond]);
|
||||
return <ValidateMemoization inputs={[cond]} output={memoized} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{cond: false}],
|
||||
sequentialRenders: [
|
||||
{cond: false},
|
||||
{cond: false},
|
||||
{cond: true},
|
||||
{cond: true},
|
||||
{cond: false},
|
||||
{cond: true},
|
||||
{cond: false},
|
||||
{cond: true},
|
||||
],
|
||||
};
|
||||
@@ -33,16 +33,17 @@ import { c as _c } from "react/compiler-runtime";
|
||||
import { useMemo } from "react";
|
||||
|
||||
function Component(props) {
|
||||
const $ = _c(5);
|
||||
const $ = _c(6);
|
||||
let t0;
|
||||
if (
|
||||
$[0] !== props.a ||
|
||||
$[1] !== props.b ||
|
||||
$[2] !== props.cond ||
|
||||
$[3] !== props.cond2
|
||||
) {
|
||||
bb0: {
|
||||
const y = [];
|
||||
bb0: {
|
||||
let y;
|
||||
if (
|
||||
$[0] !== props.a ||
|
||||
$[1] !== props.b ||
|
||||
$[2] !== props.cond ||
|
||||
$[3] !== props.cond2
|
||||
) {
|
||||
y = [];
|
||||
if (props.cond) {
|
||||
y.push(props.a);
|
||||
}
|
||||
@@ -52,15 +53,17 @@ function Component(props) {
|
||||
}
|
||||
|
||||
y.push(props.b);
|
||||
t0 = y;
|
||||
$[0] = props.a;
|
||||
$[1] = props.b;
|
||||
$[2] = props.cond;
|
||||
$[3] = props.cond2;
|
||||
$[4] = y;
|
||||
$[5] = t0;
|
||||
} else {
|
||||
y = $[4];
|
||||
t0 = $[5];
|
||||
}
|
||||
$[0] = props.a;
|
||||
$[1] = props.b;
|
||||
$[2] = props.cond;
|
||||
$[3] = props.cond2;
|
||||
$[4] = t0;
|
||||
} else {
|
||||
t0 = $[4];
|
||||
t0 = y;
|
||||
}
|
||||
const x = t0;
|
||||
return x;
|
||||
|
||||
@@ -48,7 +48,10 @@ export {
|
||||
printReactiveFunction,
|
||||
printReactiveFunctionWithOutlined,
|
||||
} from './ReactiveScopes';
|
||||
export {parseConfigPragmaForTests} from './Utils/TestUtils';
|
||||
export {
|
||||
parseConfigPragmaForTests,
|
||||
parseConfigPragmaAsString,
|
||||
} from './Utils/TestUtils';
|
||||
declare global {
|
||||
let __DEV__: boolean | null | undefined;
|
||||
}
|
||||
|
||||
@@ -74,7 +74,13 @@ function getDebugChannel(req) {
|
||||
return activeDebugChannels.get(requestId);
|
||||
}
|
||||
|
||||
async function renderApp(res, returnValue, formState, noCache, debugChannel) {
|
||||
async function renderApp(
|
||||
res,
|
||||
returnValue,
|
||||
formState,
|
||||
noCache,
|
||||
promiseForDebugChannel
|
||||
) {
|
||||
const {renderToPipeableStream} = await import(
|
||||
'react-server-dom-webpack/server'
|
||||
);
|
||||
@@ -126,7 +132,7 @@ async function renderApp(res, returnValue, formState, noCache, debugChannel) {
|
||||
// For client-invoked server actions we refresh the tree and return a return value.
|
||||
const payload = {root, returnValue, formState};
|
||||
const {pipe} = renderToPipeableStream(payload, moduleMap, {
|
||||
debugChannel,
|
||||
debugChannel: await promiseForDebugChannel,
|
||||
filterStackFrame,
|
||||
});
|
||||
pipe(res);
|
||||
@@ -379,20 +385,23 @@ app.on('error', function (error) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
// Open a websocket server for Debug information
|
||||
const WebSocket = require('ws');
|
||||
const webSocketServer = new WebSocket.Server({noServer: true});
|
||||
|
||||
const webSocketServer = new WebSocket.Server({
|
||||
server: httpServer,
|
||||
path: '/debug-channel',
|
||||
});
|
||||
|
||||
webSocketServer.on('connection', (ws, req) => {
|
||||
const url = new URL(req.url, `http://${req.headers.host}`);
|
||||
const requestId = url.searchParams.get('id');
|
||||
|
||||
activeDebugChannels.set(requestId, ws);
|
||||
|
||||
ws.on('close', (code, reason) => {
|
||||
activeDebugChannels.delete(requestId);
|
||||
});
|
||||
httpServer.on('upgrade', (request, socket, head) => {
|
||||
const DEBUG_CHANNEL_PATH = '/debug-channel?';
|
||||
if (request.url.startsWith(DEBUG_CHANNEL_PATH)) {
|
||||
const requestId = request.url.slice(DEBUG_CHANNEL_PATH.length);
|
||||
const promiseForWs = new Promise(resolve => {
|
||||
webSocketServer.handleUpgrade(request, socket, head, ws => {
|
||||
ws.on('close', () => {
|
||||
activeDebugChannels.delete(requestId);
|
||||
});
|
||||
resolve(ws);
|
||||
});
|
||||
});
|
||||
activeDebugChannels.set(requestId, promiseForWs);
|
||||
} else {
|
||||
socket.destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,52 +14,18 @@ function findSourceMapURL(fileName) {
|
||||
);
|
||||
}
|
||||
|
||||
async function createWebSocketStream(url) {
|
||||
const ws = new WebSocket(url);
|
||||
ws.binaryType = 'arraybuffer';
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
ws.addEventListener('open', resolve, {once: true});
|
||||
ws.addEventListener('error', reject, {once: true});
|
||||
});
|
||||
|
||||
const writable = new WritableStream({
|
||||
write(chunk) {
|
||||
ws.send(chunk);
|
||||
},
|
||||
close() {
|
||||
ws.close();
|
||||
},
|
||||
abort(reason) {
|
||||
ws.close(1000, reason && String(reason));
|
||||
},
|
||||
});
|
||||
|
||||
const readable = new ReadableStream({
|
||||
start(controller) {
|
||||
ws.addEventListener('message', event => {
|
||||
controller.enqueue(event.data);
|
||||
});
|
||||
ws.addEventListener('close', () => {
|
||||
controller.close();
|
||||
});
|
||||
ws.addEventListener('error', err => {
|
||||
controller.error(err);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return {readable, writable};
|
||||
}
|
||||
|
||||
let updateRoot;
|
||||
async function callServer(id, args) {
|
||||
let response;
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
if (
|
||||
process.env.NODE_ENV === 'development' &&
|
||||
typeof WebSocketStream === 'function'
|
||||
) {
|
||||
const requestId = crypto.randomUUID();
|
||||
const debugChannel = await createWebSocketStream(
|
||||
`ws://localhost:3001/debug-channel?id=${requestId}`
|
||||
const wss = new WebSocketStream(
|
||||
'ws://localhost:3001/debug-channel?' + requestId
|
||||
);
|
||||
const debugChannel = await wss.opened;
|
||||
response = createFromFetch(
|
||||
fetch('/', {
|
||||
method: 'POST',
|
||||
@@ -108,11 +74,15 @@ function Shell({data}) {
|
||||
|
||||
async function hydrateApp() {
|
||||
let response;
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
if (
|
||||
process.env.NODE_ENV === 'development' &&
|
||||
typeof WebSocketStream === 'function'
|
||||
) {
|
||||
const requestId = crypto.randomUUID();
|
||||
const debugChannel = await createWebSocketStream(
|
||||
`ws://localhost:3001/debug-channel?id=${requestId}`
|
||||
const wss = new WebSocketStream(
|
||||
'ws://localhost:3001/debug-channel?' + requestId
|
||||
);
|
||||
const debugChannel = await wss.opened;
|
||||
response = createFromFetch(
|
||||
fetch('/', {
|
||||
headers: {
|
||||
|
||||
@@ -22,22 +22,15 @@ yarn add eslint-plugin-react-hooks --dev
|
||||
|
||||
#### >= 6.0.0
|
||||
|
||||
For users of 6.0 and beyond, add the `recommended` config.
|
||||
For users of 6.0 and beyond, simply add the `recommended` config.
|
||||
|
||||
```js
|
||||
// eslint.config.js
|
||||
import reactHooks from 'eslint-plugin-react-hooks';
|
||||
import { defineConfig } from 'eslint/config';
|
||||
import * as reactHooks from 'eslint-plugin-react-hooks';
|
||||
|
||||
export default defineConfig([
|
||||
{
|
||||
files: ["src/**/*.{js,jsx,ts,tsx}"],
|
||||
plugins: {
|
||||
'react-hooks': reactHooks,
|
||||
},
|
||||
extends: ['react-hooks/recommended'],
|
||||
},
|
||||
]);
|
||||
export default [
|
||||
// ...
|
||||
reactHooks.configs.recommended,
|
||||
];
|
||||
```
|
||||
|
||||
#### 5.2.0
|
||||
@@ -45,18 +38,12 @@ export default defineConfig([
|
||||
For users of 5.2.0 (the first version with flat config support), add the `recommended-latest` config.
|
||||
|
||||
```js
|
||||
import reactHooks from 'eslint-plugin-react-hooks';
|
||||
import { defineConfig } from 'eslint/config';
|
||||
import * as reactHooks from 'eslint-plugin-react-hooks';
|
||||
|
||||
export default defineConfig([
|
||||
{
|
||||
files: ["src/**/*.{js,jsx,ts,tsx}"],
|
||||
plugins: {
|
||||
'react-hooks': reactHooks,
|
||||
},
|
||||
extends: ['react-hooks/recommended-latest'],
|
||||
},
|
||||
]);
|
||||
export default [
|
||||
// ...
|
||||
reactHooks.configs['recommended-latest'],
|
||||
];
|
||||
```
|
||||
|
||||
### Legacy Config (.eslintrc)
|
||||
|
||||
52
packages/react-client/src/ReactFlightClient.js
vendored
52
packages/react-client/src/ReactFlightClient.js
vendored
@@ -1010,15 +1010,10 @@ export function reportGlobalError(
|
||||
if (__DEV__) {
|
||||
const debugChannel = response._debugChannel;
|
||||
if (debugChannel !== undefined) {
|
||||
// If we don't have any more ways of reading data, we don't have to send
|
||||
// any more neither. So we close the writable side.
|
||||
// If we don't have any more ways of reading data, we don't have to send any
|
||||
// more neither. So we close the writable side.
|
||||
closeDebugChannel(debugChannel);
|
||||
response._debugChannel = undefined;
|
||||
// Make sure the debug channel is not closed a second time when the
|
||||
// Response gets GC:ed.
|
||||
if (debugChannelRegistry !== null) {
|
||||
debugChannelRegistry.unregister(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1074,14 +1069,7 @@ function getTaskName(type: mixed): string {
|
||||
}
|
||||
}
|
||||
|
||||
function initializeElement(
|
||||
response: Response,
|
||||
element: any,
|
||||
lazyType: null | LazyComponent<
|
||||
React$Element<any>,
|
||||
SomeChunk<React$Element<any>>,
|
||||
>,
|
||||
): void {
|
||||
function initializeElement(response: Response, element: any): void {
|
||||
if (!__DEV__) {
|
||||
return;
|
||||
}
|
||||
@@ -1148,18 +1136,6 @@ function initializeElement(
|
||||
if (owner !== null) {
|
||||
initializeFakeStack(response, owner);
|
||||
}
|
||||
|
||||
// In case the JSX runtime has validated the lazy type as a static child, we
|
||||
// need to transfer this information to the element.
|
||||
if (
|
||||
lazyType &&
|
||||
lazyType._store &&
|
||||
lazyType._store.validated &&
|
||||
!element._store.validated
|
||||
) {
|
||||
element._store.validated = lazyType._store.validated;
|
||||
}
|
||||
|
||||
// TODO: We should be freezing the element but currently, we might write into
|
||||
// _debugInfo later. We could move it into _store which remains mutable.
|
||||
Object.freeze(element.props);
|
||||
@@ -1172,7 +1148,7 @@ function createElement(
|
||||
props: mixed,
|
||||
owner: ?ReactComponentInfo, // DEV-only
|
||||
stack: ?ReactStackTrace, // DEV-only
|
||||
validated: 0 | 1 | 2, // DEV-only
|
||||
validated: number, // DEV-only
|
||||
):
|
||||
| React$Element<any>
|
||||
| LazyComponent<React$Element<any>, SomeChunk<React$Element<any>>> {
|
||||
@@ -1249,7 +1225,7 @@ function createElement(
|
||||
handler.reason,
|
||||
);
|
||||
if (__DEV__) {
|
||||
initializeElement(response, element, null);
|
||||
initializeElement(response, element);
|
||||
// Conceptually the error happened inside this Element but right before
|
||||
// it was rendered. We don't have a client side component to render but
|
||||
// we can add some DebugInfo to explain that this was conceptually a
|
||||
@@ -1268,7 +1244,7 @@ function createElement(
|
||||
}
|
||||
erroredChunk._debugInfo = [erroredComponent];
|
||||
}
|
||||
return createLazyChunkWrapper(erroredChunk, validated);
|
||||
return createLazyChunkWrapper(erroredChunk);
|
||||
}
|
||||
if (handler.deps > 0) {
|
||||
// We have blocked references inside this Element but we can turn this into
|
||||
@@ -1277,17 +1253,16 @@ function createElement(
|
||||
createBlockedChunk(response);
|
||||
handler.value = element;
|
||||
handler.chunk = blockedChunk;
|
||||
const lazyType = createLazyChunkWrapper(blockedChunk, validated);
|
||||
if (__DEV__) {
|
||||
// After we have initialized any blocked references, initialize stack etc.
|
||||
const init = initializeElement.bind(null, response, element, lazyType);
|
||||
/// After we have initialized any blocked references, initialize stack etc.
|
||||
const init = initializeElement.bind(null, response, element);
|
||||
blockedChunk.then(init, init);
|
||||
}
|
||||
return lazyType;
|
||||
return createLazyChunkWrapper(blockedChunk);
|
||||
}
|
||||
}
|
||||
if (__DEV__) {
|
||||
initializeElement(response, element, null);
|
||||
initializeElement(response, element);
|
||||
}
|
||||
|
||||
return element;
|
||||
@@ -1295,7 +1270,6 @@ function createElement(
|
||||
|
||||
function createLazyChunkWrapper<T>(
|
||||
chunk: SomeChunk<T>,
|
||||
validated: 0 | 1 | 2, // DEV-only
|
||||
): LazyComponent<T, SomeChunk<T>> {
|
||||
const lazyType: LazyComponent<T, SomeChunk<T>> = {
|
||||
$$typeof: REACT_LAZY_TYPE,
|
||||
@@ -1307,8 +1281,6 @@ function createLazyChunkWrapper<T>(
|
||||
const chunkDebugInfo: ReactDebugInfo =
|
||||
chunk._debugInfo || (chunk._debugInfo = ([]: ReactDebugInfo));
|
||||
lazyType._debugInfo = chunkDebugInfo;
|
||||
// Initialize a store for key validation by the JSX runtime.
|
||||
lazyType._store = {validated: validated};
|
||||
}
|
||||
return lazyType;
|
||||
}
|
||||
@@ -2113,7 +2085,7 @@ function parseModelString(
|
||||
}
|
||||
// We create a React.lazy wrapper around any lazy values.
|
||||
// When passed into React, we'll know how to suspend on this.
|
||||
return createLazyChunkWrapper(chunk, 0);
|
||||
return createLazyChunkWrapper(chunk);
|
||||
}
|
||||
case '@': {
|
||||
// Promise
|
||||
@@ -2462,7 +2434,7 @@ function ResponseInstance(
|
||||
// When a Response gets GC:ed because nobody is referring to any of the
|
||||
// objects that lazily load from the Response anymore, then we can close
|
||||
// the debug channel.
|
||||
debugChannelRegistry.register(this, debugChannel, this);
|
||||
debugChannelRegistry.register(this, debugChannel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,8 +80,8 @@ export default function Element({data, index, style}: Props): React.Node {
|
||||
};
|
||||
|
||||
// $FlowFixMe[missing-local-annot]
|
||||
const handleClick = ({metaKey, button}) => {
|
||||
if (id !== null && button === 0) {
|
||||
const handleClick = ({metaKey}) => {
|
||||
if (id !== null) {
|
||||
logEvent({
|
||||
event_name: 'select-element',
|
||||
metadata: {source: 'click-element'},
|
||||
|
||||
@@ -16,15 +16,14 @@
|
||||
|
||||
.TreeWrapper {
|
||||
border-top: 1px solid var(--color-border);
|
||||
flex: 1 1 65%;
|
||||
flex: 1 1 var(--horizontal-resize-tree-percentage);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.InspectedElementWrapper {
|
||||
flex: 0 0 calc(100% - var(--horizontal-resize-tree-percentage));
|
||||
flex: 1 1 35%;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
@@ -60,12 +59,12 @@
|
||||
|
||||
.TreeWrapper {
|
||||
border-top: 1px solid var(--color-border);
|
||||
flex: 1 1 50%;
|
||||
flex: 1 1 var(--vertical-resize-tree-percentage);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.InspectedElementWrapper {
|
||||
flex: 0 0 calc(100% - var(--vertical-resize-tree-percentage));
|
||||
flex: 1 1 50%;
|
||||
}
|
||||
|
||||
.TreeWrapper + .ResizeBarWrapper .ResizeBar {
|
||||
|
||||
@@ -2,18 +2,13 @@
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 0.25rem;
|
||||
padding: 0 0.25rem;
|
||||
}
|
||||
|
||||
.SuspenseTimelineInput {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
/*
|
||||
* `overflow: auto` will add scrollbars but the input will not actually grow beyond visible content.
|
||||
* `overflow: hidden` will constrain the input to its visible content.
|
||||
*/
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.SuspenseTimelineRootSwitcher {
|
||||
@@ -21,6 +16,20 @@
|
||||
max-width: 3rem;
|
||||
}
|
||||
|
||||
.SuspenseTimelineProgressIndicator {
|
||||
align-self: center;
|
||||
.SuspenseTimelineMarkers {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.SuspenseTimelineMarkers > * {
|
||||
flex: 1 1 0;
|
||||
overflow: visible;
|
||||
visibility: hidden;
|
||||
width: 0
|
||||
}
|
||||
|
||||
.SuspenseTimelineActiveMarker {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,14 @@ import type {Element, SuspenseNode} from '../../../frontend/types';
|
||||
import type Store from '../../store';
|
||||
|
||||
import * as React from 'react';
|
||||
import {useContext, useLayoutEffect, useMemo, useRef, useState} from 'react';
|
||||
import {
|
||||
useContext,
|
||||
useId,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import {BridgeContext, StoreContext} from '../context';
|
||||
import {TreeDispatcherContext} from '../Components/TreeContext';
|
||||
import {useHighlightHostInstance} from '../hooks';
|
||||
@@ -105,6 +112,30 @@ function SuspenseTimelineInput({rootID}: {rootID: Element['id'] | void}) {
|
||||
setValue(max);
|
||||
}
|
||||
|
||||
const markersID = useId();
|
||||
const markers: React.Node[] = useMemo(() => {
|
||||
return timeline.map((suspense, index) => {
|
||||
const takesUpSpace =
|
||||
suspense.rects !== null &&
|
||||
suspense.rects.some(rect => {
|
||||
return rect.width > 0 && rect.height > 0;
|
||||
});
|
||||
|
||||
return takesUpSpace ? (
|
||||
<option
|
||||
key={suspense.id}
|
||||
className={
|
||||
index === value ? styles.SuspenseTimelineActiveMarker : undefined
|
||||
}
|
||||
value={index}>
|
||||
#{index + 1}
|
||||
</option>
|
||||
) : (
|
||||
<option key={suspense.id} />
|
||||
);
|
||||
});
|
||||
}, [timeline, value]);
|
||||
|
||||
if (rootID === undefined) {
|
||||
return <div className={styles.SuspenseTimelineInput}>Root not found.</div>;
|
||||
}
|
||||
@@ -188,26 +219,25 @@ function SuspenseTimelineInput({rootID}: {rootID: Element['id'] | void}) {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
{value}/{max}
|
||||
</div>
|
||||
<div className={styles.SuspenseTimelineInput}>
|
||||
<input
|
||||
className={styles.SuspenseTimelineSlider}
|
||||
type="range"
|
||||
min={min}
|
||||
max={max}
|
||||
value={value}
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
onFocus={handleFocus}
|
||||
onPointerMove={handlePointerMove}
|
||||
onPointerUp={clearHighlightHostInstance}
|
||||
ref={inputRef}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
<div className={styles.SuspenseTimelineInput}>
|
||||
<input
|
||||
className={styles.SuspenseTimelineSlider}
|
||||
type="range"
|
||||
min={min}
|
||||
max={max}
|
||||
list={markersID}
|
||||
value={value}
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
onFocus={handleFocus}
|
||||
onPointerMove={handlePointerMove}
|
||||
onPointerUp={clearHighlightHostInstance}
|
||||
ref={inputRef}
|
||||
/>
|
||||
<datalist id={markersID} className={styles.SuspenseTimelineMarkers}>
|
||||
{markers}
|
||||
</datalist>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,5 +10,5 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export default function SuspenseTreeList(_: {}): React$Node {
|
||||
return <div>Activity slices not implemented yet</div>;
|
||||
return <div>Activity slices</div>;
|
||||
}
|
||||
|
||||
@@ -126,7 +126,6 @@ function wwwOnCaughtError(
|
||||
|
||||
defaultOnCaughtError(error, errorInfo);
|
||||
}
|
||||
const noopOnDefaultTransitionIndicator = noop;
|
||||
|
||||
export function createRoot(
|
||||
container: Element | Document | DocumentFragment,
|
||||
@@ -138,7 +137,6 @@ export function createRoot(
|
||||
({
|
||||
onUncaughtError: wwwOnUncaughtError,
|
||||
onCaughtError: wwwOnCaughtError,
|
||||
onDefaultTransitionIndicator: noopOnDefaultTransitionIndicator,
|
||||
}: any),
|
||||
options,
|
||||
),
|
||||
@@ -157,7 +155,6 @@ export function hydrateRoot(
|
||||
({
|
||||
onUncaughtError: wwwOnUncaughtError,
|
||||
onCaughtError: wwwOnCaughtError,
|
||||
onDefaultTransitionIndicator: noopOnDefaultTransitionIndicator,
|
||||
}: any),
|
||||
options,
|
||||
),
|
||||
@@ -214,6 +211,7 @@ function getReactRootElementInContainer(container: any) {
|
||||
// This isn't reachable because onRecoverableError isn't called in the
|
||||
// legacy API.
|
||||
const noopOnRecoverableError = noop;
|
||||
const noopOnDefaultTransitionIndicator = noop;
|
||||
|
||||
function legacyCreateRootFromDOMContainer(
|
||||
container: Container,
|
||||
|
||||
@@ -46,10 +46,7 @@ import {
|
||||
createPublicRootInstance,
|
||||
type PublicRootInstance,
|
||||
} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
|
||||
import {
|
||||
disableLegacyMode,
|
||||
enableDefaultTransitionIndicator,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {disableLegacyMode} from 'shared/ReactFeatureFlags';
|
||||
|
||||
if (typeof ReactFiberErrorDialog.showErrorDialog !== 'function') {
|
||||
throw new Error(
|
||||
@@ -135,12 +132,6 @@ function render(
|
||||
if (options && options.onRecoverableError !== undefined) {
|
||||
onRecoverableError = options.onRecoverableError;
|
||||
}
|
||||
let onDefaultTransitionIndicator = nativeOnDefaultTransitionIndicator;
|
||||
if (enableDefaultTransitionIndicator) {
|
||||
if (options && options.onDefaultTransitionIndicator !== undefined) {
|
||||
onDefaultTransitionIndicator = options.onDefaultTransitionIndicator;
|
||||
}
|
||||
}
|
||||
|
||||
const publicRootInstance = createPublicRootInstance(containerTag);
|
||||
const rootInstance = {
|
||||
@@ -160,7 +151,7 @@ function render(
|
||||
onUncaughtError,
|
||||
onCaughtError,
|
||||
onRecoverableError,
|
||||
onDefaultTransitionIndicator,
|
||||
nativeOnDefaultTransitionIndicator,
|
||||
null,
|
||||
);
|
||||
|
||||
|
||||
@@ -134,7 +134,6 @@ export type RenderRootOptions = {
|
||||
error: mixed,
|
||||
errorInfo: {+componentStack?: ?string},
|
||||
) => void,
|
||||
onDefaultTransitionIndicator?: () => void | (() => void),
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
20
packages/react-reconciler/src/ReactFiberHooks.js
vendored
20
packages/react-reconciler/src/ReactFiberHooks.js
vendored
@@ -73,7 +73,6 @@ import {
|
||||
includesSomeLane,
|
||||
isGestureRender,
|
||||
GestureLane,
|
||||
UpdateLanes,
|
||||
} from './ReactFiberLane';
|
||||
import {
|
||||
ContinuousEventPriority,
|
||||
@@ -2984,20 +2983,6 @@ function rerenderDeferredValue<T>(value: T, initialValue?: T): T {
|
||||
}
|
||||
}
|
||||
|
||||
function isRenderingDeferredWork(): boolean {
|
||||
if (!includesSomeLane(renderLanes, DeferredLane)) {
|
||||
// None of the render lanes are deferred lanes.
|
||||
return false;
|
||||
}
|
||||
// At least one of the render lanes are deferred lanes. However, if the
|
||||
// current render is also batched together with an update, then we can't
|
||||
// say that the render is wholly the result of deferred work. We can check
|
||||
// this by checking if the root render lanes contain any "update" lanes, i.e.
|
||||
// lanes that are only assigned to updates, like setState.
|
||||
const rootRenderLanes = getWorkInProgressRootRenderLanes();
|
||||
return !includesSomeLane(rootRenderLanes, UpdateLanes);
|
||||
}
|
||||
|
||||
function mountDeferredValueImpl<T>(hook: Hook, value: T, initialValue?: T): T {
|
||||
if (
|
||||
// When `initialValue` is provided, we defer the initial render even if the
|
||||
@@ -3006,7 +2991,7 @@ function mountDeferredValueImpl<T>(hook: Hook, value: T, initialValue?: T): T {
|
||||
// However, to avoid waterfalls, we do not defer if this render
|
||||
// was itself spawned by an earlier useDeferredValue. Check if DeferredLane
|
||||
// is part of the render lanes.
|
||||
!isRenderingDeferredWork()
|
||||
!includesSomeLane(renderLanes, DeferredLane)
|
||||
) {
|
||||
// Render with the initial value
|
||||
hook.memoizedState = initialValue;
|
||||
@@ -3053,7 +3038,8 @@ function updateDeferredValueImpl<T>(
|
||||
}
|
||||
|
||||
const shouldDeferValue =
|
||||
!includesOnlyNonUrgentLanes(renderLanes) && !isRenderingDeferredWork();
|
||||
!includesOnlyNonUrgentLanes(renderLanes) &&
|
||||
!includesSomeLane(renderLanes, DeferredLane);
|
||||
if (shouldDeferValue) {
|
||||
// This is an urgent update. Since the value has changed, keep using the
|
||||
// previous value and spawn a deferred render to update it later.
|
||||
|
||||
49
packages/react-reconciler/src/ReactFiberLane.js
vendored
49
packages/react-reconciler/src/ReactFiberLane.js
vendored
@@ -73,20 +73,6 @@ const TransitionLane12: Lane = /* */ 0b0000000000010000000
|
||||
const TransitionLane13: Lane = /* */ 0b0000000000100000000000000000000;
|
||||
const TransitionLane14: Lane = /* */ 0b0000000001000000000000000000000;
|
||||
|
||||
const TransitionUpdateLanes =
|
||||
TransitionLane1 |
|
||||
TransitionLane2 |
|
||||
TransitionLane3 |
|
||||
TransitionLane4 |
|
||||
TransitionLane5 |
|
||||
TransitionLane6 |
|
||||
TransitionLane7 |
|
||||
TransitionLane8 |
|
||||
TransitionLane9 |
|
||||
TransitionLane10;
|
||||
const TransitionDeferredLanes =
|
||||
TransitionLane11 | TransitionLane12 | TransitionLane13 | TransitionLane14;
|
||||
|
||||
const RetryLanes: Lanes = /* */ 0b0000011110000000000000000000000;
|
||||
const RetryLane1: Lane = /* */ 0b0000000010000000000000000000000;
|
||||
const RetryLane2: Lane = /* */ 0b0000000100000000000000000000000;
|
||||
@@ -108,7 +94,7 @@ export const DeferredLane: Lane = /* */ 0b1000000000000000000
|
||||
// Any lane that might schedule an update. This is used to detect infinite
|
||||
// update loops, so it doesn't include hydration lanes or retries.
|
||||
export const UpdateLanes: Lanes =
|
||||
SyncLane | InputContinuousLane | DefaultLane | TransitionUpdateLanes;
|
||||
SyncLane | InputContinuousLane | DefaultLane | TransitionLanes;
|
||||
|
||||
export const HydrationLanes =
|
||||
SyncHydrationLane |
|
||||
@@ -169,8 +155,7 @@ export function getLabelForLane(lane: Lane): string | void {
|
||||
|
||||
export const NoTimestamp = -1;
|
||||
|
||||
let nextTransitionUpdateLane: Lane = TransitionLane1;
|
||||
let nextTransitionDeferredLane: Lane = TransitionLane11;
|
||||
let nextTransitionLane: Lane = TransitionLane1;
|
||||
let nextRetryLane: Lane = RetryLane1;
|
||||
|
||||
function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes {
|
||||
@@ -205,12 +190,11 @@ function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes {
|
||||
case TransitionLane8:
|
||||
case TransitionLane9:
|
||||
case TransitionLane10:
|
||||
return lanes & TransitionUpdateLanes;
|
||||
case TransitionLane11:
|
||||
case TransitionLane12:
|
||||
case TransitionLane13:
|
||||
case TransitionLane14:
|
||||
return lanes & TransitionDeferredLanes;
|
||||
return lanes & TransitionLanes;
|
||||
case RetryLane1:
|
||||
case RetryLane2:
|
||||
case RetryLane3:
|
||||
@@ -695,23 +679,14 @@ export function isGestureRender(lanes: Lanes): boolean {
|
||||
return lanes === GestureLane;
|
||||
}
|
||||
|
||||
export function claimNextTransitionUpdateLane(): Lane {
|
||||
export function claimNextTransitionLane(): Lane {
|
||||
// Cycle through the lanes, assigning each new transition to the next lane.
|
||||
// In most cases, this means every transition gets its own lane, until we
|
||||
// run out of lanes and cycle back to the beginning.
|
||||
const lane = nextTransitionUpdateLane;
|
||||
nextTransitionUpdateLane <<= 1;
|
||||
if ((nextTransitionUpdateLane & TransitionUpdateLanes) === NoLanes) {
|
||||
nextTransitionUpdateLane = TransitionLane1;
|
||||
}
|
||||
return lane;
|
||||
}
|
||||
|
||||
export function claimNextTransitionDeferredLane(): Lane {
|
||||
const lane = nextTransitionDeferredLane;
|
||||
nextTransitionDeferredLane <<= 1;
|
||||
if ((nextTransitionDeferredLane & TransitionDeferredLanes) === NoLanes) {
|
||||
nextTransitionDeferredLane = TransitionLane11;
|
||||
const lane = nextTransitionLane;
|
||||
nextTransitionLane <<= 1;
|
||||
if ((nextTransitionLane & TransitionLanes) === NoLanes) {
|
||||
nextTransitionLane = TransitionLane1;
|
||||
}
|
||||
return lane;
|
||||
}
|
||||
@@ -977,14 +952,6 @@ function markSpawnedDeferredLane(
|
||||
// Entangle the spawned lane with the DeferredLane bit so that we know it
|
||||
// was the result of another render. This lets us avoid a useDeferredValue
|
||||
// waterfall — only the first level will defer.
|
||||
// TODO: Now that there is a reserved set of transition lanes that are used
|
||||
// exclusively for deferred work, we should get rid of this special
|
||||
// DeferredLane bit; the same information can be inferred by checking whether
|
||||
// the lane is one of the TransitionDeferredLanes. The only reason this still
|
||||
// exists is because we need to also do the same for OffscreenLane. That
|
||||
// requires additional changes because there are more places around the
|
||||
// codebase that treat OffscreenLane as a magic value; would need to check
|
||||
// for a new OffscreenDeferredLane, too. Will leave this for a follow-up.
|
||||
const spawnedLaneIndex = laneToIndex(spawnedLane);
|
||||
root.entangledLanes |= spawnedLane;
|
||||
root.entanglements[spawnedLaneIndex] |=
|
||||
|
||||
@@ -228,20 +228,9 @@ export function logComponentRender(
|
||||
? 'tertiary-dark'
|
||||
: 'primary-dark'
|
||||
: 'error';
|
||||
|
||||
if (!__DEV__) {
|
||||
console.timeStamp(
|
||||
name,
|
||||
startTime,
|
||||
endTime,
|
||||
COMPONENTS_TRACK,
|
||||
undefined,
|
||||
color,
|
||||
);
|
||||
} else {
|
||||
const debugTask = fiber._debugTask;
|
||||
if (__DEV__ && debugTask) {
|
||||
const props = fiber.memoizedProps;
|
||||
const debugTask = fiber._debugTask;
|
||||
|
||||
if (
|
||||
props !== null &&
|
||||
alternate !== null &&
|
||||
@@ -279,45 +268,38 @@ export function logComponentRender(
|
||||
reusableComponentDevToolDetails.properties = properties;
|
||||
reusableComponentOptions.start = startTime;
|
||||
reusableComponentOptions.end = endTime;
|
||||
|
||||
if (debugTask != null) {
|
||||
debugTask.run(
|
||||
// $FlowFixMe[method-unbinding]
|
||||
performance.measure.bind(
|
||||
performance,
|
||||
'\u200b' + name,
|
||||
reusableComponentOptions,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
performance.measure('\u200b' + name, reusableComponentOptions);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (debugTask != null) {
|
||||
debugTask.run(
|
||||
// $FlowFixMe[method-unbinding]
|
||||
console.timeStamp.bind(
|
||||
console,
|
||||
name,
|
||||
startTime,
|
||||
endTime,
|
||||
COMPONENTS_TRACK,
|
||||
undefined,
|
||||
color,
|
||||
performance.measure.bind(
|
||||
performance,
|
||||
'\u200b' + name,
|
||||
reusableComponentOptions,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
console.timeStamp(
|
||||
name,
|
||||
startTime,
|
||||
endTime,
|
||||
COMPONENTS_TRACK,
|
||||
undefined,
|
||||
color,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
debugTask.run(
|
||||
// $FlowFixMe[method-unbinding]
|
||||
console.timeStamp.bind(
|
||||
console,
|
||||
name,
|
||||
startTime,
|
||||
endTime,
|
||||
COMPONENTS_TRACK,
|
||||
undefined,
|
||||
color,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
console.timeStamp(
|
||||
name,
|
||||
startTime,
|
||||
endTime,
|
||||
COMPONENTS_TRACK,
|
||||
undefined,
|
||||
color,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ import {
|
||||
getNextLanes,
|
||||
includesSyncLane,
|
||||
markStarvedLanesAsExpired,
|
||||
claimNextTransitionUpdateLane,
|
||||
claimNextTransitionLane,
|
||||
getNextLanesToFlushSync,
|
||||
checkIfRootIsPrerendering,
|
||||
isGestureRender,
|
||||
@@ -716,7 +716,7 @@ export function requestTransitionLane(
|
||||
: // We may or may not be inside an async action scope. If we are, this
|
||||
// is the first update in that scope. Either way, we need to get a
|
||||
// fresh transition lane.
|
||||
claimNextTransitionUpdateLane();
|
||||
claimNextTransitionLane();
|
||||
}
|
||||
return currentEventTransitionLane;
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ import {
|
||||
OffscreenLane,
|
||||
SyncUpdateLanes,
|
||||
UpdateLanes,
|
||||
claimNextTransitionDeferredLane,
|
||||
claimNextTransitionLane,
|
||||
checkIfRootIsPrerendering,
|
||||
includesOnlyViewTransitionEligibleLanes,
|
||||
isGestureRender,
|
||||
@@ -827,7 +827,7 @@ export function requestDeferredLane(): Lane {
|
||||
workInProgressDeferredLane = OffscreenLane;
|
||||
} else {
|
||||
// Everything else is spawned as a transition.
|
||||
workInProgressDeferredLane = claimNextTransitionDeferredLane();
|
||||
workInProgressDeferredLane = claimNextTransitionLane();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -608,48 +608,6 @@ describe('ReactDeferredValue', () => {
|
||||
},
|
||||
);
|
||||
|
||||
it(
|
||||
"regression: useDeferredValue's initial value argument works even if an unrelated " +
|
||||
'transition is suspended',
|
||||
async () => {
|
||||
// Simulates a previous bug where a new useDeferredValue hook is mounted
|
||||
// while some unrelated transition is suspended. In the regression case,
|
||||
// the initial values was skipped/ignored.
|
||||
|
||||
function Content({text}) {
|
||||
return (
|
||||
<AsyncText text={useDeferredValue(text, `Preview ${text}...`)} />
|
||||
);
|
||||
}
|
||||
|
||||
function App({text}) {
|
||||
// Use a key to force a new Content instance to be mounted each time
|
||||
// the text changes.
|
||||
return <Content key={text} text={text} />;
|
||||
}
|
||||
|
||||
const root = ReactNoop.createRoot();
|
||||
|
||||
// Render a previous UI using useDeferredValue. Suspend on the
|
||||
// final value.
|
||||
resolveText('Preview A...');
|
||||
await act(() => startTransition(() => root.render(<App text="A" />)));
|
||||
assertLog(['Preview A...', 'Suspend! [A]']);
|
||||
|
||||
// While it's still suspended, update the UI to show a different screen
|
||||
// with a different preview value. We should be able to show the new
|
||||
// preview even though the previous transition never finished.
|
||||
resolveText('Preview B...');
|
||||
await act(() => startTransition(() => root.render(<App text="B" />)));
|
||||
assertLog(['Preview B...', 'Suspend! [B]']);
|
||||
|
||||
// Now finish loading the final value.
|
||||
await act(() => resolveText('B'));
|
||||
assertLog(['B']);
|
||||
expect(root).toMatchRenderedOutput('B');
|
||||
},
|
||||
);
|
||||
|
||||
it('avoids a useDeferredValue waterfall when separated by a Suspense boundary', async () => {
|
||||
// Same as the previous test but with a Suspense boundary separating the
|
||||
// two useDeferredValue hooks.
|
||||
|
||||
@@ -2846,64 +2846,4 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
|
||||
expect(container.innerHTML).toBe('<p>Hi</p>');
|
||||
});
|
||||
|
||||
it('should not have missing key warnings when a static child is blocked on debug info', async () => {
|
||||
const ClientComponent = clientExports(function ClientComponent({element}) {
|
||||
return (
|
||||
<div>
|
||||
<span>Hi</span>
|
||||
{element}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
let debugReadableStreamController;
|
||||
|
||||
const debugReadableStream = new ReadableStream({
|
||||
start(controller) {
|
||||
debugReadableStreamController = controller;
|
||||
},
|
||||
});
|
||||
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(
|
||||
<ClientComponent element={<span>Sebbie</span>} />,
|
||||
webpackMap,
|
||||
{
|
||||
debugChannel: {
|
||||
writable: new WritableStream({
|
||||
write(chunk) {
|
||||
debugReadableStreamController.enqueue(chunk);
|
||||
},
|
||||
close() {
|
||||
debugReadableStreamController.close();
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
function ClientRoot({response}) {
|
||||
return use(response);
|
||||
}
|
||||
|
||||
const response = ReactServerDOMClient.createFromReadableStream(stream, {
|
||||
debugChannel: {readable: createDelayedStream(debugReadableStream)},
|
||||
});
|
||||
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
|
||||
await act(() => {
|
||||
root.render(<ClientRoot response={response} />);
|
||||
});
|
||||
|
||||
// Wait for the debug info to be processed.
|
||||
await act(() => {});
|
||||
|
||||
expect(container.innerHTML).toBe(
|
||||
'<div><span>Hi</span><span>Sebbie</span></div>',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -59,10 +59,7 @@ export type LazyComponent<T, P> = {
|
||||
$$typeof: symbol | number,
|
||||
_payload: P,
|
||||
_init: (payload: P) => T,
|
||||
|
||||
// __DEV__
|
||||
_debugInfo?: null | ReactDebugInfo,
|
||||
_store?: {validated: 0 | 1 | 2, ...}, // 0: not validated, 1: validated, 2: force fail
|
||||
};
|
||||
|
||||
function lazyInitializer<T>(payload: Payload<T>): T {
|
||||
|
||||
@@ -804,14 +804,6 @@ function validateChildKeys(node) {
|
||||
if (node._store) {
|
||||
node._store.validated = 1;
|
||||
}
|
||||
} else if (isLazyType(node)) {
|
||||
if (node._payload.status === 'fulfilled') {
|
||||
if (isValidElement(node._payload.value) && node._payload.value._store) {
|
||||
node._payload.value._store.validated = 1;
|
||||
}
|
||||
} else if (node._store) {
|
||||
node._store.validated = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -830,11 +822,3 @@ export function isValidElement(object) {
|
||||
object.$$typeof === REACT_ELEMENT_TYPE
|
||||
);
|
||||
}
|
||||
|
||||
export function isLazyType(object) {
|
||||
return (
|
||||
typeof object === 'object' &&
|
||||
object !== null &&
|
||||
object.$$typeof === REACT_LAZY_TYPE
|
||||
);
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ export const enableSuspenseyImages: boolean = false;
|
||||
export const enableFizzBlockingRender: boolean = true;
|
||||
export const enableSrcObject: boolean = false;
|
||||
export const enableHydrationChangeEvent: boolean = true;
|
||||
export const enableDefaultTransitionIndicator: boolean = true;
|
||||
export const enableDefaultTransitionIndicator: boolean = false;
|
||||
export const ownerStackLimit = 1e4;
|
||||
export const enableComponentPerformanceTrack: boolean =
|
||||
__PROFILE__ && dynamicFlags.enableComponentPerformanceTrack;
|
||||
|
||||
@@ -66,7 +66,7 @@ export const enableSuspenseyImages = false;
|
||||
export const enableFizzBlockingRender = true;
|
||||
export const enableSrcObject = false;
|
||||
export const enableHydrationChangeEvent = false;
|
||||
export const enableDefaultTransitionIndicator = true;
|
||||
export const enableDefaultTransitionIndicator = false;
|
||||
export const enableFragmentRefs = false;
|
||||
export const enableFragmentRefsScrollIntoView = false;
|
||||
export const ownerStackLimit = 1e4;
|
||||
|
||||
@@ -79,7 +79,7 @@ export const enableSuspenseyImages: boolean = false;
|
||||
export const enableFizzBlockingRender: boolean = true;
|
||||
export const enableSrcObject: boolean = false;
|
||||
export const enableHydrationChangeEvent: boolean = false;
|
||||
export const enableDefaultTransitionIndicator: boolean = true;
|
||||
export const enableDefaultTransitionIndicator: boolean = false;
|
||||
|
||||
export const enableFragmentRefs: boolean = false;
|
||||
export const enableFragmentRefsScrollIntoView: boolean = false;
|
||||
|
||||
@@ -109,7 +109,7 @@ export const enableSuspenseyImages: boolean = false;
|
||||
export const enableFizzBlockingRender: boolean = true;
|
||||
export const enableSrcObject: boolean = false;
|
||||
export const enableHydrationChangeEvent: boolean = false;
|
||||
export const enableDefaultTransitionIndicator: boolean = true;
|
||||
export const enableDefaultTransitionIndicator: boolean = false;
|
||||
|
||||
export const ownerStackLimit = 1e4;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user