Compare commits

..

29 Commits

Author SHA1 Message Date
Sebastian "Sebbie" Silbermann
4a9df08157 Stop creating Owner Stacks if many have been created recently (#32529)
Co-authored-by: Jack Pope <jackpope1@gmail.com>
2025-03-23 15:47:03 -07:00
mofeiZ
da996a15be [compiler][be] Move e2e tests to BabelPlugin transformer (#32706)
Clean up jest-e2e setup since
https://github.com/facebook/react/pull/32663 and other features need
program context (e.g. changing imports)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32706).
* #32663
* __->__ #32706
2025-03-21 20:05:22 -04:00
Dimitri POSTOLOV
6b1a2c1d81 fix(react-compiler): optimize components declared with arrow function and implicit return and compilationMode: 'infer' (#31792)
fixes https://github.com/facebook/react/issues/31601
https://github.com/facebook/react/issues/31639 cc @josephsavona
2025-03-21 16:46:02 -07:00
lauren
de4aad5ba6 [ci] Add missing permissions to runtime_commit_artifacts.yml (#32710)
Turns out we need permissions to write to `contents` after all.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32710).
* #32711
* __->__ #32710
2025-03-21 18:01:02 -04:00
lauren
156f0eca20 [ci] Don't use pull_request_target (#32708)
`pull_request_target` gives access to repository secrets and permissions
for use from forks, for example to add a comment.

> Due to the dangers inherent to automatic processing of PRs, GitHub’s
standard pull_request workflow trigger by default prevents write
permissions and secrets access to the target repository. However, in
some scenarios such access is needed to properly process the PR. To this
end the pull_request_target workflow trigger was introduced.

> The reason to introduce the pull_request_target trigger was to enable
workflows to label PRs (e.g. needs review) or to comment on the PR.

(via
https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/)

In this case there is no reason for us to allow this, so let's just use
the normal `pull_request` trigger which is less permissive.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32708).
* __->__ #32708
* #32709
2025-03-21 16:17:28 -04:00
lauren
4f080e498c [ci] Also give permissions on pull_requests (#32709)
Missed one
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32709).
* #32708
* __->__ #32709
2025-03-21 16:17:15 -04:00
lauren
fe8c10695c [ci] Add missing permissions (#32707)
Missed these ones earlier.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32707).
* #32708
* __->__ #32707
2025-03-21 16:07:09 -04:00
Ricky
daee08562c [activity] remove ref for now (#32645)
Followup from https://github.com/facebook/react/pull/32499

Manual mode is unused and has some bugs such as revealing hidden
boundaries when manually toggling. We also want to change how manual
mode works, and do some refactors to Activity to make it easier to
support. For now we'll remove it, then add it back after the other
changes we have planned.
2025-03-21 14:44:02 -04:00
lauren
ab693a926f [ci] Scope permissions for all workflows (#32704) 2025-03-21 14:40:55 -04:00
lauren
607615f4f6 [ci] Scope permissions for runtime_commit_artifacts.yml (#32701) 2025-03-21 14:40:34 -04:00
Sebastian Markbåge
e1e740717b Force layout before startViewTransition (#32699)
This works around this Safari bug.
https://bugs.webkit.org/show_bug.cgi?id=290146

This unfortunate because it may cause additional layouts if there's more
updates to the tree coming by manual mutation before it gets painted
naturally. However, we might end up wanting to read layout early anyway.

This affects the fixture because we clone the `<link>` from the `<head>`
which is itself another bug. However, it should be possible to have
`<link>` tags inserted into the new tree so this is still relevant.
2025-03-21 10:05:31 -04:00
lauren
ac799e569d [ci] Bump number of shards for test_build to 10 (#32693)
I noticed `test_build` can take a while so let's bump the number of
shards
2025-03-20 17:49:36 -04:00
mofeiZ
0962f684a0 [compiler][bugfix] Don't insert hook guards in retry pipeline (#32665)
Fixing bug from https://github.com/facebook/react/pull/32164 -- prior to
this PR, we inserted hook guards even for functions that bailed out of
compilation.
2025-03-20 17:25:08 -04:00
lauren
b888986054 [ci] Rename other stale branch workflow (#32692)
Makes it easier to tell what is what
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32692).
* __->__ #32692
* #32691
2025-03-20 17:19:47 -04:00
lauren
addce2f9f2 [ci] Add daily stale branch cache cleanup (#32691)
Cleans up stale non-main caches daily
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32691).
* #32692
* __->__ #32691
2025-03-20 17:17:44 -04:00
lauren
74bcf3d0d2 [ci] Don't fail on cache miss (#32690)
Partially reverts #32686.

PR caches inherit from caches generated in `main`. If it cannot find
that cache, it will create one scoped to just that PR (and PRs that
inherit from it).

There is an edge case where cache eviction can happen in the middle of a
test run. If cache eviction removes a `main` cache, child jobs that
depend on it will start failing because of the `fail-on-cache-miss`
setting.

This PR reverts the default behavior. If this happens, the workflow will
still continue in slow mode where it will `yarn install` child jobs
instead of reusing from cache. This is slower but will at least allow
workflows to continue.

Additionally I added restore keys so that we can fallback to other
caches if present so `yarn install` doesn't need to start over from
scratch.
2025-03-20 17:02:39 -04:00
Ricky
b630219b13 [refactor] move isValidElementType to react-is (#32518) 2025-03-20 16:51:33 -04:00
Jack Pope
7943da1e81 Set accurate value for alwaysThrottleRetries on www (#32684)
This flag value was updated in
https://github.com/facebook/react/pull/28965 (seemingly unrelated, maybe
as part of unit testing). But its still controlled by a dynamic flag in
www. Let's update this to VARIANT to accurately represent the state of
the rollout.

Before:
<img width="1340" alt="Screenshot 2025-03-20 at 10 45 30 AM"
src="https://github.com/user-attachments/assets/d0405a36-eb71-4108-9e23-8d462cc68fb4"
/>

After:
<img width="1351" alt="Screenshot 2025-03-20 at 10 45 11 AM"
src="https://github.com/user-attachments/assets/459d260d-7a25-430b-95a6-d6a91d958417"
/>
2025-03-20 14:28:55 -04:00
Joe Savona
e3c06424ae [compiler] Refactor validations to return Result and log where appropriate
Updates ~all of our validations to return a Result, and then updates callers to either unwrap() if they should bailout or else just log.

ghstack-source-id: 418b5f5aa2b7dd49ca76b3f98a48a35150691d7e
Pull Request resolved: https://github.com/facebook/react/pull/32688
2025-03-20 11:02:02 -07:00
Joe Savona
5f4c5c920f [compiler] Validate static components
React uses function identity to determine whether a given JSX expression represents the same type of component and should reconcile (keep state, update props) or replace (teardown state, create a new instance). This PR adds off-by-default validation to check that developers are not dynamically creating components during render.

The check is local and intentionally conservative. We specifically look for the results of call expressions, new expressions, or function expressions that are then used directly (or aliased) as a JSX tag. This allows common sketchy but fine-in-practice cases like passing a reference to a component from a parent as props, but catches very obvious mistakes such as:

```js
function Example() {
  const Component = createComponent();
  return <Component />;
}
```

We could expand this to catch more cases, but this seems like a reasonable starting point. Note that I tried enabling the validation by default and the only fixtures that error are the new ones added here. I'll also test this internally. What i'm imagining is that we enable this in the linter but not the compiler.

ghstack-source-id: e7408c0a55478b40d65489703d209e8fa7205e45
Pull Request resolved: https://github.com/facebook/react/pull/32683
2025-03-20 11:02:02 -07:00
lauren
112224d8d2 [ci] Also cache playground playwright browsers (#32687)
Following #32678, do the same for the playground e2e test since this
step can sometimes take many minutes to complete.
2025-03-20 13:53:31 -04:00
lauren
87d7e4c55b [ci] Fail on cache miss (#32686)
Since we use a centralized cache we should fail subsequent steps if the
child jobs are unable to restore the cache from the first 2 jobs.

Also fix some incorrect hashes used for the fixture tests.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32686).
* __->__ #32686
* #32685
2025-03-20 12:22:06 -04:00
lauren
3bcf8c23de [ci] Warm cache (#32685)
Try restoring from old caches as a base to speed up the case where
node_modules needs updating.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32685).
* #32686
* __->__ #32685
2025-03-20 12:21:50 -04:00
Sebastian Markbåge
a4f9bd586b Enable Fragment refs in Experimental (#32670)
That we can test it out in Next.js router conditionally when
experimental is on for other reasons.
2025-03-19 20:38:27 -04:00
lauren
ff8f6f21f7 [ci] Fix Will commit these changes www step (#32681)
Unlike the fbsource version of the step, www doesn't add any changes so
the `force` input doesn't actually work
2025-03-19 18:13:06 -04:00
lauren
19176e3c08 [ci] Use correct revision for Meta builds (#32680)
There was a bug previously in our commit artifacts step where the
emitted REVISION hash would reference the commit on the builds branch
rather than from `main`.

Given that our internal manual sync script also does this, let's align
them both to always reference the commit from `main` instead.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32680).
* __->__ #32680
* #32679
* #32678
2025-03-19 17:24:43 -04:00
lauren
d16c26da40 [ci] Specify if-no-files-found on actions/upload-artifact@v4 (#32679)
Defaults to warn, but since some steps require these artifacts to be
uploaded we specify an error if its not found. Some other steps like
playwright test-results are only uploaded on failure so it's okay to
ignore.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32679).
* #32680
* __->__ #32679
* #32678
2025-03-19 17:22:40 -04:00
lauren
a8c155cab9 [ci] Cache playwright browsers (#32678)
No reason to download them from scratch every time.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32678).
* #32680
* #32679
* __->__ #32678
2025-03-19 17:22:17 -04:00
lauren
995410463a [ci] Parameterize branch cleanup (#32677)
Allow a PR number to be passed as input
2025-03-19 16:39:01 -04:00
154 changed files with 12330 additions and 2102 deletions

View File

@@ -336,11 +336,11 @@ module.exports = {
'packages/react-devtools-extensions/**/*.js',
'packages/react-devtools-timeline/**/*.js',
'packages/react-native-renderer/**/*.js',
'packages/eslint-plugin-react-hooks/**/*.js',
'packages/jest-react/**/*.js',
'packages/internal-test-utils/**/*.js',
'packages/**/__tests__/*.js',
'packages/**/npm/*.js',
'compiler/packages/eslint-plugin-react-hooks/**/*.js',
],
rules: {
'react-internal/prod-error-codes': OFF,
@@ -515,7 +515,7 @@ module.exports = {
},
},
{
files: ['compiler/packages/eslint-plugin-react-hooks/src/**/*'],
files: ['packages/eslint-plugin-react-hooks/src/**/*'],
extends: ['plugin:@typescript-eslint/recommended'],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint', 'eslint-plugin'],

View File

@@ -1,15 +1,20 @@
name: (Compiler) Discord Notify
on:
pull_request_target:
pull_request:
types: [opened, ready_for_review]
paths:
- compiler/**
- .github/workflows/compiler_**.yml
permissions: {}
jobs:
check_maintainer:
uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main
permissions:
# Used by check_maintainer
contents: read
with:
actor: ${{ github.event.pull_request.user.login }}

View File

@@ -8,6 +8,8 @@ on:
- compiler/**
- .github/workflows/compiler_playground.yml
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
@@ -40,7 +42,19 @@ jobs:
**/node_modules
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- name: Check Playwright version
id: playwright_version
run: echo "playwright_version=$(npm ls @playwright/test | grep @playwright | sed 's/.*@//' | head -1)" >> "$GITHUB_OUTPUT"
- name: Cache Playwright Browsers for version ${{ steps.playwright_version.outputs.playwright_version }}
id: cache_playwright_browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-browsers-v6-${{ runner.arch }}-${{ runner.os }}-${{ steps.playwright_version.outputs.playwright_version }}
- run: npx playwright install --with-deps chromium
if: steps.cache_playwright_browsers.outputs.cache-hit != 'true'
- run: npx playwright install-deps
if: steps.cache_playwright_browsers.outputs.cache-hit == 'true'
- run: CI=true yarn test
- run: ls -R test-results
if: '!cancelled()'
@@ -50,3 +64,4 @@ jobs:
with:
name: test-results
path: compiler/apps/playground/test-results
if-no-files-found: ignore

View File

@@ -20,11 +20,12 @@ on:
NPM_TOKEN:
required: true
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
GH_TOKEN: ${{ github.token }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
defaults:

View File

@@ -15,6 +15,8 @@ on:
required: true
type: string
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles

View File

@@ -5,6 +5,8 @@ on:
# At 10 minutes past 16:00 on Mon, Tue, Wed, Thu, and Fri
- cron: 10 16 * * 1,2,3,4,5
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles

View File

@@ -5,6 +5,8 @@ on:
# At 10 minutes past 9:00 on Mon
- cron: 10 9 * * 1
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles

View File

@@ -8,6 +8,8 @@ on:
- compiler/**
- .github/workflows/compiler_typescript.yml
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true

View File

@@ -9,6 +9,8 @@ on:
required: false
type: string
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
@@ -18,6 +20,9 @@ jobs:
download_build:
name: Download base build
runs-on: ubuntu-latest
permissions:
# We use github.token to download the build artifact from a previous runtime_build_and_test.yml run
actions: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
@@ -47,6 +52,7 @@ jobs:
with:
name: build
path: build
if-no-files-found: error
build_devtools_and_process_artifacts:
name: Build DevTools and process artifacts
@@ -84,17 +90,20 @@ jobs:
with:
name: react-devtools
path: build/devtools.tgz
if-no-files-found: error
# Simplifies getting the extension for local testing
- name: Archive chrome extension
uses: actions/upload-artifact@v4
with:
name: react-devtools-chrome-extension
path: build/devtools/chrome-extension.zip
if-no-files-found: error
- name: Archive firefox extension
uses: actions/upload-artifact@v4
with:
name: react-devtools-firefox-extension
path: build/devtools/firefox-extension.zip
if-no-files-found: error
run_devtools_tests_for_versions:
name: Run DevTools tests for versions
@@ -179,3 +188,4 @@ jobs:
with:
name: screenshots
path: ./tmp/screenshots
if-no-files-found: warn

View File

@@ -7,6 +7,8 @@ on:
paths-ignore:
- compiler/**
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
@@ -41,6 +43,16 @@ jobs:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Warm with old cache
if: steps.node_modules.outputs.cache-hit != 'true'
uses: actions/cache/restore@v4
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-node_modules-v6-
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Save cache
@@ -74,6 +86,16 @@ jobs:
cache-dependency-path: |
yarn.lock
compiler/yarn.lock
- name: Warm with old cache
if: steps.node_modules.outputs.cache-hit != 'true'
uses: actions/cache/restore@v4
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
restore-keys: |
runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-and-compiler-node_modules-v6-
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd compiler install --frozen-lockfile
@@ -127,6 +149,9 @@ jobs:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-node_modules-v6-
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -154,6 +179,9 @@ jobs:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-node_modules-v6-
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -241,6 +269,9 @@ jobs:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
restore-keys: |
runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-and-compiler-node_modules-v6-
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -282,6 +313,9 @@ jobs:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
restore-keys: |
runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-and-compiler-node_modules-v6-
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -302,6 +336,7 @@ jobs:
with:
name: _build_${{ matrix.worker_id }}_${{ matrix.release_channel }}
path: build
if-no-files-found: error
test_build:
name: yarn test-build
@@ -340,9 +375,16 @@ jobs:
# TODO: Test more persistent configurations?
]
shard:
- 1/3
- 2/3
- 3/3
- 1/10
- 2/10
- 3/10
- 4/10
- 5/10
- 6/10
- 7/10
- 8/10
- 9/10
- 10/10
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -362,6 +404,9 @@ jobs:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
restore-keys: |
runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-and-compiler-node_modules-v6-
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -398,6 +443,9 @@ jobs:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-node_modules-v6-
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -426,6 +474,7 @@ jobs:
path: |
./build.tgz
./build2.tgz
if-no-files-found: error
check_error_codes:
name: Search build artifacts for unminified errors
@@ -447,6 +496,9 @@ jobs:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-node_modules-v6-
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -484,6 +536,9 @@ jobs:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-node_modules-v6-
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -517,7 +572,7 @@ jobs:
with:
path: |
**/node_modules
key: fixtures_dom-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
key: fixtures_dom-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'fixtures/dom/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn --cwd fixtures/dom install --frozen-lockfile
@@ -561,11 +616,26 @@ jobs:
with:
path: |
**/node_modules
key: fixtures_flight-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
key: fixtures_flight-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'fixtures/flight/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd fixtures/flight install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Check Playwright version
id: playwright_version
run: echo "playwright_version=$(npm ls @playwright/test | grep @playwright | sed 's/.*@//' | head -1)" >> "$GITHUB_OUTPUT"
- name: Cache Playwright Browsers for version ${{ steps.playwright_version.outputs.playwright_version }}
id: cache_playwright_browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-browsers-v6-${{ runner.arch }}-${{ runner.os }}-${{ steps.playwright_version.outputs.playwright_version }}
- name: Playwright install deps
if: steps.cache_playwright_browsers.outputs.cache-hit != 'true'
working-directory: fixtures/flight
run: npx playwright install --with-deps chromium
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -574,16 +644,6 @@ jobs:
merge-multiple: true
- name: Display structure of build
run: ls -R build
- name: Install fixture dependencies
working-directory: fixtures/flight
run: |
yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
if [ $? -ne 0 ]; then
yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
fi
- name: Playwright install deps
working-directory: fixtures/flight
run: npx playwright install --with-deps chromium
- name: Run tests
working-directory: fixtures/flight
run: yarn test
@@ -595,11 +655,13 @@ jobs:
with:
name: flight-playwright-report
path: fixtures/flight/playwright-report
if-no-files-found: warn
- name: Archive Flight fixture artifacts
uses: actions/upload-artifact@v4
with:
name: flight-test-results
path: fixtures/flight/test-results
if-no-files-found: ignore
# ----- DEVTOOLS -----
build_devtools_and_process_artifacts:
@@ -626,6 +688,9 @@ jobs:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-node_modules-v6-
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -647,6 +712,7 @@ jobs:
with:
name: react-devtools-${{ matrix.browser }}-extension
path: build/devtools/${{ matrix.browser }}-extension.zip
if-no-files-found: error
merge_devtools_artifacts:
name: Merge DevTools artifacts
@@ -679,6 +745,9 @@ jobs:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-node_modules-v6-
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -700,7 +769,10 @@ jobs:
sizebot:
if: ${{ github.event_name == 'pull_request' && github.ref_name != 'main' && github.event.pull_request.base.ref == 'main' }}
name: Run sizebot
needs: [build_and_lint, runtime_node_modules_cache]
needs: [build_and_lint]
permissions:
# We use github.token to download the build artifact from a previous runtime_build_and_test.yml run
actions: read
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -712,7 +784,7 @@ jobs:
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4 # note: this does not reuse centralized cache since it has unique cache key
id: node_modules
with:
path: |
@@ -738,8 +810,6 @@ jobs:
run: ls -R base-build
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build for PR
uses: actions/download-artifact@v4
with:
@@ -759,3 +829,4 @@ jobs:
with:
name: sizebot-message
path: sizebot-message.md
if-no-files-found: ignore

View File

@@ -22,6 +22,8 @@ on:
default: false
type: boolean
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
@@ -30,6 +32,40 @@ env:
jobs:
download_artifacts:
runs-on: ubuntu-latest
permissions:
# We use github.token to download the build artifact from a previous runtime_build_and_test.yml run
actions: read
steps:
- uses: actions/checkout@v4
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-release-node_modules-v6-${{ 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
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd scripts/release install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- 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 }}
- name: Display structure of build
run: ls -R build
- name: Archive build
uses: actions/upload-artifact@v4
with:
name: build
path: build/
if-no-files-found: error
process_artifacts:
runs-on: ubuntu-latest
needs: [download_artifacts]
outputs:
www_branch_count: ${{ steps.check_branches.outputs.www_branch_count }}
fbsource_branch_count: ${{ steps.check_branches.outputs.fbsource_branch_count }}
@@ -69,25 +105,11 @@ jobs:
run: |
echo "www_branch_count=$(git ls-remote --heads origin "refs/heads/meta-www" | wc -l)" >> "$GITHUB_OUTPUT"
echo "fbsource_branch_count=$(git ls-remote --heads origin "refs/heads/meta-fbsource" | wc -l)" >> "$GITHUB_OUTPUT"
- uses: actions/setup-node@v4
- name: Restore downloaded build
uses: actions/download-artifact@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-release-node_modules-v6-${{ 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: 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 }}
name: build
path: build
- name: Display structure of build
run: ls -R build
- name: Strip @license from eslint plugin and react-refresh
@@ -151,9 +173,9 @@ jobs:
ls -R ./compiled-rn
- name: Add REVISION files
run: |
echo ${{ github.sha }} >> ./compiled/facebook-www/REVISION
echo ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} >> ./compiled/facebook-www/REVISION
cp ./compiled/facebook-www/REVISION ./compiled/facebook-www/REVISION_TRANSFORMS
echo ${{ github.sha}} >> ./compiled-rn/facebook-fbsource/xplat/js/react-native-github/Libraries/Renderer/REVISION
echo ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} >> ./compiled-rn/facebook-fbsource/xplat/js/react-native-github/Libraries/Renderer/REVISION
- name: "Get current version string"
id: get_current_version
run: |
@@ -170,15 +192,20 @@ jobs:
with:
name: compiled
path: compiled/
if-no-files-found: error
- uses: actions/upload-artifact@v4
with:
name: compiled-rn
path: compiled-rn/
if-no-files-found: error
commit_www_artifacts:
needs: download_artifacts
if: inputs.force == true || (github.ref == 'refs/heads/main' && needs.download_artifacts.outputs.www_branch_count == '0')
needs: [download_artifacts, process_artifacts]
if: inputs.force == true || (github.ref == 'refs/heads/main' && needs.process_artifacts.outputs.www_branch_count == '0')
runs-on: ubuntu-latest
permissions:
# Used to push a commit to builds/facebook-www
contents: write
steps:
- uses: actions/checkout@v4
with:
@@ -190,12 +217,12 @@ jobs:
name: compiled
path: compiled/
- name: Revert version changes
if: needs.download_artifacts.outputs.last_version_classic != '' && needs.download_artifacts.outputs.last_version_modern != ''
if: needs.process_artifacts.outputs.last_version_classic != '' && needs.process_artifacts.outputs.last_version_modern != ''
env:
CURRENT_VERSION_CLASSIC: ${{ needs.download_artifacts.outputs.current_version_classic }}
CURRENT_VERSION_MODERN: ${{ needs.download_artifacts.outputs.current_version_modern }}
LAST_VERSION_CLASSIC: ${{ needs.download_artifacts.outputs.last_version_classic }}
LAST_VERSION_MODERN: ${{ needs.download_artifacts.outputs.last_version_modern }}
CURRENT_VERSION_CLASSIC: ${{ needs.process_artifacts.outputs.current_version_classic }}
CURRENT_VERSION_MODERN: ${{ needs.process_artifacts.outputs.current_version_modern }}
LAST_VERSION_CLASSIC: ${{ needs.process_artifacts.outputs.last_version_classic }}
LAST_VERSION_MODERN: ${{ needs.process_artifacts.outputs.last_version_modern }}
run: |
echo "Reverting $CURRENT_VERSION_CLASSIC to $LAST_VERSION_CLASSIC"
grep -rl "$CURRENT_VERSION_CLASSIC" ./compiled || echo "No files found with $CURRENT_VERSION_CLASSIC"
@@ -225,12 +252,12 @@ jobs:
echo "should_commit=false" >> "$GITHUB_OUTPUT"
fi
- name: Re-apply version changes
if: inputs.force == true || (steps.check_should_commit.outputs.should_commit == 'true' && needs.download_artifacts.outputs.last_version_classic != '' && needs.download_artifacts.outputs.last_version_modern != '')
if: inputs.force == true || (steps.check_should_commit.outputs.should_commit == 'true' && needs.process_artifacts.outputs.last_version_classic != '' && needs.process_artifacts.outputs.last_version_modern != '')
env:
CURRENT_VERSION_CLASSIC: ${{ needs.download_artifacts.outputs.current_version_classic }}
CURRENT_VERSION_MODERN: ${{ needs.download_artifacts.outputs.current_version_modern }}
LAST_VERSION_CLASSIC: ${{ needs.download_artifacts.outputs.last_version_classic }}
LAST_VERSION_MODERN: ${{ needs.download_artifacts.outputs.last_version_modern }}
CURRENT_VERSION_CLASSIC: ${{ needs.process_artifacts.outputs.current_version_classic }}
CURRENT_VERSION_MODERN: ${{ needs.process_artifacts.outputs.current_version_modern }}
LAST_VERSION_CLASSIC: ${{ needs.process_artifacts.outputs.last_version_classic }}
LAST_VERSION_MODERN: ${{ needs.process_artifacts.outputs.last_version_modern }}
run: |
echo "Re-applying $LAST_VERSION_CLASSIC to $CURRENT_VERSION_CLASSIC"
grep -rl "$LAST_VERSION_CLASSIC" ./compiled || echo "No files found with $LAST_VERSION_CLASSIC"
@@ -244,8 +271,8 @@ jobs:
- name: Will commit these changes
if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
run: |
echo ":"
git status -u
git add .
git status
- name: Check commit message
if: inputs.dry_run
run: |
@@ -264,8 +291,11 @@ jobs:
run: git push
commit_fbsource_artifacts:
needs: download_artifacts
if: inputs.force == true || (github.ref == 'refs/heads/main' && needs.download_artifacts.outputs.fbsource_branch_count == '0')
needs: [download_artifacts, process_artifacts]
permissions:
# Used to push a commit to builds/facebook-fbsource
contents: write
if: inputs.force == true || (github.ref == 'refs/heads/main' && needs.process_artifacts.outputs.fbsource_branch_count == '0')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -278,10 +308,10 @@ jobs:
name: compiled-rn
path: compiled-rn/
- name: Revert version changes
if: needs.download_artifacts.outputs.last_version_rn != ''
if: needs.process_artifacts.outputs.last_version_rn != ''
env:
CURRENT_VERSION: ${{ needs.download_artifacts.outputs.current_version_rn }}
LAST_VERSION: ${{ needs.download_artifacts.outputs.last_version_rn }}
CURRENT_VERSION: ${{ needs.process_artifacts.outputs.current_version_rn }}
LAST_VERSION: ${{ needs.process_artifacts.outputs.last_version_rn }}
run: |
echo "Reverting $CURRENT_VERSION to $LAST_VERSION"
grep -rl "$CURRENT_VERSION" ./compiled-rn || echo "No files found with $CURRENT_VERSION"
@@ -307,10 +337,10 @@ jobs:
echo "should_commit=false" >> "$GITHUB_OUTPUT"
fi
- name: Re-apply version changes
if: inputs.force == true || (steps.check_should_commit.outputs.should_commit == 'true' && needs.download_artifacts.outputs.last_version_rn != '')
if: inputs.force == true || (steps.check_should_commit.outputs.should_commit == 'true' && needs.process_artifacts.outputs.last_version_rn != '')
env:
CURRENT_VERSION: ${{ needs.download_artifacts.outputs.current_version_rn }}
LAST_VERSION: ${{ needs.download_artifacts.outputs.last_version_rn }}
CURRENT_VERSION: ${{ needs.process_artifacts.outputs.current_version_rn }}
LAST_VERSION: ${{ needs.process_artifacts.outputs.last_version_rn }}
run: |
echo "Re-applying $LAST_VERSION to $CURRENT_VERSION"
grep -rl "$LAST_VERSION" ./compiled-rn || echo "No files found with $LAST_VERSION"

View File

@@ -1,15 +1,20 @@
name: (Runtime) Discord Notify
on:
pull_request_target:
pull_request:
types: [opened, ready_for_review]
paths-ignore:
- compiler/**
- .github/workflows/compiler_**.yml
permissions: {}
jobs:
check_maintainer:
uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main
permissions:
# Used by check_maintainer
contents: read
with:
actor: ${{ github.event.pull_request.user.login }}

View File

@@ -7,6 +7,8 @@ on:
paths-ignore:
- compiler/**
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true

View File

@@ -8,6 +8,8 @@ on:
- main
workflow_dispatch:
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles

View File

@@ -17,11 +17,12 @@ on:
NPM_TOKEN:
required: true
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
GH_TOKEN: ${{ github.token }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
jobs:

View File

@@ -6,6 +6,8 @@ on:
prerelease_commit_sha:
required: true
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles

View File

@@ -5,6 +5,8 @@ on:
# At 10 minutes past 16:00 on Mon, Tue, Wed, Thu, and Fri
- cron: 10 16 * * 1,2,3,4,5
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles

View File

@@ -31,11 +31,12 @@ on:
type: boolean
default: false
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
GH_TOKEN: ${{ github.token }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
jobs:

View File

@@ -14,6 +14,8 @@ on:
is_core_team:
value: ${{ jobs.check_maintainer.outputs.is_core_team }}
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
@@ -22,6 +24,9 @@ env:
jobs:
check_maintainer:
runs-on: ubuntu-latest
permissions:
# We fetch the contents of the MAINTAINERS file
contents: read
outputs:
is_core_team: ${{ steps.check_if_actor_is_maintainer.outputs.result }}
steps:

View File

@@ -1,11 +1,17 @@
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#force-deletion-of-caches-overriding-default-cache-eviction-policy
name: (Shared) Cleanup Branch Caches
name: (Shared) Cleanup Merged Branch Caches
on:
pull_request:
types:
- closed
workflow_dispatch:
inputs:
pr_number:
required: true
type: string
permissions: {}
jobs:
cleanup:
@@ -23,13 +29,13 @@ jobs:
## Setting this to not fail the workflow while deleting cache keys.
set +e
echo "Deleting caches..."
for cacheKey in $cacheKeysForPR
do
gh cache delete $cacheKey
echo "Deleting $cacheKey"
done
echo "Done"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
BRANCH: refs/pull/${{ github.event.pull_request.number }}/merge
BRANCH: refs/pull/${{ inputs.pr_number || github.event.pull_request.number }}/merge

View File

@@ -0,0 +1,35 @@
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#force-deletion-of-caches-overriding-default-cache-eviction-policy
name: (Shared) Cleanup Stale Branch Caches
on:
schedule:
- cron: 0 0 * * *
workflow_dispatch:
permissions: {}
jobs:
cleanup:
runs-on: ubuntu-latest
permissions:
# `actions:write` permission is required to delete caches
# See also: https://docs.github.com/en/rest/actions/cache?apiVersion=2022-11-28#delete-a-github-actions-cache-for-a-repository-using-a-cache-id
actions: write
contents: read
steps:
- name: Cleanup
run: |
echo "Fetching list of cache keys"
cacheKeysForPR=$(gh cache list --limit 100 --json id,ref --jq '.[] | select(.ref != "refs/heads/main") | .id')
## Setting this to not fail the workflow while deleting cache keys.
set +e
for cacheKey in $cacheKeysForPR
do
gh cache delete $cacheKey
echo "Deleting $cacheKey"
done
echo "Done"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}

View File

@@ -5,6 +5,8 @@ on:
branches:
- 'builds/facebook-**'
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
@@ -13,6 +15,9 @@ env:
jobs:
close_pr:
runs-on: ubuntu-latest
permissions:
# Used to create a review and close PRs
pull-requests: write
steps:
- name: Close PR
uses: actions/github-script@v7

View File

@@ -1,7 +1,9 @@
name: (Shared) Label Core Team PRs
on:
pull_request_target:
pull_request:
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
@@ -11,6 +13,9 @@ env:
jobs:
check_maintainer:
uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main
permissions:
# Used by check_maintainer
contents: read
with:
actor: ${{ github.event.pull_request.user.login }}
@@ -18,6 +23,11 @@ jobs:
if: ${{ needs.check_maintainer.outputs.is_core_team == 'true' }}
runs-on: ubuntu-latest
needs: check_maintainer
permissions:
# Used to add labels on issues
issues: write
# Used to add labels on PRs
pull-requests: write
steps:
- name: Label PR as React Core Team
uses: actions/github-script@v7

View File

@@ -5,6 +5,8 @@ on:
branches: [main]
pull_request:
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true

View File

@@ -6,6 +6,8 @@ on:
- cron: '0 * * * *'
workflow_dispatch:
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles

View File

@@ -33,6 +33,7 @@ const canaryChannelLabel = 'canary';
const rcNumber = 0;
const stablePackages = {
'eslint-plugin-react-hooks': '5.2.0',
'jest-react': '0.17.0',
react: ReactVersion,
'react-art': ReactVersion,

View File

@@ -0,0 +1,19 @@
'use strict';
/**
* HACK: @poteto React Compiler inlines Zod in its build artifact. Zod spreads values passed to .map
* which causes issues in @babel/plugin-transform-spread in loose mode, as it will result in
* {undefined: undefined} which fails to parse.
*
* [@babel/plugin-transform-block-scoping', {throwIfClosureRequired: true}] also causes issues with
* the built version of the compiler. The minimal set of plugins needed for this file is reexported
* from babel.config-ts.
*
* I will remove this hack later when we move eslint-plugin-react-hooks into the compiler directory.
**/
const baseConfig = require('./babel.config-ts');
module.exports = {
plugins: baseConfig.plugins,
};

View File

@@ -16,7 +16,7 @@
"snap:build": "yarn workspace snap run build",
"snap:ci": "yarn snap:build && yarn snap",
"ts:analyze-trace": "scripts/ts-analyze-trace.sh",
"lint": "../../node_modules/eslint-v8/bin/eslint.js src",
"lint": "yarn eslint src",
"watch": "yarn build --watch"
},
"dependencies": {
@@ -43,7 +43,7 @@
"babel-jest": "^29.0.3",
"babel-plugin-fbt": "^1.0.0",
"babel-plugin-fbt-runtime": "^1.0.0",
"eslint-v8": "npm:eslint@^8.57.1",
"eslint": "^8.57.1",
"invariant": "^2.2.4",
"jest": "^29.0.3",
"jest-environment-jsdom": "^29.0.3",

View File

@@ -7,7 +7,4 @@
const makeE2EConfig = require('../jest/makeE2EConfig');
const config = makeE2EConfig('e2e with forget', true);
config.setupFilesAfterEnv = ['<rootDir>/../scripts/jest/setupEnvE2E.js'];
module.exports = config;
module.exports = makeE2EConfig('e2e with forget', true);

View File

@@ -5,19 +5,16 @@
* LICENSE file in the root directory of this source tree.
*/
import {jsx} from '@babel/plugin-syntax-jsx';
import babelJest from 'babel-jest';
import {compile} from 'babel-plugin-react-compiler';
import {
validateEnvironmentConfig,
EnvironmentConfig,
} from 'babel-plugin-react-compiler';
import {execSync} from 'child_process';
import type {NodePath, Visitor} from '@babel/traverse';
import type {CallExpression, FunctionDeclaration} from '@babel/types';
import * as t from '@babel/types';
import {
EnvironmentConfig,
validateEnvironmentConfig,
} from 'babel-plugin-react-compiler';
import {basename} from 'path';
import type {CallExpression} from '@babel/types';
import BabelPluginReactCompiler from 'babel-plugin-react-compiler';
/**
* -- IMPORTANT --
@@ -28,10 +25,19 @@ import {basename} from 'path';
const e2eTransformerCacheKey = 1;
const forgetOptions: EnvironmentConfig = validateEnvironmentConfig({
enableAssumeHooksFollowRulesOfReact: true,
enableFunctionOutlining: false,
});
const debugMode = process.env['DEBUG_FORGET_COMPILER'] != null;
const compilerCacheKey = execSync(
'yarn --silent --cwd ../.. hash packages/babel-plugin-react-compiler/dist',
)
.toString()
.trim();
if (debugMode) {
console.log('cachebreaker', compilerCacheKey);
}
module.exports = (useForget: boolean) => {
function createTransformer() {
return babelJest.createTransformer({
@@ -42,15 +48,14 @@ module.exports = (useForget: boolean) => {
plugins: [
useForget
? [
ReactForgetFunctionTransform,
BabelPluginReactCompiler,
{
environment: forgetOptions,
/*
* Jest hashes the babel config as a cache breaker.
* (see https://github.com/jestjs/jest/blob/v29.6.2/packages/babel-jest/src/index.ts#L84)
*/
compilerCacheKey: execSync(
'yarn --silent --cwd ../.. hash packages/babel-plugin-react-compiler/dist',
).toString(),
compilerCacheKey,
transformOptionsCacheKey: forgetOptions,
e2eTransformerCacheKey,
},
@@ -105,104 +110,3 @@ module.exports = (useForget: boolean) => {
createTransformer,
};
};
// Mostly copied from react/scripts/babel/transform-forget.js
function isReactComponentLike(fn: NodePath<FunctionDeclaration>): boolean {
let isReactComponent = false;
let hasNoUseForgetDirective = false;
/*
* React components start with an upper case letter,
* React hooks start with `use`
*/
if (
fn.node.id == null ||
(fn.node.id.name[0].toUpperCase() !== fn.node.id.name[0] &&
!/^use[A-Z0-9]/.test(fn.node.id.name))
) {
return false;
}
fn.traverse({
DirectiveLiteral(path) {
if (path.node.value === 'use no forget') {
hasNoUseForgetDirective = true;
}
},
JSX(path) {
// Is there is a JSX node created in the current function context?
if (path.scope.getFunctionParent()?.path.node === fn.node) {
isReactComponent = true;
}
},
CallExpression(path) {
// Is there hook usage?
if (
path.node.callee.type === 'Identifier' &&
!/^use[A-Z0-9]/.test(path.node.callee.name)
) {
isReactComponent = true;
}
},
});
if (hasNoUseForgetDirective) {
return false;
}
return isReactComponent;
}
function ReactForgetFunctionTransform() {
const compiledFns = new Set();
const visitor = {
FunctionDeclaration(fn: NodePath<FunctionDeclaration>, state: any): void {
if (compiledFns.has(fn.node)) {
return;
}
if (!isReactComponentLike(fn)) {
return;
}
if (debugMode) {
const filename = basename(state.file.opts.filename);
if (fn.node.loc && fn.node.id) {
console.log(
` Compiling ${filename}:${fn.node.loc.start.line}:${fn.node.loc.start.column} ${fn.node.id.name}`,
);
} else {
console.log(` Compiling ${filename} ${fn.node.id?.name}`);
}
}
const compiled = compile(
fn,
forgetOptions,
'Other',
'all_features',
'_c',
null,
null,
null,
);
compiledFns.add(compiled);
const fun = t.functionDeclaration(
compiled.id,
compiled.params,
compiled.body,
compiled.generator,
compiled.async,
);
fn.replaceWith(fun);
fn.skip();
},
};
return {
name: 'react-forget-e2e',
inherits: jsx,
visitor,
};
}

View File

@@ -1,16 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
const ReactCompilerRuntime = require('react/compiler-runtime');
/*
* Our e2e babel transform currently only compiles functions, not programs.
* As a result, our e2e transpiled code does not contain an import for the
* memo cache function. As a temporary hack, we add a `_c` global, which is
* the name that is used for the import by default.
*/
globalThis._c = ReactCompilerRuntime.c;

View File

@@ -6,6 +6,7 @@
*/
import type {SourceLocation} from './HIR';
import {Err, Ok, Result} from './Utils/Result';
import {assertExhaustive} from './Utils/utils';
export enum ErrorSeverity {
@@ -224,6 +225,10 @@ export class CompilerError extends Error {
return this.details.length > 0;
}
asResult(): Result<void, CompilerError> {
return this.hasErrors() ? Err(this) : Ok(undefined);
}
/*
* An error is critical if it means the compiler has entered into a broken state and cannot
* continue safely. Other expected errors such as Todos mean that we can skip over that component

View File

@@ -100,8 +100,9 @@ import {propagateScopeDependenciesHIR} from '../HIR/PropagateScopeDependenciesHI
import {outlineJSX} from '../Optimization/OutlineJsx';
import {optimizePropsMethodCalls} from '../Optimization/OptimizePropsMethodCalls';
import {transformFire} from '../Transform';
import {validateNoImpureFunctionsInRender} from '../Validation/ValiateNoImpureFunctionsInRender';
import {validateNoImpureFunctionsInRender} from '../Validation/ValidateNoImpureFunctionsInRender';
import {CompilerError} from '..';
import {validateStaticComponents} from '../Validation/ValidateStaticComponents';
export type CompilerPipelineValue =
| {kind: 'ast'; name: string; value: CodegenFunction}
@@ -161,7 +162,7 @@ function runWithEnvironment(
log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
validateContextVariableLValues(hir);
validateUseMemo(hir);
validateUseMemo(hir).unwrap();
if (
env.isInferredMemoEnabled &&
@@ -202,10 +203,10 @@ function runWithEnvironment(
if (env.isInferredMemoEnabled) {
if (env.config.validateHooksUsage) {
validateHooksUsage(hir);
validateHooksUsage(hir).unwrap();
}
if (env.config.validateNoCapitalizedCalls) {
validateNoCapitalizedCalls(hir);
validateNoCapitalizedCalls(hir).unwrap();
}
}
@@ -255,23 +256,23 @@ function runWithEnvironment(
}
if (env.config.validateRefAccessDuringRender) {
validateNoRefAccessInRender(hir);
validateNoRefAccessInRender(hir).unwrap();
}
if (env.config.validateNoSetStateInRender) {
validateNoSetStateInRender(hir);
validateNoSetStateInRender(hir).unwrap();
}
if (env.config.validateNoSetStateInPassiveEffects) {
validateNoSetStateInPassiveEffects(hir);
env.logErrors(validateNoSetStateInPassiveEffects(hir));
}
if (env.config.validateNoJSXInTryStatements) {
validateNoJSXInTryStatement(hir);
env.logErrors(validateNoJSXInTryStatement(hir));
}
if (env.config.validateNoImpureFunctionsInRender) {
validateNoImpureFunctionsInRender(hir);
validateNoImpureFunctionsInRender(hir).unwrap();
}
}
@@ -293,6 +294,10 @@ function runWithEnvironment(
});
if (env.isInferredMemoEnabled) {
if (env.config.validateStaticComponents) {
env.logErrors(validateStaticComponents(hir));
}
/**
* Only create reactive scopes (which directly map to generated memo blocks)
* if inferred memoization is enabled. This makes all later passes which
@@ -509,14 +514,14 @@ function runWithEnvironment(
});
if (env.config.validateMemoizedEffectDependencies) {
validateMemoizedEffectDependencies(reactiveFunction);
validateMemoizedEffectDependencies(reactiveFunction).unwrap();
}
if (
env.config.enablePreserveExistingMemoizationGuarantees ||
env.config.validatePreserveExistingMemoizationGuarantees
) {
validatePreservedManualMemoization(reactiveFunction);
validatePreservedManualMemoization(reactiveFunction).unwrap();
}
const ast = codegenFunction(reactiveFunction, {

View File

@@ -451,6 +451,12 @@ export function compileProgram(
pass.code,
),
};
if (
!compileResult.compiledFn.hasFireRewrite &&
!compileResult.compiledFn.hasLoweredContextAccess
) {
return null;
}
} catch (err) {
// TODO: we might want to log error here, but this will also result in duplicate logging
if (err instanceof CompilerError) {
@@ -1002,31 +1008,39 @@ function callsHooksOrCreatesJsx(
return invokesHooks || createsJsx;
}
function isNonNode(node?: t.Expression | null): boolean {
if (!node) {
return true;
}
switch (node.type) {
case 'ObjectExpression':
case 'ArrowFunctionExpression':
case 'FunctionExpression':
case 'BigIntLiteral':
case 'ClassExpression':
case 'NewExpression': // technically `new Array()` is legit, but unlikely
return true;
}
return false;
}
function returnsNonNode(
node: NodePath<
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
>,
): boolean {
let hasReturn = false;
let returnsNonNode = false;
if (
// node.traverse#ArrowFunctionExpression isn't called for the root node
node.type === 'ArrowFunctionExpression' &&
node.node.body.type !== 'BlockStatement'
) {
returnsNonNode = isNonNode(node.node.body);
}
node.traverse({
ReturnStatement(ret) {
hasReturn = true;
const argument = ret.node.argument;
if (argument == null) {
returnsNonNode = true;
} else {
switch (argument.type) {
case 'ObjectExpression':
case 'ArrowFunctionExpression':
case 'FunctionExpression':
case 'BigIntLiteral':
case 'ClassExpression':
case 'NewExpression': // technically `new Array()` is legit, but unlikely
returnsNonNode = true;
}
}
returnsNonNode = isNonNode(ret.node.argument);
},
// Skip traversing all nested functions and their return statements
ArrowFunctionExpression: skipNestedFunctions(node),
@@ -1035,7 +1049,7 @@ function returnsNonNode(
ObjectMethod: node => node.skip(),
});
return !hasReturn || returnsNonNode;
return returnsNonNode;
}
/*

View File

@@ -330,6 +330,11 @@ const EnvironmentConfigSchema = z.object({
*/
validateNoJSXInTryStatements: z.boolean().default(false),
/**
* Validates against dynamically creating components during render.
*/
validateStaticComponents: z.boolean().default(false),
/**
* Validates that the dependencies of all effect hooks are memoized. This helps ensure
* that Forget does not introduce infinite renders caused by a dependency changing,
@@ -932,6 +937,19 @@ export class Environment {
return makeScopeId(this.#nextScope++);
}
logErrors(errors: Result<void, CompilerError>): void {
if (errors.isOk() || this.logger == null) {
return;
}
for (const error of errors.unwrapErr().details) {
this.logger.logEvent(this.filename, {
kind: 'CompileError',
detail: error,
fnLoc: null,
});
}
}
isContextIdentifier(node: t.Identifier): boolean {
return this.#contextIdentifiers.has(node);
}

View File

@@ -14,7 +14,7 @@ import {
renameVariables,
} from '.';
import {CompilerError, ErrorSeverity} from '../CompilerError';
import {Environment, EnvironmentConfig, ExternalFunction} from '../HIR';
import {Environment, ExternalFunction} from '../HIR';
import {
ArrayPattern,
BlockId,
@@ -156,7 +156,7 @@ export function codegenFunction(
const compiled = compileResult.unwrap();
const hookGuard = fn.env.config.enableEmitHookGuards;
if (hookGuard != null) {
if (hookGuard != null && fn.env.isInferredMemoEnabled) {
compiled.body = t.blockStatement([
createHookGuard(
hookGuard,
@@ -250,7 +250,11 @@ export function codegenFunction(
}
const emitInstrumentForget = fn.env.config.enableEmitInstrumentForget;
if (emitInstrumentForget != null && fn.id != null) {
if (
emitInstrumentForget != null &&
fn.id != null &&
fn.env.isInferredMemoEnabled
) {
/*
* Technically, this is a conditional hook call. However, we expect
* __DEV__ and gating identifier to be runtime constants
@@ -548,7 +552,7 @@ function codegenBlockNoReset(
}
function wrapCacheDep(cx: Context, value: t.Expression): t.Expression {
if (cx.env.config.enableEmitFreeze != null) {
if (cx.env.config.enableEmitFreeze != null && cx.env.isInferredMemoEnabled) {
// The import declaration for emitFreeze is inserted in the Babel plugin
return t.conditionalExpression(
t.identifier('__DEV__'),
@@ -1553,7 +1557,7 @@ function createHookGuard(
* ```
*/
function createCallExpression(
config: EnvironmentConfig,
env: Environment,
callee: t.Expression,
args: Array<t.Expression | t.SpreadElement>,
loc: SourceLocation | null,
@@ -1564,8 +1568,8 @@ function createCallExpression(
callExpr.loc = loc;
}
const hookGuard = config.enableEmitHookGuards;
if (hookGuard != null && isHook) {
const hookGuard = env.config.enableEmitHookGuards;
if (hookGuard != null && isHook && env.isInferredMemoEnabled) {
const iife = t.functionExpression(
null,
[],
@@ -1701,7 +1705,7 @@ function codegenInstructionValue(
const callee = codegenPlaceToExpression(cx, instrValue.callee);
const args = instrValue.args.map(arg => codegenArgument(cx, arg));
value = createCallExpression(
cx.env.config,
cx.env,
callee,
args,
instrValue.loc,
@@ -1791,7 +1795,7 @@ function codegenInstructionValue(
);
const args = instrValue.args.map(arg => codegenArgument(cx, arg));
value = createCallExpression(
cx.env.config,
cx.env,
memberExpr,
args,
instrValue.loc,

View File

@@ -26,6 +26,7 @@ import {
eachTerminalOperand,
} from '../HIR/visitors';
import {assertExhaustive} from '../Utils/utils';
import {Result} from '../Utils/Result';
/**
* Represents the possible kinds of value which may be stored at a given Place during
@@ -87,7 +88,9 @@ function joinKinds(a: Kind, b: Kind): Kind {
* may not appear as the callee of a conditional call.
* See the note for Kind.PotentialHook for sources of potential hooks
*/
export function validateHooksUsage(fn: HIRFunction): void {
export function validateHooksUsage(
fn: HIRFunction,
): Result<void, CompilerError> {
const unconditionalBlocks = computeUnconditionalBlocks(fn);
const errors = new CompilerError();
@@ -423,9 +426,7 @@ export function validateHooksUsage(fn: HIRFunction): void {
for (const [, error] of errorsByPlace) {
errors.push(error);
}
if (errors.hasErrors()) {
throw errors;
}
return errors.asResult();
}
function visitFunctionExpression(errors: CompilerError, fn: HIRFunction): void {

View File

@@ -22,6 +22,7 @@ import {
ReactiveFunctionVisitor,
visitReactiveFunction,
} from '../ReactiveScopes/visitors';
import {Result} from '../Utils/Result';
/**
* Validates that all known effect dependencies are memoized. The algorithm checks two things:
@@ -47,12 +48,12 @@ import {
* mutate(object); // ... mutable range ends here after this mutation
* ```
*/
export function validateMemoizedEffectDependencies(fn: ReactiveFunction): void {
export function validateMemoizedEffectDependencies(
fn: ReactiveFunction,
): Result<void, CompilerError> {
const errors = new CompilerError();
visitReactiveFunction(fn, new Visitor(), errors);
if (errors.hasErrors()) {
throw errors;
}
return errors.asResult();
}
class Visitor extends ReactiveFunctionVisitor<CompilerError> {

View File

@@ -4,11 +4,14 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError, EnvironmentConfig} from '..';
import {CompilerError, EnvironmentConfig, ErrorSeverity} from '..';
import {HIRFunction, IdentifierId} from '../HIR';
import {DEFAULT_GLOBALS} from '../HIR/Globals';
import {Result} from '../Utils/Result';
export function validateNoCapitalizedCalls(fn: HIRFunction): void {
export function validateNoCapitalizedCalls(
fn: HIRFunction,
): Result<void, CompilerError> {
const envConfig: EnvironmentConfig = fn.env.config;
const ALLOW_LIST = new Set([
...DEFAULT_GLOBALS.keys(),
@@ -26,6 +29,7 @@ export function validateNoCapitalizedCalls(fn: HIRFunction): void {
);
};
const errors = new CompilerError();
const capitalLoadGlobals = new Map<IdentifierId, string>();
const capitalizedProperties = new Map<IdentifierId, string>();
const reason =
@@ -73,7 +77,8 @@ export function validateNoCapitalizedCalls(fn: HIRFunction): void {
const propertyIdentifier = value.property.identifier.id;
const propertyName = capitalizedProperties.get(propertyIdentifier);
if (propertyName != null) {
CompilerError.throwInvalidReact({
errors.push({
severity: ErrorSeverity.InvalidReact,
reason,
description: `${propertyName} may be a component.`,
loc: value.loc,
@@ -85,4 +90,5 @@ export function validateNoCapitalizedCalls(fn: HIRFunction): void {
}
}
}
return errors.asResult();
}

View File

@@ -8,6 +8,7 @@
import {CompilerError, ErrorSeverity} from '..';
import {HIRFunction} from '../HIR';
import {getFunctionCallSignature} from '../Inference/InferReferenceEffects';
import {Result} from '../Utils/Result';
/**
* Checks that known-impure functions are not called during render. Examples of invalid functions to
@@ -18,7 +19,9 @@ import {getFunctionCallSignature} from '../Inference/InferReferenceEffects';
* this in several of our validation passes and should unify those analyses into a reusable helper
* and use it here.
*/
export function validateNoImpureFunctionsInRender(fn: HIRFunction): void {
export function validateNoImpureFunctionsInRender(
fn: HIRFunction,
): Result<void, CompilerError> {
const errors = new CompilerError();
for (const [, block] of fn.body.blocks) {
for (const instr of block.instructions) {
@@ -46,7 +49,5 @@ export function validateNoImpureFunctionsInRender(fn: HIRFunction): void {
}
}
}
if (errors.hasErrors()) {
throw errors;
}
return errors.asResult();
}

View File

@@ -7,6 +7,7 @@
import {CompilerError, ErrorSeverity} from '..';
import {BlockId, HIRFunction} from '../HIR';
import {Result} from '../Utils/Result';
import {retainWhere} from '../Utils/utils';
/**
@@ -19,7 +20,9 @@ import {retainWhere} from '../Utils/utils';
* created within a try block. JSX is allowed within a catch statement, unless that catch
* is itself nested inside an outer try.
*/
export function validateNoJSXInTryStatement(fn: HIRFunction): void {
export function validateNoJSXInTryStatement(
fn: HIRFunction,
): Result<void, CompilerError> {
const activeTryBlocks: Array<BlockId> = [];
const errors = new CompilerError();
for (const [, block] of fn.body.blocks) {
@@ -46,7 +49,5 @@ export function validateNoJSXInTryStatement(fn: HIRFunction): void {
activeTryBlocks.push(block.terminal.handler);
}
}
if (errors.hasErrors()) {
throw errors;
}
return errors.asResult();
}

View File

@@ -99,9 +99,11 @@ class Env extends Map<IdentifierId, RefAccessType> {
}
}
export function validateNoRefAccessInRender(fn: HIRFunction): void {
export function validateNoRefAccessInRender(
fn: HIRFunction,
): Result<void, CompilerError> {
const env = new Env();
validateNoRefAccessInRenderImpl(fn, env).unwrap();
return validateNoRefAccessInRenderImpl(fn, env).map(_ => undefined);
}
function refTypeOfType(place: Place): RefAccessType {

View File

@@ -14,6 +14,7 @@ import {
Place,
} from '../HIR';
import {eachInstructionValueOperand} from '../HIR/visitors';
import {Result} from '../Utils/Result';
/**
* Validates against calling setState in the body of a *passive* effect (useEffect),
@@ -23,7 +24,9 @@ import {eachInstructionValueOperand} from '../HIR/visitors';
* often bad for performance and frequently has more efficient and straightforward
* alternatives. See https://react.dev/learn/you-might-not-need-an-effect for examples.
*/
export function validateNoSetStateInPassiveEffects(fn: HIRFunction): void {
export function validateNoSetStateInPassiveEffects(
fn: HIRFunction,
): Result<void, CompilerError> {
const setStateFunctions: Map<IdentifierId, Place> = new Map();
const errors = new CompilerError();
for (const [, block] of fn.body.blocks) {
@@ -98,9 +101,7 @@ export function validateNoSetStateInPassiveEffects(fn: HIRFunction): void {
}
}
if (errors.hasErrors()) {
throw errors;
}
return errors.asResult();
}
function getSetStateCall(

View File

@@ -9,7 +9,7 @@ import {CompilerError, ErrorSeverity} from '../CompilerError';
import {HIRFunction, IdentifierId, isSetStateType} from '../HIR';
import {computeUnconditionalBlocks} from '../HIR/ComputeUnconditionalBlocks';
import {eachInstructionValueOperand} from '../HIR/visitors';
import {Err, Ok, Result} from '../Utils/Result';
import {Result} from '../Utils/Result';
/**
* Validates that the given function does not have an infinite update loop
@@ -39,9 +39,11 @@ import {Err, Ok, Result} from '../Utils/Result';
* y();
* ```
*/
export function validateNoSetStateInRender(fn: HIRFunction): void {
export function validateNoSetStateInRender(
fn: HIRFunction,
): Result<void, CompilerError> {
const unconditionalSetStateFunctions: Set<IdentifierId> = new Set();
validateNoSetStateInRenderImpl(fn, unconditionalSetStateFunctions).unwrap();
return validateNoSetStateInRenderImpl(fn, unconditionalSetStateFunctions);
}
function validateNoSetStateInRenderImpl(
@@ -145,9 +147,5 @@ function validateNoSetStateInRenderImpl(
}
}
if (errors.hasErrors()) {
return Err(errors);
} else {
return Ok(undefined);
}
return errors.asResult();
}

View File

@@ -5,9 +5,10 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError, Effect, ErrorSeverity} from '..';
import {CompilerError, ErrorSeverity} from '../CompilerError';
import {
DeclarationId,
Effect,
GeneratedSource,
Identifier,
IdentifierId,
@@ -30,6 +31,7 @@ import {
ReactiveFunctionVisitor,
visitReactiveFunction,
} from '../ReactiveScopes/visitors';
import {Result} from '../Utils/Result';
import {getOrInsertDefault} from '../Utils/utils';
/**
@@ -39,15 +41,15 @@ import {getOrInsertDefault} from '../Utils/utils';
* This can occur if a value's mutable range somehow extended to include a hook and
* was pruned.
*/
export function validatePreservedManualMemoization(fn: ReactiveFunction): void {
export function validatePreservedManualMemoization(
fn: ReactiveFunction,
): Result<void, CompilerError> {
const state = {
errors: new CompilerError(),
manualMemoState: null,
};
visitReactiveFunction(fn, new Visitor(), state);
if (state.errors.hasErrors()) {
throw state.errors;
}
return state.errors.asResult();
}
const DEBUG = false;

View File

@@ -0,0 +1,83 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError, ErrorSeverity} from '../CompilerError';
import {HIRFunction, IdentifierId, SourceLocation} from '../HIR';
import {Result} from '../Utils/Result';
/**
* Validates against components that are created dynamically and whose identity is not guaranteed
* to be stable (which would cause the component to reset on each re-render).
*/
export function validateStaticComponents(
fn: HIRFunction,
): Result<void, CompilerError> {
const error = new CompilerError();
const knownDynamicComponents = new Map<IdentifierId, SourceLocation>();
for (const block of fn.body.blocks.values()) {
phis: for (const phi of block.phis) {
for (const operand of phi.operands.values()) {
const loc = knownDynamicComponents.get(operand.identifier.id);
if (loc != null) {
knownDynamicComponents.set(phi.place.identifier.id, loc);
continue phis;
}
}
}
for (const instr of block.instructions) {
const {lvalue, value} = instr;
switch (value.kind) {
case 'FunctionExpression':
case 'NewExpression':
case 'MethodCall':
case 'CallExpression': {
knownDynamicComponents.set(lvalue.identifier.id, value.loc);
break;
}
case 'LoadLocal': {
const loc = knownDynamicComponents.get(value.place.identifier.id);
if (loc != null) {
knownDynamicComponents.set(lvalue.identifier.id, loc);
}
break;
}
case 'StoreLocal': {
const loc = knownDynamicComponents.get(value.value.identifier.id);
if (loc != null) {
knownDynamicComponents.set(lvalue.identifier.id, loc);
knownDynamicComponents.set(value.lvalue.place.identifier.id, loc);
}
break;
}
case 'JsxExpression': {
if (value.tag.kind === 'Identifier') {
const location = knownDynamicComponents.get(
value.tag.identifier.id,
);
if (location != null) {
error.push({
reason: `Components created during render will reset their state each time they are created. Declare components outside of render. `,
severity: ErrorSeverity.InvalidReact,
loc: value.tag.loc,
description: null,
suggestions: null,
});
error.push({
reason: `The component may be created during render`,
severity: ErrorSeverity.InvalidReact,
loc: location,
description: null,
suggestions: null,
});
}
}
}
}
}
}
return error.asResult();
}

View File

@@ -5,10 +5,12 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError} from '..';
import {CompilerError, ErrorSeverity} from '..';
import {FunctionExpression, HIRFunction, IdentifierId} from '../HIR';
import {Result} from '../Utils/Result';
export function validateUseMemo(fn: HIRFunction): void {
export function validateUseMemo(fn: HIRFunction): Result<void, CompilerError> {
const errors = new CompilerError();
const useMemos = new Set<IdentifierId>();
const react = new Set<IdentifierId>();
const functions = new Map<IdentifierId, FunctionExpression>();
@@ -61,7 +63,8 @@ export function validateUseMemo(fn: HIRFunction): void {
}
if (body.loweredFunc.func.params.length > 0) {
CompilerError.throwInvalidReact({
errors.push({
severity: ErrorSeverity.InvalidReact,
reason: 'useMemo callbacks may not accept any arguments',
description: null,
loc: body.loc,
@@ -70,7 +73,8 @@ export function validateUseMemo(fn: HIRFunction): void {
}
if (body.loweredFunc.func.async || body.loweredFunc.func.generator) {
CompilerError.throwInvalidReact({
errors.push({
severity: ErrorSeverity.InvalidReact,
reason:
'useMemo callbacks may not be async or generator functions',
description: null,
@@ -84,4 +88,5 @@ export function validateUseMemo(fn: HIRFunction): void {
}
}
}
return errors.asResult();
}

View File

@@ -12,6 +12,7 @@ globalThis.constantValue = 'global test value';
test('literal-constant-propagation', () => {
function Component() {
'use memo';
const x = 'test value 1';
return <div>{x}</div>;
}
@@ -38,6 +39,7 @@ test('literal-constant-propagation', () => {
test('global-constant-propagation', () => {
function Component() {
'use memo';
const x = constantValue;
return <div>{x}</div>;
@@ -65,6 +67,7 @@ test('global-constant-propagation', () => {
test('lambda-constant-propagation', () => {
function Component() {
'use memo';
const x = 'test value 1';
const getDiv = () => <div>{x}</div>;
return getDiv();
@@ -92,6 +95,7 @@ test('lambda-constant-propagation', () => {
test('lambda-constant-propagation-of-phi-node', () => {
function Component({noopCallback}) {
'use memo';
const x = 'test value 1';
if (constantValue) {
noopCallback();

View File

@@ -16,6 +16,7 @@ function Button({label}) {
let currentTheme = 'light';
function useTheme() {
'use memo';
return currentTheme;
}

View File

@@ -10,6 +10,7 @@ import * as React from 'react';
import {expectLogsAndClear, log} from './expectLogs';
function Counter(props) {
'use memo';
let value = props.value;
let a = value++;
expect(a).toBe(props.value); // postfix

View File

@@ -0,0 +1,39 @@
## Input
```javascript
// @compilationMode(infer)
const Test = () => <div />;
export const FIXTURE_ENTRYPOINT = {
fn: Test,
params: [{}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer)
const Test = () => {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = <div />;
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
};
export const FIXTURE_ENTRYPOINT = {
fn: Test,
params: [{}],
};
```
### Eval output
(kind: ok) <div></div>

View File

@@ -0,0 +1,7 @@
// @compilationMode(infer)
const Test = () => <div />;
export const FIXTURE_ENTRYPOINT = {
fn: Test,
params: [{}],
};

View File

@@ -1,38 +0,0 @@
## Input
```javascript
// @validateNoJSXInTryStatements
import {identity} from 'shared-runtime';
function Component(props) {
let el;
try {
let value;
try {
value = identity(props.foo);
} catch {
el = <div value={value} />;
}
} catch {
return null;
}
return el;
}
```
## Error
```
9 | value = identity(props.foo);
10 | } catch {
> 11 | el = <div value={value} />;
| ^^^^^^^^^^^^^^^^^^^^^ InvalidReact: Unexpected JSX element within a try statement. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary) (11:11)
12 | }
13 | } catch {
14 | return null;
```

View File

@@ -1,31 +0,0 @@
## Input
```javascript
// @validateNoJSXInTryStatements
function Component(props) {
let el;
try {
el = <div />;
} catch {
return null;
}
return el;
}
```
## Error
```
3 | let el;
4 | try {
> 5 | el = <div />;
| ^^^^^^^ InvalidReact: Unexpected JSX element within a try statement. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary) (5:5)
6 | } catch {
7 | return null;
8 | }
```

View File

@@ -1,37 +0,0 @@
## Input
```javascript
// @validateNoSetStateInPassiveEffects
import {useEffect, useState} from 'react';
function Component() {
const [state, setState] = useState(0);
const f = () => {
setState(s => s + 1);
};
const g = () => {
f();
};
useEffect(() => {
g();
});
return state;
}
```
## Error
```
11 | };
12 | useEffect(() => {
> 13 | g();
| ^ InvalidReact: Calling setState directly within a useEffect causes cascading renders and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect) (13:13)
14 | });
15 | return state;
16 | }
```

View File

@@ -1,31 +0,0 @@
## Input
```javascript
// @validateNoSetStateInPassiveEffects
import {useEffect, useState} from 'react';
function Component() {
const [state, setState] = useState(0);
useEffect(() => {
setState(s => s + 1);
});
return state;
}
```
## Error
```
5 | const [state, setState] = useState(0);
6 | useEffect(() => {
> 7 | setState(s => s + 1);
| ^^^^^^^^ InvalidReact: Calling setState directly within a useEffect causes cascading renders and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect) (7:7)
8 | });
9 | return state;
10 | }
```

View File

@@ -0,0 +1,73 @@
## Input
```javascript
// @logger @validateNoJSXInTryStatements
import {identity} from 'shared-runtime';
function Component(props) {
let el;
try {
let value;
try {
value = identity(props.foo);
} catch {
el = <div value={value} />;
}
} catch {
return null;
}
return el;
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @logger @validateNoJSXInTryStatements
import { identity } from "shared-runtime";
function Component(props) {
const $ = _c(4);
let el;
try {
let value;
try {
let t0;
if ($[0] !== props.foo) {
t0 = identity(props.foo);
$[0] = props.foo;
$[1] = t0;
} else {
t0 = $[1];
}
value = t0;
} catch {
let t0;
if ($[2] !== value) {
t0 = <div value={value} />;
$[2] = value;
$[3] = t0;
} else {
t0 = $[3];
}
el = t0;
}
} catch {
return null;
}
return el;
}
```
## Logs
```
{"kind":"CompileError","detail":{"options":{"reason":"Unexpected JSX element within a try statement. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)","description":null,"severity":"InvalidReact","loc":{"start":{"line":11,"column":11,"index":214},"end":{"line":11,"column":32,"index":235},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"}}},"fnLoc":null}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":83},"end":{"line":17,"column":1,"index":290},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"},"fnName":"Component","memoSlots":4,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -1,4 +1,4 @@
// @validateNoJSXInTryStatements
// @logger @validateNoJSXInTryStatements
import {identity} from 'shared-runtime';
function Component(props) {

View File

@@ -0,0 +1,50 @@
## Input
```javascript
// @logger @validateNoJSXInTryStatements
function Component(props) {
let el;
try {
el = <div />;
} catch {
return null;
}
return el;
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @logger @validateNoJSXInTryStatements
function Component(props) {
const $ = _c(1);
let el;
try {
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = <div />;
$[0] = t0;
} else {
t0 = $[0];
}
el = t0;
} catch {
return null;
}
return el;
}
```
## Logs
```
{"kind":"CompileError","detail":{"options":{"reason":"Unexpected JSX element within a try statement. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)","description":null,"severity":"InvalidReact","loc":{"start":{"line":5,"column":9,"index":96},"end":{"line":5,"column":16,"index":103},"filename":"invalid-jsx-in-try-with-catch.ts"}}},"fnLoc":null}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":41},"end":{"line":10,"column":1,"index":152},"filename":"invalid-jsx-in-try-with-catch.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -0,0 +1,73 @@
## Input
```javascript
// @logger @validateNoSetStateInPassiveEffects
import {useEffect, useState} from 'react';
function Component() {
const [state, setState] = useState(0);
const f = () => {
setState(s => s + 1);
};
const g = () => {
f();
};
useEffect(() => {
g();
});
return state;
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @logger @validateNoSetStateInPassiveEffects
import { useEffect, useState } from "react";
function Component() {
const $ = _c(2);
const [state, setState] = useState(0);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const f = () => {
setState(_temp);
};
t0 = () => {
f();
};
$[0] = t0;
} else {
t0 = $[0];
}
const g = t0;
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = () => {
g();
};
$[1] = t1;
} else {
t1 = $[1];
}
useEffect(t1);
return state;
}
function _temp(s) {
return s + 1;
}
```
## Logs
```
{"kind":"CompileError","detail":{"options":{"reason":"Calling setState directly within a useEffect causes cascading renders and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect)","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":13,"column":4,"index":264},"end":{"line":13,"column":5,"index":265},"filename":"invalid-setState-in-useEffect-transitive.ts","identifierName":"g"}}},"fnLoc":null}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":91},"end":{"line":16,"column":1,"index":292},"filename":"invalid-setState-in-useEffect-transitive.ts"},"fnName":"Component","memoSlots":2,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -1,4 +1,4 @@
// @validateNoSetStateInPassiveEffects
// @logger @validateNoSetStateInPassiveEffects
import {useEffect, useState} from 'react';
function Component() {

View File

@@ -0,0 +1,53 @@
## Input
```javascript
// @logger @validateNoSetStateInPassiveEffects
import {useEffect, useState} from 'react';
function Component() {
const [state, setState] = useState(0);
useEffect(() => {
setState(s => s + 1);
});
return state;
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @logger @validateNoSetStateInPassiveEffects
import { useEffect, useState } from "react";
function Component() {
const $ = _c(1);
const [state, setState] = useState(0);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
setState(_temp);
};
$[0] = t0;
} else {
t0 = $[0];
}
useEffect(t0);
return state;
}
function _temp(s) {
return s + 1;
}
```
## Logs
```
{"kind":"CompileError","detail":{"options":{"reason":"Calling setState directly within a useEffect causes cascading renders and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect)","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":7,"column":4,"index":179},"end":{"line":7,"column":12,"index":187},"filename":"invalid-setState-in-useEffect.ts","identifierName":"setState"}}},"fnLoc":null}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":91},"end":{"line":10,"column":1,"index":224},"filename":"invalid-setState-in-useEffect.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -1,4 +1,4 @@
// @validateNoSetStateInPassiveEffects
// @logger @validateNoSetStateInPassiveEffects
import {useEffect, useState} from 'react';
function Component() {

View File

@@ -0,0 +1,27 @@
## Input
```javascript
// @flow @enableEmitHookGuards @panicThreshold(none) @enableFire
component Foo(useDynamicHook) {
useDynamicHook();
return <div>hello world</div>;
}
```
## Code
```javascript
function Foo({
useDynamicHook,
}: $ReadOnly<{ useDynamicHook: any }>): React.Node {
useDynamicHook();
return <div>hello world</div>;
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -0,0 +1,6 @@
// @flow @enableEmitHookGuards @panicThreshold(none) @enableFire
component Foo(useDynamicHook) {
useDynamicHook();
return <div>hello world</div>;
}

View File

@@ -0,0 +1,59 @@
## Input
```javascript
// @logger @validateStaticComponents
function Example(props) {
let Component;
if (props.cond) {
Component = createComponent();
} else {
Component = DefaultComponent;
}
return <Component />;
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @logger @validateStaticComponents
function Example(props) {
const $ = _c(3);
let Component;
if (props.cond) {
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = createComponent();
$[0] = t0;
} else {
t0 = $[0];
}
Component = t0;
} else {
Component = DefaultComponent;
}
let t0;
if ($[1] !== Component) {
t0 = <Component />;
$[1] = Component;
$[2] = t0;
} else {
t0 = $[2];
}
return t0;
}
```
## Logs
```
{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":9,"column":10,"index":194},"end":{"line":9,"column":19,"index":203},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"}}},"fnLoc":null}
{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":5,"column":16,"index":116},"end":{"line":5,"column":33,"index":133},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"}}},"fnLoc":null}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":37},"end":{"line":10,"column":1,"index":209},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"},"fnName":"Example","memoSlots":3,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -0,0 +1,10 @@
// @logger @validateStaticComponents
function Example(props) {
let Component;
if (props.cond) {
Component = createComponent();
} else {
Component = DefaultComponent;
}
return <Component />;
}

View File

@@ -0,0 +1,41 @@
## Input
```javascript
// @logger @validateStaticComponents
function Example(props) {
const Component = createComponent();
return <Component />;
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @logger @validateStaticComponents
function Example(props) {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const Component = createComponent();
t0 = <Component />;
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
```
## Logs
```
{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":10,"index":112},"end":{"line":4,"column":19,"index":121},"filename":"invalid-dynamically-construct-component-in-render.ts"}}},"fnLoc":null}
{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":3,"column":20,"index":83},"end":{"line":3,"column":37,"index":100},"filename":"invalid-dynamically-construct-component-in-render.ts"}}},"fnLoc":null}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":37},"end":{"line":5,"column":1,"index":127},"filename":"invalid-dynamically-construct-component-in-render.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -0,0 +1,5 @@
// @logger @validateStaticComponents
function Example(props) {
const Component = createComponent();
return <Component />;
}

View File

@@ -0,0 +1,46 @@
## Input
```javascript
// @logger @validateStaticComponents
function Example(props) {
function Component() {
return <div />;
}
return <Component />;
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @logger @validateStaticComponents
function Example(props) {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const Component = function Component() {
return <div />;
};
t0 = <Component />;
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
```
## Logs
```
{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":6,"column":10,"index":122},"end":{"line":6,"column":19,"index":131},"filename":"invalid-dynamically-constructed-component-function.ts"}}},"fnLoc":null}
{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":3,"column":2,"index":65},"end":{"line":5,"column":3,"index":111},"filename":"invalid-dynamically-constructed-component-function.ts"}}},"fnLoc":null}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":37},"end":{"line":7,"column":1,"index":137},"filename":"invalid-dynamically-constructed-component-function.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -0,0 +1,7 @@
// @logger @validateStaticComponents
function Example(props) {
function Component() {
return <div />;
}
return <Component />;
}

View File

@@ -0,0 +1,50 @@
## Input
```javascript
// @logger @validateStaticComponents
function Example(props) {
const Component = props.foo.bar();
return <Component />;
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @logger @validateStaticComponents
function Example(props) {
const $ = _c(4);
let t0;
if ($[0] !== props.foo) {
t0 = props.foo.bar();
$[0] = props.foo;
$[1] = t0;
} else {
t0 = $[1];
}
const Component = t0;
let t1;
if ($[2] !== Component) {
t1 = <Component />;
$[2] = Component;
$[3] = t1;
} else {
t1 = $[3];
}
return t1;
}
```
## Logs
```
{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":10,"index":110},"end":{"line":4,"column":19,"index":119},"filename":"invalid-dynamically-constructed-component-method-call.ts"}}},"fnLoc":null}
{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":3,"column":20,"index":83},"end":{"line":3,"column":35,"index":98},"filename":"invalid-dynamically-constructed-component-method-call.ts"}}},"fnLoc":null}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":37},"end":{"line":5,"column":1,"index":125},"filename":"invalid-dynamically-constructed-component-method-call.ts"},"fnName":"Example","memoSlots":4,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -0,0 +1,5 @@
// @logger @validateStaticComponents
function Example(props) {
const Component = props.foo.bar();
return <Component />;
}

View File

@@ -0,0 +1,41 @@
## Input
```javascript
// @logger @validateStaticComponents
function Example(props) {
const Component = new ComponentFactory();
return <Component />;
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @logger @validateStaticComponents
function Example(props) {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const Component = new ComponentFactory();
t0 = <Component />;
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
```
## Logs
```
{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":10,"index":117},"end":{"line":4,"column":19,"index":126},"filename":"invalid-dynamically-constructed-component-new.ts"}}},"fnLoc":null}
{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":3,"column":20,"index":83},"end":{"line":3,"column":42,"index":105},"filename":"invalid-dynamically-constructed-component-new.ts"}}},"fnLoc":null}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":37},"end":{"line":5,"column":1,"index":132},"filename":"invalid-dynamically-constructed-component-new.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -0,0 +1,5 @@
// @logger @validateStaticComponents
function Example(props) {
const Component = new ComponentFactory();
return <Component />;
}

View File

@@ -0,0 +1,49 @@
## Input
```javascript
// @flow @enableEmitHookGuards @panicThreshold(none) @enableFire
import {useEffect, fire} from 'react';
function Component(props, useDynamicHook) {
'use memo';
useDynamicHook();
const foo = props => {
console.log(props);
};
useEffect(() => {
fire(foo(props));
});
return <div>hello world</div>;
}
```
## Code
```javascript
import { $dispatcherGuard } from "react-compiler-runtime";
import { useFire } from "react/compiler-runtime";
import { useEffect, fire } from "react";
function Component(props, useDynamicHook) {
"use memo";
useDynamicHook();
const foo = _temp;
const t0 = useFire(foo);
useEffect(() => {
t0(props);
});
return <div>hello world</div>;
}
function _temp(props_0) {
console.log(props_0);
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -0,0 +1,15 @@
// @flow @enableEmitHookGuards @panicThreshold(none) @enableFire
import {useEffect, fire} from 'react';
function Component(props, useDynamicHook) {
'use memo';
useDynamicHook();
const foo = props => {
console.log(props);
};
useEffect(() => {
fire(foo(props));
});
return <div>hello world</div>;
}

View File

@@ -26,7 +26,7 @@
"@types/eslint": "^8.56.12",
"@types/node": "^20.2.5",
"babel-jest": "^29.0.3",
"eslint-v8": "npm:eslint@^8.57.1",
"eslint": "8.57.0",
"hermes-eslint": "^0.25.1",
"jest": "^29.5.0"
},

View File

@@ -1,15 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
module.exports = {
presets: ['@babel/preset-env', '@babel/preset-typescript'],
plugins: [
['@babel/plugin-transform-private-property-in-object', {loose: true}],
['@babel/plugin-transform-class-properties', {loose: true}],
['@babel/plugin-transform-private-methods', {loose: true}],
],
};

View File

@@ -1,36 +0,0 @@
import {defineConfig} from 'tsup';
export default defineConfig({
entry: ['./src/index.ts'],
outDir: './dist',
external: [
'@babel/core',
'@babel/parser',
'@babel/plugin-proposal-private-methods',
'hermes-parser',
'zod',
'zod-validation-error',
],
splitting: false,
sourcemap: false,
dts: false,
bundle: true,
format: 'cjs',
platform: 'node',
banner: {
js: `/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @lightSyntaxTransform
* @noflow
* @nolint
* @preventMunge
* @preserve-invariant-messages
*/
"use no memo";`,
},
});

View File

@@ -1,7 +1,6 @@
const PUBLISHABLE_PACKAGES = [
'babel-plugin-react-compiler',
'eslint-plugin-react-compiler',
'eslint-plugin-react-hooks',
'react-compiler-healthcheck',
'react-compiler-runtime',
];

File diff suppressed because it is too large Load Diff

23
fixtures/owner-stacks/.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View File

@@ -0,0 +1,70 @@
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `yarn start`
Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
The page will reload when you make changes.\
You may also see any lint errors in the console.
### `yarn test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `yarn build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `yarn eject`
**Note: this is a one-way operation. Once you `eject`, you can't go back!**
If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
### Analyzing the Bundle Size
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
### Making a Progressive Web App
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
### Advanced Configuration
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
### Deployment
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
### `yarn build` fails to minify
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)

View File

@@ -0,0 +1,36 @@
{
"name": "owner-stacks",
"version": "0.1.0",
"private": true,
"dependencies": {
"react": "experimental",
"react-dom": "experimental",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.0"
},
"scripts": {
"prestart": "cp -a ../../build/oss-experimental/. node_modules",
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@@ -0,0 +1,62 @@
.App-header {
background-color: #282c34;
min-height: 10vh;
display: flex;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.page {
display: flex;
height: 90vh;
}
.content {
flex: 4;
background-color: #f0f0f0;
padding: 20px;
display: flex;
flex-direction: column;
text-align: left;
}
.content.highlight {
background-color: yellow;
}
.sidebar {
flex: 1;
background-color: #d0d0d0;
padding: 20px;
}
.line-number {
display: inline-block;
width: 30px;
text-align: right;
margin-right: 10px;
color: #888;
}
.text-symbol {
position: relative;
display: inline-block;
}
.hovercard {
display: none;
position: absolute;
top: -40px;
left: 0;
background-color: white;
border: 1px solid #ccc;
padding: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
z-index: 10;
}
.text-symbol:hover .hovercard {
display: block;
}

View File

@@ -0,0 +1,143 @@
import {useState} from 'react';
import {flushSync} from 'react-dom';
import './App.css';
const text = `
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
`;
const loremIpsum = text + text;
function TextSymbol({char, highlight}) {
const randomColor = highlight
? `#${Math.floor(Math.random() * 16777215).toString(16)}`
: 'transparent';
return (
<span className="text-symbol" style={{backgroundColor: randomColor}}>
{char}
<div className="hovercard">
<p>Character: {char}</p>
<p>Color: {randomColor}</p>
</div>
</span>
);
}
function TextLine({sentence, highlight, lineNumber}) {
const randomColor = highlight
? `#${Math.floor(Math.random() * 16777215).toString(16)}`
: 'transparent';
return (
<div style={{backgroundColor: randomColor}}>
<span className="line-number">{lineNumber}</span>
{sentence.split('').map((char, index) => (
<TextSymbol key={index} char={char} highlight={highlight} />
))}
</div>
);
}
function App() {
const [highlight, setHighlight] = useState(false);
const toggleHighlight = () => {
console.time('toggleHighlight');
flushSync(() => {
setHighlight(!highlight);
});
console.timeEnd('toggleHighlight');
};
return (
<div className="App">
<header className="App-header">Owner Stacks Stress Test</header>
<div className="page">
<div className={`content ${highlight ? 'highlight' : ''}`}>
{loremIpsum
.trim()
.split('\n')
.map((sentence, index) => (
<TextLine
key={index}
sentence={sentence.trim()}
highlight={highlight}
lineNumber={index + 1}
/>
))}
</div>
<div className="sidebar">
<button onClick={toggleHighlight}>
{highlight ? 'Remove Highlight' : 'Highlight Content'}
</button>
</div>
</div>
</div>
);
}
export default App;

View File

@@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

View File

@@ -0,0 +1,17 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,13 @@
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({getCLS, getFID, getFCP, getLCP, getTTFB}) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

File diff suppressed because it is too large Load Diff

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