wuba/taro-playground

启用 Hermes

zhiqingchen opened this issue · 3 comments

文档

https://reactnative.dev/docs/hermes

问题

  • Taro RN 在编译时,没有使用 react-native/scripts/react-native-xcode.shnode_modules/react-native/react.gradle
    以至于没有进行字节码的打包,相关内容需要进行重构
  • 入口文件通过 --entry-file 传入 node_modules/metro/src/node-haste/DependencyGraph/assets/empty-module.js

补充一点 Android 相关的 gradle 分析

简要介绍: (个人理解, 非官方) Taro RN 暂时没有支持 Hermes 是因为有一部分字节码打包操作是在 node_modules/react-native/react.gradle 这里, 但是在 Taro RN 中的 bundle 和 assets 资源的处理是在 rn-runner 里面做的处理, 所以需要

  1. rn-runner 添加对应 hermes 处理
  2. 修改 node_modules/react-native/react.gradle 来自行配置

先看下 react-native 默认项目的 app/build.gralde 中相关打包配置

有个老外的解释 Bundling React Native during Android release builds

/**
 * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
 * and bundleReleaseJsAndAssets).
 * These basically call `react-native bundle` with the correct arguments during the Android build
 * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
 * bundle directly from the development server. Below you can see all the possible configurations
 * and their defaults. If you decide to add a configuration block, make sure to add it before the
 * `apply from: "../../node_modules/react-native/react.gradle"` line.
 *
 * project.ext.react = [
 *   // the name of the generated asset file containing your JS bundle
 *   bundleAssetName: "index.android.bundle",
 *
 *   // the entry file for bundle generation
 *   entryFile: "index.android.js",
 *
 *   // whether to bundle JS and assets in debug mode
 *   bundleInDebug: false,
.
.
.
*/

稍微翻译一下 node_modules/react-native/react.gradle

概述:

  1. 读取一堆配置, 包括项目目录, react-native cli 可执行文件, hermesc 可执行文件, node 可执行文件等路径
  2. 跑一下 react-native bundle 命令, 生成一下 jsbundle (index.android.bundle) 跟 对应的 sourcemap (index.android.bundle.map)
  3. 如果开启 hermes 的话, 就把生成的 jsbundle 文件转换成 hermes 的字节码文件(hbc), 同时处理一下 sourcemap (如果需要的话)
  4. 打包完成, copy 对应的 assets 与 jsbundle 文件

第3条详细说一下:

3.1 如果开启 hermes, 就把 minify 关掉, 因为没必要, (但是不关闭其实也可以)
3.2 找到 HermesCompiler 可执行文件 (hermesc) node_modules/hermes-engine/osx-bin/hermesc // osx-bin 根据平台修改
3.3 执行参数 hermesc -emit-binary -out <字节码文件输出路径> <jsbundle文件路径>
3.4 如果开启了 sourcemap 选项 ["-O", "--out-source-map"] (gradle 里面那一段), 就追加一个参数, 3.3 中的命令变为
hermesc -emit-binary -out <字节码文件输出路径> <jsbundle文件路径> --out-source-map // 注意, 这里的sourcemap 输出无法自定义名称, 会生成 <jsbundle文件路径.hbc.map> 这种格式(大概, 没仔细验证)
3.5 使用 node_modules/react-native/scripts/compose-source-maps.js 这个小工具, 来拼装 jsbundle.mapcompiler.hbc.map , 整体命令如: node ./node_modules/react-native/scripts/compose-source-maps.js jsbundle.map <jsbundle文件路径.hbc.map> -o <输出合并之后的sourcemap>

至此, 可以获得一个新的, 对应 hbc 代码的 sourcemap, 在下方有一个实现, 但同样没有验证 :XD

题外话, metro 中也包含了一个 metro-hermes-compiler, 也是一个简单封装, 可以参考

/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import org.apache.tools.ant.taskdefs.condition.Os
/* 追加注释:: 读取 app/build.gradle 中的配置 */
def config = project.hasProperty("react") ? project.react : [:];

/* 追加注释:: 查找入口文件, 优先级 ENV.ENTRY_FILE -> config.entryFile ->  ${projectDir}/../../index.android.js -> index.js */
def detectEntryFile(config) {
    if (System.getenv('ENTRY_FILE')) {
        return System.getenv('ENTRY_FILE')
    } else if (config.entryFile) {
        return config.entryFile
    } else if ((new File("${projectDir}/../../index.android.js")).exists()) {
        return "index.android.js"
    }

    return "index.js";
}

/* 追加注释:: 查找 react-native 命令所在位置, 优先级 config.cliPath ->  ${projectDir}/../../node_modules/react-native/cli.js  */
/**
 * Detects CLI location in a similar fashion to the React Native CLI
 */
def detectCliPath(config) {
    if (config.cliPath) {
        return "${projectDir}/${config.cliPath}"
    }
    if (new File("${projectDir}/../../node_modules/react-native/cli.js").exists()) {
        return "${projectDir}/../../node_modules/react-native/cli.js"
    }
    throw new Exception("Couldn't determine CLI location. " +
            "Please set `project.ext.react.cliPath` to the path of the react-native cli.js");
}

/* 追加注释:: 拼装 sourcemap 的小工具 */
def composeSourceMapsPath = config.composeSourceMapsPath ?: "node_modules/react-native/scripts/compose-source-maps.js"
/* 追加注释:: bundle的名称, TODO: 分包处理这里需要改动 */
def bundleAssetName = config.bundleAssetName ?: "index.android.bundle"
/* 追加注释:: 调用上面函数获取配置 */
def entryFile = detectEntryFile(config)
/* 追加注释:: 打包命令名称, 默认 bundle */
def bundleCommand = config.bundleCommand ?: "bundle"
/* 追加注释:: 根路径 */
def reactRoot = file(config.root ?: "../../")
/* 追加注释:: 排除路径, TODO: 分包处理这块也需要改动 */
def inputExcludes = config.inputExcludes ?: ["android/**", "ios/**"]
/* 追加注释:: 打包配置 */
def bundleConfig = config.bundleConfig ? "${reactRoot}/${config.bundleConfig}" : null ;
/* 追加注释:: 开启Vm CleanUp, 下面有用, 类似resetCache? */
def enableVmCleanup = config.enableVmCleanup == null ? true : config.enableVmCleanup
/* 追加注释:: hermesc 命令所在位置, 根据系统区分 */
def hermesCommand = config.hermesCommand ?: "../../node_modules/hermes-engine/%OS-BIN%/hermesc"

/* 追加注释:: 获取 React Native Dev Server 端口 */
def reactNativeDevServerPort() {
    def value = project.getProperties().get("reactNativeDevServerPort")
    return value != null ? value : "8081"
}

/* 追加注释:: 获取 React Native 开发工具 Server 端口 */
def reactNativeInspectorProxyPort() {
    def value = project.getProperties().get("reactNativeInspectorProxyPort")
    return value != null ? value : reactNativeDevServerPort()
}

/* 追加注释:: 获取上面 hermsCommons 会用到的 %OS-BIN% 变量 */
def getHermesOSBin() {
    if (Os.isFamily(Os.FAMILY_WINDOWS)) return "win64-bin";
    if (Os.isFamily(Os.FAMILY_MAC)) return "osx-bin";
    if (Os.isOs(null, "linux", "amd64", null)) return "linux64-bin";
    throw new Exception("OS not recognized. Please set project.ext.react.hermesCommand " +
            "to the path of a working Hermes compiler.");
}

/* 追加注释:: 替换一下定义中的 %OS-BIN% 变量, 获取真正的 hermesc 可执行文件路径 */
/* 追加注释:: 原注释内容大意为, 确保 herems 配置不会被 inspect, 只有 JSC 可以 remote debugger, 但实际上并不是这里做的操作, 蜜汁注释 */
// Make sure not to inspect the Hermes config unless we need it,
// to avoid breaking any JSC-only setups.
def getHermesCommand = {
    // If the project specifies a Hermes command, don't second guess it.
    if (!hermesCommand.contains("%OS-BIN%")) {
        return hermesCommand
    }

    // Execution on Windows fails with / as separator
    return hermesCommand
            .replaceAll("%OS-BIN%", getHermesOSBin())
            .replace('/' as char, File.separatorChar);
}

/* 追加注释:: enableHermesForVariant 是函数的话, 就执行一下, 否则, 就读取 true/false 值 */
// Set enableHermesForVariant to a function to configure per variant,
// or set `enableHermes` to True/False to set all of them
def enableHermesForVariant = config.enableHermesForVariant ?: {
    def variant -> config.enableHermes ?: false
}

/* 追加注释:: 跟上面意思差不多, 区分一下 debug/release, release 会追加一下 sourcemap输出 */
// Set hermesFlagsForVariant to a function to configure per variant,
// or set `hermesFlagsRelease` and `hermesFlagsDebug` to an array
def hermesFlagsForVariant = config.hermesFlagsForVariant ?: {
    def variant ->
        def hermesFlags;
        if (variant.name.toLowerCase().contains("release")) {
            // Can't use ?: since that will also substitute valid empty lists
            hermesFlags = config.hermesFlagsRelease
            if (hermesFlags == null) hermesFlags = ["-O", "-output-source-map"]
        } else {
            hermesFlags = config.hermesFlagsDebug
            if (hermesFlags == null) hermesFlags = []
        }
        return hermesFlags
}
/* 追加注释:: 跟上面意思也差不多,deleteDebugFilesForVariant 不是函数的话, deleteDebugFiles 就靠 variant名字是不是 release 来判断*/
// Set deleteDebugFilesForVariant to a function to configure per variant,
// defaults to True for Release variants and False for debug variants
def deleteDebugFilesForVariant = config.deleteDebugFilesForVariant ?: {
    def variant -> variant.name.toLowerCase().contains("release")
}

/* 追加注释::  获取React Native DevServer 跟 inspector Proxy Port (remote Debugger)用 */
android {
    buildTypes.all {
        resValue "integer", "react_native_dev_server_port", reactNativeDevServerPort()
        resValue "integer", "react_native_inspector_proxy_port", reactNativeInspectorProxyPort()
    }
}

afterEvaluate {
    def isAndroidLibrary = plugins.hasPlugin("com.android.library")
    def variants = isAndroidLibrary ? android.libraryVariants : android.applicationVariants
    variants.all { def variant ->
        /* 追加注释:: targetName 通常是 Debug | Release */
        // Create variant and target names
        def targetName = variant.name.capitalize()
        def targetPath = variant.dirName
        /* 追加注释::
         * TODO: 这里要大改这一堆配置要大改了, 现在大多数都在都在 config/rn 字段里面
         */
        // React js bundle directories
        /* 追加注释:: bundle 输出目录,  然而, Taro Rn 的bundle 并不是这里, 所以emm, 要改 */
        def jsBundleDir = file("$buildDir/generated/assets/react/${targetPath}")
        /* 追加注释:: 资源目录,  然而, Taro Rn 的资源配置也不在这里, 所以emm, 要改 */
        def resourcesDir = file("$buildDir/generated/res/react/${targetPath}")
        /* 追加注释:: sourcemap 文件名称,  emm TODO: 分包要改这里 */
        def jsBundleFile = file("$jsBundleDir/$bundleAssetName")
        /* 追加注释:: sourcemap 文件名称,  emm TODO: 分包要改这里 */
        def jsSourceMapsDir = file("$buildDir/generated/sourcemaps/react/${targetPath}")
        /* 追加注释:: FIXME: 没看懂这个是什么 */
        def jsIntermediateSourceMapsDir = file("$buildDir/intermediates/sourcemaps/react/${targetPath}")
        /* 追加注释:: 这两个emm看不懂啊!!, 盲猜是 js 打包的mapper 跟 字节码的 mapper */
        def jsPackagerSourceMapFile = file("$jsIntermediateSourceMapsDir/${bundleAssetName}.packager.map")
        def jsCompilerSourceMapFile = file("$jsIntermediateSourceMapsDir/${bundleAssetName}.compiler.map")
        /* 追加注释:: 由上面两个 mapper 合并起来的 mapper */
        def jsOutputSourceMapFile = file("$jsSourceMapsDir/${bundleAssetName}.map")

        /* 追加注释:: 添加 node 命令到cli, 以及 react-native 这个命令 */
        // Additional node and packager commandline arguments
        def nodeExecutableAndArgs = config.nodeExecutableAndArgs ?: ["node"]
        def cliPath = detectCliPath(config)

        def execCommand = []

        if (Os.isFamily(Os.FAMILY_WINDOWS)) {
            execCommand.addAll(["cmd", "/c", *nodeExecutableAndArgs, cliPath])
        } else {
            execCommand.addAll([*nodeExecutableAndArgs, cliPath])
        }

        /* 追加注释:: 是否开启 hermes, 从配置中读取 */
        def enableHermes = enableHermesForVariant(variant)

        /* 追加注释:: 创建 bundle${targetName}JsAndAssets Gradle 任务  */
        def currentBundleTask = tasks.create(
                name: "bundle${targetName}JsAndAssets",
                type: Exec) {
            group = "react"
            description = "bundle JS and assets for ${targetName}."

            /* 追加注释:: 清理文件夹与创建初始文件  */
            // Create dirs if they are not there (e.g. the "clean" task just ran)
            doFirst {
                jsBundleDir.deleteDir()
                jsBundleDir.mkdirs()
                resourcesDir.deleteDir()
                resourcesDir.mkdirs()
                jsIntermediateSourceMapsDir.deleteDir()
                jsIntermediateSourceMapsDir.mkdirs()
                jsSourceMapsDir.deleteDir()
                jsSourceMapsDir.mkdirs()
            }

            // Set up inputs and outputs so gradle can cache the result
            inputs.files fileTree(dir: reactRoot, excludes: inputExcludes)
            outputs.dir(jsBundleDir)
            outputs.dir(resourcesDir)

            // Set up the call to the react-native cli
            workingDir(reactRoot)

            // Set up dev mode
            def devEnabled = !(config."devDisabledIn${targetName}"
                    || targetName.toLowerCase().contains("release"))

            def extraArgs = []

            /* 追加注释:: 下面一堆都是设置 react-native bundle 命令参数, 条件判断追加一堆  */
            if (bundleConfig) {
                extraArgs.add("--config")
                extraArgs.add(bundleConfig)
            }

            // Hermes doesn't require JS minification.
            if (enableHermes && !devEnabled) {
                extraArgs.add("--minify")
                extraArgs.add("false")
            }

            if (config.extraPackagerArgs) {
                extraArgs.addAll(config.extraPackagerArgs)
            }

            commandLine(*execCommand, bundleCommand, "--platform", "android", "--dev", "${devEnabled}",
                    "--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir,
                    "--sourcemap-output", enableHermes ? jsPackagerSourceMapFile : jsOutputSourceMapFile, *extraArgs)

            /* 追加注释:: 重点来了, 如果开始了 enableHermes */
            if (enableHermes) {
                doLast {
                    /* 追加注释:: 从配置中读取一下 hermes 相关的配置 */
                    def hermesFlags = hermesFlagsForVariant(variant)
                    /* 追加注释:: 要输出的 hbc 字节码文件临时文件名 */
                    def hbcTempFile = file("${jsBundleFile}.hbc")
                    /* 追加注释:: 拼装执行命令 */
                    exec {
                        if (Os.isFamily(Os.FAMILY_WINDOWS)) {
                            commandLine("cmd", "/c", getHermesCommand(), "-emit-binary", "-out", hbcTempFile, jsBundleFile, *hermesFlags)
                        } else {
                            commandLine(getHermesCommand(), "-emit-binary", "-out", hbcTempFile, jsBundleFile, *hermesFlags)
                        }
                    }
                    /* 追加注释:: 字节码转换完毕之后, 将字节码文件替换原来的 bundle 文件 */
                    ant.move(
                            file: hbcTempFile,
                            toFile: jsBundleFile
                    );
                    /* 追加注释:: 处理 sourcemap , 如果需要的话 */
                    if (hermesFlags.contains("-output-source-map")) {
                        ant.move(
                                // Hermes will generate a source map with this exact name
                                file: "${jsBundleFile}.hbc.map",
                                tofile: jsCompilerSourceMapFile
                        );
                        exec {
                            // TODO: set task dependencies for caching

                            // Set up the call to the compose-source-maps script
                            workingDir(reactRoot)
                            if (Os.isFamily(Os.FAMILY_WINDOWS)) {
                                commandLine("cmd", "/c", *nodeExecutableAndArgs, composeSourceMapsPath, jsPackagerSourceMapFile, jsCompilerSourceMapFile, "-o", jsOutputSourceMapFile)
                            } else {
                                commandLine(*nodeExecutableAndArgs, composeSourceMapsPath, jsPackagerSourceMapFile, jsCompilerSourceMapFile, "-o", jsOutputSourceMapFile)
                            }
                        }
                    }
                }
            }

            /* 追加注释:: 是否启用这个任务, 也就是最上面的那个小例子里面的 bundleInDebug | bundleInRelease, 当然, Taro RN 目前(3.4.1) 并不能直接启用 */
            enabled config."bundleIn${targetName}" != null
                    ? config."bundleIn${targetName}"
                    : config."bundleIn${variant.buildType.name.capitalize()}" != null
                    ? config."bundleIn${variant.buildType.name.capitalize()}"
                    : targetName.toLowerCase().contains("release")
        }

        /* 追加注释:: 整理一下本任务[bundle${targetName}JsAndAssets]对外开放的最小接口 */
        // Expose a minimal interface on the application variant and the task itself:
        variant.ext.bundleJsAndAssets = currentBundleTask
        currentBundleTask.ext.generatedResFolders = files(resourcesDir).builtBy(currentBundleTask)
        currentBundleTask.ext.generatedAssetsFolders = files(jsBundleDir).builtBy(currentBundleTask)

        /* 追加注释:: 注册资源生成资源文件目录, for Android plugin 3.x */
        // registerGeneratedResFolders for Android plugin 3.x
        if (variant.respondsTo("registerGeneratedResFolders")) {
            variant.registerGeneratedResFolders(currentBundleTask.generatedResFolders)
        } else {
            variant.registerResGeneratingTask(currentBundleTask)
        }
        /* 追加注释:: 啊, mergeResourcesProvider 依赖我 [bundle${targetName}JsAndAssets] */
        variant.mergeResourcesProvider.get().dependsOn(currentBundleTask)

        /* 追加注释:: 啊找找名为 packageApplication 的task  */
        // packageApplication for Android plugin 3.x
        def packageTask = variant.hasProperty("packageApplication")
                ? variant.packageApplicationProvider.get()
                : tasks.findByName("package${targetName}")
        if (variant.hasProperty("packageLibrary")) {
            packageTask = variant.packageLibrary
        }

        /* 追加注释:: 啊找找名为 buildPreBundleTask 的task  */
        // pre bundle build task for Android plugin 3.2+
        def buildPreBundleTask = tasks.findByName("build${targetName}PreBundle")

        /* 追加注释:: 
         * 啊找找名为 resourcesDirConfigValue 的配置, 名字类似 resourcesDirDebug | resourcesDirRelease 这样 
         * 如果存在的话, 就 copy 一下 资源文件们
         */
        def resourcesDirConfigValue = config."resourcesDir${targetName}"
        if (resourcesDirConfigValue) {
           /* 追加注释:: 如果有的话, 就创建一个 [copy${targetName}BundledResources] 的任务, 把资源文件copy 一下 */
            def currentCopyResTask = tasks.create(
                    name: "copy${targetName}BundledResources",
                    type: Copy) {
                group = "react"
                description = "copy bundled resources into custom location for ${targetName}."

                from(resourcesDir)
                into(file(resourcesDirConfigValue))

                dependsOn(currentBundleTask)

                enabled(currentBundleTask.enabled)
            }

            packageTask.dependsOn(currentCopyResTask)
            if (buildPreBundleTask != null) {
                buildPreBundleTask.dependsOn(currentCopyResTask)
            }
        }

        /* 
         * 追加注释: 创建一个 [copy${targetName}BundledJs] 的任务, 把 bundle 文件copy 一下, 
         * 一如既往, 包含一堆判断
         */
        def currentAssetsCopyTask = tasks.create(
                name: "copy${targetName}BundledJs",
                type: Copy) {
            group = "react"
            description = "copy bundled JS into ${targetName}."

            if (config."jsBundleDir${targetName}") {
                from(jsBundleDir)
                into(file(config."jsBundleDir${targetName}"))
            } else {
                into ("$buildDir/intermediates")
                if (isAndroidLibrary) {
                    into ("library_assets/${variant.name}/out") {
                        from(jsBundleDir)
                    }
                } else {
                    into ("assets/${targetPath}") {
                        from(jsBundleDir)
                    }

                    // Workaround for Android Gradle Plugin 3.2+ new asset directory
                    into ("merged_assets/${variant.name}/merge${targetName}Assets/out") {
                        from(jsBundleDir)
                    }

                    // Workaround for Android Gradle Plugin 3.4+ new asset directory
                    into ("merged_assets/${variant.name}/out") {
                        from(jsBundleDir)
                    }
                }
            }

            // mergeAssets must run first, as it clears the intermediates directory
            dependsOn(variant.mergeAssetsProvider.get())

            enabled(currentBundleTask.enabled)
            dependsOn(currentBundleTask)
        }

        /* 
         * 追加注释:: 如果是 Android plugin 4.1+ 的话, 
         * 就把上面  资源 assets copy 的操作追加到这个 mergeResourcesTask [merge${targetName}Resources] 这货的依赖
         * 如果存在 buildPreBundleTask, 就追加到 buildPreBundleTask 的依赖 (Android plugin 3.x?) 
         */
        // mergeResources task runs before the bundle file is copied to the intermediate asset directory from Android plugin 4.1+.
        // This ensures to copy the bundle file before mergeResources task starts
        def mergeResourcesTask = tasks.findByName("merge${targetName}Resources")
        mergeResourcesTask.dependsOn(currentAssetsCopyTask)

        packageTask.dependsOn(currentAssetsCopyTask)
        if (buildPreBundleTask != null) {
            buildPreBundleTask.dependsOn(currentAssetsCopyTask)
        }

        /* 
         * 追加注释:: 删掉一些不用的 .so 文件, 如果 'enableVmCleanup: true' 的话
         * 比如 hermes 的 release 删掉 debug 相关, jsc 的话, 就把 所有 hemres 相关删掉
         * 用来减小包的体积, 所以动态修改 JS 引擎, 至少在配置上是可行的(修改这里)
         */ 
        // Delete the VM related libraries that this build doesn't need.
        // The application can manage this manually by setting 'enableVmCleanup: false'
        //
        // This should really be done by packaging all Hermes related libs into
        // two separate HermesDebug and HermesRelease AARs, but until then we'll
        // kludge it by deleting the .so files out of the /transforms/ directory.
        def cleanup = deleteDebugFilesForVariant(variant)

        def vmSelectionAction = { libDir ->
            fileTree(libDir).matching {
                if (enableHermes) {
                    // For Hermes, delete all the libjsc* files
                    include "**/libjsc*.so"

                    if (cleanup) {
                        // Reduce size by deleting the debugger/inspector
                        include '**/libhermes-inspector.so'
                        include '**/libhermes-executor-debug.so'
                        include '**/libhermes-executor-common-debug.so'
                    } else {
                        // Release libs take precedence and must be removed
                        // to allow debugging
                        include '**/libhermes-executor-release.so'
                        include '**/libhermes-executor-common-release.so'
                    }
                } else {
                    // For JSC, delete all the libhermes* files
                    include "**/libhermes*.so"
                }
            }.visit { details ->
                def targetVariant1 = ".*/transforms/[^/]*/${targetPath}/.*"
                def targetVariant2 = ".*/merged_native_libs/${targetPath}/out/lib/.*"
                def targetVariant3 = ".*/stripped_native_libs/${targetPath}/out/lib/.*"
                def path = details.file.getAbsolutePath().replace(File.separatorChar, '/' as char)
                if ((path.matches(targetVariant1) || path.matches(targetVariant2) || path.matches(targetVariant3)) && details.file.isFile()) {
                    details.file.delete()
                }
            }
        }
        
        /* 
         * 追加注释:: 具体的clean 操作
         */ 
        if (enableVmCleanup) {
            def task = tasks.findByName("package${targetName}")
            def transformsLibDir = "$buildDir/intermediates/transforms/"
            task.doFirst { vmSelectionAction(transformsLibDir) }

            def sTask = tasks.findByName("strip${targetName}DebugSymbols")
            if (sTask != null) {
                def strippedLibDir = "$buildDir/intermediates/stripped_native_libs/${targetPath}/out/lib/"
                sTask.doLast { vmSelectionAction(strippedLibDir) }
            }

            def mTask = tasks.findByName("merge${targetName}NativeLibs")
            if (mTask != null) {
                def mergedLibDir = "$buildDir/intermediates/merged_native_libs/${targetPath}/out/lib/"
                mTask.doLast { vmSelectionAction(mergedLibDir) }
            }
        }
    }
}

JS 版本的实现

const hermesCLIPath = {
  // hermesCommand="./node_modules/hermes-engine/osx-bin/hermesc";
  // # composeSourceMapsPath="./node_modules/react-native/scripts/compose-source-maps.js";
  // for mac 其他系统先不写, osx-bin 这一段替换为对应系统就行, 可以通过 os.platform 判断
  hermesCommandPath: require.resolve('hermes-engine/osx-bin/hermesc'),
  composeSourceMapsPath: require.resolve(
    'react-native/scripts/compose-source-maps.js'
  )
}

/** 
 * hermes hbc 编译 && sourmcemap 处理 
 **/
const processHermesHBCAndSourcemap = (
  bundleFileName: string,
  bundleSourcemapFileName?: string
) => {
  const hbcOutput = bundleFileName + '.hbc'

  let hbcCLI = `${hermesCLIPath.hermesCommandPath} -emit-binary -out ${hbcOutput} ${bundleFileName}`
  try {
    if (bundleSourcemapFileName) {
      /**
       * -output-source-map 这个参数输出的 map,
       * 会直接在 bundle 名称之后追展现一个 hbc.map: (待验证), 不支持自定义名称
       **/
      const compilerSourcemap = bundleFileName + '.hbc.map'

      const hermesSourceMapOutput = compilerSourcemap.replace(
        '.map',
        '.hermes.map'
      )
      hbcCLI += ' -output-source-map'
      const composeSourcemap = `node ${hermesCLIPath.composeSourceMapsPath} ${bundleSourcemapFileName} ${compilerSourcemap} -o ${hermesSourceMapOutput}`

      console.log(`RUN CLI:: ${hbcCLI}`)
      spawnSync(hbcCLI, { shell: true, stdio: 'inherit' })
      console.log(`RUN CLI:: ${composeSourcemap}`)
      spawnSync(composeSourcemap, { shell: true, stdio: 'inherit' })
    } else {
      console.log(`RUN CLI:: ${hbcCLI}`)
      spawnSync(hbcCLI, { shell: true, stdio: 'inherit' })
    }
  } catch (error) {
    console.log('hermes hbc package error ', error)
    console.trace(error)
  }
}
snice commented

1.8.0开始已经默认用hermes了吧

是的,Hermes可自行开启或关闭,使用方案与 RN 一致,