@@ -27,6 +27,26 @@ static bool IsScriptLoadingLogEnabled() {
2727 return value ? [value boolValue ] : false ;
2828}
2929
30+ // Helper function to check if a module name looks like an optional external module
31+ static bool IsLikelyOptionalModule (const std::string& moduleName) {
32+ // Skip Node.js built-in modules (they should be handled separately)
33+ if (moduleName.rfind (" node:" , 0 ) == 0 ) {
34+ return false ;
35+ }
36+
37+ // Check if it's a bare module name (no path separators) that could be an npm package
38+ if (moduleName.find (' /' ) == std::string::npos && moduleName.find (' \\ ' ) == std::string::npos &&
39+ moduleName[0 ] != ' .' && moduleName[0 ] != ' ~' && moduleName[0 ] != ' /' ) {
40+ return true ;
41+ }
42+ return false ;
43+ }
44+
45+ // Helper function to check if a module name is a Node.js built-in module
46+ static bool IsNodeBuiltinModule (const std::string& moduleName) {
47+ return moduleName.rfind (" node:" , 0 ) == 0 ;
48+ }
49+
3050// ────────────────────────────────────────────────────────────────────────────
3151// Simple in-process registry: maps absolute file paths → compiled Module handles
3252std::unordered_map<std::string, v8::Global<v8::Module>> g_moduleRegistry;
@@ -196,9 +216,112 @@ static bool IsScriptLoadingLogEnabled() {
196216 // If we still didn’t resolve to an actual file, surface an exception instead
197217 // of letting ReadModule() assert while trying to open a directory.
198218 if (!isFile (absPath)) {
199- std::string msg = " Cannot find module " + spec + " (tried " + absPath + " )" ;
200- isolate->ThrowException (v8::Exception::Error (tns::ToV8String (isolate, msg)));
201- return v8::MaybeLocal<v8::Module>();
219+ // Check if this is a Node.js built-in module (e.g., node:url)
220+ if (IsNodeBuiltinModule (spec)) {
221+ // Strip the "node:" prefix and try to resolve as a regular module
222+ std::string builtinName = spec.substr (5 ); // Remove "node:" prefix
223+ std::string builtinPath = RuntimeConfig.ApplicationPath + " /" + builtinName + " .mjs" ;
224+
225+ // Check if a polyfill file exists
226+ if (!isFile (builtinPath)) {
227+ // Create a basic polyfill for the built-in module
228+ std::string polyfillContent;
229+
230+ if (builtinName == " url" ) {
231+ // Create a polyfill for node:url with fileURLToPath
232+ polyfillContent = " // Polyfill for node:url\n "
233+ " export function fileURLToPath(url) {\n "
234+ " if (typeof url === 'string') {\n "
235+ " if (url.startsWith('file://')) {\n "
236+ " return decodeURIComponent(url.slice(7));\n "
237+ " }\n "
238+ " return url;\n "
239+ " }\n "
240+ " if (url && typeof url.href === 'string') {\n "
241+ " return fileURLToPath(url.href);\n "
242+ " }\n "
243+ " throw new Error('Invalid URL');\n "
244+ " }\n "
245+ " \n "
246+ " export function pathToFileURL(path) {\n "
247+ " return new URL('file://' + encodeURIComponent(path));\n "
248+ " }\n " ;
249+ } else {
250+ // Generic polyfill for other Node.js built-in modules
251+ polyfillContent = " // Polyfill for node:" + builtinName +
252+ " \n "
253+ " console.warn('Node.js built-in module \\ 'node:" +
254+ builtinName +
255+ " \\ ' is not fully supported in NativeScript');\n "
256+ " export default {};\n " ;
257+ }
258+
259+ // Write polyfill file
260+ NSString * polyfillPathStr = [NSString stringWithUTF8String: builtinPath.c_str ()];
261+ NSString * polyfillContentStr = [NSString stringWithUTF8String: polyfillContent.c_str ()];
262+
263+ if ([polyfillContentStr writeToFile: polyfillPathStr
264+ atomically: YES
265+ encoding: NSUTF8StringEncoding
266+ error: nil ]) {
267+ // File created successfully, now resolve it normally
268+ absPath = builtinPath;
269+ } else {
270+ // Failed to create file, fall back to throwing error
271+ std::string msg = " Cannot find module " + spec + " (tried " + absPath + " )" ;
272+ isolate->ThrowException (v8::Exception::Error (tns::ToV8String (isolate, msg)));
273+ return v8::MaybeLocal<v8::Module>();
274+ }
275+ } else {
276+ // Polyfill file already exists, use it
277+ absPath = builtinPath;
278+ }
279+ } else if (IsLikelyOptionalModule (spec)) {
280+ // Create a placeholder file on disk that webpack can resolve to
281+ std::string appPath = RuntimeConfig.ApplicationPath ;
282+ std::string placeholderPath = appPath + " /" + spec + " .mjs" ;
283+
284+ // Check if placeholder file already exists
285+ if (!isFile (placeholderPath)) {
286+ // Create placeholder content
287+ std::string placeholderContent = " const error = new Error('Module \\ '" + spec +
288+ " \\ ' is not available. This is an optional module.');\n "
289+ " const proxy = new Proxy({}, {\n "
290+ " get: function(target, prop) { throw error; },\n "
291+ " set: function(target, prop, value) { throw error; },\n "
292+ " has: function(target, prop) { return false; },\n "
293+ " ownKeys: function(target) { return []; },\n "
294+ " getPrototypeOf: function(target) { return null; }\n "
295+ " });\n "
296+ " export default proxy;\n " ;
297+
298+ // Write placeholder file
299+ NSString * placeholderPathStr = [NSString stringWithUTF8String: placeholderPath.c_str ()];
300+ NSString * placeholderContentStr =
301+ [NSString stringWithUTF8String: placeholderContent.c_str ()];
302+
303+ if ([placeholderContentStr writeToFile: placeholderPathStr
304+ atomically: YES
305+ encoding: NSUTF8StringEncoding
306+ error: nil ]) {
307+ // File created successfully, now resolve it normally
308+ absPath = placeholderPath;
309+ } else {
310+ // Failed to create file, fall back to throwing error
311+ std::string msg = " Cannot find module " + spec + " (tried " + absPath + " )" ;
312+ isolate->ThrowException (v8::Exception::Error (tns::ToV8String (isolate, msg)));
313+ return v8::MaybeLocal<v8::Module>();
314+ }
315+ } else {
316+ // Placeholder file already exists, use it
317+ absPath = placeholderPath;
318+ }
319+ } else {
320+ // Not an optional module, throw the original error
321+ std::string msg = " Cannot find module " + spec + " (tried " + absPath + " )" ;
322+ isolate->ThrowException (v8::Exception::Error (tns::ToV8String (isolate, msg)));
323+ return v8::MaybeLocal<v8::Module>();
324+ }
202325 }
203326
204327 // Special handling for JSON imports (e.g. import data from './foo.json' assert {type:'json'})
0 commit comments