Compare commits
1 Commits
pr31584
...
sync-nextj
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6230622a1a |
699
.circleci/config.yml
Normal file
699
.circleci/config.yml
Normal file
@@ -0,0 +1,699 @@
|
||||
version: 2.1
|
||||
|
||||
aliases:
|
||||
- &docker
|
||||
- image: cimg/node:18.20.1-browsers
|
||||
|
||||
- &environment
|
||||
TZ: /usr/share/zoneinfo/America/Los_Angeles
|
||||
|
||||
- &restore_yarn_cache_fixtures_dom
|
||||
restore_cache:
|
||||
name: Restore yarn cache for fixtures/dom
|
||||
keys:
|
||||
- v2-yarn_cache-{{ arch }}-{{ checksum "yarn.lock" }}-fixtures/dom
|
||||
|
||||
- &yarn_install_fixtures_dom
|
||||
run:
|
||||
name: Install dependencies in fixtures/dom
|
||||
working_directory: fixtures/dom
|
||||
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
|
||||
|
||||
- &yarn_install_fixtures_dom_retry
|
||||
run:
|
||||
name: Install dependencies in fixtures/dom (retry)
|
||||
when: on_fail
|
||||
working_directory: fixtures/dom
|
||||
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
|
||||
|
||||
- &save_yarn_cache_fixtures_dom
|
||||
save_cache:
|
||||
name: Save yarn cache for fixtures/dom
|
||||
key: v2-yarn_cache-{{ arch }}-{{ checksum "yarn.lock" }}-fixtures/dom
|
||||
paths:
|
||||
- ~/.cache/yarn
|
||||
|
||||
- &TEST_PARALLELISM 20
|
||||
|
||||
- &attach_workspace
|
||||
at: build
|
||||
|
||||
commands:
|
||||
setup_node_modules:
|
||||
description: "Restore node_modules"
|
||||
steps:
|
||||
- restore_cache:
|
||||
name: Restore yarn cache
|
||||
keys:
|
||||
- v2-yarn_cache-{{ arch }}-{{ checksum "yarn.lock" }}
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: |
|
||||
yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
|
||||
if [ $? -ne 0 ]; then
|
||||
yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
|
||||
fi
|
||||
environment:
|
||||
# If we start needing the Electron binary, please ensure the binary is cached in CI following https://www.electronjs.org/docs/latest/tutorial/installation
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
|
||||
- save_cache:
|
||||
name: Save yarn cache
|
||||
key: v2-yarn_cache-{{ arch }}-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- ~/.cache/yarn
|
||||
|
||||
# The CircleCI API doesn't yet support triggering a specific workflow, but it
|
||||
# does support triggering a pipeline. So as a workaround you can triggger the
|
||||
# entire pipeline and use parameters to disable everything except the workflow
|
||||
# you want. CircleCI recommends this workaround here:
|
||||
# https://support.circleci.com/hc/en-us/articles/360050351292-How-to-trigger-a-workflow-via-CircleCI-API-v2-
|
||||
parameters:
|
||||
# This is only set when triggering the CI pipeline via an API request.
|
||||
prerelease_commit_sha:
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
jobs:
|
||||
yarn_lint:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
- setup_node_modules
|
||||
- run: node ./scripts/prettier/index
|
||||
- run: node ./scripts/tasks/eslint
|
||||
- run: ./scripts/circleci/check_license.sh
|
||||
- run: ./scripts/circleci/test_print_warnings.sh
|
||||
|
||||
yarn_flow:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
parallelism: 5
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
- setup_node_modules
|
||||
- run: node ./scripts/tasks/flow-ci
|
||||
|
||||
|
||||
yarn_flags:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
- setup_node_modules
|
||||
- run: yarn flags
|
||||
|
||||
scrape_warning_messages:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
- setup_node_modules
|
||||
- run:
|
||||
command: |
|
||||
mkdir -p ./build/__test_utils__
|
||||
node ./scripts/print-warnings/print-warnings.js > build/__test_utils__/ReactAllWarnings.js
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- build
|
||||
|
||||
yarn_build:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
parallelism: 40
|
||||
steps:
|
||||
- checkout
|
||||
- setup_node_modules
|
||||
- run: yarn build
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- build
|
||||
|
||||
download_build:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
parameters:
|
||||
revision:
|
||||
type: string
|
||||
steps:
|
||||
- checkout
|
||||
- setup_node_modules
|
||||
- run:
|
||||
name: Download artifacts for revision
|
||||
command: |
|
||||
git fetch origin main
|
||||
cd ./scripts/release && yarn && cd ../../
|
||||
scripts/release/download-experimental-build.js --commit=<< parameters.revision >> --allowBrokenCI
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- build
|
||||
|
||||
download_base_build_for_sizebot:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- setup_node_modules
|
||||
- run:
|
||||
name: Download artifacts for base revision
|
||||
command: |
|
||||
git fetch origin main
|
||||
cd ./scripts/release && yarn && cd ../../
|
||||
scripts/release/download-experimental-build.js --commit=$(git merge-base HEAD origin/main) --allowBrokenCI
|
||||
mv ./build ./base-build
|
||||
|
||||
- run:
|
||||
# TODO: The `download-experimental-build` script copies the npm
|
||||
# packages into the `node_modules` directory. This is a historical
|
||||
# quirk of how the release script works. Let's pretend they
|
||||
# don't exist.
|
||||
name: Delete extraneous files
|
||||
command: rm -rf ./base-build/node_modules
|
||||
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- base-build
|
||||
|
||||
process_artifacts_combined:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- setup_node_modules
|
||||
- run: echo "<< pipeline.git.revision >>" >> build/COMMIT_SHA
|
||||
# Compress build directory into a single tarball for easy download
|
||||
- run: tar -zcvf ./build.tgz ./build
|
||||
# TODO: Migrate scripts to use `build` directory instead of `build2`
|
||||
- run: cp ./build.tgz ./build2.tgz
|
||||
- store_artifacts:
|
||||
path: ./build2.tgz
|
||||
- store_artifacts:
|
||||
path: ./build.tgz
|
||||
|
||||
sizebot:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run: echo "<< pipeline.git.revision >>" >> build/COMMIT_SHA
|
||||
- setup_node_modules
|
||||
- run:
|
||||
command: node ./scripts/tasks/danger
|
||||
- store_artifacts:
|
||||
path: sizebot-message.md
|
||||
|
||||
build_devtools_and_process_artifacts:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- setup_node_modules
|
||||
- run:
|
||||
environment:
|
||||
RELEASE_CHANNEL: experimental
|
||||
command: ./scripts/circleci/pack_and_store_devtools_artifacts.sh
|
||||
- store_artifacts:
|
||||
path: ./build/devtools.tgz
|
||||
# Simplifies getting the extension for local testing
|
||||
- store_artifacts:
|
||||
path: ./build/devtools/chrome-extension.zip
|
||||
destination: react-devtools-chrome-extension.zip
|
||||
- store_artifacts:
|
||||
path: ./build/devtools/firefox-extension.zip
|
||||
destination: react-devtools-firefox-extension.zip
|
||||
|
||||
run_devtools_e2e_tests:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- setup_node_modules
|
||||
- run:
|
||||
name: Playwright install deps
|
||||
command: |
|
||||
npx playwright install
|
||||
sudo npx playwright install-deps
|
||||
- run:
|
||||
environment:
|
||||
RELEASE_CHANNEL: experimental
|
||||
command: ./scripts/circleci/run_devtools_e2e_tests.js
|
||||
|
||||
run_fixtures_flight_tests:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
# Fixture copies some built packages from the workroot after install.
|
||||
# That means dependencies of the built packages are not installed.
|
||||
# We need to install dependencies of the workroot to fulfill all dependency constraints
|
||||
- setup_node_modules
|
||||
- restore_cache:
|
||||
name: Restore yarn cache of fixture
|
||||
keys:
|
||||
- v2-yarn_cache_fixtures_flight-{{ arch }}-{{ checksum "yarn.lock" }}
|
||||
- run:
|
||||
name: Install fixture dependencies
|
||||
working_directory: fixtures/flight
|
||||
command: |
|
||||
yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
|
||||
if [ $? -ne 0 ]; then
|
||||
yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
|
||||
fi
|
||||
- save_cache:
|
||||
name: Save yarn cache of fixture
|
||||
key: v2-yarn_cache_fixtures_flight-{{ arch }}-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- ~/.cache/yarn
|
||||
- run:
|
||||
working_directory: fixtures/flight
|
||||
name: Playwright install deps
|
||||
command: |
|
||||
npx playwright install
|
||||
sudo npx playwright install-deps
|
||||
- run:
|
||||
name: Run tests
|
||||
working_directory: fixtures/flight
|
||||
command: yarn test
|
||||
environment:
|
||||
# Otherwise the webserver is a blackbox
|
||||
DEBUG: pw:webserver
|
||||
- store_artifacts:
|
||||
path: fixtures/flight/playwright-report
|
||||
- store_artifacts:
|
||||
path: fixtures/flight/test-results
|
||||
|
||||
run_devtools_tests_for_versions:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
parallelism: *TEST_PARALLELISM
|
||||
parameters:
|
||||
version:
|
||||
type: string
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- setup_node_modules
|
||||
- run: ./scripts/circleci/download_devtools_regression_build.js << parameters.version >> --replaceBuild
|
||||
- run: node ./scripts/jest/jest-cli.js --build --project devtools --release-channel=experimental --reactVersion << parameters.version >> --ci
|
||||
|
||||
run_devtools_e2e_tests_for_versions:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
parallelism: *TEST_PARALLELISM
|
||||
parameters:
|
||||
version:
|
||||
type: string
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- setup_node_modules
|
||||
- run:
|
||||
name: Playwright install deps
|
||||
command: |
|
||||
npx playwright install
|
||||
sudo npx playwright install-deps
|
||||
- run: ./scripts/circleci/download_devtools_regression_build.js << parameters.version >>
|
||||
- run:
|
||||
environment:
|
||||
RELEASE_CHANNEL: experimental
|
||||
command: ./scripts/circleci/run_devtools_e2e_tests.js << parameters.version >>
|
||||
- run:
|
||||
name: Cleanup build regression folder
|
||||
command: rm -r ./build-regression
|
||||
- store_artifacts:
|
||||
path: ./tmp/screenshots
|
||||
|
||||
yarn_lint_build:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- setup_node_modules
|
||||
- run: yarn lint-build
|
||||
|
||||
yarn_check_release_dependencies:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- setup_node_modules
|
||||
- run: yarn check-release-dependencies
|
||||
|
||||
|
||||
check_error_codes:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace: *attach_workspace
|
||||
- setup_node_modules
|
||||
- run:
|
||||
name: Search build artifacts for unminified errors
|
||||
command: |
|
||||
yarn extract-errors
|
||||
git diff --quiet || (echo "Found unminified errors. Either update the error codes map or disable error minification for the affected build, if appropriate." && false)
|
||||
|
||||
check_generated_fizz_runtime:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace: *attach_workspace
|
||||
- setup_node_modules
|
||||
- run:
|
||||
name: Confirm generated inline Fizz runtime is up to date
|
||||
command: |
|
||||
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)
|
||||
|
||||
yarn_test:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
parallelism: *TEST_PARALLELISM
|
||||
parameters:
|
||||
args:
|
||||
type: string
|
||||
steps:
|
||||
- checkout
|
||||
- setup_node_modules
|
||||
- run: yarn test <<parameters.args>> --ci
|
||||
|
||||
yarn_test_build:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
parallelism: *TEST_PARALLELISM
|
||||
parameters:
|
||||
args:
|
||||
type: string
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- setup_node_modules
|
||||
- run: yarn test --build <<parameters.args>> --ci
|
||||
|
||||
RELEASE_CHANNEL_stable_yarn_test_dom_fixtures:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- setup_node_modules
|
||||
- *restore_yarn_cache_fixtures_dom
|
||||
- *yarn_install_fixtures_dom
|
||||
- *yarn_install_fixtures_dom_retry
|
||||
- *save_yarn_cache_fixtures_dom
|
||||
- run:
|
||||
name: Run DOM fixture tests
|
||||
environment:
|
||||
RELEASE_CHANNEL: stable
|
||||
working_directory: fixtures/dom
|
||||
command: |
|
||||
yarn predev
|
||||
yarn test --maxWorkers=2
|
||||
|
||||
publish_prerelease:
|
||||
parameters:
|
||||
commit_sha:
|
||||
type: string
|
||||
release_channel:
|
||||
type: string
|
||||
dist_tag:
|
||||
type: string
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- setup_node_modules
|
||||
- run:
|
||||
name: Run publish script
|
||||
command: |
|
||||
git fetch origin main
|
||||
cd ./scripts/release && yarn && cd ../../
|
||||
scripts/release/prepare-release-from-ci.js --skipTests -r << parameters.release_channel >> --commit=<< parameters.commit_sha >>
|
||||
cp ./scripts/release/ci-npmrc ~/.npmrc
|
||||
scripts/release/publish.js --ci --tags << parameters.dist_tag >>
|
||||
|
||||
workflows:
|
||||
|
||||
build_and_test:
|
||||
unless: << pipeline.parameters.prerelease_commit_sha >>
|
||||
jobs:
|
||||
- yarn_flags:
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- builds/facebook-www
|
||||
- yarn_flow:
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- builds/facebook-www
|
||||
- check_generated_fizz_runtime:
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- builds/facebook-www
|
||||
- yarn_lint:
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- builds/facebook-www
|
||||
- yarn_test:
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- builds/facebook-www
|
||||
matrix:
|
||||
parameters:
|
||||
args:
|
||||
# Intentionally passing these as strings instead of creating a
|
||||
# separate parameter per CLI argument, since it's easier to
|
||||
# control/see which combinations we want to run.
|
||||
- "-r=stable --env=development"
|
||||
- "-r=stable --env=production"
|
||||
- "-r=experimental --env=development"
|
||||
- "-r=experimental --env=production"
|
||||
- "-r=www-classic --env=development --variant=false"
|
||||
- "-r=www-classic --env=production --variant=false"
|
||||
- "-r=www-classic --env=development --variant=true"
|
||||
- "-r=www-classic --env=production --variant=true"
|
||||
- "-r=www-modern --env=development --variant=false"
|
||||
- "-r=www-modern --env=production --variant=false"
|
||||
- "-r=www-modern --env=development --variant=true"
|
||||
- "-r=www-modern --env=production --variant=true"
|
||||
- "-r=xplat --env=development --variant=false"
|
||||
- "-r=xplat --env=development --variant=true"
|
||||
- "-r=xplat --env=production --variant=false"
|
||||
- "-r=xplat --env=production --variant=true"
|
||||
|
||||
# TODO: Test more persistent configurations?
|
||||
- '-r=stable --env=development --persistent'
|
||||
- '-r=experimental --env=development --persistent'
|
||||
- yarn_build:
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- builds/facebook-www
|
||||
- scrape_warning_messages:
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- builds/facebook-www
|
||||
- process_artifacts_combined:
|
||||
requires:
|
||||
- scrape_warning_messages
|
||||
- yarn_build
|
||||
- yarn_test_build:
|
||||
requires:
|
||||
- yarn_build
|
||||
matrix:
|
||||
parameters:
|
||||
args:
|
||||
# Intentionally passing these as strings instead of creating a
|
||||
# separate parameter per CLI argument, since it's easier to
|
||||
# control/see which combinations we want to run.
|
||||
- "-r=stable --env=development"
|
||||
- "-r=stable --env=production"
|
||||
- "-r=experimental --env=development"
|
||||
- "-r=experimental --env=production"
|
||||
|
||||
# Dev Tools
|
||||
- "--project=devtools -r=experimental"
|
||||
|
||||
# TODO: Update test config to support www build tests
|
||||
# - "-r=www-classic --env=development --variant=false"
|
||||
# - "-r=www-classic --env=production --variant=false"
|
||||
# - "-r=www-classic --env=development --variant=true"
|
||||
# - "-r=www-classic --env=production --variant=true"
|
||||
# - "-r=www-modern --env=development --variant=false"
|
||||
# - "-r=www-modern --env=production --variant=false"
|
||||
# - "-r=www-modern --env=development --variant=true"
|
||||
# - "-r=www-modern --env=production --variant=true"
|
||||
|
||||
# TODO: Update test config to support xplat build tests
|
||||
# - "-r=xplat --env=development --variant=false"
|
||||
# - "-r=xplat --env=development --variant=true"
|
||||
# - "-r=xplat --env=production --variant=false"
|
||||
# - "-r=xplat --env=production --variant=true"
|
||||
|
||||
# TODO: Test more persistent configurations?
|
||||
- download_base_build_for_sizebot:
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- main
|
||||
- builds/facebook-www
|
||||
- sizebot:
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- main
|
||||
requires:
|
||||
- download_base_build_for_sizebot
|
||||
- yarn_build
|
||||
- yarn_lint_build:
|
||||
requires:
|
||||
- yarn_build
|
||||
- yarn_check_release_dependencies:
|
||||
requires:
|
||||
- yarn_build
|
||||
- check_error_codes:
|
||||
requires:
|
||||
- yarn_build
|
||||
- RELEASE_CHANNEL_stable_yarn_test_dom_fixtures:
|
||||
requires:
|
||||
- yarn_build
|
||||
- build_devtools_and_process_artifacts:
|
||||
requires:
|
||||
- yarn_build
|
||||
- run_devtools_e2e_tests:
|
||||
requires:
|
||||
- build_devtools_and_process_artifacts
|
||||
- run_fixtures_flight_tests:
|
||||
requires:
|
||||
- yarn_build
|
||||
|
||||
devtools_regression_tests:
|
||||
unless: << pipeline.parameters.prerelease_commit_sha >>
|
||||
triggers:
|
||||
- schedule:
|
||||
# DevTools regression tests run once a day
|
||||
cron: "0 0 * * *"
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- main
|
||||
jobs:
|
||||
- download_build:
|
||||
revision: << pipeline.git.revision >>
|
||||
- build_devtools_and_process_artifacts:
|
||||
requires:
|
||||
- download_build
|
||||
- run_devtools_tests_for_versions:
|
||||
requires:
|
||||
- build_devtools_and_process_artifacts
|
||||
matrix:
|
||||
parameters:
|
||||
version:
|
||||
- "16.0"
|
||||
- "16.5" # schedule package
|
||||
- "16.8" # hooks
|
||||
- "17.0"
|
||||
- "18.0"
|
||||
- run_devtools_e2e_tests_for_versions:
|
||||
requires:
|
||||
- build_devtools_and_process_artifacts
|
||||
matrix:
|
||||
parameters:
|
||||
version:
|
||||
- "16.0"
|
||||
- "16.5" # schedule package
|
||||
- "16.8" # hooks
|
||||
- "17.0"
|
||||
- "18.0"
|
||||
|
||||
# Used to publish a prerelease manually via the command line
|
||||
publish_preleases:
|
||||
when: << pipeline.parameters.prerelease_commit_sha >>
|
||||
jobs:
|
||||
- publish_prerelease:
|
||||
name: Publish to Canary channel
|
||||
commit_sha: << pipeline.parameters.prerelease_commit_sha >>
|
||||
release_channel: stable
|
||||
# The tags to use when publishing canaries. The main one we should
|
||||
# always include is "canary" but we can use multiple (e.g. alpha,
|
||||
# beta, rc). To declare multiple, use a comma-separated string, like
|
||||
# this:
|
||||
# dist_tag: "canary,alpha,beta,rc"
|
||||
#
|
||||
# TODO: We currently tag canaries with "next" in addition to "canary"
|
||||
# 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"
|
||||
- publish_prerelease:
|
||||
name: Publish to Experimental channel
|
||||
requires:
|
||||
# NOTE: Intentionally running these jobs sequentially because npm
|
||||
# will sometimes fail if you try to concurrently publish two
|
||||
# different versions of the same package, even if they use different
|
||||
# dist tags.
|
||||
- Publish to Canary channel
|
||||
commit_sha: << pipeline.parameters.prerelease_commit_sha >>
|
||||
release_channel: experimental
|
||||
dist_tag: experimental
|
||||
|
||||
# Publishes on a cron schedule
|
||||
publish_preleases_nightly:
|
||||
unless: << pipeline.parameters.prerelease_commit_sha >>
|
||||
triggers:
|
||||
- schedule:
|
||||
# At 10 minutes past 16:00 on Mon, Tue, Wed, Thu, and Fri
|
||||
cron: "10 16 * * 1,2,3,4,5"
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- main
|
||||
jobs:
|
||||
- publish_prerelease:
|
||||
name: Publish to Canary channel
|
||||
commit_sha: << pipeline.git.revision >>
|
||||
release_channel: stable
|
||||
dist_tag: "canary,next,rc"
|
||||
- publish_prerelease:
|
||||
name: Publish to Experimental channel
|
||||
requires:
|
||||
# NOTE: Intentionally running these jobs sequentially because npm
|
||||
# will sometimes fail if you try to concurrently publish two
|
||||
# different versions of the same package, even if they use different
|
||||
# dist tags.
|
||||
- Publish to Canary channel
|
||||
commit_sha: << pipeline.git.revision >>
|
||||
release_channel: experimental
|
||||
dist_tag: experimental
|
||||
@@ -8,9 +8,11 @@ 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
|
||||
|
||||
63
.eslintrc.js
63
.eslintrc.js
@@ -12,18 +12,15 @@ const WARNING = 1;
|
||||
const ERROR = 2;
|
||||
|
||||
module.exports = {
|
||||
extends: ['prettier', 'plugin:jest/recommended'],
|
||||
extends: ['prettier'],
|
||||
|
||||
// Stop ESLint from looking for a configuration file in parent folders
|
||||
root: true,
|
||||
|
||||
reportUnusedDisableDirectives: true,
|
||||
|
||||
plugins: [
|
||||
'babel',
|
||||
'ft-flow',
|
||||
'jest',
|
||||
'es',
|
||||
'no-for-of-loops',
|
||||
'no-function-declare-after-return',
|
||||
'react',
|
||||
@@ -48,7 +45,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,
|
||||
@@ -248,7 +245,7 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
'no-shadow': ERROR,
|
||||
'no-unused-vars': [ERROR, {args: 'none', ignoreRestSiblings: true}],
|
||||
'no-unused-vars': [ERROR, {args: 'none'}],
|
||||
'no-use-before-define': OFF,
|
||||
'no-useless-concat': OFF,
|
||||
quotes: [ERROR, 'single', {avoidEscape: true, allowTemplateLiterals: true}],
|
||||
@@ -379,49 +376,16 @@ module.exports = {
|
||||
files: ['**/__tests__/*.js'],
|
||||
rules: {
|
||||
// https://github.com/jest-community/eslint-plugin-jest
|
||||
// Meh, who cares.
|
||||
'jest/consistent-test-it': OFF,
|
||||
// Meh, we have a lot of these, who cares.
|
||||
'jest/no-alias-methods': OFF,
|
||||
// We do conditions based on feature flags.
|
||||
'jest/no-conditional-expect': OFF,
|
||||
// We have our own assertion helpers.
|
||||
'jest/expect-expect': OFF,
|
||||
// Lame rule that fires in itRender helpers or in render methods.
|
||||
'jest/no-standalone-expect': OFF,
|
||||
'jest/no-focused-tests': ERROR,
|
||||
'jest/valid-expect': ERROR,
|
||||
'jest/valid-expect-in-promise': ERROR,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Rules specific to test setup helper files.
|
||||
files: [
|
||||
'**/setupTests.js',
|
||||
'**/setupEnv.js',
|
||||
'**/jest/TestFlags.js',
|
||||
'**/dom-event-testing-library/testHelpers.js',
|
||||
'**/utils/ReactDOMServerIntegrationTestUtils.js',
|
||||
'**/babel/transform-react-version-pragma.js',
|
||||
'**/babel/transform-test-gate-pragma.js',
|
||||
],
|
||||
// disable no focused tests for test setup helper files even if they are inside __tests__ directory
|
||||
files: ['**/setupTests.js'],
|
||||
rules: {
|
||||
// Some helpers intentionally focus tests.
|
||||
'jest/no-focused-tests': OFF,
|
||||
// Test fn helpers don't use static text names.
|
||||
'jest/valid-title': OFF,
|
||||
// We have our own assertion helpers.
|
||||
'jest/expect-expect': OFF,
|
||||
// Some helpers intentionally disable tests.
|
||||
'jest/no-disabled-tests': OFF,
|
||||
// Helpers export text function helpers.
|
||||
'jest/no-export': OFF,
|
||||
// The examples in comments trigger false errors.
|
||||
'jest/no-commented-out-tests': OFF,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/jest/TestFlags.js'],
|
||||
rules: {
|
||||
// The examples in comments trigger false errors.
|
||||
'jest/no-commented-out-tests': OFF,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -436,7 +400,6 @@ 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': [
|
||||
@@ -488,17 +451,11 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
'packages/react-devtools-extensions/**/*.js',
|
||||
'packages/react-devtools-shared/src/hook.js',
|
||||
'packages/react-devtools-shared/src/backend/console.js',
|
||||
'packages/react-devtools-shared/src/backend/shared/DevToolsComponentStackFrame.js',
|
||||
],
|
||||
files: ['packages/react-devtools-extensions/**/*.js'],
|
||||
globals: {
|
||||
__IS_CHROME__: 'readonly',
|
||||
__IS_FIREFOX__: 'readonly',
|
||||
__IS_EDGE__: 'readonly',
|
||||
__IS_NATIVE__: 'readonly',
|
||||
__IS_INTERNAL_VERSION__: 'readonly',
|
||||
},
|
||||
},
|
||||
@@ -569,7 +526,6 @@ module.exports = {
|
||||
React$Node: 'readonly',
|
||||
React$Portal: 'readonly',
|
||||
React$Ref: 'readonly',
|
||||
React$RefSetter: 'readonly',
|
||||
ReadableStreamController: 'readonly',
|
||||
ReadableStreamReader: 'readonly',
|
||||
RequestInfo: 'readonly',
|
||||
@@ -582,7 +538,6 @@ module.exports = {
|
||||
TimeoutID: 'readonly',
|
||||
WheelEventHandler: 'readonly',
|
||||
FinalizationRegistry: 'readonly',
|
||||
Omit: 'readonly',
|
||||
|
||||
spyOnDev: 'readonly',
|
||||
spyOnDevAndProd: 'readonly',
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
c998bb1ed4b3285398c9c7797135d3f060243c6a
|
||||
fd2b3e13d330a4559f5aa21462e1cb2cbbcf144b
|
||||
|
||||
@@ -55,10 +55,3 @@ 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
|
||||
|
||||
107
.github/dependabot.yml
vendored
107
.github/dependabot.yml
vendored
@@ -1,10 +1,107 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "npm"
|
||||
directories:
|
||||
- "/fixtures/*"
|
||||
directory: "/fixtures/art"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
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"
|
||||
open-pull-requests-limit: 0
|
||||
ignore:
|
||||
- dependency-name: "*"
|
||||
|
||||
@@ -1,26 +1,8 @@
|
||||
name: (Runtime) Commit Artifacts for Meta WWW and fbsource V2
|
||||
name: Commit Artifacts for Meta WWW and fbsource
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["(Runtime) Build and Test"]
|
||||
types: [completed]
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
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
|
||||
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
|
||||
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
|
||||
push:
|
||||
branches: [main, meta-www, meta-fbsource]
|
||||
|
||||
jobs:
|
||||
download_artifacts:
|
||||
@@ -64,39 +46,106 @@ jobs:
|
||||
run: |
|
||||
echo "www_branch_count=$(git ls-remote --heads origin "refs/heads/meta-www" | wc -l)" >> "$GITHUB_OUTPUT"
|
||||
echo "fbsource_branch_count=$(git ls-remote --heads origin "refs/heads/meta-fbsource" | wc -l)" >> "$GITHUB_OUTPUT"
|
||||
- uses: actions/setup-node@v4
|
||||
- name: Download and unzip artifacts
|
||||
uses: actions/github-script@v6
|
||||
env:
|
||||
CIRCLECI_TOKEN: ${{secrets.CIRCLECI_TOKEN_DIFFTRAIN}}
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
cache-dependency-path: yarn.lock
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: runtime-release-node_modules-${{ 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
|
||||
script: |
|
||||
const cp = require('child_process');
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
function execHelper(command, options, streamStdout = false) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const proc = cp.exec(
|
||||
command,
|
||||
options,
|
||||
(error, stdout) => (error ? reject(error) : resolve(stdout.trim())),
|
||||
);
|
||||
if (streamStdout) {
|
||||
proc.stdout.pipe(process.stdout);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let artifactsUrl = null;
|
||||
// This is a temporary, dirty hack to avoid needing a GitHub auth token in the circleci
|
||||
// workflow to notify this GitHub action. Sorry!
|
||||
let iter = 0;
|
||||
spinloop: while (iter < 15) {
|
||||
const res = await github.rest.repos.listCommitStatusesForRef({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: context.sha
|
||||
});
|
||||
for (const status of res.data) {
|
||||
if (/process_artifacts_combined/.test(status.context)) {
|
||||
switch (status.state) {
|
||||
case 'pending': {
|
||||
console.log(`${status.context} is still pending`);
|
||||
break;
|
||||
}
|
||||
case 'failure':
|
||||
case 'error': {
|
||||
throw new Error(`${status.context} has failed or errored`);
|
||||
}
|
||||
case 'success': {
|
||||
// The status does not include a build ID, but we can extract it
|
||||
// from the URL. I couldn't find a better way to do this.
|
||||
const ciBuildId = /\/facebook\/react\/([0-9]+)/.exec(
|
||||
status.target_url,
|
||||
)[1];
|
||||
if (Number.parseInt(ciBuildId, 10) + '' === ciBuildId) {
|
||||
artifactsUrl =
|
||||
`https://circleci.com/api/v1.1/project/github/facebook/react/${ciBuildId}/artifacts`;
|
||||
console.log(`Found artifactsUrl: ${artifactsUrl}`);
|
||||
break spinloop;
|
||||
} else {
|
||||
throw new Error(`${ciBuildId} isn't a number`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Unhandled status state: ${status.state}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
iter++;
|
||||
console.log("Sleeping for 60s...");
|
||||
await sleep(60_000);
|
||||
}
|
||||
if (artifactsUrl != null) {
|
||||
const {CIRCLECI_TOKEN} = process.env;
|
||||
const res = await fetch(artifactsUrl, {
|
||||
headers: {
|
||||
'Circle-Token': CIRCLECI_TOKEN
|
||||
}
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!Array.isArray(data) && data.message != null) {
|
||||
throw `CircleCI returned: ${data.message}`;
|
||||
}
|
||||
for (const artifact of data) {
|
||||
if (artifact.path === 'build.tgz') {
|
||||
console.log(`Downloading and unzipping ${artifact.url}`);
|
||||
await execHelper(
|
||||
`curl -L ${artifact.url} -H "Circle-Token: ${CIRCLECI_TOKEN}" | tar -xvz`
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
process.exitCode = 1;
|
||||
}
|
||||
- name: Strip @license from eslint plugin and react-refresh
|
||||
run: |
|
||||
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
|
||||
@@ -125,14 +174,13 @@ 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-dom,react-is,react-test-renderer}/
|
||||
mkdir -p ${BASE_FOLDER}/RKJSModules/vendor/react/{scheduler,react,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/
|
||||
|
||||
@@ -149,7 +197,7 @@ jobs:
|
||||
run: |
|
||||
echo ${{ github.sha }} >> ./compiled/facebook-www/REVISION
|
||||
cp ./compiled/facebook-www/REVISION ./compiled/facebook-www/REVISION_TRANSFORMS
|
||||
echo ${{ github.sha}} >> ./compiled-rn/facebook-fbsource/xplat/js/react-native-github/Libraries/Renderer/REVISION
|
||||
echo ${{ github.sha }} >> ./compiled-rn/facebook-fbsource/xplat/js/react-native-github/Libraries/Renderer/REVISION
|
||||
- name: "Get current version string"
|
||||
id: get_current_version
|
||||
run: |
|
||||
@@ -162,18 +210,18 @@ jobs:
|
||||
echo "current_version_classic=$VERSION_CLASSIC" >> "$GITHUB_OUTPUT"
|
||||
echo "current_version_modern=$VERSION_MODERN" >> "$GITHUB_OUTPUT"
|
||||
echo "current_version_rn=$VERSION_NATIVE_FB" >> "$GITHUB_OUTPUT"
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: compiled
|
||||
path: compiled/
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: compiled-rn
|
||||
path: compiled-rn/
|
||||
|
||||
commit_www_artifacts:
|
||||
needs: download_artifacts
|
||||
if: inputs.force == true || (github.ref == 'refs/heads/main' && needs.download_artifacts.outputs.www_branch_count == '0')
|
||||
if: ${{ (github.ref == 'refs/heads/main' && needs.download_artifacts.outputs.www_branch_count == '0') || github.ref == 'refs/heads/meta-www' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -181,7 +229,7 @@ jobs:
|
||||
ref: builds/facebook-www
|
||||
- name: Ensure clean directory
|
||||
run: rm -rf compiled
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: compiled
|
||||
path: compiled/
|
||||
@@ -202,26 +250,21 @@ jobs:
|
||||
grep -rl "$CURRENT_VERSION_MODERN" ./compiled || echo "No files found with $CURRENT_VERSION_MODERN"
|
||||
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
|
||||
- name: Check if only the REVISION file has changed
|
||||
id: check_should_commit
|
||||
run: |
|
||||
echo "Full git status"
|
||||
git add .
|
||||
git status
|
||||
echo "===================="
|
||||
if git status --porcelain | grep -qv '/REVISION'; then
|
||||
echo "Changes detected"
|
||||
echo "===== Changes ====="
|
||||
git --no-pager diff -U0 | grep '^[+-]' | head -n 50
|
||||
echo "==================="
|
||||
echo "should_commit=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "No Changes detected"
|
||||
echo "should_commit=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
- name: Re-apply version changes
|
||||
if: inputs.force == true || (steps.check_should_commit.outputs.should_commit == 'true' && needs.download_artifacts.outputs.last_version_classic != '' && needs.download_artifacts.outputs.last_version_modern != '')
|
||||
if: 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 }}
|
||||
@@ -238,26 +281,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: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
|
||||
if: steps.check_should_commit.outputs.should_commit == 'true'
|
||||
run: |
|
||||
echo ":"
|
||||
git status -u
|
||||
- name: Commit changes to branch
|
||||
if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
|
||||
uses: stefanzweifel/git-auto-commit-action@v5
|
||||
if: steps.check_should_commit.outputs.should_commit == 'true'
|
||||
uses: stefanzweifel/git-auto-commit-action@v4
|
||||
with:
|
||||
commit_message: |
|
||||
${{ github.event.workflow_run.head_commit.message || format('Manual build of {0}', github.event.workflow_run.head_sha || github.sha) }}
|
||||
${{ github.event.head_commit.message }}
|
||||
|
||||
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 }})
|
||||
DiffTrain build for [${{ github.sha }}](https://github.com/facebook/react/commit/${{ github.sha }})
|
||||
branch: builds/facebook-www
|
||||
commit_user_name: ${{ github.triggering_actor }}
|
||||
commit_user_email: ${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}
|
||||
commit_user_name: ${{ github.actor }}
|
||||
commit_user_email: ${{ github.actor }}@users.noreply.github.com
|
||||
create_branch: true
|
||||
|
||||
commit_fbsource_artifacts:
|
||||
needs: download_artifacts
|
||||
if: inputs.force == true || (github.ref == 'refs/heads/main' && needs.download_artifacts.outputs.fbsource_branch_count == '0')
|
||||
if: ${{ (github.ref == 'refs/heads/main' && needs.download_artifacts.outputs.fbsource_branch_count == '0') || github.ref == 'refs/heads/meta-fbsource' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -265,7 +308,7 @@ jobs:
|
||||
ref: builds/facebook-fbsource
|
||||
- name: Ensure clean directory
|
||||
run: rm -rf compiled-rn
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: compiled-rn
|
||||
path: compiled-rn/
|
||||
@@ -279,27 +322,24 @@ jobs:
|
||||
grep -rl "$CURRENT_VERSION" ./compiled-rn || echo "No files found with $CURRENT_VERSION"
|
||||
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'
|
||||
- name: Check if only the REVISION file has changed
|
||||
id: check_should_commit
|
||||
run: |
|
||||
echo "Full git status"
|
||||
git add .
|
||||
git --no-pager diff -U0 --cached | grep '^[+-]' | head -n 100
|
||||
git status
|
||||
echo "===================="
|
||||
# Ignore REVISION or lines removing @generated headers.
|
||||
if git diff --cached ':(exclude)*REVISION' | grep -vE "^(@@|diff|index|\-\-\-|\+\+\+|\- \* @generated SignedSource)" | grep "^[+-]" > /dev/null; then
|
||||
echo "Checking for changes"
|
||||
# Check if there are changes in the files other than REVISION or @generated headers
|
||||
# We also filter out the file name lines with "---" and "+++".
|
||||
if git diff -- . ':(exclude)*REVISION' | grep -vE "^(@@|diff|index|\-\-\-|\+\+\+|@generated SignedSource)" | grep "^[+-]" > /dev/null; then
|
||||
echo "Changes detected"
|
||||
echo "===== Changes ====="
|
||||
git --no-pager diff --cached ':(exclude)*REVISION' | grep -vE "^(@@|diff|index|\-\-\-|\+\+\+|\- \* @generated SignedSource)" | grep "^[+-]" | head -n 50
|
||||
echo "==================="
|
||||
echo "should_commit=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "No Changes detected"
|
||||
echo "should_commit=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
- name: Re-apply version changes
|
||||
if: inputs.force == true || (steps.check_should_commit.outputs.should_commit == 'true' && needs.download_artifacts.outputs.last_version_rn != '')
|
||||
if: 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 }}
|
||||
@@ -308,116 +348,20 @@ jobs:
|
||||
grep -rl "$LAST_VERSION" ./compiled-rn || echo "No files found with $LAST_VERSION"
|
||||
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: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
|
||||
- name: Will commit these changes
|
||||
if: steps.check_should_commit.outputs.should_commit == 'true'
|
||||
run: |
|
||||
echo ":"
|
||||
git add .
|
||||
- name: Signing files
|
||||
if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
// TODO: Move this to a script file.
|
||||
// We currently can't call scripts from the repo because
|
||||
// at this point in the workflow, we're on the compiled
|
||||
// artifact branch (so the scripts don't exist).
|
||||
// We can fix this with a composite action in the main repo.
|
||||
// This script is duplicated above.
|
||||
const fs = require('fs');
|
||||
const crypto = require('crypto');
|
||||
const {execSync} = require('child_process');
|
||||
|
||||
// TODO: when we move this to a script, we can use this from npm.
|
||||
// Copy of signedsource since we can't install deps on this branch.
|
||||
const GENERATED = '@' + 'generated';
|
||||
const NEWTOKEN = '<<SignedSource::*O*zOeWoEQle#+L!plEphiEmie@IsG>>';
|
||||
const PATTERN = new RegExp(`${GENERATED} (?:SignedSource<<([a-f0-9]{32})>>)`);
|
||||
|
||||
const TokenNotFoundError = new Error(
|
||||
`SignedSource.signFile(...): Cannot sign file without token: ${NEWTOKEN}`
|
||||
);
|
||||
|
||||
function hash(data, encoding) {
|
||||
const md5sum = crypto.createHash('md5');
|
||||
md5sum.update(data, encoding);
|
||||
return md5sum.digest('hex');
|
||||
}
|
||||
|
||||
const SignedSource = {
|
||||
getSigningToken() {
|
||||
return `${GENERATED} ${NEWTOKEN}`;
|
||||
},
|
||||
isSigned(data) {
|
||||
return PATTERN.exec(data) != null;
|
||||
},
|
||||
signFile(data) {
|
||||
if (!data.includes(NEWTOKEN)) {
|
||||
if (SignedSource.isSigned(data)) {
|
||||
// Signing a file that was previously signed.
|
||||
data = data.replace(PATTERN, SignedSource.getSigningToken());
|
||||
} else {
|
||||
throw TokenNotFoundError;
|
||||
}
|
||||
}
|
||||
return data.replace(NEWTOKEN, `SignedSource<<${hash(data, 'utf8')}>>`);
|
||||
},
|
||||
};
|
||||
|
||||
const directory = './compiled-rn';
|
||||
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!
|
||||
const files = result.split('\n').filter(file => file.endsWith('.js'));
|
||||
|
||||
if (files.length === 0) {
|
||||
throw new Error(
|
||||
'git status returned no files to sign. this job should not have run.'
|
||||
);
|
||||
} else {
|
||||
files.forEach(line => {
|
||||
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');
|
||||
const signedContents = SignedSource.signFile(
|
||||
originalContents
|
||||
// Need to add the header in, since it's not inserted at build time.
|
||||
.replace(' */\n', ` * ${SignedSource.getSigningToken()}\n */\n`)
|
||||
);
|
||||
|
||||
fs.writeFileSync(file, signedContents, 'utf8');
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
process.exitCode = 1;
|
||||
console.error('Error signing files:', e);
|
||||
}
|
||||
- name: Will commit these changes
|
||||
if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
|
||||
run: |
|
||||
git add .
|
||||
git status
|
||||
git status -u
|
||||
- name: Commit changes to branch
|
||||
if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
|
||||
uses: stefanzweifel/git-auto-commit-action@v5
|
||||
if: steps.check_should_commit.outputs.should_commit == 'true'
|
||||
uses: stefanzweifel/git-auto-commit-action@v4
|
||||
with:
|
||||
commit_message: |
|
||||
${{ github.event.workflow_run.head_commit.message || format('Manual build of {0}', github.event.workflow_run.head_sha || github.sha) }}
|
||||
${{ github.event.head_commit.message }}
|
||||
|
||||
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 }})
|
||||
DiffTrain build for commit https://github.com/facebook/react/commit/${{ github.sha }}.
|
||||
branch: builds/facebook-fbsource
|
||||
commit_user_name: ${{ github.triggering_actor }}
|
||||
commit_user_email: ${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}
|
||||
commit_user_name: ${{ github.actor }}
|
||||
commit_user_email: ${{ github.actor }}@users.noreply.github.com
|
||||
create_branch: true
|
||||
34
.github/workflows/compiler-playground.yml
vendored
Normal file
34
.github/workflows/compiler-playground.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: Compiler Playground
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
paths:
|
||||
- "compiler/**"
|
||||
- .github/workflows/compiler-playground.yml
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: compiler
|
||||
|
||||
jobs:
|
||||
playground:
|
||||
name: Test playground
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18.x
|
||||
cache: "yarn"
|
||||
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
|
||||
- run: npx playwright install --with-deps chromium
|
||||
- run: yarn workspace playground test
|
||||
@@ -1,4 +1,4 @@
|
||||
name: (Compiler) Rust
|
||||
name: React Compiler (Rust)
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -15,14 +15,9 @@ 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
|
||||
TZ: /usr/share/zoneinfo/America/Los_Angeles
|
||||
|
||||
defaults:
|
||||
run:
|
||||
@@ -1,21 +1,12 @@
|
||||
name: (Compiler) TypeScript
|
||||
name: React Compiler (TypeScript)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
paths:
|
||||
- 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
|
||||
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
|
||||
- "compiler/**"
|
||||
- .github/workflows/compiler-typescript.yml
|
||||
|
||||
defaults:
|
||||
run:
|
||||
@@ -32,6 +23,25 @@ jobs:
|
||||
- id: set-matrix
|
||||
run: echo "matrix=$(find packages -mindepth 1 -maxdepth 1 -type d | sed 's!packages/!!g' | tr '\n' ',' | sed s/.$// | jq -Rsc '. / "," - [""]')" >> $GITHUB_OUTPUT
|
||||
|
||||
# Hardcoded to improve parallelism for babel-plugin-react-compiler
|
||||
prettier:
|
||||
name: Run prettier
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18.x
|
||||
cache: "yarn"
|
||||
cache-dependency-path: compiler/yarn.lock
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('compiler/**/yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn prettier:ci
|
||||
|
||||
# Hardcoded to improve parallelism
|
||||
lint:
|
||||
name: Lint babel-plugin-react-compiler
|
||||
@@ -40,14 +50,14 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
node-version: 18.x
|
||||
cache: "yarn"
|
||||
cache-dependency-path: compiler/yarn.lock
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
|
||||
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('compiler/**/yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn workspace babel-plugin-react-compiler lint
|
||||
|
||||
@@ -59,15 +69,15 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
node-version: 18.x
|
||||
cache: "yarn"
|
||||
cache-dependency-path: compiler/yarn.lock
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
|
||||
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('compiler/**/yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn workspace babel-plugin-react-compiler jest
|
||||
|
||||
@@ -83,14 +93,14 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
node-version: 18.x
|
||||
cache: "yarn"
|
||||
cache-dependency-path: compiler/yarn.lock
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
|
||||
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('compiler/**/yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn workspace ${{ matrix.workspace_name }} test
|
||||
52
.github/workflows/compiler_playground.yml
vendored
52
.github/workflows/compiler_playground.yml
vendored
@@ -1,52 +0,0 @@
|
||||
name: (Compiler) Playground
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
paths:
|
||||
- compiler/**
|
||||
- .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
|
||||
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
|
||||
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: compiler/apps/playground
|
||||
|
||||
jobs:
|
||||
playground:
|
||||
name: Test playground
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
cache-dependency-path: compiler/**/yarn.lock
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
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 test
|
||||
- name: Archive test results
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-results
|
||||
path: test-results
|
||||
55
.github/workflows/compiler_prereleases.yml
vendored
55
.github/workflows/compiler_prereleases.yml
vendored
@@ -1,55 +0,0 @@
|
||||
name: (Compiler) Publish Prereleases
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
commit_sha:
|
||||
required: true
|
||||
default: ''
|
||||
type: string
|
||||
release_channel:
|
||||
required: true
|
||||
type: string
|
||||
dist_tag:
|
||||
required: true
|
||||
type: string
|
||||
version_name:
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
NPM_TOKEN:
|
||||
required: true
|
||||
|
||||
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:
|
||||
run:
|
||||
working-directory: compiler
|
||||
|
||||
jobs:
|
||||
publish_prerelease:
|
||||
name: Publish prelease (${{ inputs.release_channel }}) ${{ inputs.commit_sha }} @${{ inputs.dist_tag }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
cache-dependency-path: compiler/yarn.lock
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
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: |
|
||||
cp ./scripts/release/ci-npmrc ~/.npmrc
|
||||
scripts/release/publish.js --frfr --ci --versionName=${{ inputs.version_name }} --tag ${{ inputs.dist_tag }}
|
||||
@@ -1,31 +0,0 @@
|
||||
name: (Compiler) Publish Prereleases Manual
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
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
|
||||
|
||||
jobs:
|
||||
publish_prerelease_experimental:
|
||||
name: Publish to Experimental channel
|
||||
uses: facebook/react/.github/workflows/compiler_prereleases.yml@main
|
||||
with:
|
||||
commit_sha: ${{ inputs.prerelease_commit_sha || github.sha }}
|
||||
release_channel: ${{ inputs.release_channel }}
|
||||
dist_tag: ${{ inputs.dist_tag }}
|
||||
version_name: ${{ inputs.version_name }}
|
||||
secrets:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
@@ -1,21 +0,0 @@
|
||||
name: (Compiler) Publish Prereleases Nightly
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# At 10 minutes past 16:00 on Mon, Tue, Wed, Thu, and Fri
|
||||
- cron: 10 16 * * 1,2,3,4,5
|
||||
|
||||
env:
|
||||
TZ: /usr/share/zoneinfo/America/Los_Angeles
|
||||
|
||||
jobs:
|
||||
publish_prerelease_experimental:
|
||||
name: Publish to Experimental channel
|
||||
uses: facebook/react/.github/workflows/compiler_prereleases.yml@main
|
||||
with:
|
||||
commit_sha: ${{ github.sha }}
|
||||
release_channel: experimental
|
||||
dist_tag: experimental
|
||||
version_name: '0.0.0'
|
||||
secrets:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
@@ -1,21 +0,0 @@
|
||||
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 }}
|
||||
205
.github/workflows/devtools_check_repro.yml
vendored
Normal file
205
.github/workflows/devtools_check_repro.yml
vendored
Normal file
@@ -0,0 +1,205 @@
|
||||
name: DevTools Check for bug repro
|
||||
on:
|
||||
issues:
|
||||
types: [opened, edited]
|
||||
issue_comment:
|
||||
types: [created, edited]
|
||||
|
||||
jobs:
|
||||
check-repro:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v3
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const URL_REGEXP = /### Website or app[\r\n]+([^#]+)###/m;
|
||||
const REPRO_STEPS_REGEXP = /### Repro steps[\r\n]+([^#]+)###/m;
|
||||
const LABEL_NEEDS_MORE_INFORMATION = "Resolution: Needs More Information";
|
||||
const LABEL_UNCONFIRMED = "Status: Unconfirmed";
|
||||
|
||||
function debug(...args) {
|
||||
core.info(args.map(JSON.stringify).join(' '));
|
||||
}
|
||||
|
||||
if (context.payload.comment) {
|
||||
debug('Ignoring comment update.');
|
||||
return;
|
||||
}
|
||||
|
||||
const user = context.payload.sender.login;
|
||||
const issue = context.payload.issue;
|
||||
const body = issue.body;
|
||||
|
||||
const urlMatch = body.match(URL_REGEXP);
|
||||
const reproStepsMatch = body.match(REPRO_STEPS_REGEXP);
|
||||
|
||||
const url = urlMatch !== null ? urlMatch[1].trim() : null;
|
||||
const reproSteps = reproStepsMatch !== null ? reproStepsMatch[1].trim() : null;
|
||||
|
||||
if (!url || !reproSteps) {
|
||||
debug('This issue is not a DevTools bug report.');
|
||||
return;
|
||||
}
|
||||
|
||||
debug(`found URL "${url}"`);
|
||||
debug(`found repro steps "${reproSteps}"`);
|
||||
|
||||
async function createComment(comment) {
|
||||
// Format
|
||||
comment = comment
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.join("\n")
|
||||
.trim();
|
||||
|
||||
await github.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: comment,
|
||||
});
|
||||
}
|
||||
|
||||
async function getGitHubActionComments() {
|
||||
debug(`Loading existing comments...`);
|
||||
|
||||
const comments = await github.issues.listComments({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
});
|
||||
|
||||
return comments.data.filter(comment => {
|
||||
debug(`comment by user: "${comment.user.login}"`);
|
||||
return comment.user.login === 'github-actions[bot]';
|
||||
});
|
||||
}
|
||||
|
||||
async function getIssueLabels() {
|
||||
const issues = await github.issues.listLabelsOnIssue({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
});
|
||||
|
||||
return issues.data;
|
||||
}
|
||||
|
||||
async function updateIssue(state, assignees = []) {
|
||||
await github.issues.update({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state,
|
||||
assignees,
|
||||
});
|
||||
}
|
||||
|
||||
async function closeWithComment(comment) {
|
||||
if (issue.state !== 'open') {
|
||||
debug(`Issue is not open`);
|
||||
return;
|
||||
}
|
||||
|
||||
const labels = await getIssueLabels();
|
||||
const label = labels.find(label => label.name === LABEL_UNCONFIRMED);
|
||||
if (!label) {
|
||||
debug(`Issue was not opened via DevTools bug report template`);
|
||||
return;
|
||||
}
|
||||
|
||||
const comments = await getGitHubActionComments();
|
||||
if (comments.length > 0) {
|
||||
debug(`Already commented on issue; won't comment again`);
|
||||
return;
|
||||
}
|
||||
|
||||
debug(`Missing required information`);
|
||||
|
||||
await github.issues.addLabels({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: [LABEL_NEEDS_MORE_INFORMATION],
|
||||
});
|
||||
|
||||
await createComment(comment);
|
||||
await updateIssue('closed', [user]);
|
||||
}
|
||||
|
||||
async function openWithComment(comment) {
|
||||
if (issue.state !== 'closed') {
|
||||
debug(`Issue is already open`);
|
||||
return;
|
||||
}
|
||||
|
||||
const labels = await getIssueLabels();
|
||||
const label = labels.find(label => label.name === LABEL_NEEDS_MORE_INFORMATION);
|
||||
if (!label) {
|
||||
debug(`Issue was not tagged as needs information`);
|
||||
return;
|
||||
}
|
||||
|
||||
const comments = await getGitHubActionComments();
|
||||
if (comments.length === 0) {
|
||||
debug(`Issue was closed by someone else; won't reopen`);
|
||||
return;
|
||||
}
|
||||
|
||||
debug(`Re-opening closed issue`);
|
||||
|
||||
await github.issues.removeLabel({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
name: LABEL_NEEDS_MORE_INFORMATION,
|
||||
});
|
||||
|
||||
await createComment(comment);
|
||||
await updateIssue('open');
|
||||
}
|
||||
|
||||
const PROBABLY_NOT_A_URL_REGEX = /(^Chrome$|^Firefox$| Website)/i;
|
||||
|
||||
const COMMENT_HEADER = `
|
||||
@${user}: We're sorry you've seen this error. ❤️
|
||||
`.trim();
|
||||
|
||||
const COMMENT_FOOTER = `
|
||||
Please help us by providing a link to a CodeSandbox (https://codesandbox.io/s/new), a repository on GitHub, or a minimal code example that reproduces the problem. (Screenshots or videos can also be helpful if they help provide context on how to repro the bug.)
|
||||
|
||||
Here are some tips for providing a minimal example: https://stackoverflow.com/help/mcve
|
||||
|
||||
Issues without repros are automatically closed but we will re-open if you update with repro info.
|
||||
`.trim();
|
||||
|
||||
if (url.includes("localhost")) {
|
||||
closeWithComment(`
|
||||
${COMMENT_HEADER}
|
||||
|
||||
Unfortunately the URL you provided ("localhost") is not publicly accessible. (This means that we will not be able to reproduce the problem you're reporting.)
|
||||
|
||||
${COMMENT_FOOTER}
|
||||
`);
|
||||
} else if (url.length < 10 || url.match(PROBABLY_NOT_A_URL_REGEX)) {
|
||||
closeWithComment(`
|
||||
${COMMENT_HEADER}
|
||||
|
||||
It looks like you forgot to specify a valid URL. (This means that we will not be able to reproduce the problem you're reporting.)
|
||||
|
||||
${COMMENT_FOOTER}
|
||||
`);
|
||||
} else if (reproSteps.length < 25) {
|
||||
closeWithComment(`
|
||||
${COMMENT_HEADER}
|
||||
|
||||
Unfortunately, it doesn't look like this issue has enough info for one of us to reproduce and fix it though.
|
||||
|
||||
${COMMENT_FOOTER}
|
||||
`);
|
||||
} else {
|
||||
openWithComment(`
|
||||
Thank you for providing repro steps! Re-opening issue now for triage.
|
||||
`);
|
||||
}
|
||||
178
.github/workflows/devtools_regression_tests.yml
vendored
178
.github/workflows/devtools_regression_tests.yml
vendored
@@ -1,178 +0,0 @@
|
||||
name: (DevTools) Regression Tests
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: 0 0 * * *
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
commit_sha:
|
||||
required: false
|
||||
type: string
|
||||
|
||||
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
|
||||
|
||||
jobs:
|
||||
download_build:
|
||||
name: Download base build
|
||||
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
|
||||
- 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
|
||||
- run: yarn install --frozen-lockfile
|
||||
working-directory: scripts/release
|
||||
- name: Download react-devtools artifacts for base revision
|
||||
run: |
|
||||
git fetch origin main
|
||||
GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=${{ inputs.commit_sha || '$(git rev-parse origin/main)' }}
|
||||
- name: Display structure of build
|
||||
run: ls -R build
|
||||
- name: Archive build
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build
|
||||
path: build
|
||||
|
||||
build_devtools_and_process_artifacts:
|
||||
name: Build DevTools and process artifacts
|
||||
needs: download_build
|
||||
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
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
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
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: build
|
||||
path: build
|
||||
- run: ./scripts/ci/pack_and_store_devtools_artifacts.sh
|
||||
env:
|
||||
RELEASE_CHANNEL: experimental
|
||||
- name: Display structure of build
|
||||
run: ls -R build
|
||||
- name: Archive devtools build
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: react-devtools
|
||||
path: build/devtools.tgz
|
||||
# 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
|
||||
- name: Archive firefox extension
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: react-devtools-firefox-extension
|
||||
path: build/devtools/firefox-extension.zip
|
||||
|
||||
run_devtools_tests_for_versions:
|
||||
name: Run DevTools tests for versions
|
||||
needs: build_devtools_and_process_artifacts
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
version:
|
||||
- "16.0"
|
||||
- "16.5" # schedule package
|
||||
- "16.8" # hooks
|
||||
- "17.0"
|
||||
- "18.0"
|
||||
- "18.2" # compiler polyfill
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- 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
|
||||
id: node_modules
|
||||
with:
|
||||
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
|
||||
- name: Display structure of build
|
||||
run: ls -R build
|
||||
- run: ./scripts/ci/download_devtools_regression_build.js ${{ matrix.version }} --replaceBuild
|
||||
- run: node ./scripts/jest/jest-cli.js --build --project devtools --release-channel=experimental --reactVersion ${{ matrix.version }} --ci
|
||||
|
||||
run_devtools_e2e_tests_for_versions:
|
||||
name: Run DevTools e2e tests for versions
|
||||
needs: build_devtools_and_process_artifacts
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
version:
|
||||
- "16.0"
|
||||
- "16.5" # schedule package
|
||||
- "16.8" # hooks
|
||||
- "17.0"
|
||||
- "18.0"
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- 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
|
||||
id: node_modules
|
||||
with:
|
||||
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
|
||||
- name: Display structure of build
|
||||
run: ls -R build
|
||||
- name: Playwright install deps
|
||||
run: |
|
||||
npx playwright install
|
||||
sudo npx playwright install-deps
|
||||
- run: ./scripts/ci/download_devtools_regression_build.js ${{ matrix.version }}
|
||||
- run: ls -R build-regression
|
||||
- run: ./scripts/ci/run_devtools_e2e_tests.js ${{ matrix.version }}
|
||||
env:
|
||||
RELEASE_CHANNEL: experimental
|
||||
- name: Cleanup build regression folder
|
||||
run: rm -r ./build-regression
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: screenshots
|
||||
path: ./tmp/screenshots
|
||||
@@ -1,19 +1,20 @@
|
||||
name: (Runtime) Fuzz tests
|
||||
|
||||
name: facebook/react/fuzz_tests
|
||||
on:
|
||||
schedule:
|
||||
- cron: 0 * * * *
|
||||
- cron: 0 * * * *
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
TZ: /usr/share/zoneinfo/America/Los_Angeles
|
||||
|
||||
inputs:
|
||||
prerelease_commit_sha:
|
||||
required: false
|
||||
jobs:
|
||||
test_fuzz:
|
||||
if: inputs.prerelease_commit_sha == ''
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TZ: "/usr/share/zoneinfo/America/Los_Angeles"
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: actions/setup-node@v4
|
||||
645
.github/workflows/runtime_build_and_test.yml
vendored
645
.github/workflows/runtime_build_and_test.yml
vendored
@@ -1,645 +0,0 @@
|
||||
name: (Runtime) Build and Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
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
|
||||
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
|
||||
|
||||
jobs:
|
||||
# ----- FLOW -----
|
||||
discover_flow_inline_configs:
|
||||
name: Discover flow inline configs
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.result }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/github-script@v7
|
||||
id: set-matrix
|
||||
with:
|
||||
script: |
|
||||
const inlinedHostConfigs = require('./scripts/shared/inlinedHostConfigs.js');
|
||||
return inlinedHostConfigs.map(config => config.shortName);
|
||||
|
||||
flow:
|
||||
name: Flow check ${{ matrix.flow_inline_config_shortname }}
|
||||
needs: discover_flow_inline_configs
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
strategy:
|
||||
matrix:
|
||||
flow_inline_config_shortname: ${{ fromJSON(needs.discover_flow_inline_configs.outputs.matrix) }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- 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
|
||||
id: node_modules
|
||||
with:
|
||||
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: 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
|
||||
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
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
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 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)
|
||||
|
||||
# ----- FEATURE FLAGS -----
|
||||
flags:
|
||||
name: Check flags
|
||||
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
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
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 flags
|
||||
|
||||
# ----- TESTS -----
|
||||
test:
|
||||
name: yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }})
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
params:
|
||||
- "-r=stable --env=development"
|
||||
- "-r=stable --env=production"
|
||||
- "-r=experimental --env=development"
|
||||
- "-r=experimental --env=production"
|
||||
- "-r=www-classic --env=development --variant=false"
|
||||
- "-r=www-classic --env=production --variant=false"
|
||||
- "-r=www-classic --env=development --variant=true"
|
||||
- "-r=www-classic --env=production --variant=true"
|
||||
- "-r=www-modern --env=development --variant=false"
|
||||
- "-r=www-modern --env=production --variant=false"
|
||||
- "-r=www-modern --env=development --variant=true"
|
||||
- "-r=www-modern --env=production --variant=true"
|
||||
- "-r=xplat --env=development --variant=false"
|
||||
- "-r=xplat --env=development --variant=true"
|
||||
- "-r=xplat --env=production --variant=false"
|
||||
- "-r=xplat --env=production --variant=true"
|
||||
# TODO: Test more persistent configurations?
|
||||
- "-r=stable --env=development --persistent"
|
||||
- "-r=experimental --env=development --persistent"
|
||||
shard:
|
||||
- 1/5
|
||||
- 2/5
|
||||
- 3/5
|
||||
- 4/5
|
||||
- 5/5
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- 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
|
||||
id: node_modules
|
||||
with:
|
||||
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
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
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]
|
||||
release_channel: [stable, experimental]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
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@v4
|
||||
id: node_modules
|
||||
with:
|
||||
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 build --index=${{ matrix.worker_id }} --total=20 --r=${{ matrix.release_channel }} --ci
|
||||
env:
|
||||
CI: github
|
||||
RELEASE_CHANNEL: ${{ matrix.release_channel }}
|
||||
NODE_INDEX: ${{ matrix.worker_id }}
|
||||
- name: Lint build
|
||||
run: yarn lint-build
|
||||
- name: Display structure of build
|
||||
run: ls -R build
|
||||
- name: Archive build
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: _build_${{ matrix.worker_id }}_${{ matrix.release_channel }}
|
||||
path: build
|
||||
|
||||
test_build:
|
||||
name: yarn test-build
|
||||
needs: build_and_lint
|
||||
strategy:
|
||||
matrix:
|
||||
test_params: [
|
||||
# Intentionally passing these as strings instead of creating a
|
||||
# separate parameter per CLI argument, since it's easier to
|
||||
# control/see which combinations we want to run.
|
||||
-r=stable --env=development,
|
||||
-r=stable --env=production,
|
||||
-r=experimental --env=development,
|
||||
-r=experimental --env=production,
|
||||
|
||||
# Dev Tools
|
||||
--project=devtools -r=experimental,
|
||||
|
||||
# TODO: Update test config to support www build tests
|
||||
# - "-r=www-classic --env=development --variant=false"
|
||||
# - "-r=www-classic --env=production --variant=false"
|
||||
# - "-r=www-classic --env=development --variant=true"
|
||||
# - "-r=www-classic --env=production --variant=true"
|
||||
# - "-r=www-modern --env=development --variant=false"
|
||||
# - "-r=www-modern --env=production --variant=false"
|
||||
# - "-r=www-modern --env=development --variant=true"
|
||||
# - "-r=www-modern --env=production --variant=true"
|
||||
|
||||
# TODO: Update test config to support xplat build tests
|
||||
# - "-r=xplat --env=development --variant=false"
|
||||
# - "-r=xplat --env=development --variant=true"
|
||||
# - "-r=xplat --env=production --variant=false"
|
||||
# - "-r=xplat --env=production --variant=true"
|
||||
|
||||
# TODO: Test more persistent configurations?
|
||||
]
|
||||
shard:
|
||||
- 1/3
|
||||
- 2/3
|
||||
- 3/3
|
||||
continue-on-error: true
|
||||
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
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
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
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: _build_*
|
||||
path: build
|
||||
merge-multiple: true
|
||||
- name: Display structure of build
|
||||
run: ls -R build
|
||||
- run: yarn test --build ${{ matrix.test_params }} --shard=${{ matrix.shard }} --ci
|
||||
|
||||
process_artifacts_combined:
|
||||
name: Process artifacts combined
|
||||
needs: build_and_lint
|
||||
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
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
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
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: _build_*
|
||||
path: build
|
||||
merge-multiple: true
|
||||
- name: Display structure of build
|
||||
run: ls -R build
|
||||
- run: echo ${{ github.sha }} >> build/COMMIT_SHA
|
||||
- name: Scrape warning messages
|
||||
run: |
|
||||
mkdir -p ./build/__test_utils__
|
||||
node ./scripts/print-warnings/print-warnings.js > build/__test_utils__/ReactAllWarnings.js
|
||||
# Compress build directory into a single tarball for easy download
|
||||
- run: tar -zcvf ./build.tgz ./build
|
||||
# TODO: Migrate scripts to use `build` directory instead of `build2`
|
||||
- run: cp ./build.tgz ./build2.tgz
|
||||
- name: Archive build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: artifacts_combined
|
||||
path: |
|
||||
./build.tgz
|
||||
./build2.tgz
|
||||
|
||||
check_error_codes:
|
||||
name: Search build artifacts for unminified errors
|
||||
needs: build_and_lint
|
||||
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
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
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
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: _build_*
|
||||
path: build
|
||||
merge-multiple: true
|
||||
- name: Display structure of build
|
||||
run: ls -R build
|
||||
- name: Search build artifacts for unminified errors
|
||||
run: |
|
||||
yarn extract-errors
|
||||
git diff --quiet || (echo "Found unminified errors. Either update the error codes map or disable error minification for the affected build, if appropriate." && false)
|
||||
|
||||
check_release_dependencies:
|
||||
name: Check release dependencies
|
||||
needs: build_and_lint
|
||||
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
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
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
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: _build_*
|
||||
path: build
|
||||
merge-multiple: true
|
||||
- name: Display structure of build
|
||||
run: ls -R build
|
||||
- run: yarn check-release-dependencies
|
||||
|
||||
RELEASE_CHANNEL_stable_yarn_test_dom_fixtures:
|
||||
name: Check fixtures DOM (stable)
|
||||
needs: build_and_lint
|
||||
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
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
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 install --frozen-lockfile
|
||||
- run: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
|
||||
working-directory: fixtures/dom
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: _build_*
|
||||
path: build
|
||||
merge-multiple: true
|
||||
- name: Display structure of build
|
||||
run: ls -R build
|
||||
- name: Run DOM fixture tests
|
||||
run: |
|
||||
yarn predev
|
||||
yarn test
|
||||
working-directory: fixtures/dom
|
||||
env:
|
||||
RELEASE_CHANNEL: stable
|
||||
|
||||
# ----- FLIGHT -----
|
||||
run_fixtures_flight_tests:
|
||||
name: Run fixtures Flight tests
|
||||
needs: build_and_lint
|
||||
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
|
||||
# Fixture copies some built packages from the workroot after install.
|
||||
# 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
|
||||
id: node_modules
|
||||
with:
|
||||
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
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: _build_*
|
||||
path: build
|
||||
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
|
||||
sudo npx playwright install-deps
|
||||
- name: Run tests
|
||||
working-directory: fixtures/flight
|
||||
run: yarn test
|
||||
env:
|
||||
# Otherwise the webserver is a blackbox
|
||||
DEBUG: pw:webserver
|
||||
- name: Archive Flight fixture artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: flight-playwright-report
|
||||
path: fixtures/flight/playwright-report
|
||||
- name: Archive Flight fixture artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: flight-test-results
|
||||
path: fixtures/flight/test-results
|
||||
|
||||
# ----- DEVTOOLS -----
|
||||
build_devtools_and_process_artifacts:
|
||||
name: Build DevTools and process artifacts
|
||||
needs: build_and_lint
|
||||
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
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
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
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: _build_*
|
||||
path: build
|
||||
merge-multiple: true
|
||||
- run: ./scripts/ci/pack_and_store_devtools_artifacts.sh
|
||||
env:
|
||||
RELEASE_CHANNEL: experimental
|
||||
- name: Display structure of build
|
||||
run: ls -R build
|
||||
- name: Archive devtools build
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: react-devtools
|
||||
path: build/devtools.tgz
|
||||
# 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
|
||||
- name: Archive firefox extension
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: react-devtools-firefox-extension
|
||||
path: build/devtools/firefox-extension.zip
|
||||
|
||||
run_devtools_e2e_tests:
|
||||
name: Run DevTools e2e tests
|
||||
needs: build_devtools_and_process_artifacts
|
||||
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
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
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
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: _build_*
|
||||
path: build
|
||||
merge-multiple: true
|
||||
- run: |
|
||||
npx playwright install
|
||||
sudo npx playwright install-deps
|
||||
- run: ./scripts/ci/run_devtools_e2e_tests.js
|
||||
env:
|
||||
RELEASE_CHANNEL: experimental
|
||||
|
||||
# ----- SIZEBOT -----
|
||||
download_base_build_for_sizebot:
|
||||
if: ${{ github.event_name == 'pull_request' && github.ref_name != 'main' }}
|
||||
name: Download base build for sizebot
|
||||
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
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
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
|
||||
- name: Download artifacts for base revision
|
||||
run: |
|
||||
git fetch origin main
|
||||
GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=$(git rev-parse origin/main)
|
||||
mv ./build ./base-build
|
||||
# TODO: The `download-experimental-build` script copies the npm
|
||||
# packages into the `node_modules` directory. This is a historical
|
||||
# quirk of how the release script works. Let's pretend they
|
||||
# don't exist.
|
||||
- name: Delete extraneous files
|
||||
run: rm -rf ./base-build/node_modules
|
||||
- name: Display structure of base-build
|
||||
run: ls -R base-build
|
||||
- name: Archive base-build
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: base-build
|
||||
path: base-build
|
||||
|
||||
sizebot:
|
||||
name: Run sizebot
|
||||
needs: [build_and_lint, download_base_build_for_sizebot]
|
||||
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
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
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
|
||||
- name: Restore archived build for PR
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: _build_*
|
||||
path: build
|
||||
merge-multiple: true
|
||||
- name: Scrape warning messages
|
||||
run: |
|
||||
mkdir -p ./build/__test_utils__
|
||||
node ./scripts/print-warnings/print-warnings.js > build/__test_utils__/ReactAllWarnings.js
|
||||
- name: Display structure of build for PR
|
||||
run: ls -R build
|
||||
- name: Restore archived base-build from origin/main
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: base-build
|
||||
path: base-build
|
||||
- name: Display structure of base-build from origin/main
|
||||
run: ls -R base-build
|
||||
- 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
|
||||
52
.github/workflows/runtime_prereleases.yml
vendored
52
.github/workflows/runtime_prereleases.yml
vendored
@@ -1,52 +0,0 @@
|
||||
name: (Runtime) Publish Prereleases
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
commit_sha:
|
||||
required: true
|
||||
default: ''
|
||||
type: string
|
||||
release_channel:
|
||||
required: true
|
||||
type: string
|
||||
dist_tag:
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
NPM_TOKEN:
|
||||
required: true
|
||||
|
||||
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:
|
||||
publish_prerelease:
|
||||
name: Publish prelease (${{ inputs.release_channel }}) ${{ inputs.commit_sha }} @${{ inputs.dist_tag }}
|
||||
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
|
||||
- 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
|
||||
- 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
|
||||
scripts/release/publish.js --ci --tags ${{ inputs.dist_tag }}
|
||||
47
.github/workflows/runtime_prereleases_manual.yml
vendored
47
.github/workflows/runtime_prereleases_manual.yml
vendored
@@ -1,47 +0,0 @@
|
||||
name: (Runtime) Publish Prereleases Manual
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
prerelease_commit_sha:
|
||||
required: true
|
||||
|
||||
env:
|
||||
TZ: /usr/share/zoneinfo/America/Los_Angeles
|
||||
|
||||
jobs:
|
||||
|
||||
publish_prerelease_canary:
|
||||
name: Publish to Canary channel
|
||||
uses: facebook/react/.github/workflows/runtime_prereleases.yml@main
|
||||
with:
|
||||
commit_sha: ${{ inputs.prerelease_commit_sha }}
|
||||
release_channel: stable
|
||||
# The tags to use when publishing canaries. The main one we should
|
||||
# always include is "canary" but we can use multiple (e.g. alpha,
|
||||
# beta, rc). To declare multiple, use a comma-separated string, like
|
||||
# this:
|
||||
# dist_tag: "canary,alpha,beta,rc"
|
||||
#
|
||||
# TODO: We currently tag canaries with "next" in addition to "canary"
|
||||
# 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
|
||||
secrets:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
publish_prerelease_experimental:
|
||||
name: Publish to Experimental channel
|
||||
uses: facebook/react/.github/workflows/runtime_prereleases.yml@main
|
||||
# NOTE: Intentionally running these jobs sequentially because npm
|
||||
# will sometimes fail if you try to concurrently publish two
|
||||
# different versions of the same package, even if they use different
|
||||
# dist tags.
|
||||
needs: publish_prerelease_canary
|
||||
with:
|
||||
commit_sha: ${{ inputs.prerelease_commit_sha }}
|
||||
release_channel: experimental
|
||||
dist_tag: experimental
|
||||
secrets:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
@@ -1,35 +0,0 @@
|
||||
name: (Runtime) Publish Prereleases Nightly
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# At 10 minutes past 16:00 on Mon, Tue, Wed, Thu, and Fri
|
||||
- cron: 10 16 * * 1,2,3,4,5
|
||||
|
||||
env:
|
||||
TZ: /usr/share/zoneinfo/America/Los_Angeles
|
||||
|
||||
jobs:
|
||||
publish_prerelease_canary:
|
||||
name: Publish to Canary channel
|
||||
uses: facebook/react/.github/workflows/runtime_prereleases.yml@main
|
||||
with:
|
||||
commit_sha: ${{ github.sha }}
|
||||
release_channel: stable
|
||||
dist_tag: canary,next
|
||||
secrets:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
publish_prerelease_experimental:
|
||||
name: Publish to Experimental channel
|
||||
uses: facebook/react/.github/workflows/runtime_prereleases.yml@main
|
||||
# NOTE: Intentionally running these jobs sequentially because npm
|
||||
# will sometimes fail if you try to concurrently publish two
|
||||
# different versions of the same package, even if they use different
|
||||
# dist tags.
|
||||
needs: publish_prerelease_canary
|
||||
with:
|
||||
commit_sha: ${{ github.sha }}
|
||||
release_channel: experimental
|
||||
dist_tag: experimental
|
||||
secrets:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
96
.github/workflows/shared_lint.yml
vendored
96
.github/workflows/shared_lint.yml
vendored
@@ -1,96 +0,0 @@
|
||||
name: (Shared) Lint
|
||||
|
||||
on:
|
||||
push:
|
||||
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
|
||||
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
|
||||
|
||||
jobs:
|
||||
prettier:
|
||||
name: Run prettier
|
||||
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
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
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
|
||||
- run: yarn prettier-check
|
||||
|
||||
eslint:
|
||||
name: Run eslint
|
||||
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
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
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
|
||||
- run: node ./scripts/tasks/eslint
|
||||
|
||||
check_license:
|
||||
name: Check license
|
||||
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
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
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
|
||||
- run: ./scripts/ci/check_license.sh
|
||||
|
||||
test_print_warnings:
|
||||
name: Test print warnings
|
||||
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
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
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
|
||||
- run: ./scripts/ci/test_print_warnings.sh
|
||||
@@ -1,13 +1,9 @@
|
||||
# Configuration for stale action workflow - https://github.com/actions/stale
|
||||
name: (Shared) Manage stale issues and PRs
|
||||
name: 'Manage stale issues and PRs'
|
||||
on:
|
||||
schedule:
|
||||
# Run hourly
|
||||
- cron: '0 * * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
TZ: /usr/share/zoneinfo/America/Los_Angeles
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
@@ -1,6 +1,6 @@
|
||||
# react runtime
|
||||
build
|
||||
|
||||
compiler
|
||||
packages/react-devtools-core/dist
|
||||
packages/react-devtools-extensions/chrome/build
|
||||
packages/react-devtools-extensions/firefox/build
|
||||
@@ -14,28 +14,3 @@ packages/react-devtools-shared/src/hooks/__tests__/__source__/__untransformed__/
|
||||
packages/react-devtools-shell/dist
|
||||
packages/react-devtools-timeline/dist
|
||||
packages/react-devtools-timeline/static
|
||||
|
||||
# react compiler
|
||||
compiler/**/dist
|
||||
compiler/**/__tests__/fixtures/**/*.expect.md
|
||||
compiler/**/.next
|
||||
|
||||
# contains invalid graphql`...` which results in a promise rejection error from `yarn prettier-all`.
|
||||
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-kitchensink.js
|
||||
|
||||
compiler/crates
|
||||
compiler/target
|
||||
compiler/apps/playground/public
|
||||
|
||||
compiler/**/LICENSE
|
||||
compiler/*.md*
|
||||
compiler/*.json
|
||||
compiler/*.css
|
||||
compiler/*.webmanifest
|
||||
compiler/*.map
|
||||
compiler/*.sh
|
||||
compiler/*.txt
|
||||
compiler/*.ico
|
||||
compiler/*.svg
|
||||
compiler/*.lock
|
||||
compiler/*.toml
|
||||
|
||||
@@ -1,23 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
const {esNextPaths} = require('./scripts/shared/pathsByLanguageVersion');
|
||||
const {
|
||||
esNextPaths,
|
||||
typescriptPaths,
|
||||
} = require('./scripts/shared/pathsByLanguageVersion');
|
||||
|
||||
module.exports = {
|
||||
plugins: ['prettier-plugin-hermes-parser'],
|
||||
bracketSpacing: false,
|
||||
singleQuote: true,
|
||||
bracketSameLine: true,
|
||||
trailingComma: 'es5',
|
||||
printWidth: 80,
|
||||
parser: 'hermes',
|
||||
parser: 'flow',
|
||||
arrowParens: 'avoid',
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.code-workspace'],
|
||||
options: {
|
||||
parser: 'json-stringify',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: esNextPaths,
|
||||
options: {
|
||||
@@ -25,7 +21,7 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['*.ts', '*.tsx'],
|
||||
files: typescriptPaths,
|
||||
options: {
|
||||
trailingComma: 'all',
|
||||
parser: 'typescript',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# [React](https://react.dev/) · [](https://github.com/facebook/react/blob/main/LICENSE) [](https://www.npmjs.com/package/react) [](https://github.com/facebook/react/actions/workflows/runtime_build_and_test.yml) [](https://github.com/facebook/react/actions/workflows/compiler_typescript.yml) [](https://legacy.reactjs.org/docs/how-to-contribute.html#your-first-pull-request)
|
||||
# [React](https://react.dev/) · [](https://github.com/facebook/react/blob/main/LICENSE) [](https://www.npmjs.com/package/react) [](https://circleci.com/gh/facebook/react) [](https://legacy.reactjs.org/docs/how-to-contribute.html#your-first-pull-request)
|
||||
|
||||
React is a JavaScript library for building user interfaces.
|
||||
|
||||
@@ -18,7 +18,7 @@ React has been designed for gradual adoption from the start, and **you can use a
|
||||
|
||||
## Documentation
|
||||
|
||||
You can find the React documentation [on the website](https://react.dev/).
|
||||
You can find the React documentation [on the website](https://react.dev/).
|
||||
|
||||
Check out the [Getting Started](https://react.dev/learn) page for a quick overview.
|
||||
|
||||
@@ -55,7 +55,7 @@ root.render(<HelloMessage name="Taylor" />);
|
||||
|
||||
This example will render "Hello Taylor" into a container on the page.
|
||||
|
||||
You'll notice that we used an HTML-like syntax; [we call it JSX](https://react.dev/learn#writing-markup-with-jsx). JSX is not required to use React, but it makes code more readable, and writing it feels like writing HTML.
|
||||
You'll notice that we used an HTML-like syntax; [we call it JSX](https://react.dev/learn#writing-markup-with-jsx). JSX is not required to use React, but it makes code more readable, and writing it feels like writing HTML.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
@@ -25,12 +25,12 @@ const ReactVersion = '19.0.0';
|
||||
// choose to change it to "alpha", "beta", "rc", etc.
|
||||
//
|
||||
// It only affects the label used in the version string. To customize the
|
||||
// npm dist tags used during publish, refer to .github/workflows/runtime_prereleases_*.yml.
|
||||
// npm dist tags used during publish, refer to .circleci/config.yml.
|
||||
const canaryChannelLabel = 'rc';
|
||||
|
||||
// If the canaryChannelLabel is "rc", the build pipeline will use this to build
|
||||
// an RC version of the packages.
|
||||
const rcNumber = 1;
|
||||
const rcNumber = 0;
|
||||
|
||||
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 = ['react-markup'];
|
||||
const experimentalPackages = [];
|
||||
|
||||
module.exports = {
|
||||
ReactVersion,
|
||||
|
||||
@@ -43,12 +43,6 @@ module.exports = {
|
||||
|
||||
"multiline-comment-style": ["error", "starred-block"],
|
||||
|
||||
/**
|
||||
* We sometimes need to check for control characters in regexes for things like preserving input
|
||||
* strings
|
||||
*/
|
||||
"no-control-regex": "off",
|
||||
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
|
||||
/*
|
||||
@@ -88,7 +82,7 @@ module.exports = {
|
||||
],
|
||||
"@typescript-eslint/array-type": ["error", { default: "generic" }],
|
||||
"@typescript-eslint/triple-slash-reference": "off",
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"@typescript-eslint/no-var-requires": "off"
|
||||
},
|
||||
parser: "@typescript-eslint/parser",
|
||||
plugins: ["@typescript-eslint"],
|
||||
|
||||
21
compiler/.prettierignore
Normal file
21
compiler/.prettierignore
Normal file
@@ -0,0 +1,21 @@
|
||||
**/dist
|
||||
**/__tests__/fixtures/**/*.expect.md
|
||||
**/__tests__/fixtures/**/*.flow.js
|
||||
**/.next
|
||||
|
||||
crates
|
||||
apps/playground/public
|
||||
|
||||
**/LICENSE
|
||||
.*
|
||||
*.md*
|
||||
*.json
|
||||
*.css
|
||||
*.webmanifest
|
||||
*.map
|
||||
*.sh
|
||||
*.txt
|
||||
*.ico
|
||||
*.svg
|
||||
*.lock
|
||||
*.toml
|
||||
9
compiler/.prettierrc.js
Normal file
9
compiler/.prettierrc.js
Normal file
@@ -0,0 +1,9 @@
|
||||
const config = {
|
||||
requirePragma: false,
|
||||
parser: "babel-ts",
|
||||
semi: true,
|
||||
singleQuote: false,
|
||||
trailingComma: "es5"
|
||||
}
|
||||
|
||||
module.exports = config;
|
||||
8
compiler/.vscode/settings.json
vendored
Normal file
8
compiler/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"[typescript][typescriptreact]": {
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": "explicit"
|
||||
}
|
||||
}
|
||||
}
|
||||
1
compiler/.watchmanconfig
Normal file
1
compiler/.watchmanconfig
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
80
compiler/CODE_OF_CONDUCT.md
Normal file
80
compiler/CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to make participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all project spaces, and it also applies when
|
||||
an individual is representing the project or its community in public spaces.
|
||||
Examples of representing a project or community include using an official
|
||||
project e-mail address, posting via an official social media account, or acting
|
||||
as an appointed representative at an online or offline event. Representation of
|
||||
a project may be further defined and clarified by project maintainers.
|
||||
|
||||
This Code of Conduct also applies outside the project spaces when there is a
|
||||
reasonable belief that an individual's behavior may have a negative impact on
|
||||
the project or its community.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at <opensource-conduct@fb.com>. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
31
compiler/CONTRIBUTING.md
Normal file
31
compiler/CONTRIBUTING.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Contributing to react-forget
|
||||
We want to make contributing to this project as easy and transparent as
|
||||
possible.
|
||||
|
||||
## Pull Requests
|
||||
We actively welcome your pull requests.
|
||||
|
||||
1. Fork the repo and create your branch from `main`.
|
||||
2. If you've added code that should be tested, add tests.
|
||||
3. If you've changed APIs, update the documentation.
|
||||
4. Ensure the test suite passes.
|
||||
5. Make sure your code lints.
|
||||
6. If you haven't already, complete the Contributor License Agreement ("CLA").
|
||||
|
||||
## Contributor License Agreement ("CLA")
|
||||
In order to accept your pull request, we need you to submit a CLA. You only need
|
||||
to do this once to work on any of Facebook's open source projects.
|
||||
|
||||
Complete your CLA here: <https://code.facebook.com/cla>
|
||||
|
||||
## Issues
|
||||
We use GitHub issues to track public bugs. Please ensure your description is
|
||||
clear and has sufficient instructions to be able to reproduce the issue.
|
||||
|
||||
Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe
|
||||
disclosure of security bugs. In those cases, please go through the process
|
||||
outlined on that page and do not file a public issue.
|
||||
|
||||
## License
|
||||
By contributing to react-forget, you agree that your contributions will be licensed
|
||||
under the LICENSE file in the root directory of this source tree.
|
||||
21
compiler/LICENSE
Normal file
21
compiler/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -5,8 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {expect, test} from '@playwright/test';
|
||||
import {encodeStore, type Store} from '../../lib/stores';
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { encodeStore, type Store } from "../../lib/stores";
|
||||
|
||||
const STORE: Store = {
|
||||
source: `export default function TestComponent({ x }) {
|
||||
@@ -17,33 +17,33 @@ const STORE: Store = {
|
||||
const HASH = encodeStore(STORE);
|
||||
|
||||
function concat(data: Array<string>): string {
|
||||
return data.join('');
|
||||
return data.join("");
|
||||
}
|
||||
|
||||
test('editor should compile successfully', async ({page}) => {
|
||||
await page.goto(`/#${HASH}`, {waitUntil: 'networkidle'});
|
||||
test("editor should compile successfully", async ({ page }) => {
|
||||
await page.goto(`/#${HASH}`, { waitUntil: "networkidle" });
|
||||
await page.screenshot({
|
||||
fullPage: true,
|
||||
path: 'test-results/00-on-networkidle.png',
|
||||
path: "test-results/00-on-networkidle.png",
|
||||
});
|
||||
|
||||
// User input from hash compiles
|
||||
await page.screenshot({
|
||||
fullPage: true,
|
||||
path: 'test-results/01-show-js-before.png',
|
||||
path: "test-results/01-show-js-before.png",
|
||||
});
|
||||
const userInput =
|
||||
(await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? [];
|
||||
expect(concat(userInput)).toMatchSnapshot('user-input.txt');
|
||||
(await page.locator(".monaco-editor").nth(2).allInnerTexts()) ?? [];
|
||||
expect(concat(userInput)).toMatchSnapshot("user-input.txt");
|
||||
|
||||
// Reset button works
|
||||
page.on('dialog', dialog => dialog.accept());
|
||||
await page.getByRole('button', {name: 'Reset'}).click();
|
||||
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-show-js-after.png",
|
||||
});
|
||||
const defaultInput =
|
||||
(await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? [];
|
||||
expect(concat(defaultInput)).toMatchSnapshot('default-input.txt');
|
||||
(await page.locator(".monaco-editor").nth(2).allInnerTexts()) ?? [];
|
||||
expect(concat(defaultInput)).toMatchSnapshot("default-input.txt");
|
||||
});
|
||||
|
||||
@@ -5,24 +5,25 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import type {NextPage} from 'next';
|
||||
import Head from 'next/head';
|
||||
import {SnackbarProvider} from 'notistack';
|
||||
import {Editor, Header, StoreProvider} from '../components';
|
||||
import MessageSnackbar from '../components/Message';
|
||||
import type { NextPage } from "next";
|
||||
import Head from "next/head";
|
||||
import { SnackbarProvider } from "notistack";
|
||||
import { Editor, Header, StoreProvider } from "../components";
|
||||
import MessageSnackbar from "../components/Message";
|
||||
|
||||
const Home: NextPage = () => {
|
||||
return (
|
||||
<div className="flex flex-col w-screen h-screen font-light">
|
||||
<Head>
|
||||
<title>
|
||||
{process.env.NODE_ENV === 'development'
|
||||
? '[DEV] React Compiler Playground'
|
||||
: 'React Compiler Playground'}
|
||||
{process.env.NODE_ENV === "development"
|
||||
? "[DEV] React Compiler Playground"
|
||||
: "React Compiler Playground"}
|
||||
</title>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"></meta>
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"
|
||||
></meta>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<link
|
||||
@@ -44,7 +45,8 @@ const Home: NextPage = () => {
|
||||
<SnackbarProvider
|
||||
preventDuplicate
|
||||
maxSnack={10}
|
||||
Components={{message: MessageSnackbar}}>
|
||||
Components={{ message: MessageSnackbar }}
|
||||
>
|
||||
<Header />
|
||||
<Editor />
|
||||
</SnackbarProvider>
|
||||
|
||||
@@ -5,25 +5,26 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import '../styles/globals.css';
|
||||
import "../styles/globals.css";
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}): JSX.Element {
|
||||
'use no memo';
|
||||
}) {
|
||||
"use no memo";
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>
|
||||
{process.env.NODE_ENV === 'development'
|
||||
? '[DEV] React Compiler Playground'
|
||||
: 'React Compiler Playground'}
|
||||
{process.env.NODE_ENV === "development"
|
||||
? "[DEV] React Compiler Playground"
|
||||
: "React Compiler Playground"}
|
||||
</title>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"></meta>
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"
|
||||
></meta>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<link
|
||||
|
||||
@@ -5,19 +5,20 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import {SnackbarProvider} from 'notistack';
|
||||
import {Editor, Header, StoreProvider} from '../components';
|
||||
import MessageSnackbar from '../components/Message';
|
||||
import { SnackbarProvider } from "notistack";
|
||||
import { Editor, Header, StoreProvider } from "../components";
|
||||
import MessageSnackbar from "../components/Message";
|
||||
|
||||
export default function Page(): JSX.Element {
|
||||
export default function Hoot() {
|
||||
return (
|
||||
<StoreProvider>
|
||||
<SnackbarProvider
|
||||
preventDuplicate
|
||||
maxSnack={10}
|
||||
Components={{message: MessageSnackbar}}>
|
||||
Components={{ message: MessageSnackbar }}
|
||||
>
|
||||
<Header />
|
||||
<Editor />
|
||||
</SnackbarProvider>
|
||||
|
||||
21
compiler/apps/playground/babel.config.js
Normal file
21
compiler/apps/playground/babel.config.js
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
module.exports = function (api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: ["next/babel"],
|
||||
plugins: [
|
||||
[
|
||||
"babel-plugin-react-compiler",
|
||||
{
|
||||
runtimeModule: "react-compiler-runtime",
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
||||
};
|
||||
@@ -11,86 +11,86 @@
|
||||
|
||||
module.exports = {
|
||||
// Text colors
|
||||
primary: '#23272F', // gray-90
|
||||
'primary-dark': '#F6F7F9', // gray-5
|
||||
secondary: '#404756', // gray-70
|
||||
'secondary-dark': '#EBECF0', // gray-10
|
||||
link: '#087EA4', // blue-50
|
||||
'link-dark': '#149ECA', // blue-40
|
||||
syntax: '#EBECF0', // gray-10
|
||||
wash: '#FFFFFF',
|
||||
'wash-dark': '#23272F', // gray-90
|
||||
card: '#F6F7F9', // gray-05
|
||||
'card-dark': '#343A46', // gray-80
|
||||
highlight: '#E6F7FF', // blue-10
|
||||
'highlight-dark': 'rgba(88,175,223,.1)',
|
||||
border: '#EBECF0', // gray-10
|
||||
'border-dark': '#343A46', // gray-80
|
||||
'secondary-button': '#EBECF0', // gray-10
|
||||
'secondary-button-dark': '#404756', // gray-70
|
||||
primary: "#23272F", // gray-90
|
||||
"primary-dark": "#F6F7F9", // gray-5
|
||||
secondary: "#404756", // gray-70
|
||||
"secondary-dark": "#EBECF0", // gray-10
|
||||
link: "#087EA4", // blue-50
|
||||
"link-dark": "#149ECA", // blue-40
|
||||
syntax: "#EBECF0", // gray-10
|
||||
wash: "#FFFFFF",
|
||||
"wash-dark": "#23272F", // gray-90
|
||||
card: "#F6F7F9", // gray-05
|
||||
"card-dark": "#343A46", // gray-80
|
||||
highlight: "#E6F7FF", // blue-10
|
||||
"highlight-dark": "rgba(88,175,223,.1)",
|
||||
border: "#EBECF0", // gray-10
|
||||
"border-dark": "#343A46", // gray-80
|
||||
"secondary-button": "#EBECF0", // gray-10
|
||||
"secondary-button-dark": "#404756", // gray-70
|
||||
|
||||
// Gray
|
||||
'gray-95': '#16181D',
|
||||
'gray-90': '#23272F',
|
||||
'gray-80': '#343A46',
|
||||
'gray-70': '#404756',
|
||||
'gray-60': '#4E5769',
|
||||
'gray-50': '#5E687E', // unused
|
||||
'gray-40': '#78839B',
|
||||
'gray-30': '#99A1B3',
|
||||
'gray-20': '#BCC1CD',
|
||||
'gray-10': '#EBECF0',
|
||||
'gray-5': '#F6F7F9',
|
||||
"gray-95": "#16181D",
|
||||
"gray-90": "#23272F",
|
||||
"gray-80": "#343A46",
|
||||
"gray-70": "#404756",
|
||||
"gray-60": "#4E5769",
|
||||
"gray-50": "#5E687E", // unused
|
||||
"gray-40": "#78839B",
|
||||
"gray-30": "#99A1B3",
|
||||
"gray-20": "#BCC1CD",
|
||||
"gray-10": "#EBECF0",
|
||||
"gray-5": "#F6F7F9",
|
||||
|
||||
// Blue
|
||||
'blue-60': '#045975',
|
||||
'blue-50': '#087EA4',
|
||||
'blue-40': '#149ECA', // Brand Blue
|
||||
'blue-30': '#58C4DC', // unused
|
||||
'blue-20': '#ABE2ED',
|
||||
'blue-10': '#E6F7FF', // todo: doesn't match illustrations
|
||||
'blue-5': '#E6F6FA',
|
||||
"blue-60": "#045975",
|
||||
"blue-50": "#087EA4",
|
||||
"blue-40": "#149ECA", // Brand Blue
|
||||
"blue-30": "#58C4DC", // unused
|
||||
"blue-20": "#ABE2ED",
|
||||
"blue-10": "#E6F7FF", // todo: doesn't match illustrations
|
||||
"blue-5": "#E6F6FA",
|
||||
|
||||
// Yellow
|
||||
'yellow-60': '#B65700',
|
||||
'yellow-50': '#C76A15',
|
||||
'yellow-40': '#DB7D27', // unused
|
||||
'yellow-30': '#FABD62', // unused
|
||||
'yellow-20': '#FCDEB0', // unused
|
||||
'yellow-10': '#FDE7C7',
|
||||
'yellow-5': '#FEF5E7',
|
||||
"yellow-60": "#B65700",
|
||||
"yellow-50": "#C76A15",
|
||||
"yellow-40": "#DB7D27", // unused
|
||||
"yellow-30": "#FABD62", // unused
|
||||
"yellow-20": "#FCDEB0", // unused
|
||||
"yellow-10": "#FDE7C7",
|
||||
"yellow-5": "#FEF5E7",
|
||||
|
||||
// Purple
|
||||
'purple-60': '#2B3491', // unused
|
||||
'purple-50': '#575FB7',
|
||||
'purple-40': '#6B75DB',
|
||||
'purple-30': '#8891EC',
|
||||
'purple-20': '#C3C8F5', // unused
|
||||
'purple-10': '#E7E9FB',
|
||||
'purple-5': '#F3F4FD',
|
||||
"purple-60": "#2B3491", // unused
|
||||
"purple-50": "#575FB7",
|
||||
"purple-40": "#6B75DB",
|
||||
"purple-30": "#8891EC",
|
||||
"purple-20": "#C3C8F5", // unused
|
||||
"purple-10": "#E7E9FB",
|
||||
"purple-5": "#F3F4FD",
|
||||
|
||||
// Green
|
||||
'green-60': '#2B6E62',
|
||||
'green-50': '#388F7F',
|
||||
'green-40': '#44AC99',
|
||||
'green-30': '#7FCCBF',
|
||||
'green-20': '#ABDED5',
|
||||
'green-10': '#E5F5F2',
|
||||
'green-5': '#F4FBF9',
|
||||
"green-60": "#2B6E62",
|
||||
"green-50": "#388F7F",
|
||||
"green-40": "#44AC99",
|
||||
"green-30": "#7FCCBF",
|
||||
"green-20": "#ABDED5",
|
||||
"green-10": "#E5F5F2",
|
||||
"green-5": "#F4FBF9",
|
||||
|
||||
// RED
|
||||
'red-60': '#712D28',
|
||||
'red-50': '#A6423A', // unused
|
||||
'red-40': '#C1554D',
|
||||
'red-30': '#D07D77',
|
||||
'red-20': '#E5B7B3', // unused
|
||||
'red-10': '#F2DBD9', // unused
|
||||
'red-5': '#FAF1F0',
|
||||
"red-60": "#712D28",
|
||||
"red-50": "#A6423A", // unused
|
||||
"red-40": "#C1554D",
|
||||
"red-30": "#D07D77",
|
||||
"red-20": "#E5B7B3", // unused
|
||||
"red-10": "#F2DBD9", // unused
|
||||
"red-5": "#FAF1F0",
|
||||
|
||||
// MISC
|
||||
'code-block': '#99a1b30f', // gray-30 @ 6%
|
||||
'gradient-blue': '#58C4DC', // Only used for the landing gradient for now.
|
||||
"code-block": "#99a1b30f", // gray-30 @ 6%
|
||||
"gradient-blue": "#58C4DC", // Only used for the landing gradient for now.
|
||||
github: {
|
||||
highlight: '#fffbdd',
|
||||
highlight: "#fffbdd",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -5,76 +5,70 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {parse as babelParse} from '@babel/parser';
|
||||
import * as HermesParser from 'hermes-parser';
|
||||
import traverse, {NodePath} from '@babel/traverse';
|
||||
import * as t from '@babel/types';
|
||||
import { parse, ParserPlugin } from "@babel/parser";
|
||||
import traverse, { NodePath } from "@babel/traverse";
|
||||
import * as t from "@babel/types";
|
||||
import {
|
||||
CompilerError,
|
||||
CompilerErrorDetail,
|
||||
Effect,
|
||||
ErrorSeverity,
|
||||
parseConfigPragmaForTests,
|
||||
parseConfigPragma,
|
||||
printHIR,
|
||||
printReactiveFunction,
|
||||
run,
|
||||
ValueKind,
|
||||
runPlayground,
|
||||
type Hook,
|
||||
} from 'babel-plugin-react-compiler/src';
|
||||
import {type ReactFunctionType} from 'babel-plugin-react-compiler/src/HIR/Environment';
|
||||
import clsx from 'clsx';
|
||||
import invariant from 'invariant';
|
||||
import {useSnackbar} from 'notistack';
|
||||
import {useDeferredValue, useMemo} from 'react';
|
||||
import {useMountEffect} from '../../hooks';
|
||||
import {defaultStore} from '../../lib/defaultStore';
|
||||
} from "babel-plugin-react-compiler/src";
|
||||
import { type ReactFunctionType } from "babel-plugin-react-compiler/src/HIR/Environment";
|
||||
import clsx from "clsx";
|
||||
import invariant from "invariant";
|
||||
import { useSnackbar } from "notistack";
|
||||
import { useDeferredValue, useMemo } from "react";
|
||||
import { useMountEffect } from "../../hooks";
|
||||
import { defaultStore } from "../../lib/defaultStore";
|
||||
import {
|
||||
createMessage,
|
||||
initStoreFromUrlOrLocalStorage,
|
||||
MessageLevel,
|
||||
MessageSource,
|
||||
type Store,
|
||||
} from '../../lib/stores';
|
||||
import {useStore, useStoreDispatch} from '../StoreContext';
|
||||
import Input from './Input';
|
||||
} from "../../lib/stores";
|
||||
import { useStore, useStoreDispatch } from "../StoreContext";
|
||||
import Input from "./Input";
|
||||
import {
|
||||
CompilerOutput,
|
||||
default as Output,
|
||||
PrintedCompilerPipelineValue,
|
||||
} from './Output';
|
||||
import {printFunctionWithOutlined} from 'babel-plugin-react-compiler/src/HIR/PrintHIR';
|
||||
import {printReactiveFunctionWithOutlined} from 'babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction';
|
||||
|
||||
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, {
|
||||
babel: true,
|
||||
flow: 'all',
|
||||
sourceType: 'module',
|
||||
enableExperimentalComponentSyntax: true,
|
||||
});
|
||||
} else {
|
||||
return babelParse(input, {
|
||||
plugins: ['typescript', 'jsx'],
|
||||
sourceType: 'module',
|
||||
});
|
||||
}
|
||||
}
|
||||
} from "./Output";
|
||||
|
||||
function parseFunctions(
|
||||
source: string,
|
||||
language: 'flow' | 'typescript',
|
||||
source: string
|
||||
): Array<
|
||||
| NodePath<t.FunctionDeclaration>
|
||||
| NodePath<t.ArrowFunctionExpression>
|
||||
| NodePath<t.FunctionExpression>
|
||||
NodePath<
|
||||
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
|
||||
>
|
||||
> {
|
||||
const items: Array<
|
||||
| NodePath<t.FunctionDeclaration>
|
||||
| NodePath<t.ArrowFunctionExpression>
|
||||
| NodePath<t.FunctionExpression>
|
||||
NodePath<
|
||||
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
|
||||
>
|
||||
> = [];
|
||||
try {
|
||||
const ast = parseInput(source, language);
|
||||
const isFlow = source
|
||||
.trim()
|
||||
.split("\n", 1)[0]
|
||||
.match(/\s*\/\/\s*\@flow\s*/);
|
||||
let type_transform: ParserPlugin;
|
||||
if (isFlow) {
|
||||
type_transform = "flow";
|
||||
} else {
|
||||
type_transform = "typescript";
|
||||
}
|
||||
const ast = parse(source, {
|
||||
plugins: [type_transform, "jsx"],
|
||||
sourceType: "module",
|
||||
});
|
||||
traverse(ast, {
|
||||
FunctionDeclaration(nodePath) {
|
||||
items.push(nodePath);
|
||||
@@ -103,7 +97,7 @@ function parseFunctions(
|
||||
|
||||
const COMMON_HOOKS: Array<[string, Hook]> = [
|
||||
[
|
||||
'useFragment',
|
||||
"useFragment",
|
||||
{
|
||||
valueKind: ValueKind.Frozen,
|
||||
effectKind: Effect.Freeze,
|
||||
@@ -112,7 +106,7 @@ const COMMON_HOOKS: Array<[string, Hook]> = [
|
||||
},
|
||||
],
|
||||
[
|
||||
'usePaginationFragment',
|
||||
"usePaginationFragment",
|
||||
{
|
||||
valueKind: ValueKind.Frozen,
|
||||
effectKind: Effect.Freeze,
|
||||
@@ -121,7 +115,7 @@ const COMMON_HOOKS: Array<[string, Hook]> = [
|
||||
},
|
||||
],
|
||||
[
|
||||
'useRefetchableFragment',
|
||||
"useRefetchableFragment",
|
||||
{
|
||||
valueKind: ValueKind.Frozen,
|
||||
effectKind: Effect.Freeze,
|
||||
@@ -130,7 +124,7 @@ const COMMON_HOOKS: Array<[string, Hook]> = [
|
||||
},
|
||||
],
|
||||
[
|
||||
'useLazyLoadQuery',
|
||||
"useLazyLoadQuery",
|
||||
{
|
||||
valueKind: ValueKind.Frozen,
|
||||
effectKind: Effect.Freeze,
|
||||
@@ -139,7 +133,7 @@ const COMMON_HOOKS: Array<[string, Hook]> = [
|
||||
},
|
||||
],
|
||||
[
|
||||
'usePreloadedQuery',
|
||||
"usePreloadedQuery",
|
||||
{
|
||||
valueKind: ValueKind.Frozen,
|
||||
effectKind: Effect.Freeze,
|
||||
@@ -153,37 +147,26 @@ function isHookName(s: string): boolean {
|
||||
return /^use[A-Z0-9]/.test(s);
|
||||
}
|
||||
|
||||
function getReactFunctionType(id: t.Identifier | null): ReactFunctionType {
|
||||
if (id != null) {
|
||||
if (isHookName(id.name)) {
|
||||
return 'Hook';
|
||||
function getReactFunctionType(
|
||||
id: NodePath<t.Identifier | null | undefined>
|
||||
): ReactFunctionType {
|
||||
if (id && id.node && id.isIdentifier()) {
|
||||
if (isHookName(id.node.name)) {
|
||||
return "Hook";
|
||||
}
|
||||
|
||||
const isPascalCaseNameSpace = /^[A-Z].*/;
|
||||
if (isPascalCaseNameSpace.test(id.name)) {
|
||||
return 'Component';
|
||||
if (isPascalCaseNameSpace.test(id.node.name)) {
|
||||
return "Component";
|
||||
}
|
||||
}
|
||||
return 'Other';
|
||||
return "Other";
|
||||
}
|
||||
|
||||
function getFunctionIdentifier(
|
||||
fn:
|
||||
| NodePath<t.FunctionDeclaration>
|
||||
| NodePath<t.ArrowFunctionExpression>
|
||||
| NodePath<t.FunctionExpression>,
|
||||
): 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<string, Array<PrintedCompilerPipelineValue>>();
|
||||
function compile(source: string): CompilerOutput {
|
||||
const results = new Map<string, PrintedCompilerPipelineValue[]>();
|
||||
const error = new CompilerError();
|
||||
const upsert: (result: PrintedCompilerPipelineValue) => void = result => {
|
||||
const upsert = (result: PrintedCompilerPipelineValue) => {
|
||||
const entry = results.get(result.name);
|
||||
if (Array.isArray(entry)) {
|
||||
entry.push(result);
|
||||
@@ -191,45 +174,49 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
|
||||
results.set(result.name, [result]);
|
||||
}
|
||||
};
|
||||
let language: 'flow' | 'typescript';
|
||||
if (source.match(/\@flow/)) {
|
||||
language = 'flow';
|
||||
} 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 = parseConfigPragmaForTests(pragma);
|
||||
const pragma = source.substring(0, source.indexOf("\n"));
|
||||
const config = parseConfigPragma(pragma);
|
||||
|
||||
for (const fn of parseFunctions(source, language)) {
|
||||
const id = withIdentifier(getFunctionIdentifier(fn));
|
||||
for (const result of runPlayground(
|
||||
for (const fn of parseFunctions(source)) {
|
||||
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,
|
||||
})
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const id = fn.get("id");
|
||||
for (const result of run(
|
||||
fn,
|
||||
{
|
||||
...config,
|
||||
customHooks: new Map([...COMMON_HOOKS]),
|
||||
},
|
||||
getReactFunctionType(id),
|
||||
"_c",
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)) {
|
||||
const fnName = id.name;
|
||||
const fnName = fn.node.id?.name ?? null;
|
||||
switch (result.kind) {
|
||||
case 'ast': {
|
||||
case "ast": {
|
||||
upsert({
|
||||
kind: 'ast',
|
||||
kind: "ast",
|
||||
fnName,
|
||||
name: result.name,
|
||||
value: {
|
||||
type: 'FunctionDeclaration',
|
||||
id: withIdentifier(result.value.id),
|
||||
type: "FunctionDeclaration",
|
||||
id: result.value.id,
|
||||
async: result.value.async,
|
||||
generator: result.value.generator,
|
||||
body: result.value.body,
|
||||
@@ -238,27 +225,27 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'hir': {
|
||||
case "hir": {
|
||||
upsert({
|
||||
kind: 'hir',
|
||||
kind: "hir",
|
||||
fnName,
|
||||
name: result.name,
|
||||
value: printFunctionWithOutlined(result.value),
|
||||
value: printHIR(result.value.body),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'reactive': {
|
||||
case "reactive": {
|
||||
upsert({
|
||||
kind: 'reactive',
|
||||
kind: "reactive",
|
||||
fnName,
|
||||
name: result.name,
|
||||
value: printReactiveFunctionWithOutlined(result.value),
|
||||
value: printReactiveFunction(result.value),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'debug': {
|
||||
case "debug": {
|
||||
upsert({
|
||||
kind: 'debug',
|
||||
kind: "debug",
|
||||
fnName,
|
||||
name: result.name,
|
||||
value: result.value,
|
||||
@@ -273,17 +260,13 @@ 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({
|
||||
@@ -291,24 +274,24 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
|
||||
reason: `Unexpected failure when transforming input! ${err}`,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
}),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
if (error.hasErrors()) {
|
||||
return [{kind: 'err', results, error: error}, language];
|
||||
return { kind: "err", results, error: error };
|
||||
}
|
||||
return [{kind: 'ok', results}, language];
|
||||
return { kind: "ok", results };
|
||||
}
|
||||
|
||||
export default function Editor(): JSX.Element {
|
||||
export default function Editor() {
|
||||
const store = useStore();
|
||||
const deferredStore = useDeferredValue(store);
|
||||
const dispatchStore = useStoreDispatch();
|
||||
const {enqueueSnackbar} = useSnackbar();
|
||||
const [compilerOutput, language] = useMemo(
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const compilerOutput = useMemo(
|
||||
() => compile(deferredStore.source),
|
||||
[deferredStore.source],
|
||||
[deferredStore.source]
|
||||
);
|
||||
|
||||
useMountEffect(() => {
|
||||
@@ -316,35 +299,34 @@ export default function Editor(): JSX.Element {
|
||||
try {
|
||||
mountStore = initStoreFromUrlOrLocalStorage();
|
||||
} catch (e) {
|
||||
invariant(e instanceof Error, 'Only Error may be caught.');
|
||||
invariant(e instanceof Error, "Only Error may be caught.");
|
||||
enqueueSnackbar(e.message, {
|
||||
variant: 'message',
|
||||
variant: "message",
|
||||
...createMessage(
|
||||
'Bad URL - fell back to the default Playground.',
|
||||
"Bad URL - fell back to the default Playground.",
|
||||
MessageLevel.Info,
|
||||
MessageSource.Playground,
|
||||
MessageSource.Playground
|
||||
),
|
||||
});
|
||||
mountStore = defaultStore;
|
||||
}
|
||||
dispatchStore({
|
||||
type: 'setStore',
|
||||
payload: {store: mountStore},
|
||||
type: "setStore",
|
||||
payload: { store: mountStore },
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative flex basis top-14">
|
||||
<div className={clsx('relative sm:basis-1/4')}>
|
||||
<div className={clsx("relative sm:basis-1/4")}>
|
||||
<Input
|
||||
language={language}
|
||||
errors={
|
||||
compilerOutput.kind === 'err' ? compilerOutput.error.details : []
|
||||
compilerOutput.kind === "err" ? compilerOutput.error.details : []
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className={clsx('flex sm:flex flex-wrap')}>
|
||||
<div className={clsx("flex sm:flex flex-wrap")}>
|
||||
<Output store={deferredStore} compilerOutput={compilerOutput} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,27 +5,27 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import MonacoEditor, {loader, type Monaco} from '@monaco-editor/react';
|
||||
import {CompilerErrorDetail} from 'babel-plugin-react-compiler/src';
|
||||
import invariant from 'invariant';
|
||||
import type {editor} from 'monaco-editor';
|
||||
import * as monaco from 'monaco-editor';
|
||||
import {Resizable} from 're-resizable';
|
||||
import {useEffect, useState} from 'react';
|
||||
import {renderReactCompilerMarkers} from '../../lib/reactCompilerMonacoDiagnostics';
|
||||
import {useStore, useStoreDispatch} from '../StoreContext';
|
||||
import {monacoOptions} from './monacoOptions';
|
||||
// @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';
|
||||
import MonacoEditor, { loader, type Monaco } from "@monaco-editor/react";
|
||||
import { CompilerErrorDetail } from "babel-plugin-react-compiler/src";
|
||||
import invariant from "invariant";
|
||||
import type { editor } from "monaco-editor";
|
||||
import * as monaco from "monaco-editor";
|
||||
import { Resizable } from "re-resizable";
|
||||
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
|
||||
import React$Types from "../../node_modules/@types/react/index.d.ts";
|
||||
|
||||
loader.config({monaco});
|
||||
loader.config({ monaco });
|
||||
|
||||
type Props = {
|
||||
errors: Array<CompilerErrorDetail>;
|
||||
language: 'flow' | 'typescript';
|
||||
errors: CompilerErrorDetail[];
|
||||
};
|
||||
|
||||
export default function Input({errors, language}: Props): JSX.Element {
|
||||
export default function Input({ errors }: Props) {
|
||||
const [monaco, setMonaco] = useState<Monaco | null>(null);
|
||||
const store = useStore();
|
||||
const dispatchStore = useStoreDispatch();
|
||||
@@ -35,72 +35,48 @@ export default function Input({errors, language}: Props): JSX.Element {
|
||||
if (!monaco) return;
|
||||
const uri = monaco.Uri.parse(`file:///index.js`);
|
||||
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.
|
||||
*/
|
||||
model.updateOptions({tabSize: 2});
|
||||
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.
|
||||
model.updateOptions({ tabSize: 2 });
|
||||
}, [monaco, errors]);
|
||||
|
||||
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.
|
||||
*/
|
||||
if (!monaco) return;
|
||||
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
|
||||
diagnosticCodesToIgnore: [
|
||||
8002,
|
||||
8003,
|
||||
8004,
|
||||
8005,
|
||||
8006,
|
||||
8008,
|
||||
8009,
|
||||
8010,
|
||||
8011,
|
||||
8012,
|
||||
8013,
|
||||
...(language === 'flow'
|
||||
? [7028 /* unused label */, 6133 /* var declared but not read */]
|
||||
: []),
|
||||
],
|
||||
noSemanticValidation: true,
|
||||
// Monaco can't validate Flow component syntax
|
||||
noSyntaxValidation: language === 'flow',
|
||||
});
|
||||
}, [monaco, language]);
|
||||
|
||||
const handleChange: (value: string | undefined) => void = value => {
|
||||
const handleChange = (value: string | undefined) => {
|
||||
if (!value) return;
|
||||
|
||||
dispatchStore({
|
||||
type: 'updateFile',
|
||||
type: "updateFile",
|
||||
payload: {
|
||||
source: value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleMount: (
|
||||
_: editor.IStandaloneCodeEditor,
|
||||
monaco: Monaco,
|
||||
) => void = (_, monaco) => {
|
||||
const handleMount = (_: editor.IStandaloneCodeEditor, monaco: Monaco) => {
|
||||
setMonaco(monaco);
|
||||
|
||||
// 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.
|
||||
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
|
||||
diagnosticCodesToIgnore: [
|
||||
8002, 8003, 8004, 8005, 8006, 8008, 8009, 8010, 8011, 8012, 8013,
|
||||
],
|
||||
noSemanticValidation: true,
|
||||
noSyntaxValidation: false,
|
||||
});
|
||||
|
||||
const tscOptions = {
|
||||
allowNonTsExtensions: true,
|
||||
target: monaco.languages.typescript.ScriptTarget.ES2015,
|
||||
moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
|
||||
jsx: monaco.languages.typescript.JsxEmit.Preserve,
|
||||
typeRoots: ['node_modules/@types'],
|
||||
typeRoots: ["node_modules/@types"],
|
||||
allowSyntheticDefaultImports: true,
|
||||
};
|
||||
monaco.languages.typescript.javascriptDefaults.setCompilerOptions(
|
||||
tscOptions,
|
||||
tscOptions
|
||||
);
|
||||
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
|
||||
...tscOptions,
|
||||
@@ -111,17 +87,15 @@ export default function Input({errors, language}: Props): JSX.Element {
|
||||
// Add React type declarations to Monaco
|
||||
const reactLib = [
|
||||
React$Types,
|
||||
'file:///node_modules/@types/react/index.d.ts',
|
||||
"file:///node_modules/@types/react/index.d.ts",
|
||||
] as [any, string];
|
||||
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();
|
||||
});
|
||||
@@ -131,20 +105,17 @@ export default function Input({errors, language}: Props): JSX.Element {
|
||||
<div className="relative flex flex-col flex-none border-r border-gray-200">
|
||||
<Resizable
|
||||
minWidth={650}
|
||||
enable={{right: true}}
|
||||
/**
|
||||
* Restrict MonacoEditor's height, since the config autoLayout:true
|
||||
* will grow the editor to fit within parent element
|
||||
*/
|
||||
className="!h-[calc(100vh_-_3.5rem)]">
|
||||
enable={{ right: true }}
|
||||
// Restrict MonacoEditor's height, since the config autoLayout:true
|
||||
// will grow the editor to fit within parent element
|
||||
className="!h-[calc(100vh_-_3.5rem)]"
|
||||
>
|
||||
<MonacoEditor
|
||||
path={'index.js'}
|
||||
/**
|
||||
* .js and .jsx files are specified to be TS so that Monaco can actually
|
||||
* check their syntax using its TS language service. They are still JS files
|
||||
* due to their extensions, so TS language features don't work.
|
||||
*/
|
||||
language={'javascript'}
|
||||
path={"index.js"}
|
||||
// .js and .jsx files are specified to be TS so that Monaco can actually
|
||||
// check their syntax using its TS language service. They are still JS files
|
||||
// due to their extensions, so TS language features don't work.
|
||||
language={"javascript"}
|
||||
value={store.source}
|
||||
onMount={handleMount}
|
||||
onChange={handleChange}
|
||||
|
||||
@@ -5,47 +5,47 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import generate from '@babel/generator';
|
||||
import * as t from '@babel/types';
|
||||
import generate from "@babel/generator";
|
||||
import * as t from "@babel/types";
|
||||
import {
|
||||
CodeIcon,
|
||||
DocumentAddIcon,
|
||||
InformationCircleIcon,
|
||||
} from '@heroicons/react/outline';
|
||||
import MonacoEditor, {DiffEditor} from '@monaco-editor/react';
|
||||
import {type CompilerError} from 'babel-plugin-react-compiler/src';
|
||||
import parserBabel from 'prettier/plugins/babel';
|
||||
import * as prettierPluginEstree from 'prettier/plugins/estree';
|
||||
import * as prettier from 'prettier/standalone';
|
||||
import {memo, ReactNode, useEffect, useState} from 'react';
|
||||
import {type Store} from '../../lib/stores';
|
||||
import TabbedWindow from '../TabbedWindow';
|
||||
import {monacoOptions} from './monacoOptions';
|
||||
} from "@heroicons/react/outline";
|
||||
import MonacoEditor, { DiffEditor } from "@monaco-editor/react";
|
||||
import { type CompilerError } from "babel-plugin-react-compiler/src";
|
||||
import parserBabel from "prettier/plugins/babel";
|
||||
import * as prettierPluginEstree from "prettier/plugins/estree";
|
||||
import * as prettier from "prettier/standalone";
|
||||
import { memo, useEffect, useState } from "react";
|
||||
import { type Store } from "../../lib/stores";
|
||||
import TabbedWindow from "../TabbedWindow";
|
||||
import { monacoOptions } from "./monacoOptions";
|
||||
const MemoizedOutput = memo(Output);
|
||||
|
||||
export default MemoizedOutput;
|
||||
|
||||
export type PrintedCompilerPipelineValue =
|
||||
| {
|
||||
kind: 'ast';
|
||||
kind: "ast";
|
||||
name: string;
|
||||
fnName: string | null;
|
||||
value: t.FunctionDeclaration;
|
||||
}
|
||||
| {
|
||||
kind: 'hir';
|
||||
kind: "hir";
|
||||
name: string;
|
||||
fnName: string | null;
|
||||
value: string;
|
||||
}
|
||||
| {kind: 'reactive'; name: string; fnName: string | null; value: string}
|
||||
| {kind: 'debug'; name: string; fnName: string | null; value: string};
|
||||
| { kind: "reactive"; name: string; fnName: string | null; value: string }
|
||||
| { kind: "debug"; name: string; fnName: string | null; value: string };
|
||||
|
||||
export type CompilerOutput =
|
||||
| {kind: 'ok'; results: Map<string, Array<PrintedCompilerPipelineValue>>}
|
||||
| { kind: "ok"; results: Map<string, PrintedCompilerPipelineValue[]> }
|
||||
| {
|
||||
kind: 'err';
|
||||
results: Map<string, Array<PrintedCompilerPipelineValue>>;
|
||||
kind: "err";
|
||||
results: Map<string, PrintedCompilerPipelineValue[]>;
|
||||
error: CompilerError;
|
||||
};
|
||||
|
||||
@@ -54,10 +54,7 @@ type Props = {
|
||||
compilerOutput: CompilerOutput;
|
||||
};
|
||||
|
||||
async function tabify(
|
||||
source: string,
|
||||
compilerOutput: CompilerOutput,
|
||||
): Promise<Map<string, ReactNode>> {
|
||||
async function tabify(source: string, compilerOutput: CompilerOutput) {
|
||||
const tabs = new Map<string, React.ReactNode>();
|
||||
const reorderedTabs = new Map<string, React.ReactNode>();
|
||||
const concattedResults = new Map<string, string>();
|
||||
@@ -66,7 +63,7 @@ async function tabify(
|
||||
for (const [passName, results] of compilerOutput.results) {
|
||||
for (const result of results) {
|
||||
switch (result.kind) {
|
||||
case 'hir': {
|
||||
case "hir": {
|
||||
const prev = concattedResults.get(result.name);
|
||||
const next = result.value;
|
||||
const identName = `function ${result.fnName}`;
|
||||
@@ -77,7 +74,7 @@ async function tabify(
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'reactive': {
|
||||
case "reactive": {
|
||||
const prev = concattedResults.get(passName);
|
||||
const next = result.value;
|
||||
if (prev != null) {
|
||||
@@ -87,57 +84,56 @@ async function tabify(
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'ast':
|
||||
case "ast":
|
||||
topLevelFnDecls.push(result.value);
|
||||
break;
|
||||
case 'debug': {
|
||||
case "debug": {
|
||||
concattedResults.set(passName, result.value);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
const _: never = result;
|
||||
throw new Error('Unexpected result kind');
|
||||
throw new Error("Unexpected result kind");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let lastPassOutput: string | null = null;
|
||||
let nonDiffPasses = ['HIR', 'BuildReactiveFunction', 'EnvironmentConfig'];
|
||||
for (const [passName, text] of concattedResults) {
|
||||
tabs.set(
|
||||
passName,
|
||||
<TextTabContent
|
||||
output={text}
|
||||
diff={lastPassOutput}
|
||||
showInfoPanel={!nonDiffPasses.includes(passName)}></TextTabContent>,
|
||||
diff={lastPassOutput ?? null}
|
||||
showInfoPanel={true}
|
||||
></TextTabContent>
|
||||
);
|
||||
lastPassOutput = text;
|
||||
}
|
||||
// Ensure that JS and the JS source map come first
|
||||
if (topLevelFnDecls.length > 0) {
|
||||
/**
|
||||
* Make a synthetic Program so we can have a single AST with all the top level
|
||||
* FunctionDeclarations
|
||||
*/
|
||||
// Make a synthetic Program so we can have a single AST with all the top level
|
||||
// FunctionDeclarations
|
||||
const ast = t.program(topLevelFnDecls);
|
||||
const {code, sourceMapUrl} = await codegen(ast, source);
|
||||
const { code, sourceMapUrl } = await codegen(ast, source);
|
||||
reorderedTabs.set(
|
||||
'JS',
|
||||
"JS",
|
||||
<TextTabContent
|
||||
output={code}
|
||||
diff={null}
|
||||
showInfoPanel={false}></TextTabContent>,
|
||||
showInfoPanel={false}
|
||||
></TextTabContent>
|
||||
);
|
||||
if (sourceMapUrl) {
|
||||
reorderedTabs.set(
|
||||
'SourceMap',
|
||||
"SourceMap",
|
||||
<>
|
||||
<iframe
|
||||
src={sourceMapUrl}
|
||||
className="w-full h-monaco_small sm:h-monaco"
|
||||
title="Generated Code"
|
||||
/>
|
||||
</>,
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -149,23 +145,23 @@ async function tabify(
|
||||
|
||||
async function codegen(
|
||||
ast: t.Program,
|
||||
source: string,
|
||||
): Promise<{code: any; sourceMapUrl: string | null}> {
|
||||
source: string
|
||||
): Promise<{ code: any; sourceMapUrl: string | null }> {
|
||||
const generated = generate(
|
||||
ast,
|
||||
{sourceMaps: true, sourceFileName: 'input.js'},
|
||||
source,
|
||||
{ sourceMaps: true, sourceFileName: "input.js" },
|
||||
source
|
||||
);
|
||||
const sourceMapUrl = getSourceMapUrl(
|
||||
generated.code,
|
||||
JSON.stringify(generated.map),
|
||||
JSON.stringify(generated.map)
|
||||
);
|
||||
const codegenOutput = await prettier.format(generated.code, {
|
||||
semi: true,
|
||||
parser: 'babel',
|
||||
parser: "babel",
|
||||
plugins: [parserBabel, prettierPluginEstree],
|
||||
});
|
||||
return {code: codegenOutput, sourceMapUrl};
|
||||
return { code: codegenOutput, sourceMapUrl };
|
||||
}
|
||||
|
||||
function utf16ToUTF8(s: string): string {
|
||||
@@ -176,27 +172,27 @@ function getSourceMapUrl(code: string, map: string): string | null {
|
||||
code = utf16ToUTF8(code);
|
||||
map = utf16ToUTF8(map);
|
||||
return `https://evanw.github.io/source-map-visualization/#${btoa(
|
||||
`${code.length}\0${code}${map.length}\0${map}`,
|
||||
`${code.length}\0${code}${map.length}\0${map}`
|
||||
)}`;
|
||||
}
|
||||
|
||||
function Output({store, compilerOutput}: Props): JSX.Element {
|
||||
const [tabsOpen, setTabsOpen] = useState<Set<string>>(() => new Set(['JS']));
|
||||
function Output({ store, compilerOutput }: Props) {
|
||||
const [tabsOpen, setTabsOpen] = useState<Set<string>>(() => new Set(["JS"]));
|
||||
const [tabs, setTabs] = useState<Map<string, React.ReactNode>>(
|
||||
() => new Map(),
|
||||
() => new Map()
|
||||
);
|
||||
useEffect(() => {
|
||||
tabify(store.source, compilerOutput).then(tabs => {
|
||||
tabify(store.source, compilerOutput).then((tabs) => {
|
||||
setTabs(tabs);
|
||||
});
|
||||
}, [store.source, compilerOutput]);
|
||||
|
||||
const changedPasses: Set<string> = new Set(['JS', 'HIR']); // Initial and final passes should always be bold
|
||||
let lastResult: string = '';
|
||||
const changedPasses: Set<string> = new Set();
|
||||
let lastResult: string = "";
|
||||
for (const [passName, results] of compilerOutput.results) {
|
||||
for (const result of results) {
|
||||
let currResult = '';
|
||||
if (result.kind === 'hir' || result.kind === 'reactive') {
|
||||
let currResult = "";
|
||||
if (result.kind === "hir" || result.kind === "reactive") {
|
||||
currResult += `function ${result.fnName}\n\n${result.value}`;
|
||||
}
|
||||
if (currResult !== lastResult) {
|
||||
@@ -215,16 +211,18 @@ function Output({store, compilerOutput}: Props): JSX.Element {
|
||||
tabs={tabs}
|
||||
changedPasses={changedPasses}
|
||||
/>
|
||||
{compilerOutput.kind === 'err' ? (
|
||||
{compilerOutput.kind === "err" ? (
|
||||
<div
|
||||
className="flex flex-wrap absolute bottom-0 bg-white grow border-y border-grey-200 transition-all ease-in"
|
||||
style={{width: 'calc(100vw - 650px)'}}>
|
||||
style={{ width: "calc(100vw - 650px)" }}
|
||||
>
|
||||
<div className="w-full p-4 basis-full border-b">
|
||||
<h2>COMPILER ERRORS</h2>
|
||||
</div>
|
||||
<pre
|
||||
className="p-4 basis-full text-red-600 overflow-y-scroll whitespace-pre-wrap"
|
||||
style={{width: 'calc(100vw - 650px)', height: '150px'}}>
|
||||
style={{ width: "calc(100vw - 650px)", height: "150px" }}
|
||||
>
|
||||
<code>{compilerOutput.error.toString()}</code>
|
||||
</pre>
|
||||
</div>
|
||||
@@ -241,20 +239,19 @@ function TextTabContent({
|
||||
output: string;
|
||||
diff: string | null;
|
||||
showInfoPanel: boolean;
|
||||
}): JSX.Element {
|
||||
}) {
|
||||
const [diffMode, setDiffMode] = useState(false);
|
||||
return (
|
||||
/**
|
||||
* Restrict MonacoEditor's height, since the config autoLayout:true
|
||||
* will grow the editor to fit within parent element
|
||||
*/
|
||||
// Restrict MonacoEditor's height, since the config autoLayout:true
|
||||
// will grow the editor to fit within parent element
|
||||
<div className="w-full h-monaco_small sm:h-monaco">
|
||||
{showInfoPanel ? (
|
||||
<div className="flex items-center gap-1 bg-amber-50 p-2">
|
||||
{diff != null && output !== diff ? (
|
||||
<button
|
||||
className="flex items-center gap-1 transition-colors duration-150 ease-in text-secondary hover:text-link"
|
||||
onClick={() => setDiffMode(diffMode => !diffMode)}>
|
||||
onClick={() => setDiffMode((diffMode) => !diffMode)}
|
||||
>
|
||||
{!diffMode ? (
|
||||
<>
|
||||
<DocumentAddIcon className="w-5 h-5" /> Show Diff
|
||||
@@ -282,7 +279,7 @@ function TextTabContent({
|
||||
options={{
|
||||
...monacoOptions,
|
||||
readOnly: true,
|
||||
lineNumbers: 'off',
|
||||
lineNumbers: "off",
|
||||
glyphMargin: false,
|
||||
// Undocumented see https://github.com/Microsoft/vscode/issues/30795#issuecomment-410998882
|
||||
lineDecorationsWidth: 0,
|
||||
@@ -296,7 +293,7 @@ function TextTabContent({
|
||||
options={{
|
||||
...monacoOptions,
|
||||
readOnly: true,
|
||||
lineNumbers: 'off',
|
||||
lineNumbers: "off",
|
||||
glyphMargin: false,
|
||||
// Undocumented see https://github.com/Microsoft/vscode/issues/30795#issuecomment-410998882
|
||||
lineDecorationsWidth: 0,
|
||||
|
||||
@@ -5,13 +5,11 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
/**
|
||||
* monaco-editor is currently not compatible with ssr
|
||||
* https://github.com/vercel/next.js/issues/31692
|
||||
*/
|
||||
const Editor = dynamic(() => import('./EditorImpl'), {
|
||||
// monaco-editor is currently not compatible with ssr
|
||||
// https://github.com/vercel/next.js/issues/31692
|
||||
const Editor = dynamic(() => import("./EditorImpl"), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import type {EditorProps} from '@monaco-editor/react';
|
||||
import type { EditorProps } from "@monaco-editor/react";
|
||||
|
||||
export const monacoOptions: Partial<EditorProps['options']> = {
|
||||
export const monacoOptions: Partial<EditorProps["options"]> = {
|
||||
fontSize: 14,
|
||||
padding: {top: 8},
|
||||
padding: { top: 8 },
|
||||
scrollbar: {
|
||||
verticalScrollbarSize: 10,
|
||||
alwaysConsumeMouseWheel: false,
|
||||
@@ -22,11 +22,11 @@ export const monacoOptions: Partial<EditorProps['options']> = {
|
||||
fontFamily: '"Source Code Pro", monospace',
|
||||
glyphMargin: true,
|
||||
|
||||
autoClosingBrackets: 'languageDefined',
|
||||
autoClosingDelete: 'always',
|
||||
autoClosingOvertype: 'always',
|
||||
autoClosingBrackets: "languageDefined",
|
||||
autoClosingDelete: "always",
|
||||
autoClosingOvertype: "always",
|
||||
|
||||
automaticLayout: true,
|
||||
wordWrap: 'on',
|
||||
wrappingIndent: 'deepIndent',
|
||||
wordWrap: "on",
|
||||
wrappingIndent: "deepIndent",
|
||||
};
|
||||
|
||||
@@ -5,39 +5,39 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {RefreshIcon, ShareIcon} from '@heroicons/react/outline';
|
||||
import {CheckIcon} from '@heroicons/react/solid';
|
||||
import clsx from 'clsx';
|
||||
import Link from 'next/link';
|
||||
import {useSnackbar} from 'notistack';
|
||||
import {useState} from 'react';
|
||||
import {defaultStore} from '../lib/defaultStore';
|
||||
import {IconGitHub} from './Icons/IconGitHub';
|
||||
import Logo from './Logo';
|
||||
import {useStoreDispatch} from './StoreContext';
|
||||
import { RefreshIcon, ShareIcon } from "@heroicons/react/outline";
|
||||
import { CheckIcon } from "@heroicons/react/solid";
|
||||
import clsx from "clsx";
|
||||
import Link from "next/link";
|
||||
import { useSnackbar } from "notistack";
|
||||
import { useState } from "react";
|
||||
import { defaultStore } from "../lib/defaultStore";
|
||||
import { IconGitHub } from "./Icons/IconGitHub";
|
||||
import Logo from "./Logo";
|
||||
import { useStoreDispatch } from "./StoreContext";
|
||||
|
||||
export default function Header(): JSX.Element {
|
||||
export default function Header() {
|
||||
const [showCheck, setShowCheck] = useState(false);
|
||||
const dispatchStore = useStoreDispatch();
|
||||
const {enqueueSnackbar, closeSnackbar} = useSnackbar();
|
||||
const { enqueueSnackbar, closeSnackbar } = useSnackbar();
|
||||
|
||||
const handleReset: () => void = () => {
|
||||
if (confirm('Are you sure you want to reset the playground?')) {
|
||||
/**
|
||||
* Close open snackbars if any. This is necessary because when displaying
|
||||
* outputs (Preview or not), we only close previous snackbars if we received
|
||||
* new messages, which is needed in order to display "Bad URL" or success
|
||||
* messages when loading Playground for the first time. Otherwise, messages
|
||||
* such as "Bad URL" will be closed by the outputs calling `closeSnackbar`.
|
||||
*/
|
||||
const handleReset = () => {
|
||||
if (confirm("Are you sure you want to reset the playground?")) {
|
||||
/*
|
||||
Close open snackbars if any. This is necessary because when displaying
|
||||
outputs (Preview or not), we only close previous snackbars if we received
|
||||
new messages, which is needed in order to display "Bad URL" or success
|
||||
messages when loading Playground for the first time. Otherwise, messages
|
||||
such as "Bad URL" will be closed by the outputs calling `closeSnackbar`.
|
||||
*/
|
||||
closeSnackbar();
|
||||
dispatchStore({type: 'setStore', payload: {store: defaultStore}});
|
||||
dispatchStore({ type: "setStore", payload: { store: defaultStore } });
|
||||
}
|
||||
};
|
||||
|
||||
const handleShare: () => void = () => {
|
||||
const handleShare = () => {
|
||||
navigator.clipboard.writeText(location.href).then(() => {
|
||||
enqueueSnackbar('URL copied to clipboard');
|
||||
enqueueSnackbar("URL copied to clipboard");
|
||||
setShowCheck(true);
|
||||
// Show the check mark icon briefly after URL is copied
|
||||
setTimeout(() => setShowCheck(false), 1000);
|
||||
@@ -49,8 +49,8 @@ export default function Header(): JSX.Element {
|
||||
<div className="flex items-center flex-none h-full gap-2 text-lg">
|
||||
<Logo
|
||||
className={clsx(
|
||||
'w-8 h-8 text-link',
|
||||
process.env.NODE_ENV === 'development' && 'text-yellow-600',
|
||||
"w-8 h-8 text-link",
|
||||
process.env.NODE_ENV === "development" && "text-yellow-600"
|
||||
)}
|
||||
/>
|
||||
<p className="hidden select-none sm:block">React Compiler Playground</p>
|
||||
@@ -60,7 +60,8 @@ export default function Header(): JSX.Element {
|
||||
title="Reset Playground"
|
||||
aria-label="Reset Playground"
|
||||
className="flex items-center gap-1 transition-colors duration-150 ease-in text-secondary hover:text-link"
|
||||
onClick={handleReset}>
|
||||
onClick={handleReset}
|
||||
>
|
||||
<RefreshIcon className="w-5 h-5" />
|
||||
<p className="hidden sm:block">Reset</p>
|
||||
</button>
|
||||
@@ -69,7 +70,8 @@ export default function Header(): JSX.Element {
|
||||
aria-label="Copy sharable URL"
|
||||
className="flex items-center gap-1 transition-colors duration-150 ease-in text-secondary hover:text-link"
|
||||
onClick={handleShare}
|
||||
disabled={showCheck}>
|
||||
disabled={showCheck}
|
||||
>
|
||||
{!showCheck ? (
|
||||
<ShareIcon className="w-5 h-5" />
|
||||
) : (
|
||||
@@ -82,7 +84,8 @@ export default function Header(): JSX.Element {
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
aria-label="Open on GitHub"
|
||||
className="flex items-center gap-1 transition-colors duration-150 ease-in text-secondary hover:text-link">
|
||||
className="flex items-center gap-1 transition-colors duration-150 ease-in text-secondary hover:text-link"
|
||||
>
|
||||
<IconGitHub />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {memo} from 'react';
|
||||
import { memo } from "react";
|
||||
|
||||
export const IconGitHub = memo<JSX.IntrinsicElements['svg']>(
|
||||
export const IconGitHub = memo<JSX.IntrinsicElements["svg"]>(
|
||||
function IconGitHub(props) {
|
||||
return (
|
||||
<svg
|
||||
@@ -16,9 +16,10 @@ export const IconGitHub = memo<JSX.IntrinsicElements['svg']>(
|
||||
height="1.5em"
|
||||
viewBox="0 -2 24 24"
|
||||
fill="currentColor"
|
||||
{...props}>
|
||||
{...props}
|
||||
>
|
||||
<path d="M10 0a10 10 0 0 0-3.16 19.49c.5.1.68-.22.68-.48l-.01-1.7c-2.78.6-3.37-1.34-3.37-1.34-.46-1.16-1.11-1.47-1.11-1.47-.9-.62.07-.6.07-.6 1 .07 1.53 1.03 1.53 1.03.9 1.52 2.34 1.08 2.91.83.1-.65.35-1.09.63-1.34-2.22-.25-4.55-1.11-4.55-4.94 0-1.1.39-1.99 1.03-2.69a3.6 3.6 0 0 1 .1-2.64s.84-.27 2.75 1.02a9.58 9.58 0 0 1 5 0c1.91-1.3 2.75-1.02 2.75-1.02.55 1.37.2 2.4.1 2.64.64.7 1.03 1.6 1.03 2.69 0 3.84-2.34 4.68-4.57 4.93.36.31.68.92.68 1.85l-.01 2.75c0 .26.18.58.69.48A10 10 0 0 0 10 0"></path>
|
||||
</svg>
|
||||
);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
@@ -7,13 +7,14 @@
|
||||
|
||||
// https://github.com/reactjs/reactjs.org/blob/main/beta/src/components/Logo.tsx
|
||||
|
||||
export default function Logo(props: JSX.IntrinsicElements['svg']): JSX.Element {
|
||||
export default function Logo(props: JSX.IntrinsicElements["svg"]) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 410 369"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}>
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M204.995 224.552C226.56 224.552 244.042 207.07 244.042 185.506C244.042 163.941 226.56 146.459 204.995 146.459C183.43 146.459 165.948 163.941 165.948 185.506C165.948 207.07 183.43 224.552 204.995 224.552Z"
|
||||
fill="currentColor"
|
||||
|
||||
@@ -10,13 +10,13 @@ import {
|
||||
ExclamationIcon,
|
||||
InformationCircleIcon,
|
||||
XIcon,
|
||||
} from '@heroicons/react/solid';
|
||||
import {CustomContentProps, SnackbarContent, useSnackbar} from 'notistack';
|
||||
import {forwardRef} from 'react';
|
||||
import {MessageLevel, MessageSource} from '../lib/stores';
|
||||
} from "@heroicons/react/solid";
|
||||
import { CustomContentProps, SnackbarContent, useSnackbar } from "notistack";
|
||||
import { forwardRef } from "react";
|
||||
import { MessageLevel, MessageSource } from "../lib/stores";
|
||||
|
||||
// https://notistack.com/examples/advanced/custom-component#custom-variant-(typescript)
|
||||
declare module 'notistack' {
|
||||
declare module "notistack" {
|
||||
interface VariantOverrides {
|
||||
message: {
|
||||
title: string;
|
||||
@@ -34,14 +34,15 @@ interface MessageProps extends CustomContentProps {
|
||||
}
|
||||
|
||||
const Message = forwardRef<HTMLDivElement, MessageProps>(
|
||||
({id, title, level, source, codeframe}, ref) => {
|
||||
const {closeSnackbar} = useSnackbar();
|
||||
({ id, title, level, source, codeframe }, ref) => {
|
||||
const { closeSnackbar } = useSnackbar();
|
||||
const isDismissible = source !== MessageSource.Playground;
|
||||
|
||||
return (
|
||||
<SnackbarContent
|
||||
ref={ref}
|
||||
className="flex items-start justify-between gap-3 px-4 py-3 text-sm bg-white border rounded-md shadow w-toast">
|
||||
className="flex items-start justify-between gap-3 px-4 py-3 text-sm bg-white border rounded-md shadow w-toast"
|
||||
>
|
||||
<div className="flex gap-3 w-toast-body">
|
||||
{level === MessageLevel.Warning ? (
|
||||
<div className="flex items-center justify-center flex-none rounded-md w-7 h-7 bg-amber-100">
|
||||
@@ -68,15 +69,16 @@ const Message = forwardRef<HTMLDivElement, MessageProps>(
|
||||
{isDismissible ? (
|
||||
<button
|
||||
className="flex items-center justify-center flex-none transition-colors duration-150 ease-in rounded-md justify-self-end group w-7 h-7 hover:bg-gray-200"
|
||||
onClick={() => closeSnackbar(id)}>
|
||||
onClick={() => closeSnackbar(id)}
|
||||
>
|
||||
<XIcon className="w-5 h-5 fill-gray-500 group-hover:fill-gray-800" />
|
||||
</button>
|
||||
) : null}
|
||||
</SnackbarContent>
|
||||
);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
Message.displayName = 'MessageComponent';
|
||||
Message.displayName = "MessageComponent";
|
||||
|
||||
export default Message;
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import type {Dispatch, ReactNode} from 'react';
|
||||
import {useReducer} from 'react';
|
||||
import createContext from '../lib/createContext';
|
||||
import {emptyStore} from '../lib/defaultStore';
|
||||
import type {Store} from '../lib/stores';
|
||||
import {saveStore} from '../lib/stores';
|
||||
import type { Dispatch, ReactNode } from "react";
|
||||
import { useReducer } from "react";
|
||||
import createContext from "../lib/createContext";
|
||||
import { emptyStore } from "../lib/defaultStore";
|
||||
import type { Store } from "../lib/stores";
|
||||
import { saveStore } from "../lib/stores";
|
||||
|
||||
const StoreContext = createContext<Store>();
|
||||
|
||||
@@ -29,7 +29,7 @@ export const useStoreDispatch = StoreDispatchContext.useContext;
|
||||
/**
|
||||
* Make Store and dispatch function available to all sub-components in children.
|
||||
*/
|
||||
export function StoreProvider({children}: {children: ReactNode}): JSX.Element {
|
||||
export function StoreProvider({ children }: { children: ReactNode }) {
|
||||
const [store, dispatch] = useReducer(storeReducer, emptyStore);
|
||||
|
||||
return (
|
||||
@@ -43,13 +43,13 @@ export function StoreProvider({children}: {children: ReactNode}): JSX.Element {
|
||||
|
||||
type ReducerAction =
|
||||
| {
|
||||
type: 'setStore';
|
||||
type: "setStore";
|
||||
payload: {
|
||||
store: Store;
|
||||
};
|
||||
}
|
||||
| {
|
||||
type: 'updateFile';
|
||||
type: "updateFile";
|
||||
payload: {
|
||||
source: string;
|
||||
};
|
||||
@@ -57,14 +57,14 @@ type ReducerAction =
|
||||
|
||||
function storeReducer(store: Store, action: ReducerAction): Store {
|
||||
switch (action.type) {
|
||||
case 'setStore': {
|
||||
case "setStore": {
|
||||
const newStore = action.payload.store;
|
||||
|
||||
saveStore(newStore);
|
||||
return newStore;
|
||||
}
|
||||
case 'updateFile': {
|
||||
const {source} = action.payload;
|
||||
case "updateFile": {
|
||||
const { source } = action.payload;
|
||||
|
||||
const newStore = {
|
||||
...store,
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {Resizable} from 're-resizable';
|
||||
import React, {useCallback} from 'react';
|
||||
import { Resizable } from "re-resizable";
|
||||
import React, { useCallback } from "react";
|
||||
|
||||
type TabsRecord = Map<string, React.ReactNode>;
|
||||
|
||||
@@ -21,14 +21,15 @@ export default function TabbedWindow(props: {
|
||||
return (
|
||||
<div
|
||||
className="flex items-center justify-center"
|
||||
style={{width: 'calc(100vw - 650px)'}}>
|
||||
style={{ width: "calc(100vw - 650px)" }}
|
||||
>
|
||||
No compiler output detected, see errors below
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="flex flex-row">
|
||||
{Array.from(props.tabs.keys()).map(name => {
|
||||
{Array.from(props.tabs.keys()).map((name) => {
|
||||
return (
|
||||
<TabbedWindowItem
|
||||
name={name}
|
||||
@@ -69,21 +70,17 @@ function TabbedWindowItem({
|
||||
setTabsOpen(nextState);
|
||||
}, [tabsOpen, name, setTabsOpen]);
|
||||
|
||||
// Replace spaces with non-breaking spaces
|
||||
const displayName = name.replace(/ /g, '\u00A0');
|
||||
|
||||
return (
|
||||
<div key={name} className="flex flex-row">
|
||||
{isShow ? (
|
||||
<Resizable className="border-r" minWidth={550} enable={{right: true}}>
|
||||
<Resizable className="border-r" minWidth={550} enable={{ right: true }}>
|
||||
<h2
|
||||
title="Minimize tab"
|
||||
aria-label="Minimize tab"
|
||||
onClick={toggleTabs}
|
||||
className={`p-4 duration-150 ease-in border-b cursor-pointer border-grey-200 ${
|
||||
hasChanged ? 'font-bold' : 'font-light'
|
||||
} text-secondary hover:text-link`}>
|
||||
- {displayName}
|
||||
className={`p-4 duration-150 ease-in border-b cursor-pointer border-grey-200 ${hasChanged ? "font-bold" : "font-light"} text-secondary hover:text-link`}
|
||||
>
|
||||
- {name}
|
||||
</h2>
|
||||
{tabs.get(name) ?? <div>No output for {name}</div>}
|
||||
</Resizable>
|
||||
@@ -92,12 +89,11 @@ function TabbedWindowItem({
|
||||
<button
|
||||
title={`Expand compiler tab: ${name}`}
|
||||
aria-label={`Expand compiler tab: ${name}`}
|
||||
style={{transform: 'rotate(90deg) translate(-50%)'}}
|
||||
style={{ transform: "rotate(90deg) translate(-50%)" }}
|
||||
onClick={toggleTabs}
|
||||
className={`flex-grow-0 w-5 transition-colors duration-150 ease-in ${
|
||||
hasChanged ? 'font-bold' : 'font-light'
|
||||
} text-secondary hover:text-link`}>
|
||||
{displayName}
|
||||
className={`flex-grow-0 w-5 transition-colors duration-150 ease-in ${hasChanged ? "font-bold" : "font-light"} text-secondary hover:text-link`}
|
||||
>
|
||||
{name}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
export {default as Editor} from './Editor';
|
||||
export {default as Header} from './Header';
|
||||
export {StoreProvider} from './StoreContext';
|
||||
export { default as Editor } from "./Editor";
|
||||
export { default as Header } from "./Header";
|
||||
export { StoreProvider } from "./StoreContext";
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
export {default as useMountEffect} from './useMountEffect';
|
||||
export { default as useMountEffect } from "./useMountEffect";
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import type {EffectCallback} from 'react';
|
||||
import {useEffect} from 'react';
|
||||
import type { EffectCallback } from "react";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export default function useMountEffect(effect: EffectCallback) {
|
||||
return useEffect(effect, []);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React from "react";
|
||||
|
||||
/**
|
||||
* Replacement to React.createContext.
|
||||
@@ -23,18 +23,15 @@ import React from 'react';
|
||||
* Instead, it throws an error when `useContext` is not called within a
|
||||
* Provider with a value.
|
||||
*/
|
||||
export default function createContext<T>(): {
|
||||
useContext: () => NonNullable<T>;
|
||||
Provider: React.Provider<T | null>;
|
||||
} {
|
||||
export default function createContext<T>() {
|
||||
const context = React.createContext<T | null>(null);
|
||||
|
||||
function useContext(): NonNullable<T> {
|
||||
function useContext() {
|
||||
const c = React.useContext(context);
|
||||
if (!c)
|
||||
throw new Error('useContext must be within a Provider with a value');
|
||||
throw new Error("useContext must be within a Provider with a value");
|
||||
return c;
|
||||
}
|
||||
|
||||
return {useContext, Provider: context.Provider};
|
||||
return { useContext, Provider: context.Provider };
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import type {Store} from './stores';
|
||||
import type { Store } from "./stores";
|
||||
|
||||
const index = `\
|
||||
export default function MyApp() {
|
||||
@@ -18,5 +18,5 @@ export const defaultStore: Store = {
|
||||
};
|
||||
|
||||
export const emptyStore: Store = {
|
||||
source: '',
|
||||
source: "",
|
||||
};
|
||||
|
||||
@@ -5,16 +5,16 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {Monaco} from '@monaco-editor/react';
|
||||
import { Monaco } from "@monaco-editor/react";
|
||||
import {
|
||||
CompilerErrorDetail,
|
||||
ErrorSeverity,
|
||||
} from 'babel-plugin-react-compiler/src';
|
||||
import {MarkerSeverity, type editor} from 'monaco-editor';
|
||||
} from "babel-plugin-react-compiler/src";
|
||||
import { MarkerSeverity, type editor } from "monaco-editor";
|
||||
|
||||
function mapReactCompilerSeverityToMonaco(
|
||||
level: ErrorSeverity,
|
||||
monaco: Monaco,
|
||||
monaco: Monaco
|
||||
): MarkerSeverity {
|
||||
switch (level) {
|
||||
case ErrorSeverity.Todo:
|
||||
@@ -26,9 +26,9 @@ function mapReactCompilerSeverityToMonaco(
|
||||
|
||||
function mapReactCompilerDiagnosticToMonacoMarker(
|
||||
detail: CompilerErrorDetail,
|
||||
monaco: Monaco,
|
||||
monaco: Monaco
|
||||
): editor.IMarkerData | null {
|
||||
if (detail.loc == null || typeof detail.loc === 'symbol') {
|
||||
if (detail.loc == null || typeof detail.loc === "symbol") {
|
||||
return null;
|
||||
}
|
||||
const severity = mapReactCompilerSeverityToMonaco(detail.severity, monaco);
|
||||
@@ -46,9 +46,9 @@ function mapReactCompilerDiagnosticToMonacoMarker(
|
||||
type ReactCompilerMarkerConfig = {
|
||||
monaco: Monaco;
|
||||
model: editor.ITextModel;
|
||||
details: Array<CompilerErrorDetail>;
|
||||
details: CompilerErrorDetail[];
|
||||
};
|
||||
let decorations: Array<string> = [];
|
||||
let decorations: string[] = [];
|
||||
export function renderReactCompilerMarkers({
|
||||
monaco,
|
||||
model,
|
||||
@@ -63,27 +63,27 @@ export function renderReactCompilerMarkers({
|
||||
markers.push(marker);
|
||||
}
|
||||
if (markers.length > 0) {
|
||||
monaco.editor.setModelMarkers(model, 'owner', markers);
|
||||
const newDecorations = markers.map(marker => {
|
||||
monaco.editor.setModelMarkers(model, "owner", markers);
|
||||
const newDecorations = markers.map((marker) => {
|
||||
return {
|
||||
range: new monaco.Range(
|
||||
marker.startLineNumber,
|
||||
marker.startColumn,
|
||||
marker.endLineNumber,
|
||||
marker.endColumn,
|
||||
marker.endColumn
|
||||
),
|
||||
options: {
|
||||
isWholeLine: true,
|
||||
glyphMarginClassName: 'bg-red-300',
|
||||
glyphMarginClassName: "bg-red-300",
|
||||
},
|
||||
};
|
||||
});
|
||||
decorations = model.deltaDecorations(decorations, newDecorations);
|
||||
} else {
|
||||
monaco.editor.setModelMarkers(model, 'owner', []);
|
||||
monaco.editor.setModelMarkers(model, "owner", []);
|
||||
decorations = model.deltaDecorations(
|
||||
model.getAllDecorations().map(d => d.id),
|
||||
[],
|
||||
model.getAllDecorations().map((d) => d.id),
|
||||
[]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
export * from './messages';
|
||||
export * from './store';
|
||||
export * from "./messages";
|
||||
export * from "./store";
|
||||
|
||||
@@ -27,10 +27,10 @@ export interface Message {
|
||||
export function createMessage(
|
||||
message: string,
|
||||
level: MessageLevel,
|
||||
source: MessageSource,
|
||||
source: MessageSource
|
||||
): Message {
|
||||
const [title, ...body] = message.split('\n');
|
||||
const codeframe = body.length > 0 ? body.join('\n') : undefined;
|
||||
const [title, ...body] = message.split("\n");
|
||||
const codeframe = body.length > 0 ? body.join("\n") : undefined;
|
||||
|
||||
return {
|
||||
source,
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import invariant from 'invariant';
|
||||
import invariant from "invariant";
|
||||
import {
|
||||
compressToEncodedURIComponent,
|
||||
decompressFromEncodedURIComponent,
|
||||
} from 'lz-string';
|
||||
import {defaultStore} from '../defaultStore';
|
||||
} from "lz-string";
|
||||
import { defaultStore } from "../defaultStore";
|
||||
|
||||
/**
|
||||
* Global Store for Playground
|
||||
@@ -28,10 +28,10 @@ export function decodeStore(hash: string): Store {
|
||||
/**
|
||||
* Serialize, encode, and save @param store to localStorage and update URL.
|
||||
*/
|
||||
export function saveStore(store: Store): void {
|
||||
export function saveStore(store: Store) {
|
||||
const hash = encodeStore(store);
|
||||
localStorage.setItem('playgroundStore', hash);
|
||||
history.replaceState({}, '', `#${hash}`);
|
||||
localStorage.setItem("playgroundStore", hash);
|
||||
history.replaceState({}, "", `#${hash}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,9 +41,9 @@ export function saveStore(store: Store): void {
|
||||
function isValidStore(raw: unknown): raw is Store {
|
||||
return (
|
||||
raw != null &&
|
||||
typeof raw == 'object' &&
|
||||
'source' in raw &&
|
||||
typeof raw['source'] === 'string'
|
||||
typeof raw == "object" &&
|
||||
"source" in raw &&
|
||||
typeof raw["source"] === "string"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -53,17 +53,15 @@ function isValidStore(raw: unknown): raw is Store {
|
||||
*/
|
||||
export function initStoreFromUrlOrLocalStorage(): Store {
|
||||
const encodedSourceFromUrl = location.hash.slice(1);
|
||||
const encodedSourceFromLocal = localStorage.getItem('playgroundStore');
|
||||
const encodedSourceFromLocal = localStorage.getItem("playgroundStore");
|
||||
const encodedSource = encodedSourceFromUrl || encodedSourceFromLocal;
|
||||
|
||||
/**
|
||||
* No data in the URL and no data in the localStorage to fallback to.
|
||||
* Initialize with the default store.
|
||||
*/
|
||||
// No data in the URL and no data in the localStorage to fallback to.
|
||||
// Initialize with the default store.
|
||||
if (!encodedSource) return defaultStore;
|
||||
|
||||
const raw = decodeStore(encodedSource);
|
||||
|
||||
invariant(isValidStore(raw), 'Invalid Store');
|
||||
invariant(isValidStore(raw), "Invalid Store");
|
||||
return raw;
|
||||
}
|
||||
|
||||
20
compiler/apps/playground/lib/types.d.ts
vendored
20
compiler/apps/playground/lib/types.d.ts
vendored
@@ -1,20 +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.
|
||||
*/
|
||||
|
||||
// v0.17.1
|
||||
declare module "hermes-parser" {
|
||||
type HermesParserOptions = {
|
||||
allowReturnOutsideFunction?: boolean;
|
||||
babel?: boolean;
|
||||
flow?: "all" | "detect";
|
||||
enableExperimentalComponentSyntax?: boolean;
|
||||
sourceFilename?: string;
|
||||
sourceType?: "module" | "script" | "unambiguous";
|
||||
tokens?: boolean;
|
||||
};
|
||||
export function parse(code: string, options: Partial<HermesParserOptions>);
|
||||
}
|
||||
2
compiler/apps/playground/next-env.d.ts
vendored
2
compiler/apps/playground/next-env.d.ts
vendored
@@ -2,4 +2,4 @@
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
|
||||
@@ -5,48 +5,40 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
|
||||
const path = require('path');
|
||||
const MonacoWebpackPlugin = require("monaco-editor-webpack-plugin");
|
||||
const path = require("path");
|
||||
|
||||
const nextConfig = {
|
||||
experimental: {
|
||||
reactCompiler: true,
|
||||
},
|
||||
reactStrictMode: true,
|
||||
webpack: (config, options) => {
|
||||
// Load *.d.ts files as strings using https://webpack.js.org/guides/asset-modules/#source-assets.
|
||||
config.module.rules.push({
|
||||
test: /\.d\.ts/,
|
||||
type: 'asset/source',
|
||||
type: "asset/source",
|
||||
});
|
||||
|
||||
// Monaco Editor
|
||||
if (!options.isServer) {
|
||||
config.plugins.push(
|
||||
new MonacoWebpackPlugin({
|
||||
languages: ['typescript', 'javascript'],
|
||||
filename: 'static/[name].worker.js',
|
||||
languages: ["typescript", "javascript"],
|
||||
filename: "static/[name].worker.js",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
config.resolve.alias = {
|
||||
...config.resolve.alias,
|
||||
'react-compiler-runtime': path.resolve(
|
||||
"react-compiler-runtime": path.resolve(
|
||||
__dirname,
|
||||
'../../packages/react-compiler-runtime'
|
||||
"../../packages/react-compiler-runtime"
|
||||
),
|
||||
};
|
||||
config.resolve.fallback = {
|
||||
fs: false,
|
||||
path: false,
|
||||
os: false,
|
||||
};
|
||||
|
||||
return config;
|
||||
},
|
||||
|
||||
transpilePackages: ['monaco-editor'],
|
||||
transpilePackages: ["monaco-editor"],
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
|
||||
@@ -3,60 +3,59 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "cd ../.. && concurrently --kill-others -n compiler,runtime,playground \"yarn workspace babel-plugin-react-compiler run build --watch\" \"yarn workspace react-compiler-runtime run build --watch\" \"wait-on packages/babel-plugin-react-compiler/dist/index.js && cd apps/playground && NODE_ENV=development next dev\"",
|
||||
"build:compiler": "cd ../.. && concurrently -n compiler,runtime \"yarn workspace babel-plugin-react-compiler run build\" \"yarn workspace react-compiler-runtime run build\"",
|
||||
"build": "yarn build:compiler && next build",
|
||||
"postbuild": "node ./scripts/downloadFonts.js",
|
||||
"postinstall": "./scripts/link-compiler.sh",
|
||||
"vercel-build": "yarn build",
|
||||
"dev": "NODE_ENV=development next dev",
|
||||
"build": "next build && node ./scripts/downloadFonts.js",
|
||||
"vercel-build": "yarn workspaces run build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"postinstall": "yarn playwright install",
|
||||
"test": "playwright test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.18.9",
|
||||
"@babel/generator": "^7.18.9",
|
||||
"@babel/parser": "^7.18.9",
|
||||
"@babel/plugin-syntax-typescript": "^7.18.9",
|
||||
"@babel/core": "^7.19.1",
|
||||
"@babel/parser": "^7.19.1",
|
||||
"@babel/plugin-syntax-typescript": "^7.18.6",
|
||||
"@babel/plugin-transform-block-scoping": "^7.18.9",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.18.9",
|
||||
"@babel/preset-react": "^7.18.9",
|
||||
"@babel/preset-typescript": "^7.18.9",
|
||||
"@babel/traverse": "^7.18.9",
|
||||
"@babel/types": "7.18.9",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.18.6",
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"@babel/traverse": "^7.19.1",
|
||||
"@babel/types": "^7.19.0",
|
||||
"@heroicons/react": "^1.0.6",
|
||||
"@monaco-editor/react": "^4.4.6",
|
||||
"@playwright/test": "^1.42.1",
|
||||
"@use-gesture/react": "^10.2.22",
|
||||
"hermes-eslint": "^0.25.0",
|
||||
"hermes-parser": "^0.25.0",
|
||||
"hermes-eslint": "^0.14.0",
|
||||
"invariant": "^2.2.4",
|
||||
"lz-string": "^1.5.0",
|
||||
"monaco-editor": "^0.52.0",
|
||||
"next": "^15.0.1",
|
||||
"monaco-editor": "^0.34.1",
|
||||
"next": "^13.5.6",
|
||||
"notistack": "^3.0.0-alpha.7",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier": "3.0.3",
|
||||
"pretty-format": "^29.3.1",
|
||||
"re-resizable": "^6.9.16",
|
||||
"react": "19.0.0-rc-77b637d6-20241016",
|
||||
"react-dom": "19.0.0-rc-77b637d6-20241016"
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-compiler-runtime": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "18.11.9",
|
||||
"@types/react": "npm:types-react@19.0.0-rc.1",
|
||||
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
|
||||
"@types/prettier": "^2.7.1",
|
||||
"@types/react": "18.0.25",
|
||||
"@types/react-dom": "18.0.9",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"clsx": "^1.2.1",
|
||||
"concurrently": "^7.4.0",
|
||||
"eslint": "^8.28.0",
|
||||
"eslint-config-next": "^15.0.1",
|
||||
"eslint-config-next": "^13.5.6",
|
||||
"monaco-editor-webpack-plugin": "^7.1.0",
|
||||
"postcss": "^8.4.31",
|
||||
"tailwindcss": "^3.2.4",
|
||||
"wait-on": "^7.2.0"
|
||||
"tailwindcss": "^3.2.4"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "npm:types-react@19.0.0-rc.1",
|
||||
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1"
|
||||
"./**/@babel/parser": "7.7.4",
|
||||
"./**/@babel/types": "7.7.4",
|
||||
"@babel/core": "7.2.0",
|
||||
"@babel/traverse": "7.1.6",
|
||||
"@babel/generator": "7.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {defineConfig, devices} from '@playwright/test';
|
||||
import path from 'path';
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
import path from "path";
|
||||
|
||||
// Use process.env.PORT by default and fallback to port 3000
|
||||
const PORT = process.env.PORT || 3000;
|
||||
@@ -19,18 +19,19 @@ export default defineConfig({
|
||||
// Timeout per test
|
||||
timeout: 30 * 1000,
|
||||
// Test directory
|
||||
testDir: path.join(__dirname, '__tests__/e2e'),
|
||||
testDir: path.join(__dirname, "__tests__/e2e"),
|
||||
// If a test fails, retry it additional 2 times
|
||||
retries: 2,
|
||||
// Artifacts folder where screenshots, videos, and traces are stored.
|
||||
outputDir: 'test-results/',
|
||||
outputDir: "test-results/",
|
||||
// Note: we only use text snapshots, so its safe to omit the host environment name
|
||||
snapshotPathTemplate: '{testDir}/__snapshots__/{testFilePath}/{arg}{ext}',
|
||||
snapshotPathTemplate: "{testDir}/__snapshots__/{testFilePath}/{arg}{ext}",
|
||||
|
||||
// Run your local dev server before starting the tests:
|
||||
// https://playwright.dev/docs/test-advanced#launching-a-development-web-server-during-the-tests
|
||||
webServer: {
|
||||
command: 'yarn dev',
|
||||
command:
|
||||
"yarn workspace babel-plugin-react-compiler build && yarn workspace react-compiler-runtime build && yarn dev",
|
||||
url: baseURL,
|
||||
timeout: 300 * 1000,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
@@ -43,7 +44,7 @@ export default defineConfig({
|
||||
|
||||
// Retry a test if its failing with enabled tracing. This allows you to analyze the DOM, console logs, network traffic etc.
|
||||
// More information: https://playwright.dev/docs/trace-viewer
|
||||
trace: 'retry-with-trace',
|
||||
trace: "retry-with-trace",
|
||||
|
||||
// All available context options: https://playwright.dev/docs/api/class-browser#browser-new-context
|
||||
// contextOptions: {
|
||||
@@ -53,8 +54,8 @@ export default defineConfig({
|
||||
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: {...devices['Desktop Chrome']},
|
||||
name: "chromium",
|
||||
use: { ...devices["Desktop Chrome"] },
|
||||
},
|
||||
// {
|
||||
// name: 'Desktop Firefox',
|
||||
|
||||
@@ -5,16 +5,16 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const {execSync} = require('child_process');
|
||||
const { execSync } = require("child_process");
|
||||
|
||||
// So that we don't need to check them into the repo.
|
||||
// See https://github.com/reactjs/reactjs.org/blob/main/beta/scripts/downloadFonts.js.
|
||||
execSync(
|
||||
'curl https://conf.reactjs.org/fonts/Optimistic_Display_W_Lt.woff2 --output public/fonts/Optimistic_Display_W_Lt.woff2'
|
||||
"curl https://conf.reactjs.org/fonts/Optimistic_Display_W_Lt.woff2 --output public/fonts/Optimistic_Display_W_Lt.woff2"
|
||||
);
|
||||
execSync(
|
||||
'curl https://conf.reactjs.org/fonts/Optimistic_Display_W_Md.woff2 --output public/fonts/Optimistic_Display_W_Md.woff2'
|
||||
"curl https://conf.reactjs.org/fonts/Optimistic_Display_W_Md.woff2 --output public/fonts/Optimistic_Display_W_Md.woff2"
|
||||
);
|
||||
execSync(
|
||||
'curl https://conf.reactjs.org/fonts/Optimistic_Display_W_Bd.woff2 --output public/fonts/Optimistic_Display_W_Bd.woff2'
|
||||
"curl https://conf.reactjs.org/fonts/Optimistic_Display_W_Bd.woff2 --output public/fonts/Optimistic_Display_W_Bd.woff2"
|
||||
);
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# 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.
|
||||
|
||||
set -eo pipefail
|
||||
|
||||
HERE=$(pwd)
|
||||
|
||||
cd ../../packages/react-compiler-runtime && yarn --silent link && cd $HERE
|
||||
cd ../../packages/babel-plugin-react-compiler && yarn --silent link && cd $HERE
|
||||
|
||||
yarn --silent link babel-plugin-react-compiler
|
||||
yarn --silent link react-compiler-runtime
|
||||
@@ -5,33 +5,33 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const defaultTheme = require('tailwindcss/defaultTheme');
|
||||
const colors = require('./colors');
|
||||
const defaultTheme = require("tailwindcss/defaultTheme");
|
||||
const colors = require("./colors");
|
||||
|
||||
module.exports = {
|
||||
content: [
|
||||
'./app/**/*.{js,ts,jsx,tsx}',
|
||||
'./pages/**/*.{js,ts,jsx,tsx}',
|
||||
'./components/**/*.{js,ts,jsx,tsx}',
|
||||
'./lib/forgetMonacoDiagnostics.ts',
|
||||
"./app/**/*.{js,ts,jsx,tsx}",
|
||||
"./pages/**/*.{js,ts,jsx,tsx}",
|
||||
"./components/**/*.{js,ts,jsx,tsx}",
|
||||
"./lib/forgetMonacoDiagnostics.ts",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors,
|
||||
width: {
|
||||
toast: 'min(900px, 100vw - 40px)',
|
||||
'toast-body': 'calc(100% - 60px)',
|
||||
'toast-title': 'calc(100% - 40px)',
|
||||
toast: "min(900px, 100vw - 40px)",
|
||||
"toast-body": "calc(100% - 60px)",
|
||||
"toast-title": "calc(100% - 40px)",
|
||||
},
|
||||
height: {
|
||||
content: 'calc(100vh - 45px)',
|
||||
monaco: 'calc(100vh - 93px)',
|
||||
monaco_small: 'calc(100vh - 129px)',
|
||||
content: "calc(100vh - 45px)",
|
||||
monaco: "calc(100vh - 93px)",
|
||||
monaco_small: "calc(100vh - 129px)",
|
||||
},
|
||||
fontFamily: {
|
||||
sans: [
|
||||
'Optimistic Display',
|
||||
'-apple-system',
|
||||
"Optimistic Display",
|
||||
"-apple-system",
|
||||
...defaultTheme.fontFamily.sans,
|
||||
],
|
||||
},
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"../../../**"
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -151,7 +151,6 @@ impl MergedBlocks {
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[error("Expected predecessor {predecessor} to exist")]
|
||||
#[allow(dead_code)]
|
||||
pub struct ExpectedPredecessorToExist {
|
||||
predecessor: BlockId,
|
||||
}
|
||||
|
||||
@@ -159,7 +159,6 @@ impl<'m> std::fmt::Debug for ScopeView<'m> {
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[allow(dead_code)]
|
||||
pub struct LabelView<'m> {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) manager: &'m ScopeManager,
|
||||
|
||||
@@ -11,7 +11,7 @@ The idea of React Compiler is to allow developers to use React's familiar declar
|
||||
* Retain React's familiar declarative, component-oriented programming model. Ie, the solution should not fundamentally change how developers think about writing React, and should generally _remove_ concepts (the need to use React.memo(), useMemo(), and useCallback()) rather than introduce new concepts.
|
||||
* "Just work" on idiomatic React code that follows React's rules (pure render functions, the rules of hooks, etc).
|
||||
* Support typical debugging and profiling tools and workflows.
|
||||
* Be predictable and understandable enough by React developers — i.e. developers should be able to quickly develop a rough intuition of how React Compiler works.
|
||||
* Be predictable and understandable enough by React developers — ie developers should be able to quickly develop a rough intuition of how React Compiler works.
|
||||
* Not require explicit annotations (types or otherwise) for typical product code. We may provide features that allow developers to opt-in to using type information to enable additional optimizations, but the compiler should work well without type information or other annotations.
|
||||
|
||||
## Non-Goals
|
||||
@@ -24,15 +24,15 @@ The following are explicitly *not* goals for React Compiler:
|
||||
* The amount of code may regress startup times, which would conflict with our goal of neutral startup performance.
|
||||
* Support code that violates React's rules. React's rules exist to help developers build robust, scalable applications and form a contract that allows us to continue improving React without breaking applications. React Compiler depends on these rules to safely transform code, and violations of rules will therefore break React Compiler's optimizations.
|
||||
* Support legacy React features. Notably we will not support class components due to their inherent mutable state being shared across multiple methods with complex lifetimes and data flow.
|
||||
* Support 100% of the JavaScript language. In particular, we will not support rarely used features and/or features which are known to be unsafe or which cannot be modeled soundly. For example, nested classes that capture values from their closure are difficult to model accurately because of mutability, and `eval()` is unsafe. We aim to support the vast majority of JavaScript code (and the TypeScript and Flow dialects)
|
||||
* Support 100% of the JavaScript language. In particular, we will not support rarely used features and/or features which are known to be unsafe or which cannot be modeled soundly. For example, nested classes that capture values from their closure are difficult to model accurately bc of mutability, and `eval()` is unsafe. We aim to support the vast majority of JavaScript code (and the TypeScript and Flow dialects)
|
||||
|
||||
## Design Principles
|
||||
|
||||
Many aspects of the design follow naturally from the above goals:
|
||||
|
||||
* The compiler output must be high-level code that retains not just the semantics of the input but also is expressed using similar constructs to the input. For example, rather than convert logical expressions (`a ?? b`) into an `if` statement, we retain the high-level form of the logical expression. Rather than convert all looping constructs to a single form, we retain the original form of the loop. This follows from our goals:
|
||||
* High-level code is more compact, and helps reduce the impact of compilation on application size.
|
||||
* High-level constructs that match what the developer wrote are easier to debug.
|
||||
* High-level code is more compact, and helps reduce the impact of compilation on application size
|
||||
* High-level constructs that match what the developer wrote are easier to debug
|
||||
* From the above, it then follows that the compiler's internal representation must also be high-level enough to be able to output the original high-level constructs. The internal representation is what we call a High-level Intermediate Representation (HIR) — a name borrowed from Rust Compiler. However, React Compiler's HIR is perhaps even more suited to this name, as it retains high-level information (distinguishing if vs logical vs ternary, or for vs while vs for..of) but also represents code as a control-flow graph with no nesting.
|
||||
|
||||
## Architecture
|
||||
@@ -47,7 +47,7 @@ The core of the compiler is largely decoupled from Babel, using its own intermed
|
||||
- Validation: We run various validation passes to check that the input is valid React, ie that it does not break the rules. This includes looking for conditional hook calls, unconditional setState calls, etc.
|
||||
- **Optimization**: Various passes such as dead code elimination and constant propagation can generally improve performance and reduce the amount of instructions to be optimized later.
|
||||
- **Type Inference** (InferTypes): We run a conservative type inference pass to identify certain key types of data that may appear in the program that are relevant for further analysis, such as which values are hooks, primitives, etc.
|
||||
- **Inferring Reactive Scopes**: Several passes are involved in determining groups of values that are created/mutated together and the set of instructions involved in creating/mutating those values. We call these groups "reactive scopes", and each can have one or more declarations (or occasionally a reassignment).
|
||||
- **Inferring Reactive Scopes**: Several passes are involved in determing groups of values that are created/mutated together and the set of instructions involved in creating/mutating those values. We call these groups "reactive scopes", and each can have one or more declarations (or occasionally a reassignment).
|
||||
- **Constructing/Optimizing Reactive Scopes**: Once the compiler determines the set of reactive scopes, it then transforms the program to make these scopes explicit in the HIR. The code is later converted to a ReactiveFunction, which is a hybrid of the HIR and an AST. Scopes are further pruned and transformed. For example, the compiler cannot make hook calls conditional, so any reactive scopes that contain a hook call must be pruned. If two consecutive scopes will always invalidate together, we attempt to merge them to reduce overhead, etc.
|
||||
- **Codegen**: Finally, the ReactiveFunction hybrid HIR/AST is converted back to a raw Babel AST node, and returned to the Babel plugin.
|
||||
- **Babel Plugin**: The Babel plugin replaces the original node with the new version.
|
||||
|
||||
@@ -2,28 +2,36 @@
|
||||
"private": true,
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
"packages/*"
|
||||
"packages/*",
|
||||
"apps/*"
|
||||
],
|
||||
"nohoist": [
|
||||
"**/next",
|
||||
"**/next/**"
|
||||
]
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/facebook/react.git"
|
||||
"url": "git+https://github.com/facebook/react-forget.git"
|
||||
},
|
||||
"scripts": {
|
||||
"bundle:meta": "scripts/bundle-meta.sh",
|
||||
"bundle:oss": "scripts/bundle-oss.sh",
|
||||
"copyright": "node scripts/copyright.js",
|
||||
"hash": "scripts/hash.sh",
|
||||
"start": "yarn workspace playground run start",
|
||||
"next": "yarn workspace playground run dev",
|
||||
"build": "yarn workspaces run build",
|
||||
"dev": "echo 'DEPRECATED: use `cd apps/playground && yarn dev` instead!' && sleep 5 && cd apps/playground && yarn dev",
|
||||
"dev": "concurrently --kill-others -n compiler,runtime,playground \"yarn workspace babel-plugin-react-compiler run build --watch\" \"yarn workspace react-compiler-runtime run build --watch\" \"wait-on packages/babel-plugin-react-compiler/dist/index.js && yarn workspace playground run dev\"",
|
||||
"test": "yarn workspaces run test",
|
||||
"prettier:write": "prettier --write . --log-level=warn",
|
||||
"prettier:ci": "prettier --check . --log-level=warn",
|
||||
"snap": "yarn workspace babel-plugin-react-compiler run snap",
|
||||
"snap:build": "yarn workspace snap run build",
|
||||
"npm:publish": "node scripts/release/publish"
|
||||
},
|
||||
"dependencies": {
|
||||
"fs-extra": "^4.0.2"
|
||||
"postinstall": "perl -p -i -e 's/react\\.element/react.transitional.element/' packages/snap/node_modules/fbt/lib/FbtReactUtil.js && perl -p -i -e 's/didWarnAboutUsingAct = false;/didWarnAboutUsingAct = true;/' packages/babel-plugin-react-compiler/node_modules/react-dom/cjs/react-dom-test-utils.development.js",
|
||||
"npm:publish": "node scripts/release/publish-manual"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^25.0.7",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
@@ -33,12 +41,10 @@
|
||||
"@tsconfig/strictest": "^2.0.5",
|
||||
"concurrently": "^7.4.0",
|
||||
"folder-hash": "^4.0.4",
|
||||
"object-assign": "^4.1.1",
|
||||
"ora": "5.4.1",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-hermes-parser": "^0.23.0",
|
||||
"prettier": "^3.2.5",
|
||||
"prompt-promise": "^1.0.3",
|
||||
"rollup": "^4.22.4",
|
||||
"rollup": "^4.13.2",
|
||||
"rollup-plugin-banner2": "^1.2.3",
|
||||
"rollup-plugin-prettier": "^4.1.1",
|
||||
"typescript": "^5.4.3",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
/** @type {import('jest').Config} */
|
||||
const config = {
|
||||
projects: ['<rootDir>/scripts/jest/*.config.js'],
|
||||
projects: ["<rootDir>/scripts/jest/*.config.js"],
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
{
|
||||
"name": "babel-plugin-react-compiler",
|
||||
"version": "0.0.0-experimental-334f00b-20240725",
|
||||
"version": "0.0.0-experimental-938cd9a-20240601",
|
||||
"description": "Babel plugin for React Compiler.",
|
||||
"main": "dist/index.js",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"dist",
|
||||
"!*.tsbuildinfo"
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "rimraf dist && rollup --config --bundleConfigAsCjs",
|
||||
"test": "./scripts/link-react-compiler-runtime.sh && yarn snap:ci",
|
||||
"test": "yarn snap:ci",
|
||||
"jest": "yarn build && ts-node node_modules/.bin/jest",
|
||||
"snap": "node ../snap/dist/main.js",
|
||||
"snap:build": "yarn workspace snap run build",
|
||||
@@ -19,11 +18,16 @@
|
||||
"lint": "yarn eslint src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.19.0"
|
||||
"@babel/generator": "7.2.0",
|
||||
"@babel/types": "^7.19.0",
|
||||
"chalk": "4",
|
||||
"invariant": "^2.2.4",
|
||||
"pretty-format": "^24",
|
||||
"zod": "^3.22.4",
|
||||
"zod-validation-error": "^2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.2.0",
|
||||
"@babel/generator": "7.2.0",
|
||||
"@babel/parser": "^7.2.0",
|
||||
"@babel/plugin-syntax-typescript": "^7.18.6",
|
||||
"@babel/plugin-transform-block-scoping": "^7.18.9",
|
||||
@@ -37,25 +41,20 @@
|
||||
"@types/invariant": "^2.2.35",
|
||||
"@types/jest": "^29.0.3",
|
||||
"@types/node": "^18.7.18",
|
||||
"@typescript-eslint/eslint-plugin": "^8.7.0",
|
||||
"@typescript-eslint/parser": "^8.7.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.4.0",
|
||||
"@typescript-eslint/parser": "^7.4.0",
|
||||
"babel-jest": "^29.0.3",
|
||||
"babel-plugin-fbt": "^1.0.0",
|
||||
"babel-plugin-fbt-runtime": "^1.0.0",
|
||||
"chalk": "4",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint": "8.27.0",
|
||||
"glob": "^7.1.6",
|
||||
"invariant": "^2.2.4",
|
||||
"jest": "^29.0.3",
|
||||
"jest-environment-jsdom": "^29.0.3",
|
||||
"pretty-format": "^24",
|
||||
"react": "0.0.0-experimental-0bc30748-20241028",
|
||||
"react-dom": "0.0.0-experimental-0bc30748-20241028",
|
||||
"react": "19.0.0-beta-b498834eab-20240506",
|
||||
"react-dom": "19.0.0-beta-b498834eab-20240506",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"zod": "^3.22.4",
|
||||
"zod-validation-error": "^2.1.0"
|
||||
"ts-node": "^10.9.2"
|
||||
},
|
||||
"resolutions": {
|
||||
"./**/@babel/parser": "7.7.4",
|
||||
@@ -64,10 +63,5 @@
|
||||
"@babel/core": "7.2.0",
|
||||
"@babel/generator": "7.2.0",
|
||||
"@babel/traverse": "7.7.4"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/facebook/react.git",
|
||||
"directory": "compiler/packages/babel-plugin-react-compiler"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,30 +5,29 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
import {nodeResolve} from '@rollup/plugin-node-resolve';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import json from '@rollup/plugin-json';
|
||||
import path from 'path';
|
||||
import process from 'process';
|
||||
import terser from '@rollup/plugin-terser';
|
||||
import prettier from 'rollup-plugin-prettier';
|
||||
import banner2 from 'rollup-plugin-banner2';
|
||||
import typescript from "@rollup/plugin-typescript";
|
||||
import { nodeResolve } from "@rollup/plugin-node-resolve";
|
||||
import commonjs from "@rollup/plugin-commonjs";
|
||||
import json from "@rollup/plugin-json";
|
||||
import path from "path";
|
||||
import process from "process";
|
||||
import terser from "@rollup/plugin-terser";
|
||||
import prettier from "rollup-plugin-prettier";
|
||||
import banner2 from "rollup-plugin-banner2";
|
||||
|
||||
const NO_INLINE = new Set(['@babel/types']);
|
||||
const NO_INLINE = new Set(["@babel/types"]);
|
||||
|
||||
const DEV_ROLLUP_CONFIG = {
|
||||
input: 'src/index.ts',
|
||||
input: "src/index.ts",
|
||||
output: {
|
||||
file: 'dist/index.js',
|
||||
format: 'cjs',
|
||||
file: "dist/index.js",
|
||||
format: "cjs",
|
||||
sourcemap: false,
|
||||
exports: 'named',
|
||||
exports: "named",
|
||||
},
|
||||
plugins: [
|
||||
typescript({
|
||||
tsconfig: './tsconfig.json',
|
||||
outputToFilesystem: true,
|
||||
tsconfig: "./tsconfig.json",
|
||||
compilerOptions: {
|
||||
noEmit: true,
|
||||
},
|
||||
@@ -36,8 +35,8 @@ const DEV_ROLLUP_CONFIG = {
|
||||
json(),
|
||||
nodeResolve({
|
||||
preferBuiltins: true,
|
||||
resolveOnly: module => NO_INLINE.has(module) === false,
|
||||
rootDir: path.join(process.cwd(), '..'),
|
||||
resolveOnly: (module) => NO_INLINE.has(module) === false,
|
||||
rootDir: path.join(process.cwd(), ".."),
|
||||
}),
|
||||
commonjs(),
|
||||
terser({
|
||||
|
||||
@@ -5,15 +5,15 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import type * as BabelCore from '@babel/core';
|
||||
import {NodePath} from '@babel/core';
|
||||
import * as t from '@babel/types';
|
||||
import type * as BabelCore from "@babel/core";
|
||||
import { NodePath } from "@babel/core";
|
||||
import * as t from "@babel/types";
|
||||
|
||||
export default function AnnotateReactCodeBabelPlugin(
|
||||
_babel: typeof BabelCore,
|
||||
_babel: typeof BabelCore
|
||||
): BabelCore.PluginObj {
|
||||
return {
|
||||
name: 'annotate-react-code',
|
||||
name: "annotate-react-code",
|
||||
visitor: {
|
||||
Program(prog): void {
|
||||
annotate(prog);
|
||||
@@ -56,23 +56,23 @@ function buildTypeOfReactForget(): t.Statement {
|
||||
// typeof globalThis[Symbol.for("react_forget")]
|
||||
return t.expressionStatement(
|
||||
t.unaryExpression(
|
||||
'typeof',
|
||||
"typeof",
|
||||
t.memberExpression(
|
||||
t.identifier('globalThis'),
|
||||
t.identifier("globalThis"),
|
||||
t.callExpression(
|
||||
t.memberExpression(
|
||||
t.identifier('Symbol'),
|
||||
t.identifier('for'),
|
||||
false,
|
||||
t.identifier("Symbol"),
|
||||
t.identifier("for"),
|
||||
false,
|
||||
false
|
||||
),
|
||||
[t.stringLiteral('react_forget')],
|
||||
[t.stringLiteral("react_forget")]
|
||||
),
|
||||
true,
|
||||
false,
|
||||
false
|
||||
),
|
||||
true,
|
||||
),
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -89,9 +89,9 @@ type BabelFn =
|
||||
| NodePath<t.ArrowFunctionExpression>;
|
||||
|
||||
export function isComponentDeclaration(
|
||||
node: t.FunctionDeclaration,
|
||||
node: t.FunctionDeclaration
|
||||
): node is ComponentDeclaration {
|
||||
return Object.prototype.hasOwnProperty.call(node, '__componentDeclaration');
|
||||
return Object.prototype.hasOwnProperty.call(node, "__componentDeclaration");
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -101,7 +101,7 @@ export function isComponentDeclaration(
|
||||
function isComponentOrHookLike(
|
||||
node: NodePath<
|
||||
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
|
||||
>,
|
||||
>
|
||||
): boolean {
|
||||
const functionName = getFunctionName(node);
|
||||
// Check if the name is component or hook like:
|
||||
@@ -114,7 +114,7 @@ function isComponentOrHookLike(
|
||||
* helpers are _usually_ named with lowercase, but some code may
|
||||
* violate this rule
|
||||
*/
|
||||
node.get('params').length <= 1
|
||||
node.get("params").length <= 1
|
||||
);
|
||||
} else if (functionName !== null && isHook(functionName)) {
|
||||
// Hooks have hook invocations or JSX, but can take any # of arguments
|
||||
@@ -151,11 +151,11 @@ function isHook(path: NodePath<t.Expression | t.PrivateName>): boolean {
|
||||
} else if (
|
||||
path.isMemberExpression() &&
|
||||
!path.node.computed &&
|
||||
isHook(path.get('property'))
|
||||
isHook(path.get("property"))
|
||||
) {
|
||||
const obj = path.get('object').node;
|
||||
const obj = path.get("object").node;
|
||||
const isPascalCaseNameSpace = /^[A-Z].*/;
|
||||
return obj.type === 'Identifier' && isPascalCaseNameSpace.test(obj.name);
|
||||
return obj.type === "Identifier" && isPascalCaseNameSpace.test(obj.name);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -177,8 +177,8 @@ function isComponentName(path: NodePath<t.Expression>): boolean {
|
||||
function isForwardRefCallback(path: NodePath<t.Expression>): boolean {
|
||||
return !!(
|
||||
path.parentPath.isCallExpression() &&
|
||||
path.parentPath.get('callee').isExpression() &&
|
||||
isReactAPI(path.parentPath.get('callee'), 'forwardRef')
|
||||
path.parentPath.get("callee").isExpression() &&
|
||||
isReactAPI(path.parentPath.get("callee"), "forwardRef")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -190,22 +190,22 @@ function isForwardRefCallback(path: NodePath<t.Expression>): boolean {
|
||||
function isMemoCallback(path: NodePath<t.Expression>): boolean {
|
||||
return (
|
||||
path.parentPath.isCallExpression() &&
|
||||
path.parentPath.get('callee').isExpression() &&
|
||||
isReactAPI(path.parentPath.get('callee'), 'memo')
|
||||
path.parentPath.get("callee").isExpression() &&
|
||||
isReactAPI(path.parentPath.get("callee"), "memo")
|
||||
);
|
||||
}
|
||||
|
||||
function isReactAPI(
|
||||
path: NodePath<t.Expression | t.PrivateName | t.V8IntrinsicIdentifier>,
|
||||
functionName: string,
|
||||
functionName: string
|
||||
): boolean {
|
||||
const node = path.node;
|
||||
return (
|
||||
(node.type === 'Identifier' && node.name === functionName) ||
|
||||
(node.type === 'MemberExpression' &&
|
||||
node.object.type === 'Identifier' &&
|
||||
node.object.name === 'React' &&
|
||||
node.property.type === 'Identifier' &&
|
||||
(node.type === "Identifier" && node.name === functionName) ||
|
||||
(node.type === "MemberExpression" &&
|
||||
node.object.type === "Identifier" &&
|
||||
node.object.name === "React" &&
|
||||
node.property.type === "Identifier" &&
|
||||
node.property.name === functionName)
|
||||
);
|
||||
}
|
||||
@@ -218,7 +218,7 @@ function callsHooksOrCreatesJsx(node: NodePath<t.Node>): boolean {
|
||||
createsJsx = true;
|
||||
},
|
||||
CallExpression(call) {
|
||||
const callee = call.get('callee');
|
||||
const callee = call.get("callee");
|
||||
if (callee.isExpression() && isHook(callee)) {
|
||||
invokesHooks = true;
|
||||
}
|
||||
@@ -239,10 +239,10 @@ function callsHooksOrCreatesJsx(node: NodePath<t.Node>): boolean {
|
||||
function getFunctionName(
|
||||
path: NodePath<
|
||||
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
|
||||
>,
|
||||
>
|
||||
): NodePath<t.Expression> | null {
|
||||
if (path.isFunctionDeclaration()) {
|
||||
const id = path.get('id');
|
||||
const id = path.get("id");
|
||||
if (id.isIdentifier()) {
|
||||
return id;
|
||||
}
|
||||
@@ -250,31 +250,31 @@ function getFunctionName(
|
||||
}
|
||||
let id: NodePath<t.LVal | t.Expression | t.PrivateName> | null = null;
|
||||
const parent = path.parentPath;
|
||||
if (parent.isVariableDeclarator() && parent.get('init').node === path.node) {
|
||||
if (parent.isVariableDeclarator() && parent.get("init").node === path.node) {
|
||||
// const useHook = () => {};
|
||||
id = parent.get('id');
|
||||
id = parent.get("id");
|
||||
} else if (
|
||||
parent.isAssignmentExpression() &&
|
||||
parent.get('right').node === path.node &&
|
||||
parent.get('operator') === '='
|
||||
parent.get("right").node === path.node &&
|
||||
parent.get("operator") === "="
|
||||
) {
|
||||
// useHook = () => {};
|
||||
id = parent.get('left');
|
||||
id = parent.get("left");
|
||||
} else if (
|
||||
parent.isProperty() &&
|
||||
parent.get('value').node === path.node &&
|
||||
!parent.get('computed') &&
|
||||
parent.get('key').isLVal()
|
||||
parent.get("value").node === path.node &&
|
||||
!parent.get("computed") &&
|
||||
parent.get("key").isLVal()
|
||||
) {
|
||||
/*
|
||||
* {useHook: () => {}}
|
||||
* {useHook() {}}
|
||||
*/
|
||||
id = parent.get('key');
|
||||
id = parent.get("key");
|
||||
} else if (
|
||||
parent.isAssignmentPattern() &&
|
||||
parent.get('right').node === path.node &&
|
||||
!parent.get('computed')
|
||||
parent.get("right").node === path.node &&
|
||||
!parent.get("computed")
|
||||
) {
|
||||
/*
|
||||
* const {useHook = () => {}} = {};
|
||||
@@ -283,7 +283,7 @@ function getFunctionName(
|
||||
* Kinda clowny, but we'd said we'd follow spec convention for
|
||||
* `IsAnonymousFunctionDefinition()` usage.
|
||||
*/
|
||||
id = parent.get('left');
|
||||
id = parent.get("left");
|
||||
}
|
||||
if (id !== null && (id.isIdentifier() || id.isMemberExpression())) {
|
||||
return id;
|
||||
|
||||
@@ -5,27 +5,27 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const {tests} = require('./eslint-plugin-react-hooks-test-cases');
|
||||
const { tests } = require("./eslint-plugin-react-hooks-test-cases");
|
||||
const {
|
||||
runBabelPluginReactCompiler,
|
||||
} = require('../dist/Babel/RunReactCompilerBabelPlugin');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const prettier = require('prettier');
|
||||
const prettierConfigPath = require.resolve('../.prettierrc');
|
||||
const process = require('process');
|
||||
const {createHash} = require('crypto');
|
||||
const {create} = require('domain');
|
||||
} = require("../dist/Babel/RunReactCompilerBabelPlugin");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const prettier = require("prettier");
|
||||
const prettierConfigPath = require.resolve("../.prettierrc");
|
||||
const process = require("process");
|
||||
const { createHash } = require("crypto");
|
||||
const { create } = require("domain");
|
||||
|
||||
const FIXTURES_DIR = path.join(
|
||||
process.cwd(),
|
||||
'src',
|
||||
'__tests__',
|
||||
'fixtures',
|
||||
'compiler',
|
||||
'rules-of-hooks'
|
||||
"src",
|
||||
"__tests__",
|
||||
"fixtures",
|
||||
"compiler",
|
||||
"rules-of-hooks"
|
||||
);
|
||||
|
||||
const PRETTIER_OPTIONS = prettier.resolveConfig.sync(FIXTURES_DIR, {
|
||||
@@ -34,10 +34,10 @@ const PRETTIER_OPTIONS = prettier.resolveConfig.sync(FIXTURES_DIR, {
|
||||
|
||||
const fixtures = [];
|
||||
for (const test of tests.valid) {
|
||||
fixtures.push({code: test.code, valid: true});
|
||||
fixtures.push({ code: test.code, valid: true });
|
||||
}
|
||||
for (const test of tests.invalid) {
|
||||
fixtures.push({code: test.code, valid: false});
|
||||
fixtures.push({ code: test.code, valid: false });
|
||||
}
|
||||
|
||||
for (const fixture of fixtures) {
|
||||
@@ -47,8 +47,8 @@ for (const fixture of fixtures) {
|
||||
// Does the fixture pass with hooks validation disabled? if not skip it
|
||||
runBabelPluginReactCompiler(
|
||||
fixture.code,
|
||||
'rules-of-hooks.js',
|
||||
'typescript',
|
||||
"rules-of-hooks.js",
|
||||
"typescript",
|
||||
{
|
||||
environment: {
|
||||
validateHooksUsage: false,
|
||||
@@ -59,8 +59,8 @@ for (const fixture of fixtures) {
|
||||
try {
|
||||
runBabelPluginReactCompiler(
|
||||
fixture.code,
|
||||
'rules-of-hooks.js',
|
||||
'typescript',
|
||||
"rules-of-hooks.js",
|
||||
"typescript",
|
||||
{
|
||||
environment: {
|
||||
validateHooksUsage: true,
|
||||
@@ -74,7 +74,7 @@ for (const fixture of fixtures) {
|
||||
error = e;
|
||||
}
|
||||
let code = fixture.code;
|
||||
let prefix = '';
|
||||
let prefix = "";
|
||||
if (error !== null) {
|
||||
prefix = `todo.bail.`;
|
||||
code = `// @skip\n// Unsupported input\n${code}`;
|
||||
@@ -92,11 +92,11 @@ for (const fixture of fixtures) {
|
||||
code = `// @skip\n// Failed but should have passed\n${code}`;
|
||||
}
|
||||
const formatted = prettier.format(code, PRETTIER_OPTIONS);
|
||||
const hmac = createHash('sha256');
|
||||
hmac.update(formatted, 'utf8');
|
||||
const hmac = createHash("sha256");
|
||||
hmac.update(formatted, "utf8");
|
||||
let name = `${prefix}rules-of-hooks-${hmac
|
||||
.digest('hex')
|
||||
.digest("hex")
|
||||
.substring(0, 12)}.js`;
|
||||
const fixturePath = path.join(FIXTURES_DIR, name);
|
||||
fs.writeFileSync(fixturePath, formatted, 'utf8');
|
||||
fs.writeFileSync(fixturePath, formatted, "utf8");
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
// NOTE: Extracted from https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
* A string template tag that removes padding from the left side of multi-line strings
|
||||
*/
|
||||
function normalizeIndent(strings) {
|
||||
const codeLines = strings[0].split('\n');
|
||||
const codeLines = strings[0].split("\n");
|
||||
const leftPadding = codeLines[1].match(/\s+/)[0];
|
||||
return codeLines.map(line => line.slice(leftPadding.length)).join('\n');
|
||||
return codeLines.map((line) => line.slice(leftPadding.length)).join("\n");
|
||||
}
|
||||
|
||||
module.exports.tests = {
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const makeE2EConfig = require('../jest/makeE2EConfig');
|
||||
const makeE2EConfig = require("../jest/makeE2EConfig");
|
||||
|
||||
module.exports = makeE2EConfig('e2e no forget', false);
|
||||
module.exports = makeE2EConfig("e2e no forget", false);
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const makeE2EConfig = require('../jest/makeE2EConfig');
|
||||
const makeE2EConfig = require("../jest/makeE2EConfig");
|
||||
|
||||
const config = makeE2EConfig('e2e with forget', true);
|
||||
config.setupFilesAfterEnv = ['<rootDir>/../scripts/jest/setupEnvE2E.js'];
|
||||
const config = makeE2EConfig("e2e with forget", true);
|
||||
config.setupFilesAfterEnv = ["<rootDir>/../scripts/jest/setupEnvE2E.js"];
|
||||
|
||||
module.exports = config;
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
displayName: 'main',
|
||||
preset: 'ts-jest',
|
||||
rootDir: '../../src',
|
||||
testPathIgnorePatterns: ['e2e', 'TestDriver', 'test-utils', 'fixtures'],
|
||||
displayName: "main",
|
||||
preset: "ts-jest",
|
||||
rootDir: "../../src",
|
||||
testPathIgnorePatterns: ["e2e", "TestDriver", "test-utils", "fixtures"],
|
||||
globals: {
|
||||
__DEV__: true,
|
||||
},
|
||||
|
||||
@@ -8,27 +8,27 @@
|
||||
module.exports = function makeE2EConfig(displayName, useForget) {
|
||||
return {
|
||||
displayName,
|
||||
testEnvironment: 'jsdom',
|
||||
rootDir: '../../src',
|
||||
testMatch: ['**/*.e2e.(js|tsx)'],
|
||||
testEnvironment: "jsdom",
|
||||
rootDir: "../../src",
|
||||
testMatch: ["**/*.e2e.(js|tsx)"],
|
||||
modulePathIgnorePatterns: [
|
||||
// ignore snapshots from the opposite forget configuration
|
||||
useForget ? '.*\\.no-forget\\.snap$' : '.*\\.with-forget\\.snap$',
|
||||
useForget ? ".*\\.no-forget\\.snap$" : ".*\\.with-forget\\.snap$",
|
||||
// ignore snapshots from the main project
|
||||
'.*\\.ts\\.snap$',
|
||||
".*\\.ts\\.snap$",
|
||||
],
|
||||
globals: {
|
||||
__FORGET__: useForget,
|
||||
},
|
||||
snapshotResolver: useForget
|
||||
? '<rootDir>/../scripts/jest/snapshot-resolver-with-forget.js'
|
||||
: '<rootDir>/../scripts/jest/snapshot-resolver-no-forget.js',
|
||||
? "<rootDir>/../scripts/jest/snapshot-resolver-with-forget.js"
|
||||
: "<rootDir>/../scripts/jest/snapshot-resolver-no-forget.js",
|
||||
|
||||
transform: {
|
||||
'\\.[tj]sx?$': useForget
|
||||
? '<rootDir>/../scripts/jest/transform-with-forget'
|
||||
: '<rootDir>/../scripts/jest/transform-no-forget',
|
||||
"\\.[tj]sx?$": useForget
|
||||
? "<rootDir>/../scripts/jest/transform-with-forget"
|
||||
: "<rootDir>/../scripts/jest/transform-no-forget",
|
||||
},
|
||||
transformIgnorePatterns: ['/node_modules/'],
|
||||
transformIgnorePatterns: ["/node_modules/"],
|
||||
};
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
module.exports = function makeSnapshotResolver(useForget) {
|
||||
const modeExtension = useForget ? '.with-forget' : '.no-forget';
|
||||
const modeExtension = useForget ? ".with-forget" : ".no-forget";
|
||||
return {
|
||||
resolveSnapshotPath: (testPath, snapshotExtension) =>
|
||||
testPath + modeExtension + snapshotExtension,
|
||||
@@ -17,6 +17,6 @@ module.exports = function makeSnapshotResolver(useForget) {
|
||||
-modeExtension.length - snapshotExtension.length
|
||||
),
|
||||
|
||||
testPathForConsistencyCheck: 'some/__tests__/example.test.js',
|
||||
testPathForConsistencyCheck: "some/__tests__/example.test.js",
|
||||
};
|
||||
};
|
||||
|
||||
@@ -5,19 +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 {compile} from 'babel-plugin-react-compiler';
|
||||
import {execSync} from 'child_process';
|
||||
import { jsx } from "@babel/plugin-syntax-jsx";
|
||||
import babelJest from "babel-jest";
|
||||
import { compile } from "babel-plugin-react-compiler";
|
||||
import { execSync } from "child_process";
|
||||
|
||||
import type {NodePath, Visitor} from '@babel/traverse';
|
||||
import type {CallExpression, FunctionDeclaration} from '@babel/types';
|
||||
import * as t from '@babel/types';
|
||||
import type { NodePath, Visitor } from "@babel/traverse";
|
||||
import type { CallExpression, FunctionDeclaration } from "@babel/types";
|
||||
import * as t from "@babel/types";
|
||||
import {
|
||||
EnvironmentConfig,
|
||||
validateEnvironmentConfig,
|
||||
} from 'babel-plugin-react-compiler';
|
||||
import {basename} from 'path';
|
||||
} from "babel-plugin-react-compiler";
|
||||
import { basename } from "path";
|
||||
|
||||
/**
|
||||
* -- IMPORTANT --
|
||||
@@ -28,16 +28,15 @@ import {basename} from 'path';
|
||||
const e2eTransformerCacheKey = 1;
|
||||
const forgetOptions: EnvironmentConfig = validateEnvironmentConfig({
|
||||
enableAssumeHooksFollowRulesOfReact: true,
|
||||
enableFunctionOutlining: false,
|
||||
});
|
||||
const debugMode = process.env['DEBUG_FORGET_COMPILER'] != null;
|
||||
const debugMode = process.env["DEBUG_FORGET_COMPILER"] != null;
|
||||
|
||||
module.exports = (useForget: boolean) => {
|
||||
function createTransformer() {
|
||||
return babelJest.createTransformer({
|
||||
passPerPreset: true,
|
||||
presets: [
|
||||
'@babel/preset-typescript',
|
||||
"@babel/preset-typescript",
|
||||
{
|
||||
plugins: [
|
||||
useForget
|
||||
@@ -49,36 +48,36 @@ module.exports = (useForget: boolean) => {
|
||||
* (see https://github.com/jestjs/jest/blob/v29.6.2/packages/babel-jest/src/index.ts#L84)
|
||||
*/
|
||||
compilerCacheKey: execSync(
|
||||
'yarn --silent --cwd ../.. hash packages/babel-plugin-react-compiler/dist',
|
||||
"yarn --silent --cwd ../.. hash packages/babel-plugin-react-compiler/dist"
|
||||
).toString(),
|
||||
transformOptionsCacheKey: forgetOptions,
|
||||
e2eTransformerCacheKey,
|
||||
},
|
||||
]
|
||||
: '@babel/plugin-syntax-jsx',
|
||||
: "@babel/plugin-syntax-jsx",
|
||||
],
|
||||
},
|
||||
'@babel/preset-react',
|
||||
"@babel/preset-react",
|
||||
{
|
||||
plugins: [
|
||||
[
|
||||
function BabelPluginRewriteRequirePath(): {visitor: Visitor} {
|
||||
function BabelPluginRewriteRequirePath(): { visitor: Visitor } {
|
||||
return {
|
||||
visitor: {
|
||||
CallExpression(path: NodePath<CallExpression>): void {
|
||||
const {callee} = path.node;
|
||||
const { callee } = path.node;
|
||||
if (
|
||||
callee.type === 'Identifier' &&
|
||||
callee.name === 'require'
|
||||
callee.type === "Identifier" &&
|
||||
callee.name === "require"
|
||||
) {
|
||||
const arg = path.node.arguments[0];
|
||||
if (arg.type === 'StringLiteral') {
|
||||
if (arg.type === "StringLiteral") {
|
||||
/*
|
||||
* The compiler adds requires of "React", which is expected to be a wrapper
|
||||
* around the "react" package. For tests, we just rewrite the require.
|
||||
*/
|
||||
if (arg.value === 'React') {
|
||||
arg.value = 'react';
|
||||
if (arg.value === "React") {
|
||||
arg.value = "react";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,7 +86,7 @@ module.exports = (useForget: boolean) => {
|
||||
};
|
||||
},
|
||||
],
|
||||
'@babel/plugin-transform-modules-commonjs',
|
||||
"@babel/plugin-transform-modules-commonjs",
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -125,7 +124,7 @@ function isReactComponentLike(fn: NodePath<FunctionDeclaration>): boolean {
|
||||
|
||||
fn.traverse({
|
||||
DirectiveLiteral(path) {
|
||||
if (path.node.value === 'use no forget') {
|
||||
if (path.node.value === "use no forget") {
|
||||
hasNoUseForgetDirective = true;
|
||||
}
|
||||
},
|
||||
@@ -140,7 +139,7 @@ function isReactComponentLike(fn: NodePath<FunctionDeclaration>): boolean {
|
||||
CallExpression(path) {
|
||||
// Is there hook usage?
|
||||
if (
|
||||
path.node.callee.type === 'Identifier' &&
|
||||
path.node.callee.type === "Identifier" &&
|
||||
!/^use[A-Z0-9]/.test(path.node.callee.name)
|
||||
) {
|
||||
isReactComponent = true;
|
||||
@@ -170,7 +169,7 @@ function ReactForgetFunctionTransform() {
|
||||
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}`,
|
||||
` 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}`);
|
||||
@@ -180,11 +179,11 @@ function ReactForgetFunctionTransform() {
|
||||
const compiled = compile(
|
||||
fn,
|
||||
forgetOptions,
|
||||
'Other',
|
||||
'_c',
|
||||
null,
|
||||
"Other",
|
||||
"_c",
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
compiledFns.add(compiled);
|
||||
|
||||
@@ -193,14 +192,14 @@ function ReactForgetFunctionTransform() {
|
||||
compiled.params,
|
||||
compiled.body,
|
||||
compiled.generator,
|
||||
compiled.async,
|
||||
compiled.async
|
||||
);
|
||||
fn.replaceWith(fun);
|
||||
fn.skip();
|
||||
},
|
||||
};
|
||||
return {
|
||||
name: 'react-forget-e2e',
|
||||
name: "react-forget-e2e",
|
||||
inherits: jsx,
|
||||
visitor,
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const ReactCompilerRuntime = require('react/compiler-runtime');
|
||||
const ReactCompilerRuntime = require("react/compiler-runtime");
|
||||
|
||||
/*
|
||||
* Our e2e babel transform currently only compiles functions, not programs.
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const makeSnapshotResolver = require('./makeSnapshotResolver');
|
||||
const makeSnapshotResolver = require("./makeSnapshotResolver");
|
||||
|
||||
module.exports = makeSnapshotResolver(false);
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const makeSnapshotResolver = require('./makeSnapshotResolver');
|
||||
const makeSnapshotResolver = require("./makeSnapshotResolver");
|
||||
|
||||
module.exports = makeSnapshotResolver(true);
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
module.exports = require('./makeTransform')(false);
|
||||
module.exports = require("./makeTransform")(false);
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
module.exports = require('./makeTransform')(true);
|
||||
module.exports = require("./makeTransform")(true);
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# 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.
|
||||
|
||||
set -eo pipefail
|
||||
|
||||
yarn --silent workspace react-compiler-runtime link
|
||||
yarn --silent workspace babel-plugin-react-compiler link react-compiler-runtime
|
||||
@@ -5,12 +5,12 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import type * as BabelCore from '@babel/core';
|
||||
import {compileProgram, parsePluginOptions} from '../Entrypoint';
|
||||
import type * as BabelCore from "@babel/core";
|
||||
import { compileProgram, parsePluginOptions } from "../Entrypoint";
|
||||
import {
|
||||
injectReanimatedFlag,
|
||||
pipelineUsesReanimatedPlugin,
|
||||
} from '../Entrypoint/Reanimated';
|
||||
} from "../Entrypoint/Reanimated";
|
||||
|
||||
/*
|
||||
* The React Forget Babel Plugin
|
||||
@@ -18,10 +18,10 @@ import {
|
||||
* @returns
|
||||
*/
|
||||
export default function BabelPluginReactCompiler(
|
||||
_babel: typeof BabelCore,
|
||||
_babel: typeof BabelCore
|
||||
): BabelCore.PluginObj {
|
||||
return {
|
||||
name: 'react-forget',
|
||||
name: "react-forget",
|
||||
visitor: {
|
||||
/*
|
||||
* Note: Babel does some "smart" merging of visitors across plugins, so even if A is inserted
|
||||
@@ -30,16 +30,13 @@ export default function BabelPluginReactCompiler(
|
||||
*/
|
||||
Program(prog, pass): void {
|
||||
let opts = parsePluginOptions(pass.opts);
|
||||
const isDev =
|
||||
(typeof __DEV__ !== 'undefined' && __DEV__ === true) ||
|
||||
process.env['NODE_ENV'] === 'development';
|
||||
if (
|
||||
opts.enableReanimatedCheck === true &&
|
||||
pipelineUsesReanimatedPlugin(pass.file.opts.plugins)
|
||||
) {
|
||||
opts = injectReanimatedFlag(opts);
|
||||
}
|
||||
if (isDev) {
|
||||
if (process.env["NODE_ENV"] === "development") {
|
||||
opts = {
|
||||
...opts,
|
||||
environment: {
|
||||
|
||||
@@ -5,25 +5,25 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import type * as BabelCore from '@babel/core';
|
||||
import {transformFromAstSync} from '@babel/core';
|
||||
import * as BabelParser from '@babel/parser';
|
||||
import invariant from 'invariant';
|
||||
import type {PluginOptions} from '../Entrypoint';
|
||||
import BabelPluginReactCompiler from './BabelPlugin';
|
||||
import type * as BabelCore from "@babel/core";
|
||||
import { transformFromAstSync } from "@babel/core";
|
||||
import * as BabelParser from "@babel/parser";
|
||||
import invariant from "invariant";
|
||||
import type { PluginOptions } from "../Entrypoint";
|
||||
import BabelPluginReactCompiler from "./BabelPlugin";
|
||||
|
||||
export const DEFAULT_PLUGINS = ['babel-plugin-fbt', 'babel-plugin-fbt-runtime'];
|
||||
export const DEFAULT_PLUGINS = ["babel-plugin-fbt", "babel-plugin-fbt-runtime"];
|
||||
export function runBabelPluginReactCompiler(
|
||||
text: string,
|
||||
file: string,
|
||||
language: 'flow' | 'typescript',
|
||||
language: "flow" | "typescript",
|
||||
options: Partial<PluginOptions> | null,
|
||||
includeAst: boolean = false,
|
||||
includeAst: boolean = false
|
||||
): BabelCore.BabelFileResult {
|
||||
const ast = BabelParser.parse(text, {
|
||||
sourceFilename: file,
|
||||
plugins: [language, 'jsx'],
|
||||
sourceType: 'module',
|
||||
plugins: [language, "jsx"],
|
||||
sourceType: "module",
|
||||
});
|
||||
const result = transformFromAstSync(ast, text, {
|
||||
ast: includeAst,
|
||||
@@ -32,16 +32,16 @@ export function runBabelPluginReactCompiler(
|
||||
retainLines: true,
|
||||
plugins: [
|
||||
[BabelPluginReactCompiler, options],
|
||||
'babel-plugin-fbt',
|
||||
'babel-plugin-fbt-runtime',
|
||||
"babel-plugin-fbt",
|
||||
"babel-plugin-fbt-runtime",
|
||||
],
|
||||
sourceType: 'module',
|
||||
sourceType: "module",
|
||||
configFile: false,
|
||||
babelrc: false,
|
||||
});
|
||||
invariant(
|
||||
result?.code != null,
|
||||
`Expected BabelPluginReactForget to codegen successfully, got: ${result}`,
|
||||
`Expected BabelPluginReactForget to codegen successfully, got: ${result}`
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -5,37 +5,37 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import type {SourceLocation} from './HIR';
|
||||
import {assertExhaustive} from './Utils/utils';
|
||||
import type { SourceLocation } from "./HIR";
|
||||
import { assertExhaustive } from "./Utils/utils";
|
||||
|
||||
export enum ErrorSeverity {
|
||||
/**
|
||||
* Invalid JS syntax, or valid syntax that is semantically invalid which may indicate some
|
||||
* misunderstanding on the user’s part.
|
||||
*/
|
||||
InvalidJS = 'InvalidJS',
|
||||
InvalidJS = "InvalidJS",
|
||||
/**
|
||||
* Code that breaks the rules of React.
|
||||
*/
|
||||
InvalidReact = 'InvalidReact',
|
||||
InvalidReact = "InvalidReact",
|
||||
/**
|
||||
* Incorrect configuration of the compiler.
|
||||
*/
|
||||
InvalidConfig = 'InvalidConfig',
|
||||
InvalidConfig = "InvalidConfig",
|
||||
/**
|
||||
* Code that can reasonably occur and that doesn't break any rules, but is unsafe to preserve
|
||||
* memoization.
|
||||
*/
|
||||
CannotPreserveMemoization = 'CannotPreserveMemoization',
|
||||
CannotPreserveMemoization = "CannotPreserveMemoization",
|
||||
/**
|
||||
* Unhandled syntax that we don't support yet.
|
||||
*/
|
||||
Todo = 'Todo',
|
||||
Todo = "Todo",
|
||||
/**
|
||||
* An unexpected internal error in the compiler that indicates critical issues that can panic
|
||||
* the compiler.
|
||||
*/
|
||||
Invariant = 'Invariant',
|
||||
Invariant = "Invariant",
|
||||
}
|
||||
|
||||
export enum CompilerSuggestionOperation {
|
||||
@@ -79,19 +79,19 @@ export class CompilerErrorDetail {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
get reason(): CompilerErrorDetailOptions['reason'] {
|
||||
get reason(): CompilerErrorDetailOptions["reason"] {
|
||||
return this.options.reason;
|
||||
}
|
||||
get description(): CompilerErrorDetailOptions['description'] {
|
||||
get description(): CompilerErrorDetailOptions["description"] {
|
||||
return this.options.description;
|
||||
}
|
||||
get severity(): CompilerErrorDetailOptions['severity'] {
|
||||
get severity(): CompilerErrorDetailOptions["severity"] {
|
||||
return this.options.severity;
|
||||
}
|
||||
get loc(): CompilerErrorDetailOptions['loc'] {
|
||||
get loc(): CompilerErrorDetailOptions["loc"] {
|
||||
return this.options.loc;
|
||||
}
|
||||
get suggestions(): CompilerErrorDetailOptions['suggestions'] {
|
||||
get suggestions(): CompilerErrorDetailOptions["suggestions"] {
|
||||
return this.options.suggestions;
|
||||
}
|
||||
|
||||
@@ -100,10 +100,10 @@ export class CompilerErrorDetail {
|
||||
if (this.description != null) {
|
||||
buffer.push(`. ${this.description}`);
|
||||
}
|
||||
if (this.loc != null && typeof this.loc !== 'symbol') {
|
||||
if (this.loc != null && typeof this.loc !== "symbol") {
|
||||
buffer.push(` (${this.loc.start.line}:${this.loc.end.line})`);
|
||||
}
|
||||
return buffer.join('');
|
||||
return buffer.join("");
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
@@ -116,7 +116,7 @@ export class CompilerError extends Error {
|
||||
|
||||
static invariant(
|
||||
condition: unknown,
|
||||
options: Omit<CompilerErrorDetailOptions, 'severity'>,
|
||||
options: Omit<CompilerErrorDetailOptions, "severity">
|
||||
): asserts condition {
|
||||
if (!condition) {
|
||||
const errors = new CompilerError();
|
||||
@@ -124,57 +124,57 @@ export class CompilerError extends Error {
|
||||
new CompilerErrorDetail({
|
||||
...options,
|
||||
severity: ErrorSeverity.Invariant,
|
||||
}),
|
||||
})
|
||||
);
|
||||
throw errors;
|
||||
}
|
||||
}
|
||||
|
||||
static throwTodo(
|
||||
options: Omit<CompilerErrorDetailOptions, 'severity'>,
|
||||
options: Omit<CompilerErrorDetailOptions, "severity">
|
||||
): never {
|
||||
const errors = new CompilerError();
|
||||
errors.pushErrorDetail(
|
||||
new CompilerErrorDetail({...options, severity: ErrorSeverity.Todo}),
|
||||
new CompilerErrorDetail({ ...options, severity: ErrorSeverity.Todo })
|
||||
);
|
||||
throw errors;
|
||||
}
|
||||
|
||||
static throwInvalidJS(
|
||||
options: Omit<CompilerErrorDetailOptions, 'severity'>,
|
||||
options: Omit<CompilerErrorDetailOptions, "severity">
|
||||
): never {
|
||||
const errors = new CompilerError();
|
||||
errors.pushErrorDetail(
|
||||
new CompilerErrorDetail({
|
||||
...options,
|
||||
severity: ErrorSeverity.InvalidJS,
|
||||
}),
|
||||
})
|
||||
);
|
||||
throw errors;
|
||||
}
|
||||
|
||||
static throwInvalidReact(
|
||||
options: Omit<CompilerErrorDetailOptions, 'severity'>,
|
||||
options: Omit<CompilerErrorDetailOptions, "severity">
|
||||
): never {
|
||||
const errors = new CompilerError();
|
||||
errors.pushErrorDetail(
|
||||
new CompilerErrorDetail({
|
||||
...options,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
}),
|
||||
})
|
||||
);
|
||||
throw errors;
|
||||
}
|
||||
|
||||
static throwInvalidConfig(
|
||||
options: Omit<CompilerErrorDetailOptions, 'severity'>,
|
||||
options: Omit<CompilerErrorDetailOptions, "severity">
|
||||
): never {
|
||||
const errors = new CompilerError();
|
||||
errors.pushErrorDetail(
|
||||
new CompilerErrorDetail({
|
||||
...options,
|
||||
severity: ErrorSeverity.InvalidConfig,
|
||||
}),
|
||||
})
|
||||
);
|
||||
throw errors;
|
||||
}
|
||||
@@ -187,7 +187,7 @@ export class CompilerError extends Error {
|
||||
|
||||
constructor(...args: Array<any>) {
|
||||
super(...args);
|
||||
this.name = 'ReactCompilerError';
|
||||
this.name = "ReactCompilerError";
|
||||
}
|
||||
|
||||
override get message(): string {
|
||||
@@ -197,7 +197,7 @@ export class CompilerError extends Error {
|
||||
override set message(_message: string) {}
|
||||
|
||||
override toString(): string {
|
||||
return this.details.map(detail => detail.toString()).join('\n\n');
|
||||
return this.details.map((detail) => detail.toString()).join("\n\n");
|
||||
}
|
||||
|
||||
push(options: CompilerErrorDetailOptions): CompilerErrorDetail {
|
||||
@@ -206,7 +206,7 @@ export class CompilerError extends Error {
|
||||
description: options.description ?? null,
|
||||
severity: options.severity,
|
||||
suggestions: options.suggestions,
|
||||
loc: typeof options.loc === 'symbol' ? null : options.loc,
|
||||
loc: typeof options.loc === "symbol" ? null : options.loc,
|
||||
});
|
||||
return this.pushErrorDetail(detail);
|
||||
}
|
||||
@@ -226,7 +226,7 @@ export class CompilerError extends Error {
|
||||
* but otherwise continue compiling the rest of the app.
|
||||
*/
|
||||
isCritical(): boolean {
|
||||
return this.details.some(detail => {
|
||||
return this.details.some((detail) => {
|
||||
switch (detail.severity) {
|
||||
case ErrorSeverity.Invariant:
|
||||
case ErrorSeverity.InvalidJS:
|
||||
@@ -237,7 +237,7 @@ export class CompilerError extends Error {
|
||||
case ErrorSeverity.Todo:
|
||||
return false;
|
||||
default:
|
||||
assertExhaustive(detail.severity, 'Unhandled error severity');
|
||||
assertExhaustive(detail.severity, "Unhandled error severity");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {NodePath} from '@babel/core';
|
||||
import * as t from '@babel/types';
|
||||
import {PluginOptions} from './Options';
|
||||
import { NodePath } from "@babel/core";
|
||||
import * as t from "@babel/types";
|
||||
import { PluginOptions } from "./Options";
|
||||
|
||||
export function insertGatedFunctionDeclaration(
|
||||
fnPath: NodePath<
|
||||
@@ -17,14 +17,15 @@ export function insertGatedFunctionDeclaration(
|
||||
| t.FunctionDeclaration
|
||||
| t.ArrowFunctionExpression
|
||||
| t.FunctionExpression,
|
||||
gating: NonNullable<PluginOptions['gating']>,
|
||||
): void {
|
||||
gating: NonNullable<PluginOptions["gating"]>
|
||||
): NodePath<t.ConditionalExpression | t.VariableDeclaration> {
|
||||
const gatingExpression = t.conditionalExpression(
|
||||
t.callExpression(t.identifier(gating.importSpecifierName), []),
|
||||
buildFunctionExpression(compiled),
|
||||
buildFunctionExpression(fnPath.node),
|
||||
buildFunctionExpression(fnPath.node)
|
||||
);
|
||||
|
||||
let compiledFn;
|
||||
/*
|
||||
* Convert function declarations to named variables *unless* this is an
|
||||
* `export default function ...` since `export default const ...` is
|
||||
@@ -32,50 +33,33 @@ export function insertGatedFunctionDeclaration(
|
||||
* conditional expression
|
||||
*/
|
||||
if (
|
||||
fnPath.parentPath.node.type !== 'ExportDefaultDeclaration' &&
|
||||
fnPath.node.type === 'FunctionDeclaration' &&
|
||||
fnPath.parentPath.node.type !== "ExportDefaultDeclaration" &&
|
||||
fnPath.node.type === "FunctionDeclaration" &&
|
||||
fnPath.node.id != null
|
||||
) {
|
||||
fnPath.replaceWith(
|
||||
t.variableDeclaration('const', [
|
||||
compiledFn = 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,
|
||||
),
|
||||
]),
|
||||
);
|
||||
])
|
||||
)[0];
|
||||
} else {
|
||||
fnPath.replaceWith(gatingExpression);
|
||||
compiledFn = fnPath.replaceWith(gatingExpression)[0];
|
||||
}
|
||||
|
||||
return compiledFn;
|
||||
}
|
||||
|
||||
function buildFunctionExpression(
|
||||
node:
|
||||
| t.FunctionDeclaration
|
||||
| t.ArrowFunctionExpression
|
||||
| t.FunctionExpression,
|
||||
node: t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
|
||||
): t.ArrowFunctionExpression | t.FunctionExpression {
|
||||
if (
|
||||
node.type === 'ArrowFunctionExpression' ||
|
||||
node.type === 'FunctionExpression'
|
||||
node.type === "ArrowFunctionExpression" ||
|
||||
node.type === "FunctionExpression"
|
||||
) {
|
||||
return node;
|
||||
} else {
|
||||
const fn: t.FunctionExpression = {
|
||||
type: 'FunctionExpression',
|
||||
type: "FunctionExpression",
|
||||
async: node.async,
|
||||
generator: node.generator,
|
||||
loc: node.loc ?? null,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user