Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90cf7bba08 | ||
|
|
c19f052b2d | ||
|
|
d2d5350e65 | ||
|
|
8ba22587b5 | ||
|
|
7606b29048 | ||
|
|
54e602d891 |
@@ -331,7 +331,6 @@ module.exports = {
|
||||
'packages/react-server-dom-turbopack/**/*.js',
|
||||
'packages/react-server-dom-parcel/**/*.js',
|
||||
'packages/react-server-dom-fb/**/*.js',
|
||||
'packages/react-server-dom-unbundled/**/*.js',
|
||||
'packages/react-test-renderer/**/*.js',
|
||||
'packages/react-debug-tools/**/*.js',
|
||||
'packages/react-devtools-extensions/**/*.js',
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
name: (Compiler) Discord Notify
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
pull_request_target:
|
||||
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 }}
|
||||
|
||||
@@ -24,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Discord Webhook Action
|
||||
uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4
|
||||
uses: tsickert/discord-webhook@v6.0.0
|
||||
with:
|
||||
webhook-url: ${{ secrets.COMPILER_DISCORD_WEBHOOK_URL }}
|
||||
embed-author-name: ${{ github.event.pull_request.user.login }}
|
||||
|
||||
20
.github/workflows/compiler_playground.yml
vendored
20
.github/workflows/compiler_playground.yml
vendored
@@ -8,8 +8,6 @@ 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
|
||||
@@ -38,23 +36,10 @@ jobs:
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
|
||||
path: "**/node_modules"
|
||||
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- name: 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()'
|
||||
@@ -64,4 +49,3 @@ jobs:
|
||||
with:
|
||||
name: test-results
|
||||
path: compiler/apps/playground/test-results
|
||||
if-no-files-found: ignore
|
||||
|
||||
8
.github/workflows/compiler_prereleases.yml
vendored
8
.github/workflows/compiler_prereleases.yml
vendored
@@ -20,12 +20,11 @@ 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:
|
||||
@@ -47,9 +46,8 @@ jobs:
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
|
||||
path: "**/node_modules"
|
||||
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- name: Publish packages to npm
|
||||
run: |
|
||||
|
||||
@@ -15,8 +15,6 @@ on:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions: {}
|
||||
|
||||
env:
|
||||
TZ: /usr/share/zoneinfo/America/Los_Angeles
|
||||
|
||||
|
||||
@@ -5,8 +5,6 @@ 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
|
||||
|
||||
|
||||
@@ -5,8 +5,6 @@ on:
|
||||
# At 10 minutes past 9:00 on Mon
|
||||
- cron: 10 9 * * 1
|
||||
|
||||
permissions: {}
|
||||
|
||||
env:
|
||||
TZ: /usr/share/zoneinfo/America/Los_Angeles
|
||||
|
||||
|
||||
17
.github/workflows/compiler_typescript.yml
vendored
17
.github/workflows/compiler_typescript.yml
vendored
@@ -8,8 +8,6 @@ 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
|
||||
@@ -48,9 +46,8 @@ jobs:
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
|
||||
path: "**/node_modules"
|
||||
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn workspace babel-plugin-react-compiler lint
|
||||
|
||||
@@ -69,9 +66,8 @@ jobs:
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
|
||||
path: "**/node_modules"
|
||||
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn workspace babel-plugin-react-compiler jest
|
||||
|
||||
@@ -94,9 +90,8 @@ jobs:
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
|
||||
path: "**/node_modules"
|
||||
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: xvfb-run -a yarn workspace ${{ matrix.workspace_name }} test
|
||||
if: runner.os == 'Linux' && matrix.workspace_name == 'react-forgive'
|
||||
|
||||
33
.github/workflows/devtools_regression_tests.yml
vendored
33
.github/workflows/devtools_regression_tests.yml
vendored
@@ -9,8 +9,6 @@ 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
|
||||
@@ -20,9 +18,6 @@ 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
|
||||
@@ -34,13 +29,13 @@ jobs:
|
||||
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') }}
|
||||
path: "**/node_modules"
|
||||
key: runtime-release-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn --cwd scripts/release install --frozen-lockfile
|
||||
- run: yarn install --frozen-lockfile
|
||||
working-directory: scripts/release
|
||||
- name: Download react-devtools artifacts for base revision
|
||||
run: |
|
||||
git fetch origin main
|
||||
@@ -52,7 +47,6 @@ jobs:
|
||||
with:
|
||||
name: build
|
||||
path: build
|
||||
if-no-files-found: error
|
||||
|
||||
build_devtools_and_process_artifacts:
|
||||
name: Build DevTools and process artifacts
|
||||
@@ -69,9 +63,8 @@ jobs:
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
path: "**/node_modules"
|
||||
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
@@ -90,20 +83,17 @@ 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
|
||||
@@ -130,9 +120,8 @@ jobs:
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
path: "**/node_modules"
|
||||
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- name: Restore all archived build artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
@@ -165,9 +154,8 @@ jobs:
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
path: "**/node_modules"
|
||||
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- name: Restore all archived build artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
@@ -188,4 +176,3 @@ jobs:
|
||||
with:
|
||||
name: screenshots
|
||||
path: ./tmp/screenshots
|
||||
if-no-files-found: warn
|
||||
|
||||
386
.github/workflows/runtime_build_and_test.yml
vendored
386
.github/workflows/runtime_build_and_test.yml
vendored
@@ -3,21 +3,9 @@ name: (Runtime) Build and Test
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
tags:
|
||||
# To get CI for backport releases.
|
||||
# This will duplicate CI for releases from main which is acceptable
|
||||
- "v*"
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- compiler/**
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
commit_sha:
|
||||
required: false
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
@@ -29,91 +17,6 @@ env:
|
||||
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
|
||||
|
||||
jobs:
|
||||
# ----- NODE_MODULES CACHE -----
|
||||
# Centralize the node_modules cache so it is saved once and each subsequent job only needs to
|
||||
# restore the cache. Prevents race conditions where multiple workflows try to write to the cache.
|
||||
runtime_node_modules_cache:
|
||||
name: Cache Runtime node_modules
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
- name: Check cache hit
|
||||
uses: actions/cache/restore@v4
|
||||
id: node_modules
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
lookup-only: true
|
||||
- uses: actions/setup-node@v4
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
with:
|
||||
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-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
- run: yarn install --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- name: Save cache
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
|
||||
runtime_compiler_node_modules_cache:
|
||||
name: Cache Runtime, Compiler node_modules
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
- name: Check cache hit
|
||||
uses: actions/cache/restore@v4
|
||||
id: node_modules
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
lookup-only: true
|
||||
- uses: actions/setup-node@v4
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
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-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
- run: yarn install --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- run: yarn --cwd compiler install --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- name: Save cache
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
|
||||
# ----- FLOW -----
|
||||
discover_flow_inline_configs:
|
||||
name: Discover flow inline configs
|
||||
@@ -123,7 +26,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/github-script@v7
|
||||
id: set-matrix
|
||||
with:
|
||||
@@ -133,7 +36,7 @@ jobs:
|
||||
|
||||
flow:
|
||||
name: Flow check ${{ matrix.flow_inline_config_shortname }}
|
||||
needs: [discover_flow_inline_configs, runtime_node_modules_cache]
|
||||
needs: discover_flow_inline_configs
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -142,52 +45,45 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
cache-dependency-path: yarn.lock
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache/restore@v4
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
path: "**/node_modules"
|
||||
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- run: node ./scripts/tasks/flow-ci ${{ matrix.flow_inline_config_shortname }}
|
||||
|
||||
# ----- FIZZ -----
|
||||
check_generated_fizz_runtime:
|
||||
name: Confirm generated inline Fizz runtime is up to date
|
||||
needs: [runtime_node_modules_cache]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
cache-dependency-path: yarn.lock
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache/restore@v4
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
path: "**/node_modules"
|
||||
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- run: |
|
||||
yarn generate-inline-fizz-runtime
|
||||
git diff --quiet || (echo "There was a change to the Fizz runtime. Run `yarn generate-inline-fizz-runtime` and check in the result." && false)
|
||||
@@ -195,34 +91,30 @@ jobs:
|
||||
# ----- FEATURE FLAGS -----
|
||||
flags:
|
||||
name: Check flags
|
||||
needs: [runtime_node_modules_cache]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
cache-dependency-path: yarn.lock
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache/restore@v4
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
path: "**/node_modules"
|
||||
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- run: yarn flags
|
||||
|
||||
# ----- TESTS -----
|
||||
test:
|
||||
name: yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }})
|
||||
needs: [runtime_compiler_node_modules_cache]
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -256,102 +148,56 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
cache-dependency-path: |
|
||||
yarn.lock
|
||||
compiler/yarn.lock
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache/restore@v4
|
||||
id: node_modules
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
- 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 compiler install --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- run: yarn test ${{ matrix.params }} --ci --shard=${{ matrix.shard }}
|
||||
|
||||
# Hardcoded to improve parallelism
|
||||
test-linter:
|
||||
name: Test eslint-plugin-react-hooks
|
||||
needs: [runtime_compiler_node_modules_cache]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
cache-dependency-path: |
|
||||
yarn.lock
|
||||
compiler/yarn.lock
|
||||
cache-dependency-path: yarn.lock
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
- name: Install runtime dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- name: Install compiler dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
working-directory: compiler
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- run: ./scripts/react-compiler/build-compiler.sh && ./scripts/react-compiler/link-compiler.sh
|
||||
- run: yarn workspace eslint-plugin-react-hooks test
|
||||
|
||||
path: "**/node_modules"
|
||||
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn test ${{ matrix.params }} --ci --shard=${{ matrix.shard }}
|
||||
|
||||
# ----- BUILD -----
|
||||
build_and_lint:
|
||||
name: yarn build and lint
|
||||
needs: [runtime_compiler_node_modules_cache]
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# yml is dumb. update the --total arg to yarn build if you change the number of workers
|
||||
worker_id: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24]
|
||||
worker_id: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19]
|
||||
release_channel: [stable, experimental]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
cache-dependency-path: |
|
||||
yarn.lock
|
||||
compiler/yarn.lock
|
||||
cache-dependency-path: yarn.lock
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 11.0.22
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache/restore@v4
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
path: "**/node_modules"
|
||||
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- run: yarn --cwd compiler install --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- run: yarn build --index=${{ matrix.worker_id }} --total=25 --r=${{ matrix.release_channel }} --ci
|
||||
- run: yarn build --index=${{ matrix.worker_id }} --total=20 --r=${{ matrix.release_channel }} --ci
|
||||
env:
|
||||
CI: github
|
||||
RELEASE_CHANNEL: ${{ matrix.release_channel }}
|
||||
@@ -365,11 +211,10 @@ jobs:
|
||||
with:
|
||||
name: _build_${{ matrix.worker_id }}_${{ matrix.release_channel }}
|
||||
path: build
|
||||
if-no-files-found: error
|
||||
|
||||
test_build:
|
||||
name: yarn test-build
|
||||
needs: [build_and_lint, runtime_compiler_node_modules_cache]
|
||||
needs: build_and_lint
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -404,42 +249,28 @@ jobs:
|
||||
# TODO: Test more persistent configurations?
|
||||
]
|
||||
shard:
|
||||
- 1/10
|
||||
- 2/10
|
||||
- 3/10
|
||||
- 4/10
|
||||
- 5/10
|
||||
- 6/10
|
||||
- 7/10
|
||||
- 8/10
|
||||
- 9/10
|
||||
- 10/10
|
||||
- 1/3
|
||||
- 2/3
|
||||
- 3/3
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
cache-dependency-path: |
|
||||
yarn.lock
|
||||
compiler/yarn.lock
|
||||
cache-dependency-path: yarn.lock
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache/restore@v4
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
path: "**/node_modules"
|
||||
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- run: yarn --cwd compiler install --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
@@ -452,29 +283,26 @@ jobs:
|
||||
|
||||
process_artifacts_combined:
|
||||
name: Process artifacts combined
|
||||
needs: [build_and_lint, runtime_node_modules_cache]
|
||||
needs: build_and_lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
cache-dependency-path: yarn.lock
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache/restore@v4
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
path: "**/node_modules"
|
||||
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
@@ -483,7 +311,7 @@ jobs:
|
||||
merge-multiple: true
|
||||
- name: Display structure of build
|
||||
run: ls -R build
|
||||
- run: echo ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }} >> build/COMMIT_SHA
|
||||
- run: echo ${{ github.sha }} >> build/COMMIT_SHA
|
||||
- name: Scrape warning messages
|
||||
run: |
|
||||
mkdir -p ./build/__test_utils__
|
||||
@@ -499,33 +327,29 @@ jobs:
|
||||
path: |
|
||||
./build.tgz
|
||||
./build2.tgz
|
||||
if-no-files-found: error
|
||||
|
||||
check_error_codes:
|
||||
name: Search build artifacts for unminified errors
|
||||
needs: [build_and_lint, runtime_node_modules_cache]
|
||||
needs: build_and_lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
cache-dependency-path: yarn.lock
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache/restore@v4
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
path: "**/node_modules"
|
||||
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
@@ -541,29 +365,26 @@ jobs:
|
||||
|
||||
check_release_dependencies:
|
||||
name: Check release dependencies
|
||||
needs: [build_and_lint, runtime_node_modules_cache]
|
||||
needs: build_and_lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
cache-dependency-path: yarn.lock
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache/restore@v4
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
path: "**/node_modules"
|
||||
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
@@ -581,23 +402,22 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
cache-dependency-path: yarn.lock
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4 # note: this does not reuse centralized cache since it has unique cache key
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: fixtures_dom-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'fixtures/dom/yarn.lock') }}
|
||||
path: "**/node_modules"
|
||||
key: fixtures_dom-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn --cwd fixtures/dom install --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- run: yarn install --frozen-lockfile
|
||||
working-directory: fixtures/dom
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
@@ -622,7 +442,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -632,31 +452,14 @@ jobs:
|
||||
# That means dependencies of the built packages are not installed.
|
||||
# We need to install dependencies of the workroot to fulfill all dependency constraints
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4 # note: this does not reuse centralized cache since it has unique cache key
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: fixtures_flight-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'fixtures/flight/yarn.lock') }}
|
||||
path: "**/node_modules"
|
||||
key: fixtures_flight-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
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:
|
||||
@@ -665,6 +468,16 @@ 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
|
||||
@@ -676,18 +489,16 @@ 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:
|
||||
name: Build DevTools and process artifacts
|
||||
needs: [build_and_lint, runtime_node_modules_cache]
|
||||
needs: build_and_lint
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -696,24 +507,21 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
cache-dependency-path: yarn.lock
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache/restore@v4
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
path: "**/node_modules"
|
||||
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
@@ -731,7 +539,6 @@ 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
|
||||
@@ -746,29 +553,26 @@ jobs:
|
||||
|
||||
run_devtools_e2e_tests:
|
||||
name: Run DevTools e2e tests
|
||||
needs: [build_and_lint, runtime_node_modules_cache]
|
||||
needs: build_and_lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
cache-dependency-path: yarn.lock
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache/restore@v4
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
path: "**/node_modules"
|
||||
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
@@ -787,32 +591,26 @@ jobs:
|
||||
if: ${{ github.event_name == 'pull_request' && github.ref_name != 'main' && github.event.pull_request.base.ref == 'main' }}
|
||||
name: Run sizebot
|
||||
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
|
||||
with:
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
cache-dependency-path: yarn.lock
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4 # note: this does not reuse centralized cache since it has unique cache key
|
||||
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') }}
|
||||
path: "**/node_modules"
|
||||
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- run: yarn --cwd scripts/release install --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
working-directory: scripts/release
|
||||
- name: Download artifacts for base revision
|
||||
run: |
|
||||
GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=$(git rev-parse ${{ github.event.pull_request.base.sha }})
|
||||
@@ -827,6 +625,7 @@ jobs:
|
||||
run: ls -R base-build
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
- name: Restore archived build for PR
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
@@ -839,11 +638,10 @@ jobs:
|
||||
node ./scripts/print-warnings/print-warnings.js > build/__test_utils__/ReactAllWarnings.js
|
||||
- name: Display structure of build for PR
|
||||
run: ls -R build
|
||||
- run: echo ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }} >> build/COMMIT_SHA
|
||||
- run: echo ${{ github.sha }} >> build/COMMIT_SHA
|
||||
- run: node ./scripts/tasks/danger
|
||||
- name: Archive sizebot results
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sizebot-message
|
||||
path: sizebot-message.md
|
||||
if-no-files-found: ignore
|
||||
|
||||
170
.github/workflows/runtime_commit_artifacts.yml
vendored
170
.github/workflows/runtime_commit_artifacts.yml
vendored
@@ -16,13 +16,6 @@ on:
|
||||
required: true
|
||||
default: false
|
||||
type: boolean
|
||||
dry_run:
|
||||
description: Perform a dry run (run everything except push)
|
||||
required: true
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
permissions: {}
|
||||
|
||||
env:
|
||||
TZ: /usr/share/zoneinfo/America/Los_Angeles
|
||||
@@ -32,40 +25,6 @@ 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 }}
|
||||
@@ -105,11 +64,27 @@ 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"
|
||||
- name: Restore downloaded build
|
||||
uses: actions/download-artifact@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
name: build
|
||||
path: build
|
||||
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-${{ 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
|
||||
name: yarn install (react)
|
||||
- run: yarn install --frozen-lockfile
|
||||
name: yarn install (scripts/release)
|
||||
working-directory: scripts/release
|
||||
- name: Download artifacts for base revision
|
||||
run: |
|
||||
GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }}
|
||||
- name: Display structure of build
|
||||
run: ls -R build
|
||||
- name: Strip @license from eslint plugin and react-refresh
|
||||
@@ -132,10 +107,9 @@ jobs:
|
||||
mkdir ./compiled/facebook-www/__test_utils__
|
||||
mv build/__test_utils__/ReactAllWarnings.js ./compiled/facebook-www/__test_utils__/ReactAllWarnings.js
|
||||
|
||||
# Move eslint-plugin-react-hooks into eslint-plugin-react-hooks
|
||||
mkdir ./compiled/eslint-plugin-react-hooks
|
||||
# Move eslint-plugin-react-hooks into facebook-www
|
||||
mv build/oss-experimental/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js \
|
||||
./compiled/eslint-plugin-react-hooks/index.js
|
||||
./compiled/facebook-www/eslint-plugin-react-hooks.js
|
||||
|
||||
# Move unstable_server-external-runtime.js into facebook-www
|
||||
mv build/oss-experimental/react-dom/unstable_server-external-runtime.js \
|
||||
@@ -173,9 +147,9 @@ jobs:
|
||||
ls -R ./compiled-rn
|
||||
- name: Add REVISION files
|
||||
run: |
|
||||
echo ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} >> ./compiled/facebook-www/REVISION
|
||||
echo ${{ github.sha }} >> ./compiled/facebook-www/REVISION
|
||||
cp ./compiled/facebook-www/REVISION ./compiled/facebook-www/REVISION_TRANSFORMS
|
||||
echo ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} >> ./compiled-rn/facebook-fbsource/xplat/js/react-native-github/Libraries/Renderer/REVISION
|
||||
echo ${{ github.sha}} >> ./compiled-rn/facebook-fbsource/xplat/js/react-native-github/Libraries/Renderer/REVISION
|
||||
- name: "Get current version string"
|
||||
id: get_current_version
|
||||
run: |
|
||||
@@ -192,20 +166,15 @@ 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, process_artifacts]
|
||||
if: inputs.force == true || (github.ref == 'refs/heads/main' && needs.process_artifacts.outputs.www_branch_count == '0')
|
||||
needs: download_artifacts
|
||||
if: inputs.force == true || (github.ref == 'refs/heads/main' && needs.download_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:
|
||||
@@ -217,12 +186,12 @@ jobs:
|
||||
name: compiled
|
||||
path: compiled/
|
||||
- name: Revert version changes
|
||||
if: needs.process_artifacts.outputs.last_version_classic != '' && needs.process_artifacts.outputs.last_version_modern != ''
|
||||
if: needs.download_artifacts.outputs.last_version_classic != '' && needs.download_artifacts.outputs.last_version_modern != ''
|
||||
env:
|
||||
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 }}
|
||||
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 }}
|
||||
run: |
|
||||
echo "Reverting $CURRENT_VERSION_CLASSIC to $LAST_VERSION_CLASSIC"
|
||||
grep -rl "$CURRENT_VERSION_CLASSIC" ./compiled || echo "No files found with $CURRENT_VERSION_CLASSIC"
|
||||
@@ -252,12 +221,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.process_artifacts.outputs.last_version_classic != '' && needs.process_artifacts.outputs.last_version_modern != '')
|
||||
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 != '')
|
||||
env:
|
||||
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 }}
|
||||
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 }}
|
||||
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"
|
||||
@@ -271,31 +240,24 @@ jobs:
|
||||
- name: Will commit these changes
|
||||
if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
|
||||
run: |
|
||||
git add .
|
||||
git status
|
||||
- name: Check commit message
|
||||
if: inputs.dry_run
|
||||
run: |
|
||||
git fetch origin --quiet
|
||||
git show ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} --no-patch --pretty=format:"%B"
|
||||
echo ":"
|
||||
git status -u
|
||||
- name: Commit changes to branch
|
||||
if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
|
||||
run: |
|
||||
git config --global user.email "${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}"
|
||||
git config --global user.name "${{ github.triggering_actor }}"
|
||||
uses: stefanzweifel/git-auto-commit-action@v5
|
||||
with:
|
||||
commit_message: |
|
||||
${{ github.event.workflow_run.head_commit.message || format('Manual build of {0}', github.event.workflow_run.head_sha || github.sha) }}
|
||||
|
||||
git fetch origin --quiet
|
||||
git commit -m "$(git show ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} --no-patch --pretty=format:'%B%n%nDiffTrain build for [${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha}})')" || echo "No changes to commit"
|
||||
- name: Push changes to branch
|
||||
if: inputs.dry_run == false && (inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true')
|
||||
run: git push
|
||||
DiffTrain build for [${{ github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha || github.sha }})
|
||||
branch: builds/facebook-www
|
||||
commit_user_name: ${{ github.triggering_actor }}
|
||||
commit_user_email: ${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}
|
||||
create_branch: true
|
||||
|
||||
commit_fbsource_artifacts:
|
||||
needs: [download_artifacts, 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')
|
||||
needs: download_artifacts
|
||||
if: inputs.force == true || (github.ref == 'refs/heads/main' && needs.download_artifacts.outputs.fbsource_branch_count == '0')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -308,10 +270,10 @@ jobs:
|
||||
name: compiled-rn
|
||||
path: compiled-rn/
|
||||
- name: Revert version changes
|
||||
if: needs.process_artifacts.outputs.last_version_rn != ''
|
||||
if: needs.download_artifacts.outputs.last_version_rn != ''
|
||||
env:
|
||||
CURRENT_VERSION: ${{ needs.process_artifacts.outputs.current_version_rn }}
|
||||
LAST_VERSION: ${{ needs.process_artifacts.outputs.last_version_rn }}
|
||||
CURRENT_VERSION: ${{ needs.download_artifacts.outputs.current_version_rn }}
|
||||
LAST_VERSION: ${{ needs.download_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"
|
||||
@@ -337,10 +299,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.process_artifacts.outputs.last_version_rn != '')
|
||||
if: inputs.force == true || (steps.check_should_commit.outputs.should_commit == 'true' && needs.download_artifacts.outputs.last_version_rn != '')
|
||||
env:
|
||||
CURRENT_VERSION: ${{ needs.process_artifacts.outputs.current_version_rn }}
|
||||
LAST_VERSION: ${{ needs.process_artifacts.outputs.last_version_rn }}
|
||||
CURRENT_VERSION: ${{ needs.download_artifacts.outputs.current_version_rn }}
|
||||
LAST_VERSION: ${{ needs.download_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"
|
||||
@@ -447,19 +409,15 @@ jobs:
|
||||
run: |
|
||||
git add .
|
||||
git status
|
||||
- name: Check commit message
|
||||
if: inputs.dry_run
|
||||
run: |
|
||||
git fetch origin --quiet
|
||||
git show ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} --no-patch --pretty=format:"%B"
|
||||
- name: Commit changes to branch
|
||||
if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
|
||||
run: |
|
||||
git config --global user.email "${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}"
|
||||
git config --global user.name "${{ github.triggering_actor }}"
|
||||
uses: stefanzweifel/git-auto-commit-action@v5
|
||||
with:
|
||||
commit_message: |
|
||||
${{ github.event.workflow_run.head_commit.message || format('Manual build of {0}', github.event.workflow_run.head_sha || github.sha) }}
|
||||
|
||||
git fetch origin --quiet
|
||||
git commit -m "$(git show ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} --no-patch --pretty=format:'%B%n%nDiffTrain build for [${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha}})')" || echo "No changes to commit"
|
||||
- name: Push changes to branch
|
||||
if: inputs.dry_run == false && (inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true')
|
||||
run: git push
|
||||
DiffTrain build for [${{ github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha || github.sha }})
|
||||
branch: builds/facebook-fbsource
|
||||
commit_user_name: ${{ github.triggering_actor }}
|
||||
commit_user_email: ${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}
|
||||
create_branch: true
|
||||
|
||||
9
.github/workflows/runtime_discord_notify.yml
vendored
9
.github/workflows/runtime_discord_notify.yml
vendored
@@ -1,20 +1,15 @@
|
||||
name: (Runtime) Discord Notify
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
pull_request_target:
|
||||
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 }}
|
||||
|
||||
@@ -24,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Discord Webhook Action
|
||||
uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4
|
||||
uses: tsickert/discord-webhook@v6.0.0
|
||||
with:
|
||||
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
embed-author-name: ${{ github.event.pull_request.user.login }}
|
||||
|
||||
12
.github/workflows/runtime_eslint_plugin_e2e.yml
vendored
12
.github/workflows/runtime_eslint_plugin_e2e.yml
vendored
@@ -7,8 +7,6 @@ on:
|
||||
paths-ignore:
|
||||
- compiler/**
|
||||
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
@@ -37,20 +35,16 @@ jobs:
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
cache-dependency-path: |
|
||||
yarn.lock
|
||||
compiler/yarn.lock
|
||||
cache-dependency-path: yarn.lock
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-and-compiler-eslint_e2e-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
path: "node_modules"
|
||||
key: runtime-eslint_e2e-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn --cwd compiler install --frozen-lockfile
|
||||
- name: Build plugin
|
||||
working-directory: fixtures/eslint-v${{ matrix.eslint_major }}
|
||||
run: node build.mjs
|
||||
|
||||
2
.github/workflows/runtime_fuzz_tests.yml
vendored
2
.github/workflows/runtime_fuzz_tests.yml
vendored
@@ -8,8 +8,6 @@ on:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
env:
|
||||
TZ: /usr/share/zoneinfo/America/Los_Angeles
|
||||
|
||||
|
||||
11
.github/workflows/runtime_prereleases.yml
vendored
11
.github/workflows/runtime_prereleases.yml
vendored
@@ -17,12 +17,11 @@ 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:
|
||||
@@ -40,13 +39,13 @@ jobs:
|
||||
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') }}
|
||||
path: "**/node_modules"
|
||||
key: runtime-release-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn --cwd scripts/release install --frozen-lockfile
|
||||
- run: yarn install --frozen-lockfile
|
||||
working-directory: scripts/release
|
||||
- run: |
|
||||
scripts/release/prepare-release-from-ci.js --skipTests -r ${{ inputs.release_channel }} --commit=${{ inputs.commit_sha }}
|
||||
cp ./scripts/release/ci-npmrc ~/.npmrc
|
||||
|
||||
@@ -6,8 +6,6 @@ on:
|
||||
prerelease_commit_sha:
|
||||
required: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
env:
|
||||
TZ: /usr/share/zoneinfo/America/Los_Angeles
|
||||
|
||||
|
||||
@@ -5,8 +5,6 @@ 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
|
||||
|
||||
|
||||
@@ -31,12 +31,11 @@ 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:
|
||||
@@ -45,7 +44,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Discord Webhook Action
|
||||
uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4
|
||||
uses: tsickert/discord-webhook@v6.0.0
|
||||
with:
|
||||
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
embed-author-name: ${{ github.event.sender.login }}
|
||||
@@ -72,13 +71,13 @@ jobs:
|
||||
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') }}
|
||||
path: "**/node_modules"
|
||||
key: runtime-release-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn --cwd scripts/release install --frozen-lockfile
|
||||
- run: yarn install --frozen-lockfile
|
||||
working-directory: scripts/release
|
||||
- run: cp ./scripts/release/ci-npmrc ~/.npmrc
|
||||
- if: '${{ inputs.only_packages }}'
|
||||
name: 'Prepare ${{ inputs.only_packages }} from NPM'
|
||||
|
||||
@@ -14,8 +14,6 @@ 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
|
||||
@@ -24,9 +22,6 @@ 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:
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#force-deletion-of-caches-overriding-default-cache-eviction-policy
|
||||
|
||||
name: (Shared) Cleanup Merged Branch Caches
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- closed
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr_number:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
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 key"
|
||||
cacheKeysForPR=$(gh cache list --ref $BRANCH --limit 100 --json id --jq '.[].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 }}
|
||||
BRANCH: refs/pull/${{ inputs.pr_number || github.event.pull_request.number }}/merge
|
||||
@@ -1,35 +0,0 @@
|
||||
# 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 }}
|
||||
@@ -5,8 +5,6 @@ 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
|
||||
@@ -15,9 +13,6 @@ 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
|
||||
|
||||
12
.github/workflows/shared_label_core_team_prs.yml
vendored
12
.github/workflows/shared_label_core_team_prs.yml
vendored
@@ -1,9 +1,7 @@
|
||||
name: (Shared) Label Core Team PRs
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
permissions: {}
|
||||
pull_request_target:
|
||||
|
||||
env:
|
||||
TZ: /usr/share/zoneinfo/America/Los_Angeles
|
||||
@@ -13,9 +11,6 @@ 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 }}
|
||||
|
||||
@@ -23,11 +18,6 @@ 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
|
||||
|
||||
22
.github/workflows/shared_lint.yml
vendored
22
.github/workflows/shared_lint.yml
vendored
@@ -5,8 +5,6 @@ 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
|
||||
@@ -30,9 +28,8 @@ jobs:
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: shared-lint-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
|
||||
path: "**/node_modules"
|
||||
key: shared-lint-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
@@ -51,9 +48,8 @@ jobs:
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: shared-lint-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
|
||||
path: "**/node_modules"
|
||||
key: shared-lint-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
@@ -72,9 +68,8 @@ jobs:
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: shared-lint-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
|
||||
path: "**/node_modules"
|
||||
key: shared-lint-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
@@ -93,9 +88,8 @@ jobs:
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: shared-lint-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
|
||||
path: "**/node_modules"
|
||||
key: shared-lint-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
|
||||
2
.github/workflows/shared_stale.yml
vendored
2
.github/workflows/shared_stale.yml
vendored
@@ -6,8 +6,6 @@ on:
|
||||
- cron: '0 * * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
env:
|
||||
TZ: /usr/share/zoneinfo/America/Los_Angeles
|
||||
|
||||
|
||||
@@ -7,18 +7,18 @@
|
||||
//
|
||||
// The @latest channel uses the version as-is, e.g.:
|
||||
//
|
||||
// 19.1.3
|
||||
// 19.1.0
|
||||
//
|
||||
// The @canary channel appends additional information, with the scheme
|
||||
// <version>-<label>-<commit_sha>, e.g.:
|
||||
//
|
||||
// 19.1.3-canary-a1c2d3e4
|
||||
// 19.1.0-canary-a1c2d3e4
|
||||
//
|
||||
// The @experimental channel doesn't include a version, only a date and a sha, e.g.:
|
||||
//
|
||||
// 0.0.0-experimental-241c4467e-20200129
|
||||
|
||||
const ReactVersion = '19.1.3';
|
||||
const ReactVersion = '19.1.0';
|
||||
|
||||
// The label used by the @canary channel. Represents the upcoming release's
|
||||
// stability. Most of the time, this will be "canary", but we may temporarily
|
||||
@@ -34,7 +34,7 @@ const rcNumber = 0;
|
||||
|
||||
const stablePackages = {
|
||||
'eslint-plugin-react-hooks': '5.2.0',
|
||||
'jest-react': '0.16.0',
|
||||
'jest-react': '0.17.0',
|
||||
react: ReactVersion,
|
||||
'react-art': ReactVersion,
|
||||
'react-dom': ReactVersion,
|
||||
|
||||
@@ -4,7 +4,8 @@ module.exports = {
|
||||
plugins: [
|
||||
'@babel/plugin-syntax-jsx',
|
||||
'@babel/plugin-transform-flow-strip-types',
|
||||
['@babel/plugin-proposal-class-properties', {loose: true}],
|
||||
['@babel/plugin-transform-class-properties', {loose: true}],
|
||||
'@babel/plugin-transform-classes',
|
||||
'syntax-trailing-function-commas',
|
||||
[
|
||||
'@babel/plugin-proposal-object-rest-spread',
|
||||
|
||||
@@ -7,4 +7,7 @@
|
||||
|
||||
const makeE2EConfig = require('../jest/makeE2EConfig');
|
||||
|
||||
module.exports = makeE2EConfig('e2e with forget', true);
|
||||
const config = makeE2EConfig('e2e with forget', true);
|
||||
config.setupFilesAfterEnv = ['<rootDir>/../scripts/jest/setupEnvE2E.js'];
|
||||
|
||||
module.exports = config;
|
||||
|
||||
@@ -5,16 +5,19 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {jsx} from '@babel/plugin-syntax-jsx';
|
||||
import babelJest from 'babel-jest';
|
||||
import {
|
||||
validateEnvironmentConfig,
|
||||
EnvironmentConfig,
|
||||
} from 'babel-plugin-react-compiler';
|
||||
import {compile} from 'babel-plugin-react-compiler';
|
||||
import {execSync} from 'child_process';
|
||||
|
||||
import type {NodePath, Visitor} from '@babel/traverse';
|
||||
import type {CallExpression} from '@babel/types';
|
||||
import BabelPluginReactCompiler from 'babel-plugin-react-compiler';
|
||||
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';
|
||||
|
||||
/**
|
||||
* -- IMPORTANT --
|
||||
@@ -25,19 +28,10 @@ import BabelPluginReactCompiler from 'babel-plugin-react-compiler';
|
||||
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({
|
||||
@@ -48,14 +42,15 @@ module.exports = (useForget: boolean) => {
|
||||
plugins: [
|
||||
useForget
|
||||
? [
|
||||
BabelPluginReactCompiler,
|
||||
ReactForgetFunctionTransform,
|
||||
{
|
||||
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,
|
||||
compilerCacheKey: execSync(
|
||||
'yarn --silent --cwd ../.. hash packages/babel-plugin-react-compiler/dist',
|
||||
).toString(),
|
||||
transformOptionsCacheKey: forgetOptions,
|
||||
e2eTransformerCacheKey,
|
||||
},
|
||||
@@ -110,3 +105,103 @@ 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',
|
||||
'_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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* 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;
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
injectReanimatedFlag,
|
||||
pipelineUsesReanimatedPlugin,
|
||||
} from '../Entrypoint/Reanimated';
|
||||
import validateNoUntransformedReferences from '../Entrypoint/ValidateNoUntransformedReferences';
|
||||
|
||||
const ENABLE_REACT_COMPILER_TIMINGS =
|
||||
process.env['ENABLE_REACT_COMPILER_TIMINGS'] === '1';
|
||||
@@ -62,19 +61,12 @@ export default function BabelPluginReactCompiler(
|
||||
},
|
||||
};
|
||||
}
|
||||
const result = compileProgram(prog, {
|
||||
compileProgram(prog, {
|
||||
opts,
|
||||
filename: pass.filename ?? null,
|
||||
comments: pass.file.ast.comments ?? [],
|
||||
code: pass.file.code,
|
||||
});
|
||||
validateNoUntransformedReferences(
|
||||
prog,
|
||||
pass.filename ?? null,
|
||||
opts.logger,
|
||||
opts.environment,
|
||||
result?.retryErrors ?? [],
|
||||
);
|
||||
if (ENABLE_REACT_COMPILER_TIMINGS === true) {
|
||||
performance.mark(`${filename}:end`, {
|
||||
detail: 'BabelPlugin:Program:end',
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
*/
|
||||
|
||||
import type {SourceLocation} from './HIR';
|
||||
import {Err, Ok, Result} from './Utils/Result';
|
||||
import {assertExhaustive} from './Utils/utils';
|
||||
|
||||
export enum ErrorSeverity {
|
||||
@@ -225,10 +224,6 @@ 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
|
||||
|
||||
@@ -8,119 +8,7 @@
|
||||
import {NodePath} from '@babel/core';
|
||||
import * as t from '@babel/types';
|
||||
import {PluginOptions} from './Options';
|
||||
import {CompilerError} from '../CompilerError';
|
||||
|
||||
/**
|
||||
* Gating rewrite for function declarations which are referenced before their
|
||||
* declaration site.
|
||||
*
|
||||
* ```js
|
||||
* // original
|
||||
* export default React.memo(Foo);
|
||||
* function Foo() { ... }
|
||||
*
|
||||
* // React compiler optimized + gated
|
||||
* import {gating} from 'myGating';
|
||||
* export default React.memo(Foo);
|
||||
* const gating_result = gating(); <- inserted
|
||||
* function Foo_optimized() {} <- inserted
|
||||
* function Foo_unoptimized() {} <- renamed from Foo
|
||||
* function Foo() { <- inserted function, which can be hoisted by JS engines
|
||||
* if (gating_result) return Foo_optimized();
|
||||
* else return Foo_unoptimized();
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
function insertAdditionalFunctionDeclaration(
|
||||
fnPath: NodePath<t.FunctionDeclaration>,
|
||||
compiled: t.FunctionDeclaration,
|
||||
gating: NonNullable<PluginOptions['gating']>,
|
||||
): void {
|
||||
const originalFnName = fnPath.node.id;
|
||||
const originalFnParams = fnPath.node.params;
|
||||
const compiledParams = fnPath.node.params;
|
||||
/**
|
||||
* Note that other than `export default function() {}`, all other function
|
||||
* declarations must have a binding identifier. Since default exports cannot
|
||||
* be referenced, it's safe to assume that all function declarations passed
|
||||
* here will have an identifier.
|
||||
* https://tc39.es/ecma262/multipage/ecmascript-language-functions-and-classes.html#sec-function-definitions
|
||||
*/
|
||||
CompilerError.invariant(originalFnName != null && compiled.id != null, {
|
||||
reason:
|
||||
'Expected function declarations that are referenced elsewhere to have a named identifier',
|
||||
loc: fnPath.node.loc ?? null,
|
||||
});
|
||||
CompilerError.invariant(originalFnParams.length === compiledParams.length, {
|
||||
reason:
|
||||
'Expected React Compiler optimized function declarations to have the same number of parameters as source',
|
||||
loc: fnPath.node.loc ?? null,
|
||||
});
|
||||
|
||||
const gatingCondition = fnPath.scope.generateUidIdentifier(
|
||||
`${gating.importSpecifierName}_result`,
|
||||
);
|
||||
const unoptimizedFnName = fnPath.scope.generateUidIdentifier(
|
||||
`${originalFnName.name}_unoptimized`,
|
||||
);
|
||||
const optimizedFnName = fnPath.scope.generateUidIdentifier(
|
||||
`${originalFnName.name}_optimized`,
|
||||
);
|
||||
/**
|
||||
* Step 1: rename existing functions
|
||||
*/
|
||||
compiled.id.name = optimizedFnName.name;
|
||||
fnPath.get('id').replaceInline(unoptimizedFnName);
|
||||
|
||||
/**
|
||||
* Step 2: insert new function declaration
|
||||
*/
|
||||
const newParams: Array<t.Identifier | t.RestElement> = [];
|
||||
const genNewArgs: Array<() => t.Identifier | t.SpreadElement> = [];
|
||||
for (let i = 0; i < originalFnParams.length; i++) {
|
||||
const argName = `arg${i}`;
|
||||
if (originalFnParams[i].type === 'RestElement') {
|
||||
newParams.push(t.restElement(t.identifier(argName)));
|
||||
genNewArgs.push(() => t.spreadElement(t.identifier(argName)));
|
||||
} else {
|
||||
newParams.push(t.identifier(argName));
|
||||
genNewArgs.push(() => t.identifier(argName));
|
||||
}
|
||||
}
|
||||
// insertAfter called in reverse order of how nodes should appear in program
|
||||
fnPath.insertAfter(
|
||||
t.functionDeclaration(
|
||||
originalFnName,
|
||||
newParams,
|
||||
t.blockStatement([
|
||||
t.ifStatement(
|
||||
gatingCondition,
|
||||
t.returnStatement(
|
||||
t.callExpression(
|
||||
compiled.id,
|
||||
genNewArgs.map(fn => fn()),
|
||||
),
|
||||
),
|
||||
t.returnStatement(
|
||||
t.callExpression(
|
||||
unoptimizedFnName,
|
||||
genNewArgs.map(fn => fn()),
|
||||
),
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
);
|
||||
fnPath.insertBefore(
|
||||
t.variableDeclaration('const', [
|
||||
t.variableDeclarator(
|
||||
gatingCondition,
|
||||
t.callExpression(t.identifier(gating.importSpecifierName), []),
|
||||
),
|
||||
]),
|
||||
);
|
||||
fnPath.insertBefore(compiled);
|
||||
}
|
||||
export function insertGatedFunctionDeclaration(
|
||||
fnPath: NodePath<
|
||||
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
|
||||
@@ -130,57 +18,47 @@ export function insertGatedFunctionDeclaration(
|
||||
| t.ArrowFunctionExpression
|
||||
| t.FunctionExpression,
|
||||
gating: NonNullable<PluginOptions['gating']>,
|
||||
referencedBeforeDeclaration: boolean,
|
||||
): void {
|
||||
if (referencedBeforeDeclaration && fnPath.isFunctionDeclaration()) {
|
||||
CompilerError.invariant(compiled.type === 'FunctionDeclaration', {
|
||||
reason: 'Expected compiled node type to match input type',
|
||||
description: `Got ${compiled.type} but expected FunctionDeclaration`,
|
||||
loc: fnPath.node.loc ?? null,
|
||||
});
|
||||
insertAdditionalFunctionDeclaration(fnPath, compiled, gating);
|
||||
} else {
|
||||
const gatingExpression = t.conditionalExpression(
|
||||
t.callExpression(t.identifier(gating.importSpecifierName), []),
|
||||
buildFunctionExpression(compiled),
|
||||
buildFunctionExpression(fnPath.node),
|
||||
);
|
||||
const gatingExpression = t.conditionalExpression(
|
||||
t.callExpression(t.identifier(gating.importSpecifierName), []),
|
||||
buildFunctionExpression(compiled),
|
||||
buildFunctionExpression(fnPath.node),
|
||||
);
|
||||
|
||||
/*
|
||||
* Convert function declarations to named variables *unless* this is an
|
||||
* `export default function ...` since `export default const ...` is
|
||||
* not supported. For that case we fall through to replacing w the raw
|
||||
* conditional expression
|
||||
*/
|
||||
if (
|
||||
fnPath.parentPath.node.type !== 'ExportDefaultDeclaration' &&
|
||||
fnPath.node.type === 'FunctionDeclaration' &&
|
||||
fnPath.node.id != null
|
||||
) {
|
||||
fnPath.replaceWith(
|
||||
t.variableDeclaration('const', [
|
||||
t.variableDeclarator(fnPath.node.id, gatingExpression),
|
||||
]),
|
||||
);
|
||||
} else if (
|
||||
fnPath.parentPath.node.type === 'ExportDefaultDeclaration' &&
|
||||
fnPath.node.type !== 'ArrowFunctionExpression' &&
|
||||
fnPath.node.id != null
|
||||
) {
|
||||
fnPath.insertAfter(
|
||||
t.exportDefaultDeclaration(t.identifier(fnPath.node.id.name)),
|
||||
);
|
||||
fnPath.parentPath.replaceWith(
|
||||
t.variableDeclaration('const', [
|
||||
t.variableDeclarator(
|
||||
t.identifier(fnPath.node.id.name),
|
||||
gatingExpression,
|
||||
),
|
||||
]),
|
||||
);
|
||||
} else {
|
||||
fnPath.replaceWith(gatingExpression);
|
||||
}
|
||||
/*
|
||||
* Convert function declarations to named variables *unless* this is an
|
||||
* `export default function ...` since `export default const ...` is
|
||||
* not supported. For that case we fall through to replacing w the raw
|
||||
* conditional expression
|
||||
*/
|
||||
if (
|
||||
fnPath.parentPath.node.type !== 'ExportDefaultDeclaration' &&
|
||||
fnPath.node.type === 'FunctionDeclaration' &&
|
||||
fnPath.node.id != null
|
||||
) {
|
||||
fnPath.replaceWith(
|
||||
t.variableDeclaration('const', [
|
||||
t.variableDeclarator(fnPath.node.id, gatingExpression),
|
||||
]),
|
||||
);
|
||||
} else if (
|
||||
fnPath.parentPath.node.type === 'ExportDefaultDeclaration' &&
|
||||
fnPath.node.type !== 'ArrowFunctionExpression' &&
|
||||
fnPath.node.id != null
|
||||
) {
|
||||
fnPath.insertAfter(
|
||||
t.exportDefaultDeclaration(t.identifier(fnPath.node.id.name)),
|
||||
);
|
||||
fnPath.parentPath.replaceWith(
|
||||
t.variableDeclaration('const', [
|
||||
t.variableDeclarator(
|
||||
t.identifier(fnPath.node.id.name),
|
||||
gatingExpression,
|
||||
),
|
||||
]),
|
||||
);
|
||||
} else {
|
||||
fnPath.replaceWith(gatingExpression);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
EnvironmentConfig,
|
||||
ExternalFunction,
|
||||
parseEnvironmentConfig,
|
||||
tryParseExternalFunction,
|
||||
} from '../HIR/Environment';
|
||||
import {hasOwnProperty} from '../Utils/utils';
|
||||
import {fromZodError} from 'zod-validation-error';
|
||||
@@ -272,14 +271,6 @@ export function parsePluginOptions(obj: unknown): PluginOptions {
|
||||
parsedOptions[key] = parseTargetConfig(value);
|
||||
break;
|
||||
}
|
||||
case 'gating': {
|
||||
if (value == null) {
|
||||
parsedOptions[key] = null;
|
||||
} else {
|
||||
parsedOptions[key] = tryParseExternalFunction(value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
parsedOptions[key] = value;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import {
|
||||
pruneUnusedLabelsHIR,
|
||||
} from '../HIR';
|
||||
import {
|
||||
CompilerMode,
|
||||
Environment,
|
||||
EnvironmentConfig,
|
||||
ReactFunctionType,
|
||||
@@ -100,9 +99,7 @@ import {propagateScopeDependenciesHIR} from '../HIR/PropagateScopeDependenciesHI
|
||||
import {outlineJSX} from '../Optimization/OutlineJsx';
|
||||
import {optimizePropsMethodCalls} from '../Optimization/OptimizePropsMethodCalls';
|
||||
import {transformFire} from '../Transform';
|
||||
import {validateNoImpureFunctionsInRender} from '../Validation/ValidateNoImpureFunctionsInRender';
|
||||
import {CompilerError} from '..';
|
||||
import {validateStaticComponents} from '../Validation/ValidateStaticComponents';
|
||||
import {validateNoImpureFunctionsInRender} from '../Validation/ValiateNoImpureFunctionsInRender';
|
||||
|
||||
export type CompilerPipelineValue =
|
||||
| {kind: 'ast'; name: string; value: CodegenFunction}
|
||||
@@ -116,7 +113,6 @@ function run(
|
||||
>,
|
||||
config: EnvironmentConfig,
|
||||
fnType: ReactFunctionType,
|
||||
mode: CompilerMode,
|
||||
useMemoCacheIdentifier: string,
|
||||
logger: Logger | null,
|
||||
filename: string | null,
|
||||
@@ -126,7 +122,6 @@ function run(
|
||||
const env = new Environment(
|
||||
func.scope,
|
||||
fnType,
|
||||
mode,
|
||||
config,
|
||||
contextIdentifiers,
|
||||
logger,
|
||||
@@ -162,13 +157,13 @@ function runWithEnvironment(
|
||||
log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
|
||||
|
||||
validateContextVariableLValues(hir);
|
||||
validateUseMemo(hir).unwrap();
|
||||
validateUseMemo(hir);
|
||||
|
||||
if (
|
||||
env.isInferredMemoEnabled &&
|
||||
!env.config.enablePreserveExistingManualUseMemo &&
|
||||
!env.config.disableMemoizationForDebugging &&
|
||||
!env.config.enableChangeDetectionForDebugging
|
||||
!env.config.enableChangeDetectionForDebugging &&
|
||||
!env.config.enableMinimalTransformsForRetry
|
||||
) {
|
||||
dropManualMemoization(hir);
|
||||
log({kind: 'hir', name: 'DropManualMemoization', value: hir});
|
||||
@@ -201,13 +196,8 @@ function runWithEnvironment(
|
||||
inferTypes(hir);
|
||||
log({kind: 'hir', name: 'InferTypes', value: hir});
|
||||
|
||||
if (env.isInferredMemoEnabled) {
|
||||
if (env.config.validateHooksUsage) {
|
||||
validateHooksUsage(hir).unwrap();
|
||||
}
|
||||
if (env.config.validateNoCapitalizedCalls) {
|
||||
validateNoCapitalizedCalls(hir).unwrap();
|
||||
}
|
||||
if (env.config.validateHooksUsage) {
|
||||
validateHooksUsage(hir);
|
||||
}
|
||||
|
||||
if (env.config.enableFire) {
|
||||
@@ -215,6 +205,10 @@ function runWithEnvironment(
|
||||
log({kind: 'hir', name: 'TransformFire', value: hir});
|
||||
}
|
||||
|
||||
if (env.config.validateNoCapitalizedCalls) {
|
||||
validateNoCapitalizedCalls(hir);
|
||||
}
|
||||
|
||||
if (env.config.lowerContextAccess) {
|
||||
lowerContextAccess(hir, env.config.lowerContextAccess);
|
||||
}
|
||||
@@ -225,12 +219,7 @@ function runWithEnvironment(
|
||||
analyseFunctions(hir);
|
||||
log({kind: 'hir', name: 'AnalyseFunctions', value: hir});
|
||||
|
||||
const fnEffectErrors = inferReferenceEffects(hir);
|
||||
if (env.isInferredMemoEnabled) {
|
||||
if (fnEffectErrors.length > 0) {
|
||||
CompilerError.throw(fnEffectErrors[0]);
|
||||
}
|
||||
}
|
||||
inferReferenceEffects(hir);
|
||||
log({kind: 'hir', name: 'InferReferenceEffects', value: hir});
|
||||
|
||||
validateLocalsNotReassignedAfterRender(hir);
|
||||
@@ -250,30 +239,28 @@ function runWithEnvironment(
|
||||
inferMutableRanges(hir);
|
||||
log({kind: 'hir', name: 'InferMutableRanges', value: hir});
|
||||
|
||||
if (env.isInferredMemoEnabled) {
|
||||
if (env.config.assertValidMutableRanges) {
|
||||
assertValidMutableRanges(hir);
|
||||
}
|
||||
if (env.config.assertValidMutableRanges) {
|
||||
assertValidMutableRanges(hir);
|
||||
}
|
||||
|
||||
if (env.config.validateRefAccessDuringRender) {
|
||||
validateNoRefAccessInRender(hir).unwrap();
|
||||
}
|
||||
if (env.config.validateRefAccessDuringRender) {
|
||||
validateNoRefAccessInRender(hir);
|
||||
}
|
||||
|
||||
if (env.config.validateNoSetStateInRender) {
|
||||
validateNoSetStateInRender(hir).unwrap();
|
||||
}
|
||||
if (env.config.validateNoSetStateInRender) {
|
||||
validateNoSetStateInRender(hir);
|
||||
}
|
||||
|
||||
if (env.config.validateNoSetStateInPassiveEffects) {
|
||||
env.logErrors(validateNoSetStateInPassiveEffects(hir));
|
||||
}
|
||||
if (env.config.validateNoSetStateInPassiveEffects) {
|
||||
validateNoSetStateInPassiveEffects(hir);
|
||||
}
|
||||
|
||||
if (env.config.validateNoJSXInTryStatements) {
|
||||
env.logErrors(validateNoJSXInTryStatement(hir));
|
||||
}
|
||||
if (env.config.validateNoJSXInTryStatements) {
|
||||
validateNoJSXInTryStatement(hir);
|
||||
}
|
||||
|
||||
if (env.config.validateNoImpureFunctionsInRender) {
|
||||
validateNoImpureFunctionsInRender(hir).unwrap();
|
||||
}
|
||||
if (env.config.validateNoImpureFunctionsInRender) {
|
||||
validateNoImpureFunctionsInRender(hir);
|
||||
}
|
||||
|
||||
inferReactivePlaces(hir);
|
||||
@@ -293,16 +280,7 @@ function runWithEnvironment(
|
||||
value: hir,
|
||||
});
|
||||
|
||||
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
|
||||
* transform reactive-scope labeled instructions no-ops.
|
||||
*/
|
||||
if (!env.config.enableMinimalTransformsForRetry) {
|
||||
inferReactiveScopeVariables(hir);
|
||||
log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir});
|
||||
}
|
||||
@@ -514,14 +492,14 @@ function runWithEnvironment(
|
||||
});
|
||||
|
||||
if (env.config.validateMemoizedEffectDependencies) {
|
||||
validateMemoizedEffectDependencies(reactiveFunction).unwrap();
|
||||
validateMemoizedEffectDependencies(reactiveFunction);
|
||||
}
|
||||
|
||||
if (
|
||||
env.config.enablePreserveExistingMemoizationGuarantees ||
|
||||
env.config.validatePreserveExistingMemoizationGuarantees
|
||||
) {
|
||||
validatePreservedManualMemoization(reactiveFunction).unwrap();
|
||||
validatePreservedManualMemoization(reactiveFunction);
|
||||
}
|
||||
|
||||
const ast = codegenFunction(reactiveFunction, {
|
||||
@@ -551,7 +529,6 @@ export function compileFn(
|
||||
>,
|
||||
config: EnvironmentConfig,
|
||||
fnType: ReactFunctionType,
|
||||
mode: CompilerMode,
|
||||
useMemoCacheIdentifier: string,
|
||||
logger: Logger | null,
|
||||
filename: string | null,
|
||||
@@ -561,7 +538,6 @@ export function compileFn(
|
||||
func,
|
||||
config,
|
||||
fnType,
|
||||
mode,
|
||||
useMemoCacheIdentifier,
|
||||
logger,
|
||||
filename,
|
||||
|
||||
@@ -16,6 +16,8 @@ import {
|
||||
EnvironmentConfig,
|
||||
ExternalFunction,
|
||||
ReactFunctionType,
|
||||
MINIMAL_RETRY_CONFIG,
|
||||
tryParseExternalFunction,
|
||||
} from '../HIR/Environment';
|
||||
import {CodegenFunction} from '../ReactiveScopes';
|
||||
import {isComponentDeclaration} from '../Utils/ComponentDeclaration';
|
||||
@@ -271,9 +273,6 @@ function isFilePartOfSources(
|
||||
return false;
|
||||
}
|
||||
|
||||
type CompileProgramResult = {
|
||||
retryErrors: Array<{fn: BabelFn; error: CompilerError}>;
|
||||
};
|
||||
/**
|
||||
* `compileProgram` is directly invoked by the react-compiler babel plugin, so
|
||||
* exceptions thrown by this function will fail the babel build.
|
||||
@@ -288,16 +287,16 @@ type CompileProgramResult = {
|
||||
export function compileProgram(
|
||||
program: NodePath<t.Program>,
|
||||
pass: CompilerPass,
|
||||
): CompileProgramResult | null {
|
||||
): void {
|
||||
if (shouldSkipCompilation(program, pass)) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
|
||||
const environment = pass.opts.environment;
|
||||
const restrictedImportsErr = validateRestrictedImports(program, environment);
|
||||
if (restrictedImportsErr) {
|
||||
handleError(restrictedImportsErr, pass, null);
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
const useMemoCacheIdentifier = program.scope.generateUidIdentifier('c');
|
||||
|
||||
@@ -368,7 +367,7 @@ export function compileProgram(
|
||||
filename: pass.filename ?? null,
|
||||
},
|
||||
);
|
||||
const retryErrors: Array<{fn: BabelFn; error: CompilerError}> = [];
|
||||
|
||||
const processFn = (
|
||||
fn: BabelFn,
|
||||
fnType: ReactFunctionType,
|
||||
@@ -409,7 +408,6 @@ export function compileProgram(
|
||||
fn,
|
||||
environment,
|
||||
fnType,
|
||||
'all_features',
|
||||
useMemoCacheIdentifier.name,
|
||||
pass.opts.logger,
|
||||
pass.filename,
|
||||
@@ -420,7 +418,28 @@ export function compileProgram(
|
||||
compileResult = {kind: 'error', error: err};
|
||||
}
|
||||
}
|
||||
|
||||
// If non-memoization features are enabled, retry regardless of error kind
|
||||
if (compileResult.kind === 'error' && environment.enableFire) {
|
||||
try {
|
||||
compileResult = {
|
||||
kind: 'compile',
|
||||
compiledFn: compileFn(
|
||||
fn,
|
||||
{
|
||||
...environment,
|
||||
...MINIMAL_RETRY_CONFIG,
|
||||
},
|
||||
fnType,
|
||||
useMemoCacheIdentifier.name,
|
||||
pass.opts.logger,
|
||||
pass.filename,
|
||||
pass.code,
|
||||
),
|
||||
};
|
||||
} catch (err) {
|
||||
compileResult = {kind: 'error', error: err};
|
||||
}
|
||||
}
|
||||
if (compileResult.kind === 'error') {
|
||||
/**
|
||||
* If an opt out directive is present, log only instead of throwing and don't mark as
|
||||
@@ -431,39 +450,7 @@ export function compileProgram(
|
||||
} else {
|
||||
handleError(compileResult.error, pass, fn.node.loc ?? null);
|
||||
}
|
||||
// If non-memoization features are enabled, retry regardless of error kind
|
||||
if (
|
||||
!(environment.enableFire || environment.inferEffectDependencies != null)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
compileResult = {
|
||||
kind: 'compile',
|
||||
compiledFn: compileFn(
|
||||
fn,
|
||||
environment,
|
||||
fnType,
|
||||
'no_inferred_memo',
|
||||
useMemoCacheIdentifier.name,
|
||||
pass.opts.logger,
|
||||
pass.filename,
|
||||
pass.code,
|
||||
),
|
||||
};
|
||||
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) {
|
||||
retryErrors.push({fn, error: err});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pass.opts.logger?.logEvent(pass.filename, {
|
||||
@@ -552,28 +539,32 @@ export function compileProgram(
|
||||
program.node.directives,
|
||||
);
|
||||
if (moduleScopeOptOutDirectives.length > 0) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
let gating: null | {
|
||||
gatingFn: ExternalFunction;
|
||||
referencedBeforeDeclared: Set<CompileResult>;
|
||||
} = null;
|
||||
|
||||
if (pass.opts.gating != null) {
|
||||
gating = {
|
||||
gatingFn: pass.opts.gating,
|
||||
referencedBeforeDeclared:
|
||||
getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns),
|
||||
};
|
||||
const error = checkFunctionReferencedBeforeDeclarationAtTopLevel(
|
||||
program,
|
||||
compiledFns.map(result => {
|
||||
return result.originalFn;
|
||||
}),
|
||||
);
|
||||
if (error) {
|
||||
handleError(error, pass, null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const hasLoweredContextAccess = compiledFns.some(
|
||||
c => c.compiledFn.hasLoweredContextAccess,
|
||||
);
|
||||
const externalFunctions: Array<ExternalFunction> = [];
|
||||
let gating: null | ExternalFunction = null;
|
||||
try {
|
||||
// TODO: check for duplicate import specifiers
|
||||
if (gating != null) {
|
||||
externalFunctions.push(gating.gatingFn);
|
||||
if (pass.opts.gating != null) {
|
||||
gating = tryParseExternalFunction(pass.opts.gating);
|
||||
externalFunctions.push(gating);
|
||||
}
|
||||
|
||||
const lowerContextAccess = environment.lowerContextAccess;
|
||||
@@ -610,7 +601,7 @@ export function compileProgram(
|
||||
}
|
||||
} catch (err) {
|
||||
handleError(err, pass, null);
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -622,12 +613,7 @@ export function compileProgram(
|
||||
const transformedFn = createNewFunctionNode(originalFn, compiledFn);
|
||||
|
||||
if (gating != null && kind === 'original') {
|
||||
insertGatedFunctionDeclaration(
|
||||
originalFn,
|
||||
transformedFn,
|
||||
gating.gatingFn,
|
||||
gating.referencedBeforeDeclared.has(result),
|
||||
);
|
||||
insertGatedFunctionDeclaration(originalFn, transformedFn, gating);
|
||||
} else {
|
||||
originalFn.replaceWith(transformedFn);
|
||||
}
|
||||
@@ -652,7 +638,6 @@ export function compileProgram(
|
||||
}
|
||||
addImportsToProgram(program, externalFunctions);
|
||||
}
|
||||
return {retryErrors};
|
||||
}
|
||||
|
||||
function shouldSkipCompilation(
|
||||
@@ -1008,39 +993,31 @@ 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) {
|
||||
returnsNonNode = isNonNode(ret.node.argument);
|
||||
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;
|
||||
}
|
||||
}
|
||||
},
|
||||
// Skip traversing all nested functions and their return statements
|
||||
ArrowFunctionExpression: skipNestedFunctions(node),
|
||||
@@ -1049,7 +1026,7 @@ function returnsNonNode(
|
||||
ObjectMethod: node => node.skip(),
|
||||
});
|
||||
|
||||
return returnsNonNode;
|
||||
return !hasReturn || returnsNonNode;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1116,23 +1093,20 @@ function getFunctionName(
|
||||
}
|
||||
}
|
||||
|
||||
function getFunctionReferencedBeforeDeclarationAtTopLevel(
|
||||
function checkFunctionReferencedBeforeDeclarationAtTopLevel(
|
||||
program: NodePath<t.Program>,
|
||||
fns: Array<CompileResult>,
|
||||
): Set<CompileResult> {
|
||||
const fnNames = new Map<string, {id: t.Identifier; fn: CompileResult}>(
|
||||
fns: Array<BabelFn>,
|
||||
): CompilerError | null {
|
||||
const fnIds = new Set(
|
||||
fns
|
||||
.map<[NodePath<t.Expression> | null, CompileResult]>(fn => [
|
||||
getFunctionName(fn.originalFn),
|
||||
fn,
|
||||
])
|
||||
.map(fn => getFunctionName(fn))
|
||||
.filter(
|
||||
(entry): entry is [NodePath<t.Identifier>, CompileResult] =>
|
||||
!!entry[0] && entry[0].isIdentifier(),
|
||||
(name): name is NodePath<t.Identifier> => !!name && name.isIdentifier(),
|
||||
)
|
||||
.map(entry => [entry[0].node.name, {id: entry[0].node, fn: entry[1]}]),
|
||||
.map(name => name.node),
|
||||
);
|
||||
const referencedBeforeDeclaration = new Set<CompileResult>();
|
||||
const fnNames = new Map([...fnIds].map(id => [id.name, id]));
|
||||
const errors = new CompilerError();
|
||||
|
||||
program.traverse({
|
||||
TypeAnnotation(path) {
|
||||
@@ -1158,7 +1132,8 @@ function getFunctionReferencedBeforeDeclarationAtTopLevel(
|
||||
* We've reached the declaration, hoisting is no longer possible, stop
|
||||
* checking for this component name.
|
||||
*/
|
||||
if (id.node === fn.id) {
|
||||
if (fnIds.has(id.node)) {
|
||||
fnIds.delete(id.node);
|
||||
fnNames.delete(id.node.name);
|
||||
return;
|
||||
}
|
||||
@@ -1169,12 +1144,20 @@ function getFunctionReferencedBeforeDeclarationAtTopLevel(
|
||||
* top level scope.
|
||||
*/
|
||||
if (scope === null && id.isReferencedIdentifier()) {
|
||||
referencedBeforeDeclaration.add(fn.fn);
|
||||
errors.pushErrorDetail(
|
||||
new CompilerErrorDetail({
|
||||
reason: `Encountered a function used before its declaration, which breaks Forget's gating codegen due to hoisting`,
|
||||
description: `Rewrite the reference to ${fn.name} to not rely on hoisting to fix this issue`,
|
||||
loc: fn.loc ?? null,
|
||||
suggestions: null,
|
||||
severity: ErrorSeverity.Invariant,
|
||||
}),
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return referencedBeforeDeclaration;
|
||||
return errors.details.length > 0 ? errors : null;
|
||||
}
|
||||
|
||||
function getReactCompilerRuntimeModule(opts: PluginOptions): string {
|
||||
|
||||
@@ -1,275 +0,0 @@
|
||||
import {NodePath} from '@babel/core';
|
||||
import * as t from '@babel/types';
|
||||
|
||||
import {
|
||||
CompilerError,
|
||||
CompilerErrorDetailOptions,
|
||||
EnvironmentConfig,
|
||||
ErrorSeverity,
|
||||
Logger,
|
||||
} from '..';
|
||||
import {getOrInsertWith} from '../Utils/utils';
|
||||
import {Environment} from '../HIR';
|
||||
import {DEFAULT_EXPORT} from '../HIR/Environment';
|
||||
|
||||
function throwInvalidReact(
|
||||
options: Omit<CompilerErrorDetailOptions, 'severity'>,
|
||||
{logger, filename}: TraversalState,
|
||||
): never {
|
||||
const detail: CompilerErrorDetailOptions = {
|
||||
...options,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
};
|
||||
logger?.logEvent(filename, {
|
||||
kind: 'CompileError',
|
||||
fnLoc: null,
|
||||
detail,
|
||||
});
|
||||
CompilerError.throw(detail);
|
||||
}
|
||||
function assertValidEffectImportReference(
|
||||
numArgs: number,
|
||||
paths: Array<NodePath<t.Node>>,
|
||||
context: TraversalState,
|
||||
): void {
|
||||
for (const path of paths) {
|
||||
const parent = path.parentPath;
|
||||
if (parent != null && parent.isCallExpression()) {
|
||||
const args = parent.get('arguments');
|
||||
/**
|
||||
* Only error on untransformed references of the form `useMyEffect(...)`
|
||||
* or `moduleNamespace.useMyEffect(...)`, with matching argument counts.
|
||||
* TODO: do we also want a mode to also hard error on non-call references?
|
||||
*/
|
||||
if (args.length === numArgs) {
|
||||
const maybeErrorDiagnostic = matchCompilerDiagnostic(
|
||||
path,
|
||||
context.transformErrors,
|
||||
);
|
||||
/**
|
||||
* Note that we cannot easily check the type of the first argument here,
|
||||
* as it may have already been transformed by the compiler (and not
|
||||
* memoized).
|
||||
*/
|
||||
throwInvalidReact(
|
||||
{
|
||||
reason:
|
||||
'[InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. ' +
|
||||
'This will break your build! ' +
|
||||
'To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics.',
|
||||
description: maybeErrorDiagnostic
|
||||
? `(Bailout reason: ${maybeErrorDiagnostic})`
|
||||
: null,
|
||||
loc: parent.node.loc ?? null,
|
||||
},
|
||||
context,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function assertValidFireImportReference(
|
||||
paths: Array<NodePath<t.Node>>,
|
||||
context: TraversalState,
|
||||
): void {
|
||||
if (paths.length > 0) {
|
||||
const maybeErrorDiagnostic = matchCompilerDiagnostic(
|
||||
paths[0],
|
||||
context.transformErrors,
|
||||
);
|
||||
throwInvalidReact(
|
||||
{
|
||||
reason:
|
||||
'[Fire] Untransformed reference to compiler-required feature. ' +
|
||||
'Either remove this `fire` call or ensure it is successfully transformed by the compiler',
|
||||
description: maybeErrorDiagnostic
|
||||
? `(Bailout reason: ${maybeErrorDiagnostic})`
|
||||
: null,
|
||||
loc: paths[0].node.loc ?? null,
|
||||
},
|
||||
context,
|
||||
);
|
||||
}
|
||||
}
|
||||
export default function validateNoUntransformedReferences(
|
||||
path: NodePath<t.Program>,
|
||||
filename: string | null,
|
||||
logger: Logger | null,
|
||||
env: EnvironmentConfig,
|
||||
transformErrors: Array<{fn: NodePath<t.Node>; error: CompilerError}>,
|
||||
): void {
|
||||
const moduleLoadChecks = new Map<
|
||||
string,
|
||||
Map<string, CheckInvalidReferenceFn>
|
||||
>();
|
||||
if (env.enableFire) {
|
||||
/**
|
||||
* Error on any untransformed references to `fire` (e.g. including non-call
|
||||
* expressions)
|
||||
*/
|
||||
for (const module of Environment.knownReactModules) {
|
||||
const react = getOrInsertWith(moduleLoadChecks, module, () => new Map());
|
||||
react.set('fire', assertValidFireImportReference);
|
||||
}
|
||||
}
|
||||
if (env.inferEffectDependencies) {
|
||||
for (const {
|
||||
function: {source, importSpecifierName},
|
||||
numRequiredArgs,
|
||||
} of env.inferEffectDependencies) {
|
||||
const module = getOrInsertWith(moduleLoadChecks, source, () => new Map());
|
||||
module.set(
|
||||
importSpecifierName,
|
||||
assertValidEffectImportReference.bind(null, numRequiredArgs),
|
||||
);
|
||||
}
|
||||
}
|
||||
if (moduleLoadChecks.size > 0) {
|
||||
transformProgram(path, moduleLoadChecks, filename, logger, transformErrors);
|
||||
}
|
||||
}
|
||||
|
||||
type TraversalState = {
|
||||
shouldInvalidateScopes: boolean;
|
||||
program: NodePath<t.Program>;
|
||||
logger: Logger | null;
|
||||
filename: string | null;
|
||||
transformErrors: Array<{fn: NodePath<t.Node>; error: CompilerError}>;
|
||||
};
|
||||
type CheckInvalidReferenceFn = (
|
||||
paths: Array<NodePath<t.Node>>,
|
||||
context: TraversalState,
|
||||
) => void;
|
||||
|
||||
function validateImportSpecifier(
|
||||
specifier: NodePath<t.ImportSpecifier>,
|
||||
importSpecifierChecks: Map<string, CheckInvalidReferenceFn>,
|
||||
state: TraversalState,
|
||||
): void {
|
||||
const imported = specifier.get('imported');
|
||||
const specifierName: string =
|
||||
imported.node.type === 'Identifier'
|
||||
? imported.node.name
|
||||
: imported.node.value;
|
||||
const checkFn = importSpecifierChecks.get(specifierName);
|
||||
if (checkFn == null) {
|
||||
return;
|
||||
}
|
||||
if (state.shouldInvalidateScopes) {
|
||||
state.shouldInvalidateScopes = false;
|
||||
state.program.scope.crawl();
|
||||
}
|
||||
|
||||
const local = specifier.get('local');
|
||||
const binding = local.scope.getBinding(local.node.name);
|
||||
CompilerError.invariant(binding != null, {
|
||||
reason: 'Expected binding to be found for import specifier',
|
||||
loc: local.node.loc ?? null,
|
||||
});
|
||||
checkFn(binding.referencePaths, state);
|
||||
}
|
||||
|
||||
function validateNamespacedImport(
|
||||
specifier: NodePath<t.ImportNamespaceSpecifier | t.ImportDefaultSpecifier>,
|
||||
importSpecifierChecks: Map<string, CheckInvalidReferenceFn>,
|
||||
state: TraversalState,
|
||||
): void {
|
||||
if (state.shouldInvalidateScopes) {
|
||||
state.shouldInvalidateScopes = false;
|
||||
state.program.scope.crawl();
|
||||
}
|
||||
const local = specifier.get('local');
|
||||
const binding = local.scope.getBinding(local.node.name);
|
||||
const defaultCheckFn = importSpecifierChecks.get(DEFAULT_EXPORT);
|
||||
|
||||
CompilerError.invariant(binding != null, {
|
||||
reason: 'Expected binding to be found for import specifier',
|
||||
loc: local.node.loc ?? null,
|
||||
});
|
||||
const filteredReferences = new Map<
|
||||
CheckInvalidReferenceFn,
|
||||
Array<NodePath<t.Node>>
|
||||
>();
|
||||
for (const reference of binding.referencePaths) {
|
||||
if (defaultCheckFn != null) {
|
||||
getOrInsertWith(filteredReferences, defaultCheckFn, () => []).push(
|
||||
reference,
|
||||
);
|
||||
}
|
||||
const parent = reference.parentPath;
|
||||
if (
|
||||
parent != null &&
|
||||
parent.isMemberExpression() &&
|
||||
parent.get('object') === reference
|
||||
) {
|
||||
if (parent.node.computed || parent.node.property.type !== 'Identifier') {
|
||||
continue;
|
||||
}
|
||||
const checkFn = importSpecifierChecks.get(parent.node.property.name);
|
||||
if (checkFn != null) {
|
||||
getOrInsertWith(filteredReferences, checkFn, () => []).push(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const [checkFn, references] of filteredReferences) {
|
||||
checkFn(references, state);
|
||||
}
|
||||
}
|
||||
function transformProgram(
|
||||
path: NodePath<t.Program>,
|
||||
|
||||
moduleLoadChecks: Map<string, Map<string, CheckInvalidReferenceFn>>,
|
||||
filename: string | null,
|
||||
logger: Logger | null,
|
||||
transformErrors: Array<{fn: NodePath<t.Node>; error: CompilerError}>,
|
||||
): void {
|
||||
const traversalState: TraversalState = {
|
||||
shouldInvalidateScopes: true,
|
||||
program: path,
|
||||
filename,
|
||||
logger,
|
||||
transformErrors,
|
||||
};
|
||||
path.traverse({
|
||||
ImportDeclaration(path: NodePath<t.ImportDeclaration>) {
|
||||
const importSpecifierChecks = moduleLoadChecks.get(
|
||||
path.node.source.value,
|
||||
);
|
||||
if (importSpecifierChecks == null) {
|
||||
return;
|
||||
}
|
||||
const specifiers = path.get('specifiers');
|
||||
for (const specifier of specifiers) {
|
||||
if (specifier.isImportSpecifier()) {
|
||||
validateImportSpecifier(
|
||||
specifier,
|
||||
importSpecifierChecks,
|
||||
traversalState,
|
||||
);
|
||||
} else {
|
||||
validateNamespacedImport(
|
||||
specifier as NodePath<
|
||||
t.ImportNamespaceSpecifier | t.ImportDefaultSpecifier
|
||||
>,
|
||||
importSpecifierChecks,
|
||||
traversalState,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function matchCompilerDiagnostic(
|
||||
badReference: NodePath<t.Node>,
|
||||
transformErrors: Array<{fn: NodePath<t.Node>; error: CompilerError}>,
|
||||
): string | null {
|
||||
for (const {fn, error} of transformErrors) {
|
||||
if (fn.isAncestor(badReference)) {
|
||||
return error.toString();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -1455,11 +1455,6 @@ function lowerObjectPropertyKey(
|
||||
kind: 'identifier',
|
||||
name: key.node.name,
|
||||
};
|
||||
} else if (key.isNumericLiteral()) {
|
||||
return {
|
||||
kind: 'identifier',
|
||||
name: String(key.node.value),
|
||||
};
|
||||
}
|
||||
|
||||
builder.errors.push({
|
||||
|
||||
@@ -13,13 +13,11 @@ import {
|
||||
BlockId,
|
||||
DependencyPathEntry,
|
||||
GeneratedSource,
|
||||
getHookKind,
|
||||
HIRFunction,
|
||||
Identifier,
|
||||
IdentifierId,
|
||||
InstructionId,
|
||||
InstructionValue,
|
||||
LoweredFunction,
|
||||
PropertyLiteral,
|
||||
ReactiveScopeDependency,
|
||||
ScopeId,
|
||||
@@ -114,9 +112,6 @@ export function collectHoistablePropertyLoads(
|
||||
hoistableFromOptionals,
|
||||
registry,
|
||||
nestedFnImmutableContext: null,
|
||||
assumedInvokedFns: fn.env.config.enableTreatFunctionDepsAsConditional
|
||||
? new Set()
|
||||
: getAssumedInvokedFunctions(fn),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -132,11 +127,6 @@ type CollectHoistablePropertyLoadsContext = {
|
||||
* but are currently kept separate for readability.
|
||||
*/
|
||||
nestedFnImmutableContext: ReadonlySet<IdentifierId> | null;
|
||||
/**
|
||||
* Functions which are assumed to be eventually called (as opposed to ones which might
|
||||
* not be called, e.g. the 0th argument of Array.map)
|
||||
*/
|
||||
assumedInvokedFns: ReadonlySet<LoweredFunction>;
|
||||
};
|
||||
function collectHoistablePropertyLoadsImpl(
|
||||
fn: HIRFunction,
|
||||
@@ -348,13 +338,7 @@ function collectNonNullsInBlocks(
|
||||
context.registry.getOrCreateIdentifier(identifier),
|
||||
);
|
||||
}
|
||||
const nodes = new Map<
|
||||
BlockId,
|
||||
{
|
||||
block: BasicBlock;
|
||||
assumedNonNullObjects: Set<PropertyPathNode>;
|
||||
}
|
||||
>();
|
||||
const nodes = new Map<BlockId, BlockInfo>();
|
||||
for (const [_, block] of fn.body.blocks) {
|
||||
const assumedNonNullObjects = new Set<PropertyPathNode>(
|
||||
knownNonNullIdentifiers,
|
||||
@@ -374,30 +358,32 @@ function collectNonNullsInBlocks(
|
||||
) {
|
||||
assumedNonNullObjects.add(maybeNonNull);
|
||||
}
|
||||
if (instr.value.kind === 'FunctionExpression') {
|
||||
if (
|
||||
(instr.value.kind === 'FunctionExpression' ||
|
||||
instr.value.kind === 'ObjectMethod') &&
|
||||
!fn.env.config.enableTreatFunctionDepsAsConditional
|
||||
) {
|
||||
const innerFn = instr.value.loweredFunc;
|
||||
if (context.assumedInvokedFns.has(innerFn)) {
|
||||
const innerHoistableMap = collectHoistablePropertyLoadsImpl(
|
||||
innerFn.func,
|
||||
{
|
||||
...context,
|
||||
nestedFnImmutableContext:
|
||||
context.nestedFnImmutableContext ??
|
||||
new Set(
|
||||
innerFn.func.context
|
||||
.filter(place =>
|
||||
isImmutableAtInstr(place.identifier, instr.id, context),
|
||||
)
|
||||
.map(place => place.identifier.id),
|
||||
),
|
||||
},
|
||||
);
|
||||
const innerHoistables = assertNonNull(
|
||||
innerHoistableMap.get(innerFn.func.body.entry),
|
||||
);
|
||||
for (const entry of innerHoistables.assumedNonNullObjects) {
|
||||
assumedNonNullObjects.add(entry);
|
||||
}
|
||||
const innerHoistableMap = collectHoistablePropertyLoadsImpl(
|
||||
innerFn.func,
|
||||
{
|
||||
...context,
|
||||
nestedFnImmutableContext:
|
||||
context.nestedFnImmutableContext ??
|
||||
new Set(
|
||||
innerFn.func.context
|
||||
.filter(place =>
|
||||
isImmutableAtInstr(place.identifier, instr.id, context),
|
||||
)
|
||||
.map(place => place.identifier.id),
|
||||
),
|
||||
},
|
||||
);
|
||||
const innerHoistables = assertNonNull(
|
||||
innerHoistableMap.get(innerFn.func.body.entry),
|
||||
);
|
||||
for (const entry of innerHoistables.assumedNonNullObjects) {
|
||||
assumedNonNullObjects.add(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -605,130 +591,3 @@ function reduceMaybeOptionalChains(
|
||||
}
|
||||
} while (changed);
|
||||
}
|
||||
|
||||
function getAssumedInvokedFunctions(
|
||||
fn: HIRFunction,
|
||||
temporaries: Map<
|
||||
IdentifierId,
|
||||
{fn: LoweredFunction; mayInvoke: Set<LoweredFunction>}
|
||||
> = new Map(),
|
||||
): ReadonlySet<LoweredFunction> {
|
||||
const hoistableFunctions = new Set<LoweredFunction>();
|
||||
/**
|
||||
* Step 1: Conservatively collect identifier to function expression mappings
|
||||
*/
|
||||
for (const block of fn.body.blocks.values()) {
|
||||
for (const {lvalue, value} of block.instructions) {
|
||||
/**
|
||||
* Conservatively only match function expressions which can have guaranteed ssa.
|
||||
* ObjectMethods and ObjectProperties do not.
|
||||
*/
|
||||
if (value.kind === 'FunctionExpression') {
|
||||
temporaries.set(lvalue.identifier.id, {
|
||||
fn: value.loweredFunc,
|
||||
mayInvoke: new Set(),
|
||||
});
|
||||
} else if (value.kind === 'StoreLocal') {
|
||||
const lvalue = value.lvalue.place.identifier;
|
||||
const maybeLoweredFunc = temporaries.get(value.value.identifier.id);
|
||||
if (maybeLoweredFunc != null) {
|
||||
temporaries.set(lvalue.id, maybeLoweredFunc);
|
||||
}
|
||||
} else if (value.kind === 'LoadLocal') {
|
||||
const maybeLoweredFunc = temporaries.get(value.place.identifier.id);
|
||||
if (maybeLoweredFunc != null) {
|
||||
temporaries.set(lvalue.identifier.id, maybeLoweredFunc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Step 2: Forward pass to do analysis of assumed function calls. Note that
|
||||
* this is conservative and does not count indirect references through
|
||||
* containers (e.g. `return {cb: () => {...}})`).
|
||||
*/
|
||||
for (const block of fn.body.blocks.values()) {
|
||||
for (const {lvalue, value} of block.instructions) {
|
||||
if (value.kind === 'CallExpression') {
|
||||
const callee = value.callee;
|
||||
const maybeHook = getHookKind(fn.env, callee.identifier);
|
||||
const maybeLoweredFunc = temporaries.get(callee.identifier.id);
|
||||
if (maybeLoweredFunc != null) {
|
||||
// Direct calls
|
||||
hoistableFunctions.add(maybeLoweredFunc.fn);
|
||||
} else if (maybeHook != null) {
|
||||
/**
|
||||
* Assume arguments to all hooks are safe to invoke
|
||||
*/
|
||||
for (const arg of value.args) {
|
||||
if (arg.kind === 'Identifier') {
|
||||
const maybeLoweredFunc = temporaries.get(arg.identifier.id);
|
||||
if (maybeLoweredFunc != null) {
|
||||
hoistableFunctions.add(maybeLoweredFunc.fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (value.kind === 'JsxExpression') {
|
||||
/**
|
||||
* Assume JSX attributes and children are safe to invoke
|
||||
*/
|
||||
for (const attr of value.props) {
|
||||
if (attr.kind === 'JsxSpreadAttribute') {
|
||||
continue;
|
||||
}
|
||||
const maybeLoweredFunc = temporaries.get(attr.place.identifier.id);
|
||||
if (maybeLoweredFunc != null) {
|
||||
hoistableFunctions.add(maybeLoweredFunc.fn);
|
||||
}
|
||||
}
|
||||
for (const child of value.children ?? []) {
|
||||
const maybeLoweredFunc = temporaries.get(child.identifier.id);
|
||||
if (maybeLoweredFunc != null) {
|
||||
hoistableFunctions.add(maybeLoweredFunc.fn);
|
||||
}
|
||||
}
|
||||
} else if (value.kind === 'FunctionExpression') {
|
||||
/**
|
||||
* Recursively traverse into other function expressions which may invoke
|
||||
* or pass already declared functions to react (e.g. as JSXAttributes).
|
||||
*
|
||||
* If lambda A calls lambda B, we assume lambda B is safe to invoke if
|
||||
* lambda A is -- even if lambda B is conditionally called. (see
|
||||
* `conditional-call-chain` fixture for example).
|
||||
*/
|
||||
const loweredFunc = value.loweredFunc.func;
|
||||
const lambdasCalled = getAssumedInvokedFunctions(
|
||||
loweredFunc,
|
||||
temporaries,
|
||||
);
|
||||
const maybeLoweredFunc = temporaries.get(lvalue.identifier.id);
|
||||
if (maybeLoweredFunc != null) {
|
||||
for (const called of lambdasCalled) {
|
||||
maybeLoweredFunc.mayInvoke.add(called);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (block.terminal.kind === 'return') {
|
||||
/**
|
||||
* Assume directly returned functions are safe to call
|
||||
*/
|
||||
const maybeLoweredFunc = temporaries.get(
|
||||
block.terminal.value.identifier.id,
|
||||
);
|
||||
if (maybeLoweredFunc != null) {
|
||||
hoistableFunctions.add(maybeLoweredFunc.fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const [_, {fn, mayInvoke}] of temporaries) {
|
||||
if (hoistableFunctions.has(fn)) {
|
||||
for (const called of mayInvoke) {
|
||||
hoistableFunctions.add(called);
|
||||
}
|
||||
}
|
||||
}
|
||||
return hoistableFunctions;
|
||||
}
|
||||
|
||||
@@ -96,8 +96,6 @@ export const MacroSchema = z.union([
|
||||
z.tuple([z.string(), z.array(MacroMethodSchema)]),
|
||||
]);
|
||||
|
||||
export type CompilerMode = 'all_features' | 'no_inferred_memo';
|
||||
|
||||
export type Macro = z.infer<typeof MacroSchema>;
|
||||
export type MacroMethod = z.infer<typeof MacroMethodSchema>;
|
||||
|
||||
@@ -330,11 +328,6 @@ 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,
|
||||
@@ -557,6 +550,8 @@ const EnvironmentConfigSchema = z.object({
|
||||
*/
|
||||
disableMemoizationForDebugging: z.boolean().default(false),
|
||||
|
||||
enableMinimalTransformsForRetry: z.boolean().default(false),
|
||||
|
||||
/**
|
||||
* When true, rather using memoized values, the compiler will always re-compute
|
||||
* values, and then use a heuristic to compare the memoized value to the newly
|
||||
@@ -631,6 +626,17 @@ const EnvironmentConfigSchema = z.object({
|
||||
|
||||
export type EnvironmentConfig = z.infer<typeof EnvironmentConfigSchema>;
|
||||
|
||||
export const MINIMAL_RETRY_CONFIG: PartialEnvironmentConfig = {
|
||||
validateHooksUsage: false,
|
||||
validateRefAccessDuringRender: false,
|
||||
validateNoSetStateInRender: false,
|
||||
validateNoSetStateInPassiveEffects: false,
|
||||
validateNoJSXInTryStatements: false,
|
||||
validateMemoizedEffectDependencies: false,
|
||||
validateNoCapitalizedCalls: null,
|
||||
validateBlocklistedImports: null,
|
||||
enableMinimalTransformsForRetry: true,
|
||||
};
|
||||
/**
|
||||
* For test fixtures and playground only.
|
||||
*
|
||||
@@ -845,7 +851,6 @@ export class Environment {
|
||||
code: string | null;
|
||||
config: EnvironmentConfig;
|
||||
fnType: ReactFunctionType;
|
||||
compilerMode: CompilerMode;
|
||||
useMemoCacheIdentifier: string;
|
||||
hasLoweredContextAccess: boolean;
|
||||
hasFireRewrite: boolean;
|
||||
@@ -856,7 +861,6 @@ export class Environment {
|
||||
constructor(
|
||||
scope: BabelScope,
|
||||
fnType: ReactFunctionType,
|
||||
compilerMode: CompilerMode,
|
||||
config: EnvironmentConfig,
|
||||
contextIdentifiers: Set<t.Identifier>,
|
||||
logger: Logger | null,
|
||||
@@ -866,7 +870,6 @@ export class Environment {
|
||||
) {
|
||||
this.#scope = scope;
|
||||
this.fnType = fnType;
|
||||
this.compilerMode = compilerMode;
|
||||
this.config = config;
|
||||
this.filename = filename;
|
||||
this.code = code;
|
||||
@@ -921,10 +924,6 @@ export class Environment {
|
||||
this.#hoistedIdentifiers = new Set();
|
||||
}
|
||||
|
||||
get isInferredMemoEnabled(): boolean {
|
||||
return this.compilerMode !== 'no_inferred_memo';
|
||||
}
|
||||
|
||||
get nextIdentifierId(): IdentifierId {
|
||||
return makeIdentifierId(this.#nextIdentifer++);
|
||||
}
|
||||
@@ -937,19 +936,6 @@ 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);
|
||||
}
|
||||
@@ -1139,7 +1125,6 @@ export class Environment {
|
||||
moduleName.toLowerCase() === 'react-dom'
|
||||
);
|
||||
}
|
||||
static knownReactModules: ReadonlyArray<string> = ['react', 'react-dom'];
|
||||
|
||||
getFallthroughPropertyType(
|
||||
receiver: Type,
|
||||
|
||||
@@ -703,10 +703,6 @@ export type ObjectPropertyKey =
|
||||
| {
|
||||
kind: 'computed';
|
||||
name: Place;
|
||||
}
|
||||
| {
|
||||
kind: 'number';
|
||||
name: number;
|
||||
};
|
||||
|
||||
export type ObjectProperty = {
|
||||
|
||||
@@ -330,9 +330,6 @@ function printObjectPropertyKey(key: ObjectPropertyKey): string {
|
||||
case 'computed': {
|
||||
return `[${printPlace(key.name)}]`;
|
||||
}
|
||||
case 'number': {
|
||||
return String(key.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {
|
||||
CompilerError,
|
||||
CompilerErrorDetailOptions,
|
||||
ErrorSeverity,
|
||||
ValueKind,
|
||||
} from '..';
|
||||
import {CompilerError, ErrorSeverity, ValueKind} from '..';
|
||||
import {
|
||||
AbstractValue,
|
||||
BasicBlock,
|
||||
@@ -295,21 +290,21 @@ export function inferTerminalFunctionEffects(
|
||||
return functionEffects;
|
||||
}
|
||||
|
||||
export function transformFunctionEffectErrors(
|
||||
export function raiseFunctionEffectErrors(
|
||||
functionEffects: Array<FunctionEffect>,
|
||||
): Array<CompilerErrorDetailOptions> {
|
||||
return functionEffects.map(eff => {
|
||||
): void {
|
||||
functionEffects.forEach(eff => {
|
||||
switch (eff.kind) {
|
||||
case 'ReactMutation':
|
||||
case 'GlobalMutation': {
|
||||
return eff.error;
|
||||
CompilerError.throw(eff.error);
|
||||
}
|
||||
case 'ContextMutation': {
|
||||
return {
|
||||
CompilerError.throw({
|
||||
severity: ErrorSeverity.Invariant,
|
||||
reason: `Unexpected ContextMutation in top-level function effects`,
|
||||
loc: eff.loc,
|
||||
};
|
||||
});
|
||||
}
|
||||
default:
|
||||
assertExhaustive(
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {CompilerError, CompilerErrorDetailOptions} from '../CompilerError';
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {Environment} from '../HIR';
|
||||
import {
|
||||
AbstractValue,
|
||||
@@ -49,7 +49,7 @@ import {assertExhaustive} from '../Utils/utils';
|
||||
import {
|
||||
inferTerminalFunctionEffects,
|
||||
inferInstructionFunctionEffects,
|
||||
transformFunctionEffectErrors,
|
||||
raiseFunctionEffectErrors,
|
||||
} from './InferFunctionEffects';
|
||||
|
||||
const UndefinedValue: InstructionValue = {
|
||||
@@ -103,7 +103,7 @@ const UndefinedValue: InstructionValue = {
|
||||
export default function inferReferenceEffects(
|
||||
fn: HIRFunction,
|
||||
options: {isFunctionExpression: boolean} = {isFunctionExpression: false},
|
||||
): Array<CompilerErrorDetailOptions> {
|
||||
): void {
|
||||
/*
|
||||
* Initial state contains function params
|
||||
* TODO: include module declarations here as well
|
||||
@@ -241,9 +241,8 @@ export default function inferReferenceEffects(
|
||||
|
||||
if (options.isFunctionExpression) {
|
||||
fn.effects = functionEffects;
|
||||
return [];
|
||||
} else {
|
||||
return transformFunctionEffectErrors(functionEffects);
|
||||
} else if (!fn.env.config.enableMinimalTransformsForRetry) {
|
||||
raiseFunctionEffectErrors(functionEffects);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
renameVariables,
|
||||
} from '.';
|
||||
import {CompilerError, ErrorSeverity} from '../CompilerError';
|
||||
import {Environment, ExternalFunction} from '../HIR';
|
||||
import {Environment, EnvironmentConfig, 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 && fn.env.isInferredMemoEnabled) {
|
||||
if (hookGuard != null) {
|
||||
compiled.body = t.blockStatement([
|
||||
createHookGuard(
|
||||
hookGuard,
|
||||
@@ -250,11 +250,7 @@ export function codegenFunction(
|
||||
}
|
||||
|
||||
const emitInstrumentForget = fn.env.config.enableEmitInstrumentForget;
|
||||
if (
|
||||
emitInstrumentForget != null &&
|
||||
fn.id != null &&
|
||||
fn.env.isInferredMemoEnabled
|
||||
) {
|
||||
if (emitInstrumentForget != null && fn.id != null) {
|
||||
/*
|
||||
* Technically, this is a conditional hook call. However, we expect
|
||||
* __DEV__ and gating identifier to be runtime constants
|
||||
@@ -552,7 +548,7 @@ function codegenBlockNoReset(
|
||||
}
|
||||
|
||||
function wrapCacheDep(cx: Context, value: t.Expression): t.Expression {
|
||||
if (cx.env.config.enableEmitFreeze != null && cx.env.isInferredMemoEnabled) {
|
||||
if (cx.env.config.enableEmitFreeze != null) {
|
||||
// The import declaration for emitFreeze is inserted in the Babel plugin
|
||||
return t.conditionalExpression(
|
||||
t.identifier('__DEV__'),
|
||||
@@ -1557,7 +1553,7 @@ function createHookGuard(
|
||||
* ```
|
||||
*/
|
||||
function createCallExpression(
|
||||
env: Environment,
|
||||
config: EnvironmentConfig,
|
||||
callee: t.Expression,
|
||||
args: Array<t.Expression | t.SpreadElement>,
|
||||
loc: SourceLocation | null,
|
||||
@@ -1568,8 +1564,8 @@ function createCallExpression(
|
||||
callExpr.loc = loc;
|
||||
}
|
||||
|
||||
const hookGuard = env.config.enableEmitHookGuards;
|
||||
if (hookGuard != null && isHook && env.isInferredMemoEnabled) {
|
||||
const hookGuard = config.enableEmitHookGuards;
|
||||
if (hookGuard != null && isHook) {
|
||||
const iife = t.functionExpression(
|
||||
null,
|
||||
[],
|
||||
@@ -1705,7 +1701,7 @@ function codegenInstructionValue(
|
||||
const callee = codegenPlaceToExpression(cx, instrValue.callee);
|
||||
const args = instrValue.args.map(arg => codegenArgument(cx, arg));
|
||||
value = createCallExpression(
|
||||
cx.env,
|
||||
cx.env.config,
|
||||
callee,
|
||||
args,
|
||||
instrValue.loc,
|
||||
@@ -1795,7 +1791,7 @@ function codegenInstructionValue(
|
||||
);
|
||||
const args = instrValue.args.map(arg => codegenArgument(cx, arg));
|
||||
value = createCallExpression(
|
||||
cx.env,
|
||||
cx.env.config,
|
||||
memberExpr,
|
||||
args,
|
||||
instrValue.loc,
|
||||
@@ -2433,9 +2429,6 @@ function codegenObjectPropertyKey(
|
||||
});
|
||||
return expr;
|
||||
}
|
||||
case 'number': {
|
||||
return t.numericLiteral(key.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
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
|
||||
@@ -19,9 +18,7 @@ import {Result} from '../Utils/Result';
|
||||
* 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,
|
||||
): Result<void, CompilerError> {
|
||||
export function validateNoImpureFunctionsInRender(fn: HIRFunction): void {
|
||||
const errors = new CompilerError();
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
for (const instr of block.instructions) {
|
||||
@@ -49,5 +46,7 @@ export function validateNoImpureFunctionsInRender(
|
||||
}
|
||||
}
|
||||
}
|
||||
return errors.asResult();
|
||||
if (errors.hasErrors()) {
|
||||
throw errors;
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,6 @@ 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
|
||||
@@ -88,9 +87,7 @@ 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,
|
||||
): Result<void, CompilerError> {
|
||||
export function validateHooksUsage(fn: HIRFunction): void {
|
||||
const unconditionalBlocks = computeUnconditionalBlocks(fn);
|
||||
|
||||
const errors = new CompilerError();
|
||||
@@ -426,7 +423,9 @@ export function validateHooksUsage(
|
||||
for (const [, error] of errorsByPlace) {
|
||||
errors.push(error);
|
||||
}
|
||||
return errors.asResult();
|
||||
if (errors.hasErrors()) {
|
||||
throw errors;
|
||||
}
|
||||
}
|
||||
|
||||
function visitFunctionExpression(errors: CompilerError, fn: HIRFunction): void {
|
||||
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
ReactiveFunctionVisitor,
|
||||
visitReactiveFunction,
|
||||
} from '../ReactiveScopes/visitors';
|
||||
import {Result} from '../Utils/Result';
|
||||
|
||||
/**
|
||||
* Validates that all known effect dependencies are memoized. The algorithm checks two things:
|
||||
@@ -48,12 +47,12 @@ import {Result} from '../Utils/Result';
|
||||
* mutate(object); // ... mutable range ends here after this mutation
|
||||
* ```
|
||||
*/
|
||||
export function validateMemoizedEffectDependencies(
|
||||
fn: ReactiveFunction,
|
||||
): Result<void, CompilerError> {
|
||||
export function validateMemoizedEffectDependencies(fn: ReactiveFunction): void {
|
||||
const errors = new CompilerError();
|
||||
visitReactiveFunction(fn, new Visitor(), errors);
|
||||
return errors.asResult();
|
||||
if (errors.hasErrors()) {
|
||||
throw errors;
|
||||
}
|
||||
}
|
||||
|
||||
class Visitor extends ReactiveFunctionVisitor<CompilerError> {
|
||||
|
||||
@@ -4,14 +4,11 @@
|
||||
* 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, ErrorSeverity} from '..';
|
||||
import {CompilerError, EnvironmentConfig} from '..';
|
||||
import {HIRFunction, IdentifierId} from '../HIR';
|
||||
import {DEFAULT_GLOBALS} from '../HIR/Globals';
|
||||
import {Result} from '../Utils/Result';
|
||||
|
||||
export function validateNoCapitalizedCalls(
|
||||
fn: HIRFunction,
|
||||
): Result<void, CompilerError> {
|
||||
export function validateNoCapitalizedCalls(fn: HIRFunction): void {
|
||||
const envConfig: EnvironmentConfig = fn.env.config;
|
||||
const ALLOW_LIST = new Set([
|
||||
...DEFAULT_GLOBALS.keys(),
|
||||
@@ -29,7 +26,6 @@ export function validateNoCapitalizedCalls(
|
||||
);
|
||||
};
|
||||
|
||||
const errors = new CompilerError();
|
||||
const capitalLoadGlobals = new Map<IdentifierId, string>();
|
||||
const capitalizedProperties = new Map<IdentifierId, string>();
|
||||
const reason =
|
||||
@@ -77,8 +73,7 @@ export function validateNoCapitalizedCalls(
|
||||
const propertyIdentifier = value.property.identifier.id;
|
||||
const propertyName = capitalizedProperties.get(propertyIdentifier);
|
||||
if (propertyName != null) {
|
||||
errors.push({
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
CompilerError.throwInvalidReact({
|
||||
reason,
|
||||
description: `${propertyName} may be a component.`,
|
||||
loc: value.loc,
|
||||
@@ -90,5 +85,4 @@ export function validateNoCapitalizedCalls(
|
||||
}
|
||||
}
|
||||
}
|
||||
return errors.asResult();
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
import {CompilerError, ErrorSeverity} from '..';
|
||||
import {BlockId, HIRFunction} from '../HIR';
|
||||
import {Result} from '../Utils/Result';
|
||||
import {retainWhere} from '../Utils/utils';
|
||||
|
||||
/**
|
||||
@@ -20,9 +19,7 @@ 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,
|
||||
): Result<void, CompilerError> {
|
||||
export function validateNoJSXInTryStatement(fn: HIRFunction): void {
|
||||
const activeTryBlocks: Array<BlockId> = [];
|
||||
const errors = new CompilerError();
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
@@ -49,5 +46,7 @@ export function validateNoJSXInTryStatement(
|
||||
activeTryBlocks.push(block.terminal.handler);
|
||||
}
|
||||
}
|
||||
return errors.asResult();
|
||||
if (errors.hasErrors()) {
|
||||
throw errors;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,11 +99,9 @@ class Env extends Map<IdentifierId, RefAccessType> {
|
||||
}
|
||||
}
|
||||
|
||||
export function validateNoRefAccessInRender(
|
||||
fn: HIRFunction,
|
||||
): Result<void, CompilerError> {
|
||||
export function validateNoRefAccessInRender(fn: HIRFunction): void {
|
||||
const env = new Env();
|
||||
return validateNoRefAccessInRenderImpl(fn, env).map(_ => undefined);
|
||||
validateNoRefAccessInRenderImpl(fn, env).unwrap();
|
||||
}
|
||||
|
||||
function refTypeOfType(place: Place): RefAccessType {
|
||||
|
||||
@@ -14,7 +14,6 @@ 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),
|
||||
@@ -24,9 +23,7 @@ import {Result} from '../Utils/Result';
|
||||
* 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,
|
||||
): Result<void, CompilerError> {
|
||||
export function validateNoSetStateInPassiveEffects(fn: HIRFunction): void {
|
||||
const setStateFunctions: Map<IdentifierId, Place> = new Map();
|
||||
const errors = new CompilerError();
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
@@ -101,7 +98,9 @@ export function validateNoSetStateInPassiveEffects(
|
||||
}
|
||||
}
|
||||
|
||||
return errors.asResult();
|
||||
if (errors.hasErrors()) {
|
||||
throw errors;
|
||||
}
|
||||
}
|
||||
|
||||
function getSetStateCall(
|
||||
|
||||
@@ -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 {Result} from '../Utils/Result';
|
||||
import {Err, Ok, Result} from '../Utils/Result';
|
||||
|
||||
/**
|
||||
* Validates that the given function does not have an infinite update loop
|
||||
@@ -39,11 +39,9 @@ import {Result} from '../Utils/Result';
|
||||
* y();
|
||||
* ```
|
||||
*/
|
||||
export function validateNoSetStateInRender(
|
||||
fn: HIRFunction,
|
||||
): Result<void, CompilerError> {
|
||||
export function validateNoSetStateInRender(fn: HIRFunction): void {
|
||||
const unconditionalSetStateFunctions: Set<IdentifierId> = new Set();
|
||||
return validateNoSetStateInRenderImpl(fn, unconditionalSetStateFunctions);
|
||||
validateNoSetStateInRenderImpl(fn, unconditionalSetStateFunctions).unwrap();
|
||||
}
|
||||
|
||||
function validateNoSetStateInRenderImpl(
|
||||
@@ -147,5 +145,9 @@ function validateNoSetStateInRenderImpl(
|
||||
}
|
||||
}
|
||||
|
||||
return errors.asResult();
|
||||
if (errors.hasErrors()) {
|
||||
return Err(errors);
|
||||
} else {
|
||||
return Ok(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,9 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {CompilerError, ErrorSeverity} from '../CompilerError';
|
||||
import {CompilerError, Effect, ErrorSeverity} from '..';
|
||||
import {
|
||||
DeclarationId,
|
||||
Effect,
|
||||
GeneratedSource,
|
||||
Identifier,
|
||||
IdentifierId,
|
||||
@@ -31,7 +30,6 @@ import {
|
||||
ReactiveFunctionVisitor,
|
||||
visitReactiveFunction,
|
||||
} from '../ReactiveScopes/visitors';
|
||||
import {Result} from '../Utils/Result';
|
||||
import {getOrInsertDefault} from '../Utils/utils';
|
||||
|
||||
/**
|
||||
@@ -41,15 +39,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,
|
||||
): Result<void, CompilerError> {
|
||||
export function validatePreservedManualMemoization(fn: ReactiveFunction): void {
|
||||
const state = {
|
||||
errors: new CompilerError(),
|
||||
manualMemoState: null,
|
||||
};
|
||||
visitReactiveFunction(fn, new Visitor(), state);
|
||||
return state.errors.asResult();
|
||||
if (state.errors.hasErrors()) {
|
||||
throw state.errors;
|
||||
}
|
||||
}
|
||||
|
||||
const DEBUG = false;
|
||||
|
||||
@@ -1,83 +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.
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -5,12 +5,10 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {CompilerError, ErrorSeverity} from '..';
|
||||
import {CompilerError} from '..';
|
||||
import {FunctionExpression, HIRFunction, IdentifierId} from '../HIR';
|
||||
import {Result} from '../Utils/Result';
|
||||
|
||||
export function validateUseMemo(fn: HIRFunction): Result<void, CompilerError> {
|
||||
const errors = new CompilerError();
|
||||
export function validateUseMemo(fn: HIRFunction): void {
|
||||
const useMemos = new Set<IdentifierId>();
|
||||
const react = new Set<IdentifierId>();
|
||||
const functions = new Map<IdentifierId, FunctionExpression>();
|
||||
@@ -63,8 +61,7 @@ export function validateUseMemo(fn: HIRFunction): Result<void, CompilerError> {
|
||||
}
|
||||
|
||||
if (body.loweredFunc.func.params.length > 0) {
|
||||
errors.push({
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
CompilerError.throwInvalidReact({
|
||||
reason: 'useMemo callbacks may not accept any arguments',
|
||||
description: null,
|
||||
loc: body.loc,
|
||||
@@ -73,8 +70,7 @@ export function validateUseMemo(fn: HIRFunction): Result<void, CompilerError> {
|
||||
}
|
||||
|
||||
if (body.loweredFunc.func.async || body.loweredFunc.func.generator) {
|
||||
errors.push({
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
CompilerError.throwInvalidReact({
|
||||
reason:
|
||||
'useMemo callbacks may not be async or generator functions',
|
||||
description: null,
|
||||
@@ -88,5 +84,4 @@ export function validateUseMemo(fn: HIRFunction): Result<void, CompilerError> {
|
||||
}
|
||||
}
|
||||
}
|
||||
return errors.asResult();
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ globalThis.constantValue = 'global test value';
|
||||
|
||||
test('literal-constant-propagation', () => {
|
||||
function Component() {
|
||||
'use memo';
|
||||
const x = 'test value 1';
|
||||
return <div>{x}</div>;
|
||||
}
|
||||
@@ -39,7 +38,6 @@ test('literal-constant-propagation', () => {
|
||||
|
||||
test('global-constant-propagation', () => {
|
||||
function Component() {
|
||||
'use memo';
|
||||
const x = constantValue;
|
||||
|
||||
return <div>{x}</div>;
|
||||
@@ -67,7 +65,6 @@ test('global-constant-propagation', () => {
|
||||
|
||||
test('lambda-constant-propagation', () => {
|
||||
function Component() {
|
||||
'use memo';
|
||||
const x = 'test value 1';
|
||||
const getDiv = () => <div>{x}</div>;
|
||||
return getDiv();
|
||||
@@ -95,7 +92,6 @@ test('lambda-constant-propagation', () => {
|
||||
|
||||
test('lambda-constant-propagation-of-phi-node', () => {
|
||||
function Component({noopCallback}) {
|
||||
'use memo';
|
||||
const x = 'test value 1';
|
||||
if (constantValue) {
|
||||
noopCallback();
|
||||
|
||||
@@ -16,7 +16,6 @@ function Button({label}) {
|
||||
|
||||
let currentTheme = 'light';
|
||||
function useTheme() {
|
||||
'use memo';
|
||||
return currentTheme;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ 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
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
|
||||
## 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>
|
||||
@@ -1,7 +0,0 @@
|
||||
// @compilationMode(infer)
|
||||
const Test = () => <div />;
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Test,
|
||||
params: [{}],
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
|
||||
## 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;
|
||||
```
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @logger @validateNoJSXInTryStatements
|
||||
// @validateNoJSXInTryStatements
|
||||
import {identity} from 'shared-runtime';
|
||||
|
||||
function Component(props) {
|
||||
@@ -0,0 +1,31 @@
|
||||
|
||||
## 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 | }
|
||||
```
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @logger @validateNoJSXInTryStatements
|
||||
// @validateNoJSXInTryStatements
|
||||
function Component(props) {
|
||||
let el;
|
||||
try {
|
||||
@@ -0,0 +1,37 @@
|
||||
|
||||
## 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 | }
|
||||
```
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @logger @validateNoSetStateInPassiveEffects
|
||||
// @validateNoSetStateInPassiveEffects
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
function Component() {
|
||||
@@ -0,0 +1,31 @@
|
||||
|
||||
## 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 | }
|
||||
```
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @logger @validateNoSetStateInPassiveEffects
|
||||
// @validateNoSetStateInPassiveEffects
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
function Component() {
|
||||
@@ -23,11 +23,11 @@ import { c as _c } from "react/compiler-runtime";
|
||||
function Component(props) {
|
||||
const $ = _c(4);
|
||||
let t0;
|
||||
if ($[0] !== props) {
|
||||
if ($[0] !== props.name) {
|
||||
t0 = function () {
|
||||
return <div>{props.name}</div>;
|
||||
};
|
||||
$[0] = props;
|
||||
$[0] = props.name;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @flow @gating
|
||||
import {Stringify} from 'shared-runtime';
|
||||
import * as React from 'react';
|
||||
|
||||
component Foo(ref: React.RefSetter<Controls>) {
|
||||
return <Stringify ref={ref} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: eval('(...args) => React.createElement(Foo, args)'),
|
||||
params: [{ref: React.createRef()}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { Stringify } from "shared-runtime";
|
||||
import * as React from "react";
|
||||
|
||||
const Foo = React.forwardRef(Foo_withRef);
|
||||
const _isForgetEnabled_Fixtures_result = isForgetEnabled_Fixtures();
|
||||
function _Foo_withRef_optimized(_$$empty_props_placeholder$$, ref) {
|
||||
const $ = _c(2);
|
||||
let t0;
|
||||
if ($[0] !== ref) {
|
||||
t0 = <Stringify ref={ref} />;
|
||||
$[0] = ref;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
function _Foo_withRef_unoptimized(
|
||||
_$$empty_props_placeholder$$: $ReadOnly<{}>,
|
||||
ref: React.RefSetter<Controls>,
|
||||
): React.Node {
|
||||
return <Stringify ref={ref} />;
|
||||
}
|
||||
function Foo_withRef(arg0, arg1) {
|
||||
if (_isForgetEnabled_Fixtures_result)
|
||||
return _Foo_withRef_optimized(arg0, arg1);
|
||||
else return _Foo_withRef_unoptimized(arg0, arg1);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: eval("(...args) => React.createElement(Foo, args)"),
|
||||
params: [{ ref: React.createRef() }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"ref":null}</div>
|
||||
@@ -1,12 +0,0 @@
|
||||
// @flow @gating
|
||||
import {Stringify} from 'shared-runtime';
|
||||
import * as React from 'react';
|
||||
|
||||
component Foo(ref: React.RefSetter<Controls>) {
|
||||
return <Stringify ref={ref} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: eval('(...args) => React.createElement(Foo, args)'),
|
||||
params: [{ref: React.createRef()}],
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @flow @gating
|
||||
component Foo(ref: React.RefSetter<Controls>) {
|
||||
return <Bar ref={ref} />;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
1 | // @flow @gating
|
||||
> 2 | component Foo(ref: React.RefSetter<Controls>) {
|
||||
| ^^^ Invariant: Encountered a function used before its declaration, which breaks Forget's gating codegen due to hoisting. Rewrite the reference to Foo_withRef to not rely on hoisting to fix this issue (2:2)
|
||||
3 | return <Bar ref={ref} />;
|
||||
4 | }
|
||||
5 |
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
// @flow @gating
|
||||
component Foo(ref: React.RefSetter<Controls>) {
|
||||
return <Bar ref={ref} />;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @gating
|
||||
const Foo = React.forwardRef(Foo_withRef);
|
||||
function Foo_withRef(props, ref) {
|
||||
return <Bar ref={ref} {...props}></Bar>;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
1 | // @gating
|
||||
2 | const Foo = React.forwardRef(Foo_withRef);
|
||||
> 3 | function Foo_withRef(props, ref) {
|
||||
| ^^^^^^^^^^^ Invariant: Encountered a function used before its declaration, which breaks Forget's gating codegen due to hoisting. Rewrite the reference to Foo_withRef to not rely on hoisting to fix this issue (3:3)
|
||||
4 | return <Bar ref={ref} {...props}></Bar>;
|
||||
5 | }
|
||||
6 |
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
// @gating
|
||||
const Foo = React.forwardRef(Foo_withRef);
|
||||
function Foo_withRef(props, ref) {
|
||||
return <Bar ref={ref} {...props}></Bar>;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @gating
|
||||
import {memo} from 'react';
|
||||
|
||||
export default memo(Foo);
|
||||
function Foo() {}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
3 |
|
||||
4 | export default memo(Foo);
|
||||
> 5 | function Foo() {}
|
||||
| ^^^ Invariant: Encountered a function used before its declaration, which breaks Forget's gating codegen due to hoisting. Rewrite the reference to Foo to not rely on hoisting to fix this issue (5:5)
|
||||
6 |
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
// @gating
|
||||
import {memo} from 'react';
|
||||
|
||||
export default memo(Foo);
|
||||
function Foo() {}
|
||||
@@ -1,61 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @gating
|
||||
import {createRef, forwardRef} from 'react';
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
const Foo = forwardRef(Foo_withRef);
|
||||
function Foo_withRef(props, ref) {
|
||||
return <Stringify ref={ref} {...props} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: eval('(...args) => React.createElement(Foo, args)'),
|
||||
params: [{prop1: 1, prop2: 2, ref: createRef()}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
|
||||
import { c as _c } from "react/compiler-runtime"; // @gating
|
||||
import { createRef, forwardRef } from "react";
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
const Foo = forwardRef(Foo_withRef);
|
||||
const _isForgetEnabled_Fixtures_result = isForgetEnabled_Fixtures();
|
||||
function _Foo_withRef_optimized(props, ref) {
|
||||
const $ = _c(3);
|
||||
let t0;
|
||||
if ($[0] !== props || $[1] !== ref) {
|
||||
t0 = <Stringify ref={ref} {...props} />;
|
||||
$[0] = props;
|
||||
$[1] = ref;
|
||||
$[2] = t0;
|
||||
} else {
|
||||
t0 = $[2];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
function _Foo_withRef_unoptimized(props, ref) {
|
||||
return <Stringify ref={ref} {...props} />;
|
||||
}
|
||||
function Foo_withRef(arg0, arg1) {
|
||||
if (_isForgetEnabled_Fixtures_result)
|
||||
return _Foo_withRef_optimized(arg0, arg1);
|
||||
else return _Foo_withRef_unoptimized(arg0, arg1);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: eval("(...args) => React.createElement(Foo, args)"),
|
||||
params: [{ prop1: 1, prop2: 2, ref: createRef() }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"0":{"prop1":1,"prop2":2,"ref":{"current":null}},"ref":"[[ cyclic ref *3 ]]"}</div>
|
||||
@@ -1,13 +0,0 @@
|
||||
// @gating
|
||||
import {createRef, forwardRef} from 'react';
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
const Foo = forwardRef(Foo_withRef);
|
||||
function Foo_withRef(props, ref) {
|
||||
return <Stringify ref={ref} {...props} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: eval('(...args) => React.createElement(Foo, args)'),
|
||||
params: [{prop1: 1, prop2: 2, ref: createRef()}],
|
||||
};
|
||||
@@ -1,64 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @gating
|
||||
import {memo} from 'react';
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
export default memo(Foo);
|
||||
function Foo({prop1, prop2}) {
|
||||
'use memo';
|
||||
return <Stringify prop1={prop1} prop2={prop2} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: eval('Foo'),
|
||||
params: [{prop1: 1, prop2: 2}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
|
||||
import { c as _c } from "react/compiler-runtime"; // @gating
|
||||
import { memo } from "react";
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
export default memo(Foo);
|
||||
const _isForgetEnabled_Fixtures_result = isForgetEnabled_Fixtures();
|
||||
function _Foo_optimized(t0) {
|
||||
"use memo";
|
||||
const $ = _c(3);
|
||||
const { prop1, prop2 } = t0;
|
||||
let t1;
|
||||
if ($[0] !== prop1 || $[1] !== prop2) {
|
||||
t1 = <Stringify prop1={prop1} prop2={prop2} />;
|
||||
$[0] = prop1;
|
||||
$[1] = prop2;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
function _Foo_unoptimized({ prop1, prop2 }) {
|
||||
"use memo";
|
||||
return <Stringify prop1={prop1} prop2={prop2} />;
|
||||
}
|
||||
function Foo(arg0) {
|
||||
if (_isForgetEnabled_Fixtures_result) return _Foo_optimized(arg0);
|
||||
else return _Foo_unoptimized(arg0);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: eval("Foo"),
|
||||
params: [{ prop1: 1, prop2: 2 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"prop1":1,"prop2":2}</div>
|
||||
@@ -1,14 +0,0 @@
|
||||
// @gating
|
||||
import {memo} from 'react';
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
export default memo(Foo);
|
||||
function Foo({prop1, prop2}) {
|
||||
'use memo';
|
||||
return <Stringify prop1={prop1} prop2={prop2} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: eval('Foo'),
|
||||
params: [{prop1: 1, prop2: 2}],
|
||||
};
|
||||
@@ -1,59 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @gating
|
||||
import * as React from 'react';
|
||||
|
||||
let Foo;
|
||||
const MemoFoo = React.memo(Foo);
|
||||
Foo = () => <div>hello world!</div>;
|
||||
|
||||
/**
|
||||
* Evaluate this fixture module to assert that compiler + original have the same
|
||||
* runtime error message.
|
||||
*/
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: () => {},
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
|
||||
import { c as _c } from "react/compiler-runtime"; // @gating
|
||||
import * as React from "react";
|
||||
|
||||
let Foo;
|
||||
const MemoFoo = React.memo(Foo);
|
||||
Foo = isForgetEnabled_Fixtures()
|
||||
? () => {
|
||||
const $ = _c(1);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = <div>hello world!</div>;
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
: () => <div>hello world!</div>;
|
||||
|
||||
/**
|
||||
* Evaluate this fixture module to assert that compiler + original have the same
|
||||
* runtime error message.
|
||||
*/
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: isForgetEnabled_Fixtures() ? () => {} : () => {},
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok)
|
||||
logs: ['memo: The first argument must be a component. Instead received: %s','undefined']
|
||||
@@ -1,15 +0,0 @@
|
||||
// @gating
|
||||
import * as React from 'react';
|
||||
|
||||
let Foo;
|
||||
const MemoFoo = React.memo(Foo);
|
||||
Foo = () => <div>hello world!</div>;
|
||||
|
||||
/**
|
||||
* Evaluate this fixture module to assert that compiler + original have the same
|
||||
* runtime error message.
|
||||
*/
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: () => {},
|
||||
params: [],
|
||||
};
|
||||
@@ -1,86 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @gating
|
||||
import * as React from 'react';
|
||||
|
||||
/**
|
||||
* Test that the correct `Foo` is printed
|
||||
*/
|
||||
let Foo = () => <div>hello world 1!</div>;
|
||||
const MemoOne = React.memo(Foo);
|
||||
Foo = () => <div>hello world 2!</div>;
|
||||
const MemoTwo = React.memo(Foo);
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: () => {
|
||||
'use no memo';
|
||||
return (
|
||||
<>
|
||||
<MemoOne />
|
||||
<MemoTwo />
|
||||
</>
|
||||
);
|
||||
},
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
|
||||
import { c as _c } from "react/compiler-runtime"; // @gating
|
||||
import * as React from "react";
|
||||
|
||||
/**
|
||||
* Test that the correct `Foo` is printed
|
||||
*/
|
||||
let Foo = isForgetEnabled_Fixtures()
|
||||
? () => {
|
||||
const $ = _c(1);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = <div>hello world 1!</div>;
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
: () => <div>hello world 1!</div>;
|
||||
const MemoOne = React.memo(Foo);
|
||||
Foo = isForgetEnabled_Fixtures()
|
||||
? () => {
|
||||
const $ = _c(1);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = <div>hello world 2!</div>;
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
: () => <div>hello world 2!</div>;
|
||||
const MemoTwo = React.memo(Foo);
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: () => {
|
||||
"use no memo";
|
||||
return (
|
||||
<>
|
||||
<MemoOne />
|
||||
<MemoTwo />
|
||||
</>
|
||||
);
|
||||
},
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>hello world 1!</div><div>hello world 2!</div>
|
||||
@@ -1,23 +0,0 @@
|
||||
// @gating
|
||||
import * as React from 'react';
|
||||
|
||||
/**
|
||||
* Test that the correct `Foo` is printed
|
||||
*/
|
||||
let Foo = () => <div>hello world 1!</div>;
|
||||
const MemoOne = React.memo(Foo);
|
||||
Foo = () => <div>hello world 2!</div>;
|
||||
const MemoTwo = React.memo(Foo);
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: () => {
|
||||
'use no memo';
|
||||
return (
|
||||
<>
|
||||
<MemoOne />
|
||||
<MemoTwo />
|
||||
</>
|
||||
);
|
||||
},
|
||||
params: [],
|
||||
};
|
||||
@@ -1,26 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @compilationMode(infer) @panicThreshold(none)
|
||||
import useMyEffect from 'useEffectWrapper';
|
||||
|
||||
function nonReactFn(arg) {
|
||||
useMyEffect(() => [1, 2, arg]);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
3 |
|
||||
4 | function nonReactFn(arg) {
|
||||
> 5 | useMyEffect(() => [1, 2, arg]);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (5:5)
|
||||
6 | }
|
||||
7 |
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
// @inferEffectDependencies @compilationMode(infer) @panicThreshold(none)
|
||||
import useMyEffect from 'useEffectWrapper';
|
||||
|
||||
function nonReactFn(arg) {
|
||||
useMyEffect(() => [1, 2, arg]);
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @compilationMode(infer) @panicThreshold(none)
|
||||
import {useEffect} from 'react';
|
||||
|
||||
function nonReactFn(arg) {
|
||||
useEffect(() => [1, 2, arg]);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
3 |
|
||||
4 | function nonReactFn(arg) {
|
||||
> 5 | useEffect(() => [1, 2, arg]);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (5:5)
|
||||
6 | }
|
||||
7 |
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
// @inferEffectDependencies @compilationMode(infer) @panicThreshold(none)
|
||||
import {useEffect} from 'react';
|
||||
|
||||
function nonReactFn(arg) {
|
||||
useEffect(() => [1, 2, arg]);
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold(none)
|
||||
import {useEffect} from 'react';
|
||||
|
||||
/**
|
||||
* Error on non-inlined effect functions:
|
||||
* 1. From the effect hook callee's perspective, it only makes sense
|
||||
* to either
|
||||
* (a) never hard error (i.e. failing to infer deps is acceptable) or
|
||||
* (b) always hard error,
|
||||
* regardless of whether the callback function is an inline fn.
|
||||
* 2. (Technical detail) it's harder to support detecting cases in which
|
||||
* function (pre-Forget transform) was inline but becomes memoized
|
||||
*/
|
||||
function Component({foo}) {
|
||||
function f() {
|
||||
console.log(foo);
|
||||
}
|
||||
|
||||
// No inferred dep array, the argument is not a lambda
|
||||
useEffect(f);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
18 |
|
||||
19 | // No inferred dep array, the argument is not a lambda
|
||||
> 20 | useEffect(f);
|
||||
| ^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (20:20)
|
||||
21 | }
|
||||
22 |
|
||||
```
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
// @inferEffectDependencies @panicThreshold(none)
|
||||
import {useEffect} from 'react';
|
||||
|
||||
/**
|
||||
* Error on non-inlined effect functions:
|
||||
* 1. From the effect hook callee's perspective, it only makes sense
|
||||
* to either
|
||||
* (a) never hard error (i.e. failing to infer deps is acceptable) or
|
||||
* (b) always hard error,
|
||||
* regardless of whether the callback function is an inline fn.
|
||||
* 2. (Technical detail) it's harder to support detecting cases in which
|
||||
* function (pre-Forget transform) was inline but becomes memoized
|
||||
*/
|
||||
function Component({foo}) {
|
||||
function f() {
|
||||
console.log(foo);
|
||||
}
|
||||
|
||||
// No inferred dep array, the argument is not a lambda
|
||||
useEffect(f);
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold(none)
|
||||
import React from 'react';
|
||||
|
||||
function NonReactiveDepInEffect() {
|
||||
const obj = makeObject_Primitives();
|
||||
React.useEffect(() => print(obj));
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
4 | function NonReactiveDepInEffect() {
|
||||
5 | const obj = makeObject_Primitives();
|
||||
> 6 | React.useEffect(() => print(obj));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (6:6)
|
||||
7 | }
|
||||
8 |
|
||||
```
|
||||
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold(none)
|
||||
import {useSpecialEffect} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Note that a react compiler-based transform still has limitations on JS syntax.
|
||||
* We should surface these as actionable lint / build errors to devs.
|
||||
*/
|
||||
function Component({prop1}) {
|
||||
'use memo';
|
||||
useSpecialEffect(() => {
|
||||
try {
|
||||
console.log(prop1);
|
||||
} finally {
|
||||
console.log('exiting');
|
||||
}
|
||||
}, [prop1]);
|
||||
return <div>{prop1}</div>;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
8 | function Component({prop1}) {
|
||||
9 | 'use memo';
|
||||
> 10 | useSpecialEffect(() => {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
> 11 | try {
|
||||
| ^^^^^^^^^
|
||||
> 12 | console.log(prop1);
|
||||
| ^^^^^^^^^
|
||||
> 13 | } finally {
|
||||
| ^^^^^^^^^
|
||||
> 14 | console.log('exiting');
|
||||
| ^^^^^^^^^
|
||||
> 15 | }
|
||||
| ^^^^^^^^^
|
||||
> 16 | }, [prop1]);
|
||||
| ^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics.. (Bailout reason: Todo: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause (11:15)) (10:16)
|
||||
17 | return <div>{prop1}</div>;
|
||||
18 | }
|
||||
19 |
|
||||
```
|
||||
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
// @inferEffectDependencies @panicThreshold(none)
|
||||
import {useSpecialEffect} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Note that a react compiler-based transform still has limitations on JS syntax.
|
||||
* We should surface these as actionable lint / build errors to devs.
|
||||
*/
|
||||
function Component({prop1}) {
|
||||
'use memo';
|
||||
useSpecialEffect(() => {
|
||||
try {
|
||||
console.log(prop1);
|
||||
} finally {
|
||||
console.log('exiting');
|
||||
}
|
||||
}, [prop1]);
|
||||
return <div>{prop1}</div>;
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold(none)
|
||||
import {useEffect} from 'react';
|
||||
|
||||
function Component({propVal}) {
|
||||
'use no memo';
|
||||
useEffect(() => [propVal]);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
4 | function Component({propVal}) {
|
||||
5 | 'use no memo';
|
||||
> 6 | useEffect(() => [propVal]);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (6:6)
|
||||
7 | }
|
||||
8 |
|
||||
```
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
// @inferEffectDependencies @panicThreshold(none)
|
||||
import {useEffect} from 'react';
|
||||
|
||||
function Component({propVal}) {
|
||||
'use no memo';
|
||||
useEffect(() => [propVal]);
|
||||
}
|
||||
@@ -34,6 +34,13 @@ function Component({foo, bar}) {
|
||||
console.log(bar.qux);
|
||||
});
|
||||
|
||||
function f() {
|
||||
console.log(foo);
|
||||
}
|
||||
|
||||
// No inferred dep array, the argument is not a lambda
|
||||
useEffect(f);
|
||||
|
||||
useEffectWrapper(() => {
|
||||
console.log(foo);
|
||||
});
|
||||
@@ -51,7 +58,7 @@ import useEffectWrapper from "useEffectWrapper";
|
||||
const moduleNonReactive = 0;
|
||||
|
||||
function Component(t0) {
|
||||
const $ = _c(12);
|
||||
const $ = _c(14);
|
||||
const { foo, bar } = t0;
|
||||
|
||||
const ref = useRef(0);
|
||||
@@ -112,7 +119,7 @@ function Component(t0) {
|
||||
useEffect(t4, [bar.baz, bar.qux]);
|
||||
let t5;
|
||||
if ($[10] !== foo) {
|
||||
t5 = () => {
|
||||
t5 = function f() {
|
||||
console.log(foo);
|
||||
};
|
||||
$[10] = foo;
|
||||
@@ -120,7 +127,20 @@ function Component(t0) {
|
||||
} else {
|
||||
t5 = $[11];
|
||||
}
|
||||
useEffectWrapper(t5, [foo]);
|
||||
const f = t5;
|
||||
|
||||
useEffect(f);
|
||||
let t6;
|
||||
if ($[12] !== foo) {
|
||||
t6 = () => {
|
||||
console.log(foo);
|
||||
};
|
||||
$[12] = foo;
|
||||
$[13] = t6;
|
||||
} else {
|
||||
t6 = $[13];
|
||||
}
|
||||
useEffectWrapper(t6, [foo]);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
@@ -30,6 +30,13 @@ function Component({foo, bar}) {
|
||||
console.log(bar.qux);
|
||||
});
|
||||
|
||||
function f() {
|
||||
console.log(foo);
|
||||
}
|
||||
|
||||
// No inferred dep array, the argument is not a lambda
|
||||
useEffect(f);
|
||||
|
||||
useEffectWrapper(() => {
|
||||
console.log(foo);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies
|
||||
import React from 'react';
|
||||
|
||||
function NonReactiveDepInEffect() {
|
||||
const obj = makeObject_Primitives();
|
||||
React.useEffect(() => print(obj));
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies
|
||||
import React from "react";
|
||||
|
||||
function NonReactiveDepInEffect() {
|
||||
const $ = _c(2);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = makeObject_Primitives();
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
const obj = t0;
|
||||
let t1;
|
||||
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = () => print(obj);
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
React.useEffect(t1);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user