@@ -1240,117 +1240,111 @@ static bool IsDocumentsPath(const std::string& path) {
12401240
12411241 // Check if this is a Node.js built-in module (e.g., node:url)
12421242 if (IsNodeBuiltinModule (spec)) {
1243- // Strip the "node:" prefix and try to resolve as a regular module
1243+ // Strip the "node:" prefix and create an in-memory polyfill module.
12441244 std::string builtinName = spec.substr (5 ); // Remove "node:" prefix
1245- std::string builtinPath = NormalizePath (RuntimeConfig.ApplicationPath + " /" + builtinName + " .mjs" );
1246-
1247- // Check if a polyfill file exists
1248- if (!isFile (builtinPath)) {
1249- // Create a basic polyfill for the built-in module
1250- std::string polyfillContent;
1251-
1252- if (builtinName == " url" ) {
1253- // Create a polyfill for node:url with fileURLToPath
1254- polyfillContent = " // Polyfill for node:url\n "
1255- " export function fileURLToPath(url) {\n "
1256- " if (typeof url === 'string') {\n "
1257- " if (url.startsWith('file://')) {\n "
1258- " return decodeURIComponent(url.slice(7));\n "
1259- " }\n "
1260- " return url;\n "
1261- " }\n "
1262- " if (url && typeof url.href === 'string') {\n "
1263- " return fileURLToPath(url.href);\n "
1264- " }\n "
1265- " throw new Error('Invalid URL');\n "
1266- " }\n "
1267- " \n "
1268- " export function pathToFileURL(path) {\n "
1269- " return new URL('file://' + encodeURIComponent(path));\n "
1270- " }\n " ;
1271- } else {
1272- // Generic polyfill for other Node.js built-in modules
1273- polyfillContent = " // Polyfill for node:" + builtinName +
1274- " \n "
1275- " console.warn('Node.js built-in module \\ 'node:" +
1276- builtinName +
1277- " \\ ' is not fully supported in NativeScript');\n "
1278- " export default {};\n " ;
1279- }
1280-
1281- // Write polyfill file
1282- NSString * polyfillPathStr = [NSString stringWithUTF8String: builtinPath.c_str ()];
1283- NSString * polyfillContentStr = [NSString stringWithUTF8String: polyfillContent.c_str ()];
1284-
1285- if ([polyfillContentStr writeToFile: polyfillPathStr
1286- atomically: YES
1287- encoding: NSUTF8StringEncoding
1288- error: nil ]) {
1289- // File created successfully, now resolve it normally
1290- absPath = builtinPath;
1291- } else {
1292- // Failed to create file, fall back to throwing error
1293- std::string msg = " Cannot find module " + spec + " (tried " + absPath + " )" ;
1294- if (RuntimeConfig.IsDebug ) {
1295- Log (@" Debug mode - Node.js polyfill creation failed: %s " , msg.c_str ());
1296- // Return empty instead of crashing in debug mode
1297- return v8::MaybeLocal<v8::Module>();
1298- } else {
1299- isolate->ThrowException (v8::Exception::Error (tns::ToV8String (isolate, msg)));
1300- return v8::MaybeLocal<v8::Module>();
1301- }
1245+
1246+ // Use a virtual key for registry
1247+ std::string key = std::string (" node:" ) + builtinName;
1248+
1249+ auto itExisting = g_moduleRegistry.find (key);
1250+ if (itExisting != g_moduleRegistry.end ()) {
1251+ v8::Local<v8::Module> existing = itExisting->second .Get (isolate);
1252+ if (!existing.IsEmpty () && existing->GetStatus () != v8::Module::kErrored ) {
1253+ return v8::MaybeLocal<v8::Module>(existing);
1254+ }
1255+ RemoveModuleFromRegistry (key);
1256+ }
1257+
1258+ std::string polyfillContent;
1259+ if (builtinName == " url" ) {
1260+ // Polyfill for node:url with fileURLToPath/pathToFileURL
1261+ polyfillContent =
1262+ " // In-memory polyfill for node:url\n "
1263+ " export function fileURLToPath(url) {\n "
1264+ " if (typeof url === 'string') {\n "
1265+ " if (url.startsWith('file://')) {\n "
1266+ " return decodeURIComponent(url.slice(7));\n "
1267+ " }\n "
1268+ " return url;\n "
1269+ " }\n "
1270+ " if (url && typeof url.href === 'string') {\n "
1271+ " return fileURLToPath(url.href);\n "
1272+ " }\n "
1273+ " throw new Error('Invalid URL');\n "
1274+ " }\n "
1275+ " \n "
1276+ " export function pathToFileURL(path) {\n "
1277+ " const encoded = encodeURIComponent(path).replace(/%2F/g, '/');\n "
1278+ " return new URL('file://' + encoded);\n "
1279+ " }\n " ;
1280+ } else {
1281+ // Generic polyfill for other Node.js built-in modules
1282+ polyfillContent =
1283+ " // In-memory polyfill for node:" + builtinName + " \n " +
1284+ " console.warn('Node.js built-in module \\ 'node:" + builtinName +
1285+ " \\ ' is not fully supported in NativeScript');\n " +
1286+ " export default {};\n " ;
1287+ }
1288+
1289+ v8::MaybeLocal<v8::Module> m =
1290+ CompileModuleForResolveRegisterOnly (isolate, context, polyfillContent, key);
1291+ if (!m.IsEmpty ()) {
1292+ v8::Local<v8::Module> mod;
1293+ if (m.ToLocal (&mod)) {
1294+ return m;
13021295 }
1296+ }
1297+
1298+ std::string msg = " Cannot find module " + spec + " (failed to create in-memory polyfill)" ;
1299+ if (RuntimeConfig.IsDebug ) {
1300+ Log (@" Debug mode - Node.js polyfill creation failed: %s " , msg.c_str ());
1301+ return v8::MaybeLocal<v8::Module>();
13031302 } else {
1304- // Polyfill file already exists, use it
1305- absPath = builtinPath ;
1303+ isolate-> ThrowException ( v8::Exception::Error ( tns::ToV8String (isolate, msg)));
1304+ return v8::MaybeLocal<v8::Module>() ;
13061305 }
13071306 } else if (IsLikelyOptionalModule (spec)) {
1308- // Treat bare specifiers as optional modules by creating a placeholder ES module that
1309- // throws on property access. This lets applications guard optional imports at runtime
1310- // without crashing during startup, especially in development.
1311- std::string appPath = RuntimeConfig.ApplicationPath ;
1312- std::string placeholderPath = NormalizePath (appPath + " /" + spec + " .mjs" );
1313-
1314- // Check if placeholder file already exists
1315- if (!isFile (placeholderPath)) {
1316- // Create placeholder content
1317- std::string placeholderContent = " const error = new Error('Module \\\' " + spec +
1318- " \\\' is not available. This is an optional module.');\n "
1319- " const proxy = new Proxy({}, {\n "
1320- " get: function(target, prop) { throw error; },\n "
1321- " set: function(target, prop, value) { throw error; },\n "
1322- " has: function(target, prop) { return false; },\n "
1323- " ownKeys: function(target) { return []; },\n "
1324- " getPrototypeOf: function(target) { return null; }\n "
1325- " });\n "
1326- " export default proxy;\n " ;
1327-
1328- // Write placeholder file
1329- NSString * placeholderPathStr = [NSString stringWithUTF8String: placeholderPath.c_str ()];
1330- NSString * placeholderContentStr =
1331- [NSString stringWithUTF8String: placeholderContent.c_str ()];
1332-
1333- if ([placeholderContentStr writeToFile: placeholderPathStr
1334- atomically: YES
1335- encoding: NSUTF8StringEncoding
1336- error: nil ]) {
1337- // File created successfully, now resolve it normally
1338- absPath = placeholderPath;
1339- } else {
1340- // Failed to create file. In debug, avoid throwing to keep dev sessions alive; in release
1341- // throw to surface the missing optional module.
1342- std::string msg = " Cannot find module " + spec + " (tried " + absPath + " )" ;
1343- if (RuntimeConfig.IsDebug ) {
1344- Log (@" Debug mode - Optional module placeholder creation failed: %s " , msg.c_str ());
1345- return v8::MaybeLocal<v8::Module>();
1346- } else {
1347- isolate->ThrowException (v8::Exception::Error (tns::ToV8String (isolate, msg)));
1348- return v8::MaybeLocal<v8::Module>();
1349- }
1307+ // Treat bare specifiers as optional modules with an in-memory placeholder ES module
1308+ // that throws on property access. This avoids bundle writes in iOS release builds.
1309+
1310+ std::string key = std::string (" optional:" ) + spec;
1311+ auto itExisting = g_moduleRegistry.find (key);
1312+ if (itExisting != g_moduleRegistry.end ()) {
1313+ v8::Local<v8::Module> existing = itExisting->second .Get (isolate);
1314+ if (!existing.IsEmpty () && existing->GetStatus () != v8::Module::kErrored ) {
1315+ return v8::MaybeLocal<v8::Module>(existing);
1316+ }
1317+ RemoveModuleFromRegistry (key);
1318+ }
1319+
1320+ std::string placeholderContent =
1321+ " const error = new Error(\" Module '" + spec +
1322+ " ' is not available. This is an optional module.\" );\n "
1323+ " const proxy = new Proxy({}, {\n "
1324+ " get: function(target, prop) { throw error; },\n "
1325+ " set: function(target, prop, value) { throw error; },\n "
1326+ " has: function(target, prop) { return false; },\n "
1327+ " ownKeys: function(target) { return []; },\n "
1328+ " getPrototypeOf: function(target) { return null; }\n "
1329+ " });\n "
1330+ " export default proxy;\n " ;
1331+
1332+ v8::MaybeLocal<v8::Module> m =
1333+ CompileModuleForResolveRegisterOnly (isolate, context, placeholderContent, key);
1334+ if (!m.IsEmpty ()) {
1335+ v8::Local<v8::Module> mod;
1336+ if (m.ToLocal (&mod)) {
1337+ return m;
13501338 }
1339+ }
1340+
1341+ std::string msg = " Cannot find module " + spec + " (failed to create in-memory optional placeholder)" ;
1342+ if (RuntimeConfig.IsDebug ) {
1343+ Log (@" Debug mode - Optional module placeholder creation failed: %s " , msg.c_str ());
1344+ return v8::MaybeLocal<v8::Module>();
13511345 } else {
1352- // Placeholder file already exists, use it
1353- absPath = placeholderPath ;
1346+ isolate-> ThrowException ( v8::Exception::Error ( tns::ToV8String (isolate, msg)));
1347+ return v8::MaybeLocal<v8::Module>() ;
13541348 }
13551349 } else {
13561350 // Not an optional module, throw the original error
0 commit comments