-
Notifications
You must be signed in to change notification settings - Fork 47
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
启用 Hermes #26
Comments
补充一点 Android 相关的 gradle 分析简要介绍: (个人理解, 非官方) Taro RN 暂时没有支持 Hermes 是因为有一部分字节码打包操作是在
先看下 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概述:
第3条详细说一下:3.1 如果开启 hermes, 就把 minify 关掉, 因为没必要, (但是不关闭其实也可以) 至此, 可以获得一个新的, 对应 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)
}
} |
1.8.0开始已经默认用hermes了吧 |
是的,Hermes可自行开启或关闭,使用方案与 RN 一致, |
文档
https://reactnative.dev/docs/hermes
问题
react-native/scripts/react-native-xcode.sh
和node_modules/react-native/react.gradle
以至于没有进行字节码的打包,相关内容需要进行重构
The text was updated successfully, but these errors were encountered: