-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathplugin.ts
More file actions
119 lines (110 loc) · 3.99 KB
/
plugin.ts
File metadata and controls
119 lines (110 loc) · 3.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import assert from "node:assert/strict";
import path from "node:path";
import type { PluginObj, NodePath } from "@babel/core";
import * as t from "@babel/types";
import {
determineModuleContext,
isNodeApiModule,
findNodeAddonForBindings,
} from "../path-utils";
export type PluginOptions = {
stripPathSuffix?: boolean;
};
function assertOptions(opts: unknown): asserts opts is PluginOptions {
assert(typeof opts === "object" && opts !== null, "Expected an object");
if ("stripPathSuffix" in opts) {
assert(
typeof opts.stripPathSuffix === "boolean",
"Expected 'stripPathSuffix' to be a boolean"
);
}
}
// This function should work with both CommonJS and ECMAScript modules,
// (pretending that addons are supported with ES module imports), hence it
// must accept following import specifiers:
// - "Relative specifiers" (e.g. `./build/Release/addon.node`)
// - "Bare specifiers", in particular
// - to an entry point (e.g. `@callstack/example-addon`)
// - any specific exported feature within
// - "Absolute specifiers" like `node:fs/promise` and URLs.
//
// This function should also respect the Package entry points defined in the
// respective "package.json" file using "main" or "exports" and "imports"
// fields (including conditional exports and subpath imports).
// - https://nodejs.org/api/packages.html#package-entry-points
// - https://nodejs.org/api/packages.html#subpath-imports
function tryResolveModulePath(id: string, from: string): string | undefined {
if (id.includes(":")) {
// This must be a prefixed "Absolute specifier". We assume its a built-in
// module and pass it through without any changes. For security reasons,
// we don't support URLs to dynamic libraries (like Node-API addons).
return undefined;
} else {
// TODO: Stay compatible with https://nodejs.org/api/modules.html#all-together
try {
return require.resolve(id, { paths: [from] });
} catch {
return undefined;
}
}
}
export function replaceWithRequireNodeAddon3(
p: NodePath,
resolvedPath: string,
originalId: string
) {
const { packageName, relativePath } = determineModuleContext(resolvedPath);
const dotRelativePath = relativePath.startsWith("./") ? relativePath : `./${relativePath}`;
p.replaceWith(
t.callExpression(
t.memberExpression(
t.callExpression(t.identifier("require"), [
t.stringLiteral("react-native-node-api-modules"),
]),
t.identifier("requireNodeAddon")
),
[dotRelativePath, packageName, originalId]
.map(t.stringLiteral),
)
);
}
export function plugin(): PluginObj {
return {
visitor: {
CallExpression(p) {
assertOptions(this.opts);
if (typeof this.filename !== "string") {
// This transformation only works when the filename is known
return;
}
const from = path.dirname(this.filename);
const { node } = p;
const [argument] = node.arguments;
if (
node.callee.type === "Identifier" &&
node.callee.name === "require" &&
argument.type === "StringLiteral"
) {
// Require call with a string literal argument
const id = argument.value;
if (id === "bindings" && p.parent.type === "CallExpression") {
const [argument] = p.parent.arguments;
if (argument.type === "StringLiteral") {
const id = argument.value;
const resolvedPath = findNodeAddonForBindings(id, from);
if (resolvedPath !== undefined) {
replaceWithRequireNodeAddon3(p.parentPath, resolvedPath, id);
}
}
} else {
// This should handle "bare specifiers" and "private imports" that start with `#`
const resolvedPath = tryResolveModulePath(id, from);
if (!!resolvedPath && isNodeApiModule(resolvedPath)) {
replaceWithRequireNodeAddon3(p, resolvedPath, id);
}
}
}
},
},
};
}