Skip to content

Commit 1742e42

Browse files
committed
Refactor module handling and add path module support
- Removed unnecessary release call for signature in Cif.mm. - Added napi_env member to ObjCBridgeState and initialized it in the constructor. - Implemented cleanup logic for napi references in ObjCBridgeState destructor. - Enhanced v8-module-loader to support package.json "type" field lookups and shebang stripping. - Updated module resolution logic to handle .cjs and .mjs extensions for CommonJS and ESM respectively. - Introduced Path module with methods for path manipulation (basename, dirname, extname, etc.). - Integrated Path module into Node module for seamless usage.
1 parent 01c15a7 commit 1742e42

File tree

8 files changed

+1053
-14
lines changed

8 files changed

+1053
-14
lines changed

NativeScript/ffi/Cif.mm

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,6 @@
107107
}
108108
}
109109

110-
[signature release];
111-
112110
ffi_status status = ffi_prep_cif(&cif, FFI_DEFAULT_ABI, totalArgc, rtype, this->atypes);
113111

114112
if (status != FFI_OK) {

NativeScript/ffi/ObjCBridge.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ class ObjCBridgeState {
197197
}
198198

199199
public:
200+
napi_env env = nullptr;
200201
uint64_t lifetimeToken = 0;
201202
std::unordered_map<id, napi_ref> objectRefs;
202203

NativeScript/ffi/ObjCBridge.mm

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,7 @@ void registerLegacyCompatGlobals(napi_env env, napi_value global, ObjCBridgeStat
496496

497497
ObjCBridgeState::ObjCBridgeState(napi_env env, const char* metadata_path,
498498
const void* metadata_ptr) {
499+
this->env = env;
499500
napi_set_instance_data(env, this, finalize_bridge_data, nil);
500501
lifetimeToken = RegisterBridgeState(this);
501502

@@ -536,6 +537,57 @@ void registerLegacyCompatGlobals(napi_env env, napi_value global, ObjCBridgeStat
536537
ObjCBridgeState::~ObjCBridgeState() {
537538
UnregisterBridgeState(this);
538539

540+
auto deleteRef = [&](napi_ref& ref) {
541+
if (env != nullptr && ref != nullptr) {
542+
napi_delete_reference(env, ref);
543+
ref = nullptr;
544+
}
545+
};
546+
547+
for (auto& pair : constructorsByPointer) {
548+
deleteRef(pair.second);
549+
}
550+
constructorsByPointer.clear();
551+
552+
for (auto& frame : roundTripCacheFrames) {
553+
for (auto& entry : frame) {
554+
deleteRef(entry.second);
555+
}
556+
}
557+
roundTripCacheFrames.clear();
558+
559+
for (auto& entry : recentRoundTripCache) {
560+
deleteRef(entry.second);
561+
}
562+
recentRoundTripCache.clear();
563+
564+
std::unordered_set<napi_ref> classAndProtocolConstructorRefs;
565+
classAndProtocolConstructorRefs.reserve(classes.size() + protocols.size());
566+
for (const auto& pair : classes) {
567+
if (pair.second != nullptr && pair.second->constructor != nullptr) {
568+
classAndProtocolConstructorRefs.insert(pair.second->constructor);
569+
}
570+
}
571+
for (const auto& pair : protocols) {
572+
if (pair.second != nullptr && pair.second->constructor != nullptr) {
573+
classAndProtocolConstructorRefs.insert(pair.second->constructor);
574+
}
575+
}
576+
for (auto& pair : mdValueCache) {
577+
napi_ref& ref = pair.second;
578+
if (ref != nullptr && classAndProtocolConstructorRefs.find(ref) == classAndProtocolConstructorRefs.end()) {
579+
deleteRef(ref);
580+
}
581+
}
582+
mdValueCache.clear();
583+
584+
deleteRef(pointerClass);
585+
deleteRef(referenceClass);
586+
deleteRef(functionReferenceClass);
587+
deleteRef(createNativeProxy);
588+
deleteRef(createFastEnumeratorIterator);
589+
deleteRef(transferOwnershipToNative);
590+
539591
// Clean up cached Cif objects
540592
for (auto& pair : cifs) {
541593
delete pair.second;

NativeScript/napi/v8/v8-module-loader.cpp

Lines changed: 160 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,104 @@
77
#include <filesystem>
88
#include <fstream>
99
#include <sstream>
10+
#include <unordered_map>
1011
#include <unordered_set>
1112

1213
#include "runtime/RuntimeConfig.h"
1314

1415
namespace v8impl {
1516

17+
namespace {
18+
19+
// Cache for package.json "type" field lookups
20+
std::unordered_map<std::string, bool> g_packageTypeCache;
21+
22+
// Strip shebang line from source code (e.g., #!/usr/bin/env node)
23+
std::string StripShebang(const std::string& source) {
24+
if (source.size() >= 2 && source[0] == '#' && source[1] == '!') {
25+
size_t lineEnd = source.find('\n');
26+
if (lineEnd != std::string::npos) {
27+
return source.substr(lineEnd + 1);
28+
}
29+
return ""; // Entire file is just a shebang
30+
}
31+
return source;
32+
}
33+
34+
// Check if path has .cjs extension (explicitly CommonJS)
35+
bool IsCJSModule(const std::string& path) {
36+
return path.size() >= 4 && path.compare(path.size() - 4, 4, ".cjs") == 0;
37+
}
38+
39+
// Find nearest package.json by walking up from directory
40+
std::string FindPackageJson(const std::filesystem::path& startDir) {
41+
std::filesystem::path current = startDir;
42+
43+
while (!current.empty() && current != current.root_path()) {
44+
std::filesystem::path packagePath = current / "package.json";
45+
std::error_code ec;
46+
if (std::filesystem::exists(packagePath, ec) && !ec) {
47+
return packagePath.string();
48+
}
49+
current = current.parent_path();
50+
}
51+
52+
return "";
53+
}
54+
55+
// Check if package.json has "type": "module"
56+
bool IsPackageTypeModule(const std::string& packageJsonPath) {
57+
auto cacheIt = g_packageTypeCache.find(packageJsonPath);
58+
if (cacheIt != g_packageTypeCache.end()) {
59+
return cacheIt->second;
60+
}
61+
62+
bool isModule = false;
63+
64+
std::ifstream file(packageJsonPath);
65+
if (file.is_open()) {
66+
std::string content((std::istreambuf_iterator<char>(file)),
67+
std::istreambuf_iterator<char>());
68+
file.close();
69+
70+
// Simple JSON parsing for "type": "module"
71+
// Look for "type" followed by : and "module"
72+
size_t typePos = content.find("\"type\"");
73+
if (typePos != std::string::npos) {
74+
size_t colonPos = content.find(':', typePos + 6);
75+
if (colonPos != std::string::npos) {
76+
size_t valueStart = content.find('"', colonPos + 1);
77+
if (valueStart != std::string::npos) {
78+
size_t valueEnd = content.find('"', valueStart + 1);
79+
if (valueEnd != std::string::npos) {
80+
std::string typeValue =
81+
content.substr(valueStart + 1, valueEnd - valueStart - 1);
82+
isModule = (typeValue == "module");
83+
}
84+
}
85+
}
86+
}
87+
}
88+
89+
g_packageTypeCache[packageJsonPath] = isModule;
90+
return isModule;
91+
}
92+
93+
// Determine if a .js file should be treated as ESM based on nearest
94+
// package.json
95+
bool ShouldTreatAsESModule(const std::string& path) {
96+
std::filesystem::path filePath(path);
97+
std::string packageJson = FindPackageJson(filePath.parent_path());
98+
99+
if (!packageJson.empty()) {
100+
return IsPackageTypeModule(packageJson);
101+
}
102+
103+
return false; // Default to CommonJS
104+
}
105+
106+
} // namespace
107+
16108
// Global registry for ES modules
17109
std::unordered_map<std::string, v8::Global<v8::Module>> g_moduleRegistry;
18110

@@ -59,9 +151,8 @@ std::string ModulePathToURL(const std::string& modulePath) {
59151

60152
bool IsNodeBuiltinSpecifier(const std::string& specifier) {
61153
static const std::unordered_set<std::string> kBuiltins = {
62-
"url", "node:url", "fs", "node:fs",
63-
"fs/promises", "node:fs/promises", "web", "node:web",
64-
"stream/web", "node:stream/web"};
154+
"url", "node:url", "fs", "node:fs", "fs/promises", "node:fs/promises",
155+
"path", "node:path", "web", "node:web", "stream/web", "node:stream/web"};
65156
return kBuiltins.contains(specifier);
66157
}
67158

@@ -149,6 +240,38 @@ export default __fsp;
149240
)";
150241
}
151242

243+
if (builtinName == "path") {
244+
return R"(
245+
const __load = (name) => {
246+
if (typeof globalThis.require === "function") {
247+
return globalThis.require(name);
248+
}
249+
if (typeof globalThis.__nativeRequire === "function") {
250+
const dir = typeof globalThis.__approot === "string" ? `${globalThis.__approot}/app` : "";
251+
return globalThis.__nativeRequire(name, dir);
252+
}
253+
throw new Error(`Cannot load builtin module '${name}'`);
254+
};
255+
const __path = __load("node:path");
256+
export const basename = __path.basename;
257+
export const dirname = __path.dirname;
258+
export const extname = __path.extname;
259+
export const isAbsolute = __path.isAbsolute;
260+
export const join = __path.join;
261+
export const normalize = __path.normalize;
262+
export const parse = __path.parse;
263+
export const format = __path.format;
264+
export const relative = __path.relative;
265+
export const resolve = __path.resolve;
266+
export const toNamespacedPath = __path.toNamespacedPath;
267+
export const sep = __path.sep;
268+
export const delimiter = __path.delimiter;
269+
export const posix = __path.posix;
270+
export const win32 = __path.win32;
271+
export default __path;
272+
)";
273+
}
274+
152275
if (builtinName == "web") {
153276
return R"(
154277
const __load = (name) => {
@@ -201,7 +324,22 @@ export default __streamWeb;
201324
}
202325

203326
bool IsESModule(const std::string& path) {
204-
return path.size() >= 4 && path.compare(path.size() - 4, 4, ".mjs") == 0;
327+
// .mjs is always ESM
328+
if (path.size() >= 4 && path.compare(path.size() - 4, 4, ".mjs") == 0) {
329+
return true;
330+
}
331+
332+
// .cjs is always CommonJS
333+
if (IsCJSModule(path)) {
334+
return false;
335+
}
336+
337+
// .js files: check package.json "type" field
338+
if (path.size() >= 3 && path.compare(path.size() - 3, 3, ".js") == 0) {
339+
return ShouldTreatAsESModule(path);
340+
}
341+
342+
return false;
205343
}
206344

207345
bool IsJSONModule(const std::string& path) {
@@ -216,7 +354,7 @@ std::string ReadFileContent(const std::string& path) {
216354

217355
std::stringstream buffer;
218356
buffer << file.rdbuf();
219-
return buffer.str();
357+
return StripShebang(buffer.str());
220358
}
221359

222360
v8::Local<v8::String> WrapModuleContent(v8::Isolate* isolate,
@@ -278,6 +416,13 @@ std::string ResolveESModulePath(v8::Isolate* isolate,
278416
return NormalizeModulePath(jsPath);
279417
}
280418

419+
// Try with .cjs extension (explicit CommonJS)
420+
std::filesystem::path cjsPath = fullPath.string() + ".cjs";
421+
if (std::filesystem::exists(cjsPath) &&
422+
std::filesystem::is_regular_file(cjsPath)) {
423+
return NormalizeModulePath(cjsPath);
424+
}
425+
281426
// Try directory with index.mjs
282427
if (std::filesystem::exists(fullPath) &&
283428
std::filesystem::is_directory(fullPath)) {
@@ -293,6 +438,13 @@ std::string ResolveESModulePath(v8::Isolate* isolate,
293438
std::filesystem::is_regular_file(indexJs)) {
294439
return NormalizeModulePath(indexJs);
295440
}
441+
442+
// Try directory with index.cjs
443+
std::filesystem::path indexCjs = fullPath / "index.cjs";
444+
if (std::filesystem::exists(indexCjs) &&
445+
std::filesystem::is_regular_file(indexCjs)) {
446+
return NormalizeModulePath(indexCjs);
447+
}
296448
}
297449

298450
throw std::runtime_error("Module not found: " + moduleName);
@@ -644,6 +796,9 @@ void CleanupESModuleSystem(v8::Isolate* isolate) {
644796

645797
// Clear the registry
646798
g_moduleRegistry.clear();
799+
800+
// Clear the package.json type cache
801+
g_packageTypeCache.clear();
647802
}
648803

649804
} // namespace v8impl

0 commit comments

Comments
 (0)