diff --git a/.circleci/config.yml b/.circleci/config.yml index 37544484f2..7d273d0163 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -48,197 +48,6 @@ parameters: default: '' jobs: - 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 --ci=circleci - - 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_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=circleci - - 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 - publish_prerelease: parameters: commit_sha: @@ -262,78 +71,6 @@ jobs: scripts/release/publish.js --ci --tags << parameters.dist_tag >> workflows: - - build_and_test: - unless: << pipeline.parameters.prerelease_commit_sha >> - jobs: - - 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 - - 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 - - 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 >> diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 63d0d11fb6..2c19b24845 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -1 +1,2 @@ c998bb1ed4b3285398c9c7797135d3f060243c6a +fd2b3e13d330a4559f5aa21462e1cb2cbbcf144b diff --git a/.github/workflows/devtools_regression_tests.yml b/.github/workflows/devtools_regression_tests.yml new file mode 100644 index 0000000000..72b82f6b71 --- /dev/null +++ b/.github/workflows/devtools_regression_tests.yml @@ -0,0 +1,171 @@ +name: (DevTools) Regression Tests + +on: + schedule: + - cron: 0 0 * * * + workflow_dispatch: + inputs: + prerelease_commit_sha: + required: false + +env: + TZ: /usr/share/zoneinfo/America/Los_Angeles + +jobs: + download_build: + if: inputs.prerelease_commit_sha == '' + name: Download base build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 18.20.1 + 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') }} + - 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-ghaction.js --commit=$(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: 18.20.1 + 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') }} + - run: yarn install --frozen-lockfile + - name: Restore archived build + uses: actions/download-artifact@v4 + with: + name: build + path: build + - run: ./scripts/circleci/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" + continue-on-error: true + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 18.20.1 + 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') }} + - 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/circleci/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=github + + 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: 18.20.1 + 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') }} + - 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/circleci/download_devtools_regression_build.js ${{ matrix.version }} + - run: ls -R build-regression + - run: ./scripts/circleci/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 diff --git a/.github/workflows/runtime_build_and_test.yml b/.github/workflows/runtime_build_and_test.yml index 181bb2ffa8..2cf5011a00 100644 --- a/.github/workflows/runtime_build_and_test.yml +++ b/.github/workflows/runtime_build_and_test.yml @@ -180,9 +180,8 @@ jobs: - name: Archive build uses: actions/upload-artifact@v4 with: - name: build_${{ matrix.worker_id }}_${{ matrix.release_channel }} - path: | - build + name: _build_${{ matrix.worker_id }}_${{ matrix.release_channel }} + path: build test_build: name: yarn test-build @@ -242,6 +241,7 @@ jobs: - name: Restore archived build uses: actions/download-artifact@v4 with: + pattern: _build_* path: build merge-multiple: true - name: Display structure of build @@ -269,6 +269,7 @@ jobs: - name: Restore archived build uses: actions/download-artifact@v4 with: + pattern: _build_* path: build merge-multiple: true - name: Display structure of build @@ -311,6 +312,7 @@ jobs: - name: Restore archived build uses: actions/download-artifact@v4 with: + pattern: _build_* path: build merge-multiple: true - name: Display structure of build @@ -341,6 +343,7 @@ jobs: - name: Restore archived build uses: actions/download-artifact@v4 with: + pattern: _build_* path: build merge-multiple: true - name: Display structure of build @@ -370,6 +373,7 @@ jobs: - name: Restore archived build uses: actions/download-artifact@v4 with: + pattern: _build_* path: build merge-multiple: true - name: Display structure of build @@ -407,6 +411,7 @@ jobs: - name: Restore archived build uses: actions/download-artifact@v4 with: + pattern: _build_* path: build merge-multiple: true - name: Display structure of build @@ -462,6 +467,7 @@ jobs: - name: Restore archived build uses: actions/download-artifact@v4 with: + pattern: _build_* path: build merge-multiple: true - run: ./scripts/circleci/pack_and_store_devtools_artifacts.sh @@ -507,6 +513,7 @@ jobs: - name: Restore archived build uses: actions/download-artifact@v4 with: + pattern: _build_* path: build merge-multiple: true - run: | @@ -515,3 +522,88 @@ jobs: - run: ./scripts/circleci/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: 18.20.1 + 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') }} + - 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-ghaction.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: 18.20.1 + 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') }} + - 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 diff --git a/.github/workflows/runtime_commit_artifacts.yml b/.github/workflows/runtime_commit_artifacts.yml index c665f9b1bd..c0a6874119 100644 --- a/.github/workflows/runtime_commit_artifacts.yml +++ b/.github/workflows/runtime_commit_artifacts.yml @@ -1,8 +1,11 @@ -name: (Runtime) Commit Artifacts for Meta WWW and fbsource +name: (Runtime) Commit Artifacts for Meta WWW and fbsource V2 on: - push: - branches: [main, meta-www, meta-fbsource] + workflow_run: + workflows: ["(Runtime) Build and Test"] + types: [completed] + branches: + - main env: TZ: /usr/share/zoneinfo/America/Los_Angeles @@ -49,102 +52,27 @@ jobs: run: | echo "www_branch_count=$(git ls-remote --heads origin "refs/heads/meta-www" | wc -l)" >> "$GITHUB_OUTPUT" echo "fbsource_branch_count=$(git ls-remote --heads origin "refs/heads/meta-fbsource" | wc -l)" >> "$GITHUB_OUTPUT" - - name: Download and unzip artifacts - uses: actions/github-script@v6 - env: - CIRCLECI_TOKEN: ${{secrets.CIRCLECI_TOKEN_DIFFTRAIN}} + - uses: actions/setup-node@v4 with: - script: | - // TODO: Move this to a script file. - 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; - } + node-version: 18.20.1 + 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') }} + - 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-ghaction.js --commit=${{ github.event.workflow_run.head_sha }} + - name: Display structure of build + run: ls -R build - name: Strip @license from eslint plugin and react-refresh run: | sed -i -e 's/ @license React*//' \ @@ -199,9 +127,9 @@ jobs: ls -R ./compiled-rn - name: Add REVISION files run: | - echo ${{ github.sha }} >> ./compiled/facebook-www/REVISION + echo ${{ github.event.workflow_run.head_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.event.workflow_run.head_sha}} >> ./compiled-rn/facebook-fbsource/xplat/js/react-native-github/Libraries/Renderer/REVISION - name: "Get current version string" id: get_current_version run: | @@ -214,11 +142,11 @@ 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@v3 + - uses: actions/upload-artifact@v4 with: name: compiled path: compiled/ - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: compiled-rn path: compiled-rn/ @@ -233,7 +161,7 @@ jobs: ref: builds/facebook-www - name: Ensure clean directory run: rm -rf compiled - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: compiled path: compiled/ @@ -298,12 +226,12 @@ jobs: uses: stefanzweifel/git-auto-commit-action@v4 with: commit_message: | - ${{ github.event.head_commit.message }} + ${{ github.event.workflow_run.head_commit.message }} - DiffTrain build for [${{ github.sha }}](https://github.com/facebook/react/commit/${{ github.sha }}) + DiffTrain build for [${{ github.event.workflow_run.head_sha }}](https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha }}) branch: builds/facebook-www - commit_user_name: ${{ github.actor }} - commit_user_email: ${{ github.actor }}@users.noreply.github.com + commit_user_name: ${{ github.event.workflow_run.triggering_actor.login }} + commit_user_email: ${{ github.event.workflow_run.triggering_actor.email || format('{0}@users.noreply.github.com', github.event.workflow_run.triggering_actor.login) }} create_branch: true commit_fbsource_artifacts: @@ -316,7 +244,7 @@ jobs: ref: builds/facebook-fbsource - name: Ensure clean directory run: rm -rf compiled-rn - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: compiled-rn path: compiled-rn/ @@ -365,7 +293,7 @@ jobs: git add . - name: Signing files if: steps.check_should_commit.outputs.should_commit == 'true' - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: script: | // TODO: Move this to a script file. @@ -456,10 +384,10 @@ jobs: uses: stefanzweifel/git-auto-commit-action@v4 with: commit_message: | - ${{ github.event.head_commit.message }} + ${{ github.event.workflow_run.head_commit.message }} - DiffTrain build for commit https://github.com/facebook/react/commit/${{ github.sha }}. + DiffTrain build for commit https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha }}. branch: builds/facebook-fbsource - commit_user_name: ${{ github.actor }} - commit_user_email: ${{ github.actor }}@users.noreply.github.com + commit_user_name: ${{ github.event.workflow_run.triggering_actor.login }} + commit_user_email: ${{ github.event.workflow_run.triggering_actor.email || format('{0}@users.noreply.github.com', github.event.workflow_run.triggering_actor.login) }} create_branch: true diff --git a/.prettierignore b/.prettierignore index 1c461d88c6..8d05ed8891 100644 --- a/.prettierignore +++ b/.prettierignore @@ -18,14 +18,24 @@ packages/react-devtools-timeline/static # react compiler compiler/**/dist compiler/**/__tests__/fixtures/**/*.expect.md -compiler/**/__tests__/fixtures/**/*.flow.js 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/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-mixed-scope-and-local-variables-with-default.js +compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-mixed-scope-declarations-and-locals.js +compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-kitchensink.js +compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-inside-logical-expression.js +compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-logical.js +compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/readonly-object-method-calls-mutable-lambda.js +compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/readonly-object-method-calls.js +compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/tagged-template-in-hook.js +compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/tagged-template-literal.js + compiler/crates compiler/apps/playground/public compiler/**/LICENSE -compiler/.* compiler/*.md* compiler/*.json compiler/*.css diff --git a/.prettierrc.js b/.prettierrc.js index 5f3e78b0b8..37cf9c9d3a 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -1,18 +1,15 @@ 'use strict'; -const { - compilerPaths, - esNextPaths, - typescriptPaths, -} = require('./scripts/shared/pathsByLanguageVersion'); +const {esNextPaths} = require('./scripts/shared/pathsByLanguageVersion'); module.exports = { + plugins: ['prettier-plugin-hermes-parser'], bracketSpacing: false, singleQuote: true, bracketSameLine: true, trailingComma: 'es5', printWidth: 80, - parser: 'flow', + parser: 'hermes', arrowParens: 'avoid', overrides: [ { @@ -28,25 +25,11 @@ module.exports = { }, }, { - files: typescriptPaths, + files: ['*.ts', '*.tsx'], options: { trailingComma: 'all', parser: 'typescript', }, }, - { - files: compilerPaths, - options: { - requirePragma: false, - parser: 'babel-ts', - semi: true, - singleQuote: false, - trailingComma: 'es5', - bracketSpacing: true, - bracketSameLine: false, - printWidth: 80, - arrowParens: 'always', - }, - }, ], }; diff --git a/compiler/apps/playground/__tests__/e2e/page.spec.ts b/compiler/apps/playground/__tests__/e2e/page.spec.ts index 3bb6637f95..bc93352a09 100644 --- a/compiler/apps/playground/__tests__/e2e/page.spec.ts +++ b/compiler/apps/playground/__tests__/e2e/page.spec.ts @@ -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 { - 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(2).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(2).allInnerTexts()) ?? []; - expect(concat(defaultInput)).toMatchSnapshot("default-input.txt"); + (await page.locator('.monaco-editor').nth(2).allInnerTexts()) ?? []; + expect(concat(defaultInput)).toMatchSnapshot('default-input.txt'); }); diff --git a/compiler/apps/playground/app/index.tsx b/compiler/apps/playground/app/index.tsx index 75411cd36e..3bbf2e9b55 100644 --- a/compiler/apps/playground/app/index.tsx +++ b/compiler/apps/playground/app/index.tsx @@ -5,25 +5,24 @@ * 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 (
- {process.env.NODE_ENV === "development" - ? "[DEV] React Compiler Playground" - : "React Compiler Playground"} + {process.env.NODE_ENV === 'development' + ? '[DEV] React Compiler Playground' + : 'React Compiler Playground'} + content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"> { + Components={{message: MessageSnackbar}}>
diff --git a/compiler/apps/playground/app/layout.tsx b/compiler/apps/playground/app/layout.tsx index be0d04e932..3e888ae955 100644 --- a/compiler/apps/playground/app/layout.tsx +++ b/compiler/apps/playground/app/layout.tsx @@ -5,26 +5,21 @@ * 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; -}) { - "use no memo"; +export default function RootLayout({children}: {children: React.ReactNode}) { + 'use no memo'; return ( - {process.env.NODE_ENV === "development" - ? "[DEV] React Compiler Playground" - : "React Compiler Playground"} + {process.env.NODE_ENV === 'development' + ? '[DEV] React Compiler Playground' + : 'React Compiler Playground'} + content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"> + Components={{message: MessageSnackbar}}>
diff --git a/compiler/apps/playground/babel.config.js b/compiler/apps/playground/babel.config.js index 900b52dd30..0edf2e9b72 100644 --- a/compiler/apps/playground/babel.config.js +++ b/compiler/apps/playground/babel.config.js @@ -8,12 +8,12 @@ module.exports = function (api) { api.cache(true); return { - presets: ["next/babel"], + presets: ['next/babel'], plugins: [ [ - "babel-plugin-react-compiler", + 'babel-plugin-react-compiler', { - runtimeModule: "react-compiler-runtime", + runtimeModule: 'react-compiler-runtime', }, ], ], diff --git a/compiler/apps/playground/colors.js b/compiler/apps/playground/colors.js index 7259e1f32b..bd5f1426f5 100644 --- a/compiler/apps/playground/colors.js +++ b/compiler/apps/playground/colors.js @@ -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', }, }; diff --git a/compiler/apps/playground/components/Editor/EditorImpl.tsx b/compiler/apps/playground/components/Editor/EditorImpl.tsx index a96672194b..ebac65dc4b 100644 --- a/compiler/apps/playground/components/Editor/EditorImpl.tsx +++ b/compiler/apps/playground/components/Editor/EditorImpl.tsx @@ -5,10 +5,10 @@ * LICENSE file in the root directory of this source tree. */ -import { parse as babelParse, ParserPlugin } from "@babel/parser"; -import * as HermesParser from "hermes-parser"; -import traverse, { NodePath } from "@babel/traverse"; -import * as t from "@babel/types"; +import {parse as babelParse, ParserPlugin} from '@babel/parser'; +import * as HermesParser from 'hermes-parser'; +import traverse, {NodePath} from '@babel/traverse'; +import * as t from '@babel/types'; import { CompilerError, CompilerErrorDetail, @@ -20,51 +20,51 @@ import { run, ValueKind, 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"; +} 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") { +function parseInput(input: string, language: 'flow' | 'typescript') { // Extract the first line to quickly check for custom test directives - if (language === "flow") { + if (language === 'flow') { return HermesParser.parse(input, { babel: true, - flow: "all", - sourceType: "module", + flow: 'all', + sourceType: 'module', enableExperimentalComponentSyntax: true, }); } else { return babelParse(input, { - plugins: ["typescript", "jsx"], - sourceType: "module", + plugins: ['typescript', 'jsx'], + sourceType: 'module', }); } } function parseFunctions( source: string, - language: "flow" | "typescript" + language: 'flow' | 'typescript', ): Array< NodePath< t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression @@ -105,7 +105,7 @@ function parseFunctions( const COMMON_HOOKS: Array<[string, Hook]> = [ [ - "useFragment", + 'useFragment', { valueKind: ValueKind.Frozen, effectKind: Effect.Freeze, @@ -114,7 +114,7 @@ const COMMON_HOOKS: Array<[string, Hook]> = [ }, ], [ - "usePaginationFragment", + 'usePaginationFragment', { valueKind: ValueKind.Frozen, effectKind: Effect.Freeze, @@ -123,7 +123,7 @@ const COMMON_HOOKS: Array<[string, Hook]> = [ }, ], [ - "useRefetchableFragment", + 'useRefetchableFragment', { valueKind: ValueKind.Frozen, effectKind: Effect.Freeze, @@ -132,7 +132,7 @@ const COMMON_HOOKS: Array<[string, Hook]> = [ }, ], [ - "useLazyLoadQuery", + 'useLazyLoadQuery', { valueKind: ValueKind.Frozen, effectKind: Effect.Freeze, @@ -141,7 +141,7 @@ const COMMON_HOOKS: Array<[string, Hook]> = [ }, ], [ - "usePreloadedQuery", + 'usePreloadedQuery', { valueKind: ValueKind.Frozen, effectKind: Effect.Freeze, @@ -156,22 +156,22 @@ function isHookName(s: string): boolean { } function getReactFunctionType( - id: NodePath + id: NodePath, ): ReactFunctionType { if (id && id.node && id.isIdentifier()) { if (isHookName(id.node.name)) { - return "Hook"; + return 'Hook'; } const isPascalCaseNameSpace = /^[A-Z].*/; if (isPascalCaseNameSpace.test(id.node.name)) { - return "Component"; + return 'Component'; } } - return "Other"; + return 'Other'; } -function compile(source: string): [CompilerOutput, "flow" | "typescript"] { +function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { const results = new Map(); const error = new CompilerError(); const upsert = (result: PrintedCompilerPipelineValue) => { @@ -182,15 +182,15 @@ function compile(source: string): [CompilerOutput, "flow" | "typescript"] { results.set(result.name, [result]); } }; - let language: "flow" | "typescript"; + let language: 'flow' | 'typescript'; if (source.match(/\@flow/)) { - language = "flow"; + language = 'flow'; } else { - language = "typescript"; + language = 'typescript'; } try { // Extract the first line to quickly check for custom test directives - const pragma = source.substring(0, source.indexOf("\n")); + const pragma = source.substring(0, source.indexOf('\n')); const config = parseConfigPragma(pragma); for (const fn of parseFunctions(source, language)) { @@ -199,16 +199,16 @@ function compile(source: string): [CompilerOutput, "flow" | "typescript"] { new CompilerErrorDetail({ reason: `Unexpected function type ${fn.node.type}`, description: - "Playground only supports parsing function declarations", + 'Playground only supports parsing function declarations', severity: ErrorSeverity.Todo, loc: fn.node.loc ?? null, suggestions: null, - }) + }), ); continue; } - const id = fn.get("id"); + const id = fn.get('id'); for (const result of run( fn, { @@ -216,20 +216,20 @@ function compile(source: string): [CompilerOutput, "flow" | "typescript"] { customHooks: new Map([...COMMON_HOOKS]), }, getReactFunctionType(id), - "_c", + '_c', + null, null, null, - null )) { 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", + type: 'FunctionDeclaration', id: result.value.id, async: result.value.async, generator: result.value.generator, @@ -239,27 +239,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), }); break; } - case "reactive": { + case 'reactive': { upsert({ - kind: "reactive", + kind: 'reactive', fnName, name: result.name, value: printReactiveFunctionWithOutlined(result.value), }); break; } - case "debug": { + case 'debug': { upsert({ - kind: "debug", + kind: 'debug', fnName, name: result.name, value: result.value, @@ -288,24 +288,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}, language]; } - return [{ kind: "ok", results }, language]; + return [{kind: 'ok', results}, language]; } export default function Editor() { const store = useStore(); const deferredStore = useDeferredValue(store); const dispatchStore = useStoreDispatch(); - const { enqueueSnackbar } = useSnackbar(); + const {enqueueSnackbar} = useSnackbar(); const [compilerOutput, language] = useMemo( () => compile(deferredStore.source), - [deferredStore.source] + [deferredStore.source], ); useMountEffect(() => { @@ -313,35 +313,35 @@ export default function Editor() { 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 ( <>
-
+
-
+
diff --git a/compiler/apps/playground/components/Editor/Input.tsx b/compiler/apps/playground/components/Editor/Input.tsx index 219f13ba23..c2ce8efc70 100644 --- a/compiler/apps/playground/components/Editor/Input.tsx +++ b/compiler/apps/playground/components/Editor/Input.tsx @@ -5,28 +5,28 @@ * 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"; +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"; +import React$Types from '../../node_modules/@types/react/index.d.ts'; -loader.config({ monaco }); +loader.config({monaco}); type Props = { errors: CompilerErrorDetail[]; - language: "flow" | "typescript"; + language: 'flow' | 'typescript'; }; -export default function Input({ errors, language }: Props) { +export default function Input({errors, language}: Props) { const [monaco, setMonaco] = useState(null); const store = useStore(); const dispatchStore = useStoreDispatch(); @@ -36,11 +36,11 @@ export default function Input({ errors, language }: Props) { 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 }); + 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 }); + model.updateOptions({tabSize: 2}); }, [monaco, errors]); const flowDiagnosticDisable = [ @@ -64,11 +64,11 @@ export default function Input({ errors, language }: Props) { 8011, 8012, 8013, - ...(language === "flow" ? flowDiagnosticDisable : []), + ...(language === 'flow' ? flowDiagnosticDisable : []), ], noSemanticValidation: true, // Monaco can't validate Flow component syntax - noSyntaxValidation: language === "flow", + noSyntaxValidation: language === 'flow', }); }, [monaco, language]); @@ -76,7 +76,7 @@ export default function Input({ errors, language }: Props) { if (!value) return; dispatchStore({ - type: "updateFile", + type: 'updateFile', payload: { source: value, }, @@ -91,11 +91,11 @@ export default function Input({ errors, language }: Props) { 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, @@ -106,7 +106,7 @@ export default function Input({ errors, language }: Props) { // 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); @@ -124,17 +124,16 @@ export default function Input({ errors, language }: Props) {
+ className="!h-[calc(100vh_-_3.5rem)]"> } + | {kind: 'ok'; results: Map} | { - kind: "err"; + kind: 'err'; results: Map; error: CompilerError; }; @@ -63,7 +63,7 @@ async function tabify(source: string, compilerOutput: CompilerOutput) { 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}`; @@ -74,7 +74,7 @@ async function tabify(source: string, compilerOutput: CompilerOutput) { } break; } - case "reactive": { + case 'reactive': { const prev = concattedResults.get(passName); const next = result.value; if (prev != null) { @@ -84,30 +84,29 @@ async function tabify(source: string, compilerOutput: CompilerOutput) { } 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"]; + let nonDiffPasses = ['HIR', 'BuildReactiveFunction', 'EnvironmentConfig']; for (const [passName, text] of concattedResults) { tabs.set( passName, + showInfoPanel={!nonDiffPasses.includes(passName)}>, ); lastPassOutput = text; } @@ -116,25 +115,24 @@ async function tabify(source: string, compilerOutput: CompilerOutput) { // 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', + showInfoPanel={false}>, ); if (sourceMapUrl) { reorderedTabs.set( - "SourceMap", + 'SourceMap', <>