diff --git a/.editorconfig b/.editorconfig
index 07552cfff88ba..48d2b3d27e85a 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -8,11 +8,9 @@ indent_size = 2
indent_style = space
insert_final_newline = true
max_line_length = 80
-trim_trailing_whitespace = true
[*.md]
max_line_length = 0
-trim_trailing_whitespace = false
[COMMIT_EDITMSG]
max_line_length = 0
diff --git a/.eslintrc.js b/.eslintrc.js
index ed134392a9727..faeb5077f21a8 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -23,6 +23,7 @@ module.exports = {
'babel',
'ft-flow',
'jest',
+ 'es',
'no-for-of-loops',
'no-function-declare-after-return',
'react',
@@ -47,7 +48,7 @@ module.exports = {
'ft-flow/no-unused-expressions': ERROR,
// 'ft-flow/no-weak-types': WARNING,
// 'ft-flow/require-valid-file-annotation': ERROR,
-
+ 'es/no-optional-chaining': ERROR,
'no-cond-assign': OFF,
'no-constant-condition': OFF,
'no-control-regex': OFF,
@@ -435,6 +436,7 @@ module.exports = {
'packages/react-dom/src/test-utils/*.js',
],
rules: {
+ 'es/no-optional-chaining': OFF,
'react-internal/no-production-logging': OFF,
'react-internal/warning-args': OFF,
'react-internal/safe-string-coercion': [
@@ -567,6 +569,7 @@ module.exports = {
React$Node: 'readonly',
React$Portal: 'readonly',
React$Ref: 'readonly',
+ React$RefSetter: 'readonly',
ReadableStreamController: 'readonly',
ReadableStreamReader: 'readonly',
RequestInfo: 'readonly',
diff --git a/.github/ISSUE_TEMPLATE/compiler_bug_report.yml b/.github/ISSUE_TEMPLATE/compiler_bug_report.yml
index fc7952fd5a80f..233201d3f5b1a 100644
--- a/.github/ISSUE_TEMPLATE/compiler_bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/compiler_bug_report.yml
@@ -55,3 +55,10 @@ body:
Please provide your React version in the app where this issue occurred.
validations:
required: true
+- type: input
+ attributes:
+ label: What version of React Compiler are you using?
+ description: |
+ Please provide the exact React Compiler version in the app where this issue occurred.
+ validations:
+ required: true
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index b436c26c50c29..11800ad757595 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -1,107 +1,10 @@
version: 2
updates:
- package-ecosystem: "npm"
- directory: "/fixtures/art"
+ directories:
+ - "/fixtures/*"
schedule:
- interval: "weekly"
- open-pull-requests-limit: 0
- - package-ecosystem: "npm"
- directory: "/fixtures/attribute-behavior"
- schedule:
- interval: "weekly"
- open-pull-requests-limit: 0
- - package-ecosystem: "npm"
- directory: "/fixtures/concurrent"
- schedule:
- interval: "weekly"
- open-pull-requests-limit: 0
- - package-ecosystem: "npm"
- directory: "/fixtures/devtools"
- schedule:
- interval: "weekly"
- open-pull-requests-limit: 0
- - package-ecosystem: "npm"
- directory: "/fixtures/dom"
- schedule:
- interval: "weekly"
- open-pull-requests-limit: 0
- - package-ecosystem: "npm"
- directory: "/fixtures/eslint"
- schedule:
- interval: "weekly"
- open-pull-requests-limit: 0
- - package-ecosystem: "npm"
- directory: "/fixtures/expiration"
- schedule:
- interval: "weekly"
- open-pull-requests-limit: 0
- - package-ecosystem: "npm"
- directory: "/fixtures/fiber-debugger"
- schedule:
- interval: "weekly"
- open-pull-requests-limit: 0
- - package-ecosystem: "npm"
- directory: "/fixtures/fiber-triangle"
- schedule:
- interval: "weekly"
- open-pull-requests-limit: 0
- - package-ecosystem: "npm"
- directory: "/fixtures/fizz"
- schedule:
- interval: "weekly"
- open-pull-requests-limit: 0
- - package-ecosystem: "npm"
- directory: "/fixtures/fizz-ssr-browser"
- schedule:
- interval: "weekly"
- open-pull-requests-limit: 0
- - package-ecosystem: "npm"
- directory: "/fixtures/flight"
- schedule:
- interval: "weekly"
- open-pull-requests-limit: 0
- - package-ecosystem: "npm"
- directory: "/fixtures/flight-browser"
- schedule:
- interval: "weekly"
- open-pull-requests-limit: 0
- - package-ecosystem: "npm"
- directory: "/fixtures/flight-esm"
- schedule:
- interval: "weekly"
- open-pull-requests-limit: 0
- - package-ecosystem: "npm"
- directory: "/fixtures/legacy-jsx-runtimes"
- schedule:
- interval: "weekly"
- open-pull-requests-limit: 0
- - package-ecosystem: "npm"
- directory: "/fixtures/nesting"
- schedule:
- interval: "weekly"
- open-pull-requests-limit: 0
- - package-ecosystem: "npm"
- directory: "/fixtures/packaging"
- schedule:
- interval: "weekly"
- open-pull-requests-limit: 0
- - package-ecosystem: "npm"
- directory: "/fixtures/scheduler"
- schedule:
- interval: "weekly"
- open-pull-requests-limit: 0
- - package-ecosystem: "npm"
- directory: "/fixtures/ssr"
- schedule:
- interval: "weekly"
- open-pull-requests-limit: 0
- - package-ecosystem: "npm"
- directory: "/fixtures/ssr-2"
- schedule:
- interval: "weekly"
- open-pull-requests-limit: 0
- - package-ecosystem: "npm"
- directory: "/fixtures/stacks"
- schedule:
- interval: "weekly"
+ interval: "monthly"
open-pull-requests-limit: 0
+ ignore:
+ - dependency-name: "*"
diff --git a/.github/workflows/compiler_playground.yml b/.github/workflows/compiler_playground.yml
index a96950ccc0b2a..81c6c3ee42a27 100644
--- a/.github/workflows/compiler_playground.yml
+++ b/.github/workflows/compiler_playground.yml
@@ -6,7 +6,11 @@ on:
pull_request:
paths:
- compiler/**
- - .github/workflows/compiler-playground.yml
+ - .github/workflows/compiler_playground.yml
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
+ cancel-in-progress: true
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
@@ -15,7 +19,7 @@ env:
defaults:
run:
- working-directory: compiler
+ working-directory: compiler/apps/playground
jobs:
playground:
@@ -27,13 +31,25 @@ jobs:
with:
node-version-file: '.nvmrc'
cache: yarn
- cache-dependency-path: compiler/yarn.lock
+ cache-dependency-path: compiler/**/yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
- key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('compiler/**/yarn.lock') }}
- - run: yarn install --frozen-lockfile
+ key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
+ - name: yarn install compiler
+ run: yarn install --frozen-lockfile
+ working-directory: compiler
+ - name: yarn install playground
+ run: yarn install --frozen-lockfile
- run: npx playwright install --with-deps chromium
- - run: yarn workspace playground test
+ - run: CI=true yarn test
+ - run: ls -R test-results
+ if: '!cancelled()'
+ - name: Archive test results
+ if: '!cancelled()'
+ uses: actions/upload-artifact@v4
+ with:
+ name: test-results
+ path: compiler/apps/playground/test-results
diff --git a/.github/workflows/compiler_prereleases.yml b/.github/workflows/compiler_prereleases.yml
index 25ad6adde8861..4f4954dd952b4 100644
--- a/.github/workflows/compiler_prereleases.yml
+++ b/.github/workflows/compiler_prereleases.yml
@@ -13,6 +13,9 @@ on:
dist_tag:
required: true
type: string
+ version_name:
+ required: true
+ type: string
secrets:
NPM_TOKEN:
required: true
@@ -44,9 +47,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
- key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('compiler/**/yarn.lock') }}
+ key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- name: Publish packages to npm
run: |
cp ./scripts/release/ci-npmrc ~/.npmrc
- scripts/release/publish.js --frfr --ci --tags ${{ inputs.dist_tag }}
+ scripts/release/publish.js --frfr --ci --versionName=${{ inputs.version_name }} --tag ${{ inputs.dist_tag }}
diff --git a/.github/workflows/compiler_prereleases_manual.yml b/.github/workflows/compiler_prereleases_manual.yml
index 87de411ddcb6e..3e42ae2cf200d 100644
--- a/.github/workflows/compiler_prereleases_manual.yml
+++ b/.github/workflows/compiler_prereleases_manual.yml
@@ -5,6 +5,15 @@ on:
inputs:
prerelease_commit_sha:
required: false
+ release_channel:
+ required: true
+ type: string
+ dist_tag:
+ required: true
+ type: string
+ version_name:
+ required: true
+ type: string
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
@@ -15,7 +24,8 @@ jobs:
uses: facebook/react/.github/workflows/compiler_prereleases.yml@main
with:
commit_sha: ${{ inputs.prerelease_commit_sha || github.sha }}
- release_channel: experimental
- dist_tag: experimental
+ release_channel: ${{ inputs.release_channel }}
+ dist_tag: ${{ inputs.dist_tag }}
+ version_name: ${{ inputs.version_name }}
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
diff --git a/.github/workflows/compiler_prereleases_nightly.yml b/.github/workflows/compiler_prereleases_nightly.yml
index 90aafac4f631f..82f893aa5ef43 100644
--- a/.github/workflows/compiler_prereleases_nightly.yml
+++ b/.github/workflows/compiler_prereleases_nightly.yml
@@ -16,5 +16,6 @@ jobs:
commit_sha: ${{ github.sha }}
release_channel: experimental
dist_tag: experimental
+ version_name: '0.0.0'
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
diff --git a/.github/workflows/compiler_prereleases_weekly.yml b/.github/workflows/compiler_prereleases_weekly.yml
new file mode 100644
index 0000000000000..79a9451b6972a
--- /dev/null
+++ b/.github/workflows/compiler_prereleases_weekly.yml
@@ -0,0 +1,21 @@
+name: (Compiler) Publish Prereleases Weekly
+
+on:
+ schedule:
+ # At 10 minutes past 9:00 on Mon
+ - cron: 10 9 * * 1
+
+env:
+ TZ: /usr/share/zoneinfo/America/Los_Angeles
+
+jobs:
+ publish_prerelease_beta:
+ name: Publish to beta channel
+ uses: facebook/react/.github/workflows/compiler_prereleases.yml@main
+ with:
+ commit_sha: ${{ github.sha }}
+ release_channel: beta
+ dist_tag: beta
+ version_name: '19.0.0'
+ secrets:
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
diff --git a/.github/workflows/compiler_rust.yml b/.github/workflows/compiler_rust.yml
index 3cda1325ca49e..50d42a37763e3 100644
--- a/.github/workflows/compiler_rust.yml
+++ b/.github/workflows/compiler_rust.yml
@@ -15,6 +15,10 @@ on:
- compiler/Cargo.*
- compiler/*.toml
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
+ cancel-in-progress: true
+
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: -Dwarnings
diff --git a/.github/workflows/compiler_typescript.yml b/.github/workflows/compiler_typescript.yml
index 6fc00047a16f2..7ec6297038479 100644
--- a/.github/workflows/compiler_typescript.yml
+++ b/.github/workflows/compiler_typescript.yml
@@ -8,6 +8,10 @@ on:
- compiler/**
- .github/workflows/compiler_typescript.yml
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
+ cancel-in-progress: true
+
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
@@ -43,7 +47,7 @@ jobs:
uses: actions/cache@v4
with:
path: "**/node_modules"
- key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('compiler/**/yarn.lock') }}
+ 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
@@ -63,7 +67,7 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
- key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('compiler/**/yarn.lock') }}
+ 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
@@ -87,6 +91,6 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
- key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('compiler/**/yarn.lock') }}
+ key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn workspace ${{ matrix.workspace_name }} test
diff --git a/.github/workflows/devtools_regression_tests.yml b/.github/workflows/devtools_regression_tests.yml
index 1183d062d1abe..64d6707aa3688 100644
--- a/.github/workflows/devtools_regression_tests.yml
+++ b/.github/workflows/devtools_regression_tests.yml
@@ -30,7 +30,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
- key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
+ 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 install --frozen-lockfile
working-directory: scripts/release
@@ -62,7 +64,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
- key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
+ 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
- name: Restore archived build
uses: actions/download-artifact@v4
@@ -103,6 +107,7 @@ jobs:
- "16.8" # hooks
- "17.0"
- "18.0"
+ - "18.2" # compiler polyfill
continue-on-error: true
steps:
- uses: actions/checkout@v4
@@ -116,7 +121,7 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
- key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
+ 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
@@ -150,7 +155,7 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
- key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
+ 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
diff --git a/.github/workflows/runtime_build_and_test.yml b/.github/workflows/runtime_build_and_test.yml
index 1f780d4ee37c4..b9cbe4d447bbb 100644
--- a/.github/workflows/runtime_build_and_test.yml
+++ b/.github/workflows/runtime_build_and_test.yml
@@ -7,6 +7,10 @@ on:
paths-ignore:
- compiler/**
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
+ cancel-in-progress: true
+
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
@@ -48,7 +52,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
- key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
+ 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: node ./scripts/tasks/flow-ci ${{ matrix.flow_inline_config_shortname }}
@@ -68,7 +74,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
- key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
+ 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 generate-inline-fizz-runtime
@@ -90,7 +98,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
- key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
+ 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 flags
@@ -139,7 +149,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
- key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
+ 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 }}
@@ -168,7 +180,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
- key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
+ 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 build --index=${{ matrix.worker_id }} --total=20 --r=${{ matrix.release_channel }} --ci
env:
@@ -238,7 +252,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
- key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
+ 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
- name: Restore archived build
uses: actions/download-artifact@v4
@@ -266,7 +282,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
- key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
+ 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
- name: Restore archived build
uses: actions/download-artifact@v4
@@ -309,7 +327,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
- key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
+ 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
- name: Restore archived build
uses: actions/download-artifact@v4
@@ -340,7 +360,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
- key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
+ 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
- name: Restore archived build
uses: actions/download-artifact@v4
@@ -368,7 +390,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
- key: v2-yarn_cache_fixtures_dom-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
+ key: fixtures_dom-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 install --frozen-lockfile --cache-folder ~/.cache/yarn
working-directory: fixtures/dom
@@ -408,7 +432,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
- key: v2-yarn_cache_fixtures_flight-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
+ 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
- name: Restore archived build
uses: actions/download-artifact@v4
@@ -464,7 +490,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
- key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
+ 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
- name: Restore archived build
uses: actions/download-artifact@v4
@@ -510,7 +538,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
- key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
+ 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
- name: Restore archived build
uses: actions/download-artifact@v4
@@ -543,6 +573,8 @@ jobs:
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
+ - name: Ensure clean build directory
+ run: rm -rf build
- run: yarn install --frozen-lockfile
- run: yarn install --frozen-lockfile
working-directory: scripts/release
@@ -581,7 +613,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
- key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
+ 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
- name: Restore archived build for PR
uses: actions/download-artifact@v4
diff --git a/.github/workflows/runtime_commit_artifacts.yml b/.github/workflows/runtime_commit_artifacts.yml
index 9f786b9b5eccc..4315256affe45 100644
--- a/.github/workflows/runtime_commit_artifacts.yml
+++ b/.github/workflows/runtime_commit_artifacts.yml
@@ -11,6 +11,11 @@ on:
commit_sha:
required: false
type: string
+ force:
+ description: 'Force a commit to the builds/... branches'
+ required: true
+ default: false
+ type: boolean
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
@@ -69,7 +74,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
- key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
+ 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
@@ -77,7 +84,7 @@ jobs:
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 }}
+ 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
@@ -85,6 +92,11 @@ jobs:
sed -i -e 's/ @license React*//' \
build/oss-experimental/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js \
build/oss-experimental/react-refresh/cjs/react-refresh-babel.development.js
+ - name: Insert @headers into eslint plugin and react-refresh
+ run: |
+ sed -i -e 's/ LICENSE file in the root directory of this source tree./ LICENSE file in the root directory of this source tree.\n *\n * @noformat\n * @nolint\n * @lightSyntaxTransform\n * @preventMunge\n * @oncall react_core/' \
+ build/oss-experimental/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js \
+ build/oss-experimental/react-refresh/cjs/react-refresh-babel.development.js
- name: Move relevant files for React in www into compiled
run: |
# Move the facebook-www folder into compiled
@@ -113,13 +125,14 @@ jobs:
run: |
BASE_FOLDER='compiled-rn/facebook-fbsource/xplat/js'
mkdir -p ${BASE_FOLDER}/react-native-github/Libraries/Renderer/
- mkdir -p ${BASE_FOLDER}/RKJSModules/vendor/react/{scheduler,react,react-is,react-test-renderer}/
+ mkdir -p ${BASE_FOLDER}/RKJSModules/vendor/react/{scheduler,react,react-dom,react-is,react-test-renderer}/
# Move React Native renderer
mv build/react-native/implementations/ $BASE_FOLDER/react-native-github/Libraries/Renderer/
mv build/react-native/shims/ $BASE_FOLDER/react-native-github/Libraries/Renderer/
mv build/facebook-react-native/scheduler/cjs/ $BASE_FOLDER/RKJSModules/vendor/react/scheduler/
mv build/facebook-react-native/react/cjs/ $BASE_FOLDER/RKJSModules/vendor/react/react/
+ mv build/facebook-react-native/react-dom/cjs/ $BASE_FOLDER/RKJSModules/vendor/react/react-dom/
mv build/facebook-react-native/react-is/cjs/ $BASE_FOLDER/RKJSModules/vendor/react/react-is/
mv build/facebook-react-native/react-test-renderer/cjs/ $BASE_FOLDER/RKJSModules/vendor/react/react-test-renderer/
@@ -134,9 +147,9 @@ jobs:
ls -R ./compiled-rn
- name: Add REVISION files
run: |
- echo ${{ github.event.workflow_run.head_sha }} >> ./compiled/facebook-www/REVISION
+ echo ${{ github.sha }} >> ./compiled/facebook-www/REVISION
cp ./compiled/facebook-www/REVISION ./compiled/facebook-www/REVISION_TRANSFORMS
- echo ${{ github.event.workflow_run.head_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: |
@@ -160,7 +173,7 @@ jobs:
commit_www_artifacts:
needs: download_artifacts
- if: ${{ (github.ref == 'refs/heads/main' && needs.download_artifacts.outputs.www_branch_count == '0') || github.ref == 'refs/heads/meta-www' }}
+ if: inputs.force == true || (github.ref == 'refs/heads/main' && needs.download_artifacts.outputs.www_branch_count == '0')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -190,6 +203,7 @@ jobs:
grep -rl "$CURRENT_VERSION_MODERN" ./compiled | xargs -r sed -i -e "s/$CURRENT_VERSION_MODERN/$LAST_VERSION_MODERN/g"
grep -rl "$CURRENT_VERSION_MODERN" ./compiled || echo "Modern version reverted"
- name: Check for changes
+ if: inputs.force != true
id: check_should_commit
run: |
echo "Full git status"
@@ -207,7 +221,7 @@ jobs:
echo "should_commit=false" >> "$GITHUB_OUTPUT"
fi
- name: Re-apply version changes
- if: steps.check_should_commit.outputs.should_commit == 'true' && needs.download_artifacts.outputs.last_version_classic != '' && needs.download_artifacts.outputs.last_version_modern != ''
+ if: inputs.force == true || (steps.check_should_commit.outputs.should_commit == 'true' && needs.download_artifacts.outputs.last_version_classic != '' && needs.download_artifacts.outputs.last_version_modern != '')
env:
CURRENT_VERSION_CLASSIC: ${{ needs.download_artifacts.outputs.current_version_classic }}
CURRENT_VERSION_MODERN: ${{ needs.download_artifacts.outputs.current_version_modern }}
@@ -224,26 +238,26 @@ jobs:
grep -rl "$LAST_VERSION_MODERN" ./compiled | xargs -r sed -i -e "s/$LAST_VERSION_MODERN/$CURRENT_VERSION_MODERN/g"
grep -rl "$LAST_VERSION_MODERN" ./compiled || echo "Classic version re-applied"
- name: Will commit these changes
- if: steps.check_should_commit.outputs.should_commit == 'true'
+ if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
run: |
echo ":"
git status -u
- name: Commit changes to branch
- if: steps.check_should_commit.outputs.should_commit == 'true'
- uses: stefanzweifel/git-auto-commit-action@v4
+ if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
+ uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: |
- ${{ github.event.workflow_run.head_commit.message }}
+ ${{ github.event.workflow_run.head_commit.message || format('Manual build of {0}', github.event.workflow_run.head_sha || github.sha) }}
- DiffTrain build for [${{ github.event.workflow_run.head_sha }}](https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha }})
+ DiffTrain build for [${{ github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha || github.sha }})
branch: builds/facebook-www
- commit_user_name: ${{ github.event.workflow_run.triggering_actor.login }}
- commit_user_email: ${{ github.event.workflow_run.triggering_actor.email || format('{0}@users.noreply.github.com', github.event.workflow_run.triggering_actor.login) }}
+ 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
- if: ${{ (github.ref == 'refs/heads/main' && needs.download_artifacts.outputs.fbsource_branch_count == '0') || github.ref == 'refs/heads/meta-fbsource' }}
+ 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
@@ -266,6 +280,7 @@ jobs:
grep -rl "$CURRENT_VERSION" ./compiled-rn | xargs -r sed -i -e "s/$CURRENT_VERSION/$LAST_VERSION/g"
grep -rl "$CURRENT_VERSION" ./compiled-rn || echo "Version reverted"
- name: Check for changes
+ if: inputs.force != 'true'
id: check_should_commit
run: |
echo "Full git status"
@@ -284,7 +299,7 @@ jobs:
echo "should_commit=false" >> "$GITHUB_OUTPUT"
fi
- name: Re-apply version changes
- if: steps.check_should_commit.outputs.should_commit == 'true' && needs.download_artifacts.outputs.last_version_rn != ''
+ if: inputs.force == true || (steps.check_should_commit.outputs.should_commit == 'true' && needs.download_artifacts.outputs.last_version_rn != '')
env:
CURRENT_VERSION: ${{ needs.download_artifacts.outputs.current_version_rn }}
LAST_VERSION: ${{ needs.download_artifacts.outputs.last_version_rn }}
@@ -294,12 +309,12 @@ jobs:
grep -rl "$LAST_VERSION" ./compiled-rn | xargs -r sed -i -e "s/$LAST_VERSION/$CURRENT_VERSION/g"
grep -rl "$LAST_VERSION" ./compiled-rn || echo "Version re-applied"
- name: Add files for signing
- if: steps.check_should_commit.outputs.should_commit == 'true'
+ if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
run: |
echo ":"
git add .
- name: Signing files
- if: steps.check_should_commit.outputs.should_commit == 'true'
+ if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
uses: actions/github-script@v7
with:
script: |
@@ -353,8 +368,9 @@ jobs:
console.log('Signing files in directory:', directory);
try {
const result = execSync(`git status --porcelain ${directory}`, {encoding: 'utf8'});
+ console.log(result);
- // Parse the git status output to get file paths
+ // Parse the git status output to get file paths!
const files = result.split('\n').filter(file => file.endsWith('.js'));
if (files.length === 0) {
@@ -363,7 +379,14 @@ jobs:
);
} else {
files.forEach(line => {
- const file = line.slice(3).trim();
+ let file = null;
+ if (line.startsWith('D ')) {
+ return;
+ } else if (line.startsWith('R ')) {
+ file = line.slice(line.indexOf('->') + 3);
+ } else {
+ file = line.slice(3).trim();
+ }
if (file) {
console.log(' Signing file:', file);
const originalContents = fs.readFileSync(file, 'utf8');
@@ -382,19 +405,19 @@ jobs:
console.error('Error signing files:', e);
}
- name: Will commit these changes
- if: steps.check_should_commit.outputs.should_commit == 'true'
+ if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
run: |
git add .
git status
- name: Commit changes to branch
- if: steps.check_should_commit.outputs.should_commit == 'true'
- uses: stefanzweifel/git-auto-commit-action@v4
+ if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
+ uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: |
- ${{ github.event.workflow_run.head_commit.message }}
+ ${{ github.event.workflow_run.head_commit.message || format('Manual build of {0}', github.event.workflow_run.head_sha || github.sha) }}
- DiffTrain build for commit https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha }}.
+ DiffTrain build for [${{ github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha || github.sha }})
branch: builds/facebook-fbsource
- commit_user_name: ${{ github.event.workflow_run.triggering_actor.login }}
- commit_user_email: ${{ github.event.workflow_run.triggering_actor.email || format('{0}@users.noreply.github.com', github.event.workflow_run.triggering_actor.login) }}
+ commit_user_name: ${{ github.triggering_actor }}
+ commit_user_email: ${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}
create_branch: true
diff --git a/.github/workflows/runtime_prereleases.yml b/.github/workflows/runtime_prereleases.yml
index 0f3f79432b5c2..e52bed6bb16bf 100644
--- a/.github/workflows/runtime_prereleases.yml
+++ b/.github/workflows/runtime_prereleases.yml
@@ -40,7 +40,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
- key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
+ 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 install --frozen-lockfile
working-directory: scripts/release
diff --git a/.github/workflows/runtime_prereleases_manual.yml b/.github/workflows/runtime_prereleases_manual.yml
index 4d031e7b4d69d..4c25ddc79bd35 100644
--- a/.github/workflows/runtime_prereleases_manual.yml
+++ b/.github/workflows/runtime_prereleases_manual.yml
@@ -27,7 +27,7 @@ jobs:
# because this used to be called the "next" channel and some
# downstream consumers might still expect that tag. We can remove this
# after some time has elapsed and the change has been communicated.
- dist_tag: canary,next,rc
+ dist_tag: canary,next
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
diff --git a/.github/workflows/runtime_prereleases_nightly.yml b/.github/workflows/runtime_prereleases_nightly.yml
index a4c2c2d0225b5..fe038042f332c 100644
--- a/.github/workflows/runtime_prereleases_nightly.yml
+++ b/.github/workflows/runtime_prereleases_nightly.yml
@@ -15,7 +15,7 @@ jobs:
with:
commit_sha: ${{ github.sha }}
release_channel: stable
- dist_tag: canary,next,rc
+ dist_tag: canary,next
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
diff --git a/.github/workflows/shared_lint.yml b/.github/workflows/shared_lint.yml
index 31834c3fb9688..36c1df9eb86f9 100644
--- a/.github/workflows/shared_lint.yml
+++ b/.github/workflows/shared_lint.yml
@@ -5,6 +5,10 @@ on:
branches: [main]
pull_request:
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
+ cancel-in-progress: true
+
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
@@ -25,7 +29,9 @@ jobs:
uses: actions/cache@v4
with:
path: "**/node_modules"
- key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
+ 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
- run: yarn prettier-check
@@ -43,7 +49,9 @@ jobs:
uses: actions/cache@v4
with:
path: "**/node_modules"
- key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
+ 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
- run: node ./scripts/tasks/eslint
@@ -61,7 +69,9 @@ jobs:
uses: actions/cache@v4
with:
path: "**/node_modules"
- key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
+ 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
- run: ./scripts/ci/check_license.sh
@@ -79,6 +89,8 @@ jobs:
uses: actions/cache@v4
with:
path: "**/node_modules"
- key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
+ 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
- run: ./scripts/ci/test_print_warnings.sh
diff --git a/.prettierignore b/.prettierignore
index ecb518e4f0f6e..7e09af76a3af8 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -24,6 +24,7 @@ compiler/**/.next
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-kitchensink.js
compiler/crates
+compiler/target
compiler/apps/playground/public
compiler/**/LICENSE
diff --git a/ReactVersions.js b/ReactVersions.js
index 736d6c8f54717..ead100384e8bc 100644
--- a/ReactVersions.js
+++ b/ReactVersions.js
@@ -30,7 +30,7 @@ const canaryChannelLabel = 'rc';
// If the canaryChannelLabel is "rc", the build pipeline will use this to build
// an RC version of the packages.
-const rcNumber = 0;
+const rcNumber = 1;
const stablePackages = {
'eslint-plugin-react-hooks': '5.1.0',
@@ -52,7 +52,7 @@ const stablePackages = {
// These packages do not exist in the @canary or @latest channel, only
// @experimental. We don't use semver, just the commit sha, so this is just a
// list of package names instead of a map.
-const experimentalPackages = [];
+const experimentalPackages = ['react-markup'];
module.exports = {
ReactVersion,
diff --git a/compiler/apps/playground/README.md b/compiler/apps/playground/README.md
index 0a2fef224a0b0..7b6d5f729ad96 100644
--- a/compiler/apps/playground/README.md
+++ b/compiler/apps/playground/README.md
@@ -26,6 +26,13 @@ $ npm run dev
$ yarn
```
+## Testing
+```sh
+# Install playwright browser binaries
+$ npx playwright install --with-deps
+# Run tests
+$ yarn test
+```
## Deployment
This project has been deployed using Vercel. Vercel does the exact same thing as we would
diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt
new file mode 100644
index 0000000000000..ba680bbb57232
--- /dev/null
+++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt
@@ -0,0 +1,13 @@
+function TestComponent(t0) {
+ const $ = _c(2);
+ const { x } = t0;
+ let t1;
+ if ($[0] !== x) {
+ t1 = ;
+ $[0] = x;
+ $[1] = t1;
+ } else {
+ t1 = $[1];
+ }
+ return t1;
+}
diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt
new file mode 100644
index 0000000000000..2cbd09bba6179
--- /dev/null
+++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt
@@ -0,0 +1,11 @@
+function MyApp() {
+ const $ = _c(1);
+ let t0;
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
+ t0 =
Hello World
;
+ $[0] = t0;
+ } else {
+ t0 = $[0];
+ }
+ return t0;
+}
diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/default-input.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/default-input.txt
deleted file mode 100644
index a26ec5baaf5ab..0000000000000
--- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/default-input.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-function MyApp() {
- const $ = _c(1);
- let t0;
- if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
- t0 = Hello World
;
- $[0] = t0;
- } else {
- t0 = $[0];
- }
- return t0;
-}
\ No newline at end of file
diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/function-scope-beats-module-scope-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/function-scope-beats-module-scope-output.txt
new file mode 100644
index 0000000000000..5bcfcb75edd18
--- /dev/null
+++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/function-scope-beats-module-scope-output.txt
@@ -0,0 +1,14 @@
+function TestComponent(t0) {
+ "use memo";
+ const $ = _c(2);
+ const { x } = t0;
+ let t1;
+ if ($[0] !== x) {
+ t1 = ;
+ $[0] = x;
+ $[1] = t1;
+ } else {
+ t1 = $[1];
+ }
+ return t1;
+}
diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt
new file mode 100644
index 0000000000000..ba680bbb57232
--- /dev/null
+++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt
@@ -0,0 +1,13 @@
+function TestComponent(t0) {
+ const $ = _c(2);
+ const { x } = t0;
+ let t1;
+ if ($[0] !== x) {
+ t1 = ;
+ $[0] = x;
+ $[1] = t1;
+ } else {
+ t1 = $[1];
+ }
+ return t1;
+}
diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt
new file mode 100644
index 0000000000000..2c69ddc1d65b8
--- /dev/null
+++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt
@@ -0,0 +1,3 @@
+function TestComponent({ x }) {
+ return ;
+}
diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt
new file mode 100644
index 0000000000000..804bacab97e05
--- /dev/null
+++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt
@@ -0,0 +1,28 @@
+function TestComponent(t0) {
+ "use memo";
+ const $ = _c(2);
+ const { x } = t0;
+ let t1;
+ if ($[0] !== x) {
+ t1 = ;
+ $[0] = x;
+ $[1] = t1;
+ } else {
+ t1 = $[1];
+ }
+ return t1;
+}
+function anonymous_1(t0) {
+ "use memo";
+ const $ = _c(2);
+ const { x } = t0;
+ let t1;
+ if ($[0] !== x) {
+ t1 = ;
+ $[0] = x;
+ $[1] = t1;
+ } else {
+ t1 = $[1];
+ }
+ return t1;
+}
diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt
new file mode 100644
index 0000000000000..5fb66309fc70c
--- /dev/null
+++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt
@@ -0,0 +1,8 @@
+function anonymous_1() {
+ "use no memo";
+ return ;
+}
+function anonymous_3({ x }) {
+ "use no memo";
+ return ;
+}
diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/user-input.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/user-input.txt
deleted file mode 100644
index f45eb2c625c40..0000000000000
--- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/user-input.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-function TestComponent(t0) {
- const $ = _c(2);
- const { x } = t0;
- let t1;
- if ($[0] !== x) {
- t1 = ;
- $[0] = x;
- $[1] = t1;
- } else {
- t1 = $[1];
- }
- return t1;
-}
\ No newline at end of file
diff --git a/compiler/apps/playground/__tests__/e2e/page.spec.ts b/compiler/apps/playground/__tests__/e2e/page.spec.ts
index bc93352a0992a..846e6227bd1a1 100644
--- a/compiler/apps/playground/__tests__/e2e/page.spec.ts
+++ b/compiler/apps/playground/__tests__/e2e/page.spec.ts
@@ -7,43 +7,137 @@
import {expect, test} from '@playwright/test';
import {encodeStore, type Store} from '../../lib/stores';
+import {format} from 'prettier';
-const STORE: Store = {
- source: `export default function TestComponent({ x }) {
- return ;
+function print(data: Array): Promise {
+ return format(data.join(''), {parser: 'babel'});
}
-`,
-};
-const HASH = encodeStore(STORE);
-function concat(data: Array): string {
- return data.join('');
+const DIRECTIVE_TEST_CASES = [
+ {
+ name: 'module-scope-use-memo',
+ input: `
+'use memo';
+export default function TestComponent({ x }) {
+ return ;
+}`,
+ },
+ {
+ name: 'module-scope-use-no-memo',
+ input: `
+'use no memo';
+export default function TestComponent({ x }) {
+ return ;
+}`,
+ },
+ {
+ name: 'use-memo',
+ input: `
+function TestComponent({ x }) {
+ 'use memo';
+ return ;
}
+const TestComponent2 = ({ x }) => {
+ 'use memo';
+ return ;
+};`,
+ },
+ {
+ name: 'use-no-memo',
+ input: `
+const TestComponent = function() {
+ 'use no memo';
+ return ;
+};
+const TestComponent2 = ({ x }) => {
+ 'use no memo';
+ return ;
+};`,
+ },
+ {
+ name: 'function-scope-beats-module-scope',
+ input: `
+'use no memo';
+function TestComponent({ x }) {
+ 'use memo';
+ return ;
+}`,
+ },
+];
-test('editor should compile successfully', async ({page}) => {
- await page.goto(`/#${HASH}`, {waitUntil: 'networkidle'});
+test('editor should open successfully', async ({page}) => {
+ await page.goto(`/`, {waitUntil: 'networkidle'});
await page.screenshot({
fullPage: true,
- path: 'test-results/00-on-networkidle.png',
+ path: 'test-results/00-fresh-page.png',
});
+});
+
+test('editor should compile from hash successfully', async ({page}) => {
+ const store: Store = {
+ source: `export default function TestComponent({ x }) {
+ return ;
+ }
+ `,
+ };
+ const hash = encodeStore(store);
+ await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});
// User input from hash compiles
await page.screenshot({
fullPage: true,
- path: 'test-results/01-show-js-before.png',
+ path: 'test-results/01-compiles-from-hash.png',
});
- const userInput =
- (await page.locator('.monaco-editor').nth(2).allInnerTexts()) ?? [];
- expect(concat(userInput)).toMatchSnapshot('user-input.txt');
+ const text =
+ (await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? [];
+ const output = await print(text);
+
+ expect(output).not.toEqual('');
+ expect(output).toMatchSnapshot('01-user-output.txt');
+});
+
+test('reset button works', async ({page}) => {
+ const store: Store = {
+ source: `export default function TestComponent({ x }) {
+ return ;
+ }
+ `,
+ };
+ const hash = encodeStore(store);
+ await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});
// Reset button works
page.on('dialog', dialog => dialog.accept());
await page.getByRole('button', {name: 'Reset'}).click();
await page.screenshot({
fullPage: true,
- path: 'test-results/02-show-js-after.png',
+ path: 'test-results/02-reset-button-works.png',
});
- const defaultInput =
- (await page.locator('.monaco-editor').nth(2).allInnerTexts()) ?? [];
- expect(concat(defaultInput)).toMatchSnapshot('default-input.txt');
+ const text =
+ (await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? [];
+ const output = await print(text);
+
+ expect(output).not.toEqual('');
+ expect(output).toMatchSnapshot('02-default-output.txt');
});
+
+DIRECTIVE_TEST_CASES.forEach((t, idx) =>
+ test(`directives work: ${t.name}`, async ({page}) => {
+ const store: Store = {
+ source: t.input,
+ };
+ const hash = encodeStore(store);
+ await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});
+ await page.screenshot({
+ fullPage: true,
+ path: `test-results/03-0${idx}-${t.name}.png`,
+ });
+
+ const text =
+ (await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? [];
+ const output = await print(text);
+
+ expect(output).not.toEqual('');
+ expect(output).toMatchSnapshot(`${t.name}-output.txt`);
+ }),
+);
diff --git a/compiler/apps/playground/app/layout.tsx b/compiler/apps/playground/app/layout.tsx
index 3e888ae955a89..9a6daac239faf 100644
--- a/compiler/apps/playground/app/layout.tsx
+++ b/compiler/apps/playground/app/layout.tsx
@@ -7,7 +7,11 @@
import '../styles/globals.css';
-export default function RootLayout({children}: {children: React.ReactNode}) {
+export default function RootLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}): JSX.Element {
'use no memo';
return (
diff --git a/compiler/apps/playground/app/page.tsx b/compiler/apps/playground/app/page.tsx
index 4b0da58a7028b..d5214af422e03 100644
--- a/compiler/apps/playground/app/page.tsx
+++ b/compiler/apps/playground/app/page.tsx
@@ -11,7 +11,7 @@ import {SnackbarProvider} from 'notistack';
import {Editor, Header, StoreProvider} from '../components';
import MessageSnackbar from '../components/Message';
-export default function Hoot() {
+export default function Page(): JSX.Element {
return (
+ | NodePath
+ | NodePath;
+enum MemoizeDirectiveState {
+ Enabled = 'Enabled',
+ Disabled = 'Disabled',
+ Undefined = 'Undefined',
+}
+
+const MEMOIZE_ENABLED_OR_UNDEFINED_STATES = new Set([
+ MemoizeDirectiveState.Enabled,
+ MemoizeDirectiveState.Undefined,
+]);
+
+const MEMOIZE_ENABLED_OR_DISABLED_STATES = new Set([
+ MemoizeDirectiveState.Enabled,
+ MemoizeDirectiveState.Disabled,
+]);
+function parseInput(input: string, language: 'flow' | 'typescript'): any {
// Extract the first line to quickly check for custom test directives
if (language === 'flow') {
return HermesParser.parse(input, {
@@ -65,29 +84,36 @@ function parseInput(input: string, language: 'flow' | 'typescript') {
function parseFunctions(
source: string,
language: 'flow' | 'typescript',
-): Array<
- NodePath<
- t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
- >
-> {
- const items: Array<
- NodePath<
- t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
- >
- > = [];
+): Array<{
+ compilationEnabled: boolean;
+ fn: FunctionLike;
+}> {
+ const items: Array<{
+ compilationEnabled: boolean;
+ fn: FunctionLike;
+ }> = [];
try {
const ast = parseInput(source, language);
traverse(ast, {
FunctionDeclaration(nodePath) {
- items.push(nodePath);
+ items.push({
+ compilationEnabled: shouldCompile(nodePath),
+ fn: nodePath,
+ });
nodePath.skip();
},
ArrowFunctionExpression(nodePath) {
- items.push(nodePath);
+ items.push({
+ compilationEnabled: shouldCompile(nodePath),
+ fn: nodePath,
+ });
nodePath.skip();
},
FunctionExpression(nodePath) {
- items.push(nodePath);
+ items.push({
+ compilationEnabled: shouldCompile(nodePath),
+ fn: nodePath,
+ });
nodePath.skip();
},
});
@@ -100,9 +126,48 @@ function parseFunctions(
suggestions: null,
});
}
+
return items;
}
+function shouldCompile(fn: FunctionLike): boolean {
+ const {body} = fn.node;
+ if (t.isBlockStatement(body)) {
+ const selfCheck = checkExplicitMemoizeDirectives(body.directives);
+ if (selfCheck === MemoizeDirectiveState.Enabled) return true;
+ if (selfCheck === MemoizeDirectiveState.Disabled) return false;
+
+ const parentWithDirective = fn.findParent(parentPath => {
+ if (parentPath.isBlockStatement() || parentPath.isProgram()) {
+ const directiveCheck = checkExplicitMemoizeDirectives(
+ parentPath.node.directives,
+ );
+ return MEMOIZE_ENABLED_OR_DISABLED_STATES.has(directiveCheck);
+ }
+ return false;
+ });
+
+ if (!parentWithDirective) return true;
+ const parentDirectiveCheck = checkExplicitMemoizeDirectives(
+ (parentWithDirective.node as t.Program | t.BlockStatement).directives,
+ );
+ return MEMOIZE_ENABLED_OR_UNDEFINED_STATES.has(parentDirectiveCheck);
+ }
+ return false;
+}
+
+function checkExplicitMemoizeDirectives(
+ directives: Array,
+): MemoizeDirectiveState {
+ if (findDirectiveEnablingMemoization(directives).length) {
+ return MemoizeDirectiveState.Enabled;
+ }
+ if (findDirectiveDisablingMemoization(directives).length) {
+ return MemoizeDirectiveState.Disabled;
+ }
+ return MemoizeDirectiveState.Undefined;
+}
+
const COMMON_HOOKS: Array<[string, Hook]> = [
[
'useFragment',
@@ -155,26 +220,37 @@ function isHookName(s: string): boolean {
return /^use[A-Z0-9]/.test(s);
}
-function getReactFunctionType(
- id: NodePath,
-): ReactFunctionType {
- if (id && id.node && id.isIdentifier()) {
- if (isHookName(id.node.name)) {
+function getReactFunctionType(id: t.Identifier | null): ReactFunctionType {
+ if (id != null) {
+ if (isHookName(id.name)) {
return 'Hook';
}
const isPascalCaseNameSpace = /^[A-Z].*/;
- if (isPascalCaseNameSpace.test(id.node.name)) {
+ if (isPascalCaseNameSpace.test(id.name)) {
return 'Component';
}
}
return 'Other';
}
+function getFunctionIdentifier(
+ fn:
+ | NodePath
+ | NodePath
+ | NodePath,
+): t.Identifier | null {
+ if (fn.isArrowFunctionExpression()) {
+ return null;
+ }
+ const id = fn.get('id');
+ return Array.isArray(id) === false && id.isIdentifier() ? id.node : null;
+}
+
function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
- const results = new Map();
+ const results = new Map>();
const error = new CompilerError();
- const upsert = (result: PrintedCompilerPipelineValue) => {
+ const upsert: (result: PrintedCompilerPipelineValue) => void = result => {
const entry = results.get(result.name);
if (Array.isArray(entry)) {
entry.push(result);
@@ -188,40 +264,50 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
} else {
language = 'typescript';
}
+ let count = 0;
+ const withIdentifier = (id: t.Identifier | null): t.Identifier => {
+ if (id != null && id.name != null) {
+ return id;
+ } else {
+ return t.identifier(`anonymous_${count++}`);
+ }
+ };
try {
// Extract the first line to quickly check for custom test directives
const pragma = source.substring(0, source.indexOf('\n'));
- const config = parseConfigPragma(pragma);
-
- for (const fn of parseFunctions(source, language)) {
- if (!fn.isFunctionDeclaration()) {
- error.pushErrorDetail(
- new CompilerErrorDetail({
- reason: `Unexpected function type ${fn.node.type}`,
- description:
- 'Playground only supports parsing function declarations',
- severity: ErrorSeverity.Todo,
- loc: fn.node.loc ?? null,
- suggestions: null,
- }),
- );
+ const config = parseConfigPragmaForTests(pragma);
+ const parsedFunctions = parseFunctions(source, language);
+ for (const func of parsedFunctions) {
+ const id = withIdentifier(getFunctionIdentifier(func.fn));
+ const fnName = id.name;
+ if (!func.compilationEnabled) {
+ upsert({
+ kind: 'ast',
+ fnName,
+ name: 'CodeGen',
+ value: {
+ type: 'FunctionDeclaration',
+ id:
+ func.fn.isArrowFunctionExpression() ||
+ func.fn.isFunctionExpression()
+ ? withIdentifier(null)
+ : func.fn.node.id,
+ async: func.fn.node.async,
+ generator: !!func.fn.node.generator,
+ body: func.fn.node.body as t.BlockStatement,
+ params: func.fn.node.params,
+ },
+ });
continue;
}
-
- const id = fn.get('id');
- for (const result of run(
- fn,
+ for (const result of runPlayground(
+ func.fn,
{
...config,
customHooks: new Map([...COMMON_HOOKS]),
},
getReactFunctionType(id),
- '_c',
- null,
- null,
- null,
)) {
- const fnName = fn.node.id?.name ?? null;
switch (result.kind) {
case 'ast': {
upsert({
@@ -230,7 +316,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
name: result.name,
value: {
type: 'FunctionDeclaration',
- id: result.value.id,
+ id: withIdentifier(result.value.id),
async: result.value.async,
generator: result.value.generator,
body: result.value.body,
@@ -274,13 +360,17 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
}
}
} catch (err) {
- // error might be an invariant violation or other runtime error
- // (i.e. object shape that is not CompilerError)
+ /**
+ * error might be an invariant violation or other runtime error
+ * (i.e. object shape that is not CompilerError)
+ */
if (err instanceof CompilerError && err.details.length > 0) {
error.details.push(...err.details);
} else {
- // Handle unexpected failures by logging (to get a stack trace)
- // and reporting
+ /**
+ * Handle unexpected failures by logging (to get a stack trace)
+ * and reporting
+ */
console.error(err);
error.details.push(
new CompilerErrorDetail({
@@ -298,7 +388,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
return [{kind: 'ok', results}, language];
}
-export default function Editor() {
+export default function Editor(): JSX.Element {
const store = useStore();
const deferredStore = useDeferredValue(store);
const dispatchStore = useStoreDispatch();
diff --git a/compiler/apps/playground/components/Editor/Input.tsx b/compiler/apps/playground/components/Editor/Input.tsx
index c2ce8efc70d3f..34df68787db79 100644
--- a/compiler/apps/playground/components/Editor/Input.tsx
+++ b/compiler/apps/playground/components/Editor/Input.tsx
@@ -15,18 +15,17 @@ import {useEffect, useState} from 'react';
import {renderReactCompilerMarkers} from '../../lib/reactCompilerMonacoDiagnostics';
import {useStore, useStoreDispatch} from '../StoreContext';
import {monacoOptions} from './monacoOptions';
-// TODO: Make TS recognize .d.ts files, in addition to loading them with webpack.
-// @ts-ignore
+// @ts-expect-error TODO: Make TS recognize .d.ts files, in addition to loading them with webpack.
import React$Types from '../../node_modules/@types/react/index.d.ts';
loader.config({monaco});
type Props = {
- errors: CompilerErrorDetail[];
+ errors: Array;
language: 'flow' | 'typescript';
};
-export default function Input({errors, language}: Props) {
+export default function Input({errors, language}: Props): JSX.Element {
const [monaco, setMonaco] = useState(null);
const store = useStore();
const dispatchStore = useStoreDispatch();
@@ -38,18 +37,19 @@ export default function Input({errors, language}: Props) {
const model = monaco.editor.getModel(uri);
invariant(model, 'Model must exist for the selected input file.');
renderReactCompilerMarkers({monaco, model, details: errors});
- // N.B. that `tabSize` is a model property, not an editor property.
- // So, the tab size has to be set per model.
+ /**
+ * N.B. that `tabSize` is a model property, not an editor property.
+ * So, the tab size has to be set per model.
+ */
model.updateOptions({tabSize: 2});
}, [monaco, errors]);
- const flowDiagnosticDisable = [
- 7028 /* unused label */, 6133 /* var declared but not read */,
- ];
useEffect(() => {
- // Ignore "can only be used in TypeScript files." errors, since
- // we want to support syntax highlighting for Flow (*.js) files
- // and Flow is not a built-in language.
+ /**
+ * Ignore "can only be used in TypeScript files." errors, since
+ * we want to support syntax highlighting for Flow (*.js) files
+ * and Flow is not a built-in language.
+ */
if (!monaco) return;
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
diagnosticCodesToIgnore: [
@@ -64,7 +64,9 @@ export default function Input({errors, language}: Props) {
8011,
8012,
8013,
- ...(language === 'flow' ? flowDiagnosticDisable : []),
+ ...(language === 'flow'
+ ? [7028 /* unused label */, 6133 /* var declared but not read */]
+ : []),
],
noSemanticValidation: true,
// Monaco can't validate Flow component syntax
@@ -72,7 +74,7 @@ export default function Input({errors, language}: Props) {
});
}, [monaco, language]);
- const handleChange = (value: string | undefined) => {
+ const handleChange: (value: string | undefined) => void = value => {
if (!value) return;
dispatchStore({
@@ -83,7 +85,10 @@ export default function Input({errors, language}: Props) {
});
};
- const handleMount = (_: editor.IStandaloneCodeEditor, monaco: Monaco) => {
+ const handleMount: (
+ _: editor.IStandaloneCodeEditor,
+ monaco: Monaco,
+ ) => void = (_, monaco) => {
setMonaco(monaco);
const tscOptions = {
@@ -111,10 +116,12 @@ export default function Input({errors, language}: Props) {
monaco.languages.typescript.javascriptDefaults.addExtraLib(...reactLib);
monaco.languages.typescript.typescriptDefaults.addExtraLib(...reactLib);
- // Remeasure the font in case the custom font is loaded only after
- // Monaco Editor is mounted.
- // N.B. that this applies also to the output editor as it seems
- // Monaco Editor instances share the same font config.
+ /**
+ * Remeasure the font in case the custom font is loaded only after
+ * Monaco Editor is mounted.
+ * N.B. that this applies also to the output editor as it seems
+ * Monaco Editor instances share the same font config.
+ */
document.fonts.ready.then(() => {
monaco.editor.remeasureFonts();
});
@@ -125,14 +132,18 @@ export default function Input({errors, language}: Props) {
}
+ | {kind: 'ok'; results: Map>}
| {
kind: 'err';
- results: Map;
+ results: Map>;
error: CompilerError;
};
@@ -54,7 +54,10 @@ type Props = {
compilerOutput: CompilerOutput;
};
-async function tabify(source: string, compilerOutput: CompilerOutput) {
+async function tabify(
+ source: string,
+ compilerOutput: CompilerOutput,
+): Promise