@@ -27,6 +27,26 @@ static bool IsScriptLoadingLogEnabled() {
27
27
return value ? [value boolValue ] : false ;
28
28
}
29
29
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
+
30
50
// ────────────────────────────────────────────────────────────────────────────
31
51
// Simple in-process registry: maps absolute file paths → compiled Module handles
32
52
std::unordered_map<std::string, v8::Global<v8::Module>> g_moduleRegistry;
@@ -196,9 +216,112 @@ static bool IsScriptLoadingLogEnabled() {
196
216
// If we still didn’t resolve to an actual file, surface an exception instead
197
217
// of letting ReadModule() assert while trying to open a directory.
198
218
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
+ }
202
325
}
203
326
204
327
// Special handling for JSON imports (e.g. import data from './foo.json' assert {type:'json'})
0 commit comments