```js
// here, `a?.b.c` is a single optional chain
// (evaluates to undefined if a is nullish)
a?.b.c;
// here, 'a?.b` is an optional chain, and `.c` is an unconditional load
// (nullthrows if a is nullish)
(a?.b).c;
```
---
Next PR in stack will add a bailout for `(a?.b).c`.
(If we want to properly handle `(a?.b).c`, we might want to model optional
chains explicitly in the HIR. We currently assume that any `PropertyLoad` whose
lhs is an optional property load is read conditionally.)
This is incredibly obvious in hindsight, but for exposure logging to work
correctly we need to *call* the underlying `MobileConfig.getBool` function at
the callsite – otherwise the bool is evaluated once (and only once) when the
module is loaded.
Tested internally and verified that in dogfooding the exposure logging was
working correctly
This PR has a bunch of surrounding refactoring. See individual commits.
The main change is that we no longer special case `typeof is ===
'string'` as a special case according to the
`enableCustomElementPropertySupport` flag.
Effectively this means that you can't use custom properties/events,
other than the ones React knows about on `<input is="my-input">`
extensions.
This is unfortunate but there's too many paths that are forked in
inconsistent ways since we fork based on tag name. I think __the
solution is to let all React elements set unknown properties/events in
the same way as this flag__ but that's a bigger change than this flag
implies.
Since `is` is not universally supported yet anyway, this doesn't seem
like a huge loss. Attributes still work.
We still support passing the `is` prop and turn that into the
appropriate createElement call.
@josepharhar
## Summary
Our toy webpack plugin for Server Components is pretty broken right now
because, now that `.client.js` convention is gone, it ends up adding
every single JS file it can find (including `node_modules`) as a
potential async dependency. Instead, it should only look for files with
the `'use client'` directive.
The ideal way is to implement this by bundling the RSC graph first.
Then, we would know which `'use client'` files were actually discovered
— and so there would be no point to scanning the disk for them. That's
how Next.js bundler does it.
We're not doing that here.
This toy plugin is very simple, and I'm not planning to do heavy
lifting. I'm just bringing it up to date with the convention. The change
is that we now read every file we discover (alas), bail if it has no
`'use client'`, and parse it if it does (to verify it's actually used as
a directive). I've changed to use `acorn-loose` because it's forgiving
of JSX (and likely TypeScript/Flow). Otherwise, this wouldn't work on
uncompiled source.
## Test plan
Verified I can get our initial Server Components Demo running after this
change. Previously, it would get stuck compiling and then emit thousands
of errors.
Also confirmed the fixture still works. (It doesn’t work correctly on
the first load after dev server starts, but that’s already the case on
main so seems unrelated.)
Normally we allow any attribute/property on custom elements. However
it's a shared namespace. The `aria-` namespace applies to all generic
elements which are shared with custom elements. So arguably adding
custom extensions there is a really bad idea since it can conflict with
future additions.
It's possible there is a new standard one that's polyfilled by a custom
element but the same issue applies to React in general that we might
warn for very new additions so we just have to be quick on that.
cc @josepharhar
- Fixes a missing break in InferTypes - I disabled no-fallthrough previously
because it would erroneously report that certain cases with non-builtin throws
(eg `invariant`) would fall through. This brings the rule back but allows
disabling it with a `// break omitted` comment, since it's still helpful in
catching some actual missing breaks.
- Add `DEFAULT_SHAPES` ShapeRegistry, which holds builtins and all
`ObjectShapes` used in `DEFAULT_GLOBALS`.
- Add a few typed objects / functions into `DEFAULT_GLOBALS` (used for tests)
- Add type inference and `infer-global-object` test
Adds `GlobalRegistry`, which holds the names and types of known global objects,
i.e.
```js
type GlobalRegistry = Map<string, PrimitiveType | ObjectType | FunctionType |
HookType | PolyType>;
// ...
globalRegistry.get("NaN"); // {kind: "Primitive"}
globalRegistry.get("parseInt"); // {kind: "Function", shapeId: "..."}
globalRegistry.get("Math"); // {kind: "Object", shapeId: "..."}
```
Since we currently do not track module imports and module-level declarations,
builtin and custom hooks currently also live in GlobalRegistry.
```js
globalRegistry.get("useState"); // {kind: "Hook", definition: {...}}
globalRegistry.get("useFreeze"); // {kind: Hook, definition: {...}}
```
This PR does not allow Forget users to define their own globals. When we add
this as a configuration, we should not expose `ShapeRegistry` to the user, as a
user-provided ShapeRegistry may accidentally be not well formed. (i.e. missing
(1) required shapes (BuiltInArray for [] and BuiltInObject for {}) or (2) some
recursive shapeIds)
```js
export type UserType = UserObject | UserFunction | "Primitive" | "BuiltinObject"
| ...;
export type UserObject = {
kind: "Object",
properties: Map<string, UserType>
}
export type UserFunction = {
kind: "Function",
properties: Map<string, UserType>,
signature: ...
}
export type UserGlobals = Map<string, UserType>;
class Environment {
constructor(globals: Map<string, UserType>, ...) {
// ...
addUserDefinedGlobals(this.#globals, this.#shapes);
```
---
Simplify Environment options by:
- EnvironmentOptions -> EnvironmentConfig
config is now directly passed around instead of being eagerly merged.
- Moving merging / initialization logic into `Environment` constructor. From my
understanding, there is no need to decouple merged options from an environment.
This prepares Environment for the next PR, which adds non-stateful properties to
Environment (i.e. a `GlobalRegistry`) that should be converted from config
values (i.e. not directly exposed to the user due to potentially inconsistent
inputs)
---
#1254 added inference for hooks loaded from globals. This is the only time we
need to generate a type equation assigning `lval` to a resolved`Hook` type.
@gsathya Would love to get your feedback here on the change. From my
understanding, this change is technically incorrect, since the type equation we
generate should be dependent on the `callee` type (i.e. `Hook` if callee is a
hook, `Function` if callee is a function).
Would the next step be to consolidate `Hook` and `Function` types?
```js
type Function {
...
isHook: boolean, // set by inference
}
type FunctionSignature {
isHook: boolean, // set when adding to ShapeRegistry
}
```
This is a step towards getting rid of the meta programming in
DOMProperty and CSSProperty.
This moves isAttributeNameSafe and isUnitlessNumber to a separate shared
modules.
isUnitlessNumber is now a single switch instead of meta-programming.
There is a slight behavior change here in that I hard code a specific
set of vendor-prefixed attributes instead of prefixing all the unitless
properties. I based this list on what getComputedStyle returns in
current browsers. I removed Opera prefixes because they were [removed in
Opera](https://dev.opera.com/blog/css-vendor-prefixes-in-opera-12-50-snapshots/)
itself. I included the ms ones mentioned [in the original
PR](https://github.com/facebook/react/commit/5abcce534382d85887f3d33475e8e54e3b5d8457).
These shouldn't really be used anymore anyway so should be pretty safe.
Worst case, they'll fallback to the other property if you specify both.
Finally I inline the mustUseProperty special cases - which are also the
only thing that uses propertyName. These are really all controlled
components and all booleans.
I'm making a small breaking change here by treating `checked` and
`selected` specially only on the `input` and `option` tags instead of
all tags. That's because those are the only DOM nodes that actually have
those properties but we used to set them as expandos instead of
attributes before. That's why one of the tests is updated to now use
`input` instead of testing an expando on a `div` which isn't a real use
case. Interestingly this also uncovered that we update checked twice for
some reason but keeping that logic for now.
Ideally `multiple` and `muted` should move into `select` and
`audio`/`video` respectively for the same reason.
No change to the attribute-behavior fixture.
This is a change to some undefined behavior that we though we would do
at one point but decided not to roll out. It's already disabled
everywhere, so this just deletes the branch from the implementation and
the tests.
## Summary
Updates the `useMemoCache()` tests to validate that the memo cache
persists when a component does a setState during render or throws during
render. Forget's compilation output follows the general pattern used in
this test and is resilient to rendering running partway and then again
with different inputs.
## How did you test this change?
`yarn test` (this is a test-only change)
This is not really part of the bindings, it's more part of the package
entry points. /shared/ is not really right neither because it's more
like an isomorphic entry point and not some utility.
We currently throw an error when disableJavaScriptURLs is on and trigger
an error boundary. I kind of thought that's what would happen with CSP
or Trusted Types anyway. However, that's not what happens. Instead, in
those environments what happens is that the error is triggered when you
try to actually visit those links. So if you `preventDefault()` or
something it'll never show up and since the error just logs to the
console or to a violation logger, it's effectively a noop to users.
We can simulate the same without CSP by simply generating a different
`javascript:` url that throws instead of executing the potential attack
vector.
This still allows these to be used - at least as long as you
preventDefault before using them in practice. This might be legit for
forms. We still don't recommend using them for links-as-buttons since
it'll be possible to "Open in a New Tab" and other weird artifacts. For
links we still recommend the technique of assigning a button role etc.
It also is a little nicer when an attack actually happens because at
least it doesn't allow an attacker to trigger error boundaries and
effectively deny access to a page.
This deletes the ReactIncrementalTriangle test suite, which I originally
added back in 2017 when I was working on Fiber's "resuming" feature. It
was meant to simulate a similar scenario as Seb's "Sierpinski Triangle"
Fiber demo.
We eventually ended up removing resuming, but we kept this fuzz tester
around since it wasn't really harming anything. However, over the years,
we've had to make many small tweaks to decouple it from implementation
details, to the point that it doesn't test anything useful anymore. And
the thing that it originally tested has long since been removed.
If or when we do add back resuming, we would write a different fuzz
tester from scratch rather than build on this one.
So rather than continue to contrive ways to prevent it from breaking, I
propose we delete it.
We still have other fuzz testers for things like Suspense and context
propagation. Only this particular one has outlived its usefulness.
## Summary
With https://github.com/facebook/react/pull/26349 we now serialize
`undefined`. However, deserializing it on the client is currently
indistinguishable from the value missing entirely due to how
`JSON.parse` treats `undefined` return value of reviver functions.
This leads to inconsistent behavior of the `Object.hasOwn` or `in`
operator (used for narrowing in TypeScript). In TypeScript-speak, `{
prop: T | undefined}` will arrive as `{ prop?: T }`.
## How did you test this change?
- Added test that is expected to fail. Though ideally the implementation
of the component would not care whether it's used on the client or
server.
I use a shared helper when setting properties into a helper whether it's
initial or update.
I moved the special cases per tag to commit phase so we can check it
only once. This also effectively inlines getHostProps which can be done
in a single check per prop key.
The diffProperties operation is simplified to mostly just generating a
plain diff of all properties, generating an update payload. This might
generate a few more entries that are now ignored in the commit phase.
that previously would've been ignored earlier. We could skip this and
just do the whole diff in the commit phase by always scheduling a commit
phase update.
I tested the attribute table (one change documented below) and a few
select DOM fixtures.
This updates the Suspense fuzz tester to use `act` to recursively flush
timers instead of doing it manually.
This still isn't great because ideally the fuzz tester wouldn't fake
timers at all. It should resolve promises using a custom queue instead
of Jest's fake timer queue, like we've started doing in our other
Suspense tests (i.e. the `resolveText` pattern). That's because our
internal `act` API (not the public one, the one we use in our tests)
uses Jest's fake timer queue as a way to force Suspense fallbacks to
appear.
However I'm not interested in upgrading this test suite to a better
strategy right now because if I were writing a Suspense fuzzer today I
would probably use an entirely different approach. So this is just an
incremental improvement to make it slightly less decoupled to React
implementation details.
I already taught `lowerAssignment()` to handle assignment patterns for
destructuring, we just have to call this helper for assignment pattern params
too.
In a lambda, a return/throw terminal could return a captured context ref needs
to be treated as a mutation to correctly alias the returned context ref and the
lvalue.
Terminal operands are generally not mutating so this hasn't mattered so far. But
in a lambda, a return terminal could return a captured context ref which needs
to be treated as a mutation to correctly alias the returned context ref and the
lvalue.
I rewrote some of our tests that deal with microtasks with the aim of
making them less coupled to implementation details. This is related to
an upcoming change to move update processing into a microtask.
`JSXEmptyExpression` is never added to a React element's children [in
`react.buildChildren`](https://github.com/babel/babel/blob/main/packages/babel-types/src/builders/react/buildChildren.ts),
which is [used
by](https://github.com/babel/babel/blob/main/packages/babel-plugin-transform-react-jsx/src/create-plugin.ts#L649)
`plugin-transform-react-jsx`].
An alternative would be to represent JSX expressions differently in HIR, then
codegen `JSXEmptyExpression`s back when we encounter an `EmptyExpression`
```js
- children: Array<Place>,
- children: Array<Place | "EmptyExpression">,
```
(We could also retain `JSXEmptyExpression` as an `InstructionValue` that
produces a Primitive. However, this would make babel types in Codegen a bit more
messy, as `JSXEmptyExpression` does not extend `Expression` (which currently is
the result of every `InstructionValue`).)
Continuing my journey to migrate all the Scheduler flush* methods to
async versions of the same helpers.
`unstable_flushExpired` is a rarely used helper that is only meant to be
used to test a very particular implementation detail (update starvation
prevention, or what we sometimes refer to as "expiration").
I've prefixed the new helper with `unstable_`, too, to indicate that our
tests should almost always prefer one of the other patterns instead.
Creates a new helper, `const temp: Place = lowerValueToTemporary(builder,
value)` which creates a new temporary and an instruction to write that value to
the temporary. We have this pattern all over BuildHIR, and the new helper makes
this all a bit tidier.
Adds support for `await` expressions. We have primarily seen await used inside
callbacks, not directly within component render logic, but because we construct
HIR for lambdas it is helpful to be able to model await rather than require
everyone to rewrite to use the Promise API. Note a subtlety: awaiting a promise
is a mutative operation, so we a) model it as a Mutate effect and b) avoid DCE
of await expressions since they may cause side effects. See the test cases for
examples.
Adds a new helper method that we can use when processing expressions whose
evaluation ordering may not be preserved. This was previously the case only for
switch test case values, but we can use this for AssignmentPattern
(destructuring default values) as well.
"Supports" default values in destructuring (AssignmentPattern) by lowering to a
ternary, even in the output. Examples:
```javascript
// Input:
const [x = 'default'] = y;
// Output:
const [t0] = y;
const x = t0 === undefined ? 'default' : t0;
```
```javascript
// Input 2
const [{x} = makeObject()] = y;
// Output 2
const [t0] = y;
const {x} = t0 === undefined ? makeObject() : t0;
```
Note that this is how Babel lowers AssignmentPattern, so it isn't too bad. This
should help avoid the need to update product code, even if the output isn't
perfectly ideal.
This is kind of a hack, but i think it's worth it given that JSXNamespacedName
is relatively uncommon. Adding a new InstructionValue variant to represent a
namespaced name is one option, but then that isn't a valid expression and can't
appear as an operand anywhere else. Instead, we lower namespaced names as a
primitive (string) as `${namespace}:${name}` — exploiting the fact the namespace
and name can't have a colon, and non-namespaced tagnames also can't have colons.
It's a bit of a hack but it's contained to the JSX processing code. If folks
have strong opinions on this i'm happy to change but this felt reasonable as a
quick and reliable way to unblock support.
NOTE: there is a larger question of what to do about compiling `fbt` tags.
Before we can do anything with them, though, we need to parse them.