@crossbind/core-embind-napi 2.0.0-beta.62 → 2.0.0-beta.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -7
- package/cpp/CMakeLists.txt +7 -0
- package/cpp/check-undefined.cmake +22 -2
- package/cpp/src/node_api_module.cpp +10 -6
- package/js/addonLocation.js +13 -0
- package/js/addonPlatform.js +19 -0
- package/js/loader.js +44 -17
- package/js/missingAddon.js +9 -0
- package/js/stopOnExit.js +15 -0
- package/js/unpackedPath.js +5 -0
- package/package.json +2 -2
- package/third_party/node-api-jsi/README.md +4 -0
- package/third_party/node-api-jsi/src/NodeApiJsiRuntime.cpp +16 -5
- package/third_party/node-api-jsi/src/NodeApiJsiRuntime.h +2 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Runs the crossbind embind runtime inside a native Node.js addon, so the bindings
|
|
|
4
4
|
WebAssembly and React Native also build `.node` addons for Node.js and Electron.
|
|
5
5
|
|
|
6
6
|
<a href="https://www.npmjs.com/package/@crossbind/core-embind-napi">
|
|
7
|
-
<img alt="NPM version" src="https://img.shields.io/npm/v/@crossbind/core-embind-napi?style=for-the-badge" />
|
|
7
|
+
<img alt="NPM version" src="https://img.shields.io/npm/v/@crossbind/core-embind-napi/beta?style=for-the-badge" />
|
|
8
8
|
</a>
|
|
9
9
|
<a href="https://github.com/crossbind/crossbind/blob/main/LICENSE">
|
|
10
10
|
<img alt="License" src="https://img.shields.io/github/license/crossbind/crossbind?style=for-the-badge" />
|
|
@@ -12,15 +12,17 @@ WebAssembly and React Native also build `.node` addons for Node.js and Electron.
|
|
|
12
12
|
|
|
13
13
|
## Use
|
|
14
14
|
|
|
15
|
-
Add it next to `crossbind` and build the
|
|
15
|
+
Add it next to `crossbind` and build the addons of this machine's platform, or of every desktop
|
|
16
|
+
platform:
|
|
16
17
|
|
|
17
18
|
```bash
|
|
18
|
-
pnpm add -D crossbind @crossbind/core-embind-napi
|
|
19
|
-
pnpm crossbind build -p
|
|
19
|
+
pnpm add -D crossbind@beta @crossbind/core-embind-napi@beta
|
|
20
|
+
pnpm crossbind build -p host -e node -b release
|
|
21
|
+
pnpm crossbind build -p darwin,linux,linuxmusl,win32 -e node -b release
|
|
20
22
|
```
|
|
21
23
|
|
|
22
|
-
`dist/` then holds one addon per platform and architecture (`<name>.darwin-arm64.node`,
|
|
23
|
-
`<name>.
|
|
24
|
+
`dist/` then holds one addon per platform and architecture (e.g. `<name>.darwin-arm64.node`,
|
|
25
|
+
`<name>.linux-x64.node`, `<name>.win32-x64.node`) and the loader that picks one at runtime:
|
|
24
26
|
|
|
25
27
|
```js
|
|
26
28
|
const initNative = require('./dist/<name>.native.cjs');
|
|
@@ -30,12 +32,14 @@ const { Native } = await initNative();
|
|
|
30
32
|
|
|
31
33
|
crossbind resolves this package from your project; you never import it yourself. See the
|
|
32
34
|
[Node.js integration guide](https://github.com/crossbind/crossbind/blob/main/docs/playbooks/integration/nodejs.md#native-addon-node-api) for
|
|
33
|
-
requirements and current limits (
|
|
35
|
+
requirements, Electron and current limits (no `worker_threads` yet).
|
|
34
36
|
|
|
35
37
|
## Contents
|
|
36
38
|
|
|
37
39
|
- `cpp/` — the addon's CMake project and its Node-API entry point.
|
|
38
40
|
- `js/loader.js` — the loader crossbind bundles into `dist/<name>.native.cjs`.
|
|
41
|
+
- `js/addonPlatform.js` — tells glibc and musl Linux apart, so the loader picks the `linux` or `linuxmusl` addon.
|
|
42
|
+
- `js/unpackedPath.js` — points a packaged Electron app's addon at the data its asar archive keeps unpacked.
|
|
39
43
|
- `third_party/node-api-jsi/` — Microsoft's JSI implementation over Node-API (MIT), vendored at a
|
|
40
44
|
pinned commit; provenance and local patches are in its README.
|
|
41
45
|
|
package/cpp/CMakeLists.txt
CHANGED
|
@@ -45,8 +45,10 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
|
|
45
45
|
find_package(Threads REQUIRED)
|
|
46
46
|
target_link_libraries(addon PRIVATE Threads::Threads ${CMAKE_DL_LIBS})
|
|
47
47
|
target_link_options(addon PRIVATE "LINKER:--exclude-libs,ALL")
|
|
48
|
+
file(GLOB MUSL_LIBC "${CMAKE_SYSROOT}/lib/ld-musl-*.so.1")
|
|
48
49
|
add_custom_command(TARGET addon POST_BUILD
|
|
49
50
|
COMMAND "${CMAKE_COMMAND}" "-DNM=${CMAKE_NM}" "-DADDON=$<TARGET_FILE:addon>"
|
|
51
|
+
"-DUNVERSIONED_LIBRARIES=${MUSL_LIBC}"
|
|
50
52
|
-P "${CMAKE_CURRENT_SOURCE_DIR}/check-undefined.cmake"
|
|
51
53
|
VERBATIM)
|
|
52
54
|
endif()
|
|
@@ -66,4 +68,9 @@ if(APPLE)
|
|
|
66
68
|
string(REGEX REPLACE "^NODE_API_FUNC\\(([A-Za-z0-9_]+)\\).*" "\\1" func "${line}")
|
|
67
69
|
target_link_options(addon PRIVATE "LINKER:-U,_${func}")
|
|
68
70
|
endforeach()
|
|
71
|
+
# Only Node-API's entry points leave the addon: dyld merges exported weak definitions across the
|
|
72
|
+
# images of a process, so two addons would share inline state such as embind's registration guards.
|
|
73
|
+
target_link_options(addon PRIVATE
|
|
74
|
+
"LINKER:-exported_symbol,_napi_register_module_v1"
|
|
75
|
+
"LINKER:-exported_symbol,_node_api_module_get_api_version_v1")
|
|
69
76
|
endif()
|
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
# A Linux shared object may leave symbols undefined, and the loader binds functions lazily: an addon
|
|
2
2
|
# that misses an archive loads, then dies at its first call into the missing code. Every undefined
|
|
3
3
|
# symbol has to be a Node-API function or carry the version of a system library that defines it.
|
|
4
|
+
# musl versions nothing, so a name its C library (UNVERSIONED_LIBRARIES) defines passes as it is.
|
|
5
|
+
set(defined)
|
|
6
|
+
foreach(library IN LISTS UNVERSIONED_LIBRARIES)
|
|
7
|
+
execute_process(
|
|
8
|
+
COMMAND "${NM}" -D --defined-only "${library}"
|
|
9
|
+
OUTPUT_VARIABLE exports
|
|
10
|
+
COMMAND_ERROR_IS_FATAL ANY)
|
|
11
|
+
string(REPLACE "\n" ";" export_lines "${exports}")
|
|
12
|
+
foreach(export_line IN LISTS export_lines)
|
|
13
|
+
if(export_line MATCHES " ([^ ]+)$")
|
|
14
|
+
list(APPEND defined "${CMAKE_MATCH_1}")
|
|
15
|
+
endif()
|
|
16
|
+
endforeach()
|
|
17
|
+
endforeach()
|
|
4
18
|
execute_process(
|
|
5
19
|
COMMAND "${NM}" -D --undefined-only "${ADDON}"
|
|
6
20
|
OUTPUT_VARIABLE listing
|
|
@@ -8,8 +22,14 @@ execute_process(
|
|
|
8
22
|
string(REPLACE "\n" ";" lines "${listing}")
|
|
9
23
|
set(unresolved)
|
|
10
24
|
foreach(line IN LISTS lines)
|
|
11
|
-
if(line MATCHES "^ *U ([^@ ]+)$"
|
|
12
|
-
|
|
25
|
+
if(NOT line MATCHES "^ *U ([^@ ]+)$")
|
|
26
|
+
continue()
|
|
27
|
+
endif()
|
|
28
|
+
# A failed MATCHES clears CMAKE_MATCH_1, so the name is kept before the next one.
|
|
29
|
+
set(symbol "${CMAKE_MATCH_1}")
|
|
30
|
+
list(FIND defined "${symbol}" index)
|
|
31
|
+
if(NOT symbol MATCHES "^(napi|node_api)_" AND index EQUAL -1)
|
|
32
|
+
list(APPEND unresolved "${symbol}")
|
|
13
33
|
endif()
|
|
14
34
|
endforeach()
|
|
15
35
|
if(unresolved)
|
|
@@ -82,9 +82,10 @@ std::string readString(napi_env env, napi_value value) {
|
|
|
82
82
|
return result;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
// start(dataPath) registers every EMSCRIPTEN_BINDINGS block of the addon into this
|
|
86
|
-
// embind runtime, so embind.js must already be loaded.
|
|
87
|
-
//
|
|
85
|
+
// start(dataPath, scope) registers every EMSCRIPTEN_BINDINGS block of the addon into this
|
|
86
|
+
// environment's embind runtime, so embind.js must already be loaded. scope is the object that
|
|
87
|
+
// runtime keeps its state on, which this addon's global() returns. A second call in the same
|
|
88
|
+
// environment is a no-op: registering twice fails with "Cannot register public name ... twice".
|
|
88
89
|
napi_value Start(napi_env env, napi_callback_info info) {
|
|
89
90
|
void *existing = nullptr;
|
|
90
91
|
NODE_API(napi_get_instance_data)(env, &existing);
|
|
@@ -100,17 +101,20 @@ napi_value Start(napi_env env, napi_callback_info info) {
|
|
|
100
101
|
return nullptr;
|
|
101
102
|
}
|
|
102
103
|
|
|
103
|
-
size_t argc =
|
|
104
|
-
napi_value argv[
|
|
104
|
+
size_t argc = 2;
|
|
105
|
+
napi_value argv[2];
|
|
105
106
|
NODE_API(napi_get_cb_info)(env, info, &argc, argv, nullptr, nullptr);
|
|
106
107
|
const std::string dataPath = argc > 0 ? readString(env, argv[0]) : std::string();
|
|
108
|
+
napi_valuetype scopeType = napi_undefined;
|
|
109
|
+
if (argc > 1) NODE_API(napi_typeof)(env, argv[1], &scopeType);
|
|
110
|
+
napi_value scope = scopeType == napi_object ? argv[1] : nullptr;
|
|
107
111
|
|
|
108
112
|
// An exception must not unwind through Node-API, which would end the process.
|
|
109
113
|
try {
|
|
110
114
|
auto state = std::make_unique<EnvState>();
|
|
111
115
|
state->env = env;
|
|
112
116
|
JSRuntimeApi::setCurrent(&state->api);
|
|
113
|
-
state->runtime = Microsoft::NodeApiJsi::makeNodeApiJsiRuntime(env, &state->api, [] {});
|
|
117
|
+
state->runtime = Microsoft::NodeApiJsi::makeNodeApiJsiRuntime(env, &state->api, [] {}, scope);
|
|
114
118
|
// The environment owns it before any binding registers: registered functions point into it,
|
|
115
119
|
// so it has to outlive a registration that fails halfway.
|
|
116
120
|
EnvState *owned = state.release();
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
// A project's own build leaves the addon beside the loader. A package that publishes one addon per
|
|
4
|
+
// platform ships it in <package>-<platform>-<arch>, an optional dependency whose main is the addon.
|
|
5
|
+
export default function addonLocation({ dir, fileName, packageName, exists, resolve }) {
|
|
6
|
+
const beside = path.join(dir, fileName);
|
|
7
|
+
if (exists(beside) || !packageName) return beside;
|
|
8
|
+
try {
|
|
9
|
+
return resolve(packageName);
|
|
10
|
+
} catch {
|
|
11
|
+
return beside;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// The platform part of an addon file name. Node reports musl and glibc systems alike as 'linux'; a
|
|
2
|
+
// process linked against glibc names that glibc in its report header, a musl one does not.
|
|
3
|
+
export default function addonPlatform(platform = process.platform, readReport = processReport) {
|
|
4
|
+
if (platform !== 'linux') return platform;
|
|
5
|
+
const header = readReport()?.header;
|
|
6
|
+
return !header || header.glibcVersionRuntime ? 'linux' : 'linuxmusl';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function processReport() {
|
|
10
|
+
if (!process.report) return undefined;
|
|
11
|
+
// Only the header is read; the network section can stall on DNS lookups.
|
|
12
|
+
const { excludeNetwork } = process.report;
|
|
13
|
+
process.report.excludeNetwork = true;
|
|
14
|
+
try {
|
|
15
|
+
return process.report.getReport();
|
|
16
|
+
} finally {
|
|
17
|
+
process.report.excludeNetwork = excludeNetwork;
|
|
18
|
+
}
|
|
19
|
+
}
|
package/js/loader.js
CHANGED
|
@@ -1,12 +1,29 @@
|
|
|
1
1
|
// Entry of the per-project loader crossbind bundles next to the native addons
|
|
2
2
|
// (dist/<name>.native.cjs). It keeps the wasm build's initNative() contract, so the same app code
|
|
3
3
|
// runs on either build.
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
4
6
|
import path from 'node:path';
|
|
5
|
-
import Module from '@crossbind/core-embind-jsi';
|
|
7
|
+
import Module, { crossbindScope } from '@crossbind/core-embind-jsi';
|
|
6
8
|
import systemConfig from 'crossbind/systemConfig';
|
|
9
|
+
import addonPlatform from './addonPlatform.js';
|
|
10
|
+
import addonLocation from './addonLocation.js';
|
|
11
|
+
import missingAddonRemedy from './missingAddon.js';
|
|
12
|
+
import stopOnExit from './stopOnExit.js';
|
|
13
|
+
import unpackedPath from './unpackedPath.js';
|
|
7
14
|
|
|
8
|
-
|
|
9
|
-
|
|
15
|
+
const platform = addonPlatform();
|
|
16
|
+
const fill = (pattern) => pattern.replace('{platform}', platform).replace('{arch}', process.arch);
|
|
17
|
+
const addonPackage = systemConfig.paths.addonPackage ? fill(systemConfig.paths.addonPackage) : null;
|
|
18
|
+
|
|
19
|
+
function addonFile(config) {
|
|
20
|
+
return config.addonPath ?? addonLocation({
|
|
21
|
+
dir: __dirname,
|
|
22
|
+
fileName: fill(systemConfig.paths.addon),
|
|
23
|
+
packageName: addonPackage,
|
|
24
|
+
exists: fs.existsSync,
|
|
25
|
+
resolve: createRequire(__filename).resolve,
|
|
26
|
+
});
|
|
10
27
|
}
|
|
11
28
|
|
|
12
29
|
function loadAddon(file) {
|
|
@@ -14,38 +31,43 @@ function loadAddon(file) {
|
|
|
14
31
|
try {
|
|
15
32
|
process.dlopen(addon, file);
|
|
16
33
|
} catch (error) {
|
|
17
|
-
throw new Error(
|
|
18
|
-
`crossbind: cannot load ${file} - build it with \`crossbind build -p ${process.platform} -a ${process.arch}\`.`,
|
|
19
|
-
{ cause: error },
|
|
20
|
-
);
|
|
34
|
+
throw new Error(`crossbind: cannot load ${file} - ${fs.existsSync(file) ? error.message : missingAddonRemedy({ platform, arch: process.arch, addonPackage, published: Object.keys(systemConfig.env) })}.`, { cause: error });
|
|
21
35
|
}
|
|
22
36
|
return addon.exports;
|
|
23
37
|
}
|
|
24
38
|
|
|
25
|
-
function setEnv(dataPath, runtimeEnv) {
|
|
39
|
+
function setEnv(dataPath, runtimeEnv = {}) {
|
|
40
|
+
const fillDataPath = (value) => String(value).replace('_CROSSBIND_DATA_PATH_', dataPath);
|
|
26
41
|
// Build-time values are keyed by addon: a target-scoped value must not leak from one arch's
|
|
27
|
-
// build into another's.
|
|
28
|
-
|
|
29
|
-
Object.entries(env).forEach(([key, value]) => {
|
|
30
|
-
Module.Crossbind.setEnv(key,
|
|
42
|
+
// build into another's. They never replace a variable the process already has; values passed to
|
|
43
|
+
// initNative() do, as in the wasm runtime, even after a package entry booted the addon.
|
|
44
|
+
Object.entries(systemConfig.env[`${platform}-${process.arch}`] ?? {}).forEach(([key, value]) => {
|
|
45
|
+
Module.Crossbind.setEnv(key, fillDataPath(value), false);
|
|
46
|
+
});
|
|
47
|
+
Object.entries(runtimeEnv).forEach(([key, value]) => {
|
|
48
|
+
Module.Crossbind.setEnv(key, fillDataPath(value), true);
|
|
31
49
|
});
|
|
32
50
|
}
|
|
33
51
|
|
|
34
52
|
let addon = null;
|
|
53
|
+
let addonDataPath = null;
|
|
35
54
|
|
|
36
55
|
function boot(config) {
|
|
37
|
-
const dataPath = config.dataPath ?? path.join(__dirname, 'data');
|
|
38
56
|
// Loaded and started once per process: Node cannot unload an addon, and a second dlopen gets a
|
|
39
57
|
// fresh environment whose start() would register every binding twice.
|
|
40
58
|
if (!addon) {
|
|
41
|
-
|
|
42
|
-
loaded
|
|
59
|
+
addonDataPath = config.dataPath ?? unpackedPath(path.join(__dirname, 'data'));
|
|
60
|
+
const loaded = loadAddon(addonFile(config));
|
|
61
|
+
// The embind runtime this bundle carries keeps its state on crossbindScope (crossbind's utils/scopedEmbind.js).
|
|
62
|
+
loaded.start(addonDataPath, crossbindScope);
|
|
43
63
|
// Native values released after this (thread-local ones die inside exit()) must not call
|
|
44
64
|
// back into the runtime.
|
|
45
|
-
|
|
65
|
+
stopOnExit(() => loaded.stop());
|
|
46
66
|
addon = loaded;
|
|
67
|
+
} else if (config.dataPath && config.dataPath !== addonDataPath) {
|
|
68
|
+
throw new Error(`crossbind: the addon started with its data at ${addonDataPath}; a process cannot move it to ${config.dataPath}.`);
|
|
47
69
|
}
|
|
48
|
-
setEnv(
|
|
70
|
+
setEnv(addonDataPath, config.env);
|
|
49
71
|
return Module;
|
|
50
72
|
}
|
|
51
73
|
|
|
@@ -63,6 +85,11 @@ export default function initNative(config = {}) {
|
|
|
63
85
|
return booted;
|
|
64
86
|
}
|
|
65
87
|
|
|
88
|
+
// Loading an addon is synchronous, so a package entry boots it on require.
|
|
89
|
+
initNative.sync = function sync(config = {}) {
|
|
90
|
+
return boot(config);
|
|
91
|
+
};
|
|
92
|
+
|
|
66
93
|
// The wasm runtime's contract: forget the booted module so the next call boots again. Native state
|
|
67
94
|
// cannot be reset (Node cannot unload an addon), so that call resolves the same module.
|
|
68
95
|
initNative.terminate = function terminate() {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// What a process without its addon is told: install the addon package npm left out, learn that the package
|
|
2
|
+
// publishes none for this machine, or build the addon an app makes itself. published lists the
|
|
3
|
+
// <platform>-<arch> of every addon the package ships.
|
|
4
|
+
export default function missingAddonRemedy({ platform, arch, addonPackage, published }) {
|
|
5
|
+
const target = `${platform}-${arch}`;
|
|
6
|
+
if (!addonPackage) return `build it with \`crossbind build -p ${platform} -a ${arch}\``;
|
|
7
|
+
if (!published.includes(target)) return `this package publishes no addon for ${target}, only for ${published.join(', ')}`;
|
|
8
|
+
return `install ${addonPackage}, which npm leaves out with --omit=optional; addons exist for ${published.join(', ')}`;
|
|
9
|
+
}
|
package/js/stopOnExit.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Every package bundles a loader of its own, and Node warns at an eleventh 'exit' listener, so the addons share one,
|
|
2
|
+
// found by a registered symbol. Each boot moves it to the end of the list: the stops then run after every handler
|
|
3
|
+
// registered so far, as a listener added at that boot would, since a call into an addon after its stop crashes.
|
|
4
|
+
const ADDON_STOPS = Symbol.for('crossbind.addonStops.v1');
|
|
5
|
+
|
|
6
|
+
export default function stopOnExit(stop, proc = process) {
|
|
7
|
+
if (!proc[ADDON_STOPS]) {
|
|
8
|
+
const stops = [];
|
|
9
|
+
proc[ADDON_STOPS] = { stops, onExit: () => stops.forEach((stopAddon) => stopAddon()) };
|
|
10
|
+
}
|
|
11
|
+
const { stops, onExit } = proc[ADDON_STOPS];
|
|
12
|
+
stops.push(stop);
|
|
13
|
+
proc.removeListener('exit', onExit);
|
|
14
|
+
proc.on('exit', onExit);
|
|
15
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Native code cannot read inside an Electron asar archive, so a packaged app keeps what an addon
|
|
2
|
+
// reads in <archive>.asar.unpacked beside it.
|
|
3
|
+
export default function unpackedPath(file, isElectron = Boolean(process.versions.electron)) {
|
|
4
|
+
return isElectron ? file.replace(/\.asar(?=[\\/]|$)/, '.asar.unpacked') : file;
|
|
5
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crossbind/core-embind-napi",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.63",
|
|
4
4
|
"description": "Node-API host for the crossbind embind runtime: native Node.js addons from the same C++ and Rust bindings.",
|
|
5
5
|
"homepage": "https://github.com/crossbind/crossbind/tree/main/core/embind-napi#readme",
|
|
6
6
|
"repository": {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"third_party"
|
|
15
15
|
],
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@crossbind/core-embind-jsi": "^2.0.0-beta.
|
|
17
|
+
"@crossbind/core-embind-jsi": "^2.0.0-beta.63"
|
|
18
18
|
},
|
|
19
19
|
"keywords": [
|
|
20
20
|
"embind",
|
|
@@ -53,3 +53,7 @@ no `jsi-version.h` is needed.
|
|
|
53
53
|
it. A string return went from 75 to 69 ns and a string argument from 95 to 84 ns; together with
|
|
54
54
|
the direct BigInt read, a Rust `xxh64` call on 32 bytes with a BigInt seed and result went from
|
|
55
55
|
141 to 110 ns. A million callbacks in one call peak at the same 70 MB.
|
|
56
|
+
- `makeNodeApiJsiRuntime` takes an optional object that `global()` returns in place of the
|
|
57
|
+
global object. Each addon's loader bundles its own embind runtime, which keeps its `Module` and
|
|
58
|
+
the functions the addon calls by name on such an object, so addons of several packages run in
|
|
59
|
+
one process. The runtime's own lookups (`Error`, `Proxy`, `Symbol`) still use the global object.
|
|
@@ -200,7 +200,8 @@ class NodeApiJsiRuntime : public jsi::Runtime, public crossbind::IDirectReads {
|
|
|
200
200
|
NodeApiJsiRuntime(
|
|
201
201
|
napi_env env,
|
|
202
202
|
JSRuntimeApi *jsrApi,
|
|
203
|
-
std::function<void()> onDelete
|
|
203
|
+
std::function<void()> onDelete,
|
|
204
|
+
napi_value globalScope) noexcept;
|
|
204
205
|
~NodeApiJsiRuntime() override;
|
|
205
206
|
|
|
206
207
|
jsi::ICast *castInterface(const jsi::UUID &interfaceUUID) override;
|
|
@@ -1116,6 +1117,7 @@ class NodeApiJsiRuntime : public jsi::Runtime, public crossbind::IDirectReads {
|
|
|
1116
1117
|
struct CachedValue {
|
|
1117
1118
|
NodeApiRefHolder Error;
|
|
1118
1119
|
NodeApiRefHolder Global;
|
|
1120
|
+
NodeApiRefHolder GlobalScope;
|
|
1119
1121
|
NodeApiRefHolder HostObjectProxyHandler;
|
|
1120
1122
|
NodeApiRefHolder ProxyConstructor;
|
|
1121
1123
|
NodeApiRefHolder SymbolToString;
|
|
@@ -1214,7 +1216,8 @@ bool StringKey::EqualTo::operator()(
|
|
|
1214
1216
|
NodeApiJsiRuntime::NodeApiJsiRuntime(
|
|
1215
1217
|
napi_env env,
|
|
1216
1218
|
JSRuntimeApi *jsrApi,
|
|
1217
|
-
std::function<void()> onDelete
|
|
1219
|
+
std::function<void()> onDelete,
|
|
1220
|
+
napi_value globalScope) noexcept
|
|
1218
1221
|
: env_(env), jsrApi_(jsrApi), onDelete_(std::move(onDelete)) {
|
|
1219
1222
|
NodeApiScope scope{*this};
|
|
1220
1223
|
propertyId_.Error = makeNodeApiRef(
|
|
@@ -1266,6 +1269,10 @@ NodeApiJsiRuntime::NodeApiJsiRuntime(
|
|
|
1266
1269
|
|
|
1267
1270
|
cachedValue_.Global =
|
|
1268
1271
|
makeNodeApiRef(getGlobal(), NodeApiPointerValueKind::Object);
|
|
1272
|
+
if (globalScope) {
|
|
1273
|
+
cachedValue_.GlobalScope =
|
|
1274
|
+
makeNodeApiRef(globalScope, NodeApiPointerValueKind::Object);
|
|
1275
|
+
}
|
|
1269
1276
|
cachedValue_.Error = makeNodeApiRef(
|
|
1270
1277
|
getProperty(
|
|
1271
1278
|
getNodeApiValue(cachedValue_.Global),
|
|
@@ -1337,7 +1344,9 @@ bool NodeApiJsiRuntime::drainMicrotasks(int maxMicrotasksHint) {
|
|
|
1337
1344
|
#endif
|
|
1338
1345
|
|
|
1339
1346
|
jsi::Object NodeApiJsiRuntime::global() {
|
|
1340
|
-
|
|
1347
|
+
const NodeApiRefHolder &global =
|
|
1348
|
+
cachedValue_.GlobalScope ? cachedValue_.GlobalScope : cachedValue_.Global;
|
|
1349
|
+
return make<jsi::Object>(global->clone(*this));
|
|
1341
1350
|
}
|
|
1342
1351
|
|
|
1343
1352
|
std::string NodeApiJsiRuntime::description() {
|
|
@@ -3737,8 +3746,10 @@ void NodeApiJsiRuntime::popPointerValueScope() noexcept {
|
|
|
3737
3746
|
std::unique_ptr<jsi::Runtime> makeNodeApiJsiRuntime(
|
|
3738
3747
|
napi_env env,
|
|
3739
3748
|
JSRuntimeApi *jsrApi,
|
|
3740
|
-
std::function<void()> onDelete
|
|
3741
|
-
|
|
3749
|
+
std::function<void()> onDelete,
|
|
3750
|
+
napi_value globalScope) noexcept {
|
|
3751
|
+
return std::make_unique<NodeApiJsiRuntime>(
|
|
3752
|
+
env, jsrApi, std::move(onDelete), globalScope);
|
|
3742
3753
|
}
|
|
3743
3754
|
|
|
3744
3755
|
} // namespace Microsoft::NodeApiJsi
|
|
@@ -15,7 +15,8 @@ namespace Microsoft::NodeApiJsi {
|
|
|
15
15
|
std::unique_ptr<facebook::jsi::Runtime> makeNodeApiJsiRuntime(
|
|
16
16
|
napi_env env,
|
|
17
17
|
JSRuntimeApi *jsrApi,
|
|
18
|
-
std::function<void()> onDelete
|
|
18
|
+
std::function<void()> onDelete,
|
|
19
|
+
napi_value globalScope = nullptr) noexcept;
|
|
19
20
|
|
|
20
21
|
struct NodeApiEnvScope {
|
|
21
22
|
NodeApiEnvScope(napi_env env) : env_(env) {
|