mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Update on "[compiler] Null out source locations where not explicitly preserved"
Inspired by #32950. Specifically from https://github.com/facebook/react/issues/32950#issuecomment-2837887871, it sounds like Babel by default emits source map information for all nodes, even when they don't have a `loc` property set. Code coverage tools then pick up the synthesized source location information for this, leading to the issue described. It wasn't super clear how to omit nodes from the source map. A few google searches didn't turn up any documented way to do so: the user can do so via a function supplied to babel, but plugins can't. Entire files can be opted out, but not nodes. I asked an LLM which answered that explicitly setting `node.loc = null` omits the node from source maps, but that didn't work. What did work was explicitly setting the `loc` property to an object that follows the shape of a source location, but with all properties nulled out. With that, we get the desired result: <img width="553" alt="Screenshot 2025-04-30 at 10 50 30 AM" src="https://github.com/user-attachments/assets/cf55fd32-8375-4cfa-8202-49b45ef02931" /> I'm going to feature-flag this since setting some of these properties in this way might break in other versions of Babel or have other issues. [ghstack-poisoned]
This commit is contained in:
@@ -3609,31 +3609,40 @@ function lowerAssignment(
|
||||
|
||||
let temporary;
|
||||
if (builder.isContextIdentifier(lvalue)) {
|
||||
if (kind !== InstructionKind.Reassign && !isHoistedIdentifier) {
|
||||
if (kind === InstructionKind.Const) {
|
||||
builder.errors.push({
|
||||
reason: `Expected \`const\` declaration not to be reassigned`,
|
||||
severity: ErrorSeverity.InvalidJS,
|
||||
loc: lvalue.node.loc ?? null,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
lowerValueToTemporary(builder, {
|
||||
kind: 'DeclareContext',
|
||||
lvalue: {
|
||||
kind: InstructionKind.Let,
|
||||
place: {...place},
|
||||
},
|
||||
loc: place.loc,
|
||||
if (kind === InstructionKind.Const && !isHoistedIdentifier) {
|
||||
builder.errors.push({
|
||||
reason: `Expected \`const\` declaration not to be reassigned`,
|
||||
severity: ErrorSeverity.InvalidJS,
|
||||
loc: lvalue.node.loc ?? null,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
|
||||
temporary = lowerValueToTemporary(builder, {
|
||||
kind: 'StoreContext',
|
||||
lvalue: {place: {...place}, kind: InstructionKind.Reassign},
|
||||
value,
|
||||
loc,
|
||||
});
|
||||
if (
|
||||
kind !== InstructionKind.Const &&
|
||||
kind !== InstructionKind.Reassign &&
|
||||
kind !== InstructionKind.Let &&
|
||||
kind !== InstructionKind.Function
|
||||
) {
|
||||
builder.errors.push({
|
||||
reason: `Unexpected context variable kind`,
|
||||
severity: ErrorSeverity.InvalidJS,
|
||||
loc: lvalue.node.loc ?? null,
|
||||
suggestions: null,
|
||||
});
|
||||
temporary = lowerValueToTemporary(builder, {
|
||||
kind: 'UnsupportedNode',
|
||||
node: lvalueNode,
|
||||
loc: lvalueNode.loc ?? GeneratedSource,
|
||||
});
|
||||
} else {
|
||||
temporary = lowerValueToTemporary(builder, {
|
||||
kind: 'StoreContext',
|
||||
lvalue: {place: {...place}, kind},
|
||||
value,
|
||||
loc,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const typeAnnotation = lvalue.get('typeAnnotation');
|
||||
let type: t.FlowType | t.TSType | null;
|
||||
|
||||
@@ -746,6 +746,27 @@ export enum InstructionKind {
|
||||
Function = 'Function',
|
||||
}
|
||||
|
||||
export function convertHoistedLValueKind(
|
||||
kind: InstructionKind,
|
||||
): InstructionKind | null {
|
||||
switch (kind) {
|
||||
case InstructionKind.HoistedLet:
|
||||
return InstructionKind.Let;
|
||||
case InstructionKind.HoistedConst:
|
||||
return InstructionKind.Const;
|
||||
case InstructionKind.HoistedFunction:
|
||||
return InstructionKind.Function;
|
||||
case InstructionKind.Let:
|
||||
case InstructionKind.Const:
|
||||
case InstructionKind.Function:
|
||||
case InstructionKind.Reassign:
|
||||
case InstructionKind.Catch:
|
||||
return null;
|
||||
default:
|
||||
assertExhaustive(kind, 'Unexpected lvalue kind');
|
||||
}
|
||||
}
|
||||
|
||||
function _staticInvariantInstructionValueHasLocation(
|
||||
value: InstructionValue,
|
||||
): SourceLocation {
|
||||
@@ -880,8 +901,20 @@ export type InstructionValue =
|
||||
| StoreLocal
|
||||
| {
|
||||
kind: 'StoreContext';
|
||||
/**
|
||||
* StoreContext kinds:
|
||||
* Reassign: context variable reassignment in source
|
||||
* Const: const declaration + assignment in source
|
||||
* ('const' context vars are ones whose declarations are hoisted)
|
||||
* Let: let declaration + assignment in source
|
||||
* Function: function declaration in source (similar to `const`)
|
||||
*/
|
||||
lvalue: {
|
||||
kind: InstructionKind.Reassign;
|
||||
kind:
|
||||
| InstructionKind.Reassign
|
||||
| InstructionKind.Const
|
||||
| InstructionKind.Let
|
||||
| InstructionKind.Function;
|
||||
place: Place;
|
||||
};
|
||||
value: Place;
|
||||
|
||||
+50
-20
@@ -30,6 +30,7 @@ import {
|
||||
FunctionExpression,
|
||||
ObjectMethod,
|
||||
PropertyLiteral,
|
||||
convertHoistedLValueKind,
|
||||
} from './HIR';
|
||||
import {
|
||||
collectHoistablePropertyLoads,
|
||||
@@ -246,12 +247,18 @@ function isLoadContextMutable(
|
||||
id: InstructionId,
|
||||
): instrValue is LoadContext {
|
||||
if (instrValue.kind === 'LoadContext') {
|
||||
CompilerError.invariant(instrValue.place.identifier.scope != null, {
|
||||
reason:
|
||||
'[PropagateScopeDependencies] Expected all context variables to be assigned a scope',
|
||||
loc: instrValue.loc,
|
||||
});
|
||||
return id >= instrValue.place.identifier.scope.range.end;
|
||||
/**
|
||||
* Not all context variables currently have scopes due to limitations of
|
||||
* mutability analysis for function expressions.
|
||||
*
|
||||
* Currently, many function expressions references are inferred to be
|
||||
* 'Read' | 'Freeze' effects which don't replay mutable effects of captured
|
||||
* context.
|
||||
*/
|
||||
return (
|
||||
instrValue.place.identifier.scope != null &&
|
||||
id >= instrValue.place.identifier.scope.range.end
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -471,6 +478,9 @@ export class DependencyCollectionContext {
|
||||
}
|
||||
this.#reassignments.set(identifier, decl);
|
||||
}
|
||||
hasDeclared(identifier: Identifier): boolean {
|
||||
return this.#declarations.has(identifier.declarationId);
|
||||
}
|
||||
|
||||
// Checks if identifier is a valid dependency in the current scope
|
||||
#checkValidDependency(maybeDependency: ReactiveScopeDependency): boolean {
|
||||
@@ -672,21 +682,21 @@ export function handleInstruction(
|
||||
});
|
||||
} else if (value.kind === 'DeclareLocal' || value.kind === 'DeclareContext') {
|
||||
/*
|
||||
* Some variables may be declared and never initialized. We need
|
||||
* to retain (and hoist) these declarations if they are included
|
||||
* in a reactive scope. One approach is to simply add all `DeclareLocal`s
|
||||
* as scope declarations.
|
||||
* Some variables may be declared and never initialized. We need to retain
|
||||
* (and hoist) these declarations if they are included in a reactive scope.
|
||||
* One approach is to simply add all `DeclareLocal`s as scope declarations.
|
||||
*
|
||||
* Context variables with hoisted declarations only become live after their
|
||||
* first assignment. We only declare real DeclareLocal / DeclareContext
|
||||
* instructions (not hoisted ones) to avoid generating dependencies on
|
||||
* hoisted declarations.
|
||||
*/
|
||||
|
||||
/*
|
||||
* We add context variable declarations here, not at `StoreContext`, since
|
||||
* context Store / Loads are modeled as reads and mutates to the underlying
|
||||
* variable reference (instead of through intermediate / inlined temporaries)
|
||||
*/
|
||||
context.declare(value.lvalue.place.identifier, {
|
||||
id,
|
||||
scope: context.currentScope,
|
||||
});
|
||||
if (convertHoistedLValueKind(value.lvalue.kind) === null) {
|
||||
context.declare(value.lvalue.place.identifier, {
|
||||
id,
|
||||
scope: context.currentScope,
|
||||
});
|
||||
}
|
||||
} else if (value.kind === 'Destructure') {
|
||||
context.visitOperand(value.value);
|
||||
for (const place of eachPatternOperand(value.lvalue.pattern)) {
|
||||
@@ -698,6 +708,26 @@ export function handleInstruction(
|
||||
scope: context.currentScope,
|
||||
});
|
||||
}
|
||||
} else if (value.kind === 'StoreContext') {
|
||||
/**
|
||||
* Some StoreContext variables have hoisted declarations. If we're storing
|
||||
* to a context variable that hasn't yet been declared, the StoreContext is
|
||||
* the declaration.
|
||||
* (see corresponding logic in PruneHoistedContext)
|
||||
*/
|
||||
if (
|
||||
!context.hasDeclared(value.lvalue.place.identifier) ||
|
||||
value.lvalue.kind !== InstructionKind.Reassign
|
||||
) {
|
||||
context.declare(value.lvalue.place.identifier, {
|
||||
id,
|
||||
scope: context.currentScope,
|
||||
});
|
||||
}
|
||||
|
||||
for (const operand of eachInstructionValueOperand(value)) {
|
||||
context.visitOperand(operand);
|
||||
}
|
||||
} else {
|
||||
for (const operand of eachInstructionValueOperand(value)) {
|
||||
context.visitOperand(operand);
|
||||
|
||||
+8
-2
@@ -176,9 +176,15 @@ export function inferMutableLifetimes(
|
||||
if (
|
||||
instr.value.kind === 'DeclareContext' ||
|
||||
(instr.value.kind === 'StoreContext' &&
|
||||
instr.value.lvalue.kind !== InstructionKind.Reassign)
|
||||
instr.value.lvalue.kind !== InstructionKind.Reassign &&
|
||||
!contextVariableDeclarationInstructions.has(
|
||||
instr.value.lvalue.place.identifier,
|
||||
))
|
||||
) {
|
||||
// Save declarations of context variables
|
||||
/**
|
||||
* Save declarations of context variables if they hasn't already been
|
||||
* declared (due to hoisted declarations).
|
||||
*/
|
||||
contextVariableDeclarationInstructions.set(
|
||||
instr.value.lvalue.place.identifier,
|
||||
instr.id,
|
||||
|
||||
+41
-6
@@ -111,7 +111,10 @@ export default function inferReferenceEffects(
|
||||
* Initial state contains function params
|
||||
* TODO: include module declarations here as well
|
||||
*/
|
||||
const initialState = InferenceState.empty(fn.env);
|
||||
const initialState = InferenceState.empty(
|
||||
fn.env,
|
||||
options.isFunctionExpression,
|
||||
);
|
||||
const value: InstructionValue = {
|
||||
kind: 'Primitive',
|
||||
loc: fn.loc,
|
||||
@@ -255,6 +258,7 @@ type FreezeAction = {values: Set<InstructionValue>; reason: Set<ValueReason>};
|
||||
// Maintains a mapping of top-level variables to the kind of value they hold
|
||||
class InferenceState {
|
||||
env: Environment;
|
||||
#isFunctionExpression: boolean;
|
||||
|
||||
// The kind of each value, based on its allocation site
|
||||
#values: Map<InstructionValue, AbstractValue>;
|
||||
@@ -267,16 +271,25 @@ class InferenceState {
|
||||
|
||||
constructor(
|
||||
env: Environment,
|
||||
isFunctionExpression: boolean,
|
||||
values: Map<InstructionValue, AbstractValue>,
|
||||
variables: Map<IdentifierId, Set<InstructionValue>>,
|
||||
) {
|
||||
this.env = env;
|
||||
this.#isFunctionExpression = isFunctionExpression;
|
||||
this.#values = values;
|
||||
this.#variables = variables;
|
||||
}
|
||||
|
||||
static empty(env: Environment): InferenceState {
|
||||
return new InferenceState(env, new Map(), new Map());
|
||||
static empty(
|
||||
env: Environment,
|
||||
isFunctionExpression: boolean,
|
||||
): InferenceState {
|
||||
return new InferenceState(env, isFunctionExpression, new Map(), new Map());
|
||||
}
|
||||
|
||||
get isFunctionExpression(): boolean {
|
||||
return this.#isFunctionExpression;
|
||||
}
|
||||
|
||||
// (Re)initializes a @param value with its default @param kind.
|
||||
@@ -394,9 +407,14 @@ class InferenceState {
|
||||
|
||||
freezeValues(values: Set<InstructionValue>, reason: Set<ValueReason>): void {
|
||||
for (const value of values) {
|
||||
if (value.kind === 'DeclareContext') {
|
||||
if (
|
||||
value.kind === 'DeclareContext' ||
|
||||
(value.kind === 'StoreContext' &&
|
||||
(value.lvalue.kind === InstructionKind.Let ||
|
||||
value.lvalue.kind === InstructionKind.Const))
|
||||
) {
|
||||
/**
|
||||
* Avoid freezing hoisted context declarations
|
||||
* Avoid freezing context variable declarations, hoisted or otherwise
|
||||
* function Component() {
|
||||
* const cb = useBar(() => foo(2)); // produces a hoisted context declaration
|
||||
* const foo = useFoo(); // reassigns to the context variable
|
||||
@@ -613,6 +631,7 @@ class InferenceState {
|
||||
} else {
|
||||
return new InferenceState(
|
||||
this.env,
|
||||
this.#isFunctionExpression,
|
||||
nextValues ?? new Map(this.#values),
|
||||
nextVariables ?? new Map(this.#variables),
|
||||
);
|
||||
@@ -627,6 +646,7 @@ class InferenceState {
|
||||
clone(): InferenceState {
|
||||
return new InferenceState(
|
||||
this.env,
|
||||
this.#isFunctionExpression,
|
||||
new Map(this.#values),
|
||||
new Map(this.#variables),
|
||||
);
|
||||
@@ -1591,6 +1611,14 @@ function inferBlock(
|
||||
);
|
||||
|
||||
const lvalue = instr.lvalue;
|
||||
if (instrValue.lvalue.kind !== InstructionKind.Reassign) {
|
||||
state.initialize(instrValue, {
|
||||
kind: ValueKind.Mutable,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
context: new Set(),
|
||||
});
|
||||
state.define(instrValue.lvalue.place, instrValue);
|
||||
}
|
||||
state.alias(lvalue, instrValue.value);
|
||||
lvalue.effect = Effect.Store;
|
||||
continuation = {kind: 'funeffects'};
|
||||
@@ -1781,8 +1809,15 @@ function inferBlock(
|
||||
if (block.terminal.kind === 'return' || block.terminal.kind === 'throw') {
|
||||
if (
|
||||
state.isDefined(operand) &&
|
||||
state.kind(operand).kind === ValueKind.Context
|
||||
((operand.identifier.type.kind === 'Function' &&
|
||||
state.isFunctionExpression) ||
|
||||
state.kind(operand).kind === ValueKind.Context)
|
||||
) {
|
||||
/**
|
||||
* Returned values should only be typed as 'frozen' if they are both (1)
|
||||
* local and (2) not a function expression which may capture and mutate
|
||||
* this function's outer context.
|
||||
*/
|
||||
effect = Effect.ConditionallyMutate;
|
||||
} else {
|
||||
effect = Effect.Freeze;
|
||||
|
||||
+16
@@ -1002,6 +1002,14 @@ function codegenTerminal(
|
||||
lval = codegenLValue(cx, iterableItem.value.lvalue.pattern);
|
||||
break;
|
||||
}
|
||||
case 'StoreContext': {
|
||||
CompilerError.throwTodo({
|
||||
reason: 'Support non-trivial for..in inits',
|
||||
description: null,
|
||||
loc: terminal.init.loc,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
default:
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Expected a StoreLocal or Destructure to be assigned to the collection`,
|
||||
@@ -1094,6 +1102,14 @@ function codegenTerminal(
|
||||
lval = codegenLValue(cx, iterableItem.value.lvalue.pattern);
|
||||
break;
|
||||
}
|
||||
case 'StoreContext': {
|
||||
CompilerError.throwTodo({
|
||||
reason: 'Support non-trivial for..of inits',
|
||||
description: null,
|
||||
loc: terminal.init.loc,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
default:
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Expected a StoreLocal or Destructure to be assigned to the collection`,
|
||||
|
||||
+6
@@ -255,6 +255,12 @@ function writeReactiveValue(writer: Writer, value: ReactiveValue): void {
|
||||
}
|
||||
}
|
||||
|
||||
export function printReactiveTerminal(terminal: ReactiveTerminal): string {
|
||||
const writer = new Writer();
|
||||
writeTerminal(writer, terminal);
|
||||
return writer.complete();
|
||||
}
|
||||
|
||||
function writeTerminal(writer: Writer, terminal: ReactiveTerminal): void {
|
||||
switch (terminal.kind) {
|
||||
case 'break': {
|
||||
|
||||
+36
-113
@@ -5,14 +5,16 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {CompilerError} from '..';
|
||||
import {
|
||||
DeclarationId,
|
||||
convertHoistedLValueKind,
|
||||
IdentifierId,
|
||||
InstructionKind,
|
||||
ReactiveFunction,
|
||||
ReactiveInstruction,
|
||||
ReactiveScopeBlock,
|
||||
ReactiveStatement,
|
||||
} from '../HIR';
|
||||
import {empty, Stack} from '../Utils/Stack';
|
||||
import {
|
||||
ReactiveFunctionTransform,
|
||||
Transformed,
|
||||
@@ -24,133 +26,54 @@ import {
|
||||
* original instruction kind.
|
||||
*/
|
||||
export function pruneHoistedContexts(fn: ReactiveFunction): void {
|
||||
const hoistedIdentifiers: HoistedIdentifiers = new Map();
|
||||
visitReactiveFunction(fn, new Visitor(), hoistedIdentifiers);
|
||||
visitReactiveFunction(fn, new Visitor(), {
|
||||
activeScopes: empty(),
|
||||
});
|
||||
}
|
||||
|
||||
const REWRITTEN_HOISTED_CONST: unique symbol = Symbol(
|
||||
'REWRITTEN_HOISTED_CONST',
|
||||
);
|
||||
const REWRITTEN_HOISTED_LET: unique symbol = Symbol('REWRITTEN_HOISTED_LET');
|
||||
type VisitorState = {
|
||||
activeScopes: Stack<Set<IdentifierId>>;
|
||||
};
|
||||
|
||||
type HoistedIdentifiers = Map<
|
||||
DeclarationId,
|
||||
| InstructionKind
|
||||
| typeof REWRITTEN_HOISTED_CONST
|
||||
| typeof REWRITTEN_HOISTED_LET
|
||||
>;
|
||||
|
||||
class Visitor extends ReactiveFunctionTransform<HoistedIdentifiers> {
|
||||
class Visitor extends ReactiveFunctionTransform<VisitorState> {
|
||||
override visitScope(scope: ReactiveScopeBlock, state: VisitorState): void {
|
||||
state.activeScopes = state.activeScopes.push(
|
||||
new Set(scope.scope.declarations.keys()),
|
||||
);
|
||||
this.traverseScope(scope, state);
|
||||
state.activeScopes.pop();
|
||||
}
|
||||
override transformInstruction(
|
||||
instruction: ReactiveInstruction,
|
||||
state: HoistedIdentifiers,
|
||||
state: VisitorState,
|
||||
): Transformed<ReactiveStatement> {
|
||||
this.visitInstruction(instruction, state);
|
||||
|
||||
/**
|
||||
* Remove hoisted declarations to preserve TDZ
|
||||
*/
|
||||
if (
|
||||
instruction.value.kind === 'DeclareContext' &&
|
||||
instruction.value.lvalue.kind === 'HoistedConst'
|
||||
) {
|
||||
state.set(
|
||||
instruction.value.lvalue.place.identifier.declarationId,
|
||||
InstructionKind.Const,
|
||||
if (instruction.value.kind === 'DeclareContext') {
|
||||
const maybeNonHoisted = convertHoistedLValueKind(
|
||||
instruction.value.lvalue.kind,
|
||||
);
|
||||
return {kind: 'remove'};
|
||||
if (maybeNonHoisted != null) {
|
||||
return {kind: 'remove'};
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
instruction.value.kind === 'DeclareContext' &&
|
||||
instruction.value.lvalue.kind === 'HoistedLet'
|
||||
instruction.value.kind === 'StoreContext' &&
|
||||
instruction.value.lvalue.kind !== InstructionKind.Reassign
|
||||
) {
|
||||
state.set(
|
||||
instruction.value.lvalue.place.identifier.declarationId,
|
||||
InstructionKind.Let,
|
||||
/**
|
||||
* Rewrite StoreContexts let/const/functions that will be pre-declared in
|
||||
* codegen to reassignments.
|
||||
*/
|
||||
const lvalueId = instruction.value.lvalue.place.identifier.id;
|
||||
const isDeclaredByScope = state.activeScopes.find(scope =>
|
||||
scope.has(lvalueId),
|
||||
);
|
||||
return {kind: 'remove'};
|
||||
}
|
||||
|
||||
if (
|
||||
instruction.value.kind === 'DeclareContext' &&
|
||||
instruction.value.lvalue.kind === 'HoistedFunction'
|
||||
) {
|
||||
state.set(
|
||||
instruction.value.lvalue.place.identifier.declarationId,
|
||||
InstructionKind.Function,
|
||||
);
|
||||
return {kind: 'remove'};
|
||||
}
|
||||
|
||||
if (instruction.value.kind === 'StoreContext') {
|
||||
const kind = state.get(
|
||||
instruction.value.lvalue.place.identifier.declarationId,
|
||||
);
|
||||
if (kind != null) {
|
||||
CompilerError.invariant(kind !== REWRITTEN_HOISTED_CONST, {
|
||||
reason: 'Expected exactly one store to a hoisted const variable',
|
||||
loc: instruction.loc,
|
||||
});
|
||||
if (
|
||||
kind === InstructionKind.Const ||
|
||||
kind === InstructionKind.Function
|
||||
) {
|
||||
state.set(
|
||||
instruction.value.lvalue.place.identifier.declarationId,
|
||||
REWRITTEN_HOISTED_CONST,
|
||||
);
|
||||
return {
|
||||
kind: 'replace',
|
||||
value: {
|
||||
kind: 'instruction',
|
||||
instruction: {
|
||||
...instruction,
|
||||
value: {
|
||||
...instruction.value,
|
||||
lvalue: {
|
||||
...instruction.value.lvalue,
|
||||
kind,
|
||||
},
|
||||
type: null,
|
||||
kind: 'StoreLocal',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
} else if (kind !== REWRITTEN_HOISTED_LET) {
|
||||
/**
|
||||
* Context variables declared with let may have reassignments. Only
|
||||
* insert a `DeclareContext` for the first encountered `StoreContext`
|
||||
* instruction.
|
||||
*/
|
||||
state.set(
|
||||
instruction.value.lvalue.place.identifier.declarationId,
|
||||
REWRITTEN_HOISTED_LET,
|
||||
);
|
||||
return {
|
||||
kind: 'replace-many',
|
||||
value: [
|
||||
{
|
||||
kind: 'instruction',
|
||||
instruction: {
|
||||
id: instruction.id,
|
||||
lvalue: null,
|
||||
value: {
|
||||
kind: 'DeclareContext',
|
||||
lvalue: {
|
||||
kind: InstructionKind.Let,
|
||||
place: {...instruction.value.lvalue.place},
|
||||
},
|
||||
loc: instruction.value.loc,
|
||||
},
|
||||
loc: instruction.loc,
|
||||
},
|
||||
},
|
||||
{kind: 'instruction', instruction},
|
||||
],
|
||||
};
|
||||
}
|
||||
if (isDeclaredByScope) {
|
||||
instruction.value.lvalue.kind = InstructionKind.Reassign;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+519
-468
File diff suppressed because it is too large
Load Diff
+1
-2
@@ -34,8 +34,7 @@ function bar(a, b) {
|
||||
if ($[0] !== a || $[1] !== b) {
|
||||
const x = [a, b];
|
||||
y = {};
|
||||
let t;
|
||||
t = {};
|
||||
let t = {};
|
||||
|
||||
y = x[0][1];
|
||||
t = x[1][0];
|
||||
|
||||
+1
-2
@@ -35,8 +35,7 @@ function bar(a, b) {
|
||||
if ($[0] !== a || $[1] !== b) {
|
||||
const x = [a, b];
|
||||
y = {};
|
||||
let t;
|
||||
t = {};
|
||||
let t = {};
|
||||
const f0 = function () {
|
||||
y = x[0][1];
|
||||
t = x[1][0];
|
||||
|
||||
+1
-2
@@ -33,8 +33,7 @@ function useTest() {
|
||||
const $ = _c(1);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
let w;
|
||||
w = {};
|
||||
let w = {};
|
||||
|
||||
const t1 = (w = 42);
|
||||
const t2 = w;
|
||||
|
||||
+1
-2
@@ -30,8 +30,7 @@ function Component(props) {
|
||||
const $ = _c(1);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
let x;
|
||||
x = null;
|
||||
let x = null;
|
||||
const callback = () => {
|
||||
console.log(x);
|
||||
};
|
||||
|
||||
+39
-28
@@ -2,13 +2,22 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {Stringify, useIdentity} from 'shared-runtime';
|
||||
|
||||
function Component() {
|
||||
const data = useData();
|
||||
const data = useIdentity(
|
||||
new Map([
|
||||
[0, 'value0'],
|
||||
[1, 'value1'],
|
||||
])
|
||||
);
|
||||
const items = [];
|
||||
// NOTE: `i` is a context variable because it's reassigned and also referenced
|
||||
// within a closure, the `onClick` handler of each item
|
||||
for (let i = MIN; i <= MAX; i += INCREMENT) {
|
||||
items.push(<div key={i} onClick={() => data.set(i)} />);
|
||||
items.push(
|
||||
<Stringify key={i} onClick={() => data.get(i)} shouldInvokeFns={true} />
|
||||
);
|
||||
}
|
||||
return <>{items}</>;
|
||||
}
|
||||
@@ -17,10 +26,6 @@ const MIN = 0;
|
||||
const MAX = 3;
|
||||
const INCREMENT = 1;
|
||||
|
||||
function useData() {
|
||||
return new Map();
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
params: [],
|
||||
fn: Component,
|
||||
@@ -32,41 +37,47 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { Stringify, useIdentity } from "shared-runtime";
|
||||
|
||||
function Component() {
|
||||
const $ = _c(2);
|
||||
const data = useData();
|
||||
const $ = _c(3);
|
||||
let t0;
|
||||
if ($[0] !== data) {
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = new Map([
|
||||
[0, "value0"],
|
||||
[1, "value1"],
|
||||
]);
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
const data = useIdentity(t0);
|
||||
let t1;
|
||||
if ($[1] !== data) {
|
||||
const items = [];
|
||||
for (let i = MIN; i <= MAX; i = i + INCREMENT, i) {
|
||||
items.push(<div key={i} onClick={() => data.set(i)} />);
|
||||
items.push(
|
||||
<Stringify
|
||||
key={i}
|
||||
onClick={() => data.get(i)}
|
||||
shouldInvokeFns={true}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
|
||||
t0 = <>{items}</>;
|
||||
$[0] = data;
|
||||
$[1] = t0;
|
||||
t1 = <>{items}</>;
|
||||
$[1] = data;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
t1 = $[2];
|
||||
}
|
||||
return t0;
|
||||
return t1;
|
||||
}
|
||||
|
||||
const MIN = 0;
|
||||
const MAX = 3;
|
||||
const INCREMENT = 1;
|
||||
|
||||
function useData() {
|
||||
const $ = _c(1);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = new Map();
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
params: [],
|
||||
fn: Component,
|
||||
@@ -75,4 +86,4 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div></div><div></div><div></div><div></div>
|
||||
(kind: ok) <div>{"onClick":{"kind":"Function","result":"value0"},"shouldInvokeFns":true}</div><div>{"onClick":{"kind":"Function","result":"value1"},"shouldInvokeFns":true}</div><div>{"onClick":{"kind":"Function"},"shouldInvokeFns":true}</div><div>{"onClick":{"kind":"Function"},"shouldInvokeFns":true}</div>
|
||||
+11
-6
@@ -1,10 +1,19 @@
|
||||
import {Stringify, useIdentity} from 'shared-runtime';
|
||||
|
||||
function Component() {
|
||||
const data = useData();
|
||||
const data = useIdentity(
|
||||
new Map([
|
||||
[0, 'value0'],
|
||||
[1, 'value1'],
|
||||
])
|
||||
);
|
||||
const items = [];
|
||||
// NOTE: `i` is a context variable because it's reassigned and also referenced
|
||||
// within a closure, the `onClick` handler of each item
|
||||
for (let i = MIN; i <= MAX; i += INCREMENT) {
|
||||
items.push(<div key={i} onClick={() => data.set(i)} />);
|
||||
items.push(
|
||||
<Stringify key={i} onClick={() => data.get(i)} shouldInvokeFns={true} />
|
||||
);
|
||||
}
|
||||
return <>{items}</>;
|
||||
}
|
||||
@@ -13,10 +22,6 @@ const MIN = 0;
|
||||
const MAX = 3;
|
||||
const INCREMENT = 1;
|
||||
|
||||
function useData() {
|
||||
return new Map();
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
params: [],
|
||||
fn: Component,
|
||||
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {CONST_TRUE, useIdentity} from 'shared-runtime';
|
||||
|
||||
const hidden = CONST_TRUE;
|
||||
function useFoo() {
|
||||
const makeCb = useIdentity(() => {
|
||||
const logIntervalId = () => {
|
||||
log(intervalId);
|
||||
};
|
||||
|
||||
let intervalId;
|
||||
if (!hidden) {
|
||||
intervalId = 2;
|
||||
}
|
||||
return () => {
|
||||
logIntervalId();
|
||||
};
|
||||
});
|
||||
|
||||
return <Stringify fn={makeCb()} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { CONST_TRUE, useIdentity } from "shared-runtime";
|
||||
|
||||
const hidden = CONST_TRUE;
|
||||
function useFoo() {
|
||||
const $ = _c(4);
|
||||
const makeCb = useIdentity(_temp);
|
||||
let t0;
|
||||
if ($[0] !== makeCb) {
|
||||
t0 = makeCb();
|
||||
$[0] = makeCb;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
let t1;
|
||||
if ($[2] !== t0) {
|
||||
t1 = <Stringify fn={t0} shouldInvokeFns={true} />;
|
||||
$[2] = t0;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
function _temp() {
|
||||
const logIntervalId = () => {
|
||||
log(intervalId);
|
||||
};
|
||||
let intervalId;
|
||||
if (!hidden) {
|
||||
intervalId = 2;
|
||||
}
|
||||
return () => {
|
||||
logIntervalId();
|
||||
};
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Stringify is not defined
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import {CONST_TRUE, useIdentity} from 'shared-runtime';
|
||||
|
||||
const hidden = CONST_TRUE;
|
||||
function useFoo() {
|
||||
const makeCb = useIdentity(() => {
|
||||
const logIntervalId = () => {
|
||||
log(intervalId);
|
||||
};
|
||||
|
||||
let intervalId;
|
||||
if (!hidden) {
|
||||
intervalId = 2;
|
||||
}
|
||||
return () => {
|
||||
logIntervalId();
|
||||
};
|
||||
});
|
||||
|
||||
return <Stringify fn={makeCb()} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [],
|
||||
};
|
||||
+1
-2
@@ -30,8 +30,7 @@ function Foo() {
|
||||
getX = () => x;
|
||||
console.log(getX());
|
||||
|
||||
let x;
|
||||
x = 4;
|
||||
let x = 4;
|
||||
x = x + 5;
|
||||
$[0] = getX;
|
||||
} else {
|
||||
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {CONST_NUMBER1, Stringify} from 'shared-runtime';
|
||||
|
||||
function useHook({cond}) {
|
||||
'use memo';
|
||||
const getX = () => x;
|
||||
|
||||
let x;
|
||||
if (cond) {
|
||||
x = CONST_NUMBER1;
|
||||
}
|
||||
return <Stringify getX={getX} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: () => {},
|
||||
params: [{cond: true}],
|
||||
sequentialRenders: [{cond: true}, {cond: true}, {cond: false}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { CONST_NUMBER1, Stringify } from "shared-runtime";
|
||||
|
||||
function useHook(t0) {
|
||||
"use memo";
|
||||
const $ = _c(2);
|
||||
const { cond } = t0;
|
||||
let t1;
|
||||
if ($[0] !== cond) {
|
||||
const getX = () => x;
|
||||
|
||||
let x;
|
||||
if (cond) {
|
||||
x = CONST_NUMBER1;
|
||||
}
|
||||
|
||||
t1 = <Stringify getX={getX} shouldInvokeFns={true} />;
|
||||
$[0] = cond;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: () => {},
|
||||
params: [{ cond: true }],
|
||||
sequentialRenders: [{ cond: true }, { cond: true }, { cond: false }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok)
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import {CONST_NUMBER1, Stringify} from 'shared-runtime';
|
||||
|
||||
function useHook({cond}) {
|
||||
'use memo';
|
||||
const getX = () => x;
|
||||
|
||||
let x;
|
||||
if (cond) {
|
||||
x = CONST_NUMBER1;
|
||||
}
|
||||
return <Stringify getX={getX} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: () => {},
|
||||
params: [{cond: true}],
|
||||
sequentialRenders: [{cond: true}, {cond: true}, {cond: false}],
|
||||
};
|
||||
+1
-2
@@ -36,8 +36,7 @@ function hoisting(cond) {
|
||||
items.push(bar());
|
||||
};
|
||||
|
||||
let bar;
|
||||
bar = _temp;
|
||||
let bar = _temp;
|
||||
foo();
|
||||
}
|
||||
$[0] = cond;
|
||||
|
||||
+2
-4
@@ -41,11 +41,9 @@ function hoisting() {
|
||||
return result;
|
||||
};
|
||||
|
||||
let foo;
|
||||
foo = () => bar + baz;
|
||||
let foo = () => bar + baz;
|
||||
|
||||
let bar;
|
||||
bar = 3;
|
||||
let bar = 3;
|
||||
const baz = 2;
|
||||
t0 = qux();
|
||||
$[0] = t0;
|
||||
|
||||
+1
-2
@@ -37,8 +37,7 @@ function useHook(t0) {
|
||||
if ($[0] !== cond) {
|
||||
const getX = () => x;
|
||||
|
||||
let x;
|
||||
x = CONST_NUMBER0;
|
||||
let x = CONST_NUMBER0;
|
||||
if (cond) {
|
||||
x = x + CONST_NUMBER1;
|
||||
x;
|
||||
|
||||
+1
-2
@@ -38,8 +38,7 @@ function useHook(t0) {
|
||||
if ($[0] !== cond) {
|
||||
const getX = () => x;
|
||||
|
||||
let x;
|
||||
x = CONST_NUMBER0;
|
||||
let x = CONST_NUMBER0;
|
||||
if (cond) {
|
||||
x = x + CONST_NUMBER1;
|
||||
x;
|
||||
|
||||
+2
-4
@@ -29,10 +29,8 @@ function hoisting() {
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
foo = () => bar + baz;
|
||||
|
||||
let bar;
|
||||
bar = 3;
|
||||
let baz;
|
||||
baz = 2;
|
||||
let bar = 3;
|
||||
let baz = 2;
|
||||
$[0] = foo;
|
||||
} else {
|
||||
foo = $[0];
|
||||
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {Stringify, useIdentity} from 'shared-runtime';
|
||||
|
||||
function Component({prop1, prop2}) {
|
||||
'use memo';
|
||||
|
||||
const data = useIdentity(
|
||||
new Map([
|
||||
[0, 'value0'],
|
||||
[1, 'value1'],
|
||||
])
|
||||
);
|
||||
let i = 0;
|
||||
const items = [];
|
||||
items.push(
|
||||
<Stringify
|
||||
key={i}
|
||||
onClick={() => data.get(i) + prop1}
|
||||
shouldInvokeFns={true}
|
||||
/>
|
||||
);
|
||||
i = i + 1;
|
||||
items.push(
|
||||
<Stringify
|
||||
key={i}
|
||||
onClick={() => data.get(i) + prop2}
|
||||
shouldInvokeFns={true}
|
||||
/>
|
||||
);
|
||||
return <>{items}</>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{prop1: 'prop1', prop2: 'prop2'}],
|
||||
sequentialRenders: [
|
||||
{prop1: 'prop1', prop2: 'prop2'},
|
||||
{prop1: 'prop1', prop2: 'prop2'},
|
||||
{prop1: 'changed', prop2: 'prop2'},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { Stringify, useIdentity } from "shared-runtime";
|
||||
|
||||
function Component(t0) {
|
||||
"use memo";
|
||||
const $ = _c(12);
|
||||
const { prop1, prop2 } = t0;
|
||||
let t1;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = new Map([
|
||||
[0, "value0"],
|
||||
[1, "value1"],
|
||||
]);
|
||||
$[0] = t1;
|
||||
} else {
|
||||
t1 = $[0];
|
||||
}
|
||||
const data = useIdentity(t1);
|
||||
let t2;
|
||||
if ($[1] !== data || $[2] !== prop1 || $[3] !== prop2) {
|
||||
let i = 0;
|
||||
const items = [];
|
||||
items.push(
|
||||
<Stringify
|
||||
key={i}
|
||||
onClick={() => data.get(i) + prop1}
|
||||
shouldInvokeFns={true}
|
||||
/>,
|
||||
);
|
||||
i = i + 1;
|
||||
|
||||
const t3 = i;
|
||||
let t4;
|
||||
if ($[5] !== data || $[6] !== i || $[7] !== prop2) {
|
||||
t4 = () => data.get(i) + prop2;
|
||||
$[5] = data;
|
||||
$[6] = i;
|
||||
$[7] = prop2;
|
||||
$[8] = t4;
|
||||
} else {
|
||||
t4 = $[8];
|
||||
}
|
||||
let t5;
|
||||
if ($[9] !== t3 || $[10] !== t4) {
|
||||
t5 = <Stringify key={t3} onClick={t4} shouldInvokeFns={true} />;
|
||||
$[9] = t3;
|
||||
$[10] = t4;
|
||||
$[11] = t5;
|
||||
} else {
|
||||
t5 = $[11];
|
||||
}
|
||||
items.push(t5);
|
||||
t2 = <>{items}</>;
|
||||
$[1] = data;
|
||||
$[2] = prop1;
|
||||
$[3] = prop2;
|
||||
$[4] = t2;
|
||||
} else {
|
||||
t2 = $[4];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ prop1: "prop1", prop2: "prop2" }],
|
||||
sequentialRenders: [
|
||||
{ prop1: "prop1", prop2: "prop2" },
|
||||
{ prop1: "prop1", prop2: "prop2" },
|
||||
{ prop1: "changed", prop2: "prop2" },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"onClick":{"kind":"Function","result":"value1prop1"},"shouldInvokeFns":true}</div><div>{"onClick":{"kind":"Function","result":"value1prop2"},"shouldInvokeFns":true}</div>
|
||||
<div>{"onClick":{"kind":"Function","result":"value1prop1"},"shouldInvokeFns":true}</div><div>{"onClick":{"kind":"Function","result":"value1prop2"},"shouldInvokeFns":true}</div>
|
||||
<div>{"onClick":{"kind":"Function","result":"value1changed"},"shouldInvokeFns":true}</div><div>{"onClick":{"kind":"Function","result":"value1prop2"},"shouldInvokeFns":true}</div>
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
import {Stringify, useIdentity} from 'shared-runtime';
|
||||
|
||||
function Component({prop1, prop2}) {
|
||||
'use memo';
|
||||
|
||||
const data = useIdentity(
|
||||
new Map([
|
||||
[0, 'value0'],
|
||||
[1, 'value1'],
|
||||
])
|
||||
);
|
||||
let i = 0;
|
||||
const items = [];
|
||||
items.push(
|
||||
<Stringify
|
||||
key={i}
|
||||
onClick={() => data.get(i) + prop1}
|
||||
shouldInvokeFns={true}
|
||||
/>
|
||||
);
|
||||
i = i + 1;
|
||||
items.push(
|
||||
<Stringify
|
||||
key={i}
|
||||
onClick={() => data.get(i) + prop2}
|
||||
shouldInvokeFns={true}
|
||||
/>
|
||||
);
|
||||
return <>{items}</>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{prop1: 'prop1', prop2: 'prop2'}],
|
||||
sequentialRenders: [
|
||||
{prop1: 'prop1', prop2: 'prop2'},
|
||||
{prop1: 'prop1', prop2: 'prop2'},
|
||||
{prop1: 'changed', prop2: 'prop2'},
|
||||
],
|
||||
};
|
||||
+1
-2
@@ -37,8 +37,7 @@ function Component() {
|
||||
}
|
||||
const x = t0;
|
||||
|
||||
let x_0;
|
||||
x_0 = 56;
|
||||
let x_0 = 56;
|
||||
const fn = function () {
|
||||
x_0 = 42;
|
||||
};
|
||||
|
||||
+1
-2
@@ -33,8 +33,7 @@ function component(a) {
|
||||
m(x);
|
||||
};
|
||||
|
||||
let x;
|
||||
x = { a };
|
||||
let x = { a };
|
||||
m(x);
|
||||
$[0] = a;
|
||||
$[1] = y;
|
||||
|
||||
+1
-2
@@ -65,8 +65,7 @@ function useBar(t0, cond) {
|
||||
} else {
|
||||
t1 = $[0];
|
||||
}
|
||||
let x;
|
||||
x = useIdentity(t1);
|
||||
let x = useIdentity(t1);
|
||||
if (cond) {
|
||||
x = b;
|
||||
}
|
||||
|
||||
+1
-2
@@ -47,8 +47,7 @@ function Foo(t0) {
|
||||
if ($[0] !== arr1 || $[1] !== arr2 || $[2] !== foo) {
|
||||
const x = [arr1];
|
||||
|
||||
let y;
|
||||
y = [];
|
||||
let y = [];
|
||||
|
||||
getVal1 = _temp;
|
||||
|
||||
|
||||
+1
-2
@@ -47,8 +47,7 @@ function Foo(t0) {
|
||||
if ($[0] !== arr1 || $[1] !== arr2 || $[2] !== foo) {
|
||||
const x = [arr1];
|
||||
|
||||
let y;
|
||||
y = [];
|
||||
let y = [];
|
||||
let t2;
|
||||
let t3;
|
||||
if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {useState, useEffect} from 'react';
|
||||
import {invoke, Stringify} from 'shared-runtime';
|
||||
|
||||
function Content() {
|
||||
const [announcement, setAnnouncement] = useState('');
|
||||
const [users, setUsers] = useState([{name: 'John Doe'}, {name: 'Jane Doe'}]);
|
||||
|
||||
// This was originally passed down as an onClick, but React Compiler's test
|
||||
// evaluator doesn't yet support events outside of React
|
||||
useEffect(() => {
|
||||
if (users.length === 2) {
|
||||
let removedUserName = '';
|
||||
setUsers(prevUsers => {
|
||||
const newUsers = [...prevUsers];
|
||||
removedUserName = newUsers.at(-1).name;
|
||||
newUsers.pop();
|
||||
return newUsers;
|
||||
});
|
||||
|
||||
setAnnouncement(`Removed user (${removedUserName})`);
|
||||
}
|
||||
}, [users]);
|
||||
|
||||
return <Stringify users={users} announcement={announcement} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Content,
|
||||
params: [{}],
|
||||
sequentialRenders: [{}, {}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { useState, useEffect } from "react";
|
||||
import { invoke, Stringify } from "shared-runtime";
|
||||
|
||||
function Content() {
|
||||
const $ = _c(8);
|
||||
const [announcement, setAnnouncement] = useState("");
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = [{ name: "John Doe" }, { name: "Jane Doe" }];
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
const [users, setUsers] = useState(t0);
|
||||
let t1;
|
||||
if ($[1] !== users.length) {
|
||||
t1 = () => {
|
||||
if (users.length === 2) {
|
||||
let removedUserName = "";
|
||||
setUsers((prevUsers) => {
|
||||
const newUsers = [...prevUsers];
|
||||
removedUserName = newUsers.at(-1).name;
|
||||
newUsers.pop();
|
||||
return newUsers;
|
||||
});
|
||||
|
||||
setAnnouncement(`Removed user (${removedUserName})`);
|
||||
}
|
||||
};
|
||||
$[1] = users.length;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
}
|
||||
let t2;
|
||||
if ($[3] !== users) {
|
||||
t2 = [users];
|
||||
$[3] = users;
|
||||
$[4] = t2;
|
||||
} else {
|
||||
t2 = $[4];
|
||||
}
|
||||
useEffect(t1, t2);
|
||||
let t3;
|
||||
if ($[5] !== announcement || $[6] !== users) {
|
||||
t3 = <Stringify users={users} announcement={announcement} />;
|
||||
$[5] = announcement;
|
||||
$[6] = users;
|
||||
$[7] = t3;
|
||||
} else {
|
||||
t3 = $[7];
|
||||
}
|
||||
return t3;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Content,
|
||||
params: [{}],
|
||||
sequentialRenders: [{}, {}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"users":[{"name":"John Doe"}],"announcement":"Removed user (Jane Doe)"}</div>
|
||||
<div>{"users":[{"name":"John Doe"}],"announcement":"Removed user (Jane Doe)"}</div>
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
import {useState, useEffect} from 'react';
|
||||
import {invoke, Stringify} from 'shared-runtime';
|
||||
|
||||
function Content() {
|
||||
const [announcement, setAnnouncement] = useState('');
|
||||
const [users, setUsers] = useState([{name: 'John Doe'}, {name: 'Jane Doe'}]);
|
||||
|
||||
// This was originally passed down as an onClick, but React Compiler's test
|
||||
// evaluator doesn't yet support events outside of React
|
||||
useEffect(() => {
|
||||
if (users.length === 2) {
|
||||
let removedUserName = '';
|
||||
setUsers(prevUsers => {
|
||||
const newUsers = [...prevUsers];
|
||||
removedUserName = newUsers.at(-1).name;
|
||||
newUsers.pop();
|
||||
return newUsers;
|
||||
});
|
||||
|
||||
setAnnouncement(`Removed user (${removedUserName})`);
|
||||
}
|
||||
}, [users]);
|
||||
|
||||
return <Stringify users={users} announcement={announcement} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Content,
|
||||
params: [{}],
|
||||
sequentialRenders: [{}, {}],
|
||||
};
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function useHook(nodeID, condition) {
|
||||
const graph = useContext(GraphContext);
|
||||
const node = nodeID != null ? graph[nodeID] : null;
|
||||
|
||||
for (const key of Object.keys(node?.fields ?? {})) {
|
||||
if (condition) {
|
||||
return new Class(node.fields?.[field]);
|
||||
}
|
||||
}
|
||||
return new Class();
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function useHook(nodeID, condition) {
|
||||
const $ = _c(7);
|
||||
const graph = useContext(GraphContext);
|
||||
const node = nodeID != null ? graph[nodeID] : null;
|
||||
let t0;
|
||||
if ($[0] !== node?.fields) {
|
||||
t0 = Object.keys(node?.fields ?? {});
|
||||
$[0] = node?.fields;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
let t1;
|
||||
if ($[2] !== condition || $[3] !== node || $[4] !== t0) {
|
||||
t1 = Symbol.for("react.early_return_sentinel");
|
||||
bb0: for (const key of t0) {
|
||||
if (condition) {
|
||||
t1 = new Class(node.fields?.[field]);
|
||||
break bb0;
|
||||
}
|
||||
}
|
||||
$[2] = condition;
|
||||
$[3] = node;
|
||||
$[4] = t0;
|
||||
$[5] = t1;
|
||||
} else {
|
||||
t1 = $[5];
|
||||
}
|
||||
if (t1 !== Symbol.for("react.early_return_sentinel")) {
|
||||
return t1;
|
||||
}
|
||||
let t2;
|
||||
if ($[6] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t2 = new Class();
|
||||
$[6] = t2;
|
||||
} else {
|
||||
t2 = $[6];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
function useHook(nodeID, condition) {
|
||||
const graph = useContext(GraphContext);
|
||||
const node = nodeID != null ? graph[nodeID] : null;
|
||||
|
||||
for (const key of Object.keys(node?.fields ?? {})) {
|
||||
if (condition) {
|
||||
return new Class(node.fields?.[field]);
|
||||
}
|
||||
}
|
||||
return new Class();
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Example showing that returned inner function expressions should not be
|
||||
* typed with `freeze` effects.
|
||||
*/
|
||||
function Foo({a, b}) {
|
||||
'use memo';
|
||||
const obj = {};
|
||||
const updaterFactory = () => {
|
||||
/**
|
||||
* This returned function expression *is* a local value. But it might (1)
|
||||
* capture and mutate its context environment and (2) be called during
|
||||
* render.
|
||||
* Typing it with `freeze` effects would be incorrect as it would mean
|
||||
* inferring that calls to updaterFactory()() do not mutate its captured
|
||||
* context.
|
||||
*/
|
||||
return newValue => {
|
||||
obj.value = newValue;
|
||||
obj.a = a;
|
||||
};
|
||||
};
|
||||
|
||||
const updater = updaterFactory();
|
||||
updater(b);
|
||||
return <Stringify cb={obj} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{a: 1, b: 2}],
|
||||
sequentialRenders: [
|
||||
{a: 1, b: 2},
|
||||
{a: 1, b: 3},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Example showing that returned inner function expressions should not be
|
||||
* typed with `freeze` effects.
|
||||
*/
|
||||
function Foo(t0) {
|
||||
"use memo";
|
||||
const $ = _c(3);
|
||||
const { a, b } = t0;
|
||||
let t1;
|
||||
if ($[0] !== a || $[1] !== b) {
|
||||
const obj = {};
|
||||
const updaterFactory = () => (newValue) => {
|
||||
obj.value = newValue;
|
||||
obj.a = a;
|
||||
};
|
||||
|
||||
const updater = updaterFactory();
|
||||
updater(b);
|
||||
t1 = <Stringify cb={obj} shouldInvokeFns={true} />;
|
||||
$[0] = a;
|
||||
$[1] = b;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{ a: 1, b: 2 }],
|
||||
sequentialRenders: [
|
||||
{ a: 1, b: 2 },
|
||||
{ a: 1, b: 3 },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"cb":{"value":2,"a":1},"shouldInvokeFns":true}</div>
|
||||
<div>{"cb":{"value":3,"a":1},"shouldInvokeFns":true}</div>
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Example showing that returned inner function expressions should not be
|
||||
* typed with `freeze` effects.
|
||||
*/
|
||||
function Foo({a, b}) {
|
||||
'use memo';
|
||||
const obj = {};
|
||||
const updaterFactory = () => {
|
||||
/**
|
||||
* This returned function expression *is* a local value. But it might (1)
|
||||
* capture and mutate its context environment and (2) be called during
|
||||
* render.
|
||||
* Typing it with `freeze` effects would be incorrect as it would mean
|
||||
* inferring that calls to updaterFactory()() do not mutate its captured
|
||||
* context.
|
||||
*/
|
||||
return newValue => {
|
||||
obj.value = newValue;
|
||||
obj.a = a;
|
||||
};
|
||||
};
|
||||
|
||||
const updater = updaterFactory();
|
||||
updater(b);
|
||||
return <Stringify cb={obj} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{a: 1, b: 2}],
|
||||
sequentialRenders: [
|
||||
{a: 1, b: 2},
|
||||
{a: 1, b: 3},
|
||||
],
|
||||
};
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {makeArray, Stringify, useIdentity} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Example showing that returned inner function expressions should not be
|
||||
* typed with `freeze` effects.
|
||||
* Also see repro-returned-inner-fn-mutates-context
|
||||
*/
|
||||
function Foo({b}) {
|
||||
'use memo';
|
||||
|
||||
const fnFactory = () => {
|
||||
/**
|
||||
* This returned function expression *is* a local value. But it might (1)
|
||||
* capture and mutate its context environment and (2) be called during
|
||||
* render.
|
||||
* Typing it with `freeze` effects would be incorrect as it would mean
|
||||
* inferring that calls to updaterFactory()() do not mutate its captured
|
||||
* context.
|
||||
*/
|
||||
return () => {
|
||||
myVar = () => console.log('a');
|
||||
};
|
||||
};
|
||||
let myVar = () => console.log('b');
|
||||
useIdentity();
|
||||
|
||||
const fn = fnFactory();
|
||||
const arr = makeArray(b);
|
||||
fn(arr);
|
||||
return <Stringify cb={myVar} value={arr} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{b: 1}],
|
||||
sequentialRenders: [{b: 1}, {b: 2}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { makeArray, Stringify, useIdentity } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Example showing that returned inner function expressions should not be
|
||||
* typed with `freeze` effects.
|
||||
* Also see repro-returned-inner-fn-mutates-context
|
||||
*/
|
||||
function Foo(t0) {
|
||||
"use memo";
|
||||
const $ = _c(3);
|
||||
const { b } = t0;
|
||||
|
||||
const fnFactory = () => () => {
|
||||
myVar = _temp;
|
||||
};
|
||||
|
||||
let myVar = _temp2;
|
||||
useIdentity();
|
||||
|
||||
const fn = fnFactory();
|
||||
const arr = makeArray(b);
|
||||
fn(arr);
|
||||
let t1;
|
||||
if ($[0] !== arr || $[1] !== myVar) {
|
||||
t1 = <Stringify cb={myVar} value={arr} shouldInvokeFns={true} />;
|
||||
$[0] = arr;
|
||||
$[1] = myVar;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
function _temp2() {
|
||||
return console.log("b");
|
||||
}
|
||||
function _temp() {
|
||||
return console.log("a");
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{ b: 1 }],
|
||||
sequentialRenders: [{ b: 1 }, { b: 2 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"cb":{"kind":"Function"},"value":[1],"shouldInvokeFns":true}</div>
|
||||
<div>{"cb":{"kind":"Function"},"value":[2],"shouldInvokeFns":true}</div>
|
||||
logs: ['a','a']
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import {makeArray, Stringify, useIdentity} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Example showing that returned inner function expressions should not be
|
||||
* typed with `freeze` effects.
|
||||
* Also see repro-returned-inner-fn-mutates-context
|
||||
*/
|
||||
function Foo({b}) {
|
||||
'use memo';
|
||||
|
||||
const fnFactory = () => {
|
||||
/**
|
||||
* This returned function expression *is* a local value. But it might (1)
|
||||
* capture and mutate its context environment and (2) be called during
|
||||
* render.
|
||||
* Typing it with `freeze` effects would be incorrect as it would mean
|
||||
* inferring that calls to updaterFactory()() do not mutate its captured
|
||||
* context.
|
||||
*/
|
||||
return () => {
|
||||
myVar = () => console.log('a');
|
||||
};
|
||||
};
|
||||
let myVar = () => console.log('b');
|
||||
useIdentity();
|
||||
|
||||
const fn = fnFactory();
|
||||
const arr = makeArray(b);
|
||||
fn(arr);
|
||||
return <Stringify cb={myVar} value={arr} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{b: 1}],
|
||||
sequentialRenders: [{b: 1}, {b: 2}],
|
||||
};
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function useHook(nodeID, condition) {
|
||||
const graph = useContext(GraphContext);
|
||||
const node = nodeID != null ? graph[nodeID] : null;
|
||||
|
||||
// (2) Instead we can create a scope around the loop since the loop produces an escaping value
|
||||
let value;
|
||||
for (const key of Object.keys(node?.fields ?? {})) {
|
||||
if (condition) {
|
||||
// (1) We currently create a scope just for this instruction, then later prune the scope because
|
||||
// it's inside a loop
|
||||
value = new Class(node.fields?.[field]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function useHook(nodeID, condition) {
|
||||
const $ = _c(6);
|
||||
const graph = useContext(GraphContext);
|
||||
const node = nodeID != null ? graph[nodeID] : null;
|
||||
|
||||
let value;
|
||||
let t0;
|
||||
if ($[0] !== node?.fields) {
|
||||
t0 = Object.keys(node?.fields ?? {});
|
||||
$[0] = node?.fields;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
if ($[2] !== condition || $[3] !== node || $[4] !== t0) {
|
||||
for (const key of t0) {
|
||||
if (condition) {
|
||||
value = new Class(node.fields?.[field]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
$[2] = condition;
|
||||
$[3] = node;
|
||||
$[4] = t0;
|
||||
$[5] = value;
|
||||
} else {
|
||||
value = $[5];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
function useHook(nodeID, condition) {
|
||||
const graph = useContext(GraphContext);
|
||||
const node = nodeID != null ? graph[nodeID] : null;
|
||||
|
||||
// (2) Instead we can create a scope around the loop since the loop produces an escaping value
|
||||
let value;
|
||||
for (const key of Object.keys(node?.fields ?? {})) {
|
||||
if (condition) {
|
||||
// (1) We currently create a scope just for this instruction, then later prune the scope because
|
||||
// it's inside a loop
|
||||
value = new Class(node.fields?.[field]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
/**
|
||||
* Example of a function expression whose return value shouldn't have
|
||||
* a "freeze" effect on all operands.
|
||||
*
|
||||
* This is because the function expression is passed to `useEffect` and
|
||||
* thus is not a render function. `cleanedUp` is also created within
|
||||
* the effect and is not a render variable.
|
||||
*/
|
||||
function Component({prop}) {
|
||||
const [cleanupCount, setCleanupCount] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
let cleanedUp = false;
|
||||
setTimeout(() => {
|
||||
if (!cleanedUp) {
|
||||
cleanedUp = true;
|
||||
setCleanupCount(c => c + 1);
|
||||
}
|
||||
}, 0);
|
||||
// This return value should not have freeze effects
|
||||
// on its operands
|
||||
return () => {
|
||||
if (!cleanedUp) {
|
||||
cleanedUp = true;
|
||||
setCleanupCount(c => c + 1);
|
||||
}
|
||||
};
|
||||
}, [prop]);
|
||||
return <div>{cleanupCount}</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{prop: 5}],
|
||||
sequentialRenders: [{prop: 5}, {prop: 5}, {prop: 6}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
/**
|
||||
* Example of a function expression whose return value shouldn't have
|
||||
* a "freeze" effect on all operands.
|
||||
*
|
||||
* This is because the function expression is passed to `useEffect` and
|
||||
* thus is not a render function. `cleanedUp` is also created within
|
||||
* the effect and is not a render variable.
|
||||
*/
|
||||
function Component(t0) {
|
||||
const $ = _c(5);
|
||||
const { prop } = t0;
|
||||
const [cleanupCount, setCleanupCount] = useState(0);
|
||||
let t1;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = () => {
|
||||
let cleanedUp = false;
|
||||
setTimeout(() => {
|
||||
if (!cleanedUp) {
|
||||
cleanedUp = true;
|
||||
setCleanupCount(_temp);
|
||||
}
|
||||
}, 0);
|
||||
return () => {
|
||||
if (!cleanedUp) {
|
||||
cleanedUp = true;
|
||||
setCleanupCount(_temp2);
|
||||
}
|
||||
};
|
||||
};
|
||||
$[0] = t1;
|
||||
} else {
|
||||
t1 = $[0];
|
||||
}
|
||||
let t2;
|
||||
if ($[1] !== prop) {
|
||||
t2 = [prop];
|
||||
$[1] = prop;
|
||||
$[2] = t2;
|
||||
} else {
|
||||
t2 = $[2];
|
||||
}
|
||||
useEffect(t1, t2);
|
||||
let t3;
|
||||
if ($[3] !== cleanupCount) {
|
||||
t3 = <div>{cleanupCount}</div>;
|
||||
$[3] = cleanupCount;
|
||||
$[4] = t3;
|
||||
} else {
|
||||
t3 = $[4];
|
||||
}
|
||||
return t3;
|
||||
}
|
||||
function _temp2(c_0) {
|
||||
return c_0 + 1;
|
||||
}
|
||||
function _temp(c) {
|
||||
return c + 1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ prop: 5 }],
|
||||
sequentialRenders: [{ prop: 5 }, { prop: 5 }, { prop: 6 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>0</div>
|
||||
<div>0</div>
|
||||
<div>1</div>
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
/**
|
||||
* Example of a function expression whose return value shouldn't have
|
||||
* a "freeze" effect on all operands.
|
||||
*
|
||||
* This is because the function expression is passed to `useEffect` and
|
||||
* thus is not a render function. `cleanedUp` is also created within
|
||||
* the effect and is not a render variable.
|
||||
*/
|
||||
function Component({prop}) {
|
||||
const [cleanupCount, setCleanupCount] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
let cleanedUp = false;
|
||||
setTimeout(() => {
|
||||
if (!cleanedUp) {
|
||||
cleanedUp = true;
|
||||
setCleanupCount(c => c + 1);
|
||||
}
|
||||
}, 0);
|
||||
// This return value should not have freeze effects
|
||||
// on its operands
|
||||
return () => {
|
||||
if (!cleanedUp) {
|
||||
cleanedUp = true;
|
||||
setCleanupCount(c => c + 1);
|
||||
}
|
||||
};
|
||||
}, [prop]);
|
||||
return <div>{cleanupCount}</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{prop: 5}],
|
||||
sequentialRenders: [{prop: 5}, {prop: 5}, {prop: 6}],
|
||||
};
|
||||
+1
-2
@@ -79,8 +79,7 @@ function Component(props) {
|
||||
|
||||
function Inner(props) {
|
||||
const $ = _c(7);
|
||||
let input;
|
||||
input = null;
|
||||
let input = null;
|
||||
if (props.cond) {
|
||||
input = use(FooContext);
|
||||
}
|
||||
|
||||
@@ -17,13 +17,22 @@
|
||||
"@babel/parser": "^7.26",
|
||||
"@babel/plugin-syntax-typescript": "^7.25.9",
|
||||
"@modelcontextprotocol/sdk": "^1.9.0",
|
||||
"@types/jest": "^29.5.14",
|
||||
"algoliasearch": "^5.23.3",
|
||||
"cheerio": "^1.0.0",
|
||||
"html-to-text": "^9.0.5",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.3.3",
|
||||
"puppeteer": "^24.7.2",
|
||||
"ts-jest": "^29.3.2",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@babel/plugin-transform-runtime": "^7.26.10",
|
||||
"@babel/preset-env": "^7.26.9",
|
||||
"@babel/preset-react": "^7.26.3",
|
||||
"@babel/preset-typescript": "^7.27.0",
|
||||
"@types/html-to-text": "^9.0.4"
|
||||
},
|
||||
"license": "MIT",
|
||||
|
||||
@@ -20,6 +20,7 @@ import * as cheerio from 'cheerio';
|
||||
import {queryAlgolia} from './utils/algolia';
|
||||
import assertExhaustive from './utils/assertExhaustive';
|
||||
import {convert} from 'html-to-text';
|
||||
import {measurePerformance} from './utils/runtimePerf';
|
||||
|
||||
const server = new McpServer({
|
||||
name: 'React',
|
||||
@@ -353,6 +354,110 @@ Server Components - Shift data-heavy logic to the server whenever possible. Brea
|
||||
],
|
||||
}));
|
||||
|
||||
server.tool(
|
||||
'review-react-runtime',
|
||||
`
|
||||
Run this tool every time you propose a performance related change to verify if your suggestion actually improves performance.
|
||||
|
||||
This tool has some requirements on the code input:
|
||||
- The react code that is passed into this tool MUST contain an App functional component without arrow function.
|
||||
- DO NOT export anything since we can't parse export syntax with this tool.
|
||||
- Only import React from 'react' and use all hooks and imports using the React. prefix like React.useState and React.useEffect
|
||||
`,
|
||||
{
|
||||
text: z.string(),
|
||||
},
|
||||
async ({text}) => {
|
||||
try {
|
||||
const iterations = 20;
|
||||
|
||||
let perfData = {
|
||||
renderTime: 0,
|
||||
webVitals: {
|
||||
cls: 0,
|
||||
lcp: 0,
|
||||
inp: 0,
|
||||
fid: 0,
|
||||
ttfb: 0,
|
||||
},
|
||||
reactProfilerMetrics: {
|
||||
id: 0,
|
||||
phase: 0,
|
||||
actualDuration: 0,
|
||||
baseDuration: 0,
|
||||
startTime: 0,
|
||||
commitTime: 0,
|
||||
},
|
||||
error: null,
|
||||
};
|
||||
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
const performanceResults = await measurePerformance(text);
|
||||
perfData.renderTime += performanceResults.renderTime;
|
||||
perfData.webVitals.cls += performanceResults.webVitals.cls || 0;
|
||||
perfData.webVitals.lcp += performanceResults.webVitals.lcp || 0;
|
||||
perfData.webVitals.inp += performanceResults.webVitals.inp || 0;
|
||||
perfData.webVitals.fid += performanceResults.webVitals.fid || 0;
|
||||
perfData.webVitals.ttfb += performanceResults.webVitals.ttfb || 0;
|
||||
|
||||
perfData.reactProfilerMetrics.id +=
|
||||
performanceResults.reactProfilerMetrics.actualDuration || 0;
|
||||
perfData.reactProfilerMetrics.phase +=
|
||||
performanceResults.reactProfilerMetrics.phase || 0;
|
||||
perfData.reactProfilerMetrics.actualDuration +=
|
||||
performanceResults.reactProfilerMetrics.actualDuration || 0;
|
||||
perfData.reactProfilerMetrics.baseDuration +=
|
||||
performanceResults.reactProfilerMetrics.baseDuration || 0;
|
||||
perfData.reactProfilerMetrics.startTime +=
|
||||
performanceResults.reactProfilerMetrics.startTime || 0;
|
||||
perfData.reactProfilerMetrics.commitTime +=
|
||||
performanceResults.reactProfilerMetrics.commitTime || 0;
|
||||
}
|
||||
|
||||
const formattedResults = `
|
||||
# React Component Performance Results
|
||||
|
||||
## Mean Render Time
|
||||
${perfData.renderTime / iterations}ms
|
||||
|
||||
## Mean Web Vitals
|
||||
- Cumulative Layout Shift (CLS): ${perfData.webVitals.cls / iterations}
|
||||
- Largest Contentful Paint (LCP): ${perfData.webVitals.lcp / iterations}ms
|
||||
- Interaction to Next Paint (INP): ${perfData.webVitals.inp / iterations}ms
|
||||
- First Input Delay (FID): ${perfData.webVitals.fid / iterations}ms
|
||||
- Time to First Byte (TTFB): ${perfData.webVitals.ttfb / iterations}ms
|
||||
|
||||
## Mean React Profiler
|
||||
- Actual Duration: ${perfData.reactProfilerMetrics.actualDuration / iterations}ms
|
||||
- Base Duration: ${perfData.reactProfilerMetrics.baseDuration / iterations}ms
|
||||
- Start Time: ${perfData.reactProfilerMetrics.startTime / iterations}ms
|
||||
- Commit Time: ${perfData.reactProfilerMetrics.commitTime / iterations}ms
|
||||
|
||||
These metrics can help you evaluate the performance of your React component. Lower values generally indicate better performance.
|
||||
`;
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text' as const,
|
||||
text: formattedResults,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
isError: true,
|
||||
content: [
|
||||
{
|
||||
type: 'text' as const,
|
||||
text: `Error measuring performance: ${error.message}\n\n${error.stack}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
async function main() {
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
import * as babel from '@babel/core';
|
||||
import puppeteer from 'puppeteer';
|
||||
|
||||
export async function measurePerformance(code: string) {
|
||||
const babelOptions = {
|
||||
configFile: false,
|
||||
babelrc: false,
|
||||
presets: [
|
||||
require.resolve('@babel/preset-env'),
|
||||
require.resolve('@babel/preset-react'),
|
||||
],
|
||||
};
|
||||
|
||||
// Parse the code to AST
|
||||
const parsed = await babel.parseAsync(code, babelOptions);
|
||||
if (!parsed) {
|
||||
throw new Error('Failed to parse code');
|
||||
}
|
||||
|
||||
// Transform AST to browser-compatible JavaScript
|
||||
const transformResult = await babel.transformFromAstAsync(parsed, undefined, {
|
||||
...babelOptions,
|
||||
filename: 'file.jsx',
|
||||
plugins: [
|
||||
() => ({
|
||||
visitor: {
|
||||
ImportDeclaration(
|
||||
path: babel.NodePath<babel.types.ImportDeclaration>,
|
||||
) {
|
||||
const value = path.node.source.value;
|
||||
if (value === 'react' || value === 'react-dom') {
|
||||
path.remove();
|
||||
}
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const transpiled = transformResult?.code || undefined;
|
||||
if (!transpiled) {
|
||||
throw new Error('Failed to transpile code');
|
||||
}
|
||||
|
||||
const browser = await puppeteer.launch();
|
||||
|
||||
const page = await browser.newPage();
|
||||
await page.setViewport({width: 1280, height: 720});
|
||||
const html = buildHtml(transpiled);
|
||||
await page.setContent(html, {waitUntil: 'networkidle0'});
|
||||
|
||||
await page.waitForFunction(
|
||||
'window.__RESULT__ !== undefined && (window.__RESULT__.renderTime !== null || window.__RESULT__.error !== null)',
|
||||
);
|
||||
|
||||
const result = await page.evaluate(() => {
|
||||
return (window as any).__RESULT__;
|
||||
});
|
||||
|
||||
await browser.close();
|
||||
return result;
|
||||
}
|
||||
|
||||
function buildHtml(transpiled: string) {
|
||||
const html = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>React Performance Test</title>
|
||||
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
|
||||
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
|
||||
<script src="https://unpkg.com/web-vitals@3.0.0/dist/web-vitals.iife.js"></script>
|
||||
<style>
|
||||
body { margin: 0; }
|
||||
#root { padding: 20px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script>
|
||||
window.__RESULT__ = {
|
||||
renderTime: null,
|
||||
webVitals: {},
|
||||
reactProfilerMetrics: {},
|
||||
error: null
|
||||
};
|
||||
|
||||
webVitals.onCLS((metric) => { window.__RESULT__.webVitals.cls = metric; });
|
||||
webVitals.onLCP((metric) => { window.__RESULT__.webVitals.lcp = metric; });
|
||||
webVitals.onINP((metric) => { window.__RESULT__.webVitals.inp = metric; });
|
||||
webVitals.onFID((metric) => { window.__RESULT__.webVitals.fid = metric; });
|
||||
webVitals.onTTFB((metric) => { window.__RESULT__.webVitals.ttfb = metric; });
|
||||
|
||||
try {
|
||||
${transpiled}
|
||||
|
||||
window.App = App;
|
||||
|
||||
// Render the component to the DOM with profiling
|
||||
const AppComponent = window.App || (() => React.createElement('div', null, 'No App component exported'));
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'), {
|
||||
onUncaughtError: (error, errorInfo) => {
|
||||
window.__RESULT__.error = error;
|
||||
}
|
||||
});
|
||||
|
||||
const renderStart = performance.now()
|
||||
|
||||
root.render(
|
||||
React.createElement(React.Profiler, {
|
||||
id: 'App',
|
||||
onRender: (id, phase, actualDuration, baseDuration, startTime, commitTime) => {
|
||||
window.__RESULT__.reactProfilerMetrics.id = id;
|
||||
window.__RESULT__.reactProfilerMetrics.phase = phase;
|
||||
window.__RESULT__.reactProfilerMetrics.actualDuration = actualDuration;
|
||||
window.__RESULT__.reactProfilerMetrics.baseDuration = baseDuration;
|
||||
window.__RESULT__.reactProfilerMetrics.startTime = startTime;
|
||||
window.__RESULT__.reactProfilerMetrics.commitTime = commitTime;
|
||||
}
|
||||
}, React.createElement(AppComponent))
|
||||
);
|
||||
|
||||
const renderEnd = performance.now();
|
||||
|
||||
window.__RESULT__.renderTime = renderEnd - renderStart;
|
||||
} catch (error) {
|
||||
console.error('Error rendering component:', error);
|
||||
window.__RESULT__.error = {
|
||||
message: error.message,
|
||||
stack: error.stack
|
||||
};
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
window.onerror = function(message, url, lineNumber) {
|
||||
window.__RESULT__.error = message;
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
return html;
|
||||
}
|
||||
+2827
-2523
File diff suppressed because it is too large
Load Diff
@@ -19,7 +19,7 @@ class ThrottledWritable extends Writable {
|
||||
constructor(destination) {
|
||||
super();
|
||||
this.destination = destination;
|
||||
this.delay = 150;
|
||||
this.delay = 10;
|
||||
}
|
||||
|
||||
_write(chunk, encoding, callback) {
|
||||
@@ -49,10 +49,10 @@ export default function render(url, res) {
|
||||
// Log fatal errors
|
||||
console.error('Fatal', error);
|
||||
});
|
||||
console.log('hello');
|
||||
let didError = false;
|
||||
const {pipe, abort} = renderToPipeableStream(<App assets={assets} />, {
|
||||
bootstrapScripts: [assets['main.js']],
|
||||
progressiveChunkSize: 1024,
|
||||
onShellReady() {
|
||||
// If something errored before we started streaming, we set the error code appropriately.
|
||||
res.statusCode = didError ? 500 : 200;
|
||||
|
||||
@@ -39,11 +39,7 @@ export default class Chrome extends Component {
|
||||
{this.props.children}
|
||||
</Theme.Provider>
|
||||
</Suspense>
|
||||
<p>This should appear in the first paint.</p>
|
||||
<Suspense fallback="Loading...">
|
||||
<p>This content should not block paint.</p>
|
||||
<LargeContent />
|
||||
</Suspense>
|
||||
<LargeContent />
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `assetManifest = ${JSON.stringify(assets)};`,
|
||||
|
||||
@@ -1,243 +1,291 @@
|
||||
import React, {Fragment} from 'react';
|
||||
import React, {Fragment, Suspense} from 'react';
|
||||
|
||||
export default function LargeContent() {
|
||||
return (
|
||||
<Fragment>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris
|
||||
porttitor tortor ac lectus faucibus, eget eleifend elit hendrerit.
|
||||
Integer porttitor nisi in leo congue rutrum. Morbi sed ante posuere,
|
||||
aliquam lorem ac, imperdiet orci. Duis malesuada gravida pharetra. Cras
|
||||
facilisis arcu diam, id dictum lorem imperdiet a. Suspendisse aliquet
|
||||
tempus tortor et ultricies. Aliquam libero velit, posuere tempus ante
|
||||
sed, pellentesque tincidunt lorem. Nullam iaculis, eros a varius
|
||||
aliquet, tortor felis tempor metus, nec cursus felis eros aliquam nulla.
|
||||
Vivamus ut orci sed mauris congue lacinia. Cras eget blandit neque.
|
||||
Pellentesque a massa in turpis ullamcorper volutpat vel at massa. Sed
|
||||
ante est, auctor non diam non, vulputate ultrices metus. Maecenas dictum
|
||||
fermentum quam id aliquam. Donec porta risus vitae pretium posuere.
|
||||
Fusce facilisis eros in lacus tincidunt congue.
|
||||
</p>
|
||||
<p>
|
||||
Pellentesque habitant morbi tristique senectus et netus et malesuada
|
||||
fames ac turpis egestas. Phasellus dolor ante, iaculis vel nisl vitae,
|
||||
ornare ornare orci. Praesent sit amet lobortis sapien. Suspendisse
|
||||
pharetra posuere libero ut dapibus. Donec condimentum ante urna. Aliquam
|
||||
laoreet tincidunt lacus, sed interdum tortor dapibus elementum. Nam sed
|
||||
faucibus lorem. Suspendisse finibus, velit sed molestie finibus, risus
|
||||
purus mollis ante, sit amet aliquet sapien nulla ut nibh. In eget ligula
|
||||
metus. Duis in purus mattis, blandit magna nec, dictum nunc.
|
||||
</p>
|
||||
<p>
|
||||
Sed convallis magna id tortor blandit dictum. Suspendisse in porttitor
|
||||
neque. Integer quis metus consequat, rutrum est sit amet, finibus justo.
|
||||
In hac habitasse platea dictumst. Nullam sagittis, risus sed vehicula
|
||||
porta, sapien elit ultrices nibh, vel luctus odio tortor et ante. Sed
|
||||
porta enim in hendrerit tristique. Pellentesque id feugiat libero, sit
|
||||
amet tempor enim. Proin gravida nisl justo, vel ornare dolor bibendum
|
||||
ac. Mauris scelerisque mattis facilisis. Praesent sodales augue mollis
|
||||
orci vulputate aliquet. Mauris molestie luctus neque, sed congue elit
|
||||
congue ut. Cras quis tortor augue. In auctor nulla vel turpis dapibus
|
||||
egestas. Phasellus consequat rhoncus nisi sed dignissim. Quisque varius
|
||||
justo non ex lobortis finibus cursus nec justo. Nulla erat neque,
|
||||
commodo et sem convallis, tristique faucibus odio.
|
||||
</p>
|
||||
<p>
|
||||
Ut condimentum volutpat sem, id accumsan augue placerat vel. Donec ac
|
||||
efficitur turpis. Suspendisse pretium odio euismod sapien bibendum, sed
|
||||
tempus est condimentum. Etiam nisl magna, consequat at ullamcorper at,
|
||||
sollicitudin eu eros. In mattis ligula arcu. Sed eu consectetur turpis,
|
||||
id molestie ligula. Vestibulum et venenatis enim. Donec condimentum
|
||||
vitae nisi et placerat. Sed fringilla vehicula egestas. Proin
|
||||
consectetur, nibh non ornare scelerisque, diam lorem cursus lectus, ut
|
||||
mattis mauris purus id mi. Curabitur non ligula sit amet augue molestie
|
||||
vulputate. Donec maximus magna at volutpat aliquet. Pellentesque
|
||||
dignissim nulla eget odio eleifend tincidunt. Etiam diam lorem, ornare
|
||||
vel scelerisque vel, iaculis id risus. Donec aliquet aliquam felis, ac
|
||||
vehicula lacus suscipit vitae. Morbi eu ligula elit.
|
||||
</p>
|
||||
<p>
|
||||
Praesent pellentesque, libero ut faucibus tempor, purus elit consequat
|
||||
metus, in ornare nulla lectus at erat. Duis quis blandit turpis. Fusce
|
||||
at ligula rutrum metus molestie tempor sit amet eu justo. Maecenas
|
||||
tincidunt nisl nunc. Morbi ac metus tempor, pretium arcu vel, dapibus
|
||||
velit. Nulla convallis ligula at porta mollis. Duis magna ante, mollis
|
||||
eget nibh in, congue tempor dolor. Sed tincidunt sagittis arcu, in
|
||||
ultricies neque tempor non. Suspendisse eget nunc neque. Nulla sit amet
|
||||
odio volutpat, maximus purus id, dictum metus. Integer consequat, orci
|
||||
nec ullamcorper porta, mauris libero vestibulum ipsum, nec tempor tellus
|
||||
enim non nunc. Quisque nisl risus, dapibus sit amet purus nec, aliquam
|
||||
finibus metus. Nullam condimentum urna viverra finibus cursus. Proin et
|
||||
sollicitudin tellus, porta fermentum felis. Maecenas ac turpis sed dui
|
||||
condimentum interdum sed sed erat. Mauris ut dignissim erat.
|
||||
</p>
|
||||
<p>
|
||||
Proin varius porta dui, id fringilla elit lobortis eget. Integer at
|
||||
metus elementum, efficitur eros id, euismod est. Morbi vestibulum nibh
|
||||
ac leo luctus sagittis. Praesent rhoncus, risus sit amet mattis dictum,
|
||||
diam sapien tempor neque, vel dignissim nulla neque eget ex. Nam
|
||||
sollicitudin metus quis ullamcorper dapibus. Nam tristique euismod
|
||||
efficitur. Pellentesque rhoncus vel sem eget lacinia. Pellentesque
|
||||
volutpat velit ac dignissim luctus. Vivamus euismod tortor at ligula
|
||||
mattis porta. Vestibulum ante ipsum primis in faucibus orci luctus et
|
||||
ultrices posuere cubilia curae;
|
||||
</p>
|
||||
<p>
|
||||
Proin blandit vulputate efficitur. Pellentesque sit amet porta odio.
|
||||
Nunc pulvinar varius rhoncus. Mauris fermentum leo a imperdiet pretium.
|
||||
Mauris scelerisque justo vel ante egestas, eget tempus neque malesuada.
|
||||
Sed dictum ex vel justo dignissim, aliquam commodo diam rutrum. Integer
|
||||
dignissim est ullamcorper augue laoreet consectetur id at diam. Vivamus
|
||||
molestie blandit urna, eget pulvinar augue dictum vestibulum. Duis
|
||||
maximus bibendum mauris, ut ultricies elit rhoncus eu. Praesent gravida
|
||||
placerat mauris. Praesent tempor ipsum at nibh rhoncus sagittis. Duis
|
||||
non sem turpis. Quisque et metus leo. Sed eu purus lorem. Pellentesque
|
||||
dictum metus sed leo viverra interdum. Maecenas vel tincidunt mi.
|
||||
</p>
|
||||
<p>
|
||||
Praesent consequat dapibus pellentesque. Fusce at enim id mauris laoreet
|
||||
commodo. Nullam ut mauris euismod, rhoncus tellus vel, facilisis diam.
|
||||
Aenean porta faucibus augue, a iaculis massa iaculis in. Praesent vel
|
||||
metus purus. Etiam quis augue eget orci lobortis eleifend ac ut lorem.
|
||||
Aenean non orci quis nisi molestie maximus. Mauris interdum, eros et
|
||||
aliquam aliquam, lectus diam pharetra velit, in condimentum odio eros
|
||||
non quam. Praesent bibendum pretium turpis vitae tristique. Mauris
|
||||
convallis, massa ut fermentum fermentum, libero orci tempus ipsum,
|
||||
malesuada ultrices metus sapien placerat lectus. Ut fringilla arcu nec
|
||||
lorem ultrices mattis. Etiam id tortor feugiat magna gravida gravida.
|
||||
Morbi aliquam, mi ac pellentesque mattis, erat ex venenatis erat, a
|
||||
vestibulum eros turpis quis metus. Pellentesque tempus justo in ligula
|
||||
ultricies porta. Phasellus congue felis sit amet dolor tristique
|
||||
finibus. Nunc eget eros non est ultricies vestibulum.
|
||||
</p>
|
||||
<p>
|
||||
Donec efficitur ligula quis odio tincidunt tristique. Duis urna dolor,
|
||||
hendrerit quis enim at, accumsan auctor turpis. Vivamus ante lorem,
|
||||
maximus vitae suscipit ut, congue eget velit. Maecenas sed ligula erat.
|
||||
Aliquam mollis purus at nisi porta suscipit in ut magna. Vivamus a
|
||||
turpis nec tellus egestas suscipit nec ornare nisi. Donec vestibulum
|
||||
libero quis ex suscipit, sit amet luctus leo gravida.
|
||||
</p>
|
||||
<p>
|
||||
Praesent pharetra dolor elit, sed volutpat lorem rhoncus non. Etiam a
|
||||
neque ut velit dignissim sodales. Vestibulum neque risus, condimentum
|
||||
nec consectetur vitae, ultricies ut sapien. Integer iaculis at urna sit
|
||||
amet malesuada. Integer tincidunt, felis ac vulputate semper, velit leo
|
||||
facilisis lorem, quis aliquet leo dui id lorem. Morbi non quam quis nisl
|
||||
sagittis consequat nec vitae libero. Nunc molestie pretium libero, eu
|
||||
eleifend nibh feugiat sed. Ut in bibendum diam, sit amet vehicula risus.
|
||||
Nam ornare ac nisi ac euismod. Nullam id egestas nulla. Etiam porta
|
||||
commodo ante sit amet pellentesque. Suspendisse eleifend purus in urna
|
||||
euismod auctor non vel nisi. Suspendisse rutrum est nunc, sit amet
|
||||
lacinia lacus dictum eget. Pellentesque habitant morbi tristique
|
||||
senectus et netus et malesuada fames ac turpis egestas. Morbi a blandit
|
||||
diam.
|
||||
</p>
|
||||
<p>
|
||||
Donec eget efficitur sapien. Suspendisse diam lacus, varius eu interdum
|
||||
et, congue ac justo. Proin ipsum odio, suscipit elementum mauris sed,
|
||||
porttitor congue est. Cras dapibus dictum ante, vitae gravida elit
|
||||
venenatis sed. Sed massa sem, posuere ut enim sit amet, vestibulum
|
||||
condimentum nibh. Pellentesque pulvinar sodales lacinia. Proin id
|
||||
pretium sapien, non convallis nulla. In mollis tincidunt sem et
|
||||
porttitor.
|
||||
</p>
|
||||
<p>
|
||||
Integer at sollicitudin sem. Suspendisse sed semper orci. Nulla at nibh
|
||||
nec risus suscipit posuere egestas vitae enim. Nullam mauris justo,
|
||||
mattis vel laoreet non, finibus nec nisl. Cras iaculis ultrices nibh,
|
||||
non commodo eros aliquam non. Sed vitae mollis dui, at maximus metus. Ut
|
||||
vestibulum, enim ut lobortis vulputate, lorem urna congue elit, non
|
||||
dictum odio lorem eget velit. Morbi eleifend id ligula vitae vulputate.
|
||||
Suspendisse ac laoreet justo. Proin eu mattis diam.
|
||||
</p>
|
||||
<p>
|
||||
Nunc in ex quis enim ullamcorper scelerisque eget ac eros. Class aptent
|
||||
taciti sociosqu ad litora torquent per conubia nostra, per inceptos
|
||||
himenaeos. Aliquam turpis dui, egestas a rhoncus non, fermentum in
|
||||
tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices
|
||||
posuere cubilia curae; Aenean non risus arcu. Nam ultricies lacinia
|
||||
volutpat. Class aptent taciti sociosqu ad litora torquent per conubia
|
||||
nostra, per inceptos himenaeos. Lorem ipsum dolor sit amet, consectetur
|
||||
adipiscing elit.
|
||||
</p>
|
||||
<p>
|
||||
Aliquam a felis leo. Proin lorem ipsum, congue eu cursus in, rhoncus ut
|
||||
libero. Vestibulum sit amet consequat nunc. Ut eleifend lobortis lacus,
|
||||
vel molestie metus viverra eget. Nullam suscipit eu magna scelerisque
|
||||
suscipit. Donec dictum in diam nec lacinia. Mauris pellentesque ex ut
|
||||
purus facilisis, eget placerat turpis semper. Sed dapibus lorem ante, et
|
||||
malesuada dui eleifend ac. Sed diam felis, semper ac nulla vel, posuere
|
||||
ultricies ante.
|
||||
</p>
|
||||
<p>
|
||||
Nunc elementum odio sapien, sit amet vulputate lorem varius at. Fusce
|
||||
non sapien vitae lorem aliquam pretium sit amet congue dolor. Nunc quis
|
||||
tortor luctus, pretium ex a, tincidunt urna. Aliquam fermentum massa a
|
||||
erat pharetra varius. Curabitur at auctor dui. Sed posuere pellentesque
|
||||
massa, vel bibendum urna dictum non. Fusce eget rhoncus urna. Maecenas
|
||||
sed lectus tellus. Pellentesque convallis dapibus nisl vitae venenatis.
|
||||
Quisque ornare a dolor ac pharetra. Nam cursus, mi a lacinia accumsan,
|
||||
felis erat fringilla magna, ac mattis nunc ante a orci.
|
||||
</p>
|
||||
<p>
|
||||
Nunc vel tortor euismod, commodo tortor non, aliquam nisi. Maecenas
|
||||
tempus mollis velit non suscipit. Mauris sit amet dolor sed ex fringilla
|
||||
varius. Suspendisse vel cursus risus. Vivamus pharetra massa nec dolor
|
||||
aliquam feugiat. Fusce finibus enim commodo, scelerisque ante eu,
|
||||
laoreet ex. Curabitur placerat magna quis imperdiet lacinia. Etiam
|
||||
lectus mauris, porttitor ac lacinia sed, posuere eget lacus. Mauris
|
||||
vulputate mattis imperdiet. Nunc id aliquet libero, vitae hendrerit
|
||||
purus. Praesent vestibulum urna ac egestas tempor. In molestie, nunc sit
|
||||
amet sagittis dapibus, ligula enim fermentum mi, lacinia molestie eros
|
||||
dui in tortor. Mauris fermentum pulvinar faucibus. Curabitur laoreet
|
||||
eleifend purus, non tincidunt tortor gravida nec. Nam eu lectus congue,
|
||||
commodo libero et, porttitor est. Nullam tincidunt, nisi eu congue
|
||||
congue, magna justo commodo massa, nec efficitur dui lectus non sem.
|
||||
</p>
|
||||
<p>
|
||||
Nullam vehicula, ipsum quis lacinia tristique, elit nulla dignissim
|
||||
augue, at pulvinar metus justo ac magna. Nullam nec nunc ac sapien
|
||||
mollis cursus eu ac enim. Pellentesque a pharetra erat. Ut tempor magna
|
||||
nisi, accumsan blandit lectus volutpat nec. Vivamus vel lorem nec eros
|
||||
blandit dictum eget ac diam. Nulla nec turpis dolor. Morbi eu euismod
|
||||
libero. Nam ut tortor at arcu porta tincidunt. In gravida ligula
|
||||
fringilla ornare imperdiet. Nulla scelerisque ante erat, efficitur
|
||||
dictum metus ullamcorper vel. Nam ac purus metus. Maecenas eget tempus
|
||||
nulla. Ut magna lorem, efficitur ut ex a, semper aliquam magna. Praesent
|
||||
lobortis, velit ac posuere mattis, justo est accumsan turpis, id
|
||||
sagittis felis mi in lacus.
|
||||
</p>
|
||||
<p>
|
||||
Aenean est mi, semper nec sem at, malesuada consectetur nunc. Aenean
|
||||
consequat sem quis sem consequat, non aliquam est placerat. Cras
|
||||
malesuada magna neque, et pellentesque nibh consequat at. Sed interdum
|
||||
velit et ex interdum, vel lobortis ante vestibulum. Nam placerat lectus
|
||||
eu commodo efficitur. Pellentesque in nunc ac massa porttitor eleifend
|
||||
ut efficitur sem. Aenean at magna auctor, posuere augue in, ultrices
|
||||
arcu. Praesent dignissim augue ex, malesuada maximus metus interdum a.
|
||||
Proin nec odio in nulla vestibulum.
|
||||
</p>
|
||||
<p>
|
||||
Aenean est mi, semper nec sem at, malesuada consectetur nunc. Aenean
|
||||
consequat sem quis sem consequat, non aliquam est placerat. Cras
|
||||
malesuada magna neque, et pellentesque nibh consequat at. Sed interdum
|
||||
velit et ex interdum, vel lobortis ante vestibulum. Nam placerat lectus
|
||||
eu commodo efficitur. Pellentesque in nunc ac massa porttitor eleifend
|
||||
ut efficitur sem. Aenean at magna auctor, posuere augue in, ultrices
|
||||
arcu. Praesent dignissim augue ex, malesuada maximus metus interdum a.
|
||||
Proin nec odio in nulla vestibulum.
|
||||
</p>
|
||||
<p>
|
||||
Aenean est mi, semper nec sem at, malesuada consectetur nunc. Aenean
|
||||
consequat sem quis sem consequat, non aliquam est placerat. Cras
|
||||
malesuada magna neque, et pellentesque nibh consequat at. Sed interdum
|
||||
velit et ex interdum, vel lobortis ante vestibulum. Nam placerat lectus
|
||||
eu commodo efficitur. Pellentesque in nunc ac massa porttitor eleifend
|
||||
ut efficitur sem. Aenean at magna auctor, posuere augue in, ultrices
|
||||
arcu. Praesent dignissim augue ex, malesuada maximus metus interdum a.
|
||||
Proin nec odio in nulla vestibulum.
|
||||
</p>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris
|
||||
porttitor tortor ac lectus faucibus, eget eleifend elit hendrerit.
|
||||
Integer porttitor nisi in leo congue rutrum. Morbi sed ante posuere,
|
||||
aliquam lorem ac, imperdiet orci. Duis malesuada gravida pharetra.
|
||||
Cras facilisis arcu diam, id dictum lorem imperdiet a. Suspendisse
|
||||
aliquet tempus tortor et ultricies. Aliquam libero velit, posuere
|
||||
tempus ante sed, pellentesque tincidunt lorem. Nullam iaculis, eros a
|
||||
varius aliquet, tortor felis tempor metus, nec cursus felis eros
|
||||
aliquam nulla. Vivamus ut orci sed mauris congue lacinia. Cras eget
|
||||
blandit neque. Pellentesque a massa in turpis ullamcorper volutpat vel
|
||||
at massa. Sed ante est, auctor non diam non, vulputate ultrices metus.
|
||||
Maecenas dictum fermentum quam id aliquam. Donec porta risus vitae
|
||||
pretium posuere. Fusce facilisis eros in lacus tincidunt congue.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Pellentesque habitant morbi tristique senectus et netus et malesuada
|
||||
fames ac turpis egestas. Phasellus dolor ante, iaculis vel nisl vitae,
|
||||
ornare ornare orci. Praesent sit amet lobortis sapien. Suspendisse
|
||||
pharetra posuere libero ut dapibus. Donec condimentum ante urna.
|
||||
Aliquam laoreet tincidunt lacus, sed interdum tortor dapibus
|
||||
elementum. Nam sed faucibus lorem. Suspendisse finibus, velit sed
|
||||
molestie finibus, risus purus mollis ante, sit amet aliquet sapien
|
||||
nulla ut nibh. In eget ligula metus. Duis in purus mattis, blandit
|
||||
magna nec, dictum nunc.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Sed convallis magna id tortor blandit dictum. Suspendisse in porttitor
|
||||
neque. Integer quis metus consequat, rutrum est sit amet, finibus
|
||||
justo. In hac habitasse platea dictumst. Nullam sagittis, risus sed
|
||||
vehicula porta, sapien elit ultrices nibh, vel luctus odio tortor et
|
||||
ante. Sed porta enim in hendrerit tristique. Pellentesque id feugiat
|
||||
libero, sit amet tempor enim. Proin gravida nisl justo, vel ornare
|
||||
dolor bibendum ac. Mauris scelerisque mattis facilisis. Praesent
|
||||
sodales augue mollis orci vulputate aliquet. Mauris molestie luctus
|
||||
neque, sed congue elit congue ut. Cras quis tortor augue. In auctor
|
||||
nulla vel turpis dapibus egestas. Phasellus consequat rhoncus nisi sed
|
||||
dignissim. Quisque varius justo non ex lobortis finibus cursus nec
|
||||
justo. Nulla erat neque, commodo et sem convallis, tristique faucibus
|
||||
odio.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Ut condimentum volutpat sem, id accumsan augue placerat vel. Donec ac
|
||||
efficitur turpis. Suspendisse pretium odio euismod sapien bibendum,
|
||||
sed tempus est condimentum. Etiam nisl magna, consequat at ullamcorper
|
||||
at, sollicitudin eu eros. In mattis ligula arcu. Sed eu consectetur
|
||||
turpis, id molestie ligula. Vestibulum et venenatis enim. Donec
|
||||
condimentum vitae nisi et placerat. Sed fringilla vehicula egestas.
|
||||
Proin consectetur, nibh non ornare scelerisque, diam lorem cursus
|
||||
lectus, ut mattis mauris purus id mi. Curabitur non ligula sit amet
|
||||
augue molestie vulputate. Donec maximus magna at volutpat aliquet.
|
||||
Pellentesque dignissim nulla eget odio eleifend tincidunt. Etiam diam
|
||||
lorem, ornare vel scelerisque vel, iaculis id risus. Donec aliquet
|
||||
aliquam felis, ac vehicula lacus suscipit vitae. Morbi eu ligula elit.
|
||||
</p>
|
||||
</Suspense>
|
||||
<p>This should appear in the first paint.</p>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Praesent pellentesque, libero ut faucibus tempor, purus elit consequat
|
||||
metus, in ornare nulla lectus at erat. Duis quis blandit turpis. Fusce
|
||||
at ligula rutrum metus molestie tempor sit amet eu justo. Maecenas
|
||||
tincidunt nisl nunc. Morbi ac metus tempor, pretium arcu vel, dapibus
|
||||
velit. Nulla convallis ligula at porta mollis. Duis magna ante, mollis
|
||||
eget nibh in, congue tempor dolor. Sed tincidunt sagittis arcu, in
|
||||
ultricies neque tempor non. Suspendisse eget nunc neque. Nulla sit
|
||||
amet odio volutpat, maximus purus id, dictum metus. Integer consequat,
|
||||
orci nec ullamcorper porta, mauris libero vestibulum ipsum, nec tempor
|
||||
tellus enim non nunc. Quisque nisl risus, dapibus sit amet purus nec,
|
||||
aliquam finibus metus. Nullam condimentum urna viverra finibus cursus.
|
||||
Proin et sollicitudin tellus, porta fermentum felis. Maecenas ac
|
||||
turpis sed dui condimentum interdum sed sed erat. Mauris ut dignissim
|
||||
erat.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Proin varius porta dui, id fringilla elit lobortis eget. Integer at
|
||||
metus elementum, efficitur eros id, euismod est. Morbi vestibulum nibh
|
||||
ac leo luctus sagittis. Praesent rhoncus, risus sit amet mattis
|
||||
dictum, diam sapien tempor neque, vel dignissim nulla neque eget ex.
|
||||
Nam sollicitudin metus quis ullamcorper dapibus. Nam tristique euismod
|
||||
efficitur. Pellentesque rhoncus vel sem eget lacinia. Pellentesque
|
||||
volutpat velit ac dignissim luctus. Vivamus euismod tortor at ligula
|
||||
mattis porta. Vestibulum ante ipsum primis in faucibus orci luctus et
|
||||
ultrices posuere cubilia curae;
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Proin blandit vulputate efficitur. Pellentesque sit amet porta odio.
|
||||
Nunc pulvinar varius rhoncus. Mauris fermentum leo a imperdiet
|
||||
pretium. Mauris scelerisque justo vel ante egestas, eget tempus neque
|
||||
malesuada. Sed dictum ex vel justo dignissim, aliquam commodo diam
|
||||
rutrum. Integer dignissim est ullamcorper augue laoreet consectetur id
|
||||
at diam. Vivamus molestie blandit urna, eget pulvinar augue dictum
|
||||
vestibulum. Duis maximus bibendum mauris, ut ultricies elit rhoncus
|
||||
eu. Praesent gravida placerat mauris. Praesent tempor ipsum at nibh
|
||||
rhoncus sagittis. Duis non sem turpis. Quisque et metus leo. Sed eu
|
||||
purus lorem. Pellentesque dictum metus sed leo viverra interdum.
|
||||
Maecenas vel tincidunt mi.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Praesent consequat dapibus pellentesque. Fusce at enim id mauris
|
||||
laoreet commodo. Nullam ut mauris euismod, rhoncus tellus vel,
|
||||
facilisis diam. Aenean porta faucibus augue, a iaculis massa iaculis
|
||||
in. Praesent vel metus purus. Etiam quis augue eget orci lobortis
|
||||
eleifend ac ut lorem. Aenean non orci quis nisi molestie maximus.
|
||||
Mauris interdum, eros et aliquam aliquam, lectus diam pharetra velit,
|
||||
in condimentum odio eros non quam. Praesent bibendum pretium turpis
|
||||
vitae tristique. Mauris convallis, massa ut fermentum fermentum,
|
||||
libero orci tempus ipsum, malesuada ultrices metus sapien placerat
|
||||
lectus. Ut fringilla arcu nec lorem ultrices mattis. Etiam id tortor
|
||||
feugiat magna gravida gravida. Morbi aliquam, mi ac pellentesque
|
||||
mattis, erat ex venenatis erat, a vestibulum eros turpis quis metus.
|
||||
Pellentesque tempus justo in ligula ultricies porta. Phasellus congue
|
||||
felis sit amet dolor tristique finibus. Nunc eget eros non est
|
||||
ultricies vestibulum.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Donec efficitur ligula quis odio tincidunt tristique. Duis urna dolor,
|
||||
hendrerit quis enim at, accumsan auctor turpis. Vivamus ante lorem,
|
||||
maximus vitae suscipit ut, congue eget velit. Maecenas sed ligula
|
||||
erat. Aliquam mollis purus at nisi porta suscipit in ut magna. Vivamus
|
||||
a turpis nec tellus egestas suscipit nec ornare nisi. Donec vestibulum
|
||||
libero quis ex suscipit, sit amet luctus leo gravida.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Praesent pharetra dolor elit, sed volutpat lorem rhoncus non. Etiam a
|
||||
neque ut velit dignissim sodales. Vestibulum neque risus, condimentum
|
||||
nec consectetur vitae, ultricies ut sapien. Integer iaculis at urna
|
||||
sit amet malesuada. Integer tincidunt, felis ac vulputate semper,
|
||||
velit leo facilisis lorem, quis aliquet leo dui id lorem. Morbi non
|
||||
quam quis nisl sagittis consequat nec vitae libero. Nunc molestie
|
||||
pretium libero, eu eleifend nibh feugiat sed. Ut in bibendum diam, sit
|
||||
amet vehicula risus. Nam ornare ac nisi ac euismod. Nullam id egestas
|
||||
nulla. Etiam porta commodo ante sit amet pellentesque. Suspendisse
|
||||
eleifend purus in urna euismod auctor non vel nisi. Suspendisse rutrum
|
||||
est nunc, sit amet lacinia lacus dictum eget. Pellentesque habitant
|
||||
morbi tristique senectus et netus et malesuada fames ac turpis
|
||||
egestas. Morbi a blandit diam.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Donec eget efficitur sapien. Suspendisse diam lacus, varius eu
|
||||
interdum et, congue ac justo. Proin ipsum odio, suscipit elementum
|
||||
mauris sed, porttitor congue est. Cras dapibus dictum ante, vitae
|
||||
gravida elit venenatis sed. Sed massa sem, posuere ut enim sit amet,
|
||||
vestibulum condimentum nibh. Pellentesque pulvinar sodales lacinia.
|
||||
Proin id pretium sapien, non convallis nulla. In mollis tincidunt sem
|
||||
et porttitor.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Integer at sollicitudin sem. Suspendisse sed semper orci. Nulla at
|
||||
nibh nec risus suscipit posuere egestas vitae enim. Nullam mauris
|
||||
justo, mattis vel laoreet non, finibus nec nisl. Cras iaculis ultrices
|
||||
nibh, non commodo eros aliquam non. Sed vitae mollis dui, at maximus
|
||||
metus. Ut vestibulum, enim ut lobortis vulputate, lorem urna congue
|
||||
elit, non dictum odio lorem eget velit. Morbi eleifend id ligula vitae
|
||||
vulputate. Suspendisse ac laoreet justo. Proin eu mattis diam.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Nunc in ex quis enim ullamcorper scelerisque eget ac eros. Class
|
||||
aptent taciti sociosqu ad litora torquent per conubia nostra, per
|
||||
inceptos himenaeos. Aliquam turpis dui, egestas a rhoncus non,
|
||||
fermentum in tellus. Vestibulum ante ipsum primis in faucibus orci
|
||||
luctus et ultrices posuere cubilia curae; Aenean non risus arcu. Nam
|
||||
ultricies lacinia volutpat. Class aptent taciti sociosqu ad litora
|
||||
torquent per conubia nostra, per inceptos himenaeos. Lorem ipsum dolor
|
||||
sit amet, consectetur adipiscing elit.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Aliquam a felis leo. Proin lorem ipsum, congue eu cursus in, rhoncus
|
||||
ut libero. Vestibulum sit amet consequat nunc. Ut eleifend lobortis
|
||||
lacus, vel molestie metus viverra eget. Nullam suscipit eu magna
|
||||
scelerisque suscipit. Donec dictum in diam nec lacinia. Mauris
|
||||
pellentesque ex ut purus facilisis, eget placerat turpis semper. Sed
|
||||
dapibus lorem ante, et malesuada dui eleifend ac. Sed diam felis,
|
||||
semper ac nulla vel, posuere ultricies ante.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Nunc elementum odio sapien, sit amet vulputate lorem varius at. Fusce
|
||||
non sapien vitae lorem aliquam pretium sit amet congue dolor. Nunc
|
||||
quis tortor luctus, pretium ex a, tincidunt urna. Aliquam fermentum
|
||||
massa a erat pharetra varius. Curabitur at auctor dui. Sed posuere
|
||||
pellentesque massa, vel bibendum urna dictum non. Fusce eget rhoncus
|
||||
urna. Maecenas sed lectus tellus. Pellentesque convallis dapibus nisl
|
||||
vitae venenatis. Quisque ornare a dolor ac pharetra. Nam cursus, mi a
|
||||
lacinia accumsan, felis erat fringilla magna, ac mattis nunc ante a
|
||||
orci.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Nunc vel tortor euismod, commodo tortor non, aliquam nisi. Maecenas
|
||||
tempus mollis velit non suscipit. Mauris sit amet dolor sed ex
|
||||
fringilla varius. Suspendisse vel cursus risus. Vivamus pharetra massa
|
||||
nec dolor aliquam feugiat. Fusce finibus enim commodo, scelerisque
|
||||
ante eu, laoreet ex. Curabitur placerat magna quis imperdiet lacinia.
|
||||
Etiam lectus mauris, porttitor ac lacinia sed, posuere eget lacus.
|
||||
Mauris vulputate mattis imperdiet. Nunc id aliquet libero, vitae
|
||||
hendrerit purus. Praesent vestibulum urna ac egestas tempor. In
|
||||
molestie, nunc sit amet sagittis dapibus, ligula enim fermentum mi,
|
||||
lacinia molestie eros dui in tortor. Mauris fermentum pulvinar
|
||||
faucibus. Curabitur laoreet eleifend purus, non tincidunt tortor
|
||||
gravida nec. Nam eu lectus congue, commodo libero et, porttitor est.
|
||||
Nullam tincidunt, nisi eu congue congue, magna justo commodo massa,
|
||||
nec efficitur dui lectus non sem.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Nullam vehicula, ipsum quis lacinia tristique, elit nulla dignissim
|
||||
augue, at pulvinar metus justo ac magna. Nullam nec nunc ac sapien
|
||||
mollis cursus eu ac enim. Pellentesque a pharetra erat. Ut tempor
|
||||
magna nisi, accumsan blandit lectus volutpat nec. Vivamus vel lorem
|
||||
nec eros blandit dictum eget ac diam. Nulla nec turpis dolor. Morbi eu
|
||||
euismod libero. Nam ut tortor at arcu porta tincidunt. In gravida
|
||||
ligula fringilla ornare imperdiet. Nulla scelerisque ante erat,
|
||||
efficitur dictum metus ullamcorper vel. Nam ac purus metus. Maecenas
|
||||
eget tempus nulla. Ut magna lorem, efficitur ut ex a, semper aliquam
|
||||
magna. Praesent lobortis, velit ac posuere mattis, justo est accumsan
|
||||
turpis, id sagittis felis mi in lacus.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Aenean est mi, semper nec sem at, malesuada consectetur nunc. Aenean
|
||||
consequat sem quis sem consequat, non aliquam est placerat. Cras
|
||||
malesuada magna neque, et pellentesque nibh consequat at. Sed interdum
|
||||
velit et ex interdum, vel lobortis ante vestibulum. Nam placerat
|
||||
lectus eu commodo efficitur. Pellentesque in nunc ac massa porttitor
|
||||
eleifend ut efficitur sem. Aenean at magna auctor, posuere augue in,
|
||||
ultrices arcu. Praesent dignissim augue ex, malesuada maximus metus
|
||||
interdum a. Proin nec odio in nulla vestibulum.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Aenean est mi, semper nec sem at, malesuada consectetur nunc. Aenean
|
||||
consequat sem quis sem consequat, non aliquam est placerat. Cras
|
||||
malesuada magna neque, et pellentesque nibh consequat at. Sed interdum
|
||||
velit et ex interdum, vel lobortis ante vestibulum. Nam placerat
|
||||
lectus eu commodo efficitur. Pellentesque in nunc ac massa porttitor
|
||||
eleifend ut efficitur sem. Aenean at magna auctor, posuere augue in,
|
||||
ultrices arcu. Praesent dignissim augue ex, malesuada maximus metus
|
||||
interdum a. Proin nec odio in nulla vestibulum.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Aenean est mi, semper nec sem at, malesuada consectetur nunc. Aenean
|
||||
consequat sem quis sem consequat, non aliquam est placerat. Cras
|
||||
malesuada magna neque, et pellentesque nibh consequat at. Sed interdum
|
||||
velit et ex interdum, vel lobortis ante vestibulum. Nam placerat
|
||||
lectus eu commodo efficitur. Pellentesque in nunc ac massa porttitor
|
||||
eleifend ut efficitur sem. Aenean at magna auctor, posuere augue in,
|
||||
ultrices arcu. Praesent dignissim augue ex, malesuada maximus metus
|
||||
interdum a. Proin nec odio in nulla vestibulum.
|
||||
</p>
|
||||
</Suspense>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
+95
-60
@@ -7,52 +7,35 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
/* eslint-disable react-internal/no-production-logging */
|
||||
|
||||
import type {ReactComponentInfo} from 'shared/ReactTypes';
|
||||
|
||||
import {enableProfilerTimer} from 'shared/ReactFeatureFlags';
|
||||
|
||||
const supportsUserTiming =
|
||||
enableProfilerTimer &&
|
||||
typeof performance !== 'undefined' &&
|
||||
// $FlowFixMe[method-unbinding]
|
||||
typeof performance.measure === 'function';
|
||||
typeof console !== 'undefined' &&
|
||||
typeof console.timeStamp === 'function';
|
||||
|
||||
const COMPONENTS_TRACK = 'Server Components ⚛';
|
||||
|
||||
const componentsTrackMarker = {
|
||||
startTime: 0.001,
|
||||
detail: {
|
||||
devtools: {
|
||||
color: 'primary-light',
|
||||
track: 'Primary',
|
||||
trackGroup: COMPONENTS_TRACK,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export function markAllTracksInOrder() {
|
||||
if (supportsUserTiming) {
|
||||
// Ensure we create the Server Component track groups earlier than the Client Scheduler
|
||||
// and Client Components. We can always add the 0 time slot even if it's in the past.
|
||||
// That's still considered for ordering.
|
||||
performance.mark('Server Components Track', componentsTrackMarker);
|
||||
console.timeStamp(
|
||||
'Server Components Track',
|
||||
0.001,
|
||||
0.001,
|
||||
'Primary',
|
||||
COMPONENTS_TRACK,
|
||||
'primary-light',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Reused to avoid thrashing the GC.
|
||||
const reusableComponentDevToolDetails = {
|
||||
color: 'primary',
|
||||
track: '',
|
||||
trackGroup: COMPONENTS_TRACK,
|
||||
};
|
||||
const reusableComponentOptions = {
|
||||
start: -0,
|
||||
end: -0,
|
||||
detail: {
|
||||
devtools: reusableComponentDevToolDetails,
|
||||
},
|
||||
};
|
||||
|
||||
const trackNames = [
|
||||
'Primary',
|
||||
'Parallel',
|
||||
@@ -79,7 +62,7 @@ export function logComponentRender(
|
||||
const name = componentInfo.name;
|
||||
const isPrimaryEnv = env === rootEnv;
|
||||
const selfTime = endTime - startTime;
|
||||
reusableComponentDevToolDetails.color =
|
||||
const color =
|
||||
selfTime < 0.5
|
||||
? isPrimaryEnv
|
||||
? 'primary-light'
|
||||
@@ -93,12 +76,32 @@ export function logComponentRender(
|
||||
? 'primary-dark'
|
||||
: 'secondary-dark'
|
||||
: 'error';
|
||||
reusableComponentDevToolDetails.track = trackNames[trackIdx];
|
||||
reusableComponentOptions.start = startTime < 0 ? 0 : startTime;
|
||||
reusableComponentOptions.end = childrenEndTime;
|
||||
const entryName =
|
||||
isPrimaryEnv || env === undefined ? name : name + ' [' + env + ']';
|
||||
performance.measure(entryName, reusableComponentOptions);
|
||||
const debugTask = componentInfo.debugTask;
|
||||
if (__DEV__ && debugTask) {
|
||||
debugTask.run(
|
||||
// $FlowFixMe[method-unbinding]
|
||||
console.timeStamp.bind(
|
||||
console,
|
||||
entryName,
|
||||
startTime < 0 ? 0 : startTime,
|
||||
childrenEndTime,
|
||||
trackNames[trackIdx],
|
||||
COMPONENTS_TRACK,
|
||||
color,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
console.timeStamp(
|
||||
entryName,
|
||||
startTime < 0 ? 0 : startTime,
|
||||
childrenEndTime,
|
||||
trackNames[trackIdx],
|
||||
COMPONENTS_TRACK,
|
||||
color,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,8 +115,17 @@ export function logComponentErrored(
|
||||
error: mixed,
|
||||
): void {
|
||||
if (supportsUserTiming) {
|
||||
const properties = [];
|
||||
if (__DEV__) {
|
||||
const env = componentInfo.env;
|
||||
const name = componentInfo.name;
|
||||
const isPrimaryEnv = env === rootEnv;
|
||||
const entryName =
|
||||
isPrimaryEnv || env === undefined ? name : name + ' [' + env + ']';
|
||||
if (
|
||||
__DEV__ &&
|
||||
typeof performance !== 'undefined' &&
|
||||
// $FlowFixMe[method-unbinding]
|
||||
typeof performance.measure === 'function'
|
||||
) {
|
||||
const message =
|
||||
typeof error === 'object' &&
|
||||
error !== null &&
|
||||
@@ -122,26 +134,30 @@ export function logComponentErrored(
|
||||
String(error.message)
|
||||
: // eslint-disable-next-line react-internal/safe-string-coercion
|
||||
String(error);
|
||||
properties.push(['Error', message]);
|
||||
}
|
||||
const env = componentInfo.env;
|
||||
const name = componentInfo.name;
|
||||
const isPrimaryEnv = env === rootEnv;
|
||||
const entryName =
|
||||
isPrimaryEnv || env === undefined ? name : name + ' [' + env + ']';
|
||||
performance.measure(entryName, {
|
||||
start: startTime < 0 ? 0 : startTime,
|
||||
end: childrenEndTime,
|
||||
detail: {
|
||||
devtools: {
|
||||
color: 'error',
|
||||
track: trackNames[trackIdx],
|
||||
trackGroup: COMPONENTS_TRACK,
|
||||
tooltipText: entryName + ' Errored',
|
||||
properties,
|
||||
const properties = [['Error', message]];
|
||||
performance.measure(entryName, {
|
||||
start: startTime < 0 ? 0 : startTime,
|
||||
end: childrenEndTime,
|
||||
detail: {
|
||||
devtools: {
|
||||
color: 'error',
|
||||
track: trackNames[trackIdx],
|
||||
trackGroup: COMPONENTS_TRACK,
|
||||
tooltipText: entryName + ' Errored',
|
||||
properties,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
} else {
|
||||
console.timeStamp(
|
||||
entryName,
|
||||
startTime < 0 ? 0 : startTime,
|
||||
childrenEndTime,
|
||||
trackNames[trackIdx],
|
||||
COMPONENTS_TRACK,
|
||||
'error',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,11 +169,30 @@ export function logDedupedComponentRender(
|
||||
): void {
|
||||
if (supportsUserTiming && endTime >= 0 && trackIdx < 10) {
|
||||
const name = componentInfo.name;
|
||||
reusableComponentDevToolDetails.color = 'tertiary-light';
|
||||
reusableComponentDevToolDetails.track = trackNames[trackIdx];
|
||||
reusableComponentOptions.start = startTime < 0 ? 0 : startTime;
|
||||
reusableComponentOptions.end = endTime;
|
||||
const entryName = name + ' [deduped]';
|
||||
performance.measure(entryName, reusableComponentOptions);
|
||||
const debugTask = componentInfo.debugTask;
|
||||
if (__DEV__ && debugTask) {
|
||||
debugTask.run(
|
||||
// $FlowFixMe[method-unbinding]
|
||||
console.timeStamp.bind(
|
||||
console,
|
||||
entryName,
|
||||
startTime < 0 ? 0 : startTime,
|
||||
endTime,
|
||||
trackNames[trackIdx],
|
||||
COMPONENTS_TRACK,
|
||||
'tertiary-light',
|
||||
),
|
||||
);
|
||||
} else {
|
||||
console.timeStamp(
|
||||
entryName,
|
||||
startTime < 0 ? 0 : startTime,
|
||||
endTime,
|
||||
trackNames[trackIdx],
|
||||
COMPONENTS_TRACK,
|
||||
'tertiary-light',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import type {Element as ElementType} from 'react-devtools-shared/src/frontend/ty
|
||||
|
||||
import styles from './Element.css';
|
||||
import Icon from '../Icon';
|
||||
import {useChangeOwnerAction} from './OwnersListContext';
|
||||
|
||||
type Props = {
|
||||
data: ItemData,
|
||||
@@ -66,9 +67,10 @@ export default function Element({data, index, style}: Props): React.Node {
|
||||
warningCount: number,
|
||||
}>(errorsAndWarningsSubscription);
|
||||
|
||||
const changeOwnerAction = useChangeOwnerAction();
|
||||
const handleDoubleClick = () => {
|
||||
if (id !== null) {
|
||||
dispatch({type: 'SELECT_OWNER', payload: id});
|
||||
changeOwnerAction(id);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+39
-4
@@ -13,7 +13,7 @@ import * as React from 'react';
|
||||
import {createContext, useCallback, useContext, useEffect} from 'react';
|
||||
import {createResource} from '../../cache';
|
||||
import {BridgeContext, StoreContext} from '../context';
|
||||
import {TreeStateContext} from './TreeContext';
|
||||
import {TreeDispatcherContext, TreeStateContext} from './TreeContext';
|
||||
import {backendToFrontendSerializedElementMapper} from 'react-devtools-shared/src/utils';
|
||||
|
||||
import type {OwnersList} from 'react-devtools-shared/src/backend/types';
|
||||
@@ -70,6 +70,43 @@ type Props = {
|
||||
children: React$Node,
|
||||
};
|
||||
|
||||
function useChangeOwnerAction(): (nextOwnerID: number) => void {
|
||||
const bridge = useContext(BridgeContext);
|
||||
const store = useContext(StoreContext);
|
||||
const treeAction = useContext(TreeDispatcherContext);
|
||||
|
||||
return useCallback(
|
||||
function changeOwnerAction(nextOwnerID: number) {
|
||||
treeAction({type: 'SELECT_OWNER', payload: nextOwnerID});
|
||||
|
||||
const element = store.getElementByID(nextOwnerID);
|
||||
if (element !== null) {
|
||||
if (!inProgressRequests.has(element)) {
|
||||
let resolveFn:
|
||||
| ResolveFn
|
||||
| ((
|
||||
result:
|
||||
| Promise<Array<SerializedElement>>
|
||||
| Array<SerializedElement>,
|
||||
) => void) = ((null: any): ResolveFn);
|
||||
const promise = new Promise(resolve => {
|
||||
resolveFn = resolve;
|
||||
});
|
||||
|
||||
// $FlowFixMe[incompatible-call] found when upgrading Flow
|
||||
inProgressRequests.set(element, {promise, resolveFn});
|
||||
}
|
||||
|
||||
const rendererID = store.getRendererIDForElement(nextOwnerID);
|
||||
if (rendererID !== null) {
|
||||
bridge.send('getOwnersList', {id: nextOwnerID, rendererID});
|
||||
}
|
||||
}
|
||||
},
|
||||
[bridge, store],
|
||||
);
|
||||
}
|
||||
|
||||
function OwnersListContextController({children}: Props): React.Node {
|
||||
const bridge = useContext(BridgeContext);
|
||||
const store = useContext(StoreContext);
|
||||
@@ -95,8 +132,6 @@ function OwnersListContextController({children}: Props): React.Node {
|
||||
if (element !== null) {
|
||||
const request = inProgressRequests.get(element);
|
||||
if (request != null) {
|
||||
inProgressRequests.delete(element);
|
||||
|
||||
request.resolveFn(
|
||||
ownersList.owners === null
|
||||
? null
|
||||
@@ -129,4 +164,4 @@ function OwnersListContextController({children}: Props): React.Node {
|
||||
);
|
||||
}
|
||||
|
||||
export {OwnersListContext, OwnersListContextController};
|
||||
export {OwnersListContext, OwnersListContextController, useChangeOwnerAction};
|
||||
|
||||
+3
-2
@@ -20,7 +20,7 @@ import Button from '../Button';
|
||||
import ButtonIcon from '../ButtonIcon';
|
||||
import Toggle from '../Toggle';
|
||||
import ElementBadges from './ElementBadges';
|
||||
import {OwnersListContext} from './OwnersListContext';
|
||||
import {OwnersListContext, useChangeOwnerAction} from './OwnersListContext';
|
||||
import {TreeDispatcherContext, TreeStateContext} from './TreeContext';
|
||||
import {useIsOverflowing} from '../hooks';
|
||||
import {StoreContext} from '../context';
|
||||
@@ -81,6 +81,7 @@ export default function OwnerStack(): React.Node {
|
||||
const read = useContext(OwnersListContext);
|
||||
const {ownerID} = useContext(TreeStateContext);
|
||||
const treeDispatch = useContext(TreeDispatcherContext);
|
||||
const changeOwnerAction = useChangeOwnerAction();
|
||||
|
||||
const [state, dispatch] = useReducer<State, State, Action>(dialogReducer, {
|
||||
ownerID: null,
|
||||
@@ -116,7 +117,7 @@ export default function OwnerStack(): React.Node {
|
||||
type: 'UPDATE_SELECTED_INDEX',
|
||||
selectedIndex: index >= 0 ? index : 0,
|
||||
});
|
||||
treeDispatch({type: 'SELECT_OWNER', payload: owner.id});
|
||||
changeOwnerAction(owner.id);
|
||||
} else {
|
||||
dispatch({
|
||||
type: 'UPDATE_SELECTED_INDEX',
|
||||
|
||||
@@ -38,6 +38,7 @@ import ButtonIcon from '../ButtonIcon';
|
||||
import Button from '../Button';
|
||||
import {logEvent} from 'react-devtools-shared/src/Logger';
|
||||
import {useExtensionComponentsPanelVisibility} from 'react-devtools-shared/src/frontend/hooks/useExtensionComponentsPanelVisibility';
|
||||
import {useChangeOwnerAction} from './OwnersListContext';
|
||||
|
||||
// Never indent more than this number of pixels (even if we have the room).
|
||||
const DEFAULT_INDENTATION_SIZE = 12;
|
||||
@@ -217,13 +218,14 @@ export default function Tree(): React.Node {
|
||||
const handleBlur = useCallback(() => setTreeFocused(false), []);
|
||||
const handleFocus = useCallback(() => setTreeFocused(true), []);
|
||||
|
||||
const changeOwnerAction = useChangeOwnerAction();
|
||||
const handleKeyPress = useCallback(
|
||||
(event: $FlowFixMe) => {
|
||||
switch (event.key) {
|
||||
case 'Enter':
|
||||
case ' ':
|
||||
if (inspectedElementID !== null) {
|
||||
dispatch({type: 'SELECT_OWNER', payload: inspectedElementID});
|
||||
changeOwnerAction(inspectedElementID);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
||||
+12
-8
@@ -148,6 +148,7 @@ const TreeStateContext: ReactContext<StateContext> =
|
||||
createContext<StateContext>(((null: any): StateContext));
|
||||
TreeStateContext.displayName = 'TreeStateContext';
|
||||
|
||||
// TODO: `dispatch` is an Action and should be named accordingly.
|
||||
const TreeDispatcherContext: ReactContext<DispatcherContext> =
|
||||
createContext<DispatcherContext>(((null: any): DispatcherContext));
|
||||
TreeDispatcherContext.displayName = 'TreeDispatcherContext';
|
||||
@@ -891,19 +892,22 @@ function TreeContextController({
|
||||
? store.getIndexOfElementID(store.lastSelectedHostInstanceElementId)
|
||||
: null,
|
||||
});
|
||||
const dispatchWrapper = useMemo(
|
||||
() => (action: Action) => startTransition(() => dispatch(action)),
|
||||
const transitionDispatch = useMemo(
|
||||
() => (action: Action) =>
|
||||
startTransition(() => {
|
||||
dispatch(action);
|
||||
}),
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
// Listen for host element selections.
|
||||
useEffect(() => {
|
||||
const handler = (id: Element['id']) =>
|
||||
dispatchWrapper({type: 'SELECT_ELEMENT_BY_ID', payload: id});
|
||||
transitionDispatch({type: 'SELECT_ELEMENT_BY_ID', payload: id});
|
||||
|
||||
store.addListener('hostInstanceSelected', handler);
|
||||
return () => store.removeListener('hostInstanceSelected', handler);
|
||||
}, [store, dispatchWrapper]);
|
||||
}, [store, transitionDispatch]);
|
||||
|
||||
// If a newly-selected search result or inspection selection is inside of a collapsed subtree, auto expand it.
|
||||
// This needs to be a layout effect to avoid temporarily flashing an incorrect selection.
|
||||
@@ -927,7 +931,7 @@ function TreeContextController({
|
||||
Array<number>,
|
||||
Map<number, number>,
|
||||
]) => {
|
||||
dispatchWrapper({
|
||||
transitionDispatch({
|
||||
type: 'HANDLE_STORE_MUTATION',
|
||||
payload: [addedElementIDs, removedElementIDs],
|
||||
});
|
||||
@@ -938,7 +942,7 @@ function TreeContextController({
|
||||
// At the moment, we can treat this as a mutation.
|
||||
// We don't know which Elements were newly added/removed, but that should be okay in this case.
|
||||
// It would only impact the search state, which is unlikely to exist yet at this point.
|
||||
dispatchWrapper({
|
||||
transitionDispatch({
|
||||
type: 'HANDLE_STORE_MUTATION',
|
||||
payload: [[], new Map()],
|
||||
});
|
||||
@@ -946,11 +950,11 @@ function TreeContextController({
|
||||
|
||||
store.addListener('mutated', handleStoreMutated);
|
||||
return () => store.removeListener('mutated', handleStoreMutated);
|
||||
}, [dispatchWrapper, initialRevision, store]);
|
||||
}, [dispatch, initialRevision, store]);
|
||||
|
||||
return (
|
||||
<TreeStateContext.Provider value={state}>
|
||||
<TreeDispatcherContext.Provider value={dispatchWrapper}>
|
||||
<TreeDispatcherContext.Provider value={transitionDispatch}>
|
||||
{children}
|
||||
</TreeDispatcherContext.Provider>
|
||||
</TreeStateContext.Provider>
|
||||
|
||||
@@ -14,13 +14,6 @@ import {
|
||||
completeSegment,
|
||||
} from './fizz-instruction-set/ReactDOMFizzInstructionSetExternalRuntime';
|
||||
|
||||
if (!window.$RC) {
|
||||
// TODO: Eventually remove, we currently need to set these globals for
|
||||
// compatibility with ReactDOMFizzInstructionSet
|
||||
window.$RC = completeBoundary;
|
||||
window.$RM = new Map();
|
||||
}
|
||||
|
||||
if (document.body != null) {
|
||||
if (document.readyState === 'loading') {
|
||||
installFizzInstrObserver(document.body);
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
export const clientRenderBoundary =
|
||||
'$RX=function(b,c,d,e,f){var a=document.getElementById(b);a&&(b=a.previousSibling,b.data="$!",a=a.dataset,c&&(a.dgst=c),d&&(a.msg=d),e&&(a.stck=e),f&&(a.cstck=f),b._reactRetry&&b._reactRetry())};';
|
||||
export const completeBoundary =
|
||||
'$RC=function(b,d,e){d=document.getElementById(d);d.parentNode.removeChild(d);var a=document.getElementById(b);if(a){b=a.previousSibling;if(e)b.data="$!",a.setAttribute("data-dgst",e);else{e=b.parentNode;a=b.nextSibling;var f=0;do{if(a&&8===a.nodeType){var c=a.data;if("/$"===c||"/&"===c)if(0===f)break;else f--;else"$"!==c&&"$?"!==c&&"$!"!==c&&"&"!==c||f++}c=a.nextSibling;e.removeChild(a);a=c}while(a);for(;d.firstChild;)e.insertBefore(d.firstChild,a);b.data="$"}b._reactRetry&&b._reactRetry()}};';
|
||||
'$RC=function(b,d,e){if(d=document.getElementById(d)){d.parentNode.removeChild(d);var a=document.getElementById(b);if(a){b=a.previousSibling;if(e)b.data="$!",a.setAttribute("data-dgst",e);else{e=b.parentNode;a=b.nextSibling;var f=0;do{if(a&&8===a.nodeType){var c=a.data;if("/$"===c||"/&"===c)if(0===f)break;else f--;else"$"!==c&&"$?"!==c&&"$!"!==c&&"&"!==c||f++}c=a.nextSibling;e.removeChild(a);a=c}while(a);for(;d.firstChild;)e.insertBefore(d.firstChild,a);b.data="$"}b._reactRetry&&b._reactRetry()}}};';
|
||||
export const completeBoundaryWithStyles =
|
||||
'$RM=new Map;\n$RR=function(t,u,y){function v(n){this._p=null;n()}for(var w=$RC,p=$RM,q=new Map,r=document,g,b,h=r.querySelectorAll("link[data-precedence],style[data-precedence]"),x=[],k=0;b=h[k++];)"not all"===b.getAttribute("media")?x.push(b):("LINK"===b.tagName&&p.set(b.getAttribute("href"),b),q.set(b.dataset.precedence,g=b));b=0;h=[];var l,a;for(k=!0;;){if(k){var e=y[b++];if(!e){k=!1;b=0;continue}var c=!1,m=0;var d=e[m++];if(a=p.get(d)){var f=a._p;c=!0}else{a=r.createElement("link");a.href=\nd;a.rel="stylesheet";for(a.dataset.precedence=l=e[m++];f=e[m++];)a.setAttribute(f,e[m++]);f=a._p=new Promise(function(n,z){a.onload=v.bind(a,n);a.onerror=v.bind(a,z)});p.set(d,a)}d=a.getAttribute("media");!f||d&&!matchMedia(d).matches||h.push(f);if(c)continue}else{a=x[b++];if(!a)break;l=a.getAttribute("data-precedence");a.removeAttribute("media")}c=q.get(l)||g;c===g&&(g=a);q.set(l,a);c?c.parentNode.insertBefore(a,c.nextSibling):(c=r.head,c.insertBefore(a,c.firstChild))}Promise.all(h).then(w.bind(null,\nt,u,""),w.bind(null,t,u,"Resource failed to load"))};';
|
||||
export const completeSegment =
|
||||
|
||||
Vendored
+6
@@ -49,6 +49,12 @@ export function clientRenderBoundary(
|
||||
|
||||
export function completeBoundary(suspenseBoundaryID, contentID, errorDigest) {
|
||||
const contentNode = document.getElementById(contentID);
|
||||
if (!contentNode) {
|
||||
// If the client has failed hydration we may have already deleted the streaming
|
||||
// segments. The server may also have emitted a complete instruction but cancelled
|
||||
// the segment. Regardless we can ignore this case.
|
||||
return;
|
||||
}
|
||||
// We'll detach the content node so that regardless of what happens next we don't leave in the tree.
|
||||
// This might also help by not causing recalcing each time we move a child from here to the target.
|
||||
contentNode.parentNode.removeChild(contentNode);
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
} from 'react-reconciler/src/ReactEventPriorities';
|
||||
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
|
||||
import {HostText} from 'react-reconciler/src/ReactWorkTags';
|
||||
import {traverseFragmentInstance} from 'react-reconciler/src/ReactFiberTreeReflection';
|
||||
|
||||
// Modules provided by RN:
|
||||
import {
|
||||
@@ -622,30 +623,91 @@ export function waitForCommitToBeReady(): null {
|
||||
return null;
|
||||
}
|
||||
|
||||
export type FragmentInstanceType = null;
|
||||
export type FragmentInstanceType = {
|
||||
_fragmentFiber: Fiber,
|
||||
_observers: null | Set<IntersectionObserver>,
|
||||
observeUsing: (observer: IntersectionObserver) => void,
|
||||
unobserveUsing: (observer: IntersectionObserver) => void,
|
||||
};
|
||||
|
||||
function FragmentInstance(this: FragmentInstanceType, fragmentFiber: Fiber) {
|
||||
this._fragmentFiber = fragmentFiber;
|
||||
this._observers = null;
|
||||
}
|
||||
|
||||
// $FlowFixMe[prop-missing]
|
||||
FragmentInstance.prototype.observeUsing = function (
|
||||
this: FragmentInstanceType,
|
||||
observer: IntersectionObserver,
|
||||
): void {
|
||||
if (this._observers === null) {
|
||||
this._observers = new Set();
|
||||
}
|
||||
this._observers.add(observer);
|
||||
traverseFragmentInstance(this._fragmentFiber, observeChild, observer);
|
||||
};
|
||||
function observeChild(instance: Instance, observer: IntersectionObserver) {
|
||||
const publicInstance = getPublicInstance(instance);
|
||||
if (publicInstance == null) {
|
||||
throw new Error('Expected to find a host node. This is a bug in React.');
|
||||
}
|
||||
// $FlowFixMe[incompatible-call] Element types are behind a flag in RN
|
||||
observer.observe(publicInstance);
|
||||
return false;
|
||||
}
|
||||
// $FlowFixMe[prop-missing]
|
||||
FragmentInstance.prototype.unobserveUsing = function (
|
||||
this: FragmentInstanceType,
|
||||
observer: IntersectionObserver,
|
||||
): void {
|
||||
if (this._observers === null || !this._observers.has(observer)) {
|
||||
if (__DEV__) {
|
||||
console.error(
|
||||
'You are calling unobserveUsing() with an observer that is not being observed with this fragment ' +
|
||||
'instance. First attach the observer with observeUsing()',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this._observers.delete(observer);
|
||||
traverseFragmentInstance(this._fragmentFiber, unobserveChild, observer);
|
||||
}
|
||||
};
|
||||
function unobserveChild(instance: Instance, observer: IntersectionObserver) {
|
||||
const publicInstance = getPublicInstance(instance);
|
||||
if (publicInstance == null) {
|
||||
throw new Error('Expected to find a host node. This is a bug in React.');
|
||||
}
|
||||
// $FlowFixMe[incompatible-call] Element types are behind a flag in RN
|
||||
observer.unobserve(publicInstance);
|
||||
return false;
|
||||
}
|
||||
|
||||
export function createFragmentInstance(
|
||||
fragmentFiber: Fiber,
|
||||
): FragmentInstanceType {
|
||||
return null;
|
||||
return new (FragmentInstance: any)(fragmentFiber);
|
||||
}
|
||||
|
||||
export function updateFragmentInstanceFiber(
|
||||
fragmentFiber: Fiber,
|
||||
instance: FragmentInstanceType,
|
||||
): void {
|
||||
// Noop
|
||||
instance._fragmentFiber = fragmentFiber;
|
||||
}
|
||||
|
||||
export function commitNewChildToFragmentInstance(
|
||||
child: PublicInstance,
|
||||
child: Instance,
|
||||
fragmentInstance: FragmentInstanceType,
|
||||
): void {
|
||||
// Noop
|
||||
if (fragmentInstance._observers !== null) {
|
||||
fragmentInstance._observers.forEach(observer => {
|
||||
observeChild(child, observer);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function deleteChildFromFragmentInstance(
|
||||
child: PublicInstance,
|
||||
child: Instance,
|
||||
fragmentInstance: FragmentInstanceType,
|
||||
): void {
|
||||
// Noop
|
||||
|
||||
@@ -219,13 +219,13 @@ function addNestedProperty(
|
||||
return updatePayload;
|
||||
}
|
||||
|
||||
if (enableFastAddPropertiesInDiffing) {
|
||||
return fastAddProperties(updatePayload, nextProp, validAttributes);
|
||||
}
|
||||
|
||||
if (!isArray(nextProp)) {
|
||||
// Add each property of the leaf.
|
||||
if (enableFastAddPropertiesInDiffing) {
|
||||
return fastAddProperties(updatePayload, nextProp, validAttributes);
|
||||
} else {
|
||||
return addProperties(updatePayload, nextProp, validAttributes);
|
||||
}
|
||||
return slowAddProperties(updatePayload, nextProp, validAttributes);
|
||||
}
|
||||
|
||||
for (let i = 0; i < nextProp.length; i++) {
|
||||
@@ -516,7 +516,7 @@ function fastAddProperties(
|
||||
/**
|
||||
* addProperties adds all the valid props to the payload after being processed.
|
||||
*/
|
||||
function addProperties(
|
||||
function slowAddProperties(
|
||||
updatePayload: null | Object,
|
||||
props: Object,
|
||||
validAttributes: AttributeConfiguration,
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @emails react-core
|
||||
* @jest-environment node
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
let React;
|
||||
let ReactFabric;
|
||||
let createReactNativeComponentClass;
|
||||
let act;
|
||||
let View;
|
||||
let Text;
|
||||
|
||||
describe('Fabric FragmentRefs', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
|
||||
require('react-native/Libraries/ReactPrivate/InitializeNativeFabricUIManager');
|
||||
|
||||
React = require('react');
|
||||
ReactFabric = require('react-native-renderer/fabric');
|
||||
createReactNativeComponentClass =
|
||||
require('react-native/Libraries/ReactPrivate/ReactNativePrivateInterface')
|
||||
.ReactNativeViewConfigRegistry.register;
|
||||
({act} = require('internal-test-utils'));
|
||||
View = createReactNativeComponentClass('RCTView', () => ({
|
||||
validAttributes: {nativeID: true},
|
||||
uiViewClassName: 'RCTView',
|
||||
}));
|
||||
Text = createReactNativeComponentClass('RCTText', () => ({
|
||||
validAttributes: {nativeID: true},
|
||||
uiViewClassName: 'RCTText',
|
||||
}));
|
||||
});
|
||||
|
||||
// @gate enableFragmentRefs
|
||||
it('attaches a ref to Fragment', async () => {
|
||||
const fragmentRef = React.createRef();
|
||||
|
||||
await act(() =>
|
||||
ReactFabric.render(
|
||||
<View>
|
||||
<React.Fragment ref={fragmentRef}>
|
||||
<View>
|
||||
<Text>Hi</Text>
|
||||
</View>
|
||||
</React.Fragment>
|
||||
</View>,
|
||||
11,
|
||||
null,
|
||||
true,
|
||||
),
|
||||
);
|
||||
|
||||
expect(fragmentRef.current).not.toBe(null);
|
||||
});
|
||||
|
||||
// @gate enableFragmentRefs
|
||||
it('accepts a ref callback', async () => {
|
||||
let fragmentRef;
|
||||
|
||||
await act(() => {
|
||||
ReactFabric.render(
|
||||
<React.Fragment ref={ref => (fragmentRef = ref)}>
|
||||
<View nativeID="child">
|
||||
<Text>Hi</Text>
|
||||
</View>
|
||||
</React.Fragment>,
|
||||
11,
|
||||
null,
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
expect(fragmentRef && fragmentRef._fragmentFiber).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -180,7 +180,7 @@ const classComponentUpdater = {
|
||||
|
||||
const root = enqueueUpdate(fiber, update, lane);
|
||||
if (root !== null) {
|
||||
startUpdateTimerByLane(lane);
|
||||
startUpdateTimerByLane(lane, 'this.setState()');
|
||||
scheduleUpdateOnFiber(root, fiber, lane);
|
||||
entangleTransitions(root, fiber, lane);
|
||||
}
|
||||
@@ -206,7 +206,7 @@ const classComponentUpdater = {
|
||||
|
||||
const root = enqueueUpdate(fiber, update, lane);
|
||||
if (root !== null) {
|
||||
startUpdateTimerByLane(lane);
|
||||
startUpdateTimerByLane(lane, 'this.replaceState()');
|
||||
scheduleUpdateOnFiber(root, fiber, lane);
|
||||
entangleTransitions(root, fiber, lane);
|
||||
}
|
||||
@@ -232,7 +232,7 @@ const classComponentUpdater = {
|
||||
|
||||
const root = enqueueUpdate(fiber, update, lane);
|
||||
if (root !== null) {
|
||||
startUpdateTimerByLane(lane);
|
||||
startUpdateTimerByLane(lane, 'this.forceUpdate()');
|
||||
scheduleUpdateOnFiber(root, fiber, lane);
|
||||
entangleTransitions(root, fiber, lane);
|
||||
}
|
||||
|
||||
+16
-5
@@ -1847,6 +1847,8 @@ function updateStoreInstance<T>(
|
||||
// snapsho and getSnapshot values to bail out. We need to check one more time.
|
||||
if (checkIfSnapshotChanged(inst)) {
|
||||
// Force a re-render.
|
||||
// We intentionally don't log update times and stacks here because this
|
||||
// was not an external trigger but rather an internal one.
|
||||
forceStoreRerender(fiber);
|
||||
}
|
||||
}
|
||||
@@ -1861,6 +1863,7 @@ function subscribeToStore<T>(
|
||||
// read from the store.
|
||||
if (checkIfSnapshotChanged(inst)) {
|
||||
// Force a re-render.
|
||||
startUpdateTimerByLane(SyncLane, 'updateSyncExternalStore()');
|
||||
forceStoreRerender(fiber);
|
||||
}
|
||||
};
|
||||
@@ -3355,8 +3358,16 @@ export function requestFormReset(formFiber: Fiber) {
|
||||
);
|
||||
}
|
||||
|
||||
const stateHook = ensureFormComponentIsStateful(formFiber);
|
||||
let stateHook: Hook = ensureFormComponentIsStateful(formFiber);
|
||||
const newResetState = {};
|
||||
if (stateHook.next === null) {
|
||||
// Hack alert. If formFiber is the workInProgress Fiber then
|
||||
// we might get a broken intermediate state. Try the alternate
|
||||
// instead.
|
||||
// TODO: We should really stash the Queue somewhere stateful
|
||||
// just like how setState binds the Queue.
|
||||
stateHook = (formFiber.alternate: any).memoizedState;
|
||||
}
|
||||
const resetStateHook: Hook = (stateHook.next: any);
|
||||
const resetStateQueue = resetStateHook.queue;
|
||||
dispatchSetStateInternal(
|
||||
@@ -3495,7 +3506,7 @@ function refreshCache<T>(fiber: Fiber, seedKey: ?() => T, seedValue: T): void {
|
||||
const refreshUpdate = createLegacyQueueUpdate(lane);
|
||||
const root = enqueueLegacyQueueUpdate(provider, refreshUpdate, lane);
|
||||
if (root !== null) {
|
||||
startUpdateTimerByLane(lane);
|
||||
startUpdateTimerByLane(lane, 'refresh()');
|
||||
scheduleUpdateOnFiber(root, provider, lane);
|
||||
entangleLegacyQueueTransitions(root, provider, lane);
|
||||
}
|
||||
@@ -3564,7 +3575,7 @@ function dispatchReducerAction<S, A>(
|
||||
} else {
|
||||
const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
|
||||
if (root !== null) {
|
||||
startUpdateTimerByLane(lane);
|
||||
startUpdateTimerByLane(lane, 'dispatch()');
|
||||
scheduleUpdateOnFiber(root, fiber, lane);
|
||||
entangleTransitionUpdate(root, queue, lane);
|
||||
}
|
||||
@@ -3598,7 +3609,7 @@ function dispatchSetState<S, A>(
|
||||
lane,
|
||||
);
|
||||
if (didScheduleUpdate) {
|
||||
startUpdateTimerByLane(lane);
|
||||
startUpdateTimerByLane(lane, 'setState()');
|
||||
}
|
||||
markUpdateInDevTools(fiber, lane, action);
|
||||
}
|
||||
@@ -3760,7 +3771,7 @@ function dispatchOptimisticSetState<S, A>(
|
||||
// will never be attempted before the optimistic update. This currently
|
||||
// holds because the optimistic update is always synchronous. If we ever
|
||||
// change that, we'll need to account for this.
|
||||
startUpdateTimerByLane(lane);
|
||||
startUpdateTimerByLane(lane, 'setOptimistic()');
|
||||
scheduleUpdateOnFiber(root, fiber, lane);
|
||||
// Optimistic updates are always synchronous, so we don't need to call
|
||||
// entangleTransitionUpdate here.
|
||||
|
||||
+539
-248
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -443,7 +443,7 @@ function updateContainerImpl(
|
||||
|
||||
const root = enqueueUpdate(rootFiber, update, lane);
|
||||
if (root !== null) {
|
||||
startUpdateTimerByLane(lane);
|
||||
startUpdateTimerByLane(lane, 'root.render()');
|
||||
scheduleUpdateOnFiber(root, rootFiber, lane);
|
||||
entangleTransitions(root, rootFiber, lane);
|
||||
}
|
||||
|
||||
@@ -345,9 +345,9 @@ export function doesFiberContain(
|
||||
return false;
|
||||
}
|
||||
|
||||
export function traverseFragmentInstance<A, B, C>(
|
||||
export function traverseFragmentInstance<I, A, B, C>(
|
||||
fragmentFiber: Fiber,
|
||||
fn: (Instance, A, B, C) => boolean,
|
||||
fn: (I, A, B, C) => boolean,
|
||||
a: A,
|
||||
b: B,
|
||||
c: C,
|
||||
@@ -355,9 +355,9 @@ export function traverseFragmentInstance<A, B, C>(
|
||||
traverseFragmentInstanceChildren(fragmentFiber.child, fn, a, b, c);
|
||||
}
|
||||
|
||||
function traverseFragmentInstanceChildren<A, B, C>(
|
||||
function traverseFragmentInstanceChildren<I, A, B, C>(
|
||||
child: Fiber | null,
|
||||
fn: (Instance, A, B, C) => boolean,
|
||||
fn: (I, A, B, C) => boolean,
|
||||
a: A,
|
||||
b: B,
|
||||
c: C,
|
||||
|
||||
@@ -264,6 +264,7 @@ import {
|
||||
import {
|
||||
blockingClampTime,
|
||||
blockingUpdateTime,
|
||||
blockingUpdateTask,
|
||||
blockingEventTime,
|
||||
blockingEventType,
|
||||
blockingEventIsRepeat,
|
||||
@@ -272,6 +273,7 @@ import {
|
||||
transitionClampTime,
|
||||
transitionStartTime,
|
||||
transitionUpdateTime,
|
||||
transitionUpdateTask,
|
||||
transitionEventTime,
|
||||
transitionEventType,
|
||||
transitionEventIsRepeat,
|
||||
@@ -1914,6 +1916,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
|
||||
blockingSpawnedUpdate,
|
||||
renderStartTime,
|
||||
lanes,
|
||||
blockingUpdateTask,
|
||||
);
|
||||
clearBlockingTimers();
|
||||
}
|
||||
@@ -1950,6 +1953,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
|
||||
transitionEventType,
|
||||
transitionEventIsRepeat,
|
||||
renderStartTime,
|
||||
transitionUpdateTask,
|
||||
);
|
||||
clearTransitionTimers();
|
||||
}
|
||||
|
||||
+15
-1
@@ -41,6 +41,13 @@ import * as Scheduler from 'scheduler';
|
||||
|
||||
const {unstable_now: now} = Scheduler;
|
||||
|
||||
const createTask =
|
||||
// eslint-disable-next-line react-internal/no-production-logging
|
||||
__DEV__ && console.createTask
|
||||
? // eslint-disable-next-line react-internal/no-production-logging
|
||||
console.createTask
|
||||
: (name: string) => null;
|
||||
|
||||
export let renderStartTime: number = -0;
|
||||
export let commitStartTime: number = -0;
|
||||
export let commitEndTime: number = -0;
|
||||
@@ -54,6 +61,7 @@ export let componentEffectErrors: null | Array<CapturedValue<mixed>> = null;
|
||||
|
||||
export let blockingClampTime: number = -0;
|
||||
export let blockingUpdateTime: number = -1.1; // First sync setState scheduled.
|
||||
export let blockingUpdateTask: null | ConsoleTask = null; // First sync setState's stack trace.
|
||||
export let blockingEventTime: number = -1.1; // Event timeStamp of the first setState.
|
||||
export let blockingEventType: null | string = null; // Event type of the first setState.
|
||||
export let blockingEventIsRepeat: boolean = false;
|
||||
@@ -63,6 +71,7 @@ export let blockingSuspendedTime: number = -1.1;
|
||||
export let transitionClampTime: number = -0;
|
||||
export let transitionStartTime: number = -1.1; // First startTransition call before setState.
|
||||
export let transitionUpdateTime: number = -1.1; // First transition setState scheduled.
|
||||
export let transitionUpdateTask: null | ConsoleTask = null; // First transition setState's stack trace.
|
||||
export let transitionEventTime: number = -1.1; // Event timeStamp of the first transition.
|
||||
export let transitionEventType: null | string = null; // Event type of the first transition.
|
||||
export let transitionEventIsRepeat: boolean = false;
|
||||
@@ -79,13 +88,14 @@ export function startYieldTimer(reason: SuspendedReason) {
|
||||
yieldReason = reason;
|
||||
}
|
||||
|
||||
export function startUpdateTimerByLane(lane: Lane): void {
|
||||
export function startUpdateTimerByLane(lane: Lane, method: string): void {
|
||||
if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
|
||||
return;
|
||||
}
|
||||
if (isSyncLane(lane) || isBlockingLane(lane)) {
|
||||
if (blockingUpdateTime < 0) {
|
||||
blockingUpdateTime = now();
|
||||
blockingUpdateTask = createTask(method);
|
||||
if (isAlreadyRendering()) {
|
||||
blockingSpawnedUpdate = true;
|
||||
}
|
||||
@@ -108,6 +118,7 @@ export function startUpdateTimerByLane(lane: Lane): void {
|
||||
} else if (isTransitionLane(lane)) {
|
||||
if (transitionUpdateTime < 0) {
|
||||
transitionUpdateTime = now();
|
||||
transitionUpdateTask = createTask(method);
|
||||
if (transitionStartTime < 0) {
|
||||
const newEventTime = resolveEventTimeStamp();
|
||||
const newEventType = resolveEventType();
|
||||
@@ -155,6 +166,7 @@ export function trackSuspendedTime(lanes: Lanes, renderEndTime: number) {
|
||||
|
||||
export function clearBlockingTimers(): void {
|
||||
blockingUpdateTime = -1.1;
|
||||
blockingUpdateTask = null;
|
||||
blockingSuspendedTime = -1.1;
|
||||
blockingEventIsRepeat = true;
|
||||
blockingSpawnedUpdate = false;
|
||||
@@ -194,6 +206,7 @@ export function startActionStateUpdate(): void {
|
||||
}
|
||||
if (transitionUpdateTime < 0) {
|
||||
transitionUpdateTime = ACTION_STATE_MARKER;
|
||||
transitionUpdateTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,6 +217,7 @@ export function clearAsyncTransitionTimer(): void {
|
||||
export function clearTransitionTimers(): void {
|
||||
transitionStartTime = -1.1;
|
||||
transitionUpdateTime = -1.1;
|
||||
transitionUpdateTask = null;
|
||||
transitionSuspendedTime = -1.1;
|
||||
transitionEventIsRepeat = true;
|
||||
}
|
||||
|
||||
@@ -160,6 +160,61 @@ describe('ReactFlightDOMEdge', () => {
|
||||
});
|
||||
}
|
||||
|
||||
function dripStream(input) {
|
||||
const reader = input.getReader();
|
||||
let nextDrop = 0;
|
||||
let controller = null;
|
||||
let streamDone = false;
|
||||
const buffer = [];
|
||||
function flush() {
|
||||
if (controller === null || nextDrop === 0) {
|
||||
return;
|
||||
}
|
||||
while (buffer.length > 0 && nextDrop > 0) {
|
||||
const nextChunk = buffer[0];
|
||||
if (nextChunk.byteLength <= nextDrop) {
|
||||
nextDrop -= nextChunk.byteLength;
|
||||
controller.enqueue(nextChunk);
|
||||
buffer.shift();
|
||||
if (streamDone && buffer.length === 0) {
|
||||
controller.done();
|
||||
}
|
||||
} else {
|
||||
controller.enqueue(nextChunk.subarray(0, nextDrop));
|
||||
buffer[0] = nextChunk.subarray(nextDrop);
|
||||
nextDrop = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
const output = new ReadableStream({
|
||||
start(c) {
|
||||
controller = c;
|
||||
async function pump() {
|
||||
for (;;) {
|
||||
const {value, done} = await reader.read();
|
||||
if (done) {
|
||||
streamDone = true;
|
||||
break;
|
||||
}
|
||||
buffer.push(value);
|
||||
flush();
|
||||
}
|
||||
}
|
||||
pump();
|
||||
},
|
||||
pull() {},
|
||||
cancel(reason) {
|
||||
reader.cancel(reason);
|
||||
},
|
||||
});
|
||||
function drip(n) {
|
||||
nextDrop += n;
|
||||
flush();
|
||||
}
|
||||
|
||||
return [output, drip];
|
||||
}
|
||||
|
||||
async function readResult(stream) {
|
||||
const reader = stream.getReader();
|
||||
let result = '';
|
||||
@@ -576,6 +631,67 @@ describe('ReactFlightDOMEdge', () => {
|
||||
expect(serializedContent.length).toBeLessThan(150 + expectedDebugInfoSize);
|
||||
});
|
||||
|
||||
it('should break up large sync components by outlining into streamable elements', async () => {
|
||||
const paragraphs = [];
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const text =
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris' +
|
||||
'porttitor tortor ac lectus faucibus, eget eleifend elit hendrerit.' +
|
||||
'Integer porttitor nisi in leo congue rutrum. Morbi sed ante posuere,' +
|
||||
'aliquam lorem ac, imperdiet orci. Duis malesuada gravida pharetra. Cras' +
|
||||
'facilisis arcu diam, id dictum lorem imperdiet a. Suspendisse aliquet' +
|
||||
'tempus tortor et ultricies. Aliquam libero velit, posuere tempus ante' +
|
||||
'sed, pellentesque tincidunt lorem. Nullam iaculis, eros a varius' +
|
||||
'aliquet, tortor felis tempor metus, nec cursus felis eros aliquam nulla.' +
|
||||
'Vivamus ut orci sed mauris congue lacinia. Cras eget blandit neque.' +
|
||||
'Pellentesque a massa in turpis ullamcorper volutpat vel at massa. Sed' +
|
||||
'ante est, auctor non diam non, vulputate ultrices metus. Maecenas dictum' +
|
||||
'fermentum quam id aliquam. Donec porta risus vitae pretium posuere.' +
|
||||
'Fusce facilisis eros in lacus tincidunt congue.' +
|
||||
i; /* trick dedupe */
|
||||
paragraphs.push(<p key={i}>{text}</p>);
|
||||
}
|
||||
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(paragraphs),
|
||||
);
|
||||
|
||||
const [stream2, drip] = dripStream(stream);
|
||||
|
||||
// Allow some of the content through.
|
||||
drip(5000);
|
||||
|
||||
const result = await ReactServerDOMClient.createFromReadableStream(
|
||||
stream2,
|
||||
{
|
||||
serverConsumerManifest: {
|
||||
moduleMap: null,
|
||||
moduleLoading: null,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// We should have resolved enough to be able to get the array even though some
|
||||
// of the items inside are still lazy.
|
||||
expect(result.length).toBe(20);
|
||||
|
||||
// Unblock the rest
|
||||
drip(Infinity);
|
||||
|
||||
// Use the SSR render to resolve any lazy elements
|
||||
const ssrStream = await serverAct(() =>
|
||||
ReactDOMServer.renderToReadableStream(result),
|
||||
);
|
||||
const html = await readResult(ssrStream);
|
||||
|
||||
const ssrStream2 = await serverAct(() =>
|
||||
ReactDOMServer.renderToReadableStream(paragraphs),
|
||||
);
|
||||
const html2 = await readResult(ssrStream2);
|
||||
|
||||
expect(html).toBe(html2);
|
||||
});
|
||||
|
||||
it('should be able to serialize any kind of typed array', async () => {
|
||||
const buffer = new Uint8Array([
|
||||
123, 4, 10, 5, 100, 255, 244, 45, 56, 67, 43, 124, 67, 89, 100, 20,
|
||||
|
||||
+22
-4
@@ -404,7 +404,10 @@ function isEligibleForOutlining(
|
||||
request: Request,
|
||||
boundary: SuspenseBoundary,
|
||||
): boolean {
|
||||
return boundary.byteSize > request.progressiveChunkSize;
|
||||
// For very small boundaries, don't bother producing a fallback for outlining.
|
||||
// The larger this limit is, the more we can save on preparing fallbacks in case we end up
|
||||
// outlining.
|
||||
return boundary.byteSize > 500;
|
||||
}
|
||||
|
||||
function defaultErrorHandler(error: mixed) {
|
||||
@@ -4902,6 +4905,10 @@ function flushSubtree(
|
||||
}
|
||||
}
|
||||
|
||||
// Running count for how much bytes of boundaries have flushed inlined into the currently
|
||||
// flushing root or completed boundary.
|
||||
let flushedByteSize = 0;
|
||||
|
||||
function flushSegment(
|
||||
request: Request,
|
||||
destination: Destination,
|
||||
@@ -4971,9 +4978,14 @@ function flushSegment(
|
||||
flushSubtree(request, destination, segment, hoistableState);
|
||||
|
||||
return writeEndPendingSuspenseBoundary(destination, request.renderState);
|
||||
} else if (isEligibleForOutlining(request, boundary)) {
|
||||
// This boundary is large and will be emitted separately so that we can progressively show
|
||||
// other content. We add it to the queue during the flush because we have to ensure that
|
||||
} else if (
|
||||
isEligibleForOutlining(request, boundary) &&
|
||||
flushedByteSize + boundary.byteSize > request.progressiveChunkSize
|
||||
) {
|
||||
// Inlining this boundary would make the current sequence being written too large
|
||||
// and block the parent for too long. Instead, it will be emitted separately so that we
|
||||
// can progressively show other content.
|
||||
// We add it to the queue during the flush because we have to ensure that
|
||||
// the parent flushes first so that there's something to inject it into.
|
||||
// We also have to make sure that it's emitted into the queue in a deterministic slot.
|
||||
// I.e. we can't insert it here when it completes.
|
||||
@@ -4999,6 +5011,8 @@ function flushSegment(
|
||||
|
||||
return writeEndPendingSuspenseBoundary(destination, request.renderState);
|
||||
} else {
|
||||
// We're inlining this boundary so its bytes get counted to the current running count.
|
||||
flushedByteSize += boundary.byteSize;
|
||||
if (hoistableState) {
|
||||
hoistHoistables(hoistableState, boundary.contentState);
|
||||
}
|
||||
@@ -5071,6 +5085,7 @@ function flushCompletedBoundary(
|
||||
destination: Destination,
|
||||
boundary: SuspenseBoundary,
|
||||
): boolean {
|
||||
flushedByteSize = boundary.byteSize; // Start counting bytes
|
||||
const completedSegments = boundary.completedSegments;
|
||||
let i = 0;
|
||||
for (; i < completedSegments.length; i++) {
|
||||
@@ -5098,6 +5113,7 @@ function flushPartialBoundary(
|
||||
destination: Destination,
|
||||
boundary: SuspenseBoundary,
|
||||
): boolean {
|
||||
flushedByteSize = boundary.byteSize; // Start counting bytes
|
||||
const completedSegments = boundary.completedSegments;
|
||||
let i = 0;
|
||||
for (; i < completedSegments.length; i++) {
|
||||
@@ -5191,6 +5207,8 @@ function flushCompletedQueues(
|
||||
return;
|
||||
}
|
||||
|
||||
flushedByteSize = request.byteSize; // Start counting bytes
|
||||
// TODO: Count the size of the preamble chunks too.
|
||||
flushPreamble(
|
||||
request,
|
||||
destination,
|
||||
|
||||
+46
-3
@@ -1600,6 +1600,29 @@ function renderClientElement(
|
||||
// The chunk ID we're currently rendering that we can assign debug data to.
|
||||
let debugID: null | number = null;
|
||||
|
||||
// Approximate string length of the currently serializing row.
|
||||
// Used to power outlining heuristics.
|
||||
let serializedSize = 0;
|
||||
const MAX_ROW_SIZE = 3200;
|
||||
|
||||
function deferTask(request: Request, task: Task): ReactJSONValue {
|
||||
// Like outlineTask but instead the item is scheduled to be serialized
|
||||
// after its parent in the stream.
|
||||
const newTask = createTask(
|
||||
request,
|
||||
task.model, // the currently rendering element
|
||||
task.keyPath, // unlike outlineModel this one carries along context
|
||||
task.implicitSlot,
|
||||
request.abortableTasks,
|
||||
__DEV__ ? task.debugOwner : null,
|
||||
__DEV__ ? task.debugStack : null,
|
||||
__DEV__ ? task.debugTask : null,
|
||||
);
|
||||
|
||||
pingTask(request, newTask);
|
||||
return serializeLazyID(newTask.id);
|
||||
}
|
||||
|
||||
function outlineTask(request: Request, task: Task): ReactJSONValue {
|
||||
const newTask = createTask(
|
||||
request,
|
||||
@@ -2393,6 +2416,8 @@ function renderModelDestructive(
|
||||
// Set the currently rendering model
|
||||
task.model = value;
|
||||
|
||||
serializedSize += parentPropertyName.length;
|
||||
|
||||
// Special Symbol, that's very common.
|
||||
if (value === REACT_ELEMENT_TYPE) {
|
||||
return '$';
|
||||
@@ -2442,6 +2467,10 @@ function renderModelDestructive(
|
||||
|
||||
const element: ReactElement = (value: any);
|
||||
|
||||
if (serializedSize > MAX_ROW_SIZE) {
|
||||
return deferTask(request, task);
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
const debugInfo: ?ReactDebugInfo = (value: any)._debugInfo;
|
||||
if (debugInfo) {
|
||||
@@ -2500,6 +2529,10 @@ function renderModelDestructive(
|
||||
return newChild;
|
||||
}
|
||||
case REACT_LAZY_TYPE: {
|
||||
if (serializedSize > MAX_ROW_SIZE) {
|
||||
return deferTask(request, task);
|
||||
}
|
||||
|
||||
// Reset the task's thenable state before continuing. If there was one, it was
|
||||
// from suspending the lazy before.
|
||||
task.thenableState = null;
|
||||
@@ -2811,6 +2844,7 @@ function renderModelDestructive(
|
||||
throwTaintViolation(tainted.message);
|
||||
}
|
||||
}
|
||||
serializedSize += value.length;
|
||||
// TODO: Maybe too clever. If we support URL there's no similar trick.
|
||||
if (value[value.length - 1] === 'Z') {
|
||||
// Possibly a Date, whose toJSON automatically calls toISOString
|
||||
@@ -3892,9 +3926,18 @@ function emitChunk(
|
||||
return;
|
||||
}
|
||||
// For anything else we need to try to serialize it using JSON.
|
||||
// $FlowFixMe[incompatible-type] stringify can return null for undefined but we never do
|
||||
const json: string = stringify(value, task.toJSON);
|
||||
emitModelChunk(request, task.id, json);
|
||||
// We stash the outer parent size so we can restore it when we exit.
|
||||
const parentSerializedSize = serializedSize;
|
||||
// We don't reset the serialized size counter from reentry because that indicates that we
|
||||
// are outlining a model and we actually want to include that size into the parent since
|
||||
// it will still block the parent row. It only restores to zero at the top of the stack.
|
||||
try {
|
||||
// $FlowFixMe[incompatible-type] stringify can return null for undefined but we never do
|
||||
const json: string = stringify(value, task.toJSON);
|
||||
emitModelChunk(request, task.id, json);
|
||||
} finally {
|
||||
serializedSize = parentSerializedSize;
|
||||
}
|
||||
}
|
||||
|
||||
function erroredTask(request: Request, task: Task, error: mixed): void {
|
||||
|
||||
@@ -28,3 +28,4 @@ export const enableSiblingPrerendering = __VARIANT__;
|
||||
export const enableFastAddPropertiesInDiffing = __VARIANT__;
|
||||
export const enableLazyPublicInstanceInFabric = __VARIANT__;
|
||||
export const renameElementSymbol = __VARIANT__;
|
||||
export const enableFragmentRefs = __VARIANT__;
|
||||
|
||||
@@ -30,6 +30,7 @@ export const {
|
||||
enableFastAddPropertiesInDiffing,
|
||||
enableLazyPublicInstanceInFabric,
|
||||
renameElementSymbol,
|
||||
enableFragmentRefs,
|
||||
} = dynamicFlags;
|
||||
|
||||
// The rest of the flags are static for better dead code elimination.
|
||||
@@ -84,7 +85,6 @@ export const enableGestureTransition = false;
|
||||
export const enableScrollEndPolyfill = true;
|
||||
export const enableSuspenseyImages = false;
|
||||
export const enableSrcObject = false;
|
||||
export const enableFragmentRefs = false;
|
||||
export const ownerStackLimit = 1e4;
|
||||
|
||||
// Flow magic to verify the exports of this file match the original version.
|
||||
|
||||
@@ -543,5 +543,6 @@
|
||||
"555": "Cannot requestFormReset() inside a startGestureTransition. There should be no side-effects associated with starting a Gesture until its Action is invoked. Move side-effects to the Action instead.",
|
||||
"556": "Expected prepareToHydrateHostActivityInstance() to never be called. This error is likely caused by a bug in React. Please file an issue.",
|
||||
"557": "Expected to have a hydrated activity instance. This error is likely caused by a bug in React. Please file an issue.",
|
||||
"558": "Client rendering an Activity suspended it again. This is a bug in React."
|
||||
"558": "Client rendering an Activity suspended it again. This is a bug in React.",
|
||||
"559": "Expected to find a host node. This is a bug in React."
|
||||
}
|
||||
|
||||
@@ -33,6 +33,46 @@ declare interface ConsoleTask {
|
||||
run<T>(f: () => T): T;
|
||||
}
|
||||
|
||||
declare var console: {
|
||||
assert(condition: mixed, ...data: Array<any>): void,
|
||||
clear(): void,
|
||||
count(label?: string): void,
|
||||
countReset(label?: string): void,
|
||||
debug(...data: Array<any>): void,
|
||||
dir(...data: Array<any>): void,
|
||||
dirxml(...data: Array<any>): void,
|
||||
error(...data: Array<any>): void,
|
||||
_exception(...data: Array<any>): void,
|
||||
group(...data: Array<any>): void,
|
||||
groupCollapsed(...data: Array<any>): void,
|
||||
groupEnd(): void,
|
||||
info(...data: Array<any>): void,
|
||||
log(...data: Array<any>): void,
|
||||
profile(name?: string): void,
|
||||
profileEnd(name?: string): void,
|
||||
table(
|
||||
tabularData:
|
||||
| {[key: string]: any, ...}
|
||||
| Array<{[key: string]: any, ...}>
|
||||
| Array<Array<any>>,
|
||||
): void,
|
||||
time(label?: string): void,
|
||||
timeEnd(label: string): void,
|
||||
timeStamp(
|
||||
label?: string,
|
||||
start?: string | number,
|
||||
end?: string | number,
|
||||
trackName?: string,
|
||||
trackGroup?: string,
|
||||
color?: string,
|
||||
): void,
|
||||
timeLog(label?: string, ...data?: Array<any>): void,
|
||||
trace(...data: Array<any>): void,
|
||||
warn(...data: Array<any>): void,
|
||||
createTask(label: string): ConsoleTask,
|
||||
...
|
||||
};
|
||||
|
||||
type ScrollTimelineOptions = {
|
||||
source: Element,
|
||||
axis?: 'block' | 'inline' | 'x' | 'y',
|
||||
|
||||
Reference in New Issue
Block a user