Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8219b34b60 | ||
|
|
a9410fb487 | ||
|
|
6b70072c4f | ||
|
|
b2cff47472 | ||
|
|
8943025358 |
@@ -103,6 +103,7 @@ import {validateNoFreezingKnownMutableFunctions} from '../Validation/ValidateNoF
|
||||
import {inferMutationAliasingEffects} from '../Inference/InferMutationAliasingEffects';
|
||||
import {inferMutationAliasingRanges} from '../Inference/InferMutationAliasingRanges';
|
||||
import {validateNoDerivedComputationsInEffects} from '../Validation/ValidateNoDerivedComputationsInEffects';
|
||||
import {nameAnonymousFunctions} from '../Transform/NameAnonymousFunctions';
|
||||
|
||||
export type CompilerPipelineValue =
|
||||
| {kind: 'ast'; name: string; value: CodegenFunction}
|
||||
@@ -414,6 +415,15 @@ function runWithEnvironment(
|
||||
});
|
||||
}
|
||||
|
||||
if (env.config.enableNameAnonymousFunctions) {
|
||||
nameAnonymousFunctions(hir);
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'NameAnonymougFunctions',
|
||||
value: hir,
|
||||
});
|
||||
}
|
||||
|
||||
const reactiveFunction = buildReactiveFunction(hir);
|
||||
log({
|
||||
kind: 'reactive',
|
||||
|
||||
@@ -3566,6 +3566,8 @@ function lowerFunctionToValue(
|
||||
let name: string | null = null;
|
||||
if (expr.isFunctionExpression()) {
|
||||
name = expr.get('id')?.node?.name ?? null;
|
||||
} else if (expr.isFunctionDeclaration()) {
|
||||
name = expr.get('id')?.node?.name ?? null;
|
||||
}
|
||||
const loweredFunc = lowerFunction(builder, expr);
|
||||
if (!loweredFunc) {
|
||||
|
||||
@@ -261,6 +261,8 @@ export const EnvironmentConfigSchema = z.object({
|
||||
|
||||
enableFire: z.boolean().default(false),
|
||||
|
||||
enableNameAnonymousFunctions: z.boolean().default(false),
|
||||
|
||||
/**
|
||||
* Enables inference and auto-insertion of effect dependencies. Takes in an array of
|
||||
* configurable module and import pairs to allow for user-land experimentation. For example,
|
||||
|
||||
@@ -15,6 +15,7 @@ import {Type, makeType} from './Types';
|
||||
import {z} from 'zod';
|
||||
import type {AliasingEffect} from '../Inference/AliasingEffects';
|
||||
import {isReservedWord} from '../Utils/Keyword';
|
||||
import {Err, Ok, Result} from '../Utils/Result';
|
||||
|
||||
/*
|
||||
* *******************************************************************************************
|
||||
@@ -1298,6 +1299,15 @@ export function forkTemporaryIdentifier(
|
||||
};
|
||||
}
|
||||
|
||||
export function validateIdentifierName(
|
||||
name: string,
|
||||
): Result<ValidIdentifierName, null> {
|
||||
if (isReservedWord(name) || !t.isValidIdentifier(name)) {
|
||||
return Err(null);
|
||||
}
|
||||
return Ok(makeIdentifierName(name).value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a valid identifier name. This should *not* be used for synthesizing
|
||||
* identifier names: only call this method for identifier names that appear in the
|
||||
|
||||
@@ -748,10 +748,14 @@ function applyEffect(
|
||||
case 'Alias':
|
||||
case 'Capture': {
|
||||
CompilerError.invariant(
|
||||
effect.kind === 'Capture' || initialized.has(effect.into.identifier.id),
|
||||
effect.kind === 'Capture' ||
|
||||
effect.kind === 'MaybeAlias' ||
|
||||
initialized.has(effect.into.identifier.id),
|
||||
{
|
||||
reason: `Expected destination value to already be initialized within this instruction for Alias effect`,
|
||||
description: `Destination ${printPlace(effect.into)} is not initialized in this instruction`,
|
||||
reason: `Expected destination to already be initialized within this instruction`,
|
||||
description:
|
||||
`Destination ${printPlace(effect.into)} is not initialized in this ` +
|
||||
`instruction for effect ${printAliasingEffect(effect)}`,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
@@ -767,49 +771,67 @@ function applyEffect(
|
||||
* copy-on-write semantics, then we can prune the effect
|
||||
*/
|
||||
const intoKind = state.kind(effect.into).kind;
|
||||
let isMutableDesination: boolean;
|
||||
let destinationType: 'context' | 'mutable' | null = null;
|
||||
switch (intoKind) {
|
||||
case ValueKind.Context:
|
||||
case ValueKind.Mutable:
|
||||
case ValueKind.MaybeFrozen: {
|
||||
isMutableDesination = true;
|
||||
case ValueKind.Context: {
|
||||
destinationType = 'context';
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
isMutableDesination = false;
|
||||
case ValueKind.Mutable:
|
||||
case ValueKind.MaybeFrozen: {
|
||||
destinationType = 'mutable';
|
||||
break;
|
||||
}
|
||||
}
|
||||
const fromKind = state.kind(effect.from).kind;
|
||||
let isMutableReferenceType: boolean;
|
||||
let sourceType: 'context' | 'mutable' | 'frozen' | null = null;
|
||||
switch (fromKind) {
|
||||
case ValueKind.Context: {
|
||||
sourceType = 'context';
|
||||
break;
|
||||
}
|
||||
case ValueKind.Global:
|
||||
case ValueKind.Primitive: {
|
||||
isMutableReferenceType = false;
|
||||
break;
|
||||
}
|
||||
case ValueKind.Frozen: {
|
||||
isMutableReferenceType = false;
|
||||
applyEffect(
|
||||
context,
|
||||
state,
|
||||
{
|
||||
kind: 'ImmutableCapture',
|
||||
from: effect.from,
|
||||
into: effect.into,
|
||||
},
|
||||
initialized,
|
||||
effects,
|
||||
);
|
||||
sourceType = 'frozen';
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
isMutableReferenceType = true;
|
||||
sourceType = 'mutable';
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isMutableDesination && isMutableReferenceType) {
|
||||
|
||||
if (sourceType === 'frozen') {
|
||||
applyEffect(
|
||||
context,
|
||||
state,
|
||||
{
|
||||
kind: 'ImmutableCapture',
|
||||
from: effect.from,
|
||||
into: effect.into,
|
||||
},
|
||||
initialized,
|
||||
effects,
|
||||
);
|
||||
} else if (
|
||||
(sourceType === 'mutable' && destinationType === 'mutable') ||
|
||||
effect.kind === 'MaybeAlias'
|
||||
) {
|
||||
effects.push(effect);
|
||||
} else if (
|
||||
(sourceType === 'context' && destinationType != null) ||
|
||||
(sourceType === 'mutable' && destinationType === 'context')
|
||||
) {
|
||||
applyEffect(
|
||||
context,
|
||||
state,
|
||||
{kind: 'MaybeAlias', from: effect.from, into: effect.into},
|
||||
initialized,
|
||||
effects,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -779,7 +779,13 @@ class AliasingState {
|
||||
if (edge.index >= index) {
|
||||
break;
|
||||
}
|
||||
queue.push({place: edge.node, transitive, direction: 'forwards', kind});
|
||||
queue.push({
|
||||
place: edge.node,
|
||||
transitive,
|
||||
direction: 'forwards',
|
||||
// Traversing a maybeAlias edge always downgrades to conditional mutation
|
||||
kind: edge.kind === 'maybeAlias' ? MutationKind.Conditional : kind,
|
||||
});
|
||||
}
|
||||
for (const [alias, when] of node.createdFrom) {
|
||||
if (when >= index) {
|
||||
@@ -807,7 +813,12 @@ class AliasingState {
|
||||
if (when >= index) {
|
||||
continue;
|
||||
}
|
||||
queue.push({place: alias, transitive, direction: 'backwards', kind});
|
||||
queue.push({
|
||||
place: alias,
|
||||
transitive,
|
||||
direction: 'backwards',
|
||||
kind,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* MaybeAlias indicates potential data flow from unknown function calls,
|
||||
|
||||
@@ -43,6 +43,7 @@ import {
|
||||
ValidIdentifierName,
|
||||
getHookKind,
|
||||
makeIdentifierName,
|
||||
validateIdentifierName,
|
||||
} from '../HIR/HIR';
|
||||
import {printIdentifier, printInstruction, printPlace} from '../HIR/PrintHIR';
|
||||
import {eachPatternOperand} from '../HIR/visitors';
|
||||
@@ -2326,6 +2327,11 @@ function codegenInstructionValue(
|
||||
),
|
||||
reactiveFunction,
|
||||
).unwrap();
|
||||
|
||||
const validatedName =
|
||||
instrValue.name != null
|
||||
? validateIdentifierName(instrValue.name)
|
||||
: Err(null);
|
||||
if (instrValue.type === 'ArrowFunctionExpression') {
|
||||
let body: t.BlockStatement | t.Expression = fn.body;
|
||||
if (body.body.length === 1 && loweredFunc.directives.length == 0) {
|
||||
@@ -2337,14 +2343,28 @@ function codegenInstructionValue(
|
||||
value = t.arrowFunctionExpression(fn.params, body, fn.async);
|
||||
} else {
|
||||
value = t.functionExpression(
|
||||
fn.id ??
|
||||
(instrValue.name != null ? t.identifier(instrValue.name) : null),
|
||||
validatedName
|
||||
.map<t.Identifier | null>(name => t.identifier(name))
|
||||
.unwrapOr(null),
|
||||
fn.params,
|
||||
fn.body,
|
||||
fn.generator,
|
||||
fn.async,
|
||||
);
|
||||
}
|
||||
if (
|
||||
cx.env.config.enableNameAnonymousFunctions &&
|
||||
validatedName.isErr() &&
|
||||
instrValue.name != null
|
||||
) {
|
||||
const name = instrValue.name;
|
||||
value = t.memberExpression(
|
||||
t.objectExpression([t.objectProperty(t.stringLiteral(name), value)]),
|
||||
t.stringLiteral(name),
|
||||
true,
|
||||
false,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'TaggedTemplateExpression': {
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* 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 {
|
||||
FunctionExpression,
|
||||
getHookKind,
|
||||
HIRFunction,
|
||||
IdentifierId,
|
||||
} from '../HIR';
|
||||
|
||||
export function nameAnonymousFunctions(fn: HIRFunction): void {
|
||||
if (fn.id == null) {
|
||||
return;
|
||||
}
|
||||
const parentName = fn.id;
|
||||
const functions = nameAnonymousFunctionsImpl(fn);
|
||||
function visit(node: Node, prefix: string): void {
|
||||
if (node.generatedName != null) {
|
||||
/**
|
||||
* Note that we don't generate a name for functions that already had one,
|
||||
* so we'll only add the prefix to anonymous functions regardless of
|
||||
* nesting depth.
|
||||
*/
|
||||
const name = `${prefix}${node.generatedName}]`;
|
||||
node.fn.name = name;
|
||||
}
|
||||
/**
|
||||
* Whether or not we generated a name for the function at this node,
|
||||
* traverse into its nested functions to assign them names
|
||||
*/
|
||||
const nextPrefix = `${prefix}${node.generatedName ?? node.fn.name ?? '<anonymous>'} > `;
|
||||
for (const inner of node.inner) {
|
||||
visit(inner, nextPrefix);
|
||||
}
|
||||
}
|
||||
for (const node of functions) {
|
||||
visit(node, `${parentName}[`);
|
||||
}
|
||||
}
|
||||
|
||||
type Node = {
|
||||
fn: FunctionExpression;
|
||||
generatedName: string | null;
|
||||
inner: Array<Node>;
|
||||
};
|
||||
|
||||
function nameAnonymousFunctionsImpl(fn: HIRFunction): Array<Node> {
|
||||
// Functions that we track to generate names for
|
||||
const functions: Map<IdentifierId, Node> = new Map();
|
||||
// Tracks temporaries that read from variables/globals/properties
|
||||
const names: Map<IdentifierId, string> = new Map();
|
||||
// Tracks all function nodes to bubble up for later renaming
|
||||
const nodes: Array<Node> = [];
|
||||
for (const block of fn.body.blocks.values()) {
|
||||
for (const instr of block.instructions) {
|
||||
const {lvalue, value} = instr;
|
||||
switch (value.kind) {
|
||||
case 'LoadGlobal': {
|
||||
names.set(lvalue.identifier.id, value.binding.name);
|
||||
break;
|
||||
}
|
||||
case 'LoadContext':
|
||||
case 'LoadLocal': {
|
||||
const name = value.place.identifier.name;
|
||||
if (name != null && name.kind === 'named') {
|
||||
names.set(lvalue.identifier.id, name.value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'PropertyLoad': {
|
||||
const objectName = names.get(value.object.identifier.id);
|
||||
if (objectName != null) {
|
||||
names.set(
|
||||
lvalue.identifier.id,
|
||||
`${objectName}.${String(value.property)}`,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'FunctionExpression': {
|
||||
const inner = nameAnonymousFunctionsImpl(value.loweredFunc.func);
|
||||
const node: Node = {
|
||||
fn: value,
|
||||
generatedName: null,
|
||||
inner,
|
||||
};
|
||||
/**
|
||||
* Bubble-up all functions, even if they're named, so that we can
|
||||
* later generate names for any inner anonymous functions
|
||||
*/
|
||||
nodes.push(node);
|
||||
if (value.name == null) {
|
||||
// but only generate names for anonymous functions
|
||||
functions.set(lvalue.identifier.id, node);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'StoreContext':
|
||||
case 'StoreLocal': {
|
||||
const node = functions.get(value.value.identifier.id);
|
||||
const variableName = value.lvalue.place.identifier.name;
|
||||
if (
|
||||
node != null &&
|
||||
variableName != null &&
|
||||
variableName.kind === 'named'
|
||||
) {
|
||||
node.generatedName = variableName.value;
|
||||
functions.delete(value.value.identifier.id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'CallExpression':
|
||||
case 'MethodCall': {
|
||||
const callee =
|
||||
value.kind === 'MethodCall' ? value.property : value.callee;
|
||||
const hookKind = getHookKind(fn.env, callee.identifier);
|
||||
let calleeName: string | null = null;
|
||||
if (hookKind != null && hookKind !== 'Custom') {
|
||||
calleeName = hookKind;
|
||||
} else {
|
||||
calleeName = names.get(callee.identifier.id) ?? '(anonymous)';
|
||||
}
|
||||
let fnArgCount = 0;
|
||||
for (const arg of value.args) {
|
||||
if (arg.kind === 'Identifier' && functions.has(arg.identifier.id)) {
|
||||
fnArgCount++;
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < value.args.length; i++) {
|
||||
const arg = value.args[i]!;
|
||||
if (arg.kind === 'Spread') {
|
||||
continue;
|
||||
}
|
||||
const node = functions.get(arg.identifier.id);
|
||||
if (node != null) {
|
||||
const generatedName =
|
||||
fnArgCount > 1 ? `${calleeName}(arg${i})` : `${calleeName}()`;
|
||||
node.generatedName = generatedName;
|
||||
functions.delete(arg.identifier.id);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'JsxExpression': {
|
||||
for (const attr of value.props) {
|
||||
if (attr.kind === 'JsxSpreadAttribute') {
|
||||
continue;
|
||||
}
|
||||
const node = functions.get(attr.place.identifier.id);
|
||||
if (node != null) {
|
||||
const elementName =
|
||||
value.tag.kind === 'BuiltinTag'
|
||||
? value.tag.name
|
||||
: (names.get(value.tag.identifier.id) ?? null);
|
||||
const propName =
|
||||
elementName == null
|
||||
? attr.name
|
||||
: `<${elementName}>.${attr.name}`;
|
||||
node.generatedName = `${propName}`;
|
||||
functions.delete(attr.place.identifier.id);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
IdentifierId,
|
||||
InstructionValue,
|
||||
ManualMemoDependency,
|
||||
Place,
|
||||
PrunedReactiveScopeBlock,
|
||||
ReactiveFunction,
|
||||
ReactiveInstruction,
|
||||
@@ -28,10 +29,7 @@ import {
|
||||
SourceLocation,
|
||||
} from '../HIR';
|
||||
import {printIdentifier, printManualMemoDependency} from '../HIR/PrintHIR';
|
||||
import {
|
||||
eachInstructionValueLValue,
|
||||
eachInstructionValueOperand,
|
||||
} from '../HIR/visitors';
|
||||
import {eachInstructionValueOperand} from '../HIR/visitors';
|
||||
import {collectMaybeMemoDependencies} from '../Inference/DropManualMemoization';
|
||||
import {
|
||||
ReactiveFunctionVisitor,
|
||||
@@ -339,53 +337,56 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
|
||||
* @returns a @{ManualMemoDependency} representing the variable +
|
||||
* property reads represented by @value
|
||||
*/
|
||||
recordDepsInValue(value: ReactiveValue, state: VisitorState): void {
|
||||
recordDepsInValue(
|
||||
value: ReactiveValue,
|
||||
state: VisitorState,
|
||||
): ManualMemoDependency | null {
|
||||
switch (value.kind) {
|
||||
case 'SequenceExpression': {
|
||||
for (const instr of value.instructions) {
|
||||
this.visitInstruction(instr, state);
|
||||
}
|
||||
this.recordDepsInValue(value.value, state);
|
||||
break;
|
||||
const result = this.recordDepsInValue(value.value, state);
|
||||
return result;
|
||||
}
|
||||
case 'OptionalExpression': {
|
||||
this.recordDepsInValue(value.value, state);
|
||||
break;
|
||||
return this.recordDepsInValue(value.value, state);
|
||||
}
|
||||
case 'ConditionalExpression': {
|
||||
this.recordDepsInValue(value.test, state);
|
||||
this.recordDepsInValue(value.consequent, state);
|
||||
this.recordDepsInValue(value.alternate, state);
|
||||
break;
|
||||
return null;
|
||||
}
|
||||
case 'LogicalExpression': {
|
||||
this.recordDepsInValue(value.left, state);
|
||||
this.recordDepsInValue(value.right, state);
|
||||
break;
|
||||
return null;
|
||||
}
|
||||
default: {
|
||||
collectMaybeMemoDependencies(value, this.temporaries, false);
|
||||
if (
|
||||
value.kind === 'StoreLocal' ||
|
||||
value.kind === 'StoreContext' ||
|
||||
value.kind === 'Destructure'
|
||||
) {
|
||||
for (const storeTarget of eachInstructionValueLValue(value)) {
|
||||
state.manualMemoState?.decls.add(
|
||||
storeTarget.identifier.declarationId,
|
||||
);
|
||||
if (storeTarget.identifier.name?.kind === 'named') {
|
||||
this.temporaries.set(storeTarget.identifier.id, {
|
||||
root: {
|
||||
kind: 'NamedLocal',
|
||||
value: storeTarget,
|
||||
},
|
||||
path: [],
|
||||
});
|
||||
}
|
||||
const dep = collectMaybeMemoDependencies(
|
||||
value,
|
||||
this.temporaries,
|
||||
false,
|
||||
);
|
||||
if (value.kind === 'StoreLocal' || value.kind === 'StoreContext') {
|
||||
const storeTarget = value.lvalue.place;
|
||||
state.manualMemoState?.decls.add(
|
||||
storeTarget.identifier.declarationId,
|
||||
);
|
||||
if (storeTarget.identifier.name?.kind === 'named' && dep == null) {
|
||||
const dep: ManualMemoDependency = {
|
||||
root: {
|
||||
kind: 'NamedLocal',
|
||||
value: storeTarget,
|
||||
},
|
||||
path: [],
|
||||
};
|
||||
this.temporaries.set(storeTarget.identifier.id, dep);
|
||||
return dep;
|
||||
}
|
||||
}
|
||||
break;
|
||||
return dep;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -402,15 +403,19 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
|
||||
state.manualMemoState.decls.add(lvalue.identifier.declarationId);
|
||||
}
|
||||
|
||||
this.recordDepsInValue(value, state);
|
||||
if (lvalue != null) {
|
||||
temporaries.set(lvalue.identifier.id, {
|
||||
root: {
|
||||
kind: 'NamedLocal',
|
||||
value: {...lvalue},
|
||||
},
|
||||
path: [],
|
||||
});
|
||||
const maybeDep = this.recordDepsInValue(value, state);
|
||||
if (lvalId != null) {
|
||||
if (maybeDep != null) {
|
||||
temporaries.set(lvalId, maybeDep);
|
||||
} else if (isNamedLocal) {
|
||||
temporaries.set(lvalId, {
|
||||
root: {
|
||||
kind: 'NamedLocal',
|
||||
value: {...(instr.lvalue as Place)},
|
||||
},
|
||||
path: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
/**
|
||||
* Repro from https://github.com/facebook/react/issues/34262
|
||||
*
|
||||
* The compiler memoizes more precisely than the original code, with two reactive scopes:
|
||||
* - One for `transform(input)` with `input` as dep
|
||||
* - One for `{value}` with `value` as dep
|
||||
*
|
||||
* When we validate preserving manual memoization we incorrectly reject this, because
|
||||
* the original memoization had `object` depending on `input` but our scope depends on
|
||||
* `value`.
|
||||
*
|
||||
* This fixture adds a later potential mutation, which extends the scope and should
|
||||
* fail validation. This confirms that even though we allow the dependency to diverge,
|
||||
* we still check that the output value is memoized.
|
||||
*/
|
||||
function useInputValue(input) {
|
||||
const object = React.useMemo(() => {
|
||||
const {value} = transform(input);
|
||||
return {value};
|
||||
}, [input]);
|
||||
mutate(object);
|
||||
return object;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.
|
||||
|
||||
error.repro-preserve-memoization-inner-destructured-value-mistaken-as-dependency-later-mutation.ts:19:17
|
||||
17 | */
|
||||
18 | function useInputValue(input) {
|
||||
> 19 | const object = React.useMemo(() => {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
> 20 | const {value} = transform(input);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
> 21 | return {value};
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
> 22 | }, [input]);
|
||||
| ^^^^^^^^^^^^^^ Could not preserve existing memoization
|
||||
23 | mutate(object);
|
||||
24 | return object;
|
||||
25 | }
|
||||
```
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
/**
|
||||
* Repro from https://github.com/facebook/react/issues/34262
|
||||
*
|
||||
* The compiler memoizes more precisely than the original code, with two reactive scopes:
|
||||
* - One for `transform(input)` with `input` as dep
|
||||
* - One for `{value}` with `value` as dep
|
||||
*
|
||||
* When we validate preserving manual memoization we incorrectly reject this, because
|
||||
* the original memoization had `object` depending on `input` but our scope depends on
|
||||
* `value`.
|
||||
*
|
||||
* This fixture adds a later potential mutation, which extends the scope and should
|
||||
* fail validation. This confirms that even though we allow the dependency to diverge,
|
||||
* we still check that the output value is memoized.
|
||||
*/
|
||||
function useInputValue(input) {
|
||||
const object = React.useMemo(() => {
|
||||
const {value} = transform(input);
|
||||
return {value};
|
||||
}, [input]);
|
||||
mutate(object);
|
||||
return object;
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import {identity, Stringify, useHook} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Repro from https://github.com/facebook/react/issues/34262
|
||||
*
|
||||
* The compiler memoizes more precisely than the original code, with two reactive scopes:
|
||||
* - One for `transform(input)` with `input` as dep
|
||||
* - One for `{value}` with `value` as dep
|
||||
*
|
||||
* When we validate preserving manual memoization we incorrectly reject this, because
|
||||
* the original memoization had `object` depending on `input` but our scope depends on
|
||||
* `value`.
|
||||
*/
|
||||
function useInputValue(input) {
|
||||
// Conflate the `identity(input, x)` call with something outside the useMemo,
|
||||
// to try and break memoization of `value`. This gets correctly flagged since
|
||||
// the dependency is being mutated
|
||||
let x = {};
|
||||
useHook();
|
||||
const object = React.useMemo(() => {
|
||||
const {value} = identity(input, x);
|
||||
return {value};
|
||||
}, [input, x]);
|
||||
return object;
|
||||
}
|
||||
|
||||
function Component() {
|
||||
return <Stringify value={useInputValue({value: 42}).value} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This dependency may be mutated later, which could cause the value to change unexpectedly.
|
||||
|
||||
error.repro-preserve-memoization-inner-destructured-value-mistaken-as-dependency-mutated-dep.ts:25:13
|
||||
23 | const {value} = identity(input, x);
|
||||
24 | return {value};
|
||||
> 25 | }, [input, x]);
|
||||
| ^ This dependency may be modified later
|
||||
26 | return object;
|
||||
27 | }
|
||||
28 |
|
||||
```
|
||||
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import {identity, Stringify, useHook} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Repro from https://github.com/facebook/react/issues/34262
|
||||
*
|
||||
* The compiler memoizes more precisely than the original code, with two reactive scopes:
|
||||
* - One for `transform(input)` with `input` as dep
|
||||
* - One for `{value}` with `value` as dep
|
||||
*
|
||||
* When we validate preserving manual memoization we incorrectly reject this, because
|
||||
* the original memoization had `object` depending on `input` but our scope depends on
|
||||
* `value`.
|
||||
*/
|
||||
function useInputValue(input) {
|
||||
// Conflate the `identity(input, x)` call with something outside the useMemo,
|
||||
// to try and break memoization of `value`. This gets correctly flagged since
|
||||
// the dependency is being mutated
|
||||
let x = {};
|
||||
useHook();
|
||||
const object = React.useMemo(() => {
|
||||
const {value} = identity(input, x);
|
||||
return {value};
|
||||
}, [input, x]);
|
||||
return object;
|
||||
}
|
||||
|
||||
function Component() {
|
||||
return <Stringify value={useInputValue({value: 42}).value} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{}],
|
||||
};
|
||||
@@ -0,0 +1,272 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableNameAnonymousFunctions
|
||||
|
||||
import {useEffect} from 'react';
|
||||
import {identity, Stringify, useIdentity} from 'shared-runtime';
|
||||
import * as SharedRuntime from 'shared-runtime';
|
||||
|
||||
function Component(props) {
|
||||
function named() {
|
||||
const inner = () => props.named;
|
||||
return inner();
|
||||
}
|
||||
const namedVariable = function () {
|
||||
return props.namedVariable;
|
||||
};
|
||||
const methodCall = SharedRuntime.identity(() => props.methodCall);
|
||||
const call = identity(() => props.call);
|
||||
const builtinElementAttr = <div onClick={() => props.builtinElementAttr} />;
|
||||
const namedElementAttr = <Stringify onClick={() => props.namedElementAttr} />;
|
||||
const hookArgument = useIdentity(() => props.hookArgument);
|
||||
useEffect(() => {
|
||||
console.log(props.useEffect);
|
||||
JSON.stringify(null, null, () => props.useEffect);
|
||||
const g = () => props.useEffect;
|
||||
console.log(g());
|
||||
}, [props.useEffect]);
|
||||
return (
|
||||
<>
|
||||
{named()}
|
||||
{namedVariable()}
|
||||
{methodCall()}
|
||||
{call()}
|
||||
{builtinElementAttr}
|
||||
{namedElementAttr}
|
||||
{hookArgument()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const TODO_FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [
|
||||
{
|
||||
named: '<named>',
|
||||
namedVariable: '<namedVariable>',
|
||||
methodCall: '<methodCall>',
|
||||
call: '<call>',
|
||||
builtinElementAttr: '<builtinElementAttr>',
|
||||
namedElementAttr: '<namedElementAttr>',
|
||||
hookArgument: '<hookArgument>',
|
||||
useEffect: '<useEffect>',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enableNameAnonymousFunctions
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { identity, Stringify, useIdentity } from "shared-runtime";
|
||||
import * as SharedRuntime from "shared-runtime";
|
||||
|
||||
function Component(props) {
|
||||
const $ = _c(31);
|
||||
let t0;
|
||||
if ($[0] !== props.named) {
|
||||
t0 = function named() {
|
||||
const inner = { "Component[named > inner]": () => props.named }[
|
||||
"Component[named > inner]"
|
||||
];
|
||||
return inner();
|
||||
};
|
||||
$[0] = props.named;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
const named = t0;
|
||||
let t1;
|
||||
if ($[2] !== props.namedVariable) {
|
||||
t1 = {
|
||||
"Component[namedVariable]": function () {
|
||||
return props.namedVariable;
|
||||
},
|
||||
}["Component[namedVariable]"];
|
||||
$[2] = props.namedVariable;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
}
|
||||
const namedVariable = t1;
|
||||
let t2;
|
||||
if ($[4] !== props.methodCall) {
|
||||
t2 = { "Component[SharedRuntime.identity()]": () => props.methodCall }[
|
||||
"Component[SharedRuntime.identity()]"
|
||||
];
|
||||
$[4] = props.methodCall;
|
||||
$[5] = t2;
|
||||
} else {
|
||||
t2 = $[5];
|
||||
}
|
||||
const methodCall = SharedRuntime.identity(t2);
|
||||
let t3;
|
||||
if ($[6] !== props.call) {
|
||||
t3 = { "Component[identity()]": () => props.call }["Component[identity()]"];
|
||||
$[6] = props.call;
|
||||
$[7] = t3;
|
||||
} else {
|
||||
t3 = $[7];
|
||||
}
|
||||
const call = identity(t3);
|
||||
let t4;
|
||||
if ($[8] !== props.builtinElementAttr) {
|
||||
t4 = (
|
||||
<div
|
||||
onClick={
|
||||
{ "Component[<div>.onClick]": () => props.builtinElementAttr }[
|
||||
"Component[<div>.onClick]"
|
||||
]
|
||||
}
|
||||
/>
|
||||
);
|
||||
$[8] = props.builtinElementAttr;
|
||||
$[9] = t4;
|
||||
} else {
|
||||
t4 = $[9];
|
||||
}
|
||||
const builtinElementAttr = t4;
|
||||
let t5;
|
||||
if ($[10] !== props.namedElementAttr) {
|
||||
t5 = (
|
||||
<Stringify
|
||||
onClick={
|
||||
{ "Component[<Stringify>.onClick]": () => props.namedElementAttr }[
|
||||
"Component[<Stringify>.onClick]"
|
||||
]
|
||||
}
|
||||
/>
|
||||
);
|
||||
$[10] = props.namedElementAttr;
|
||||
$[11] = t5;
|
||||
} else {
|
||||
t5 = $[11];
|
||||
}
|
||||
const namedElementAttr = t5;
|
||||
let t6;
|
||||
if ($[12] !== props.hookArgument) {
|
||||
t6 = { "Component[useIdentity()]": () => props.hookArgument }[
|
||||
"Component[useIdentity()]"
|
||||
];
|
||||
$[12] = props.hookArgument;
|
||||
$[13] = t6;
|
||||
} else {
|
||||
t6 = $[13];
|
||||
}
|
||||
const hookArgument = useIdentity(t6);
|
||||
let t7;
|
||||
let t8;
|
||||
if ($[14] !== props.useEffect) {
|
||||
t7 = {
|
||||
"Component[useEffect()]": () => {
|
||||
console.log(props.useEffect);
|
||||
JSON.stringify(
|
||||
null,
|
||||
null,
|
||||
{
|
||||
"Component[useEffect() > JSON.stringify()]": () => props.useEffect,
|
||||
}["Component[useEffect() > JSON.stringify()]"],
|
||||
);
|
||||
const g = { "Component[useEffect() > g]": () => props.useEffect }[
|
||||
"Component[useEffect() > g]"
|
||||
];
|
||||
console.log(g());
|
||||
},
|
||||
}["Component[useEffect()]"];
|
||||
t8 = [props.useEffect];
|
||||
$[14] = props.useEffect;
|
||||
$[15] = t7;
|
||||
$[16] = t8;
|
||||
} else {
|
||||
t7 = $[15];
|
||||
t8 = $[16];
|
||||
}
|
||||
useEffect(t7, t8);
|
||||
let t9;
|
||||
if ($[17] !== named) {
|
||||
t9 = named();
|
||||
$[17] = named;
|
||||
$[18] = t9;
|
||||
} else {
|
||||
t9 = $[18];
|
||||
}
|
||||
let t10;
|
||||
if ($[19] !== namedVariable) {
|
||||
t10 = namedVariable();
|
||||
$[19] = namedVariable;
|
||||
$[20] = t10;
|
||||
} else {
|
||||
t10 = $[20];
|
||||
}
|
||||
const t11 = methodCall();
|
||||
const t12 = call();
|
||||
let t13;
|
||||
if ($[21] !== hookArgument) {
|
||||
t13 = hookArgument();
|
||||
$[21] = hookArgument;
|
||||
$[22] = t13;
|
||||
} else {
|
||||
t13 = $[22];
|
||||
}
|
||||
let t14;
|
||||
if (
|
||||
$[23] !== builtinElementAttr ||
|
||||
$[24] !== namedElementAttr ||
|
||||
$[25] !== t10 ||
|
||||
$[26] !== t11 ||
|
||||
$[27] !== t12 ||
|
||||
$[28] !== t13 ||
|
||||
$[29] !== t9
|
||||
) {
|
||||
t14 = (
|
||||
<>
|
||||
{t9}
|
||||
{t10}
|
||||
{t11}
|
||||
{t12}
|
||||
{builtinElementAttr}
|
||||
{namedElementAttr}
|
||||
{t13}
|
||||
</>
|
||||
);
|
||||
$[23] = builtinElementAttr;
|
||||
$[24] = namedElementAttr;
|
||||
$[25] = t10;
|
||||
$[26] = t11;
|
||||
$[27] = t12;
|
||||
$[28] = t13;
|
||||
$[29] = t9;
|
||||
$[30] = t14;
|
||||
} else {
|
||||
t14 = $[30];
|
||||
}
|
||||
return t14;
|
||||
}
|
||||
|
||||
export const TODO_FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [
|
||||
{
|
||||
named: "<named>",
|
||||
namedVariable: "<namedVariable>",
|
||||
methodCall: "<methodCall>",
|
||||
call: "<call>",
|
||||
builtinElementAttr: "<builtinElementAttr>",
|
||||
namedElementAttr: "<namedElementAttr>",
|
||||
hookArgument: "<hookArgument>",
|
||||
useEffect: "<useEffect>",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
@@ -0,0 +1,53 @@
|
||||
// @enableNameAnonymousFunctions
|
||||
|
||||
import {useEffect} from 'react';
|
||||
import {identity, Stringify, useIdentity} from 'shared-runtime';
|
||||
import * as SharedRuntime from 'shared-runtime';
|
||||
|
||||
function Component(props) {
|
||||
function named() {
|
||||
const inner = () => props.named;
|
||||
return inner();
|
||||
}
|
||||
const namedVariable = function () {
|
||||
return props.namedVariable;
|
||||
};
|
||||
const methodCall = SharedRuntime.identity(() => props.methodCall);
|
||||
const call = identity(() => props.call);
|
||||
const builtinElementAttr = <div onClick={() => props.builtinElementAttr} />;
|
||||
const namedElementAttr = <Stringify onClick={() => props.namedElementAttr} />;
|
||||
const hookArgument = useIdentity(() => props.hookArgument);
|
||||
useEffect(() => {
|
||||
console.log(props.useEffect);
|
||||
JSON.stringify(null, null, () => props.useEffect);
|
||||
const g = () => props.useEffect;
|
||||
console.log(g());
|
||||
}, [props.useEffect]);
|
||||
return (
|
||||
<>
|
||||
{named()}
|
||||
{namedVariable()}
|
||||
{methodCall()}
|
||||
{call()}
|
||||
{builtinElementAttr}
|
||||
{namedElementAttr}
|
||||
{hookArgument()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const TODO_FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [
|
||||
{
|
||||
named: '<named>',
|
||||
namedVariable: '<namedVariable>',
|
||||
methodCall: '<methodCall>',
|
||||
call: '<call>',
|
||||
builtinElementAttr: '<builtinElementAttr>',
|
||||
namedElementAttr: '<namedElementAttr>',
|
||||
hookArgument: '<hookArgument>',
|
||||
useEffect: '<useEffect>',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,82 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @compilationMode:"infer"
|
||||
function Component() {
|
||||
const dispatch = useDispatch();
|
||||
// const [state, setState] = useState(0);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
type="file"
|
||||
onChange={event => {
|
||||
dispatch(...event.target);
|
||||
event.target.value = '';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function useDispatch() {
|
||||
'use no memo';
|
||||
// skip compilation to make it easier to debug the above function
|
||||
return (...values) => {
|
||||
console.log(...values);
|
||||
};
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer"
|
||||
function Component() {
|
||||
const $ = _c(2);
|
||||
const dispatch = useDispatch();
|
||||
let t0;
|
||||
if ($[0] !== dispatch) {
|
||||
t0 = (
|
||||
<div>
|
||||
<input
|
||||
type="file"
|
||||
onChange={(event) => {
|
||||
dispatch(...event.target);
|
||||
event.target.value = "";
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
$[0] = dispatch;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
function useDispatch() {
|
||||
"use no memo";
|
||||
// skip compilation to make it easier to debug the above function
|
||||
return (...values) => {
|
||||
console.log(...values);
|
||||
};
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div><input type="file"></div>
|
||||
@@ -0,0 +1,30 @@
|
||||
// @compilationMode:"infer"
|
||||
function Component() {
|
||||
const dispatch = useDispatch();
|
||||
// const [state, setState] = useState(0);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
type="file"
|
||||
onChange={event => {
|
||||
dispatch(...event.target);
|
||||
event.target.value = '';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function useDispatch() {
|
||||
'use no memo';
|
||||
// skip compilation to make it easier to debug the above function
|
||||
return (...values) => {
|
||||
console.log(...values);
|
||||
};
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{}],
|
||||
};
|
||||
@@ -1,109 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import {identity, Stringify} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Repro from https://github.com/facebook/react/issues/34262
|
||||
*
|
||||
* The compiler memoizes more precisely than the original code, with two reactive scopes:
|
||||
* - One for `transform(input)` with `input` as dep
|
||||
* - One for `{value}` with `value` as dep
|
||||
*
|
||||
* Previously ValidatePreservedManualMemoization rejected this input, because
|
||||
* the original memoization had `object` depending on `input` but we split the scope per above,
|
||||
* and the scope for the FinishMemoize instruction is the second scope which depends on `value`
|
||||
*/
|
||||
function useInputValue(input) {
|
||||
const object = React.useMemo(() => {
|
||||
const {value} = identity(input);
|
||||
return {value};
|
||||
}, [input]);
|
||||
return object;
|
||||
}
|
||||
|
||||
function Component() {
|
||||
return <Stringify value={useInputValue({value: 42}).value} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { identity, Stringify } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Repro from https://github.com/facebook/react/issues/34262
|
||||
*
|
||||
* The compiler memoizes more precisely than the original code, with two reactive scopes:
|
||||
* - One for `transform(input)` with `input` as dep
|
||||
* - One for `{value}` with `value` as dep
|
||||
*
|
||||
* Previously ValidatePreservedManualMemoization rejected this input, because
|
||||
* the original memoization had `object` depending on `input` but we split the scope per above,
|
||||
* and the scope for the FinishMemoize instruction is the second scope which depends on `value`
|
||||
*/
|
||||
function useInputValue(input) {
|
||||
const $ = _c(4);
|
||||
let t0;
|
||||
if ($[0] !== input) {
|
||||
t0 = identity(input);
|
||||
$[0] = input;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
const { value } = t0;
|
||||
let t1;
|
||||
if ($[2] !== value) {
|
||||
t1 = { value };
|
||||
$[2] = value;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
}
|
||||
const object = t1;
|
||||
return object;
|
||||
}
|
||||
|
||||
function Component() {
|
||||
const $ = _c(3);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = { value: 42 };
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
const t1 = useInputValue(t0);
|
||||
let t2;
|
||||
if ($[1] !== t1.value) {
|
||||
t2 = <Stringify value={t1.value} />;
|
||||
$[1] = t1.value;
|
||||
$[2] = t2;
|
||||
} else {
|
||||
t2 = $[2];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"value":42}</div>
|
||||
@@ -1,31 +0,0 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import {identity, Stringify} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Repro from https://github.com/facebook/react/issues/34262
|
||||
*
|
||||
* The compiler memoizes more precisely than the original code, with two reactive scopes:
|
||||
* - One for `transform(input)` with `input` as dep
|
||||
* - One for `{value}` with `value` as dep
|
||||
*
|
||||
* Previously ValidatePreservedManualMemoization rejected this input, because
|
||||
* the original memoization had `object` depending on `input` but we split the scope per above,
|
||||
* and the scope for the FinishMemoize instruction is the second scope which depends on `value`
|
||||
*/
|
||||
function useInputValue(input) {
|
||||
const object = React.useMemo(() => {
|
||||
const {value} = identity(input);
|
||||
return {value};
|
||||
}, [input]);
|
||||
return object;
|
||||
}
|
||||
|
||||
function Component() {
|
||||
return <Stringify value={useInputValue({value: 42}).value} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{}],
|
||||
};
|
||||
@@ -5,13 +5,19 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
export type MessagePayload = null | string | number | boolean | { [key: string]: MessagePayload } | MessagePayload[];
|
||||
export type Message = { event: string, payload?: MessagePayload };
|
||||
export type MessagePayload =
|
||||
| null
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| {[key: string]: MessagePayload}
|
||||
| MessagePayload[];
|
||||
export type Message = {event: string; payload?: MessagePayload};
|
||||
|
||||
export type WallListener = (message: Message) => void;
|
||||
export type Wall = {
|
||||
listen: (fn: WallListener) => Function,
|
||||
send: (event: string, payload?: MessagePayload) => void,
|
||||
listen: (fn: WallListener) => Function;
|
||||
send: (event: string, payload?: MessagePayload) => void;
|
||||
};
|
||||
|
||||
export type Bridge = {
|
||||
@@ -22,7 +28,7 @@ export type Bridge = {
|
||||
export type Store = Object;
|
||||
export type BrowserTheme = 'dark' | 'light';
|
||||
export type Config = {
|
||||
supportsReloadAndProfile?: boolean,
|
||||
supportsReloadAndProfile?: boolean;
|
||||
};
|
||||
|
||||
export function createBridge(wall: Wall): Bridge;
|
||||
@@ -55,15 +61,23 @@ export type CanViewElementSource = (
|
||||
source: ReactFunctionLocation | ReactCallSite,
|
||||
symbolicatedSource: ReactFunctionLocation | ReactCallSite | null,
|
||||
) => boolean;
|
||||
export type FetchFileWithCaching = (url: string) => Promise<string>;
|
||||
|
||||
export type InitializationOptions = {
|
||||
bridge: Bridge,
|
||||
store: Store,
|
||||
theme?: BrowserTheme,
|
||||
viewAttributeSourceFunction?: ViewAttributeSource,
|
||||
viewElementSourceFunction?: ViewElementSource,
|
||||
canViewElementSourceFunction?: CanViewElementSource,
|
||||
bridge: Bridge;
|
||||
store: Store;
|
||||
theme?: BrowserTheme;
|
||||
viewAttributeSourceFunction?: ViewAttributeSource;
|
||||
viewElementSourceFunction?: ViewElementSource;
|
||||
canViewElementSourceFunction?: CanViewElementSource;
|
||||
fetchFileWithCaching?: FetchFileWithCaching;
|
||||
};
|
||||
|
||||
export function initializeComponents(node: Element | Document, options: InitializationOptions): void;
|
||||
export function initializeProfiler(node: Element | Document, options: InitializationOptions): void;
|
||||
export function initializeComponents(
|
||||
node: Element | Document,
|
||||
options: InitializationOptions,
|
||||
): void;
|
||||
export function initializeProfiler(
|
||||
node: Element | Document,
|
||||
options: InitializationOptions,
|
||||
): void;
|
||||
|
||||
@@ -24,6 +24,7 @@ import type {
|
||||
ViewAttributeSource,
|
||||
ViewElementSource,
|
||||
} from 'react-devtools-shared/src/devtools/views/DevTools';
|
||||
import type {FetchFileWithCaching} from 'react-devtools-shared/src/devtools/views/Components/FetchFileWithCachingContext';
|
||||
import type {Config} from 'react-devtools-shared/src/devtools/store';
|
||||
|
||||
export function createBridge(wall?: Wall): FrontendBridge {
|
||||
@@ -50,6 +51,7 @@ type InitializationOptions = {
|
||||
viewAttributeSourceFunction?: ViewAttributeSource,
|
||||
viewElementSourceFunction?: ViewElementSource,
|
||||
canViewElementSourceFunction?: CanViewElementSource,
|
||||
fetchFileWithCaching?: FetchFileWithCaching,
|
||||
};
|
||||
|
||||
function initializeTab(
|
||||
@@ -64,6 +66,7 @@ function initializeTab(
|
||||
viewAttributeSourceFunction,
|
||||
viewElementSourceFunction,
|
||||
canViewElementSourceFunction,
|
||||
fetchFileWithCaching,
|
||||
} = options;
|
||||
const root = createRoot(contentWindow);
|
||||
|
||||
@@ -79,6 +82,7 @@ function initializeTab(
|
||||
viewAttributeSourceFunction={viewAttributeSourceFunction}
|
||||
viewElementSourceFunction={viewElementSourceFunction}
|
||||
canViewElementSourceFunction={canViewElementSourceFunction}
|
||||
fetchFileWithCaching={fetchFileWithCaching}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -228,8 +228,8 @@ describe('commit tree', () => {
|
||||
[root]
|
||||
▾ <App>
|
||||
<Suspense>
|
||||
[shell]
|
||||
<Suspense name="App>?" rects={null}>
|
||||
[suspense-root] rects={null}
|
||||
<Suspense name="App" rects={null}>
|
||||
`);
|
||||
utils.act(() => modernRender(<App renderChildren={true} />));
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
@@ -237,8 +237,8 @@ describe('commit tree', () => {
|
||||
▾ <App>
|
||||
▾ <Suspense>
|
||||
<LazyInnerComponent>
|
||||
[shell]
|
||||
<Suspense name="App>?" rects={null}>
|
||||
[suspense-root] rects={null}
|
||||
<Suspense name="App" rects={null}>
|
||||
`);
|
||||
utils.act(() => modernRender(<App renderChildren={false} />));
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
@@ -303,8 +303,8 @@ describe('commit tree', () => {
|
||||
[root]
|
||||
▾ <App>
|
||||
<Suspense>
|
||||
[shell]
|
||||
<Suspense name="App>?" rects={null}>
|
||||
[suspense-root] rects={null}
|
||||
<Suspense name="App" rects={null}>
|
||||
`);
|
||||
utils.act(() => modernRender(<App renderChildren={false} />));
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
|
||||
@@ -133,7 +133,7 @@ describe('Store', () => {
|
||||
<Suspense>
|
||||
▾ <Parent>
|
||||
<Child>
|
||||
[shell]
|
||||
[suspense-root] rects={null}
|
||||
<Suspense name="Unknown" rects={null}>
|
||||
`);
|
||||
});
|
||||
@@ -492,8 +492,8 @@ describe('Store', () => {
|
||||
<Component key="Outside">
|
||||
▾ <Suspense>
|
||||
<Loading>
|
||||
[shell]
|
||||
<Suspense name="Wrapper>?" rects={null}>
|
||||
[suspense-root] rects={[{x:1,y:2,width:5,height:1}, {x:1,y:2,width:10,height:1}]}
|
||||
<Suspense name="Wrapper" rects={null}>
|
||||
`);
|
||||
|
||||
await act(() => {
|
||||
@@ -505,8 +505,8 @@ describe('Store', () => {
|
||||
<Component key="Outside">
|
||||
▾ <Suspense>
|
||||
<Component key="Inside">
|
||||
[shell]
|
||||
<Suspense name="Wrapper>?" rects={[{x:1,y:2,width:5,height:1}]}>
|
||||
[suspense-root] rects={[{x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}]}
|
||||
<Suspense name="Wrapper" rects={[{x:1,y:2,width:5,height:1}]}>
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -584,7 +584,7 @@ describe('Store', () => {
|
||||
▾ <Suspense name="three">
|
||||
<Loading key="Suspense 3 Fallback">
|
||||
<Component key="Unrelated at End">
|
||||
[shell]
|
||||
[suspense-root] rects={[{x:1,y:2,width:10,height:1}]}
|
||||
<Suspense name="parent" rects={[{x:1,y:2,width:10,height:1}]}>
|
||||
<Suspense name="one" rects={null}>
|
||||
<Suspense name="two" rects={null}>
|
||||
@@ -612,7 +612,7 @@ describe('Store', () => {
|
||||
▾ <Suspense name="three">
|
||||
<Loading key="Suspense 3 Fallback">
|
||||
<Component key="Unrelated at End">
|
||||
[shell]
|
||||
[suspense-root] rects={[{x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}]}
|
||||
<Suspense name="parent" rects={[{x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}]}>
|
||||
<Suspense name="one" rects={null}>
|
||||
<Suspense name="two" rects={null}>
|
||||
@@ -640,7 +640,7 @@ describe('Store', () => {
|
||||
▾ <Suspense name="three">
|
||||
<Loading key="Suspense 3 Fallback">
|
||||
<Component key="Unrelated at End">
|
||||
[shell]
|
||||
[suspense-root] rects={[{x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}]}
|
||||
<Suspense name="parent" rects={[{x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}]}>
|
||||
<Suspense name="one" rects={null}>
|
||||
<Suspense name="two" rects={null}>
|
||||
@@ -668,7 +668,7 @@ describe('Store', () => {
|
||||
▾ <Suspense name="three">
|
||||
<Loading key="Suspense 3 Fallback">
|
||||
<Component key="Unrelated at End">
|
||||
[shell]
|
||||
[suspense-root] rects={[{x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}]}
|
||||
<Suspense name="parent" rects={[{x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}]}>
|
||||
<Suspense name="one" rects={null}>
|
||||
<Suspense name="two" rects={null}>
|
||||
@@ -689,7 +689,7 @@ describe('Store', () => {
|
||||
<Component key="Outside">
|
||||
▾ <Suspense name="parent">
|
||||
<Loading key="Parent Fallback">
|
||||
[shell]
|
||||
[suspense-root] rects={[{x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}]}
|
||||
<Suspense name="parent" rects={[{x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}]}>
|
||||
<Suspense name="one" rects={null}>
|
||||
<Suspense name="two" rects={null}>
|
||||
@@ -717,7 +717,7 @@ describe('Store', () => {
|
||||
▾ <Suspense name="three">
|
||||
<Loading key="Suspense 3 Fallback">
|
||||
<Component key="Unrelated at End">
|
||||
[shell]
|
||||
[suspense-root] rects={[{x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}]}
|
||||
<Suspense name="parent" rects={[{x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}]}>
|
||||
<Suspense name="one" rects={null}>
|
||||
<Suspense name="two" rects={null}>
|
||||
@@ -745,7 +745,7 @@ describe('Store', () => {
|
||||
▾ <Suspense name="three">
|
||||
<Loading key="Suspense 3 Fallback">
|
||||
<Component key="Unrelated at End">
|
||||
[shell]
|
||||
[suspense-root] rects={[{x:1,y:2,width:10,height:1}]}
|
||||
<Suspense name="parent" rects={[{x:1,y:2,width:10,height:1}]}>
|
||||
<Suspense name="one" rects={null}>
|
||||
<Suspense name="two" rects={null}>
|
||||
@@ -773,7 +773,7 @@ describe('Store', () => {
|
||||
▾ <Suspense name="three">
|
||||
<Loading key="Suspense 3 Fallback">
|
||||
<Component key="Unrelated at End">
|
||||
[shell]
|
||||
[suspense-root] rects={[{x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}]}
|
||||
<Suspense name="parent" rects={[{x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}]}>
|
||||
<Suspense name="one" rects={null}>
|
||||
<Suspense name="two" rects={null}>
|
||||
@@ -792,7 +792,7 @@ describe('Store', () => {
|
||||
<Component key="Outside">
|
||||
▾ <Suspense name="parent">
|
||||
<Loading key="Parent Fallback">
|
||||
[shell]
|
||||
[suspense-root] rects={[{x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}]}
|
||||
<Suspense name="parent" rects={[{x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}]}>
|
||||
<Suspense name="one" rects={null}>
|
||||
<Suspense name="two" rects={null}>
|
||||
@@ -813,7 +813,7 @@ describe('Store', () => {
|
||||
<Component key="Outside">
|
||||
▾ <Suspense name="parent">
|
||||
<Loading key="Parent Fallback">
|
||||
[shell]
|
||||
[suspense-root] rects={[{x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}]}
|
||||
<Suspense name="parent" rects={[{x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}]}>
|
||||
<Suspense name="one" rects={null}>
|
||||
<Suspense name="two" rects={null}>
|
||||
@@ -839,7 +839,7 @@ describe('Store', () => {
|
||||
▾ <Suspense name="three">
|
||||
<Loading key="Suspense 3 Fallback">
|
||||
<Component key="Unrelated at End">
|
||||
[shell]
|
||||
[suspense-root] rects={[{x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}]}
|
||||
<Suspense name="parent" rects={[{x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}]}>
|
||||
<Suspense name="one" rects={null}>
|
||||
<Suspense name="two" rects={null}>
|
||||
@@ -865,7 +865,7 @@ describe('Store', () => {
|
||||
▾ <Suspense name="three">
|
||||
<Loading key="Suspense 3 Fallback">
|
||||
<Component key="Unrelated at End">
|
||||
[shell]
|
||||
[suspense-root] rects={[{x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}]}
|
||||
<Suspense name="parent" rects={[{x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}]}>
|
||||
<Suspense name="one" rects={null}>
|
||||
<Suspense name="two" rects={null}>
|
||||
@@ -893,7 +893,7 @@ describe('Store', () => {
|
||||
▾ <Suspense name="three">
|
||||
<Loading key="Suspense 3 Fallback">
|
||||
<Component key="Unrelated at End">
|
||||
[shell]
|
||||
[suspense-root] rects={[{x:1,y:2,width:10,height:1}]}
|
||||
<Suspense name="parent" rects={[{x:1,y:2,width:10,height:1}]}>
|
||||
<Suspense name="one" rects={null}>
|
||||
<Suspense name="two" rects={null}>
|
||||
@@ -948,7 +948,7 @@ describe('Store', () => {
|
||||
▾ <Suspense name="three">
|
||||
<Component key="Suspense 3 Content">
|
||||
<Component key="Unrelated at End">
|
||||
[shell]
|
||||
[suspense-root] rects={[{x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}]}
|
||||
<Suspense name="parent" rects={[{x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}]}>
|
||||
<Suspense name="one" rects={[{x:1,y:2,width:5,height:1}]}>
|
||||
<Suspense name="two" rects={[{x:1,y:2,width:5,height:1}]}>
|
||||
@@ -981,7 +981,7 @@ describe('Store', () => {
|
||||
▾ <Suspense name="three">
|
||||
<Component key="Suspense 3 Fallback">
|
||||
<Component key="Unrelated at End">
|
||||
[shell]
|
||||
[suspense-root] rects={[{x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}]}
|
||||
<Suspense name="parent" rects={[{x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}]}>
|
||||
<Suspense name="one" rects={[{x:1,y:2,width:5,height:1}]}>
|
||||
<Suspense name="two" rects={[{x:1,y:2,width:5,height:1}]}>
|
||||
@@ -1009,7 +1009,7 @@ describe('Store', () => {
|
||||
▾ <Suspense name="three">
|
||||
<Component key="Suspense 3 Content">
|
||||
<Component key="Unrelated at End">
|
||||
[shell]
|
||||
[suspense-root] rects={[{x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}]}
|
||||
<Suspense name="parent" rects={[{x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}]}>
|
||||
<Suspense name="one" rects={[{x:1,y:2,width:5,height:1}]}>
|
||||
<Suspense name="two" rects={[{x:1,y:2,width:5,height:1}]}>
|
||||
@@ -1053,8 +1053,8 @@ describe('Store', () => {
|
||||
<Component key="A">
|
||||
▾ <Suspense>
|
||||
<Loading>
|
||||
[shell]
|
||||
<Suspense name="Wrapper>?" rects={null}>
|
||||
[suspense-root] rects={[{x:1,y:2,width:5,height:1}, {x:1,y:2,width:10,height:1}]}
|
||||
<Suspense name="Wrapper" rects={null}>
|
||||
`);
|
||||
|
||||
await act(() => {
|
||||
@@ -1068,8 +1068,8 @@ describe('Store', () => {
|
||||
▾ <Suspense>
|
||||
<Component key="B">
|
||||
<Component key="C">
|
||||
[shell]
|
||||
<Suspense name="Wrapper>?" rects={[{x:1,y:2,width:5,height:1}]}>
|
||||
[suspense-root] rects={[{x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}]}
|
||||
<Suspense name="Wrapper" rects={[{x:1,y:2,width:5,height:1}]}>
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -1406,8 +1406,8 @@ describe('Store', () => {
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
[root]
|
||||
▸ <Wrapper>
|
||||
[shell]
|
||||
<Suspense name="Wrapper>?" rects={null}>
|
||||
[suspense-root] rects={[{x:1,y:2,width:5,height:1}, {x:1,y:2,width:10,height:1}]}
|
||||
<Suspense name="Wrapper" rects={null}>
|
||||
`);
|
||||
|
||||
// This test isn't meaningful unless we expand the suspended tree
|
||||
@@ -1423,8 +1423,8 @@ describe('Store', () => {
|
||||
<Component key="Outside">
|
||||
▾ <Suspense>
|
||||
<Loading>
|
||||
[shell]
|
||||
<Suspense name="Wrapper>?" rects={null}>
|
||||
[suspense-root] rects={[{x:1,y:2,width:5,height:1}, {x:1,y:2,width:10,height:1}]}
|
||||
<Suspense name="Wrapper" rects={null}>
|
||||
`);
|
||||
|
||||
await act(() => {
|
||||
@@ -1436,8 +1436,8 @@ describe('Store', () => {
|
||||
<Component key="Outside">
|
||||
▾ <Suspense>
|
||||
<Component key="Inside">
|
||||
[shell]
|
||||
<Suspense name="Wrapper>?" rects={[{x:1,y:2,width:5,height:1}]}>
|
||||
[suspense-root] rects={[{x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}]}
|
||||
<Suspense name="Wrapper" rects={[{x:1,y:2,width:5,height:1}]}>
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -1662,8 +1662,8 @@ describe('Store', () => {
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
[root]
|
||||
▸ <SuspenseTree>
|
||||
[shell]
|
||||
<Suspense name="SuspenseTree>?" rects={null}>
|
||||
[suspense-root] rects={null}
|
||||
<Suspense name="SuspenseTree" rects={null}>
|
||||
`);
|
||||
|
||||
await act(() =>
|
||||
@@ -1677,8 +1677,8 @@ describe('Store', () => {
|
||||
▾ <SuspenseTree>
|
||||
▾ <Suspense>
|
||||
▸ <Parent>
|
||||
[shell]
|
||||
<Suspense name="SuspenseTree>?" rects={null}>
|
||||
[suspense-root] rects={null}
|
||||
<Suspense name="SuspenseTree" rects={null}>
|
||||
`);
|
||||
|
||||
const rendererID = getRendererID();
|
||||
@@ -1696,8 +1696,8 @@ describe('Store', () => {
|
||||
▾ <SuspenseTree>
|
||||
▾ <Suspense>
|
||||
<Fallback>
|
||||
[shell]
|
||||
<Suspense name="SuspenseTree>?" rects={null}>
|
||||
[suspense-root] rects={null}
|
||||
<Suspense name="SuspenseTree" rects={null}>
|
||||
`);
|
||||
|
||||
await act(() =>
|
||||
@@ -1712,8 +1712,8 @@ describe('Store', () => {
|
||||
▾ <SuspenseTree>
|
||||
▾ <Suspense>
|
||||
▸ <Parent>
|
||||
[shell]
|
||||
<Suspense name="SuspenseTree>?" rects={null}>
|
||||
[suspense-root] rects={null}
|
||||
<Suspense name="SuspenseTree" rects={null}>
|
||||
`);
|
||||
});
|
||||
});
|
||||
@@ -2017,8 +2017,8 @@ describe('Store', () => {
|
||||
[root]
|
||||
▾ <App>
|
||||
<Suspense>
|
||||
[shell]
|
||||
<Suspense name="App>?" rects={null}>
|
||||
[suspense-root] rects={null}
|
||||
<Suspense name="App" rects={null}>
|
||||
`);
|
||||
|
||||
await Promise.resolve();
|
||||
@@ -2031,8 +2031,8 @@ describe('Store', () => {
|
||||
▾ <App>
|
||||
▾ <Suspense>
|
||||
<LazyInnerComponent>
|
||||
[shell]
|
||||
<Suspense name="App>?" rects={null}>
|
||||
[suspense-root] rects={null}
|
||||
<Suspense name="App" rects={null}>
|
||||
`);
|
||||
|
||||
// Render again to unmount it
|
||||
@@ -2522,8 +2522,8 @@ describe('Store', () => {
|
||||
▾ <App>
|
||||
▾ <Suspense>
|
||||
<ChildA>
|
||||
[shell]
|
||||
<Suspense name="App>?" rects={null}>
|
||||
[suspense-root] rects={null}
|
||||
<Suspense name="App" rects={null}>
|
||||
`);
|
||||
|
||||
await actAsync(() => render(<App renderA={false} />));
|
||||
@@ -2533,8 +2533,8 @@ describe('Store', () => {
|
||||
▾ <App>
|
||||
▾ <Suspense>
|
||||
<ChildB>
|
||||
[shell]
|
||||
<Suspense name="App>?" rects={null}>
|
||||
[suspense-root] rects={null}
|
||||
<Suspense name="App" rects={null}>
|
||||
`);
|
||||
});
|
||||
});
|
||||
@@ -2860,7 +2860,7 @@ describe('Store', () => {
|
||||
▾ <Suspense name="content">
|
||||
▾ <Suspense name="fallback">
|
||||
<Component key="fallback-fallback">
|
||||
[shell]
|
||||
[suspense-root] rects={[{x:1,y:2,width:19,height:1}]}
|
||||
<Suspense name="content" rects={null}>
|
||||
<Suspense name="fallback" rects={null}>
|
||||
`);
|
||||
@@ -2874,7 +2874,7 @@ describe('Store', () => {
|
||||
▾ <Suspense name="content">
|
||||
▾ <Suspense name="fallback">
|
||||
<Component key="fallback-content">
|
||||
[shell]
|
||||
[suspense-root] rects={[{x:1,y:2,width:10,height:1}]}
|
||||
<Suspense name="content" rects={null}>
|
||||
<Suspense name="fallback" rects={[{x:1,y:2,width:10,height:1}]}>
|
||||
`);
|
||||
@@ -2887,7 +2887,7 @@ describe('Store', () => {
|
||||
[root]
|
||||
▾ <Suspense name="content">
|
||||
<Component key="content">
|
||||
[shell]
|
||||
[suspense-root] rects={[{x:1,y:2,width:4,height:1}]}
|
||||
<Suspense name="content" rects={[{x:1,y:2,width:4,height:1}]}>
|
||||
`);
|
||||
});
|
||||
@@ -2990,7 +2990,7 @@ describe('Store', () => {
|
||||
▾ <Suspense name="main">
|
||||
▾ <Suspense name="main-fallback">
|
||||
<Component key="main-fallback-fallback">
|
||||
[shell]
|
||||
[suspense-root] rects={[{x:1,y:2,width:19,height:1}, {x:1,y:2,width:19,height:1}]}
|
||||
<Suspense name="head" rects={null}>
|
||||
<Suspense name="head-fallback" rects={null}>
|
||||
<Suspense name="main" rects={null}>
|
||||
@@ -3013,7 +3013,7 @@ describe('Store', () => {
|
||||
▾ <WithSuspenseInFallback>
|
||||
▾ <Suspense name="main">
|
||||
<Component key="main-content">
|
||||
[shell]
|
||||
[suspense-root] rects={[{x:1,y:2,width:4,height:1}, {x:1,y:2,width:4,height:1}]}
|
||||
<Suspense name="head" rects={[{x:1,y:2,width:4,height:1}]}>
|
||||
<Suspense name="main" rects={[{x:1,y:2,width:4,height:1}]}>
|
||||
`);
|
||||
@@ -3043,7 +3043,7 @@ describe('Store', () => {
|
||||
▾ <WithSuspenseInFallback>
|
||||
▾ <Suspense name="main">
|
||||
<Component key="main-content">
|
||||
[shell]
|
||||
[suspense-root] rects={[{x:1,y:2,width:4,height:1}, {x:1,y:2,width:10,height:1}, {x:1,y:2,width:4,height:1}]}
|
||||
<Suspense name="head" rects={[{x:1,y:2,width:4,height:1}]}>
|
||||
<Suspense name="head-fallback" rects={[{x:1,y:2,width:10,height:1}]}>
|
||||
<Suspense name="main" rects={[{x:1,y:2,width:4,height:1}]}>
|
||||
@@ -3074,10 +3074,15 @@ describe('Store', () => {
|
||||
▾ <WithSuspenseInFallback>
|
||||
▾ <Suspense name="main">
|
||||
<Component key="main-content">
|
||||
[shell]
|
||||
[suspense-root] rects={[{x:1,y:2,width:4,height:1}, {x:1,y:2,width:10,height:1}, {x:1,y:2,width:19,height:1}, {x:1,y:2,width:4,height:1}]}
|
||||
<Suspense name="head" rects={[{x:1,y:2,width:4,height:1}]}>
|
||||
<Suspense name="head-fallback" rects={[{x:1,y:2,width:10,height:1}]}>
|
||||
<Suspense name="main" rects={[{x:1,y:2,width:4,height:1}]}>
|
||||
`);
|
||||
});
|
||||
|
||||
it('should handle an empty root', async () => {
|
||||
await actAsync(() => render(null));
|
||||
expect(store).toMatchInlineSnapshot(`[root]`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -156,7 +156,7 @@ describe('Store component filters', () => {
|
||||
<div>
|
||||
▾ <Suspense>
|
||||
<div>
|
||||
[shell]
|
||||
[suspense-root] rects={[]}
|
||||
<Suspense name="Unknown" rects={[]}>
|
||||
<Suspense name="Unknown" rects={[]}>
|
||||
`);
|
||||
@@ -174,7 +174,7 @@ describe('Store component filters', () => {
|
||||
<div>
|
||||
▾ <Suspense>
|
||||
<div>
|
||||
[shell]
|
||||
[suspense-root] rects={[]}
|
||||
<Suspense name="Unknown" rects={[]}>
|
||||
<Suspense name="Unknown" rects={[]}>
|
||||
`);
|
||||
@@ -192,7 +192,7 @@ describe('Store component filters', () => {
|
||||
<div>
|
||||
▾ <Suspense>
|
||||
<div>
|
||||
[shell]
|
||||
[suspense-root] rects={[]}
|
||||
<Suspense name="Unknown" rects={[]}>
|
||||
<Suspense name="Unknown" rects={[]}>
|
||||
`);
|
||||
|
||||
@@ -1368,9 +1368,9 @@ describe('TreeListContext', () => {
|
||||
▾ <Child>
|
||||
▾ <Suspense>
|
||||
<Grandchild>
|
||||
[shell]
|
||||
<Suspense name="Parent>?" rects={null}>
|
||||
<Suspense name="Child>?" rects={null}>
|
||||
[suspense-root] rects={null}
|
||||
<Suspense name="Parent" rects={null}>
|
||||
<Suspense name="Child" rects={null}>
|
||||
`);
|
||||
|
||||
const outerSuspenseID = ((store.getElementIDAtIndex(1): any): number);
|
||||
@@ -1410,9 +1410,9 @@ describe('TreeListContext', () => {
|
||||
▾ <Child>
|
||||
▾ <Suspense>
|
||||
<Grandchild>
|
||||
[shell]
|
||||
<Suspense name="Parent>?" rects={null}>
|
||||
<Suspense name="Child>?" rects={null}>
|
||||
[suspense-root] rects={null}
|
||||
<Suspense name="Parent" rects={null}>
|
||||
<Suspense name="Child" rects={null}>
|
||||
`);
|
||||
});
|
||||
});
|
||||
@@ -2369,7 +2369,7 @@ describe('TreeListContext', () => {
|
||||
expect(state).toMatchInlineSnapshot(`
|
||||
[root]
|
||||
<Suspense>
|
||||
[shell]
|
||||
[suspense-root] rects={null}
|
||||
<Suspense name="Unknown" rects={null}>
|
||||
`);
|
||||
|
||||
@@ -2378,7 +2378,7 @@ describe('TreeListContext', () => {
|
||||
expect(state).toMatchInlineSnapshot(`
|
||||
[root]
|
||||
<Suspense>
|
||||
[shell]
|
||||
[suspense-root] rects={null}
|
||||
<Suspense name="Unknown" rects={null}>
|
||||
`);
|
||||
});
|
||||
@@ -2404,7 +2404,7 @@ describe('TreeListContext', () => {
|
||||
expect(state).toMatchInlineSnapshot(`
|
||||
[root]
|
||||
<Suspense>
|
||||
[shell]
|
||||
[suspense-root] rects={null}
|
||||
<Suspense name="Unknown" rects={null}>
|
||||
`);
|
||||
|
||||
@@ -2426,7 +2426,7 @@ describe('TreeListContext', () => {
|
||||
▾ <Suspense>
|
||||
<Child> ⚠
|
||||
<Child>
|
||||
[shell]
|
||||
[suspense-root] rects={null}
|
||||
<Suspense name="Unknown" rects={null}>
|
||||
`);
|
||||
});
|
||||
@@ -2456,7 +2456,7 @@ describe('TreeListContext', () => {
|
||||
▾ <Suspense>
|
||||
▾ <Fallback>
|
||||
<Child> ✕
|
||||
[shell]
|
||||
[suspense-root] rects={null}
|
||||
<Suspense name="Unknown" rects={null}>
|
||||
`);
|
||||
|
||||
@@ -2475,7 +2475,7 @@ describe('TreeListContext', () => {
|
||||
[root]
|
||||
▾ <Suspense>
|
||||
<Child>
|
||||
[shell]
|
||||
[suspense-root] rects={null}
|
||||
<Suspense name="Unknown" rects={null}>
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -3700,28 +3700,32 @@ export function attach(
|
||||
// just use the Fiber anyway.
|
||||
// Fallbacks get attributed to the parent so we only measure if we're
|
||||
// showing primary content.
|
||||
if (OffscreenComponent === -1) {
|
||||
const isTimedOut = fiber.memoizedState !== null;
|
||||
if (!isTimedOut) {
|
||||
newSuspenseNode.rects = measureInstance(newInstance);
|
||||
}
|
||||
} else {
|
||||
const hydrated = isFiberHydrated(fiber);
|
||||
if (hydrated) {
|
||||
const contentFiber = fiber.child;
|
||||
if (contentFiber === null) {
|
||||
throw new Error(
|
||||
'There should always be an Offscreen Fiber child in a hydrated Suspense boundary.',
|
||||
);
|
||||
if (fiber.tag === SuspenseComponent) {
|
||||
if (OffscreenComponent === -1) {
|
||||
const isTimedOut = fiber.memoizedState !== null;
|
||||
if (!isTimedOut) {
|
||||
newSuspenseNode.rects = measureInstance(newInstance);
|
||||
}
|
||||
} else {
|
||||
// This Suspense Fiber is still dehydrated. It won't have any children
|
||||
// until hydration.
|
||||
}
|
||||
const isTimedOut = fiber.memoizedState !== null;
|
||||
if (!isTimedOut) {
|
||||
newSuspenseNode.rects = measureInstance(newInstance);
|
||||
const hydrated = isFiberHydrated(fiber);
|
||||
if (hydrated) {
|
||||
const contentFiber = fiber.child;
|
||||
if (contentFiber === null) {
|
||||
throw new Error(
|
||||
'There should always be an Offscreen Fiber child in a hydrated Suspense boundary.',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// This Suspense Fiber is still dehydrated. It won't have any children
|
||||
// until hydration.
|
||||
}
|
||||
const isTimedOut = fiber.memoizedState !== null;
|
||||
if (!isTimedOut) {
|
||||
newSuspenseNode.rects = measureInstance(newInstance);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
newSuspenseNode.rects = measureInstance(newInstance);
|
||||
}
|
||||
recordSuspenseMount(newSuspenseNode, reconcilingParentSuspenseNode);
|
||||
}
|
||||
|
||||
@@ -1950,14 +1950,13 @@ export default class Store extends EventEmitter<{
|
||||
throw error;
|
||||
}
|
||||
|
||||
_guessSuspenseName(element: Element): string | null {
|
||||
// TODO: Use key
|
||||
_guessSuspenseName(element: Element): string {
|
||||
const owner = this._idToElement.get(element.ownerID);
|
||||
if (owner !== undefined) {
|
||||
// TODO: This is clowny
|
||||
return `${owner.displayName || 'Unknown'}>?`;
|
||||
let name = 'Unknown';
|
||||
if (owner !== undefined && owner.displayName !== null) {
|
||||
name = owner.displayName;
|
||||
}
|
||||
|
||||
return null;
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,22 +54,21 @@ export function printElement(
|
||||
}${key}${name}>${hocs}${suffix}`;
|
||||
}
|
||||
|
||||
function printSuspense(
|
||||
suspense: SuspenseNode,
|
||||
includeWeight: boolean = false,
|
||||
): string {
|
||||
function printRects(rects: SuspenseNode['rects']): string {
|
||||
if (rects === null) {
|
||||
return ' rects={null}';
|
||||
} else {
|
||||
return ` rects={[${rects.map(rect => `{x:${rect.x},y:${rect.y},width:${rect.width},height:${rect.height}}`).join(', ')}]}`;
|
||||
}
|
||||
}
|
||||
|
||||
function printSuspense(suspense: SuspenseNode): string {
|
||||
let name = '';
|
||||
if (suspense.name !== null) {
|
||||
name = ` name="${suspense.name}"`;
|
||||
}
|
||||
|
||||
let printedRects = '';
|
||||
const rects = suspense.rects;
|
||||
if (rects === null) {
|
||||
printedRects = ' rects={null}';
|
||||
} else {
|
||||
printedRects = ` rects={[${rects.map(rect => `{x:${rect.x},y:${rect.y},width:${rect.width},height:${rect.height}}`).join(', ')}]}`;
|
||||
}
|
||||
const printedRects = printRects(suspense.rects);
|
||||
|
||||
return `<Suspense${name}${printedRects}>`;
|
||||
}
|
||||
@@ -178,13 +177,13 @@ export function printStore(
|
||||
rootWeight += weight;
|
||||
|
||||
if (includeSuspense) {
|
||||
const shell = store.getSuspenseByID(rootID);
|
||||
const root = store.getSuspenseByID(rootID);
|
||||
// Roots from legacy renderers don't have a separate Suspense tree
|
||||
if (shell !== null) {
|
||||
if (shell.children.length > 0) {
|
||||
snapshotLines.push('[shell]');
|
||||
for (let i = 0; i < shell.children.length; i++) {
|
||||
const childID = shell.children[i];
|
||||
if (root !== null) {
|
||||
if (root.children.length > 0) {
|
||||
snapshotLines.push('[suspense-root] ' + printRects(root.rects));
|
||||
for (let i = 0; i < root.children.length; i++) {
|
||||
const childID = root.children[i];
|
||||
const child = store.getSuspenseByID(childID);
|
||||
if (child === null) {
|
||||
throw new Error(
|
||||
|
||||
Reference in New Issue
Block a user