Compare commits

..

5 Commits

Author SHA1 Message Date
Mofei Zhang
c19f052b2d [compiler][ez] Move compiler gating tests
Move all gating tests to `gating/`
2025-03-13 11:52:25 -04:00
Mofei Zhang
d2d5350e65 [compiler][optim] more shapes for mixedreadonly
- Add `at`, `indexOf`, and `includes`
- Optimize MixedReadOnly which is currently only used by hook return values. Hook return values are typed as Frozen, this change propagates that to return values of aliasing function calls (such as `at`). One potential issue is that  developers may pass `enableAssumeHooksFollowRulesOfReact:false` and set `transitiveMixedData`, expecting their transitive mixed data to be mutable. This is a bit of an edge case and already doesn't have clear semantics.
2025-03-13 11:52:25 -04:00
Mofei Zhang
8ba22587b5 [compiler][optim] infer mixedReadOnly for numeric and computed properties
Expand type inference to infer mixedReadOnly types for numeric and computed property accesses.
```js
function Component({idx})
  const data = useFragment(...)
  // we want to type `posts` correctly as Array
  const posts = data.viewers[idx].posts.slice(0, 5);
  // ...
}
```
2025-03-13 11:52:25 -04:00
Mofei Zhang
7606b29048 [compiler][optim] Add shape for Array.from
(see title)
2025-03-13 11:52:25 -04:00
Mofei Zhang
54e602d891 [compiler] Patch array and argument spread mutability
Array and argument spreads may mutate stateful iterables. Spread sites should have `ConditionallyMutate` effects (e.g. mutate if the ValueKind is mutable, otherwise read).

See
- [ecma spec (13.2.4.1 Runtime Semantics: ArrayAccumulation. SpreadElement : ... AssignmentExpression)](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-runtime-semantics-arrayaccumulation).
- [ecma spec 13.3.8.1 Runtime Semantics: ArgumentListEvaluation](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-runtime-semantics-argumentlistevaluation)

Note that
- Object and JSX Attribute spreads do not evaluate iterables (srcs [mozilla](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#description), [ecma](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-runtime-semantics-propertydefinitionevaluation))
- An ideal mutability inference system could model known collections (i.e. Arrays or Sets) as a "mutated collection of non-mutable objects" (see `todo-granular-iterator-semantics`), but this is not what we do today. As such, an array / argument spread will always extend the range of built-in arrays, sets, etc
- Due to HIR limitations, call expressions with argument spreads may cause unnecessary bailouts and/or scope merging when we know the call itself has `freeze`, `capture`, or `read` semantics (e.g. `useHook(...mutableValue)`)
  We can deal with this by rewriting these call instructions to (1) create an intermediate array to consume the iterator and (2) capture and spread the array at the callsite
2025-03-13 11:52:25 -04:00
140 changed files with 1362 additions and 5293 deletions

View File

@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Discord Webhook Action
uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4
uses: tsickert/discord-webhook@v6.0.0
with:
webhook-url: ${{ secrets.COMPILER_DISCORD_WEBHOOK_URL }}
embed-author-name: ${{ github.event.pull_request.user.login }}

View File

@@ -36,9 +36,8 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: compiler-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
path: "**/node_modules"
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: npx playwright install --with-deps chromium
- run: CI=true yarn test

View File

@@ -46,9 +46,8 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: compiler-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
path: "**/node_modules"
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- name: Publish packages to npm
run: |

View File

@@ -46,9 +46,8 @@ jobs:
- name: Restore cached node_modules
uses: actions/cache@v4
with:
path: |
**/node_modules
key: compiler-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
path: "**/node_modules"
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn workspace babel-plugin-react-compiler lint
@@ -67,9 +66,8 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: compiler-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
path: "**/node_modules"
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn workspace babel-plugin-react-compiler jest
@@ -92,9 +90,8 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: compiler-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
path: "**/node_modules"
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: xvfb-run -a yarn workspace ${{ matrix.workspace_name }} test
if: runner.os == 'Linux' && matrix.workspace_name == 'react-forgive'

View File

@@ -29,13 +29,13 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-release-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
path: "**/node_modules"
key: runtime-release-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: yarn --cwd scripts/release install --frozen-lockfile
- run: yarn install --frozen-lockfile
working-directory: scripts/release
- name: Download react-devtools artifacts for base revision
run: |
git fetch origin main
@@ -63,9 +63,8 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -121,9 +120,8 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- name: Restore all archived build artifacts
uses: actions/download-artifact@v4
@@ -156,9 +154,8 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- name: Restore all archived build artifacts
uses: actions/download-artifact@v4

View File

@@ -55,9 +55,8 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -80,9 +79,8 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -107,9 +105,8 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -156,20 +153,16 @@ jobs:
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: |
yarn.lock
compiler/yarn.lock
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: yarn --cwd compiler install --frozen-lockfile
- run: yarn test ${{ matrix.params }} --ci --shard=${{ matrix.shard }}
# ----- BUILD -----
@@ -190,9 +183,7 @@ jobs:
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: |
yarn.lock
compiler/yarn.lock
cache-dependency-path: yarn.lock
- uses: actions/setup-java@v4
with:
distribution: temurin
@@ -201,13 +192,11 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: yarn --cwd compiler install --frozen-lockfile
- run: yarn build --index=${{ matrix.worker_id }} --total=20 --r=${{ matrix.release_channel }} --ci
env:
CI: github
@@ -272,20 +261,16 @@ jobs:
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: |
yarn.lock
compiler/yarn.lock
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: yarn --cwd compiler install --frozen-lockfile
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -313,9 +298,8 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -327,7 +311,7 @@ jobs:
merge-multiple: true
- name: Display structure of build
run: ls -R build
- run: echo ${{ github.event.pull_request.head.sha || github.sha }} >> build/COMMIT_SHA
- run: echo ${{ github.sha }} >> build/COMMIT_SHA
- name: Scrape warning messages
run: |
mkdir -p ./build/__test_utils__
@@ -361,9 +345,8 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -397,9 +380,8 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -430,12 +412,12 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: fixtures_dom-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: "**/node_modules"
key: fixtures_dom-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn --cwd fixtures/dom install --frozen-lockfile
- run: yarn install --frozen-lockfile
working-directory: fixtures/dom
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -473,9 +455,8 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: fixtures_flight-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: "**/node_modules"
key: fixtures_flight-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -536,9 +517,8 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -588,9 +568,8 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -626,12 +605,12 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn --cwd scripts/release install --frozen-lockfile
- run: yarn install --frozen-lockfile
working-directory: scripts/release
- name: Download artifacts for base revision
run: |
GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=$(git rev-parse ${{ github.event.pull_request.base.sha }})
@@ -659,7 +638,7 @@ jobs:
node ./scripts/print-warnings/print-warnings.js > build/__test_utils__/ReactAllWarnings.js
- name: Display structure of build for PR
run: ls -R build
- run: echo ${{ github.event.pull_request.head.sha || github.sha }} >> build/COMMIT_SHA
- run: echo ${{ github.sha }} >> build/COMMIT_SHA
- run: node ./scripts/tasks/danger
- name: Archive sizebot results
uses: actions/upload-artifact@v4

View File

@@ -16,11 +16,6 @@ on:
required: true
default: false
type: boolean
dry_run:
description: Perform a dry run (run everything except push)
required: true
default: false
type: boolean
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
@@ -78,13 +73,15 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-release-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
path: "**/node_modules"
key: runtime-release-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: yarn --cwd scripts/release install --frozen-lockfile
name: yarn install (react)
- run: yarn install --frozen-lockfile
name: yarn install (scripts/release)
working-directory: scripts/release
- name: Download artifacts for base revision
run: |
GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }}
@@ -110,10 +107,9 @@ jobs:
mkdir ./compiled/facebook-www/__test_utils__
mv build/__test_utils__/ReactAllWarnings.js ./compiled/facebook-www/__test_utils__/ReactAllWarnings.js
# Move eslint-plugin-react-hooks into eslint-plugin-react-hooks
mkdir ./compiled/eslint-plugin-react-hooks
# Move eslint-plugin-react-hooks into facebook-www
mv build/oss-experimental/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js \
./compiled/eslint-plugin-react-hooks/index.js
./compiled/facebook-www/eslint-plugin-react-hooks.js
# Move unstable_server-external-runtime.js into facebook-www
mv build/oss-experimental/react-dom/unstable_server-external-runtime.js \
@@ -248,16 +244,16 @@ jobs:
git status -u
- name: Commit changes to branch
if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
run: |
git config --global user.email "${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}"
git config --global user.name "${{ github.triggering_actor }}"
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: |
${{ github.event.workflow_run.head_commit.message || format('Manual build of {0}', github.event.workflow_run.head_sha || github.sha) }}
git commit -m "${{ github.event.workflow_run.head_commit.message || format('Manual build of {0}', github.event.workflow_run.head_sha || github.sha) }}
DiffTrain build for [${{ github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha || github.sha }})" || echo "No changes to commit"
- name: Push changes to branch
if: inputs.dry_run == false && (inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true')
run: git push
DiffTrain build for [${{ github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha || github.sha }})
branch: builds/facebook-www
commit_user_name: ${{ github.triggering_actor }}
commit_user_email: ${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}
create_branch: true
commit_fbsource_artifacts:
needs: download_artifacts
@@ -415,13 +411,13 @@ jobs:
git status
- name: Commit changes to branch
if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
run: |
git config --global user.email "${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}"
git config --global user.name "${{ github.triggering_actor }}"
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: |
${{ github.event.workflow_run.head_commit.message || format('Manual build of {0}', github.event.workflow_run.head_sha || github.sha) }}
git commit -m "${{ github.event.workflow_run.head_commit.message || format('Manual build of {0}', github.event.workflow_run.head_sha || github.sha) }}
DiffTrain build for [${{ github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha || github.sha }})" || echo "No changes to commit"
- name: Push changes to branch
if: inputs.dry_run == false && (inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true')
run: git push
DiffTrain build for [${{ github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha || github.sha }})
branch: builds/facebook-fbsource
commit_user_name: ${{ github.triggering_actor }}
commit_user_email: ${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}
create_branch: true

View File

@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Discord Webhook Action
uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4
uses: tsickert/discord-webhook@v6.0.0
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
embed-author-name: ${{ github.event.pull_request.user.login }}

View File

@@ -35,20 +35,16 @@ jobs:
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: |
yarn.lock
compiler/yarn.lock
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-and-compiler-eslint_e2e-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
path: "node_modules"
key: runtime-eslint_e2e-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: yarn --cwd compiler install --frozen-lockfile
- name: Build plugin
working-directory: fixtures/eslint-v${{ matrix.eslint_major }}
run: node build.mjs

View File

@@ -39,13 +39,13 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-release-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
path: "**/node_modules"
key: runtime-release-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: yarn --cwd scripts/release install --frozen-lockfile
- run: yarn install --frozen-lockfile
working-directory: scripts/release
- run: |
scripts/release/prepare-release-from-ci.js --skipTests -r ${{ inputs.release_channel }} --commit=${{ inputs.commit_sha }}
cp ./scripts/release/ci-npmrc ~/.npmrc

View File

@@ -44,7 +44,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Discord Webhook Action
uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4
uses: tsickert/discord-webhook@v6.0.0
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
embed-author-name: ${{ github.event.sender.login }}
@@ -71,13 +71,13 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-release-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
path: "**/node_modules"
key: runtime-release-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: yarn --cwd scripts/release install --frozen-lockfile
- run: yarn install --frozen-lockfile
working-directory: scripts/release
- run: cp ./scripts/release/ci-npmrc ~/.npmrc
- if: '${{ inputs.only_packages }}'
name: 'Prepare ${{ inputs.only_packages }} from NPM'

View File

@@ -28,9 +28,8 @@ jobs:
- name: Restore cached node_modules
uses: actions/cache@v4
with:
path: |
**/node_modules
key: shared-lint-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
path: "**/node_modules"
key: shared-lint-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -49,9 +48,8 @@ jobs:
- name: Restore cached node_modules
uses: actions/cache@v4
with:
path: |
**/node_modules
key: shared-lint-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
path: "**/node_modules"
key: shared-lint-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -70,9 +68,8 @@ jobs:
- name: Restore cached node_modules
uses: actions/cache@v4
with:
path: |
**/node_modules
key: shared-lint-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
path: "**/node_modules"
key: shared-lint-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -91,9 +88,8 @@ jobs:
- name: Restore cached node_modules
uses: actions/cache@v4
with:
path: |
**/node_modules
key: shared-lint-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
path: "**/node_modules"
key: shared-lint-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile

View File

@@ -4,7 +4,8 @@ module.exports = {
plugins: [
'@babel/plugin-syntax-jsx',
'@babel/plugin-transform-flow-strip-types',
['@babel/plugin-proposal-class-properties', {loose: true}],
['@babel/plugin-transform-class-properties', {loose: true}],
'@babel/plugin-transform-classes',
'syntax-trailing-function-commas',
[
'@babel/plugin-proposal-object-rest-spread',

View File

@@ -181,7 +181,6 @@ function ReactForgetFunctionTransform() {
fn,
forgetOptions,
'Other',
'all_features',
'_c',
null,
null,

View File

@@ -11,7 +11,6 @@ import {
injectReanimatedFlag,
pipelineUsesReanimatedPlugin,
} from '../Entrypoint/Reanimated';
import validateNoUntransformedReferences from '../Entrypoint/ValidateNoUntransformedReferences';
const ENABLE_REACT_COMPILER_TIMINGS =
process.env['ENABLE_REACT_COMPILER_TIMINGS'] === '1';
@@ -62,19 +61,12 @@ export default function BabelPluginReactCompiler(
},
};
}
const result = compileProgram(prog, {
compileProgram(prog, {
opts,
filename: pass.filename ?? null,
comments: pass.file.ast.comments ?? [],
code: pass.file.code,
});
validateNoUntransformedReferences(
prog,
pass.filename ?? null,
opts.logger,
opts.environment,
result?.retryErrors ?? [],
);
if (ENABLE_REACT_COMPILER_TIMINGS === true) {
performance.mark(`${filename}:end`, {
detail: 'BabelPlugin:Program:end',

View File

@@ -8,119 +8,7 @@
import {NodePath} from '@babel/core';
import * as t from '@babel/types';
import {PluginOptions} from './Options';
import {CompilerError} from '../CompilerError';
/**
* Gating rewrite for function declarations which are referenced before their
* declaration site.
*
* ```js
* // original
* export default React.memo(Foo);
* function Foo() { ... }
*
* // React compiler optimized + gated
* import {gating} from 'myGating';
* export default React.memo(Foo);
* const gating_result = gating(); <- inserted
* function Foo_optimized() {} <- inserted
* function Foo_unoptimized() {} <- renamed from Foo
* function Foo() { <- inserted function, which can be hoisted by JS engines
* if (gating_result) return Foo_optimized();
* else return Foo_unoptimized();
* }
* ```
*/
function insertAdditionalFunctionDeclaration(
fnPath: NodePath<t.FunctionDeclaration>,
compiled: t.FunctionDeclaration,
gating: NonNullable<PluginOptions['gating']>,
): void {
const originalFnName = fnPath.node.id;
const originalFnParams = fnPath.node.params;
const compiledParams = fnPath.node.params;
/**
* Note that other than `export default function() {}`, all other function
* declarations must have a binding identifier. Since default exports cannot
* be referenced, it's safe to assume that all function declarations passed
* here will have an identifier.
* https://tc39.es/ecma262/multipage/ecmascript-language-functions-and-classes.html#sec-function-definitions
*/
CompilerError.invariant(originalFnName != null && compiled.id != null, {
reason:
'Expected function declarations that are referenced elsewhere to have a named identifier',
loc: fnPath.node.loc ?? null,
});
CompilerError.invariant(originalFnParams.length === compiledParams.length, {
reason:
'Expected React Compiler optimized function declarations to have the same number of parameters as source',
loc: fnPath.node.loc ?? null,
});
const gatingCondition = fnPath.scope.generateUidIdentifier(
`${gating.importSpecifierName}_result`,
);
const unoptimizedFnName = fnPath.scope.generateUidIdentifier(
`${originalFnName.name}_unoptimized`,
);
const optimizedFnName = fnPath.scope.generateUidIdentifier(
`${originalFnName.name}_optimized`,
);
/**
* Step 1: rename existing functions
*/
compiled.id.name = optimizedFnName.name;
fnPath.get('id').replaceInline(unoptimizedFnName);
/**
* Step 2: insert new function declaration
*/
const newParams: Array<t.Identifier | t.RestElement> = [];
const genNewArgs: Array<() => t.Identifier | t.SpreadElement> = [];
for (let i = 0; i < originalFnParams.length; i++) {
const argName = `arg${i}`;
if (originalFnParams[i].type === 'RestElement') {
newParams.push(t.restElement(t.identifier(argName)));
genNewArgs.push(() => t.spreadElement(t.identifier(argName)));
} else {
newParams.push(t.identifier(argName));
genNewArgs.push(() => t.identifier(argName));
}
}
// insertAfter called in reverse order of how nodes should appear in program
fnPath.insertAfter(
t.functionDeclaration(
originalFnName,
newParams,
t.blockStatement([
t.ifStatement(
gatingCondition,
t.returnStatement(
t.callExpression(
compiled.id,
genNewArgs.map(fn => fn()),
),
),
t.returnStatement(
t.callExpression(
unoptimizedFnName,
genNewArgs.map(fn => fn()),
),
),
),
]),
),
);
fnPath.insertBefore(
t.variableDeclaration('const', [
t.variableDeclarator(
gatingCondition,
t.callExpression(t.identifier(gating.importSpecifierName), []),
),
]),
);
fnPath.insertBefore(compiled);
}
export function insertGatedFunctionDeclaration(
fnPath: NodePath<
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
@@ -130,57 +18,47 @@ export function insertGatedFunctionDeclaration(
| t.ArrowFunctionExpression
| t.FunctionExpression,
gating: NonNullable<PluginOptions['gating']>,
referencedBeforeDeclaration: boolean,
): void {
if (referencedBeforeDeclaration && fnPath.isFunctionDeclaration()) {
CompilerError.invariant(compiled.type === 'FunctionDeclaration', {
reason: 'Expected compiled node type to match input type',
description: `Got ${compiled.type} but expected FunctionDeclaration`,
loc: fnPath.node.loc ?? null,
});
insertAdditionalFunctionDeclaration(fnPath, compiled, gating);
} else {
const gatingExpression = t.conditionalExpression(
t.callExpression(t.identifier(gating.importSpecifierName), []),
buildFunctionExpression(compiled),
buildFunctionExpression(fnPath.node),
);
const gatingExpression = t.conditionalExpression(
t.callExpression(t.identifier(gating.importSpecifierName), []),
buildFunctionExpression(compiled),
buildFunctionExpression(fnPath.node),
);
/*
* Convert function declarations to named variables *unless* this is an
* `export default function ...` since `export default const ...` is
* not supported. For that case we fall through to replacing w the raw
* conditional expression
*/
if (
fnPath.parentPath.node.type !== 'ExportDefaultDeclaration' &&
fnPath.node.type === 'FunctionDeclaration' &&
fnPath.node.id != null
) {
fnPath.replaceWith(
t.variableDeclaration('const', [
t.variableDeclarator(fnPath.node.id, gatingExpression),
]),
);
} else if (
fnPath.parentPath.node.type === 'ExportDefaultDeclaration' &&
fnPath.node.type !== 'ArrowFunctionExpression' &&
fnPath.node.id != null
) {
fnPath.insertAfter(
t.exportDefaultDeclaration(t.identifier(fnPath.node.id.name)),
);
fnPath.parentPath.replaceWith(
t.variableDeclaration('const', [
t.variableDeclarator(
t.identifier(fnPath.node.id.name),
gatingExpression,
),
]),
);
} else {
fnPath.replaceWith(gatingExpression);
}
/*
* Convert function declarations to named variables *unless* this is an
* `export default function ...` since `export default const ...` is
* not supported. For that case we fall through to replacing w the raw
* conditional expression
*/
if (
fnPath.parentPath.node.type !== 'ExportDefaultDeclaration' &&
fnPath.node.type === 'FunctionDeclaration' &&
fnPath.node.id != null
) {
fnPath.replaceWith(
t.variableDeclaration('const', [
t.variableDeclarator(fnPath.node.id, gatingExpression),
]),
);
} else if (
fnPath.parentPath.node.type === 'ExportDefaultDeclaration' &&
fnPath.node.type !== 'ArrowFunctionExpression' &&
fnPath.node.id != null
) {
fnPath.insertAfter(
t.exportDefaultDeclaration(t.identifier(fnPath.node.id.name)),
);
fnPath.parentPath.replaceWith(
t.variableDeclaration('const', [
t.variableDeclarator(
t.identifier(fnPath.node.id.name),
gatingExpression,
),
]),
);
} else {
fnPath.replaceWith(gatingExpression);
}
}

View File

@@ -12,7 +12,6 @@ import {
EnvironmentConfig,
ExternalFunction,
parseEnvironmentConfig,
tryParseExternalFunction,
} from '../HIR/Environment';
import {hasOwnProperty} from '../Utils/utils';
import {fromZodError} from 'zod-validation-error';
@@ -272,14 +271,6 @@ export function parsePluginOptions(obj: unknown): PluginOptions {
parsedOptions[key] = parseTargetConfig(value);
break;
}
case 'gating': {
if (value == null) {
parsedOptions[key] = null;
} else {
parsedOptions[key] = tryParseExternalFunction(value);
}
break;
}
default: {
parsedOptions[key] = value;
}

View File

@@ -24,7 +24,6 @@ import {
pruneUnusedLabelsHIR,
} from '../HIR';
import {
CompilerMode,
Environment,
EnvironmentConfig,
ReactFunctionType,
@@ -101,7 +100,6 @@ import {outlineJSX} from '../Optimization/OutlineJsx';
import {optimizePropsMethodCalls} from '../Optimization/OptimizePropsMethodCalls';
import {transformFire} from '../Transform';
import {validateNoImpureFunctionsInRender} from '../Validation/ValiateNoImpureFunctionsInRender';
import {CompilerError} from '..';
export type CompilerPipelineValue =
| {kind: 'ast'; name: string; value: CodegenFunction}
@@ -115,7 +113,6 @@ function run(
>,
config: EnvironmentConfig,
fnType: ReactFunctionType,
mode: CompilerMode,
useMemoCacheIdentifier: string,
logger: Logger | null,
filename: string | null,
@@ -125,7 +122,6 @@ function run(
const env = new Environment(
func.scope,
fnType,
mode,
config,
contextIdentifiers,
logger,
@@ -164,10 +160,10 @@ function runWithEnvironment(
validateUseMemo(hir);
if (
env.isInferredMemoEnabled &&
!env.config.enablePreserveExistingManualUseMemo &&
!env.config.disableMemoizationForDebugging &&
!env.config.enableChangeDetectionForDebugging
!env.config.enableChangeDetectionForDebugging &&
!env.config.enableMinimalTransformsForRetry
) {
dropManualMemoization(hir);
log({kind: 'hir', name: 'DropManualMemoization', value: hir});
@@ -200,13 +196,8 @@ function runWithEnvironment(
inferTypes(hir);
log({kind: 'hir', name: 'InferTypes', value: hir});
if (env.isInferredMemoEnabled) {
if (env.config.validateHooksUsage) {
validateHooksUsage(hir);
}
if (env.config.validateNoCapitalizedCalls) {
validateNoCapitalizedCalls(hir);
}
if (env.config.validateHooksUsage) {
validateHooksUsage(hir);
}
if (env.config.enableFire) {
@@ -214,6 +205,10 @@ function runWithEnvironment(
log({kind: 'hir', name: 'TransformFire', value: hir});
}
if (env.config.validateNoCapitalizedCalls) {
validateNoCapitalizedCalls(hir);
}
if (env.config.lowerContextAccess) {
lowerContextAccess(hir, env.config.lowerContextAccess);
}
@@ -224,12 +219,7 @@ function runWithEnvironment(
analyseFunctions(hir);
log({kind: 'hir', name: 'AnalyseFunctions', value: hir});
const fnEffectErrors = inferReferenceEffects(hir);
if (env.isInferredMemoEnabled) {
if (fnEffectErrors.length > 0) {
CompilerError.throw(fnEffectErrors[0]);
}
}
inferReferenceEffects(hir);
log({kind: 'hir', name: 'InferReferenceEffects', value: hir});
validateLocalsNotReassignedAfterRender(hir);
@@ -249,30 +239,28 @@ function runWithEnvironment(
inferMutableRanges(hir);
log({kind: 'hir', name: 'InferMutableRanges', value: hir});
if (env.isInferredMemoEnabled) {
if (env.config.assertValidMutableRanges) {
assertValidMutableRanges(hir);
}
if (env.config.assertValidMutableRanges) {
assertValidMutableRanges(hir);
}
if (env.config.validateRefAccessDuringRender) {
validateNoRefAccessInRender(hir);
}
if (env.config.validateRefAccessDuringRender) {
validateNoRefAccessInRender(hir);
}
if (env.config.validateNoSetStateInRender) {
validateNoSetStateInRender(hir);
}
if (env.config.validateNoSetStateInRender) {
validateNoSetStateInRender(hir);
}
if (env.config.validateNoSetStateInPassiveEffects) {
validateNoSetStateInPassiveEffects(hir);
}
if (env.config.validateNoSetStateInPassiveEffects) {
validateNoSetStateInPassiveEffects(hir);
}
if (env.config.validateNoJSXInTryStatements) {
validateNoJSXInTryStatement(hir);
}
if (env.config.validateNoJSXInTryStatements) {
validateNoJSXInTryStatement(hir);
}
if (env.config.validateNoImpureFunctionsInRender) {
validateNoImpureFunctionsInRender(hir);
}
if (env.config.validateNoImpureFunctionsInRender) {
validateNoImpureFunctionsInRender(hir);
}
inferReactivePlaces(hir);
@@ -292,12 +280,7 @@ function runWithEnvironment(
value: hir,
});
if (env.isInferredMemoEnabled) {
/**
* Only create reactive scopes (which directly map to generated memo blocks)
* if inferred memoization is enabled. This makes all later passes which
* transform reactive-scope labeled instructions no-ops.
*/
if (!env.config.enableMinimalTransformsForRetry) {
inferReactiveScopeVariables(hir);
log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir});
}
@@ -546,7 +529,6 @@ export function compileFn(
>,
config: EnvironmentConfig,
fnType: ReactFunctionType,
mode: CompilerMode,
useMemoCacheIdentifier: string,
logger: Logger | null,
filename: string | null,
@@ -556,7 +538,6 @@ export function compileFn(
func,
config,
fnType,
mode,
useMemoCacheIdentifier,
logger,
filename,

View File

@@ -16,6 +16,8 @@ import {
EnvironmentConfig,
ExternalFunction,
ReactFunctionType,
MINIMAL_RETRY_CONFIG,
tryParseExternalFunction,
} from '../HIR/Environment';
import {CodegenFunction} from '../ReactiveScopes';
import {isComponentDeclaration} from '../Utils/ComponentDeclaration';
@@ -271,9 +273,6 @@ function isFilePartOfSources(
return false;
}
type CompileProgramResult = {
retryErrors: Array<{fn: BabelFn; error: CompilerError}>;
};
/**
* `compileProgram` is directly invoked by the react-compiler babel plugin, so
* exceptions thrown by this function will fail the babel build.
@@ -288,16 +287,16 @@ type CompileProgramResult = {
export function compileProgram(
program: NodePath<t.Program>,
pass: CompilerPass,
): CompileProgramResult | null {
): void {
if (shouldSkipCompilation(program, pass)) {
return null;
return;
}
const environment = pass.opts.environment;
const restrictedImportsErr = validateRestrictedImports(program, environment);
if (restrictedImportsErr) {
handleError(restrictedImportsErr, pass, null);
return null;
return;
}
const useMemoCacheIdentifier = program.scope.generateUidIdentifier('c');
@@ -368,7 +367,7 @@ export function compileProgram(
filename: pass.filename ?? null,
},
);
const retryErrors: Array<{fn: BabelFn; error: CompilerError}> = [];
const processFn = (
fn: BabelFn,
fnType: ReactFunctionType,
@@ -409,7 +408,6 @@ export function compileProgram(
fn,
environment,
fnType,
'all_features',
useMemoCacheIdentifier.name,
pass.opts.logger,
pass.filename,
@@ -420,7 +418,28 @@ export function compileProgram(
compileResult = {kind: 'error', error: err};
}
}
// If non-memoization features are enabled, retry regardless of error kind
if (compileResult.kind === 'error' && environment.enableFire) {
try {
compileResult = {
kind: 'compile',
compiledFn: compileFn(
fn,
{
...environment,
...MINIMAL_RETRY_CONFIG,
},
fnType,
useMemoCacheIdentifier.name,
pass.opts.logger,
pass.filename,
pass.code,
),
};
} catch (err) {
compileResult = {kind: 'error', error: err};
}
}
if (compileResult.kind === 'error') {
/**
* If an opt out directive is present, log only instead of throwing and don't mark as
@@ -431,33 +450,7 @@ export function compileProgram(
} else {
handleError(compileResult.error, pass, fn.node.loc ?? null);
}
// If non-memoization features are enabled, retry regardless of error kind
if (
!(environment.enableFire || environment.inferEffectDependencies != null)
) {
return null;
}
try {
compileResult = {
kind: 'compile',
compiledFn: compileFn(
fn,
environment,
fnType,
'no_inferred_memo',
useMemoCacheIdentifier.name,
pass.opts.logger,
pass.filename,
pass.code,
),
};
} catch (err) {
// TODO: we might want to log error here, but this will also result in duplicate logging
if (err instanceof CompilerError) {
retryErrors.push({fn, error: err});
}
return null;
}
return null;
}
pass.opts.logger?.logEvent(pass.filename, {
@@ -546,28 +539,32 @@ export function compileProgram(
program.node.directives,
);
if (moduleScopeOptOutDirectives.length > 0) {
return null;
return;
}
let gating: null | {
gatingFn: ExternalFunction;
referencedBeforeDeclared: Set<CompileResult>;
} = null;
if (pass.opts.gating != null) {
gating = {
gatingFn: pass.opts.gating,
referencedBeforeDeclared:
getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns),
};
const error = checkFunctionReferencedBeforeDeclarationAtTopLevel(
program,
compiledFns.map(result => {
return result.originalFn;
}),
);
if (error) {
handleError(error, pass, null);
return;
}
}
const hasLoweredContextAccess = compiledFns.some(
c => c.compiledFn.hasLoweredContextAccess,
);
const externalFunctions: Array<ExternalFunction> = [];
let gating: null | ExternalFunction = null;
try {
// TODO: check for duplicate import specifiers
if (gating != null) {
externalFunctions.push(gating.gatingFn);
if (pass.opts.gating != null) {
gating = tryParseExternalFunction(pass.opts.gating);
externalFunctions.push(gating);
}
const lowerContextAccess = environment.lowerContextAccess;
@@ -604,7 +601,7 @@ export function compileProgram(
}
} catch (err) {
handleError(err, pass, null);
return null;
return;
}
/*
@@ -616,12 +613,7 @@ export function compileProgram(
const transformedFn = createNewFunctionNode(originalFn, compiledFn);
if (gating != null && kind === 'original') {
insertGatedFunctionDeclaration(
originalFn,
transformedFn,
gating.gatingFn,
gating.referencedBeforeDeclared.has(result),
);
insertGatedFunctionDeclaration(originalFn, transformedFn, gating);
} else {
originalFn.replaceWith(transformedFn);
}
@@ -646,7 +638,6 @@ export function compileProgram(
}
addImportsToProgram(program, externalFunctions);
}
return {retryErrors};
}
function shouldSkipCompilation(
@@ -1102,23 +1093,20 @@ function getFunctionName(
}
}
function getFunctionReferencedBeforeDeclarationAtTopLevel(
function checkFunctionReferencedBeforeDeclarationAtTopLevel(
program: NodePath<t.Program>,
fns: Array<CompileResult>,
): Set<CompileResult> {
const fnNames = new Map<string, {id: t.Identifier; fn: CompileResult}>(
fns: Array<BabelFn>,
): CompilerError | null {
const fnIds = new Set(
fns
.map<[NodePath<t.Expression> | null, CompileResult]>(fn => [
getFunctionName(fn.originalFn),
fn,
])
.map(fn => getFunctionName(fn))
.filter(
(entry): entry is [NodePath<t.Identifier>, CompileResult] =>
!!entry[0] && entry[0].isIdentifier(),
(name): name is NodePath<t.Identifier> => !!name && name.isIdentifier(),
)
.map(entry => [entry[0].node.name, {id: entry[0].node, fn: entry[1]}]),
.map(name => name.node),
);
const referencedBeforeDeclaration = new Set<CompileResult>();
const fnNames = new Map([...fnIds].map(id => [id.name, id]));
const errors = new CompilerError();
program.traverse({
TypeAnnotation(path) {
@@ -1144,7 +1132,8 @@ function getFunctionReferencedBeforeDeclarationAtTopLevel(
* We've reached the declaration, hoisting is no longer possible, stop
* checking for this component name.
*/
if (id.node === fn.id) {
if (fnIds.has(id.node)) {
fnIds.delete(id.node);
fnNames.delete(id.node.name);
return;
}
@@ -1154,13 +1143,21 @@ function getFunctionReferencedBeforeDeclarationAtTopLevel(
* A null scope means there's no function scope, which means we're at the
* top level scope.
*/
if (scope === null && id.isReferencedIdentifier()) {
referencedBeforeDeclaration.add(fn.fn);
if (scope === null) {
errors.pushErrorDetail(
new CompilerErrorDetail({
reason: `Encountered a function used before its declaration, which breaks Forget's gating codegen due to hoisting`,
description: `Rewrite the reference to ${fn.name} to not rely on hoisting to fix this issue`,
loc: fn.loc ?? null,
suggestions: null,
severity: ErrorSeverity.Invariant,
}),
);
}
},
});
return referencedBeforeDeclaration;
return errors.details.length > 0 ? errors : null;
}
function getReactCompilerRuntimeModule(opts: PluginOptions): string {

View File

@@ -1,275 +0,0 @@
import {NodePath} from '@babel/core';
import * as t from '@babel/types';
import {
CompilerError,
CompilerErrorDetailOptions,
EnvironmentConfig,
ErrorSeverity,
Logger,
} from '..';
import {getOrInsertWith} from '../Utils/utils';
import {Environment} from '../HIR';
import {DEFAULT_EXPORT} from '../HIR/Environment';
function throwInvalidReact(
options: Omit<CompilerErrorDetailOptions, 'severity'>,
{logger, filename}: TraversalState,
): never {
const detail: CompilerErrorDetailOptions = {
...options,
severity: ErrorSeverity.InvalidReact,
};
logger?.logEvent(filename, {
kind: 'CompileError',
fnLoc: null,
detail,
});
CompilerError.throw(detail);
}
function assertValidEffectImportReference(
numArgs: number,
paths: Array<NodePath<t.Node>>,
context: TraversalState,
): void {
for (const path of paths) {
const parent = path.parentPath;
if (parent != null && parent.isCallExpression()) {
const args = parent.get('arguments');
/**
* Only error on untransformed references of the form `useMyEffect(...)`
* or `moduleNamespace.useMyEffect(...)`, with matching argument counts.
* TODO: do we also want a mode to also hard error on non-call references?
*/
if (args.length === numArgs) {
const maybeErrorDiagnostic = matchCompilerDiagnostic(
path,
context.transformErrors,
);
/**
* Note that we cannot easily check the type of the first argument here,
* as it may have already been transformed by the compiler (and not
* memoized).
*/
throwInvalidReact(
{
reason:
'[InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. ' +
'This will break your build! ' +
'To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics.',
description: maybeErrorDiagnostic
? `(Bailout reason: ${maybeErrorDiagnostic})`
: null,
loc: parent.node.loc ?? null,
},
context,
);
}
}
}
}
function assertValidFireImportReference(
paths: Array<NodePath<t.Node>>,
context: TraversalState,
): void {
if (paths.length > 0) {
const maybeErrorDiagnostic = matchCompilerDiagnostic(
paths[0],
context.transformErrors,
);
throwInvalidReact(
{
reason:
'[Fire] Untransformed reference to compiler-required feature. ' +
'Either remove this `fire` call or ensure it is successfully transformed by the compiler',
description: maybeErrorDiagnostic
? `(Bailout reason: ${maybeErrorDiagnostic})`
: null,
loc: paths[0].node.loc ?? null,
},
context,
);
}
}
export default function validateNoUntransformedReferences(
path: NodePath<t.Program>,
filename: string | null,
logger: Logger | null,
env: EnvironmentConfig,
transformErrors: Array<{fn: NodePath<t.Node>; error: CompilerError}>,
): void {
const moduleLoadChecks = new Map<
string,
Map<string, CheckInvalidReferenceFn>
>();
if (env.enableFire) {
/**
* Error on any untransformed references to `fire` (e.g. including non-call
* expressions)
*/
for (const module of Environment.knownReactModules) {
const react = getOrInsertWith(moduleLoadChecks, module, () => new Map());
react.set('fire', assertValidFireImportReference);
}
}
if (env.inferEffectDependencies) {
for (const {
function: {source, importSpecifierName},
numRequiredArgs,
} of env.inferEffectDependencies) {
const module = getOrInsertWith(moduleLoadChecks, source, () => new Map());
module.set(
importSpecifierName,
assertValidEffectImportReference.bind(null, numRequiredArgs),
);
}
}
if (moduleLoadChecks.size > 0) {
transformProgram(path, moduleLoadChecks, filename, logger, transformErrors);
}
}
type TraversalState = {
shouldInvalidateScopes: boolean;
program: NodePath<t.Program>;
logger: Logger | null;
filename: string | null;
transformErrors: Array<{fn: NodePath<t.Node>; error: CompilerError}>;
};
type CheckInvalidReferenceFn = (
paths: Array<NodePath<t.Node>>,
context: TraversalState,
) => void;
function validateImportSpecifier(
specifier: NodePath<t.ImportSpecifier>,
importSpecifierChecks: Map<string, CheckInvalidReferenceFn>,
state: TraversalState,
): void {
const imported = specifier.get('imported');
const specifierName: string =
imported.node.type === 'Identifier'
? imported.node.name
: imported.node.value;
const checkFn = importSpecifierChecks.get(specifierName);
if (checkFn == null) {
return;
}
if (state.shouldInvalidateScopes) {
state.shouldInvalidateScopes = false;
state.program.scope.crawl();
}
const local = specifier.get('local');
const binding = local.scope.getBinding(local.node.name);
CompilerError.invariant(binding != null, {
reason: 'Expected binding to be found for import specifier',
loc: local.node.loc ?? null,
});
checkFn(binding.referencePaths, state);
}
function validateNamespacedImport(
specifier: NodePath<t.ImportNamespaceSpecifier | t.ImportDefaultSpecifier>,
importSpecifierChecks: Map<string, CheckInvalidReferenceFn>,
state: TraversalState,
): void {
if (state.shouldInvalidateScopes) {
state.shouldInvalidateScopes = false;
state.program.scope.crawl();
}
const local = specifier.get('local');
const binding = local.scope.getBinding(local.node.name);
const defaultCheckFn = importSpecifierChecks.get(DEFAULT_EXPORT);
CompilerError.invariant(binding != null, {
reason: 'Expected binding to be found for import specifier',
loc: local.node.loc ?? null,
});
const filteredReferences = new Map<
CheckInvalidReferenceFn,
Array<NodePath<t.Node>>
>();
for (const reference of binding.referencePaths) {
if (defaultCheckFn != null) {
getOrInsertWith(filteredReferences, defaultCheckFn, () => []).push(
reference,
);
}
const parent = reference.parentPath;
if (
parent != null &&
parent.isMemberExpression() &&
parent.get('object') === reference
) {
if (parent.node.computed || parent.node.property.type !== 'Identifier') {
continue;
}
const checkFn = importSpecifierChecks.get(parent.node.property.name);
if (checkFn != null) {
getOrInsertWith(filteredReferences, checkFn, () => []).push(parent);
}
}
}
for (const [checkFn, references] of filteredReferences) {
checkFn(references, state);
}
}
function transformProgram(
path: NodePath<t.Program>,
moduleLoadChecks: Map<string, Map<string, CheckInvalidReferenceFn>>,
filename: string | null,
logger: Logger | null,
transformErrors: Array<{fn: NodePath<t.Node>; error: CompilerError}>,
): void {
const traversalState: TraversalState = {
shouldInvalidateScopes: true,
program: path,
filename,
logger,
transformErrors,
};
path.traverse({
ImportDeclaration(path: NodePath<t.ImportDeclaration>) {
const importSpecifierChecks = moduleLoadChecks.get(
path.node.source.value,
);
if (importSpecifierChecks == null) {
return;
}
const specifiers = path.get('specifiers');
for (const specifier of specifiers) {
if (specifier.isImportSpecifier()) {
validateImportSpecifier(
specifier,
importSpecifierChecks,
traversalState,
);
} else {
validateNamespacedImport(
specifier as NodePath<
t.ImportNamespaceSpecifier | t.ImportDefaultSpecifier
>,
importSpecifierChecks,
traversalState,
);
}
}
},
});
}
function matchCompilerDiagnostic(
badReference: NodePath<t.Node>,
transformErrors: Array<{fn: NodePath<t.Node>; error: CompilerError}>,
): string | null {
for (const {fn, error} of transformErrors) {
if (fn.isAncestor(badReference)) {
return error.toString();
}
}
return null;
}

View File

@@ -1455,11 +1455,6 @@ function lowerObjectPropertyKey(
kind: 'identifier',
name: key.node.name,
};
} else if (key.isNumericLiteral()) {
return {
kind: 'identifier',
name: String(key.node.value),
};
}
builder.errors.push({

View File

@@ -96,8 +96,6 @@ export const MacroSchema = z.union([
z.tuple([z.string(), z.array(MacroMethodSchema)]),
]);
export type CompilerMode = 'all_features' | 'no_inferred_memo';
export type Macro = z.infer<typeof MacroSchema>;
export type MacroMethod = z.infer<typeof MacroMethodSchema>;
@@ -552,6 +550,8 @@ const EnvironmentConfigSchema = z.object({
*/
disableMemoizationForDebugging: z.boolean().default(false),
enableMinimalTransformsForRetry: z.boolean().default(false),
/**
* When true, rather using memoized values, the compiler will always re-compute
* values, and then use a heuristic to compare the memoized value to the newly
@@ -626,6 +626,17 @@ const EnvironmentConfigSchema = z.object({
export type EnvironmentConfig = z.infer<typeof EnvironmentConfigSchema>;
export const MINIMAL_RETRY_CONFIG: PartialEnvironmentConfig = {
validateHooksUsage: false,
validateRefAccessDuringRender: false,
validateNoSetStateInRender: false,
validateNoSetStateInPassiveEffects: false,
validateNoJSXInTryStatements: false,
validateMemoizedEffectDependencies: false,
validateNoCapitalizedCalls: null,
validateBlocklistedImports: null,
enableMinimalTransformsForRetry: true,
};
/**
* For test fixtures and playground only.
*
@@ -840,7 +851,6 @@ export class Environment {
code: string | null;
config: EnvironmentConfig;
fnType: ReactFunctionType;
compilerMode: CompilerMode;
useMemoCacheIdentifier: string;
hasLoweredContextAccess: boolean;
hasFireRewrite: boolean;
@@ -851,7 +861,6 @@ export class Environment {
constructor(
scope: BabelScope,
fnType: ReactFunctionType,
compilerMode: CompilerMode,
config: EnvironmentConfig,
contextIdentifiers: Set<t.Identifier>,
logger: Logger | null,
@@ -861,7 +870,6 @@ export class Environment {
) {
this.#scope = scope;
this.fnType = fnType;
this.compilerMode = compilerMode;
this.config = config;
this.filename = filename;
this.code = code;
@@ -916,10 +924,6 @@ export class Environment {
this.#hoistedIdentifiers = new Set();
}
get isInferredMemoEnabled(): boolean {
return this.compilerMode !== 'no_inferred_memo';
}
get nextIdentifierId(): IdentifierId {
return makeIdentifierId(this.#nextIdentifer++);
}
@@ -1121,7 +1125,6 @@ export class Environment {
moduleName.toLowerCase() === 'react-dom'
);
}
static knownReactModules: ReadonlyArray<string> = ['react', 'react-dom'];
getFallthroughPropertyType(
receiver: Type,

View File

@@ -703,10 +703,6 @@ export type ObjectPropertyKey =
| {
kind: 'computed';
name: Place;
}
| {
kind: 'number';
name: number;
};
export type ObjectProperty = {

View File

@@ -330,9 +330,6 @@ function printObjectPropertyKey(key: ObjectPropertyKey): string {
case 'computed': {
return `[${printPlace(key.name)}]`;
}
case 'number': {
return String(key.name);
}
}
}

View File

@@ -5,12 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {
CompilerError,
CompilerErrorDetailOptions,
ErrorSeverity,
ValueKind,
} from '..';
import {CompilerError, ErrorSeverity, ValueKind} from '..';
import {
AbstractValue,
BasicBlock,
@@ -295,21 +290,21 @@ export function inferTerminalFunctionEffects(
return functionEffects;
}
export function transformFunctionEffectErrors(
export function raiseFunctionEffectErrors(
functionEffects: Array<FunctionEffect>,
): Array<CompilerErrorDetailOptions> {
return functionEffects.map(eff => {
): void {
functionEffects.forEach(eff => {
switch (eff.kind) {
case 'ReactMutation':
case 'GlobalMutation': {
return eff.error;
CompilerError.throw(eff.error);
}
case 'ContextMutation': {
return {
CompilerError.throw({
severity: ErrorSeverity.Invariant,
reason: `Unexpected ContextMutation in top-level function effects`,
loc: eff.loc,
};
});
}
default:
assertExhaustive(

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError, CompilerErrorDetailOptions} from '../CompilerError';
import {CompilerError} from '../CompilerError';
import {Environment} from '../HIR';
import {
AbstractValue,
@@ -49,7 +49,7 @@ import {assertExhaustive} from '../Utils/utils';
import {
inferTerminalFunctionEffects,
inferInstructionFunctionEffects,
transformFunctionEffectErrors,
raiseFunctionEffectErrors,
} from './InferFunctionEffects';
const UndefinedValue: InstructionValue = {
@@ -103,7 +103,7 @@ const UndefinedValue: InstructionValue = {
export default function inferReferenceEffects(
fn: HIRFunction,
options: {isFunctionExpression: boolean} = {isFunctionExpression: false},
): Array<CompilerErrorDetailOptions> {
): void {
/*
* Initial state contains function params
* TODO: include module declarations here as well
@@ -241,9 +241,8 @@ export default function inferReferenceEffects(
if (options.isFunctionExpression) {
fn.effects = functionEffects;
return [];
} else {
return transformFunctionEffectErrors(functionEffects);
} else if (!fn.env.config.enableMinimalTransformsForRetry) {
raiseFunctionEffectErrors(functionEffects);
}
}

View File

@@ -2429,9 +2429,6 @@ function codegenObjectPropertyKey(
});
return expr;
}
case 'number': {
return t.numericLiteral(key.name);
}
}
}

View File

@@ -1,62 +0,0 @@
## Input
```javascript
// @flow @gating
import {Stringify} from 'shared-runtime';
import * as React from 'react';
component Foo(ref: React.RefSetter<Controls>) {
return <Stringify ref={ref} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: eval('(...args) => React.createElement(Foo, args)'),
params: [{ref: React.createRef()}],
};
```
## Code
```javascript
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime";
import { Stringify } from "shared-runtime";
import * as React from "react";
const Foo = React.forwardRef(Foo_withRef);
const _isForgetEnabled_Fixtures_result = isForgetEnabled_Fixtures();
function _Foo_withRef_optimized(_$$empty_props_placeholder$$, ref) {
const $ = _c(2);
let t0;
if ($[0] !== ref) {
t0 = <Stringify ref={ref} />;
$[0] = ref;
$[1] = t0;
} else {
t0 = $[1];
}
return t0;
}
function _Foo_withRef_unoptimized(
_$$empty_props_placeholder$$: $ReadOnly<{}>,
ref: React.RefSetter<Controls>,
): React.Node {
return <Stringify ref={ref} />;
}
function Foo_withRef(arg0, arg1) {
if (_isForgetEnabled_Fixtures_result)
return _Foo_withRef_optimized(arg0, arg1);
else return _Foo_withRef_unoptimized(arg0, arg1);
}
export const FIXTURE_ENTRYPOINT = {
fn: eval("(...args) => React.createElement(Foo, args)"),
params: [{ ref: React.createRef() }],
};
```
### Eval output
(kind: ok) <div>{"ref":null}</div>

View File

@@ -1,12 +0,0 @@
// @flow @gating
import {Stringify} from 'shared-runtime';
import * as React from 'react';
component Foo(ref: React.RefSetter<Controls>) {
return <Stringify ref={ref} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: eval('(...args) => React.createElement(Foo, args)'),
params: [{ref: React.createRef()}],
};

View File

@@ -0,0 +1,24 @@
## Input
```javascript
// @flow @gating
component Foo(ref: React.RefSetter<Controls>) {
return <Bar ref={ref} />;
}
```
## Error
```
1 | // @flow @gating
> 2 | component Foo(ref: React.RefSetter<Controls>) {
| ^^^ Invariant: Encountered a function used before its declaration, which breaks Forget's gating codegen due to hoisting. Rewrite the reference to Foo_withRef to not rely on hoisting to fix this issue (2:2)
3 | return <Bar ref={ref} />;
4 | }
5 |
```

View File

@@ -0,0 +1,4 @@
// @flow @gating
component Foo(ref: React.RefSetter<Controls>) {
return <Bar ref={ref} />;
}

View File

@@ -0,0 +1,26 @@
## Input
```javascript
// @gating
const Foo = React.forwardRef(Foo_withRef);
function Foo_withRef(props, ref) {
return <Bar ref={ref} {...props}></Bar>;
}
```
## Error
```
1 | // @gating
2 | const Foo = React.forwardRef(Foo_withRef);
> 3 | function Foo_withRef(props, ref) {
| ^^^^^^^^^^^ Invariant: Encountered a function used before its declaration, which breaks Forget's gating codegen due to hoisting. Rewrite the reference to Foo_withRef to not rely on hoisting to fix this issue (3:3)
4 | return <Bar ref={ref} {...props}></Bar>;
5 | }
6 |
```

View File

@@ -0,0 +1,5 @@
// @gating
const Foo = React.forwardRef(Foo_withRef);
function Foo_withRef(props, ref) {
return <Bar ref={ref} {...props}></Bar>;
}

View File

@@ -0,0 +1,24 @@
## Input
```javascript
// @gating
import {memo} from 'react';
export default memo(Foo);
function Foo() {}
```
## Error
```
3 |
4 | export default memo(Foo);
> 5 | function Foo() {}
| ^^^ Invariant: Encountered a function used before its declaration, which breaks Forget's gating codegen due to hoisting. Rewrite the reference to Foo to not rely on hoisting to fix this issue (5:5)
6 |
```

View File

@@ -0,0 +1,5 @@
// @gating
import {memo} from 'react';
export default memo(Foo);
function Foo() {}

View File

@@ -1,60 +0,0 @@
## Input
```javascript
// @gating
import {identity, useHook as useRenamed} from 'shared-runtime';
const _ = {
useHook: () => {},
};
identity(_.useHook);
function useHook() {
useRenamed();
return <div>hello world!</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{}],
};
```
## Code
```javascript
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating
import { identity, useHook as useRenamed } from "shared-runtime";
const _ = {
useHook: isForgetEnabled_Fixtures() ? () => {} : () => {},
};
identity(_.useHook);
const useHook = isForgetEnabled_Fixtures()
? function useHook() {
const $ = _c(1);
useRenamed();
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = <div>hello world!</div>;
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
: function useHook() {
useRenamed();
return <div>hello world!</div>;
};
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{}],
};
```
### Eval output
(kind: ok) <div>hello world!</div>

View File

@@ -1,16 +0,0 @@
// @gating
import {identity, useHook as useRenamed} from 'shared-runtime';
const _ = {
useHook: () => {},
};
identity(_.useHook);
function useHook() {
useRenamed();
return <div>hello world!</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{}],
};

View File

@@ -1,61 +0,0 @@
## Input
```javascript
// @gating
import {createRef, forwardRef} from 'react';
import {Stringify} from 'shared-runtime';
const Foo = forwardRef(Foo_withRef);
function Foo_withRef(props, ref) {
return <Stringify ref={ref} {...props} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: eval('(...args) => React.createElement(Foo, args)'),
params: [{prop1: 1, prop2: 2, ref: createRef()}],
};
```
## Code
```javascript
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating
import { createRef, forwardRef } from "react";
import { Stringify } from "shared-runtime";
const Foo = forwardRef(Foo_withRef);
const _isForgetEnabled_Fixtures_result = isForgetEnabled_Fixtures();
function _Foo_withRef_optimized(props, ref) {
const $ = _c(3);
let t0;
if ($[0] !== props || $[1] !== ref) {
t0 = <Stringify ref={ref} {...props} />;
$[0] = props;
$[1] = ref;
$[2] = t0;
} else {
t0 = $[2];
}
return t0;
}
function _Foo_withRef_unoptimized(props, ref) {
return <Stringify ref={ref} {...props} />;
}
function Foo_withRef(arg0, arg1) {
if (_isForgetEnabled_Fixtures_result)
return _Foo_withRef_optimized(arg0, arg1);
else return _Foo_withRef_unoptimized(arg0, arg1);
}
export const FIXTURE_ENTRYPOINT = {
fn: eval("(...args) => React.createElement(Foo, args)"),
params: [{ prop1: 1, prop2: 2, ref: createRef() }],
};
```
### Eval output
(kind: ok) <div>{"0":{"prop1":1,"prop2":2,"ref":{"current":null}},"ref":"[[ cyclic ref *3 ]]"}</div>

View File

@@ -1,13 +0,0 @@
// @gating
import {createRef, forwardRef} from 'react';
import {Stringify} from 'shared-runtime';
const Foo = forwardRef(Foo_withRef);
function Foo_withRef(props, ref) {
return <Stringify ref={ref} {...props} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: eval('(...args) => React.createElement(Foo, args)'),
params: [{prop1: 1, prop2: 2, ref: createRef()}],
};

View File

@@ -1,64 +0,0 @@
## Input
```javascript
// @gating
import {memo} from 'react';
import {Stringify} from 'shared-runtime';
export default memo(Foo);
function Foo({prop1, prop2}) {
'use memo';
return <Stringify prop1={prop1} prop2={prop2} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: eval('Foo'),
params: [{prop1: 1, prop2: 2}],
};
```
## Code
```javascript
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating
import { memo } from "react";
import { Stringify } from "shared-runtime";
export default memo(Foo);
const _isForgetEnabled_Fixtures_result = isForgetEnabled_Fixtures();
function _Foo_optimized(t0) {
"use memo";
const $ = _c(3);
const { prop1, prop2 } = t0;
let t1;
if ($[0] !== prop1 || $[1] !== prop2) {
t1 = <Stringify prop1={prop1} prop2={prop2} />;
$[0] = prop1;
$[1] = prop2;
$[2] = t1;
} else {
t1 = $[2];
}
return t1;
}
function _Foo_unoptimized({ prop1, prop2 }) {
"use memo";
return <Stringify prop1={prop1} prop2={prop2} />;
}
function Foo(arg0) {
if (_isForgetEnabled_Fixtures_result) return _Foo_optimized(arg0);
else return _Foo_unoptimized(arg0);
}
export const FIXTURE_ENTRYPOINT = {
fn: eval("Foo"),
params: [{ prop1: 1, prop2: 2 }],
};
```
### Eval output
(kind: ok) <div>{"prop1":1,"prop2":2}</div>

View File

@@ -1,14 +0,0 @@
// @gating
import {memo} from 'react';
import {Stringify} from 'shared-runtime';
export default memo(Foo);
function Foo({prop1, prop2}) {
'use memo';
return <Stringify prop1={prop1} prop2={prop2} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: eval('Foo'),
params: [{prop1: 1, prop2: 2}],
};

View File

@@ -1,59 +0,0 @@
## Input
```javascript
// @gating
import * as React from 'react';
let Foo;
const MemoFoo = React.memo(Foo);
Foo = () => <div>hello world!</div>;
/**
* Evaluate this fixture module to assert that compiler + original have the same
* runtime error message.
*/
export const FIXTURE_ENTRYPOINT = {
fn: () => {},
params: [],
};
```
## Code
```javascript
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating
import * as React from "react";
let Foo;
const MemoFoo = React.memo(Foo);
Foo = isForgetEnabled_Fixtures()
? () => {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = <div>hello world!</div>;
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
: () => <div>hello world!</div>;
/**
* Evaluate this fixture module to assert that compiler + original have the same
* runtime error message.
*/
export const FIXTURE_ENTRYPOINT = {
fn: isForgetEnabled_Fixtures() ? () => {} : () => {},
params: [],
};
```
### Eval output
(kind: ok)
logs: ['memo: The first argument must be a component. Instead received: %s','undefined']

View File

@@ -1,15 +0,0 @@
// @gating
import * as React from 'react';
let Foo;
const MemoFoo = React.memo(Foo);
Foo = () => <div>hello world!</div>;
/**
* Evaluate this fixture module to assert that compiler + original have the same
* runtime error message.
*/
export const FIXTURE_ENTRYPOINT = {
fn: () => {},
params: [],
};

View File

@@ -1,86 +0,0 @@
## Input
```javascript
// @gating
import * as React from 'react';
/**
* Test that the correct `Foo` is printed
*/
let Foo = () => <div>hello world 1!</div>;
const MemoOne = React.memo(Foo);
Foo = () => <div>hello world 2!</div>;
const MemoTwo = React.memo(Foo);
export const FIXTURE_ENTRYPOINT = {
fn: () => {
'use no memo';
return (
<>
<MemoOne />
<MemoTwo />
</>
);
},
params: [],
};
```
## Code
```javascript
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating
import * as React from "react";
/**
* Test that the correct `Foo` is printed
*/
let Foo = isForgetEnabled_Fixtures()
? () => {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = <div>hello world 1!</div>;
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
: () => <div>hello world 1!</div>;
const MemoOne = React.memo(Foo);
Foo = isForgetEnabled_Fixtures()
? () => {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = <div>hello world 2!</div>;
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
: () => <div>hello world 2!</div>;
const MemoTwo = React.memo(Foo);
export const FIXTURE_ENTRYPOINT = {
fn: () => {
"use no memo";
return (
<>
<MemoOne />
<MemoTwo />
</>
);
},
params: [],
};
```
### Eval output
(kind: ok) <div>hello world 1!</div><div>hello world 2!</div>

View File

@@ -1,23 +0,0 @@
// @gating
import * as React from 'react';
/**
* Test that the correct `Foo` is printed
*/
let Foo = () => <div>hello world 1!</div>;
const MemoOne = React.memo(Foo);
Foo = () => <div>hello world 2!</div>;
const MemoTwo = React.memo(Foo);
export const FIXTURE_ENTRYPOINT = {
fn: () => {
'use no memo';
return (
<>
<MemoOne />
<MemoTwo />
</>
);
},
params: [],
};

View File

@@ -1,26 +0,0 @@
## Input
```javascript
// @inferEffectDependencies @compilationMode(infer) @panicThreshold(none)
import useMyEffect from 'useEffectWrapper';
function nonReactFn(arg) {
useMyEffect(() => [1, 2, arg]);
}
```
## Error
```
3 |
4 | function nonReactFn(arg) {
> 5 | useMyEffect(() => [1, 2, arg]);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (5:5)
6 | }
7 |
```

View File

@@ -1,6 +0,0 @@
// @inferEffectDependencies @compilationMode(infer) @panicThreshold(none)
import useMyEffect from 'useEffectWrapper';
function nonReactFn(arg) {
useMyEffect(() => [1, 2, arg]);
}

View File

@@ -1,26 +0,0 @@
## Input
```javascript
// @inferEffectDependencies @compilationMode(infer) @panicThreshold(none)
import {useEffect} from 'react';
function nonReactFn(arg) {
useEffect(() => [1, 2, arg]);
}
```
## Error
```
3 |
4 | function nonReactFn(arg) {
> 5 | useEffect(() => [1, 2, arg]);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (5:5)
6 | }
7 |
```

View File

@@ -1,6 +0,0 @@
// @inferEffectDependencies @compilationMode(infer) @panicThreshold(none)
import {useEffect} from 'react';
function nonReactFn(arg) {
useEffect(() => [1, 2, arg]);
}

View File

@@ -1,41 +0,0 @@
## Input
```javascript
// @inferEffectDependencies @panicThreshold(none)
import {useEffect} from 'react';
/**
* Error on non-inlined effect functions:
* 1. From the effect hook callee's perspective, it only makes sense
* to either
* (a) never hard error (i.e. failing to infer deps is acceptable) or
* (b) always hard error,
* regardless of whether the callback function is an inline fn.
* 2. (Technical detail) it's harder to support detecting cases in which
* function (pre-Forget transform) was inline but becomes memoized
*/
function Component({foo}) {
function f() {
console.log(foo);
}
// No inferred dep array, the argument is not a lambda
useEffect(f);
}
```
## Error
```
18 |
19 | // No inferred dep array, the argument is not a lambda
> 20 | useEffect(f);
| ^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (20:20)
21 | }
22 |
```

View File

@@ -1,21 +0,0 @@
// @inferEffectDependencies @panicThreshold(none)
import {useEffect} from 'react';
/**
* Error on non-inlined effect functions:
* 1. From the effect hook callee's perspective, it only makes sense
* to either
* (a) never hard error (i.e. failing to infer deps is acceptable) or
* (b) always hard error,
* regardless of whether the callback function is an inline fn.
* 2. (Technical detail) it's harder to support detecting cases in which
* function (pre-Forget transform) was inline but becomes memoized
*/
function Component({foo}) {
function f() {
console.log(foo);
}
// No inferred dep array, the argument is not a lambda
useEffect(f);
}

View File

@@ -1,27 +0,0 @@
## Input
```javascript
// @inferEffectDependencies @panicThreshold(none)
import React from 'react';
function NonReactiveDepInEffect() {
const obj = makeObject_Primitives();
React.useEffect(() => print(obj));
}
```
## Error
```
4 | function NonReactiveDepInEffect() {
5 | const obj = makeObject_Primitives();
> 6 | React.useEffect(() => print(obj));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (6:6)
7 | }
8 |
```

View File

@@ -1,51 +0,0 @@
## Input
```javascript
// @inferEffectDependencies @panicThreshold(none)
import {useSpecialEffect} from 'shared-runtime';
/**
* Note that a react compiler-based transform still has limitations on JS syntax.
* We should surface these as actionable lint / build errors to devs.
*/
function Component({prop1}) {
'use memo';
useSpecialEffect(() => {
try {
console.log(prop1);
} finally {
console.log('exiting');
}
}, [prop1]);
return <div>{prop1}</div>;
}
```
## Error
```
8 | function Component({prop1}) {
9 | 'use memo';
> 10 | useSpecialEffect(() => {
| ^^^^^^^^^^^^^^^^^^^^^^^^
> 11 | try {
| ^^^^^^^^^
> 12 | console.log(prop1);
| ^^^^^^^^^
> 13 | } finally {
| ^^^^^^^^^
> 14 | console.log('exiting');
| ^^^^^^^^^
> 15 | }
| ^^^^^^^^^
> 16 | }, [prop1]);
| ^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics.. (Bailout reason: Todo: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause (11:15)) (10:16)
17 | return <div>{prop1}</div>;
18 | }
19 |
```

View File

@@ -1,18 +0,0 @@
// @inferEffectDependencies @panicThreshold(none)
import {useSpecialEffect} from 'shared-runtime';
/**
* Note that a react compiler-based transform still has limitations on JS syntax.
* We should surface these as actionable lint / build errors to devs.
*/
function Component({prop1}) {
'use memo';
useSpecialEffect(() => {
try {
console.log(prop1);
} finally {
console.log('exiting');
}
}, [prop1]);
return <div>{prop1}</div>;
}

View File

@@ -1,27 +0,0 @@
## Input
```javascript
// @inferEffectDependencies @panicThreshold(none)
import {useEffect} from 'react';
function Component({propVal}) {
'use no memo';
useEffect(() => [propVal]);
}
```
## Error
```
4 | function Component({propVal}) {
5 | 'use no memo';
> 6 | useEffect(() => [propVal]);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (6:6)
7 | }
8 |
```

View File

@@ -1,7 +0,0 @@
// @inferEffectDependencies @panicThreshold(none)
import {useEffect} from 'react';
function Component({propVal}) {
'use no memo';
useEffect(() => [propVal]);
}

View File

@@ -34,6 +34,13 @@ function Component({foo, bar}) {
console.log(bar.qux);
});
function f() {
console.log(foo);
}
// No inferred dep array, the argument is not a lambda
useEffect(f);
useEffectWrapper(() => {
console.log(foo);
});
@@ -51,7 +58,7 @@ import useEffectWrapper from "useEffectWrapper";
const moduleNonReactive = 0;
function Component(t0) {
const $ = _c(12);
const $ = _c(14);
const { foo, bar } = t0;
const ref = useRef(0);
@@ -112,7 +119,7 @@ function Component(t0) {
useEffect(t4, [bar.baz, bar.qux]);
let t5;
if ($[10] !== foo) {
t5 = () => {
t5 = function f() {
console.log(foo);
};
$[10] = foo;
@@ -120,7 +127,20 @@ function Component(t0) {
} else {
t5 = $[11];
}
useEffectWrapper(t5, [foo]);
const f = t5;
useEffect(f);
let t6;
if ($[12] !== foo) {
t6 = () => {
console.log(foo);
};
$[12] = foo;
$[13] = t6;
} else {
t6 = $[13];
}
useEffectWrapper(t6, [foo]);
}
```

View File

@@ -30,6 +30,13 @@ function Component({foo, bar}) {
console.log(bar.qux);
});
function f() {
console.log(foo);
}
// No inferred dep array, the argument is not a lambda
useEffect(f);
useEffectWrapper(() => {
console.log(foo);
});

View File

@@ -0,0 +1,44 @@
## Input
```javascript
// @inferEffectDependencies
import React from 'react';
function NonReactiveDepInEffect() {
const obj = makeObject_Primitives();
React.useEffect(() => print(obj));
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies
import React from "react";
function NonReactiveDepInEffect() {
const $ = _c(2);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = makeObject_Primitives();
$[0] = t0;
} else {
t0 = $[0];
}
const obj = t0;
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = () => print(obj);
$[1] = t1;
} else {
t1 = $[1];
}
React.useEffect(t1);
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -1,65 +0,0 @@
## Input
```javascript
function Test() {
const obj = {
21: 'dimaMachina',
};
// Destructuring assignment
const {21: myVar} = obj;
return (
<div>
{obj[21]}
{myVar}
</div>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Test,
params: [{}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
function Test() {
const $ = _c(2);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = { 21: "dimaMachina" };
$[0] = t0;
} else {
t0 = $[0];
}
const obj = t0;
const { 21: myVar } = obj;
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = (
<div>
{obj[21]}
{myVar}
</div>
);
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: Test,
params: [{}],
};
```
### Eval output
(kind: ok) <div>dimaMachinadimaMachina</div>

View File

@@ -1,18 +0,0 @@
function Test() {
const obj = {
21: 'dimaMachina',
};
// Destructuring assignment
const {21: myVar} = obj;
return (
<div>
{obj[21]}
{myVar}
</div>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Test,
params: [{}],
};

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @validateNoCapitalizedCalls @enableFire @panicThreshold(none)
// @validateNoCapitalizedCalls @enableFire
import {fire} from 'react';
const CapitalizedCall = require('shared-runtime').sum;
@@ -24,7 +24,7 @@ function Component({prop1, bar}) {
## Code
```javascript
import { useFire } from "react/compiler-runtime"; // @validateNoCapitalizedCalls @enableFire @panicThreshold(none)
import { useFire } from "react/compiler-runtime"; // @validateNoCapitalizedCalls @enableFire
import { fire } from "react";
const CapitalizedCall = require("shared-runtime").sum;

View File

@@ -1,4 +1,4 @@
// @validateNoCapitalizedCalls @enableFire @panicThreshold(none)
// @validateNoCapitalizedCalls @enableFire
import {fire} from 'react';
const CapitalizedCall = require('shared-runtime').sum;

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @enableFire @panicThreshold(none)
// @enableFire
import {useRef} from 'react';
function Component({props, bar}) {
@@ -26,7 +26,7 @@ function Component({props, bar}) {
## Code
```javascript
import { useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold(none)
import { useFire } from "react/compiler-runtime"; // @enableFire
import { useRef } from "react";
function Component(t0) {

View File

@@ -1,4 +1,4 @@
// @enableFire @panicThreshold(none)
// @enableFire
import {useRef} from 'react';
function Component({props, bar}) {

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees @enableFire @panicThreshold(none)
// @validatePreserveExistingMemoizationGuarantees @enableFire
import {fire} from 'react';
import {sum} from 'shared-runtime';
@@ -24,7 +24,7 @@ function Component({prop1, bar}) {
## Code
```javascript
import { useFire } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableFire @panicThreshold(none)
import { useFire } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableFire
import { fire } from "react";
import { sum } from "shared-runtime";

View File

@@ -1,4 +1,4 @@
// @validatePreserveExistingMemoizationGuarantees @enableFire @panicThreshold(none)
// @validatePreserveExistingMemoizationGuarantees @enableFire
import {fire} from 'react';
import {sum} from 'shared-runtime';

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @enableFire @panicThreshold(none)
// @enableFire
import {fire} from 'react';
function Component({prop1}) {
@@ -20,7 +20,7 @@ function Component({prop1}) {
## Code
```javascript
import { useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold(none)
import { useFire } from "react/compiler-runtime"; // @enableFire
import { fire } from "react";
function Component(t0) {

View File

@@ -1,4 +1,4 @@
// @enableFire @panicThreshold(none)
// @enableFire
import {fire} from 'react';
function Component({prop1}) {

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @flow @enableFire @panicThreshold(none)
// @flow @enableFire
import {fire} from 'react';
import {print} from 'shared-runtime';

View File

@@ -1,4 +1,4 @@
// @flow @enableFire @panicThreshold(none)
// @flow @enableFire
import {fire} from 'react';
import {print} from 'shared-runtime';

View File

@@ -1,42 +0,0 @@
## Input
```javascript
// @inferEffectDependencies @panicThreshold(none)
import {useRef} from 'react';
import {useSpecialEffect} from 'shared-runtime';
/**
* The retry pipeline disables memoization features, which means we need to
* provide an alternate implementation of effect dependencies which does not
* rely on memoization.
*/
function useFoo({cond}) {
const ref = useRef();
const derived = cond ? ref.current : makeObject();
useSpecialEffect(() => {
log(derived);
}, [derived]);
return ref;
}
```
## Error
```
11 | const ref = useRef();
12 | const derived = cond ? ref.current : makeObject();
> 13 | useSpecialEffect(() => {
| ^^^^^^^^^^^^^^^^^^^^^^^^
> 14 | log(derived);
| ^^^^^^^^^^^^^^^^^
> 15 | }, [derived]);
| ^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics.. (Bailout reason: Invariant: Expected function expression scope to exist (13:15)) (13:15)
16 | return ref;
17 | }
18 |
```

View File

@@ -1,17 +0,0 @@
// @inferEffectDependencies @panicThreshold(none)
import {useRef} from 'react';
import {useSpecialEffect} from 'shared-runtime';
/**
* The retry pipeline disables memoization features, which means we need to
* provide an alternate implementation of effect dependencies which does not
* rely on memoization.
*/
function useFoo({cond}) {
const ref = useRef();
const derived = cond ? ref.current : makeObject();
useSpecialEffect(() => {
log(derived);
}, [derived]);
return ref;
}

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @enableFire @panicThreshold(none)
// @enableFire
import {fire} from 'react';
/**
@@ -29,13 +29,21 @@ function Component({prop1}) {
## Error
```
9 | function Component({prop1}) {
10 | const foo = () => {
> 11 | try {
| ^^^^^
> 12 | console.log(prop1);
| ^^^^^^^^^^^^^^^^^^^^^^^^^
> 13 | } finally {
| ^^^^^^^^^^^^^^^^^^^^^^^^^
> 14 | console.log('jbrown215');
| ^^^^^^^^^^^^^^^^^^^^^^^^^
> 15 | }
| ^^^^^^ Todo: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause (11:15)
16 | };
17 | useEffect(() => {
> 18 | fire(foo());
| ^^^^ InvalidReact: [Fire] Untransformed reference to compiler-required feature. Either remove this `fire` call or ensure it is successfully transformed by the compiler. (Bailout reason: Todo: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause (11:15)) (18:18)
19 | });
20 | }
21 |
18 | fire(foo());
```

View File

@@ -1,4 +1,4 @@
// @enableFire @panicThreshold(none)
// @enableFire
import {fire} from 'react';
/**

View File

@@ -1,23 +0,0 @@
## Input
```javascript
// @enableFire @panicThreshold(none)
import {fire} from 'react';
console.log(fire == null);
```
## Error
```
2 | import {fire} from 'react';
3 |
> 4 | console.log(fire == null);
| ^^^^ InvalidReact: [Fire] Untransformed reference to compiler-required feature. Either remove this `fire` call or ensure it is successfully transformed by the compiler (4:4)
5 |
```

View File

@@ -1,4 +0,0 @@
// @enableFire @panicThreshold(none)
import {fire} from 'react';
console.log(fire == null);

View File

@@ -1,42 +0,0 @@
## Input
```javascript
// @enableFire @panicThreshold(none)
import {fire} from 'react';
/**
* TODO: we should eventually distinguish between `use no memo` and `use no
* compiler` directives. The former should be used to *only* disable memoization
* features.
*/
function Component({props, bar}) {
'use no memo';
const foo = () => {
console.log(props);
};
useEffect(() => {
fire(foo(props));
fire(foo());
fire(bar());
});
return null;
}
```
## Error
```
13 | };
14 | useEffect(() => {
> 15 | fire(foo(props));
| ^^^^ InvalidReact: [Fire] Untransformed reference to compiler-required feature. Either remove this `fire` call or ensure it is successfully transformed by the compiler (15:15)
16 | fire(foo());
17 | fire(bar());
18 | });
```

View File

@@ -1,95 +0,0 @@
## Input
```javascript
// @enableFire @panicThreshold(none)
import {fire} from 'react';
/**
* Compilation of this file should succeed.
*/
function NonFireComponent({prop1}) {
/**
* This component bails out but does not use fire
*/
const foo = () => {
try {
console.log(prop1);
} finally {
console.log('jbrown215');
}
};
useEffect(() => {
foo();
});
}
function FireComponent(props) {
/**
* This component uses fire and compiles successfully
*/
const foo = props => {
console.log(props);
};
useEffect(() => {
fire(foo(props));
});
return null;
}
```
## Code
```javascript
import { useFire } from "react/compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @enableFire @panicThreshold(none)
import { fire } from "react";
/**
* Compilation of this file should succeed.
*/
function NonFireComponent({ prop1 }) {
/**
* This component bails out but does not use fire
*/
const foo = () => {
try {
console.log(prop1);
} finally {
console.log("jbrown215");
}
};
useEffect(() => {
foo();
});
}
function FireComponent(props) {
const $ = _c(3);
const foo = _temp;
const t0 = useFire(foo);
let t1;
if ($[0] !== props || $[1] !== t0) {
t1 = () => {
t0(props);
};
$[0] = props;
$[1] = t0;
$[2] = t1;
} else {
t1 = $[2];
}
useEffect(t1);
return null;
}
function _temp(props_0) {
console.log(props_0);
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -1,35 +0,0 @@
// @enableFire @panicThreshold(none)
import {fire} from 'react';
/**
* Compilation of this file should succeed.
*/
function NonFireComponent({prop1}) {
/**
* This component bails out but does not use fire
*/
const foo = () => {
try {
console.log(prop1);
} finally {
console.log('jbrown215');
}
};
useEffect(() => {
foo();
});
}
function FireComponent(props) {
/**
* This component uses fire and compiles successfully
*/
const foo = props => {
console.log(props);
};
useEffect(() => {
fire(foo(props));
});
return null;
}

View File

@@ -0,0 +1,47 @@
## Input
```javascript
// @enableFire
import {fire} from 'react';
function Component({props, bar}) {
'use no memo';
const foo = () => {
console.log(props);
};
useEffect(() => {
fire(foo(props));
fire(foo());
fire(bar());
});
return null;
}
```
## Code
```javascript
// @enableFire
import { fire } from "react";
function Component({ props, bar }) {
"use no memo";
const foo = () => {
console.log(props);
};
useEffect(() => {
fire(foo(props));
fire(foo());
fire(bar());
});
return null;
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -1,11 +1,6 @@
// @enableFire @panicThreshold(none)
// @enableFire
import {fire} from 'react';
/**
* TODO: we should eventually distinguish between `use no memo` and `use no
* compiler` directives. The former should be used to *only* disable memoization
* features.
*/
function Component({props, bar}) {
'use no memo';
const foo = () => {

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @enableFire @panicThreshold(none)
// @enableFire
import {fire, useEffect} from 'react';
import {Stringify} from 'shared-runtime';
@@ -29,7 +29,7 @@ function Component(props) {
## Code
```javascript
import { useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold(none)
import { useFire } from "react/compiler-runtime"; // @enableFire
import { fire, useEffect } from "react";
import { Stringify } from "shared-runtime";

View File

@@ -1,4 +1,4 @@
// @enableFire @panicThreshold(none)
// @enableFire
import {fire, useEffect} from 'react';
import {Stringify} from 'shared-runtime';

View File

@@ -274,38 +274,6 @@ const tests: CompilerTestCases = {
},
],
},
{
name: 'Pipeline errors are reported',
code: normalizeIndent`
import useMyEffect from 'useMyEffect';
function Component({a}) {
'use no memo';
useMyEffect(() => console.log(a.b));
return <div>Hello world</div>;
}
`,
options: [
{
environment: {
inferEffectDependencies: [
{
function: {
source: 'useMyEffect',
importSpecifierName: 'default',
},
numRequiredArgs: 1,
},
],
},
},
],
errors: [
{
message:
'[InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics.',
},
],
},
],
};

View File

@@ -16,7 +16,3 @@ class Fixture extends React.Component {
Fixture.propTypes = propTypes;
export default Fixture;
Fixture.Controls = function FixtureControls({children}) {
return <div className="test-fixture__controls">{children}</div>;
};

View File

@@ -1,96 +0,0 @@
import TestCase from '../../TestCase';
import Fixture from '../../Fixture';
const React = window.React;
const {Fragment, useEffect, useRef, useState} = React;
function WrapperComponent(props) {
return props.children;
}
function handler(e) {
const text = e.currentTarget.innerText;
alert('You clicked: ' + text);
}
export default function EventListenerCase() {
const fragmentRef = useRef(null);
const [extraChildCount, setExtraChildCount] = useState(0);
useEffect(() => {
fragmentRef.current.addEventListener('click', handler);
const lastFragmentRefValue = fragmentRef.current;
return () => {
lastFragmentRefValue.removeEventListener('click', handler);
};
});
return (
<TestCase title="Event Registration">
<TestCase.Steps>
<li>Click one of the children, observe the alert</li>
<li>Add a new child, click it, observe the alert</li>
<li>Remove the event listeners, click a child, observe no alert</li>
<li>Add the event listeners back, click a child, observe the alert</li>
</TestCase.Steps>
<TestCase.ExpectedResult>
<p>
Fragment refs can manage event listeners on the first level of host
children. This page loads with an effect that sets up click event
hanndlers on each child card. Clicking on a card will show an alert
with the card's text.
</p>
<p>
New child nodes will also have event listeners applied. Removed nodes
will have their listeners cleaned up.
</p>
</TestCase.ExpectedResult>
<Fixture>
<Fixture.Controls>
<div>Target count: {extraChildCount + 3}</div>
<button
onClick={() => {
setExtraChildCount(prev => prev + 1);
}}>
Add Child
</button>
<button
onClick={() => {
fragmentRef.current.addEventListener('click', handler);
}}>
Add click event listeners
</button>
<button
onClick={() => {
fragmentRef.current.removeEventListener('click', handler);
}}>
Remove click event listeners
</button>
</Fixture.Controls>
<div className="card-container">
<Fragment ref={fragmentRef}>
<div className="card" id="child-a">
Child A
</div>
<div className="card" id="child-b">
Child B
</div>
<WrapperComponent>
<div className="card" id="child-c">
Child C
</div>
{Array.from({length: extraChildCount}).map((_, index) => (
<div className="card" id={'extra-child-' + index} key={index}>
Extra Child {index}
</div>
))}
</WrapperComponent>
</Fragment>
</div>
</Fixture>
</TestCase>
);
}

View File

@@ -1,56 +0,0 @@
import TestCase from '../../TestCase';
import Fixture from '../../Fixture';
const React = window.React;
const {Fragment, useEffect, useRef, useState} = React;
export default function FocusCase() {
const fragmentRef = useRef(null);
return (
<TestCase title="Focus Management">
<TestCase.Steps>
<li>Click to focus the first child</li>
<li>Click to focus the last child</li>
<li>Click to blur any focus within the fragment</li>
</TestCase.Steps>
<TestCase.ExpectedResult>
<p>
The focus method will focus the first focusable child within the
fragment, skipping any unfocusable children.
</p>
<p>
The focusLast method is the reverse, focusing the last focusable
child.
</p>
<p>
Blur will call blur on the document, only if one of the children
within the fragment is the active element.
</p>
</TestCase.ExpectedResult>
<Fixture>
<Fixture.Controls>
<button onClick={() => fragmentRef.current.focus()}>
Focus first child
</button>
<button onClick={() => fragmentRef.current.focusLast()}>
Focus last child
</button>
<button onClick={() => fragmentRef.current.blur()}>Blur</button>
</Fixture.Controls>
<div className="highlight-focused-children" style={{display: 'flex'}}>
<Fragment ref={fragmentRef}>
<div style={{outline: '1px solid black'}}>Unfocusable div</div>
<button>Button 1</button>
<button>Button 2</button>
<input type="text" placeholder="Input field" />
<div style={{outline: '1px solid black'}}>Unfocusable div</div>
</Fragment>
</div>
</Fixture>
</TestCase>
);
}

View File

@@ -1,100 +0,0 @@
import TestCase from '../../TestCase';
import Fixture from '../../Fixture';
const React = window.React;
const {Fragment, useEffect, useRef, useState} = React;
export default function GetClientRectsCase() {
const fragmentRef = useRef(null);
const [rects, setRects] = useState([]);
const getRects = () => {
const rects = fragmentRef.current.getClientRects();
setRects(rects);
};
return (
<TestCase title="getClientRects">
<TestCase.Steps>
<li>
Click the "Print Rects" button to get the client rects of the
elements.
</li>
</TestCase.Steps>
<TestCase.ExpectedResult>
Calling getClientRects on the fragment instance will return a list of a
DOMRectList for each child node.
</TestCase.ExpectedResult>
<Fixture>
<Fixture.Controls>
<button onClick={getRects}>Print Rects</button>
<div style={{display: 'flex'}}>
<div
style={{
position: 'relative',
width: '30vw',
height: '30vh',
border: '1px solid black',
}}>
{rects.map(({x, y, width, height}, index) => {
const scale = 0.3;
return (
<div
key={index}
style={{
position: 'absolute',
top: y * scale,
left: x * scale,
width: width * scale,
height: height * scale,
border: '1px solid red',
boxSizing: 'border-box',
}}></div>
);
})}
</div>
<div>
{rects.map(({x, y, width, height}, index) => {
return (
<div>
{index} :: {`{`}x: {x}, y: {y}, width: {width}, height:{' '}
{height}
{`}`}
</div>
);
})}
</div>
</div>
</Fixture.Controls>
<Fragment ref={fragmentRef}>
<span
style={{
width: '300px',
height: '250px',
backgroundColor: 'lightblue',
fontSize: 20,
border: '1px solid black',
marginBottom: '10px',
}}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua.
</span>
<div
style={{
width: '150px',
height: '100px',
backgroundColor: 'lightgreen',
border: '1px solid black',
}}></div>
<div
style={{
width: '500px',
height: '50px',
backgroundColor: 'lightpink',
border: '1px solid black',
}}></div>
</Fragment>
</Fixture>
</TestCase>
);
}

View File

@@ -1,155 +0,0 @@
import TestCase from '../../TestCase';
import Fixture from '../../Fixture';
const React = window.React;
const {Fragment, useEffect, useRef, useState} = React;
function WrapperComponent(props) {
return props.children;
}
function ObservedChild({id}) {
return (
<div id={id} className="observable-card">
{id}
</div>
);
}
const initialItems = [
['A', false],
['B', false],
['C', false],
];
export default function IntersectionObserverCase() {
const fragmentRef = useRef(null);
const [items, setItems] = useState(initialItems);
const addedItems = items.slice(3);
const anyOnScreen = items.some(([, onScreen]) => onScreen);
const observerRef = useRef(null);
useEffect(() => {
if (observerRef.current === null) {
observerRef.current = new IntersectionObserver(
entries => {
setItems(prev => {
const newItems = [...prev];
entries.forEach(entry => {
const index = newItems.findIndex(
([id]) => id === entry.target.id
);
newItems[index] = [entry.target.id, entry.isIntersecting];
});
return newItems;
});
},
{
threshold: [0.5],
}
);
}
fragmentRef.current.observeUsing(observerRef.current);
const lastFragmentRefValue = fragmentRef.current;
return () => {
lastFragmentRefValue.unobserveUsing(observerRef.current);
observerRef.current = null;
};
}, []);
return (
<TestCase title="Intersection Observer">
<TestCase.Steps>
<li>
Scroll the children into view, observe the sidebar appears and shows
which children are in the viewport
</li>
<li>
Add a new child and observe that the Intersection Observer is applied
</li>
<li>
Click Unobserve and observe that the state of children in the viewport
is no longer updated
</li>
<li>
Click Observe and observe that the state of children in the viewport
is updated again
</li>
</TestCase.Steps>
<TestCase.ExpectedResult>
<p>
Fragment refs manage Intersection Observers on the first level of host
children. This page loads with an effect that sets up an Inersection
Observer applied to each child card.
</p>
<p>
New child nodes will also have the observer applied. Removed nodes
will be unobserved.
</p>
</TestCase.ExpectedResult>
<Fixture>
<Fixture.Controls>
<button
onClick={() => {
setItems(prev => [
...prev,
[`Extra child: ${prev.length + 1}`, false],
]);
}}>
Add Child
</button>
<button
onClick={() => {
setItems(prev => {
if (prev.length === 3) {
return prev;
}
return prev.slice(0, prev.length - 1);
});
}}>
Remove Child
</button>
<button
onClick={() => {
fragmentRef.current.observeUsing(observerRef.current);
}}>
Observe
</button>
<button
onClick={() => {
fragmentRef.current.unobserveUsing(observerRef.current);
setItems(prev => {
return prev.map(item => [item[0], false]);
});
}}>
Unobserve
</button>
{anyOnScreen && (
<div className="fixed-sidebar card-container">
<p>
<strong>Children on screen:</strong>
</p>
{items.map(item => (
<div className={`card ${item[1] ? 'onscreen' : null}`}>
{item[0]}
</div>
))}
</div>
)}
</Fixture.Controls>
<Fragment ref={fragmentRef}>
<ObservedChild id="A" />
<WrapperComponent>
<ObservedChild id="B" />
</WrapperComponent>
<ObservedChild id="C" />
{addedItems.map((_, index) => (
<ObservedChild id={`Extra child: ${index + 4}`} />
))}
</Fragment>
</Fixture>
</TestCase>
);
}

View File

@@ -1,63 +0,0 @@
import TestCase from '../../TestCase';
import Fixture from '../../Fixture';
const React = window.React;
const {Fragment, useEffect, useRef, useState} = React;
export default function ResizeObserverCase() {
const fragmentRef = useRef(null);
const [width, setWidth] = useState([0, 0, 0]);
useEffect(() => {
const resizeObserver = new window.ResizeObserver(entries => {
if (entries.length > 0) {
setWidth(prev => {
const newWidth = [...prev];
entries.forEach(entry => {
const index = parseInt(entry.target.id, 10);
newWidth[index] = Math.round(entry.contentRect.width);
});
return newWidth;
});
}
});
fragmentRef.current.observeUsing(resizeObserver);
const lastFragmentRefValue = fragmentRef.current;
return () => {
lastFragmentRefValue.unobserveUsing(resizeObserver);
};
}, []);
return (
<TestCase title="Resize Observer">
<TestCase.Steps>
<li>Resize the viewport width until the children respond</li>
<li>See that the width data updates as they elements resize</li>
</TestCase.Steps>
<TestCase.ExpectedResult>
The Fragment Ref has a ResizeObserver attached which has a callback to
update the width state of each child node.
</TestCase.ExpectedResult>
<Fixture>
<Fragment ref={fragmentRef}>
<div className="card" id="0" style={{width: '100%'}}>
<p>
Width: <b>{width[0]}px</b>
</p>
</div>
<div className="card" id="1" style={{width: '80%'}}>
<p>
Width: <b>{width[1]}px</b>
</p>
</div>
<div className="card" id="2" style={{width: '50%'}}>
<p>
Width: <b>{width[2]}px</b>
</p>
</div>
</Fragment>
</Fixture>
</TestCase>
);
}

View File

@@ -1,20 +1,104 @@
import Fixture from '../../Fixture';
import FixtureSet from '../../FixtureSet';
import EventListenerCase from './EventListenerCase';
import IntersectionObserverCase from './IntersectionObserverCase';
import ResizeObserverCase from './ResizeObserverCase';
import FocusCase from './FocusCase';
import GetClientRectsCase from './GetClientRectsCase';
import TestCase from '../../TestCase';
const React = window.React;
const {Fragment, useEffect, useRef, useState} = React;
function WrapperComponent(props) {
return props.children;
}
function handler(e) {
const text = e.currentTarget.innerText;
alert('You clicked: ' + text);
}
export default function FragmentRefsPage() {
const fragmentRef = useRef(null);
const [extraChildCount, setExtraChildCount] = useState(0);
React.useEffect(() => {
fragmentRef.current.addEventListener('click', handler);
const lastFragmentRefValue = fragmentRef.current;
return () => {
lastFragmentRefValue.removeEventListener('click', handler);
};
});
return (
<FixtureSet title="Fragment Refs">
<EventListenerCase />
<IntersectionObserverCase />
<ResizeObserverCase />
<FocusCase />
<GetClientRectsCase />
<TestCase title="Event registration">
<TestCase.Steps>
<li>Click one of the children, observe the alert</li>
<li>Add a new child, click it, observe the alert</li>
<li>Remove the event listeners, click a child, observe no alert</li>
<li>
Add the event listeners back, click a child, observe the alert
</li>
</TestCase.Steps>
<TestCase.ExpectedResult>
<p>
Fragment refs can manage event listeners on the first level of host
children. This page loads with an effect that sets up click event
hanndlers on each child card. Clicking on a card will show an alert
with the card's text.
</p>
<p>
New child nodes will also have event listeners applied. Removed
nodes will have their listeners cleaned up.
</p>
</TestCase.ExpectedResult>
<Fixture>
<div className="control-box" id="control-box">
<div>Target count: {extraChildCount + 3}</div>
<button
onClick={() => {
setExtraChildCount(prev => prev + 1);
}}>
Add Child
</button>
<button
onClick={() => {
fragmentRef.current.addEventListener('click', handler);
}}>
Add click event listeners
</button>
<button
onClick={() => {
fragmentRef.current.removeEventListener('click', handler);
}}>
Remove click event listeners
</button>
<div class="card-container">
<Fragment ref={fragmentRef}>
<div className="card" id="child-a">
Child A
</div>
<div className="card" id="child-b">
Child B
</div>
<WrapperComponent>
<div className="card" id="child-c">
Child C
</div>
{Array.from({length: extraChildCount}).map((_, index) => (
<div
className="card"
id={'extra-child-' + index}
key={index}>
Extra Child {index}
</div>
))}
</WrapperComponent>
</Fragment>
</div>
</div>
</Fixture>
</TestCase>
</FixtureSet>
);
}

View File

@@ -224,7 +224,7 @@ p {
}
.test-case__body {
padding: 10px 10px 0 10px;
padding: 10px;
}
.test-case__desc {
@@ -280,17 +280,11 @@ p {
.test-fixture {
padding: 20px;
margin: 0 -10px; /* opposite of .test-case padding */
margin: 0 -15px; /* opposite of .test-case padding */
background-color: #f4f4f4;
border-top: 1px solid #d9d9d9;
}
.test-fixture__controls {
margin: -20px -20px 20px -20px;
padding: 20px;
border: 1px solid #444;
}
.field-group {
overflow: hidden;
}
@@ -328,43 +322,3 @@ tbody tr:nth-child(even) {
margin: 10px;
padding: 10px;
}
.observable-card {
height: 200px;
border: 1px solid black;
background: #e0e0e0;
padding: 20px;
font-size: 18px;
overflow: auto;
margin-bottom: 50px;
position: relative;
}
.observable-card::after {
content: "";
position: absolute;
top: 50%;
left: 0;
width: 100%;
border-top: 1px dotted red;
}
.fixed-sidebar {
position: fixed;
top: 0;
left: 0;
height: 100%;
width: 200px;
z-index: 1000;
background-color: gray;
display: flex;
flex-direction: column;
}
.onscreen {
background-color: green;
}
.highlight-focused-children *:focus {
outline: 2px solid green;
}

View File

@@ -20,7 +20,7 @@
"prestart": "cp -r ../../build/oss-experimental/* ./node_modules/",
"prebuild": "cp -r ../../build/oss-experimental/* ./node_modules/",
"dev": "concurrently \"npm run dev:server\" \"npm run dev:client\"",
"dev:client": "BROWSER=none PORT=3001 react-scripts start",
"dev:client": "PORT=3001 react-scripts start",
"dev:server": "NODE_ENV=development node server",
"start": "react-scripts build && NODE_ENV=production node server",
"build": "react-scripts build",

View File

@@ -26,7 +26,7 @@
"prestart": "cp -r ../../build/oss-experimental/* ./node_modules/",
"prebuild": "cp -r ../../build/oss-experimental/* ./node_modules/",
"dev": "concurrently \"npm run dev:server\" \"npm run dev:client\"",
"dev:client": "BROWSER=none PORT=3001 react-scripts start",
"dev:client": "PORT=3001 react-scripts start",
"dev:server": "NODE_ENV=development node server",
"start": "react-scripts build && NODE_ENV=production node server",
"build": "react-scripts build",

View File

@@ -8,16 +8,7 @@
}
.swipe-recognizer {
width: 300px;
background: #eee;
width: 200px;
border: 1px solid #333333;
border-radius: 10px;
padding: 20px;
}
.button {
background: #000;
color: #fff;
border: 0px;
border-radius: 5px;
padding: 10px;
}

View File

@@ -4,7 +4,6 @@ import React, {
unstable_useSwipeTransition as useSwipeTransition,
useEffect,
useState,
useId,
} from 'react';
import SwipeRecognizer from './SwipeRecognizer';
@@ -40,11 +39,6 @@ function Component() {
);
}
function Id() {
// This is just testing that Id inside a ViewTransition can hydrate correctly.
return <span id={useId()} />;
}
export default function Page({url, navigate}) {
const [renderedUrl, startGesture] = useSwipeTransition('/?a', url, '/?b');
const show = renderedUrl === '/?b';
@@ -74,80 +68,69 @@ export default function Page({url, navigate}) {
</ViewTransition>
);
return (
<div className="swipe-recognizer">
<SwipeRecognizer
action={swipeAction}
gesture={startGesture}
direction={show ? 'left' : 'right'}>
<button
className="button"
onClick={() => {
navigate(url === '/?b' ? '/?a' : '/?b');
}}>
{url === '/?b' ? 'Goto A' : 'Goto B'}
</button>
<ViewTransition className="none">
<div>
<ViewTransition>
<div>
<ViewTransition className={transitions['slide-on-nav']}>
<h1>{!show ? 'A' : 'B' + counter}</h1>
</ViewTransition>
</div>
</ViewTransition>
<ViewTransition
className={{
'navigation-back': transitions['slide-right'],
'navigation-forward': transitions['slide-left'],
}}>
<h1>{!show ? 'A' + counter : 'B'}</h1>
</ViewTransition>
{show ? (
<div>
{a}
{b}
</div>
) : (
<div>
{b}
{a}
</div>
)}
<ViewTransition>
{show ? (
<div>hello{exclamation}</div>
) : (
<section>Loading</section>
)}
</ViewTransition>
<p>
<Id />
</p>
{show ? null : (
<ViewTransition>
<div>world{exclamation}</div>
</ViewTransition>
)}
<Activity mode={show ? 'visible' : 'hidden'}>
<ViewTransition>
<div>!!</div>
</ViewTransition>
</Activity>
<p>these</p>
<p>rows</p>
<p>exist</p>
<p>to</p>
<p>test</p>
<p>scrolling</p>
<p>content</p>
<p>out</p>
<p>of</p>
<p>the</p>
<p>viewport</p>
{show ? <Component /> : null}
<div>
<button
onClick={() => {
navigate(show ? '/?a' : '/?b');
}}>
{show ? 'A' : 'B'}
</button>
<ViewTransition className="none">
<div>
<ViewTransition className={transitions['slide-on-nav']}>
<h1>{!show ? 'A' : 'B'}</h1>
</ViewTransition>
<ViewTransition
className={{
'navigation-back': transitions['slide-right'],
'navigation-forward': transitions['slide-left'],
}}>
<h1>{!show ? 'A' + counter : 'B' + counter}</h1>
</ViewTransition>
{show ? (
<div>
{a}
{b}
</div>
) : (
<div>
{b}
{a}
</div>
)}
<ViewTransition>
{show ? <div>hello{exclamation}</div> : <section>Loading</section>}
</ViewTransition>
<p>scroll me</p>
<p></p>
<p></p>
<p></p>
<p></p>
<p></p>
<p></p>
<div className="swipe-recognizer">
<SwipeRecognizer
action={swipeAction}
gesture={startGesture}
direction={show ? 'left' : 'right'}>
Swipe me
</SwipeRecognizer>
</div>
</ViewTransition>
</SwipeRecognizer>
<p></p>
<p></p>
{show ? null : (
<ViewTransition>
<div>world{exclamation}</div>
</ViewTransition>
)}
<Activity mode={show ? 'visible' : 'hidden'}>
<ViewTransition>
<div>!!</div>
</ViewTransition>
</Activity>
{show ? <Component /> : null}
</div>
</ViewTransition>
</div>
);
}

View File

@@ -21,7 +21,7 @@
"@babel/plugin-transform-block-scoped-functions": "^7.10.4",
"@babel/plugin-transform-block-scoping": "^7.11.1",
"@babel/plugin-transform-class-properties": "^7.25.9",
"@babel/plugin-transform-classes": "^7.10.4",
"@babel/plugin-transform-classes": "^7.25.9",
"@babel/plugin-transform-computed-properties": "^7.10.4",
"@babel/plugin-transform-destructuring": "^7.10.4",
"@babel/plugin-transform-for-of": "^7.10.4",
@@ -118,7 +118,7 @@
"testRegex": "/scripts/jest/dont-run-jest-directly\\.js$"
},
"scripts": {
"prebuild": "./scripts/react-compiler/link-compiler.sh",
"prebuild": "yarn --cwd compiler install --frozen-lockfile && ./scripts/react-compiler/link-compiler.sh",
"build": "node ./scripts/rollup/build-all-release-channels.js",
"build-for-devtools": "cross-env RELEASE_CHANNEL=experimental yarn build react/index,react/jsx,react/compiler-runtime,react-dom/index,react-dom/client,react-dom/unstable_testing,react-dom/test-utils,react-is,react-debug-tools,scheduler,react-test-renderer,react-refresh,react-art --type=NODE",
"build-for-devtools-dev": "yarn build-for-devtools --type=NODE_DEV",

View File

@@ -518,10 +518,6 @@ export function measureInstance(instance) {
return null;
}
export function measureClonedInstance(instance) {
return null;
}
export function wasInstanceInViewport(measurement): boolean {
return true;
}

Some files were not shown because too many files have changed in this diff Show More