Skip to content

Commit

Permalink
feat(cli): allow Hermes to be run post-bundle (#2423)
Browse files Browse the repository at this point in the history
  • Loading branch information
tido64 authored Sep 28, 2023
1 parent 564f7ca commit 839ba0e
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .changeset/big-wolves-begin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rnx-kit/config": patch
---

Add `HermesOptions` for controlling Hermes bytecode output
5 changes: 5 additions & 0 deletions .changeset/nervous-yaks-hide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rnx-kit/cli": patch
---

Allow Hermes to be run post-bundle
5 changes: 3 additions & 2 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,9 @@ command-line, they are explicitly set to default values.

| Parameter | Default Value |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
| entryFile | "index.js" |
| bundleOutput | "index.<`platform`>.bundle" (Windows, Android), or "index.<`platform`>.jsbundle" (iOS, macOS) |
| entryFile | `"index.js"` |
| bundleOutput | `"index.<platform>.bundle"` (Windows, Android) or `"index.<platform>.jsbundle"` (iOS, macOS) |
| hermes | `false` |
| treeShake | `false` |
| plugins | `["@rnx-kit/metro-plugin-cyclic-dependencies-detector", "@rnx-kit/metro-plugin-duplicates-checker", "@rnx-kit/metro-plugin-typescript"]` |

Expand Down
11 changes: 11 additions & 0 deletions packages/cli/src/bundle.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Config as CLIConfig } from "@react-native-community/cli-types";
import { loadMetroConfig } from "@rnx-kit/metro-service";
import { commonBundleCommandOptions } from "./bundle/cliOptions";
import { emitBytecode } from "./bundle/hermes";
import { getCliPlatformBundleConfigs } from "./bundle/kit-config";
import { metroBundle } from "./bundle/metro";
import {
Expand Down Expand Up @@ -28,6 +29,7 @@ export async function rnxBundle(

applyBundleConfigOverrides(cliOptions, bundleConfigs, [
...overridableCommonBundleOptions,
"hermes",
"treeShake",
]);

Expand All @@ -38,6 +40,15 @@ export async function rnxBundle(
cliOptions.dev,
cliOptions.minify
);

const { bundleOutput, hermes, sourcemapOutput } = bundleConfig;
if (hermes) {
emitBytecode(
bundleOutput,
sourcemapOutput,
hermes === true ? {} : hermes
);
}
}
}

Expand Down
104 changes: 104 additions & 0 deletions packages/cli/src/bundle/hermes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import type { HermesOptions } from "@rnx-kit/config";
import { error, info } from "@rnx-kit/console";
import { findPackageDependencyDir } from "@rnx-kit/tools-node/package";
import { spawnSync } from "child_process";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";

function hermesBinaryInDir(hermesc: string): string | null {
switch (os.platform()) {
case "darwin":
return path.join(hermesc, "osx-bin", "hermesc");
case "linux":
return path.join(hermesc, "linux64-bin", "hermesc");
case "win32":
return path.join(hermesc, "win64-bin", "hermesc.exe");
default:
return null;
}
}

function findHermesBinary() {
const locations = [
() => {
const rnPath = findPackageDependencyDir("react-native");
if (!rnPath) {
throw new Error("Cannot find module 'react-native'");
}
return path.join(rnPath, "sdks", "hermesc");
},
() => findPackageDependencyDir("hermes-engine"),
];

for (const getLocation of locations) {
const location = getLocation();
if (location) {
const hermesc = hermesBinaryInDir(location);
if (hermesc && fs.existsSync(hermesc)) {
return hermesc;
}
}
}

return null;
}

function getOutput(args: string[]): string | null {
const length = args.length;
for (let i = 0; i < length; ++i) {
const flag = args[i];
if (flag === "-out") {
return args[i + 1];
} else if (flag.startsWith("-out=")) {
return flag.substring(5);
}
}
return null;
}

function isSourceMapFlag(flag: string): boolean {
return flag === "-source-map" || flag.startsWith("-source-map=");
}

export function emitBytecode(
input: string,
sourcemap: string | undefined,
options: HermesOptions
): void {
const cmd = options.command || findHermesBinary();
if (!cmd) {
error("No Hermes compiler was found");
return;
}

const args = [
"-emit-binary",
// If Hermes can't detect the width of the terminal, it will set the limit
// to "unlimited". Since we might be passing a minified bundle to Hermes,
// limit output width to avoid issues when it outputs diagnostics. See:
// - https://github.com/microsoft/rnx-kit/issues/2416
// - https://github.com/microsoft/rnx-kit/issues/2419
// - https://github.com/microsoft/rnx-kit/issues/2424
"-max-diagnostic-width=80",
...(options.flags ?? ["-O", "-output-source-map", "-w"]),
];

let output = getOutput(args);
if (!output) {
output = input + ".hbc";
args.push("-out", output);
}

if (sourcemap && !args.some(isSourceMapFlag)) {
args.push("-source-map", sourcemap);
}

args.push(input);

info("Emitting bytecode to:", output);
const result = spawnSync(cmd, args, { stdio: "inherit" });
if (result.status !== 0) {
throw result.error;
}
}
1 change: 1 addition & 0 deletions packages/cli/src/bundle/overrides.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type BundleConfigOverrides = Partial<
| "treeShake"
| "unstableTransformProfile"
| "indexedRamBundle"
| "hermes"
>
>;

Expand Down
21 changes: 21 additions & 0 deletions packages/config/src/bundleConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@ import type { Options as EsbuildOptions } from "@rnx-kit/metro-serializer-esbuil
import type { AllPlatforms } from "@rnx-kit/tools-react-native/platform";
import type { OutputOptions } from "metro/src/shared/types";

export type HermesOptions = {
/**
* Path to `hermesc` binary. By default, `cli` will try to find it in
* `node_modules`.
*/
command?: string;

/**
* List of arguments passed to `hermesc`. By default, this is
* `["-O", "-output-source-map", "-w"]`.
*/
flags?: string[];
};

export type TypeScriptValidationOptions = {
/**
* Controls whether an error is thrown when type-validation fails.
Expand Down Expand Up @@ -105,6 +119,13 @@ export type BundleParameters = BundlerPlugins & {
*/
treeShake?: boolean | EsbuildOptions;

/**
* Whether to run the Hermes compiler on the output bundle.
*
* Only applies to `rnx-bundle` command.
*/
hermes?: boolean | HermesOptions;

/**
* List of plugins to add to the bundling process.
*
Expand Down
1 change: 1 addition & 0 deletions packages/config/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export type {
BundleConfig,
BundleParameters,
BundlerPlugins,
HermesOptions,
TypeScriptValidationOptions,
} from "./bundleConfig";

Expand Down

0 comments on commit 839ba0e

Please sign in to comment.