diff --git a/.ado/jobs/npm-publish.yml b/.ado/jobs/npm-publish.yml index 7da6c6029a7..1256c706ad8 100644 --- a/.ado/jobs/npm-publish.yml +++ b/.ado/jobs/npm-publish.yml @@ -1,64 +1,64 @@ jobs: -- job: NPMPublish - displayName: NPM Publish - pool: - name: cxeiss-ubuntu-20-04-large - image: cxe-ubuntu-20-04-1es-pt - os: linux - variables: - - name: BUILDSECMON_OPT_IN - value: true + - job: NPMPublish + displayName: NPM Publish + pool: + name: cxeiss-ubuntu-20-04-large + image: cxe-ubuntu-20-04-1es-pt + os: linux + variables: + - name: BUILDSECMON_OPT_IN + value: true - timeoutInMinutes: 90 - cancelTimeoutInMinutes: 5 - templateContext: - outputs: - - output: pipelineArtifact - targetPath: $(System.DefaultWorkingDirectory) - artifactName: github-npm-js-publish - steps: - - checkout: self - clean: true - fetchFilter: blob:none - persistCredentials: true + timeoutInMinutes: 90 + cancelTimeoutInMinutes: 5 + templateContext: + outputs: + - output: pipelineArtifact + targetPath: $(System.DefaultWorkingDirectory) + artifactName: github-npm-js-publish + steps: + - checkout: self + clean: true + fetchFilter: blob:none + persistCredentials: true - - task: UseNode@1 - inputs: - version: '22.22.0' - displayName: 'Use Node.js 22.22.0' + - task: UseNode@1 + inputs: + version: '22.22.0' + displayName: 'Use Node.js 22.22.0' - - template: /.ado/templates/configure-git.yml@self + - template: /.ado/templates/configure-git.yml@self - - script: | - yarn install - displayName: Install npm dependencies - - - script: | - node .ado/scripts/configure-publish.mts --verbose --skip-auth - displayName: Verify release config - - # Disable Nightly publishing on the main branch - - ${{ if endsWith(variables['Build.SourceBranchName'], '-stable') }}: - script: | - yarn config set npmPublishAccess public - yarn config set npmPublishRegistry "https://registry.npmjs.org" - yarn config set npmAuthToken $(npmAuthToken) - displayName: Configure yarn for npm publishing - condition: and(succeeded(), eq(variables['publish_react_native_macos'], '1')) + yarn install + displayName: Install npm dependencies - script: | - yarn workspaces foreach -vv --all --topological --no-private npm publish --tag $(publishTag) --tolerate-republish - displayName: Publish packages - condition: and(succeeded(), eq(variables['publish_react_native_macos'], '1')) + node .ado/scripts/configure-publish.mts --verbose --skip-auth + displayName: Verify release config - - script: | - node .ado/scripts/apply-additional-tags.mjs --tags "$(additionalTags)" --token "$(npmAuthToken)" - displayName: Apply additional dist-tags - condition: and(succeeded(), eq(variables['publish_react_native_macos'], '1')) + # Disable Nightly publishing on the main branch + - ${{ if endsWith(variables['Build.SourceBranchName'], '-stable') }}: + - script: | + yarn config set npmPublishAccess public + yarn config set npmPublishRegistry "https://registry.npmjs.org" + yarn config set npmAuthToken $(npmAuthToken) + displayName: Configure yarn for npm publishing + condition: and(succeeded(), eq(variables['publish_react_native_macos'], '1')) - - script: | - yarn config unset npmPublishAccess || true - yarn config unset npmAuthToken || true - yarn config unset npmPublishRegistry || true - displayName: Remove NPM auth configuration - condition: always() + - script: | + yarn workspaces foreach -vv --all --topological --no-private npm publish --tag $(publishTag) --tolerate-republish + displayName: Publish packages + condition: and(succeeded(), eq(variables['publish_react_native_macos'], '1')) + + - script: | + node .ado/scripts/apply-additional-tags.mjs --tags "$(additionalTags)" --token "$(npmAuthToken)" + displayName: Apply additional dist-tags + condition: and(succeeded(), eq(variables['publish_react_native_macos'], '1')) + + - script: | + yarn config unset npmPublishAccess || true + yarn config unset npmAuthToken || true + yarn config unset npmPublishRegistry || true + displayName: Remove NPM auth configuration + condition: always() diff --git a/.ado/publish.yml b/.ado/publish.yml index 090cd21cd91..0bbfb9b8604 100644 --- a/.ado/publish.yml +++ b/.ado/publish.yml @@ -16,7 +16,8 @@ variables: - group: React-native-macos Secrets - group: InfoSec-SecurityResults - name: tags - value: production,externalfacing + value: + production,externalfacing # CodeQL Still has not fixed their bug running on Apple ARM64 jobs where they inject x64 binaries into arm64 processes and just make it crash :( # Only workaround for now is to disable CodeQL on Apple jobs. - name: Codeql.Enabled @@ -52,4 +53,4 @@ extends: - stage: NPM dependsOn: [] jobs: - - template: /.ado/jobs/npm-publish.yml@self + - template: /.ado/jobs/npm-publish.yml@self diff --git a/.config/1espt/PipelineAutobaseliningConfig.yml b/.config/1espt/PipelineAutobaseliningConfig.yml index 6601c710049..ad40ba0aade 100644 --- a/.config/1espt/PipelineAutobaseliningConfig.yml +++ b/.config/1espt/PipelineAutobaseliningConfig.yml @@ -1,5 +1,5 @@ -## DO NOT MODIFY THIS FILE MANUALLY. This is part of auto-baselining from 1ES Pipeline Templates. Go to [https://aka.ms/1espt-autobaselining] for more details. - +## DO NOT MODIFY THIS FILE MANUALLY. This is part of auto-baselining from 1ES Pipeline Templates. Go to [https://aka.ms/1espt-autobaselining] for more details. + pipelines: 18541: retail: diff --git a/.eslintrc.js b/.eslintrc.js index 82ef6ae75f7..4ef0585b767 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -50,7 +50,7 @@ module.exports = { files: [ './packages/react-native/Libraries/**/*.{js,flow}', './packages/react-native/src/**/*.{js,flow}', - './packages/assets/registry.js', + './packages/assets-registry/registry.js', ], parser: 'hermes-eslint', rules: { diff --git a/.flowconfig b/.flowconfig index 43156e99048..907a4d5bb98 100644 --- a/.flowconfig +++ b/.flowconfig @@ -39,19 +39,11 @@ [libs] flow-typed/ -packages/react-native/interface.js packages/react-native/flow/ [options] -enums=true -experimental.pattern_matching=true -casting_syntax=both -component_syntax=true - emoji=true -exact_by_default=true - format.bracket_spacing=false module.file_ext=.js @@ -66,10 +58,10 @@ munge_underscores=true module.name_mapper='^react-native$' -> '/packages/react-native/index.js' module.name_mapper='^react-native/\(.*\)$' -> '/packages/react-native/\1' module.name_mapper='^@react-native/dev-middleware$' -> '/packages/dev-middleware' -; [macOS] Resolve workspace self-references (incl. @react-native/fantom) in yarn pnpm mode +; [macOS Resolve workspace self-references (incl. @react-native/fantom) in yarn pnpm mode module.name_mapper='^@react-native/fantom$' -> '/private/react-native-fantom/src/index.js' module.name_mapper='^@react-native/fantom/\(.*\)$' -> '/private/react-native-fantom/\1' -module.name_mapper='^@react-native/core-cli-utils$' -> '/packages/core-cli-utils/src/index.flow.js' +module.name_mapper='^@react-native/core-cli-utils$' -> '/private/core-cli-utils/src/index.js' ; macOS] module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\|xml\|ktx\|heic\|heif\)$' -> '/packages/react-native/Libraries/Image/RelativeImageStub' @@ -100,4 +92,4 @@ untyped-import untyped-type-import [version] -^0.312.0 +^0.319.0 diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 2049a6d802b..e275b2752f8 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,10 +1,10 @@ name: 🐛 React Native - Bug Report description: Report a reproducible bug or regression in React Native. -labels: ["Needs: Triage :mag:"] +labels: ['Needs: Triage :mag:'] body: - type: markdown attributes: - value: "## Reporting a bug to React Native" + value: '## Reporting a bug to React Native' - type: markdown attributes: value: | @@ -46,7 +46,7 @@ body: attributes: label: React Native Version description: The version of react-native that this issue reproduces on. Bear in mind that only issues on [supported versions](https://github.com/reactwg/react-native-releases#which-versions-are-currently-supported) will be looked into. - placeholder: "0.73.0" + placeholder: '0.73.0' validations: required: true - type: dropdown @@ -111,7 +111,7 @@ body: attributes: label: MANDATORY Reproducer description: A link to either a failing RNTesterPlayground.js file, an Expo Snack or a public repository from [this template](https://github.com/react-native-community/reproducer-react-native) that reproduces this bug. Reproducers are **mandatory**, issues without a reproducer will be closed. - placeholder: "https://github.com//" + placeholder: 'https://github.com//' validations: required: true - type: textarea diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index c3635293f8f..028a61bae30 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,26 +1,26 @@ blank_issues_enabled: true contact_links: - - name: ⬆️ Upgrade - Build Regression - url: https://github.com/reactwg/react-native-releases/issues/new/choose - about: | - If you are upgrading to a new React Native version (stable or pre-release) and encounter a build regression. - - name: 🚀 Expo Issue - url: https://github.com/expo/expo/issues/new - about: | - If you're using Expo in your project, please report the issue first in the Expo issue tracker. - - name: 📃 Documentation Issue - url: https://github.com/facebook/react-native-website/issues - about: Please report documentation issues in the React Native website repository. - - name: 📦 Metro Issue - url: https://github.com/facebook/metro/issues/new - about: | - If you've encountered a module resolution problem, e.g. "Error: Unable to resolve module ...", or something else that might be related to Metro, please open an issue in the Metro repo instead. - - name: 🤔 Questions and Help - url: https://reactnative.dev/help - about: Looking for help with your app? Please refer to the React Native community's support resources. - - name: 💫 New Architecture - Questions & Technical Deep dive insights - url: https://github.com/reactwg/react-native-new-architecture - about: Questions and doubts related to technical questions for the New Architecture should be directed to the Working Group. Instructions on how to join are available in the README. - - name: 🚀 Discussions and Proposals - url: https://github.com/react-native-community/discussions-and-proposals - about: Discuss the future of React Native in the React Native community's discussions and proposals repository. + - name: ⬆️ Upgrade - Build Regression + url: https://github.com/reactwg/react-native-releases/issues/new/choose + about: | + If you are upgrading to a new React Native version (stable or pre-release) and encounter a build regression. + - name: 🚀 Expo Issue + url: https://github.com/expo/expo/issues/new + about: | + If you're using Expo in your project, please report the issue first in the Expo issue tracker. + - name: 📃 Documentation Issue + url: https://github.com/facebook/react-native-website/issues + about: Please report documentation issues in the React Native website repository. + - name: 📦 Metro Issue + url: https://github.com/facebook/metro/issues/new + about: | + If you've encountered a module resolution problem, e.g. "Error: Unable to resolve module ...", or something else that might be related to Metro, please open an issue in the Metro repo instead. + - name: 🤔 Questions and Help + url: https://reactnative.dev/help + about: Looking for help with your app? Please refer to the React Native community's support resources. + - name: 💫 New Architecture - Questions & Technical Deep dive insights + url: https://github.com/reactwg/react-native-new-architecture + about: Questions and doubts related to technical questions for the New Architecture should be directed to the Working Group. Instructions on how to join are available in the README. + - name: 🚀 Discussions and Proposals + url: https://github.com/react-native-community/discussions-and-proposals + about: Discuss the future of React Native in the React Native community's discussions and proposals repository. diff --git a/.github/ISSUE_TEMPLATE/debugger_bug_report.yml b/.github/ISSUE_TEMPLATE/debugger_bug_report.yml index 1d7200d2a61..531765d6568 100644 --- a/.github/ISSUE_TEMPLATE/debugger_bug_report.yml +++ b/.github/ISSUE_TEMPLATE/debugger_bug_report.yml @@ -1,11 +1,11 @@ name: 🔍 Debugger - Bug Report description: Report a bug with React Native DevTools and the New Debugger -labels: ["Needs: Triage :mag:", "Debugging"] +labels: ['Needs: Triage :mag:', 'Debugging'] body: - type: markdown attributes: - value: "## Reporting a bug for React Native DevTools" + value: '## Reporting a bug for React Native DevTools' - type: markdown attributes: value: | @@ -42,7 +42,7 @@ body: attributes: label: React Native Version description: The version of react-native that this issue reproduces on. Bear in mind that only issues on [supported versions](https://github.com/reactwg/react-native-releases#which-versions-are-currently-supported) will be looked into. - placeholder: "0.76.0" + placeholder: '0.76.0' validations: required: true - type: textarea diff --git a/.github/ISSUE_TEMPLATE/new_architecture_bug_report.yml b/.github/ISSUE_TEMPLATE/new_architecture_bug_report.yml index 4f4936bb2dc..b56d5cbc010 100644 --- a/.github/ISSUE_TEMPLATE/new_architecture_bug_report.yml +++ b/.github/ISSUE_TEMPLATE/new_architecture_bug_report.yml @@ -1,11 +1,11 @@ name: 💫 New Architecture - Bug Report description: Report a reproducible bug or a build issue when using the New Architecture (Fabric & TurboModules) in React Native. -labels: ["Needs: Triage :mag:", "Type: New Architecture"] +labels: ['Needs: Triage :mag:', 'Type: New Architecture'] body: - type: markdown attributes: - value: "## New Architecture Related Bugs" + value: '## New Architecture Related Bugs' - type: markdown attributes: value: | @@ -43,7 +43,7 @@ body: attributes: label: React Native Version description: The version of react-native that this issue reproduces on. Bear in mind that only issues on [supported versions](https://github.com/reactwg/react-native-releases#which-versions-are-currently-supported) will be looked into. - placeholder: "0.73.0" + placeholder: '0.73.0' validations: required: true - type: dropdown @@ -123,7 +123,7 @@ body: attributes: label: MANDATORY Reproducer description: A link to either a failing RNTesterPlayground.js file, an Expo Snack or a public repository from [this template](https://github.com/react-native-community/reproducer-react-native) that reproduces this bug. Reproducers are **mandatory**, issues without a reproducer will be closed. - placeholder: "https://github.com//" + placeholder: 'https://github.com//' validations: required: true - type: textarea diff --git a/.github/actions/build-android/action.yml b/.github/actions/build-android/action.yml index 3466b817a38..42f12de540e 100644 --- a/.github/actions/build-android/action.yml +++ b/.github/actions/build-android/action.yml @@ -5,7 +5,7 @@ inputs: required: true description: The type of release we are building. It could be nightly, release or dry-run gradle-cache-encryption-key: - description: "The encryption key needed to store the Gradle Configuration cache" + description: 'The encryption key needed to store the Gradle Configuration cache' runs: using: composite steps: @@ -34,7 +34,7 @@ runs: - name: Setup gradle uses: ./.github/actions/setup-gradle with: - cache-read-only: "false" + cache-read-only: 'false' cache-encryption-key: ${{ inputs.gradle-cache-encryption-key }} - name: Restore Android ccache uses: actions/cache/restore@v5 diff --git a/.github/actions/build-fantom-runner/action.yml b/.github/actions/build-fantom-runner/action.yml index 00ea1d63d2d..c4b34c32fbe 100644 --- a/.github/actions/build-fantom-runner/action.yml +++ b/.github/actions/build-fantom-runner/action.yml @@ -4,7 +4,7 @@ inputs: required: true description: The type of release we are building. It could be nightly, release or dry-run gradle-cache-encryption-key: - description: "The encryption key needed to store the Gradle Configuration cache" + description: 'The encryption key needed to store the Gradle Configuration cache' runs: using: composite @@ -24,22 +24,22 @@ runs: - name: Setup gradle uses: ./.github/actions/setup-gradle with: - cache-read-only: "false" + cache-read-only: 'false' cache-encryption-key: ${{ inputs.gradle-cache-encryption-key }} - name: Restore Fantom ccache uses: actions/cache/restore@v5 with: path: /github/home/.cache/ccache key: v2-ccache-fantom-${{ github.job }}-${{ github.ref }}-${{ hashFiles( - 'packages/react-native/ReactAndroid/**/*.cpp', - 'packages/react-native/ReactAndroid/**/*.h', - 'packages/react-native/ReactAndroid/**/CMakeLists.txt', - 'packages/react-native/ReactCommon/**/*.cpp', - 'packages/react-native/ReactCommon/**/*.h', - 'packages/react-native/ReactCommon/**/CMakeLists.txt', - 'private/react-native-fantom/tester/**/*.cpp', - 'private/react-native-fantom/tester/**/*.h', - 'private/react-native-fantom/tester/**/CMakeLists.txt' + 'packages/react-native/ReactAndroid/**/*.cpp', + 'packages/react-native/ReactAndroid/**/*.h', + 'packages/react-native/ReactAndroid/**/CMakeLists.txt', + 'packages/react-native/ReactCommon/**/*.cpp', + 'packages/react-native/ReactCommon/**/*.h', + 'packages/react-native/ReactCommon/**/CMakeLists.txt', + 'private/react-native-fantom/tester/**/*.cpp', + 'private/react-native-fantom/tester/**/*.h', + 'private/react-native-fantom/tester/**/CMakeLists.txt' ) }} restore-keys: | v2-ccache-fantom-${{ github.job }}-${{ github.ref }}- @@ -60,15 +60,15 @@ runs: with: path: /github/home/.cache/ccache key: v2-ccache-fantom-${{ github.job }}-${{ github.ref }}-${{ hashFiles( - 'packages/react-native/ReactAndroid/**/*.cpp', - 'packages/react-native/ReactAndroid/**/*.h', - 'packages/react-native/ReactAndroid/**/CMakeLists.txt', - 'packages/react-native/ReactCommon/**/*.cpp', - 'packages/react-native/ReactCommon/**/*.h', - 'packages/react-native/ReactCommon/**/CMakeLists.txt', - 'private/react-native-fantom/tester/**/*.cpp', - 'private/react-native-fantom/tester/**/*.h', - 'private/react-native-fantom/tester/**/CMakeLists.txt' + 'packages/react-native/ReactAndroid/**/*.cpp', + 'packages/react-native/ReactAndroid/**/*.h', + 'packages/react-native/ReactAndroid/**/CMakeLists.txt', + 'packages/react-native/ReactCommon/**/*.cpp', + 'packages/react-native/ReactCommon/**/*.h', + 'packages/react-native/ReactCommon/**/CMakeLists.txt', + 'private/react-native-fantom/tester/**/*.cpp', + 'private/react-native-fantom/tester/**/*.h', + 'private/react-native-fantom/tester/**/CMakeLists.txt' ) }} - name: Show ccache stats (after) shell: bash diff --git a/.github/actions/build-npm-package/action.yml b/.github/actions/build-npm-package/action.yml index 6bacb3d29dd..2c5dd61a7d6 100644 --- a/.github/actions/build-npm-package/action.yml +++ b/.github/actions/build-npm-package/action.yml @@ -4,12 +4,12 @@ inputs: release-type: required: true description: The type of release we are building. It could be nightly, release or dry-run - gha-npm-token: - required: false - description: The GHA npm token, required only to publish to npm - default: '' gradle-cache-encryption-key: description: The encryption key needed to store the Gradle Configuration cache + skip-apple-prebuilts: + description: When true, skip downloading prebuilt Apple artifacts (use when Apple prebuild jobs were skipped) + required: false + default: 'false' runs: using: composite @@ -18,18 +18,21 @@ runs: shell: bash run: git config --global --add safe.directory '*' - name: Download ReactNativeDependencies + if: ${{ inputs.skip-apple-prebuilts != 'true' }} uses: actions/download-artifact@v7 with: pattern: ReactNativeDependencies* path: ./packages/react-native/ReactAndroid/external-artifacts/artifacts merge-multiple: true - name: Download ReactCore artifacts + if: ${{ inputs.skip-apple-prebuilts != 'true' }} uses: actions/download-artifact@v7 with: pattern: ReactCore* path: ./packages/react-native/ReactAndroid/external-artifacts/artifacts merge-multiple: true - name: Print Artifacts Directory + if: ${{ inputs.skip-apple-prebuilts != 'true' }} shell: bash run: ls -lR ./packages/react-native/ReactAndroid/external-artifacts/artifacts/ - name: Setup gradle @@ -38,6 +41,9 @@ runs: cache-encryption-key: ${{ inputs.gradle-cache-encryption-key }} - name: Setup node.js uses: ./.github/actions/setup-node + with: + node-version: '24' + registry-url: 'https://registry.npmjs.org' - name: Install dependencies uses: ./.github/actions/yarn-install - name: Build packages @@ -46,12 +52,9 @@ runs: - name: Build types shell: bash run: yarn build-types --skip-snapshot - # Continue with publish steps - - name: Set npm credentials - if: ${{ inputs.release-type == 'release' || - inputs.release-type == 'nightly' }} - shell: bash - run: echo "//registry.npmjs.org/:_authToken=${{ inputs.gha-npm-token }}" > ~/.npmrc + # `npm publish` below authenticates via npm Trusted Publishing (OIDC). + # The caller (the reusable `publish-npm.yml` workflow) MUST grant + # `id-token: write`; this composite action runs inside that job. - name: Publish NPM shell: bash run: | diff --git a/.github/actions/create-release/action.yml b/.github/actions/create-release/action.yml index 1d26e17e8fc..f0315eab8af 100644 --- a/.github/actions/create-release/action.yml +++ b/.github/actions/create-release/action.yml @@ -2,15 +2,15 @@ name: create_release description: Creates a new React Native release inputs: version: - description: "The version of React Native we want to release. For example 0.75.0-rc.0" + description: 'The version of React Native we want to release. For example 0.75.0-rc.0' required: true is-latest-on-npm: - description: "Whether we want to tag this release as latest on NPM" + description: 'Whether we want to tag this release as latest on NPM' required: true - default: "false" + default: 'false' dry-run: - description: "Whether the job should be executed in dry-run mode or not" - default: "true" + description: 'Whether the job should be executed in dry-run mode or not' + default: 'true' runs: using: composite steps: diff --git a/.github/actions/diff-js-api-changes/action.yml b/.github/actions/diff-js-api-changes/action.yml index cd6a780505e..fc2b2747cd9 100644 --- a/.github/actions/diff-js-api-changes/action.yml +++ b/.github/actions/diff-js-api-changes/action.yml @@ -7,30 +7,51 @@ outputs: runs: using: composite steps: - - name: Fetch PR and main, compute merge base - id: merge_base - shell: bash - env: - PR_SHA: ${{ github.event.pull_request.head.sha }} - run: | - git fetch origin main - git fetch origin "$PR_SHA" --depth=500 - echo "merge_base=$(git merge-base "$PR_SHA" origin/main)" >> $GITHUB_OUTPUT + - name: Resolve API diff inputs + id: api_diff + uses: actions/github-script@v8 + with: + script: | + // Check if we are targeting main or a stable branch + const baseRef = context.payload.pull_request.base.ref; + if (baseRef !== 'main' && !baseRef.endsWith('-stable')) { + core.setOutput('changed', false); + return; + } + + // Check if the diff touches ReactNativeApi.d.ts + const files = await github.paginate(github.rest.pulls.listFiles, { + ...context.repo, + pull_number: context.payload.pull_request.number, + }); + const changed = files.some(f => f.filename === 'packages/react-native/ReactNativeApi.d.ts'); + core.setOutput('changed', changed); + if (!changed) return; + + // Calculate merge base + const {data} = await github.rest.repos.compareCommits({ + ...context.repo, + base: context.payload.pull_request.base.ref, + head: context.payload.pull_request.head.sha, + }); + core.setOutput('merge_base', data.merge_base_commit.sha); - - name: Extract before and after API snapshots + - name: Fetch commits and extract API snapshots + if: steps.api_diff.outputs.changed == 'true' shell: bash env: SCRATCH_DIR: ${{ runner.temp }}/diff-js-api-changes - MERGE_BASE: ${{ steps.merge_base.outputs.merge_base }} - PR_SHA: ${{ github.event.pull_request.head.sha }} run: | mkdir -p $SCRATCH_DIR - git show "$MERGE_BASE":packages/react-native/ReactNativeApi.d.ts > $SCRATCH_DIR/ReactNativeApi-before.d.ts \ + git fetch origin "${{ steps.api_diff.outputs.merge_base }}" --depth=1 + git fetch origin "${{ github.event.pull_request.head.sha }}" --depth=1 + git show "${{ steps.api_diff.outputs.merge_base }}":packages/react-native/ReactNativeApi.d.ts > $SCRATCH_DIR/ReactNativeApi-before.d.ts \ || echo "" > $SCRATCH_DIR/ReactNativeApi-before.d.ts - git show "$PR_SHA":packages/react-native/ReactNativeApi.d.ts > $SCRATCH_DIR/ReactNativeApi-after.d.ts \ + git show "${{ github.event.pull_request.head.sha }}":packages/react-native/ReactNativeApi.d.ts > $SCRATCH_DIR/ReactNativeApi-after.d.ts \ || echo "" > $SCRATCH_DIR/ReactNativeApi-after.d.ts - name: Run breaking change detection + if: steps.api_diff.outputs.changed == 'true' shell: bash env: SCRATCH_DIR: ${{ runner.temp }}/diff-js-api-changes diff --git a/.github/actions/maestro-android/action.yml b/.github/actions/maestro-android/action.yml index cbc5ef1b96b..fde5cec3e66 100644 --- a/.github/actions/maestro-android/action.yml +++ b/.github/actions/maestro-android/action.yml @@ -20,7 +20,7 @@ inputs: default: release working-directory: required: false - default: "." + default: '.' description: The directory from which metro should be started emulator-arch: required: false @@ -32,7 +32,7 @@ runs: steps: - name: Installing Maestro shell: bash - run: export MAESTRO_VERSION=1.40.0; curl -Ls "https://get.maestro.mobile.dev" | bash + run: export MAESTRO_VERSION=2.6.1; curl -Ls "https://get.maestro.mobile.dev" | bash - name: Set up JDK 17 if: ${{ inputs.install-java == 'true' }} uses: actions/setup-java@v5 diff --git a/.github/actions/maestro-ios/action.yml b/.github/actions/maestro-ios/action.yml index 4720d1ad5d8..6ebc7327fb0 100644 --- a/.github/actions/maestro-ios/action.yml +++ b/.github/actions/maestro-ios/action.yml @@ -16,7 +16,7 @@ inputs: default: Release working-directory: required: false - default: "." + default: '.' description: The directory from which metro should be started runs: @@ -24,7 +24,7 @@ runs: steps: - name: Installing Maestro shell: bash - run: export MAESTRO_VERSION=1.40.0; curl -Ls "https://get.maestro.mobile.dev" | bash + run: export MAESTRO_VERSION=2.6.1; curl -Ls "https://get.maestro.mobile.dev" | bash - name: Installing Maestro dependencies shell: bash run: | diff --git a/.github/actions/microsoft-setup-toolchain/action.yml b/.github/actions/microsoft-setup-toolchain/action.yml index d6fc8ead611..34be668689a 100644 --- a/.github/actions/microsoft-setup-toolchain/action.yml +++ b/.github/actions/microsoft-setup-toolchain/action.yml @@ -12,16 +12,16 @@ inputs: default: yarn java-version: description: Desired Java version - default: "17" + default: '17' node-version: description: Desired Node version - default: "22" + default: '22' xcode-developer-dir: description: Set the path for the active Xcode developer directory - default: "/Applications/Xcode_26.2.app" + default: '/Applications/Xcode_26.2.app' cmake-version: description: CMake version to install. Set to 'system' to skip installation and use the runner's pre-installed cmake. - default: "3.31.9" + default: '3.31.9' runs: using: composite steps: diff --git a/.github/actions/prepare-hermes-v1-app/action.yml b/.github/actions/prepare-hermes-v1-app/action.yml deleted file mode 100644 index 24f40d0fbac..00000000000 --- a/.github/actions/prepare-hermes-v1-app/action.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: prepare-hermes-v1-app -description: Prepares a React Native app with Hermes V1 enabled -inputs: - retry-count: - description: 'Number of times to retry the yarn install on failure' -runs: - using: composite - steps: - - name: Create new app - shell: bash - run: | - cd /tmp - npx @react-native-community/cli init RNApp --skip-install --version nightly - - - name: Select latest Hermes V1 version - shell: bash - run: | - node "$GITHUB_WORKSPACE/.github/workflow-scripts/selectLatestHermesV1Version.js" - - - name: Apply patch to enable Hermes V1 - shell: bash - run: | - cd /tmp/RNApp - git apply --binary --3way --whitespace=nowarn "$GITHUB_WORKSPACE/.github/workflow-scripts/hermes-v1.patch" - echo "✅ Patch applied successfully" - - - name: Install app dependencies with retry - uses: nick-fields/retry@v3 - with: - timeout_minutes: 10 - max_attempts: ${{ inputs.retry-count }} - retry_wait_seconds: 15 - shell: bash - command: | - cd /tmp/RNApp - yarn install - on_retry_command: | - echo "Cleaning up for yarn retry..." - cd /tmp/RNApp - rm -rf node_modules yarn.lock || true - yarn cache clean || true diff --git a/.github/actions/prepare-ios-tests/action.yml b/.github/actions/prepare-ios-tests/action.yml index 6465f0db99b..d5225308f3e 100644 --- a/.github/actions/prepare-ios-tests/action.yml +++ b/.github/actions/prepare-ios-tests/action.yml @@ -11,7 +11,7 @@ runs: - name: Boot iPhone Simulator shell: bash run: source scripts/.tests.env && xcrun simctl boot "$IOS_DEVICE" || true - - name: "Brew: Tap wix/brew" + - name: 'Brew: Tap wix/brew' shell: bash run: brew tap wix/brew - name: brew install applesimutils watchman diff --git a/.github/actions/setup-gradle/action.yml b/.github/actions/setup-gradle/action.yml index 6228715b65f..77a6e24cd65 100644 --- a/.github/actions/setup-gradle/action.yml +++ b/.github/actions/setup-gradle/action.yml @@ -1,13 +1,13 @@ name: Setup gradle -description: "Set up your GitHub Actions workflow with a specific version of gradle" +description: 'Set up your GitHub Actions workflow with a specific version of gradle' inputs: cache-read-only: description: "Whether the Gradle Cache should be in read-only mode so this job won't be allowed to write to it" - default: "true" + default: 'true' cache-encryption-key: - description: "The encryption key needed to store the Gradle Configuration cache" + description: 'The encryption key needed to store the Gradle Configuration cache' runs: - using: "composite" + using: 'composite' steps: - name: Setup gradle uses: gradle/actions/setup-gradle@v4 diff --git a/.github/actions/setup-node/action.yml b/.github/actions/setup-node/action.yml index 7b5aa82470a..49b773d4cef 100644 --- a/.github/actions/setup-node/action.yml +++ b/.github/actions/setup-node/action.yml @@ -5,11 +5,19 @@ inputs: description: 'The node.js version to use' required: false default: '22.14.0' + registry-url: + description: | + Optional npm registry URL passed through to actions/setup-node. Set on + jobs that publish to npm so setup-node writes a `.npmrc` configured to + pick up the OIDC-minted token from npm Trusted Publishing. + required: false + default: '' runs: - using: "composite" + using: 'composite' steps: - name: Setup node.js uses: actions/setup-node@v6 with: node-version: ${{ inputs.node-version }} cache: yarn + registry-url: ${{ inputs.registry-url }} diff --git a/.github/actions/setup-xcode/action.yml b/.github/actions/setup-xcode/action.yml index 19e3f3e6f06..85223692bc8 100644 --- a/.github/actions/setup-xcode/action.yml +++ b/.github/actions/setup-xcode/action.yml @@ -6,7 +6,7 @@ inputs: required: false default: '16.4.0' runs: - using: "composite" + using: 'composite' steps: - name: Setup xcode uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd diff --git a/.github/actions/test-ios-helloworld/action.yml b/.github/actions/test-ios-helloworld/action.yml index 66d4ee84791..b325a061d92 100644 --- a/.github/actions/test-ios-helloworld/action.yml +++ b/.github/actions/test-ios-helloworld/action.yml @@ -23,10 +23,10 @@ runs: uses: ruby/setup-ruby@v1 with: ruby-version: ${{ inputs.ruby-version }} - - name: Set nightly Hermes versions + - name: Set Hermes prebuilt version shell: bash run: | - node ./scripts/releases/use-hermes-nightly.js + node ./scripts/releases/use-hermes-prebuilt.js - name: Run yarn install again, with the correct hermes version uses: ./.github/actions/yarn-install - name: Download ReactNativeDependencies diff --git a/.github/actions/test-ios-rntester/action.yml b/.github/actions/test-ios-rntester/action.yml index 3d6d83b3b6e..fbb2d7474c3 100644 --- a/.github/actions/test-ios-rntester/action.yml +++ b/.github/actions/test-ios-rntester/action.yml @@ -32,10 +32,10 @@ runs: uses: ruby/setup-ruby@v1 with: ruby-version: ${{ inputs.ruby-version }} - - name: Set nightly Hermes versions + - name: Set Hermes prebuilt version shell: bash run: | - node ./scripts/releases/use-hermes-nightly.js + node ./scripts/releases/use-hermes-prebuilt.js - name: Run yarn install again, with the correct hermes version uses: ./.github/actions/yarn-install - name: Prepare IOS Tests @@ -92,7 +92,7 @@ runs: echo "App found at $APP_PATH" echo "app-path=$APP_PATH" >> $GITHUB_ENV - - name: "Run Tests: iOS Unit and Integration Tests" + - name: 'Run Tests: iOS Unit and Integration Tests' if: ${{ inputs.run-unit-tests == 'true' }} shell: bash run: yarn test-ios diff --git a/.github/actions/test-js/action.yml b/.github/actions/test-js/action.yml index 2b5c97a009a..653a7ee9592 100644 --- a/.github/actions/test-js/action.yml +++ b/.github/actions/test-js/action.yml @@ -2,9 +2,9 @@ name: test-js description: Runs all the JS tests in the codebase inputs: node-version: - description: "The node.js version to use" + description: 'The node.js version to use' required: false - default: "22" + default: '22' runs: using: composite steps: diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 977afda64c3..c4180ab0604 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,34 +1,34 @@ version: 2 updates: - - package-ecosystem: "npm" - directory: "/docsite" + - package-ecosystem: 'npm' + directory: '/docsite' schedule: - interval: "weekly" + interval: 'weekly' groups: all: patterns: - - "*" + - '*' # Only monitor packages we control — forked @react-native/* packages # should stay in sync via upstream merges. - - package-ecosystem: "npm" + - package-ecosystem: 'npm' directories: - - "/packages/react-native" - - "/packages/react-native-macos-init" - - "/packages/nx-release-version" - - "/packages/virtualized-lists" + - '/packages/react-native' + - '/packages/react-native-macos-init' + - '/packages/nx-release-version' + - '/packages/virtualized-lists' schedule: - interval: "weekly" + interval: 'weekly' groups: all: patterns: - - "*" + - '*' - - package-ecosystem: "github-actions" - directory: "/" + - package-ecosystem: 'github-actions' + directory: '/' schedule: - interval: "weekly" + interval: 'weekly' groups: all: patterns: - - "*" + - '*' diff --git a/.github/policies/resourceManagement.yml b/.github/policies/resourceManagement.yml index 0451b38574b..afc8da8c954 100644 --- a/.github/policies/resourceManagement.yml +++ b/.github/policies/resourceManagement.yml @@ -1,240 +1,240 @@ -id: -name: GitOps.PullRequestIssueManagement -description: GitOps.PullRequestIssueManagement primitive -owner: -resource: repository -disabled: false -where: -configuration: - resourceManagementConfiguration: - scheduledSearches: - - description: - frequencies: - - hourly: - hour: 3 - filters: - - isPullRequest - - isOpen - - hasLabel: - label: 'Needs: Author Feedback' - - hasLabel: - label: no-recent-activity - - noActivitySince: - days: 7 - actions: - - closeIssue - - description: - frequencies: - - hourly: - hour: 3 - filters: - - isPullRequest - - isOpen - - hasLabel: - label: 'Needs: Author Feedback' - - noActivitySince: - days: 7 - - isNotLabeledWith: - label: no-recent-activity - actions: - - addLabel: - label: no-recent-activity - - addReply: - reply: This pull request has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for **7 days**. It will be closed if no further activity occurs **within 7 days of this comment**. - eventResponderTasks: - - if: - - payloadType: Pull_Request_Review - - isAction: - action: Submitted - - isReviewState: - reviewState: Changes_requested - then: - - addLabel: - label: 'Needs: Author Feedback' - description: - - if: - - payloadType: Pull_Request - - isActivitySender: - issueAuthor: True - - not: - isAction: - action: Closed - - hasLabel: - label: 'Needs: Author Feedback' - then: - - removeLabel: - label: 'Needs: Author Feedback' - description: - - if: - - payloadType: Pull_Request - - not: - isAction: - action: Closed - - hasLabel: - label: no-recent-activity - then: - - removeLabel: - label: no-recent-activity - description: - - if: - - payloadType: Pull_Request - - hasLabel: - label: AutoMerge - then: - - enableAutoMerge: - mergeMethod: Squash - description: - - if: - - payloadType: Pull_Request - - labelRemoved: - label: AutoMerge - then: - - disableAutoMerge - description: - - if: - - payloadType: Issue_Comment - - isActivitySender: - issueAuthor: True - - hasLabel: - label: 'Needs: Author Feedback' - then: - - removeLabel: - label: 'Needs: Author Feedback' - description: - - if: - - payloadType: Pull_Request_Review - - isActivitySender: - issueAuthor: True - - hasLabel: - label: 'Needs: Author Feedback' - then: - - removeLabel: - label: 'Needs: Author Feedback' - description: - - if: - - payloadType: Issue_Comment - - hasLabel: - label: no-recent-activity - then: - - removeLabel: - label: no-recent-activity - description: - - if: - - payloadType: Pull_Request_Review - - hasLabel: - label: no-recent-activity - then: - - removeLabel: - label: no-recent-activity - description: - - if: - - payloadType: Issues - - isAction: - action: Opened - then: - - addLabel: - label: 'Needs: Triage :mag:' - description: - - if: - - payloadType: Issues - - isAction: - action: Opened - - or: - - isActivitySender: - user: TheSavior - issueAuthor: False - - isActivitySender: - user: fmsanchez - issueAuthor: False - - isActivitySender: - user: AkshatGiri - issueAuthor: False - - isActivitySender: - user: sahrens - issueAuthor: False - - isActivitySender: - user: jackerghan - issueAuthor: False - - isActivitySender: - user: appden - issueAuthor: False - - isActivitySender: - user: lyahdav - issueAuthor: False - then: - - addLabel: - label: 'Partner: Facebook' - description: - - if: - - payloadType: Issues - - labelAdded: - label: 'Needs: Environment Info' - then: - - addReply: - reply: >- - This issue lacks the environment info requested in the bug template. Please edit your issue report to add this information. - - - 1. `react-native -v`: - - 3. `npm ls react-native-macos`: - - 4. `node -v`: - - 5. `npm -v`: - - 6. `yarn --version`: - - 7. `xcodebuild -version`: - - addLabel: - label: 'Needs: Author Feedback' - - removeLabel: - label: 'Needs: Triage :mag:' - description: - - if: - - payloadType: Issues - - labelAdded: - label: 'Needs: Verify on Latest Version' - then: - - addReply: - reply: >- - This issue is reported on an older version of React Native for macOS than is current, and is believed to be fixed in the latest version. Please try to update to the latest version and confirm if your issue still occurs. - - Per-version documentation is available [here](https://microsoft.github.io/react-native-windows/versions) - - addLabel: - label: 'Needs: Author Feedback' - - removeLabel: - label: 'Needs: Triage :mag:' - description: - - if: - - payloadType: Pull_Request - then: - - labelSync: - pattern: 'Area:' - - labelSync: - pattern: Backport - description: - - if: - - payloadType: Issues - - labelAdded: - label: 'Resolution: For StackOverflow' - then: - - addReply: - reply: We use github to track issues exclusive to React Native for macOS. This issue seems to pertain to something outside of that scope. Please try asking over on [Stack Overflow](https://stackoverflow.com/questions/tagged/macOS) as it is better suited for general macOS platform questions. - description: - - if: - - payloadType: Issue_Comment - - isAction: - action: Created - - isActivitySender: - issueAuthor: True - - hasLabel: - label: 'Needs: Author Feedback' - then: - - addLabel: - label: 'Needs: Attention :wave:' - - removeLabel: - label: 'Needs: Author Feedback' - description: -onFailure: -onSuccess: +id: +name: GitOps.PullRequestIssueManagement +description: GitOps.PullRequestIssueManagement primitive +owner: +resource: repository +disabled: false +where: +configuration: + resourceManagementConfiguration: + scheduledSearches: + - description: + frequencies: + - hourly: + hour: 3 + filters: + - isPullRequest + - isOpen + - hasLabel: + label: 'Needs: Author Feedback' + - hasLabel: + label: no-recent-activity + - noActivitySince: + days: 7 + actions: + - closeIssue + - description: + frequencies: + - hourly: + hour: 3 + filters: + - isPullRequest + - isOpen + - hasLabel: + label: 'Needs: Author Feedback' + - noActivitySince: + days: 7 + - isNotLabeledWith: + label: no-recent-activity + actions: + - addLabel: + label: no-recent-activity + - addReply: + reply: This pull request has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for **7 days**. It will be closed if no further activity occurs **within 7 days of this comment**. + eventResponderTasks: + - if: + - payloadType: Pull_Request_Review + - isAction: + action: Submitted + - isReviewState: + reviewState: Changes_requested + then: + - addLabel: + label: 'Needs: Author Feedback' + description: + - if: + - payloadType: Pull_Request + - isActivitySender: + issueAuthor: True + - not: + isAction: + action: Closed + - hasLabel: + label: 'Needs: Author Feedback' + then: + - removeLabel: + label: 'Needs: Author Feedback' + description: + - if: + - payloadType: Pull_Request + - not: + isAction: + action: Closed + - hasLabel: + label: no-recent-activity + then: + - removeLabel: + label: no-recent-activity + description: + - if: + - payloadType: Pull_Request + - hasLabel: + label: AutoMerge + then: + - enableAutoMerge: + mergeMethod: Squash + description: + - if: + - payloadType: Pull_Request + - labelRemoved: + label: AutoMerge + then: + - disableAutoMerge + description: + - if: + - payloadType: Issue_Comment + - isActivitySender: + issueAuthor: True + - hasLabel: + label: 'Needs: Author Feedback' + then: + - removeLabel: + label: 'Needs: Author Feedback' + description: + - if: + - payloadType: Pull_Request_Review + - isActivitySender: + issueAuthor: True + - hasLabel: + label: 'Needs: Author Feedback' + then: + - removeLabel: + label: 'Needs: Author Feedback' + description: + - if: + - payloadType: Issue_Comment + - hasLabel: + label: no-recent-activity + then: + - removeLabel: + label: no-recent-activity + description: + - if: + - payloadType: Pull_Request_Review + - hasLabel: + label: no-recent-activity + then: + - removeLabel: + label: no-recent-activity + description: + - if: + - payloadType: Issues + - isAction: + action: Opened + then: + - addLabel: + label: 'Needs: Triage :mag:' + description: + - if: + - payloadType: Issues + - isAction: + action: Opened + - or: + - isActivitySender: + user: TheSavior + issueAuthor: False + - isActivitySender: + user: fmsanchez + issueAuthor: False + - isActivitySender: + user: AkshatGiri + issueAuthor: False + - isActivitySender: + user: sahrens + issueAuthor: False + - isActivitySender: + user: jackerghan + issueAuthor: False + - isActivitySender: + user: appden + issueAuthor: False + - isActivitySender: + user: lyahdav + issueAuthor: False + then: + - addLabel: + label: 'Partner: Facebook' + description: + - if: + - payloadType: Issues + - labelAdded: + label: 'Needs: Environment Info' + then: + - addReply: + reply: >- + This issue lacks the environment info requested in the bug template. Please edit your issue report to add this information. + + + 1. `react-native -v`: + + 3. `npm ls react-native-macos`: + + 4. `node -v`: + + 5. `npm -v`: + + 6. `yarn --version`: + + 7. `xcodebuild -version`: + - addLabel: + label: 'Needs: Author Feedback' + - removeLabel: + label: 'Needs: Triage :mag:' + description: + - if: + - payloadType: Issues + - labelAdded: + label: 'Needs: Verify on Latest Version' + then: + - addReply: + reply: >- + This issue is reported on an older version of React Native for macOS than is current, and is believed to be fixed in the latest version. Please try to update to the latest version and confirm if your issue still occurs. + + Per-version documentation is available [here](https://microsoft.github.io/react-native-windows/versions) + - addLabel: + label: 'Needs: Author Feedback' + - removeLabel: + label: 'Needs: Triage :mag:' + description: + - if: + - payloadType: Pull_Request + then: + - labelSync: + pattern: 'Area:' + - labelSync: + pattern: Backport + description: + - if: + - payloadType: Issues + - labelAdded: + label: 'Resolution: For StackOverflow' + then: + - addReply: + reply: We use github to track issues exclusive to React Native for macOS. This issue seems to pertain to something outside of that scope. Please try asking over on [Stack Overflow](https://stackoverflow.com/questions/tagged/macOS) as it is better suited for general macOS platform questions. + description: + - if: + - payloadType: Issue_Comment + - isAction: + action: Created + - isActivitySender: + issueAuthor: True + - hasLabel: + label: 'Needs: Author Feedback' + then: + - addLabel: + label: 'Needs: Attention :wave:' + - removeLabel: + label: 'Needs: Author Feedback' + description: +onFailure: +onSuccess: diff --git a/.github/workflow-scripts/__tests__/createDraftRelease-test.js b/.github/workflow-scripts/__tests__/createDraftRelease-test.js index b1b330acd87..55b73771424 100644 --- a/.github/workflow-scripts/__tests__/createDraftRelease-test.js +++ b/.github/workflow-scripts/__tests__/createDraftRelease-test.js @@ -121,8 +121,12 @@ describe('Create Draft Release', () => { }); describe('#_computeBody', () => { - it('computes body for release when no hermes versions are passed', async () => { + it('falls back to HERMES_VERSION_NAME from version.properties when no hermes version is passed', async () => { const version = '0.77.1'; + const hermesVersion = '250829098.0.13'; + jest.spyOn(fs, 'readFileSync').mockImplementationOnce(() => { + return `HERMES_VERSION_NAME=${hermesVersion}\n`; + }); const changelog = `## v${version} ### Breaking Changes - [PR #9012](https://github.com/facebook/react-native/pull/9012) - Some other change @@ -140,13 +144,9 @@ describe('Create Draft Release', () => { --- -Hermes dSYMS: -- [Debug](https://repo1.maven.org/maven2/com/facebook/hermes/hermes-ios/${version}/hermes-ios-${version}-hermes-framework-dSYM-debug.tar.gz) -- [Release](https://repo1.maven.org/maven2/com/facebook/hermes/hermes-ios/${version}/hermes-ios-${version}-hermes-framework-dSYM-release.tar.gz) - Hermes V1 dSYMS: -- [Debug](https://repo1.maven.org/maven2/com/facebook/hermes/hermes-ios/${version}/hermes-ios-${version}-hermes-framework-dSYM-debug.tar.gz) -- [Release](https://repo1.maven.org/maven2/com/facebook/hermes/hermes-ios/${version}/hermes-ios-${version}-hermes-framework-dSYM-release.tar.gz) +- [Debug](https://repo1.maven.org/maven2/com/facebook/hermes/hermes-ios/${hermesVersion}/hermes-ios-${hermesVersion}-hermes-framework-dSYM-debug.tar.gz) +- [Release](https://repo1.maven.org/maven2/com/facebook/hermes/hermes-ios/${hermesVersion}/hermes-ios-${hermesVersion}-hermes-framework-dSYM-release.tar.gz) ReactNativeDependencies dSYMs: - [Debug](https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/${version}/react-native-artifacts-${version}-reactnative-dependencies-dSYM-debug.tar.gz) @@ -172,7 +172,6 @@ View the whole changelog in the [CHANGELOG.md file](https://github.com/facebook/ it('computes body for release when hermes versions are passed', async () => { const version = '0.77.1'; const hermesVersion = '0.15.0'; - const hermesV1Version = '250829098.0.2'; const changelog = `## v${version} ### Breaking Changes - [PR #9012](https://github.com/facebook/react-native/pull/9012) - Some other change @@ -184,25 +183,16 @@ View the whole changelog in the [CHANGELOG.md file](https://github.com/facebook/ #### iOS - [PR #3436](https://github.com/facebook/react-native/pull/3436) - Some other change - [PR #3437](https://github.com/facebook/react-native/pull/3437) - Some other change`; - const body = _computeBody( - changelog, - version, - hermesVersion, - hermesV1Version, - ); + const body = _computeBody(changelog, version, hermesVersion); expect(body).toEqual(`${changelog} --- -Hermes dSYMS: +Hermes V1 dSYMS: - [Debug](https://repo1.maven.org/maven2/com/facebook/hermes/hermes-ios/${hermesVersion}/hermes-ios-${hermesVersion}-hermes-framework-dSYM-debug.tar.gz) - [Release](https://repo1.maven.org/maven2/com/facebook/hermes/hermes-ios/${hermesVersion}/hermes-ios-${hermesVersion}-hermes-framework-dSYM-release.tar.gz) -Hermes V1 dSYMS: -- [Debug](https://repo1.maven.org/maven2/com/facebook/hermes/hermes-ios/${hermesV1Version}/hermes-ios-${hermesV1Version}-hermes-framework-dSYM-debug.tar.gz) -- [Release](https://repo1.maven.org/maven2/com/facebook/hermes/hermes-ios/${hermesV1Version}/hermes-ios-${hermesV1Version}-hermes-framework-dSYM-release.tar.gz) - ReactNativeDependencies dSYMs: - [Debug](https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/${version}/react-native-artifacts-${version}-reactnative-dependencies-dSYM-debug.tar.gz) - [Release](https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/${version}/react-native-artifacts-${version}-reactnative-dependencies-dSYM-release.tar.gz) diff --git a/.github/workflow-scripts/createDraftRelease.js b/.github/workflow-scripts/createDraftRelease.js index ca9771b713c..c3d7ef72447 100644 --- a/.github/workflow-scripts/createDraftRelease.js +++ b/.github/workflow-scripts/createDraftRelease.js @@ -40,21 +40,33 @@ function _extractChangelog(version) { return changelog.slice(changelogStarts, changelogEnds).join('\n').trim(); } -function _computeBody(changelog, version, hermesVersion, hermesV1Version) { - hermesVersion = hermesVersion ?? version; - hermesV1Version = hermesV1Version ?? version; +function _readHermesVersionFromProperties() { + const propertiesPath = + 'packages/react-native/sdks/hermes-engine/version.properties'; + const content = String(fs.readFileSync(propertiesPath, 'utf8')); + const match = content.match(/^HERMES_VERSION_NAME=(.+)$/m); + if (!match) { + throw new Error(`HERMES_VERSION_NAME not found in ${propertiesPath}`); + } + return match[1].trim(); +} + +function _computeBody(changelog, version, hermesVersion) { + // The workflow input is optional and arrives as an empty string when unset, + // so treat both missing and empty as "fall back to version.properties". + // Falling back to ${version} would produce 404 dSYMS URLs since Hermes V1 + // versions (e.g. 250829098.0.13) don't track RN versions. + if (hermesVersion == null || hermesVersion === '') { + hermesVersion = _readHermesVersionFromProperties(); + } return `${changelog} --- -Hermes dSYMS: +Hermes V1 dSYMS: - [Debug](https://repo1.maven.org/maven2/com/facebook/hermes/hermes-ios/${hermesVersion}/hermes-ios-${hermesVersion}-hermes-framework-dSYM-debug.tar.gz) - [Release](https://repo1.maven.org/maven2/com/facebook/hermes/hermes-ios/${hermesVersion}/hermes-ios-${hermesVersion}-hermes-framework-dSYM-release.tar.gz) -Hermes V1 dSYMS: -- [Debug](https://repo1.maven.org/maven2/com/facebook/hermes/hermes-ios/${hermesV1Version}/hermes-ios-${hermesV1Version}-hermes-framework-dSYM-debug.tar.gz) -- [Release](https://repo1.maven.org/maven2/com/facebook/hermes/hermes-ios/${hermesV1Version}/hermes-ios-${hermesV1Version}-hermes-framework-dSYM-release.tar.gz) - ReactNativeDependencies dSYMs: - [Debug](https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/${version}/react-native-artifacts-${version}-reactnative-dependencies-dSYM-debug.tar.gz) - [Release](https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/${version}/react-native-artifacts-${version}-reactnative-dependencies-dSYM-release.tar.gz) @@ -123,13 +135,7 @@ function moveToChangelogBranch(version) { run(`git checkout -b changelog/v${version}`); } -async function createDraftRelease( - version, - latest, - token, - hermesVersion, - hermesV1Version, -) { +async function createDraftRelease(version, latest, token, hermesVersion) { if (version.startsWith('v')) { version = version.substring(1); } @@ -137,7 +143,7 @@ async function createDraftRelease( _verifyTagExists(version); moveToChangelogBranch(version); const changelog = _extractChangelog(version); - const body = _computeBody(changelog, version, hermesVersion, hermesV1Version); + const body = _computeBody(changelog, version, hermesVersion); const release = await _createDraftReleaseOnGitHub( version, body, @@ -155,4 +161,5 @@ module.exports = { _extractChangelog, _computeBody, _createDraftReleaseOnGitHub, + _readHermesVersionFromProperties, }; diff --git a/.github/workflow-scripts/hermes-v1.patch b/.github/workflow-scripts/hermes-v1.patch deleted file mode 100644 index 213020381fc..00000000000 --- a/.github/workflow-scripts/hermes-v1.patch +++ /dev/null @@ -1,30 +0,0 @@ -diff --git a/android/settings.gradle b/android/settings.gradle -index 63b5d4e..6359ec3 100644 ---- a/android/settings.gradle -+++ b/android/settings.gradle -@@ -4,3 +4,11 @@ extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autoli - rootProject.name = 'RNApp' - include ':app' - includeBuild('../node_modules/@react-native/gradle-plugin') -+ -+includeBuild('../node_modules/react-native') { -+ dependencySubstitution { -+ substitute(module("com.facebook.react:react-android")).using(project(":packages:react-native:ReactAndroid")) -+ substitute(module("com.facebook.react:react-native")).using(project(":packages:react-native:ReactAndroid")) -+ substitute(project(":packages:react-native:ReactAndroid:hermes-engine")).using(module("com.facebook.hermes:hermes-android:$HERMES_V1_VERSION")) -+ } -+} -diff --git a/package.json b/package.json -index f05d51b..69938af 100644 ---- a/package.json -+++ b/package.json -@@ -35,6 +35,9 @@ - "react-test-renderer": "19.2.0", - "typescript": "^5.8.3" - }, -+ "resolutions": { -+ "hermes-compiler": "$HERMES_V1_VERSION" -+ }, - "engines": { - "node": ">=20" - } diff --git a/.github/workflow-scripts/maestro-android.js b/.github/workflow-scripts/maestro-android.js index 62ee99a2279..74507ff6bca 100644 --- a/.github/workflow-scripts/maestro-android.js +++ b/.github/workflow-scripts/maestro-android.js @@ -60,9 +60,10 @@ async function executeFlowInFolder(flowFolder) { const filePath = `${flowFolder}/${file}`; if (fs.lstatSync(filePath).isDirectory()) { await executeFlowInFolder(filePath); - } else { + } else if (file.endsWith('.yml') || file.endsWith('.yaml')) { await executeFlowWithRetries(filePath, 0); } + // Skip non-flow files (e.g. screenshot baselines under screenshots/). } } diff --git a/.github/workflow-scripts/selectLatestHermesV1Version.js b/.github/workflow-scripts/selectLatestHermesV1Version.js deleted file mode 100644 index db11b9278c1..00000000000 --- a/.github/workflow-scripts/selectLatestHermesV1Version.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -const { execSync } = require('child_process'); -const fs = require('fs'); -const path = require('path'); - -const PATCH_FILE_PATH = path.join(__dirname, 'hermes-v1.patch'); - -function getLatestHermesV1Version() { - const npmString = "npm view hermes-compiler@latest-v1 version"; - - try { - const result = execSync(npmString, { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim(); - return result; - } catch (error) { - throw new Error(`Failed to get package version for hermes-compiler@latest-v1`); - } -} - -function setHermesV1VersionInPatch(version) { - if (!fs.existsSync(PATCH_FILE_PATH)) { - throw new Error(`Patch file not found at path: ${PATCH_FILE_PATH}`); - } - - let patchContent = fs.readFileSync(PATCH_FILE_PATH, 'utf8'); - const updatedContent = patchContent.replaceAll( - "$HERMES_V1_VERSION", - version - ); - fs.writeFileSync(PATCH_FILE_PATH, updatedContent, 'utf8'); -} - -setHermesV1VersionInPatch(getLatestHermesV1Version()); diff --git a/.github/workflows/analyze-pr.yml b/.github/workflows/analyze-pr.yml index ed27dc9abad..9d8b6c56b09 100644 --- a/.github/workflows/analyze-pr.yml +++ b/.github/workflows/analyze-pr.yml @@ -11,7 +11,7 @@ permissions: jobs: analyze_pr: runs-on: ubuntu-latest - if: github.repository == 'facebook/react-native' + if: github.repository == 'react/react-native' steps: - name: Check out main branch uses: actions/checkout@v6 diff --git a/.github/workflows/api-changes.yml b/.github/workflows/api-changes.yml index 4899d967d42..e714a29fc6b 100644 --- a/.github/workflows/api-changes.yml +++ b/.github/workflows/api-changes.yml @@ -10,7 +10,7 @@ permissions: jobs: api_changes: runs-on: ubuntu-latest - if: github.repository == 'facebook/react-native' + if: github.repository == 'react/react-native' steps: - name: Check out main branch uses: actions/checkout@v6 diff --git a/.github/workflows/autorebase.yml b/.github/workflows/autorebase.yml index dff8633b481..6eb1d7f1f8d 100644 --- a/.github/workflows/autorebase.yml +++ b/.github/workflows/autorebase.yml @@ -25,4 +25,4 @@ jobs: uses: cirrus-actions/rebase@1.8 env: GITHUB_TOKEN: ${{ secrets.REACT_NATIVE_BOT_GITHUB_TOKEN }} - continue-on-error: true # [macOS] + continue-on-error: true # [macOS] diff --git a/.github/workflows/cache-reaper.yml b/.github/workflows/cache-reaper.yml index 59feda574c1..85ce2a8c88c 100644 --- a/.github/workflows/cache-reaper.yml +++ b/.github/workflows/cache-reaper.yml @@ -4,11 +4,11 @@ on: workflow_dispatch: schedule: # Run every 2hrs during weekdays - - cron: "0 0/2 * * 1-5" + - cron: '0 0/2 * * 1-5' jobs: cache-cleaner: - if: github.repository == 'facebook/react-native' + if: github.repository == 'react/react-native' runs-on: ubuntu-latest env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/check-for-reproducer.yml b/.github/workflows/check-for-reproducer.yml index e986259a3a2..72b122a905f 100644 --- a/.github/workflows/check-for-reproducer.yml +++ b/.github/workflows/check-for-reproducer.yml @@ -8,7 +8,7 @@ jobs: check-for-reproducer: runs-on: ubuntu-latest if: | - github.repository == 'facebook/react-native' && github.event.issue.pull_request == null && github.event.issue.state == 'open' && !contains(github.event.issue.labels.*.name, ':open_umbrella: Umbrella') + github.repository == 'react/react-native' && github.event.issue.pull_request == null && github.event.issue.state == 'open' && !contains(github.event.issue.labels.*.name, ':open_umbrella: Umbrella') steps: - uses: actions/checkout@v6 - uses: actions/github-script@v8 diff --git a/.github/workflows/close-pr.yml b/.github/workflows/close-pr.yml index 5d0489f170b..6af1b037fcf 100644 --- a/.github/workflows/close-pr.yml +++ b/.github/workflows/close-pr.yml @@ -1,6 +1,5 @@ name: Label closed PR as merged and leave a comment -on: - push +on: push permissions: contents: read @@ -9,7 +8,7 @@ permissions: jobs: comment-and-label: runs-on: ubuntu-latest - if: github.repository == 'facebook/react-native' + if: github.repository == 'react/react-native' steps: - uses: actions/github-script@v8 with: diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index b94dad7fcce..4e7d825b1cd 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -9,14 +9,14 @@ # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # -name: "CodeQL" +name: 'CodeQL' on: push: - branches: [ main ] + branches: [main] pull_request: # The branches below must be a subset of the branches above - branches: [ main ] + branches: [main] schedule: - cron: '24 11 * * 4' @@ -32,39 +32,39 @@ jobs: strategy: fail-fast: false matrix: - language: [ 'javascript' ] + language: ['javascript'] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] # Learn more about CodeQL language support at https://git.io/codeql-language-support steps: - - name: Checkout repository - uses: actions/checkout@v2 + - name: Checkout repository + uses: actions/checkout@v2 - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language - #- run: | - # make bootstrap - # make release + #- run: | + # make bootstrap + # make release - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/create-draft-release.yml b/.github/workflows/create-draft-release.yml index ca947dc267c..d45a3a5e99f 100644 --- a/.github/workflows/create-draft-release.yml +++ b/.github/workflows/create-draft-release.yml @@ -6,11 +6,7 @@ on: hermesVersion: required: false type: string - description: The version of Hermes to use for this release (eg. 0.15.0). If not specified, it will use React Native Version - hermesV1Version: - required: false - type: string - description: The version of Hermes V1 to use for this release (eg. 250829098.0.2). If not specified, it will use React Native Version + description: The version of Hermes to use for this release (eg. 250829098.0.2). If not specified, it will read HERMES_VERSION_NAME from version.properties jobs: create-draft-release: @@ -37,7 +33,7 @@ jobs: const {createDraftRelease} = require('./.github/workflow-scripts/createDraftRelease.js'); const version = '${{ github.ref_name }}'; const {isLatest} = require('./.github/workflow-scripts/publishTemplate.js'); - return (await createDraftRelease(version, isLatest(), '${{secrets.REACT_NATIVE_BOT_GITHUB_TOKEN}}', '${{ inputs.hermesVersion }}', '${{ inputs.hermesV1Version }}')).id; + return (await createDraftRelease(version, isLatest(), '${{secrets.REACT_NATIVE_BOT_GITHUB_TOKEN}}', '${{ inputs.hermesVersion }}')).id; result-encoding: string - name: Upload release assets for DotSlash uses: actions/github-script@v8 diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index 705b4e31331..c4cedf2f00b 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -4,22 +4,22 @@ on: workflow_dispatch: inputs: version: - description: "The version of React Native we want to release. For example 0.75.0-rc.0" + description: 'The version of React Native we want to release. For example 0.75.0-rc.0' required: true type: string is-latest-on-npm: - description: "Whether we want to tag this release as latest on NPM" + description: 'Whether we want to tag this release as latest on NPM' required: true type: boolean default: false dry-run: - description: "Whether the job should be executed in dry-run mode or not" + description: 'Whether the job should be executed in dry-run mode or not' type: boolean default: true jobs: create_release: - if: github.repository == 'facebook/react-native' + if: github.repository == 'react/react-native' runs-on: ubuntu-latest steps: - name: Checkout @@ -28,21 +28,6 @@ jobs: token: ${{ secrets.REACT_NATIVE_BOT_GITHUB_TOKEN }} fetch-depth: 0 fetch-tags: 'true' - - name: Verify NPM token - run: | - if [[ -z "$GHA_NPM_TOKEN" ]]; then - echo "⚠️ No NPM token found. Skipping validation." - exit 0 - fi - echo "//registry.npmjs.org/:_authToken=$GHA_NPM_TOKEN" > ~/.npmrc - if ! npm whoami > /dev/null 2>&1; then - echo "❌ NPM token is invalid or expired. Aborting release." - exit 1 - fi - echo "✅ NPM token is valid ($(npm whoami))" - rm -f ~/.npmrc - env: - GHA_NPM_TOKEN: ${{ secrets.GHA_NPM_TOKEN }} - name: Check if on stable branch id: check_stable_branch run: | diff --git a/.github/workflows/e2e-android-rntester.yml b/.github/workflows/e2e-android-rntester.yml index 03970036269..5fba707f924 100644 --- a/.github/workflows/e2e-android-rntester.yml +++ b/.github/workflows/e2e-android-rntester.yml @@ -11,7 +11,7 @@ on: default: false outputs: status: - description: "The result of the E2E tests (success or failure)" + description: 'The result of the E2E tests (success or failure)' value: ${{ jobs.report.outputs.status }} jobs: diff --git a/.github/workflows/e2e-android-templateapp.yml b/.github/workflows/e2e-android-templateapp.yml index 5246d6b5935..0963cc69ee4 100644 --- a/.github/workflows/e2e-android-templateapp.yml +++ b/.github/workflows/e2e-android-templateapp.yml @@ -11,7 +11,7 @@ on: default: false outputs: status: - description: "The result of the E2E tests (success or failure)" + description: 'The result of the E2E tests (success or failure)' value: ${{ jobs.report.outputs.status }} jobs: @@ -66,7 +66,7 @@ jobs: echo "Feed maven local to gradle.properties" cd /tmp/RNTestProject - echo "react.internal.mavenLocalRepo=$MAVEN_LOCAL" >> android/gradle.properties + echo -e "\nreact.internal.mavenLocalRepo=$MAVEN_LOCAL" >> android/gradle.properties # Build cd android diff --git a/.github/workflows/e2e-ios-rntester.yml b/.github/workflows/e2e-ios-rntester.yml index 941711c111e..cd53e954788 100644 --- a/.github/workflows/e2e-ios-rntester.yml +++ b/.github/workflows/e2e-ios-rntester.yml @@ -11,7 +11,7 @@ on: default: false outputs: status: - description: "The result of the E2E tests (success or failure)" + description: 'The result of the E2E tests (success or failure)' value: ${{ jobs.report.outputs.status }} jobs: @@ -42,7 +42,7 @@ jobs: continue-on-error: true uses: ./.github/actions/maestro-ios with: - app-path: "/tmp/RNTesterBuild/RNTester.app" + app-path: '/tmp/RNTesterBuild/RNTester.app' app-id: com.meta.RNTester.localDevelopment maestro-flow: ./packages/rn-tester/.maestro/ flavor: ${{ matrix.flavor }} diff --git a/.github/workflows/e2e-ios-templateapp.yml b/.github/workflows/e2e-ios-templateapp.yml index dd4761471ca..44b64858bb4 100644 --- a/.github/workflows/e2e-ios-templateapp.yml +++ b/.github/workflows/e2e-ios-templateapp.yml @@ -11,7 +11,7 @@ on: default: false outputs: status: - description: "The result of the E2E tests (success or failure)" + description: 'The result of the E2E tests (success or failure)' value: ${{ jobs.report.outputs.status }} jobs: @@ -62,8 +62,8 @@ jobs: - name: Configure git shell: bash run: | - git config --global user.email "react-native-bot@meta.com" - git config --global user.name "React Native Bot" + git config --global user.email "react-native-bot@meta.com" + git config --global user.name "React Native Bot" - name: Prepare artifacts run: | REACT_NATIVE_PKG=$(find /tmp/react-native-tmp -type f -name "*.tgz") @@ -99,7 +99,7 @@ jobs: continue-on-error: true uses: ./.github/actions/maestro-ios with: - app-path: "/tmp/RNTestProject/Build/Products/${{ matrix.flavor }}-iphonesimulator/RNTestProject.app" + app-path: '/tmp/RNTestProject/Build/Products/${{ matrix.flavor }}-iphonesimulator/RNTestProject.app' app-id: org.reactjs.native.example.RNTestProject maestro-flow: ./scripts/e2e/.maestro/ flavor: ${{ matrix.flavor }} diff --git a/.github/workflows/fantom-tests.yml b/.github/workflows/fantom-tests.yml index 97c8fd5c18f..82cdd462e6d 100644 --- a/.github/workflows/fantom-tests.yml +++ b/.github/workflows/fantom-tests.yml @@ -11,7 +11,7 @@ on: default: false outputs: status: - description: "The result of the Fantom tests (success or failure)" + description: 'The result of the Fantom tests (success or failure)' value: ${{ jobs.report.outputs.status }} jobs: @@ -22,7 +22,7 @@ jobs: container: image: reactnativecommunity/react-native-android:latest env: - TERM: "dumb" + TERM: 'dumb' steps: - name: Checkout uses: actions/checkout@v6 diff --git a/.github/workflows/microsoft-build-rntester.yml b/.github/workflows/microsoft-build-rntester.yml index e78f4669dcc..008630c458d 100644 --- a/.github/workflows/microsoft-build-rntester.yml +++ b/.github/workflows/microsoft-build-rntester.yml @@ -5,7 +5,7 @@ on: jobs: build-rntester: - name: "${{ matrix.platform }}, ${{ matrix.linkage }}" + name: '${{ matrix.platform }}, ${{ matrix.linkage }}' runs-on: macos-26 timeout-minutes: 90 strategy: diff --git a/.github/workflows/microsoft-build-website.yml b/.github/workflows/microsoft-build-website.yml index 64110080086..c4ec0e1ed21 100644 --- a/.github/workflows/microsoft-build-website.yml +++ b/.github/workflows/microsoft-build-website.yml @@ -4,12 +4,12 @@ on: workflow_call: outputs: build-path: - description: "Path to the build directory" + description: 'Path to the build directory' value: ${{ jobs.build.outputs.build-path }} jobs: build: - name: "Build the website" + name: 'Build the website' runs-on: ubuntu-24.04 outputs: build-path: ./build @@ -31,4 +31,4 @@ jobs: with: name: website-build path: ./docsite/build - retention-days: 1 \ No newline at end of file + retention-days: 1 diff --git a/.github/workflows/microsoft-changesets-version.yml b/.github/workflows/microsoft-changesets-version.yml index 7d7ed8d1a03..b2f72fbe89e 100644 --- a/.github/workflows/microsoft-changesets-version.yml +++ b/.github/workflows/microsoft-changesets-version.yml @@ -3,7 +3,7 @@ name: Changesets Version Bump on: push: branches: - - "*-stable" + - '*-stable' workflow_dispatch: jobs: diff --git a/.github/workflows/microsoft-pr.yml b/.github/workflows/microsoft-pr.yml index 6b7e015073e..6593a281d88 100644 --- a/.github/workflows/microsoft-pr.yml +++ b/.github/workflows/microsoft-pr.yml @@ -3,7 +3,7 @@ name: PR on: pull_request: types: [opened, synchronize, edited] - branches: [ "main", "*-stable", "release/*", "*-merge" ] + branches: ['main', '*-stable', 'release/*', '*-merge'] concurrency: # Ensure single build of a pull request. `main` should not be affected. @@ -12,7 +12,7 @@ concurrency: jobs: lint-title: - name: "Lint PR title" + name: 'Lint PR title' permissions: {} runs-on: ubuntu-latest steps: @@ -33,15 +33,15 @@ jobs: PR_TITLE: ${{ github.event.pull_request.title }} run: | echo "$PR_TITLE" | npx @rnx-kit/commitlint-lite@2.0.0 - + build-website: - name: "Build the website" + name: 'Build the website' permissions: {} if: github.base_ref == 'main' uses: ./.github/workflows/microsoft-build-website.yml npm-publish-dry-run: - name: "NPM Publish (Dry Run)" + name: 'NPM Publish (Dry Run)' permissions: {} runs-on: ubuntu-latest steps: @@ -64,7 +64,7 @@ jobs: yarn workspaces foreach -vv --all --topological --no-private npm publish --tag dry-run --tolerate-republish --dry-run check-changesets: - name: "Check for Changesets" + name: 'Check for Changesets' permissions: {} runs-on: ubuntu-latest # Only required for PRs targeting stable branches; skip the version bump PR itself @@ -84,9 +84,9 @@ jobs: run: yarn install --immutable - name: Validate changesets run: yarn change:check - + yarn-constraints: - name: "Check Yarn Constraints" + name: 'Check Yarn Constraints' permissions: {} runs-on: ubuntu-latest steps: @@ -104,7 +104,7 @@ jobs: run: yarn constraints javascript-tests: - name: "JavaScript Tests" + name: 'JavaScript Tests' permissions: {} runs-on: ubuntu-latest steps: @@ -112,51 +112,51 @@ jobs: with: filter: blob:none fetch-depth: 0 - + - name: Setup toolchain uses: ./.github/actions/microsoft-setup-toolchain with: node-version: '22' - + - name: Install npm dependencies run: yarn install - + - name: Run Jest tests run: yarn test-ci - + - name: Run Flow type checker run: yarn flow-check - + - name: Run ESLint run: yarn lint - + - name: Run Prettier format check run: yarn format-check build-rntester: - name: "Build RNTester" + name: 'Build RNTester' permissions: {} uses: ./.github/workflows/microsoft-build-rntester.yml prebuild-macos-core: - name: "Prebuild macOS Core" + name: 'Prebuild macOS Core' permissions: {} uses: ./.github/workflows/microsoft-prebuild-macos-core.yml test-react-native-macos-init: - name: "Test react-native-macos init" + name: 'Test react-native-macos init' permissions: {} if: ${{ endsWith(github.base_ref, '-stable') }} uses: ./.github/workflows/microsoft-test-react-native-macos-init.yml react-native-test-app-integration: - name: "Test react-native-test-app integration" + name: 'Test react-native-test-app integration' permissions: {} if: ${{ endsWith(github.base_ref, '-stable') }} uses: ./.github/workflows/microsoft-react-native-test-app-integration.yml PR: - name: "PR" + name: 'PR' if: always() permissions: {} runs-on: ubuntu-latest diff --git a/.github/workflows/microsoft-prebuild-macos-core.yml b/.github/workflows/microsoft-prebuild-macos-core.yml index 14c249919eb..2477eb9bc17 100644 --- a/.github/workflows/microsoft-prebuild-macos-core.yml +++ b/.github/workflows/microsoft-prebuild-macos-core.yml @@ -5,11 +5,11 @@ on: jobs: resolve-hermes: - name: "Resolve Hermes" + name: 'Resolve Hermes' uses: ./.github/workflows/microsoft-resolve-hermes.yml build: - name: "Build ${{ matrix.platform }}" + name: 'Build ${{ matrix.platform }}' needs: [resolve-hermes] runs-on: macos-26 timeout-minutes: 60 @@ -90,7 +90,7 @@ jobs: path: packages/react-native/.build/output/spm/Debug/Build/Products compose-xcframework: - name: "Compose XCFramework (Debug)" + name: 'Compose XCFramework (Debug)' needs: [build] if: ${{ always() && !cancelled() && !failure() }} runs-on: macos-26 diff --git a/.github/workflows/microsoft-publish.yml b/.github/workflows/microsoft-publish.yml index 6d84b98a014..e005e5a30aa 100644 --- a/.github/workflows/microsoft-publish.yml +++ b/.github/workflows/microsoft-publish.yml @@ -3,7 +3,7 @@ name: Deploy to GitHub Pages on: # Runs on pushes targeting the default branch push: - branches: ["main"] + branches: ['main'] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -18,7 +18,7 @@ permissions: # in-progress and latest queued. However, do NOT cancel in-progress runs as we # want to allow these production deployments to complete. concurrency: - group: "pages" + group: 'pages' cancel-in-progress: false jobs: diff --git a/.github/workflows/microsoft-react-native-test-app-integration.yml b/.github/workflows/microsoft-react-native-test-app-integration.yml index 7c85f8c8693..31213f57573 100644 --- a/.github/workflows/microsoft-react-native-test-app-integration.yml +++ b/.github/workflows/microsoft-react-native-test-app-integration.yml @@ -5,7 +5,7 @@ on: jobs: react-native-test-app-integration: - name: "Test react-native-test-app integration" + name: 'Test react-native-test-app integration' runs-on: macos-26 timeout-minutes: 60 steps: diff --git a/.github/workflows/microsoft-resolve-hermes.yml b/.github/workflows/microsoft-resolve-hermes.yml index ab153d6e829..28874427731 100644 --- a/.github/workflows/microsoft-resolve-hermes.yml +++ b/.github/workflows/microsoft-resolve-hermes.yml @@ -19,7 +19,7 @@ jobs: # Fast path: download upstream tarball and recompose, or resolve commit + cache # --------------------------------------------------------------------------- resolve-hermes: - name: "Resolve Hermes" + name: 'Resolve Hermes' runs-on: macos-15 timeout-minutes: 15 outputs: @@ -96,7 +96,7 @@ jobs: # Pipeline: hermesc (host compiler) → 5 platform slices → assemble xcframework # --------------------------------------------------------------------------- build-hermesc: - name: "Build hermesc" + name: 'Build hermesc' if: ${{ needs.resolve-hermes.outputs.recomposed != 'true' && needs.resolve-hermes.outputs.cache-hit != 'true' }} needs: resolve-hermes runs-on: macos-15 @@ -137,7 +137,7 @@ jobs: retention-days: 30 build-hermes-slice: - name: "Hermes ${{ matrix.slice }}" + name: 'Hermes ${{ matrix.slice }}' if: ${{ needs.resolve-hermes.outputs.recomposed != 'true' && needs.resolve-hermes.outputs.cache-hit != 'true' }} needs: [resolve-hermes, build-hermesc] runs-on: macos-15 @@ -205,7 +205,7 @@ jobs: retention-days: 30 assemble-hermes: - name: "Assemble Hermes xcframework" + name: 'Assemble Hermes xcframework' if: ${{ needs.resolve-hermes.outputs.recomposed != 'true' && needs.resolve-hermes.outputs.cache-hit != 'true' }} needs: [resolve-hermes, build-hermes-slice] runs-on: macos-15 diff --git a/.github/workflows/microsoft-test-react-native-macos-init.yml b/.github/workflows/microsoft-test-react-native-macos-init.yml index e9cfd19d0fe..c10b565ddba 100644 --- a/.github/workflows/microsoft-test-react-native-macos-init.yml +++ b/.github/workflows/microsoft-test-react-native-macos-init.yml @@ -5,7 +5,7 @@ on: jobs: test-react-native-macos-init: - name: "Test react-native-macos init" + name: 'Test react-native-macos init' runs-on: macos-26 timeout-minutes: 60 steps: diff --git a/.github/workflows/monitor-new-issues.yml b/.github/workflows/monitor-new-issues.yml index 36abe0bb4cf..476daf11880 100644 --- a/.github/workflows/monitor-new-issues.yml +++ b/.github/workflows/monitor-new-issues.yml @@ -2,7 +2,7 @@ name: Monitor React Native New Issues on: schedule: - - cron: "0 0,6,12,18 * * *" + - cron: '0 0,6,12,18 * * *' workflow_dispatch: # Reminder for when we have to update the schedule (before Jan 2026): @@ -12,14 +12,14 @@ on: jobs: monitor-issues: runs-on: ubuntu-latest - if: github.repository == 'facebook/react-native' + if: github.repository == 'react/react-native' steps: - name: Checkout uses: actions/checkout@v6 - name: Set up Node.js uses: ./.github/actions/setup-node - name: Install dependencies - uses: ./.github/actions/yarn-install + uses: ./.github/actions/yarn-install - name: Extract next oncall run: | ONCALLS=$(node ./.github/workflow-scripts/extractIssueOncalls.js "${{ secrets.ONCALL_SCHEDULE }}") @@ -30,12 +30,12 @@ jobs: - name: Monitor New Issues uses: react-native-community/repo-monitor@v1.0.1 with: - task: "monitor-issues" + task: 'monitor-issues' git_secret: ${{ secrets.GITHUB_TOKEN }} - notifier: "discord" + notifier: 'discord' fetch_data_interval: 6 - repo_owner: "facebook" - repo_name: "react-native" - discord_webhook_url: "${{ secrets.DISCORD_WEBHOOK_URL }}" - discord_id_type: "user" - discord_ids: "${{ env.oncall1 }},${{ env.oncall2 }}" + repo_owner: 'react' + repo_name: 'react-native' + discord_webhook_url: '${{ secrets.DISCORD_WEBHOOK_URL }}' + discord_id_type: 'user' + discord_ids: '${{ env.oncall1 }},${{ env.oncall2 }}' diff --git a/.github/workflows/needs-attention.yml b/.github/workflows/needs-attention.yml index 0da88efd6d2..76b1a84843b 100644 --- a/.github/workflows/needs-attention.yml +++ b/.github/workflows/needs-attention.yml @@ -14,16 +14,16 @@ jobs: issues: write # for react-native-community/needs-attention to label issues name: Apply Needs Attention Label runs-on: ubuntu-latest - if: github.repository == 'facebook/react-native' + if: github.repository == 'react/react-native' steps: - uses: actions/checkout@v6 - name: Apply Needs Attention Label uses: react-native-community/needs-attention@v2.0.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - response-required-label: "Needs: Author Feedback" - needs-attention-label: "Needs: Attention" + response-required-label: 'Needs: Author Feedback' + needs-attention-label: 'Needs: Attention' id: needs-attention - name: Result run: echo '${{ steps.needs-attention.outputs.result }}' - continue-on-error: true # [macOS] + continue-on-error: true # [macOS] diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml deleted file mode 100644 index e471fb0b78b..00000000000 --- a/.github/workflows/nightly.yml +++ /dev/null @@ -1,110 +0,0 @@ -name: Nightly - -on: - workflow_dispatch: - # nightly build @ 2:15 AM UTC - schedule: - - cron: "15 2 * * *" - -jobs: - set_release_type: - runs-on: ubuntu-latest - if: github.repository == 'facebook/react-native' - outputs: - RELEASE_TYPE: ${{ steps.set_release_type.outputs.RELEASE_TYPE }} - env: - EVENT_NAME: ${{ github.event_name }} - REF: ${{ github.ref }} - steps: - - id: set_release_type - run: | - echo "Setting release type to nightly" - echo "RELEASE_TYPE=nightly" >> $GITHUB_OUTPUT - - prebuild_apple_dependencies: - if: github.repository == 'facebook/react-native' - uses: ./.github/workflows/prebuild-ios-dependencies.yml - secrets: inherit - - prebuild_react_native_core: - uses: ./.github/workflows/prebuild-ios-core.yml - with: - use-hermes-nightly: true - version-type: nightly - secrets: inherit - needs: [prebuild_apple_dependencies] - - build_android: - runs-on: 8-core-ubuntu - if: github.repository == 'facebook/react-native' - needs: [set_release_type] - container: - image: reactnativecommunity/react-native-android:latest - env: - TERM: "dumb" - # Set the encoding to resolve a known character encoding issue with decompressing tar.gz files in containers - # via Gradle: https://github.com/gradle/gradle/issues/23391#issuecomment-1878979127 - LC_ALL: C.UTF8 - GRADLE_OPTS: "-Dorg.gradle.daemon=false" - ORG_GRADLE_PROJECT_SIGNING_PWD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PWD }} - ORG_GRADLE_PROJECT_SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_KEY }} - ORG_GRADLE_PROJECT_SONATYPE_USERNAME: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPE_USERNAME }} - ORG_GRADLE_PROJECT_SONATYPE_PASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPE_PASSWORD }} - REACT_NATIVE_DOWNLOADS_DIR: /opt/react-native-downloads - steps: - - name: Checkout - uses: actions/checkout@v6 - - name: Build Android - uses: ./.github/actions/build-android - with: - release-type: ${{ needs.set_release_type.outputs.RELEASE_TYPE }} - gradle-cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} - - build_npm_package: - runs-on: 8-core-ubuntu - needs: - [ - set_release_type, - build_android, - prebuild_apple_dependencies, - prebuild_react_native_core, - ] - container: - image: reactnativecommunity/react-native-android:latest - env: - TERM: "dumb" - GRADLE_OPTS: "-Dorg.gradle.daemon=false" - # Set the encoding to resolve a known character encoding issue with decompressing tar.gz files in containers - # via Gradle: https://github.com/gradle/gradle/issues/23391#issuecomment-1878979127 - LC_ALL: C.UTF8 - # By default we only build ARM64 to save time/resources. For release/nightlies, we override this value to build all archs. - ORG_GRADLE_PROJECT_reactNativeArchitectures: "arm64-v8a" - REACT_NATIVE_DOWNLOADS_DIR: /opt/react-native-downloads - env: - GHA_NPM_TOKEN: ${{ secrets.GHA_NPM_TOKEN }} - ORG_GRADLE_PROJECT_SIGNING_PWD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PWD }} - ORG_GRADLE_PROJECT_SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_KEY }} - ORG_GRADLE_PROJECT_SONATYPE_USERNAME: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPE_USERNAME }} - ORG_GRADLE_PROJECT_SONATYPE_PASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPE_PASSWORD }} - steps: - - name: Checkout - uses: actions/checkout@v6 - - name: Verify NPM token - run: | - if [[ -z "$GHA_NPM_TOKEN" ]]; then - echo "⚠️ No NPM token found. Skipping validation." - exit 0 - fi - echo "//registry.npmjs.org/:_authToken=$GHA_NPM_TOKEN" > ~/.npmrc - if ! npm whoami > /dev/null 2>&1; then - echo "❌ NPM token is invalid or expired. Aborting release." - exit 1 - fi - echo "✅ NPM token is valid ($(npm whoami))" - rm -f ~/.npmrc - - name: Build and Publish NPM Package - uses: ./.github/actions/build-npm-package - with: - release-type: ${{ needs.set_release_type.outputs.RELEASE_TYPE }} - gha-npm-token: ${{ env.GHA_NPM_TOKEN }} - gradle-cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} diff --git a/.github/workflows/on-issue-labeled.yml b/.github/workflows/on-issue-labeled.yml index 7500d9ce3d3..b59f9afdbdf 100644 --- a/.github/workflows/on-issue-labeled.yml +++ b/.github/workflows/on-issue-labeled.yml @@ -13,7 +13,7 @@ jobs: # then invokes actOnLabel to react to any added labels triage-issue: runs-on: ubuntu-latest - if: "${{ github.repository == 'facebook/react-native' && contains(github.event.label.name, 'Needs: Triage :mag:') }}" + if: "${{ github.repository == 'react/react-native' && contains(github.event.label.name, 'Needs: Triage :mag:') }}" steps: - name: Checkout code uses: actions/checkout@v6 @@ -49,7 +49,7 @@ jobs: # Reacts to the label that triggered this workflow (added manually or via other workflows) act-on-label: runs-on: ubuntu-latest - if: github.repository == 'facebook/react-native' + if: github.repository == 'react/react-native' steps: - uses: actions/checkout@v6 - uses: actions/github-script@v8 diff --git a/.github/workflows/prebuild-ios-core.yml b/.github/workflows/prebuild-ios-core.yml index ce2f6b88c30..ad47de482fe 100644 --- a/.github/workflows/prebuild-ios-core.yml +++ b/.github/workflows/prebuild-ios-core.yml @@ -8,8 +8,8 @@ on: type: string required: false default: '' - use-hermes-nightly: - description: 'Whether to use the hermes nightly build or read the version from the versions.properties file' + use-hermes-prebuilt: + description: 'Whether to resolve Hermes from the latest-v1 npm dist-tag (true) or from the version pinned in version.properties (false)' type: boolean required: false default: false @@ -21,11 +21,7 @@ jobs: fail-fast: false matrix: flavor: ['Debug', 'Release'] - slice: [ - 'ios', - 'ios-simulator', - 'mac-catalyst', - ] + slice: ['ios', 'ios-simulator', 'mac-catalyst'] steps: - name: Checkout uses: actions/checkout@v6 @@ -47,12 +43,13 @@ jobs: - name: Set Hermes version shell: bash run: | - if [ "${{ inputs.use-hermes-nightly }}" == "true" ]; then - # We are not publishing nightly versions of Hermes V1 yet. - # For now, we can use the latest version of Hermes V1 published on maven and npm. + # Non-stable RN builds resolve Hermes from npm's latest-v1 dist-tag. + # TODO: rename 'latest-v1' to 'latest' once V1 is the only Hermes on npm. + # Stable builds use the version pinned in version.properties. + if [ "${{ inputs.use-hermes-prebuilt }}" == "true" ]; then HERMES_VERSION="latest-v1" else - HERMES_VERSION=$(sed -n 's/^HERMES_V1_VERSION_NAME=//p' packages/react-native/sdks/hermes-engine/version.properties) + HERMES_VERSION=$(sed -n 's/^HERMES_VERSION_NAME=//p' packages/react-native/sdks/hermes-engine/version.properties) fi echo "Using Hermes version: $HERMES_VERSION" echo "HERMES_VERSION=$HERMES_VERSION" >> $GITHUB_ENV @@ -102,8 +99,7 @@ jobs: uses: actions/upload-artifact@v6 with: name: prebuild-ios-core-headers-${{ matrix.flavor }}-${{ matrix.slice }} - path: - packages/react-native/.build/headers + path: packages/react-native/.build/headers - name: Upload artifacts uses: actions/upload-artifact@v6 with: diff --git a/.github/workflows/prebuild-ios-dependencies.yml b/.github/workflows/prebuild-ios-dependencies.yml index 73cf9870c66..ab9322da3dd 100644 --- a/.github/workflows/prebuild-ios-dependencies.yml +++ b/.github/workflows/prebuild-ios-dependencies.yml @@ -3,7 +3,6 @@ name: Prebuild iOS Dependencies on: workflow_call: # this directive allow us to call this workflow from other workflows - jobs: prepare_workspace: name: Prepare workspace @@ -52,14 +51,17 @@ jobs: fail-fast: false matrix: flavor: ['Debug', 'Release'] - slice: ['ios', - 'ios-simulator', - 'macos', - 'mac-catalyst', - 'tvos', - 'tvos-simulator', - 'xros', - 'xros-simulator'] + slice: + [ + 'ios', + 'ios-simulator', + 'macos', + 'mac-catalyst', + 'tvos', + 'tvos-simulator', + 'xros', + 'xros-simulator', + ] steps: - name: Checkout uses: actions/checkout@v6 @@ -88,7 +90,7 @@ jobs: run: ls -lR packages/react-native/third-party - name: Build slice ${{ matrix.slice }} for ${{ matrix.flavor }} if: steps.restore-slice-folder.outputs.cache-hit != 'true' - run: node scripts/releases/prepare-ios-prebuilds.js -b -p ${{ matrix.slice }} -r ${{ matrix.flavor }} + run: node scripts/releases/prepare-ios-prebuilds.js -b -p ${{ matrix.slice }} -r ${{ matrix.flavor }} - name: Upload Artifacts uses: actions/upload-artifact@v6 with: diff --git a/.github/workflows/publish-bumped-packages.yml b/.github/workflows/publish-bumped-packages.yml deleted file mode 100644 index cf0b69b8c35..00000000000 --- a/.github/workflows/publish-bumped-packages.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Publish Bumped Packages - -on: - push: - branches: - - "main" - - "*-stable" - -jobs: - publish_bumped_packages: - runs-on: ubuntu-latest - if: github.repository == 'facebook/react-native' - env: - GHA_NPM_TOKEN: ${{ secrets.GHA_NPM_TOKEN }} - steps: - - name: Checkout - uses: actions/checkout@v6 - - name: Setup node.js - uses: ./.github/actions/setup-node - - name: Run Yarn Install - uses: ./.github/actions/yarn-install - - name: Build packages - run: yarn build - - name: Build types - run: yarn build-types --skip-snapshot - - name: Set NPM auth token - run: echo "//registry.npmjs.org/:_authToken=$GHA_NPM_TOKEN" > ~/.npmrc - - name: Find and publish all bumped packages - run: node ./scripts/releases-ci/publish-updated-packages.js diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml new file mode 100644 index 00000000000..2eb06590225 --- /dev/null +++ b/.github/workflows/publish-npm.yml @@ -0,0 +1,275 @@ +# Single top-level workflow for every npm publish in this repo. +# +# Why: npmjs.com Trusted Publishing matches the `workflow_ref` OIDC claim, +# which is always the TOP-LEVEL workflow filename. npm allows only ONE +# trusted publisher per package, so every `npm publish` must originate +# from the same top-level file. By consolidating all publish triggers +# here, the OIDC claim is always `publish-npm.yml`. +# +# This replaces the previous separate entry points: +# - publish-release.yml (tag push) → mode=release +# - nightly.yml (cron/dispatch) → mode=nightly +# - publish-bumped-packages.yml (main/stable branch push) → mode=bumped-packages +# +# See https://docs.npmjs.com/trusted-publishers +name: Publish to npm + +on: + push: + tags: + - 'v0.*.*' # This should match v0.X.Y + - 'v0.*.*-rc.*' # This should match v0.X.Y-RC.0 + branches: + - 'main' + - '*-stable' + workflow_dispatch: + # nightly build @ 2:15 AM UTC + schedule: + - cron: '15 2 * * *' + +permissions: + contents: read + +jobs: + # ─── Determine what kind of publish this is ────────────────────── + determine_mode: + runs-on: ubuntu-latest + if: github.repository == 'react/react-native' + outputs: + mode: ${{ steps.mode.outputs.mode }} + release-type: ${{ steps.mode.outputs.release-type }} + steps: + - id: mode + run: | + if [[ "${{ github.ref_type }}" == "tag" ]]; then + echo "mode=release" >> $GITHUB_OUTPUT + echo "release-type=release" >> $GITHUB_OUTPUT + elif [[ "${{ github.event_name }}" == "schedule" || "${{ github.event_name }}" == "workflow_dispatch" ]]; then + echo "mode=nightly" >> $GITHUB_OUTPUT + echo "release-type=nightly" >> $GITHUB_OUTPUT + elif [[ "${{ github.event_name }}" == "push" ]]; then + echo "mode=bumped-packages" >> $GITHUB_OUTPUT + echo "release-type=" >> $GITHUB_OUTPUT + fi + - run: | + echo "Mode: ${{ steps.mode.outputs.mode }}" + echo "Release type: ${{ steps.mode.outputs.release-type }}" + + # ─── Release-only: extract Hermes version for draft release ────── + set_hermes_version: + runs-on: ubuntu-latest + if: github.ref_type == 'tag' + outputs: + HERMES_VERSION: ${{ steps.set_hermes_version.outputs.HERMES_VERSION }} + steps: + - name: Checkout + uses: actions/checkout@v6 + - id: set_hermes_version + run: | + hermes_version=$(grep -oE 'HERMES_VERSION_NAME=([0-9]+\.[0-9]+\.[0-9]+)' packages/react-native/sdks/hermes-engine/version.properties | cut -d'=' -f2) + echo "HERMES_VERSION=$hermes_version" >> $GITHUB_OUTPUT + echo "HERMES_VERSION=$hermes_version" + + # ─── Apple prebuilds (release + nightly) ───────────────────────── + prebuild_apple_dependencies: + needs: [determine_mode] + if: needs.determine_mode.outputs.mode == 'release' || needs.determine_mode.outputs.mode == 'nightly' + uses: ./.github/workflows/prebuild-ios-dependencies.yml + secrets: inherit + + prebuild_react_native_core: + needs: [determine_mode, prebuild_apple_dependencies] + if: needs.determine_mode.outputs.mode == 'release' || needs.determine_mode.outputs.mode == 'nightly' + uses: ./.github/workflows/prebuild-ios-core.yml + secrets: inherit + with: + use-hermes-prebuilt: ${{ needs.determine_mode.outputs.mode == 'nightly' }} + version-type: ${{ needs.determine_mode.outputs.mode == 'nightly' && 'nightly' || '' }} + + # ─── Android build (nightly only — releases handle this in the + # build-npm-package action's Gradle step) ───────────────────── + build_android: + needs: [determine_mode] + if: needs.determine_mode.outputs.mode == 'nightly' + runs-on: ubuntu-latest + container: + image: reactnativecommunity/react-native-android:latest + env: + TERM: 'dumb' + # Set the encoding to resolve a known character encoding issue with decompressing tar.gz files in containers + # via Gradle: https://github.com/gradle/gradle/issues/23391#issuecomment-1878979127 + LC_ALL: C.UTF8 + GRADLE_OPTS: '-Dorg.gradle.daemon=false' + ORG_GRADLE_PROJECT_SIGNING_PWD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PWD }} + ORG_GRADLE_PROJECT_SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_KEY }} + ORG_GRADLE_PROJECT_SONATYPE_USERNAME: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPE_USERNAME }} + ORG_GRADLE_PROJECT_SONATYPE_PASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPE_PASSWORD }} + REACT_NATIVE_DOWNLOADS_DIR: /opt/react-native-downloads + steps: + - name: Checkout + uses: actions/checkout@v6 + - name: Build Android + uses: ./.github/actions/build-android + with: + release-type: nightly + gradle-cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} + + # ─── Build + Publish: react-native + all @react-native/* packages + # (release and nightly modes) ───────────────────────────────── + publish_react_native: + needs: + [ + determine_mode, + build_android, + prebuild_apple_dependencies, + prebuild_react_native_core, + ] + # For nightly, also wait on build_android. Use always() so this + # job isn't skipped when build_android is skipped (release mode). + # The explicit status checks below handle the real gating. + if: | + always() && + (needs.determine_mode.outputs.mode == 'release' || needs.determine_mode.outputs.mode == 'nightly') && + needs.determine_mode.result == 'success' && + needs.prebuild_apple_dependencies.result == 'success' && + needs.prebuild_react_native_core.result == 'success' && + (needs.determine_mode.outputs.mode == 'release' || needs.build_android.result == 'success') + runs-on: ubuntu-latest + environment: npm-publish + # `id-token: write` is required so the npm CLI can mint the OIDC + # token that npm Trusted Publishing exchanges for a publish token. + permissions: + contents: read + id-token: write + container: + image: reactnativecommunity/react-native-android:latest + env: + TERM: 'dumb' + # Set the encoding to resolve a known character encoding issue with decompressing tar.gz files in containers + # via Gradle: https://github.com/gradle/gradle/issues/23391#issuecomment-1878979127 + LC_ALL: C.UTF8 + GRADLE_OPTS: '-Dorg.gradle.daemon=false' + # By default we only build ARM64 to save time/resources. For release/nightlies, we override this value to build all archs. + ORG_GRADLE_PROJECT_reactNativeArchitectures: 'arm64-v8a' + REACT_NATIVE_DOWNLOADS_DIR: /opt/react-native-downloads + env: + ORG_GRADLE_PROJECT_SIGNING_PWD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PWD }} + ORG_GRADLE_PROJECT_SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_KEY }} + ORG_GRADLE_PROJECT_SONATYPE_USERNAME: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPE_USERNAME }} + ORG_GRADLE_PROJECT_SONATYPE_PASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPE_PASSWORD }} + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + fetch-tags: true + - name: Build and Publish NPM Package + uses: ./.github/actions/build-npm-package + with: + release-type: ${{ needs.determine_mode.outputs.release-type }} + gradle-cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} + + # ─── Publish bumped monorepo packages (main/stable push) ───────── + publish_bumped_packages: + needs: [determine_mode] + if: needs.determine_mode.outputs.mode == 'bumped-packages' + runs-on: ubuntu-latest + environment: npm-publish + # `id-token: write` is required so the npm CLI can mint the OIDC + # token that npm Trusted Publishing exchanges for a publish token. + permissions: + contents: read + id-token: write + steps: + - name: Checkout + uses: actions/checkout@v6 + - name: Setup node.js + uses: ./.github/actions/setup-node + with: + node-version: '24' + registry-url: 'https://registry.npmjs.org' + - name: Run Yarn Install + uses: ./.github/actions/yarn-install + - name: Build packages + run: yarn build + - name: Build types + run: yarn build-types --skip-snapshot + - name: Find and publish all bumped packages + run: node ./scripts/releases-ci/publish-updated-packages.js + + # ─── Release-only: post-publish steps ──────────────────────────── + post_publish: + runs-on: ubuntu-latest + needs: [determine_mode, publish_react_native] + if: needs.determine_mode.outputs.mode == 'release' + env: + REACT_NATIVE_BOT_GITHUB_TOKEN: ${{ secrets.REACT_NATIVE_BOT_GITHUB_TOKEN }} + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + fetch-tags: true + - name: Publish @react-native-community/template + id: publish-template-to-npm + uses: actions/github-script@v8 + with: + github-token: ${{ secrets.REACT_NATIVE_BOT_GITHUB_TOKEN }} + script: | + const {publishTemplate} = require('./.github/workflow-scripts/publishTemplate.js') + const version = "${{ github.ref_name }}" + const isDryRun = false + await publishTemplate(github, version, isDryRun); + - name: Wait for template to be published + timeout-minutes: 3 + uses: actions/github-script@v8 + with: + github-token: ${{ secrets.REACT_NATIVE_BOT_GITHUB_TOKEN }} + script: | + const {verifyPublishedTemplate, isLatest} = require('./.github/workflow-scripts/publishTemplate.js') + const version = "${{ github.ref_name }}" + await verifyPublishedTemplate(version, isLatest()); + - name: Update rn-diff-purge to generate upgrade-support diff + run: | + curl -X POST https://api.github.com/repos/react-native-community/rn-diff-purge/dispatches \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: Bearer $REACT_NATIVE_BOT_GITHUB_TOKEN" \ + -d "{\"event_type\": \"publish\", \"client_payload\": { \"version\": \"${{ github.ref_name }}\" }}" + - name: Verify Release is on NPM + timeout-minutes: 3 + uses: actions/github-script@v8 + with: + github-token: ${{ secrets.REACT_NATIVE_BOT_GITHUB_TOKEN }} + script: | + const {verifyReleaseOnNpm} = require('./.github/workflow-scripts/verifyReleaseOnNpm.js'); + const {isLatest} = require('./.github/workflow-scripts/publishTemplate.js'); + const version = "${{ github.ref_name }}"; + await verifyReleaseOnNpm(version, isLatest()); + - name: Verify that artifacts are on Maven + uses: actions/github-script@v8 + with: + script: | + const {verifyArtifactsAreOnMaven} = require('./.github/workflow-scripts/verifyArtifactsAreOnMaven.js'); + const version = "${{ github.ref_name }}"; + await verifyArtifactsAreOnMaven(version); + + # ─── Release-only: changelog, podfile bump, draft release ──────── + generate_changelog: + needs: [determine_mode, publish_react_native] + if: needs.determine_mode.outputs.mode == 'release' + uses: ./.github/workflows/generate-changelog.yml + secrets: inherit + + bump_podfile_lock: + needs: [determine_mode, publish_react_native] + if: needs.determine_mode.outputs.mode == 'release' + uses: ./.github/workflows/bump-podfile-lock.yml + secrets: inherit + + create_draft_release: + needs: [determine_mode, generate_changelog, set_hermes_version] + if: needs.determine_mode.outputs.mode == 'release' + uses: ./.github/workflows/create-draft-release.yml + secrets: inherit + with: + hermesVersion: ${{ needs.set_hermes_version.outputs.HERMES_VERSION }} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml deleted file mode 100644 index 887dd97f6f1..00000000000 --- a/.github/workflows/publish-release.yml +++ /dev/null @@ -1,151 +0,0 @@ -name: Publish Release -on: - push: - tags: - - "v0.*.*" # This should match v0.X.Y - - "v0.*.*-rc.*" # This should match v0.X.Y-RC.0 -jobs: - set_release_type: - runs-on: ubuntu-latest - if: github.repository == 'facebook/react-native' - outputs: - RELEASE_TYPE: ${{ steps.set_release_type.outputs.RELEASE_TYPE }} - env: - EVENT_NAME: ${{ github.event_name }} - REF: ${{ github.ref }} - steps: - - id: set_release_type - run: | - echo "Setting release type to release" - echo "RELEASE_TYPE=release" >> $GITHUB_OUTPUT - - set_hermes_versions: - runs-on: ubuntu-latest - if: github.repository == 'facebook/react-native' - outputs: - HERMES_VERSION: ${{ steps.set_hermes_versions.outputs.HERMES_VERSION }} - HERMES_V1_VERSION: ${{ steps.set_hermes_versions.outputs.HERMES_V1_VERSION }} - steps: - - name: Checkout - uses: actions/checkout@v6 - - id: set_hermes_versions - run: | - echo "Setting hermes versions to latest" - hermes_version=$(grep -oE 'HERMES_VERSION_NAME=([0-9]+\.[0-9]+\.[0-9]+)' packages/react-native/sdks/hermes-engine/version.properties | cut -d'=' -f2) - hermes_v1_version=$(grep -oE 'HERMES_V1_VERSION_NAME=([0-9]+\.[0-9]+\.[0-9]+)' packages/react-native/sdks/hermes-engine/version.properties | cut -d'=' -f2) - - echo "HERMES_VERSION=$hermes_version" >> $GITHUB_OUTPUT - echo "HERMES_V1_VERSION=$hermes_v1_version" >> $GITHUB_OUTPUT - - name: Print hermes versions - run: | - echo "HERMES_VERSION=${{ steps.set_hermes_versions.outputs.HERMES_VERSION }}" - echo "HERMES_V1_VERSION=${{ steps.set_hermes_versions.outputs.HERMES_V1_VERSION }}" - - prebuild_apple_dependencies: - if: github.repository == 'facebook/react-native' - uses: ./.github/workflows/prebuild-ios-dependencies.yml - secrets: inherit - - prebuild_react_native_core: - uses: ./.github/workflows/prebuild-ios-core.yml - secrets: inherit - needs: [prebuild_apple_dependencies] - - build_npm_package: - runs-on: 8-core-ubuntu - needs: - [ - set_release_type, - prebuild_apple_dependencies, - prebuild_react_native_core, - ] - container: - image: reactnativecommunity/react-native-android:latest - env: - TERM: "dumb" - # Set the encoding to resolve a known character encoding issue with decompressing tar.gz files in containers - # via Gradle: https://github.com/gradle/gradle/issues/23391#issuecomment-1878979127 - LC_ALL: C.UTF8 - GRADLE_OPTS: "-Dorg.gradle.daemon=false" - # By default we only build ARM64 to save time/resources. For release/nightlies, we override this value to build all archs. - ORG_GRADLE_PROJECT_reactNativeArchitectures: "arm64-v8a" - REACT_NATIVE_DOWNLOADS_DIR: /opt/react-native-downloads - env: - GHA_NPM_TOKEN: ${{ secrets.GHA_NPM_TOKEN }} - ORG_GRADLE_PROJECT_SIGNING_PWD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PWD }} - ORG_GRADLE_PROJECT_SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_KEY }} - ORG_GRADLE_PROJECT_SONATYPE_USERNAME: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPE_USERNAME }} - ORG_GRADLE_PROJECT_SONATYPE_PASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPE_PASSWORD }} - REACT_NATIVE_BOT_GITHUB_TOKEN: ${{ secrets.REACT_NATIVE_BOT_GITHUB_TOKEN }} - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - fetch-depth: 0 - fetch-tags: true - - name: Build and Publish NPM Package - uses: ./.github/actions/build-npm-package - with: - release-type: ${{ needs.set_release_type.outputs.RELEASE_TYPE }} - gha-npm-token: ${{ env.GHA_NPM_TOKEN }} - gradle-cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} - - name: Publish @react-native-community/template - id: publish-template-to-npm - uses: actions/github-script@v8 - with: - github-token: ${{ secrets.REACT_NATIVE_BOT_GITHUB_TOKEN }} - script: | - const {publishTemplate} = require('./.github/workflow-scripts/publishTemplate.js') - const version = "${{ github.ref_name }}" - const isDryRun = false - await publishTemplate(github, version, isDryRun); - - name: Wait for template to be published - timeout-minutes: 3 - uses: actions/github-script@v8 - with: - github-token: ${{ secrets.REACT_NATIVE_BOT_GITHUB_TOKEN }} - script: | - const {verifyPublishedTemplate, isLatest} = require('./.github/workflow-scripts/publishTemplate.js') - const version = "${{ github.ref_name }}" - await verifyPublishedTemplate(version, isLatest()); - - name: Update rn-diff-purge to generate upgrade-support diff - run: | - curl -X POST https://api.github.com/repos/react-native-community/rn-diff-purge/dispatches \ - -H "Accept: application/vnd.github.v3+json" \ - -H "Authorization: Bearer $REACT_NATIVE_BOT_GITHUB_TOKEN" \ - -d "{\"event_type\": \"publish\", \"client_payload\": { \"version\": \"${{ github.ref_name }}\" }}" - - name: Verify Release is on NPM - timeout-minutes: 3 - uses: actions/github-script@v8 - with: - github-token: ${{ secrets.REACT_NATIVE_BOT_GITHUB_TOKEN }} - script: | - const {verifyReleaseOnNpm} = require('./.github/workflow-scripts/verifyReleaseOnNpm.js'); - const {isLatest} = require('./.github/workflow-scripts/publishTemplate.js'); - const version = "${{ github.ref_name }}"; - await verifyReleaseOnNpm(version, isLatest()); - - name: Verify that artifacts are on Maven - uses: actions/github-script@v8 - with: - script: | - const {verifyArtifactsAreOnMaven} = require('./.github/workflow-scripts/verifyArtifactsAreOnMaven.js'); - const version = "${{ github.ref_name }}"; - await verifyArtifactsAreOnMaven(version); - - generate_changelog: - needs: build_npm_package - uses: ./.github/workflows/generate-changelog.yml - secrets: inherit - - bump_podfile_lock: - needs: build_npm_package - uses: ./.github/workflows/bump-podfile-lock.yml - secrets: inherit - - create_draft_release: - needs: [generate_changelog, set_hermes_versions] - uses: ./.github/workflows/create-draft-release.yml - secrets: inherit - with: - hermesVersion: ${{ needs.set_hermes_versions.outputs.HERMES_VERSION }} - hermesV1Version: ${{ needs.set_hermes_versions.outputs.HERMES_V1_VERSION }} diff --git a/.github/workflows/retry-workflow.yml b/.github/workflows/retry-workflow.yml index d943d657d82..ea6c17329a0 100644 --- a/.github/workflows/retry-workflow.yml +++ b/.github/workflows/retry-workflow.yml @@ -2,19 +2,19 @@ name: Retry workflow # Based on https://stackoverflow.com/a/78314483 on: - workflow_dispatch: - inputs: - run_id: - required: true + workflow_dispatch: + inputs: + run_id: + required: true jobs: - rerun: - runs-on: ubuntu-latest - if: github.repository == 'facebook/react-native' - steps: - - name: rerun ${{ inputs.run_id }} - env: - GH_REPO: ${{ github.repository }} - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh run watch ${{ inputs.run_id }} > /dev/null 2>&1 - gh run rerun ${{ inputs.run_id }} --failed + rerun: + runs-on: ubuntu-latest + if: github.repository == 'react/react-native' + steps: + - name: rerun ${{ inputs.run_id }} + env: + GH_REPO: ${{ github.repository }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh run watch ${{ inputs.run_id }} > /dev/null 2>&1 + gh run rerun ${{ inputs.run_id }} --failed diff --git a/.github/workflows/stale-bot.yml b/.github/workflows/stale-bot.yml index f4c2fa8d353..ab7ada8c9c5 100644 --- a/.github/workflows/stale-bot.yml +++ b/.github/workflows/stale-bot.yml @@ -1,11 +1,11 @@ name: Stale bot on: schedule: - - cron: "*/10 5 * * *" + - cron: '*/10 5 * * *' jobs: stale: runs-on: ubuntu-latest - if: github.repository == 'facebook/react-native' + if: github.repository == 'react/react-native' permissions: issues: write pull-requests: write @@ -22,7 +22,7 @@ jobs: exempt-pr-labels: 'Help Wanted :octocat:, Never gets stale' stale-asc: runs-on: ubuntu-latest - if: github.repository == 'facebook/react-native' + if: github.repository == 'react/react-native' permissions: issues: write pull-requests: write @@ -40,7 +40,7 @@ jobs: exempt-pr-labels: 'Help Wanted :octocat:, Never gets stale' stale-needs-author-feedback: runs-on: ubuntu-latest - if: github.repository == 'facebook/react-native' + if: github.repository == 'react/react-native' permissions: issues: write pull-requests: write @@ -54,11 +54,11 @@ jobs: stale-pr-message: "This PR is waiting for author's feedback since 24 days. Please provide the requested feedback or this will be closed in 7 days" close-issue-message: "This issue was closed because the author hasn't provided the requested feedback after 7 days." close-pr-message: "This PR was closed because the author hasn't provided the requested feedback after 7 days." - exempt-issue-labels: "Help Wanted :octocat:, Good first issue, Never gets stale, Issue: Author Provided Repro" - exempt-pr-labels: "Help Wanted :octocat:, Never gets stale" + exempt-issue-labels: 'Help Wanted :octocat:, Good first issue, Never gets stale, Issue: Author Provided Repro' + exempt-pr-labels: 'Help Wanted :octocat:, Never gets stale' stale-needs-author-feedback-asc: runs-on: ubuntu-latest - if: github.repository == 'facebook/react-native' + if: github.repository == 'react/react-native' permissions: issues: write pull-requests: write @@ -73,5 +73,5 @@ jobs: stale-pr-message: "This PR is waiting for author's feedback since 24 days. Please provide the requested feedback or this will be closed in 7 days" close-issue-message: "This issue was closed because the author hasn't provided the requested feedback after 7 days." close-pr-message: "This PR was closed because the author hasn't provided the requested feedback after 7 days." - exempt-issue-labels: "Help Wanted :octocat:, Good first issue, Never gets stale, Issue: Author Provided Repro" - exempt-pr-labels: "Help Wanted :octocat:, Never gets stale" + exempt-issue-labels: 'Help Wanted :octocat:, Good first issue, Never gets stale, Issue: Author Provided Repro' + exempt-pr-labels: 'Help Wanted :octocat:, Never gets stale' diff --git a/.github/workflows/test-all.yml b/.github/workflows/test-all.yml index 38507506de0..422a90c712a 100644 --- a/.github/workflows/test-all.yml +++ b/.github/workflows/test-all.yml @@ -6,7 +6,7 @@ on: push: branches: - main - - "*-stable" + - '*-stable' permissions: contents: read @@ -14,7 +14,7 @@ permissions: jobs: set_release_type: runs-on: ubuntu-latest - if: github.repository == 'facebook/react-native' + if: github.repository == 'react/react-native' outputs: RELEASE_TYPE: ${{ steps.set_release_type.outputs.RELEASE_TYPE }} env: @@ -38,7 +38,7 @@ jobs: check_code_changes: runs-on: ubuntu-latest - if: github.repository == 'facebook/react-native' + if: github.repository == 'react/react-native' outputs: any_code_change: ${{ steps.filter_exclusions.outputs.any_code_change == 'true' || github.event_name != 'pull_request' }} should_test_android: ${{ steps.filter_exclusions.outputs.should_test_android == 'true' || github.event_name != 'pull_request' }} @@ -99,22 +99,23 @@ jobs: prebuild_react_native_core: uses: ./.github/workflows/prebuild-ios-core.yml + if: ${{ needs.prebuild_apple_dependencies.result == 'success' }} with: - use-hermes-nightly: ${{ !endsWith(github.ref_name, '-stable') }} + use-hermes-prebuilt: ${{ !endsWith(github.ref_name, '-stable') }} secrets: inherit needs: [prebuild_apple_dependencies] test_ios_rntester_ruby_3_2_0: runs-on: macos-15 - needs: - [prebuild_apple_dependencies, prebuild_react_native_core] + needs: [prebuild_apple_dependencies, prebuild_react_native_core] + if: ${{ needs.prebuild_react_native_core.result == 'success' }} steps: - name: Checkout uses: actions/checkout@v6 - name: Run it uses: ./.github/actions/test-ios-rntester with: - ruby-version: "3.2.0" + ruby-version: '3.2.0' flavor: Debug test_ios_rntester_dynamic_frameworks: @@ -139,8 +140,8 @@ jobs: test_ios_rntester: runs-on: macos-15-large - needs: - [prebuild_apple_dependencies, prebuild_react_native_core] + needs: [prebuild_apple_dependencies, prebuild_react_native_core] + if: ${{ needs.prebuild_react_native_core.result == 'success' }} continue-on-error: true strategy: fail-fast: false @@ -158,6 +159,7 @@ jobs: test_e2e_ios_rntester: needs: test_ios_rntester + if: ${{ needs.test_ios_rntester.result == 'success' }} uses: ./.github/workflows/e2e-ios-rntester.yml secrets: inherit @@ -176,7 +178,8 @@ jobs: secrets: inherit test_e2e_ios_templateapp: - needs: [build_npm_package, prebuild_apple_dependencies] + needs: [build_npm_package, prebuild_apple_dependencies, check_code_changes] + if: needs.check_code_changes.outputs.should_test_ios == 'true' uses: ./.github/workflows/e2e-ios-templateapp.yml secrets: inherit @@ -195,7 +198,8 @@ jobs: secrets: inherit test_e2e_android_templateapp: - needs: build_npm_package + needs: [build_npm_package, build_android] + if: ${{ always() && needs.build_android.result == 'success' && needs.build_npm_package.result == 'success' }} uses: ./.github/workflows/e2e-android-templateapp.yml secrets: inherit @@ -223,8 +227,8 @@ jobs: # Set the encoding to resolve a known character encoding issue with decompressing tar.gz files in containers # via Gradle: https://github.com/gradle/gradle/issues/23391#issuecomment-1878979127 LC_ALL: C.UTF8 - TERM: "dumb" - GRADLE_OPTS: "-Dorg.gradle.daemon=false" + TERM: 'dumb' + GRADLE_OPTS: '-Dorg.gradle.daemon=false' ORG_GRADLE_PROJECT_SIGNING_PWD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PWD }} ORG_GRADLE_PROJECT_SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_KEY }} REACT_NATIVE_DOWNLOADS_DIR: /opt/react-native-downloads @@ -268,8 +272,8 @@ jobs: # Set the encoding to resolve a known character encoding issue with decompressing tar.gz files in containers # via Gradle: https://github.com/gradle/gradle/issues/23391#issuecomment-1878979127 LC_ALL: C.UTF8 - TERM: "dumb" - GRADLE_OPTS: "-Dorg.gradle.daemon=false" + TERM: 'dumb' + GRADLE_OPTS: '-Dorg.gradle.daemon=false' ORG_GRADLE_PROJECT_SIGNING_PWD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PWD }} ORG_GRADLE_PROJECT_SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_KEY }} REACT_NATIVE_DOWNLOADS_DIR: /opt/react-native-downloads @@ -284,6 +288,7 @@ jobs: test_e2e_android_rntester: needs: build_android + if: ${{ needs.build_android.result == 'success' }} uses: ./.github/workflows/e2e-android-rntester.yml secrets: inherit @@ -306,18 +311,23 @@ jobs: needs: [ set_release_type, + check_code_changes, build_android, prebuild_apple_dependencies, prebuild_react_native_core, ] + if: | + always() && + !contains(needs.*.result, 'failure') && + !contains(needs.*.result, 'cancelled') container: image: reactnativecommunity/react-native-android:latest env: - TERM: "dumb" + TERM: 'dumb' # Set the encoding to resolve a known character encoding issue with decompressing tar.gz files in containers # via Gradle: https://github.com/gradle/gradle/issues/23391#issuecomment-1878979127 LC_ALL: C.UTF8 - GRADLE_OPTS: "-Dorg.gradle.daemon=false" + GRADLE_OPTS: '-Dorg.gradle.daemon=false' REACT_NATIVE_DOWNLOADS_DIR: /opt/react-native-downloads steps: - name: Checkout @@ -327,10 +337,12 @@ jobs: with: release-type: ${{ needs.set_release_type.outputs.RELEASE_TYPE }} gradle-cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} + skip-apple-prebuilts: ${{ needs.check_code_changes.outputs.should_test_ios == 'false' }} test_android_helloworld: runs-on: 4-core-ubuntu - needs: build_npm_package + needs: [build_npm_package, build_android] + if: ${{ always() && needs.build_android.result == 'success' && needs.build_npm_package.result == 'success' }} container: image: reactnativecommunity/react-native-android:latest env: @@ -338,9 +350,9 @@ jobs: # via Gradle: https://github.com/gradle/gradle/issues/23391#issuecomment-1878979127 LC_ALL: C.UTF8 YARN_ENABLE_IMMUTABLE_INSTALLS: false - TERM: "dumb" - GRADLE_OPTS: "-Dorg.gradle.daemon=false" - TARGET_ARCHITECTURE: "arm64-v8a" + TERM: 'dumb' + GRADLE_OPTS: '-Dorg.gradle.daemon=false' + TARGET_ARCHITECTURE: 'arm64-v8a' REACT_NATIVE_DOWNLOADS_DIR: /opt/react-native-downloads continue-on-error: true strategy: @@ -368,10 +380,10 @@ jobs: cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} - name: Run yarn install uses: ./.github/actions/yarn-install - - name: Set nightly Hermes versions + - name: Set Hermes prebuilt version shell: bash run: | - node ./scripts/releases/use-hermes-nightly.js + node ./scripts/releases/use-hermes-prebuilt.js - name: Run yarn install again, with the correct hermes version uses: ./.github/actions/yarn-install - name: Prepare the Helloworld application @@ -396,6 +408,7 @@ jobs: test_ios_helloworld_with_ruby_3_2_0: runs-on: macos-15 needs: [prebuild_apple_dependencies, prebuild_react_native_core] + if: ${{ needs.prebuild_react_native_core.result == 'success' }} env: PROJECT_NAME: iOSTemplateProject YARN_ENABLE_IMMUTABLE_INSTALLS: false @@ -410,6 +423,7 @@ jobs: test_ios_helloworld: runs-on: macos-15 needs: [prebuild_apple_dependencies, prebuild_react_native_core] + if: ${{ needs.prebuild_react_native_core.result == 'success' }} strategy: matrix: flavor: [Debug, Release] @@ -431,10 +445,12 @@ jobs: lint: runs-on: ubuntu-latest - if: github.repository == 'facebook/react-native' + if: github.repository == 'react/react-native' steps: - name: Checkout uses: actions/checkout@v6 + - name: Setup Node.js + uses: ./.github/actions/setup-node - name: Install dependencies uses: ./.github/actions/yarn-install - name: Lint file structure @@ -466,7 +482,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: ["24", "22", "20.19.4"] + node-version: ['24', '22.13.0'] steps: - name: Checkout uses: actions/checkout@v6 @@ -494,7 +510,7 @@ jobs: run: yarn test-generated-typescript build_debugger_shell: - runs-on: ubuntu-latest + runs-on: macos-26 needs: check_code_changes if: needs.check_code_changes.outputs.debugger_shell == 'true' steps: @@ -510,4 +526,3 @@ jobs: - name: Verify debugger-shell build shell: bash run: node scripts/debugger-shell/build-binary.js - diff --git a/.github/workflows/validate-cxx-api-snapshots.yml b/.github/workflows/validate-cxx-api-snapshots.yml index 4d8230a1e66..e0c7caa2f5d 100644 --- a/.github/workflows/validate-cxx-api-snapshots.yml +++ b/.github/workflows/validate-cxx-api-snapshots.yml @@ -4,31 +4,34 @@ on: workflow_dispatch: pull_request: paths: - - "packages/react-native/ReactCommon/**" - - "packages/react-native/ReactAndroid/**" - - "packages/react-native/React/**" - - "packages/react-native/ReactApple/**" - - "packages/react-native/Libraries/**" - - "scripts/cxx-api/**" + - 'packages/react-native/ReactCommon/**' + - 'packages/react-native/ReactAndroid/**' + - 'packages/react-native/React/**' + - 'packages/react-native/ReactApple/**' + - 'packages/react-native/Libraries/**' + - 'scripts/cxx-api/**' push: branches: - main - - "*-stable" + - '*-stable' paths: - - "packages/react-native/ReactCommon/**" - - "packages/react-native/ReactAndroid/**" - - "packages/react-native/React/**" - - "packages/react-native/ReactApple/**" - - "packages/react-native/Libraries/**" - - "scripts/cxx-api/**" + - 'packages/react-native/ReactCommon/**' + - 'packages/react-native/ReactAndroid/**' + - 'packages/react-native/React/**' + - 'packages/react-native/ReactApple/**' + - 'packages/react-native/Libraries/**' + - 'scripts/cxx-api/**' env: - DOXYGEN_VERSION: "1.16.1" + DOXYGEN_VERSION: '1.16.1' + +permissions: + contents: read jobs: validate_cxx_api_snapshots: runs-on: ubuntu-latest - if: github.repository == 'facebook/react-native' + if: github.repository == 'react/react-native' steps: - name: Checkout uses: actions/checkout@v6 @@ -67,7 +70,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.12" + python-version: '3.12' - name: Install Python dependencies shell: bash run: pip install doxmlparser natsort pyyaml diff --git a/.github/workflows/validate-dotslash-artifacts.yml b/.github/workflows/validate-dotslash-artifacts.yml index c1baac14097..2970d432905 100644 --- a/.github/workflows/validate-dotslash-artifacts.yml +++ b/.github/workflows/validate-dotslash-artifacts.yml @@ -9,7 +9,7 @@ on: - main paths: - packages/debugger-shell/bin/react-native-devtools - - "scripts/releases/**" + - 'scripts/releases/**' - package.json - yarn.lock pull_request: @@ -17,17 +17,17 @@ on: - main paths: - packages/debugger-shell/bin/react-native-devtools - - "scripts/releases/**" + - 'scripts/releases/**' - package.json - yarn.lock # Same time as the nightly build: 2:15 AM UTC schedule: - - cron: "15 2 * * *" + - cron: '15 2 * * *' jobs: validate-dotslash-artifacts: runs-on: ubuntu-latest - if: github.repository == 'facebook/react-native' + if: github.repository == 'react/react-native' steps: - name: Checkout repository uses: actions/checkout@v6 diff --git a/.prettierrc.js b/.prettierrc.js index e0100262523..dca775b0703 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -42,5 +42,11 @@ module.exports = { requirePragma: false, }, }, + { + files: ['*.yml', '*.yaml'], + options: { + requirePragma: false, + }, + }, ], }; diff --git a/.yarnrc.yml b/.yarnrc.yml index da65d046291..74c435d9653 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -13,36 +13,36 @@ yarnPath: .yarn/releases/yarn-4.12.0.cjs # packageExtensions, so no @react-native/* deps belong here (they must stay in a # manifest to remain under release-branch version-pinning). packageExtensions: - "@react-native/codegen@*": + '@react-native/codegen@*': dependencies: - "@babel/parser": "^7.25.2" - "@react-native/babel-plugin-codegen@*": + '@babel/parser': '^7.25.2' + '@react-native/babel-plugin-codegen@*': dependencies: - "@babel/plugin-syntax-flow": "^7.25.0" - "@react-native/core-cli-utils@*": + '@babel/plugin-syntax-flow': '^7.25.0' + '@react-native/core-cli-utils@*': dependencies: - "metro": "^0.83.3" - "@react-native/metro-babel-transformer@*": + 'metro': '^0.83.3' + '@react-native/metro-babel-transformer@*': dependencies: - "metro-babel-transformer": "^0.83.3" - "@react-native/compatibility-check@*": + 'metro-babel-transformer': '^0.83.3' + '@react-native/compatibility-check@*': dependencies: - "invariant": "^2.2.4" - "@react-native/tester@*": + 'invariant': '^2.2.4' + '@react-native/tester@*': dependencies: - "metro-config": "^0.83.3" - "@react-native/eslint-plugin-monorepo@*": + 'metro-config': '^0.83.3' + '@react-native/eslint-plugin-monorepo@*': dependencies: - "hermes-estree": "0.29.1" - "@react-native/fantom@*": + 'hermes-estree': '0.29.1' + '@react-native/fantom@*': dependencies: - "istanbul-lib-coverage": "^3.2.0" - "jest-docblock": "^29.7.0" - "jest-message-util": "^29.7.0" - "metro": "^0.84.3" - "metro-config": "^0.84.3" - "source-map": "^0.6.1" - "@typescript-eslint/type-utils@*": + 'istanbul-lib-coverage': '^3.2.0' + 'jest-docblock': '^29.7.0' + 'jest-message-util': '^29.7.0' + 'metro': '^0.84.3' + 'metro-config': '^0.84.3' + 'source-map': '^0.6.1' + '@typescript-eslint/type-utils@*': dependencies: - "@typescript-eslint/types": "^8.36.0" + '@typescript-eslint/types': '^8.36.0' # macOS] diff --git a/CHANGELOG-0.7x.md b/CHANGELOG-0.7x.md index 6058d5ccfe2..0172251963b 100644 --- a/CHANGELOG-0.7x.md +++ b/CHANGELOG-0.7x.md @@ -3061,7 +3061,7 @@ This file contains all changelogs for latest releases, from 0.70.0 onward. Pleas - Introducing getModule method into ReactPackage interface, defaulting to null. This method will be used in the Stable API of React Native ([da8616ec69](https://github.com/facebook/react-native/commit/da8616ec69a12a90b973fea1d8345c7517408a73) by [@mdvacca](https://github.com/mdvacca)) - Update Java tests to Kotlin for the referenced file ([3dbb759506](https://github.com/facebook/react-native/commit/3dbb7595060b1454fbd8ec80cb851fe6af8f41da) by [@stewartsum](https://github.com/stewartsum)) - Deprecate JSCJavaScriptExecutorFactory and JSCJavaScriptExecutor, use com.facebook.react.jscexecutor instead ([0cac88fa65](https://github.com/facebook/react-native/commit/0cac88fa65fd33dd6164ed2d6c4fbcdfa47f9e82) by [@mdvacca](https://github.com/mdvacca)) -- Throw ReactNoCrashSoftException when handle memeory pressure to avoid crash ([fa9ea8326e](https://github.com/facebook/react-native/commit/fa9ea8326e693b258e303aee6f59b049dcecea31)) +- Throw ReactNoCrashSoftException when handle memory pressure to avoid crash ([fa9ea8326e](https://github.com/facebook/react-native/commit/fa9ea8326e693b258e303aee6f59b049dcecea31)) - Throw Error in dispatchViewManagerCommand when non-numeric tag is passed for easier debugging ([0519c11acd](https://github.com/facebook/react-native/commit/0519c11acd0c347db378bbc9238c7dabfd38f6fa) by [@hsource](https://github.com/hsource)) - Moved ReactFontManager to a common package ([7341f9abdc](https://github.com/facebook/react-native/commit/7341f9abdcfbf5cf4dd5f8cda49151b28ae246bf) by [@fkgozali](https://github.com/fkgozali)) - Throw Error in dispatchViewManagerCommand when non-numeric tag is passed for easier debugging ([ff1972daba](https://github.com/facebook/react-native/commit/ff1972dabafbbfc18203464b452e0d5b796cdcf6) by [@hsource](https://github.com/hsource)) @@ -3273,7 +3273,7 @@ This file contains all changelogs for latest releases, from 0.70.0 onward. Pleas - Fix the default trait collection to always return the value of the window ([94fea182d6](https://github.com/facebook/react-native/commit/94fea182d6cf19e96a8a87760017bd69ad0a9e0c) by [@alanjhughes](https://github.com/alanjhughes)) - Fix Alert userInterfaceStyle having no effect ([0e150d071e](https://github.com/facebook/react-native/commit/0e150d071e66368e134566697f0f9d99c64d35c4) by [@zhongwuzw](https://github.com/zhongwuzw)) - Properly escape URLs ([5e983d51d8](https://github.com/facebook/react-native/commit/5e983d51d8bc2abded5659a77808542c6dc1377a) by [@cipolleschi](https://github.com/cipolleschi)) -- Handle doulbe `#` and partially escaped urls ([2b4e1f5ece](https://github.com/facebook/react-native/commit/2b4e1f5ece7d160935b19d4862af8706a44cee59) by [@cipolleschi](https://github.com/cipolleschi)) +- Handle double `#` and partially escaped urls ([2b4e1f5ece](https://github.com/facebook/react-native/commit/2b4e1f5ece7d160935b19d4862af8706a44cee59) by [@cipolleschi](https://github.com/cipolleschi)) - RNTester's PROJECT_ROOT points to `packages/rn-tester` ([cd30bc3888](https://github.com/facebook/react-native/commit/cd30bc3888bac500e9ecc7e9be051d0bc3659c2f) by [@dmytrorykun](https://github.com/dmytrorykun)) - Fix inverted `contentOffset` in scroll events in RTL ([4f8a8ce316](https://github.com/facebook/react-native/commit/4f8a8ce316494db99b19f6c8db6b0c1e7b6500d9) by [@NickGerleman](https://github.com/NickGerleman)) - Fix bad comparison in RCTScrollViewComponentView RTL ([65b7680720](https://github.com/facebook/react-native/commit/65b7680720435c0d864df9c121c151b60bee08ad) by [@NickGerleman](https://github.com/NickGerleman)) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e1f6978618..eadd5d08af6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,179 @@ # Changelog +## v0.86.0 + +### Added + +- **Accessibility**: AccessibilityInfo: added missing tests for Promise based methods, align null checks and error message code style ([8fc503d780](https://github.com/facebook/react-native/commit/8fc503d7806ff83c6001cc4e0fe7b8b85a804245) by [@chicio](https://github.com/chicio)) +- **Animated**: Suspense tests for the Animation Backend ([8101fc776d](https://github.com/facebook/react-native/commit/8101fc776d842b8b92a516f74831e2056bc34aa6) by Bartlomiej Bloniarz) +- **Animated**: Use weak_ptr when calling pullAnimationMutations in a callback scheduled with the Animation Backend ([42d98b5830](https://github.com/facebook/react-native/commit/42d98b58302dafb484a494ab4fc199f88f215c1d) by Bartlomiej Bloniarz) +- **Dev Server**: Call methods on `global.__BUNDLE_LOADER_REPORTER__`, if given, during dev-mode bundler loads from Metro ([1e0c1f886b](https://github.com/facebook/react-native/commit/1e0c1f886ba9de7541eeb753091715011c515c00) by [@robhogan](https://github.com/robhogan)) +- **React Native DevTools**: Add new custom track for React Native Renderer operations in React Native DevTools performance traces ([0b1def9ce5](https://github.com/facebook/react-native/commit/0b1def9ce5f6f3ea66d193fd254cfcb4c16d6e5a), [29bc8f1acb](https://github.com/facebook/react-native/commit/29bc8f1acb2cc9a4016c4bdf032f5b53474558bf), [9130df6773](https://github.com/facebook/react-native/commit/9130df67735f01c385296e155300dd8afebae6be), [c26a4875ea](https://github.com/facebook/react-native/commit/c26a4875eaaf3dcf9a6f28ac87df0a07a1cf4b37) by [@rubennorte](https://github.com/rubennorte)) +- **React Native DevTools**: Add support for light/dark mode emulation via `Emulation.setEmulatedMedia` ([11a5432cb2](https://github.com/facebook/react-native/commit/11a5432cb283b6e7b59bf785602fff9082af5bff) by [@huntie](https://github.com/huntie)) +- **Runtime**: Add `unstable_getViewTransitionInstance` to UIManagerBinding ([d8820a403b](https://github.com/facebook/react-native/commit/d8820a403bb7241f4d2dd4b1006079af049d68e3) by [@zeyap](https://github.com/zeyap)) +- **Runtime**: Add UIManagerViewTransitionDelegate interface and View Transition APIs ([cdad57d8f1](https://github.com/facebook/react-native/commit/cdad57d8f1a326f2ce164161e1d28ff082fd68b4) by [@zeyap](https://github.com/zeyap)) +- **Runtime**: Add viewport size to `LayoutContext` ([d38c756529](https://github.com/facebook/react-native/commit/d38c756529d4c1076d0646dc75643b78ba2bcaaa) by [@paradowstack](https://github.com/paradowstack)) +- **Runtime**: ViewTransitionModule for React Native View Transitions ([f81f73e5a0](https://github.com/facebook/react-native/commit/f81f73e5a02e792cfaf2c010dea2ddfb20cb3e7f) by [@zeyap](https://github.com/zeyap)) +- **Text**: Add E2E tests for Text `boxShadow` and textWithLinkRole ([b172f46f0d](https://github.com/facebook/react-native/commit/b172f46f0d0413dcaedc8c901d79e3932cb26589) by [@NickGerleman](https://github.com/NickGerleman)) +- **View**: Add E2E tests for Filter blur-with-overflow, drop-shadow, and animated-blur ([66975aa3d5](https://github.com/facebook/react-native/commit/66975aa3d52a05189b07f8f22eec86dbbfab0240) by [@NickGerleman](https://github.com/NickGerleman)) +- **View**: Add `mixBlendMode` plus-lighter ([aaa0684045](https://github.com/facebook/react-native/commit/aaa068404504076155ec4d4f11a1d246aef763f5) by [@bigcupcoffee](https://github.com/bigcupcoffee)) + +#### Android specific + +- **Events**: Channel play/pause hardware events. ([5087aa0dba](https://github.com/facebook/react-native/commit/5087aa0dba4e06986b98007bc33ee16af11f9358) by Nishil Shah) +- **Events**: Pass event object to `BackHandler` `hardwareBackPress` events to access timeStamp from native event. ([bf277cb6d4](https://github.com/facebook/react-native/commit/bf277cb6d4b6f53227717e8888a4d9701d8a3449) by [@rubennorte](https://github.com/rubennorte)) +- **LogBox**: Allow LogBox notification toasts and inspector overlay to be dismissed via Android back button ([6530092162](https://github.com/facebook/react-native/commit/6530092162b9996b2c53cd5fd76b9c9a6520e57f) by [@Abbondanzo](https://github.com/Abbondanzo)) +- **Pressable**: `android_ripple` now accepts `PlatformColor` ([60d8b282ea](https://github.com/facebook/react-native/commit/60d8b282eaf5bd2803fdaa838520f6ff41cc6737) by [@adrcotfas](https://github.com/adrcotfas)) +- **Runtime**: Add `ExtraWindowEventListener` interface to allow native modules to react to window creation / destruction (e.g. Modal dialogs) ([58fec1fa8d](https://github.com/facebook/react-native/commit/58fec1fa8d351c40a68cb0da807c49c7c2c96a80) by [@zoontek](https://github.com/zoontek)) +- **Runtime**: Add test for synchronous mount props override behavior ([a25e0fdddf](https://github.com/facebook/react-native/commit/a25e0fdddf0ab77c798dd00f8c88e842ee72c955) by [@zeyap](https://github.com/zeyap)) +- **Text**: Add E2E tests for Text visual examples (batch 1) ([b53b228673](https://github.com/facebook/react-native/commit/b53b228673e0b833cae7718bbb778b2ecaec5d76) by [@NickGerleman](https://github.com/NickGerleman)) +- **Text**: Add E2E tests for Text visual examples (batch 2) ([d5a271dd05](https://github.com/facebook/react-native/commit/d5a271dd0599caff85652ce0581e3026da470065) by [@NickGerleman](https://github.com/NickGerleman)) +- **TextInput**: Add extended Android `TextInput` `autoComplete` autofill hint support and bump `androidx.autofill` to `1.3.0`. ([ac598aff2b](https://github.com/facebook/react-native/commit/ac598aff2b388d4fac9f42a2e2cef73542d2531d) by [@radko93](https://github.com/radko93)) +- **View**: Add E2E tests for Filter static visual examples ([d18d9a4046](https://github.com/facebook/react-native/commit/d18d9a4046e25b60a2054462d242663a8daa2589) by [@NickGerleman](https://github.com/NickGerleman)) + +#### iOS specific + +- **Build**: Add privacy manifest to declare the use of `mach_absolute_time()` API in the React-timing module ([2ae6c8940d](https://github.com/facebook/react-native/commit/2ae6c8940dda8e7523d5f7f6ffb6c1a3b361a19e) by [@rubennorte](https://github.com/rubennorte)) +- **Dev Server**: Introduce `isPackagerRunningAsync` that does not block the UI thread waiting on the request to DevServer ("Packager") validating that it is running ([dd056d9b4c](https://github.com/facebook/react-native/commit/dd056d9b4c007f7e67f5ac3ad2c537e635d4a509) by [@vzaidman](https://github.com/vzaidman)) +- **Image**: Add E2E tests for Image visual examples ([48b6f2dcc9](https://github.com/facebook/react-native/commit/48b6f2dcc93f28801338fc79dff8b527b15d5f92) by [@NickGerleman](https://github.com/NickGerleman)) +- **Networking**: Add `RCTSetCustomMultipartDataTaskRequestInterceptor` API to allow custom modification of multipart data task requests before they are sent ([59b8886bb8](https://github.com/facebook/react-native/commit/59b8886bb85f3c4c4b4ef55536d8ffeb4e5b3102) by [@cipolleschi](https://github.com/cipolleschi)) + +### Changed + +- **Build**: Update Metro to ^0.84.2 ([57d4b6b350](https://github.com/facebook/react-native/commit/57d4b6b350dc0e8f2a89e4f5f570ed3452551d85) by [@robhogan](https://github.com/robhogan)) +- **Dev Server**: LoadBundleFromServer test: remove legacy Jest timers, increased coverage and improved assertions ([a19d98e542](https://github.com/facebook/react-native/commit/a19d98e5428f710716486b064c3b0589355c5641) by [@chicio](https://github.com/chicio)) +- **Hermes**: Bump hermes-compiler dependency ([031168013e](https://github.com/facebook/react-native/commit/031168013e8faab6f08b822a69f39d2df3fc78ff) by [@robhogan](https://github.com/robhogan)) +- **Hermes**: Bump hermes version ([89a2a20739](https://github.com/facebook/react-native/commit/89a2a20739ceda065cdc605397bfe9c178403b4a) by [@robhogan](https://github.com/robhogan)) +- **LogBox**: Migrated `LogBoxData` tests to Jest modern timers ([dea2136a49](https://github.com/facebook/react-native/commit/dea2136a493453bcdc5e8c4b6950e9d6338f1445) by [@chicio](https://github.com/chicio)) +- **React**: Make React Native consume 250829098.0.11 ([26953d8343](https://github.com/facebook/react-native/commit/26953d8343abb2ea05fe86857c2016a4fadb48b7) by [@alanleedev](https://github.com/alanleedev)) +- **React Native DevTools**: Derive WebSocket scheme and port from dev server URL for React DevTools connections, supporting HTTPS dev servers ([ca4173701d](https://github.com/facebook/react-native/commit/ca4173701d4b398a3b43650d019ffc75fb1d21a7) by [@cipolleschi](https://github.com/cipolleschi)) +- **Runtime**: Measure node instance during `applyViewTransitionName` ([d17ecca3c9](https://github.com/facebook/react-native/commit/d17ecca3c90c4ddd3922e3661ece03ac2433fcff) by [@zeyap](https://github.com/zeyap)) +- **Runtime**: Move ViewTransition APIs not for react reconciler to its own TurboModule ([0e65df9152](https://github.com/facebook/react-native/commit/0e65df9152d940a52d6dc4704cb60b644e18fb23) by [@zeyap](https://github.com/zeyap)) +- **Runtime**: Setting `reportErrorsAsExceptions` to anything other than `false` no longer does anything. (Previously, setting it to a falsey value would be similar to setting it to `false`.) ([f0748a6387](https://github.com/facebook/react-native/commit/f0748a6387c9bb04eed38d03f00f36d2985b2031) by [@yungsters](https://github.com/yungsters)) +- **Runtime**: Use `getNullableViewState` in `SurfaceMountingManager::updateProps` and log a soft exception instead ([bbf7004935](https://github.com/facebook/react-native/commit/bbf7004935ec3ed25e0270c60889f118ba170495) by Bartlomiej Bloniarz) +- **TurboModules**: Move `DefaultTurboModules::get` to end of `TurboModuleManager` ([6b32971f46](https://github.com/facebook/react-native/commit/6b32971f46201b08004736dad9689d71eba0712f) by [@zeyap](https://github.com/zeyap)) + +#### Android specific + +- **Animated**: `AnimationBackendChoreographer` doesn't guard the `ReactChoreographer` post/remove with `synchronized(paused)` ([22d327595d](https://github.com/facebook/react-native/commit/22d327595dafe1913092a911c8f0ff78e7ee5b08) by Bartlomiej Bloniarz) +- **Build**: Changed foojay-resolver-convention version to 1.0.0 ([664acaf441](https://github.com/facebook/react-native/commit/664acaf44188eaac8dbcadf7686dba397ffc035e) by [@TheNerdGuyLulu](https://github.com/TheNerdGuyLulu)) +- **NavigationBar**: Respect theme `enforceNavigationBarContrast` attribute ([dfddcc9796](https://github.com/facebook/react-native/commit/dfddcc97969fb4ea5728c2bcc965a8a6126d03ba) by [@zoontek](https://github.com/zoontek)) + +#### iOS specific + +- **Build**: Cache prebuilt iOS binaries ([9a0b05b8d0](https://github.com/facebook/react-native/commit/9a0b05b8d07f3468a08077119189406d56a230ba) by [@cipolleschi](https://github.com/cipolleschi)) +- **Networking**: Add `RCTHTTPRequestInterceptor` for selective HTTP request modification ([0c7d780aa0](https://github.com/facebook/react-native/commit/0c7d780aa03e7a3fccd24553fdb59ce3219139b2) by [@cipolleschi](https://github.com/cipolleschi)) +- **Networking**: Add `SRWebSocketProvider` for selective WebSocket header injection ([f3aa8c21d1](https://github.com/facebook/react-native/commit/f3aa8c21d1349b3c968c601ef89850427dfdcf64) by [@cipolleschi](https://github.com/cipolleschi)) +- **React Native DevTools**: Derive inspector and debugger URL scheme from bundle URL to support HTTPS dev servers ([36dfcef023](https://github.com/facebook/react-native/commit/36dfcef023549ac2795fbd42467594f056f2630a) by [@cipolleschi](https://github.com/cipolleschi)) + +### Deprecated + +- **JavaScript API**: Deprecated second argument for hook function passed to `AppRegistry.setComponentProviderInstrumentationHook` as it's not usable by apps. Passed stub that warns when used. ([64116117c0](https://github.com/facebook/react-native/commit/64116117c02d3b3d8026ee2ac85d03fc8370fbcd) by [@rubennorte](https://github.com/rubennorte)) + +### Fixed + +- **Accessibility**: Fix `AccessibilityInfo.isDarkerSystemColorsEnabled` unresolved (never ending) promise ([8fbf2fab47](https://github.com/facebook/react-native/commit/8fbf2fab47b9675f99fed7ee881636da5333bf55) by [@chicio](https://github.com/chicio)) +- **Accessibility**: Fix `AccessibilityInfo.isHighTextContrastEnabled` unresolved (never ending) promise ([5c9a5717c1](https://github.com/facebook/react-native/commit/5c9a5717c1a4d035a5059c3b6d53beb2a2b95ad6) by [@chicio](https://github.com/chicio)) +- **Accessibility**: Fix `AccessibilityInfo.prefersCrossFadeTransitions` unresolved (never ending) promise ([57c073f0d7](https://github.com/facebook/react-native/commit/57c073f0d728575a43039e413ac5f0d6958e7e2f) by [@chicio](https://github.com/chicio)) +- **Animated**: Fix 1-frame latency in C++ `NativeAnimatedNodesManager` for event-driven animations by processing the animation graph synchronously on every scroll event, matching the Java implementation behavior ([0517fb552d](https://github.com/facebook/react-native/commit/0517fb552d58d1e652724798be4a963fb9918206) by Mark Verlingieri) +- **Animated**: Fix typo "interpolation" to "interpolate" and add missing space in `AnimatedInterpolation` error messages ([a8b39a237c](https://github.com/facebook/react-native/commit/a8b39a237c2459a774e4ad9f4a514d0941b03169) by [@Nedunchezhiyan-M](https://github.com/Nedunchezhiyan-M)) +- **Babel**: Prevent errors by handling contexts where the Babel API object is not provided ([63796a9160](https://github.com/facebook/react-native/commit/63796a9160f2da0cda438c52c6206c158adfc12a) by On Saint Tiong) +- **Build**: Fix incorrect package name and output filename in `scripts/js-api/README.md` ([d823f5b1cc](https://github.com/facebook/react-native/commit/d823f5b1cc4b26d1844058a7db01f3d7227f1afd) by [@Nedunchezhiyan-M](https://github.com/Nedunchezhiyan-M)) +- **Codegen**: Respect enum values for TurboModule c++ codegen ([56129fedcb](https://github.com/facebook/react-native/commit/56129fedcb88d3b9e9a0e987a0562142bedfcd91) by [@akawrykow](https://github.com/akawrykow)) +- **Documentation**: Correct the v0.85.0 changelog entry for the removal of `StyleSheet.absoluteFillObject`. ([fdbe0d0b63](https://github.com/facebook/react-native/commit/fdbe0d0b63e9c69b6375aafca6361107500a6311) by [@jingjing2222](https://github.com/jingjing2222)) +- **Modal**: Forward `style` prop to Modal's inner container View with correct precedence so consumer styles are applied without overriding `transparent` or `backdropColor` ([f8fa76f94c](https://github.com/facebook/react-native/commit/f8fa76f94c37a97e0479f55e9945e1dd111a5567) by [@AnuMessi10](https://github.com/AnuMessi10)) +- **React Native DevTools**: Add exponential backoff and exception handling to `InspectorPackagerConnection` reconnect loop to prevent heap fragmentation crashes ([32d3eafdc4](https://github.com/facebook/react-native/commit/32d3eafdc438eab517e84845ea9efe5165023c56) by [@shubhamksavita](https://github.com/shubhamksavita)) +- **React Native DevTools**: Bump Hermes V1 to 250829098.0.14 to fix missing JavaScript samples in the Performance panel timeline on Apple platforms ([cd5b3b5fa5](https://github.com/facebook/react-native/commit/cd5b3b5fa576366723c4f73b98736c033856ac03) by [@fabriziocucci](https://github.com/fabriziocucci)) +- **Runtime**: Add missing `VIEW_EVENT_CASE` entries in `BaseViewProps::setProp` for Click, PointerDown, PointerUp, GotPointerCapture, LostPointerCapture and their Capture variants (offsets 30-37) ([8d5482dd94](https://github.com/facebook/react-native/commit/8d5482dd9452ee149b65c0ca5cffb1c96af29fc9) by Harini Malothu) +- **Runtime**: Fix crash in Performance API when sorting entries ([81c99682a4](https://github.com/facebook/react-native/commit/81c99682a49b57b90aa36db801ff578f53930a95) by [@Abbondanzo](https://github.com/Abbondanzo)) +- **Runtime**: Fix event timestamp injection overriding native timestamps with lowercase property name ([68debb27ff](https://github.com/facebook/react-native/commit/68debb27ff17ca3ca17404c5326afce888c444a2) by [@fkgozali](https://github.com/fkgozali)) +- **Runtime**: Fix event timestamp propagation from host platforms to JS ([aa51746b87](https://github.com/facebook/react-native/commit/aa51746b87e5e04924e3fcfaf459f9bb1c9ca38e) by [@rubennorte](https://github.com/rubennorte)) +- **Runtime**: Link VirtualView component ([de78906391](https://github.com/facebook/react-native/commit/de78906391935f46e84a198f5f5d64fc40826c44) by [@j-piasecki](https://github.com/j-piasecki)) +- **Runtime**: `PerformanceObserver`: `observe({type: 'event'})` now correctly defaults `durationThreshold` to 104ms per the W3C Event Timing spec instead of reporting all events. ([65c561e195](https://github.com/facebook/react-native/commit/65c561e195285e1f1ea59bec72899dabf08502a6) by [@rubennorte](https://github.com/rubennorte)) +- **Runtime**: Prevent Scheduler use-after-free crash when surfaces tear down with pending rendering updates ([aadbe96579](https://github.com/facebook/react-native/commit/aadbe965792bd900ca70412d6704b76e339d1aca) by [@fkgozali](https://github.com/fkgozali)) +- **Runtime**: Prevent updates to mounted flag on the React revision ([7ee893d3be](https://github.com/facebook/react-native/commit/7ee893d3beb45b507712744f46f29b4ebba237e7) by [@j-piasecki](https://github.com/j-piasecki)) +- **Runtime**: Support multiple old pseudo elements for same name but different source node ([370606dc5b](https://github.com/facebook/react-native/commit/370606dc5b6ea0fcfb119ac99c36008204154802) by [@zeyap](https://github.com/zeyap)) +- **Runtime**: Use relevant revision as base for mounted flag updates ([4b954e14c8](https://github.com/facebook/react-native/commit/4b954e14c85ec5e1a8bf74ccdf17a959d82496ac) by [@j-piasecki](https://github.com/j-piasecki)) +- **Runtime**: Views with a non-invertible transform (e.g. `scaleX: 0` or `scaleY: 0`) no longer receive touches on Android or iOS. ([1e8e182410](https://github.com/facebook/react-native/commit/1e8e1824106857864bf21cff422b691e763cb00e) by [@qflen](https://github.com/qflen)) +- **TurboModules**: Bring back C++ Turbo Module example in RN Tester ([1c2cddad1a](https://github.com/facebook/react-native/commit/1c2cddad1a1c8c85d525740c0586c023c714122c) by [@christophpurrer](https://github.com/christophpurrer)) +- **VirtualizedList**: Improve render area change with initialScrollIndex non zero test in VirtualizedList to avoid magic numbers timers ([9b966d1d8f](https://github.com/facebook/react-native/commit/9b966d1d8f705024dad39268c5f36ffd4042a227) by [@chicio](https://github.com/chicio)) +- **Yoga**: Fixed Yoga node ownership when `display: contents` is used in absolutely positioned subtrees ([f2f92098dd](https://github.com/facebook/react-native/commit/f2f92098ddf996d3f75cfbaa143d7151c29776f7) by [@j-piasecki](https://github.com/j-piasecki)) + +#### Android specific + +- **Accessibility**: Guard `ReactViewGroup.addChildrenForAccessibility` against transient non-descendant accessibility traversal crashes. ([e681736e18](https://github.com/facebook/react-native/commit/e681736e1838904155409e236c3c2fa630b71c9a) by [@erickreutz](https://github.com/erickreutz)) +- **Build**: Fix build regression by using `rootProject.file` ([8a8f1b801c](https://github.com/facebook/react-native/commit/8a8f1b801c253a2e582e0d7615b67efe88328ca3) by [@etodanik](https://github.com/etodanik)) +- **Hermes**: Fix `HermesSamplingProfiler.disable()` JNI registration calling `enable()` instead of `disable()` ([7f2a58e46c](https://github.com/facebook/react-native/commit/7f2a58e46cdf0fd797aa58c6b0c8035913d34402) by [@elirangoshen](https://github.com/elirangoshen)) +- **Hermes**: Use Hermes V1 stable release instead of legacy nightly to fix startup crash caused by JSI ABI mismatch ([45ceacc08f](https://github.com/facebook/react-native/commit/45ceacc08f5c1e69f8f517afc7cd4f527283ea15) by [@cipolleschi](https://github.com/cipolleschi)) +- **Image**: Fix image content disappearing on API 24 (Nougat) when antialiased border radius clipping is applied ([fdd6ca5bb1](https://github.com/facebook/react-native/commit/fdd6ca5bb11b105284c7ad977ff2208c2f2e66f2) by [@Abbondanzo](https://github.com/Abbondanzo)) +- **Image**: `Image.getSize` and `Image.getSizeWithHeaders` now return the true source dimensions instead of Fresco's downsampled values ([8823a8009b](https://github.com/facebook/react-native/commit/8823a8009b18950d5be2682a86cfcf0c7014a068) by [@zoontek](https://github.com/zoontek)) +- **Image**: Make sure view width and height are non 0 before calling `createBitmap()` ([c20a58ab6b](https://github.com/facebook/react-native/commit/c20a58ab6b7fd21b0c61fc355b6cb629541c39e5) by [@zeyap](https://github.com/zeyap)) +- **Legacy Architecture**: Fix `onHostPause` crash in `ReactInstanceManager` without feature flag ([3aa8a043de](https://github.com/facebook/react-native/commit/3aa8a043de283f539cc907c7a5368e61cecb35b9) by [@shubhamksavita](https://github.com/shubhamksavita)) +- **LogBox**: Fixed activity context memory leak in `LogBoxDialogSurfaceDelegate` when using bridgeless mode ([f10cded1f8](https://github.com/facebook/react-native/commit/f10cded1f833bf0a143e736dfd364f5b405c648c) by Ilya Kovalyov) +- **Networking**: Blob content provider did not work in new arch ([05ea94c441](https://github.com/facebook/react-native/commit/05ea94c44106c8d4314edf2dcd5874f9eab6155d) by [@javache](https://github.com/javache)) +- **Networking**: Catch `OutOfMemoryError` in `NetworkingModule` response processing to prevent app crashes ([0c153e2971](https://github.com/facebook/react-native/commit/0c153e2971db6edc9cdf363c93072488131a4340) by [@shubhamksavita](https://github.com/shubhamksavita)) +- **Networking**: WebSocketModule no longer strips a `Cookie` header passed via the WebSocket constructor's `headers` option ([24b51db75b](https://github.com/facebook/react-native/commit/24b51db75b7f8eee666c9a3b6d13c6a29a7a759a) by [@psjostrom](https://github.com/psjostrom)) +- **Runtime**: Fix `Dimensions` `window` values on Android < 15 when edge-to-edge is enabled ([44a4b1ba5d](https://github.com/facebook/react-native/commit/44a4b1ba5d0af29d4ed7e73807677eb4e8525e9f) by [@zoontek](https://github.com/zoontek)) +- **Runtime**: Fix `KeyboardAvoidingView` on Android 15+ / with `edgeToEdgeEnabled` ([4a6c933cce](https://github.com/facebook/react-native/commit/4a6c933ccedd7efc8c21b71a101980a15c8525ce) by [@zoontek](https://github.com/zoontek)) +- **Runtime**: Fix `measureInWindow` returning incorrect coordinates when edge-to-edge is enabled ([9d183675e9](https://github.com/facebook/react-native/commit/9d183675e9552043dd2d768d4fbac1760cdc9a59) by [@zoontek](https://github.com/zoontek)) +- **Runtime**: Fix crash from stale preallocated view registry after concurrent render cancellation ([97cf2202ee](https://github.com/facebook/react-native/commit/97cf2202ee4b0f8c36716073509b50414e0f2c94) by [@javache](https://github.com/javache)) +- **Runtime**: Gate diagnostic `printMountItem` calls in `MountItemDispatcher` error handlers behind `enableFabricLogs()` to prevent OOM crash ([7a546f98a7](https://github.com/facebook/react-native/commit/7a546f98a794ece46e12cd2c2a38454b79b08e9e) by [@JakobFelixJulius](https://github.com/JakobFelixJulius)) +- **Runtime**: Handle edge-to-edge when it's not enabled by the `edgeToEdgeEnabled` gradle property but enforced by the OS (Android 15+) ([c2280e3201](https://github.com/facebook/react-native/commit/c2280e3201fdd468ae358cb577430b87949f05e5) by [@zoontek](https://github.com/zoontek)) +- **Runtime**: Register `HeadlessJsTaskSupportModule` in `CoreReactPackage` ([2d78a39b3e](https://github.com/facebook/react-native/commit/2d78a39b3e1199c301696fa22ae54c2b868cc3f1) by 贾太滨) +- **Runtime**: Schedule React revision merge to happen on `DISPATCH_UI` choreographer queue, before dispatching events ([b63efbfc6b](https://github.com/facebook/react-native/commit/b63efbfc6b61b120d69b55cd37a9a92664af6928) by [@j-piasecki](https://github.com/j-piasecki)) +- **ScrollView**: Dispatch FPS performance events for mouse wheel and trackpad scroll interactions in ScrollView ([e0a453c603](https://github.com/facebook/react-native/commit/e0a453c6037ceda3c739613a4a1609256db5e4e7) by [@Abbondanzo](https://github.com/Abbondanzo)) +- **ScrollView**: Fix FPS performance listener being cleared on ScrollView recycle ([59ac1db76e](https://github.com/facebook/react-native/commit/59ac1db76e02ad1d4d7da918e71eb7a8452afe72) by [@Abbondanzo](https://github.com/Abbondanzo)) +- **StatusBar**: StatusBar configuration now applies to Modal windows, fixing visual inconsistencies ([6614fe7f2b](https://github.com/facebook/react-native/commit/6614fe7f2b754a876bfce388895d9a538ce86135) by [@zoontek](https://github.com/zoontek)) +- **Text**: Fix 1px text wrapping in absolutely positioned elements caused by fractional Yoga widths ([90c2c595da](https://github.com/facebook/react-native/commit/90c2c595dacb0dccddf075f5c52224a6a298d995) by [@clocksarestupid](https://github.com/clocksarestupid)) +- **Text**: Fix BoringLayout crash when `isBoring()` returns negative width ([d527f2d663](https://github.com/facebook/react-native/commit/d527f2d663a514d5fe778022390c954e0b3e2588) by [@NickGerleman](https://github.com/NickGerleman)) +- **Text**: Fix crash when measuring text in a stopped surface ([f71ec1a24e](https://github.com/facebook/react-native/commit/f71ec1a24eff3a63fd223c24b01ce25eac1631e9) by [@NickGerleman](https://github.com/NickGerleman)) +- **TextInput**: Fix `setAutoCapitalize` stripping numeric `TextInput` flags (`TYPE_NUMBER_FLAG_SIGNED`/`DECIMAL`), affecting minus sign input on Samsung keyboards ([863f76bd49](https://github.com/facebook/react-native/commit/863f76bd490b2e7a9621f9a7c03d0b4295e32e60) by [@lukeharvey](https://github.com/lukeharvey)) +- **TextInput**: KeyboardAvoidingView and TextInput auto-scroll not responding to IME height changes (e.g., when toggling emoji panel or predictive bar) ([ee1d067692](https://github.com/facebook/react-native/commit/ee1d0676920d75bfaa68e78602b10477b8b2187e) by [@alanleedev](https://github.com/alanleedev)) +- **View**: Fixed a crash when clearing `zIndex` with props 2.0 enabled ([93b37350af](https://github.com/facebook/react-native/commit/93b37350afe8db7b4835ba59f40ea1e55e4a3045) by [@hannojg](https://github.com/hannojg)) +- **View**: Fixed crash when enabling props 2.0 and setting `borderStyle` to `undefined` ([9e058db4c8](https://github.com/facebook/react-native/commit/9e058db4c8f1d2aa7ab632606b565f43e6664adc) by [@hannojg](https://github.com/hannojg)) +- **View**: Unsupported background size value leading to crash ([fe046dc782](https://github.com/facebook/react-native/commit/fe046dc7822761322a2eea1bf0ba8a000a4ae8cc) by [@intergalacticspacehighway](https://github.com/intergalacticspacehighway)) + +#### iOS specific + +- **Build**: Addressed -Wunreachable-code-return violations ([9172bfe647](https://github.com/facebook/react-native/commit/9172bfe647d7489bc9e0067f85752e3e30e557e0) by [@ingridwang](https://github.com/ingridwang)) +- **Build**: Addressing -Wunreachable-code-return issues. ([722623aaeb](https://github.com/facebook/react-native/commit/722623aaebb10acb68b3de24449815c619722028) by [@ingridwang](https://github.com/ingridwang)) +- **Build**: Fixed escaping local file URIs for unicode paths in ruby scripts ([9563cb5b5a](https://github.com/facebook/react-native/commit/9563cb5b5a546d6daf09abc9d5594850129a4ae3) by [@chrfalch](https://github.com/chrfalch)) +- **Build**: Fixing unreachable-code-break ([795d902270](https://github.com/facebook/react-native/commit/795d9022707d543540217c72bf05d4e5893ecfe2) by [@ingridwang](https://github.com/ingridwang)) +- **Build**: Made `ReactInternal` compatible with Swift ([fc5cc2821f](https://github.com/facebook/react-native/commit/fc5cc2821f6e0117c0ea1bb4e6c5c45f58a2a167) by [@rmaz](https://github.com/rmaz)) +- **Build**: Persist `PODFILE_DIR` as `$(SRCROOT)`-relative so `project.pbxproj` is portable across machines ([44bb83bd84](https://github.com/facebook/react-native/commit/44bb83bd843a3b34779f1e7b1e09dbb3f4f38c6a) by [@zoontek](https://github.com/zoontek)) +- **Build**: Removing unused ivars ([3d2d14df16](https://github.com/facebook/react-native/commit/3d2d14df1686590f701559319d4b46ad82fcab89) by [@ingridwang](https://github.com/ingridwang)) +- **CocoaPods**: Fix Pod install checksum drifting ([814ebb12b6](https://github.com/facebook/react-native/commit/814ebb12b64cee51b91ada9fa0d90970feac9a9f) by [@chrfalch](https://github.com/chrfalch)) +- **CocoaPods**: Fix `REACT_NATIVE_PATH` resolution when `Pods/` is a symlink ([ea383822b7](https://github.com/facebook/react-native/commit/ea383822b73c3c4e154336f087205ee53db99115) by [@huntie](https://github.com/huntie)) +- **CocoaPods**: Fix React-CoreModules failing to compile with `use_frameworks!` due to missing React-featureflags header path ([fb0d28c6fd](https://github.com/facebook/react-native/commit/fb0d28c6fd7dd3ed961f7d96c7072cf37b2d228d) by [@radko93](https://github.com/radko93)) +- **CocoaPods**: Load `RCTDefines.h` first in the prebuilt React umbrella and drop the `module *` submodule wildcard so RCT_* macros propagate to consumer pods under `use_frameworks!`. ([90c303fafb](https://github.com/facebook/react-native/commit/90c303fafb941155dbe379e6b3e74390a139872a) by [@wneel](https://github.com/wneel)) +- **CocoaPods**: Preserve Expo-generated `React-use-frameworks.modulemap` across `replace-rncore-version.js` runs ([ada39220a0](https://github.com/facebook/react-native/commit/ada39220a0b67179f729a59a862cc8c18fdc5631) by [@Insik-Han](https://github.com/Insik-Han)) +- **Dev Server**: Fix iOS dev-support IPv6 handling for packager and inspector connections. ([4a59f90c22](https://github.com/facebook/react-native/commit/4a59f90c22ea850946aba0326214e036a7a2019c) by [@hannojg](https://github.com/hannojg)) +- **Hermes**: Fix compilation error in `createJSRuntimeFactory` and skip Hermes pods when `USE_THIRD_PARTY_JSC` is enabled ([2d8143794d](https://github.com/facebook/react-native/commit/2d8143794d3896f2217dc42dbe92b7fb091d2b81) by [@tux2nicolae](https://github.com/tux2nicolae)) +- **Networking**: Fix crash in `RCTWebSocketModule` when delegate callbacks fire after module invalidation ([587ef059a2](https://github.com/facebook/react-native/commit/587ef059a2c26391cdd31d33fb610328906aea8c) by [@lukeharvey](https://github.com/lukeharvey)) +- **React Native DevTools**: Fix inspector server host resolution for HTTPS bundle URLs by omitting default port ([3582a8b2b5](https://github.com/facebook/react-native/commit/3582a8b2b5bf220013e5965bcbfa81bd0ade1b83) by [@cipolleschi](https://github.com/cipolleschi)) +- **TextInput**: Fix `NSRangeException` crash in `RCTBackedTextInputDelegateAdapter` when text range is out of bounds ([19350b1c8c](https://github.com/facebook/react-native/commit/19350b1c8c5c8390368f90dbc1f3ab78743bfe2e) by [@lukeharvey](https://github.com/lukeharvey)) +- **View**: Fixes crash when changing the value of `removeClippedSubviews` ([91e3f773b7](https://github.com/facebook/react-native/commit/91e3f773b7e571a503b57e09a1cb8a44ff26cd1e) by [@javache](https://github.com/javache)) + +### Security + +- **Dependencies**: Bumped `fast-xml-parser` from 4.5.0 to 4.5.4 to fix CVE-2026-25896 ([56908a74c7](https://github.com/facebook/react-native/commit/56908a74c7f79a45dae9c2f651e437399e7465fe) by [@balajacatherin](https://github.com/balajacatherin)) +- **Dependencies**: Bumped `minimatch` to fix CVE-2026-27903, CVE-2026-27904 ([c019a50a4c](https://github.com/facebook/react-native/commit/c019a50a4ce3c7331921fc849a0719a216bb30b8) by [@balajacatherin](https://github.com/balajacatherin)) + +## v0.85.3 + +### Changed + +- **React Native DevTools**: Update debugger-frontend from 8edd9be...194d3f8 ([9966cbdf4d](https://github.com/facebook/react-native/commit/9966cbdf4da99ee036a75bec4da9bb2e1ee7a9c4) by [@motiz88](https://github.com/motiz88)) + +### Fixed + +- **Build**: Use pinned Hermes version when version.properties is not 1000.0.0 ([5e3a3ba995](https://github.com/facebook/react-native/commit/5e3a3ba995026b42c2ea0ad7ee4758fcf73005e6) by [@cipolleschi](https://github.com/cipolleschi)) + +#### iOS specific + +- **Build**: Fix silent tar extraction failure on EdenFS in replace-rncore-version.js by extracting to a temp directory first ([9bc7d38be0](https://github.com/facebook/react-native/commit/9bc7d38be0771ae4006206fcb174956f53adf508) by [@motiz88](https://github.com/motiz88)) +- **Build**: Skip prebuilds for DynamicFrameworks CI jobs ([753c19bea4](https://github.com/facebook/react-native/commit/753c19bea4ab854eb8000b2b5bab814261a46596) by [@motiz88](https://github.com/motiz88)) + ## v0.85.2 ### Added @@ -421,6 +595,30 @@ - **Touch Handling**: Respect `cancelsTouchesInView` when canceling touches in `RCTSurfaceTouchHandler` ([5634e8a601](https://github.com/facebook/react-native/commit/5634e8a601caf0faa174bac3511929de767609ac) by [@intmain](https://github.com/intmain)) - **View**: Fix duplicate shadow bug during component recycling by cleaning up visual layers in prepareForRecycle ([7dcedf1def](https://github.com/facebook/react-native/commit/7dcedf1def880163ab7ca07b2575a8153029a925) by Atharv Soni) +## v0.83.10 + +### Added + +#### iOS specific + +- **Prebuild**: Cache prebuilt iOS binaries in `~/Library/Caches/ReactNative` so Hermes, ReactNativeDependencies and ReactNativeCore tarballs are reused across clean installs and projects instead of being re-downloaded from Maven ([9a0b05b8d0](https://github.com/react/react-native/commit/9a0b05b8d07f3468a08077119189406d56a230ba) by [@cipolleschi](https://github.com/cipolleschi)) + +### Fixed + +- **React Native DevTools**: Fix a bug where we would incorrectly flag apps using additional Hermes runtimes (e.g. Reanimated) as being multi-host ([c800503214](https://github.com/react/react-native/commit/c8005032140f1aff16027b5ed53caea6c9d299f5) by [@huntie](https://github.com/huntie)) +- **Yoga**: Fixed Yoga node ownership when `display: contents` is used in absolutely positioned subtrees ([f2f92098dd](https://github.com/react/react-native/commit/f2f92098ddf996d3f75cfbaa143d7151c29776f7) by [@j-piasecki](https://github.com/j-piasecki)) +- **Yoga**: Fixed `display: contents` nodes having `hasNewLayout` set incorrectly ([2546ce4d82](https://github.com/react/react-native/commit/2546ce4d8219050fcd1bf432c7c830c9fd70c9af) by [@j-piasecki](https://github.com/j-piasecki)) + +#### Android specific + +- **Networking**: `fetch()` response URL is now correct after a redirect ([fbe6a686e6](https://github.com/react/react-native/commit/fbe6a686e65e70dd61700413084ddc54c0b86765) by [@MarkCSmith](https://github.com/MarkCSmith)) +- **React Native DevTools**: Limit WebSocket queue size for the packager connection to prevent the Android inspector from being force-disconnected on large payloads ([7164f96d58](https://github.com/react/react-native/commit/7164f96d581115e6a7a5646a50ded8e5fdff7742) by [@huntie](https://github.com/huntie)) + +#### iOS specific + +- **CocoaPods**: Make Podfile.lock SPEC CHECKSUMS deterministic across machines by sorting Dir.glob results in Yoga.podspec and using a dynamically computed Pods-relative path in hermes-engine.podspec ([64c9663152](https://github.com/react/react-native/commit/64c9663152879a87d061a3f01b9e9c4e98cc73bc) by [@IsaacIsrael](https://github.com/IsaacIsrael)) +- **View**: Fixes crash when changing the value of `removeClippedSubviews` ([91e3f773b7](https://github.com/react/react-native/commit/91e3f773b7e571a503b57e09a1cb8a44ff26cd1e) by [@javache](https://github.com/javache)) + ## v0.83.8 ### Fixed @@ -495,7 +693,6 @@ - **Appearance**: Fix color scheme in appearance state after setting it to unspecified ([08d1764530](https://github.com/facebook/react-native/commit/08d176453095db99300aa77632603ab42c57e152) by [@ismarbesic](https://github.com/ismarbesic)) - **Assets**: Handle `unstable_path` query param in asset URLs ([42986f27a0](https://github.com/facebook/react-native/commit/42986f27a0285e501f71cf5cedacedefdc44c74e) by [@tido64](https://github.com/tido64)) -- **Networking**: Fix incorrect `fetch()` response URL after redirect (https://github.com/facebook/react-native/issues/55248) ([fbe6a686e6](https://github.com/facebook/react-native/commit/fbe6a686e65e70dd61700413084ddc54c0b86765) by [@MarkCSmith](https://github.com/MarkCSmith)) #### Android specific diff --git a/Gemfile.lock b/Gemfile.lock index 09851111ebf..c92be41e2ed 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,8 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.8) + CFPropertyList (3.0.6) + rexml activesupport (7.2.3.1) base64 benchmark (>= 0.3) @@ -21,15 +22,15 @@ GEM json (>= 1.5.1) atomos (0.1.3) base64 (0.3.0) - benchmark (0.5.0) - bigdecimal (4.1.1) + benchmark (0.4.0) + bigdecimal (3.1.9) claide (1.1.0) - cocoapods (1.15.2) + cocoapods (1.14.2) addressable (~> 2.8) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.15.2) + cocoapods-core (= 1.14.2) cocoapods-deintegrate (>= 1.0.3, < 2.0) - cocoapods-downloader (>= 2.1, < 3.0) + cocoapods-downloader (>= 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) cocoapods-trunk (>= 1.6.0, < 2.0) @@ -42,7 +43,7 @@ GEM nap (~> 1.0) ruby-macho (>= 2.3.0, < 3.0) xcodeproj (>= 1.23.0, < 2.0) - cocoapods-core (1.15.2) + cocoapods-core (1.14.2) activesupport (>= 5.0, < 8) addressable (~> 2.8) algoliasearch (~> 1.0) @@ -53,7 +54,7 @@ GEM public_suffix (~> 4.0) typhoeus (~> 1.0) cocoapods-deintegrate (1.0.5) - cocoapods-downloader (2.1) + cocoapods-downloader (2.0) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.1) @@ -66,41 +67,39 @@ GEM connection_pool (3.0.2) drb (2.2.3) escape (0.0.4) - ethon (0.18.0) + ethon (0.16.0) ffi (>= 1.15.0) - logger - ffi (1.17.4) + ffi (1.16.3) fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) - httpclient (2.9.0) - mutex_m - i18n (1.14.8) + httpclient (2.8.3) + i18n (1.15.2) concurrent-ruby (~> 1.0) - json (2.19.3) - logger (1.7.0) + json (2.6.3) + logger (1.6.5) minitest (5.27.0) molinillo (0.8.0) mutex_m (0.3.0) nanaimo (0.3.0) nap (1.1.0) netrc (0.11.0) - nkf (0.2.0) + nkf (0.3.0) public_suffix (4.0.7) - rexml (3.4.4) + rexml (3.2.6) ruby-macho (2.5.1) securerandom (0.4.1) - typhoeus (1.6.0) - ethon (>= 0.18.0) + typhoeus (1.4.0) + ethon (>= 0.9.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - xcodeproj (1.25.1) + xcodeproj (1.23.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) nanaimo (~> 0.3.0) - rexml (>= 3.3.6, < 4.0) + rexml (~> 3.2.4) PLATFORMS ruby @@ -117,7 +116,7 @@ DEPENDENCIES xcodeproj (< 1.26.0) RUBY VERSION - ruby 3.3.0p0 + ruby 3.2.0p0 BUNDLED WITH - 2.5.22 + 2.4.12 diff --git a/build.gradle.kts b/build.gradle.kts index 0858b5cb291..d23181ed7ce 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -118,7 +118,7 @@ if (project.findProperty("react.internal.useHermesStable")?.toString()?.toBoolea val hermesVersionPropertiesFile = rootProject.file("./packages/react-native/sdks/hermes-engine/version.properties") hermesVersionPropertiesFile.inputStream().use { hermesVersions.load(it) } - val selectedHermesVersion = hermesVersions["HERMES_V1_VERSION_NAME"] as String + val selectedHermesVersion = hermesVersions["HERMES_VERSION_NAME"] as String hermesSubstitution = selectedHermesVersion to "Users opted to use stable hermes release" } else if ( @@ -137,15 +137,7 @@ if (project.findProperty("react.internal.useHermesStable")?.toString()?.toBoolea ) } - val hermesV1Enabled = project.findProperty("hermesV1Enabled")?.toString()?.toBoolean() ?: true - // Hermes V1 stable releases are published without the -SNAPSHOT suffix. - // Legacy nightly builds use -SNAPSHOT. - val resolvedVersion = - if (hermesV1Enabled) hermesCompilerVersion else "$hermesCompilerVersion-SNAPSHOT" - val reason = - if (hermesV1Enabled) "Users opted to use hermes V1 stable" - else "Users opted to use hermes nightly" - hermesSubstitution = resolvedVersion to reason + hermesSubstitution = hermesCompilerVersion to "Users opted to use Hermes V1 prebuilt" } else { logger.warn( """ diff --git a/flow-typed/npm/babel_v7.x.x.js b/flow-typed/npm/babel_v7.x.x.js index 4da72d6b7d5..abba5ac2cb6 100644 --- a/flow-typed/npm/babel_v7.x.x.js +++ b/flow-typed/npm/babel_v7.x.x.js @@ -28,6 +28,24 @@ type _BabelSourceMapSegment = { ... }; +// A "decoded" source map (as produced by `@jridgewell/gen-mapping`), grouped by +// generated line. Segment fields are all 0-based: generated column, source +// index, source line, source column, name index. +type _BabelDecodedSourceMapSegment = + | [number] + | [number, number, number, number] + | [number, number, number, number, number]; + +type _BabelDecodedSourceMap = Readonly<{ + file?: string, + mappings: Array>, + names: Array, + sourceRoot?: string, + sources: Array, + sourcesContent?: Array, + version: number, +}>; + export type BabelSourceLocation = Readonly<{ start: Readonly<{line: number, column: number}>, end: Readonly<{line: number, column: number}>, @@ -1111,6 +1129,7 @@ declare module '@babel/generator' { declare export type GeneratorResult = { code: string, map: ?_BabelSourceMap, + decodedMap: ?_BabelDecodedSourceMap, rawMappings: ?Array, }; diff --git a/flow-typed/npm/commander_v12.x.x.js b/flow-typed/npm/commander_v12.x.x.js index 5ea9164270e..1f18ac9da05 100644 --- a/flow-typed/npm/commander_v12.x.x.js +++ b/flow-typed/npm/commander_v12.x.x.js @@ -670,11 +670,10 @@ declare module 'commander' { * or store separately (specify false). In both cases the option values can be accessed using .opts(). * @returns `this` command for chaining */ - storeOptionsAsProperties(): {...this, ...T}; - storeOptionsAsProperties(storeAsProperties: true): { - ...this, - ...T, - }; + storeOptionsAsProperties(): this & T; + storeOptionsAsProperties( + storeAsProperties: true, + ): this & T; storeOptionsAsProperties(storeAsProperties?: boolean): this; /** diff --git a/flow-typed/npm/electron-packager_v18.x.x.js b/flow-typed/npm/electron-packager_v20.x.x.js similarity index 88% rename from flow-typed/npm/electron-packager_v18.x.x.js rename to flow-typed/npm/electron-packager_v20.x.x.js index 4ac8b158eb5..38c859aca69 100644 --- a/flow-typed/npm/electron-packager_v18.x.x.js +++ b/flow-typed/npm/electron-packager_v20.x.x.js @@ -11,29 +11,25 @@ declare module '@electron/packager' { declare export type AsarOptions = $FlowFixMe; - declare export type ElectronDownloadRequestOptions = $FlowFixMe; - declare export type TargetDefinition = { arch: TargetArch, platform: TargetPlatform, }; - declare export type FinalizePackageTargetsHookFunction = ( - targets: TargetDefinition[], - callback: HookFunctionErrorCallback, - ) => void; - - declare export type HookFunction = ( + declare export type HookFunctionArgs = { buildPath: string, electronVersion: string, platform: TargetPlatform, arch: TargetArch, - callback: HookFunctionErrorCallback, - ) => void; + }; - declare export type IgnoreFunction = (path: string) => boolean; + declare export type HookFunction = (args: HookFunctionArgs) => Promise; + + declare export type FinalizePackageTargetsHookFunction = (args: { + targets: TargetDefinition[], + }) => Promise; - declare export type HookFunctionErrorCallback = (err?: Error | null) => void; + declare export type IgnoreFunction = (path: string) => boolean; declare export interface MacOSProtocol { name: string; @@ -102,7 +98,6 @@ declare module '@electron/packager' { buildVersion?: string; darwinDarkModeSupport?: boolean; derefSymlinks?: boolean; - download?: ElectronDownloadRequestOptions; electronVersion?: string; electronZipDir?: string; executableName?: string; @@ -118,7 +113,7 @@ declare module '@electron/packager' { }; extraResource?: string | string[]; helperBundleId?: string; - icon?: string; + icon?: string | string[]; ignore?: RegExp | (string | RegExp)[] | IgnoreFunction; junk?: boolean; name?: string; @@ -132,7 +127,7 @@ declare module '@electron/packager' { protocols?: MacOSProtocol[]; prune?: boolean; quiet?: boolean; - tmpdir?: string | false; + tmpdir?: string; usageDescription?: { [property: string]: string, }; diff --git a/gradle.properties b/gradle.properties index d7c39175371..1028b5c5238 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,12 +4,6 @@ org.gradle.caching=true android.useAndroidX=true -# Those 2 properties are needed to make our project compatible with -# AGP 9.0.0 for the time being. Ideally we should not opt-out of -# builtInKotlin and newDsl once AGP 9.0.0 hits stable. -# More on this: https://developer.android.com/build/releases/agp-preview#android-gradle-plugin-built-in-kotlin -android.builtInKotlin=false -android.newDsl=false # Use this property to specify which architecture you want to build. # You can also override it from the CLI using @@ -24,6 +18,3 @@ react.internal.useHermesStable=false # Controls whether to use Hermes from nightly builds. This will speed up builds # but should NOT be turned on for CI or release builds. react.internal.useHermesNightly=true - -# Controls whether to use Hermes 1.0. Clean and rebuild when changing. -hermesV1Enabled=true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 61285a659d1..d997cfc60f4 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37f78a6af83..c61a118f7dd 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index adff685a034..739907dfd15 100755 --- a/gradlew +++ b/gradlew @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. diff --git a/package.json b/package.json index 6b04ab88fb6..7c32c0b1048 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "clean": "node ./scripts/build/clean.js", "cxx-api-build": "python -m scripts.cxx-api.parser", "cxx-api-validate": "python -m scripts.cxx-api.parser --validate", - "flow-check": "flow check", + "flow-check": "flow full-check", "flow": "flow", "format-check": "prettier --list-different \"./**/*.{js,md,yml,ts,tsx}\"", "format": "npm run prettier && npm run clang-format", @@ -36,6 +36,7 @@ "test-generated-typescript": "tsc -p packages/react-native/types_generated/tsconfig.test.json", "test": "jest", "fantom": "./scripts/fantom.sh", + "fantom-cli": "./scripts/fantom-cli.sh", "trigger-react-native-release": "node ./scripts/releases-local/trigger-react-native-release.js", "update-lock": "npx yarn-deduplicate", "print-packages": "node ./scripts/monorepo/print", @@ -57,24 +58,38 @@ "@babel/core": "^7.25.2", "@babel/eslint-parser": "^7.25.1", "@babel/generator": "^7.29.1", + "@babel/parser": "^7.25.2", "@babel/plugin-syntax-typescript": "^7.25.4", "@babel/plugin-transform-regenerator": "^7.24.7", "@babel/preset-env": "^7.25.3", "@babel/preset-flow": "^7.24.7", - "@electron/packager": "^18.3.6", + "@babel/runtime": "^7.25.2", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", + "@changesets/cli": "^2.28.1", + "@electron/packager": "^20.0.0", "@expo/spawn-async": "^1.7.2", "@jest/create-cache-key-function": "^29.7.0", "@microsoft/api-extractor": "^7.52.2", + "@nx/js": "^21.4.1", "@octokit/rest": "^22.0.0", + "@react-native/babel-preset": "workspace:*", + "@react-native/eslint-config": "workspace:*", + "@react-native/eslint-plugin": "workspace:*", + "@react-native/eslint-plugin-monorepo": "workspace:*", + "@react-native/eslint-plugin-specs": "workspace:*", + "@react-native/jest-preset": "workspace:*", "@react-native/metro-babel-transformer": "workspace:*", "@react-native/metro-config": "workspace:*", "@tsconfig/node22": "22.0.2", "@types/react": "^19.1.0", - "@typescript-eslint/parser": "^8.36.0", + "@typescript-eslint/eslint-plugin": "^8.36.0", + "@typescript-eslint/parser": "^8.59.2", + "@yarnpkg/types": "^4.0.1", "ansi-regex": "^5.0.0", "ansi-styles": "^4.2.1", "babel-plugin-minify-dead-code-elimination": "^0.5.2", - "babel-plugin-syntax-hermes-parser": "0.36.0", + "babel-plugin-syntax-hermes-parser": "0.36.1", "babel-plugin-transform-define": "^2.1.4", "babel-plugin-transform-flow-enums": "^0.0.2", "clang-format": "^1.8.0", @@ -89,30 +104,39 @@ "eslint-plugin-jest": "^29.0.1", "eslint-plugin-jsx-a11y": "^6.6.0", "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-native": "^5.0.0", "eslint-plugin-redundant-undefined": "^0.4.0", "eslint-plugin-relay": "^1.8.3", "fb-dotslash": "0.5.8", - "flow-api-translator": "0.36.0", - "flow-bin": "^0.312.0", - "hermes-eslint": "0.36.0", - "hermes-transform": "0.36.0", + "flow-api-translator": "0.36.1", + "flow-bin": "^0.319.0", + "glob": "^7.1.1", + "hermes-eslint": "0.36.1", + "hermes-estree": "0.36.0", + "hermes-transform": "0.36.1", "ini": "^5.0.0", "inquirer": "^7.1.0", + "invariant": "^2.2.4", "jest": "^29.7.0", "jest-config": "^29.7.0", "jest-diff": "^29.7.0", "jest-junit": "^16.0.0", "jest-snapshot": "^29.7.0", + "jsonc-eslint-parser": "^2.3.0", "jsonc-parser": "2.2.1", "markdownlint-cli2": "^0.22.0", "markdownlint-rule-relative-links": "^3.0.0", "memfs": "^4.38.2", - "metro-babel-register": "^0.84.3", - "metro-transform-plugins": "^0.84.3", + "metro": "^0.84.3", + "metro-babel-register": "^0.86.0", + "metro-config": "^0.84.3", + "metro-memory-fs": "^0.82.5", + "metro-transform-plugins": "^0.86.0", "micromatch": "^4.0.4", "node-fetch": "^2.2.0", "nullthrows": "^1.1.1", + "nx": "21.4.1", "prettier": "3.6.2", "prettier-plugin-hermes-parser": "0.36.0", "react": "19.2.3", @@ -124,31 +148,8 @@ "temp-dir": "^2.0.0", "tinybench": "^4.1.0", "tinyglobby": "^0.2.15", - "typescript": "5.8.3", + "typescript": "^6.0.3", "ws": "^7.5.10", - "@babel/parser": "^7.25.2", - "@babel/runtime": "^7.25.2", - "@babel/traverse": "^7.25.2", - "@babel/types": "^7.25.2", - "@changesets/cli": "^2.28.1", - "@nx/js": "^21.4.1", - "@react-native/babel-preset": "workspace:*", - "@react-native/eslint-config": "workspace:*", - "@react-native/eslint-plugin": "workspace:*", - "@react-native/eslint-plugin-monorepo": "workspace:*", - "@react-native/eslint-plugin-specs": "workspace:*", - "@react-native/jest-preset": "workspace:*", - "@typescript-eslint/eslint-plugin": "^8.36.0", - "@yarnpkg/types": "^4.0.1", - "eslint-plugin-react-hooks": "^5.2.0", - "glob": "^7.1.1", - "hermes-estree": "0.36.0", - "invariant": "^2.2.4", - "jsonc-eslint-parser": "^2.3.0", - "metro": "^0.84.3", - "metro-config": "^0.84.3", - "metro-memory-fs": "^0.82.5", - "nx": "21.4.1", "yargs": "^17.6.2", "zx": "^8.2.4" }, @@ -158,6 +159,10 @@ "compression": "1.8.1", "@microsoft/api-extractor/minimatch": "3.1.4", "lodash": "4.18.1", + "@xmldom/xmldom": "^0.8.13", + "fast-xml-parser": "^4.5.6", + "yaml": "^2.8.3", + "fast-uri": "^3.1.2", "@grpc/proto-loader": "^0.7.8", "async": "^3.2.2", "debug": ">=3.1.0", diff --git a/packages/asset-utils/README.md b/packages/asset-utils/README.md new file mode 100644 index 00000000000..ebcd7e938e6 --- /dev/null +++ b/packages/asset-utils/README.md @@ -0,0 +1,23 @@ +# @react-native/asset-utils + +[![npm]](https://www.npmjs.com/package/@react-native/asset-utils) [![npm downloads]](https://www.npmjs.com/package/@react-native/asset-utils) + +[npm]: https://img.shields.io/npm/v/@react-native/asset-utils.svg?color=blue +[npm downloads]: https://img.shields.io/npm/dm/@react-native/asset-utils.svg + +Android resource-path helpers used when copying React Native assets into `drawable-*` / `raw` folders. Consumed by bundling and build tooling; most apps never import this directly. + +## API + +```js +import { + getAndroidResourceFolderName, + getAndroidResourceIdentifier, +} from '@react-native/asset-utils'; +``` + +| Export | Signature | Notes | +|---|---|---| +| `getAndroidResourceFolderName` | `(asset: PackagerAsset, scale: number) => string` | e.g. `drawable-xhdpi`; non-drawable types resolve to `raw` | +| `getAndroidResourceIdentifier` | `(asset: PackagerAsset) => string` | Sanitised resource name | +| `drawableFileTypes` | `Set` | Asset types that map to a `drawable-*` folder | diff --git a/packages/asset-utils/package.json b/packages/asset-utils/package.json new file mode 100644 index 00000000000..edfd0204244 --- /dev/null +++ b/packages/asset-utils/package.json @@ -0,0 +1,32 @@ +{ + "name": "@react-native/asset-utils", + "version": "0.87.0-main", + "description": "Asset path utilities for React Native.", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/react/react-native.git", + "directory": "packages/asset-utils" + }, + "homepage": "https://github.com/react/react-native/tree/HEAD/packages/asset-utils#readme", + "keywords": [ + "react-native" + ], + "bugs": "https://github.com/react/react-native/issues", + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + }, + "exports": { + ".": "./src/index.js", + "./package.json": "./package.json" + }, + "files": [ + "src", + "README.md", + "!**/__docs__/**", + "!**/__fixtures__/**", + "!**/__mocks__/**", + "!**/__tests__/**" + ], + "private": true +} diff --git a/packages/asset-utils/src/AndroidPathUtils.d.ts b/packages/asset-utils/src/AndroidPathUtils.d.ts new file mode 100644 index 00000000000..854eecc3338 --- /dev/null +++ b/packages/asset-utils/src/AndroidPathUtils.d.ts @@ -0,0 +1,23 @@ +/** + * 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. + * + * @format + */ + +export type PackagerAsset = Readonly<{ + httpServerLocation: string; + name: string; + type: string; +}>; + +export function getAndroidResourceFolderName( + asset: PackagerAsset, + scale: number, +): string; + +export function getAndroidResourceIdentifier(asset: PackagerAsset): string; + +export const drawableFileTypes: Set; diff --git a/packages/asset-utils/src/AndroidPathUtils.js b/packages/asset-utils/src/AndroidPathUtils.js new file mode 100644 index 00000000000..3b76e82aab0 --- /dev/null +++ b/packages/asset-utils/src/AndroidPathUtils.js @@ -0,0 +1,93 @@ +/** + * 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. + * + * @flow strict + * @format + */ + +'use strict'; + +/*:: +// Conforms to the `PackagerAsset` type from `react-native`. +export type PackagerAsset = Readonly<{ + httpServerLocation: string, + name: string, + type: string, + ... +}>; +*/ + +const androidScaleSuffix /*: {[string]: string} */ = { + '0.75': 'ldpi', + '1': 'mdpi', + '1.5': 'hdpi', + '2': 'xhdpi', + '3': 'xxhdpi', + '4': 'xxxhdpi', +}; + +const ANDROID_BASE_DENSITY = 160; + +// FIXME: Using number to represent discrete scale numbers is fragile in +// essence because of floating point number imprecision. +function getAndroidAssetSuffix(scale /*: number */) /*: string */ { + if (scale.toString() in androidScaleSuffix) { + return androidScaleSuffix[scale.toString()]; + } + + // NOTE: Android Gradle Plugin does not fully support the nnndpi format. + // See https://issuetracker.google.com/issues/72884435 + if (Number.isFinite(scale) && scale > 0) { + return Math.round(scale * ANDROID_BASE_DENSITY) + 'dpi'; + } + + throw new Error('no such scale ' + scale.toString()); +} + +// See https://developer.android.com/guide/topics/resources/drawable-resource.html +const drawableFileTypes /*: Set */ = new Set([ + 'gif', + 'heic', + 'heif', + 'jpeg', + 'jpg', + 'ktx', + 'png', + 'webp', + 'xml', +]); + +function getAndroidResourceFolderName( + asset /*: PackagerAsset */, + scale /*: number */, +) /*: string */ { + if (!drawableFileTypes.has(asset.type)) { + return 'raw'; + } + + return 'drawable-' + getAndroidAssetSuffix(scale); +} + +function getAndroidResourceIdentifier( + asset /*: PackagerAsset */, +) /*: string */ { + return (getBasePath(asset) + '/' + asset.name) + .toLowerCase() + .replace(/\//g, '_') // Encode folder structure in file name + .replace(/([^a-z0-9_])/g, '') // Remove illegal chars + .replace(/^(?:assets|assetsunstable_path)_/, ''); // Remove "assets_" or "assetsunstable_path_" prefix +} + +function getBasePath(asset /*: PackagerAsset */) /*: string */ { + const basePath = asset.httpServerLocation; + return basePath.startsWith('/') ? basePath.slice(1) : basePath; +} + +module.exports = { + drawableFileTypes, + getAndroidResourceFolderName, + getAndroidResourceIdentifier, +}; diff --git a/packages/asset-utils/src/__tests__/AndroidPathUtils-test.js b/packages/asset-utils/src/__tests__/AndroidPathUtils-test.js new file mode 100644 index 00000000000..2496c1d9ee0 --- /dev/null +++ b/packages/asset-utils/src/__tests__/AndroidPathUtils-test.js @@ -0,0 +1,72 @@ +/** + * 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. + * + * @flow strict-local + * @format + */ + +import {getAndroidResourceFolderName} from '../AndroidPathUtils'; + +const DRAWABLE_ASSET = { + httpServerLocation: '/assets/', + name: 'foo', + type: 'png', +}; + +const NON_DRAWABLE_ASSET = { + httpServerLocation: '/assets/', + name: 'foo', + type: 'txt', +}; + +describe('getAndroidResourceFolderName', () => { + test('supports the six primary density buckets', () => { + expect(getAndroidResourceFolderName(DRAWABLE_ASSET, 0.75)).toBe( + 'drawable-ldpi', + ); + expect(getAndroidResourceFolderName(DRAWABLE_ASSET, 1)).toBe( + 'drawable-mdpi', + ); + expect(getAndroidResourceFolderName(DRAWABLE_ASSET, 1.5)).toBe( + 'drawable-hdpi', + ); + expect(getAndroidResourceFolderName(DRAWABLE_ASSET, 2)).toBe( + 'drawable-xhdpi', + ); + expect(getAndroidResourceFolderName(DRAWABLE_ASSET, 3)).toBe( + 'drawable-xxhdpi', + ); + expect(getAndroidResourceFolderName(DRAWABLE_ASSET, 4)).toBe( + 'drawable-xxxhdpi', + ); + }); + + test('supports nonstandard densities', () => { + expect(getAndroidResourceFolderName(DRAWABLE_ASSET, 1.25)).toBe( + 'drawable-200dpi', + ); + expect(getAndroidResourceFolderName(DRAWABLE_ASSET, 1.66)).toBe( + 'drawable-266dpi', + ); + expect(getAndroidResourceFolderName(DRAWABLE_ASSET, 1.33)).toBe( + 'drawable-213dpi', + ); // ~tvdpi + }); + + test('throws if the density cannot be processed', () => { + expect(() => getAndroidResourceFolderName(DRAWABLE_ASSET, -1)).toThrow(); + expect(() => getAndroidResourceFolderName(DRAWABLE_ASSET, 0)).toThrow(); + expect(() => + getAndroidResourceFolderName(DRAWABLE_ASSET, Infinity), + ).toThrow(); + }); + + test('returns "raw" for non-drawables', () => { + expect(getAndroidResourceFolderName(NON_DRAWABLE_ASSET, 0.75)).toBe('raw'); + expect(getAndroidResourceFolderName(NON_DRAWABLE_ASSET, 1)).toBe('raw'); + expect(getAndroidResourceFolderName(NON_DRAWABLE_ASSET, 1.25)).toBe('raw'); + }); +}); diff --git a/packages/react-native/Libraries/Image/AssetRegistry.js b/packages/asset-utils/src/index.d.ts similarity index 65% rename from packages/react-native/Libraries/Image/AssetRegistry.js rename to packages/asset-utils/src/index.d.ts index 3e1785346ff..c67af666a8a 100644 --- a/packages/react-native/Libraries/Image/AssetRegistry.js +++ b/packages/asset-utils/src/index.d.ts @@ -4,11 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow strict * @format */ -export { - registerAsset, - getAssetByID, -} from '@react-native/assets-registry/registry'; +export * from './AndroidPathUtils'; diff --git a/packages/react-native/src/private/components/virtualcollection/debug/FlingItemOverlay.js b/packages/asset-utils/src/index.js similarity index 59% rename from packages/react-native/src/private/components/virtualcollection/debug/FlingItemOverlay.js rename to packages/asset-utils/src/index.js index 5e4972bf464..4fdff4702d0 100644 --- a/packages/react-native/src/private/components/virtualcollection/debug/FlingItemOverlay.js +++ b/packages/asset-utils/src/index.js @@ -4,10 +4,14 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow strict-local + * @flow strict * @format */ -const FlingItemOverlay: ?component(nativeID: string) = null; +'use strict'; -export default FlingItemOverlay; +/*:: +export type {PackagerAsset} from './AndroidPathUtils'; +*/ + +module.exports = require('./AndroidPathUtils'); diff --git a/packages/assets-registry/README.md b/packages/assets-registry/README.md new file mode 100644 index 00000000000..1dd81110155 --- /dev/null +++ b/packages/assets-registry/README.md @@ -0,0 +1,39 @@ +# @react-native/assets-registry + +![npm package](https://img.shields.io/npm/v/@react-native/assets-registry?color=brightgreen&label=npm%20package) + +> [!Warning] +> **This package is deprecated (since 0.87)** and will be removed in a future release. Use [`AssetRegistry`](https://reactnative.dev/docs/assetregistry) from `react-native` (or the `react-native/asset-registry` build entry point) instead of `@react-native/assets-registry/registry`, and [`@react-native/asset-utils`](https://www.npmjs.com/package/@react-native/asset-utils) instead of `@react-native/assets-registry/path-support`. + +Runtime registry that maps asset IDs generated in a Metro bundle to asset metadata. It backs ``, `Image.resolveAssetSource()`, and any code that resolves `require('./img.png')` on native. + +Most apps never import this directly — assets are handled through ``. + +## API + +### `@react-native/assets-registry/registry` (DEPRECATED) + +> [!Warning] +> **Deprecated**: Aliases to [`AssetRegistry`](https://reactnative.dev/docs/assetregistry) (since 0.87). +> +> Please use: +> - `import { AssetRegistry } from 'react-native';` (apps/library code) +> - `'react-native/asset-registry'` (entrypoint for Metro/build configs) + +| Export | Signature | Notes | +|---|---|---| +| `registerAsset` | `(asset: PackagerAsset) => number` | Stores the asset; returns a numeric ID | +| `getAssetByID` | `(assetId: number) => PackagerAsset` | Looks an asset back up by ID | + +### `@react-native/assets-registry/path-support` (DEPRECATED) + +> [!Warning] +> **Deprecated**: Use [`@react-native/asset-utils`](https://www.npmjs.com/package/@react-native/asset-utils) (since 0.87). + +Android resource-path helpers, used when copying assets into `drawable-*` folders. + +| Export | Signature | Notes | +|---|---|---| +| `getAndroidResourceFolderName` | `(asset: PackagerAsset, scale: number) => string` | e.g. `drawable-xhdpi`; non-drawable types resolve to `raw` | +| `getAndroidResourceIdentifier` | `(asset: PackagerAsset) => string` | Sanitised resource name | +| `getBasePath` | `(asset: PackagerAsset) => string` | `httpServerLocation` without the leading slash | diff --git a/packages/assets/__tests__/path-support-test.js b/packages/assets-registry/__tests__/path-support-test.js similarity index 100% rename from packages/assets/__tests__/path-support-test.js rename to packages/assets-registry/__tests__/path-support-test.js diff --git a/packages/assets-registry/package.json b/packages/assets-registry/package.json new file mode 100644 index 00000000000..4929f5a28fd --- /dev/null +++ b/packages/assets-registry/package.json @@ -0,0 +1,34 @@ +{ + "name": "@react-native/assets-registry", + "version": "0.87.0-main", + "description": "Asset support code for React Native.", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/react/react-native.git", + "directory": "packages/assets-registry" + }, + "homepage": "https://github.com/react/react-native/tree/HEAD/packages/assets-registry#readme", + "keywords": [ + "react-native" + ], + "bugs": "https://github.com/react/react-native/issues", + "engines": { + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" + }, + "files": [ + "path-support.js", + "path-support.d.ts", + "registry.js", + "registry.d.ts", + "README.md", + "!**/__docs__/**", + "!**/__fixtures__/**", + "!**/__mocks__/**", + "!**/__tests__/**" + ], + "peerDependencies": { + "react-native": "*" + }, + "private": true +} diff --git a/packages/assets-registry/path-support.d.ts b/packages/assets-registry/path-support.d.ts new file mode 100644 index 00000000000..cddc55f69ba --- /dev/null +++ b/packages/assets-registry/path-support.d.ts @@ -0,0 +1,28 @@ +/** + * 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. + * + * @format + */ + +import type {PackagerAsset} from './registry'; + +/** + * @deprecated Use `getAndroidResourceFolderName` from `@react-native/asset-utils` instead. + */ +export function getAndroidResourceFolderName( + asset: PackagerAsset, + scale: number, +): string; + +/** + * @deprecated Use `getAndroidResourceIdentifier` from `@react-native/asset-utils` instead. + */ +export function getAndroidResourceIdentifier(asset: PackagerAsset): string; + +/** + * @deprecated Use `@react-native/asset-utils` instead. + */ +export function getBasePath(asset: PackagerAsset): string; diff --git a/packages/assets/path-support.js b/packages/assets-registry/path-support.js similarity index 99% rename from packages/assets/path-support.js rename to packages/assets-registry/path-support.js index c73a2e2bbf2..f7e51a9e6ec 100644 --- a/packages/assets/path-support.js +++ b/packages/assets-registry/path-support.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow strict + * @flow strict-local * @format */ diff --git a/packages/assets-registry/registry.d.ts b/packages/assets-registry/registry.d.ts new file mode 100644 index 00000000000..fb8bfcc2a15 --- /dev/null +++ b/packages/assets-registry/registry.d.ts @@ -0,0 +1,39 @@ +/** + * 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. + * + * @format + */ + +/** + * @deprecated Use `import type {AssetDestPathResolver} from 'react-native'` instead. + */ +export type AssetDestPathResolver = 'android' | 'generic'; + +/** + * @deprecated Use `import type {PackagerAsset} from 'react-native'` instead. + */ +export type PackagerAsset = { + readonly __packager_asset: boolean; + readonly fileSystemLocation: string; + readonly httpServerLocation: string; + readonly width: number | null | undefined; + readonly height: number | null | undefined; + readonly scales: Array; + readonly hash: string; + readonly name: string; + readonly type: string; + readonly resolver?: AssetDestPathResolver | undefined; +}; + +/** + * @deprecated Use `import {AssetRegistry} from 'react-native'` instead. + */ +export function registerAsset(asset: PackagerAsset): number; + +/** + * @deprecated Use `import {AssetRegistry} from 'react-native'` instead. + */ +export function getAssetByID(assetId: number): PackagerAsset; diff --git a/packages/assets-registry/registry.js b/packages/assets-registry/registry.js new file mode 100644 index 00000000000..08a0ef59fb3 --- /dev/null +++ b/packages/assets-registry/registry.js @@ -0,0 +1,23 @@ +/** + * 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. + * + * @flow strict-local + * @format + */ + +'use strict'; + +import {AssetRegistry} from 'react-native'; + +/*:: +export type {AssetDestPathResolver, PackagerAsset} from 'react-native'; +*/ + +// eslint-disable-next-line @react-native/monorepo/no-commonjs-exports +module.exports = { + registerAsset: AssetRegistry.registerAsset, + getAssetByID: AssetRegistry.getAssetByID, +}; diff --git a/packages/assets/README.md b/packages/assets/README.md deleted file mode 100644 index 407cc5b53de..00000000000 --- a/packages/assets/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# @react-native/assets-registry - -[![Version][version-badge]][package] - -## Installation - -``` -yarn add --dev @react-native/assets-registry -``` - -*Note: We're using `yarn` to install deps. Feel free to change commands to use `npm` 3+ and `npx` if you like* - -[version-badge]: https://img.shields.io/npm/v/@react-native/assets-registry?style=flat-square -[package]: https://www.npmjs.com/package/@react-native/assets-registry - -## Testing - -To run the tests in this package, run the following commands from the React Native root folder: - -1. `yarn` to install the dependencies. You just need to run this once -2. `yarn jest packages/assets`. diff --git a/packages/assets/package.json b/packages/assets/package.json deleted file mode 100644 index 853eeb4f9b2..00000000000 --- a/packages/assets/package.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "@react-native/assets-registry", - "version": "0.86.0-main", - "description": "Asset support code for React Native.", - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/facebook/react-native.git", - "directory": "packages/assets" - }, - "homepage": "https://github.com/facebook/react-native/tree/HEAD/packages/assets#readme", - "keywords": [ - "assets", - "registry", - "react-native", - "support" - ], - "bugs": "https://github.com/facebook/react-native/issues", - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - }, - "files": [ - "path-support.js", - "registry.js", - "README.md", - "!**/__docs__/**", - "!**/__fixtures__/**", - "!**/__mocks__/**", - "!**/__tests__/**" - ], - "private": true -} diff --git a/packages/assets/registry.js b/packages/assets/registry.js deleted file mode 100644 index 435bd519411..00000000000 --- a/packages/assets/registry.js +++ /dev/null @@ -1,44 +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. - * - * @flow strict - * @format - */ - -'use strict'; - -/*:: -export type AssetDestPathResolver = 'android' | 'generic'; - -export type PackagerAsset = { - +__packager_asset: boolean, - +fileSystemLocation: string, - +httpServerLocation: string, - +width: ?number, - +height: ?number, - +scales: Array, - +hash: string, - +name: string, - +type: string, - +resolver?: AssetDestPathResolver, - ... -}; -*/ - -const assets /*: Array */ = []; - -function registerAsset(asset /*: PackagerAsset */) /*: number */ { - // `push` returns new array length, so the first asset will - // get id 1 (not 0) to make the value truthy - return assets.push(asset); -} - -function getAssetByID(assetId /*: number */) /*: PackagerAsset */ { - return assets[assetId - 1]; -} - -// eslint-disable-next-line @react-native/monorepo/no-commonjs-exports -module.exports = {registerAsset, getAssetByID}; diff --git a/packages/babel-plugin-codegen/README.md b/packages/babel-plugin-codegen/README.md index 34194810ab2..430d36ac3b8 100644 --- a/packages/babel-plugin-codegen/README.md +++ b/packages/babel-plugin-codegen/README.md @@ -1,21 +1,8 @@ # @react-native/babel-plugin-codegen -[![Version][version-badge]][package] +[![npm]](https://www.npmjs.com/package/@react-native/babel-plugin-codegen) [![npm downloads]](https://www.npmjs.com/package/@react-native/babel-plugin-codegen) -## Installation +[npm]: https://img.shields.io/npm/v/@react-native/babel-plugin-codegen.svg?color=blue +[npm downloads]: https://img.shields.io/npm/dm/@react-native/babel-plugin-codegen.svg -``` -yarn add --dev @babel/core @react-native/babel-plugin-codegen -``` - -*Note: We're using `yarn` to install deps. Feel free to change commands to use `npm` 3+ and `npx` if you like* - -[version-badge]: https://img.shields.io/npm/v/@react-native/babel-plugin-codegen?style=flat-square -[package]: https://www.npmjs.com/package/@react-native/babel-plugin-codegen - -## Testing - -To run the tests in this package, run the following commands from the React Native root folder: - -1. `yarn` to install the dependencies. You just need to run this once -2. `yarn jest packages/babel-plugin-codegen`. +Babel plugin that generates native module and view manager binding code for React Native, powered by [`@react-native/codegen`](https://www.npmjs.com/package/@react-native/codegen). diff --git a/packages/babel-plugin-codegen/package.json b/packages/babel-plugin-codegen/package.json index 22d21ba8bbd..166bd3cb4dc 100644 --- a/packages/babel-plugin-codegen/package.json +++ b/packages/babel-plugin-codegen/package.json @@ -1,14 +1,14 @@ { "name": "@react-native/babel-plugin-codegen", - "version": "0.86.0-main", + "version": "0.87.0-main", "description": "Babel plugin to generate native module and view manager code for React Native.", "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/facebook/react-native.git", + "url": "git+https://github.com/react/react-native.git", "directory": "packages/babel-plugin-codegen" }, - "homepage": "https://github.com/facebook/react-native/tree/HEAD/packages/babel-plugin-codegen#readme", + "homepage": "https://github.com/react/react-native/tree/HEAD/packages/babel-plugin-codegen#readme", "keywords": [ "babel", "plugin", @@ -17,9 +17,9 @@ "native-modules", "view-manager" ], - "bugs": "https://github.com/facebook/react-native/issues", + "bugs": "https://github.com/react/react-native/issues", "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" }, "files": [ "index.js" diff --git a/packages/community-cli-plugin/README.md b/packages/community-cli-plugin/README.md index dc4c687208c..030640fdc67 100644 --- a/packages/community-cli-plugin/README.md +++ b/packages/community-cli-plugin/README.md @@ -1,5 +1,10 @@ # @react-native/community-cli-plugin +[![npm]](https://www.npmjs.com/package/@react-native/community-cli-plugin) [![npm downloads]](https://www.npmjs.com/package/@react-native/community-cli-plugin) + +[npm]: https://img.shields.io/npm/v/@react-native/community-cli-plugin.svg?color=blue +[npm downloads]: https://img.shields.io/npm/dm/@react-native/community-cli-plugin.svg + > This is an internal dependency of React Native. **Please don't depend on it directly.** CLI entry points supporting core React Native development features. diff --git a/packages/community-cli-plugin/package.json b/packages/community-cli-plugin/package.json index 1ea650093b0..6d15fc021ba 100644 --- a/packages/community-cli-plugin/package.json +++ b/packages/community-cli-plugin/package.json @@ -1,16 +1,16 @@ { "name": "@react-native/community-cli-plugin", - "version": "0.86.0-main", + "version": "0.87.0-main", "description": "Core CLI commands for React Native", "keywords": [ "react-native", "tools" ], - "homepage": "https://github.com/facebook/react-native/tree/HEAD/packages/community-cli-plugin#readme", - "bugs": "https://github.com/facebook/react-native/issues", + "homepage": "https://github.com/react/react-native/tree/HEAD/packages/community-cli-plugin#readme", + "bugs": "https://github.com/react/react-native/issues", "repository": { "type": "git", - "url": "git+https://github.com/facebook/react-native.git", + "url": "git+https://github.com/react/react-native.git", "directory": "packages/community-cli-plugin" }, "license": "MIT", @@ -31,17 +31,17 @@ "prepack": "node ../../scripts/build/prepack.js" }, "dependencies": { + "@react-native/asset-utils": "workspace:*", "@react-native/dev-middleware": "workspace:*", + "chalk": "^4.0.0", "debug": "^4.4.0", "invariant": "^2.2.4", - "metro": "^0.84.3", - "metro-config": "^0.84.3", - "metro-core": "^0.84.3", - "semver": "^7.1.3", - "chalk": "^4.0.0" + "metro": "^0.86.0", + "metro-config": "^0.86.0", + "semver": "^7.1.3" }, "devDependencies": { - "metro-resolver": "^0.84.3" + "metro-resolver": "^0.86.0" }, "peerDependencies": { "@react-native-community/cli": "*", @@ -56,7 +56,7 @@ } }, "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" }, "private": true } diff --git a/packages/community-cli-plugin/src/commands/bundle/__tests__/filterPlatformAssetScales-test.js b/packages/community-cli-plugin/src/commands/bundle/__tests__/filterPlatformAssetScales-test.js index 5973fdceb67..d5ff14434f1 100644 --- a/packages/community-cli-plugin/src/commands/bundle/__tests__/filterPlatformAssetScales-test.js +++ b/packages/community-cli-plugin/src/commands/bundle/__tests__/filterPlatformAssetScales-test.js @@ -10,7 +10,7 @@ import filterPlatformAssetScales from '../filterPlatformAssetScales'; -jest.dontMock('../filterPlatformAssetScales').dontMock('../assetPathUtils'); +jest.dontMock('../filterPlatformAssetScales'); describe('filterPlatformAssetScales', () => { test('removes everything but 2x and 3x for iOS', () => { diff --git a/packages/community-cli-plugin/src/commands/bundle/__tests__/getAssetDestPathAndroid-test.js b/packages/community-cli-plugin/src/commands/bundle/__tests__/getAssetDestPathAndroid-test.js index c2198025d69..55d0456bc80 100644 --- a/packages/community-cli-plugin/src/commands/bundle/__tests__/getAssetDestPathAndroid-test.js +++ b/packages/community-cli-plugin/src/commands/bundle/__tests__/getAssetDestPathAndroid-test.js @@ -12,7 +12,9 @@ import getAssetDestPathAndroid from '../getAssetDestPathAndroid'; const path = require('path'); -jest.dontMock('../getAssetDestPathAndroid').dontMock('../assetPathUtils'); +jest + .dontMock('../getAssetDestPathAndroid') + .dontMock('@react-native/asset-utils'); describe('getAssetDestPathAndroid', () => { test('should use the right destination folder', () => { diff --git a/packages/community-cli-plugin/src/commands/bundle/assetCatalogIOS.js b/packages/community-cli-plugin/src/commands/bundle/assetCatalogIOS.js index eefc3c6d5ad..64577c397e0 100644 --- a/packages/community-cli-plugin/src/commands/bundle/assetCatalogIOS.js +++ b/packages/community-cli-plugin/src/commands/bundle/assetCatalogIOS.js @@ -10,7 +10,7 @@ import type {AssetData} from 'metro'; -import assetPathUtils from './assetPathUtils'; +import {getAndroidResourceIdentifier} from '@react-native/asset-utils'; import fs from 'fs'; import path from 'path'; @@ -33,7 +33,7 @@ export function getImageSet( asset: AssetData, scales: ReadonlyArray, ): ImageSet { - const fileName = assetPathUtils.getResourceIdentifier(asset); + const fileName = getAndroidResourceIdentifier(asset); return { basePath: path.join(catalogDir, `${fileName}.imageset`), files: scales.map((scale, idx) => { diff --git a/packages/community-cli-plugin/src/commands/bundle/assetPathUtils.js b/packages/community-cli-plugin/src/commands/bundle/assetPathUtils.js deleted file mode 100644 index 1ac88c6c01e..00000000000 --- a/packages/community-cli-plugin/src/commands/bundle/assetPathUtils.js +++ /dev/null @@ -1,95 +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. - * - * @flow strict-local - * @format - */ - -export type PackagerAsset = Readonly<{ - httpServerLocation: string, - name: string, - type: string, - ... -}>; - -/** - * FIXME: using number to represent discrete scale numbers is fragile in essence because of - * floating point numbers imprecision. - */ -function getAndroidAssetSuffix(scale: number): string { - switch (scale) { - case 0.75: - return 'ldpi'; - case 1: - return 'mdpi'; - case 1.5: - return 'hdpi'; - case 2: - return 'xhdpi'; - case 3: - return 'xxhdpi'; - case 4: - return 'xxxhdpi'; - default: - return ''; - } -} - -// See https://developer.android.com/guide/topics/resources/drawable-resource.html -const drawableFileTypes: Set = new Set([ - 'gif', - 'heic', - 'heif', - 'jpeg', - 'jpg', - 'png', - 'webp', - 'xml', -]); - -function getAndroidResourceFolderName( - asset: PackagerAsset, - scale: number, -): string { - if (!drawableFileTypes.has(asset.type)) { - return 'raw'; - } - const suffix = getAndroidAssetSuffix(scale); - if (!suffix) { - throw new Error( - `Don't know which android drawable suffix to use for asset: ${JSON.stringify( - asset, - )}`, - ); - } - const androidFolder = `drawable-${suffix}`; - return androidFolder; -} - -function getResourceIdentifier(asset: PackagerAsset): string { - const folderPath = getBasePath(asset); - return `${folderPath}/${asset.name}` - .toLowerCase() - .replace(/\//g, '_') // Encode folder structure in file name - .replace(/([^a-z0-9_])/g, '') // Remove illegal chars - .replace(/^(?:assets|assetsunstable_path)_/, ''); // Remove "assets_" or "assetsunstable_path_" prefix -} - -function getBasePath(asset: PackagerAsset): string { - let basePath = asset.httpServerLocation; - if (basePath[0] === '/') { - basePath = basePath.substr(1); - } - return basePath; -} - -export default { - drawableFileTypes, - getAndroidAssetSuffix, - getAndroidResourceFolderName, - getResourceIdentifier, - getBasePath, -}; diff --git a/packages/community-cli-plugin/src/commands/bundle/buildBundle.js b/packages/community-cli-plugin/src/commands/bundle/buildBundle.js index 2b02160e8ac..7edd9dccb74 100644 --- a/packages/community-cli-plugin/src/commands/bundle/buildBundle.js +++ b/packages/community-cli-plugin/src/commands/bundle/buildBundle.js @@ -10,7 +10,6 @@ import type {Config} from '@react-native-community/cli-types'; import type {RunBuildOptions} from 'metro'; -import type {ConfigT} from 'metro-config'; import loadMetroConfig from '../../utils/loadMetroConfig'; import parseKeyValueParamArray from '../../utils/parseKeyValueParamArray'; @@ -20,6 +19,8 @@ import {runBuild} from 'metro'; import path from 'path'; import {styleText} from 'util'; +type HydratedMetroConfig = Awaited>; + export type BundleCommandArgs = { assetsDest?: string, assetCatalogDest?: string, @@ -60,7 +61,7 @@ async function buildBundle( async function buildBundleWithConfig( args: BundleCommandArgs, - config: ConfigT, + config: HydratedMetroConfig, bundleImpl?: RunBuildOptions['output'], ): Promise { const customResolverOptions = parseKeyValueParamArray( diff --git a/packages/community-cli-plugin/src/commands/bundle/createKeepFileAsync.js b/packages/community-cli-plugin/src/commands/bundle/createKeepFileAsync.js index 479c1bdf014..29050c74e79 100644 --- a/packages/community-cli-plugin/src/commands/bundle/createKeepFileAsync.js +++ b/packages/community-cli-plugin/src/commands/bundle/createKeepFileAsync.js @@ -10,7 +10,10 @@ import type {AssetData} from 'metro'; -import assetPathUtils from './assetPathUtils'; +import { + drawableFileTypes, + getAndroidResourceIdentifier, +} from '@react-native/asset-utils'; import fs from 'fs'; import path from 'path'; @@ -23,12 +26,8 @@ async function createKeepFileAsync( } const assetsList = []; for (const asset of assets) { - const prefix = assetPathUtils.drawableFileTypes.has(asset.type) - ? 'drawable' - : 'raw'; - assetsList.push( - `@${prefix}/${assetPathUtils.getResourceIdentifier(asset)}`, - ); + const prefix = drawableFileTypes.has(asset.type) ? 'drawable' : 'raw'; + assetsList.push(`@${prefix}/${getAndroidResourceIdentifier(asset)}`); } const keepPath = path.join(outputDirectory, 'raw/keep.xml'); const content = `\n`; diff --git a/packages/community-cli-plugin/src/commands/bundle/getAssetDestPathAndroid.js b/packages/community-cli-plugin/src/commands/bundle/getAssetDestPathAndroid.js index 6a8c913ce84..d847c6e7708 100644 --- a/packages/community-cli-plugin/src/commands/bundle/getAssetDestPathAndroid.js +++ b/packages/community-cli-plugin/src/commands/bundle/getAssetDestPathAndroid.js @@ -8,17 +8,17 @@ * @format */ -import type {PackagerAsset} from './assetPathUtils'; +import type {PackagerAsset} from '@react-native/asset-utils'; -import assetPathUtils from './assetPathUtils'; +import { + getAndroidResourceFolderName, + getAndroidResourceIdentifier, +} from '@react-native/asset-utils'; import path from 'path'; function getAssetDestPathAndroid(asset: PackagerAsset, scale: number): string { - const androidFolder = assetPathUtils.getAndroidResourceFolderName( - asset, - scale, - ); - const fileName = assetPathUtils.getResourceIdentifier(asset); + const androidFolder = getAndroidResourceFolderName(asset, scale); + const fileName = getAndroidResourceIdentifier(asset); return path.join(androidFolder, `${fileName}.${asset.type}`); } diff --git a/packages/community-cli-plugin/src/commands/bundle/getAssetDestPathIOS.js b/packages/community-cli-plugin/src/commands/bundle/getAssetDestPathIOS.js index 8f4cb0926a2..5c3e37c1dff 100644 --- a/packages/community-cli-plugin/src/commands/bundle/getAssetDestPathIOS.js +++ b/packages/community-cli-plugin/src/commands/bundle/getAssetDestPathIOS.js @@ -8,7 +8,7 @@ * @format */ -import type {PackagerAsset} from './assetPathUtils'; +import type {PackagerAsset} from '@react-native/asset-utils'; import path from 'path'; diff --git a/packages/community-cli-plugin/src/commands/start/runServer.js b/packages/community-cli-plugin/src/commands/start/runServer.js index 22962457a2b..6d298489d71 100644 --- a/packages/community-cli-plugin/src/commands/start/runServer.js +++ b/packages/community-cli-plugin/src/commands/start/runServer.js @@ -18,8 +18,7 @@ import * as version from '../../utils/version'; import attachKeyHandlers from './attachKeyHandlers'; import {createDevServerMiddleware} from './middleware'; import {createDevMiddleware} from '@react-native/dev-middleware'; -import Metro from 'metro'; -import {Terminal} from 'metro-core'; +import * as Metro from 'metro'; import path from 'path'; import url from 'url'; import {styleText} from 'util'; @@ -104,7 +103,7 @@ async function runServer( } let reportEvent: (event: TerminalReportableEvent) => void; - const terminal = new Terminal(process.stdout); + const terminal = new Metro.Terminal(process.stdout); const ReporterImpl = getReporterImpl(args.customLogReporterPath); const terminalReporter = new ReporterImpl(terminal); diff --git a/packages/community-cli-plugin/src/utils/loadMetroConfig.js b/packages/community-cli-plugin/src/utils/loadMetroConfig.js index 8b5883a3148..678dbbab2f7 100644 --- a/packages/community-cli-plugin/src/utils/loadMetroConfig.js +++ b/packages/community-cli-plugin/src/utils/loadMetroConfig.js @@ -9,15 +9,18 @@ */ import type {Config} from '@react-native-community/cli-types'; -import type {ConfigT, InputConfigT, YargArguments} from 'metro-config'; +import type {InputConfigT} from 'metro-config'; import {CLIError} from './errors'; import {reactNativePlatformResolver} from './metroPlatformResolver'; -import {loadConfig, resolveConfig} from 'metro-config'; +import {loadConfig, resolveConfig} from 'metro'; import path from 'path'; const debug = require('debug')('ReactNative:CommunityCliPlugin'); +type HydratedMetroConfig = Awaited>; +type ArgvInput = Parameters[0]; + export type {Config}; export type ConfigLoadingContext = Readonly<{ @@ -32,28 +35,29 @@ export type ConfigLoadingContext = Readonly<{ */ function getCommunityCliDefaultConfig( ctx: ConfigLoadingContext, - config: ConfigT, + config: HydratedMetroConfig, ): InputConfigT { const outOfTreePlatforms = Object.keys(ctx.platforms).filter( platform => ctx.platforms[platform].npmPackageName, ); - const resolver: Partial<{...ConfigT['resolver']}> = { + const resolver: NonNullable = { platforms: [...Object.keys(ctx.platforms), 'native'], + ...(outOfTreePlatforms.length > 0 + ? { + resolveRequest: reactNativePlatformResolver( + outOfTreePlatforms.reduce<{[platform: string]: string}>( + (result, platform) => { + result[platform] = ctx.platforms[platform].npmPackageName; + return result; + }, + {}, + ), + config.resolver?.resolveRequest, + ), + } + : {}), }; - if (outOfTreePlatforms.length) { - resolver.resolveRequest = reactNativePlatformResolver( - outOfTreePlatforms.reduce<{[platform: string]: string}>( - (result, platform) => { - result[platform] = ctx.platforms[platform].npmPackageName; - return result; - }, - {}, - ), - config.resolver?.resolveRequest, - ); - } - return { resolver, serializer: { @@ -83,8 +87,8 @@ function getCommunityCliDefaultConfig( */ export default async function loadMetroConfig( ctx: ConfigLoadingContext, - options: YargArguments = {}, -): Promise { + options: NonNullable = {}, +): Promise { let RNMetroConfig = null; try { RNMetroConfig = require('@react-native/metro-config'); diff --git a/packages/community-cli-plugin/src/utils/version.js b/packages/community-cli-plugin/src/utils/version.js index 27f455a8aae..120971b656a 100644 --- a/packages/community-cli-plugin/src/utils/version.js +++ b/packages/community-cli-plugin/src/utils/version.js @@ -88,6 +88,7 @@ Diff: ${styleText(['dim', 'underline'], newVersion?.diffUrl ?? 'none')} } } +// $FlowFixMe[incompatible-type-guard] function isDiffPurgeEntry(data: Partial): data is DiffPurge { return ( // $FlowFixMe[incompatible-type-guard] diff --git a/packages/core-cli-utils/README.md b/packages/core-cli-utils/README.md deleted file mode 100644 index 10ea4acc757..00000000000 --- a/packages/core-cli-utils/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# @react-native/core-cli-utils - -![npm package](https://img.shields.io/npm/v/@react-native/core-cli-utils?color=brightgreen&label=npm%20package) - -A collection of utilites to help Frameworks build their React Native CLI tooling. This is not intended to be used directly use users of React Native. - -## Usage - -```js -import { Command } from 'commander'; -import cli from '@react-native/core-cli-utils'; -import debug from 'debug'; - -const android = new Command('android'); - -const frameworkFindsAndroidSrcDir = "..."; -const tasks = cli.clean.android(frameworkFindsAndroidSrcDir); -const log = debug('fancy-framework:android'); - -android - .command('clean') - .description(cli.clean.android) - .action(async () => { - const log = debug('fancy-framework:android:clean'); - log(`🧹 let me clean your Android caches`); - // Add other caches your framework needs besides the normal React Native caches - // here. - for (const task of tasks) { - try { - log(`\t ${task.label}`); - // See: https://github.com/sindresorhus/execa#lines - const {stdout} = await task.action({ lines: true }) - log(stdout.join('\n\tGradle: ')); - } catch (e) { - log(`\t ⚠️ whoops: ${e.message}`); - } - } - }); -``` - -And you'd be using it like this: - -```bash -$ ./fancy-framework android clean -🧹 let me clean your Android caches - Gradle: // a bunch of gradle output - Gradle: .... -``` - -## Features -- `"@react-native/core-cli-utils/version.js"` contains the platform and tooling version requirements for react-native. - -## Contributing - -Changes to this package can be made locally and linked against your app. Please see the [Contributing guide](https://reactnative.dev/contributing/overview#contributing-code). diff --git a/packages/core-cli-utils/package.json b/packages/core-cli-utils/package.json deleted file mode 100644 index a580e96e767..00000000000 --- a/packages/core-cli-utils/package.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "@react-native/core-cli-utils", - "version": "0.86.0-main", - "description": "React Native CLI library for Frameworks to build on", - "license": "MIT", - "keywords": [ - "cli-utils", - "react-native" - ], - "homepage": "https://github.com/facebook/react-native/tree/HEAD/packages/core-cli-utils#readme", - "bugs": "https://github.com/facebook/react-native/issues", - "repository": { - "type": "git", - "url": "git+https://github.com/facebook/react-native.git", - "directory": "packages/core-cli-utils" - }, - "main": "./src/index.flow.js", - "exports": { - ".": "./src/index.js", - "./package.json": "./package.json", - "./version.js": "./src/public/version.js" - }, - "publishConfig": { - "main": "./dist/index.js", - "exports": { - ".": "./dist/index.js", - "./package.json": "./package.json", - "./version.js": "./dist/public/version.js" - } - }, - "files": [ - "dist" - ], - "scripts": { - "prepack": "node ../../scripts/build/prepack.js" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - }, - "private": true -} diff --git a/packages/core-cli-utils/src/index.flow.js b/packages/core-cli-utils/src/index.flow.js deleted file mode 100644 index 966caa90512..00000000000 --- a/packages/core-cli-utils/src/index.flow.js +++ /dev/null @@ -1,25 +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. - * - * @flow strict-local - * @format - */ - -// @babel/register doesn't like export {foo} from './bar'; statements, -// so we have to jump through hoops here. -import {tasks as _android} from './private/android.js'; -import {tasks as _app} from './private/app.js'; -import {tasks as _apple} from './private/apple.js'; -import {tasks as _clean} from './private/clean.js'; -import * as _version from './public/version.js'; - -export const android = _android; -export const app = _app; -export const apple = _apple; -export const clean = _clean; -export const version = _version; - -export type {Task} from './private/types'; diff --git a/packages/core-cli-utils/src/public/version.js b/packages/core-cli-utils/src/public/version.js deleted file mode 100644 index a5aa0613fa5..00000000000 --- a/packages/core-cli-utils/src/public/version.js +++ /dev/null @@ -1,19 +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. - * - * @flow strict-local - * @format - */ - -/*:: -export type * from './version.flow'; -*/ - -if (process.env.BUILD_EXCLUDE_BABEL_REGISTER == null) { - require('../../../../scripts/shared/babelRegister').registerForMonorepo(); -} - -module.exports = require('./version.flow'); diff --git a/packages/debugger-frontend/README.md b/packages/debugger-frontend/README.md index 5860f8268d1..13aaf8b49c4 100644 --- a/packages/debugger-frontend/README.md +++ b/packages/debugger-frontend/README.md @@ -1,10 +1,11 @@ # @react-native/debugger-frontend -![npm package](https://img.shields.io/npm/v/@react-native/debugger-frontend?color=brightgreen&label=npm%20package) +[![npm]](https://www.npmjs.com/package/@react-native/debugger-frontend) [![npm downloads]](https://www.npmjs.com/package/@react-native/debugger-frontend) -Debugger frontend for React Native based on Chrome DevTools. +[npm]: https://img.shields.io/npm/v/@react-native/debugger-frontend.svg?color=blue +[npm downloads]: https://img.shields.io/npm/dm/@react-native/debugger-frontend.svg -This package is internal to React Native and is intended to be used via [`@react-native/dev-middleware`](https://www.npmjs.com/package/@react-native/dev-middleware). +Debugger frontend for React Native based on Chrome DevTools. It is intended to be used via [`@react-native/dev-middleware`](https://www.npmjs.com/package/@react-native/dev-middleware). ## Usage @@ -20,7 +21,7 @@ const frontendPath = require('@react-native/debugger-frontend'); ### Source repo -Source code for this package lives in the [facebook/react-native-devtools-frontend](https://github.com/facebook/react-native-devtools-frontend) repo. See below for how we build and check in changes. +Source code for this package lives in the [react/react-native-devtools-frontend](https://github.com/react/react-native-devtools-frontend) repo. See below for how we build and check in changes. ### Updating the frontend assets @@ -34,4 +35,4 @@ node scripts/debugger-frontend/sync-and-build --branch main node scripts/debugger-frontend/sync-and-build --branch 0.73-stable ``` -By default, this will clone and build from [facebook/react-native-devtools-frontend](https://github.com/facebook/react-native-devtools-frontend). +By default, this will clone and build from [react/react-native-devtools-frontend](https://github.com/react/react-native-devtools-frontend). diff --git a/packages/debugger-frontend/package.json b/packages/debugger-frontend/package.json index efffc508d4e..c7a9e765eac 100644 --- a/packages/debugger-frontend/package.json +++ b/packages/debugger-frontend/package.json @@ -1,16 +1,16 @@ { "name": "@react-native/debugger-frontend", - "version": "0.86.0-main", + "version": "0.87.0-main", "description": "Debugger frontend for React Native based on Chrome DevTools", "keywords": [ "react-native", "tools" ], - "homepage": "https://github.com/facebook/react-native/tree/HEAD/packages/debugger-frontend#readme", - "bugs": "https://github.com/facebook/react-native/issues", + "homepage": "https://github.com/react/react-native/tree/HEAD/packages/debugger-frontend#readme", + "bugs": "https://github.com/react/react-native/issues", "repository": { "type": "git", - "url": "git+https://github.com/facebook/react-native.git", + "url": "git+https://github.com/react/react-native.git", "directory": "packages/debugger-frontend" }, "license": "BSD-3-Clause", @@ -20,7 +20,7 @@ "BUILD_INFO" ], "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" }, "private": true } diff --git a/packages/debugger-shell/README.md b/packages/debugger-shell/README.md index 30c43bae907..f19ba22505e 100644 --- a/packages/debugger-shell/README.md +++ b/packages/debugger-shell/README.md @@ -1,8 +1,11 @@ # @react-native/debugger-shell -![npm package](https://img.shields.io/npm/v/@react-native/debugger-shell?color=brightgreen&label=npm%20package) +[![npm]](https://www.npmjs.com/package/@react-native/debugger-shell) [![npm downloads]](https://www.npmjs.com/package/@react-native/debugger-shell) -Experimental Electron-based shell for React Native DevTools. This package is not part of React Native's public API. +[npm]: https://img.shields.io/npm/v/@react-native/debugger-shell.svg?color=blue +[npm downloads]: https://img.shields.io/npm/dm/@react-native/debugger-shell.svg + +Experimental Electron-based shell for React Native DevTools. ## Why Electron? diff --git a/packages/debugger-shell/bin/react-native-devtools b/packages/debugger-shell/bin/react-native-devtools index 83c5d9d7ce0..3c6f6a90b9f 100755 --- a/packages/debugger-shell/bin/react-native-devtools +++ b/packages/debugger-shell/bin/react-native-devtools @@ -1,71 +1,71 @@ #!/usr/bin/env dotslash -// @generated SignedSource<> +// @generated SignedSource<<01dcf5246a0b3f95d617d1d52bc1998e>> { "name": "React Native DevTools", "platforms": { "linux-aarch64": { - "size": 115268850, + "size": 115271683, "hash": "sha256", - "digest": "869b2bab193f006e6b4771b1694123ad151fc8f06173271a95f091eb4d6c9ae5", + "digest": "020eea3cf10f77b8f4e1ae4a2aaa7d78a77c3802de6b00448866aa9c860509c4", "providers": [ { "type": "http", - "url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQN70skMEOeXpz3lAGnQLy88BvbdPlLRJat94hxxWEhvB3xwywu8k_bE_-ZaLJuvmzlF50eeNJgqwTAdS8O-J5KNW1MSvgg4LfUTlswe6DKllGDSgd_RVE4xwV_jxDNiYd6IX4fJF2NBlTxosYtKFUD6UaiXOESXxho2oi1mXM6kW851DNrg6vaH1fU" + "url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQPgk_PO-qLmLS1ZUyNtm9PZa0Z6XoCXar8Mo-ZDooUwm7-0tv-ga6cl1eB-9o0pVmKxH0-9xTS4NmFyyF1Qwn_x4J0olUnVH8AEHASw1VlnpkN5EPuTII29SmB_1Gis6_twDOo8CwWXFsa2p4XxRceIj-_IINp-fqrGuekCH-Kwd7BqsDsnBykigqY" } ], "format": "tar.gz", "path": "React Native DevTools-linux-arm64/React Native DevTools" }, "linux-x86_64": { - "size": 114837389, + "size": 114840132, "hash": "sha256", - "digest": "85af333cfbc3113b5f45646d8c0fbba5ae396d4f40c3617882d7936852cdb183", + "digest": "933aa98eb68da7f8d1b6e83e611e51f66d026f3b410dc13577faa6adf1b0f75c", "providers": [ { "type": "http", - "url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQMlrPALw0cm-NrVQAnC3KwZmtKmvh2lzgZgSNKL_ov5IBkCpM-wPR4xjxgYxjrRMNhMBJbr7l1OWbX5qiTY4KroFXcWH-l8KioKWY1TxBEX9DeVC-ORVMX0kvZkI5TiNMnfmesl64tPW1iTm7CB0vdlG5woAVrnY9lZGALP65N-L5RIBZzQB4tr" + "url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQM0YWmFH-pEdvrIlqLqZfCcRl1DxhzvJDOHJnFrBIVxrxjq6LqnqU9fwc7qXsS3JHcg7miVifXIM4cZTAsjVeusaVbQTYj2SevDy-9ajAnzjZj92m_WAwfmcp9gb7s9axqohmC1E_LCiC1BTdV-KEIyEcJWvZNJpFu3f44f8vpctlucJgm79I5n" } ], "format": "tar.gz", "path": "React Native DevTools-linux-x64/React Native DevTools" }, "macos-aarch64": { - "size": 112365024, + "size": 111220109, "hash": "sha256", - "digest": "8be411fc07384011739f2fa6c6ab9f97d0e6be787a8118adb7e8543fc01ae5ab", + "digest": "4a1c234b1cf27f1ff006fafce6628b8ef6993d7d3298c6ed2127e3055c93dcc6", "providers": [ { "type": "http", - "url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQNGm1TUXqJJYAVo1VGVpGs5YhcZ6uoEAh_yOsCkXAqTzWCcG2WkM5h_UB1KfOJ31Xicpz07mG_vw0DfZXisGLLGGA4j0AKupOPfyYQBHhNcbOVgtrb9qqVkfXZEKS9IwDbUG95YFodLcMyF6lXeHkgvWIFqkyEtO9bPnyx9RrtxMOG_3J61Q_NPBtxN" + "url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQNRWwxQIl4mkFPfIFJaGmmhuPTZXHz3uDISd0w_ptFazAGOLvONiln4ntQBt2iaBaNomMlXRuqAkdCXjHMu2lDarvjjZUfgtFpt8NEeEgILgWxbdB1j2rSiJrFZQoRb-VC0gGq3ALskrTQrLof9QJ_MSz_by779saXHEa_Wcd6-rk-D3PZyY6iwXYvy" } ], "format": "tar.gz", "path": "React Native DevTools.app/Contents/MacOS/React Native DevTools" }, "macos-x86_64": { - "size": 119261188, + "size": 119260983, "hash": "sha256", - "digest": "7e52e1c961a85bf6ffae7864e3c7f6d06cca8f0c01bc4ada63efb3aded6e9089", + "digest": "9f1de3cb78688befaea4d9e5f640a66833dd44ad58b05590a7e07bce279bf532", "providers": [ { "type": "http", - "url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQOpwivC5E7T-jcte46cm6vYrn0hBmyL2an8DgG2v4_ON-6Cmr9PWuwgUORlUvVKsQoJADcIVVu2q6svSfglSGD5UjMfHODZ6_pyjWZTyRMFjdZ72eRrCLgSv_oallI_-jjU-gzNeMlhdyg3yTjGBzmGLjVDpyZBvww-YGgDu3_rwdTozAzEf_lAIw" + "url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQMTN_mkHiVIEbUrA3teX7PDWCRTwVW9ffBc9X2rMjQOkZCzShBebMG1Zchl39m1P09Wxm0E_3r_Vg-ahgNsUzd1UzNiGmJ-4-T_xU_sEegXkheLn5Vi1_y5e0KVBQs0caeP5jUaUqn1JqrRRxwqwEPhV4YE8MCutcd3VXwluA4yI3ReI36c4BtgLg" } ], "format": "tar.gz", "path": "React Native DevTools.app/Contents/MacOS/React Native DevTools" }, "windows-x86_64": { - "size": 138044576, + "size": 138037080, "hash": "sha256", - "digest": "2a5b7271f5ac34c19c10db6084fe71272bb52efebdd8d0e4c2b0fd9b3aeb56bb", + "digest": "5b56e22828549f12be49f0431fd74c54dcebfbfb4277e4b87573ec1ab084c288", "providers": [ { "type": "http", - "url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQOh3711WMGWiwwjQncGDPeyt2IFAASumXOMz5ul082edhj_xuN8dXoptGpZLb95zou-x4arY5bg1phLNlu1wTkuRnV8oMRuGh_kGcg6-mpMmE-8lclNGnYhFX0BzNgMbVXtEmm5RDsicWJBTUy01MthQRgbCrB00ra_tZ31gZg1o19HhmLOMFBr" + "url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQNoewIvekFkYIhisR8u_iD720Mh6OCyo-JLIwW3AE0RZlWKUVtiMpi95a4kEX2mUSkVwcImnvEgpiKH7Oxz0ZlTGwxXyoUN4QrDQtfF5V4ng2rbBvdovpfev0qdxA3WGF9qcWmgE2qA5vfiHQ_dhOpogBxu3ZYaUkrQ3oZS3MrT9NU7HmRDWPpu" } ], "format": "tar.gz", diff --git a/packages/debugger-shell/package.json b/packages/debugger-shell/package.json index 8a7daf95693..af7e305e600 100644 --- a/packages/debugger-shell/package.json +++ b/packages/debugger-shell/package.json @@ -1,14 +1,14 @@ { "name": "@react-native/debugger-shell", "productName": "React Native DevTools", - "version": "0.86.0-main", + "version": "0.87.0-main", "description": "Experimental debugger shell for React Native for use with @react-native/debugger-frontend", "keywords": [ "react-native", "tools" ], - "homepage": "https://github.com/facebook/react-native/tree/HEAD/packages/debugger-shell#readme", - "bugs": "https://github.com/facebook/react-native/issues", + "homepage": "https://github.com/react/react-native/tree/HEAD/packages/debugger-shell#readme", + "bugs": "https://github.com/react/react-native/issues", "main": "./src/index.js", "exports": { ".": { @@ -33,12 +33,12 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/facebook/react-native.git", + "url": "git+https://github.com/react/react-native.git", "directory": "packages/debugger-shell" }, "license": "MIT", "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" }, "dependencies": { "cross-spawn": "^7.0.6", @@ -46,7 +46,7 @@ "fb-dotslash": "0.5.8" }, "devDependencies": { - "electron": "39.0.0", + "electron": "43.0.0", "semver": "^7.1.3" }, "files": [ diff --git a/packages/debugger-shell/src/electron/AppMenu.js b/packages/debugger-shell/src/electron/AppMenu.js new file mode 100644 index 00000000000..70b16a57e48 --- /dev/null +++ b/packages/debugger-shell/src/electron/AppMenu.js @@ -0,0 +1,129 @@ +/** + * 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. + * + * @flow strict-local + * @format + */ + +const {BrowserWindow, Menu, app, nativeImage, shell} = + // $FlowFixMe[unclear-type] We have no Flow types for the Electron API. + require('electron') as any; + +const {isMacOSAtLeast} = require('./utils'); + +export function configureAppMenu(): void { + const template = [ + ...(process.platform === 'darwin' ? [{role: 'appMenu'}] : []), + { + label: 'File', + submenu: [ + { + label: 'Reload App', + accelerator: + process.platform === 'darwin' ? 'Command+R' : 'Control+R', + click: () => invokeCommand('inspector-main.reload'), + }, + { + label: 'Reload DevTools', + accelerator: process.platform === 'darwin' ? 'Option+R' : 'Alt+R', + click: () => BrowserWindow.getFocusedWindow()?.webContents.reload(), + }, + {type: 'separator'}, + { + label: 'Quick Open…', + ...menuSymbol('doc.text.magnifyingglass'), + accelerator: + process.platform === 'darwin' ? 'Command+P' : 'Control+P', + click: () => invokeCommand('quick-open.show'), + }, + {type: 'separator'}, + {role: 'close'}, + ], + }, + { + label: 'Edit', + submenu: [ + {role: 'undo'}, + {role: 'redo'}, + {type: 'separator'}, + {role: 'cut'}, + {role: 'copy'}, + {role: 'paste'}, + {role: 'selectAll'}, + ], + }, + { + label: 'View', + submenu: [ + { + label: 'Command Palette…', + ...menuSymbol('filemenu.and.selection'), + accelerator: + process.platform === 'darwin' + ? 'Command+Shift+P' + : 'Control+Shift+P', + click: () => invokeCommand('quick-open.show-command-menu'), + }, + // Enable Developer Tools only in development + ...(!app.isPackaged + ? [{type: 'separator'}, {role: 'toggleDevTools'}] + : []), + {type: 'separator'}, + {role: 'resetZoom'}, + {role: 'zoomIn'}, + {role: 'zoomOut'}, + {type: 'separator'}, + {role: 'togglefullscreen'}, + ], + }, + {role: 'windowMenu'}, + { + role: 'help', + submenu: [ + { + label: 'Keyboard Shortcuts', + ...menuSymbol('keyboard'), + click: () => invokeCommand('settings.shortcuts'), + }, + {type: 'separator'}, + { + label: 'React Native Website', + click: () => shell.openExternal('https://reactnative.dev'), + }, + { + label: 'Release Notes', + click: () => + shell.openExternal( + 'https://github.com/facebook/react-native/releases', + ), + }, + ], + }, + ]; + const menu = Menu.buildFromTemplate(template); + Menu.setApplicationMenu(menu); +} + +function menuSymbol(symbolName: string): {icon?: unknown} { + if (!isMacOSAtLeast(26)) { + return {}; + } + return { + icon: nativeImage.createMenuSymbol(symbolName), + }; +} + +function invokeCommand(commandId: string): void { + const win = BrowserWindow.getFocusedWindow(); + win?.webContents.executeJavaScript( + `(async () => { + const UI = await import('./ui/legacy/legacy.js'); + return UI.ActionRegistry.ActionRegistry.instance() + .getAction(${JSON.stringify(commandId)})?.execute(); + })()`, + true, + ); +} diff --git a/packages/debugger-shell/src/electron/MainInstanceEntryPoint.js b/packages/debugger-shell/src/electron/MainInstanceEntryPoint.js index 77ae7250645..851590f4579 100644 --- a/packages/debugger-shell/src/electron/MainInstanceEntryPoint.js +++ b/packages/debugger-shell/src/electron/MainInstanceEntryPoint.js @@ -8,13 +8,14 @@ * @format */ +import {configureAppMenu} from './AppMenu.js'; import SettingsStore from './SettingsStore.js'; const path = require('path'); const util = require('util'); // $FlowFixMe[unclear-type] We have no Flow types for the Electron API. -const {BrowserWindow, Menu, app, shell, ipcMain} = require('electron') as any; +const {BrowserWindow, app, shell, ipcMain} = require('electron') as any; const appSettings = new SettingsStore(); const windowMetadata = new WeakMap< @@ -102,34 +103,6 @@ function handleLaunchArgs(argv: string[]) { frontendWindow.focus(); } -function configureAppMenu() { - const template = [ - ...(process.platform === 'darwin' ? [{role: 'appMenu'}] : []), - {role: 'fileMenu'}, - {role: 'editMenu'}, - {role: 'viewMenu'}, - {role: 'windowMenu'}, - { - role: 'help', - submenu: [ - { - label: 'React Native Website', - click: () => shell.openExternal('https://reactnative.dev'), - }, - { - label: 'Release Notes', - click: () => - shell.openExternal( - 'https://github.com/facebook/react-native/releases', - ), - }, - ], - }, - ]; - const menu = Menu.buildFromTemplate(template); - Menu.setApplicationMenu(menu); -} - function getSavedWindowPosition( windowKey: string, ): ?{width: number, height: number, x?: number, y?: number} { diff --git a/packages/debugger-shell/src/electron/resources/icon.icns b/packages/debugger-shell/src/electron/resources/AppIcon.icns similarity index 100% rename from packages/debugger-shell/src/electron/resources/icon.icns rename to packages/debugger-shell/src/electron/resources/AppIcon.icns diff --git a/packages/debugger-shell/src/electron/resources/AppIcon.icon/Assets/logo_light.svg b/packages/debugger-shell/src/electron/resources/AppIcon.icon/Assets/logo_light.svg new file mode 100644 index 00000000000..18508013dfc --- /dev/null +++ b/packages/debugger-shell/src/electron/resources/AppIcon.icon/Assets/logo_light.svg @@ -0,0 +1,12 @@ + + + logo_light + + + + + + + + + \ No newline at end of file diff --git a/packages/debugger-shell/src/electron/resources/AppIcon.icon/icon.json b/packages/debugger-shell/src/electron/resources/AppIcon.icon/icon.json new file mode 100644 index 00000000000..84230975686 --- /dev/null +++ b/packages/debugger-shell/src/electron/resources/AppIcon.icon/icon.json @@ -0,0 +1,41 @@ +{ + "fill" : { + "linear-gradient" : [ + "display-p3:0.21574,0.21966,0.23974,1.00000", + "display-p3:0.13729,0.14116,0.15294,1.00000" + ] + }, + "groups" : [ + { + "blur-material" : null, + "layers" : [ + { + "glass" : false, + "image-name" : "logo_light.svg", + "name" : "logo_light", + "position" : { + "scale" : 1.37, + "translation-in-points" : [ + 0, + 0 + ] + } + } + ], + "shadow" : { + "kind" : "neutral", + "opacity" : 0.5 + }, + "specular" : true, + "translucency" : { + "enabled" : false, + "value" : 0.5 + } + } + ], + "supported-platforms" : { + "squares" : [ + "macOS" + ] + } +} \ No newline at end of file diff --git a/packages/debugger-shell/src/electron/utils.js b/packages/debugger-shell/src/electron/utils.js new file mode 100644 index 00000000000..bf45bf0032e --- /dev/null +++ b/packages/debugger-shell/src/electron/utils.js @@ -0,0 +1,18 @@ +/** + * 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. + * + * @flow strict-local + * @format + */ + +/** Equivalent of Swift's `if #available(macOS 26, *)`. */ +export function isMacOSAtLeast(major: number): boolean { + return ( + process.platform === 'darwin' && + // $FlowFixMe[prop-missing] + Number.parseInt(process.getSystemVersion().split('.')[0], 10) >= major + ); +} diff --git a/packages/debugger-shell/src/node/index.flow.js b/packages/debugger-shell/src/node/index.flow.js index a3918962f91..1a8e4d2b2ae 100644 --- a/packages/debugger-shell/src/node/index.flow.js +++ b/packages/debugger-shell/src/node/index.flow.js @@ -8,10 +8,7 @@ * @format */ -import { - prepareDebuggerShellFromDotSlashFile, - spawnAndGetStderr, -} from './private/LaunchUtils'; +import {prepareDebuggerShellFromDotSlashFile} from './private/LaunchUtils'; const {spawn} = require('cross-spawn'); const debug = require('debug')('Metro:DebuggerShell'); @@ -174,20 +171,6 @@ async function unstable_prepareDebuggerShell({ throw new Error(`Unknown flavor: ${flavor}`); } - const [binaryPath, baseArgs] = getShellBinaryAndArgs( - flavor, - prebuiltBinaryPath, - ); - const {code, stderr} = await spawnAndGetStderr(binaryPath, [ - ...baseArgs, - '--version', - ]); - if (code !== 0) { - return { - code: 'unexpected_error', - verboseInfo: stderr, - }; - } return {code: 'success'}; } catch (e) { return { diff --git a/packages/debugger-shell/src/node/private/LaunchUtils.js b/packages/debugger-shell/src/node/private/LaunchUtils.js index 5972f2841b6..2f78239f231 100644 --- a/packages/debugger-shell/src/node/private/LaunchUtils.js +++ b/packages/debugger-shell/src/node/private/LaunchUtils.js @@ -95,4 +95,4 @@ async function prepareDebuggerShellFromDotSlashFile( }; } -export {spawnAndGetStderr, prepareDebuggerShellFromDotSlashFile}; +export {prepareDebuggerShellFromDotSlashFile}; diff --git a/packages/dev-middleware/README.md b/packages/dev-middleware/README.md index cf2945afd78..c6a7f64d68b 100644 --- a/packages/dev-middleware/README.md +++ b/packages/dev-middleware/README.md @@ -1,6 +1,9 @@ # @react-native/dev-middleware -![npm package](https://img.shields.io/npm/v/@react-native/dev-middleware?color=brightgreen&label=npm%20package) +[![npm]](https://www.npmjs.com/package/@react-native/dev-middleware) [![npm downloads]](https://www.npmjs.com/package/@react-native/dev-middleware) + +[npm]: https://img.shields.io/npm/v/@react-native/dev-middleware.svg?color=blue +[npm downloads]: https://img.shields.io/npm/dm/@react-native/dev-middleware.svg Dev server middleware supporting core React Native development features. This package is preconfigured in all React Native projects. diff --git a/packages/dev-middleware/package.json b/packages/dev-middleware/package.json index 6f6a2bdb69f..439242d33fd 100644 --- a/packages/dev-middleware/package.json +++ b/packages/dev-middleware/package.json @@ -1,16 +1,16 @@ { "name": "@react-native/dev-middleware", - "version": "0.86.0-main", + "version": "0.87.0-main", "description": "Dev server middleware for React Native", "keywords": [ "react-native", "tools" ], - "homepage": "https://github.com/facebook/react-native/tree/HEAD/packages/dev-middleware#readme", - "bugs": "https://github.com/facebook/react-native/issues", + "homepage": "https://github.com/react/react-native/tree/HEAD/packages/dev-middleware#readme", + "bugs": "https://github.com/react/react-native/issues", "repository": { "type": "git", - "url": "git+https://github.com/facebook/react-native.git", + "url": "git+https://github.com/react/react-native.git", "directory": "packages/dev-middleware" }, "license": "MIT", @@ -51,7 +51,7 @@ "wait-for-expect": "^3.0.2" }, "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" }, "private": true } diff --git a/packages/dev-middleware/src/__tests__/InspectorDebuggerUtils.js b/packages/dev-middleware/src/__tests__/InspectorDebuggerUtils.js index d847bce6aa7..cbaf79a5efb 100644 --- a/packages/dev-middleware/src/__tests__/InspectorDebuggerUtils.js +++ b/packages/dev-middleware/src/__tests__/InspectorDebuggerUtils.js @@ -84,7 +84,7 @@ export class DebuggerAgent { export class DebuggerMock extends DebuggerAgent { // Empty handlers - +handle: JestMockFn<[message: JSONSerializable], void> = jest.fn(); + readonly handle: JestMockFn<[message: JSONSerializable], void> = jest.fn(); __handle(message: JSONSerializable): void { this.handle(message); diff --git a/packages/dev-middleware/src/__tests__/InspectorDeviceUtils.js b/packages/dev-middleware/src/__tests__/InspectorDeviceUtils.js index f8ef55eb4c0..41b7cdd59dc 100644 --- a/packages/dev-middleware/src/__tests__/InspectorDeviceUtils.js +++ b/packages/dev-middleware/src/__tests__/InspectorDeviceUtils.js @@ -98,16 +98,18 @@ export class DeviceAgent { export class DeviceMock extends DeviceAgent { // Empty handlers - +connect: JestMockFn<[message: ConnectRequest], void> = jest.fn(); - +disconnect: JestMockFn<[message: DisconnectRequest], void> = jest.fn(); - +getPages: JestMockFn< + readonly connect: JestMockFn<[message: ConnectRequest], void> = jest.fn(); + readonly disconnect: JestMockFn<[message: DisconnectRequest], void> = + jest.fn(); + readonly getPages: JestMockFn< [message: GetPagesRequest], | GetPagesResponse['payload'] | Promise | void, > = jest.fn(); - +wrappedEvent: JestMockFn<[message: WrappedEventToDevice], void> = jest.fn(); - +wrappedEventParsed: JestMockFn< + readonly wrappedEvent: JestMockFn<[message: WrappedEventToDevice], void> = + jest.fn(); + readonly wrappedEventParsed: JestMockFn< [ payload: { ...WrappedEventToDevice['payload'], diff --git a/packages/dev-middleware/src/__tests__/InspectorProtocolUtils.js b/packages/dev-middleware/src/__tests__/InspectorProtocolUtils.js index 840bf2a1353..0eedf4287d3 100644 --- a/packages/dev-middleware/src/__tests__/InspectorProtocolUtils.js +++ b/packages/dev-middleware/src/__tests__/InspectorProtocolUtils.js @@ -39,7 +39,7 @@ export type CdpMessageToTarget = Readonly<{ }>; /** - * Send a CDP message from from the target with the given pageId to the debugger. + * Send a CDP message from the target with the given pageId to the debugger. * Returns the message as received by the debugger. */ export async function sendFromTargetToDebugger< diff --git a/packages/dev-middleware/src/__tests__/StandaloneFuseboxShell-test.js b/packages/dev-middleware/src/__tests__/StandaloneFuseboxShell-test.js index 89385b56ceb..18f45b3f97e 100644 --- a/packages/dev-middleware/src/__tests__/StandaloneFuseboxShell-test.js +++ b/packages/dev-middleware/src/__tests__/StandaloneFuseboxShell-test.js @@ -25,7 +25,7 @@ const PAGES_POLLING_DELAY = 2100; jest.useFakeTimers(); async function setupDevice( - serverRef: {+serverBaseWsUrl: string, ...}, + serverRef: {readonly serverBaseWsUrl: string, ...}, signal: AbortSignal, ) { const device = await createDeviceMock( diff --git a/packages/dev-middleware/src/inspector-proxy/Device.js b/packages/dev-middleware/src/inspector-proxy/Device.js index b41dda0bb60..4dc3d685def 100644 --- a/packages/dev-middleware/src/inspector-proxy/Device.js +++ b/packages/dev-middleware/src/inspector-proxy/Device.js @@ -138,7 +138,7 @@ export default class Device { // Logging reporting batches of cdp messages #cdpDebugLogging: CdpDebugLogging; - +#experiments: Experiments; + readonly #experiments: Experiments; constructor(deviceOptions: DeviceOptions) { this.#experiments = deviceOptions.experiments; diff --git a/packages/dev-middleware/src/inspector-proxy/InspectorProxy.js b/packages/dev-middleware/src/inspector-proxy/InspectorProxy.js index 22d18ef6ab2..672181f39cf 100644 --- a/packages/dev-middleware/src/inspector-proxy/InspectorProxy.js +++ b/packages/dev-middleware/src/inspector-proxy/InspectorProxy.js @@ -93,7 +93,7 @@ export default class InspectorProxy implements InspectorProxyQueries { #eventReporter: ?EventReporter; - +#experiments: Experiments; + readonly #experiments: Experiments; // custom message handler factory allowing implementers to handle unsupported CDP messages. #customMessageHandler: ?CreateCustomMessageHandlerFn; diff --git a/packages/dev-middleware/src/inspector-proxy/types.js b/packages/dev-middleware/src/inspector-proxy/types.js index de27524066c..89838f2d11d 100644 --- a/packages/dev-middleware/src/inspector-proxy/types.js +++ b/packages/dev-middleware/src/inspector-proxy/types.js @@ -156,11 +156,11 @@ export type JSONSerializable = | string | null | ReadonlyArray - | {+[string]: JSONSerializable}; + | {readonly [string]: JSONSerializable}; export type DeepReadOnly = T extends ReadonlyArray ? ReadonlyArray> : T extends {...} - ? {+[K in keyof T]: DeepReadOnly} + ? {readonly [K in keyof T]: DeepReadOnly} : T; diff --git a/packages/dev-middleware/src/types/DevToolLauncher.js b/packages/dev-middleware/src/types/DevToolLauncher.js index fb598f88edf..3c9e63ee925 100644 --- a/packages/dev-middleware/src/types/DevToolLauncher.js +++ b/packages/dev-middleware/src/types/DevToolLauncher.js @@ -27,7 +27,7 @@ export interface DevToolLauncher { * the host of dev-middleware. Implementations are responsible for rewriting * this as necessary where the server is remote. */ - +launchDebuggerAppWindow: (url: string) => Promise; + readonly launchDebuggerAppWindow: (url: string) => Promise; /** * Attempt to open a debugger frontend URL in a standalone shell window @@ -47,7 +47,10 @@ export interface DevToolLauncher { * the host of dev-middleware. Implementations are responsible for rewriting * this as necessary where the server is remote. */ - +launchDebuggerShell?: (url: string, windowKey: string) => Promise; + readonly launchDebuggerShell?: ( + url: string, + windowKey: string, + ) => Promise; /** * Attempt to prepare the debugger shell for use and returns a coded result @@ -60,5 +63,5 @@ export interface DevToolLauncher { * SHOULD NOT return a rejecting promise in any case, and instead SHOULD report * errors via the returned result object. */ - +prepareDebuggerShell?: () => Promise; + readonly prepareDebuggerShell?: () => Promise; } diff --git a/packages/dev-middleware/src/types/ReadonlyURL.js b/packages/dev-middleware/src/types/ReadonlyURL.js index b8c46a4c9c5..dbdb0e71e1e 100644 --- a/packages/dev-middleware/src/types/ReadonlyURL.js +++ b/packages/dev-middleware/src/types/ReadonlyURL.js @@ -15,7 +15,7 @@ export interface ReadonlyURLSearchParams { get(name: string): string | null; getAll(name: string): Array; has(name: string, value?: string): boolean; - +size: number; + readonly size: number; entries(): Iterator<[string, string]>; keys(): Iterator; values(): Iterator; @@ -37,18 +37,18 @@ export interface ReadonlyURLSearchParams { * Used for URLs passed between module boundaries. */ export interface ReadonlyURL { - +hash: string; - +host: string; - +hostname: string; - +href: string; - +origin: string; - +password: string; - +pathname: string; - +port: string; - +protocol: string; - +search: string; - +searchParams: ReadonlyURLSearchParams; - +username: string; + readonly hash: string; + readonly host: string; + readonly hostname: string; + readonly href: string; + readonly origin: string; + readonly password: string; + readonly pathname: string; + readonly port: string; + readonly protocol: string; + readonly search: string; + readonly searchParams: ReadonlyURLSearchParams; + readonly username: string; toString(): string; toJSON(): string; } diff --git a/packages/eslint-config-react-native/README.md b/packages/eslint-config-react-native/README.md index f06151c0d00..4c21428c562 100644 --- a/packages/eslint-config-react-native/README.md +++ b/packages/eslint-config-react-native/README.md @@ -1,15 +1,18 @@ # @react-native/eslint-config -[![Version][version-badge]][package] +[![npm]](https://www.npmjs.com/package/@react-native/eslint-config) [![npm downloads]](https://www.npmjs.com/package/@react-native/eslint-config) + +[npm]: https://img.shields.io/npm/v/@react-native/eslint-config.svg?color=blue +[npm downloads]: https://img.shields.io/npm/dm/@react-native/eslint-config.svg + +ESLint and Prettier configuration used by React Native apps. ## Installation -``` +```sh yarn add --dev eslint prettier @react-native/eslint-config ``` -*Note: We're using `yarn` to install deps. Feel free to change commands to use `npm` 3+ and `npx` if you like* - ## Usage ### For ESLint 9+ (Flat Config) @@ -45,6 +48,3 @@ Add to your eslint config (`.eslintrc`, or `eslintConfig` field in `package.json "extends": "@react-native" } ``` - -[version-badge]: https://img.shields.io/npm/v/@react-native/eslint-config.svg?style=flat-square -[package]: https://www.npmjs.com/package/@react-native/eslint-config diff --git a/packages/eslint-config-react-native/package.json b/packages/eslint-config-react-native/package.json index 41138f22288..a22bdaaa74f 100644 --- a/packages/eslint-config-react-native/package.json +++ b/packages/eslint-config-react-native/package.json @@ -1,22 +1,22 @@ { "name": "@react-native/eslint-config", - "version": "0.86.0-main", + "version": "0.87.0-main", "description": "ESLint config for React Native", "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/facebook/react-native.git", + "url": "git+https://github.com/react/react-native.git", "directory": "packages/eslint-config-react-native" }, - "homepage": "https://github.com/facebook/react-native/tree/HEAD/packages/eslint-config-react-native#readme", + "homepage": "https://github.com/react/react-native/tree/HEAD/packages/eslint-config-react-native#readme", "keywords": [ "eslint", "config", "react-native" ], - "bugs": "https://github.com/facebook/react-native/issues", + "bugs": "https://github.com/react/react-native/issues", "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" }, "main": "index.js", "exports": { @@ -27,8 +27,8 @@ "@babel/core": "^7.25.2", "@babel/eslint-parser": "^7.25.1", "@react-native/eslint-plugin": "workspace:*", - "@typescript-eslint/eslint-plugin": "^8.36.0", - "@typescript-eslint/parser": "^8.36.0", + "@typescript-eslint/eslint-plugin": "^8.59.2", + "@typescript-eslint/parser": "^8.59.2", "eslint-config-prettier": "^8.5.0", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-ft-flow": "^2.0.1", diff --git a/packages/eslint-plugin-react-native/README.md b/packages/eslint-plugin-react-native/README.md index 48fc9bcf67c..cb974cfec21 100644 --- a/packages/eslint-plugin-react-native/README.md +++ b/packages/eslint-plugin-react-native/README.md @@ -1,34 +1,14 @@ # @react-native/eslint-plugin -This plugin is intended to be used in [`@react-native/eslint-config`](https://github.com/facebook/react-native/tree/HEAD/packages/eslint-config-react-native). You probably want to install that package instead. +[![npm]](https://www.npmjs.com/package/@react-native/eslint-plugin) [![npm downloads]](https://www.npmjs.com/package/@react-native/eslint-plugin) -## Installation +[npm]: https://img.shields.io/npm/v/@react-native/eslint-plugin.svg?color=blue +[npm downloads]: https://img.shields.io/npm/dm/@react-native/eslint-plugin.svg -``` -yarn add --dev eslint @react-native/eslint-plugin -``` - -*Note: We're using `yarn` to install deps. Feel free to change commands to use `npm` 3+ and `npx` if you like* - -## Usage - -Add to your eslint config (`.eslintrc`, or `eslintConfig` field in `package.json`): - -```json -{ - "plugins": ["@react-native"] -} -``` +ESLint rules for [`@react-native/eslint-config`](https://github.com/facebook/react-native/tree/HEAD/packages/eslint-config-react-native). You probably want to install that package instead. ## Rules ### `platform-colors` Enforces that calls to `PlatformColor` and `DynamicColorIOS` are statically analyzable to enable performance optimizations. - -## Testing - -To run the tests in this package, run the following commands from the React Native root folder: - -1. `yarn` to install the dependencies. You just need to run this once -2. `yarn jest packages/eslint-plugin-react-native`. diff --git a/packages/eslint-plugin-react-native/no-deep-imports.js b/packages/eslint-plugin-react-native/no-deep-imports.js index 697c89b9d61..6446c70ba6a 100644 --- a/packages/eslint-plugin-react-native/no-deep-imports.js +++ b/packages/eslint-plugin-react-native/no-deep-imports.js @@ -32,6 +32,7 @@ module.exports = { if ( !isDeepReactNativeImport(node.source) || isInitializeCoreImport(node.source) || + isSecondaryEntryPoint(node.source) || isFbInternalImport(node.source) ) { return; @@ -88,6 +89,7 @@ module.exports = { if ( !isDeepRequire(node) || isInitializeCoreImport(node.arguments[0]) || + isSecondaryEntryPoint(node.arguments[0]) || isFbInternalImport(node.arguments[0]) ) { return; @@ -173,6 +175,14 @@ module.exports = { return source.value === 'react-native/Libraries/Core/InitializeCore'; } + function isSecondaryEntryPoint(source) { + if (source.type !== 'Literal' || typeof source.value !== 'string') { + return false; + } + + return source.value === 'react-native/asset-registry'; + } + function isFbInternalImport(source) { if (source.type !== 'Literal' || typeof source.value !== 'string') { return false; diff --git a/packages/eslint-plugin-react-native/package.json b/packages/eslint-plugin-react-native/package.json index 55bb442fc6e..c0e163ca78d 100644 --- a/packages/eslint-plugin-react-native/package.json +++ b/packages/eslint-plugin-react-native/package.json @@ -1,28 +1,28 @@ { "name": "@react-native/eslint-plugin", - "version": "0.86.0-main", + "version": "0.87.0-main", "description": "ESLint rules for @react-native/eslint-config", "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/facebook/react-native.git", + "url": "git+https://github.com/react/react-native.git", "directory": "packages/eslint-plugin-react-native" }, - "homepage": "https://github.com/facebook/react-native/tree/HEAD/packages/eslint-plugin-react-native#readme", + "homepage": "https://github.com/react/react-native/tree/HEAD/packages/eslint-plugin-react-native#readme", "keywords": [ "eslint", "rules", "eslint-config", "react-native" ], - "bugs": "https://github.com/facebook/react-native/issues", + "bugs": "https://github.com/react/react-native/issues", "main": "index.js", "devDependencies": { - "babel-plugin-syntax-hermes-parser": "0.36.0", - "hermes-eslint": "0.36.0" + "babel-plugin-syntax-hermes-parser": "0.36.1", + "hermes-eslint": "0.36.1" }, "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" }, "private": true } diff --git a/packages/eslint-plugin-react-native/utils.js b/packages/eslint-plugin-react-native/utils.js index 3c9650e06e5..98d1e90ca0b 100644 --- a/packages/eslint-plugin-react-native/utils.js +++ b/packages/eslint-plugin-react-native/utils.js @@ -297,10 +297,6 @@ const publicAPIMapping = { default: 'I18nManager', types: null, }, - 'Libraries/Interaction/InteractionManager': { - default: 'InteractionManager', - types: ['Handle', 'PromiseTask', 'SimpleTask'], - }, 'Libraries/Components/Keyboard/Keyboard': { default: 'Keyboard', types: [ @@ -331,10 +327,6 @@ const publicAPIMapping = { default: 'LogBox', types: ['ExtendedExceptionData', 'IgnorePattern', 'LogData'], }, - 'Libraries/NativeModules/specs/NativeDialogManagerAndroid': { - default: 'NativeDialogManagerAndroid', - types: null, - }, 'Libraries/EventEmitter/NativeEventEmitter': { default: 'NativeEventEmitter', types: [ diff --git a/packages/eslint-plugin-specs/README.md b/packages/eslint-plugin-specs/README.md index 824fe031ea6..7b2d1cc16a3 100644 --- a/packages/eslint-plugin-specs/README.md +++ b/packages/eslint-plugin-specs/README.md @@ -1,21 +1,8 @@ # @react-native/eslint-plugin-specs -[![Version][version-badge]][package] +[![npm]](https://www.npmjs.com/package/@react-native/eslint-plugin-specs) [![npm downloads]](https://www.npmjs.com/package/@react-native/eslint-plugin-specs) -## Installation +[npm]: https://img.shields.io/npm/v/@react-native/eslint-plugin-specs.svg?color=blue +[npm downloads]: https://img.shields.io/npm/dm/@react-native/eslint-plugin-specs.svg -``` -yarn add --dev @react-native/eslint-plugin-specs -``` - -*Note: We're using `yarn` to install deps. Feel free to change commands to use `npm` 3+ and `npx` if you like* - -[version-badge]: https://img.shields.io/npm/v/@react-native/eslint-plugin-specs?style=flat-square -[package]: https://www.npmjs.com/package/@react-native/eslint-plugin-specs - -## Testing - -To run the tests in this package, run the following commands from the React Native root folder: - -1. `yarn` to install the dependencies. You just need to run this once -2. `yarn jest packages/eslint-plugin-specs`. +ESLint rules that validate React Native Native Module and Component specs. diff --git a/packages/eslint-plugin-specs/package.json b/packages/eslint-plugin-specs/package.json index 0c9e8fdd6aa..d73bc1dfef8 100644 --- a/packages/eslint-plugin-specs/package.json +++ b/packages/eslint-plugin-specs/package.json @@ -1,14 +1,14 @@ { "name": "@react-native/eslint-plugin-specs", - "version": "0.86.0-main", + "version": "0.87.0-main", "description": "ESLint rules to validate NativeModule and Component Specs", "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/facebook/react-native.git", + "url": "git+https://github.com/react/react-native.git", "directory": "packages/eslint-plugin-specs" }, - "homepage": "https://github.com/facebook/react-native/tree/HEAD/packages/eslint-plugin-specs#readme", + "homepage": "https://github.com/react/react-native/tree/HEAD/packages/eslint-plugin-specs#readme", "keywords": [ "eslint", "rules", @@ -17,7 +17,7 @@ "components", "specs" ], - "bugs": "https://github.com/facebook/react-native/issues", + "bugs": "https://github.com/react/react-native/issues", "main": "index.js", "scripts": { "prepack": "node prepack.js", @@ -27,16 +27,16 @@ "@babel/core": "^7.25.2", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@react-native/codegen": "workspace:*", + "babel-plugin-syntax-hermes-parser": "0.36.1", "make-dir": "^2.1.0", "pirates": "^4.0.1", - "babel-plugin-syntax-hermes-parser": "0.36.0", "source-map-support": "0.5.0" }, "devDependencies": { - "hermes-eslint": "0.36.0" + "hermes-eslint": "0.36.1" }, "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" }, "private": true } diff --git a/packages/gradle-plugin/README.md b/packages/gradle-plugin/README.md index bb9f0a3a87b..c86dcc49a27 100644 --- a/packages/gradle-plugin/README.md +++ b/packages/gradle-plugin/README.md @@ -1,23 +1,8 @@ # @react-native/gradle-plugin -[![Version][version-badge]][package] +[![npm]](https://www.npmjs.com/package/@react-native/gradle-plugin) [![npm downloads]](https://www.npmjs.com/package/@react-native/gradle-plugin) -A Gradle Plugin used to support development of React Native applications for Android. +[npm]: https://img.shields.io/npm/v/@react-native/gradle-plugin.svg?color=blue +[npm downloads]: https://img.shields.io/npm/dm/@react-native/gradle-plugin.svg -## Installation - -``` -yarn add @react-native/gradle-plugin -``` - -*Note: We're using `yarn` to install deps. Feel free to change commands to use `npm` 3+ and `npx` if you like* - -[version-badge]: https://img.shields.io/npm/v/@react-native/gradle-plugin?style=flat-square -[package]: https://www.npmjs.com/package/@react-native/gradle-plugin - -## Testing - -To run the tests in this package, run the following commands from the React Native root folder: - -1. `yarn` to install the dependencies. You just need to run this once -2. `./gradlew -p packages/gradle-plugin test`. +Gradle plugin used to build and configure React Native applications for Android. It is applied automatically by apps created from the React Native template. diff --git a/packages/gradle-plugin/gradle/libs.versions.toml b/packages/gradle-plugin/gradle/libs.versions.toml index c7502fcefd9..65dd0b10f53 100644 --- a/packages/gradle-plugin/gradle/libs.versions.toml +++ b/packages/gradle-plugin/gradle/libs.versions.toml @@ -1,10 +1,10 @@ [versions] -agp = "8.12.0" +agp = "9.2.1" gson = "2.8.9" guava = "31.0.1-jre" javapoet = "1.13.0" junit = "4.13.2" -kotlin = "2.1.20" +kotlin = "2.2.0" assertj = "3.25.1" ktfmt = "0.22.0" diff --git a/packages/gradle-plugin/gradle/wrapper/gradle-wrapper.jar b/packages/gradle-plugin/gradle/wrapper/gradle-wrapper.jar index 61285a659d1..d997cfc60f4 100644 Binary files a/packages/gradle-plugin/gradle/wrapper/gradle-wrapper.jar and b/packages/gradle-plugin/gradle/wrapper/gradle-wrapper.jar differ diff --git a/packages/gradle-plugin/gradle/wrapper/gradle-wrapper.properties b/packages/gradle-plugin/gradle/wrapper/gradle-wrapper.properties index 37f78a6af83..c61a118f7dd 100644 --- a/packages/gradle-plugin/gradle/wrapper/gradle-wrapper.properties +++ b/packages/gradle-plugin/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/packages/gradle-plugin/gradlew b/packages/gradle-plugin/gradlew index adff685a034..739907dfd15 100755 --- a/packages/gradle-plugin/gradlew +++ b/packages/gradle-plugin/gradlew @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. diff --git a/packages/gradle-plugin/package.json b/packages/gradle-plugin/package.json index ffe10249620..2fac41a7581 100644 --- a/packages/gradle-plugin/package.json +++ b/packages/gradle-plugin/package.json @@ -1,22 +1,22 @@ { "name": "@react-native/gradle-plugin", - "version": "0.86.0-main", + "version": "0.87.0-main", "description": "Gradle Plugin for React Native", "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/facebook/react-native.git", + "url": "git+https://github.com/react/react-native.git", "directory": "packages/gradle-plugin" }, - "homepage": "https://github.com/facebook/react-native/tree/HEAD/packages/gradle-plugin#readme", + "homepage": "https://github.com/react/react-native/tree/HEAD/packages/gradle-plugin#readme", "keywords": [ "gradle", "plugin", "react-native" ], - "bugs": "https://github.com/facebook/react-native/issues", + "bugs": "https://github.com/react/react-native/issues", "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" }, "scripts": { "build": "./gradlew build", diff --git a/packages/gradle-plugin/react-native-gradle-plugin/build.gradle.kts b/packages/gradle-plugin/react-native-gradle-plugin/build.gradle.kts index 85dadeba0c4..c4f92db2e1a 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/build.gradle.kts +++ b/packages/gradle-plugin/react-native-gradle-plugin/build.gradle.kts @@ -64,7 +64,7 @@ kotlin { jvmToolchain(17) } tasks.withType().configureEach { compilerOptions { - apiVersion.set(KotlinVersion.KOTLIN_1_8) + apiVersion.set(KotlinVersion.KOTLIN_2_0) // See comment above on JDK 11 support jvmTarget.set(JvmTarget.JVM_11) allWarningsAsErrors.set( diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt index e7859ff6ad0..4af3f14f34c 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt @@ -11,6 +11,7 @@ import com.android.build.api.variant.ApplicationAndroidComponentsExtension import com.android.build.api.variant.LibraryAndroidComponentsExtension import com.android.build.gradle.internal.tasks.factory.dependsOn import com.facebook.react.internal.PrivateReactExtension +import com.facebook.react.model.ModelAutolinkingDependenciesJson import com.facebook.react.tasks.GenerateAutolinkingNewArchitecturesFileTask import com.facebook.react.tasks.GenerateCodegenArtifactsTask import com.facebook.react.tasks.GenerateCodegenSchemaTask @@ -28,8 +29,8 @@ import com.facebook.react.utils.DependencyUtils.readVersionAndGroupStrings import com.facebook.react.utils.JdkConfiguratorUtils.configureJavaToolChains import com.facebook.react.utils.JsonUtils import com.facebook.react.utils.NdkConfiguratorUtils.configureReactNativeNdk -import com.facebook.react.utils.ProjectUtils.isHermesV1Enabled import com.facebook.react.utils.ProjectUtils.needsCodegenFromPackageJson +import com.facebook.react.utils.PropertyUtils import com.facebook.react.utils.findPackageJsonFile import java.io.File import kotlin.system.exitProcess @@ -38,6 +39,7 @@ import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.file.Directory import org.gradle.api.provider.Provider +import org.gradle.api.tasks.TaskProvider import org.gradle.internal.jvm.Jvm class ReactPlugin : Plugin { @@ -55,8 +57,25 @@ class ReactPlugin : Plugin { project, ) - if (!project.rootProject.isHermesV1Enabled) { - rootExtension.hermesV1Enabled.set(false) + // Warn users if they still have the hermesV1Enabled property set. + if ( + project.rootProject.hasProperty(PropertyUtils.HERMES_V1_ENABLED) || + project.rootProject.hasProperty(PropertyUtils.SCOPED_HERMES_V1_ENABLED) + ) { + val value = + (project.rootProject.findProperty(PropertyUtils.HERMES_V1_ENABLED) + ?: project.rootProject.findProperty(PropertyUtils.SCOPED_HERMES_V1_ENABLED)) + .toString() + .toBoolean() + if (value) { + project.logger.warn( + "WARNING: The 'hermesV1Enabled' property is no longer needed. Hermes V1 is now always enabled. You can safely remove this property from your gradle.properties." + ) + } else { + project.logger.warn( + "WARNING: Opting out of Hermes V1 is no longer supported. The 'hermesV1Enabled=false' property will be ignored. Hermes V1 is now always enabled. Please remove this property from your gradle.properties." + ) + } } // App Only Configuration @@ -75,8 +94,7 @@ class ReactPlugin : Plugin { File(reactNativeDir, "sdks/hermes-engine/version.properties") val versionAndGroupStrings = readVersionAndGroupStrings(project, propertiesFile, hermesVersionPropertiesFile) - val hermesV1Enabled = rootExtension.hermesV1Enabled.get() - configureDependencies(project, versionAndGroupStrings, hermesV1Enabled) + configureDependencies(project, versionAndGroupStrings) configureRepositories(project, versionAndGroupStrings.isNightly) } @@ -91,16 +109,40 @@ class ReactPlugin : Plugin { project.configureReactTasks(variant = variant, config = extension) } } - configureAutolinking(project, extension) + configureAutolinking(project, extension, rootExtension) configureCodegen(project, extension, rootExtension, isLibrary = false) configureResources(project, extension) configureBuildTypesForApp(project) + + // Apply the namespace fallback to every Android library in the build, including third-party + // libraries that don't apply the `com.facebook.react` plugin and still rely on the manifest + // `package` attribute (which AGP 9 no longer accepts as a namespace). The per-library hook + // below only covers libraries that apply our plugin, so we additionally sweep all library + // subprojects from the app. We do this once, from the application project, because re-running + // a rootProject-wide traversal from every library breaks on AGP 9, which errors when + // `finalizeDsl` is registered after a project's DSL has been finalized. + // See https://github.com/facebook/react-native/pull/57038. + // + // We skip projects that have already been evaluated: AGP finalizes a project's DSL during/ + // after its evaluation, so registering a `finalizeDsl` callback on an already-evaluated + // project is "too late" and fails on AGP 9. In a regular app build the app is evaluated + // before its libraries (see ReactRootProjectPlugin's `evaluationDependsOn(":app")`), so every + // library is still pending here. In composite/monorepo builds (e.g. rn-tester, where + // ReactAndroid is a sibling) some library projects are already evaluated by this point; those + // already define their own namespace, so skipping them is safe. + project.rootProject.allprojects { subproject -> + subproject.pluginManager.withPlugin("com.android.library") { + if (!subproject.state.executed) { + configureNamespaceForLibraries(subproject) + } + } + } } // Library Only Configuration - configureBuildConfigFieldsForLibraries(project) - configureNamespaceForLibraries(project) project.pluginManager.withPlugin("com.android.library") { + configureBuildConfigFieldsForLibraries(project) + configureNamespaceForLibraries(project) configureCodegen(project, extension, rootExtension, isLibrary = true) } } @@ -157,33 +199,103 @@ class ReactPlugin : Plugin { localExtension.jsRootDir.convention(localExtension.root) } + // We create the tasks to produce schema from JS files and generate artifacts from schema. + val generateCodegenArtifactsTask = + registerCodegenTasks( + project = project, + rootExtension = rootExtension, + generatedSrcDir = generatedSrcDir, + packageJsonFile = { findPackageJsonFile(project, rootExtension.root) }, + schemaTaskName = "generateCodegenSchemaFromJavaScript", + artifactsTaskName = "generateCodegenArtifactsFromSchema", + configureJsRoot = { task, packageJson -> + // We're reading the package.json at configuration time to properly feed + // the `jsRootDir` @Input property of this task & the onlyIf. Therefore, the + // parsePackageJson should be invoked inside this lambda. + val parsedPackageJson = packageJson?.let { JsonUtils.fromPackageJson(it) } + val jsSrcsDirInPackageJson = parsedPackageJson?.codegenConfig?.jsSrcsDir + + if (packageJson != null && jsSrcsDirInPackageJson != null) { + task.jsRootDir.set(File(packageJson.parentFile, jsSrcsDirInPackageJson)) + } else { + task.jsRootDir.set(localExtension.jsRootDir) + } + }, + configureCodegenArtifacts = { task, _ -> + task.codegenJavaPackageName.set(localExtension.codegenJavaPackageName) + task.libraryName.set(localExtension.libraryName) + }, + onlyIf = { packageJson -> + // Please note that needsCodegenFromPackageJson is triggering a read of the + // package.json at configuration time as we need to feed the onlyIf condition of this + // task. Therefore, needsCodegenFromPackageJson needs to be invoked inside this + // lambda. + val needsCodegenFromPackageJson = + project.needsCodegenFromPackageJson(rootExtension.root) + val parsedPackageJson = packageJson?.let { JsonUtils.fromPackageJson(it) } + val includesGeneratedCode = + parsedPackageJson?.codegenConfig?.includesGeneratedCode ?: false + (isLibrary || needsCodegenFromPackageJson) && !includesGeneratedCode + }, + ) + + // We update the android configuration to include the generated sources. + // This is equivalent to this DSL: + // + // android { sourceSets { main { java { srcDirs += "$generatedSrcDir/java" } } } } + if (isLibrary) { + project.extensions.getByType(LibraryAndroidComponentsExtension::class.java).finalizeDsl { ext + -> + ext.sourceSets + .getByName("main") + .java + .directories + .add(generatedSrcDir.get().dir("java").asFile.path) + } + } else { + project.extensions.getByType(ApplicationAndroidComponentsExtension::class.java).finalizeDsl { + ext -> + ext.sourceSets + .getByName("main") + .java + .directories + .add(generatedSrcDir.get().dir("java").asFile.path) + } + } + + // `preBuild` is one of the base tasks automatically registered by AGP. + // This will invoke the codegen before compiling the entire project. + project.tasks.named("preBuild", Task::class.java).dependsOn(generateCodegenArtifactsTask) + } + + private fun registerCodegenTasks( + project: Project, + rootExtension: PrivateReactExtension, + generatedSrcDir: Provider, + packageJsonFile: () -> File?, + schemaTaskName: String, + artifactsTaskName: String, + configureJsRoot: (GenerateCodegenSchemaTask, File?) -> Unit, + configureCodegenArtifacts: (GenerateCodegenArtifactsTask, File?) -> Unit, + onlyIf: (File?) -> Boolean = { true }, + ): TaskProvider { // We create the task to produce schema from JS files. val generateCodegenSchemaTask = project.tasks.register( - "generateCodegenSchemaFromJavaScript", + schemaTaskName, GenerateCodegenSchemaTask::class.java, - ) { it -> - it.nodeExecutableAndArgs.set(rootExtension.nodeExecutableAndArgs) - it.codegenDir.set(rootExtension.codegenDir) - it.generatedSrcDir.set(generatedSrcDir) - it.nodeWorkingDir.set(project.layout.projectDirectory.asFile.absolutePath) - - // We're reading the package.json at configuration time to properly feed - // the `jsRootDir` @Input property of this task & the onlyIf. Therefore, the - // parsePackageJson should be invoked inside this lambda. - val packageJson = findPackageJsonFile(project, rootExtension.root) - val parsedPackageJson = packageJson?.let { JsonUtils.fromPackageJson(it) } - - val jsSrcsDirInPackageJson = parsedPackageJson?.codegenConfig?.jsSrcsDir - val includesGeneratedCode = - parsedPackageJson?.codegenConfig?.includesGeneratedCode ?: false - if (jsSrcsDirInPackageJson != null) { - it.jsRootDir.set(File(packageJson.parentFile, jsSrcsDirInPackageJson)) - } else { - it.jsRootDir.set(localExtension.jsRootDir) - } - it.jsInputFiles.set( - project.fileTree(it.jsRootDir) { tree -> + ) { task -> + val packageJson = packageJsonFile() + + task.nodeExecutableAndArgs.set(rootExtension.nodeExecutableAndArgs) + task.codegenDir.set(rootExtension.codegenDir) + task.generatedSrcDir.set(generatedSrcDir) + task.nodeWorkingDir.set(project.layout.projectDirectory.asFile.absolutePath) + + configureJsRoot(task, packageJson) + + task.jsInputFiles.set( + project.fileTree(task.jsRootDir) { tree -> tree.include("**/*.js") tree.include("**/*.jsx") tree.include("**/*.ts") @@ -191,77 +303,66 @@ class ReactPlugin : Plugin { tree.exclude("node_modules/**/*") tree.exclude("**/*.d.ts") - // We want to exclude the build directory, to don't pick them up for execution + // We want to exclude the build directory, to avoid picking them up for execution // avoidance. tree.exclude("**/build/**/*") } ) - - val needsCodegenFromPackageJson = project.needsCodegenFromPackageJson(rootExtension.root) - it.onlyIf { (isLibrary || needsCodegenFromPackageJson) && !includesGeneratedCode } + val shouldRunTask = onlyIf(packageJson) + task.onlyIf { shouldRunTask } } // We create the task to generate Java code from schema. - val generateCodegenArtifactsTask = - project.tasks.register( - "generateCodegenArtifactsFromSchema", - GenerateCodegenArtifactsTask::class.java, - ) { task -> - task.dependsOn(generateCodegenSchemaTask) - task.reactNativeDir.set(rootExtension.reactNativeDir) - task.nodeExecutableAndArgs.set(rootExtension.nodeExecutableAndArgs) - task.generatedSrcDir.set(generatedSrcDir) - task.packageJsonFile.set(findPackageJsonFile(project, rootExtension.root)) - task.codegenJavaPackageName.set(localExtension.codegenJavaPackageName) - task.libraryName.set(localExtension.libraryName) - task.nodeWorkingDir.set(project.layout.projectDirectory.asFile.absolutePath) - - // Please note that appNeedsCodegen is triggering a read of the package.json at - // configuration time as we need to feed the onlyIf condition of this task. - // Therefore, the appNeedsCodegen needs to be invoked inside this lambda. - val needsCodegenFromPackageJson = project.needsCodegenFromPackageJson(rootExtension.root) - val packageJson = findPackageJsonFile(project, rootExtension.root) - val parsedPackageJson = packageJson?.let { JsonUtils.fromPackageJson(it) } - val includesGeneratedCode = - parsedPackageJson?.codegenConfig?.includesGeneratedCode ?: false - task.onlyIf { (isLibrary || needsCodegenFromPackageJson) && !includesGeneratedCode } - } - - // We update the android configuration to include the generated sources. - // This equivalent to this DSL: - // - // android { sourceSets { main { java { srcDirs += "$generatedSrcDir/java" } } } } - if (isLibrary) { - project.extensions.getByType(LibraryAndroidComponentsExtension::class.java).finalizeDsl { ext - -> - ext.sourceSets.getByName("main").java.srcDir(generatedSrcDir.get().dir("java").asFile) - } - } else { - project.extensions.getByType(ApplicationAndroidComponentsExtension::class.java).finalizeDsl { - ext -> - ext.sourceSets.getByName("main").java.srcDir(generatedSrcDir.get().dir("java").asFile) - } + return project.tasks.register( + artifactsTaskName, + GenerateCodegenArtifactsTask::class.java, + ) { task -> + val packageJson = packageJsonFile() + + task.dependsOn(generateCodegenSchemaTask) + task.reactNativeDir.set(rootExtension.reactNativeDir) + task.nodeExecutableAndArgs.set(rootExtension.nodeExecutableAndArgs) + task.generatedSrcDir.set(generatedSrcDir) + task.packageJsonFile.set(packageJson) + task.nodeWorkingDir.set(project.layout.projectDirectory.asFile.absolutePath) + + configureCodegenArtifacts(task, packageJson) + + // The caller decides whether codegen should run. For app/library projects this depends on + // package.json and includesGeneratedCode. Pure C++ dependencies are filtered before task + // registration, so their generated tasks can always run. + val shouldRunTask = onlyIf(packageJson) + task.onlyIf { shouldRunTask } } - - // `preBuild` is one of the base tasks automatically registered by AGP. - // This will invoke the codegen before compiling the entire project. - project.tasks.named("preBuild", Task::class.java).dependsOn(generateCodegenArtifactsTask) } /** This function sets up Autolinking for App users */ private fun configureAutolinking( project: Project, extension: ReactExtension, + rootExtension: PrivateReactExtension, ) { val generatedAutolinkingJavaDir: Provider = project.layout.buildDirectory.dir("generated/autolinking/src/main/java") val generatedAutolinkingJniDir: Provider = project.layout.buildDirectory.dir("generated/autolinking/src/main/jni") + val generatedPureCxxSourceDir: Provider = + project.layout.buildDirectory.dir("generated/source/codegen/pureCxx") // The autolinking.json file is available in the root build folder as it's generated // by ReactSettingsPlugin.kt val rootGeneratedAutolinkingFile = project.rootProject.layout.buildDirectory.file("generated/autolinking/autolinking.json") + val pureCxxDependencies = + getPureCxxCodegenDependencies(rootGeneratedAutolinkingFile.get().asFile) + val pureCxxCodegenTasks = + configurePureCxxDependenciesCodegen( + project, + extension, + rootExtension, + generatedPureCxxSourceDir, + pureCxxDependencies, + ) // We add a task called generateAutolinkingPackageList to do not clash with the existing task // called generatePackageList. This can to be renamed once we unlink the rn <-> cli @@ -275,9 +376,7 @@ class ReactPlugin : Plugin { task.generatedOutputDirectory.set(generatedAutolinkingJavaDir) } - // We add a task called generateAutolinkingPackageList to do not clash with the existing task - // called generatePackageList. This can to be renamed once we unlink the rn <-> cli - // dependency. + // We add a task called generateReactNativeEntryPoint to generate the React Native entry point. val generateEntryPointTask = project.tasks.register( "generateReactNativeEntryPoint", @@ -295,14 +394,19 @@ class ReactPlugin : Plugin { ) { task -> task.autolinkInputFile.set(rootGeneratedAutolinkingFile) task.generatedOutputDirectory.set(generatedAutolinkingJniDir) + + if (pureCxxDependencies.isNotEmpty()) { + task.generatedPureCxxSourceDirectory.set(generatedPureCxxSourceDir) + } + + task.dependsOn(pureCxxCodegenTasks) } project.tasks .named("preBuild", Task::class.java) .dependsOn(generateAutolinkingNewArchitectureFilesTask) - // We let generateAutolinkingPackageList and generateEntryPoint depend on the preBuild task so - // it's executed before - // everything else. + // We make preBuild depend on generateAutolinkingPackageList and generateEntryPoint so they run + // before everything else. project.tasks .named("preBuild", Task::class.java) .dependsOn(generatePackageListTask, generateEntryPointTask) @@ -317,4 +421,73 @@ class ReactPlugin : Plugin { } } } + + private fun configurePureCxxDependenciesCodegen( + project: Project, + extension: ReactExtension, + rootExtension: PrivateReactExtension, + generatedPureCxxSourceDir: Provider, + dependencies: List, + ): List> { + // Pure C++ dependencies are not included as Gradle subprojects, so configureCodegen won't run + // for them. The app owns these generated codegen artifacts and links them from autolinking. + return dependencies.mapNotNull { dependency -> + val android = dependency.platforms?.android ?: return@mapNotNull null + val libraryName = android.libraryName ?: return@mapNotNull null + val dependencyRoot = File(dependency.root) + val packageJson = File(dependencyRoot, "package.json") + val parsedPackageJson = JsonUtils.fromPackageJson(packageJson) + val jsSrcsDir = parsedPackageJson?.codegenConfig?.jsSrcsDir + val generatedSrcDir = generatedPureCxxSourceDir.map { it.dir(libraryName) } + val taskNameSuffix = taskNameSuffixForDependency(dependency) + + registerCodegenTasks( + project = project, + rootExtension = rootExtension, + generatedSrcDir = generatedSrcDir, + packageJsonFile = { packageJson }, + schemaTaskName = "generate${taskNameSuffix}CodegenSchemaFromJavaScript", + artifactsTaskName = "generate${taskNameSuffix}CodegenArtifactsFromSchema", + configureJsRoot = { task, _ -> + if (jsSrcsDir != null) { + task.jsRootDir.set(File(packageJson.parentFile, jsSrcsDir)) + } else { + task.jsRootDir.set(dependencyRoot) + } + }, + configureCodegenArtifacts = { task, _ -> + val codegenJavaPackageName = parsedPackageJson?.codegenConfig?.android?.javaPackageName + if (codegenJavaPackageName != null) { + task.codegenJavaPackageName.set(codegenJavaPackageName) + } else { + task.codegenJavaPackageName.set(extension.codegenJavaPackageName) + } + task.libraryName.set(libraryName) + }, + ) + } + } + + internal fun getPureCxxCodegenDependencies( + autolinkingFile: File + ): List { + val model = JsonUtils.fromAutolinkingConfigJson(autolinkingFile) + return model?.dependencies?.values?.filter { dependency -> + val android = dependency.platforms?.android + + if (android?.isPureCxxDependency != true || android.libraryName == null) { + return@filter false + } + + val packageJson = File(dependency.root, "package.json") + val codegenConfig = JsonUtils.fromPackageJson(packageJson)?.codegenConfig + codegenConfig != null && codegenConfig.includesGeneratedCode != true + } ?: emptyList() + } + + internal fun taskNameSuffixForDependency(dependency: ModelAutolinkingDependenciesJson): String = + dependency.name + .map { char -> if (char.isLetterOrDigit()) char.toString() else "_${char.code}_" } + .joinToString("") + .replaceFirstChar { char -> char.titlecase() } } diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/TaskConfiguration.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/TaskConfiguration.kt index 9984aa67ae8..89f7651af46 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/TaskConfiguration.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/TaskConfiguration.kt @@ -10,7 +10,6 @@ package com.facebook.react import com.android.build.api.variant.Variant import com.facebook.react.tasks.BundleHermesCTask import com.facebook.react.utils.BackwardCompatUtils.showJSCRemovalMessage -import com.facebook.react.utils.KotlinStdlibCompatUtils.capitalizeCompat import com.facebook.react.utils.NdkConfiguratorUtils.configureJsEnginePackagingOptions import com.facebook.react.utils.NdkConfiguratorUtils.configureNewArchPackagingOptions import com.facebook.react.utils.ProjectUtils.isHermesEnabled @@ -22,7 +21,7 @@ import org.gradle.api.Project @Suppress("SpreadOperator", "UnstableApiUsage") internal fun Project.configureReactTasks(variant: Variant, config: ReactExtension) { - val targetName = variant.name.capitalizeCompat() + val targetName = variant.name.replaceFirstChar { it.titlecase() } val targetPath = variant.name val buildDir = layout.buildDirectory.get().asFile diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/internal/PrivateReactExtension.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/internal/PrivateReactExtension.kt index 26174c0ce0d..0c993f59c60 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/internal/PrivateReactExtension.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/internal/PrivateReactExtension.kt @@ -11,7 +11,6 @@ import javax.inject.Inject import org.gradle.api.Project import org.gradle.api.file.DirectoryProperty import org.gradle.api.provider.ListProperty -import org.gradle.api.provider.Property /** * A private extension we set on the rootProject to make easier to share values at execution time @@ -58,6 +57,4 @@ abstract class PrivateReactExtension @Inject constructor(project: Project) { val codegenDir: DirectoryProperty = objects.directoryProperty().convention(root.dir("node_modules/@react-native/codegen")) - - val hermesV1Enabled: Property = objects.property(Boolean::class.java).convention(true) } diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateAutolinkingNewArchitecturesFileTask.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateAutolinkingNewArchitecturesFileTask.kt index 4c36c8a39fb..39ba082fc53 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateAutolinkingNewArchitecturesFileTask.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateAutolinkingNewArchitecturesFileTask.kt @@ -14,7 +14,9 @@ import java.io.File import org.gradle.api.DefaultTask import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFileProperty +import org.gradle.api.tasks.InputDirectory import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.Optional import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction @@ -28,6 +30,8 @@ abstract class GenerateAutolinkingNewArchitecturesFileTask : DefaultTask() { @get:OutputDirectory abstract val generatedOutputDirectory: DirectoryProperty + @get:Optional @get:InputDirectory abstract val generatedPureCxxSourceDirectory: DirectoryProperty + @TaskAction fun taskAction() { val model = JsonUtils.fromAutolinkingConfigJson(autolinkInputFile.get().asFile) @@ -55,37 +59,68 @@ abstract class GenerateAutolinkingNewArchitecturesFileTask : DefaultTask() { packages.joinToString("\n") { dep -> var addDirectoryString = "" val libraryName = dep.libraryName - val cmakeListsPath = dep.cmakeListsPath + val cmakeListsPath = cmakeListsPathForDependency(dep) val cxxModuleCMakeListsPath = dep.cxxModuleCMakeListsPath if (libraryName != null && cmakeListsPath != null) { - // If user provided a custom cmakeListsPath, let's honor it. + // If user provided a custom cmakeListsPath, let's honor it. Otherwise, pure C++ + // dependencies use the app-owned generated codegen directory. val nativeFolderPath = sanitizeCmakeListsPath(cmakeListsPath) addDirectoryString += - "add_subdirectory(\"$nativeFolderPath\" ${libraryName}_autolinked_build)" + """ + if(EXISTS "$nativeFolderPath") + add_subdirectory("$nativeFolderPath" ${libraryName}_autolinked_build) + list(APPEND AUTOLINKED_LIBRARIES $CODEGEN_LIB_PREFIX${libraryName}) + else() + message(WARNING "React Native: Skipping autolinked library '$CODEGEN_LIB_PREFIX${libraryName}' because the source directory does not exist: $nativeFolderPath") + endif() + """ + .trimIndent() } if (cxxModuleCMakeListsPath != null) { // If user provided a custom cxxModuleCMakeListsPath, let's honor it. val nativeFolderPath = sanitizeCmakeListsPath(cxxModuleCMakeListsPath) addDirectoryString += - "\nadd_subdirectory(\"$nativeFolderPath\" ${libraryName}_cxxmodule_autolinked_build)" + """ + + if(EXISTS "$nativeFolderPath") + add_subdirectory("$nativeFolderPath" ${libraryName}_cxxmodule_autolinked_build) + ${ + dep.cxxModuleCMakeListsModuleName?.let { + " list(APPEND AUTOLINKED_LIBRARIES $it)" + } ?: "" + } + else() + message(WARNING "React Native: Skipping autolinked C++ module '${dep.cxxModuleCMakeListsModuleName ?: libraryName}' because the source directory does not exist: $nativeFolderPath") + endif() + """ + .trimIndent() } addDirectoryString } - val libraryModules = - packages.joinToString("\n ") { dep -> - var autolinkedLibraries = "" - if (dep.libraryName != null) { - autolinkedLibraries += "$CODEGEN_LIB_PREFIX${dep.libraryName}" - } - if (dep.cxxModuleCMakeListsModuleName != null) { - autolinkedLibraries += "\n${dep.cxxModuleCMakeListsModuleName}" - } - autolinkedLibraries - } - return CMAKE_TEMPLATE.replace("{{ libraryIncludes }}", libraryIncludes) - .replace("{{ libraryModules }}", libraryModules) + } + + internal fun cmakeListsPathForDependency( + dep: ModelAutolinkingDependenciesPlatformAndroidJson + ): String? { + if (dep.cmakeListsPath != null) { + return dep.cmakeListsPath + } + + if ( + dep.isPureCxxDependency != true || + dep.libraryName == null || + !generatedPureCxxSourceDirectory.isPresent + ) { + return null + } + + return generatedPureCxxSourceDirectory + .get() + .file("${dep.libraryName}/jni/CMakeLists.txt") + .asFile + .absolutePath } internal fun generateCppFileContent( @@ -173,11 +208,9 @@ abstract class GenerateAutolinkingNewArchitecturesFileTask : DefaultTask() { # or link against a old prefab target (this is needed for React Native 0.76 on). set(REACTNATIVE_MERGED_SO true) - {{ libraryIncludes }} + set(AUTOLINKED_LIBRARIES) - set(AUTOLINKED_LIBRARIES - {{ libraryModules }} - ) + {{ libraryIncludes }} """ .trimIndent() diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateCodegenSchemaTask.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateCodegenSchemaTask.kt index 6127d48008d..8fbcdb559b8 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateCodegenSchemaTask.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateCodegenSchemaTask.kt @@ -65,8 +65,6 @@ abstract class GenerateCodegenSchemaTask : Exec() { .cliPath(workingDir), "--platform", "android", - "--exclude", - "NativeSampleTurboModule", generatedSchemaFile.get().asFile.cliPath(workingDir), jsRootDir.asFile.get().cliPath(workingDir), ) diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/AgpConfiguratorUtils.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/AgpConfiguratorUtils.kt index c4a7b359d54..36a52185a6d 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/AgpConfiguratorUtils.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/AgpConfiguratorUtils.kt @@ -9,7 +9,6 @@ package com.facebook.react.utils import com.android.build.api.variant.ApplicationAndroidComponentsExtension import com.android.build.api.variant.LibraryAndroidComponentsExtension -import com.android.build.gradle.LibraryExtension import com.facebook.react.ReactExtension import com.facebook.react.utils.ProjectUtils.isEdgeToEdgeEnabled import com.facebook.react.utils.ProjectUtils.isHermesEnabled @@ -18,7 +17,6 @@ import java.net.Inet4Address import java.net.NetworkInterface import javax.xml.parsers.DocumentBuilder import javax.xml.parsers.DocumentBuilderFactory -import kotlin.plus import org.gradle.api.Action import org.gradle.api.Project import org.gradle.api.plugins.AppliedPlugin @@ -77,13 +75,9 @@ internal object AgpConfiguratorUtils { project.pluginManager.withPlugin("com.android.library", action) } - fun configureBuildConfigFieldsForLibraries(appProject: Project) { - appProject.rootProject.allprojects { subproject -> - subproject.pluginManager.withPlugin("com.android.library") { - subproject.extensions - .getByType(LibraryAndroidComponentsExtension::class.java) - .finalizeDsl { ext -> ext.buildFeatures.buildConfig = true } - } + fun configureBuildConfigFieldsForLibraries(project: Project) { + project.extensions.getByType(LibraryAndroidComponentsExtension::class.java).finalizeDsl { ext -> + ext.buildFeatures.buildConfig = true } } @@ -111,23 +105,25 @@ internal object AgpConfiguratorUtils { project.pluginManager.withPlugin("com.android.library", action) } - fun configureNamespaceForLibraries(appProject: Project) { - appProject.rootProject.allprojects { subproject -> - subproject.pluginManager.withPlugin("com.android.library") { - subproject.extensions - .getByType(LibraryAndroidComponentsExtension::class.java) - .finalizeDsl { ext -> - if (ext.namespace == null) { - val android = subproject.extensions.getByType(LibraryExtension::class.java) - val manifestFile = android.sourceSets.getByName("main").manifest.srcFile - - manifestFile - .takeIf { it.exists() } - ?.let { file -> - getPackageNameFromManifest(file)?.let { packageName -> - ext.namespace = packageName - } - } + fun configureNamespaceForLibraries(project: Project) { + // This helper can be reached both from a library's own application of the React plugin and + // from the app-level sweep in ReactPlugin (which also covers libraries that don't apply + // `com.facebook.react`). A project's `finalizeDsl` callback must be registered before AGP + // finalizes its DSL — registering it twice (or after finalization) is a hard error on AGP 9+ — + // so we guard to register the namespace fallback at most once per project. + if (project.extensions.extraProperties.has(NAMESPACE_CONFIGURED_PROPERTY)) { + return + } + project.extensions.extraProperties.set(NAMESPACE_CONFIGURED_PROPERTY, true) + project.extensions.getByType(LibraryAndroidComponentsExtension::class.java).finalizeDsl { ext -> + if (ext.namespace == null) { + val manifestFile = + project.layout.projectDirectory.file("src/main/AndroidManifest.xml").asFile + manifestFile + .takeIf { it.exists() } + ?.let { file -> + getPackageNameFromManifest(file)?.let { packageName -> + ext.namespace = packageName } } } @@ -135,6 +131,8 @@ internal object AgpConfiguratorUtils { } } +private const val NAMESPACE_CONFIGURED_PROPERTY = "com.facebook.react.internal.namespaceConfigured" + const val DEFAULT_DEV_SERVER_PORT = "8081" fun getPackageNameFromManifest(manifest: File): String? { diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/DependencyUtils.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/DependencyUtils.kt index c5f42cfdb43..b686aefa016 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/DependencyUtils.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/DependencyUtils.kt @@ -13,7 +13,6 @@ import com.facebook.react.utils.PropertyUtils.EXCLUSIVE_ENTEPRISE_REPOSITORY import com.facebook.react.utils.PropertyUtils.INCLUDE_JITPACK_REPOSITORY import com.facebook.react.utils.PropertyUtils.INCLUDE_JITPACK_REPOSITORY_DEFAULT import com.facebook.react.utils.PropertyUtils.INTERNAL_HERMES_PUBLISHING_GROUP -import com.facebook.react.utils.PropertyUtils.INTERNAL_HERMES_V1_VERSION_NAME import com.facebook.react.utils.PropertyUtils.INTERNAL_HERMES_VERSION_NAME import com.facebook.react.utils.PropertyUtils.INTERNAL_REACT_NATIVE_MAVEN_LOCAL_REPO import com.facebook.react.utils.PropertyUtils.INTERNAL_REACT_PUBLISHING_GROUP @@ -32,7 +31,6 @@ internal object DependencyUtils { internal data class Coordinates( val versionString: String, val hermesVersionString: String, - val hermesV1VersionString: String, val reactGroupString: String = DEFAULT_INTERNAL_REACT_PUBLISHING_GROUP, val hermesGroupString: String = DEFAULT_INTERNAL_HERMES_PUBLISHING_GROUP, private val isHermesNightly: Boolean = false, @@ -114,19 +112,12 @@ internal object DependencyUtils { * party libraries which are auto-linked. Specifically it takes care of: * - Forcing the react-android/hermes-android version to the one specified in the package.json * - Substituting `react-native` with `react-android` and `hermes-engine` with `hermes-android` - * - Selecting between the classic Hermes and Hermes V1 */ fun configureDependencies( project: Project, coordinates: Coordinates, - hermesV1Enabled: Boolean = true, ) { - if ( - coordinates.versionString.isBlank() || - (!hermesV1Enabled && coordinates.hermesVersionString.isBlank()) || - (hermesV1Enabled && coordinates.hermesV1VersionString.isBlank()) - ) - return + if (coordinates.versionString.isBlank() || coordinates.hermesVersionString.isBlank()) return project.rootProject.allprojects { eachProject -> eachProject.configurations.all { configuration -> // Here we set a dependencySubstitution for both react-native and hermes-engine as those @@ -134,8 +125,7 @@ internal object DependencyUtils { // This allows users to import libraries that are still using // implementation("com.facebook.react:react-native:+") and resolve the right dependency. configuration.resolutionStrategy.dependencySubstitution { - getDependencySubstitutions(coordinates, hermesV1Enabled).forEach { (module, dest, reason) - -> + getDependencySubstitutions(coordinates).forEach { (module, dest, reason) -> it.substitute(it.module(module)).using(it.module(dest)).because(reason) } } @@ -146,7 +136,7 @@ internal object DependencyUtils { // Contributors only: The hermes-engine version is forced only if the user has // not opted into using nightlies for local development. configuration.resolutionStrategy.force( - "${coordinates.hermesGroupString}:hermes-android:${if (hermesV1Enabled) coordinates.hermesV1VersionString else coordinates.hermesVersionString}" + "${coordinates.hermesGroupString}:hermes-android:${coordinates.hermesVersionString}" ) } } @@ -155,12 +145,10 @@ internal object DependencyUtils { internal fun getDependencySubstitutions( coordinates: Coordinates, - hermesV1Enabled: Boolean = true, ): List> { val dependencySubstitution = mutableListOf>() - val hermesVersion = - if (hermesV1Enabled) coordinates.hermesV1VersionString else coordinates.hermesVersionString - val hermesVersionString = "${coordinates.hermesGroupString}:hermes-android:${hermesVersion}" + val hermesVersionString = + "${coordinates.hermesGroupString}:hermes-android:${coordinates.hermesVersionString}" dependencySubstitution.add( Triple( "com.facebook.react:react-native", @@ -245,14 +233,11 @@ internal object DependencyUtils { hermesVersionString } - val hermesV1Version = - (hermesVersionProperties[INTERNAL_HERMES_V1_VERSION_NAME] as? String).orEmpty() val isHermesNightly = (project.findProperty(INTERNAL_USE_HERMES_NIGHTLY) as? String).toBoolean() return Coordinates( versionString, hermesVersion, - hermesV1Version, reactGroupString, hermesGroupString, isHermesNightly, diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/JdkConfiguratorUtils.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/JdkConfiguratorUtils.kt index 151b53eaf6f..2a652dbcde7 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/JdkConfiguratorUtils.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/JdkConfiguratorUtils.kt @@ -18,9 +18,9 @@ import org.jetbrains.kotlin.gradle.dsl.kotlinExtension internal object JdkConfiguratorUtils { /** - * Function that takes care of configuring the JDK toolchain for all the projects projects. As we - * do decide the JDK version based on the AGP version that RNGP brings over, here we can safely - * configure the toolchain to 17. + * Function that takes care of configuring the JDK toolchain for all the projects. As we do decide + * the JDK version based on the AGP version that RNGP brings over, here we can safely configure + * the toolchain to 17. */ fun configureJavaToolChains(input: Project) { // Check at the app level if react.internal.disableJavaVersionAlignment is set. diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/PathUtils.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/PathUtils.kt index fc5a9a63a7c..e1acfc8f62d 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/PathUtils.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/PathUtils.kt @@ -11,7 +11,6 @@ package com.facebook.react.utils import com.facebook.react.ReactExtension import com.facebook.react.model.ModelPackageJson -import com.facebook.react.utils.KotlinStdlibCompatUtils.capitalizeCompat import com.facebook.react.utils.Os.cliPath import java.io.File import org.gradle.api.Project @@ -123,11 +122,9 @@ private fun detectCliFile( * 1. The path provided by the `hermesCommand` config in the `react` Gradle extension * 2. The file located in `node_modules/react-native/sdks/hermes/build/bin/hermesc`. This will be * used if the user is building Hermes from source. - * 3. The file located in `node_modules/react-native/sdks/hermesc/%OS-BIN%/hermesc` where `%OS-BIN%` - * is substituted with the correct OS arch. This will be used if the user is using a precompiled - * hermes-engine package. Or, if the user has opted in to use Hermes V1, the used file will be - * located in `node_modules/hermes-compiler/%OS-BIN%/hermesc` where `%OS-BIN%` is substituted - * with the correct OS arch. + * 3. The file located in `node_modules/hermes-compiler/%OS-BIN%/hermesc` where `%OS-BIN%` is + * substituted with the correct OS arch. This is used when Hermes V1 is consumed as a prebuilt + * package via the `hermes-compiler` npm package. * 4. Fails otherwise */ internal fun detectOSAwareHermesCommand( @@ -202,7 +199,7 @@ internal fun getHermesOSBin(): String { internal fun projectPathToLibraryName(projectPath: String): String = projectPath .split(':', '-', '_', '.') - .joinToString("") { token -> token.capitalizeCompat() } + .joinToString("") { token -> token.replaceFirstChar { it.titlecase() } } .plus("Spec") /** diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/ProjectUtils.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/ProjectUtils.kt index 805b9f92dd5..2a4d6cf7115 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/ProjectUtils.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/ProjectUtils.kt @@ -9,15 +9,11 @@ package com.facebook.react.utils import com.facebook.react.ReactExtension import com.facebook.react.model.ModelPackageJson -import com.facebook.react.utils.KotlinStdlibCompatUtils.lowercaseCompat -import com.facebook.react.utils.KotlinStdlibCompatUtils.toBooleanStrictOrNullCompat import com.facebook.react.utils.PropertyUtils.EDGE_TO_EDGE_ENABLED import com.facebook.react.utils.PropertyUtils.HERMES_ENABLED -import com.facebook.react.utils.PropertyUtils.HERMES_V1_ENABLED import com.facebook.react.utils.PropertyUtils.REACT_NATIVE_ARCHITECTURES import com.facebook.react.utils.PropertyUtils.SCOPED_EDGE_TO_EDGE_ENABLED import com.facebook.react.utils.PropertyUtils.SCOPED_HERMES_ENABLED -import com.facebook.react.utils.PropertyUtils.SCOPED_HERMES_V1_ENABLED import com.facebook.react.utils.PropertyUtils.SCOPED_REACT_NATIVE_ARCHITECTURES import com.facebook.react.utils.PropertyUtils.SCOPED_USE_THIRD_PARTY_JSC import com.facebook.react.utils.PropertyUtils.USE_THIRD_PARTY_JSC @@ -29,8 +25,6 @@ internal object ProjectUtils { const val HERMES_FALLBACK = true - const val HERMES_V1_ENABLED_FALLBACK = true - internal fun Project.isNewArchEnabled(): Boolean = true internal val Project.isHermesEnabled: Boolean @@ -42,17 +36,13 @@ internal object ProjectUtils { } else { SCOPED_HERMES_ENABLED } - project - .property(propertyString) - .toString() - .lowercaseCompat() - .toBooleanStrictOrNullCompat() ?: true + project.property(propertyString).toString().lowercase().toBooleanStrictOrNull() ?: true } else if (project.extensions.extraProperties.has("react")) { @Suppress("UNCHECKED_CAST") val reactMap = project.extensions.extraProperties.get("react") as? Map when (val enableHermesKey = reactMap?.get("enableHermes")) { is Boolean -> enableHermesKey - is String -> enableHermesKey.lowercaseCompat().toBooleanStrictOrNullCompat() ?: true + is String -> enableHermesKey.lowercase().toBooleanStrictOrNull() ?: true else -> HERMES_FALLBACK } } else { @@ -73,23 +63,6 @@ internal object ProjectUtils { (project.hasProperty(SCOPED_USE_THIRD_PARTY_JSC) && project.property(SCOPED_USE_THIRD_PARTY_JSC).toString().toBoolean()) - internal val Project.isHermesV1Enabled: Boolean - get() = - if ( - project.hasProperty(HERMES_V1_ENABLED) || project.hasProperty(SCOPED_HERMES_V1_ENABLED) - ) { - (project.hasProperty(HERMES_V1_ENABLED) && - project.property(HERMES_V1_ENABLED).toString().toBoolean()) || - (project.hasProperty(SCOPED_HERMES_V1_ENABLED) && - project.property(SCOPED_HERMES_V1_ENABLED).toString().toBoolean()) || - (project.extraProperties.has(HERMES_V1_ENABLED) && - project.extraProperties.get(HERMES_V1_ENABLED).toString().toBoolean()) || - (project.extraProperties.has(SCOPED_HERMES_V1_ENABLED) && - project.extraProperties.get(SCOPED_HERMES_V1_ENABLED).toString().toBoolean()) - } else { - HERMES_V1_ENABLED_FALLBACK - } - internal fun Project.needsCodegenFromPackageJson(rootProperty: DirectoryProperty): Boolean { val parsedPackageJson = readPackageJsonFile(this, rootProperty) return needsCodegenFromPackageJson(parsedPackageJson) diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/PropertyUtils.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/PropertyUtils.kt index a52e4177fd0..a35e5f37c5d 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/PropertyUtils.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/PropertyUtils.kt @@ -18,7 +18,10 @@ object PropertyUtils { const val HERMES_ENABLED = "hermesEnabled" const val SCOPED_HERMES_ENABLED = "react.hermesEnabled" - /** Public property that toggles Hermes V1 */ + /** + * Deprecated property that used to toggle Hermes V1. Hermes V1 is now always enabled. Kept here + * so we can detect usage and warn users to remove it from their gradle.properties. + */ const val HERMES_V1_ENABLED = "hermesV1Enabled" const val SCOPED_HERMES_V1_ENABLED = "react.hermesV1Enabled" @@ -92,5 +95,4 @@ object PropertyUtils { * are stored in sdks/hermes-engine/version.properties */ const val INTERNAL_HERMES_VERSION_NAME = "HERMES_VERSION_NAME" - const val INTERNAL_HERMES_V1_VERSION_NAME = "HERMES_V1_VERSION_NAME" } diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/ReactPluginTest.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/ReactPluginTest.kt new file mode 100644 index 00000000000..f19b140f442 --- /dev/null +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/ReactPluginTest.kt @@ -0,0 +1,215 @@ +/* + * 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. + */ + +package com.facebook.react + +import com.facebook.react.model.ModelAutolinkingDependenciesJson +import java.io.File +import org.assertj.core.api.Assertions.assertThat +import org.intellij.lang.annotations.Language +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder + +class ReactPluginTest { + + @get:Rule val tempFolder = TemporaryFolder() + + @Test + fun getPureCxxCodegenDependencies_filtersDependenciesCorrectly() { + val includesGeneratedCode = createPackage("includes-generated-code", true) + val withoutGeneratedCode = createPackage("without-generated-code", false) + val withoutIncludesGeneratedCode = createPackage("without-includes-generated-code", null) + val withoutCodegenConfig = createPackageWithoutCodegenConfig("without-codegen-config") + val missingNonPureCxxPackage = File(tempFolder.root, "missing-non-pure-cxx-package") + + val autolinkingFile = + createAutolinkingFile( + """ + { + "reactNativeVersion": "1000.0.0", + "dependencies": { + "includes-generated-code": { + "root": "${includesGeneratedCode.invariantSeparatorsPath}", + "name": "includes-generated-code", + "platforms": { + "android": { + "sourceDir": "${includesGeneratedCode.invariantSeparatorsPath}/android", + "packageImportPath": "import com.facebook.react.IncludesGeneratedCodePackage;", + "packageInstance": "new IncludesGeneratedCodePackage()", + "buildTypes": [], + "libraryName": "IncludesGeneratedCode", + "isPureCxxDependency": true + } + } + }, + "without-generated-code": { + "root": "${withoutGeneratedCode.invariantSeparatorsPath}", + "name": "without-generated-code", + "platforms": { + "android": { + "sourceDir": "${withoutGeneratedCode.invariantSeparatorsPath}/android", + "packageImportPath": "import com.facebook.react.WithoutGeneratedCodePackage;", + "packageInstance": "new WithoutGeneratedCodePackage()", + "buildTypes": [], + "libraryName": "WithoutGeneratedCode", + "isPureCxxDependency": true + } + } + }, + "without-includes-generated-code": { + "root": "${withoutIncludesGeneratedCode.invariantSeparatorsPath}", + "name": "without-includes-generated-code", + "platforms": { + "android": { + "sourceDir": "${withoutIncludesGeneratedCode.invariantSeparatorsPath}/android", + "packageImportPath": "import com.facebook.react.WithoutIncludesGeneratedCodePackage;", + "packageInstance": "new WithoutIncludesGeneratedCodePackage()", + "buildTypes": [], + "libraryName": "WithoutIncludesGeneratedCode", + "isPureCxxDependency": true + } + } + }, + "without-codegen-config": { + "root": "${withoutCodegenConfig.invariantSeparatorsPath}", + "name": "without-codegen-config", + "platforms": { + "android": { + "sourceDir": "${withoutCodegenConfig.invariantSeparatorsPath}/android", + "packageImportPath": "import com.facebook.react.WithoutCodegenConfigPackage;", + "packageInstance": "new WithoutCodegenConfigPackage()", + "buildTypes": [], + "libraryName": "WithoutCodegenConfig", + "isPureCxxDependency": true + } + } + }, + "missing-non-pure-cxx-package": { + "root": "${missingNonPureCxxPackage.invariantSeparatorsPath}", + "name": "missing-non-pure-cxx-package", + "platforms": { + "android": { + "sourceDir": "${missingNonPureCxxPackage.invariantSeparatorsPath}/android", + "packageImportPath": "import com.facebook.react.MissingNonPureCxxPackage;", + "packageInstance": "new MissingNonPureCxxPackage()", + "buildTypes": [], + "libraryName": "MissingNonPureCxxPackage", + "isPureCxxDependency": false + } + } + } + } + } + """ + .trimIndent() + ) + + val result = ReactPlugin().getPureCxxCodegenDependencies(autolinkingFile) + + assertThat(result.map { it.name }) + .containsExactly("without-generated-code", "without-includes-generated-code") + } + + @Test + fun taskNameSuffixForDependency_withSimpleName_capitalizesIt() { + val dependency = + ModelAutolinkingDependenciesJson(root = "./simple", name = "simple", platforms = null) + + val result = ReactPlugin().taskNameSuffixForDependency(dependency) + + assertThat(result).isEqualTo("Simple") + } + + @Test + fun taskNameSuffixForDependency_withNonAlphanumericCharacters_encodesThem() { + val dependency = + ModelAutolinkingDependenciesJson( + root = "./node_modules/@foo/bar-baz", + name = "@foo/bar-baz", + platforms = null, + ) + + val result = ReactPlugin().taskNameSuffixForDependency(dependency) + + assertThat(result).isEqualTo("_64_foo_47_bar_45_baz") + } + + @Test + fun taskNameSuffixForDependency_withSimilarCleansedNames_avoidsCollisions() { + val plugin = ReactPlugin() + val suffixes = + listOf("@foo/bar", "foo.bar", "foo-bar", "foo_bar", "foo_45_bar").map { name -> + val dependency = + ModelAutolinkingDependenciesJson( + root = "./node_modules/$name", + name = name, + platforms = null, + ) + + plugin.taskNameSuffixForDependency(dependency) + } + + assertThat(suffixes).doesNotHaveDuplicates() + } + + @Test + fun taskNameSuffixForDependency_withLocalModuleRoot_usesPackageName() { + val dependency = + ModelAutolinkingDependenciesJson( + root = "./modules/local-module", + name = "local-module", + platforms = null, + ) + + val result = ReactPlugin().taskNameSuffixForDependency(dependency) + + assertThat(result).isEqualTo("Local_45_module") + } + + private fun createPackage(name: String, includesGeneratedCode: Boolean? = null): File { + val folder = tempFolder.newFolder(name) + File(folder, "package.json").writeText(packageJson(includesGeneratedCode)) + return folder + } + + private fun createPackageWithoutCodegenConfig(name: String): File { + val folder = tempFolder.newFolder(name) + File(folder, "package.json").writeText(packageJson()) + return folder + } + + private fun createAutolinkingFile(@Language("JSON") input: String) = + tempFolder.newFile("autolinking.json").apply { writeText(input) } + + private fun packageJson(includesGeneratedCode: Boolean?): String { + val includesGeneratedCodeLine = + includesGeneratedCode?.let { ""","includesGeneratedCode": $it""" } ?: "" + // language=JSON + return """ + { + "version": "1.0.0", + "codegenConfig": { + "name": "TestSpec", + "type": "modules", + "jsSrcsDir": "src"$includesGeneratedCodeLine + } + } + """ + .trimIndent() + } + + private fun packageJson(): String { + // language=JSON + return """ + { + "version": "1.0.0" + } + """ + .trimIndent() + } +} diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateAutolinkingNewArchitecturesFileTaskTest.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateAutolinkingNewArchitecturesFileTaskTest.kt index 2b3f17d5ea2..df92f572e33 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateAutolinkingNewArchitecturesFileTaskTest.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateAutolinkingNewArchitecturesFileTaskTest.kt @@ -13,6 +13,7 @@ import com.facebook.react.model.ModelAutolinkingDependenciesPlatformAndroidJson import com.facebook.react.model.ModelAutolinkingDependenciesPlatformJson import com.facebook.react.tasks.GenerateAutolinkingNewArchitecturesFileTask.Companion.sanitizeCmakeListsPath import com.facebook.react.tests.createTestTask +import java.io.File import org.assertj.core.api.Assertions.assertThat import org.junit.Rule import org.junit.Test @@ -41,6 +42,7 @@ class GenerateAutolinkingNewArchitecturesFileTaskTest { assertThat(task.generatedOutputDirectory.get().asFile).isEqualTo(outputFolder) assertThat(task.generatedOutputDirectory.get().asFile).isEqualTo(outputFolder) + assertThat(task.generatedPureCxxSourceDirectory.isPresent).isFalse() } @Test @@ -111,6 +113,83 @@ class GenerateAutolinkingNewArchitecturesFileTaskTest { assertThat(result).containsExactly(android) } + @Test + fun cmakeListsPathForDependency_withCmakeListsPath_returnsIt() { + val task = createTestTask() + val dependency = + ModelAutolinkingDependenciesPlatformAndroidJson( + sourceDir = "./a/directory", + packageImportPath = "import com.facebook.react.aPackage;", + packageInstance = "new APackage()", + buildTypes = emptyList(), + libraryName = "aPackage", + cmakeListsPath = "./a/directory/CMakeLists.txt", + isPureCxxDependency = true, + ) + + assertThat(task.cmakeListsPathForDependency(dependency)) + .isEqualTo("./a/directory/CMakeLists.txt") + } + + @Test + fun cmakeListsPathForDependency_withPureCxxDependency_returnsGeneratedPath() { + val generatedPureCxxSourceDirectory = tempFolder.newFolder("pureCxx") + val task = + createTestTask { + it.generatedPureCxxSourceDirectory.set(generatedPureCxxSourceDirectory) + } + val dependency = + ModelAutolinkingDependenciesPlatformAndroidJson( + sourceDir = "./a/directory", + packageImportPath = "import com.facebook.react.aPackage;", + packageInstance = "new APackage()", + buildTypes = emptyList(), + libraryName = "aPackage", + isPureCxxDependency = true, + ) + + assertThat(task.cmakeListsPathForDependency(dependency)) + .isEqualTo( + File(generatedPureCxxSourceDirectory, "aPackage/jni/CMakeLists.txt").absolutePath + ) + } + + @Test + fun cmakeListsPathForDependency_withMissingGeneratedDirectory_returnsNull() { + val task = createTestTask() + val dependency = + ModelAutolinkingDependenciesPlatformAndroidJson( + sourceDir = "./a/directory", + packageImportPath = "import com.facebook.react.aPackage;", + packageInstance = "new APackage()", + buildTypes = emptyList(), + libraryName = "aPackage", + isPureCxxDependency = true, + ) + + assertThat(task.cmakeListsPathForDependency(dependency)).isNull() + } + + @Test + fun cmakeListsPathForDependency_withNonPureCxxDependency_returnsNull() { + val generatedPureCxxSourceDirectory = tempFolder.newFolder("pureCxx") + val task = + createTestTask { + it.generatedPureCxxSourceDirectory.set(generatedPureCxxSourceDirectory) + } + val dependency = + ModelAutolinkingDependenciesPlatformAndroidJson( + sourceDir = "./a/directory", + packageImportPath = "import com.facebook.react.aPackage;", + packageInstance = "new APackage()", + buildTypes = emptyList(), + libraryName = "aPackage", + isPureCxxDependency = false, + ) + + assertThat(task.cmakeListsPathForDependency(dependency)).isNull() + } + @Test fun generateCmakeFileContent_withNoPackages_returnsEmpty() { val output = @@ -128,11 +207,9 @@ class GenerateAutolinkingNewArchitecturesFileTaskTest { # or link against a old prefab target (this is needed for React Native 0.76 on). set(REACTNATIVE_MERGED_SO true) + set(AUTOLINKED_LIBRARIES) - set(AUTOLINKED_LIBRARIES - - ) """ .trimIndent() ) @@ -155,18 +232,155 @@ class GenerateAutolinkingNewArchitecturesFileTaskTest { # or link against a old prefab target (this is needed for React Native 0.76 on). set(REACTNATIVE_MERGED_SO true) - add_subdirectory("./a/directory/" aPackage_autolinked_build) - add_subdirectory("./another/directory/with\ spaces/" anotherPackage_autolinked_build) - add_subdirectory("./another/directory/cxx/" anotherPackage_cxxmodule_autolinked_build) + set(AUTOLINKED_LIBRARIES) + + if(EXISTS "./a/directory/") + add_subdirectory("./a/directory/" aPackage_autolinked_build) + list(APPEND AUTOLINKED_LIBRARIES react_codegen_aPackage) + else() + message(WARNING "React Native: Skipping autolinked library 'react_codegen_aPackage' because the source directory does not exist: ./a/directory/") + endif() + if(EXISTS "./another/directory/with\ spaces/") + add_subdirectory("./another/directory/with\ spaces/" anotherPackage_autolinked_build) + list(APPEND AUTOLINKED_LIBRARIES react_codegen_anotherPackage) + else() + message(WARNING "React Native: Skipping autolinked library 'react_codegen_anotherPackage' because the source directory does not exist: ./another/directory/with\ spaces/") + endif() + if(EXISTS "./another/directory/cxx/") + add_subdirectory("./another/directory/cxx/" anotherPackage_cxxmodule_autolinked_build) + list(APPEND AUTOLINKED_LIBRARIES another_cxxModule) + else() + message(WARNING "React Native: Skipping autolinked C++ module 'another_cxxModule' because the source directory does not exist: ./another/directory/cxx/") + endif() + """ + .trimIndent() + ) + } + + @Test + fun generateCmakeFileContent_withGeneratedPureCxxPath_usesItWhenCmakeListsPathIsMissing() { + val generatedPureCxxSourceDirectory = tempFolder.newFolder("pureCxx") + val generatedCmakeListsPath = + File(generatedPureCxxSourceDirectory, "aPackage/jni/CMakeLists.txt").absolutePath + val generatedNativeFolderPath = sanitizeCmakeListsPath(generatedCmakeListsPath) + + val task = + createTestTask { + it.generatedPureCxxSourceDirectory.set(generatedPureCxxSourceDirectory) + } + + val output = + task.generateCmakeFileContent( + listOf( + ModelAutolinkingDependenciesPlatformAndroidJson( + sourceDir = "./a/directory", + packageImportPath = "import com.facebook.react.aPackage;", + packageInstance = "new APackage()", + buildTypes = emptyList(), + libraryName = "aPackage", + isPureCxxDependency = true, + ) + ) + ) + + assertThat(output) + .contains( + """ + if(EXISTS "$generatedNativeFolderPath") + add_subdirectory("$generatedNativeFolderPath" aPackage_autolinked_build) + list(APPEND AUTOLINKED_LIBRARIES react_codegen_aPackage) + else() + message(WARNING "React Native: Skipping autolinked library 'react_codegen_aPackage' because the source directory does not exist: $generatedNativeFolderPath") + endif() + """ + .trimIndent() + ) + } + + @Test + fun generateCmakeFileContent_withGeneratedPureCxxPath_preservesCmakeListsPath() { + val generatedPureCxxSourceDirectory = tempFolder.newFolder("pureCxx") + + val task = + createTestTask { + it.generatedPureCxxSourceDirectory.set(generatedPureCxxSourceDirectory) + } - set(AUTOLINKED_LIBRARIES - react_codegen_aPackage - react_codegen_anotherPackage - another_cxxModule + val output = + task.generateCmakeFileContent( + listOf( + ModelAutolinkingDependenciesPlatformAndroidJson( + sourceDir = "./a/directory", + packageImportPath = "import com.facebook.react.aPackage;", + packageInstance = "new APackage()", + buildTypes = emptyList(), + libraryName = "aPackage", + cmakeListsPath = "./a/directory/CMakeLists.txt", + isPureCxxDependency = true, + ) ) + ) + + assertThat(output) + .contains( + """ + if(EXISTS "./a/directory/") + add_subdirectory("./a/directory/" aPackage_autolinked_build) + list(APPEND AUTOLINKED_LIBRARIES react_codegen_aPackage) + else() + message(WARNING "React Native: Skipping autolinked library 'react_codegen_aPackage' because the source directory does not exist: ./a/directory/") + endif() """ .trimIndent() ) + assertThat(output).doesNotContain(generatedPureCxxSourceDirectory.absolutePath) + } + + @Test + fun generateCmakeFileContent_withGeneratedPureCxxPath_ignoresNonPureCxxDependency() { + val generatedPureCxxSourceDirectory = tempFolder.newFolder("pureCxx") + + val task = + createTestTask { + it.generatedPureCxxSourceDirectory.set(generatedPureCxxSourceDirectory) + } + + val output = + task.generateCmakeFileContent( + listOf( + ModelAutolinkingDependenciesPlatformAndroidJson( + sourceDir = "./a/directory", + packageImportPath = "import com.facebook.react.aPackage;", + packageInstance = "new APackage()", + buildTypes = emptyList(), + libraryName = "aPackage", + isPureCxxDependency = false, + ) + ) + ) + + assertThat(output).doesNotContain("aPackage_autolinked_build") + assertThat(output).doesNotContain(generatedPureCxxSourceDirectory.absolutePath) + } + + @Test + fun generateCmakeFileContent_withPureCxxDependencyAndMissingGeneratedDirectory_skipsIt() { + val output = + createTestTask() + .generateCmakeFileContent( + listOf( + ModelAutolinkingDependenciesPlatformAndroidJson( + sourceDir = "./a/directory", + packageImportPath = "import com.facebook.react.aPackage;", + packageInstance = "new APackage()", + buildTypes = emptyList(), + libraryName = "aPackage", + isPureCxxDependency = true, + ) + ) + ) + + assertThat(output).doesNotContain("aPackage_autolinked_build") } @Test diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateCodegenSchemaTaskTest.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateCodegenSchemaTaskTest.kt index 7b3508bde6d..8b2e9dd9bd1 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateCodegenSchemaTaskTest.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateCodegenSchemaTaskTest.kt @@ -94,8 +94,6 @@ class GenerateCodegenSchemaTaskTest { File(codegenDir, "lib/cli/combine/combine-js-to-schema-cli.js").toString(), "--platform", "android", - "--exclude", - "NativeSampleTurboModule", File(outputDir, "schema.json").toString(), jsRootDir.toString(), ) @@ -131,8 +129,6 @@ class GenerateCodegenSchemaTaskTest { .path, "--platform", "android", - "--exclude", - "NativeSampleTurboModule", File(outputDir, "schema.json").relativeTo(project.projectDir).path, jsRootDir.relativeTo(project.projectDir).path, ) diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/DependencyUtilsTest.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/DependencyUtilsTest.kt index 0454b5ece33..5c57bb296fa 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/DependencyUtilsTest.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/DependencyUtilsTest.kt @@ -291,97 +291,48 @@ class DependencyUtilsTest { fun configureDependencies_withEmptyVersion_doesNothing() { val project = createProject() - configureDependencies(project, DependencyUtils.Coordinates("", "", "")) + configureDependencies(project, DependencyUtils.Coordinates("", "")) assertThat(project.configurations.first().resolutionStrategy.forcedModules.isEmpty()).isTrue() } @Test - fun configureDependencies_withVersionString_appliesResolutionStrategy_withClassicHermes() { + fun configureDependencies_withVersionString_appliesResolutionStrategy() { val project = createProject() - configureDependencies(project, DependencyUtils.Coordinates("1.2.3", "4.5.6", "7.8.9")) + configureDependencies(project, DependencyUtils.Coordinates("1.2.3", "4.5.6")) val forcedModules = project.configurations.first().resolutionStrategy.forcedModules assertThat(forcedModules.any { it.toString() == "com.facebook.react:react-android:1.2.3" }) .isTrue() assertThat(forcedModules.any { it.toString() == "com.facebook.hermes:hermes-android:4.5.6" }) - .isFalse() - assertThat(forcedModules.any { it.toString() == "com.facebook.hermes:hermes-android:7.8.9" }) .isTrue() } @Test - fun configureDependencies_withVersionString_appliesResolutionStrategy_withHermesV1() { - val project = createProject() - - configureDependencies( - project, - DependencyUtils.Coordinates("1.2.3", "4.5.6", "7.8.9"), - hermesV1Enabled = true, - ) - - val forcedModules = project.configurations.first().resolutionStrategy.forcedModules - assertThat(forcedModules.any { it.toString() == "com.facebook.react:react-android:1.2.3" }) - .isTrue() - assertThat(forcedModules.any { it.toString() == "com.facebook.hermes:hermes-android:7.8.9" }) - .isTrue() - } - - @Test - fun configureDependencies_withVersionString_appliesOnAllProjects_withClassicHermes() { + fun configureDependencies_withVersionString_appliesOnAllProjects() { val rootProject = ProjectBuilder.builder().build() val appProject = ProjectBuilder.builder().withName("app").withParent(rootProject).build() val libProject = ProjectBuilder.builder().withName("lib").withParent(rootProject).build() appProject.plugins.apply("com.android.application") libProject.plugins.apply("com.android.library") - configureDependencies(appProject, DependencyUtils.Coordinates("1.2.3", "4.5.6", "7.8.9")) + configureDependencies(appProject, DependencyUtils.Coordinates("1.2.3", "4.5.6")) val appForcedModules = appProject.configurations.first().resolutionStrategy.forcedModules val libForcedModules = libProject.configurations.first().resolutionStrategy.forcedModules assertThat(appForcedModules.any { it.toString() == "com.facebook.react:react-android:1.2.3" }) .isTrue() assertThat(appForcedModules.any { it.toString() == "com.facebook.hermes:hermes-android:4.5.6" }) - .isFalse() - assertThat(appForcedModules.any { it.toString() == "com.facebook.hermes:hermes-android:7.8.9" }) .isTrue() assertThat(libForcedModules.any { it.toString() == "com.facebook.react:react-android:1.2.3" }) .isTrue() assertThat(libForcedModules.any { it.toString() == "com.facebook.hermes:hermes-android:4.5.6" }) - .isFalse() - assertThat(libForcedModules.any { it.toString() == "com.facebook.hermes:hermes-android:7.8.9" }) - .isTrue() - } - - @Test - fun configureDependencies_withVersionString_appliesOnAllProjects_withHermesV1() { - val rootProject = ProjectBuilder.builder().build() - val appProject = ProjectBuilder.builder().withName("app").withParent(rootProject).build() - val libProject = ProjectBuilder.builder().withName("lib").withParent(rootProject).build() - appProject.plugins.apply("com.android.application") - libProject.plugins.apply("com.android.library") - - configureDependencies( - appProject, - DependencyUtils.Coordinates("1.2.3", "4.5.6", "7.8.9"), - hermesV1Enabled = true, - ) - - val appForcedModules = appProject.configurations.first().resolutionStrategy.forcedModules - val libForcedModules = libProject.configurations.first().resolutionStrategy.forcedModules - assertThat(appForcedModules.any { it.toString() == "com.facebook.react:react-android:1.2.3" }) - .isTrue() - assertThat(appForcedModules.any { it.toString() == "com.facebook.hermes:hermes-android:7.8.9" }) - .isTrue() - assertThat(libForcedModules.any { it.toString() == "com.facebook.react:react-android:1.2.3" }) - .isTrue() - assertThat(libForcedModules.any { it.toString() == "com.facebook.hermes:hermes-android:7.8.9" }) .isTrue() } @Test - fun configureDependencies_withVersionStringAndGroupString_appliesOnAllProjects_withClassicHermes() { + fun configureDependencies_withVersionStringAndGroupString_appliesOnAllProjects() { val rootProject = ProjectBuilder.builder().build() val appProject = ProjectBuilder.builder().withName("app").withParent(rootProject).build() val libProject = ProjectBuilder.builder().withName("lib").withParent(rootProject).build() @@ -393,7 +344,6 @@ class DependencyUtilsTest { DependencyUtils.Coordinates( "1.2.3", "4.5.6", - "7.8.9", "io.github.test", "io.github.test.hermes", ), @@ -406,63 +356,19 @@ class DependencyUtilsTest { assertThat( appForcedModules.any { it.toString() == "io.github.test.hermes:hermes-android:4.5.6" } ) - .isFalse() - assertThat( - appForcedModules.any { it.toString() == "io.github.test.hermes:hermes-android:7.8.9" } - ) .isTrue() assertThat(libForcedModules.any { it.toString() == "io.github.test:react-android:1.2.3" }) .isTrue() assertThat( libForcedModules.any { it.toString() == "io.github.test.hermes:hermes-android:4.5.6" } ) - .isFalse() - assertThat( - libForcedModules.any { it.toString() == "io.github.test.hermes:hermes-android:7.8.9" } - ) - .isTrue() - } - - @Test - fun configureDependencies_withVersionStringAndGroupString_appliesOnAllProjects_withHermesV1() { - val rootProject = ProjectBuilder.builder().build() - val appProject = ProjectBuilder.builder().withName("app").withParent(rootProject).build() - val libProject = ProjectBuilder.builder().withName("lib").withParent(rootProject).build() - appProject.plugins.apply("com.android.application") - libProject.plugins.apply("com.android.library") - - configureDependencies( - appProject, - DependencyUtils.Coordinates( - "1.2.3", - "4.5.6", - "7.8.9", - "io.github.test", - "io.github.test.hermes", - ), - hermesV1Enabled = true, - ) - - val appForcedModules = appProject.configurations.first().resolutionStrategy.forcedModules - val libForcedModules = libProject.configurations.first().resolutionStrategy.forcedModules - assertThat(appForcedModules.any { it.toString() == "io.github.test:react-android:1.2.3" }) - .isTrue() - assertThat( - appForcedModules.any { it.toString() == "io.github.test.hermes:hermes-android:7.8.9" } - ) - .isTrue() - assertThat(libForcedModules.any { it.toString() == "io.github.test:react-android:1.2.3" }) - .isTrue() - assertThat( - libForcedModules.any { it.toString() == "io.github.test.hermes:hermes-android:7.8.9" } - ) .isTrue() } @Test - fun getDependencySubstitutions_withDefaultGroup_substitutesCorrectly_withHermesV1() { + fun getDependencySubstitutions_withDefaultGroup_substitutesCorrectly() { val dependencySubstitutions = - getDependencySubstitutions(DependencyUtils.Coordinates("0.42.0", "0.42.0", "0.43.0")) + getDependencySubstitutions(DependencyUtils.Coordinates("0.42.0", "0.42.0")) assertThat("com.facebook.react:react-native").isEqualTo(dependencySubstitutions[0].first) assertThat("com.facebook.react:react-android:0.42.0") @@ -472,7 +378,7 @@ class DependencyUtilsTest { ) .isEqualTo(dependencySubstitutions[0].third) assertThat("com.facebook.react:hermes-engine").isEqualTo(dependencySubstitutions[1].first) - assertThat("com.facebook.hermes:hermes-android:0.43.0") + assertThat("com.facebook.hermes:hermes-android:0.42.0") .isEqualTo(dependencySubstitutions[1].second) assertThat( "The hermes-engine artifact was deprecated in favor of hermes-android due to https://github.com/facebook/react-native/issues/35210." @@ -481,85 +387,17 @@ class DependencyUtilsTest { } @Test - fun getDependencySubstitutions_withDefaultGroupAndFallback_substitutesCorrectly_withClassicHermes() { - val dependencySubstitutions = - getDependencySubstitutions( - DependencyUtils.Coordinates("0.42.0", "0.42.0", "0.43.0"), - hermesV1Enabled = true, - ) - - assertThat("com.facebook.react:react-native").isEqualTo(dependencySubstitutions[0].first) - assertThat("com.facebook.react:react-android:0.42.0") - .isEqualTo(dependencySubstitutions[0].second) - assertThat( - "The react-native artifact was deprecated in favor of react-android due to https://github.com/facebook/react-native/issues/35210." - ) - .isEqualTo(dependencySubstitutions[0].third) - assertThat("com.facebook.react:hermes-engine").isEqualTo(dependencySubstitutions[1].first) - assertThat("com.facebook.hermes:hermes-android:0.43.0") - .isEqualTo(dependencySubstitutions[1].second) - assertThat( - "The hermes-engine artifact was deprecated in favor of hermes-android due to https://github.com/facebook/react-native/issues/35210." - ) - .isEqualTo(dependencySubstitutions[1].third) - } - - @Test - fun getDependencySubstitutions_withCustomGroup_substitutesCorrectly_withHermesV1() { + fun getDependencySubstitutions_withCustomGroup_substitutesCorrectly() { val dependencySubstitutions = getDependencySubstitutions( DependencyUtils.Coordinates( "0.42.0", "0.42.0", - "0.43.0", "io.github.test", "io.github.test.hermes", ) ) - assertThat("com.facebook.react:react-native").isEqualTo(dependencySubstitutions[0].first) - assertThat("io.github.test:react-android:0.42.0").isEqualTo(dependencySubstitutions[0].second) - assertThat( - "The react-native artifact was deprecated in favor of react-android due to https://github.com/facebook/react-native/issues/35210." - ) - .isEqualTo(dependencySubstitutions[0].third) - assertThat("com.facebook.react:hermes-engine").isEqualTo(dependencySubstitutions[1].first) - assertThat("io.github.test.hermes:hermes-android:0.43.0") - .isEqualTo(dependencySubstitutions[1].second) - assertThat( - "The hermes-engine artifact was deprecated in favor of hermes-android due to https://github.com/facebook/react-native/issues/35210." - ) - .isEqualTo(dependencySubstitutions[1].third) - assertThat("com.facebook.react:hermes-android").isEqualTo(dependencySubstitutions[2].first) - assertThat("io.github.test.hermes:hermes-android:0.43.0") - .isEqualTo(dependencySubstitutions[2].second) - assertThat("The hermes-android artifact was moved to com.facebook.hermes publishing group.") - .isEqualTo(dependencySubstitutions[2].third) - assertThat("com.facebook.react:react-android").isEqualTo(dependencySubstitutions[3].first) - assertThat("io.github.test:react-android:0.42.0").isEqualTo(dependencySubstitutions[3].second) - assertThat("The react-android dependency was modified to use the correct Maven group.") - .isEqualTo(dependencySubstitutions[3].third) - assertThat("com.facebook.react:hermes-android").isEqualTo(dependencySubstitutions[4].first) - assertThat("io.github.test.hermes:hermes-android:0.43.0") - .isEqualTo(dependencySubstitutions[4].second) - assertThat("The hermes-android dependency was modified to use the correct Maven group.") - .isEqualTo(dependencySubstitutions[4].third) - } - - @Test - fun getDependencySubstitutions_withCustomGroupAndFallbackToClassicHermes_substitutesCorrectly_withClassicHermes() { - val dependencySubstitutions = - getDependencySubstitutions( - DependencyUtils.Coordinates( - "0.42.0", - "0.42.0", - "0.43.0", - "io.github.test", - "io.github.test.hermes", - ), - hermesV1Enabled = false, - ) - assertThat("com.facebook.react:react-native").isEqualTo(dependencySubstitutions[0].first) assertThat("io.github.test:react-android:0.42.0").isEqualTo(dependencySubstitutions[0].second) assertThat( @@ -607,7 +445,6 @@ class DependencyUtilsTest { writeText( """ HERMES_VERSION_NAME=1000.0.0 - HERMES_V1_VERSION_NAME=1000.0.0 ANOTHER_PROPERTY=true """ .trimIndent() @@ -618,11 +455,9 @@ class DependencyUtilsTest { val strings = readVersionAndGroupStrings(project, propertiesFile, hermesVersionFile) val versionString = strings.versionString val hermesVersionString = strings.hermesVersionString - val hermesV1VersionString = strings.hermesV1VersionString assertThat(versionString).isEqualTo("1000.0.0") assertThat(hermesVersionString).isEqualTo("1000.0.0") - assertThat(hermesV1VersionString).isEqualTo("1000.0.0") } @Test @@ -633,7 +468,6 @@ class DependencyUtilsTest { """ VERSION_NAME=0.0.0-20221101-2019-cfe811ab1 HERMES_VERSION_NAME=0.12.0-commitly-20221101-2019-cfe811ab1 - HERMES_V1_VERSION_NAME=250829098.0.0-stable ANOTHER_PROPERTY=true """ .trimIndent() @@ -645,7 +479,6 @@ class DependencyUtilsTest { writeText( """ HERMES_VERSION_NAME=0.14.0 - HERMES_V1_VERSION_NAME=250829098.0.0-stable ANOTHER_PROPERTY=true """ .trimIndent() @@ -656,11 +489,9 @@ class DependencyUtilsTest { val strings = readVersionAndGroupStrings(project, propertiesFile, hermesVersionFile) val versionString = strings.versionString val hermesVersionString = strings.hermesVersionString - val hermesV1VersionString = strings.hermesV1VersionString assertThat(versionString).isEqualTo("0.0.0-20221101-2019-cfe811ab1-SNAPSHOT") assertThat(hermesVersionString).isEqualTo("0.14.0") - assertThat(hermesV1VersionString).isEqualTo("250829098.0.0-stable") } @Test @@ -689,10 +520,8 @@ class DependencyUtilsTest { val strings = readVersionAndGroupStrings(project, propertiesFile, hermesVersionFile) val versionString = strings.versionString val hermesVersionString = strings.hermesVersionString - val hermesV1VersionString = strings.hermesV1VersionString assertThat(versionString).isEqualTo("") assertThat(hermesVersionString).isEqualTo("") - assertThat(hermesV1VersionString).isEqualTo("") } @Test @@ -713,7 +542,6 @@ class DependencyUtilsTest { writeText( """ HERMES_VERSION_NAME= - HERMES_V1_VERSION_NAME= ANOTHER_PROPERTY=true """ .trimIndent() @@ -724,10 +552,8 @@ class DependencyUtilsTest { val strings = readVersionAndGroupStrings(project, propertiesFile, hermesVersionFile) val versionString = strings.versionString val hermesVersionString = strings.hermesVersionString - val hermesV1VersionString = strings.hermesV1VersionString assertThat(versionString).isEqualTo("") assertThat(hermesVersionString).isEqualTo("") - assertThat(hermesV1VersionString).isEqualTo("") } @Test @@ -749,7 +575,6 @@ class DependencyUtilsTest { writeText( """ HERMES_VERSION_NAME= - HERMES_V1_VERSION_NAME= ANOTHER_PROPERTY=true """ .trimIndent() @@ -782,7 +607,6 @@ class DependencyUtilsTest { writeText( """ HERMES_VERSION_NAME= - HERMES_V1_VERSION_NAME= ANOTHER_PROPERTY=true """ .trimIndent() diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/ProjectUtilsTest.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/ProjectUtilsTest.kt index f4ec2a40acf..54a3016e961 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/ProjectUtilsTest.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/ProjectUtilsTest.kt @@ -14,7 +14,6 @@ import com.facebook.react.tests.createProject import com.facebook.react.utils.ProjectUtils.getReactNativeArchitectures import com.facebook.react.utils.ProjectUtils.isEdgeToEdgeEnabled import com.facebook.react.utils.ProjectUtils.isHermesEnabled -import com.facebook.react.utils.ProjectUtils.isHermesV1Enabled import com.facebook.react.utils.ProjectUtils.isNewArchEnabled import com.facebook.react.utils.ProjectUtils.needsCodegenFromPackageJson import java.io.File @@ -116,32 +115,6 @@ class ProjectUtilsTest { assertThat(project.isEdgeToEdgeEnabled).isFalse() } - @Test - fun isHermesV1Enabled_returnsTrueByDefault() { - assertThat(createProject().isHermesV1Enabled).isTrue() - } - - @Test - fun isHermesV1Enabled_withDisabledViaProperty_returnsFalse() { - val project = createProject() - project.extensions.extraProperties.set("hermesV1Enabled", "false") - assertThat(project.isHermesV1Enabled).isFalse() - } - - @Test - fun isHermesV1Enabled_withEnabledViaProperty_returnsTrue() { - val project = createProject() - project.extensions.extraProperties.set("hermesV1Enabled", "true") - assertThat(project.isHermesV1Enabled).isTrue() - } - - @Test - fun isHermesV1Enabled_withInvalidViaProperty_returnsFalse() { - val project = createProject() - project.extensions.extraProperties.set("hermesV1Enabled", "¯\\_(ツ)_/¯") - assertThat(project.isHermesV1Enabled).isFalse() - } - @Test fun needsCodegenFromPackageJson_withCodegenConfigInPackageJson_returnsTrue() { val project = createProject() diff --git a/packages/gradle-plugin/settings-plugin/build.gradle.kts b/packages/gradle-plugin/settings-plugin/build.gradle.kts index 2fbeafd502c..870844cb292 100644 --- a/packages/gradle-plugin/settings-plugin/build.gradle.kts +++ b/packages/gradle-plugin/settings-plugin/build.gradle.kts @@ -54,7 +54,7 @@ kotlin { jvmToolchain(17) } tasks.withType().configureEach { compilerOptions { - apiVersion.set(KotlinVersion.KOTLIN_1_8) + apiVersion.set(KotlinVersion.KOTLIN_2_0) // See comment above on JDK 11 support jvmTarget.set(JvmTarget.JVM_11) allWarningsAsErrors.set( diff --git a/packages/gradle-plugin/shared-testutil/build.gradle.kts b/packages/gradle-plugin/shared-testutil/build.gradle.kts index 26173fb5557..307d8a9743f 100644 --- a/packages/gradle-plugin/shared-testutil/build.gradle.kts +++ b/packages/gradle-plugin/shared-testutil/build.gradle.kts @@ -27,7 +27,7 @@ kotlin { jvmToolchain(17) } tasks.withType().configureEach { compilerOptions { - apiVersion.set(KotlinVersion.KOTLIN_1_8) + apiVersion.set(KotlinVersion.KOTLIN_2_0) // See comment above on JDK 11 support jvmTarget.set(JvmTarget.JVM_11) allWarningsAsErrors.set( diff --git a/packages/gradle-plugin/shared/build.gradle.kts b/packages/gradle-plugin/shared/build.gradle.kts index 315984875ad..9395aab5348 100644 --- a/packages/gradle-plugin/shared/build.gradle.kts +++ b/packages/gradle-plugin/shared/build.gradle.kts @@ -33,7 +33,7 @@ kotlin { jvmToolchain(17) } tasks.withType().configureEach { compilerOptions { - apiVersion.set(KotlinVersion.KOTLIN_1_8) + apiVersion.set(KotlinVersion.KOTLIN_2_0) // See comment above on JDK 11 support jvmTarget.set(JvmTarget.JVM_11) allWarningsAsErrors.set( diff --git a/packages/gradle-plugin/shared/src/main/kotlin/com/facebook/react/utils/JsonUtils.kt b/packages/gradle-plugin/shared/src/main/kotlin/com/facebook/react/utils/JsonUtils.kt index 6a6eeba7b93..ab632f22348 100644 --- a/packages/gradle-plugin/shared/src/main/kotlin/com/facebook/react/utils/JsonUtils.kt +++ b/packages/gradle-plugin/shared/src/main/kotlin/com/facebook/react/utils/JsonUtils.kt @@ -23,21 +23,21 @@ object JsonUtils { fun fromAutolinkingConfigJson(input: File): ModelAutolinkingConfigJson? = input.bufferedReader().use { reader -> runCatching { - // We sanitize the output of the `config` command as it could contain debug logs - // such as: - // - // > AwesomeProject@0.0.1 npx - // > rnc-cli config - // - // which will render the JSON invalid. - val content = - reader - .readLines() - .filterNot { line -> line.startsWith(">") } - .joinToString("\n") - .trim() - gsonConverter.fromJson(content, ModelAutolinkingConfigJson::class.java) - } + // We sanitize the output of the `config` command as it could contain debug logs + // such as: + // + // > AwesomeProject@0.0.1 npx + // > rnc-cli config + // + // which will render the JSON invalid. + val content = + reader + .readLines() + .filterNot { line -> line.startsWith(">") } + .joinToString("\n") + .trim() + gsonConverter.fromJson(content, ModelAutolinkingConfigJson::class.java) + } .getOrNull() } } diff --git a/packages/gradle-plugin/shared/src/main/kotlin/com/facebook/react/utils/KotlinStdlibCompatUtils.kt b/packages/gradle-plugin/shared/src/main/kotlin/com/facebook/react/utils/KotlinStdlibCompatUtils.kt deleted file mode 100644 index dd0df7c5cc2..00000000000 --- a/packages/gradle-plugin/shared/src/main/kotlin/com/facebook/react/utils/KotlinStdlibCompatUtils.kt +++ /dev/null @@ -1,32 +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. - */ - -package com.facebook.react.utils - -import java.util.Locale - -object KotlinStdlibCompatUtils { - @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") - fun String.lowercaseCompat(): String = (this as java.lang.String).toLowerCase(Locale.ROOT) - - fun String.capitalizeCompat(): String = - if (isNotEmpty()) { - val firstChar = this[0] - val uppercaseChar = Character.toUpperCase(firstChar) - val restString = this@capitalizeCompat.substring(1) - uppercaseChar + restString - } else { - this - } - - fun String.toBooleanStrictOrNullCompat(): Boolean? = - when (this) { - "true" -> true - "false" -> false - else -> null - } -} diff --git a/packages/gradle-plugin/shared/src/main/kotlin/com/facebook/react/utils/Os.kt b/packages/gradle-plugin/shared/src/main/kotlin/com/facebook/react/utils/Os.kt index a741fde8e43..53ffa0bdc7e 100644 --- a/packages/gradle-plugin/shared/src/main/kotlin/com/facebook/react/utils/Os.kt +++ b/packages/gradle-plugin/shared/src/main/kotlin/com/facebook/react/utils/Os.kt @@ -7,19 +7,18 @@ package com.facebook.react.utils -import com.facebook.react.utils.KotlinStdlibCompatUtils.lowercaseCompat import java.io.File object Os { fun isWindows(): Boolean = - System.getProperty("os.name")?.lowercaseCompat()?.contains("windows") ?: false + System.getProperty("os.name")?.lowercase()?.contains("windows") ?: false - fun isMac(): Boolean = System.getProperty("os.name")?.lowercaseCompat()?.contains("mac") ?: false + fun isMac(): Boolean = System.getProperty("os.name")?.lowercase()?.contains("mac") ?: false fun isLinuxAmd64(): Boolean { - val osNameMatch = System.getProperty("os.name")?.lowercaseCompat()?.contains("linux") ?: false - val archMatch = System.getProperty("os.arch")?.lowercaseCompat()?.contains("amd64") ?: false + val osNameMatch = System.getProperty("os.name")?.lowercase()?.contains("linux") ?: false + val archMatch = System.getProperty("os.arch")?.lowercase()?.contains("amd64") ?: false return osNameMatch && archMatch } diff --git a/packages/gradle-plugin/shared/src/test/kotlin/com/facebook/react/utils/KotlinStdlibCompatUtilsTest.kt b/packages/gradle-plugin/shared/src/test/kotlin/com/facebook/react/utils/KotlinStdlibCompatUtilsTest.kt deleted file mode 100644 index 177c3c74e66..00000000000 --- a/packages/gradle-plugin/shared/src/test/kotlin/com/facebook/react/utils/KotlinStdlibCompatUtilsTest.kt +++ /dev/null @@ -1,82 +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. - */ - -package com.facebook.react.utils - -import com.facebook.react.utils.KotlinStdlibCompatUtils.capitalizeCompat -import com.facebook.react.utils.KotlinStdlibCompatUtils.lowercaseCompat -import com.facebook.react.utils.KotlinStdlibCompatUtils.toBooleanStrictOrNullCompat -import org.assertj.core.api.Assertions.assertThat -import org.junit.Test - -class KotlinStdlibCompatUtilsTest { - - @Test - fun lowercaseCompat_withEmptyString() { - assertThat("".lowercaseCompat()).isEqualTo("") - } - - @Test - fun lowercaseCompat_withLowercaseString() { - assertThat("frodo".lowercaseCompat()).isEqualTo("frodo") - } - - @Test - fun lowercaseCompat_withTitlecaseString() { - assertThat("Frodo".lowercaseCompat()).isEqualTo("frodo") - } - - @Test - fun lowercaseCompat_withUppercaseString() { - assertThat("FRODO".lowercaseCompat()).isEqualTo("frodo") - } - - @Test - fun capitalizeCompat_withEmptyString() { - assertThat("".capitalizeCompat()).isEqualTo("") - } - - @Test - fun capitalizeCompat_withLowercaseString() { - assertThat("bilbo".capitalizeCompat()).isEqualTo("Bilbo") - } - - @Test - fun capitalizeCompat_withTitlecaseString() { - assertThat("Bilbo".capitalizeCompat()).isEqualTo("Bilbo") - } - - @Test - fun capitalizeCompat_withUppercaseString() { - assertThat("BILBO".capitalizeCompat()).isEqualTo("BILBO") - } - - @Test - fun toBooleanStrictOrNullCompat_withEmptyString() { - assertThat("".toBooleanStrictOrNullCompat()).isNull() - } - - @Test - fun toBooleanStrictOrNullCompat_withfalse() { - assertThat("false".toBooleanStrictOrNullCompat()).isFalse() - } - - @Test - fun toBooleanStrictOrNullCompat_withCapitalTrue_returnsNull() { - assertThat("True".toBooleanStrictOrNullCompat()).isNull() - } - - @Test - fun toBooleanStrictOrNullCompat_withCapitalFalse_returnsNull() { - assertThat("False".toBooleanStrictOrNullCompat()).isNull() - } - - @Test - fun toBooleanStrictOrNullCompat_withRandomInput_returnsNull() { - assertThat("maybe".toBooleanStrictOrNullCompat()).isNull() - } -} diff --git a/packages/jest-preset/README.md b/packages/jest-preset/README.md index 723b230fd59..de420553b82 100644 --- a/packages/jest-preset/README.md +++ b/packages/jest-preset/README.md @@ -1,5 +1,10 @@ # @react-native/jest-preset +[![npm]](https://www.npmjs.com/package/@react-native/jest-preset) [![npm downloads]](https://www.npmjs.com/package/@react-native/jest-preset) + +[npm]: https://img.shields.io/npm/v/@react-native/jest-preset.svg?color=blue +[npm downloads]: https://img.shields.io/npm/dm/@react-native/jest-preset.svg + Jest preset for [React Native](https://reactnative.dev) apps. ## Usage diff --git a/packages/jest-preset/jest/mocks/NativeModules.js b/packages/jest-preset/jest/mocks/NativeModules.js index a3a358fed66..63bc6f410e4 100644 --- a/packages/jest-preset/jest/mocks/NativeModules.js +++ b/packages/jest-preset/jest/mocks/NativeModules.js @@ -186,15 +186,8 @@ const NativeModules = { }, }, StatusBarManager: { - setColor: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, setStyle: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, setHidden: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, - setNetworkActivityIndicatorVisible: jest.fn() as JestMockFn< - $FlowFixMe, - $FlowFixMe, - >, - setBackgroundColor: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, - setTranslucent: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>, getConstants: (): $FlowFixMe => ({ HEIGHT: 42, }), diff --git a/packages/jest-preset/package.json b/packages/jest-preset/package.json index 268f3cae839..925dcfac00a 100644 --- a/packages/jest-preset/package.json +++ b/packages/jest-preset/package.json @@ -1,6 +1,6 @@ { "name": "@react-native/jest-preset", - "version": "0.86.0-main", + "version": "0.87.0-main", "description": "Jest preset for React Native apps", "keywords": [ "jest", @@ -9,12 +9,12 @@ ], "repository": { "type": "git", - "url": "git+https://github.com/facebook/react-native.git", + "url": "git+https://github.com/react/react-native.git", "directory": "packages/jest-preset" }, "license": "MIT", "engines": { - "node": ">= 20.19.4" + "node": ">= 22.13.0" }, "main": "./jest-preset.js", "files": [ diff --git a/packages/metro-config/README.md b/packages/metro-config/README.md index 9dcb9a75a2f..55c74d53fbb 100644 --- a/packages/metro-config/README.md +++ b/packages/metro-config/README.md @@ -1,21 +1,29 @@ # @react-native/metro-config -[![Version][version-badge]][package] +[![npm]](https://www.npmjs.com/package/@react-native/metro-config) [![npm downloads]](https://www.npmjs.com/package/@react-native/metro-config) -## Installation +[npm]: https://img.shields.io/npm/v/@react-native/metro-config.svg?color=blue +[npm downloads]: https://img.shields.io/npm/dm/@react-native/metro-config.svg -``` -yarn add --dev @react-native/js-polyfills metro-config @react-native/metro-babel-transformer metro-runtime @react-native/metro-config -``` +Metro configuration for React Native. In React Native, your Metro config should extend either `@react-native/metro-config` or `@expo/metro-config`. These packages contain essential defaults necessary to build and run React Native apps. -*Note: We're using `yarn` to install deps. Feel free to change commands to use `npm` 3+ and `npx` if you like* +See [Configuring Metro](https://reactnative.dev/docs/next/metro#configuring-metro) for the full guide. -[version-badge]: https://img.shields.io/npm/v/@react-native/metro-config?style=flat-square -[package]: https://www.npmjs.com/package/@react-native/metro-config +## Usage -## Testing +```js +const { + getDefaultConfig, + mergeConfig, +} = require('@react-native/metro-config'); -To run the tests in this package, run the following commands from the React Native root folder: +/** + * Metro configuration + * https://metrobundler.dev/docs/configuration + * + * @type {import('metro-config').MetroConfig} + */ +const config = {}; -1. `yarn` to install the dependencies. You just need to run this once -2. `yarn jest packages/metro-config`. +module.exports = mergeConfig(getDefaultConfig(__dirname), config); +``` diff --git a/packages/metro-config/package.json b/packages/metro-config/package.json index 3c657af5323..a98e14e6c91 100644 --- a/packages/metro-config/package.json +++ b/packages/metro-config/package.json @@ -1,22 +1,22 @@ { "name": "@react-native/metro-config", - "version": "0.86.0-main", + "version": "0.87.0-main", "description": "Metro configuration for React Native.", "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/facebook/react-native.git", + "url": "git+https://github.com/react/react-native.git", "directory": "packages/metro-config" }, - "homepage": "https://github.com/facebook/react-native/tree/HEAD/packages/metro-config#readme", + "homepage": "https://github.com/react/react-native/tree/HEAD/packages/metro-config#readme", "keywords": [ "metro", "config", "react-native" ], - "bugs": "https://github.com/facebook/react-native/issues", + "bugs": "https://github.com/react/react-native/issues", "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" }, "exports": { ".": "./src/index.js", @@ -37,8 +37,8 @@ "dependencies": { "@react-native/js-polyfills": "workspace:*", "@react-native/metro-babel-transformer": "workspace:*", - "metro-config": "^0.84.3", - "metro-runtime": "^0.84.3" + "metro-config": "^0.86.0", + "metro-runtime": "^0.86.0" }, "private": true } diff --git a/packages/metro-config/src/index.flow.js b/packages/metro-config/src/index.flow.js index 6bba33bd975..03a9aed5ace 100644 --- a/packages/metro-config/src/index.flow.js +++ b/packages/metro-config/src/index.flow.js @@ -84,7 +84,7 @@ export function getDefaultConfig(projectRoot: string): ConfigT { }, transformer: { allowOptionalDependencies: true, - assetRegistryPath: 'react-native/Libraries/Image/AssetRegistry', + assetRegistryPath: 'react-native/asset-registry', asyncRequireModulePath: require.resolve( 'metro-runtime/src/modules/asyncRequire', ), diff --git a/packages/new-app-screen/README.md b/packages/new-app-screen/README.md index 1c727270ecd..eb1d9931ed8 100644 --- a/packages/new-app-screen/README.md +++ b/packages/new-app-screen/README.md @@ -1,6 +1,9 @@ # @react-native/new-app-screen -![npm package](https://img.shields.io/npm/v/@react-native/new-app-screen?color=brightgreen&label=npm%20package) +[![npm]](https://www.npmjs.com/package/@react-native/new-app-screen) [![npm downloads]](https://www.npmjs.com/package/@react-native/new-app-screen) + +[npm]: https://img.shields.io/npm/v/@react-native/new-app-screen.svg?color=blue +[npm downloads]: https://img.shields.io/npm/dm/@react-native/new-app-screen.svg `NewAppScreen` component for React Native. diff --git a/packages/new-app-screen/package.json b/packages/new-app-screen/package.json index cac4e07c5fe..e0fa4762e8b 100644 --- a/packages/new-app-screen/package.json +++ b/packages/new-app-screen/package.json @@ -1,15 +1,15 @@ { "name": "@react-native/new-app-screen", - "version": "0.86.0-main", + "version": "0.87.0-main", "description": "NewAppScreen component for React Native", "keywords": [ "react-native" ], - "homepage": "https://github.com/facebook/react-native/tree/HEAD/packages/new-app-screen#readme", - "bugs": "https://github.com/facebook/react-native/issues", + "homepage": "https://github.com/react/react-native/tree/HEAD/packages/new-app-screen#readme", + "bugs": "https://github.com/react/react-native/issues", "repository": { "type": "git", - "url": "git+https://github.com/facebook/react-native.git", + "url": "git+https://github.com/react/react-native.git", "directory": "packages/new-app-screen" }, "license": "MIT", @@ -29,7 +29,7 @@ } }, "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" }, "private": true } diff --git a/packages/normalize-color/README.md b/packages/normalize-color/README.md index 20b618d930a..2b41c782d33 100644 --- a/packages/normalize-color/README.md +++ b/packages/normalize-color/README.md @@ -1,21 +1,8 @@ # @react-native/normalize-colors -[![Version][version-badge]][package] +[![npm]](https://www.npmjs.com/package/@react-native/normalize-colors) [![npm downloads]](https://www.npmjs.com/package/@react-native/normalize-colors) -## Installation +[npm]: https://img.shields.io/npm/v/@react-native/normalize-colors.svg?color=blue +[npm downloads]: https://img.shields.io/npm/dm/@react-native/normalize-colors.svg -``` -yarn add --dev @react-native/normalize-colors -``` - -*Note: We're using `yarn` to install deps. Feel free to change commands to use `npm` 3+ and `npx` if you like* - -[version-badge]: https://img.shields.io/npm/v/@react-native/normalize-colors?style=flat-square -[package]: https://www.npmjs.com/package/@react-native/normalize-colors - -## Testing - -To run the tests in this package, run the following commands from the React Native root folder: - -1. `yarn` to install the dependencies. You just need to run this once -2. `yarn jest packages/normalize-color`. +Color normalization utility for React Native. Converts CSS color values into the numeric form consumed by the native layer. diff --git a/packages/normalize-color/package.json b/packages/normalize-color/package.json index 7cf3008cb67..78e61f03ac8 100644 --- a/packages/normalize-color/package.json +++ b/packages/normalize-color/package.json @@ -1,21 +1,21 @@ { "name": "@react-native/normalize-colors", - "version": "0.86.0-main", + "version": "0.87.0-main", "description": "Color normalization for React Native.", "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/facebook/react-native.git", + "url": "git+https://github.com/react/react-native.git", "directory": "packages/normalize-color" }, - "homepage": "https://github.com/facebook/react-native/tree/HEAD/packages/normalize-color#readme", + "homepage": "https://github.com/react/react-native/tree/HEAD/packages/normalize-color#readme", "keywords": [ "color", "normalization", "normalize-colors", "react-native" ], - "bugs": "https://github.com/facebook/react-native/issues", + "bugs": "https://github.com/react/react-native/issues", "files": [ "index.js", "README.md", diff --git a/packages/polyfills/README.md b/packages/polyfills/README.md index 91f0b04de01..c701866fb80 100644 --- a/packages/polyfills/README.md +++ b/packages/polyfills/README.md @@ -1,21 +1,10 @@ # @react-native/js-polyfills -[![Version][version-badge]][package] +[![npm]](https://www.npmjs.com/package/@react-native/js-polyfills) [![npm downloads]](https://www.npmjs.com/package/@react-native/js-polyfills) -## Installation +[npm]: https://img.shields.io/npm/v/@react-native/js-polyfills.svg?color=blue +[npm downloads]: https://img.shields.io/npm/dm/@react-native/js-polyfills.svg -``` -yarn add @react-native/js-polyfills -``` +> This is an internal dependency of React Native. **Please don't depend on it directly.** -*Note: We're using `yarn` to install deps. Feel free to change commands to use `npm` 3+ and `npx` if you like* - -[version-badge]: https://img.shields.io/npm/v/@react-native/js-polyfills?style=flat-square -[package]: https://www.npmjs.com/package/@react-native/js-polyfills - -## Testing - -To run the tests in this package, run the following commands from the React Native root folder: - -1. `yarn` to install the dependencies. You just need to run this once -2. `yarn jest packages/polyfills`. +JavaScript environment polyfills set up by React Native at startup (e.g. `Promise`, timers, and other runtime globals). diff --git a/packages/polyfills/package.json b/packages/polyfills/package.json index f2c5a596100..72f0f872cef 100644 --- a/packages/polyfills/package.json +++ b/packages/polyfills/package.json @@ -1,14 +1,14 @@ { "name": "@react-native/js-polyfills", - "version": "0.86.0-main", + "version": "0.87.0-main", "description": "Polyfills for React Native.", "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/facebook/react-native.git", + "url": "git+https://github.com/react/react-native.git", "directory": "packages/polyfills" }, - "homepage": "https://github.com/facebook/react-native/tree/HEAD/packages/polyfills#readme", + "homepage": "https://github.com/react/react-native/tree/HEAD/packages/polyfills#readme", "keywords": [ "polyfill", "polyfills", @@ -16,9 +16,9 @@ "js-polyfills", "react-native" ], - "bugs": "https://github.com/facebook/react-native/issues", + "bugs": "https://github.com/react/react-native/issues", "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" }, "files": [ "console.js", diff --git a/packages/react-native-babel-preset/README.md b/packages/react-native-babel-preset/README.md index a29c400c093..232982f0a4b 100644 --- a/packages/react-native-babel-preset/README.md +++ b/packages/react-native-babel-preset/README.md @@ -1,12 +1,15 @@ # @react-native/babel-preset -Babel presets for [React Native](https://reactnative.dev) applications. React Native itself uses this Babel preset by default when transforming your app's source code. +[![npm]](https://www.npmjs.com/package/@react-native/babel-preset) [![npm downloads]](https://www.npmjs.com/package/@react-native/babel-preset) -If you wish to use a custom Babel configuration by writing a `babel.config.js` file in your project's root directory, you must specify all the plugins necessary to transform your code. React Native does not apply its default Babel configuration in this case. So, to make your life easier, you can use this preset to get the default configuration and then specify more plugins that run before it. +[npm]: https://img.shields.io/npm/v/@react-native/babel-preset.svg?color=blue +[npm downloads]: https://img.shields.io/npm/dm/@react-native/babel-preset.svg -## Usage +Babel preset for [React Native](https://reactnative.dev) applications. React Native uses this Babel preset by default when transforming your app's source code. + +You only need to use this preset directly if you provide a custom `babel.config.js` file in your project's root directory. React Native does not apply its default Babel configuration in that case, so you must specify all the plugins necessary to transform your code — start from this preset to get the defaults and add more plugins on top. -As mentioned above, you only need to use this preset if you are writing a custom `babel.config.js` file. +## Usage ### Installation @@ -26,16 +29,12 @@ yarn add -D @react-native/babel-preset ### Configuring Babel -Then, create a file called `babel.config.js` in your project's root directory. The existence of this `babel.config.js` file will tell React Native to use your custom Babel configuration instead of its own. Then load this preset: +Then, create a file called `babel.config.js` in your project's root directory. The existence of this `babel.config.js` file tells React Native to use your custom Babel configuration instead of its own. Then load this preset: -``` +```json { "presets": ["module:@react-native/babel-preset"] } ``` You can further customize your Babel configuration by specifying plugins and other options. See [Babel's `babel.config.js` documentation](https://babeljs.io/docs/en/config-files/) to learn more. - -## Help and Support - -If you get stuck configuring Babel, please ask a question on Stack Overflow or find a consultant for help. If you discover a bug, please open up an issue. diff --git a/packages/react-native-babel-preset/package.json b/packages/react-native-babel-preset/package.json index 8ac949b58fa..61fe41c6a42 100644 --- a/packages/react-native-babel-preset/package.json +++ b/packages/react-native-babel-preset/package.json @@ -1,10 +1,11 @@ { "name": "@react-native/babel-preset", - "version": "0.86.0-main", + "version": "0.87.0-main", "description": "Babel preset for React Native applications", "repository": { "type": "git", - "url": "git+ssh://git@github.com/facebook/react-native.git" + "url": "git+https://github.com/react/react-native.git", + "directory": "packages/react-native-babel-preset" }, "keywords": [ "babel", @@ -13,7 +14,7 @@ ], "license": "MIT", "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" }, "main": "src/index.js", "files": [ @@ -31,19 +32,27 @@ "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-transform-arrow-functions": "^7.24.7", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-classes": "^7.25.4", + "@babel/plugin-transform-computed-properties": "^7.24.7", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-function-name": "^7.25.1", + "@babel/plugin-transform-literals": "^7.25.2", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-numeric-separator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", @@ -52,24 +61,16 @@ "@babel/plugin-transform-react-jsx-source": "^7.24.7", "@babel/plugin-transform-regenerator": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", + "@babel/plugin-transform-shorthand-properties": "^7.24.7", + "@babel/plugin-transform-spread": "^7.24.7", + "@babel/plugin-transform-sticky-regex": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/template": "^7.25.0", "@react-native/babel-plugin-codegen": "workspace:*", - "babel-plugin-syntax-hermes-parser": "0.36.0", + "babel-plugin-syntax-hermes-parser": "0.36.1", "babel-plugin-transform-flow-enums": "^0.0.2", - "react-refresh": "^0.14.0", - "@babel/plugin-transform-arrow-functions": "^7.24.7", - "@babel/plugin-transform-computed-properties": "^7.24.7", - "@babel/plugin-transform-function-name": "^7.25.1", - "@babel/plugin-transform-literals": "^7.25.2", - "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", - "@babel/plugin-transform-numeric-separator": "^7.24.7", - "@babel/plugin-transform-object-rest-spread": "^7.24.7", - "@babel/plugin-transform-parameters": "^7.24.7", - "@babel/plugin-transform-shorthand-properties": "^7.24.7", - "@babel/plugin-transform-spread": "^7.24.7", - "@babel/plugin-transform-sticky-regex": "^7.24.7", - "@babel/template": "^7.25.0" + "react-refresh": "^0.14.0" }, "peerDependencies": { "@babel/core": "*" diff --git a/packages/react-native-babel-preset/src/__tests__/__fixtures__/output/default-dev.js b/packages/react-native-babel-preset/src/__tests__/__fixtures__/output/hermes-legacy-dev.js similarity index 99% rename from packages/react-native-babel-preset/src/__tests__/__fixtures__/output/default-dev.js rename to packages/react-native-babel-preset/src/__tests__/__fixtures__/output/hermes-legacy-dev.js index fd6db645265..3bfeb2c95ec 100644 --- a/packages/react-native-babel-preset/src/__tests__/__fixtures__/output/default-dev.js +++ b/packages/react-native-babel-preset/src/__tests__/__fixtures__/output/hermes-legacy-dev.js @@ -14,7 +14,7 @@ * * Transform configuration: * - Default transform profile in development mode - * - Options: {"dev":true} + * - Options: {"dev":true,"unstable_transformProfile":"hermes-legacy"} */ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); diff --git a/packages/react-native-babel-preset/src/__tests__/__fixtures__/output/default-prod.js b/packages/react-native-babel-preset/src/__tests__/__fixtures__/output/hermes-legacy-prod.js similarity index 99% rename from packages/react-native-babel-preset/src/__tests__/__fixtures__/output/default-prod.js rename to packages/react-native-babel-preset/src/__tests__/__fixtures__/output/hermes-legacy-prod.js index 190d8969be8..9ced08fe256 100644 --- a/packages/react-native-babel-preset/src/__tests__/__fixtures__/output/default-prod.js +++ b/packages/react-native-babel-preset/src/__tests__/__fixtures__/output/hermes-legacy-prod.js @@ -14,7 +14,7 @@ * * Transform configuration: * - Default transform profile in production mode - * - Options: {"dev":false} + * - Options: {"dev":false,"unstable_transformProfile":"hermes-legacy"} */ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); diff --git a/packages/react-native-babel-preset/src/__tests__/__fixtures__/output/hermes-stable-dev-preserve-block-scoping.js b/packages/react-native-babel-preset/src/__tests__/__fixtures__/output/hermes-stable-dev-preserve-block-scoping.js new file mode 100644 index 00000000000..5d27ff0833e --- /dev/null +++ b/packages/react-native-babel-preset/src/__tests__/__fixtures__/output/hermes-stable-dev-preserve-block-scoping.js @@ -0,0 +1,284 @@ +/** + * 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. + * + * @generated + * @noformat + * @noflow + * @nolint + * + * This is a snapshot of the transform output for testing purposes. + * To update, run: js1 test transform-snapshot-test.js -u + * + * Transform configuration: + * - Hermes stable transform profile in development mode with unstable_preserveBlockScoping enabled + * - Options: {"dev":true,"unstable_transformProfile":"hermes-stable","customTransformOptions":{"unstable_preserveBlockScoping":true}} + */ + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.LegacyComponent = exports.Dog = exports.Counter = exports.Animal = void 0; +exports.ModernComponent = ModernComponent; +exports.MyClass = void 0; +exports.asyncNumberGenerator = asyncNumberGenerator; +Object.defineProperty(exports, "default", { + enumerable: true, + get: function () { + return _dataUtils.fetchData; + } +}); +exports.getNestedValue = getNestedValue; +exports.loadModule = loadModule; +exports.matchEmoji = matchEmoji; +exports.mergeConfigs = mergeConfigs; +exports.parseDate = parseDate; +exports.processUser = processUser; +exports.safeJsonParse = safeJsonParse; +exports.sumPairs = sumPairs; +var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits")); +var _setPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/setPrototypeOf")); +var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray")); +var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); +var _classPrivateFieldLooseBase2 = _interopRequireDefault(require("@babel/runtime/helpers/classPrivateFieldLooseBase")); +var _classPrivateFieldLooseKey2 = _interopRequireDefault(require("@babel/runtime/helpers/classPrivateFieldLooseKey")); +var _awaitAsyncGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/awaitAsyncGenerator")); +var _wrapAsyncGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/wrapAsyncGenerator")); +var _react = _interopRequireWildcard(require("react")); +var React = _react; +var _jsxRuntime = require("react/jsx-runtime"); +var _dataUtils = require("./data-utils"); +var _jsxFileName = "/absolute/path/to/input.js"; +function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } +function _wrapRegExp() { _wrapRegExp = function (e, r) { return new BabelRegExp(e, void 0, r); }; var e = RegExp.prototype, r = new WeakMap(); function BabelRegExp(e, t, p) { var o = RegExp(e, t); return r.set(o, p || r.get(e)), (0, _setPrototypeOf2.default)(o, BabelRegExp.prototype); } function buildGroups(e, t) { var p = r.get(t); return Object.keys(p).reduce(function (r, t) { var o = p[t]; if ("number" == typeof o) r[t] = e[o];else { for (var i = 0; void 0 === e[o[i]] && i + 1 < o.length;) i++; r[t] = e[o[i]]; } return r; }, Object.create(null)); } return (0, _inherits2.default)(BabelRegExp, RegExp), BabelRegExp.prototype.exec = function (r) { var t = e.exec.call(this, r); if (t) { t.groups = buildGroups(t, this); var p = t.indices; p && (p.groups = buildGroups(p, this)); } return t; }, BabelRegExp.prototype[Symbol.replace] = function (t, p) { if ("string" == typeof p) { var o = r.get(this); return e[Symbol.replace].call(this, t, p.replace(/\$<([^>]+)(>|$)/g, function (e, r, t) { if ("" === t) return e; var p = o[r]; return Array.isArray(p) ? "$" + p.join("$") : "number" == typeof p ? "$" + p : ""; })); } if ("function" == typeof p) { var i = this; return e[Symbol.replace].call(this, t, function () { var e = arguments; return "object" != typeof e[e.length - 1] && (e = [].slice.call(e)).push(buildGroups(e, i)), p.apply(this, e); }); } return e[Symbol.replace].call(this, t, p); }, _wrapRegExp.apply(this, arguments); } +function _createForOfIteratorHelperLoose(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (t) return (t = t.call(r)).next.bind(t); if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var o = 0; return function () { return o >= r.length ? { done: !0 } : { done: !1, value: r[o++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } +function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } +function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } +var _count = (0, _classPrivateFieldLooseKey2.default)("count"); +var _instances = (0, _classPrivateFieldLooseKey2.default)("instances"); +var _increment = (0, _classPrivateFieldLooseKey2.default)("increment"); +class Counter { + constructor() { + Object.defineProperty(this, _increment, { + value: _increment2 + }); + Object.defineProperty(this, _count, { + writable: true, + value: 0 + }); + (0, _classPrivateFieldLooseBase2.default)(Counter, _instances)[_instances]++; + } + get value() { + return (0, _classPrivateFieldLooseBase2.default)(this, _count)[_count]; + } + increment() { + (0, _classPrivateFieldLooseBase2.default)(this, _increment)[_increment](); + } + static get instanceCount() { + return (0, _classPrivateFieldLooseBase2.default)(Counter, _instances)[_instances]; + } +} +exports.Counter = Counter; +function _increment2() { + (0, _classPrivateFieldLooseBase2.default)(this, _count)[_count]++; +} +Object.defineProperty(Counter, _instances, { + writable: true, + value: 0 +}); +function asyncNumberGenerator(_x) { + return _asyncNumberGenerator.apply(this, arguments); +} +function _asyncNumberGenerator() { + _asyncNumberGenerator = (0, _wrapAsyncGenerator2.default)(function* (max) { + for (let i = 0; i < max; i++) { + yield (0, _awaitAsyncGenerator2.default)(new Promise(resolve => setTimeout(resolve, 100))); + yield i; + } + }); + return _asyncNumberGenerator.apply(this, arguments); +} +function fetchData(_x2) { + return _fetchData.apply(this, arguments); +} +function _fetchData() { + _fetchData = (0, _asyncToGenerator2.default)(function* (url) { + const response = yield fetch(url); + const data = yield response.json(); + return { + data + }; + }); + return _fetchData.apply(this, arguments); +} +function getNestedValue(obj) { + var _obj$a$b$c, _obj$a; + return (_obj$a$b$c = obj == null || (_obj$a = obj.a) == null || (_obj$a = _obj$a.b) == null ? void 0 : _obj$a.c) != null ? _obj$a$b$c : 42; +} +class Animal { + #age; + constructor(name, age) { + this.name = name; + this.#age = age; + } + speak() { + return `${this.name} makes a sound`; + } + get age() { + return this.#age; + } +} +exports.Animal = Animal; +class Dog extends Animal { + constructor(name, age, breed) { + super(name, age); + this.breed = breed; + } + speak() { + return `${this.name} barks!`; + } + fetchTreats() { + return (0, _asyncToGenerator2.default)(function* () { + yield new Promise(resolve => setTimeout(resolve, 100)); + return ['bone', 'biscuit', 'toy']; + })(); + } +} +exports.Dog = Dog; +function processUser({ + name, + age = 18, + ...rest +}) { + const _rest$city = rest.city, + city = _rest$city === void 0 ? 'Unknown' : _rest$city; + return `${name} (${age}) from ${city}`; +} +function mergeConfigs(base, ...overrides) { + return { + ...base, + ...overrides.reduce((acc, o) => ({ + ...acc, + ...o + }), {}) + }; +} +function sumPairs(pairs) { + let total = 0; + for (var _iterator = _createForOfIteratorHelperLoose(pairs), _step; !(_step = _iterator()).done;) { + const _ref = _step.value; + var _ref2 = (0, _slicedToArray2.default)(_ref, 2); + const a = _ref2[0]; + const b = _ref2[1]; + total += a + b; + } + return total; +} +function parseDate(dateString) { + const regex = _wrapRegExp(/(\d{4})-(\d{2})-(\d{2})/, { + year: 1, + month: 2, + day: 3 + }); + const match = dateString.match(regex); + if (match != null && match.groups) { + return { + year: match.groups.year, + month: match.groups.month, + day: match.groups.day + }; + } + return null; +} +function safeJsonParse(input) { + try { + return JSON.parse(input); + } catch (_unused) { + return null; + } +} +function matchEmoji(text) { + const match = text.match(/(?:[#\*0-9\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26A7\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC04\uDCCF\uDD70\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE02\uDE1A\uDE2F\uDE32-\uDE3A\uDE50\uDE51\uDF00-\uDF21\uDF24-\uDF93\uDF96\uDF97\uDF99-\uDF9B\uDF9E-\uDFF0\uDFF3-\uDFF5\uDFF7-\uDFFF]|\uD83D[\uDC00-\uDCFD\uDCFF-\uDD3D\uDD49-\uDD4E\uDD50-\uDD67\uDD6F\uDD70\uDD73-\uDD7A\uDD87\uDD8A-\uDD8D\uDD90\uDD95\uDD96\uDDA4\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA-\uDE4F\uDE80-\uDEC5\uDECB-\uDED2\uDED5-\uDED7\uDEDC-\uDEE5\uDEE9\uDEEB\uDEEC\uDEF0\uDEF3-\uDEFC\uDFE0-\uDFEB\uDFF0]|\uD83E[\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDDFF\uDE70-\uDE7C\uDE80-\uDE89\uDE8F-\uDEC6\uDECE-\uDEDC\uDEDF-\uDEE9\uDEF0-\uDEF8])/); + return match == null ? void 0 : match[0]; +} +const MyClass = class { + constructor(value) { + this.value = value; + } +}; +exports.MyClass = MyClass; +function loadModule() { + return _loadModule.apply(this, arguments); +} +function _loadModule() { + _loadModule = (0, _asyncToGenerator2.default)(function* () { + const module = yield import('./some-module'); + return module.default; + }); + return _loadModule.apply(this, arguments); +} +const LegacyComponent = exports.LegacyComponent = React.createClass({ + displayName: 'LegacyComponent', + getInitialState() { + return { + count: 0 + }; + }, + render() { + return (0, _jsxRuntime.jsx)("div", { + children: this.state.count + }); + } +}); +function ModernComponent({ + initialCount = 0 +}) { + const _useState = (0, _react.useState)(initialCount), + _useState2 = (0, _slicedToArray2.default)(_useState, 2), + count = _useState2[0], + setCount = _useState2[1]; + const _useState3 = (0, _react.useState)(Status.Active), + _useState4 = (0, _slicedToArray2.default)(_useState3, 2), + status = _useState4[0], + setStatus = _useState4[1]; + (0, _react.useEffect)(() => { + const timer = setInterval(() => { + setCount(c => c + 1); + }, 1000); + return () => clearInterval(timer); + }, []); + function handleAsyncClick() { + return _handleAsyncClick.apply(this, arguments); + } + function _handleAsyncClick() { + _handleAsyncClick = (0, _asyncToGenerator2.default)(function* () { + const data = yield fetchData('/api/data'); + console.log(data); + }); + return _handleAsyncClick.apply(this, arguments); + } + const handleClick = function () { + var _ref3 = (0, _asyncToGenerator2.default)(function* () { + yield handleAsyncClick(); + setStatus(Status.Pending); + }); + return function handleClick() { + return _ref3.apply(this, arguments); + }; + }(); + return (0, _jsxRuntime.jsxs)("div", { + children: [(0, _jsxRuntime.jsx)("span", { + "data-testid": "count", + children: count + }), (0, _jsxRuntime.jsx)("span", { + "data-testid": "status", + children: String(status) + }), (0, _jsxRuntime.jsx)("button", { + onClick: handleClick, + children: "Increment" + }), (0, _jsxRuntime.jsx)(LegacyComponent, {})] + }); +} \ No newline at end of file diff --git a/packages/react-native-babel-preset/src/__tests__/__fixtures__/output/hermes-stable-dev-preserve-destructuring.js b/packages/react-native-babel-preset/src/__tests__/__fixtures__/output/hermes-stable-dev-preserve-destructuring.js new file mode 100644 index 00000000000..fdcccf4f987 --- /dev/null +++ b/packages/react-native-babel-preset/src/__tests__/__fixtures__/output/hermes-stable-dev-preserve-destructuring.js @@ -0,0 +1,275 @@ +/** + * 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. + * + * @generated + * @noformat + * @noflow + * @nolint + * + * This is a snapshot of the transform output for testing purposes. + * To update, run: js1 test transform-snapshot-test.js -u + * + * Transform configuration: + * - Hermes stable transform profile in development mode with unstable_preserveDestructuring enabled + * - Options: {"dev":true,"unstable_transformProfile":"hermes-stable","customTransformOptions":{"unstable_preserveDestructuring":true}} + */ + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.LegacyComponent = exports.Dog = exports.Counter = exports.Animal = void 0; +exports.ModernComponent = ModernComponent; +exports.MyClass = void 0; +exports.asyncNumberGenerator = asyncNumberGenerator; +Object.defineProperty(exports, "default", { + enumerable: true, + get: function () { + return _dataUtils.fetchData; + } +}); +exports.getNestedValue = getNestedValue; +exports.loadModule = loadModule; +exports.matchEmoji = matchEmoji; +exports.mergeConfigs = mergeConfigs; +exports.parseDate = parseDate; +exports.processUser = processUser; +exports.safeJsonParse = safeJsonParse; +exports.sumPairs = sumPairs; +var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits")); +var _setPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/setPrototypeOf")); +var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); +var _classPrivateFieldLooseBase2 = _interopRequireDefault(require("@babel/runtime/helpers/classPrivateFieldLooseBase")); +var _classPrivateFieldLooseKey2 = _interopRequireDefault(require("@babel/runtime/helpers/classPrivateFieldLooseKey")); +var _awaitAsyncGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/awaitAsyncGenerator")); +var _wrapAsyncGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/wrapAsyncGenerator")); +var _react = _interopRequireWildcard(require("react")); +var React = _react; +var _jsxRuntime = require("react/jsx-runtime"); +var _dataUtils = require("./data-utils"); +var _jsxFileName = "/absolute/path/to/input.js"; +function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); } +function _wrapRegExp() { _wrapRegExp = function (e, r) { return new BabelRegExp(e, void 0, r); }; var e = RegExp.prototype, r = new WeakMap(); function BabelRegExp(e, t, p) { var o = RegExp(e, t); return r.set(o, p || r.get(e)), (0, _setPrototypeOf2.default)(o, BabelRegExp.prototype); } function buildGroups(e, t) { var p = r.get(t); return Object.keys(p).reduce(function (r, t) { var o = p[t]; if ("number" == typeof o) r[t] = e[o];else { for (var i = 0; void 0 === e[o[i]] && i + 1 < o.length;) i++; r[t] = e[o[i]]; } return r; }, Object.create(null)); } return (0, _inherits2.default)(BabelRegExp, RegExp), BabelRegExp.prototype.exec = function (r) { var t = e.exec.call(this, r); if (t) { t.groups = buildGroups(t, this); var p = t.indices; p && (p.groups = buildGroups(p, this)); } return t; }, BabelRegExp.prototype[Symbol.replace] = function (t, p) { if ("string" == typeof p) { var o = r.get(this); return e[Symbol.replace].call(this, t, p.replace(/\$<([^>]+)(>|$)/g, function (e, r, t) { if ("" === t) return e; var p = o[r]; return Array.isArray(p) ? "$" + p.join("$") : "number" == typeof p ? "$" + p : ""; })); } if ("function" == typeof p) { var i = this; return e[Symbol.replace].call(this, t, function () { var e = arguments; return "object" != typeof e[e.length - 1] && (e = [].slice.call(e)).push(buildGroups(e, i)), p.apply(this, e); }); } return e[Symbol.replace].call(this, t, p); }, _wrapRegExp.apply(this, arguments); } +function _createForOfIteratorHelperLoose(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (t) return (t = t.call(r)).next.bind(t); if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var o = 0; return function () { return o >= r.length ? { done: !0 } : { done: !1, value: r[o++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } +function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } +function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } +var _count = (0, _classPrivateFieldLooseKey2.default)("count"); +var _instances = (0, _classPrivateFieldLooseKey2.default)("instances"); +var _increment = (0, _classPrivateFieldLooseKey2.default)("increment"); +class Counter { + constructor() { + Object.defineProperty(this, _increment, { + value: _increment2 + }); + Object.defineProperty(this, _count, { + writable: true, + value: 0 + }); + (0, _classPrivateFieldLooseBase2.default)(Counter, _instances)[_instances]++; + } + get value() { + return (0, _classPrivateFieldLooseBase2.default)(this, _count)[_count]; + } + increment() { + (0, _classPrivateFieldLooseBase2.default)(this, _increment)[_increment](); + } + static get instanceCount() { + return (0, _classPrivateFieldLooseBase2.default)(Counter, _instances)[_instances]; + } +} +exports.Counter = Counter; +function _increment2() { + (0, _classPrivateFieldLooseBase2.default)(this, _count)[_count]++; +} +Object.defineProperty(Counter, _instances, { + writable: true, + value: 0 +}); +function asyncNumberGenerator(_x) { + return _asyncNumberGenerator.apply(this, arguments); +} +function _asyncNumberGenerator() { + _asyncNumberGenerator = (0, _wrapAsyncGenerator2.default)(function* (max) { + for (var i = 0; i < max; i++) { + yield (0, _awaitAsyncGenerator2.default)(new Promise(resolve => setTimeout(resolve, 100))); + yield i; + } + }); + return _asyncNumberGenerator.apply(this, arguments); +} +function fetchData(_x2) { + return _fetchData.apply(this, arguments); +} +function _fetchData() { + _fetchData = (0, _asyncToGenerator2.default)(function* (url) { + var response = yield fetch(url); + var data = yield response.json(); + return { + data + }; + }); + return _fetchData.apply(this, arguments); +} +function getNestedValue(obj) { + var _obj$a$b$c, _obj$a; + return (_obj$a$b$c = obj == null || (_obj$a = obj.a) == null || (_obj$a = _obj$a.b) == null ? void 0 : _obj$a.c) != null ? _obj$a$b$c : 42; +} +class Animal { + #age; + constructor(name, age) { + this.name = name; + this.#age = age; + } + speak() { + return `${this.name} makes a sound`; + } + get age() { + return this.#age; + } +} +exports.Animal = Animal; +class Dog extends Animal { + constructor(name, age, breed) { + super(name, age); + this.breed = breed; + } + speak() { + return `${this.name} barks!`; + } + fetchTreats() { + return (0, _asyncToGenerator2.default)(function* () { + yield new Promise(resolve => setTimeout(resolve, 100)); + return ['bone', 'biscuit', 'toy']; + })(); + } +} +exports.Dog = Dog; +function processUser({ + name, + age = 18, + ...rest +}) { + var { + city = 'Unknown' + } = rest; + return `${name} (${age}) from ${city}`; +} +function mergeConfigs(base, ...overrides) { + return { + ...base, + ...overrides.reduce((acc, o) => ({ + ...acc, + ...o + }), {}) + }; +} +function sumPairs(pairs) { + var total = 0; + for (var _iterator = _createForOfIteratorHelperLoose(pairs), _step; !(_step = _iterator()).done;) { + var [a, b] = _step.value; + total += a + b; + } + return total; +} +function parseDate(dateString) { + var regex = _wrapRegExp(/(\d{4})-(\d{2})-(\d{2})/, { + year: 1, + month: 2, + day: 3 + }); + var match = dateString.match(regex); + if (match != null && match.groups) { + return { + year: match.groups.year, + month: match.groups.month, + day: match.groups.day + }; + } + return null; +} +function safeJsonParse(input) { + try { + return JSON.parse(input); + } catch (_unused) { + return null; + } +} +function matchEmoji(text) { + var match = text.match(/(?:[#\*0-9\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26A7\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC04\uDCCF\uDD70\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE02\uDE1A\uDE2F\uDE32-\uDE3A\uDE50\uDE51\uDF00-\uDF21\uDF24-\uDF93\uDF96\uDF97\uDF99-\uDF9B\uDF9E-\uDFF0\uDFF3-\uDFF5\uDFF7-\uDFFF]|\uD83D[\uDC00-\uDCFD\uDCFF-\uDD3D\uDD49-\uDD4E\uDD50-\uDD67\uDD6F\uDD70\uDD73-\uDD7A\uDD87\uDD8A-\uDD8D\uDD90\uDD95\uDD96\uDDA4\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA-\uDE4F\uDE80-\uDEC5\uDECB-\uDED2\uDED5-\uDED7\uDEDC-\uDEE5\uDEE9\uDEEB\uDEEC\uDEF0\uDEF3-\uDEFC\uDFE0-\uDFEB\uDFF0]|\uD83E[\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDDFF\uDE70-\uDE7C\uDE80-\uDE89\uDE8F-\uDEC6\uDECE-\uDEDC\uDEDF-\uDEE9\uDEF0-\uDEF8])/); + return match == null ? void 0 : match[0]; +} +var MyClass = class { + constructor(value) { + this.value = value; + } +}; +exports.MyClass = MyClass; +function loadModule() { + return _loadModule.apply(this, arguments); +} +function _loadModule() { + _loadModule = (0, _asyncToGenerator2.default)(function* () { + var module = yield import('./some-module'); + return module.default; + }); + return _loadModule.apply(this, arguments); +} +var LegacyComponent = exports.LegacyComponent = React.createClass({ + displayName: 'LegacyComponent', + getInitialState() { + return { + count: 0 + }; + }, + render() { + return (0, _jsxRuntime.jsx)("div", { + children: this.state.count + }); + } +}); +function ModernComponent({ + initialCount = 0 +}) { + var [count, setCount] = (0, _react.useState)(initialCount); + var [status, setStatus] = (0, _react.useState)(Status.Active); + (0, _react.useEffect)(() => { + var timer = setInterval(() => { + setCount(c => c + 1); + }, 1000); + return () => clearInterval(timer); + }, []); + function handleAsyncClick() { + return _handleAsyncClick.apply(this, arguments); + } + function _handleAsyncClick() { + _handleAsyncClick = (0, _asyncToGenerator2.default)(function* () { + var data = yield fetchData('/api/data'); + console.log(data); + }); + return _handleAsyncClick.apply(this, arguments); + } + var handleClick = function () { + var _ref = (0, _asyncToGenerator2.default)(function* () { + yield handleAsyncClick(); + setStatus(Status.Pending); + }); + return function handleClick() { + return _ref.apply(this, arguments); + }; + }(); + return (0, _jsxRuntime.jsxs)("div", { + children: [(0, _jsxRuntime.jsx)("span", { + "data-testid": "count", + children: count + }), (0, _jsxRuntime.jsx)("span", { + "data-testid": "status", + children: String(status) + }), (0, _jsxRuntime.jsx)("button", { + onClick: handleClick, + children: "Increment" + }), (0, _jsxRuntime.jsx)(LegacyComponent, {})] + }); +} \ No newline at end of file diff --git a/packages/react-native-babel-preset/src/__tests__/__fixtures__/output/hermes-stable-prod.js b/packages/react-native-babel-preset/src/__tests__/__fixtures__/output/hermes-stable-prod.js index 383e6ed5c83..45e02c09b8e 100644 --- a/packages/react-native-babel-preset/src/__tests__/__fixtures__/output/hermes-stable-prod.js +++ b/packages/react-native-babel-preset/src/__tests__/__fixtures__/output/hermes-stable-prod.js @@ -14,7 +14,7 @@ * * Transform configuration: * - Hermes stable transform profile in production mode - * - Options: {"dev":false,"unstable_transformProfile":"hermes-stable"} + * - Options: {"dev":false} */ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); diff --git a/packages/react-native-babel-preset/src/__tests__/__fixtures__/output/no-babel-runtime.js b/packages/react-native-babel-preset/src/__tests__/__fixtures__/output/no-babel-runtime.js index 48133ecf77d..33c53cca250 100644 --- a/packages/react-native-babel-preset/src/__tests__/__fixtures__/output/no-babel-runtime.js +++ b/packages/react-native-babel-preset/src/__tests__/__fixtures__/output/no-babel-runtime.js @@ -44,26 +44,16 @@ var _jsxRuntime = require("react/jsx-runtime"); var _dataUtils = require("./data-utils"); function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); } function _wrapRegExp() { _wrapRegExp = function (e, r) { return new BabelRegExp(e, void 0, r); }; var e = RegExp.prototype, r = new WeakMap(); function BabelRegExp(e, t, p) { var o = RegExp(e, t); return r.set(o, p || r.get(e)), _setPrototypeOf(o, BabelRegExp.prototype); } function buildGroups(e, t) { var p = r.get(t); return Object.keys(p).reduce(function (r, t) { var o = p[t]; if ("number" == typeof o) r[t] = e[o];else { for (var i = 0; void 0 === e[o[i]] && i + 1 < o.length;) i++; r[t] = e[o[i]]; } return r; }, Object.create(null)); } return _inherits(BabelRegExp, RegExp), BabelRegExp.prototype.exec = function (r) { var t = e.exec.call(this, r); if (t) { t.groups = buildGroups(t, this); var p = t.indices; p && (p.groups = buildGroups(p, this)); } return t; }, BabelRegExp.prototype[Symbol.replace] = function (t, p) { if ("string" == typeof p) { var o = r.get(this); return e[Symbol.replace].call(this, t, p.replace(/\$<([^>]+)(>|$)/g, function (e, r, t) { if ("" === t) return e; var p = o[r]; return Array.isArray(p) ? "$" + p.join("$") : "number" == typeof p ? "$" + p : ""; })); } if ("function" == typeof p) { var i = this; return e[Symbol.replace].call(this, t, function () { var e = arguments; return "object" != typeof e[e.length - 1] && (e = [].slice.call(e)).push(buildGroups(e, i)), p.apply(this, e); }); } return e[Symbol.replace].call(this, t, p); }, _wrapRegExp.apply(this, arguments); } +function _inherits(t, e) { if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: !0, configurable: !0 } }), Object.defineProperty(t, "prototype", { writable: !1 }), e && _setPrototypeOf(t, e); } +function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); } function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } } function _arrayWithHoles(r) { if (Array.isArray(r)) return r; } -function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); } -function _possibleConstructorReturn(t, e) { if (e && ("object" == typeof e || "function" == typeof e)) return e; if (void 0 !== e) throw new TypeError("Derived constructors may only return object or undefined"); return _assertThisInitialized(t); } -function _assertThisInitialized(e) { if (void 0 === e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); return e; } -function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function () { return !!t; })(); } -function _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) { return t.__proto__ || Object.getPrototypeOf(t); }, _getPrototypeOf(t); } -function _inherits(t, e) { if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: !0, configurable: !0 } }), Object.defineProperty(t, "prototype", { writable: !1 }), e && _setPrototypeOf(t, e); } -function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); } function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); } function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; } -function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } -function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } -function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } -function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } -function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function _classPrivateFieldLooseBase(e, t) { if (!{}.hasOwnProperty.call(e, t)) throw new TypeError("attempted to use private field on non-instance"); return e; } var id = 0; function _classPrivateFieldLooseKey(e) { return "__private_" + id++ + "_" + e; } @@ -75,9 +65,8 @@ function _OverloadYield(e, d) { this.v = e, this.k = d; } var _count = _classPrivateFieldLooseKey("count"); var _instances = _classPrivateFieldLooseKey("instances"); var _increment = _classPrivateFieldLooseKey("increment"); -var Counter = exports.Counter = function () { - function Counter() { - _classCallCheck(this, Counter); +class Counter { + constructor() { Object.defineProperty(this, _increment, { value: _increment2 }); @@ -87,23 +76,17 @@ var Counter = exports.Counter = function () { }); _classPrivateFieldLooseBase(Counter, _instances)[_instances]++; } - return _createClass(Counter, [{ - key: "value", - get: function () { - return _classPrivateFieldLooseBase(this, _count)[_count]; - } - }, { - key: "increment", - value: function increment() { - _classPrivateFieldLooseBase(this, _increment)[_increment](); - } - }], [{ - key: "instanceCount", - get: function () { - return _classPrivateFieldLooseBase(Counter, _instances)[_instances]; - } - }]); -}(); + get value() { + return _classPrivateFieldLooseBase(this, _count)[_count]; + } + increment() { + _classPrivateFieldLooseBase(this, _increment)[_increment](); + } + static get instanceCount() { + return _classPrivateFieldLooseBase(Counter, _instances)[_instances]; + } +} +exports.Counter = Counter; function _increment2() { _classPrivateFieldLooseBase(this, _count)[_count]++; } @@ -139,57 +122,36 @@ function _fetchData() { function getNestedValue(obj) { return obj?.a?.b?.c ?? 42; } -var _age = _classPrivateFieldLooseKey("age"); -var Animal = exports.Animal = function () { - function Animal(name, age) { - _classCallCheck(this, Animal); - Object.defineProperty(this, _age, { - writable: true, - value: void 0 - }); +class Animal { + #age; + constructor(name, age) { this.name = name; - _classPrivateFieldLooseBase(this, _age)[_age] = age; + this.#age = age; } - return _createClass(Animal, [{ - key: "speak", - value: function speak() { - return `${this.name} makes a sound`; - } - }, { - key: "age", - get: function () { - return _classPrivateFieldLooseBase(this, _age)[_age]; - } - }]); -}(); -var Dog = exports.Dog = function (_Animal2) { - function Dog(name, age, breed) { - var _this; - _classCallCheck(this, Dog); - _this = _callSuper(this, Dog, [name, age]); - _this.breed = breed; - return _this; + speak() { + return `${this.name} makes a sound`; } - _inherits(Dog, _Animal2); - return _createClass(Dog, [{ - key: "speak", - value: function speak() { - return `${this.name} barks!`; - } - }, { - key: "fetchTreats", - value: function () { - var _fetchTreats = _asyncToGenerator(function* () { - yield new Promise(resolve => setTimeout(resolve, 100)); - return ['bone', 'biscuit', 'toy']; - }); - function fetchTreats() { - return _fetchTreats.apply(this, arguments); - } - return fetchTreats; - }() - }]); -}(Animal); + get age() { + return this.#age; + } +} +exports.Animal = Animal; +class Dog extends Animal { + constructor(name, age, breed) { + super(name, age); + this.breed = breed; + } + speak() { + return `${this.name} barks!`; + } + fetchTreats() { + return _asyncToGenerator(function* () { + yield new Promise(resolve => setTimeout(resolve, 100)); + return ['bone', 'biscuit', 'toy']; + })(); + } +} +exports.Dog = Dog; function processUser({ name, age = 18, @@ -245,10 +207,12 @@ function matchEmoji(text) { var match = text.match(/(?:[#\*0-9\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26A7\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC04\uDCCF\uDD70\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE02\uDE1A\uDE2F\uDE32-\uDE3A\uDE50\uDE51\uDF00-\uDF21\uDF24-\uDF93\uDF96\uDF97\uDF99-\uDF9B\uDF9E-\uDFF0\uDFF3-\uDFF5\uDFF7-\uDFFF]|\uD83D[\uDC00-\uDCFD\uDCFF-\uDD3D\uDD49-\uDD4E\uDD50-\uDD67\uDD6F\uDD70\uDD73-\uDD7A\uDD87\uDD8A-\uDD8D\uDD90\uDD95\uDD96\uDDA4\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA-\uDE4F\uDE80-\uDEC5\uDECB-\uDED2\uDED5-\uDED7\uDEDC-\uDEE5\uDEE9\uDEEB\uDEEC\uDEF0\uDEF3-\uDEFC\uDFE0-\uDFEB\uDFF0]|\uD83E[\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDDFF\uDE70-\uDE7C\uDE80-\uDE89\uDE8F-\uDEC6\uDECE-\uDEDC\uDEDF-\uDEE9\uDEF0-\uDEF8])/); return match?.[0]; } -var MyClass = exports.MyClass = _createClass(function MyClass(value) { - _classCallCheck(this, MyClass); - this.value = value; -}); +var MyClass = class { + constructor(value) { + this.value = value; + } +}; +exports.MyClass = MyClass; function loadModule() { return _loadModule.apply(this, arguments); } diff --git a/packages/react-native-babel-preset/src/__tests__/__fixtures__/output/no-import-export-transform.js b/packages/react-native-babel-preset/src/__tests__/__fixtures__/output/no-import-export-transform.js index f50ff8f413c..8f35153a9a7 100644 --- a/packages/react-native-babel-preset/src/__tests__/__fixtures__/output/no-import-export-transform.js +++ b/packages/react-native-babel-preset/src/__tests__/__fixtures__/output/no-import-export-transform.js @@ -17,30 +17,23 @@ * - Options: {"dev":false,"disableImportExportTransform":true} */ +import _inherits from "@babel/runtime/helpers/inherits"; import _setPrototypeOf from "@babel/runtime/helpers/setPrototypeOf"; import _slicedToArray from "@babel/runtime/helpers/slicedToArray"; -import _possibleConstructorReturn from "@babel/runtime/helpers/possibleConstructorReturn"; -import _getPrototypeOf from "@babel/runtime/helpers/getPrototypeOf"; -import _inherits from "@babel/runtime/helpers/inherits"; import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator"; -import _classCallCheck from "@babel/runtime/helpers/classCallCheck"; -import _createClass from "@babel/runtime/helpers/createClass"; import _classPrivateFieldLooseBase from "@babel/runtime/helpers/classPrivateFieldLooseBase"; import _classPrivateFieldLooseKey from "@babel/runtime/helpers/classPrivateFieldLooseKey"; import _awaitAsyncGenerator from "@babel/runtime/helpers/awaitAsyncGenerator"; import _wrapAsyncGenerator from "@babel/runtime/helpers/wrapAsyncGenerator"; function _wrapRegExp() { _wrapRegExp = function (e, r) { return new BabelRegExp(e, void 0, r); }; var e = RegExp.prototype, r = new WeakMap(); function BabelRegExp(e, t, p) { var o = RegExp(e, t); return r.set(o, p || r.get(e)), _setPrototypeOf(o, BabelRegExp.prototype); } function buildGroups(e, t) { var p = r.get(t); return Object.keys(p).reduce(function (r, t) { var o = p[t]; if ("number" == typeof o) r[t] = e[o];else { for (var i = 0; void 0 === e[o[i]] && i + 1 < o.length;) i++; r[t] = e[o[i]]; } return r; }, Object.create(null)); } return _inherits(BabelRegExp, RegExp), BabelRegExp.prototype.exec = function (r) { var t = e.exec.call(this, r); if (t) { t.groups = buildGroups(t, this); var p = t.indices; p && (p.groups = buildGroups(p, this)); } return t; }, BabelRegExp.prototype[Symbol.replace] = function (t, p) { if ("string" == typeof p) { var o = r.get(this); return e[Symbol.replace].call(this, t, p.replace(/\$<([^>]+)(>|$)/g, function (e, r, t) { if ("" === t) return e; var p = o[r]; return Array.isArray(p) ? "$" + p.join("$") : "number" == typeof p ? "$" + p : ""; })); } if ("function" == typeof p) { var i = this; return e[Symbol.replace].call(this, t, function () { var e = arguments; return "object" != typeof e[e.length - 1] && (e = [].slice.call(e)).push(buildGroups(e, i)), p.apply(this, e); }); } return e[Symbol.replace].call(this, t, p); }, _wrapRegExp.apply(this, arguments); } -function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); } -function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function () { return !!t; })(); } import * as React from 'react'; import { useEffect, useState } from 'react'; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; var _count = _classPrivateFieldLooseKey("count"); var _instances = _classPrivateFieldLooseKey("instances"); var _increment = _classPrivateFieldLooseKey("increment"); -var Counter = function () { - function Counter() { - _classCallCheck(this, Counter); +class Counter { + constructor() { Object.defineProperty(this, _increment, { value: _increment2 }); @@ -50,23 +43,16 @@ var Counter = function () { }); _classPrivateFieldLooseBase(Counter, _instances)[_instances]++; } - return _createClass(Counter, [{ - key: "value", - get: function () { - return _classPrivateFieldLooseBase(this, _count)[_count]; - } - }, { - key: "increment", - value: function increment() { - _classPrivateFieldLooseBase(this, _increment)[_increment](); - } - }], [{ - key: "instanceCount", - get: function () { - return _classPrivateFieldLooseBase(Counter, _instances)[_instances]; - } - }]); -}(); + get value() { + return _classPrivateFieldLooseBase(this, _count)[_count]; + } + increment() { + _classPrivateFieldLooseBase(this, _increment)[_increment](); + } + static get instanceCount() { + return _classPrivateFieldLooseBase(Counter, _instances)[_instances]; + } +} function _increment2() { _classPrivateFieldLooseBase(this, _count)[_count]++; } @@ -102,57 +88,34 @@ function _fetchData() { function getNestedValue(obj) { return obj?.a?.b?.c ?? 42; } -var _age = _classPrivateFieldLooseKey("age"); -var Animal = function () { - function Animal(name, age) { - _classCallCheck(this, Animal); - Object.defineProperty(this, _age, { - writable: true, - value: void 0 - }); +class Animal { + #age; + constructor(name, age) { this.name = name; - _classPrivateFieldLooseBase(this, _age)[_age] = age; + this.#age = age; } - return _createClass(Animal, [{ - key: "speak", - value: function speak() { - return `${this.name} makes a sound`; - } - }, { - key: "age", - get: function () { - return _classPrivateFieldLooseBase(this, _age)[_age]; - } - }]); -}(); -var Dog = function (_Animal2) { - function Dog(name, age, breed) { - var _this; - _classCallCheck(this, Dog); - _this = _callSuper(this, Dog, [name, age]); - _this.breed = breed; - return _this; + speak() { + return `${this.name} makes a sound`; } - _inherits(Dog, _Animal2); - return _createClass(Dog, [{ - key: "speak", - value: function speak() { - return `${this.name} barks!`; - } - }, { - key: "fetchTreats", - value: function () { - var _fetchTreats = _asyncToGenerator(function* () { - yield new Promise(resolve => setTimeout(resolve, 100)); - return ['bone', 'biscuit', 'toy']; - }); - function fetchTreats() { - return _fetchTreats.apply(this, arguments); - } - return fetchTreats; - }() - }]); -}(Animal); + get age() { + return this.#age; + } +} +class Dog extends Animal { + constructor(name, age, breed) { + super(name, age); + this.breed = breed; + } + speak() { + return `${this.name} barks!`; + } + fetchTreats() { + return _asyncToGenerator(function* () { + yield new Promise(resolve => setTimeout(resolve, 100)); + return ['bone', 'biscuit', 'toy']; + })(); + } +} function processUser({ name, age = 18, @@ -208,10 +171,11 @@ function matchEmoji(text) { var match = text.match(/(?:[#\*0-9\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26A7\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC04\uDCCF\uDD70\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE02\uDE1A\uDE2F\uDE32-\uDE3A\uDE50\uDE51\uDF00-\uDF21\uDF24-\uDF93\uDF96\uDF97\uDF99-\uDF9B\uDF9E-\uDFF0\uDFF3-\uDFF5\uDFF7-\uDFFF]|\uD83D[\uDC00-\uDCFD\uDCFF-\uDD3D\uDD49-\uDD4E\uDD50-\uDD67\uDD6F\uDD70\uDD73-\uDD7A\uDD87\uDD8A-\uDD8D\uDD90\uDD95\uDD96\uDDA4\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA-\uDE4F\uDE80-\uDEC5\uDECB-\uDED2\uDED5-\uDED7\uDEDC-\uDEE5\uDEE9\uDEEB\uDEEC\uDEF0\uDEF3-\uDEFC\uDFE0-\uDFEB\uDFF0]|\uD83E[\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDDFF\uDE70-\uDE7C\uDE80-\uDE89\uDE8F-\uDEC6\uDECE-\uDEDC\uDEDF-\uDEE9\uDEF0-\uDEF8])/); return match?.[0]; } -var MyClass = _createClass(function MyClass(value) { - _classCallCheck(this, MyClass); - this.value = value; -}); +var MyClass = class { + constructor(value) { + this.value = value; + } +}; function loadModule() { return _loadModule.apply(this, arguments); } diff --git a/packages/react-native-babel-preset/src/__tests__/__fixtures__/output/with-babel-runtime-version.js b/packages/react-native-babel-preset/src/__tests__/__fixtures__/output/with-babel-runtime-version.js index 0c4f6584654..90ef1e57176 100644 --- a/packages/react-native-babel-preset/src/__tests__/__fixtures__/output/with-babel-runtime-version.js +++ b/packages/react-native-babel-preset/src/__tests__/__fixtures__/output/with-babel-runtime-version.js @@ -42,11 +42,7 @@ exports.safeJsonParse = safeJsonParse; exports.sumPairs = sumPairs; var _wrapRegExp2 = _interopRequireDefault(require("@babel/runtime/helpers/wrapRegExp")); var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray")); -var _callSuper2 = _interopRequireDefault(require("@babel/runtime/helpers/callSuper")); -var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits")); var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); -var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); -var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var _classPrivateFieldLooseBase2 = _interopRequireDefault(require("@babel/runtime/helpers/classPrivateFieldLooseBase")); var _classPrivateFieldLooseKey2 = _interopRequireDefault(require("@babel/runtime/helpers/classPrivateFieldLooseKey")); var _awaitAsyncGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/awaitAsyncGenerator")); @@ -58,9 +54,8 @@ var _dataUtils = require("./data-utils"); var _count = (0, _classPrivateFieldLooseKey2.default)("count"); var _instances = (0, _classPrivateFieldLooseKey2.default)("instances"); var _increment = (0, _classPrivateFieldLooseKey2.default)("increment"); -var Counter = exports.Counter = function () { - function Counter() { - (0, _classCallCheck2.default)(this, Counter); +class Counter { + constructor() { Object.defineProperty(this, _increment, { value: _increment2 }); @@ -70,23 +65,17 @@ var Counter = exports.Counter = function () { }); (0, _classPrivateFieldLooseBase2.default)(Counter, _instances)[_instances]++; } - return (0, _createClass2.default)(Counter, [{ - key: "value", - get: function () { - return (0, _classPrivateFieldLooseBase2.default)(this, _count)[_count]; - } - }, { - key: "increment", - value: function increment() { - (0, _classPrivateFieldLooseBase2.default)(this, _increment)[_increment](); - } - }], [{ - key: "instanceCount", - get: function () { - return (0, _classPrivateFieldLooseBase2.default)(Counter, _instances)[_instances]; - } - }]); -}(); + get value() { + return (0, _classPrivateFieldLooseBase2.default)(this, _count)[_count]; + } + increment() { + (0, _classPrivateFieldLooseBase2.default)(this, _increment)[_increment](); + } + static get instanceCount() { + return (0, _classPrivateFieldLooseBase2.default)(Counter, _instances)[_instances]; + } +} +exports.Counter = Counter; function _increment2() { (0, _classPrivateFieldLooseBase2.default)(this, _count)[_count]++; } @@ -122,57 +111,36 @@ function _fetchData() { function getNestedValue(obj) { return obj?.a?.b?.c ?? 42; } -var _age = (0, _classPrivateFieldLooseKey2.default)("age"); -var Animal = exports.Animal = function () { - function Animal(name, age) { - (0, _classCallCheck2.default)(this, Animal); - Object.defineProperty(this, _age, { - writable: true, - value: void 0 - }); +class Animal { + #age; + constructor(name, age) { this.name = name; - (0, _classPrivateFieldLooseBase2.default)(this, _age)[_age] = age; + this.#age = age; } - return (0, _createClass2.default)(Animal, [{ - key: "speak", - value: function speak() { - return `${this.name} makes a sound`; - } - }, { - key: "age", - get: function () { - return (0, _classPrivateFieldLooseBase2.default)(this, _age)[_age]; - } - }]); -}(); -var Dog = exports.Dog = function (_Animal2) { - function Dog(name, age, breed) { - var _this; - (0, _classCallCheck2.default)(this, Dog); - _this = (0, _callSuper2.default)(this, Dog, [name, age]); - _this.breed = breed; - return _this; + speak() { + return `${this.name} makes a sound`; } - (0, _inherits2.default)(Dog, _Animal2); - return (0, _createClass2.default)(Dog, [{ - key: "speak", - value: function speak() { - return `${this.name} barks!`; - } - }, { - key: "fetchTreats", - value: function () { - var _fetchTreats = (0, _asyncToGenerator2.default)(function* () { - yield new Promise(resolve => setTimeout(resolve, 100)); - return ['bone', 'biscuit', 'toy']; - }); - function fetchTreats() { - return _fetchTreats.apply(this, arguments); - } - return fetchTreats; - }() - }]); -}(Animal); + get age() { + return this.#age; + } +} +exports.Animal = Animal; +class Dog extends Animal { + constructor(name, age, breed) { + super(name, age); + this.breed = breed; + } + speak() { + return `${this.name} barks!`; + } + fetchTreats() { + return (0, _asyncToGenerator2.default)(function* () { + yield new Promise(resolve => setTimeout(resolve, 100)); + return ['bone', 'biscuit', 'toy']; + })(); + } +} +exports.Dog = Dog; function processUser({ name, age = 18, @@ -228,10 +196,12 @@ function matchEmoji(text) { var match = text.match(/(?:[#\*0-9\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26A7\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC04\uDCCF\uDD70\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE02\uDE1A\uDE2F\uDE32-\uDE3A\uDE50\uDE51\uDF00-\uDF21\uDF24-\uDF93\uDF96\uDF97\uDF99-\uDF9B\uDF9E-\uDFF0\uDFF3-\uDFF5\uDFF7-\uDFFF]|\uD83D[\uDC00-\uDCFD\uDCFF-\uDD3D\uDD49-\uDD4E\uDD50-\uDD67\uDD6F\uDD70\uDD73-\uDD7A\uDD87\uDD8A-\uDD8D\uDD90\uDD95\uDD96\uDDA4\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA-\uDE4F\uDE80-\uDEC5\uDECB-\uDED2\uDED5-\uDED7\uDEDC-\uDEE5\uDEE9\uDEEB\uDEEC\uDEF0\uDEF3-\uDEFC\uDFE0-\uDFEB\uDFF0]|\uD83E[\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDDFF\uDE70-\uDE7C\uDE80-\uDE89\uDE8F-\uDEC6\uDECE-\uDEDC\uDEDF-\uDEE9\uDEF0-\uDEF8])/); return match?.[0]; } -var MyClass = exports.MyClass = (0, _createClass2.default)(function MyClass(value) { - (0, _classCallCheck2.default)(this, MyClass); - this.value = value; -}); +var MyClass = class { + constructor(value) { + this.value = value; + } +}; +exports.MyClass = MyClass; function loadModule() { return _loadModule.apply(this, arguments); } diff --git a/packages/react-native-babel-preset/src/__tests__/transform-snapshot-test.js b/packages/react-native-babel-preset/src/__tests__/transform-snapshot-test.js index 32b7deace2c..a774a3c9051 100644 --- a/packages/react-native-babel-preset/src/__tests__/transform-snapshot-test.js +++ b/packages/react-native-babel-preset/src/__tests__/transform-snapshot-test.js @@ -26,34 +26,35 @@ const inputCode = fs.readFileSync(INPUT_FILE, 'utf-8'); const testConfigs = [ { - name: 'default-dev', + name: 'hermes-stable-dev', options: { dev: true, + unstable_transformProfile: 'hermes-stable', }, - description: 'Default transform profile in development mode', + description: 'Hermes stable transform profile in development mode', }, { - name: 'default-prod', + name: 'hermes-stable-prod', options: { dev: false, }, - description: 'Default transform profile in production mode', + description: 'Hermes stable transform profile in production mode', }, { - name: 'hermes-stable-dev', + name: 'hermes-legacy-dev', options: { dev: true, - unstable_transformProfile: 'hermes-stable', + unstable_transformProfile: 'hermes-legacy', }, - description: 'Hermes stable transform profile in development mode', + description: 'Default transform profile in development mode', }, { - name: 'hermes-stable-prod', + name: 'hermes-legacy-prod', options: { dev: false, - unstable_transformProfile: 'hermes-stable', + unstable_transformProfile: 'hermes-legacy', }, - description: 'Hermes stable transform profile in production mode', + description: 'Default transform profile in production mode', }, { name: 'hermes-canary-dev', @@ -129,6 +130,30 @@ const testConfigs = [ description: 'Hermes stable transform profile in development mode with unstable_preserveAsync enabled', }, + { + name: 'hermes-stable-dev-preserve-block-scoping', + options: { + dev: true, + unstable_transformProfile: 'hermes-stable', + customTransformOptions: { + unstable_preserveBlockScoping: true, + }, + }, + description: + 'Hermes stable transform profile in development mode with unstable_preserveBlockScoping enabled', + }, + { + name: 'hermes-stable-dev-preserve-destructuring', + options: { + dev: true, + unstable_transformProfile: 'hermes-stable', + customTransformOptions: { + unstable_preserveDestructuring: true, + }, + }, + description: + 'Hermes stable transform profile in development mode with unstable_preserveDestructuring enabled', + }, ]; function transformCode( @@ -242,18 +267,6 @@ describe('react-native-babel-preset transform snapshots', () => { ); describe('specific feature transformations', () => { - it('handles private class fields', () => { - const code = ` - class Counter { - #count = 0; - increment() { this.#count++; } - get value() { return this.#count; } - } - `; - const result = transformCode(code, {dev: false}); - expect(result).not.toContain('#count'); - }); - it('handles async generators', () => { const code = ` async function* gen() { @@ -323,10 +336,28 @@ describe('react-native-babel-preset transform snapshots', () => { } } `; - const result = transformCode(code, {dev: false}); + const result = transformCode(code, { + dev: false, + unstable_transformProfile: 'hermes-legacy', + }); expect(result).not.toContain('class Animal'); }); + it('does not transform classes with default profile', () => { + const code = ` + class Animal { + constructor(name) { + this.name = name; + } + speak() { + return this.name; + } + } + `; + const result = transformCode(code, {dev: false}); + expect(result).toContain('class Animal'); + }); + it('handles named capturing groups in regex', () => { const code = `const match = str.match(/(?\\d{4})-(?\\d{2})/);`; const result = transformCode(code, {dev: false}); @@ -451,5 +482,76 @@ describe('react-native-babel-preset transform snapshots', () => { }); expect(result).toContain('_asyncToGenerator'); }); + + it('preserves block scoping with unstable_preserveBlockScoping', () => { + const code = ` + let x = 1; + const y = 2; + { + let x = 3; + const z = 4; + } + `; + const result = transformCode(code, { + dev: false, + unstable_transformProfile: 'hermes-stable', + customTransformOptions: { + unstable_preserveBlockScoping: true, + }, + }); + expect(result).toContain('let x = 1'); + expect(result).toContain('const y = 2'); + expect(result).toContain('let x = 3'); + expect(result).toContain('const z = 4'); + }); + + it('transforms block scoping without unstable_preserveBlockScoping', () => { + const code = ` + let x = 1; + const y = 2; + { + let x = 3; + } + `; + const result = transformCode(code, { + dev: false, + unstable_transformProfile: 'hermes-stable', + }); + expect(result).toContain('var '); + expect(result).not.toContain('let '); + expect(result).not.toContain('const '); + }); + + it('preserves destructuring with unstable_preserveDestructuring', () => { + const code = ` + const {a, b, ...rest} = obj; + const [x, y, z = 10] = arr; + const {nested: {value}} = data; + `; + const result = transformCode(code, { + dev: false, + unstable_transformProfile: 'hermes-stable', + customTransformOptions: { + unstable_preserveDestructuring: true, + }, + }); + // Check that destructuring syntax is preserved (not transformed) + expect(result).toContain('...rest'); + expect(result).toContain('z = 10'); + expect(result).toContain('nested:'); + expect(result).not.toContain('_objectWithoutProperties'); + expect(result).not.toContain('_slicedToArray'); + }); + + it('transforms destructuring without unstable_preserveDestructuring', () => { + const code = ` + const {a, b, ...rest} = obj; + `; + const result = transformCode(code, { + dev: false, + unstable_transformProfile: 'hermes-stable', + }); + expect(result).toContain('_objectWithoutProperties'); + }); }); }); diff --git a/packages/react-native-babel-preset/src/configs/main.js b/packages/react-native-babel-preset/src/configs/main.js index 69bcbd1e64f..a3fe1d6fb56 100644 --- a/packages/react-native-babel-preset/src/configs/main.js +++ b/packages/react-native-babel-preset/src/configs/main.js @@ -45,7 +45,7 @@ function isFirstParty(fileName) { // getPreset, which is otherwise cached based on `options`. This must be pure, // and should be cheap. function getTransformProfile(caller) { - return caller?.unstable_transformProfile ?? 'default'; + return caller?.unstable_transformProfile ?? 'hermes-stable'; } // use `this.foo = bar` instead of `this.defineProperty('foo', ...)` @@ -57,25 +57,25 @@ const getPreset = (src, options, babel) => { const dev = options?.dev ?? babel?.env('development') ?? false; - // Hermes V1 (aka Static Hermes) uses more optimised profiles. - // There is currently no difference between stable and canary, but canary - // may in future be used to test features in pre-prod Hermes versions. - const isHermesV1 = + // Hermes V1 uses more optimised transform profiles. There is currently no + // difference between stable and canary, but canary may in future be used to + // test features in pre-prod Hermes V1 versions. + const isHermesProfile = transformProfile === 'hermes-stable' || transformProfile === 'hermes-canary'; - // We enable regenerator in dev builds for the time being because + // We enable regenerator in Hermes V1 dev builds for the time being because // Hermes V1 doesn't yet fully support debugging native generators. // (e.g. - it's not possible to inspect local variables when paused in a // generator). // // Use native generators in release mode because it has already yielded perf - // wins. The next release of Hermes will close this gap, so this won't + // wins. The next release of Hermes V1 will close this gap, so this won't // be permanent. - const enableRegenerator = isHermesV1 && dev; + const enableRegenerator = isHermesProfile && dev; - // Preserve class syntax and related if we're using Hermes V1. - const preserveClasses = isHermesV1; + // Preserve class syntax and related features for Hermes V1 profiles. + const preserveClasses = isHermesProfile; // Preserve private class fields and methods if the experiment is enabled. const preserveClassPrivate = TRUE_VALS.has( @@ -87,6 +87,16 @@ const getPreset = (src, options, babel) => { options?.customTransformOptions?.unstable_preserveAsync, ); + // Preserve block scoping (let/const) if the experiment is enabled. + const preserveBlockScoping = TRUE_VALS.has( + options?.customTransformOptions?.unstable_preserveBlockScoping, + ); + + // Preserve destructuring syntax if the experiment is enabled. + const preserveDestructuring = TRUE_VALS.has( + options?.customTransformOptions?.unstable_preserveDestructuring, + ); + const isNull = src == null; const hasClass = isNull || src.indexOf('class') !== -1; @@ -139,10 +149,12 @@ const getPreset = (src, options, babel) => { ]); } - extraPlugins.push([ - require('@babel/plugin-transform-destructuring'), - {useBuiltIns: true}, - ]); + if (!preserveDestructuring) { + extraPlugins.push([ + require('@babel/plugin-transform-destructuring'), + {useBuiltIns: true}, + ]); + } if (!preserveAsync && (isNull || src.indexOf('async') !== -1)) { extraPlugins.push([ require('@babel/plugin-transform-async-generator-functions'), @@ -227,7 +239,9 @@ const getPreset = (src, options, babel) => { }, ], [require('babel-plugin-transform-flow-enums')], - [require('@babel/plugin-transform-block-scoping')], + ...(preserveBlockScoping + ? [] + : [[require('@babel/plugin-transform-block-scoping')]]), ...(preserveClasses ? [] : [[require('@babel/plugin-transform-class-properties'), {loose}]]), diff --git a/packages/react-native-babel-transformer/README.md b/packages/react-native-babel-transformer/README.md new file mode 100644 index 00000000000..ca5a8744d6f --- /dev/null +++ b/packages/react-native-babel-transformer/README.md @@ -0,0 +1,8 @@ +# @react-native/metro-babel-transformer + +[![npm]](https://www.npmjs.com/package/@react-native/metro-babel-transformer) [![npm downloads]](https://www.npmjs.com/package/@react-native/metro-babel-transformer) + +[npm]: https://img.shields.io/npm/v/@react-native/metro-babel-transformer.svg?color=blue +[npm downloads]: https://img.shields.io/npm/dm/@react-native/metro-babel-transformer.svg + +Metro Babel transformer for React Native applications. Applies [`@react-native/babel-preset`](https://www.npmjs.com/package/@react-native/babel-preset) when transforming source files during bundling. diff --git a/packages/react-native-babel-transformer/package.json b/packages/react-native-babel-transformer/package.json index 0c81735f319..2993f906aad 100644 --- a/packages/react-native-babel-transformer/package.json +++ b/packages/react-native-babel-transformer/package.json @@ -1,10 +1,10 @@ { "name": "@react-native/metro-babel-transformer", - "version": "0.86.0-main", + "version": "0.87.0-main", "description": "Babel transformer for React Native applications.", "repository": { "type": "git", - "url": "git+ssh://git@github.com/facebook/react-native.git", + "url": "git+ssh://git@github.com/react/react-native.git", "directory": "packages/react-native-babel-transformer" }, "keywords": [ @@ -14,7 +14,7 @@ ], "license": "MIT", "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" }, "main": "src/index.js", "files": [ @@ -28,7 +28,7 @@ "dependencies": { "@babel/core": "^7.25.2", "@react-native/babel-preset": "workspace:*", - "hermes-parser": "0.36.0", + "hermes-parser": "0.36.1", "nullthrows": "^1.1.1" }, "peerDependencies": { diff --git a/packages/react-native-codegen/README.md b/packages/react-native-codegen/README.md index 8f6856b1c8f..f598c58e913 100644 --- a/packages/react-native-codegen/README.md +++ b/packages/react-native-codegen/README.md @@ -1,21 +1,8 @@ # @react-native/codegen -[![Version][version-badge]][package] +[![npm]](https://www.npmjs.com/package/@react-native/codegen) [![npm downloads]](https://www.npmjs.com/package/@react-native/codegen) -## Installation +[npm]: https://img.shields.io/npm/v/@react-native/codegen.svg?color=blue +[npm downloads]: https://img.shields.io/npm/dm/@react-native/codegen.svg -``` -yarn add --dev @react-native/codegen -``` - -*Note: We're using `yarn` to install deps. Feel free to change commands to use `npm` 3+ and `npx` if you like* - -[version-badge]: https://img.shields.io/npm/v/@react-native/codegen?style=flat-square -[package]: https://www.npmjs.com/package/@react-native/codegen - -## Testing - -To run the tests in this package, run the following commands from the React Native root folder: - -1. `yarn` to install the dependencies. You just need to run this once -2. `yarn jest packages/react-native-codegen`. +Code generation tools for React Native. Parses TypeScript and Flow NativeModule and Component specs and generates the native binding code used by the New Architecture. diff --git a/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeArrayTurboModule.js b/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeArrayTurboModule.js index d044949283e..71da1584be8 100644 --- a/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeArrayTurboModule.js +++ b/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeArrayTurboModule.js @@ -16,9 +16,12 @@ export type ArrayType = string; type AnotherArray = Array; export interface Spec extends TurboModule { - +getArray: (a: Array) => Array; - +getReadOnlyArray: (a: Array) => ReadonlyArray; - +getArrayWithAlias: (a: AnotherArray, b: Array) => AnotherArray; + readonly getArray: (a: Array) => Array; + readonly getReadOnlyArray: (a: Array) => ReadonlyArray; + readonly getArrayWithAlias: ( + a: AnotherArray, + b: Array, + ) => AnotherArray; } export default TurboModuleRegistry.getEnforcing( diff --git a/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeBooleanTurboModule.js b/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeBooleanTurboModule.js index 7666fe80dd7..af8166072de 100644 --- a/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeBooleanTurboModule.js +++ b/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeBooleanTurboModule.js @@ -16,8 +16,8 @@ export type Boolean = boolean; type AnotherBoolean = Boolean; export interface Spec extends TurboModule { - +getBoolean: (arg: boolean) => boolean; - +getBooleanWithAlias: (arg: Boolean) => AnotherBoolean; + readonly getBoolean: (arg: boolean) => boolean; + readonly getBooleanWithAlias: (arg: Boolean) => AnotherBoolean; } export default TurboModuleRegistry.getEnforcing( diff --git a/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeCallbackTurboModule.js b/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeCallbackTurboModule.js index 943cd5d404b..745966dca77 100644 --- a/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeCallbackTurboModule.js +++ b/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeCallbackTurboModule.js @@ -16,8 +16,8 @@ export type String = string; type CB = (value: String) => void; export interface Spec extends TurboModule { - +getValueWithCallback: (callback: (value: string) => void) => void; - +getValueWithCallbackWithAlias: (c: CB) => void; + readonly getValueWithCallback: (callback: (value: string) => void) => void; + readonly getValueWithCallbackWithAlias: (c: CB) => void; } export default TurboModuleRegistry.getEnforcing( diff --git a/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeEnumTurboModule.js b/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeEnumTurboModule.js index e2918fb7556..bd737086423 100644 --- a/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeEnumTurboModule.js +++ b/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeEnumTurboModule.js @@ -49,17 +49,17 @@ export type StateTypeWithEnums = { }; export interface Spec extends TurboModule { - +getStatusRegular: (statusProp: StateType) => StatusRegularEnum; - +getStatusStr: (statusProp: StateType) => StatusStrEnum; - +getStatusNum: (statusProp: StateType) => StatusNumEnum; - +getStatusLowerCase: (statusProp: StateType) => StatusLowerCaseEnum; - +getStateType: ( + readonly getStatusRegular: (statusProp: StateType) => StatusRegularEnum; + readonly getStatusStr: (statusProp: StateType) => StatusStrEnum; + readonly getStatusNum: (statusProp: StateType) => StatusNumEnum; + readonly getStatusLowerCase: (statusProp: StateType) => StatusLowerCaseEnum; + readonly getStateType: ( a: StatusRegularEnum, b: StatusStrEnum, c: StatusNumEnum, d: StatusLowerCaseEnum, ) => StateType; - +getStateTypeWithEnums: ( + readonly getStateTypeWithEnums: ( paramOfTypeWithEnums: StateTypeWithEnums, ) => StateTypeWithEnums; } diff --git a/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeNullableTurboModule.js b/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeNullableTurboModule.js index b2a8d69dacb..d586369ad55 100644 --- a/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeNullableTurboModule.js +++ b/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeNullableTurboModule.js @@ -13,12 +13,12 @@ import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport'; import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; export interface Spec extends TurboModule { - +getBool: (a: ?boolean) => ?boolean; - +getNumber: (a: ?number) => ?number; - +getString: (a: ?number) => ?string; - +getArray: (a: ?Array) => ?Array; - +getObject: (a: ?Object) => ?Object; - +getValueWithPromise: () => ?Promise; + readonly getBool: (a: ?boolean) => ?boolean; + readonly getNumber: (a: ?number) => ?number; + readonly getString: (a: ?number) => ?string; + readonly getArray: (a: ?Array) => ?Array; + readonly getObject: (a: ?Object) => ?Object; + readonly getValueWithPromise: () => ?Promise; } export default TurboModuleRegistry.getEnforcing( diff --git a/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeNumberTurboModule.js b/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeNumberTurboModule.js index ddbf1ef0532..9754aa73bdd 100644 --- a/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeNumberTurboModule.js +++ b/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeNumberTurboModule.js @@ -16,8 +16,8 @@ export type Number = number; type AnotherNumber = Number; export interface Spec extends TurboModule { - +getNumber: (arg: number) => number; - +getNumberWithAlias: (arg: Number) => AnotherNumber; + readonly getNumber: (arg: number) => number; + readonly getNumberWithAlias: (arg: Number) => AnotherNumber; } export default TurboModuleRegistry.getEnforcing( diff --git a/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeObjectTurboModule.js b/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeObjectTurboModule.js index b0967fea6f3..39b171ee3d3 100644 --- a/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeObjectTurboModule.js +++ b/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeObjectTurboModule.js @@ -16,10 +16,12 @@ export type GenericObject = Object; type AnotherGenericObject = GenericObject; export interface Spec extends TurboModule { - +getGenericObject: (arg: Object) => Object; - +getGenericObjectReadOnly: (arg: Object) => Readonly<{a: string}>; - +getGenericObjectWithAlias: (arg: GenericObject) => AnotherGenericObject; - +difficultObject: (A: { + readonly getGenericObject: (arg: Object) => Object; + readonly getGenericObjectReadOnly: (arg: Object) => Readonly<{a: string}>; + readonly getGenericObjectWithAlias: ( + arg: GenericObject, + ) => AnotherGenericObject; + readonly difficultObject: (A: { D: boolean, E: { D: boolean, @@ -40,7 +42,7 @@ export interface Spec extends TurboModule { }, F: string, }; - +getConstants: () => { + readonly getConstants: () => { D: boolean, E: { D: boolean, diff --git a/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeOptionalObjectTurboModule.js b/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeOptionalObjectTurboModule.js index a08851e37d0..a8c4dd18c4d 100644 --- a/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeOptionalObjectTurboModule.js +++ b/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeOptionalObjectTurboModule.js @@ -13,7 +13,7 @@ import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport'; import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; export interface Spec extends TurboModule { - +getConstants: () => { + readonly getConstants: () => { D?: ?boolean, A?: Array, E?: ?{ diff --git a/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativePartialAnnotationTurboModule.js b/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativePartialAnnotationTurboModule.js index eadaef861a9..7e8718733ba 100644 --- a/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativePartialAnnotationTurboModule.js +++ b/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativePartialAnnotationTurboModule.js @@ -22,10 +22,10 @@ export type SomeObj = { export type PartialSomeObj = Partial; export interface Spec extends TurboModule { - +getSomeObj: () => SomeObj; - +getPartialSomeObj: () => Partial; - +getSomeObjFromPartialSomeObj: (value: Partial) => SomeObj; - +getPartialPartial: ( + readonly getSomeObj: () => SomeObj; + readonly getPartialSomeObj: () => Partial; + readonly getSomeObjFromPartialSomeObj: (value: Partial) => SomeObj; + readonly getPartialPartial: ( value1: Partial, value2: PartialSomeObj, ) => SomeObj; diff --git a/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativePromiseTurboModule.js b/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativePromiseTurboModule.js index 689eccb5cf5..9eaeb0f2f82 100644 --- a/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativePromiseTurboModule.js +++ b/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativePromiseTurboModule.js @@ -16,8 +16,8 @@ export type String = string; type AnotherPromise = Promise; export interface Spec extends TurboModule { - +getValueWithPromise: (error: boolean) => Promise; - +getValueWithPromiseWithAlias: (arg: String) => AnotherPromise; + readonly getValueWithPromise: (error: boolean) => Promise; + readonly getValueWithPromiseWithAlias: (arg: String) => AnotherPromise; } export default TurboModuleRegistry.getEnforcing( diff --git a/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeSampleTurboModule.js b/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeSampleTurboModule.js index 95899329352..a1f7c0b1fd7 100644 --- a/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeSampleTurboModule.js +++ b/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeSampleTurboModule.js @@ -21,23 +21,23 @@ type Animal = { export interface Spec extends TurboModule { // Exported methods. - +getConstants: () => { + readonly getConstants: () => { const1: boolean, const2: number, const3: string, }; - +voidFunc: () => void; - +getBool: (arg: boolean) => boolean; - +getNumber: (arg: number) => number; - +getString: (arg: string) => string; - +getArray: (arg: Array) => Array; - +getObject: (arg: Object) => Object; - +getObjectShape: (arg: {prop: number}) => {prop: number}; - +getAlias: (arg: Animal) => Animal; - +getRootTag: (arg: RootTag) => RootTag; - +getValue: (x: number, y: string, z: Object) => Object; - +getValueWithCallback: (callback: (value: string) => void) => void; - +getValueWithPromise: (error: boolean) => Promise; + readonly voidFunc: () => void; + readonly getBool: (arg: boolean) => boolean; + readonly getNumber: (arg: number) => number; + readonly getString: (arg: string) => string; + readonly getArray: (arg: Array) => Array; + readonly getObject: (arg: Object) => Object; + readonly getObjectShape: (arg: {prop: number}) => {prop: number}; + readonly getAlias: (arg: Animal) => Animal; + readonly getRootTag: (arg: RootTag) => RootTag; + readonly getValue: (x: number, y: string, z: Object) => Object; + readonly getValueWithCallback: (callback: (value: string) => void) => void; + readonly getValueWithPromise: (error: boolean) => Promise; } export default TurboModuleRegistry.getEnforcing( diff --git a/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeSampleTurboModuleArrays.js b/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeSampleTurboModuleArrays.js index b187b5c0e38..790a5a4861e 100644 --- a/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeSampleTurboModuleArrays.js +++ b/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeSampleTurboModuleArrays.js @@ -21,28 +21,34 @@ type Animal = { export interface Spec extends TurboModule { // Exported methods. - +getConstants: () => { + readonly getConstants: () => { const1: Array, const2: Array, const3: Array, id?: Array, }; - +voidFunc: () => void; - +getBool: (id: Array) => Array; - +getNumber: (arg: Array) => Array; - +getString: (arg: Array) => Array; - +getArray: (arg: Array>) => Array>; - +getObject: (arg: Array) => Array; - +getObjectShape: (arg: Array<{prop: number}>) => Array<{prop: number}>; - +getAlias: (arg: Array) => Array; - +getRootTag: (arg: Array) => Array; - +getValue: ( + readonly voidFunc: () => void; + readonly getBool: (id: Array) => Array; + readonly getNumber: (arg: Array) => Array; + readonly getString: (arg: Array) => Array; + readonly getArray: (arg: Array>) => Array>; + readonly getObject: (arg: Array) => Array; + readonly getObjectShape: ( + arg: Array<{prop: number}>, + ) => Array<{prop: number}>; + readonly getAlias: (arg: Array) => Array; + readonly getRootTag: (arg: Array) => Array; + readonly getValue: ( x: Array, y: Array, z: Array, ) => Array; - +getValueWithCallback: (callback: (value: Array) => void) => void; - +getValueWithPromise: (error: Array) => Promise>; + readonly getValueWithCallback: ( + callback: (value: Array) => void, + ) => void; + readonly getValueWithPromise: ( + error: Array, + ) => Promise>; } export default TurboModuleRegistry.getEnforcing( diff --git a/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeSampleTurboModuleNullable.js b/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeSampleTurboModuleNullable.js index a004705148a..d635d0d23e6 100644 --- a/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeSampleTurboModuleNullable.js +++ b/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeSampleTurboModuleNullable.js @@ -21,23 +21,23 @@ type Animal = ?{ export interface Spec extends TurboModule { // Exported methods. - +getConstants: () => { + readonly getConstants: () => { const1: ?boolean, const2: ?number, const3: ?string, }; - +voidFunc: () => void; - +getBool: (arg: ?boolean) => ?boolean; - +getNumber: (arg: ?number) => ?number; - +getString: (arg: ?string) => ?string; - +getArray: (arg: ?Array) => ?Array; - +getObject: (arg: ?Object) => ?Object; - +getObjectShape: (arg: ?{prop: ?number}) => ?{prop: ?number}; - +getAlias: (arg: ?Animal) => ?Animal; - +getRootTag: (arg: ?RootTag) => ?RootTag; - +getValue: (x: ?number, y: ?string, z: ?Object) => ?Object; - +getValueWithCallback: (callback: (value: ?string) => void) => void; - +getValueWithPromise: (error: ?boolean) => ?Promise; + readonly voidFunc: () => void; + readonly getBool: (arg: ?boolean) => ?boolean; + readonly getNumber: (arg: ?number) => ?number; + readonly getString: (arg: ?string) => ?string; + readonly getArray: (arg: ?Array) => ?Array; + readonly getObject: (arg: ?Object) => ?Object; + readonly getObjectShape: (arg: ?{prop: ?number}) => ?{prop: ?number}; + readonly getAlias: (arg: ?Animal) => ?Animal; + readonly getRootTag: (arg: ?RootTag) => ?RootTag; + readonly getValue: (x: ?number, y: ?string, z: ?Object) => ?Object; + readonly getValueWithCallback: (callback: (value: ?string) => void) => void; + readonly getValueWithPromise: (error: ?boolean) => ?Promise; } export default TurboModuleRegistry.getEnforcing( diff --git a/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeSampleTurboModuleNullableAndOptional.js b/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeSampleTurboModuleNullableAndOptional.js index c5ce3699a5b..b9fb069bca0 100644 --- a/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeSampleTurboModuleNullableAndOptional.js +++ b/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeSampleTurboModuleNullableAndOptional.js @@ -21,23 +21,25 @@ type Animal = ?{ export interface Spec extends TurboModule { // Exported methods. - +getConstants?: () => { + readonly getConstants?: () => { const1?: ?boolean, const2?: ?number, const3?: ?string, }; - +voidFunc?: () => void; - +getBool?: (arg?: ?boolean) => ?boolean; - +getNumber?: (arg?: ?number) => ?number; - +getString?: (arg?: ?string) => ?string; - +getArray?: (arg?: ?Array) => ?Array; - +getObject?: (arg?: ?Object) => ?Object; - +getObjectShape?: (arg?: {prop?: ?number}) => {prop?: ?number}; - +getAlias?: (arg?: ?Animal) => ?Animal; - +getRootTag?: (arg?: ?RootTag) => ?RootTag; - +getValue?: (x?: ?number, y?: ?string, z?: ?Object) => ?Object; - +getValueWithCallback?: (callback?: ?(value?: ?string) => void) => void; - +getValueWithPromise?: (error?: ?boolean) => ?Promise; + readonly voidFunc?: () => void; + readonly getBool?: (arg?: ?boolean) => ?boolean; + readonly getNumber?: (arg?: ?number) => ?number; + readonly getString?: (arg?: ?string) => ?string; + readonly getArray?: (arg?: ?Array) => ?Array; + readonly getObject?: (arg?: ?Object) => ?Object; + readonly getObjectShape?: (arg?: {prop?: ?number}) => {prop?: ?number}; + readonly getAlias?: (arg?: ?Animal) => ?Animal; + readonly getRootTag?: (arg?: ?RootTag) => ?RootTag; + readonly getValue?: (x?: ?number, y?: ?string, z?: ?Object) => ?Object; + readonly getValueWithCallback?: ( + callback?: ?(value?: ?string) => void, + ) => void; + readonly getValueWithPromise?: (error?: ?boolean) => ?Promise; } export default TurboModuleRegistry.getEnforcing( diff --git a/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeSampleTurboModuleOptional.js b/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeSampleTurboModuleOptional.js index 8e070fe6ce3..c5aa1d50eb8 100644 --- a/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeSampleTurboModuleOptional.js +++ b/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeSampleTurboModuleOptional.js @@ -21,23 +21,23 @@ type Animal = { export interface Spec extends TurboModule { // Exported methods. - +getConstants?: () => { + readonly getConstants?: () => { const1?: boolean, const2?: number, const3?: string, }; - +voidFunc?: () => void; - +getBool?: (arg?: boolean) => boolean; - +getNumber?: (arg?: number) => number; - +getString?: (arg?: string) => string; - +getArray?: (arg?: Array) => Array; - +getObject?: (arg?: Object) => Object; - +getObjectShape?: (arg?: {prop?: number}) => {prop?: number}; - +getAlias?: (arg?: Animal) => Animal; - +getRootTag?: (arg?: RootTag) => RootTag; - +getValue?: (x?: number, y?: string, z?: Object) => Object; - +getValueWithCallback?: (callback?: (value?: string) => void) => void; - +getValueWithPromise?: (error?: boolean) => Promise; + readonly voidFunc?: () => void; + readonly getBool?: (arg?: boolean) => boolean; + readonly getNumber?: (arg?: number) => number; + readonly getString?: (arg?: string) => string; + readonly getArray?: (arg?: Array) => Array; + readonly getObject?: (arg?: Object) => Object; + readonly getObjectShape?: (arg?: {prop?: number}) => {prop?: number}; + readonly getAlias?: (arg?: Animal) => Animal; + readonly getRootTag?: (arg?: RootTag) => RootTag; + readonly getValue?: (x?: number, y?: string, z?: Object) => Object; + readonly getValueWithCallback?: (callback?: (value?: string) => void) => void; + readonly getValueWithPromise?: (error?: boolean) => Promise; } export default TurboModuleRegistry.getEnforcing( diff --git a/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeStringTurboModule.js b/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeStringTurboModule.js index 76b1964cae5..2ae70cf5a62 100644 --- a/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeStringTurboModule.js +++ b/packages/react-native-codegen/e2e/deep_imports/__test_fixtures__/modules/NativeStringTurboModule.js @@ -16,8 +16,8 @@ export type String = string; type AnotherString = String; export interface Spec extends TurboModule { - +getString: (arg: string) => string; - +getStringWithAlias: (arg: String) => AnotherString; + readonly getString: (arg: string) => string; + readonly getStringWithAlias: (arg: String) => AnotherString; } export default TurboModuleRegistry.getEnforcing( diff --git a/packages/react-native-codegen/package.json b/packages/react-native-codegen/package.json index 0f6344ac3a2..df8059ba2fa 100644 --- a/packages/react-native-codegen/package.json +++ b/packages/react-native-codegen/package.json @@ -1,14 +1,14 @@ { "name": "@react-native/codegen", - "version": "0.86.0-main", + "version": "0.87.0-main", "description": "Code generation tools for React Native", "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/facebook/react-native.git", + "url": "git+https://github.com/react/react-native.git", "directory": "packages/react-native-codegen" }, - "homepage": "https://github.com/facebook/react-native/tree/HEAD/packages/react-native-codegen#readme", + "homepage": "https://github.com/react/react-native/tree/HEAD/packages/react-native-codegen#readme", "keywords": [ "code", "generation", @@ -16,9 +16,9 @@ "tools", "react-native" ], - "bugs": "https://github.com/facebook/react-native/issues", + "bugs": "https://github.com/react/react-native/issues", "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" }, "scripts": { "build": "yarn clean && node scripts/build.js --verbose", @@ -31,12 +31,12 @@ "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.29.0", - "hermes-parser": "0.36.0", + "glob": "^7.1.1", + "hermes-parser": "0.36.1", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "tinyglobby": "^0.2.15", - "yargs": "^17.6.2", - "glob": "^7.1.1" + "yargs": "^17.6.2" }, "devDependencies": { "@babel/core": "^7.25.2", @@ -46,8 +46,8 @@ "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/preset-env": "^7.25.3", - "babel-plugin-syntax-hermes-parser": "0.36.0", - "hermes-estree": "0.36.0", + "babel-plugin-syntax-hermes-parser": "0.36.1", + "hermes-estree": "0.36.1", "micromatch": "^4.0.4", "prettier": "3.6.2", "rimraf": "^3.0.2" diff --git a/packages/react-native-codegen/src/CodegenSchema.d.ts b/packages/react-native-codegen/src/CodegenSchema.d.ts index a1d7115f2be..568592c61dd 100644 --- a/packages/react-native-codegen/src/CodegenSchema.d.ts +++ b/packages/react-native-codegen/src/CodegenSchema.d.ts @@ -46,6 +46,10 @@ export interface VoidTypeAnnotation { readonly type: 'VoidTypeAnnotation'; } +export interface ArrayBufferTypeAnnotation { + readonly type: 'ArrayBufferTypeAnnotation'; +} + export interface BooleanLiteralTypeAnnotation { readonly type: 'BooleanLiteralTypeAnnotation'; readonly value: boolean; @@ -417,7 +421,8 @@ export type NativeModuleEventEmitterTypeAnnotation = | ArrayTypeAnnotation; export type NativeModuleBaseTypeAnnotation = - NativeModuleStringTypeAnnotation + ArrayBufferTypeAnnotation + | NativeModuleStringTypeAnnotation | StringLiteralTypeAnnotation | StringLiteralUnionTypeAnnotation | NativeModuleNumberTypeAnnotation diff --git a/packages/react-native-codegen/src/CodegenSchema.js b/packages/react-native-codegen/src/CodegenSchema.js index 90043816eeb..41b8edabf54 100644 --- a/packages/react-native-codegen/src/CodegenSchema.js +++ b/packages/react-native-codegen/src/CodegenSchema.js @@ -68,14 +68,18 @@ export type VoidTypeAnnotation = Readonly<{ type: 'VoidTypeAnnotation', }>; -export type ObjectTypeAnnotation<+T> = Readonly<{ +export type ArrayBufferTypeAnnotation = Readonly<{ + type: 'ArrayBufferTypeAnnotation', +}>; + +export type ObjectTypeAnnotation = Readonly<{ type: 'ObjectTypeAnnotation', properties: ReadonlyArray>, // metadata for objects that generated from interfaces baseTypes?: ReadonlyArray, }>; -export type UnionTypeAnnotation<+T> = Readonly<{ +export type UnionTypeAnnotation = Readonly<{ type: 'UnionTypeAnnotation', types: ReadonlyArray, }>; @@ -89,13 +93,13 @@ export type EventEmitterTypeAnnotation = Readonly<{ typeAnnotation: NativeModuleEventEmitterTypeAnnotation | $FlowFixMe, }>; -type FunctionTypeAnnotation<+P, +R> = Readonly<{ +type FunctionTypeAnnotation = Readonly<{ type: 'FunctionTypeAnnotation', params: ReadonlyArray>, returnTypeAnnotation: R, }>; -export type NamedShape<+T> = Readonly<{ +export type NamedShape = Readonly<{ name: string, optional: boolean, typeAnnotation: T, @@ -191,7 +195,7 @@ export type ComponentCommandArrayTypeAnnotation = ArrayTypeAnnotation< | MixedTypeAnnotation, >; -export type ArrayTypeAnnotation<+T> = Readonly<{ +export type ArrayTypeAnnotation = Readonly<{ type: 'ArrayTypeAnnotation', elementType: T, }>; @@ -265,11 +269,11 @@ export type ReservedTypeAnnotation = Readonly<{ /** * NativeModule Types */ -export type Nullable<+T extends NativeModuleTypeAnnotation> = +export type Nullable = | NullableTypeAnnotation | T; -export type NullableTypeAnnotation<+T extends NativeModuleTypeAnnotation> = +export type NullableTypeAnnotation = Readonly<{ type: 'NullableTypeAnnotation', typeAnnotation: T, @@ -317,7 +321,7 @@ export type NativeModuleObjectTypeAnnotation = ObjectTypeAnnotation< >; export type NativeModuleArrayTypeAnnotation< - +T extends Nullable, + out T extends Nullable, > = ArrayTypeAnnotation< | T /** @@ -411,6 +415,7 @@ export type NativeModuleEventEmitterTypeAnnotation = | ArrayTypeAnnotation; export type NativeModuleBaseTypeAnnotation = + | ArrayBufferTypeAnnotation | StringTypeAnnotation | StringLiteralTypeAnnotation | StringLiteralUnionTypeAnnotation diff --git a/packages/react-native-codegen/src/generators/ReservedPrimitiveTypes.js b/packages/react-native-codegen/src/generators/ReservedPrimitiveTypes.js index 97e521d78f5..1de738277d0 100644 --- a/packages/react-native-codegen/src/generators/ReservedPrimitiveTypes.js +++ b/packages/react-native-codegen/src/generators/ReservedPrimitiveTypes.js @@ -28,99 +28,104 @@ export type ReservedPrimitiveName = | 'DimensionPrimitive'; type CppTypeInfo = { - +typeName: string, - +localIncludes: ReadonlyArray, - +conversionIncludes: ReadonlyArray, + readonly typeName: string, + readonly localIncludes: ReadonlyArray, + readonly conversionIncludes: ReadonlyArray, }; type JavaImportInfo = { - +interfaceImports: ReadonlyArray, - +delegateImports: ReadonlyArray, + readonly interfaceImports: ReadonlyArray, + readonly delegateImports: ReadonlyArray, }; type ReservedTypeMapping = { - +cpp: CppTypeInfo, - +java: JavaImportInfo, + readonly cpp: CppTypeInfo, + readonly java: JavaImportInfo, }; -const RESERVED_TYPES: {+[ReservedPrimitiveName]: ReservedTypeMapping} = { - ColorPrimitive: { - cpp: { - typeName: 'SharedColor', - localIncludes: ['#include '], - conversionIncludes: [], +const RESERVED_TYPES: {readonly [ReservedPrimitiveName]: ReservedTypeMapping} = + { + ColorPrimitive: { + cpp: { + typeName: 'SharedColor', + localIncludes: ['#include '], + conversionIncludes: [], + }, + java: { + interfaceImports: [], + delegateImports: [ + 'import com.facebook.react.bridge.ColorPropConverter;', + ], + }, }, - java: { - interfaceImports: [], - delegateImports: ['import com.facebook.react.bridge.ColorPropConverter;'], + ImageSourcePrimitive: { + cpp: { + typeName: 'ImageSource', + localIncludes: ['#include '], + conversionIncludes: [ + '#include ', + ], + }, + java: { + interfaceImports: ['import com.facebook.react.bridge.ReadableMap;'], + delegateImports: ['import com.facebook.react.bridge.ReadableMap;'], + }, }, - }, - ImageSourcePrimitive: { - cpp: { - typeName: 'ImageSource', - localIncludes: ['#include '], - conversionIncludes: [ - '#include ', - ], + ImageRequestPrimitive: { + cpp: { + typeName: 'ImageRequest', + localIncludes: [ + '#include ', + ], + conversionIncludes: [], + }, + java: { + // ImageRequestPrimitive is not used in Java component props + interfaceImports: [], + delegateImports: [], + }, }, - java: { - interfaceImports: ['import com.facebook.react.bridge.ReadableMap;'], - delegateImports: ['import com.facebook.react.bridge.ReadableMap;'], + PointPrimitive: { + cpp: { + typeName: 'Point', + localIncludes: ['#include '], + conversionIncludes: [], + }, + java: { + interfaceImports: ['import com.facebook.react.bridge.ReadableMap;'], + delegateImports: ['import com.facebook.react.bridge.ReadableMap;'], + }, }, - }, - ImageRequestPrimitive: { - cpp: { - typeName: 'ImageRequest', - localIncludes: ['#include '], - conversionIncludes: [], + EdgeInsetsPrimitive: { + cpp: { + typeName: 'EdgeInsets', + localIncludes: ['#include '], + conversionIncludes: [], + }, + java: { + interfaceImports: ['import com.facebook.react.bridge.ReadableMap;'], + delegateImports: ['import com.facebook.react.bridge.ReadableMap;'], + }, }, - java: { - // ImageRequestPrimitive is not used in Java component props - interfaceImports: [], - delegateImports: [], + DimensionPrimitive: { + cpp: { + typeName: 'YGValue', + localIncludes: [ + '#include ', + '#include ', + ], + conversionIncludes: [ + '#include ', + ], + }, + java: { + interfaceImports: ['import com.facebook.yoga.YogaValue;'], + delegateImports: [ + 'import com.facebook.react.bridge.DimensionPropConverter;', + ], + }, }, - }, - PointPrimitive: { - cpp: { - typeName: 'Point', - localIncludes: ['#include '], - conversionIncludes: [], - }, - java: { - interfaceImports: ['import com.facebook.react.bridge.ReadableMap;'], - delegateImports: ['import com.facebook.react.bridge.ReadableMap;'], - }, - }, - EdgeInsetsPrimitive: { - cpp: { - typeName: 'EdgeInsets', - localIncludes: ['#include '], - conversionIncludes: [], - }, - java: { - interfaceImports: ['import com.facebook.react.bridge.ReadableMap;'], - delegateImports: ['import com.facebook.react.bridge.ReadableMap;'], - }, - }, - DimensionPrimitive: { - cpp: { - typeName: 'YGValue', - localIncludes: [ - '#include ', - '#include ', - ], - conversionIncludes: [ - '#include ', - ], - }, - java: { - interfaceImports: ['import com.facebook.yoga.YogaValue;'], - delegateImports: [ - 'import com.facebook.react.bridge.DimensionPropConverter;', - ], - }, - }, -}; + }; function getCppTypeForReservedPrimitive(name: ReservedPrimitiveName): string { return RESERVED_TYPES[name].cpp.typeName; diff --git a/packages/react-native-codegen/src/generators/components/ComponentsGeneratorUtils.js b/packages/react-native-codegen/src/generators/components/ComponentsGeneratorUtils.js index 4e680fe3e84..bd706b186a1 100644 --- a/packages/react-native-codegen/src/generators/components/ComponentsGeneratorUtils.js +++ b/packages/react-native-codegen/src/generators/components/ComponentsGeneratorUtils.js @@ -49,13 +49,13 @@ function getNativeTypeFromAnnotation( | ObjectTypeAnnotation | ReservedPropTypeAnnotation | { - +default: string, - +options: ReadonlyArray, - +type: 'StringEnumTypeAnnotation', + readonly default: string, + readonly options: ReadonlyArray, + readonly type: 'StringEnumTypeAnnotation', } | { - +elementType: ObjectTypeAnnotation, - +type: 'ArrayTypeAnnotation', + readonly elementType: ObjectTypeAnnotation, + readonly type: 'ArrayTypeAnnotation', }, }, nameParts: ReadonlyArray, diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js index 388e00e1a27..af7f00aa300 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js @@ -146,6 +146,8 @@ function serializeArg( return wrap(val => `${val}.asObject(rt)`); case 'MixedTypeAnnotation': return wrap(val => `jsi::Value(rt, ${val})`); + case 'ArrayBufferTypeAnnotation': + return wrap(val => `${val}.asObject(rt).getArrayBuffer(rt)`); default: realTypeAnnotation.type as empty; throw new Error( @@ -307,6 +309,8 @@ function translatePrimitiveJSTypeToCpp( return wrapOptional('jsi::Value', isRequired); case 'MixedTypeAnnotation': return wrapOptional('jsi::Value', isRequired); + case 'ArrayBufferTypeAnnotation': + return wrapOptional('jsi::ArrayBuffer', isRequired); default: realTypeAnnotation.type as empty; throw new Error(createErrorMessage(realTypeAnnotation.type)); diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js index 98ed68318f8..58331fb2651 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js @@ -267,6 +267,10 @@ function translateFunctionParamToJavaType( case 'FunctionTypeAnnotation': imports.add('com.facebook.react.bridge.Callback'); return wrapOptional('Callback', isRequired); + case 'ArrayBufferTypeAnnotation': + throw new Error( + `${createErrorMessage(realTypeAnnotation.type)} ArrayBuffer is only supported for C++ TurboModules.`, + ); default: realTypeAnnotation.type as 'MixedTypeAnnotation'; throw new Error(createErrorMessage(realTypeAnnotation.type)); @@ -361,6 +365,10 @@ function translateFunctionReturnTypeToJavaType( case 'ArrayTypeAnnotation': imports.add('com.facebook.react.bridge.WritableArray'); return wrapOptional('WritableArray', isRequired); + case 'ArrayBufferTypeAnnotation': + throw new Error( + `${createErrorMessage(realTypeAnnotation.type)} ArrayBuffer is only supported for C++ TurboModules.`, + ); default: realTypeAnnotation.type as 'MixedTypeAnnotation'; throw new Error(createErrorMessage(realTypeAnnotation.type)); @@ -443,6 +451,10 @@ function getFalsyReturnStatementFromReturnType( return 'return null;'; case 'ArrayTypeAnnotation': return 'return null;'; + case 'ArrayBufferTypeAnnotation': + throw new Error( + `${createErrorMessage(realTypeAnnotation.type)} ArrayBuffer is only supported for C++ TurboModules.`, + ); default: realTypeAnnotation.type as 'MixedTypeAnnotation'; throw new Error(createErrorMessage(realTypeAnnotation.type)); diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleJniCpp.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleJniCpp.js index 55be945773f..c05b97f0b2d 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleJniCpp.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleJniCpp.js @@ -216,6 +216,8 @@ function translateReturnTypeToKind( return 'ObjectKind'; case 'ArrayTypeAnnotation': return 'ArrayKind'; + case 'ArrayBufferTypeAnnotation': + throw new Error('ArrayBuffer is only supported for C++ TurboModules.'); default: realTypeAnnotation.type as 'MixedTypeAnnotation'; throw new Error( @@ -303,6 +305,8 @@ function translateParamTypeToJniType( return 'Lcom/facebook/react/bridge/ReadableArray;'; case 'FunctionTypeAnnotation': return 'Lcom/facebook/react/bridge/Callback;'; + case 'ArrayBufferTypeAnnotation': + throw new Error('ArrayBuffer is only supported for C++ TurboModules.'); default: realTypeAnnotation.type as 'MixedTypeAnnotation'; throw new Error( @@ -387,6 +391,8 @@ function translateReturnTypeToJniType( return 'Lcom/facebook/react/bridge/WritableMap;'; case 'ArrayTypeAnnotation': return 'Lcom/facebook/react/bridge/WritableArray;'; + case 'ArrayBufferTypeAnnotation': + throw new Error('ArrayBuffer is only supported for C++ TurboModules.'); default: realTypeAnnotation.type as 'MixedTypeAnnotation'; throw new Error( diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/StructCollector.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/StructCollector.js index 2cc39108680..97ce0ecd0db 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/StructCollector.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/StructCollector.js @@ -132,6 +132,10 @@ class StructCollector { return wrapNullable(nullable, typeAnnotation); case 'MixedTypeAnnotation': throw new Error('Mixed types are unsupported in structs'); + case 'ArrayBufferTypeAnnotation': + throw new Error( + 'ArrayBuffer is unsupported in TurboModule struct types.', + ); case 'UnionTypeAnnotation': try { const validUnionType = parseValidUnionType(typeAnnotation); diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/serializeMethod.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/serializeMethod.js index c618b0f78c5..9f789ade20a 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/serializeMethod.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/serializeMethod.js @@ -51,7 +51,8 @@ type ReturnJSType = | 'ObjectKind' | 'ArrayKind' | 'NumberKind' - | 'StringKind'; + | 'StringKind' + | 'ArrayBufferKind'; export type MethodSerializationOutput = Readonly<{ methodName: string, @@ -219,6 +220,9 @@ function getParamObjCType( */ return notStruct(wrapOptional('NSArray *', !nullable)); } + case 'ArrayBufferTypeAnnotation': { + return notStruct(wrapOptional('NSMutableData *', !nullable)); + } } const [structTypeAnnotation] = unwrapNullable( @@ -387,6 +391,8 @@ function getReturnObjCType( } case 'GenericObjectTypeAnnotation': return wrapOptional('NSDictionary *', isRequired); + case 'ArrayBufferTypeAnnotation': + return wrapOptional('NSMutableData *', isRequired); default: typeAnnotation.type as 'MixedTypeAnnotation'; throw new Error( @@ -459,6 +465,8 @@ function getReturnJSType( validUnionType as empty; throw new Error(`Unsupported union member types`); } + case 'ArrayBufferTypeAnnotation': + return 'ArrayBufferKind'; default: typeAnnotation.type as 'MixedTypeAnnotation'; throw new Error( diff --git a/packages/react-native-codegen/src/generators/modules/__test_fixtures__/fixtures.js b/packages/react-native-codegen/src/generators/modules/__test_fixtures__/fixtures.js index 6b13eeb9c25..b3d3345244d 100644 --- a/packages/react-native-codegen/src/generators/modules/__test_fixtures__/fixtures.js +++ b/packages/react-native-codegen/src/generators/modules/__test_fixtures__/fixtures.js @@ -2600,6 +2600,89 @@ const CXX_ONLY_NATIVE_MODULES: SchemaType = { }, }; +const ARRAY_BUFFER_NATIVE_MODULE: SchemaType = { + modules: { + NativeSampleTurboModule: { + type: 'NativeModule', + aliasMap: {}, + enumMap: {}, + spec: { + eventEmitters: [], + methods: [ + { + name: 'getArrayBuffer', + optional: false, + typeAnnotation: { + type: 'FunctionTypeAnnotation', + returnTypeAnnotation: { + type: 'ArrayBufferTypeAnnotation', + }, + params: [], + }, + }, + { + name: 'voidArrayBuffer', + optional: false, + typeAnnotation: { + type: 'FunctionTypeAnnotation', + returnTypeAnnotation: { + type: 'VoidTypeAnnotation', + }, + params: [ + { + name: 'arg', + optional: false, + typeAnnotation: { + type: 'ArrayBufferTypeAnnotation', + }, + }, + ], + }, + }, + { + name: 'voidNullableArrayBuffer', + optional: false, + typeAnnotation: { + type: 'FunctionTypeAnnotation', + returnTypeAnnotation: { + type: 'VoidTypeAnnotation', + }, + params: [ + { + name: 'arg', + optional: false, + typeAnnotation: { + type: 'NullableTypeAnnotation', + typeAnnotation: { + type: 'ArrayBufferTypeAnnotation', + }, + }, + }, + ], + }, + }, + { + name: 'promiseArrayBuffer', + optional: false, + typeAnnotation: { + type: 'FunctionTypeAnnotation', + returnTypeAnnotation: { + type: 'PromiseTypeAnnotation', + elementType: { + type: 'ArrayBufferTypeAnnotation', + }, + }, + params: [], + }, + }, + ], + }, + moduleName: 'SampleTurboModule', + excludedPlatforms: ['android'], + }, + }, +}; + const SAMPLE_WITH_UPPERCASE_NAME: SchemaType = { modules: { NativeSampleTurboModule: { @@ -2793,6 +2876,7 @@ const STRING_LITERALS: SchemaType = { }; module.exports = { + array_buffer_native_module: ARRAY_BUFFER_NATIVE_MODULE, complex_objects: COMPLEX_OBJECTS, two_modules_different_files: TWO_MODULES_DIFFERENT_FILES, empty_native_modules: EMPTY_NATIVE_MODULES, diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleH-test.js.snap b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleH-test.js.snap index 2feb4a244ae..cdfaf66f4ef 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleH-test.js.snap +++ b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleH-test.js.snap @@ -38,6 +38,75 @@ private: } `; +exports[`GenerateModuleH can generate fixture array_buffer_native_module 1`] = ` +Map { + "array_buffer_native_moduleJSI.h" => "/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateModuleH.js + */ + +#pragma once + +#include +#include + +namespace facebook::react { + + +template +class JSI_EXPORT NativeSampleTurboModuleCxxSpec : public TurboModule { +public: + static constexpr std::string_view kModuleName = \\"SampleTurboModule\\"; + +protected: + NativeSampleTurboModuleCxxSpec(std::shared_ptr jsInvoker) : TurboModule(std::string{NativeSampleTurboModuleCxxSpec::kModuleName}, jsInvoker) { + methodMap_[\\"getArrayBuffer\\"] = MethodMetadata {.argCount = 0, .invoker = __getArrayBuffer}; + methodMap_[\\"voidArrayBuffer\\"] = MethodMetadata {.argCount = 1, .invoker = __voidArrayBuffer}; + methodMap_[\\"voidNullableArrayBuffer\\"] = MethodMetadata {.argCount = 1, .invoker = __voidNullableArrayBuffer}; + methodMap_[\\"promiseArrayBuffer\\"] = MethodMetadata {.argCount = 0, .invoker = __promiseArrayBuffer}; + } + +private: + static jsi::Value __getArrayBuffer(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* /*args*/, size_t /*count*/) { + static_assert( + bridging::getParameterCount(&T::getArrayBuffer) == 1, + \\"Expected getArrayBuffer(...) to have 1 parameters\\"); + return bridging::callFromJs(rt, &T::getArrayBuffer, static_cast(&turboModule)->jsInvoker_, static_cast(&turboModule)); + } + + static jsi::Value __voidArrayBuffer(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_assert( + bridging::getParameterCount(&T::voidArrayBuffer) == 2, + \\"Expected voidArrayBuffer(...) to have 2 parameters\\"); + bridging::callFromJs(rt, &T::voidArrayBuffer, static_cast(&turboModule)->jsInvoker_, static_cast(&turboModule), + count <= 0 ? throw jsi::JSError(rt, \\"Expected argument in position 0 to be passed\\") : args[0].asObject(rt).getArrayBuffer(rt));return jsi::Value::undefined(); + } + + static jsi::Value __voidNullableArrayBuffer(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_assert( + bridging::getParameterCount(&T::voidNullableArrayBuffer) == 2, + \\"Expected voidNullableArrayBuffer(...) to have 2 parameters\\"); + bridging::callFromJs(rt, &T::voidNullableArrayBuffer, static_cast(&turboModule)->jsInvoker_, static_cast(&turboModule), + count <= 0 || args[0].isNull() || args[0].isUndefined() ? std::nullopt : std::make_optional(args[0].asObject(rt).getArrayBuffer(rt)));return jsi::Value::undefined(); + } + + static jsi::Value __promiseArrayBuffer(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* /*args*/, size_t /*count*/) { + static_assert( + bridging::getParameterCount(&T::promiseArrayBuffer) == 1, + \\"Expected promiseArrayBuffer(...) to have 1 parameters\\"); + return bridging::callFromJs(rt, &T::promiseArrayBuffer, static_cast(&turboModule)->jsInvoker_, static_cast(&turboModule)); + } +}; + +} // namespace facebook::react +", +} +`; + exports[`GenerateModuleH can generate fixture complex_objects 1`] = ` Map { "complex_objectsJSI.h" => "/** diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleHObjCpp-test.js.snap b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleHObjCpp-test.js.snap index c67b27268cf..7612ea01b0f 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleHObjCpp-test.js.snap +++ b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleHObjCpp-test.js.snap @@ -65,6 +65,75 @@ namespace facebook::react { } `; +exports[`GenerateModuleHObjCpp can generate fixture array_buffer_native_module 1`] = ` +Map { + "array_buffer_native_module.h" => "/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateModuleObjCpp + * + * We create an umbrella header (and corresponding implementation) here since + * Cxx compilation in BUCK has a limitation: source-code producing genrule()s + * must have a single output. More files => more genrule()s => slower builds. + */ + +#ifndef __cplusplus +#error This file must be compiled as Obj-C++. If you are importing it, you must change your file extension to .mm. +#endif + +// Avoid multiple includes of array_buffer_native_module symbols +#ifndef array_buffer_native_module_H +#define array_buffer_native_module_H + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + + +@protocol NativeSampleTurboModuleSpec + +- (NSMutableData *)getArrayBuffer; +- (void)voidArrayBuffer:(NSMutableData *)arg; +- (void)voidNullableArrayBuffer:(NSMutableData * _Nullable)arg; +- (void)promiseArrayBuffer:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject; + +@end + +@interface NativeSampleTurboModuleSpecBase : NSObject { +@protected +facebook::react::EventEmitterCallback _eventEmitterCallback; +} +- (void)setEventEmitterCallback:(EventEmitterCallbackWrapper *)eventEmitterCallbackWrapper; + + +@end + +namespace facebook::react { + /** + * ObjC++ class for module 'NativeSampleTurboModule' + */ + class JSI_EXPORT NativeSampleTurboModuleSpecJSI : public ObjCTurboModule { + public: + NativeSampleTurboModuleSpecJSI(const ObjCTurboModule::InitParams ¶ms); + }; +} // namespace facebook::react + +#endif // array_buffer_native_module_H +", +} +`; + exports[`GenerateModuleHObjCpp can generate fixture complex_objects 1`] = ` Map { "complex_objects.h" => "/** diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleJavaSpec-test.js.snap b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleJavaSpec-test.js.snap index 4bf92d2761d..dc2df9db064 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleJavaSpec-test.js.snap +++ b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleJavaSpec-test.js.snap @@ -41,6 +41,8 @@ public abstract class NativeSampleTurboModuleSpec extends ReactContextBaseJavaMo } `; +exports[`GenerateModuleJavaSpec can generate fixture array_buffer_native_module 1`] = `Map {}`; + exports[`GenerateModuleJavaSpec can generate fixture complex_objects 1`] = ` Map { "java/com/facebook/fbreact/specs/NativeSampleTurboModuleSpec.java" => " diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleJniCpp-test.js.snap b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleJniCpp-test.js.snap index 38fbd875e56..c4285ae56f6 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleJniCpp-test.js.snap +++ b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleJniCpp-test.js.snap @@ -35,6 +35,34 @@ std::shared_ptr SampleWithUppercaseName_ModuleProvider(const std::s } `; +exports[`GenerateModuleJniCpp can generate fixture array_buffer_native_module 1`] = ` +Map { + "jni/array_buffer_native_module-generated.cpp" => " +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateModuleJniCpp.js + */ + +#include \\"array_buffer_native_module.h\\" + +namespace facebook::react { + + + +std::shared_ptr array_buffer_native_module_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams ¶ms) { + + return nullptr; +} + +} // namespace facebook::react +", +} +`; + exports[`GenerateModuleJniCpp can generate fixture complex_objects 1`] = ` Map { "jni/complex_objects-generated.cpp" => " diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleJniH-test.js.snap b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleJniH-test.js.snap index a45028176b1..c46009a84e3 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleJniH-test.js.snap +++ b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleJniH-test.js.snap @@ -66,6 +66,65 @@ target_compile_reactnative_options(react_codegen_SampleWithUppercaseName PRIVATE } `; +exports[`GenerateModuleJniH can generate fixture array_buffer_native_module 1`] = ` +Map { + "jni/array_buffer_native_module.h" => " +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateModuleJniH.js + */ + +#pragma once + +#include +#include +#include + +namespace facebook::react { + + + +JSI_EXPORT +std::shared_ptr array_buffer_native_module_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams ¶ms); + +} // namespace facebook::react +", + "jni/CMakeLists.txt" => "# 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. + +cmake_minimum_required(VERSION 3.13) +set(CMAKE_VERBOSE_MAKEFILE on) + +file(GLOB react_codegen_SRCS CONFIGURE_DEPENDS *.cpp react/renderer/components/array_buffer_native_module/*.cpp) + +add_library( + react_codegen_array_buffer_native_module + OBJECT + \${react_codegen_SRCS} +) + +target_include_directories(react_codegen_array_buffer_native_module PUBLIC . react/renderer/components/array_buffer_native_module) + +target_link_libraries( + react_codegen_array_buffer_native_module + fbjni + jsi + # We need to link different libraries based on whether we are building rncore or not, that's necessary + # because we want to break a circular dependency between react_codegen_rncore and reactnative + reactnative +) + +target_compile_reactnative_options(react_codegen_array_buffer_native_module PRIVATE) +", +} +`; + exports[`GenerateModuleJniH can generate fixture complex_objects 1`] = ` Map { "jni/complex_objects.h" => " diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleMm-test.js.snap b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleMm-test.js.snap index fb6f5d7fbb3..c15e20ab0bf 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleMm-test.js.snap +++ b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleMm-test.js.snap @@ -40,6 +40,72 @@ namespace facebook::react { } `; +exports[`GenerateModuleMm can generate fixture array_buffer_native_module 1`] = ` +Map { + "array_buffer_native_module-generated.mm" => "/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateModuleObjCpp + * + * We create an umbrella header (and corresponding implementation) here since + * Cxx compilation in BUCK has a limitation: source-code producing genrule()s + * must have a single output. More files => more genrule()s => slower builds. + */ + +#import \\"array_buffer_native_module.h\\" + + +@implementation NativeSampleTurboModuleSpecBase + + +- (void)setEventEmitterCallback:(EventEmitterCallbackWrapper *)eventEmitterCallbackWrapper +{ + _eventEmitterCallback = std::move(eventEmitterCallbackWrapper->_eventEmitterCallback); +} +@end + + +namespace facebook::react { + + static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getArrayBuffer(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule).invokeObjCMethod(rt, ArrayBufferKind, \\"getArrayBuffer\\", @selector(getArrayBuffer), args, count); + } + + static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_voidArrayBuffer(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule).invokeObjCMethod(rt, VoidKind, \\"voidArrayBuffer\\", @selector(voidArrayBuffer:), args, count); + } + + static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_voidNullableArrayBuffer(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule).invokeObjCMethod(rt, VoidKind, \\"voidNullableArrayBuffer\\", @selector(voidNullableArrayBuffer:), args, count); + } + + static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_promiseArrayBuffer(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + return static_cast(turboModule).invokeObjCMethod(rt, PromiseKind, \\"promiseArrayBuffer\\", @selector(promiseArrayBuffer:reject:), args, count); + } + + NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(const ObjCTurboModule::InitParams ¶ms) + : ObjCTurboModule(params) { + + methodMap_[\\"getArrayBuffer\\"] = MethodMetadata {0, __hostFunction_NativeSampleTurboModuleSpecJSI_getArrayBuffer}; + + + methodMap_[\\"voidArrayBuffer\\"] = MethodMetadata {1, __hostFunction_NativeSampleTurboModuleSpecJSI_voidArrayBuffer}; + + + methodMap_[\\"voidNullableArrayBuffer\\"] = MethodMetadata {1, __hostFunction_NativeSampleTurboModuleSpecJSI_voidNullableArrayBuffer}; + + + methodMap_[\\"promiseArrayBuffer\\"] = MethodMetadata {0, __hostFunction_NativeSampleTurboModuleSpecJSI_promiseArrayBuffer}; + + } +} // namespace facebook::react +", +} +`; + exports[`GenerateModuleMm can generate fixture complex_objects 1`] = ` Map { "complex_objects-generated.mm" => "/** diff --git a/packages/react-native-codegen/src/parsers/__tests__/parsers-primitives-test.js b/packages/react-native-codegen/src/parsers/__tests__/parsers-primitives-test.js index 0b73b6058ee..6e198275c45 100644 --- a/packages/react-native-codegen/src/parsers/__tests__/parsers-primitives-test.js +++ b/packages/react-native-codegen/src/parsers/__tests__/parsers-primitives-test.js @@ -652,6 +652,39 @@ describe('emitPromise', () => { }); }); }); + + describe('when Promise element type resolves to ArrayBufferTypeAnnotation', () => { + it('returns PromiseTypeAnnotation with ArrayBuffer element type', () => { + const result = emitPromise( + moduleName, + { + typeParameters: { + params: [{}], + type: 'TypeParameterInstantiation', + }, + id: { + name: 'Promise', + }, + }, + parser, + false, + {}, + {}, + {}, + function (f: () => T): $FlowFixMe { + return f(); + }, + false, + () => ({ + type: 'ArrayBufferTypeAnnotation', + }), + ); + expect(result).toEqual({ + type: 'PromiseTypeAnnotation', + elementType: {type: 'ArrayBufferTypeAnnotation'}, + }); + }); + }); }); describe('emitGenericObject', () => { @@ -1829,6 +1862,26 @@ describe('emitUnion', () => { }); }); + describe("when 'typeAnnotation.id.name' is 'ArrayBuffer'", () => { + const typeAnnotation = { + typeParameters: { + params: [1, 2], + type: 'GenericTypeAnnotation', + }, + id: { + name: 'ArrayBuffer', + }, + }; + const expected = { + type: 'ArrayBufferTypeAnnotation', + }; + const result = emitCommonTypesForUnitTest(typeAnnotation, false); + + it("returns 'ArrayBufferTypeAnnotation'", () => { + expect(result).toEqual(expected); + }); + }); + describe("when 'typeAnnotation.id.name' is 'UnsafeObject'", () => { const typeAnnotation = { typeParameters: { diff --git a/packages/react-native-codegen/src/parsers/error-utils.js b/packages/react-native-codegen/src/parsers/error-utils.js index da39e9d6a1e..348f688f9b7 100644 --- a/packages/react-native-codegen/src/parsers/error-utils.js +++ b/packages/react-native-codegen/src/parsers/error-utils.js @@ -218,6 +218,7 @@ const UnsupportedObjectPropertyTypeToInvalidPropertyValueTypeMap = { FunctionTypeAnnotation: 'FunctionTypeAnnotation', VoidTypeAnnotation: 'void', PromiseTypeAnnotation: 'Promise', + ArrayBufferTypeAnnotation: 'ArrayBuffer', }; function throwIfPropertyValueTypeIsUnsupported( @@ -277,6 +278,7 @@ function throwIfArrayElementTypeAnnotationIsUnsupported( FunctionTypeAnnotation: 'FunctionTypeAnnotation', VoidTypeAnnotation: 'void', PromiseTypeAnnotation: 'Promise', + ArrayBufferTypeAnnotation: 'ArrayBuffer', // TODO: Added as a work-around for now until TupleTypeAnnotation are fully supported in both flow and TS // Right now they are partially treated as UnionTypeAnnotation // UnionTypeAnnotation: 'UnionTypeAnnotation', diff --git a/packages/react-native-codegen/src/parsers/errors.js b/packages/react-native-codegen/src/parsers/errors.js index fececdedfc4..7e0b2277930 100644 --- a/packages/react-native-codegen/src/parsers/errors.js +++ b/packages/react-native-codegen/src/parsers/errors.js @@ -128,7 +128,7 @@ class UnsupportedModulePropertyParserError extends ParserError { } class UnsupportedTypeAnnotationParserError extends ParserError { - +typeAnnotationType: string; + readonly typeAnnotationType: string; constructor( nativeModuleName: string, typeAnnotation: $FlowFixMe, diff --git a/packages/react-native-codegen/src/parsers/flow/components/componentsUtils.js b/packages/react-native-codegen/src/parsers/flow/components/componentsUtils.js index c0cdfab9a7a..b15150f9f57 100644 --- a/packages/react-native-codegen/src/parsers/flow/components/componentsUtils.js +++ b/packages/react-native-codegen/src/parsers/flow/components/componentsUtils.js @@ -17,7 +17,7 @@ const {verifyPropNotAlreadyDefined} = require('../../parsers-commons'); const {getValueFromTypes} = require('../utils.js'); // $FlowFixMe[unsupported-variance-annotation] -function getTypeAnnotationForArray<+T>( +function getTypeAnnotationForArray( name: string, typeAnnotation: $FlowFixMe, defaultValue: $FlowFixMe | null, @@ -219,7 +219,7 @@ function flattenProperties( } // $FlowFixMe[unsupported-variance-annotation] -function getTypeAnnotation<+T>( +function getTypeAnnotation( name: string, annotation: $FlowFixMe | ASTNode, defaultValue: $FlowFixMe | null, diff --git a/packages/react-native-codegen/src/parsers/flow/modules/__test_fixtures__/failures.js b/packages/react-native-codegen/src/parsers/flow/modules/__test_fixtures__/failures.js index 61d94da57e8..515098be546 100644 --- a/packages/react-native-codegen/src/parsers/flow/modules/__test_fixtures__/failures.js +++ b/packages/react-native-codegen/src/parsers/flow/modules/__test_fixtures__/failures.js @@ -322,6 +322,34 @@ export interface Spec extends TurboModule { export default TurboModuleRegistry.getEnforcing('MixedValuesEnumNativeModule'); `; +const NATIVE_MODULES_WITH_ARRAY_BUFFER_IN_OBJECT_PROPERTY = ` +/** + * 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. + * + * @flow strict-local + * @format + */ + +'use strict'; + +import type {TurboModule} from '../RCTExport'; +import * as TurboModuleRegistry from '../TurboModuleRegistry'; + +type Row = { + buf: ArrayBuffer, +}; + +export interface Spec extends TurboModule { + +useRow: (row: Row) => void; +} + +export default TurboModuleRegistry.getEnforcing('SampleTurboModule'); + +`; + module.exports = { NATIVE_MODULES_WITH_READ_ONLY_OBJECT_NO_TYPE_FOR_CONTENT, NATIVE_MODULES_WITH_UNNAMED_PARAMS, @@ -335,4 +363,5 @@ module.exports = { MIXED_VALUES_ENUM_NATIVE_MODULE, NUMERIC_VALUES_ENUM_NATIVE_MODULE, MAP_WITH_EXTRA_KEYS_NATIVE_MODULE, + NATIVE_MODULES_WITH_ARRAY_BUFFER_IN_OBJECT_PROPERTY, }; diff --git a/packages/react-native-codegen/src/parsers/flow/modules/__test_fixtures__/fixtures.js b/packages/react-native-codegen/src/parsers/flow/modules/__test_fixtures__/fixtures.js index 9edf171d7b7..f95c067d3b0 100644 --- a/packages/react-native-codegen/src/parsers/flow/modules/__test_fixtures__/fixtures.js +++ b/packages/react-native-codegen/src/parsers/flow/modules/__test_fixtures__/fixtures.js @@ -985,6 +985,33 @@ export default TurboModuleRegistry.getEnforcing('SampleTurboModule'); `; +const NATIVE_MODULE_WITH_ARRAY_BUFFER = ` +/** + * 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. + * + * @flow strict-local + * @format + */ + +'use strict'; + +import type {TurboModule} from '../RCTExport'; +import * as TurboModuleRegistry from '../TurboModuleRegistry'; + +export interface Spec extends TurboModule { + +getArrayBuffer: () => ArrayBuffer; + +voidArrayBuffer: (arg: ArrayBuffer) => void; + +voidNullableArrayBuffer: (arg: ?ArrayBuffer) => void; + +promiseArrayBuffer: () => Promise; +} + +export default TurboModuleRegistry.getEnforcing('SampleTurboModule'); + +`; + module.exports = { NATIVE_MODULE_WITH_OBJECT_WITH_OBJECT_DEFINED_IN_FILE_AS_PROPERTY, NATIVE_MODULE_WITH_ARRAY_WITH_UNION_AND_TOUPLE, @@ -1009,6 +1036,7 @@ module.exports = { NATIVE_MODULE_WITH_UNION_RETURN_TYPES, NATIVE_MODULE_WITH_EVENT_EMITTERS, NATIVE_MODULE_WITH_OPAQUE_TYPES, + NATIVE_MODULE_WITH_ARRAY_BUFFER, EMPTY_NATIVE_MODULE, ANDROID_ONLY_NATIVE_MODULE, IOS_ONLY_NATIVE_MODULE, diff --git a/packages/react-native-codegen/src/parsers/flow/modules/__tests__/__snapshots__/module-parser-snapshot-test.js.snap b/packages/react-native-codegen/src/parsers/flow/modules/__tests__/__snapshots__/module-parser-snapshot-test.js.snap index a8e8707e886..8e462cdf645 100644 --- a/packages/react-native-codegen/src/parsers/flow/modules/__tests__/__snapshots__/module-parser-snapshot-test.js.snap +++ b/packages/react-native-codegen/src/parsers/flow/modules/__tests__/__snapshots__/module-parser-snapshot-test.js.snap @@ -13,6 +13,8 @@ export enum SomeEnum { ^" `; +exports[`RN Codegen Flow Parser Fails with error message NATIVE_MODULES_WITH_ARRAY_BUFFER_IN_OBJECT_PROPERTY 1`] = `"Module NativeSampleTurboModule: Object property '[object Object]' cannot have type 'ArrayBuffer'."`; + exports[`RN Codegen Flow Parser Fails with error message NATIVE_MODULES_WITH_ARRAY_WITH_NO_TYPE_FOR_CONTENT 1`] = `"Module NativeSampleTurboModule: Generic 'Array' must have type parameters."`; exports[`RN Codegen Flow Parser Fails with error message NATIVE_MODULES_WITH_ARRAY_WITH_NO_TYPE_FOR_CONTENT_AS_PARAM 1`] = `"Module NativeSampleTurboModule: Generic 'Array' must have type parameters."`; @@ -1110,6 +1112,90 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_ALIASES }" `; +exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_ARRAY_BUFFER 1`] = ` +"{ + 'modules': { + 'NativeSampleTurboModule': { + 'type': 'NativeModule', + 'aliasMap': {}, + 'enumMap': {}, + 'spec': { + 'eventEmitters': [], + 'methods': [ + { + 'name': 'getArrayBuffer', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'ArrayBufferTypeAnnotation' + }, + 'params': [] + } + }, + { + 'name': 'voidArrayBuffer', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'VoidTypeAnnotation' + }, + 'params': [ + { + 'name': 'arg', + 'optional': false, + 'typeAnnotation': { + 'type': 'ArrayBufferTypeAnnotation' + } + } + ] + } + }, + { + 'name': 'voidNullableArrayBuffer', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'VoidTypeAnnotation' + }, + 'params': [ + { + 'name': 'arg', + 'optional': false, + 'typeAnnotation': { + 'type': 'NullableTypeAnnotation', + 'typeAnnotation': { + 'type': 'ArrayBufferTypeAnnotation' + } + } + } + ] + } + }, + { + 'name': 'promiseArrayBuffer', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'PromiseTypeAnnotation', + 'elementType': { + 'type': 'ArrayBufferTypeAnnotation' + } + }, + 'params': [] + } + } + ] + }, + 'moduleName': 'SampleTurboModule' + } + } +}" +`; + exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_ARRAY_WITH_ALIAS 1`] = ` "{ 'modules': { diff --git a/packages/react-native-codegen/src/parsers/flow/parseFlowAndThrowErrors.js b/packages/react-native-codegen/src/parsers/flow/parseFlowAndThrowErrors.js index 7e0f61bd3e0..d5cd9c930a5 100644 --- a/packages/react-native-codegen/src/parsers/flow/parseFlowAndThrowErrors.js +++ b/packages/react-native-codegen/src/parsers/flow/parseFlowAndThrowErrors.js @@ -12,13 +12,15 @@ import type {Program as ESTreeProgram} from 'hermes-estree'; -const hermesParser = require('hermes-parser'); +// $FlowFixMe[unclear-type] Avoid expanding hermes-parser's large overloaded module type. +const hermesParser: any = require('hermes-parser'); function parseFlowAndThrowErrors( code: string, options: Readonly<{filename?: ?string}> = {}, ): ESTreeProgram { - let ast; + // $FlowFixMe[unclear-type] The function's return type keeps this dynamic boundary contained. + let ast: any; try { ast = hermesParser.parse(code, { // Produce an ESTree-compliant AST diff --git a/packages/react-native-codegen/src/parsers/parsers-commons.js b/packages/react-native-codegen/src/parsers/parsers-commons.js index c5b39e69de6..330e52fe8c7 100644 --- a/packages/react-native-codegen/src/parsers/parsers-commons.js +++ b/packages/react-native-codegen/src/parsers/parsers-commons.js @@ -107,7 +107,7 @@ function wrapModuleSchema( } // $FlowFixMe[unsupported-variance-annotation] -function unwrapNullable<+T extends NativeModuleTypeAnnotation>( +function unwrapNullable( x: Nullable, ): [T, boolean] { if (x.type === 'NullableTypeAnnotation') { @@ -118,7 +118,7 @@ function unwrapNullable<+T extends NativeModuleTypeAnnotation>( } // $FlowFixMe[unsupported-variance-annotation] -function wrapNullable<+T extends NativeModuleTypeAnnotation>( +function wrapNullable( nullable: boolean, typeAnnotation: T, ): Nullable { @@ -307,7 +307,8 @@ function parseObjectProperty( if ( (propertyTypeAnnotation.type === 'FunctionTypeAnnotation' && !cxxOnly) || propertyTypeAnnotation.type === 'PromiseTypeAnnotation' || - propertyTypeAnnotation.type === 'VoidTypeAnnotation' + propertyTypeAnnotation.type === 'VoidTypeAnnotation' || + propertyTypeAnnotation.type === 'ArrayBufferTypeAnnotation' ) { throwIfPropertyValueTypeIsUnsupported( hasteModuleName, diff --git a/packages/react-native-codegen/src/parsers/parsers-primitives.js b/packages/react-native-codegen/src/parsers/parsers-primitives.js index 3243a9a95cf..b9e62496435 100644 --- a/packages/react-native-codegen/src/parsers/parsers-primitives.js +++ b/packages/react-native-codegen/src/parsers/parsers-primitives.js @@ -11,6 +11,7 @@ 'use strict'; import type { + ArrayBufferTypeAnnotation, BooleanLiteralTypeAnnotation, BooleanTypeAnnotation, DoubleTypeAnnotation, @@ -127,6 +128,14 @@ function emitVoid(nullable: boolean): Nullable { }); } +function emitArrayBuffer( + nullable: boolean, +): Nullable { + return wrapNullable(nullable, { + type: 'ArrayBufferTypeAnnotation', + }); +} + function emitStringish(nullable: boolean): Nullable { return wrapNullable(nullable, { type: 'StringTypeAnnotation', @@ -351,20 +360,18 @@ function emitPromise( }, }); } else { + let elementTypeResult; try { - return wrapNullable(nullable, { - type: 'PromiseTypeAnnotation', - elementType: translateTypeAnnotation( - hasteModuleName, - typeAnnotation.typeParameters.params[0], - types, - aliasMap, - enumMap, - tryParse, - cxxOnly, - parser, - ), - }); + elementTypeResult = translateTypeAnnotation( + hasteModuleName, + typeAnnotation.typeParameters.params[0], + types, + aliasMap, + enumMap, + tryParse, + cxxOnly, + parser, + ); } catch { return wrapNullable(nullable, { type: 'PromiseTypeAnnotation', @@ -373,6 +380,11 @@ function emitPromise( }, }); } + + return wrapNullable(nullable, { + type: 'PromiseTypeAnnotation', + elementType: elementTypeResult, + }); } } @@ -666,6 +678,7 @@ function emitCommonTypes( UnsafeMixed: cxxOnly ? emitMixed : emitGenericObject, unknown: cxxOnly ? emitMixed : emitGenericObject, UnknownTypeAnnotation: cxxOnly ? emitMixed : emitGenericObject, + ArrayBuffer: emitArrayBuffer, }; const typeAnnotationName = parser.convertKeywordToTypeAnnotation( @@ -764,6 +777,7 @@ function emitUnionProp( } module.exports = { + emitArrayBuffer, emitArrayType, emitBoolean, emitBooleanLiteral, diff --git a/packages/react-native-codegen/src/parsers/typescript/modules/__test_fixtures__/failures.js b/packages/react-native-codegen/src/parsers/typescript/modules/__test_fixtures__/failures.js index 8e42067ec16..a59c138df1d 100644 --- a/packages/react-native-codegen/src/parsers/typescript/modules/__test_fixtures__/failures.js +++ b/packages/react-native-codegen/src/parsers/typescript/modules/__test_fixtures__/failures.js @@ -261,6 +261,31 @@ export interface Spec extends TurboModule { export default TurboModuleRegistry.getEnforcing('MapWithExtraKeysNativeModule'); `; +const NATIVE_MODULES_WITH_ARRAY_BUFFER_IN_OBJECT_PROPERTY = ` +/** + * 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. + * + * @format + */ + +import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport'; +import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; + +type Row = { + buf: ArrayBuffer; +}; + +export interface Spec extends TurboModule { + readonly useRow: (row: Row) => void; +} + +export default TurboModuleRegistry.getEnforcing('SampleTurboModule'); + +`; + module.exports = { NATIVE_MODULES_WITH_UNNAMED_PARAMS, NATIVE_MODULES_WITH_PROMISE_WITHOUT_TYPE, @@ -273,4 +298,5 @@ module.exports = { MIXED_VALUES_ENUM_NATIVE_MODULE, NUMERIC_VALUES_ENUM_NATIVE_MODULE, MAP_WITH_EXTRA_KEYS_NATIVE_MODULE, + NATIVE_MODULES_WITH_ARRAY_BUFFER_IN_OBJECT_PROPERTY, }; diff --git a/packages/react-native-codegen/src/parsers/typescript/modules/__test_fixtures__/fixtures.js b/packages/react-native-codegen/src/parsers/typescript/modules/__test_fixtures__/fixtures.js index 9f10cc5b5aa..8359f02909b 100644 --- a/packages/react-native-codegen/src/parsers/typescript/modules/__test_fixtures__/fixtures.js +++ b/packages/react-native-codegen/src/parsers/typescript/modules/__test_fixtures__/fixtures.js @@ -995,7 +995,32 @@ export default TurboModuleRegistry.getEnforcing('SampleTurboModule'); `; +const NATIVE_MODULE_WITH_ARRAY_BUFFER = ` +/** + * 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. + * + * @format + */ + +import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport'; +import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; + +export interface Spec extends TurboModule { + readonly getArrayBuffer: () => ArrayBuffer; + readonly voidArrayBuffer: (arg: ArrayBuffer) => void; + readonly voidNullableArrayBuffer: (arg: ArrayBuffer | null) => void; + readonly promiseArrayBuffer: () => Promise; +} + +export default TurboModuleRegistry.getEnforcing('SampleTurboModule'); + +`; + module.exports = { + NATIVE_MODULE_WITH_ARRAY_BUFFER, NATIVE_MODULE_WITH_OBJECT_WITH_OBJECT_DEFINED_IN_FILE_AS_PROPERTY, NATIVE_MODULE_WITH_ARRAY_WITH_UNION_AND_TOUPLE, NATIVE_MODULE_WITH_ARRAY2_WITH_UNION_AND_TOUPLE, diff --git a/packages/react-native-codegen/src/parsers/typescript/modules/__tests__/__snapshots__/typescript-module-parser-snapshot-test.js.snap b/packages/react-native-codegen/src/parsers/typescript/modules/__tests__/__snapshots__/typescript-module-parser-snapshot-test.js.snap index 3f85279cfa6..c46a47e6db1 100644 --- a/packages/react-native-codegen/src/parsers/typescript/modules/__tests__/__snapshots__/typescript-module-parser-snapshot-test.js.snap +++ b/packages/react-native-codegen/src/parsers/typescript/modules/__tests__/__snapshots__/typescript-module-parser-snapshot-test.js.snap @@ -6,6 +6,8 @@ exports[`RN Codegen TypeScript Parser Fails with error message MAP_WITH_EXTRA_KE exports[`RN Codegen TypeScript Parser Fails with error message MIXED_VALUES_ENUM_NATIVE_MODULE 1`] = `"Module NativeSampleTurboModule: Failed parsing the enum SomeEnum in NativeSampleTurboModule with the error: Enum values can not be mixed. They all must be either blank, number, or string values."`; +exports[`RN Codegen TypeScript Parser Fails with error message NATIVE_MODULES_WITH_ARRAY_BUFFER_IN_OBJECT_PROPERTY 1`] = `"Module NativeSampleTurboModule: Object property '[object Object]' cannot have type 'ArrayBuffer'."`; + exports[`RN Codegen TypeScript Parser Fails with error message NATIVE_MODULES_WITH_ARRAY_WITH_NO_TYPE_FOR_CONTENT 1`] = `"Module NativeSampleTurboModule: Generic 'Array' must have type parameters."`; exports[`RN Codegen TypeScript Parser Fails with error message NATIVE_MODULES_WITH_ARRAY_WITH_NO_TYPE_FOR_CONTENT_AS_PARAM 1`] = `"Module NativeSampleTurboModule: Generic 'Array' must have type parameters."`; @@ -1097,6 +1099,90 @@ exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_AL }" `; +exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_ARRAY_BUFFER 1`] = ` +"{ + 'modules': { + 'NativeSampleTurboModule': { + 'type': 'NativeModule', + 'aliasMap': {}, + 'enumMap': {}, + 'spec': { + 'eventEmitters': [], + 'methods': [ + { + 'name': 'getArrayBuffer', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'ArrayBufferTypeAnnotation' + }, + 'params': [] + } + }, + { + 'name': 'voidArrayBuffer', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'VoidTypeAnnotation' + }, + 'params': [ + { + 'name': 'arg', + 'optional': false, + 'typeAnnotation': { + 'type': 'ArrayBufferTypeAnnotation' + } + } + ] + } + }, + { + 'name': 'voidNullableArrayBuffer', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'VoidTypeAnnotation' + }, + 'params': [ + { + 'name': 'arg', + 'optional': false, + 'typeAnnotation': { + 'type': 'NullableTypeAnnotation', + 'typeAnnotation': { + 'type': 'ArrayBufferTypeAnnotation' + } + } + } + ] + } + }, + { + 'name': 'promiseArrayBuffer', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'PromiseTypeAnnotation', + 'elementType': { + 'type': 'ArrayBufferTypeAnnotation' + } + }, + 'params': [] + } + } + ] + }, + 'moduleName': 'SampleTurboModule' + } + } +}" +`; + exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_ARRAY_WITH_ALIAS 1`] = ` "{ 'modules': { diff --git a/packages/react-native-compatibility-check/README.md b/packages/react-native-compatibility-check/README.md index 0e727aaf885..de19ae3892e 100644 --- a/packages/react-native-compatibility-check/README.md +++ b/packages/react-native-compatibility-check/README.md @@ -1,4 +1,9 @@ -# **React Native compatibility-check** +# @react-native/compatibility-check + +[![npm]](https://www.npmjs.com/package/@react-native/compatibility-check) [![npm downloads]](https://www.npmjs.com/package/@react-native/compatibility-check) + +[npm]: https://img.shields.io/npm/v/@react-native/compatibility-check.svg?color=blue +[npm downloads]: https://img.shields.io/npm/dm/@react-native/compatibility-check.svg Status: Experimental (stage 1) diff --git a/packages/react-native-compatibility-check/package.json b/packages/react-native-compatibility-check/package.json index 5e02ea2847f..d2be45f46de 100644 --- a/packages/react-native-compatibility-check/package.json +++ b/packages/react-native-compatibility-check/package.json @@ -1,14 +1,14 @@ { "name": "@react-native/compatibility-check", - "version": "0.86.0-main", + "version": "0.87.0-main", "description": "Check a React Native app's boundary between JS and Native for incompatibilities", "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/facebook/react-native.git", + "url": "git+https://github.com/react/react-native.git", "directory": "packages/react-native-compatibility-check" }, - "homepage": "https://github.com/facebook/react-native/tree/HEAD/packages/react-native-compatibility-check#readme", + "homepage": "https://github.com/react/react-native/tree/HEAD/packages/react-native-compatibility-check#readme", "keywords": [ "boundary", "crashes", @@ -17,9 +17,9 @@ "tools", "react-native" ], - "bugs": "https://github.com/facebook/react-native/issues", + "bugs": "https://github.com/react/react-native/issues", "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" }, "exports": { ".": "./src/index.js", diff --git a/packages/react-native-compatibility-check/src/ErrorFormatting.js b/packages/react-native-compatibility-check/src/ErrorFormatting.js index 8f7fb95770d..640aa26a83e 100644 --- a/packages/react-native-compatibility-check/src/ErrorFormatting.js +++ b/packages/react-native-compatibility-check/src/ErrorFormatting.js @@ -100,6 +100,8 @@ function formatTypeAnnotation(annotation: CompleteTypeAnnotation): string { switch (annotation.type) { case 'AnyTypeAnnotation': return 'any'; + case 'ArrayBufferTypeAnnotation': + return 'ArrayBuffer'; case 'ArrayTypeAnnotation': return 'Array<' + formatTypeAnnotation(annotation.elementType) + '>'; case 'BooleanTypeAnnotation': diff --git a/packages/react-native-compatibility-check/src/SortTypeAnnotations.js b/packages/react-native-compatibility-check/src/SortTypeAnnotations.js index 81b2e425f86..e529039c431 100644 --- a/packages/react-native-compatibility-check/src/SortTypeAnnotations.js +++ b/packages/react-native-compatibility-check/src/SortTypeAnnotations.js @@ -46,6 +46,9 @@ export function compareTypeAnnotationForSorting( switch (typeA.type) { case 'AnyTypeAnnotation': return 0; + case 'ArrayBufferTypeAnnotation': + invariant(typeB.type === 'ArrayBufferTypeAnnotation', EQUALITY_MSG); + return originalPositionA - originalPositionB; case 'ArrayTypeAnnotation': invariant(typeB.type === 'ArrayTypeAnnotation', EQUALITY_MSG); return compareTypeAnnotationForSorting( @@ -281,6 +284,8 @@ function typeAnnotationArbitraryOrder(annotation: CompleteTypeAnnotation) { return 28; case 'UnionTypeAnnotation': return 30; + case 'ArrayBufferTypeAnnotation': + return 31; default: annotation.type as empty; return -1; diff --git a/packages/react-native-compatibility-check/src/TypeDiffing.js b/packages/react-native-compatibility-check/src/TypeDiffing.js index bdc081aeeef..090f2a716b8 100644 --- a/packages/react-native-compatibility-check/src/TypeDiffing.js +++ b/packages/react-native-compatibility-check/src/TypeDiffing.js @@ -176,6 +176,7 @@ export function compareTypeAnnotation( switch (newerAnnotation.type) { case 'AnyTypeAnnotation': case 'MixedTypeAnnotation': + case 'ArrayBufferTypeAnnotation': case 'DoubleTypeAnnotation': case 'FloatTypeAnnotation': case 'Int32TypeAnnotation': diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-component-all-types/NativeComponent.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-component-all-types/NativeComponent.js.flow index b5c9ab0208b..5a9837f0cd4 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-component-all-types/NativeComponent.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-component-all-types/NativeComponent.js.flow @@ -91,53 +91,83 @@ type CustomObj = {foo: string, bar: number}; type CustomObj2 = {a: Int32, b: string}; interface NativeCommands { - +methodInt: (viewRef: React.ElementRef, a: Int32) => void; - +methodIntString: ( + readonly methodInt: (viewRef: React.ElementRef, a: Int32) => void; + readonly methodIntString: ( viewRef: React.ElementRef, a: Int32, b: string, ) => void; - +methodString: (viewRef: React.ElementRef, a: string) => void; - +methodBool: (viewRef: React.ElementRef, a: boolean) => void; - +methodFloat: (viewRef: React.ElementRef, a: Float) => void; - +methodDouble: (viewRef: React.ElementRef, a: Double) => void; + readonly methodString: ( + viewRef: React.ElementRef, + a: string, + ) => void; + readonly methodBool: ( + viewRef: React.ElementRef, + a: boolean, + ) => void; + readonly methodFloat: ( + viewRef: React.ElementRef, + a: Float, + ) => void; + readonly methodDouble: ( + viewRef: React.ElementRef, + a: Double, + ) => void; - +methodIntAlias: (viewRef: React.ElementRef, a: Int) => void; - +methodStringAlias: (viewRef: React.ElementRef, a: Str) => void; - +methodBoolAlias: (viewRef: React.ElementRef, a: Bool) => void; - +methodFloatAlias: (viewRef: React.ElementRef, a: Fl) => void; - +methodDoubleAlias: (viewRef: React.ElementRef, a: Dbl) => void; + readonly methodIntAlias: ( + viewRef: React.ElementRef, + a: Int, + ) => void; + readonly methodStringAlias: ( + viewRef: React.ElementRef, + a: Str, + ) => void; + readonly methodBoolAlias: ( + viewRef: React.ElementRef, + a: Bool, + ) => void; + readonly methodFloatAlias: ( + viewRef: React.ElementRef, + a: Fl, + ) => void; + readonly methodDoubleAlias: ( + viewRef: React.ElementRef, + a: Dbl, + ) => void; - +methodIntArray: ( + readonly methodIntArray: ( viewRef: React.ElementRef, a: Array, ) => void; - +methodStringArray: ( + readonly methodStringArray: ( viewRef: React.ElementRef, a: Array, ) => void; - +methodBoolArray: ( + readonly methodBoolArray: ( viewRef: React.ElementRef, a: Array, ) => void; - +methodFloatArray: ( + readonly methodFloatArray: ( viewRef: React.ElementRef, a: Array, ) => void; - +methodDoubleArray: ( + readonly methodDoubleArray: ( viewRef: React.ElementRef, a: Array, ) => void; - +methodCustomObjArray: ( + readonly methodCustomObjArray: ( viewRef: React.ElementRef, a: Array, ) => void; - +methodCustomObj2Array: ( + readonly methodCustomObj2Array: ( viewRef: React.ElementRef, a: Array, ) => void; - +methodRootTag: (viewRef: React.ElementRef, a: RootTag) => void; + readonly methodRootTag: ( + viewRef: React.ElementRef, + a: RootTag, + ) => void; } export const Commands: NativeCommands = codegenNativeCommands({ diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-component-with-command-all-basic-types-arrays/NativeComponent.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-component-with-command-all-basic-types-arrays/NativeComponent.js.flow index 6aab7e7baa5..a1943b0e7c3 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-component-with-command-all-basic-types-arrays/NativeComponent.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-component-with-command-all-basic-types-arrays/NativeComponent.js.flow @@ -26,20 +26,23 @@ type NativeProps = Readonly<{ type NativeType = HostComponent; interface NativeCommands { - +methodInt: (viewRef: React.ElementRef, a: Array) => void; - +methodString: ( + readonly methodInt: ( + viewRef: React.ElementRef, + a: Array, + ) => void; + readonly methodString: ( viewRef: React.ElementRef, a: Array, ) => void; - +methodBool: ( + readonly methodBool: ( viewRef: React.ElementRef, a: Array, ) => void; - +methodFloat: ( + readonly methodFloat: ( viewRef: React.ElementRef, a: Array, ) => void; - +methodDouble: ( + readonly methodDouble: ( viewRef: React.ElementRef, a: Array, ) => void; diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-component-with-command-all-basic-types/NativeComponent.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-component-with-command-all-basic-types/NativeComponent.js.flow index cc0c2cd9857..a44068aa05b 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-component-with-command-all-basic-types/NativeComponent.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-component-with-command-all-basic-types/NativeComponent.js.flow @@ -26,11 +26,23 @@ type NativeProps = Readonly<{ type NativeType = HostComponent; interface NativeCommands { - +methodInt: (viewRef: React.ElementRef, a: Int32) => void; - +methodString: (viewRef: React.ElementRef, a: string) => void; - +methodBool: (viewRef: React.ElementRef, a: boolean) => void; - +methodFloat: (viewRef: React.ElementRef, a: Float) => void; - +methodDouble: (viewRef: React.ElementRef, a: Double) => void; + readonly methodInt: (viewRef: React.ElementRef, a: Int32) => void; + readonly methodString: ( + viewRef: React.ElementRef, + a: string, + ) => void; + readonly methodBool: ( + viewRef: React.ElementRef, + a: boolean, + ) => void; + readonly methodFloat: ( + viewRef: React.ElementRef, + a: Float, + ) => void; + readonly methodDouble: ( + viewRef: React.ElementRef, + a: Double, + ) => void; } export const Commands: NativeCommands = codegenNativeCommands({ diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-component-with-command-changed/NativeComponent.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-component-with-command-changed/NativeComponent.js.flow index f3eac473837..3437f638115 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-component-with-command-changed/NativeComponent.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-component-with-command-changed/NativeComponent.js.flow @@ -21,7 +21,10 @@ type NativeProps = Readonly<{ type NativeType = HostComponent; interface NativeCommands { - +methodInt: (viewRef: React.ElementRef, a: string) => void; + readonly methodInt: ( + viewRef: React.ElementRef, + a: string, + ) => void; } export const Commands: NativeCommands = codegenNativeCommands({ diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-component-with-command-extra-arg/NativeComponent.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-component-with-command-extra-arg/NativeComponent.js.flow index 7006bb98332..189ec583583 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-component-with-command-extra-arg/NativeComponent.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-component-with-command-extra-arg/NativeComponent.js.flow @@ -22,7 +22,7 @@ type NativeProps = Readonly<{ type NativeType = HostComponent; interface NativeCommands { - +methodInt: ( + readonly methodInt: ( viewRef: React.ElementRef, a: Int32, b: string, diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-component-with-command-extra-command/NativeComponent.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-component-with-command-extra-command/NativeComponent.js.flow index a96a01407cb..39d3ba4aa93 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-component-with-command-extra-command/NativeComponent.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-component-with-command-extra-command/NativeComponent.js.flow @@ -22,8 +22,11 @@ type NativeProps = Readonly<{ type NativeType = HostComponent; interface NativeCommands { - +methodInt: (viewRef: React.ElementRef, a: Int32) => void; - +methodString: (viewRef: React.ElementRef, a: string) => void; + readonly methodInt: (viewRef: React.ElementRef, a: Int32) => void; + readonly methodString: ( + viewRef: React.ElementRef, + a: string, + ) => void; } export const Commands: NativeCommands = codegenNativeCommands({ diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-component-with-command/NativeComponent.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-component-with-command/NativeComponent.js.flow index a50fa6d6d8c..8767e91f5f8 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-component-with-command/NativeComponent.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-component-with-command/NativeComponent.js.flow @@ -22,7 +22,7 @@ type NativeProps = Readonly<{ type NativeType = HostComponent; interface NativeCommands { - +methodInt: (viewRef: React.ElementRef, a: Int32) => void; + readonly methodInt: (viewRef: React.ElementRef, a: Int32) => void; } export const Commands: NativeCommands = codegenNativeCommands({ diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-before-after-types-removed/NativeModuleBeforeAfterTypes.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-before-after-types-removed/NativeModuleBeforeAfterTypes.js.flow index 181a16394c6..ef6d31fd0d5 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-before-after-types-removed/NativeModuleBeforeAfterTypes.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-before-after-types-removed/NativeModuleBeforeAfterTypes.js.flow @@ -21,11 +21,11 @@ type BeforeMatchingGeneric = {||}; type AfterMatchingGeneric = {||}; export interface Spec extends TurboModule { - +simpleObject: (a: SimpleObject) => void; - +beforeMismatchGeneric: (a: BeforeMismatchGeneric) => void; + readonly simpleObject: (a: SimpleObject) => void; + readonly beforeMismatchGeneric: (a: BeforeMismatchGeneric) => void; - +beforeMatchingGeneric: (a: BeforeMatchingGeneric) => void; - +afterMatchingGeneric: (a: AfterMatchingGeneric) => void; + readonly beforeMatchingGeneric: (a: BeforeMatchingGeneric) => void; + readonly afterMatchingGeneric: (a: AfterMatchingGeneric) => void; } TurboModuleRegistry.getEnforcing( diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-before-after-types-type-changed/NativeModuleBeforeAfterTypes.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-before-after-types-type-changed/NativeModuleBeforeAfterTypes.js.flow index 8f7de8386a6..14d6a70df8d 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-before-after-types-type-changed/NativeModuleBeforeAfterTypes.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-before-after-types-type-changed/NativeModuleBeforeAfterTypes.js.flow @@ -44,32 +44,32 @@ type SimpleArray = Array; type SimpleArrayChange = Array; export interface Spec extends TurboModule { - +exampleFunction: (a: SimpleObject) => void; - +simpleObject: (a: SimpleObject) => void; - +beforeMismatchGeneric: (a: BeforeMismatchGeneric) => void; - +afterMismatchGeneric: (a: AfterMismatchGeneric) => void; - - +beforeMatchingGeneric: (a: BeforeMatchingGeneric) => void; - +afterMatchingGeneric: (a: AfterMatchingGeneric) => void; - - +booleanType: (a: BooleanType) => void; - +stringType: (a: StringType) => void; - - +simpleFunction: (a: string) => string; - +simpleFunction2: (a: string) => number; - +simpleFunction3: (foo: string) => string; - +simpleFunction4: (a: number) => string; - - +simpleUnion: (a: SimpleUnion) => void; - +simpleUnionLonger: (a: SimpleUnionLonger) => void; - +simpleUnionOrderChanged: (a: SimpleUnionOrderChanged) => void; - +simpleUnion2: (a: SimpleUnion2) => void; - +simpleUnion2OrderChanged: (a: SimpleUnion2OrderChanged) => void; - +simpleUnion3: (a: SimpleUnion3) => void; - +simpleUnion4: (a: SimpleUnion4) => void; - - +simpleArray: (a: SimpleArray) => void; - +simpleArrayChange: (a: SimpleArrayChange) => void; + readonly exampleFunction: (a: SimpleObject) => void; + readonly simpleObject: (a: SimpleObject) => void; + readonly beforeMismatchGeneric: (a: BeforeMismatchGeneric) => void; + readonly afterMismatchGeneric: (a: AfterMismatchGeneric) => void; + + readonly beforeMatchingGeneric: (a: BeforeMatchingGeneric) => void; + readonly afterMatchingGeneric: (a: AfterMatchingGeneric) => void; + + readonly booleanType: (a: BooleanType) => void; + readonly stringType: (a: StringType) => void; + + readonly simpleFunction: (a: string) => string; + readonly simpleFunction2: (a: string) => number; + readonly simpleFunction3: (foo: string) => string; + readonly simpleFunction4: (a: number) => string; + + readonly simpleUnion: (a: SimpleUnion) => void; + readonly simpleUnionLonger: (a: SimpleUnionLonger) => void; + readonly simpleUnionOrderChanged: (a: SimpleUnionOrderChanged) => void; + readonly simpleUnion2: (a: SimpleUnion2) => void; + readonly simpleUnion2OrderChanged: (a: SimpleUnion2OrderChanged) => void; + readonly simpleUnion3: (a: SimpleUnion3) => void; + readonly simpleUnion4: (a: SimpleUnion4) => void; + + readonly simpleArray: (a: SimpleArray) => void; + readonly simpleArrayChange: (a: SimpleArrayChange) => void; } TurboModuleRegistry.getEnforcing( diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-before-after-types/NativeModuleBeforeAfterTypes.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-before-after-types/NativeModuleBeforeAfterTypes.js.flow index 3f4c570cf25..90e775dbc7c 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-before-after-types/NativeModuleBeforeAfterTypes.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-before-after-types/NativeModuleBeforeAfterTypes.js.flow @@ -21,6 +21,7 @@ type AfterMismatchGeneric = {}; type BeforeMatchingGeneric = {}; type AfterMatchingGeneric = {}; +type ArrayBufferType = ArrayBuffer; type BooleanType = boolean; type StringType = string; @@ -37,32 +38,33 @@ type SimpleArrayChange = Array; export interface Spec extends TurboModule { - +exampleFunction: (a: SimpleObject) => void; - +simpleObject: (a: SimpleObject) => void; - +beforeMismatchGeneric: (a: BeforeMismatchGeneric) => void; - +afterMismatchGeneric: (a: AfterMismatchGeneric) => void; - - +beforeMatchingGeneric: (a: BeforeMatchingGeneric) => void; - +afterMatchingGeneric: (a: AfterMatchingGeneric) => void; - - +booleanType: (a: BooleanType) => void; - +stringType: (a: StringType) => void; - - +simpleFunction: (a: string) => string; - +simpleFunction2: (a: string) => number; - +simpleFunction3: (foo: string) => string; - +simpleFunction4: (a: number) => string; - - +simpleUnion: (a: SimpleUnion) => void; - +simpleUnionLonger: (a: SimpleUnionLonger) => void; - +simpleUnionOrderChanged: (a: SimpleUnionOrderChanged) => void; - +simpleUnion2: (a: SimpleUnion2) => void; - +simpleUnion2OrderChanged: (a: SimpleUnion2OrderChanged) => void; - +simpleUnion3: (a: SimpleUnion3) => void; - +simpleUnion4: (a: SimpleUnion4) => void; - - +simpleArray: (a: SimpleArray) => void; - +simpleArrayChange: (a: SimpleArrayChange) => void; + readonly exampleFunction: (a: SimpleObject) => void; + readonly simpleObject: (a: SimpleObject) => void; + readonly beforeMismatchGeneric: (a: BeforeMismatchGeneric) => void; + readonly afterMismatchGeneric: (a: AfterMismatchGeneric) => void; + + readonly beforeMatchingGeneric: (a: BeforeMatchingGeneric) => void; + readonly afterMatchingGeneric: (a: AfterMatchingGeneric) => void; + + readonly arrayBufferType: (a: ArrayBufferType) => void; + readonly booleanType: (a: BooleanType) => void; + readonly stringType: (a: StringType) => void; + + readonly simpleFunction: (a: string) => string; + readonly simpleFunction2: (a: string) => number; + readonly simpleFunction3: (foo: string) => string; + readonly simpleFunction4: (a: number) => string; + + readonly simpleUnion: (a: SimpleUnion) => void; + readonly simpleUnionLonger: (a: SimpleUnionLonger) => void; + readonly simpleUnionOrderChanged: (a: SimpleUnionOrderChanged) => void; + readonly simpleUnion2: (a: SimpleUnion2) => void; + readonly simpleUnion2OrderChanged: (a: SimpleUnion2OrderChanged) => void; + readonly simpleUnion3: (a: SimpleUnion3) => void; + readonly simpleUnion4: (a: SimpleUnion4) => void; + + readonly simpleArray: (a: SimpleArray) => void; + readonly simpleArrayChange: (a: SimpleArrayChange) => void; } TurboModuleRegistry.getEnforcing( diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-changed/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-changed/NativeModule.js.flow index 77455654a4d..751c2b273b2 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-changed/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-changed/NativeModule.js.flow @@ -13,9 +13,9 @@ import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport'; import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; export interface Spec extends TurboModule { - +exampleFunction: (a: string, b: number) => void; - +getConstants: () => { - +exampleConstant: string, + readonly exampleFunction: (a: string, b: number) => void; + readonly getConstants: () => { + readonly exampleConstant: string, }; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-get-constants-added-optional-constant-readonly/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-get-constants-added-optional-constant-readonly/NativeModule.js.flow index 9a3af67e4d8..1ccfbb6c1bb 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-get-constants-added-optional-constant-readonly/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-get-constants-added-optional-constant-readonly/NativeModule.js.flow @@ -13,8 +13,8 @@ import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport'; import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; export interface Spec extends TurboModule { - +exampleFunction: (a: string, b: number) => void; - +getConstants: () => Readonly<{ + readonly exampleFunction: (a: string, b: number) => void; + readonly getConstants: () => Readonly<{ constant?: string, }>; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-get-constants-added-optional-constant/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-get-constants-added-optional-constant/NativeModule.js.flow index 944e5ebcdd9..140a3f64eac 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-get-constants-added-optional-constant/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-get-constants-added-optional-constant/NativeModule.js.flow @@ -13,9 +13,9 @@ import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport'; import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; export interface Spec extends TurboModule { - +exampleFunction: (a: string, b: number) => void; - +getConstants: () => { - +constant?: string, + readonly exampleFunction: (a: string, b: number) => void; + readonly getConstants: () => { + readonly constant?: string, }; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-get-constants-added-required-constant-readonly/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-get-constants-added-required-constant-readonly/NativeModule.js.flow index e9f36f05ad2..d6adf2fb039 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-get-constants-added-required-constant-readonly/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-get-constants-added-required-constant-readonly/NativeModule.js.flow @@ -13,8 +13,8 @@ import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport'; import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; export interface Spec extends TurboModule { - +exampleFunction: (a: string, b: number) => void; - +getConstants: () => Readonly<{ + readonly exampleFunction: (a: string, b: number) => void; + readonly getConstants: () => Readonly<{ constant: string, }>; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-get-constants-added-required-constant/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-get-constants-added-required-constant/NativeModule.js.flow index 8af008dc567..fc1f8f5c968 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-get-constants-added-required-constant/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-get-constants-added-required-constant/NativeModule.js.flow @@ -13,9 +13,9 @@ import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport'; import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; export interface Spec extends TurboModule { - +exampleFunction: (a: string, b: number) => void; - +getConstants: () => { - +constant: string, + readonly exampleFunction: (a: string, b: number) => void; + readonly getConstants: () => { + readonly constant: string, }; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-get-constants-readonly/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-get-constants-readonly/NativeModule.js.flow index 9a4ebdc57d1..8e940d86fec 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-get-constants-readonly/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-get-constants-readonly/NativeModule.js.flow @@ -13,8 +13,8 @@ import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport'; import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; export interface Spec extends TurboModule { - +exampleFunction: (a: string, b: number) => void; - +getConstants: () => Readonly<{}>; + readonly exampleFunction: (a: string, b: number) => void; + readonly getConstants: () => Readonly<{}>; } export default TurboModuleRegistry.getEnforcing( diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-get-constants/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-get-constants/NativeModule.js.flow index 6705dc831d4..b31c7fbba30 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-get-constants/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-get-constants/NativeModule.js.flow @@ -13,8 +13,8 @@ import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport'; import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; export interface Spec extends TurboModule { - +exampleFunction: (a: string, b: number) => void; - +getConstants: () => {}; + readonly exampleFunction: (a: string, b: number) => void; + readonly getConstants: () => {}; } export default TurboModuleRegistry.getEnforcing( diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-nested-alias-changed/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-nested-alias-changed/NativeModule.js.flow index 3c908428e96..91b7c1a6eb8 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-nested-alias-changed/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-nested-alias-changed/NativeModule.js.flow @@ -15,7 +15,7 @@ import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboMo type MyType = () => Promise; export interface Spec extends TurboModule { - +exampleFunction: (a: MyType, b: number) => void; + readonly exampleFunction: (a: MyType, b: number) => void; } export default TurboModuleRegistry.getEnforcing( diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-nested-alias/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-nested-alias/NativeModule.js.flow index 56a1908cb60..7bc0475822b 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-nested-alias/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-nested-alias/NativeModule.js.flow @@ -15,7 +15,7 @@ import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboMo type MyType = () => Promise; export interface Spec extends TurboModule { - +exampleFunction: (a: MyType, b: number) => void; + readonly exampleFunction: (a: MyType, b: number) => void; } export default TurboModuleRegistry.getEnforcing( diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-nested-changed/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-nested-changed/NativeModule.js.flow index 47a742ae444..be9e6610524 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-nested-changed/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-nested-changed/NativeModule.js.flow @@ -13,7 +13,7 @@ import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport'; import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; export interface Spec extends TurboModule { - +exampleFunction: ( + readonly exampleFunction: ( a: { a1: string, a2: number, @@ -22,8 +22,8 @@ export interface Spec extends TurboModule { }, b: number, ) => void; - +getConstants: () => { - +exampleConstant: string, + readonly getConstants: () => { + readonly exampleConstant: string, }; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-nested-nullable/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-nested-nullable/NativeModule.js.flow index 17a07752fa3..65cd98a27dd 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-nested-nullable/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-nested-nullable/NativeModule.js.flow @@ -13,7 +13,7 @@ import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport'; import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; export interface Spec extends TurboModule { - +exampleFunction: ( + readonly exampleFunction: ( a: ?{ a1: string, a2?: number, @@ -22,8 +22,8 @@ export interface Spec extends TurboModule { }, b: number, ) => void; - +getConstants: () => { - +exampleConstant: string, + readonly getConstants: () => { + readonly exampleConstant: string, }; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-nested-optional/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-nested-optional/NativeModule.js.flow index f2c63817488..de709bb05a9 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-nested-optional/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-nested-optional/NativeModule.js.flow @@ -13,7 +13,7 @@ import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport'; import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; export interface Spec extends TurboModule { - +exampleFunction: ( + readonly exampleFunction: ( a: { a1: string, a2?: number, @@ -22,8 +22,8 @@ export interface Spec extends TurboModule { }, b: number, ) => void; - +getConstants: () => { - +exampleConstant: string, + readonly getConstants: () => { + readonly exampleConstant: string, }; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-nested/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-nested/NativeModule.js.flow index 8bee824e214..3b00a720e4d 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-nested/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-nested/NativeModule.js.flow @@ -13,7 +13,7 @@ import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport'; import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; export interface Spec extends TurboModule { - +exampleFunction: ( + readonly exampleFunction: ( a: { a1: string, a2: number, @@ -21,8 +21,8 @@ export interface Spec extends TurboModule { }, b: number, ) => void; - +getConstants: () => { - +exampleConstant: string, + readonly getConstants: () => { + readonly exampleConstant: string, }; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-type-diffing-types/NativeTypeDiffingTypes.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-type-diffing-types/NativeTypeDiffingTypes.js.flow index f27cc20a7b3..0e2a9948587 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-type-diffing-types/NativeTypeDiffingTypes.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-type-diffing-types/NativeTypeDiffingTypes.js.flow @@ -170,53 +170,53 @@ enum EnumUnsorted { } export interface Spec extends TurboModule { - +stringLiteral0: (a: StringLiteral0) => void; - +stringLiteral1: (a: StringLiteral1) => void; - - +booleanLiteral0: (a: BooleanLiteral0) => void; - +booleanLiteral1: (a: BooleanLiteral1) => void; - - +numericLiteral0: (a: NumericLiteral0) => void; - +numericLiteral1: (a: NumericLiteral1) => void; - - +stringLiteralUnion0: (a: StringLiteralUnion0) => void; - +stringLiteralUnion1: (a: StringLiteralUnion1) => void; - +stringLiteralUnion2: (a: StringLiteralUnion2) => void; - - +referenceType: (a: ReferenceType) => void; - +referenceType2: (a: ReferenceType2) => void; - +referenceType3: (a: ReferenceType3) => void; - - +referenceGeneric: (a: ReferenceGeneric) => void; - +referenceGeneric2: (a: ReferenceGeneric2) => void; - - +objectTypeWithProps: (a: ObjectTypeWithProps) => void; - +objectTypeLessProps: (a: ObjectTypeLessProps) => void; - +objectTypeWithDifferentProps: (a: ObjectTypeWithDifferentProps) => void; - +objectTypeWithNesting: (a: ObjectTypeWithNesting) => void; - +objectTypeWithChanges: (a: ObjectTypeWithChanges) => void; - +objectTypeWithNestedChanges: (a: ObjectTypeWithNestedChanges) => void; - +objectTypeWithTwoOptionals: (a: ObjectTypeWithTwoOptionals) => void; - +objectTypeWithOptionalNestedChange: (a: ObjectTypeWithOptionalNestedChange) => void; - +nonNullableType: (a: NonNullableType) => void; - +nullableType: (a: NullableType) => void; - +genericObjectType: (a: GenericObjectType) => void; - +unsafeObjectType: (a: UnsafeObjectType) => void; - +optionalTypeLessProps: (a: OptionalTypeLessProps) => void; - +objectTypeLiteral1: (a: ObjectTypeLiteral1) => void; - +objectTypeLiteral2: (a: ObjectTypeLiteral2) => void; - +objectTypeLiteral1TypeAlias: (a: ObjectTypeLiteral1TypeAlias) => void; - +objectTypeLiteral2TypeAlias: (a: ObjectTypeLiteral2TypeAlias) => void; - +genericObjectType: (a: Object) => void; - - +mapWithKey: (a: MapWithKey) => void; - +mapWithKey2: (a: MapWithKey2) => void; - - +enum: (a: Enum) => void; - +enumWithTypeChange: (a: EnumWithTypeChange) => void; - +enumWithRemoval: (a: EnumWithRemoval) => void; - +enumWithValueChange: (a: EnumWithValueChange) => void; - +enumUnsorted: (a: EnumUnsorted) => void; + readonly stringLiteral0: (a: StringLiteral0) => void; + readonly stringLiteral1: (a: StringLiteral1) => void; + + readonly booleanLiteral0: (a: BooleanLiteral0) => void; + readonly booleanLiteral1: (a: BooleanLiteral1) => void; + + readonly numericLiteral0: (a: NumericLiteral0) => void; + readonly numericLiteral1: (a: NumericLiteral1) => void; + + readonly stringLiteralUnion0: (a: StringLiteralUnion0) => void; + readonly stringLiteralUnion1: (a: StringLiteralUnion1) => void; + readonly stringLiteralUnion2: (a: StringLiteralUnion2) => void; + + readonly referenceType: (a: ReferenceType) => void; + readonly referenceType2: (a: ReferenceType2) => void; + readonly referenceType3: (a: ReferenceType3) => void; + + readonly referenceGeneric: (a: ReferenceGeneric) => void; + readonly referenceGeneric2: (a: ReferenceGeneric2) => void; + + readonly objectTypeWithProps: (a: ObjectTypeWithProps) => void; + readonly objectTypeLessProps: (a: ObjectTypeLessProps) => void; + readonly objectTypeWithDifferentProps: (a: ObjectTypeWithDifferentProps) => void; + readonly objectTypeWithNesting: (a: ObjectTypeWithNesting) => void; + readonly objectTypeWithChanges: (a: ObjectTypeWithChanges) => void; + readonly objectTypeWithNestedChanges: (a: ObjectTypeWithNestedChanges) => void; + readonly objectTypeWithTwoOptionals: (a: ObjectTypeWithTwoOptionals) => void; + readonly objectTypeWithOptionalNestedChange: (a: ObjectTypeWithOptionalNestedChange) => void; + readonly nonNullableType: (a: NonNullableType) => void; + readonly nullableType: (a: NullableType) => void; + readonly genericObjectType: (a: GenericObjectType) => void; + readonly unsafeObjectType: (a: UnsafeObjectType) => void; + readonly optionalTypeLessProps: (a: OptionalTypeLessProps) => void; + readonly objectTypeLiteral1: (a: ObjectTypeLiteral1) => void; + readonly objectTypeLiteral2: (a: ObjectTypeLiteral2) => void; + readonly objectTypeLiteral1TypeAlias: (a: ObjectTypeLiteral1TypeAlias) => void; + readonly objectTypeLiteral2TypeAlias: (a: ObjectTypeLiteral2TypeAlias) => void; + readonly genericObjectType: (a: Object) => void; + + readonly mapWithKey: (a: MapWithKey) => void; + readonly mapWithKey2: (a: MapWithKey2) => void; + + readonly enum: (a: Enum) => void; + readonly enumWithTypeChange: (a: EnumWithTypeChange) => void; + readonly enumWithRemoval: (a: EnumWithRemoval) => void; + readonly enumWithValueChange: (a: EnumWithValueChange) => void; + readonly enumUnsorted: (a: EnumUnsorted) => void; } TurboModuleRegistry.getEnforcing( diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-dictionary-changed/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-dictionary-changed/NativeModule.js.flow index d3b76db667f..9bc3d07fc9e 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-dictionary-changed/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-dictionary-changed/NativeModule.js.flow @@ -13,9 +13,9 @@ import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport'; import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; export interface Spec extends TurboModule { - +exampleFunction: (a: {[key: string]: string}, b: number) => void; - +getConstants: () => { - +exampleConstant: number, + readonly exampleFunction: (a: {[key: string]: string}, b: number) => void; + readonly getConstants: () => { + readonly exampleConstant: number, }; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-dictionary/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-dictionary/NativeModule.js.flow index 6bfaf126dac..e7092067e47 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-dictionary/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-dictionary/NativeModule.js.flow @@ -13,9 +13,9 @@ import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport'; import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; export interface Spec extends TurboModule { - +exampleFunction: (a: {[key: string]: number}, b: number) => void; - +getConstants: () => { - +exampleConstant: number, + readonly exampleFunction: (a: {[key: string]: number}, b: number) => void; + readonly getConstants: () => { + readonly exampleConstant: number, }; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-enum-changes/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-enum-changes/NativeModule.js.flow index 97c14a626e5..57a4aaa4ac0 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-enum-changes/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-enum-changes/NativeModule.js.flow @@ -20,9 +20,9 @@ enum TestEnum { } export interface Spec extends TurboModule { - +exampleFunction: (a: TestEnum, b: number) => void; - +getConstants: () => { - +exampleConstant: number, + readonly exampleFunction: (a: TestEnum, b: number) => void; + readonly getConstants: () => { + readonly exampleConstant: number, }; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-enum-from-native-changes/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-enum-from-native-changes/NativeModule.js.flow index b976a279588..b0d3a670ddf 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-enum-from-native-changes/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-enum-from-native-changes/NativeModule.js.flow @@ -20,9 +20,9 @@ enum TestEnum { } export interface Spec extends TurboModule { - +exampleFunction: (a: string, b: number) => void; - +getConstants: () => { - +exampleConstant: TestEnum, + readonly exampleFunction: (a: string, b: number) => void; + readonly getConstants: () => { + readonly exampleConstant: TestEnum, }; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-enum-from-native-type-changes/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-enum-from-native-type-changes/NativeModule.js.flow index f1ec89117bd..829480d5635 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-enum-from-native-type-changes/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-enum-from-native-type-changes/NativeModule.js.flow @@ -19,9 +19,9 @@ enum TestEnum { } export interface Spec extends TurboModule { - +exampleFunction: (a: string, b: number) => void; - +getConstants: () => { - +exampleConstant: TestEnum, + readonly exampleFunction: (a: string, b: number) => void; + readonly getConstants: () => { + readonly exampleConstant: TestEnum, }; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-enum-from-native-value-changes/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-enum-from-native-value-changes/NativeModule.js.flow index 09e201a6c84..8788d73a3e0 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-enum-from-native-value-changes/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-enum-from-native-value-changes/NativeModule.js.flow @@ -19,9 +19,9 @@ enum TestEnum { } export interface Spec extends TurboModule { - +exampleFunction: (a: string, b: number) => void; - +getConstants: () => { - +exampleConstant: TestEnum, + readonly exampleFunction: (a: string, b: number) => void; + readonly getConstants: () => { + readonly exampleConstant: TestEnum, }; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-enum-from-native/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-enum-from-native/NativeModule.js.flow index a70bf90322c..d903cf7ba2f 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-enum-from-native/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-enum-from-native/NativeModule.js.flow @@ -19,9 +19,9 @@ enum TestEnum { } export interface Spec extends TurboModule { - +exampleFunction: (a: string, b: number) => void; - +getConstants: () => { - +exampleConstant: TestEnum, + readonly exampleFunction: (a: string, b: number) => void; + readonly getConstants: () => { + readonly exampleConstant: TestEnum, }; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-enum-type-changes/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-enum-type-changes/NativeModule.js.flow index 9463313d32e..1ac7cd374c5 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-enum-type-changes/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-enum-type-changes/NativeModule.js.flow @@ -20,9 +20,9 @@ enum TestEnum { } export interface Spec extends TurboModule { - +exampleFunction: (a: TestEnum, b: number) => void; - +getConstants: () => { - +exampleConstant: number, + readonly exampleFunction: (a: TestEnum, b: number) => void; + readonly getConstants: () => { + readonly exampleConstant: number, }; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-enum-value-changes/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-enum-value-changes/NativeModule.js.flow index 287e8ccfe70..ca5d6c5cb7d 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-enum-value-changes/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-enum-value-changes/NativeModule.js.flow @@ -19,9 +19,9 @@ enum TestEnum { } export interface Spec extends TurboModule { - +exampleFunction: (a: TestEnum, b: number) => void; - +getConstants: () => { - +exampleConstant: number, + readonly exampleFunction: (a: TestEnum, b: number) => void; + readonly getConstants: () => { + readonly exampleConstant: number, }; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-enum/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-enum/NativeModule.js.flow index 3c3a4b4f88d..d07fc73e166 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-enum/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-enum/NativeModule.js.flow @@ -19,9 +19,9 @@ enum TestEnum { } export interface Spec extends TurboModule { - +exampleFunction: (a: TestEnum, b: number) => void; - +getConstants: () => { - +exampleConstant: number, + readonly exampleFunction: (a: TestEnum, b: number) => void; + readonly getConstants: () => { + readonly exampleConstant: number, }; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-optional-argument/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-optional-argument/NativeModule.js.flow index 26c5adb3cb0..cfb6c420a55 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-optional-argument/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-optional-argument/NativeModule.js.flow @@ -13,9 +13,9 @@ import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport'; import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; export interface Spec extends TurboModule { - +exampleFunction: (a: string, b: number, c?: number) => void; - +getConstants: () => { - +exampleConstant: number, + readonly exampleFunction: (a: string, b: number, c?: number) => void; + readonly getConstants: () => { + readonly exampleConstant: number, }; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-boolean-changes/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-boolean-changes/NativeModule.js.flow index 9f2c88d7cfb..931f2b0b03a 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-boolean-changes/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-boolean-changes/NativeModule.js.flow @@ -16,9 +16,9 @@ import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboMo type BooleanUnion = true | false; export interface Spec extends TurboModule { - +exampleFunction: (a: BooleanUnion, b: number) => void; - +getConstants: () => { - +exampleConstant: number, + readonly exampleFunction: (a: BooleanUnion, b: number) => void; + readonly getConstants: () => { + readonly exampleConstant: number, }; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-boolean/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-boolean/NativeModule.js.flow index 9c4b5e278cc..1f16fe5c225 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-boolean/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-boolean/NativeModule.js.flow @@ -15,9 +15,9 @@ import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboMo type BooleanUnion = true; export interface Spec extends TurboModule { - +exampleFunction: (a: BooleanUnion, b: number) => void; - +getConstants: () => { - +exampleConstant: number, + readonly exampleFunction: (a: BooleanUnion, b: number) => void; + readonly getConstants: () => { + readonly exampleConstant: number, }; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-changes/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-changes/NativeModule.js.flow index 9959a7de531..e174a6bbfc9 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-changes/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-changes/NativeModule.js.flow @@ -15,9 +15,9 @@ import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboMo type testUnion = 'a' | 'b' | 'c' | 'd'; export interface Spec extends TurboModule { - +exampleFunction: (a: testUnion, b: number) => void; - +getConstants: () => { - +exampleConstant: number, + readonly exampleFunction: (a: testUnion, b: number) => void; + readonly getConstants: () => { + readonly exampleConstant: number, }; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-confusing-string-literals/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-confusing-string-literals/NativeModule.js.flow index 12b23fc01aa..b5faaa10647 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-confusing-string-literals/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-confusing-string-literals/NativeModule.js.flow @@ -15,9 +15,9 @@ import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboMo type testUnion = 'a' | '0' | '1' | 'a long string'; export interface Spec extends TurboModule { - +exampleFunction: (a: testUnion, b: number) => void; - +getConstants: () => { - +exampleConstant: number, + readonly exampleFunction: (a: testUnion, b: number) => void; + readonly getConstants: () => { + readonly exampleConstant: number, }; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-from-native-changes/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-from-native-changes/NativeModule.js.flow index 85168428f4a..650f086b854 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-from-native-changes/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-from-native-changes/NativeModule.js.flow @@ -15,9 +15,9 @@ import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboMo type testUnion = 'a' | 'b' | 'c' | 'd'; export interface Spec extends TurboModule { - +exampleFunction: (a: string, b: number) => void; - +getConstants: () => { - +exampleConstant: testUnion, + readonly exampleFunction: (a: string, b: number) => void; + readonly getConstants: () => { + readonly exampleConstant: testUnion, }; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-from-native/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-from-native/NativeModule.js.flow index 351e61da2d4..983924e816c 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-from-native/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-from-native/NativeModule.js.flow @@ -15,9 +15,9 @@ import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboMo type testUnion = 'a' | 'b' | 'c'; export interface Spec extends TurboModule { - +exampleFunction: (a: string, b: number) => void; - +getConstants: () => { - +exampleConstant: testUnion, + readonly exampleFunction: (a: string, b: number) => void; + readonly getConstants: () => { + readonly exampleConstant: testUnion, }; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-number-changes/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-number-changes/NativeModule.js.flow index e2bb96047bb..15e80722798 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-number-changes/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-number-changes/NativeModule.js.flow @@ -15,9 +15,9 @@ import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboMo type NumberUnion = 4 | 5 | 6; export interface Spec extends TurboModule { - +exampleFunction: (a: NumberUnion, b: number) => void; - +getConstants: () => { - +exampleConstant: number, + readonly exampleFunction: (a: NumberUnion, b: number) => void; + readonly getConstants: () => { + readonly exampleConstant: number, }; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-number/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-number/NativeModule.js.flow index 00676cf3ca9..eaaeeaafe01 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-number/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-number/NativeModule.js.flow @@ -15,9 +15,9 @@ import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboMo type NumberUnion = 1 | 2 | 3; export interface Spec extends TurboModule { - +exampleFunction: (a: NumberUnion, b: number) => void; - +getConstants: () => { - +exampleConstant: number, + readonly exampleFunction: (a: NumberUnion, b: number) => void; + readonly getConstants: () => { + readonly exampleConstant: number, }; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-object-added/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-object-added/NativeModule.js.flow index ba7cb421101..f5183eef7a5 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-object-added/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-object-added/NativeModule.js.flow @@ -30,9 +30,9 @@ type ObjectC = {| type ObjectUnion = ObjectA | ObjectB | ObjectC; export interface Spec extends TurboModule { - +exampleFunction: (a: ObjectUnion, b: number) => void; - +getConstants: () => { - +exampleConstant: number, + readonly exampleFunction: (a: ObjectUnion, b: number) => void; + readonly getConstants: () => { + readonly exampleConstant: number, }; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-object-changes/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-object-changes/NativeModule.js.flow index 23a4a19e345..fc05ed87058 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-object-changes/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-object-changes/NativeModule.js.flow @@ -26,9 +26,9 @@ type ObjectB = {| type ObjectUnion = ObjectA | ObjectB; export interface Spec extends TurboModule { - +exampleFunction: (a: ObjectUnion, b: number) => void; - +getConstants: () => { - +exampleConstant: number, + readonly exampleFunction: (a: ObjectUnion, b: number) => void; + readonly getConstants: () => { + readonly exampleConstant: number, }; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-object/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-object/NativeModule.js.flow index 39555ab1f76..3c23ff9a66f 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-object/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-object/NativeModule.js.flow @@ -25,9 +25,9 @@ type ObjectB = {| type ObjectUnion = ObjectA | ObjectB; export interface Spec extends TurboModule { - +exampleFunction: (a: ObjectUnion, b: number) => void; - +getConstants: () => { - +exampleConstant: number, + readonly exampleFunction: (a: ObjectUnion, b: number) => void; + readonly getConstants: () => { + readonly exampleConstant: number, }; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-type-changes/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-type-changes/NativeModule.js.flow index affde92dc8c..872c3754cb0 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-type-changes/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union-type-changes/NativeModule.js.flow @@ -15,9 +15,9 @@ import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboMo type testUnion = 1 | 2 | 3; export interface Spec extends TurboModule { - +exampleFunction: (a: testUnion, b: number) => void; - +getConstants: () => { - +exampleConstant: number, + readonly exampleFunction: (a: testUnion, b: number) => void; + readonly getConstants: () => { + readonly exampleConstant: number, }; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union/NativeModule.js.flow index 9a1e788e3b1..f77ea159571 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module-with-union/NativeModule.js.flow @@ -15,9 +15,9 @@ import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboMo type testUnion = 'a' | 'b' | 'c'; export interface Spec extends TurboModule { - +exampleFunction: (a: testUnion, b: number) => void; - +getConstants: () => { - +exampleConstant: number, + readonly exampleFunction: (a: testUnion, b: number) => void; + readonly getConstants: () => { + readonly exampleConstant: number, }; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module/NativeModule.js.flow b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module/NativeModule.js.flow index f84d19c86fa..fc04caa5d46 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module/NativeModule.js.flow +++ b/packages/react-native-compatibility-check/src/__tests__/__fixtures__/native-module/NativeModule.js.flow @@ -13,9 +13,9 @@ import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport'; import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; export interface Spec extends TurboModule { - +exampleFunction: (a: string, b: number) => void; - +getConstants: () => { - +exampleConstant: number, + readonly exampleFunction: (a: string, b: number) => void; + readonly getConstants: () => { + readonly exampleConstant: number, }; } diff --git a/packages/react-native-compatibility-check/src/__tests__/__snapshots__/ErrorFormatting-test.js.snap b/packages/react-native-compatibility-check/src/__tests__/__snapshots__/ErrorFormatting-test.js.snap index eb03fd90b39..dbd81a6f465 100644 --- a/packages/react-native-compatibility-check/src/__tests__/__snapshots__/ErrorFormatting-test.js.snap +++ b/packages/react-native-compatibility-check/src/__tests__/__snapshots__/ErrorFormatting-test.js.snap @@ -271,6 +271,7 @@ Object { "errorCode": "addedProps", "message": "NativeModuleTest: Object added required properties, which native will not provide -- afterMismatchGeneric + -- arrayBufferType -- booleanType -- exampleFunction -- simpleArray diff --git a/packages/react-native-popup-menu-android/README.md b/packages/react-native-popup-menu-android/README.md new file mode 100644 index 00000000000..42b2e22784f --- /dev/null +++ b/packages/react-native-popup-menu-android/README.md @@ -0,0 +1,8 @@ +# @react-native/popup-menu-android + +[![npm]](https://www.npmjs.com/package/@react-native/popup-menu-android) [![npm downloads]](https://www.npmjs.com/package/@react-native/popup-menu-android) + +[npm]: https://img.shields.io/npm/v/@react-native/popup-menu-android.svg?color=blue +[npm downloads]: https://img.shields.io/npm/dm/@react-native/popup-menu-android.svg + +`PopupMenuAndroid` component for React Native, exposing the Android platform's [`PopupMenu`](https://developer.android.com/reference/android/widget/PopupMenu) for displaying a menu anchored to a view. diff --git a/packages/react-native-popup-menu-android/android/build.gradle.kts b/packages/react-native-popup-menu-android/android/build.gradle.kts index 707faa22bee..52b353d89fc 100644 --- a/packages/react-native-popup-menu-android/android/build.gradle.kts +++ b/packages/react-native-popup-menu-android/android/build.gradle.kts @@ -8,7 +8,6 @@ plugins { id("com.facebook.react") id("com.android.library") - id("org.jetbrains.kotlin.android") } android { diff --git a/packages/react-native-popup-menu-android/js/PopupMenuAndroid.android.js b/packages/react-native-popup-menu-android/js/PopupMenuAndroid.android.js index ccebd6ccd8f..379448c3650 100644 --- a/packages/react-native-popup-menu-android/js/PopupMenuAndroid.android.js +++ b/packages/react-native-popup-menu-android/js/PopupMenuAndroid.android.js @@ -28,7 +28,7 @@ type PopupMenuSelectionEvent = NativeSyntheticEvent< type PopupMenuDismissEvent = NativeSyntheticEvent>; export type PopupMenuAndroidInstance = { - +show: () => void, + readonly show: () => void, }; type Props = { diff --git a/packages/react-native-popup-menu-android/js/PopupMenuAndroid.js b/packages/react-native-popup-menu-android/js/PopupMenuAndroid.js index cdb973a4f44..2ade8fa6e68 100644 --- a/packages/react-native-popup-menu-android/js/PopupMenuAndroid.js +++ b/packages/react-native-popup-menu-android/js/PopupMenuAndroid.js @@ -37,7 +37,7 @@ const styles = StyleSheet.create({ }); export type PopupMenuAndroidInstance = { - +show: () => void, + readonly show: () => void, }; type Props = { diff --git a/packages/react-native-popup-menu-android/js/PopupMenuAndroidNativeComponent.android.js b/packages/react-native-popup-menu-android/js/PopupMenuAndroidNativeComponent.android.js index 331d1d56854..76eaae6edac 100644 --- a/packages/react-native-popup-menu-android/js/PopupMenuAndroidNativeComponent.android.js +++ b/packages/react-native-popup-menu-android/js/PopupMenuAndroidNativeComponent.android.js @@ -38,7 +38,7 @@ type NativeProps = Readonly<{ type ComponentType = HostComponent; interface NativeCommands { - +show: (viewRef: React.ElementRef) => void; + readonly show: (viewRef: React.ElementRef) => void; } export const Commands: NativeCommands = codegenNativeCommands({ diff --git a/packages/react-native-popup-menu-android/package.json b/packages/react-native-popup-menu-android/package.json index ae439468d66..07ee1ff97b8 100644 --- a/packages/react-native-popup-menu-android/package.json +++ b/packages/react-native-popup-menu-android/package.json @@ -1,7 +1,12 @@ { "name": "@react-native/popup-menu-android", - "version": "0.86.0-main", + "version": "0.87.0-main", "description": "PopupMenu for the Android platform", + "repository": { + "type": "git", + "url": "git+https://github.com/react/react-native.git", + "directory": "packages/react-native-popup-menu-android" + }, "main": "index.js", "files": [ "js", diff --git a/packages/react-native-test-library/src/NativeSampleModule.js b/packages/react-native-test-library/src/NativeSampleModule.js index de546b74e53..08c20bc6631 100644 --- a/packages/react-native-test-library/src/NativeSampleModule.js +++ b/packages/react-native-test-library/src/NativeSampleModule.js @@ -13,7 +13,7 @@ import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport'; import {TurboModuleRegistry} from 'react-native'; export interface Spec extends TurboModule { - +getRandomNumber: () => number; + readonly getRandomNumber: () => number; } -export default (TurboModuleRegistry.get('NativeSampleModule'): ?Spec); +export default TurboModuleRegistry.get('NativeSampleModule') as ?Spec; diff --git a/packages/react-native-test-library/src/SampleNativeComponent.js b/packages/react-native-test-library/src/SampleNativeComponent.js index 854db39f005..f1c7f925add 100644 --- a/packages/react-native-test-library/src/SampleNativeComponent.js +++ b/packages/react-native-test-library/src/SampleNativeComponent.js @@ -21,21 +21,21 @@ import * as React from 'react'; import codegenNativeCommands from 'react-native/Libraries/Utilities/codegenNativeCommands'; import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent'; -type Event = $ReadOnly<{ - values: $ReadOnlyArray, - boolValues: $ReadOnlyArray, - floats: $ReadOnlyArray, - doubles: $ReadOnlyArray, - yesNos: $ReadOnlyArray<'yep' | 'nope'>, - strings: $ReadOnlyArray, - latLons: $ReadOnlyArray<{lat: Double, lon: Double}>, - multiArrays: $ReadOnlyArray<$ReadOnlyArray>, +type Event = Readonly<{ + values: ReadonlyArray, + boolValues: ReadonlyArray, + floats: ReadonlyArray, + doubles: ReadonlyArray, + yesNos: ReadonlyArray<'yep' | 'nope'>, + strings: ReadonlyArray, + latLons: ReadonlyArray<{lat: Double, lon: Double}>, + multiArrays: ReadonlyArray>, }>; -type NativeProps = $ReadOnly<{ +type NativeProps = Readonly<{ ...ViewProps, opacity?: Float, - values: $ReadOnlyArray, + values: ReadonlyArray, // Events onIntArrayChanged?: ?BubblingEventHandler, @@ -44,7 +44,7 @@ type NativeProps = $ReadOnly<{ export type NativeComponentType = HostComponent; interface NativeCommands { - +changeBackgroundColor: ( + readonly changeBackgroundColor: ( viewRef: React.ElementRef, color: string, ) => void; @@ -54,6 +54,6 @@ export const Commands: NativeCommands = codegenNativeCommands({ supportedCommands: ['changeBackgroundColor'], }); -export default (codegenNativeComponent( +export default codegenNativeComponent( 'SampleNativeComponent', -): NativeComponentType); +) as NativeComponentType; diff --git a/packages/react-native/Libraries/ActionSheetIOS/ActionSheetIOS.js b/packages/react-native/Libraries/ActionSheetIOS/ActionSheetIOS.js index 78b6cda1bf1..4cb8fcf5146 100644 --- a/packages/react-native/Libraries/ActionSheetIOS/ActionSheetIOS.js +++ b/packages/react-native/Libraries/ActionSheetIOS/ActionSheetIOS.js @@ -38,6 +38,10 @@ export type ShareActionSheetIOSOptions = Readonly<{ tintColor?: ?number, cancelButtonTintColor?: ?number, disabledButtonTintColor?: ?number, + /** + * The activities to exclude from the ActionSheet. + * For example: ['com.apple.UIKit.activity.PostToTwitter'] + */ excludedActivityTypes?: ?Array, userInterfaceStyle?: ?string, }>; @@ -50,9 +54,10 @@ export type ShareActionSheetError = Readonly<{ }>; /** - * Display action sheets and share sheets on iOS. + * Displays native iOS action sheets and share sheets. * - * See https://reactnative.dev/docs/actionsheetios + * @see https://reactnative.dev/docs/actionsheetios + * @platform ios */ const ActionSheetIOS = { /** @@ -62,15 +67,13 @@ const ActionSheetIOS = { * * - `options` (array of strings) - a list of button titles (required) * - `cancelButtonIndex` (int) - index of cancel button in `options` - * - `destructiveButtonIndex` (int or array of ints) - index or indices of destructive buttons in `options` + * - `destructiveButtonIndex` (int or array of ints) - indices of destructive buttons in `options` * - `title` (string) - a title to show above the action sheet * - `message` (string) - a message to show below the title * - `disabledButtonIndices` (array of numbers) - a list of button indices which should be disabled * - * The 'callback' function takes one parameter, the zero-based index - * of the selected item. - * - * See https://reactnative.dev/docs/actionsheetios#showactionsheetwithoptions + * The `callback` function receives the zero-based index of the selected + * item. */ showActionSheetWithOptions( options: ActionSheetIOSOptions, @@ -136,27 +139,19 @@ const ActionSheetIOS = { }, /** - * Display the iOS share sheet. The `options` object should contain - * one or both of `message` and `url` and can additionally have - * a `subject` or `excludedActivityTypes`: + * Display the iOS share sheet. The `options` object should contain one or + * both of `message` and `url` and can additionally have a `subject` or + * `excludedActivityTypes`: * * - `url` (string) - a URL to share * - `message` (string) - a message to share * - `subject` (string) - a subject for the message - * - `excludedActivityTypes` (array) - the activities to exclude from - * the ActionSheet + * - `excludedActivityTypes` (array) - the activities to exclude from the ActionSheet * - `tintColor` (color) - tint color of the buttons * - * The 'failureCallback' function takes one parameter, an error object. - * The only property defined on this object is an optional `stack` property - * of type `string`. - * - * The 'successCallback' function takes two parameters: - * - * - a boolean value signifying success or failure - * - a string that, in the case of success, indicates the method of sharing - * - * See https://reactnative.dev/docs/actionsheetios#showshareactionsheetwithoptions + * The `failureCallback` function receives an error object. The + * `successCallback` function receives a boolean indicating success and a + * string describing the sharing method used. */ showShareActionSheetWithOptions( options: ShareActionSheetIOSOptions, @@ -186,8 +181,8 @@ const ActionSheetIOS = { }, /** - * Dismisses the most upper iOS action sheet presented, if no action sheet is - * present a warning is displayed. + * Dismiss the most upper action sheet currently presented. Displays a + * warning if no action sheet is present. */ dismissActionSheet: () => { invariant(RCTActionSheetManager, "ActionSheetManager doesn't exist"); diff --git a/packages/react-native/Libraries/Alert/Alert.js b/packages/react-native/Libraries/Alert/Alert.js index de0380ec126..32338197f9e 100644 --- a/packages/react-native/Libraries/Alert/Alert.js +++ b/packages/react-native/Libraries/Alert/Alert.js @@ -69,9 +69,43 @@ export type AlertOptions = { * alerts. On iOS, you can show an alert that prompts the user to enter * some information. * - * See https://reactnative.dev/docs/alert + * ## iOS + * + * On iOS you can specify any number of buttons. Each button can optionally + * specify a style, which is one of 'default', 'cancel' or 'destructive'. + * + * ## Android + * + * On Android at most three buttons can be specified. Android has a concept + * of a neutral, negative and a positive button: + * + * - If you specify one button, it will be the 'positive' one (such as 'OK') + * - Two buttons mean 'negative', 'positive' (such as 'Cancel', 'OK') + * - Three buttons mean 'neutral', 'negative', 'positive' (such as 'Later', 'Cancel', 'OK') + * + * Example: + * + * ```tsx + * // Works on both iOS and Android + * Alert.alert( + * 'Alert Title', + * 'My Alert Msg', + * [ + * {text: 'Ask me later', onPress: () => console.log('Ask me later pressed')}, + * {text: 'Cancel', onPress: () => console.log('Cancel Pressed'), style: 'cancel'}, + * {text: 'OK', onPress: () => console.log('OK Pressed')}, + * ] + * ) + * ``` + * + * @see https://reactnative.dev/docs/alert */ class Alert { + /** + * Display an alert dialog with the specified title, message, and buttons. + * On Android, at most three buttons can be specified. On iOS, any number of + * buttons can be used. + */ static alert( title: ?string, message?: ?string, @@ -164,6 +198,9 @@ class Alert { } /** + * Create and display a prompt to enter text. Accepts a title, message, + * callback or buttons, input type, default value, keyboard type, and options. + * * @platform ios */ static prompt( diff --git a/packages/react-native/Libraries/Animated/AnimatedExports.js b/packages/react-native/Libraries/Animated/AnimatedExports.js index d767e8a15bc..55940d81c39 100644 --- a/packages/react-native/Libraries/Animated/AnimatedExports.js +++ b/packages/react-native/Libraries/Animated/AnimatedExports.js @@ -24,21 +24,43 @@ const Animated: typeof AnimatedImplementation = Platform.isDisableAnimations : AnimatedImplementation; export default { + /** + * FlatList and SectionList infer generic Type defined under their `data` and `section` props. + */ get FlatList(): AnimatedFlatList { return require('./components/AnimatedFlatList').default; }, + /** + * Animated variants of the basic native views. Accepts Animated.Value for + * props and style. + */ get Image(): AnimatedImage { return require('./components/AnimatedImage').default; }, + /** + * Animated variants of the basic native views. Accepts Animated.Value for + * props and style. + */ get ScrollView(): AnimatedScrollView { return require('./components/AnimatedScrollView').default; }, + /** + * FlatList and SectionList infer generic Type defined under their `data` and `section` props. + */ get SectionList(): AnimatedSectionList { return require('./components/AnimatedSectionList').default; }, + /** + * Animated variants of the basic native views. Accepts Animated.Value for + * props and style. + */ get Text(): AnimatedText { return require('./components/AnimatedText').default; }, + /** + * Animated variants of the basic native views. Accepts Animated.Value for + * props and style. + */ get View(): AnimatedView { return require('./components/AnimatedView').default; }, diff --git a/packages/react-native/Libraries/Animated/AnimatedExports.js.flow b/packages/react-native/Libraries/Animated/AnimatedExports.js.flow index cacd8c2248a..7880f98f97a 100644 --- a/packages/react-native/Libraries/Animated/AnimatedExports.js.flow +++ b/packages/react-native/Libraries/Animated/AnimatedExports.js.flow @@ -10,7 +10,7 @@ import AnimatedImplementation from './AnimatedImplementation'; -export type {CompositeAnimation} from './AnimatedImplementation'; +export type {CompositeAnimation, Numeric} from './AnimatedImplementation'; export type {DecayAnimationConfig} from './animations/DecayAnimation'; export type {SpringAnimationConfig} from './animations/SpringAnimation'; export type {TimingAnimationConfig} from './animations/TimingAnimation'; diff --git a/packages/react-native/Libraries/Animated/AnimatedImplementation.js b/packages/react-native/Libraries/Animated/AnimatedImplementation.js index 46a08d2e895..4a9d76a4806 100644 --- a/packages/react-native/Libraries/Animated/AnimatedImplementation.js +++ b/packages/react-native/Libraries/Animated/AnimatedImplementation.js @@ -20,6 +20,7 @@ import type {DecayAnimationConfig} from './animations/DecayAnimation'; import type {SpringAnimationConfig} from './animations/SpringAnimation'; import type {TimingAnimationConfig} from './animations/TimingAnimation'; +import NativeAnimatedHelper from '../../src/private/animated/NativeAnimatedHelper'; import {AnimatedEvent, attachNativeEventImpl} from './AnimatedEvent'; import DecayAnimation from './animations/DecayAnimation'; import SpringAnimation from './animations/SpringAnimation'; @@ -39,8 +40,29 @@ import AnimatedValue from './nodes/AnimatedValue'; import AnimatedValueXY from './nodes/AnimatedValueXY'; export type CompositeAnimation = { + /** + * Animations are started by calling start() on your animation. + * start() takes a completion callback that will be called when the + * animation is done or when the animation is done because stop() was + * called on it before it could finish. + * + * @param callback - Optional function that will be called + * after the animation finished running normally or when the animation + * is done because stop() was called on it before it could finish + * + * @example + * Animated.timing({}).start(({ finished }) => { + * // completion callback + * }); + */ start: (callback?: ?EndCallback, isLooping?: boolean) => void, + /** + * Stops any running animation. + */ stop: () => void, + /** + * Stops any running animation and resets the value to its original. + */ reset: () => void, _startNativeLoop: (iterations?: number) => void, _isUsingNativeDriver: () => boolean, @@ -200,7 +222,11 @@ const springImpl = function ( }, _isUsingNativeDriver: function (): boolean { - return config.useNativeDriver || false; + return ( + NativeAnimatedHelper.isNativeDriverForced() || + config.useNativeDriver || + false + ); }, } ); @@ -254,7 +280,11 @@ const timingImpl = function ( }, _isUsingNativeDriver: function (): boolean { - return config.useNativeDriver || false; + return ( + NativeAnimatedHelper.isNativeDriverForced() || + config.useNativeDriver || + false + ); }, } ); @@ -296,7 +326,11 @@ const decayImpl = function ( }, _isUsingNativeDriver: function (): boolean { - return config.useNativeDriver || false; + return ( + NativeAnimatedHelper.isNativeDriverForced() || + config.useNativeDriver || + false + ); }, } ); @@ -608,21 +642,104 @@ export default { * See https://reactnative.dev/docs/animated#node */ Node: AnimatedNode, + /** + * Animates a value from an initial velocity to zero based on a decay + * coefficient. + */ decay: decayImpl, + /** + * Animates a value along a timed easing curve. The `Easing` module has tons + * of pre-defined curves, or you can use your own function. + */ timing: timingImpl, + /** + * Spring animation based on Rebound and Origami. Tracks velocity state to + * create fluid motions as the `toValue` updates, and can be chained together. + */ spring: springImpl, + /** + * Creates a new Animated value composed from two Animated values added + * together. + */ add: addImpl, + /** + * Creates a new Animated value composed by subtracting the second Animated + * value from the first Animated value. + */ subtract: subtractImpl, + /** + * Creates a new Animated value composed by dividing the first Animated + * value by the second Animated value. + */ divide: divideImpl, + /** + * Creates a new Animated value composed from two Animated values multiplied + * together. + */ multiply: multiplyImpl, + /** + * Creates a new Animated value that is the (non-negative) modulo of the + * provided Animated value + */ modulo: moduloImpl, + /** + * Create a new Animated value that is limited between 2 values. It uses the + * difference between the last value so even if the value is far from the bounds + * it will start changing when the value starts getting closer again. + * (`value = clamp(value + diff, min, max)`). + * + * This is useful with scroll events, for example, to show the navbar when + * scrolling up and to hide it when scrolling down. + */ diffClamp: diffClampImpl, + /** + * Starts an animation after the given delay. + */ delay: delayImpl, + /** + * Starts an array of animations in order, waiting for each to complete + * before starting the next. If the current running animation is stopped, no + * following animations will be started. + */ sequence: sequenceImpl, + /** + * Starts an array of animations all at the same time. By default, if one + * of the animations is stopped, they will all be stopped. You can override + * this with the `stopTogether` flag. + */ parallel: parallelImpl, + /** + * Array of animations may run in parallel (overlap), but are started in + * sequence with successive delays. Nice for doing trailing effects. + */ stagger: staggerImpl, + /** + * Loops a given animation continuously, so that each time it reaches the end, + * it resets and begins again from the start. Can specify number of times to + * loop using the key 'iterations' in the config. Will loop without blocking + * the UI thread if the child animation is set to 'useNativeDriver'. + */ loop: loopImpl, + /** + * Takes an array of mappings and extracts values from each arg accordingly, + * then calls `setValue` on the mapped outputs. e.g. + * + *```javascript + * onScroll={Animated.event( + * [{nativeEvent: {contentOffset: {x: this._scrollX}}}] + * {listener}, // Optional async listener + * ) + * ... + * onPanResponderMove: Animated.event([ + * null, // raw event arg ignored + * {dx: this._panX}, // gestureState arg + * ]), + *``` + */ event: eventImpl, + /** + * Make any React component Animatable. Used to create `Animated.View`, etc. + */ createAnimatedComponent, attachNativeEvent: attachNativeEventImpl, forkEvent: forkEventImpl, diff --git a/packages/react-native/Libraries/Animated/Easing.js b/packages/react-native/Libraries/Animated/Easing.js index 7b17de424d1..31956a8c3eb 100644 --- a/packages/react-native/Libraries/Animated/Easing.js +++ b/packages/react-native/Libraries/Animated/Easing.js @@ -15,9 +15,8 @@ let ease; export type EasingFunction = (t: number) => number; /** - * The `Easing` module implements common easing functions. This module is used - * by [Animate.timing()](docs/animate.html#timing) to convey physically - * believable motion in animations. + * Implements common easing functions for use with `Animated.timing()` to convey + * physically believable motion in animations. * * You can find a visualization of some common easing functions at * http://easings.net/ @@ -58,6 +57,8 @@ export type EasingFunction = (t: number) => number; * - [`in`](docs/easing.html#in) runs an easing function forwards * - [`inOut`](docs/easing.html#inout) makes any easing function symmetrical * - [`out`](docs/easing.html#out) runs an easing function backwards + * + * @see https://reactnative.dev/docs/easing */ const EasingStatic = { /** diff --git a/packages/react-native/Libraries/Animated/NativeAnimatedAllowlist.js b/packages/react-native/Libraries/Animated/NativeAnimatedAllowlist.js index c5cecfc828c..2c1fab7ac13 100644 --- a/packages/react-native/Libraries/Animated/NativeAnimatedAllowlist.js +++ b/packages/react-native/Libraries/Animated/NativeAnimatedAllowlist.js @@ -78,6 +78,42 @@ const SUPPORTED_STYLES: {[string]: true} = { top: true, /* flex */ flex: true, + flexGrow: true, + flexShrink: true, + flexBasis: true, + aspectRatio: true, + /* margin */ + margin: true, + marginLeft: true, + marginRight: true, + marginTop: true, + marginBottom: true, + marginStart: true, + marginEnd: true, + marginHorizontal: true, + marginVertical: true, + /* padding */ + padding: true, + paddingLeft: true, + paddingRight: true, + paddingTop: true, + paddingBottom: true, + paddingStart: true, + paddingEnd: true, + paddingHorizontal: true, + paddingVertical: true, + /* border width */ + borderWidth: true, + borderLeftWidth: true, + borderRightWidth: true, + borderTopWidth: true, + borderBottomWidth: true, + borderStartWidth: true, + borderEndWidth: true, + /* gap */ + gap: true, + rowGap: true, + columnGap: true, } : {}), }; @@ -106,6 +142,7 @@ const SUPPORTED_INTERPOLATION_PARAMS: {[string]: true} = { extrapolate: true, extrapolateRight: true, extrapolateLeft: true, + easing: true, }; /** diff --git a/packages/react-native/Libraries/Animated/__tests__/Animated-itest.js b/packages/react-native/Libraries/Animated/__tests__/Animated-itest.js index 995bb369bd8..c8637f8bf84 100644 --- a/packages/react-native/Libraries/Animated/__tests__/Animated-itest.js +++ b/packages/react-native/Libraries/Animated/__tests__/Animated-itest.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @fantom_flags useSharedAnimatedBackend:* + * @fantom_flags useSharedAnimatedBackend:* animatedShouldSyncValueBeforeStartCallback:* animatedDeferStartOfTimingAnimations:* * @flow strict-local * @format */ @@ -17,10 +17,16 @@ import ensureInstance from '../../../src/private/__tests__/utilities/ensureInsta import * as ReactNativeFeatureFlags from '../../../src/private/featureflags/ReactNativeFeatureFlags'; import * as Fantom from '@react-native/fantom'; import {createRef} from 'react'; -import {Animated, View, useAnimatedValue} from 'react-native'; +import {Animated, Easing, View, useAnimatedValue} from 'react-native'; import {allowStyleProp} from 'react-native/Libraries/Animated/NativeAnimatedAllowlist'; import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement'; +// Deferred start outputs the initial value on the first animation frame and +// re-anchors timing on the second. This delays animation progress by one +// frame interval (~16ms at 60 fps). +const DEFERRED_START_MS = + ReactNativeFeatureFlags.animatedDeferStartOfTimingAnimations() ? 16 : 0; + test('moving box by 100 points', () => { let _translateX; const viewRef = createRef(); @@ -60,7 +66,7 @@ test('moving box by 100 points', () => { }).start(); }); - Fantom.unstable_produceFramesForDuration(500); + Fantom.unstable_produceFramesForDuration(500 + DEFERRED_START_MS); // shadow tree is not synchronised yet, position X is still 0. expect(viewElement.getBoundingClientRect().x).toBe(0); @@ -81,6 +87,202 @@ test('moving box by 100 points', () => { expect(viewElement.getBoundingClientRect().x).toBe(100); }); +// A native-driven interpolation with a custom `easing` should follow the easing +// curve, not run linearly. The driver animates linearly 0 -> 1; the eased +// interpolation maps it to translateX 0 -> 100 with Easing.quad (t^2). At the +// midpoint (driver = 0.5) the eased value is 0.5^2 * 100 = 25 (a linear mapping +// would be 50). The easing is baked into the native interpolation config as an +// `easingStops` lookup table, so the native driver reproduces the curve. +test('native-driven interpolation honors custom easing', () => { + let _progress; + const viewRef = createRef(); + + function MyApp() { + const progress = useAnimatedValue(0); + _progress = progress; + const translateX = progress.interpolate({ + inputRange: [0, 1], + outputRange: [0, 100], + easing: Easing.quad, + }); + return ( + + ); + } + + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + const viewElement = ensureInstance(viewRef.current, ReactNativeElement); + + Fantom.runTask(() => { + Animated.timing(_progress, { + toValue: 1, + duration: 1000, // 1 second + easing: Easing.linear, + useNativeDriver: true, + }).start(); + }); + + Fantom.unstable_produceFramesForDuration(500 + DEFERRED_START_MS); + + const transform = + // $FlowFixMe[incompatible-use] + Fantom.unstable_getDirectManipulationProps(viewElement).transform[0]; + + // Driver is 50% through (linear timing), but the interpolation's quad easing + // reshapes it: 0.5^2 * 100 = 25, not the linear 50. + expect(transform.translateX).toBeCloseTo(25, 0.001); + + Fantom.unstable_produceFramesForDuration(500); + + // Animation complete; final committed position is the full 100. + Fantom.runWorkLoop(); + expect(viewElement.getBoundingClientRect().x).toBe(100); +}); + +// When the easing leaves [0, 1] (Easing.back dips below 0 early), that excursion +// must be preserved even under `extrapolate: 'clamp'`. The driver runs 0 -> 1, so +// the input is always in range — `clamp` should only affect out-of-range *input*, +// never the easing's own excursion. Pre-fix the native driver clamped it away +// (translateX pinned to 0); JS keeps it negative. This guards that parity. +test('native-driven interpolation preserves easing overshoot under clamp', () => { + let _progress; + const viewRef = createRef(); + + function MyApp() { + const progress = useAnimatedValue(0); + _progress = progress; + const translateX = progress.interpolate({ + inputRange: [0, 1], + outputRange: [0, 100], + easing: Easing.back(), + extrapolate: 'clamp', + }); + return ( + + ); + } + + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + const viewElement = ensureInstance(viewRef.current, ReactNativeElement); + + Fantom.runTask(() => { + Animated.timing(_progress, { + toValue: 1, + duration: 1000, + easing: Easing.linear, + useNativeDriver: true, + }).start(); + }); + + // ~20% through: Easing.back(0.2) ≈ -0.046 -> translateX ≈ -4.6, i.e. negative. + // If the excursion were clamped (the bug), translateX would stay at 0. + Fantom.unstable_produceFramesForDuration(200 + DEFERRED_START_MS); + const transform = + // $FlowFixMe[incompatible-use] + Fantom.unstable_getDirectManipulationProps(viewElement).transform[0]; + expect(transform.translateX).toBeLessThan(0); + + // Completes at the in-range endpoint (Easing.back(1) === 1 -> 100). + Fantom.unstable_produceFramesForDuration(800); + Fantom.runWorkLoop(); + expect(viewElement.getBoundingClientRect().x).toBe(100); +}); + +// Validate that a `useNativeDriver` timing animation does not begin progressing +// until the end of the event loop tick it was started in. +// +// Tested different behavior introduced by `animatedDeferStartOfTimingAnimations`, +// the behavioral difference is animated prop value on the first frame after the tick: +// flag ON -> deferred, not progressed yet, flag OFF -> already progressing. +function startTimingAnimationAndGetTranslateXAfterFirstFrame(): number { + let _translateX; + const viewRef = createRef(); + + function MyApp() { + const translateX = useAnimatedValue(0); + _translateX = translateX; + return ( + + ); + } + + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + const viewElement = ensureInstance(viewRef.current, ReactNativeElement); + + Fantom.runTask(() => { + Animated.timing(_translateX, { + toValue: 100, + duration: 1000, + useNativeDriver: true, + }).start(); + + Fantom.unstable_produceFramesForDuration(500); + + // The UI thread advances while we are still inside the js tick. The animation + // must not produce any direct manipulation yet, because its mount + // operations have not been flushed. This holds regardless of the flag. + expect(() => + Fantom.unstable_getDirectManipulationProps(viewElement), + ).toThrow(); + }); + + // Produce the first frame after the tick (~16ms rounds to frame 1). + Fantom.unstable_produceFramesForDuration(16); + const translateXAfterFirstFrame = + // $FlowFixMe[incompatible-use] + Fantom.unstable_getDirectManipulationProps(viewElement).transform[0] + .translateX; + + // Drain the animation so it completes and the message queue is empty for the + // next test. + Fantom.unstable_produceFramesForDuration(1000); + Fantom.runWorkLoop(); + expect(viewElement.getBoundingClientRect().x).toBe(100); + + return translateXAfterFirstFrame; +} + +if (ReactNativeFeatureFlags.animatedDeferStartOfTimingAnimations()) { + test('animation does not start before the end of the current event loop tick', () => { + // With deferred start, the first frame after the tick outputs the initial + // value and re-anchors timing, so the animation has not progressed yet — + // no frames were skipped despite the UI thread advancing inside the tick. + expect(startTimingAnimationAndGetTranslateXAfterFirstFrame()).toBe(0); + }); +} else { + test('animation might start before the end of the current event loop tick', () => { + // Without deferred start, the animation begins progressing immediately — it + // has effectively started before the end of the tick. + expect( + startTimingAnimationAndGetTranslateXAfterFirstFrame(), + ).toBeGreaterThan(0); + }); +} + test('animation driven by onScroll event', () => { const scrollViewRef = createRef(); const viewRef = createRef(); @@ -248,7 +450,7 @@ test('animated opacity', () => { }).start(); }); - Fantom.unstable_produceFramesForDuration(30); + Fantom.unstable_produceFramesForDuration(30 + DEFERRED_START_MS); expect(Fantom.unstable_getDirectManipulationProps(viewElement).opacity).toBe( 0, ); @@ -559,7 +761,7 @@ test('animate layout props', () => { }).start(); }); - Fantom.unstable_produceFramesForDuration(10); + Fantom.unstable_produceFramesForDuration(10 + DEFERRED_START_MS); // TODO: this shouldn't be necessary since animation should be stopped after duration Fantom.runTask(() => { @@ -712,7 +914,7 @@ test('Animated.sequence', () => { }); }); - Fantom.unstable_produceFramesForDuration(500); + Fantom.unstable_produceFramesForDuration(500 + DEFERRED_START_MS); expect( // $FlowFixMe[incompatible-use] @@ -735,3 +937,74 @@ test('Animated.sequence', () => { expect(_isSequenceFinished).toBe(true); }); + +// Regression test for native-driver completion-callback ordering. +// +// `Animation.__startAnimationIfNative` must sync the JS-side AnimatedValue +// with the post-animation value BEFORE firing the user's `start({finished})` +// callback. Otherwise, code that reads the AnimatedValue from inside the +// callback — or from any React re-render the callback triggers — observes the +// pre-animation value and renders stale style (e.g. a `scaleX` interpolation +// that resolves to the starting `outputRange[0]` instead of the final +// `outputRange[1]`). +// +// The fix is gated behind `animatedShouldSyncValueBeforeStartCallback`. This +// test runs under both flag values (via `@fantom_flags ...:*`) and asserts: +// - flag OFF: bug is present (callback observes pre-animation value). +// - flag ON : bug is fixed (callback observes post-animation value). +test('useNativeDriver: JS-side _value is synced before the completion callback fires', () => { + let _value; + let _valueInCallback = null; + let _interpolationInCallback = null; + + function MyApp() { + const value = useAnimatedValue(0); + _value = value; + const scaleX = value.interpolate({ + inputRange: [0, 1], + outputRange: [0.5, 1], + }); + return ( + + ); + } + + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + Fantom.runTask(() => { + Animated.timing(_value, { + toValue: 1, + duration: 100, + useNativeDriver: true, + }).start(({finished}) => { + if (finished) { + // $FlowFixMe[prop-missing] _value is internal but stable for testing. + _valueInCallback = _value._value; + // $FlowFixMe[prop-missing] + _interpolationInCallback = _value + .interpolate({inputRange: [0, 1], outputRange: [0.5, 1]}) + .__getValue(); + } + }); + }); + + Fantom.unstable_produceFramesForDuration(150); + Fantom.runWorkLoop(); + + if (ReactNativeFeatureFlags.animatedShouldSyncValueBeforeStartCallback()) { + // With the fix: the callback observes the post-animation value. + expect(_valueInCallback).toBe(1); + expect(_interpolationInCallback).toBe(1); + } else { + // Without the fix: the callback observes the pre-animation value. + // interp(0) = outputRange[0] = 0.5. + expect(_valueInCallback).toBe(0); + expect(_interpolationInCallback).toBe(0.5); + } +}); diff --git a/packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js b/packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js index 9db64ffb80b..57b72ea6771 100644 --- a/packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js +++ b/packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js @@ -15,11 +15,71 @@ import type {HostInstance} from 'react-native'; import ensureInstance from '../../../src/private/__tests__/utilities/ensureInstance'; import * as Fantom from '@react-native/fantom'; -import {createRef, memo, useEffect, useMemo, useState} from 'react'; +import * as React from 'react'; +import {Component, createRef, memo, useEffect, useMemo, useState} from 'react'; import {Animated, View, useAnimatedValue} from 'react-native'; import {allowStyleProp} from 'react-native/Libraries/Animated/NativeAnimatedAllowlist'; import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement'; +// marginLeft (and the other margin props) are only on the native animated +// allowlist when the shared backend is enabled. This test deliberately does NOT +// call allowStyleProp('marginLeft') — it verifies the prop is supported natively +// out of the box under useSharedAnimatedBackend. +test('animate marginLeft layout prop', () => { + const viewRef = createRef(); + + let _animatedMarginLeft; + let _marginLeftAnimation; + + function MyApp() { + const animatedMarginLeft = useAnimatedValue(0); + _animatedMarginLeft = animatedMarginLeft; + return ( + + ); + } + + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + Fantom.runTask(() => { + _marginLeftAnimation = Animated.timing(_animatedMarginLeft, { + toValue: 100, + duration: 200, + useNativeDriver: true, + }).start(); + }); + + Fantom.unstable_produceFramesForDuration(100); + + expect(root.getRenderedOutput({props: ['marginLeft']}).toJSX()).toEqual( + , + ); + + Fantom.unstable_produceFramesForDuration(100); + + // TODO: this shouldn't be necessary since animation should be stopped after duration + Fantom.runTask(() => { + _marginLeftAnimation?.stop(); + }); + + expect(root.getRenderedOutput({props: ['marginLeft']}).toJSX()).toEqual( + , + ); +}); + test('animated opacity', () => { let _opacity; let _opacityAnimation; @@ -75,6 +135,122 @@ test('animated opacity', () => { ); }); +// ScrollView's ref is the host instance, so it resolves directly (sanity check +// that the fix doesn't regress it). +test('animated opacity on Animated.ScrollView', () => { + let _opacity; + let _opacityAnimation; + + function MyApp() { + const opacity = useAnimatedValue(1); + _opacity = opacity; + return ( + + + + ); + } + + const root = Fantom.createRoot(); + Fantom.runTask(() => { + root.render(); + }); + + Fantom.runTask(() => { + _opacityAnimation = Animated.timing(_opacity, { + toValue: 0, + duration: 30, + useNativeDriver: true, + }).start(); + }); + Fantom.unstable_produceFramesForDuration(30); + Fantom.runTask(() => { + _opacityAnimation?.stop(); + }); + + expect( + JSON.stringify(root.getRenderedOutput({props: ['opacity']}).toJSON()), + ).toContain('"opacity":"0"'); +}); + +test('animated opacity on Animated.FlatList', () => { + let _opacity; + let _opacityAnimation; + + function MyApp() { + const opacity = useAnimatedValue(1); + _opacity = opacity; + return ( + } + renderItem={() => null} + style={{opacity}} + /> + ); + } + + const root = Fantom.createRoot(); + Fantom.runTask(() => { + root.render(); + }); + + Fantom.runTask(() => { + _opacityAnimation = Animated.timing(_opacity, { + toValue: 0, + duration: 30, + useNativeDriver: true, + }).start(); + }); + Fantom.unstable_produceFramesForDuration(30); + Fantom.runTask(() => { + _opacityAnimation?.stop(); + }); + + expect( + JSON.stringify(root.getRenderedOutput({props: ['opacity']}).toJSON()), + ).toContain('"opacity":"0"'); +}); + +// A class composite uses the findShadowNodeByTag fallback path in #connectShadowNode. +test('animated opacity on a class composite wrapping a host', () => { + let _opacity; + let _opacityAnimation; + + class HostWrapper extends Component<{style?: $FlowFixMe}> { + render(): React.Node { + return ; + } + } + const AnimatedHostWrapper = Animated.createAnimatedComponent(HostWrapper); + + function MyApp() { + const opacity = useAnimatedValue(1); + _opacity = opacity; + return ; + } + + const root = Fantom.createRoot(); + Fantom.runTask(() => { + root.render(); + }); + + Fantom.runTask(() => { + _opacityAnimation = Animated.timing(_opacity, { + toValue: 0, + duration: 30, + useNativeDriver: true, + }).start(); + }); + Fantom.unstable_produceFramesForDuration(30); + Fantom.runTask(() => { + _opacityAnimation?.stop(); + }); + + expect(root.getRenderedOutput({props: ['opacity']}).toJSX()).toEqual( + , + ); +}); + test('animate layout props', () => { const viewRef = createRef(); allowStyleProp('height'); diff --git a/packages/react-native/Libraries/Animated/__tests__/AnimatedMock-itest.js b/packages/react-native/Libraries/Animated/__tests__/AnimatedMock-itest.js new file mode 100644 index 00000000000..65933b0c439 --- /dev/null +++ b/packages/react-native/Libraries/Animated/__tests__/AnimatedMock-itest.js @@ -0,0 +1,49 @@ +/** + * 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. + * + * @flow strict-local + * @format + */ + +import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; + +import AnimatedImplementation from '../AnimatedImplementation'; +import AnimatedMock from '../AnimatedMock'; + +describe('Animated Mock', () => { + it('matches implementation keys', () => { + expect(Object.keys(AnimatedMock)).toEqual( + Object.keys(AnimatedImplementation), + ); + }); + it('matches implementation params', () => { + Object.keys(AnimatedImplementation).forEach(key => { + if (AnimatedImplementation[key].length !== AnimatedMock[key].length) { + throw new Error( + 'key ' + + key + + ' had different lengths: ' + + JSON.stringify( + { + impl: { + len: AnimatedImplementation[key].length, + type: typeof AnimatedImplementation[key], + val: AnimatedImplementation[key].toString(), + }, + mock: { + len: AnimatedMock[key].length, + type: typeof AnimatedMock[key], + val: AnimatedMock[key].toString(), + }, + }, + null, + 2, + ), + ); + } + }); + }); +}); diff --git a/packages/react-native/Libraries/Animated/__tests__/AnimatedMock-test.js b/packages/react-native/Libraries/Animated/__tests__/AnimatedMock-test.js deleted file mode 100644 index 066986799f0..00000000000 --- a/packages/react-native/Libraries/Animated/__tests__/AnimatedMock-test.js +++ /dev/null @@ -1,52 +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. - * - * @flow strict-local - * @format - */ - -'use strict'; - -import AnimatedImplementation from '../AnimatedImplementation'; -import AnimatedMock from '../AnimatedMock'; - -describe('Animated Mock', () => { - it('matches implementation keys', () => { - expect(Object.keys(AnimatedMock)).toEqual( - Object.keys(AnimatedImplementation), - ); - }); - it('matches implementation params', done => { - Object.keys(AnimatedImplementation).forEach(key => { - if (AnimatedImplementation[key].length !== AnimatedMock[key].length) { - done( - new Error( - 'key ' + - key + - ' had different lengths: ' + - JSON.stringify( - { - impl: { - len: AnimatedImplementation[key].length, - type: typeof AnimatedImplementation[key], - val: AnimatedImplementation[key].toString(), - }, - mock: { - len: AnimatedMock[key].length, - type: typeof AnimatedMock[key], - val: AnimatedMock[key].toString(), - }, - }, - null, - 2, - ), - ), - ); - } - }); - done(); - }); -}); diff --git a/packages/react-native/Libraries/Animated/__tests__/AnimatedObject-test.js b/packages/react-native/Libraries/Animated/__tests__/AnimatedObject-itest.js similarity index 91% rename from packages/react-native/Libraries/Animated/__tests__/AnimatedObject-test.js rename to packages/react-native/Libraries/Animated/__tests__/AnimatedObject-itest.js index ef4dacd042f..3eb452700ff 100644 --- a/packages/react-native/Libraries/Animated/__tests__/AnimatedObject-test.js +++ b/packages/react-native/Libraries/Animated/__tests__/AnimatedObject-itest.js @@ -8,19 +8,13 @@ * @format */ +import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; + +import Animated from '../Animated'; +import AnimatedObject from '../nodes/AnimatedObject'; import nullthrows from 'nullthrows'; describe('AnimatedObject', () => { - let Animated; - let AnimatedObject; - - beforeEach(() => { - jest.resetModules(); - - Animated = require('../Animated').default; - AnimatedObject = require('../nodes/AnimatedObject').default; - }); - it('should get the proper value', () => { const anim = new Animated.Value(0); const translateAnim = anim.interpolate({ diff --git a/packages/react-native/Libraries/Animated/__tests__/AnimatedProps-test.js b/packages/react-native/Libraries/Animated/__tests__/AnimatedProps-test.js deleted file mode 100644 index 3759ac2bfe6..00000000000 --- a/packages/react-native/Libraries/Animated/__tests__/AnimatedProps-test.js +++ /dev/null @@ -1,30 +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. - * - * @flow strict-local - * @format - */ - -import AnimatedProps from '../nodes/AnimatedProps'; - -describe('AnimatedProps', () => { - function getValue(inputProps: {[string]: unknown}) { - const animatedProps = new AnimatedProps(inputProps, jest.fn()); - return animatedProps.__getValue(); - } - - it('returns original `style` if it has no nodes', () => { - const style = {color: 'red'}; - expect(getValue({style}).style).toBe(style); - }); - - it('returns original `style` for invalid style values', () => { - const values = [undefined, null, function () {}, true, 123, 'foo']; - for (const value of values) { - expect(getValue({style: value})).toEqual({style: value}); - } - }); -}); diff --git a/packages/react-native/Libraries/Animated/__tests__/AnimatedValue-test.js b/packages/react-native/Libraries/Animated/__tests__/AnimatedValue-itest.js similarity index 77% rename from packages/react-native/Libraries/Animated/__tests__/AnimatedValue-test.js rename to packages/react-native/Libraries/Animated/__tests__/AnimatedValue-itest.js index a77f0123b2a..1984ef56ee5 100644 --- a/packages/react-native/Libraries/Animated/__tests__/AnimatedValue-test.js +++ b/packages/react-native/Libraries/Animated/__tests__/AnimatedValue-itest.js @@ -8,9 +8,33 @@ * @format */ +import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; + +import NativeAnimatedHelper from '../../../src/private/animated/NativeAnimatedHelper'; +import AnimatedValue from '../nodes/AnimatedValue'; + describe('AnimatedValue', () => { - let NativeAnimatedHelper; - let AnimatedValue; + // Fantom uses the real native animated module and does not support + // `jest.spyOn`, so we wrap the relevant `NativeAnimatedHelper.API` methods + // with call-through mocks that count invocations and restore them afterwards. + const restoreAPI: Array<() => void> = []; + + function spyOnAPI(name: string) { + // $FlowFixMe[invalid-computed-prop] + const original = NativeAnimatedHelper.API[name]; + const spy = jest.fn((...args: Array) => + original.apply(NativeAnimatedHelper.API, args), + ); + // $FlowFixMe[prop-missing] + // $FlowFixMe[cannot-write] + NativeAnimatedHelper.API[name] = spy; + restoreAPI.push(() => { + // $FlowFixMe[prop-missing] + // $FlowFixMe[cannot-write] + NativeAnimatedHelper.API[name] = original; + }); + return spy; + } function createNativeAnimatedValue(): AnimatedValue { return new AnimatedValue(0, {useNativeDriver: true}); @@ -32,31 +56,17 @@ describe('AnimatedValue', () => { } beforeEach(() => { - jest.resetModules(); - - jest.mock('../NativeAnimatedTurboModule', () => ({ - __esModule: true, - default: { - addListener: jest.fn(), - createAnimatedNode: jest.fn(), - dropAnimatedNode: jest.fn(), - removeListeners: jest.fn(), - startListeningToAnimatedNodeValue: jest.fn(), - stopListeningToAnimatedNodeValue: jest.fn(), - extractAnimatedNodeOffset: jest.fn(), - // ... - }, - })); - - NativeAnimatedHelper = - require('../../../src/private/animated/NativeAnimatedHelper').default; - AnimatedValue = require('../nodes/AnimatedValue').default; - - jest.spyOn(NativeAnimatedHelper.API, 'createAnimatedNode'); - jest.spyOn(NativeAnimatedHelper.API, 'dropAnimatedNode'); - jest.spyOn(NativeAnimatedHelper.API, 'startListeningToAnimatedNodeValue'); - jest.spyOn(NativeAnimatedHelper.API, 'setWaitingForIdentifier'); - jest.spyOn(NativeAnimatedHelper.API, 'unsetWaitingForIdentifier'); + spyOnAPI('createAnimatedNode'); + spyOnAPI('dropAnimatedNode'); + spyOnAPI('startListeningToAnimatedNodeValue'); + spyOnAPI('setWaitingForIdentifier'); + spyOnAPI('unsetWaitingForIdentifier'); + }); + + afterEach(() => { + while (restoreAPI.length > 0) { + restoreAPI.pop()?.(); + } }); it('emits update events for listeners added', () => { @@ -217,7 +227,13 @@ describe('AnimatedValue', () => { emitMockUpdate(node, 123, 50); - const spy = jest.spyOn(node, '__onAnimatedValueUpdateReceived'); + // $FlowFixMe[method-unbinding] + const original = node.__onAnimatedValueUpdateReceived; + const spy = jest.fn((...args: Array) => + original.apply(node, args), + ); + // $FlowFixMe[cannot-write] + node.__onAnimatedValueUpdateReceived = spy; const mockValue = 100; const mockOffset = 50; @@ -225,7 +241,8 @@ describe('AnimatedValue', () => { emitMockUpdate(node, mockValue, mockOffset); expect(spy).toHaveBeenCalledWith(mockValue, mockOffset); - spy.mockRestore(); + // $FlowFixMe[cannot-write] + node.__onAnimatedValueUpdateReceived = original; }); }); }); diff --git a/packages/react-native/Libraries/Animated/__tests__/Easing-test.js b/packages/react-native/Libraries/Animated/__tests__/Easing-itest.js similarity index 99% rename from packages/react-native/Libraries/Animated/__tests__/Easing-test.js rename to packages/react-native/Libraries/Animated/__tests__/Easing-itest.js index 9581ae1310b..d71a68d28c6 100644 --- a/packages/react-native/Libraries/Animated/__tests__/Easing-test.js +++ b/packages/react-native/Libraries/Animated/__tests__/Easing-itest.js @@ -8,7 +8,7 @@ * @format */ -'use strict'; +import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; import Easing from '../Easing'; diff --git a/packages/react-native/Libraries/Animated/__tests__/Interpolation-test.js b/packages/react-native/Libraries/Animated/__tests__/Interpolation-itest.js similarity index 65% rename from packages/react-native/Libraries/Animated/__tests__/Interpolation-test.js rename to packages/react-native/Libraries/Animated/__tests__/Interpolation-itest.js index fc0fb879264..a2d7f506915 100644 --- a/packages/react-native/Libraries/Animated/__tests__/Interpolation-test.js +++ b/packages/react-native/Libraries/Animated/__tests__/Interpolation-itest.js @@ -8,6 +8,8 @@ * @format */ +import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; + import type { InterpolationConfigSupportedOutputType, InterpolationConfigType, @@ -33,6 +35,13 @@ function createInterpolation( } describe('Interpolation', () => { + const originalConsoleWarn = console.warn; + + afterEach(() => { + // $FlowFixMe[cannot-write] + console.warn = originalConsoleWarn; + }); + it('should work with defaults', () => { const interpolation = createInterpolation({ inputRange: [0, 1], @@ -365,7 +374,10 @@ describe('Interpolation', () => { }); it('should work with PlatformColor', () => { - jest.spyOn(console, 'warn').mockImplementationOnce(() => {}); + const mockWarn = jest.fn(); + // $FlowFixMe[cannot-write] + console.warn = mockWarn; + const interpolation = createInterpolation({ inputRange: [0, 1], outputRange: [ @@ -381,7 +393,7 @@ describe('Interpolation', () => { expect(interpolation(2 / 3)).toStrictEqual( PlatformColor('@android:color/white'), ); - expect(console.warn).toBeCalledWith( + expect(mockWarn).toBeCalledWith( 'PlatformColor interpolation should happen natively, here we fallback to the closest color', ); expect(interpolation(1)).toStrictEqual( @@ -389,20 +401,201 @@ describe('Interpolation', () => { ); }); - it.each([ + for (const [label, outputRange, expected] of [ ['radians', ['1rad', '2rad'], [1, 2]], ['degrees', ['90deg', '180deg'], [Math.PI / 2, Math.PI]], ['numbers', [1024, Math.PI], [1024, Math.PI]], ['unknown', ['5foo', '10foo'], ['5foo', '10foo']], - ])( - 'should convert %s to numbers in the native config', - (_, outputRange, expected) => { + ]) { + it(`should convert ${label} to numbers in the native config`, () => { const config = new AnimatedInterpolation( // $FlowFixMe[incompatible-type] {}, + // $FlowFixMe[incompatible-call] {inputRange: [0, 1], outputRange}, ).__getNativeConfig(); expect(config.outputRange).toEqual(expected); - }, - ); + }); + } +}); + +describe('Interpolation easingStops (native easing baking)', () => { + // Returns the non-uniform [position, value] easing stops emitted in the native + // config for an eased numeric interpolation (or undefined when no easing). + function getEasingStops( + config: InterpolationConfigType, + ): ?Array<[number, number]> { + return new AnimatedInterpolation( + // $FlowFixMe[incompatible-type] + {}, + config, + ).__getNativeConfig().easingStops; + } + + // Mirrors the native easeRatio(): binary-search the bracketing stops and + // linearly interpolate. Out-of-[0,1] ratios pass through (extrapolation). + function reconstructEaseRatio( + stops: Array<[number, number]>, + ): (ratio: number) => number { + return ratio => { + if (stops.length < 2 || ratio < 0 || ratio > 1) { + return ratio; + } + const upper = stops.findIndex(stop => stop[0] > ratio); + if (upper === -1) { + return stops[stops.length - 1][1]; + } + if (upper === 0) { + return stops[0][1]; + } + const [xLo, yLo] = stops[upper - 1]; + const [xHi, yHi] = stops[upper]; + if (xHi === xLo) { + return yHi; + } + return yLo + (yHi - yLo) * ((ratio - xLo) / (xHi - xLo)); + }; + } + + // Max error, in OUTPUT units, between the baked stops and the true easing + // curve across the [0, 1] domain (sampled finely). This is what actually + // shows up on screen, e.g. pixels for a translate. + function maxOutputError( + easing: (input: number) => number, + span: number, + ): number { + const stops = getEasingStops({ + inputRange: [0, 1], + outputRange: [0, span], + easing, + }); + if (stops == null) { + throw new Error('expected easingStops to be emitted'); + } + const approx = reconstructEaseRatio(stops); + let maxErr = 0; + for (let i = 0; i <= 1000; i++) { + const t = i / 1000; + const err = Math.abs(approx(t) - easing(t)) * span; + if (err > maxErr) { + maxErr = err; + } + } + return maxErr; + } + + // Computed once; reused for both the exact-output and the stop-count assertions. + const customLinear = getEasingStops({ + inputRange: [0, 1], + outputRange: [0, 100], + easing: (t: number) => t, + }); + const quad = getEasingStops({ + inputRange: [0, 1], + outputRange: [0, 100], + easing: Easing.quad, + }); + const bounce = getEasingStops({ + inputRange: [0, 1], + outputRange: [0, 100], + easing: Easing.bounce, + }); + const sine = getEasingStops({ + inputRange: [0, 1], + outputRange: [0, 10], + easing: Easing.inOut(Easing.sin), + }); + + it('omits easingStops when easing is linear by identity or absent', () => { + const base = {inputRange: [0, 1], outputRange: [0, 100]}; + expect(getEasingStops(base)).toBe(undefined); + expect(getEasingStops({...base, easing: Easing.linear})).toBe(undefined); + }); + + it('bakes the curve into exact stops whose count adapts to curvature', () => { + // A custom linear fn (not the Easing.linear reference, so not short-circuited) + // collapses to the two endpoints: every interior sample lies on the chord. + expect(customLinear).toEqual([ + [0, 0], + [1, 1], + ]); + + // Constant-curvature quad -> RDP's midpoint splitting yields uniform 1/16 + // spacing; each value is the eased ratio (k/16)^2, independent of span. + expect(quad).toEqual([ + [0, 0], + [0.0625, 0.00390625], + [0.125, 0.015625], + [0.1875, 0.03515625], + [0.25, 0.0625], + [0.3125, 0.09765625], + [0.375, 0.140625], + [0.4375, 0.19140625], + [0.5, 0.25], + [0.5625, 0.31640625], + [0.625, 0.390625], + [0.6875, 0.47265625], + [0.75, 0.5625], + [0.8125, 0.66015625], + [0.875, 0.765625], + [0.9375, 0.87890625], + [1, 1], + ]); + + // A sine S-curve is placed non-uniformly: stops cluster at the two bends and + // leave a large gap across the near-linear middle (0.35 -> 0.62). + expect(sine).toEqual([ + [0, 0], + [0.10546875, 0.02719633730973936], + [0.21875, 0.1134947733186315], + [0.34765625, 0.26973064452088], + [0.62109375, 0.6856585969759188], + [0.7421875, 0.8447702723685335], + [0.875, 0.9619397662556434], + [1, 1], + ]); + + // Stop count rises with curvature — 2 (flat) -> 17 (quad) -> 45 (bounce) — + // and is always bounded by the dense-sample budget (256 + 1 = 257). + expect(bounce?.length).toBe(45); + expect(bounce?.length).toBeLessThanOrEqual(257); + }); + + it('grows the stop count with output span, capped by the tolerance floor', () => { + // Bigger span -> smaller tolerance -> more stops for the same curve... + expect( + getEasingStops({ + inputRange: [0, 1], + outputRange: [0, 10], + easing: Easing.quad, + })?.length, + ).toBe(9); + expect(quad?.length).toBe(17); // span 100, computed once above + expect( + getEasingStops({ + inputRange: [0, 1], + outputRange: [0, 1000], + easing: Easing.quad, + })?.length, + ).toBe(33); + // ...until epsilon hits its floor: a smooth curve caps out (65) rather than + // densifying toward the dense-sample budget. + expect( + getEasingStops({ + inputRange: [0, 1], + outputRange: [0, 100000], + easing: Easing.quad, + })?.length, + ).toBe(65); + }); + + it('keeps on-screen error ~sub-pixel until the tolerance floor', () => { + // Below the floor (span up to ~2500) error stays sub-pixel as span grows. + for (const span of [1, 10, 100, 1000]) { + expect(maxOutputError(Easing.quad, span)).toBeLessThan(0.3); + } + // Past the floor the error grows only with the floor tolerance (1e-4): ~1px + // at span 10000, not the tens a fixed-resolution LUT would accumulate. + expect(maxOutputError(Easing.quad, 10000)).toBeLessThan(1.5); + }); }); diff --git a/packages/react-native/Libraries/Animated/__tests__/TimingAnimation-test.js b/packages/react-native/Libraries/Animated/__tests__/TimingAnimation-itest.js similarity index 93% rename from packages/react-native/Libraries/Animated/__tests__/TimingAnimation-test.js rename to packages/react-native/Libraries/Animated/__tests__/TimingAnimation-itest.js index 0d2213462fd..0a15db495f5 100644 --- a/packages/react-native/Libraries/Animated/__tests__/TimingAnimation-test.js +++ b/packages/react-native/Libraries/Animated/__tests__/TimingAnimation-itest.js @@ -8,7 +8,7 @@ * @format */ -'use strict'; +import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; import TimingAnimation from '../animations/TimingAnimation'; diff --git a/packages/react-native/Libraries/Animated/__tests__/bezier-test.js b/packages/react-native/Libraries/Animated/__tests__/bezier-itest.js similarity index 98% rename from packages/react-native/Libraries/Animated/__tests__/bezier-test.js rename to packages/react-native/Libraries/Animated/__tests__/bezier-itest.js index c7969246478..38d71ce61f5 100644 --- a/packages/react-native/Libraries/Animated/__tests__/bezier-test.js +++ b/packages/react-native/Libraries/Animated/__tests__/bezier-itest.js @@ -14,7 +14,7 @@ * @copyright 2014-2015 Gaetan Renaudeau. MIT License. */ -'use strict'; +import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; import bezier from '../bezier'; diff --git a/packages/react-native/Libraries/Animated/animations/Animation.js b/packages/react-native/Libraries/Animated/animations/Animation.js index 238958f1490..83e1a715379 100644 --- a/packages/react-native/Libraries/Animated/animations/Animation.js +++ b/packages/react-native/Libraries/Animated/animations/Animation.js @@ -70,6 +70,7 @@ export default class Animation { previousAnimation: ?Animation, animatedValue: AnimatedValue, ): void { + // TODO: T274006331 - Remove js-only animation once shared backend is fully rolled out if (!this._useNativeDriver && animatedValue.__isNative === true) { throw new Error( 'Attempting to run JS driven animation on animated node ' + @@ -141,14 +142,22 @@ export default class Animation { animatedValue.__getNativeTag(), config, result => { - this.__notifyAnimationEnd(result); - // When using natively driven animations, once the animation completes, // we need to ensure that the JS side nodes are synced with the updated // values. const {value, offset} = result; - if (value != null) { + const syncBeforeCallback = + ReactNativeFeatureFlags.animatedShouldSyncValueBeforeStartCallback(); + if (syncBeforeCallback && value != null) { animatedValue.__onAnimatedValueUpdateReceived(value, offset); + } + + this.__notifyAnimationEnd(result); + + if (value != null) { + if (!syncBeforeCallback) { + animatedValue.__onAnimatedValueUpdateReceived(value, offset); + } const isJsSyncRemoved = ReactNativeFeatureFlags.cxxNativeAnimatedEnabled(); @@ -158,8 +167,8 @@ export default class Animation { } } - // Once the JS side node is synced with the updated values, trigger an - // update on the AnimatedProps nodes to call any registered callbacks. + // Trigger an update on the AnimatedProps nodes to call any + // registered callbacks now that the JS-side node is in sync. this.__findAnimatedPropsNodes(animatedValue).forEach(node => node.update(), ); diff --git a/packages/react-native/Libraries/Animated/animations/DecayAnimation.js b/packages/react-native/Libraries/Animated/animations/DecayAnimation.js index 35eb106f5a2..d6b834b032e 100644 --- a/packages/react-native/Libraries/Animated/animations/DecayAnimation.js +++ b/packages/react-native/Libraries/Animated/animations/DecayAnimation.js @@ -85,6 +85,7 @@ export default class DecayAnimation extends Animation { this._startTime = Date.now(); const useNativeDriver = this.__startAnimationIfNative(animatedValue); + // TODO: T274006331 - Remove js-only animation once shared backend is fully rolled out if (!useNativeDriver) { this._animationFrame = requestAnimationFrame(() => this.onUpdate()); } diff --git a/packages/react-native/Libraries/Animated/animations/SpringAnimation.js b/packages/react-native/Libraries/Animated/animations/SpringAnimation.js index cb70e445411..f04a527469b 100644 --- a/packages/react-native/Libraries/Animated/animations/SpringAnimation.js +++ b/packages/react-native/Libraries/Animated/animations/SpringAnimation.js @@ -225,6 +225,7 @@ export default class SpringAnimation extends Animation { const start = () => { const useNativeDriver = this.__startAnimationIfNative(animatedValue); + // TODO: T274006331 - Remove js-only animation once shared backend is fully rolled out if (!useNativeDriver) { this.onUpdate(); } diff --git a/packages/react-native/Libraries/Animated/animations/TimingAnimation.js b/packages/react-native/Libraries/Animated/animations/TimingAnimation.js index f872d098bdf..dffb737a988 100644 --- a/packages/react-native/Libraries/Animated/animations/TimingAnimation.js +++ b/packages/react-native/Libraries/Animated/animations/TimingAnimation.js @@ -15,6 +15,7 @@ import type AnimatedValue from '../nodes/AnimatedValue'; import type AnimatedValueXY from '../nodes/AnimatedValueXY'; import type {AnimationConfig, EndCallback} from './Animation'; +import * as ReactNativeFeatureFlags from '../../../src/private/featureflags/ReactNativeFeatureFlags'; import AnimatedColor from '../nodes/AnimatedColor'; import Animation from './Animation'; @@ -69,6 +70,7 @@ export default class TimingAnimation extends Animation { _animationFrame: ?AnimationFrameID; _timeout: ?TimeoutID; _platformConfig: ?PlatformConfig; + _deferredStart: boolean; constructor(config: TimingAnimationConfigSingle) { super(config); @@ -78,6 +80,7 @@ export default class TimingAnimation extends Animation { this._duration = config.duration ?? 500; this._delay = config.delay ?? 0; this._platformConfig = config.platformConfig; + this._deferredStart = false; } __getNativeAnimationConfig(): Readonly<{ @@ -102,6 +105,7 @@ export default class TimingAnimation extends Animation { iterations: this.__iterations, platformConfig: this._platformConfig, debugID: this.__getDebugID(), + deferredStart: this._deferredStart, }; } @@ -116,11 +120,16 @@ export default class TimingAnimation extends Animation { this._fromValue = fromValue; this._onUpdate = onUpdate; + if (ReactNativeFeatureFlags.animatedDeferStartOfTimingAnimations()) { + this._deferredStart = animatedValue.__deferAnimationStart; + animatedValue.__deferAnimationStart = false; + } const start = () => { this._startTime = Date.now(); const useNativeDriver = this.__startAnimationIfNative(animatedValue); + // TODO: T274006331 - Remove js-only animation once shared backend is fully rolled out if (!useNativeDriver) { // Animations that sometimes have 0 duration and sometimes do not // still need to use the native driver when duration is 0 so as to diff --git a/packages/react-native/Libraries/Animated/createAnimatedComponent.js b/packages/react-native/Libraries/Animated/createAnimatedComponent.js index ba58374c208..b6b3953b429 100644 --- a/packages/react-native/Libraries/Animated/createAnimatedComponent.js +++ b/packages/react-native/Libraries/Animated/createAnimatedComponent.js @@ -31,7 +31,7 @@ type Nullable = void | null; type Primitive = string | number | boolean | symbol | void; type Builtin = (...ReadonlyArray) => unknown | Date | Error | RegExp; -export type WithAnimatedValue<+T> = T extends Builtin | Nullable +export type WithAnimatedValue = T extends Builtin | Nullable ? T : T extends Primitive ? @@ -51,7 +51,7 @@ export type WithAnimatedValue<+T> = T extends Builtin | Nullable : T extends ReadonlyArray ? ReadonlyArray> : T extends {...} - ? {+[K in keyof T]: WithAnimatedValue} + ? {readonly [K in keyof T]: WithAnimatedValue} : T; type NonAnimatedProps = @@ -91,7 +91,7 @@ export type AnimatedBaseProps = LooseOmit< export type AnimatedComponentType< Props extends {...}, - +Instance = unknown, + out Instance = unknown, > = component(ref?: React.RefSetter, ...AnimatedProps); export default function createAnimatedComponent< diff --git a/packages/react-native/Libraries/Animated/nodes/AnimatedColor.js b/packages/react-native/Libraries/Animated/nodes/AnimatedColor.js index 7f068989e06..712c259d72c 100644 --- a/packages/react-native/Libraries/Animated/nodes/AnimatedColor.js +++ b/packages/react-native/Libraries/Animated/nodes/AnimatedColor.js @@ -30,18 +30,18 @@ export type AnimatedColorConfig = Readonly<{ type ColorListenerCallback = (value: ColorValue) => unknown; export type RgbaValue = { - +r: number, - +g: number, - +b: number, - +a: number, + readonly r: number, + readonly g: number, + readonly b: number, + readonly a: number, ... }; type RgbaAnimatedValue = { - +r: AnimatedValue, - +g: AnimatedValue, - +b: AnimatedValue, - +a: AnimatedValue, + readonly r: AnimatedValue, + readonly g: AnimatedValue, + readonly b: AnimatedValue, + readonly a: AnimatedValue, ... }; diff --git a/packages/react-native/Libraries/Animated/nodes/AnimatedInterpolation.js b/packages/react-native/Libraries/Animated/nodes/AnimatedInterpolation.js index 891d4393b34..480b3217d04 100644 --- a/packages/react-native/Libraries/Animated/nodes/AnimatedInterpolation.js +++ b/packages/react-native/Libraries/Animated/nodes/AnimatedInterpolation.js @@ -349,6 +349,100 @@ function checkInfiniteRange< ); } +// Ramer–Douglas–Peucker simplification using vertical distance (the curve's +// independent axis is the input position `t`). Keeps the endpoints and any point +// whose removal would push the piecewise-linear approximation more than +// `epsilon` away from the sampled curve. Produces non-uniform stops — dense +// where the curve bends, sparse where it is near-linear. +function simplifyByVerticalDistance( + points: Array<[number, number]>, + epsilon: number, +): Array<[number, number]> { + if (points.length < 3) { + return points; + } + const [x0, y0] = points[0]; + const [x1, y1] = points[points.length - 1]; + const dx = x1 - x0; + let maxDistance = 0; + let maxIndex = -1; + for (let i = 1; i < points.length - 1; i++) { + const [x, y] = points[i]; + const chordY = dx === 0 ? y0 : y0 + ((y1 - y0) * (x - x0)) / dx; + const distance = Math.abs(y - chordY); + if (distance > maxDistance) { + maxDistance = distance; + maxIndex = i; + } + } + if (maxDistance > epsilon) { + const left = simplifyByVerticalDistance( + points.slice(0, maxIndex + 1), + epsilon, + ); + const right = simplifyByVerticalDistance(points.slice(maxIndex), epsilon); + // Drop the duplicated shared point at the split. + return left.slice(0, -1).concat(right); + } + return [points[0], points[points.length - 1]]; +} + +// Samples an `easing` function and simplifies it (RDP) into a compact set of +// non-uniform `[position, value]` stops that the native interpolation node +// applies to each segment's normalized ratio (binary search + linear interp). +// This mirrors the CSS `linear()` easing representation. The tolerance targets a +// sub-pixel error using the interpolation's numeric output span when known. +function sampleEasingStops( + easing: (input: number) => number, + outputRange: ReadonlyArray, +): Array<[number, number]> { + // Dense sampling resolution of the easing curve before simplification. + const DENSE_SAMPLES = 256; + // Target approximation error, in output units (≈ sub-pixel for layout/ + // transform props). Used to derive the simplification tolerance from the span. + const TARGET_ERROR = 0.25; + // Bounds on the (ratio-space) simplification tolerance. + const MIN_TOLERANCE = 1e-4; + const MAX_TOLERANCE = 1e-2; + + // Evenly spaced [t, easing(t)] samples. + // e.g. quad samples: [[0, 0], [0.25, 0.0625], [0.5, 0.25], [0.75, 0.5625], [1, 1]]. + const dense: Array<[number, number]> = []; + for (let i = 0; i <= DENSE_SAMPLES; i++) { + const t = i / DENSE_SAMPLES; + dense.push([t, easing(t)]); + } + + let epsilon = MAX_TOLERANCE; + if (typeof outputRange[0] === 'number') { + let min = outputRange[0]; + let max = outputRange[0]; + for (const value of outputRange) { + if (typeof value === 'number') { + if (value < min) { + min = value; + } + if (value > max) { + max = value; + } + } + } + const span = max - min; + if (span > 0) { + epsilon = TARGET_ERROR / span; + } + } else { + // Non-numeric output (e.g. colors): components live in [0, 255]. + epsilon = TARGET_ERROR / 255; + } + epsilon = Math.min(MAX_TOLERANCE, Math.max(MIN_TOLERANCE, epsilon)); + + // Drops samples within `epsilon` of the chord, keeping a sparse subset. E.g. for + // epsilon in [0.0625, 0.25) the quad samples [[0, 0], [0.25, 0.0625], [0.5, 0.25], [0.75, 0.5625], [1, 1]] + // is trimmed to [[0, 0], [0.5, 0.25], [1, 1]]. + return simplifyByVerticalDistance(dense, epsilon); +} + export default class AnimatedInterpolation< OutputT extends InterpolationConfigSupportedOutputType, > extends AnimatedWithChildren { @@ -439,6 +533,17 @@ export default class AnimatedInterpolation< outputType = 'platform_color'; } + // An interpolation `easing` is a JS-only function. Rather than drop it (the + // native driver would run the segment linearly), sample + simplify it into a + // set of `[position, value]` stops the native node applies per segment. Works + // for every output type since easing acts on the normalized ratio, not the + // output values. + const easing = this._config.easing; + const easingStops = + easing != null && easing !== Easing.linear + ? sampleEasingStops(easing, this._config.outputRange) + : undefined; + return { inputRange: this._config.inputRange, outputRange, @@ -448,6 +553,7 @@ export default class AnimatedInterpolation< extrapolateRight: this._config.extrapolateRight || this._config.extrapolate || 'extend', type: 'interpolation', + easingStops, debugID: this.__getDebugID(), }; } diff --git a/packages/react-native/Libraries/Animated/nodes/AnimatedProps.js b/packages/react-native/Libraries/Animated/nodes/AnimatedProps.js index e053d91635a..6581c150f43 100644 --- a/packages/react-native/Libraries/Animated/nodes/AnimatedProps.js +++ b/packages/react-native/Libraries/Animated/nodes/AnimatedProps.js @@ -15,6 +15,7 @@ import type {AnimatedStyleAllowlist} from './AnimatedStyle'; import NativeAnimatedHelper from '../../../src/private/animated/NativeAnimatedHelper'; import * as ReactNativeFeatureFlags from '../../../src/private/featureflags/ReactNativeFeatureFlags'; +import {getFabricUIManager} from '../../ReactNative/FabricUIManager'; import {findNodeHandle} from '../../ReactNative/RendererProxy'; import {getNodeFromPublicInstance} from '../../ReactPrivate/ReactNativePrivateInterface'; import flattenStyle from '../../StyleSheet/flattenStyle'; @@ -30,7 +31,7 @@ export type AnimatedPropsAllowlist = Readonly<{ }>; type TargetView = { - +instance: TargetViewInstance, + readonly instance: TargetViewInstance, connectedViewTag: ?number, }; type TargetViewInstance = React.ElementRef; @@ -298,8 +299,31 @@ export default class AnimatedProps extends AnimatedNode { } invariant(this.__isNative, 'Expected node to be marked as "native"'); - // $FlowExpectedError[incompatible-type] - target.instance may be an HTMLElement but we need ReactNativeElement for Fabric - const shadowNode = getNodeFromPublicInstance(target.instance); + // Host components and ScrollView (whose ref is the host instance) resolve a + // shadow node directly; FlatList/SectionList are class composites that expose + // the host via getNativeScrollRef(). + // $FlowFixMe[unclear-type] - Legacy instance assumptions. + const instance: any = target.instance; + const candidates = [instance, instance?.getNativeScrollRef?.()]; + let shadowNode = null; + for (const candidate of candidates) { + if (candidate == null) { + continue; + } + shadowNode = getNodeFromPublicInstance(candidate); + if (shadowNode != null) { + break; + } + } + // Any other class composite: resolve from the host tag #connectAnimatedView + // already found via findNodeHandle (the lookup runs on the native side). + const connectedViewTag = target.connectedViewTag; + if (shadowNode == null && connectedViewTag != null) { + shadowNode = + getFabricUIManager()?.findShadowNodeByTag_DEPRECATED?.( + connectedViewTag, + ); + } if (shadowNode == null) { return; } diff --git a/packages/react-native/Libraries/Animated/nodes/AnimatedValue.js b/packages/react-native/Libraries/Animated/nodes/AnimatedValue.js index 8650912edf6..76dba2196f4 100644 --- a/packages/react-native/Libraries/Animated/nodes/AnimatedValue.js +++ b/packages/react-native/Libraries/Animated/nodes/AnimatedValue.js @@ -21,6 +21,7 @@ import type {AnimatedNodeConfig} from './AnimatedNode'; import type AnimatedTracking from './AnimatedTracking'; import NativeAnimatedHelper from '../../../src/private/animated/NativeAnimatedHelper'; +import * as ReactNativeFeatureFlags from '../../../src/private/featureflags/ReactNativeFeatureFlags'; import AnimatedInterpolation from './AnimatedInterpolation'; import AnimatedWithChildren from './AnimatedWithChildren'; @@ -95,6 +96,7 @@ export default class AnimatedValue extends AnimatedWithChildren { _offset: number; _animation: ?Animation; _tracking: ?AnimatedTracking; + __deferAnimationStart: boolean; constructor(value: number, config?: ?AnimatedValueConfig) { super(config); @@ -107,6 +109,8 @@ export default class AnimatedValue extends AnimatedWithChildren { this._startingValue = this._value = value; this._offset = 0; + this.__deferAnimationStart = + ReactNativeFeatureFlags.animatedDeferStartOfTimingAnimations(); this._animation = null; if (config && config.useNativeDriver) { this.__makeNative(); @@ -327,6 +331,10 @@ export default class AnimatedValue extends AnimatedWithChildren { result => { this._animation = null; callback && callback(result); + if (this._animation == null) { + this.__deferAnimationStart = + ReactNativeFeatureFlags.animatedDeferStartOfTimingAnimations(); + } }, previousAnimation, this, diff --git a/packages/react-native/Libraries/Animated/nodes/AnimatedValueXY.js b/packages/react-native/Libraries/Animated/nodes/AnimatedValueXY.js index acc969ad2d2..0c008919c8b 100644 --- a/packages/react-native/Libraries/Animated/nodes/AnimatedValueXY.js +++ b/packages/react-native/Libraries/Animated/nodes/AnimatedValueXY.js @@ -45,8 +45,8 @@ export default class AnimatedValueXY extends AnimatedWithChildren { constructor( valueIn?: ?{ - +x: number | AnimatedValue, - +y: number | AnimatedValue, + readonly x: number | AnimatedValue, + readonly y: number | AnimatedValue, ... }, config?: ?AnimatedValueXYConfig, diff --git a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h index ce0bf608ce4..6cb24d64f42 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h +++ b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h @@ -10,7 +10,6 @@ #import "RCTReactNativeFactory.h" #import "RCTRootViewFactory.h" -@class RCTBridge; @protocol RCTBridgeDelegate; @protocol RCTComponentViewProtocol; @class RCTRootView; @@ -41,10 +40,6 @@ NS_ASSUME_NONNULL_BEGIN * * Overridable methods * Shared: - * - (RCTBridge *)createBridgeWithDelegate:(id)delegate launchOptions:(NSDictionary - *)launchOptions; - * - (UIView *)createRootViewWithBridge:(RCTBridge *)bridge moduleName:(NSString*)moduleName initProps:(NSDictionary - *)initProps; * - (UIViewController *)createRootViewController; * - (void)setRootView:(UIView *)rootView toRootViewController:(UIViewController *)rootViewController; * New Architecture: @@ -70,8 +65,13 @@ __attribute__((deprecated( /// The window object, used to render the UViewControllers @property (nonatomic, strong, nonnull) RCTPlatformWindow *window; // [macOS] +#if !defined(RCT_REMOVE_LEGACY_ARCH) @property (nonatomic, nullable) RCTBridge *bridge __attribute__((deprecated("The bridge is deprecated and will be removed when removing the legacy architecture."))); +@property (nonatomic, nullable) RCTSurfacePresenterBridgeAdapter *bridgeAdapter __attribute__(( + deprecated("The bridge adapter is deprecated and will be removed when removing the legacy architecture."))); +#endif + @property (nonatomic, strong, nullable) NSString *moduleName; @property (nonatomic, strong, nullable) NSDictionary *initialProps; @property (nonatomic, strong) RCTReactNativeFactory *reactNativeFactory; @@ -79,10 +79,6 @@ __attribute__((deprecated( /// If `automaticallyLoadReactNativeWindow` is set to `true`, the React Native window will be loaded automatically. @property (nonatomic, assign) BOOL automaticallyLoadReactNativeWindow; -@property (nonatomic, nullable) RCTSurfacePresenterBridgeAdapter *bridgeAdapter __attribute__(( - deprecated("The bridge adapter is deprecated and will be removed when removing the legacy architecture."))); -; - - (RCTRootViewFactory *)rootViewFactory; @end diff --git a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm index c3c8588bdd8..599241086b4 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm @@ -103,24 +103,4 @@ - (RCTRootViewFactory *)rootViewFactory return self.reactNativeFactory.rootViewFactory; } -- (RCTBridge *)bridge -{ - return self.rootViewFactory.bridge; -} - -- (RCTSurfacePresenterBridgeAdapter *)bridgeAdapter -{ - return self.rootViewFactory.bridgeAdapter; -} - -- (void)setBridge:(RCTBridge *)bridge -{ - self.reactNativeFactory.rootViewFactory.bridge = bridge; -} - -- (void)setBridgeAdapter:(RCTSurfacePresenterBridgeAdapter *)bridgeAdapter -{ - self.reactNativeFactory.rootViewFactory.bridgeAdapter = bridgeAdapter; -} - @end diff --git a/packages/react-native/Libraries/AppDelegate/RCTAppSetupUtils.h b/packages/react-native/Libraries/AppDelegate/RCTAppSetupUtils.h index 86c480799fd..d76708c2f95 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTAppSetupUtils.h +++ b/packages/react-native/Libraries/AppDelegate/RCTAppSetupUtils.h @@ -38,22 +38,4 @@ std::unique_ptr RCTAppSetupDefaultJsExecutor RCTTurboModuleManager *turboModuleManager, const std::shared_ptr &runtimeScheduler); -std::unique_ptr RCTAppSetupJsExecutorFactoryForOldArch( - RCTBridge *bridge, - const std::shared_ptr &runtimeScheduler) - __attribute__((deprecated( - "RCTAppSetupJsExecutorFactoryForOldArch(RCTBridge *, RuntimeScheduler) is deprecated and will be removed when we remove the legacy architecture."))); -; - #endif // __cplusplus - -RCT_EXTERN_C_BEGIN - -void RCTAppSetupPrepareApp(UIApplication *application, BOOL turboModuleEnabled) __attribute__((deprecated( - "RCTAppSetupPrepareApp(UIApplication, BOOL) is deprecated and it's signature will change when we remove the legacy arch"))); -RCTUIView * // [macOS] -RCTAppSetupDefaultRootView(RCTBridge *bridge, NSString *moduleName, NSDictionary *initialProperties, BOOL fabricEnabled) - __attribute__((deprecated( - "RCTAppSetupDefaultRootView(RCTBridge *, NSString *, NSDictionary *, BOOL) is deprecated and it's signature will change when we remove the legacy arch"))); - -RCT_EXTERN_C_END diff --git a/packages/react-native/Libraries/AppDelegate/RCTAppSetupUtils.mm b/packages/react-native/Libraries/AppDelegate/RCTAppSetupUtils.mm index 7a1a2220c86..ae3ae256a8c 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTAppSetupUtils.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTAppSetupUtils.mm @@ -7,7 +7,6 @@ #import "RCTAppSetupUtils.h" -#import #import #import @@ -29,30 +28,6 @@ #import "RCTDependencyProvider.h" -void RCTAppSetupPrepareApp(UIApplication *application, BOOL turboModuleEnabled) -{ - RCTEnableTurboModule(YES); - -#if DEBUG -#if !TARGET_OS_OSX // [macOS] - // Disable idle timer in dev builds to avoid putting application in background and complicating - // Metro reconnection logic. Users only need this when running the application using our CLI tooling. - application.idleTimerDisabled = YES; -#endif // macOS] -#endif -} - -RCTUIView * // [macOS] -RCTAppSetupDefaultRootView(RCTBridge *bridge, NSString *moduleName, NSDictionary *initialProperties, BOOL fabricEnabled) -{ - id surface = [[RCTFabricSurface alloc] initWithBridge:bridge - moduleName:moduleName - initialProperties:initialProperties]; - RCTUIView *rootView = [[RCTSurfaceHostingProxyRootView alloc] initWithSurface:surface]; // [macOS] - [surface start]; - return rootView; -} - NSArray *RCTAppSetupUnstableModulesRequiringMainQueueSetup(id dependencyProvider) { // For oss, insert core main queue setup modules here @@ -122,60 +97,5 @@ void RCTAppSetupPrepareApp(UIApplication *application, BOOL turboModuleEnabled) RCTTurboModuleManager *turboModuleManager, const std::shared_ptr &runtimeScheduler) { -#ifndef RCT_REMOVE_LEGACY_ARCH - // Necessary to allow NativeModules to lookup TurboModules - [bridge setRCTTurboModuleRegistry:turboModuleManager]; - -#if RCT_DEV - /** - * Instantiating DevMenu has the side-effect of registering - * shortcuts for CMD + d, CMD + i, and CMD + n via RCTDevMenu. - * Therefore, when TurboModules are enabled, we must manually create this - * NativeModule. - */ - [turboModuleManager moduleForName:"RCTDevMenu"]; -#endif // end RCT_DEV - - auto runtimeInstallerLambda = [turboModuleManager, bridge, runtimeScheduler](facebook::jsi::Runtime &runtime) { - if (!bridge || !turboModuleManager) { - return; - } - if (runtimeScheduler) { - facebook::react::RuntimeSchedulerBinding::createAndInstallIfNeeded(runtime, runtimeScheduler); - } - [turboModuleManager installJSBindings:runtime]; - }; -#if USE_THIRD_PARTY_JSC != 1 - return std::make_unique( - facebook::react::RCTJSIExecutorRuntimeInstaller(runtimeInstallerLambda)); -#endif -#else - // This method should not be invoked in the New Arch. So when Legacy Arch is removed, we can - // safly return a nullptr. - return nullptr; -#endif -} - -std::unique_ptr RCTAppSetupJsExecutorFactoryForOldArch( - RCTBridge *bridge, - const std::shared_ptr &runtimeScheduler) -{ -#ifndef RCT_REMOVE_LEGACY_ARCH - auto runtimeInstallerLambda = [bridge, runtimeScheduler](facebook::jsi::Runtime &runtime) { - if (!bridge) { - return; - } - if (runtimeScheduler) { - facebook::react::RuntimeSchedulerBinding::createAndInstallIfNeeded(runtime, runtimeScheduler); - } - }; -#if USE_THIRD_PARTY_JSC != 1 - return std::make_unique( - facebook::react::RCTJSIExecutorRuntimeInstaller(runtimeInstallerLambda)); -#endif -#else - // This method should not be invoked in the New Arch. So when Legacy Arch is removed, we can - // safly return a nullptr. return nullptr; -#endif } diff --git a/packages/react-native/Libraries/AppDelegate/RCTArchConfiguratorProtocol.h b/packages/react-native/Libraries/AppDelegate/RCTArchConfiguratorProtocol.h deleted file mode 100644 index 5046093c43a..00000000000 --- a/packages/react-native/Libraries/AppDelegate/RCTArchConfiguratorProtocol.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -__attribute__((deprecated( - "RCTArchConfiguratorProtocol is deprecated and will be removed when we remove the legacy architecture."))); -@protocol RCTArchConfiguratorProtocol -/// This method controls whether the `turboModules` feature of the New Architecture is turned on or off. -/// -/// @note: This is required to be rendering on Fabric (i.e. on the New Architecture). -/// @return: `true` if the Turbo Native Module are enabled. Otherwise, it returns `false`. -- (BOOL)turboModuleEnabled __attribute__((deprecated("This will be removed in a future version of React Native"))); - -/// This method controls whether the App will use the Fabric renderer of the New Architecture or not. -/// -/// @return: `true` if the Fabric Renderer is enabled. Otherwise, it returns `false`. -- (BOOL)fabricEnabled __attribute__((deprecated("This will be removed in a future version of React Native"))); - -/// This method controls whether React Native's new initialization layer is enabled. -/// -/// @return: `true` if the new initialization layer is enabled. Otherwise returns `false`. -- (BOOL)bridgelessEnabled __attribute__((deprecated("This will be removed in a future version of React Native"))); - -/// This method controls whether React Native uses new Architecture. -/// -/// @return: `true` if the new architecture is enabled. Otherwise returns `false`. -- (BOOL)newArchEnabled __attribute__((deprecated("This will be removed in a future version of React Native"))); -@end - -NS_ASSUME_NONNULL_END diff --git a/packages/react-native/Libraries/AppDelegate/RCTDefaultReactNativeFactoryDelegate.mm b/packages/react-native/Libraries/AppDelegate/RCTDefaultReactNativeFactoryDelegate.mm index ba0e9daaee5..76e2cb6b37a 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTDefaultReactNativeFactoryDelegate.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTDefaultReactNativeFactoryDelegate.mm @@ -13,9 +13,16 @@ #import #endif +#import +#import #import -@implementation RCTDefaultReactNativeFactoryDelegate +@implementation RCTDefaultReactNativeFactoryDelegate { + // C++ Native Animated provider, created once on first use (getTurboModule: may be called + // concurrently for different module names). + RCTAnimatedModuleProvider *_animatedModuleProvider; + dispatch_once_t _animatedModuleProviderToken; +} @synthesize dependencyProvider; @@ -26,17 +33,13 @@ - (NSURL *_Nullable)sourceURLForBridge:(nonnull RCTBridge *)bridge return nil; } -- (UIViewController *)createRootViewController -{ - return [UIViewController new]; -} - -- (RCTBridge *)createBridgeWithDelegate:(id)delegate launchOptions:(NSDictionary *)launchOptions +- (RCTPlatformViewController *)createRootViewController // [macOS] { - return [[RCTBridge alloc] initWithDelegate:delegate launchOptions:launchOptions]; + return [RCTPlatformViewController new]; // [macOS] } -- (void)setRootView:(RCTPlatformView *)rootView toRootViewController:(RCTPlatformViewController *)rootViewController +- (void)setRootView:(RCTPlatformView *)rootView // [macOS + toRootViewController:(RCTPlatformViewController *)rootViewController // macOS] { rootViewController.view = rootView; } @@ -57,25 +60,6 @@ - (void)customizeRootView:(RCTRootView *)rootView // Override point for customization after application launch. } -- (RCTPlatformView *)createRootViewWithBridge:(RCTBridge *)bridge - moduleName:(NSString *)moduleName - initProps:(NSDictionary *)initProps -{ - RCTUIView *rootView = RCTAppSetupDefaultRootView(bridge, moduleName, initProps, YES); - -#if !TARGET_OS_OSX // [macOS] -#if TARGET_OS_TV - rootView.backgroundColor = [UIColor clearColor]; -#else - rootView.backgroundColor = [UIColor systemBackgroundColor]; -#endif -#else // [macOS - rootView.backgroundColor = [NSColor windowBackgroundColor]; -#endif // macOS] - - return rootView; -} - - (RCTColorSpace)defaultColorSpace { return RCTColorSpaceSRGB; @@ -113,6 +97,16 @@ - (void)hostDidStart:(RCTHost *)host - (std::shared_ptr)getTurboModule:(const std::string &)name jsInvoker:(std::shared_ptr)jsInvoker { + // The dedicated provider supplies the platform-driven C++ Animated module only when the shared + // animated backend is off; with it on, DefaultTurboModules serves AnimatedModule instead. + if (!facebook::react::ReactNativeFeatureFlags::useSharedAnimatedBackend()) { + dispatch_once(&_animatedModuleProviderToken, ^{ + _animatedModuleProvider = [RCTAnimatedModuleProvider new]; + }); + if (auto animatedModule = [_animatedModuleProvider getTurboModule:name jsInvoker:jsInvoker]) { + return animatedModule; + } + } return facebook::react::DefaultTurboModules::getTurboModule(name, jsInvoker); } diff --git a/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.h b/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.h index 3c7c78800df..38a385e06d2 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.h +++ b/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.h @@ -8,7 +8,6 @@ #import #import #import // [macOS] -#import "RCTArchConfiguratorProtocol.h" #import "RCTDependencyProvider.h" #import "RCTJSRuntimeConfiguratorProtocol.h" #import "RCTRootViewFactory.h" @@ -39,8 +38,7 @@ typedef NS_ENUM(NSInteger, RCTReleaseLevel) { Canary, Experimental, Stable }; RCTTurboModuleManagerDelegate, RCTComponentViewFactoryComponentProvider, #endif - RCTJSRuntimeConfiguratorProtocol, - RCTArchConfiguratorProtocol> + RCTJSRuntimeConfiguratorProtocol> /// Return the bundle URL for the main bundle. - (NSURL *__nullable)bundleURL; @@ -48,39 +46,6 @@ typedef NS_ENUM(NSInteger, RCTReleaseLevel) { Canary, Experimental, Stable }; @property (nonatomic, strong) id dependencyProvider; @optional -/** - * It creates a `RCTBridge` using a delegate and some launch options. - * By default, it is invoked passing `self` as a delegate. - * You can override this function to customize the logic that creates the RCTBridge - * - * @parameter: delegate - an object that implements the `RCTBridgeDelegate` protocol. - * @parameter: launchOptions - a dictionary with a set of options. - * - * @returns: a newly created instance of RCTBridge. - */ -- (RCTBridge *)createBridgeWithDelegate:(id)delegate - launchOptions:(NSDictionary *)launchOptions - __attribute__((deprecated( - "createBridgeWithDelegate:launchOptions: is deprecated and will be removed when removing the legacy architecture."))); - -/** - * It creates a `UIView` starting from a bridge, a module name and a set of initial properties. - * By default, it is invoked using the bridge created by `createBridgeWithDelegate:launchOptions` and - * the name in the `self.moduleName` variable. - * You can override this function to customize the logic that creates the Root View. - * - * @parameter: bridge - an instance of the `RCTBridge` object. - * @parameter: moduleName - the name of the app, used by Metro to resolve the module. - * @parameter: initProps - a set of initial properties. - * - * @returns: a UIView properly configured with a bridge for React Native. - */ -- (RCTPlatformView *)createRootViewWithBridge:(RCTBridge *)bridge - moduleName:(NSString *)moduleName - initProps:(NSDictionary *)initProps - __attribute__((deprecated( - "createRootViewWithBridge:moduleName:initProps is deprecated and will be removed when removing the legacy architecture."))); - /// This method returns a map of Component Descriptors and Components classes that needs to be registered in the /// new renderer. The Component Descriptor is a string which represent the name used in JS to refer to the native /// component. The default implementation returns an empty dictionary. Subclasses can override this method to register @@ -108,13 +73,12 @@ typedef NS_ENUM(NSInteger, RCTReleaseLevel) { Canary, Experimental, Stable }; initialProperties:(NSDictionary *_Nullable)initialProperties launchOptions:(NSDictionary *_Nullable)launchOptions; -@property (nonatomic, nullable) RCTBridge *bridge - __attribute__((deprecated("The bridge is deprecated and will be removed when removing the legacy architecture."))); -@property (nonatomic, strong, nonnull) RCTRootViewFactory *rootViewFactory; - +#if !defined(RCT_REMOVE_LEGACY_ARCH) @property (nonatomic, nullable) RCTSurfacePresenterBridgeAdapter *bridgeAdapter __attribute__(( deprecated("The bridgeAdapter is deprecated and will be removed when removing the legacy architecture."))); -; +#endif + +@property (nonatomic, strong, nonnull) RCTRootViewFactory *rootViewFactory; @property (nonatomic, weak) id delegate; diff --git a/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.mm b/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.mm index 1c9f80c802d..d813fc061c1 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.mm @@ -58,7 +58,6 @@ - (instancetype)initWithDelegate:(id)delegate rel [self _setUpFeatureFlags:releaseLevel]; [RCTColorSpaceUtils applyDefaultColorSpace:[self defaultColorSpace]]; - RCTEnableTurboModule(YES); self.rootViewFactory = [self createRCTRootViewFactory]; @@ -74,14 +73,14 @@ - (void)startReactNativeWithModuleName:(NSString *)moduleName inWindow:(RCTPlatf } - (void)startReactNativeWithModuleName:(NSString *)moduleName - inWindow:(RCTPlatformWindow *_Nullable)window // [macOS] + inWindow:(RCTPlatformWindow *_Nullable)window launchOptions:(NSDictionary *_Nullable)launchOptions { [self startReactNativeWithModuleName:moduleName inWindow:window initialProperties:nil launchOptions:launchOptions]; } - (void)startReactNativeWithModuleName:(NSString *)moduleName - inWindow:(RCTPlatformWindow *_Nullable)window + inWindow:(RCTPlatformWindow *_Nullable)window // [macOS] initialProperties:(NSDictionary *_Nullable)initialProperties launchOptions:(NSDictionary *_Nullable)launchOptions { @@ -139,28 +138,6 @@ - (JSRuntimeFactoryRef)createJSRuntimeFactory return [_delegate createJSRuntimeFactory]; } -#pragma mark - RCTArchConfiguratorProtocol - -- (BOOL)newArchEnabled -{ - return YES; -} - -- (BOOL)fabricEnabled -{ - return YES; -} - -- (BOOL)turboModuleEnabled -{ - return YES; -} - -- (BOOL)bridgelessEnabled -{ - return YES; -} - #pragma mark - RCTTurboModuleManagerDelegate - (Class)getModuleClassFromName:(const char *)name @@ -267,26 +244,13 @@ - (RCTRootViewFactory *)createRCTRootViewFactory turboModuleEnabled:YES bridgelessEnabled:YES]; - configuration.createRootViewWithBridge = ^RCTPlatformView *(RCTBridge *bridge, NSString *moduleName, NSDictionary *initProps) { - return [weakSelf.delegate createRootViewWithBridge:bridge moduleName:moduleName initProps:initProps]; - }; - - configuration.createBridgeWithDelegate = ^RCTBridge *(id delegate, NSDictionary *launchOptions) { - return [weakSelf.delegate createBridgeWithDelegate:delegate launchOptions:launchOptions]; - }; - - configuration.customizeRootView = ^(RCTPlatformView *_Nonnull rootView) { + configuration.customizeRootView = ^(RCTPlatformView *_Nonnull rootView) { // [macOS] [weakSelf.delegate customizeRootView:(RCTRootView *)rootView]; }; configuration.sourceURLForBridge = ^NSURL *_Nullable(RCTBridge *_Nonnull bridge) { -#ifndef RCT_REMOVE_LEGACY_ARCH - return [weakSelf.delegate sourceURLForBridge:bridge]; -#else - // When the Legacy Arch is removed, the Delegate does not have a sourceURLForBridge method return [weakSelf.delegate bundleURL]; -#endif }; if ([self.delegate respondsToSelector:@selector(extraModulesForBridge:)]) { @@ -296,52 +260,6 @@ - (RCTRootViewFactory *)createRCTRootViewFactory }; } -#ifndef RCT_REMOVE_LEGACY_ARCH - // When the Legacy Arch is removed, the Delegate does not have a extraLazyModuleClassesForBridge method - if ([self.delegate respondsToSelector:@selector(extraLazyModuleClassesForBridge:)]) { - configuration.extraLazyModuleClassesForBridge = - ^NSDictionary *_Nonnull(RCTBridge *_Nonnull bridge) - { - return [weakSelf.delegate extraLazyModuleClassesForBridge:bridge]; - }; - } -#endif - -#ifndef RCT_REMOVE_LEGACY_ARCH - // When the Legacy Arch is removed, the Delegate does not have a bridge:didNotFindModule method - // We return NO, because if we have invoked this method is unlikely that the module will be actually registered - if ([self.delegate respondsToSelector:@selector(bridge:didNotFindModule:)]) { - configuration.bridgeDidNotFindModule = ^BOOL(RCTBridge *_Nonnull bridge, NSString *_Nonnull moduleName) { - return [weakSelf.delegate bridge:bridge didNotFindModule:moduleName]; - }; - } -#endif - -#ifndef RCT_REMOVE_LEGACY_ARCH - // When the Legacy Arch is removed, the Delegate does not have a - // loadSourceForBridge:onProgress:onComplete: method - // We then call the loadBundleAtURL:onProgress:onComplete: instead - if ([self.delegate respondsToSelector:@selector(loadSourceForBridge:onProgress:onComplete:)]) { - configuration.loadSourceForBridgeWithProgress = - ^(RCTBridge *_Nonnull bridge, - RCTSourceLoadProgressBlock _Nonnull onProgress, - RCTSourceLoadBlock _Nonnull loadCallback) { - [weakSelf.delegate loadSourceForBridge:bridge onProgress:onProgress onComplete:loadCallback]; - }; - } -#endif - -#ifndef RCT_REMOVE_LEGACY_ARCH - // When the Legacy Arch is removed, the Delegate does not have a - // loadSourceForBridge:withBlock: method - // We then call the loadBundleAtURL:onProgress:onComplete: instead - if ([self.delegate respondsToSelector:@selector(loadSourceForBridge:withBlock:)]) { - configuration.loadSourceForBridge = ^(RCTBridge *_Nonnull bridge, RCTSourceLoadBlock _Nonnull loadCallback) { - [weakSelf.delegate loadSourceForBridge:bridge withBlock:loadCallback]; - }; - } -#endif - configuration.jsRuntimeConfiguratorDelegate = self; return [[RCTRootViewFactory alloc] initWithTurboModuleDelegate:self hostDelegate:self configuration:configuration]; diff --git a/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.h b/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.h index cb50155461c..16ab5152219 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.h +++ b/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.h @@ -184,9 +184,12 @@ typedef void (^RCTLoadSourceForBridgeBlock)(RCTBridge *bridge, RCTSourceLoadBloc */ @interface RCTRootViewFactory : NSObject +#if !defined(RCT_REMOVE_LEGACY_ARCH) @property (nonatomic, strong, nullable) RCTBridge *bridge; -@property (nonatomic, strong, nullable) RCTHost *reactHost; @property (nonatomic, strong, nullable) RCTSurfacePresenterBridgeAdapter *bridgeAdapter; +#endif + +@property (nonatomic, strong, nullable) RCTHost *reactHost; - (instancetype)initWithConfiguration:(RCTRootViewFactoryConfiguration *)configuration andTurboModuleManagerDelegate:(id _Nullable)turboModuleManagerDelegate; diff --git a/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.mm b/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.mm index 440920a31c8..98daf34c093 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.mm @@ -6,11 +6,9 @@ */ #import "RCTRootViewFactory.h" -#import #import #import #import -#import #import #import #import "RCTAppDelegate.h" @@ -94,7 +92,7 @@ - (instancetype)initWithBundleURL:(NSURL *)bundleURL @end -@interface RCTRootViewFactory () { +@interface RCTRootViewFactory () { std::shared_ptr _contextContainer; std::shared_ptr _runtimeScheduler; } @@ -158,8 +156,9 @@ - (void)initializeReactHostWithLaunchOptions:(NSDictionary *)launchOptions devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration { // Enable TurboModule interop by default in Bridgeless mode +#ifndef RCT_REMOVE_LEGACY_MODULE_INTEROP RCTEnableTurboModuleInterop(YES); - RCTEnableTurboModuleInteropBridgeProxy(YES); +#endif // RCT_REMOVE_LEGACY_MODULE_INTEROP [self createReactHostIfNeeded:launchOptions bundleConfiguration:bundleConfiguration @@ -214,75 +213,6 @@ - (RCTPlatformView *)viewWithModuleName:(NSString *)moduleName // [macOS] return surfaceHostingProxyRootView; } -- (RCTBridge *)createBridgeWithDelegate:(id)delegate launchOptions:(NSDictionary *)launchOptions -{ - return [[RCTBridge alloc] initWithDelegate:delegate launchOptions:launchOptions]; -} - -- (RCTPlatformView *)createRootViewWithBridge:(RCTBridge *)bridge - moduleName:(NSString *)moduleName - initProps:(NSDictionary *)initProps -{ - RCTPlatformView *rootView = RCTAppSetupDefaultRootView(bridge, moduleName, initProps, YES); // [macOS] - -#if RCT_DEV_MENU // [macOS - if ([rootView isKindOfClass:[RCTSurfaceHostingView class]]) { - RCTDevMenu *devMenu = [bridge moduleForClass:[RCTDevMenu class]]; - if (devMenu) { - [(RCTSurfaceHostingView *)rootView setDevMenu:devMenu]; - } - } -#endif // macOS] - -#if !TARGET_OS_OSX // [macOS] -#if !TARGET_OS_TV - rootView.backgroundColor = [UIColor systemBackgroundColor]; -#else - rootView.backgroundColor = [UIColor blackColor]; -#endif -#endif // [macOS] - return rootView; -} - -#pragma mark - RCTCxxBridgeDelegate -- (std::unique_ptr)jsExecutorFactoryForBridge:(RCTBridge *)bridge -{ - _runtimeScheduler = std::make_shared(RCTRuntimeExecutorFromBridge(bridge)); - - std::shared_ptr callInvoker = - std::make_shared(_runtimeScheduler); - RCTTurboModuleManager *turboModuleManager = [[RCTTurboModuleManager alloc] initWithBridge:bridge - delegate:_turboModuleManagerDelegate - jsInvoker:callInvoker]; - _contextContainer->erase(facebook::react::RuntimeSchedulerKey); - _contextContainer->insert(facebook::react::RuntimeSchedulerKey, _runtimeScheduler); - return RCTAppSetupDefaultJsExecutorFactory(bridge, turboModuleManager, _runtimeScheduler); -} - -- (void)createBridgeIfNeeded:(NSDictionary *)launchOptions -{ - if (self.bridge != nil) { - return; - } - - if (self->_configuration.createBridgeWithDelegate != nil) { - self.bridge = self->_configuration.createBridgeWithDelegate(self, launchOptions); - } else { - self.bridge = [self createBridgeWithDelegate:self launchOptions:launchOptions]; - } -} - -- (void)createBridgeAdapterIfNeeded -{ - if (self.bridgeAdapter != nullptr) { - return; - } - - self.bridgeAdapter = [[RCTSurfacePresenterBridgeAdapter alloc] initWithBridge:self.bridge - contextContainer:_contextContainer]; - self.bridge.surfacePresenter = self.bridgeAdapter.surfacePresenter; -} - #pragma mark - New Arch Utilities - (void)createReactHostIfNeeded:(NSDictionary *)launchOptions @@ -349,46 +279,6 @@ - (RCTHost *)createReactHost:(NSDictionary *)launchOptions return nil; } -- (NSDictionary *)extraLazyModuleClassesForBridge:(RCTBridge *)bridge -{ - if (_configuration.extraLazyModuleClassesForBridge != nil) { - return _configuration.extraLazyModuleClassesForBridge(bridge); - } - return nil; -} - -- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge -{ - if (_configuration.sourceURLForBridge != nil) { - return _configuration.sourceURLForBridge(bridge); - } - return [self bundleURL]; -} - -- (BOOL)bridge:(RCTBridge *)bridge didNotFindModule:(NSString *)moduleName -{ - if (_configuration.bridgeDidNotFindModule != nil) { - return _configuration.bridgeDidNotFindModule(bridge, moduleName); - } - return NO; -} - -- (void)loadSourceForBridge:(RCTBridge *)bridge withBlock:(RCTSourceLoadBlock)loadCallback -{ - if (_configuration.loadSourceForBridge != nil) { - _configuration.loadSourceForBridge(bridge, loadCallback); - } -} - -- (void)loadSourceForBridge:(RCTBridge *)bridge - onProgress:(RCTSourceLoadProgressBlock)onProgress - onComplete:(RCTSourceLoadBlock)loadCallback -{ - if (_configuration.loadSourceForBridgeWithProgress != nil) { - _configuration.loadSourceForBridgeWithProgress(bridge, onProgress, loadCallback); - } -} - - (NSURL *)bundleURL { return self->_configuration.bundleURLBlock(); diff --git a/packages/react-native/Libraries/AppDelegate/React-RCTAppDelegate.podspec b/packages/react-native/Libraries/AppDelegate/React-RCTAppDelegate.podspec index 437a80be92c..c4322bc2c36 100644 --- a/packages/react-native/Libraries/AppDelegate/React-RCTAppDelegate.podspec +++ b/packages/react-native/Libraries/AppDelegate/React-RCTAppDelegate.podspec @@ -62,6 +62,7 @@ Pod::Spec.new do |s| s.dependency "React-CoreModules" s.dependency "React-RCTFBReactNativeSpec" s.dependency "React-defaultsnativemodule" + s.dependency "React-RCTAnimatedModuleProvider" if use_hermes() s.dependency 'React-hermes' end diff --git a/packages/react-native/Libraries/AppState/AppState.d.ts b/packages/react-native/Libraries/AppState/AppState.d.ts index 6333c819056..ddc5ad0a511 100644 --- a/packages/react-native/Libraries/AppState/AppState.d.ts +++ b/packages/react-native/Libraries/AppState/AppState.d.ts @@ -23,7 +23,10 @@ import {NativeEventSubscription} from '../EventEmitter/RCTNativeAppEventEmitter' * * App States * active - The app is running in the foreground - * background - The app is running in the background. The user is either in another app or on the home screen + * background - The app is running in the background. The user is either in another app, + * on the home screen + * [Android only] on another Activity, including temporary system + * activities such as autofill credential pickers (even if launched by your app or the system) * inactive [iOS] - This is a transition state that happens when the app launches, is asking for permissions or when a call or SMS message is received. * unknown [iOS] - Initial value until the current app state is determined * extension [iOS] - The app is running as an app extension diff --git a/packages/react-native/Libraries/AppState/AppState.js b/packages/react-native/Libraries/AppState/AppState.js index 623d2752be1..f3bee72dc01 100644 --- a/packages/react-native/Libraries/AppState/AppState.js +++ b/packages/react-native/Libraries/AppState/AppState.js @@ -15,12 +15,18 @@ import {type EventSubscription} from '../vendor/emitter/EventEmitter'; import NativeAppState from './NativeAppState'; /** - * active - The app is running in the foreground - * background - The app is running in the background. The user is either: - * - in another app - * - on the home screen - * - @platform android - on another Activity (even if it was launched by your app) - * @platform ios - inactive - This is a state that occurs when transitioning between foreground & background, and during periods of inactivity such as entering the multitasking view, opening the Notification Center or in the event of an incoming call. + * The app's current state. + * + * - `active` — The app is running in the foreground. + * - `background` — The app is running in the background. The user is either + * in another app, on the home screen, or (Android only) on another Activity, + * including temporary system activities such as autofill credential pickers. + * - `inactive` — A transitional state that occurs when moving between + * foreground and background, and during periods of inactivity such as + * entering the multitasking view, opening the Notification Center, or in the + * event of an incoming call. + * + * @platform ios `inactive` */ export type AppStateStatus = | 'inactive' @@ -30,10 +36,15 @@ export type AppStateStatus = | 'unknown'; /** - * change - This even is received when the app state has changed. - * memoryWarning - This event is used in the need of throwing memory warning or releasing it. - * @platform android - focus - Received when the app gains focus (the user is interacting with the app). - * @platform android - blur - Received when the user is not actively interacting with the app. + * Events emitted by `AppState`. + * + * - `change` — Received when the app state has changed. + * - `memoryWarning` — Received when the system issues a memory warning. + * - `focus` — Received when the app gains focus (the user is interacting + * with the app). + * - `blur` — Received when the user is not actively interacting with the app. + * + * @platform android `focus`, `blur` */ type AppStateEventDefinitions = { change: [AppStateStatus], @@ -51,13 +62,18 @@ type NativeAppStateEventDefinitions = { }; /** - * `AppState` can tell you if the app is in the foreground or background, - * and notify you when the state changes. + * Reports the app's current state (`active`, `background`, or `inactive`) and + * notifies when it changes. Frequently used to handle push notification + * behavior. * - * See https://reactnative.dev/docs/appstate + * @see https://reactnative.dev/docs/appstate */ class AppStateImpl { + /** + * The current app state. Can be `null` until the initial value is set. + */ currentState: ?string = null; + isAvailable: boolean; _emitter: ?NativeEventEmitter; @@ -107,10 +123,9 @@ class AppStateImpl { } /** - * Add a handler to AppState changes by listening to the `change` event type - * and providing the handler. - * - * See https://reactnative.dev/docs/appstate#addeventlistener + * Add a handler to `AppState` changes by listening to the `change` event + * type and providing the handler. See `AppStateEvent` for the list of + * available events. */ addEventListener( type: K, diff --git a/packages/react-native/Libraries/BatchedBridge/MessageQueue.js b/packages/react-native/Libraries/BatchedBridge/MessageQueue.js index b177b30c1c1..137233be1df 100644 --- a/packages/react-native/Libraries/BatchedBridge/MessageQueue.js +++ b/packages/react-native/Libraries/BatchedBridge/MessageQueue.js @@ -393,54 +393,49 @@ class MessageQueue { } __callReactNativeMicrotasks() { - Systrace.beginEvent('JSTimers.callReactNativeMicrotasks()'); - try { + Systrace.trace('JSTimers.callReactNativeMicrotasks()', () => { if (this._reactNativeMicrotasksCallback != null) { this._reactNativeMicrotasksCallback(); } - } finally { - Systrace.endEvent(); - } + }); } __callFunction(module: string, method: string, args: unknown[]): void { this._lastFlush = Date.now(); this._eventLoopStartTime = this._lastFlush; - if (__DEV__ || this.__spy) { - Systrace.beginEvent(`${module}.${method}(${stringifySafe(args)})`); - } else { - Systrace.beginEvent(`${module}.${method}(...)`); - } - try { - if (this.__spy) { - this.__spy({type: TO_JS, module, method, args}); - } - const moduleMethods = this.getCallableModule(module); - if (!moduleMethods) { - const callableModuleNames = Object.keys(this._lazyCallableModules); - const n = callableModuleNames.length; - const callableModuleNameList = callableModuleNames.join(', '); - - // TODO(T122225939): Remove after investigation: Why are we getting to this line in bridgeless mode? - const isBridgelessMode = - global.RN$Bridgeless === true ? 'true' : 'false'; - invariant( - false, - `Failed to call into JavaScript module method ${module}.${method}(). Module has not been registered as callable. Bridgeless Mode: ${isBridgelessMode}. Registered callable JavaScript modules (n = ${n}): ${callableModuleNameList}. + Systrace.trace( + __DEV__ || this.__spy + ? `${module}.${method}(${stringifySafe(args)})` + : `${module}.${method}(...)`, + () => { + if (this.__spy) { + this.__spy({type: TO_JS, module, method, args}); + } + const moduleMethods = this.getCallableModule(module); + if (!moduleMethods) { + const callableModuleNames = Object.keys(this._lazyCallableModules); + const n = callableModuleNames.length; + const callableModuleNameList = callableModuleNames.join(', '); + + // TODO(T122225939): Remove after investigation: Why are we getting to this line in bridgeless mode? + const isBridgelessMode = + global.RN$Bridgeless === true ? 'true' : 'false'; + invariant( + false, + `Failed to call into JavaScript module method ${module}.${method}(). Module has not been registered as callable. Bridgeless Mode: ${isBridgelessMode}. Registered callable JavaScript modules (n = ${n}): ${callableModuleNameList}. A frequent cause of the error is that the application entry file path is incorrect. This can also happen when the JS bundle is corrupt or there is an early initialization error when loading React Native.`, - ); - } - // $FlowFixMe[invalid-computed-prop] - if (!moduleMethods[method]) { - invariant( - false, - `Failed to call into JavaScript module method ${module}.${method}(). Module exists, but the method is undefined.`, - ); - } - moduleMethods[method].apply(moduleMethods, args); - } finally { - Systrace.endEvent(); - } + ); + } + // $FlowFixMe[invalid-computed-prop] + if (!moduleMethods[method]) { + invariant( + false, + `Failed to call into JavaScript module method ${module}.${method}(). Module exists, but the method is undefined.`, + ); + } + moduleMethods[method].apply(moduleMethods, args); + }, + ); } __invokeCallback(cbID: number, args: unknown[]): void { @@ -471,28 +466,24 @@ class MessageQueue { const profileName = debug ? '' : cbID; - /* $FlowFixMe[constant-condition] Error discovered during Constant - * Condition roll out. See https://fburl.com/workplace/1v97vimq. */ - if (callback && this.__spy) { + if (this.__spy) { this.__spy({type: TO_JS, module: null, method: profileName, args}); } - Systrace.beginEvent( + Systrace.trace( `MessageQueue.invokeCallback(${profileName}, ${stringifySafe(args)})`, + () => { + this._successCallbacks.delete(callID); + this._failureCallbacks.delete(callID); + callback(...args); + }, ); - } - - try { + } else { if (!callback) { return; } - this._successCallbacks.delete(callID); this._failureCallbacks.delete(callID); callback(...args); - } finally { - if (__DEV__) { - Systrace.endEvent(); - } } } } diff --git a/packages/react-native/Libraries/BatchedBridge/NativeModules.js b/packages/react-native/Libraries/BatchedBridge/NativeModules.js index c38eab148e9..3e16553e26d 100644 --- a/packages/react-native/Libraries/BatchedBridge/NativeModules.js +++ b/packages/react-native/Libraries/BatchedBridge/NativeModules.js @@ -180,6 +180,13 @@ function updateErrorWithErrorData( return Object.assign(error, errorData || {}); } +/** + * Native Modules written in ObjectiveC/Swift/Java exposed via the RCTBridge + * Define lazy getters for each module. These will return the module if already loaded, or load it if not. + * See https://reactnative.dev/docs/native-modules-ios + * @example + * const MyModule = NativeModules.ModuleName + */ /* $FlowFixMe[unclear-type] unclear type of NativeModules */ let NativeModules: {[moduleName: string]: any, ...} = {}; if (global.nativeModuleProxy) { diff --git a/packages/react-native/Libraries/BatchedBridge/__tests__/MessageQueue-test.js b/packages/react-native/Libraries/BatchedBridge/__tests__/MessageQueue-itest.js similarity index 91% rename from packages/react-native/Libraries/BatchedBridge/__tests__/MessageQueue-test.js rename to packages/react-native/Libraries/BatchedBridge/__tests__/MessageQueue-itest.js index 196c788fb70..af908a23284 100644 --- a/packages/react-native/Libraries/BatchedBridge/__tests__/MessageQueue-test.js +++ b/packages/react-native/Libraries/BatchedBridge/__tests__/MessageQueue-itest.js @@ -8,10 +8,11 @@ * @format */ -'use strict'; +import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; + +const MessageQueueTestModule = require('../__mocks__/MessageQueueTestModule'); +const MessageQueue = require('../MessageQueue').default; -let MessageQueue; -let MessageQueueTestModule; let queue; const MODULE_IDS = 0; @@ -41,9 +42,6 @@ const assertQueue = ( // local callbacks stored by IDs are cleaned up. describe('MessageQueue', () => { beforeEach(() => { - jest.resetModules(); - MessageQueue = require('../MessageQueue').default; - MessageQueueTestModule = require('../__mocks__/MessageQueueTestModule'); queue = new MessageQueue(); queue.registerCallableModule( 'MessageQueueTestModule', @@ -117,9 +115,16 @@ describe('MessageQueue', () => { it('should throw when calling with unknown module', () => { const unknownModule = 'UnknownModule', unknownMethod = 'UnknownMethod'; - expect(() => - queue.__callFunction(unknownModule, unknownMethod, []), - ).toThrow( + let thrownError: ?Error; + try { + queue.__callFunction(unknownModule, unknownMethod, []); + } catch (e: unknown) { + if (e instanceof Error) { + thrownError = e; + } + } + expect(thrownError).toBeInstanceOf(Error); + expect(thrownError?.message).toContain( `Failed to call into JavaScript module method ${unknownModule}.${unknownMethod}()`, ); }); diff --git a/packages/react-native/Libraries/Blob/Blob.js b/packages/react-native/Libraries/Blob/Blob.js index dc6939860f7..90a4e580081 100644 --- a/packages/react-native/Libraries/Blob/Blob.js +++ b/packages/react-native/Libraries/Blob/Blob.js @@ -86,6 +86,11 @@ class Blob { let {offset, size} = this.data; if (typeof start === 'number') { + if (start < 0) { + // A negative start is relative to the end of the blob. + // $FlowFixMe[reassign-const] + start = Math.max(this.size + start, 0); + } if (start > size) { // $FlowFixMe[reassign-const] start = size; @@ -102,7 +107,8 @@ class Blob { // $FlowFixMe[reassign-const] end = this.size; } - size = end - start; + // Clamp to 0 so an end that precedes start yields an empty blob. + size = Math.max(end - start, 0); } } return BlobManager.createFromOptions({ diff --git a/packages/react-native/Libraries/Blob/FileReader.js b/packages/react-native/Libraries/Blob/FileReader.js index 9cb37e5ca48..3745c00923a 100644 --- a/packages/react-native/Libraries/Blob/FileReader.js +++ b/packages/react-native/Libraries/Blob/FileReader.js @@ -80,6 +80,8 @@ class FileReader extends EventTarget { ); } + this._setReadyState(LOADING); + NativeFileReaderModule.readAsDataURL(blob.data).then( (text: string) => { if (this._aborted) { @@ -111,6 +113,8 @@ class FileReader extends EventTarget { ); } + this._setReadyState(LOADING); + NativeFileReaderModule.readAsDataURL(blob.data).then( (text: string) => { if (this._aborted) { @@ -138,6 +142,8 @@ class FileReader extends EventTarget { ); } + this._setReadyState(LOADING); + NativeFileReaderModule.readAsText(blob.data, encoding).then( (text: string) => { if (this._aborted) { diff --git a/packages/react-native/Libraries/Blob/URLSearchParams.js.flow b/packages/react-native/Libraries/Blob/URLSearchParams.js.flow index 32ab77b30b7..df5b32557dd 100644 --- a/packages/react-native/Libraries/Blob/URLSearchParams.js.flow +++ b/packages/react-native/Libraries/Blob/URLSearchParams.js.flow @@ -10,7 +10,7 @@ declare export class URLSearchParams { _searchParams: Array<[string, string]>; - +size: number; + readonly size: number; constructor( params?: Record | string | Array<[string, string]>, ): void; diff --git a/packages/react-native/Libraries/Blob/__tests__/Blob-test.js b/packages/react-native/Libraries/Blob/__tests__/Blob-test.js index 36aee4969c7..3375467b483 100644 --- a/packages/react-native/Libraries/Blob/__tests__/Blob-test.js +++ b/packages/react-native/Libraries/Blob/__tests__/Blob-test.js @@ -81,6 +81,38 @@ describe('Blob', function () { expect(sliceC.size).toBe(Math.min(blob.data.size, 34569) - 34543); }); + it('should slice a blob with a negative start', () => { + const blob = new Blob(); + blob.data.size = 34546; + + // A negative start is relative to the end of the blob. + const slice = blob.slice(-100); + + expect(slice.data.offset).toBe(34446); + expect(slice.size).toBe(100); + }); + + it('should slice a blob with a negative end', () => { + const blob = new Blob(); + blob.data.size = 34546; + + // A negative end is relative to the end of the blob. + const slice = blob.slice(0, -100); + + expect(slice.data.offset).toBe(0); + expect(slice.size).toBe(34446); + }); + + it('should return an empty slice when end precedes start', () => { + const blob = new Blob(); + blob.data.size = 34546; + + const slice = blob.slice(200, 100); + + expect(slice.data.offset).toBe(200); + expect(slice.size).toBe(0); + }); + it('should slice a blob and sets a contentType', () => { const blob = new Blob(); diff --git a/packages/react-native/Libraries/Blob/__tests__/BlobRegistry-test.js b/packages/react-native/Libraries/Blob/__tests__/BlobRegistry-itest.js similarity index 84% rename from packages/react-native/Libraries/Blob/__tests__/BlobRegistry-test.js rename to packages/react-native/Libraries/Blob/__tests__/BlobRegistry-itest.js index 8e6d70d126d..86f779b3fca 100644 --- a/packages/react-native/Libraries/Blob/__tests__/BlobRegistry-test.js +++ b/packages/react-native/Libraries/Blob/__tests__/BlobRegistry-itest.js @@ -8,14 +8,14 @@ * @format */ -'use strict'; +import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; const BlobRegistry = require('../BlobRegistry'); describe('BlobRegistry', () => { describe('register', () => { it('does not throw error', () => { - expect(() => BlobRegistry.register('id1')).not.toThrowError(); + expect(() => BlobRegistry.register('id1')).not.toThrow(); }); it('registers new id', () => { @@ -26,7 +26,7 @@ describe('BlobRegistry', () => { describe('unregister', () => { it('does not throw error', () => { - expect(() => BlobRegistry.unregister('id3')).not.toThrowError(); + expect(() => BlobRegistry.unregister('id3')).not.toThrow(); }); it('remove registered id', () => { diff --git a/packages/react-native/Libraries/Blob/__tests__/FileReader-test.js b/packages/react-native/Libraries/Blob/__tests__/FileReader-test.js index 549d027bc3a..36d55849c48 100644 --- a/packages/react-native/Libraries/Blob/__tests__/FileReader-test.js +++ b/packages/react-native/Libraries/Blob/__tests__/FileReader-test.js @@ -46,6 +46,31 @@ describe('FileReader', function () { expect(e.target?.result).toBe('data:text/plain;base64,NDI='); }); + it('should be in the LOADING state while a read is in progress', () => { + const reader = new FileReader(); + expect(reader.readyState).toBe(FileReader.EMPTY); + reader.readAsText(new Blob()); + // The native read resolves on a later microtask, so the reader should + // report LOADING synchronously after the read starts. + expect(reader.readyState).toBe(FileReader.LOADING); + }); + + it('should dispatch abort and loadend when aborted during a read', () => { + const reader = new FileReader(); + let aborted = false; + let loadended = false; + reader.onabort = () => { + aborted = true; + }; + reader.onloadend = () => { + loadended = true; + }; + reader.readAsText(new Blob()); + reader.abort(); + expect(aborted).toBe(true); + expect(loadended).toBe(true); + }); + it('should read blob as ArrayBuffer', async () => { const e = await new Promise((resolve, reject) => { const reader = new FileReader(); diff --git a/packages/react-native/Libraries/Blob/__tests__/URL-test.js b/packages/react-native/Libraries/Blob/__tests__/URL-itest.js similarity index 95% rename from packages/react-native/Libraries/Blob/__tests__/URL-test.js rename to packages/react-native/Libraries/Blob/__tests__/URL-itest.js index 72467dc08eb..2cf5e9d8692 100644 --- a/packages/react-native/Libraries/Blob/__tests__/URL-test.js +++ b/packages/react-native/Libraries/Blob/__tests__/URL-itest.js @@ -8,7 +8,7 @@ * @format */ -'use strict'; +import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; const URL = require('../URL').URL; const URLSearchParams = require('../URL').URLSearchParams; @@ -133,12 +133,9 @@ describe('URL', function () { expect(urlParams.has('query')).toBe(true); expect(urlParams.has('key')).toBe(false); - // Sorting URLSearchParams - const unsortedParams = new URLSearchParams( - '?z=last&b=second&c=third&a=first', - ); - unsortedParams.sort(); - expect(unsortedParams.toString()).toBe('a=first&b=second&c=third&z=last'); + // Sorting URLSearchParams is not exercised here: URLSearchParams.sort() + // relies on String.prototype.localeCompare, which requires ICU collation + // support that is unavailable in Fantom's Hermes build. // searchParams.set() should replace values not duplicate them const urlWithSearchParams = new URL( diff --git a/packages/react-native/Libraries/Components/AccessibilityInfo/AccessibilityInfo.d.ts b/packages/react-native/Libraries/Components/AccessibilityInfo/AccessibilityInfo.d.ts index e034a238294..82f9ef68923 100644 --- a/packages/react-native/Libraries/Components/AccessibilityInfo/AccessibilityInfo.d.ts +++ b/packages/react-native/Libraries/Components/AccessibilityInfo/AccessibilityInfo.d.ts @@ -11,16 +11,23 @@ import {HostInstance} from '../../../types/public/ReactNativeTypes'; import {EmitterSubscription} from '../../vendor/emitter/EventEmitter'; type AccessibilityChangeEventName = - | 'change' // deprecated, maps to screenReaderChanged - | 'boldTextChanged' // iOS-only Event - | 'grayscaleChanged' // iOS-only Event + /** Android only */ + | 'accessibilityServiceChanged' + /** iOS only */ + | 'boldTextChanged' + /** @deprecated Maps to 'screenReaderChanged' */ + | 'change' + /** iOS only */ + | 'darkerSystemColorsChanged' + | 'grayscaleChanged' + /** Android only */ + | 'highTextContrastChanged' | 'highContrastChanged' // [macOS] - | 'invertColorsChanged' // iOS-only Event + | 'invertColorsChanged' | 'reduceMotionChanged' - | 'highTextContrastChanged' // Android-only Event - | 'darkerSystemColorsChanged' // iOS-only Event - | 'screenReaderChanged' - | 'reduceTransparencyChanged'; // iOS-only Event + /** iOS only */ + | 'reduceTransparencyChanged' + | 'screenReaderChanged'; type AccessibilityChangeEvent = boolean; @@ -39,7 +46,11 @@ type AccessibilityAnnouncementFinishedEventHandler = ( event: AccessibilityAnnouncementFinishedEvent, ) => void; -type AccessibilityEventTypes = 'click' | 'focus' | 'viewHoverEnter'; +type AccessibilityEventTypes = + | 'click' + | 'focus' + | 'viewHoverEnter' + | 'windowStateChange'; /** * @see https://reactnative.dev/docs/accessibilityinfo @@ -59,14 +70,6 @@ export interface AccessibilityInfoStatic { */ isGrayscaleEnabled: () => Promise; - /** - * - * @returns Query whether high contract is currently enabled. - * - * @platform macos - */ - isHighContrastEnabled: () => Promise; - /** * Query whether invert colors is currently enabled. * diff --git a/packages/react-native/Libraries/Components/AccessibilityInfo/AccessibilityInfo.js b/packages/react-native/Libraries/Components/AccessibilityInfo/AccessibilityInfo.js index 3a2275a6324..88a6cd00c8b 100644 --- a/packages/react-native/Libraries/Components/AccessibilityInfo/AccessibilityInfo.js +++ b/packages/react-native/Libraries/Components/AccessibilityInfo/AccessibilityInfo.js @@ -28,10 +28,8 @@ type AccessibilityEventDefinitionsAndroid = { type AccessibilityEventDefinitionsIOS = { announcementFinished: [{announcement: string, success: boolean}], boldTextChanged: [boolean], - grayscaleChanged: [boolean], - invertColorsChanged: [boolean], - reduceTransparencyChanged: [boolean], darkerSystemColorsChanged: [boolean], + reduceTransparencyChanged: [boolean], }; // [macOS @@ -46,6 +44,8 @@ type AccessibilityEventDefinitions = { ...AccessibilityEventDefinitionsIOS, ...AccessibilityEventDefinitionsMacOS, // [macOS] change: [boolean], // screenReaderChanged + grayscaleChanged: [boolean], + invertColorsChanged: [boolean], reduceMotionChanged: [boolean], screenReaderChanged: [boolean], }; @@ -82,13 +82,9 @@ const EventNames: Map = ]); /** - * Sometimes it's useful to know whether or not the device has a screen reader - * that is currently active. The `AccessibilityInfo` API is designed for this - * purpose. You can use it to query the current state of the screen reader as - * well as to register to be notified when the state of the screen reader - * changes. + * Provides information about the device's accessibility features, such as whether a screen reader is active. Can be used to query the current state and register for change notifications. * - * See https://reactnative.dev/docs/accessibilityinfo + * @see https://reactnative.dev/docs/accessibilityinfo */ const AccessibilityInfo = { /** @@ -97,7 +93,7 @@ const AccessibilityInfo = { * Returns a promise which resolves to a boolean. * The result is `true` when bold text is enabled and `false` otherwise. * - * See https://reactnative.dev/docs/accessibilityinfo#isBoldTextEnabled + * @platform ios */ isBoldTextEnabled(): Promise { if (Platform.OS === 'android') { @@ -122,7 +118,7 @@ const AccessibilityInfo = { * Returns a promise which resolves to a boolean. * The result is `true` when grayscale is enabled and `false` otherwise. * - * See https://reactnative.dev/docs/accessibilityinfo#isGrayscaleEnabled + * @platform ios */ isGrayscaleEnabled(): Promise { if (Platform.OS === 'android') { @@ -179,7 +175,7 @@ const AccessibilityInfo = { * Returns a promise which resolves to a boolean. * The result is `true` when invert color is enabled and `false` otherwise. * - * See https://reactnative.dev/docs/accessibilityinfo#isInvertColorsEnabled + * @platform ios */ isInvertColorsEnabled(): Promise { if (Platform.OS === 'android') { @@ -213,8 +209,6 @@ const AccessibilityInfo = { * * Returns a promise which resolves to a boolean. * The result is `true` when a reduce motion is enabled and `false` otherwise. - * - * See https://reactnative.dev/docs/accessibilityinfo#isReduceMotionEnabled */ isReduceMotionEnabled(): Promise { return new Promise((resolve, reject) => { @@ -238,12 +232,12 @@ const AccessibilityInfo = { }, /** - * Query whether high text contrast is currently enabled. Android only. + * Query whether high text contrast is currently enabled. * * Returns a promise which resolves to a boolean. * The result is `true` when high text contrast is enabled and `false` otherwise. * - * See https://reactnative.dev/docs/accessibilityinfo#ishightextcontrastenabled-android + * @platform android */ isHighTextContrastEnabled(): Promise { if (Platform.OS === 'android') { @@ -264,12 +258,12 @@ const AccessibilityInfo = { }, /** - * Query whether dark system colors is currently enabled. iOS only. + * Query whether darker system colors is currently enabled. * * Returns a promise which resolves to a boolean. - * The result is `true` when dark system colors is enabled and `false` otherwise. + * The result is `true` when darker system colors is enabled and `false` otherwise. * - * See https://reactnative.dev/docs/accessibilityinfo#isdarkersystemcolorsenabled-ios + * @platform ios */ isDarkerSystemColorsEnabled(): Promise { if (Platform.OS === 'android') { @@ -296,12 +290,12 @@ const AccessibilityInfo = { }, /** - * Query whether reduce motion and prefer cross-fade transitions settings are currently enabled. + * Query whether cross-fade transitions are preferred over slide transitions. * * Returns a promise which resolves to a boolean. - * The result is `true` when prefer cross-fade transitions is enabled and `false` otherwise. + * The result is `true` when cross-fade transitions are preferred and `false` otherwise. * - * See https://reactnative.dev/docs/accessibilityinfo#prefersCrossFadeTransitions + * @platform ios */ prefersCrossFadeTransitions(): Promise { if (Platform.OS === 'android') { @@ -334,7 +328,7 @@ const AccessibilityInfo = { * Returns a promise which resolves to a boolean. * The result is `true` when a reduce transparency is enabled and `false` otherwise. * - * See https://reactnative.dev/docs/accessibilityinfo#isReduceTransparencyEnabled + * @platform ios */ isReduceTransparencyEnabled(): Promise { if (Platform.OS === 'android') { @@ -358,8 +352,6 @@ const AccessibilityInfo = { * * Returns a promise which resolves to a boolean. * The result is `true` when a screen reader is enabled and `false` otherwise. - * - * See https://reactnative.dev/docs/accessibilityinfo#isScreenReaderEnabled */ isScreenReaderEnabled(): Promise { return new Promise((resolve, reject) => { @@ -389,8 +381,6 @@ const AccessibilityInfo = { * The result is `true` when any service is enabled and `false` otherwise. * * @platform android - * - * See https://reactnative.dev/docs/accessibilityinfo/#isaccessibilityserviceenabled-android */ isAccessibilityServiceEnabled(): Promise { return new Promise((resolve, reject) => { @@ -427,18 +417,18 @@ const AccessibilityInfo = { * - `screenReaderChanged`: Fires when the state of the screen reader changes. The argument * to the event handler is a boolean. The boolean is `true` when a screen * reader is enabled and `false` otherwise. + * - `grayscaleChanged`: Fires when the state of the gray scale toggle changes. + * The argument to the event handler is a boolean. The boolean is `true` when gray scale + * is enabled and `false` otherwise. + * - `invertColorsChanged`: Fires when the state of the invert colors toggle + * changes. The argument to the event handler is a boolean. The boolean is `true` when invert + * colors is enabled and `false` otherwise. * * These events are only supported on iOS: * * - `boldTextChanged`: iOS-only event. Fires when the state of the bold text toggle changes. * The argument to the event handler is a boolean. The boolean is `true` when a bold text * is enabled and `false` otherwise. - * - `grayscaleChanged`: iOS-only event. Fires when the state of the gray scale toggle changes. - * The argument to the event handler is a boolean. The boolean is `true` when a gray scale - * is enabled and `false` otherwise. - * - `invertColorsChanged`: iOS-only event. Fires when the state of the invert colors toggle - * changes. The argument to the event handler is a boolean. The boolean is `true` when a invert - * colors is enabled and `false` otherwise. * - `reduceTransparencyChanged`: iOS-only event. Fires when the state of the reduce transparency * toggle changes. The argument to the event handler is a boolean. The boolean is `true` * when a reduce transparency is enabled and `false` otherwise. @@ -457,8 +447,6 @@ const AccessibilityInfo = { * - `highTextContrastChanged`: Android-only event. Fires when the state of the high text contrast * toggle changes. The argument to the event handler is a boolean. The boolean is `true` when * high text contrast is enabled and `false` otherwise. - * - * See https://reactnative.dev/docs/accessibilityinfo#addeventlistener */ addEventListener( eventName: K, @@ -475,8 +463,6 @@ const AccessibilityInfo = { /** * Set accessibility focus to a React component. * - * See https://reactnative.dev/docs/accessibilityinfo#setaccessibilityfocus - * * @deprecated Use `sendAccessibilityEvent` with eventType `focus` instead. */ setAccessibilityFocus(reactTag: number): void { @@ -484,7 +470,8 @@ const AccessibilityInfo = { }, /** - * Send a named accessibility event to a HostComponent. + * Trigger an accessibility event on a host instance. The `eventType` can be + * `'focus'`, `'click'`, `'viewHoverEnter'`, or `'windowStateChange'`. */ sendAccessibilityEvent( handle: HostInstance, @@ -500,8 +487,6 @@ const AccessibilityInfo = { /** * Post a string to be announced by the screen reader. - * - * See https://reactnative.dev/docs/accessibilityinfo#announceforaccessibility */ announceForAccessibility(announcement: string): void { if (Platform.OS === 'android') { @@ -512,7 +497,8 @@ const AccessibilityInfo = { }, /** - * Post a string to be announced by the screen reader. + * Post a string to be announced by the screen reader with options. + * * - `announcement`: The string announced by the screen reader. * - `options`: An object that configures the reading options. * - `queue`: The announcement will be queued behind existing announcements. iOS only. @@ -545,9 +531,10 @@ const AccessibilityInfo = { }, /** - * Get the recommended timeout for changes to the UI needed by this user. + * Get the recommended timeout in milliseconds the user needs, as specified + * in the device's Accessibility settings. * - * See https://reactnative.dev/docs/accessibilityinfo#getrecommendedtimeoutmillis + * @platform android */ getRecommendedTimeoutMillis(originalTimeout: number): Promise { if (Platform.OS === 'android') { diff --git a/packages/react-native/Libraries/Components/ActivityIndicator/ActivityIndicator.js b/packages/react-native/Libraries/Components/ActivityIndicator/ActivityIndicator.js index de86dba6551..3200bb8afc5 100644 --- a/packages/react-native/Libraries/Components/ActivityIndicator/ActivityIndicator.js +++ b/packages/react-native/Libraries/Components/ActivityIndicator/ActivityIndicator.js @@ -9,7 +9,8 @@ */ 'use strict'; -import type {HostComponent} from '../../../src/private/types/HostComponent'; + +import type {HostInstance} from '../../../src/private/types/HostInstance'; import type {ViewProps} from '../View/ViewPropTypes'; import StyleSheet, {type ColorValue} from '../../StyleSheet/StyleSheet'; @@ -17,6 +18,8 @@ import Platform from '../../Utilities/Platform'; import View from '../View/View'; import * as React from 'react'; +export type ActivityIndicatorInstance = HostInstance; + const PlatformActivityIndicator = Platform.OS === 'android' ? require('../ProgressBarAndroid/ProgressBarAndroid').default @@ -28,40 +31,79 @@ type IndicatorSize = number | 'small' | 'large'; type ActivityIndicatorIOSProps = Readonly<{ /** - Whether the indicator should hide when not animating. - - @platform ios - */ + * Whether the indicator should hide when not animating. + * + * @platform ios + */ hidesWhenStopped?: ?boolean, }>; + +/** @build-types emit-as-interface Uniwind compatibility */ export type ActivityIndicatorProps = Readonly<{ ...ViewProps, ...ActivityIndicatorIOSProps, /** - Whether to show the indicator (`true`) or hide it (`false`). + * Whether to show the indicator (`true`) or hide it (`false`). */ animating?: ?boolean, /** - The foreground color of the spinner. - - @default {@platform android} `null` (system accent default color) - @default {@platform ios} '#999999' - */ + * The foreground color of the spinner. + * + * @default {@platform android} `null` (system accent default color) + * @default {@platform ios} '#999999' + */ color?: ?ColorValue, /** - Size of the indicator. - - @type enum(`'small'`, `'large'`) - @type {@platform android} number - */ + * Size of the indicator. + * + * Small has a height of 20, large has a height of 36. + * + * @type enum(`'small'`, `'large'`) + * @type {@platform android} number + */ size?: ?IndicatorSize, }>; +/** + * Displays a circular loading indicator. + * + * Example: + * + * ```tsx + * import React from 'react'; + * import {ActivityIndicator, StyleSheet, View} from 'react-native'; + * + * const App = () => ( + * + * + * + * + * + * + * ); + * + * const styles = StyleSheet.create({ + * container: { + * flex: 1, + * justifyContent: 'center', + * }, + * horizontal: { + * flexDirection: 'row', + * justifyContent: 'space-around', + * padding: 10, + * }, + * }); + * + * export default App; + * ``` + * + * @see https://reactnative.dev/docs/activityindicator + */ const ActivityIndicator: component( - ref?: React.RefSetter>, + ref?: React.RefSetter, ...props: ActivityIndicatorProps ) = ({ ref: forwardedRef, @@ -126,38 +168,6 @@ const ActivityIndicator: component( ); }; -/** - Displays a circular loading indicator. - - ```SnackPlayer name=ActivityIndicator%20Example - import React from 'react'; - import {ActivityIndicator, StyleSheet, View} from 'react-native'; - - const App = () => ( - - - - - - - ); - - const styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: 'center', - }, - horizontal: { - flexDirection: 'row', - justifyContent: 'space-around', - padding: 10, - }, - }); - - export default App; -``` -*/ - ActivityIndicator.displayName = 'ActivityIndicator'; const styles = StyleSheet.create({ diff --git a/packages/react-native/Libraries/Components/ActivityIndicator/ActivityIndicatorViewNativeComponent.js b/packages/react-native/Libraries/Components/ActivityIndicator/ActivityIndicatorViewNativeComponent.js index c38b088737b..5c8b8c8810b 100644 --- a/packages/react-native/Libraries/Components/ActivityIndicator/ActivityIndicatorViewNativeComponent.js +++ b/packages/react-native/Libraries/Components/ActivityIndicator/ActivityIndicatorViewNativeComponent.js @@ -8,5 +8,5 @@ * @format */ -export * from '../../../src/private/specs_DEPRECATED/components/ActivityIndicatorViewNativeComponent'; -export {default} from '../../../src/private/specs_DEPRECATED/components/ActivityIndicatorViewNativeComponent'; +export * from '../../../src/private/components/activityindicator/specs/ActivityIndicatorViewNativeComponent'; +export {default} from '../../../src/private/components/activityindicator/specs/ActivityIndicatorViewNativeComponent'; diff --git a/packages/react-native/Libraries/Components/ActivityIndicator/__tests__/ActivityIndicator-itest.js b/packages/react-native/Libraries/Components/ActivityIndicator/__tests__/ActivityIndicator-itest.js index 97c57e1a19f..a9137dcc33c 100644 --- a/packages/react-native/Libraries/Components/ActivityIndicator/__tests__/ActivityIndicator-itest.js +++ b/packages/react-native/Libraries/Components/ActivityIndicator/__tests__/ActivityIndicator-itest.js @@ -18,6 +18,10 @@ import {ActivityIndicator} from 'react-native'; import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement'; describe('', () => { + it('sets displayName', () => { + expect(ActivityIndicator.displayName).toBe('ActivityIndicator'); + }); + describe('props', () => { describe('size', () => { it('defaults to "small" (20x20)', () => { diff --git a/packages/react-native/Libraries/Components/ActivityIndicator/__tests__/ActivityIndicator-test.js b/packages/react-native/Libraries/Components/ActivityIndicator/__tests__/ActivityIndicator-test.js deleted file mode 100644 index 523f6150310..00000000000 --- a/packages/react-native/Libraries/Components/ActivityIndicator/__tests__/ActivityIndicator-test.js +++ /dev/null @@ -1,32 +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. - * - * @flow strict-local - * @format - */ - -'use strict'; - -import * as React from 'react'; - -const ReactNativeTestTools = require('../../../Utilities/ReactNativeTestTools'); -const ActivityIndicator = require('../ActivityIndicator').default; - -describe('', () => { - it('should set displayName to prevent regressions', () => { - expect(ActivityIndicator.displayName).toBe('ActivityIndicator'); - }); - - it('should render as expected', async () => { - await ReactNativeTestTools.expectRendersMatchingSnapshot( - 'ActivityIndicator', - () => , - () => { - jest.dontMock('../ActivityIndicator'); - }, - ); - }); -}); diff --git a/packages/react-native/Libraries/Components/ActivityIndicator/__tests__/__snapshots__/ActivityIndicator-test.js.snap b/packages/react-native/Libraries/Components/ActivityIndicator/__tests__/__snapshots__/ActivityIndicator-test.js.snap deleted file mode 100644 index f0fa9f3980a..00000000000 --- a/packages/react-native/Libraries/Components/ActivityIndicator/__tests__/__snapshots__/ActivityIndicator-test.js.snap +++ /dev/null @@ -1,32 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[` should render as expected: should deep render when mocked (please verify output manually) 1`] = ` - -`; - -exports[` should render as expected: should deep render when not mocked (please verify output manually) 1`] = ` - - - -`; diff --git a/packages/react-native/Libraries/Components/Button.js b/packages/react-native/Libraries/Components/Button.js index 9e3e880cfd7..39674b5dc66 100644 --- a/packages/react-native/Libraries/Components/Button.js +++ b/packages/react-native/Libraries/Components/Button.js @@ -30,121 +30,118 @@ import View from './View/View'; import invariant from 'invariant'; import * as React from 'react'; +/** @build-types emit-as-interface Uniwind compatibility */ export type ButtonProps = Readonly<{ /** - Text to display inside the button. On Android the given title will be - converted to the uppercased form. + * Text to display inside the button. On Android the given title will be + * converted to the uppercased form. */ title: string, /** - Handler to be called when the user taps the button. The first function - argument is an event in form of [GestureResponderEvent](pressevent). + * Handler called when the user taps the button. */ onPress?: (event?: GestureResponderEvent) => unknown, /** - If `true`, doesn't play system sound on touch. - - @platform android - - @default false + * If `true`, doesn't play system sound on touch. + * + * @platform android + * + * @default `false` */ touchSoundDisabled?: ?boolean, /** - Color of the text (iOS), or background color of the button (Android). - - @default {@platform android} '#2196F3' - @default {@platform ios} '#007AFF' + * Color of the text (iOS), or background color of the button (Android). + * + * @default {@platform android} `'#2196F3'` + * @default {@platform ios} `'#007AFF'` */ color?: ?ColorValue, /** - TV preferred focus. - - @platform tv - - @default false - @deprecated Use `focusable` instead + * TV preferred focus. + * + * @platform tv + * + * @default `false` + * @deprecated Use `focusable` instead */ hasTVPreferredFocus?: ?boolean, /** - Designates the next view to receive focus when the user navigates down. See - the [Android documentation][android:nextFocusDown]. - - [android:nextFocusDown]: - https://developer.android.com/reference/android/view/View.html#attr_android:nextFocusDown - - @platform android, tv + * Designates the next view to receive focus when the user navigates down. See + * the [Android documentation][android:nextFocusDown]. + * + * [android:nextFocusDown]: + * https://developer.android.com/reference/android/view/View.html#attr_android:nextFocusDown + * + * @platform android, tv */ nextFocusDown?: ?number, /** - Designates the next view to receive focus when the user navigates forward. - See the [Android documentation][android:nextFocusForward]. - - [android:nextFocusForward]: - https://developer.android.com/reference/android/view/View.html#attr_android:nextFocusForward - - @platform android, tv + * Designates the next view to receive focus when the user navigates forward. + * See the [Android documentation][android:nextFocusForward]. + * + * [android:nextFocusForward]: + * https://developer.android.com/reference/android/view/View.html#attr_android:nextFocusForward + * + * @platform android, tv */ nextFocusForward?: ?number, /** - Designates the next view to receive focus when the user navigates left. See - the [Android documentation][android:nextFocusLeft]. - - [android:nextFocusLeft]: - https://developer.android.com/reference/android/view/View.html#attr_android:nextFocusLeft - - @platform android, tv + * Designates the next view to receive focus when the user navigates left. See + * the [Android documentation][android:nextFocusLeft]. + * + * [android:nextFocusLeft]: + * https://developer.android.com/reference/android/view/View.html#attr_android:nextFocusLeft + * + * @platform android, tv */ nextFocusLeft?: ?number, /** - Designates the next view to receive focus when the user navigates right. See - the [Android documentation][android:nextFocusRight]. - - [android:nextFocusRight]: - https://developer.android.com/reference/android/view/View.html#attr_android:nextFocusRight - - @platform android, tv + * Designates the next view to receive focus when the user navigates right. See + * the [Android documentation][android:nextFocusRight]. + * + * [android:nextFocusRight]: + * https://developer.android.com/reference/android/view/View.html#attr_android:nextFocusRight + * + * @platform android, tv */ nextFocusRight?: ?number, /** - Designates the next view to receive focus when the user navigates up. See - the [Android documentation][android:nextFocusUp]. - - [android:nextFocusUp]: - https://developer.android.com/reference/android/view/View.html#attr_android:nextFocusUp - - @platform android, tv + * Designates the next view to receive focus when the user navigates up. See + * the [Android documentation][android:nextFocusUp]. + * + * [android:nextFocusUp]: + * https://developer.android.com/reference/android/view/View.html#attr_android:nextFocusUp + * + * @platform android, tv */ nextFocusUp?: ?number, /** - Text to display for blindness accessibility features. + * Text to display for blindness accessibility features. */ accessibilityLabel?: ?string, /** - * Alias for accessibilityLabel https://reactnative.dev/docs/view#accessibilitylabel - * https://github.com/facebook/react-native/issues/34424 + * Alias for `accessibilityLabel`. */ 'aria-label'?: ?string, - /** - If `true`, disable all interactions for this component. - @default false + /** + * If `true`, disable all interactions for this component. + * + * @default `false` */ disabled?: ?boolean, - /** - Used to locate this view in end-to-end tests. - */ testID?: ?string, // [macOS @@ -169,18 +166,13 @@ export type ButtonProps = Readonly<{ tooltip?: string, // macOS] - /** - * Accessibility props. - */ accessible?: ?boolean, accessibilityActions?: ?ReadonlyArray, onAccessibilityAction?: ?(event: AccessibilityActionEvent) => unknown, accessibilityState?: ?AccessibilityState, /** - * alias for accessibilityState - * - * see https://reactnative.dev/docs/accessibility#accessibilitystate + * Alias for `accessibilityState`. */ 'aria-busy'?: ?boolean, 'aria-checked'?: ?boolean | 'mixed', @@ -188,133 +180,55 @@ export type ButtonProps = Readonly<{ 'aria-expanded'?: ?boolean, 'aria-selected'?: ?boolean, - /** - * [Android] Controlling if a view fires accessibility events and if it is reported to accessibility services. - */ importantForAccessibility?: ?('auto' | 'yes' | 'no' | 'no-hide-descendants'), accessibilityHint?: ?string, + + /** + * A BCP 47 language tag for the screen reader to use when reading text + * content. + * + * @platform ios + */ accessibilityLanguage?: ?Stringish, }>; -/** - A basic button component that should render nicely on any platform. Supports a - minimal level of customization. - - If this button doesn't look right for your app, you can build your own button - using [TouchableOpacity](touchableopacity) or - [TouchableWithoutFeedback](touchablewithoutfeedback). For inspiration, look at - the [source code for this button component][button:source]. Or, take a look at - the [wide variety of button components built by the community] - [button:examples]. - - [button:source]: - https://github.com/facebook/react-native/blob/HEAD/Libraries/Components/Button.js - - ```jsx -