Skip to content

Commit d935b58

Browse files
committed
Add node.js prebuild docs and host implementation
1 parent c2a773a commit d935b58

File tree

4 files changed

+108
-4
lines changed

4 files changed

+108
-4
lines changed

docs/PREBUILDS.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ This document codifies the naming and directory structure of prebuilt binaries,
44

55
At the time of writing, our auto-linking host package (`react-native-node-api`) support two kinds of prebuilds:
66

7-
## `*.android.node` (for Android)
7+
## `{name}.android.node` (for Android)
88

9-
A jniLibs-like directory structure of CPU-architecture specific directories containing a single `.so` library file.
9+
A jniLibs-like directory structure of CPU-architecture specific directories containing a single shared object / dynamic library file.
1010

11-
The name of all the `.so` library files:
11+
The name of all the library files:
1212

1313
- must be the same across all CPU-architectures
1414
- can have a "lib" prefix, but doesn't have to
@@ -21,7 +21,7 @@ The name of all the `.so` library files:
2121
2222
The directory must have a `react-native-node-api-module` file (the content doesn't matter), to signal that the directory is intended for auto-linking by the `react-native-node-api-module` package.
2323

24-
## `*.apple.node` (for Apple)
24+
## `{name}.apple.node` (for Apple)
2525

2626
An XCFramework of dynamic libraries wrapped in `.framework` bundles, renamed from `.xcframework` to `.apple.node` to ease discoverability.
2727

@@ -31,6 +31,16 @@ The Apple Developer documentation on ["Creating a multiplatform binary framework
3131
3232
The directory must have a `react-native-node-api-module` file (the content doesn't matter), to signal that the directory is intended for auto-linking by the `react-native-node-api-module` package.
3333

34+
## `{name}.nodejs.node` (for Node.js)
35+
36+
A directory of OS + CPU architecture -specific directories (named `{process.platform}-{process.arch}`) containing a single shared object / dynamic library file.
37+
38+
The name of all the library files:
39+
40+
- must be the same across all CPU-architectures
41+
- can have a "lib" prefix, but doesn't have to
42+
- must have a `.node` file extension
43+
3444
## Why did we choose this naming scheme?
3545

3646
To align with prior art and established patterns around the distribution of Node-API modules for Node.js, we've chosen to use the ".node" filename extension for prebuilds of Node-API modules, targeting React Native.

packages/host/src/node/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@ export {
22
SUPPORTED_TRIPLETS,
33
ANDROID_TRIPLETS,
44
APPLE_TRIPLETS,
5+
NODE_TRIPLETS,
56
type SupportedTriplet,
67
type AndroidTriplet,
78
type AppleTriplet,
9+
type NodeTriplet,
810
isSupportedTriplet,
911
isAppleTriplet,
1012
isAndroidTriplet,
13+
isNodeTriplet,
1114
} from "./prebuilds/triplets.js";
1215

1316
export {
@@ -22,6 +25,11 @@ export {
2225
determineXCFrameworkFilename,
2326
} from "./prebuilds/apple.js";
2427

28+
export {
29+
createNodeLibsDirectory,
30+
determineNodeLibsFilename,
31+
} from "./prebuilds/node.js";
32+
2533
export { determineLibraryBasename, prettyPath } from "./path-utils.js";
2634

2735
export { weakNodeApiPath } from "./weak-node-api.js";
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import assert from "node:assert/strict";
2+
import fs from "node:fs";
3+
import path from "node:path";
4+
5+
import { determineLibraryBasename } from "../path-utils.js";
6+
import { NodeTriplet } from "./triplets.js";
7+
8+
type OSArchName =
9+
| `${typeof process.platform}-${typeof process.arch}`
10+
| "darwin-arm64;x86_64";
11+
12+
const DIRECTORY_NAMES_PER_TARGET = {
13+
"arm64;x86_64-apple-darwin": "darwin-arm64;x86_64",
14+
"arm64-apple-darwin": "darwin-arm64",
15+
"x86_64-apple-darwin": "darwin-x64",
16+
} satisfies Record<NodeTriplet, OSArchName>;
17+
18+
/**
19+
* Determine the filename of the Android libs directory based on the framework paths.
20+
* Ensuring that all framework paths have the same base name.
21+
*/
22+
export function determineNodeLibsFilename(libraryPaths: string[]) {
23+
const libraryName = determineLibraryBasename(libraryPaths);
24+
return `${libraryName}.nodejs.node`;
25+
}
26+
27+
type NodeLibsDirectoryOptions = {
28+
outputPath: string;
29+
libraryPathByTriplet: Record<NodeTriplet, string>;
30+
autoLink: boolean;
31+
};
32+
33+
export async function createNodeLibsDirectory({
34+
outputPath,
35+
libraryPathByTriplet,
36+
autoLink,
37+
}: NodeLibsDirectoryOptions) {
38+
// Delete and recreate any existing output directory
39+
await fs.promises.rm(outputPath, { recursive: true, force: true });
40+
await fs.promises.mkdir(outputPath, { recursive: true });
41+
for (const [triplet, libraryPath] of Object.entries(libraryPathByTriplet) as [
42+
NodeTriplet,
43+
string,
44+
][]) {
45+
assert(
46+
fs.existsSync(libraryPath),
47+
`Library not found: ${libraryPath} for triplet ${triplet}`,
48+
);
49+
// Create the architecture-specific directory
50+
const osArch = DIRECTORY_NAMES_PER_TARGET[triplet];
51+
const osArchOutputPath = path.join(outputPath, osArch);
52+
await fs.promises.mkdir(osArchOutputPath, { recursive: true });
53+
// Strip the ".so" extension from the library name
54+
const libraryName = path.basename(libraryPath, ".so");
55+
const nodeSuffixedName =
56+
path.extname(libraryName) === ".node"
57+
? libraryName
58+
: `${libraryName}.node`;
59+
const libraryOutputPath = path.join(osArchOutputPath, nodeSuffixedName);
60+
await fs.promises.copyFile(libraryPath, libraryOutputPath);
61+
}
62+
if (autoLink) {
63+
// Write a file to mark the Android libs directory is a Node-API module
64+
await fs.promises.writeFile(
65+
path.join(outputPath, "react-native-node-api-module"),
66+
"",
67+
"utf8",
68+
);
69+
}
70+
return outputPath;
71+
}

packages/host/src/node/prebuilds/triplets.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,18 @@ export const APPLE_TRIPLETS = [
2525

2626
export type AppleTriplet = (typeof APPLE_TRIPLETS)[number];
2727

28+
export const NODE_TRIPLETS = [
29+
"arm64;x86_64-apple-darwin",
30+
"x86_64-apple-darwin",
31+
"arm64-apple-darwin",
32+
] as const;
33+
34+
export type NodeTriplet = (typeof NODE_TRIPLETS)[number];
35+
2836
export const SUPPORTED_TRIPLETS = [
2937
...APPLE_TRIPLETS,
3038
...ANDROID_TRIPLETS,
39+
...NODE_TRIPLETS,
3140
] as const;
3241

3342
export type SupportedTriplet = (typeof SUPPORTED_TRIPLETS)[number];
@@ -49,3 +58,9 @@ export function isAppleTriplet(
4958
): triplet is AppleTriplet {
5059
return (APPLE_TRIPLETS as readonly unknown[]).includes(triplet);
5160
}
61+
62+
export function isNodeTriplet(
63+
triplet: SupportedTriplet,
64+
): triplet is NodeTriplet {
65+
return (NODE_TRIPLETS as readonly unknown[]).includes(triplet);
66+
}

0 commit comments

Comments
 (0)