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 89f7651af46e..c1700b4c1fff 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 @@ -13,6 +13,7 @@ import com.facebook.react.utils.BackwardCompatUtils.showJSCRemovalMessage import com.facebook.react.utils.NdkConfiguratorUtils.configureJsEnginePackagingOptions import com.facebook.react.utils.NdkConfiguratorUtils.configureNewArchPackagingOptions import com.facebook.react.utils.ProjectUtils.isHermesEnabled +import com.facebook.react.utils.ProjectUtils.shouldResetMetroCacheBeforeBundling import com.facebook.react.utils.ProjectUtils.useThirdPartyJSC import com.facebook.react.utils.detectedCliFile import com.facebook.react.utils.detectedEntryFile @@ -71,6 +72,7 @@ internal fun Project.configureReactTasks(variant: Variant, config: ReactExtensio task.cliFile.set(cliFile) task.bundleCommand.set(config.bundleCommand) task.entryFile.set(detectedEntryFile(config, entryFileEnvVariable)) + task.resetMetroCacheBeforeBundling.set(project.shouldResetMetroCacheBeforeBundling) task.extraPackagerArgs.set(config.extraPackagerArgs) task.bundleConfig.set(config.bundleConfig) task.bundleAssetName.set(config.bundleAssetName) diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/BundleHermesCTask.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/BundleHermesCTask.kt index 89ba3656a3e6..444352dff4d5 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/BundleHermesCTask.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/BundleHermesCTask.kt @@ -65,6 +65,8 @@ abstract class BundleHermesCTask : DefaultTask() { @get:Input abstract val devEnabled: Property + @get:Input abstract val resetMetroCacheBeforeBundling: Property + @get:Input abstract val extraPackagerArgs: ListProperty @get:Input abstract val hermesCommand: Property @@ -152,7 +154,9 @@ abstract class BundleHermesCTask : DefaultTask() { add("android") add("--dev") add(devEnabled.get().toString()) - add("--reset-cache") + if (resetMetroCacheBeforeBundling.get()) { + add("--reset-cache") + } add("--entry-file") add(entryFile.get().asFile.cliPath(rootFile)) add("--bundle-output") 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 855e34df90ab..814d20cd7dae 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 @@ -12,9 +12,11 @@ import com.facebook.react.model.ModelPackageJson import com.facebook.react.utils.PropertyUtils.EDGE_TO_EDGE_ENABLED import com.facebook.react.utils.PropertyUtils.HERMES_ENABLED import com.facebook.react.utils.PropertyUtils.REACT_NATIVE_ARCHITECTURES +import com.facebook.react.utils.PropertyUtils.RESET_METRO_CACHE_BEFORE_BUNDLING 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_REACT_NATIVE_ARCHITECTURES +import com.facebook.react.utils.PropertyUtils.SCOPED_RESET_METRO_CACHE_BEFORE_BUNDLING import com.facebook.react.utils.PropertyUtils.SCOPED_STRICT_CXX_API_ENABLED import com.facebook.react.utils.PropertyUtils.SCOPED_USE_THIRD_PARTY_JSC import com.facebook.react.utils.PropertyUtils.STRICT_CXX_API_ENABLED @@ -72,6 +74,19 @@ internal object ProjectUtils { (project.hasProperty(SCOPED_USE_THIRD_PARTY_JSC) && project.property(SCOPED_USE_THIRD_PARTY_JSC).toString().toBoolean()) + internal val Project.shouldResetMetroCacheBeforeBundling: Boolean + get() { + val value = + when { + project.hasProperty(SCOPED_RESET_METRO_CACHE_BEFORE_BUNDLING) -> + project.property(SCOPED_RESET_METRO_CACHE_BEFORE_BUNDLING) + project.hasProperty(RESET_METRO_CACHE_BEFORE_BUNDLING) -> + project.property(RESET_METRO_CACHE_BEFORE_BUNDLING) + else -> return true + } + return value.toString().lowercase().toBooleanStrictOrNull() ?: true + } + 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 32dd486eaf77..014d7410055e 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 @@ -54,6 +54,13 @@ object PropertyUtils { const val EXCLUSIVE_ENTEPRISE_REPOSITORY = "exclusiveEnterpriseRepository" const val SCOPED_EXCLUSIVE_ENTEPRISE_REPOSITORY = "react.exclusiveEnterpriseRepository" + /** + * Public property that controls whether `--reset-cache` is passed to Metro when bundling the JS + * for release builds. Default: true + */ + const val RESET_METRO_CACHE_BEFORE_BUNDLING = "resetMetroCacheBeforeBundling" + const val SCOPED_RESET_METRO_CACHE_BEFORE_BUNDLING = "react.resetMetroCacheBeforeBundling" + /** By default we include JitPack to avoid breaking user builds */ internal const val INCLUDE_JITPACK_REPOSITORY_DEFAULT = true diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/BundleHermesCTaskTest.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/BundleHermesCTaskTest.kt index a9fb597f5012..0cbae3d1622b 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/BundleHermesCTaskTest.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/BundleHermesCTaskTest.kt @@ -91,6 +91,7 @@ class BundleHermesCTaskTest { it.minifyEnabled.set(true) it.hermesEnabled.set(true) it.devEnabled.set(true) + it.resetMetroCacheBeforeBundling.set(true) it.extraPackagerArgs.set(listOf("extra", "arg")) it.hermesCommand.set("./my-hermesc") it.hermesFlags.set(listOf("flag1", "flag2")) @@ -102,6 +103,7 @@ class BundleHermesCTaskTest { assertThat(task.minifyEnabled.get()).isTrue() assertThat(task.hermesEnabled.get()).isTrue() assertThat(task.devEnabled.get()).isTrue() + assertThat(task.resetMetroCacheBeforeBundling.get()).isTrue() assertThat(task.extraPackagerArgs.get()).isEqualTo(listOf("extra", "arg")) assertThat(task.hermesCommand.get()).isEqualTo("./my-hermesc") assertThat(task.hermesFlags.get()).isEqualTo(listOf("flag1", "flag2")) @@ -211,6 +213,7 @@ class BundleHermesCTaskTest { it.resourcesDir.set(resourcesDir) it.bundleConfig.set(bundleConfig) it.minifyEnabled.set(true) + it.resetMetroCacheBeforeBundling.set(true) it.extraPackagerArgs.set(listOf("--read-global-cache")) } @@ -265,6 +268,7 @@ class BundleHermesCTaskTest { it.resourcesDir.set(resourcesDir) it.bundleConfig.set(bundleConfig) it.minifyEnabled.set(true) + it.resetMetroCacheBeforeBundling.set(true) it.extraPackagerArgs.set(listOf("--read-global-cache")) } @@ -318,6 +322,7 @@ class BundleHermesCTaskTest { it.entryFile.set(entryFile) it.resourcesDir.set(resourcesDir) it.minifyEnabled.set(true) + it.resetMetroCacheBeforeBundling.set(true) it.extraPackagerArgs.set(listOf("--read-global-cache")) } @@ -326,6 +331,32 @@ class BundleHermesCTaskTest { assertThat(bundleCommand).doesNotContain("--config") } + @Test + fun getBundleCommand_withResetMetroCacheBeforeBundlingDisabled_returnsCommandWithoutResetCache() { + val entryFile = tempFolder.newFile("index.js") + val cliFile = tempFolder.newFile("cli.js") + val bundleFile = tempFolder.newFile("bundle.js") + val sourceMapFile = tempFolder.newFile("bundle.js.map") + val resourcesDir = tempFolder.newFolder("res") + val task = + createTestTask { + it.nodeExecutableAndArgs.set(listOf("node", "arg1", "arg2")) + it.root.set(tempFolder.root) + it.cliFile.set(cliFile) + it.bundleCommand.set("bundle") + it.devEnabled.set(true) + it.entryFile.set(entryFile) + it.resourcesDir.set(resourcesDir) + it.minifyEnabled.set(true) + it.resetMetroCacheBeforeBundling.set(false) + it.extraPackagerArgs.set(listOf("--read-global-cache")) + } + + val bundleCommand = task.getBundleCommand(bundleFile, sourceMapFile) + + assertThat(bundleCommand).doesNotContain("--reset-cache") + } + @Test fun getHermescCommand_returnsCorrectCommand() { val customHermesc = "hermesc" 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 64fb15c42494..cf369e547616 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 @@ -17,6 +17,7 @@ import com.facebook.react.utils.ProjectUtils.isHermesEnabled import com.facebook.react.utils.ProjectUtils.isNewArchEnabled import com.facebook.react.utils.ProjectUtils.isStrictCxxApiEnabled import com.facebook.react.utils.ProjectUtils.needsCodegenFromPackageJson +import com.facebook.react.utils.ProjectUtils.shouldResetMetroCacheBeforeBundling import java.io.File import org.assertj.core.api.Assertions.assertThat import org.junit.Rule @@ -116,6 +117,61 @@ class ProjectUtilsTest { assertThat(project.isEdgeToEdgeEnabled).isFalse() } + @Test + fun shouldResetMetroCacheBeforeBundling_returnsTrueByDefault() { + assertThat(createProject().shouldResetMetroCacheBeforeBundling).isTrue() + } + + @Test + fun shouldResetMetroCacheBeforeBundling_withDisabledViaProperty_returnsFalse() { + val project = createProject() + project.extensions.extraProperties.set("resetMetroCacheBeforeBundling", "false") + assertThat(project.shouldResetMetroCacheBeforeBundling).isFalse() + } + + @Test + fun shouldResetMetroCacheBeforeBundling_withEnabledViaProperty_returnsTrue() { + val project = createProject() + project.extensions.extraProperties.set("resetMetroCacheBeforeBundling", "true") + assertThat(project.shouldResetMetroCacheBeforeBundling).isTrue() + } + + @Test + fun shouldResetMetroCacheBeforeBundling_withInvalidViaProperty_returnsTrue() { + val project = createProject() + project.extensions.extraProperties.set("resetMetroCacheBeforeBundling", "¯\\_(ツ)_/¯") + assertThat(project.shouldResetMetroCacheBeforeBundling).isTrue() + } + + @Test + fun shouldResetMetroCacheBeforeBundling_withDisabledViaScopedProperty_returnsFalse() { + val project = createProject() + project.extensions.extraProperties.set("react.resetMetroCacheBeforeBundling", "false") + assertThat(project.shouldResetMetroCacheBeforeBundling).isFalse() + } + + @Test + fun shouldResetMetroCacheBeforeBundling_withEnabledViaScopedProperty_returnsTrue() { + val project = createProject() + project.extensions.extraProperties.set("react.resetMetroCacheBeforeBundling", "true") + assertThat(project.shouldResetMetroCacheBeforeBundling).isTrue() + } + + @Test + fun shouldResetMetroCacheBeforeBundling_withInvalidViaScopedProperty_returnsTrue() { + val project = createProject() + project.extensions.extraProperties.set("react.resetMetroCacheBeforeBundling", "¯\\_(ツ)_/¯") + assertThat(project.shouldResetMetroCacheBeforeBundling).isTrue() + } + + @Test + fun shouldResetMetroCacheBeforeBundling_withBothProperties_scopedPropertyWins() { + val project = createProject() + project.extensions.extraProperties.set("resetMetroCacheBeforeBundling", "true") + project.extensions.extraProperties.set("react.resetMetroCacheBeforeBundling", "false") + assertThat(project.shouldResetMetroCacheBeforeBundling).isFalse() + } + @Test fun isStrictCxxApiEnabled_returnsFalseByDefault() { assertThat(createProject().isStrictCxxApiEnabled).isFalse()