Skip to content

Commit

Permalink
feat(cli): allow Hermes to be run post-bundle
Browse files Browse the repository at this point in the history
  • Loading branch information
tido64 committed May 23, 2023
1 parent 003c696 commit 39d861f
Show file tree
Hide file tree
Showing 8 changed files with 154 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
7 changes: 7 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 { emitBinary } 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,11 @@ export async function rnxBundle(
cliOptions.dev,
cliOptions.minify
);

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

Expand Down
111 changes: 111 additions & 0 deletions packages/cli/src/bundle/hermes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
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 updateSourceMappingURL(
bundle: string,
oldUrl: string,
newUrl: string
): void {
const js = fs.readFileSync(bundle, { encoding: "utf-8" });
const idx = js.lastIndexOf(oldUrl);
if (idx > 0) {
fs.writeFileSync(bundle, js.substring(0, idx) + newUrl + "\n");
}
}

const findHermesBinary = (() => {
let bin: string;
return () => {
if (!bin) {
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)) {
bin = hermesc;
break;
}
}
}
}
return bin;
};
})();

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

const jsInput = input + ".js";
info(`Renaming ${input} -> ${jsInput}`);
fs.renameSync(input, jsInput);

if (sourcemap && fs.existsSync(sourcemap)) {
const jsMapInput = jsInput + ".map";
info(`Renaming ${sourcemap} -> ${jsMapInput}`);
fs.renameSync(sourcemap, jsMapInput);

const outputDir = path.dirname(input);
updateSourceMappingURL(
jsInput,
path.relative(outputDir, sourcemap),
path.relative(outputDir, jsMapInput)
);
}

info("Emitting bytecode to:", input);
const args = [
...(options.flags ?? ["-O", "-output-source-map", "-w"]),
"-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",
"-out",
input,
jsInput,
];
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/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 39d861f

Please sign in to comment.