Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc50ab4bff |
@@ -83,6 +83,8 @@ export class ProgramContext {
|
||||
knownReferencedNames: Set<string> = new Set();
|
||||
// generated imports
|
||||
imports: Map<string, Map<string, NonLocalImportSpecifier>> = new Map();
|
||||
// hoisted program-level constants
|
||||
constants: Map<string, {uid: string; init: t.Expression}> = new Map();
|
||||
|
||||
constructor({
|
||||
program,
|
||||
@@ -182,6 +184,16 @@ export class ProgramContext {
|
||||
this.knownReferencedNames.add(name);
|
||||
}
|
||||
|
||||
addProgramConstant(name: string, init: () => t.Expression): string {
|
||||
const existing = this.constants.get(name);
|
||||
if (existing !== undefined) {
|
||||
return existing.uid;
|
||||
}
|
||||
const uid = this.newUid(name);
|
||||
this.constants.set(name, {uid, init: init()});
|
||||
return uid;
|
||||
}
|
||||
|
||||
assertGlobalBinding(
|
||||
name: string,
|
||||
localScope?: BabelScope,
|
||||
@@ -302,6 +314,13 @@ export function addImportsToProgram(
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const [, {uid, init}] of programContext.constants) {
|
||||
stmts.push(
|
||||
t.variableDeclaration('const', [
|
||||
t.variableDeclarator(t.identifier(uid), init),
|
||||
]),
|
||||
);
|
||||
}
|
||||
path.unshiftContainer('body', stmts);
|
||||
}
|
||||
|
||||
|
||||
@@ -502,6 +502,16 @@ export const EnvironmentConfigSchema = z.object({
|
||||
* 3. Force update / external sync - should use useSyncExternalStore
|
||||
*/
|
||||
enableVerboseNoSetStateInEffect: z.boolean().default(false),
|
||||
|
||||
/**
|
||||
* When enabled, the compiler emits `!Object.is(a, b)` instead of `a !== b`
|
||||
* for dependency comparisons in memoized scopes. This matches the comparison
|
||||
* semantics used by React's own hooks (useState, useMemo, etc.), which use
|
||||
* Object.is. The main difference is in handling of NaN: `NaN !== NaN` is
|
||||
* always true (causing memos to never be reused), while
|
||||
* `Object.is(NaN, NaN)` is true (correctly treating NaN as unchanged).
|
||||
*/
|
||||
enableObjectIsComparison: z.boolean().default(false),
|
||||
});
|
||||
|
||||
export type EnvironmentConfig = z.infer<typeof EnvironmentConfigSchema>;
|
||||
|
||||
@@ -182,8 +182,8 @@ export function codegenFunction(
|
||||
const index = cx.synthesizeName('$i');
|
||||
preface.push(
|
||||
t.ifStatement(
|
||||
t.binaryExpression(
|
||||
'!==',
|
||||
codegenNegatedComparison(
|
||||
cx,
|
||||
t.memberExpression(
|
||||
t.identifier(cx.synthesizeName('$')),
|
||||
t.numericLiteral(fastRefreshState.cacheIndex),
|
||||
@@ -557,6 +557,23 @@ function codegenBlockNoReset(
|
||||
return t.blockStatement(statements);
|
||||
}
|
||||
|
||||
function codegenNegatedComparison(
|
||||
cx: Context,
|
||||
left: t.Expression,
|
||||
right: t.Expression,
|
||||
): t.Expression {
|
||||
if (cx.env.config.enableObjectIsComparison) {
|
||||
const isName = cx.env.programContext.addProgramConstant('is', () =>
|
||||
t.memberExpression(t.identifier('Object'), t.identifier('is')),
|
||||
);
|
||||
return t.unaryExpression(
|
||||
'!',
|
||||
t.callExpression(t.identifier(isName), [left, right]),
|
||||
);
|
||||
}
|
||||
return t.binaryExpression('!==', left, right);
|
||||
}
|
||||
|
||||
function codegenReactiveScope(
|
||||
cx: Context,
|
||||
statements: Array<t.Statement>,
|
||||
@@ -574,8 +591,8 @@ function codegenReactiveScope(
|
||||
|
||||
for (const dep of [...scope.dependencies].sort(compareScopeDependency)) {
|
||||
const index = cx.nextCacheIndex;
|
||||
const comparison = t.binaryExpression(
|
||||
'!==',
|
||||
const comparison = codegenNegatedComparison(
|
||||
cx,
|
||||
t.memberExpression(
|
||||
t.identifier(cx.synthesizeName('$')),
|
||||
t.numericLiteral(index),
|
||||
@@ -720,8 +737,8 @@ function codegenReactiveScope(
|
||||
const name: ValidIdentifierName = earlyReturnValue.value.name.value;
|
||||
statements.push(
|
||||
t.ifStatement(
|
||||
t.binaryExpression(
|
||||
'!==',
|
||||
codegenNegatedComparison(
|
||||
cx,
|
||||
t.identifier(name),
|
||||
t.callExpression(
|
||||
t.memberExpression(t.identifier('Symbol'), t.identifier('for')),
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableObjectIsComparison
|
||||
const is = null;
|
||||
|
||||
function Component(props) {
|
||||
const x = [props.x];
|
||||
console.log(is);
|
||||
return x;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{x: 42}],
|
||||
sequentialRenders: [{x: 42}, {x: 42}, {x: 3.14}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
const _is = Object.is; // @enableObjectIsComparison
|
||||
const is = null;
|
||||
|
||||
function Component(props) {
|
||||
const $ = _c(2);
|
||||
let t0;
|
||||
if (!_is($[0], props.x)) {
|
||||
t0 = [props.x];
|
||||
$[0] = props.x;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
const x = t0;
|
||||
console.log(is);
|
||||
return x;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ x: 42 }],
|
||||
sequentialRenders: [{ x: 42 }, { x: 42 }, { x: 3.14 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [42]
|
||||
[42]
|
||||
[3.14]
|
||||
logs: [null,null,null]
|
||||
@@ -0,0 +1,14 @@
|
||||
// @enableObjectIsComparison
|
||||
const is = null;
|
||||
|
||||
function Component(props) {
|
||||
const x = [props.x];
|
||||
console.log(is);
|
||||
return x;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{x: 42}],
|
||||
sequentialRenders: [{x: 42}, {x: 42}, {x: 3.14}],
|
||||
};
|
||||
@@ -0,0 +1,91 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableObjectIsComparison
|
||||
import {makeArray} from 'shared-runtime';
|
||||
|
||||
function Component(props) {
|
||||
let x = [];
|
||||
if (props.cond) {
|
||||
x.push(props.a);
|
||||
return x;
|
||||
} else {
|
||||
return makeArray(props.b);
|
||||
}
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [],
|
||||
sequentialRenders: [
|
||||
{cond: true, a: 42},
|
||||
{cond: true, a: 42},
|
||||
{cond: false, b: 3.14},
|
||||
{cond: false, b: 3.14},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
const is = Object.is; // @enableObjectIsComparison
|
||||
import { makeArray } from "shared-runtime";
|
||||
|
||||
function Component(props) {
|
||||
const $ = _c(6);
|
||||
let t0;
|
||||
if (!is($[0], props.a) || !is($[1], props.b) || !is($[2], props.cond)) {
|
||||
t0 = Symbol.for("react.early_return_sentinel");
|
||||
bb0: {
|
||||
const x = [];
|
||||
if (props.cond) {
|
||||
x.push(props.a);
|
||||
t0 = x;
|
||||
break bb0;
|
||||
} else {
|
||||
let t1;
|
||||
if (!is($[4], props.b)) {
|
||||
t1 = makeArray(props.b);
|
||||
$[4] = props.b;
|
||||
$[5] = t1;
|
||||
} else {
|
||||
t1 = $[5];
|
||||
}
|
||||
t0 = t1;
|
||||
break bb0;
|
||||
}
|
||||
}
|
||||
$[0] = props.a;
|
||||
$[1] = props.b;
|
||||
$[2] = props.cond;
|
||||
$[3] = t0;
|
||||
} else {
|
||||
t0 = $[3];
|
||||
}
|
||||
if (!is(t0, Symbol.for("react.early_return_sentinel"))) {
|
||||
return t0;
|
||||
}
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [],
|
||||
sequentialRenders: [
|
||||
{ cond: true, a: 42 },
|
||||
{ cond: true, a: 42 },
|
||||
{ cond: false, b: 3.14 },
|
||||
{ cond: false, b: 3.14 },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [42]
|
||||
[42]
|
||||
[3.14]
|
||||
[3.14]
|
||||
@@ -0,0 +1,23 @@
|
||||
// @enableObjectIsComparison
|
||||
import {makeArray} from 'shared-runtime';
|
||||
|
||||
function Component(props) {
|
||||
let x = [];
|
||||
if (props.cond) {
|
||||
x.push(props.a);
|
||||
return x;
|
||||
} else {
|
||||
return makeArray(props.b);
|
||||
}
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [],
|
||||
sequentialRenders: [
|
||||
{cond: true, a: 42},
|
||||
{cond: true, a: 42},
|
||||
{cond: false, b: 3.14},
|
||||
{cond: false, b: 3.14},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableObjectIsComparison @enableResetCacheOnSourceFileChanges
|
||||
function Component(props) {
|
||||
const x = [props.x];
|
||||
return x;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{x: 42}],
|
||||
sequentialRenders: [{x: 42}, {x: 42}, {x: 3.14}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
const is = Object.is; // @enableObjectIsComparison @enableResetCacheOnSourceFileChanges
|
||||
function Component(props) {
|
||||
const $ = _c(3);
|
||||
if (
|
||||
!is(
|
||||
$[0],
|
||||
"eb2ec56d8fdd083c203a119ff37576dc8782330598640f725524102ae79e8b5c",
|
||||
)
|
||||
) {
|
||||
for (let $i = 0; $i < 3; $i += 1) {
|
||||
$[$i] = Symbol.for("react.memo_cache_sentinel");
|
||||
}
|
||||
$[0] = "eb2ec56d8fdd083c203a119ff37576dc8782330598640f725524102ae79e8b5c";
|
||||
}
|
||||
let t0;
|
||||
if (!is($[1], props.x)) {
|
||||
t0 = [props.x];
|
||||
$[1] = props.x;
|
||||
$[2] = t0;
|
||||
} else {
|
||||
t0 = $[2];
|
||||
}
|
||||
const x = t0;
|
||||
return x;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ x: 42 }],
|
||||
sequentialRenders: [{ x: 42 }, { x: 42 }, { x: 3.14 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [42]
|
||||
[42]
|
||||
[3.14]
|
||||
@@ -0,0 +1,11 @@
|
||||
// @enableObjectIsComparison @enableResetCacheOnSourceFileChanges
|
||||
function Component(props) {
|
||||
const x = [props.x];
|
||||
return x;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{x: 42}],
|
||||
sequentialRenders: [{x: 42}, {x: 42}, {x: 3.14}],
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableObjectIsComparison
|
||||
function Component(props) {
|
||||
const x = [props.x];
|
||||
return x;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{x: 42}],
|
||||
sequentialRenders: [{x: 42}, {x: 42}, {x: 3.14}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
const is = Object.is; // @enableObjectIsComparison
|
||||
function Component(props) {
|
||||
const $ = _c(2);
|
||||
let t0;
|
||||
if (!is($[0], props.x)) {
|
||||
t0 = [props.x];
|
||||
$[0] = props.x;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
const x = t0;
|
||||
return x;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ x: 42 }],
|
||||
sequentialRenders: [{ x: 42 }, { x: 42 }, { x: 3.14 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [42]
|
||||
[42]
|
||||
[3.14]
|
||||
@@ -0,0 +1,11 @@
|
||||
// @enableObjectIsComparison
|
||||
function Component(props) {
|
||||
const x = [props.x];
|
||||
return x;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{x: 42}],
|
||||
sequentialRenders: [{x: 42}, {x: 42}, {x: 3.14}],
|
||||
};
|
||||
Reference in New Issue
Block a user