Skip to content

Dynamic import #872

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft

Dynamic import #872

wants to merge 5 commits into from

Conversation

mookums
Copy link
Contributor

@mookums mookums commented Jul 9, 2025

This adds support for dynamic imports of modules (eg. await import("xyz")). This is in draft at the moment as it is dependent on lightpanda-io/zig-v8-fork#83 as it requires the isPromise().

return @constCast(resolver.getPromise().handle);
};

const referrer_full_url = blk: {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary? I would have thought that you can use resource_str directly as the key to context.module_identifier.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sometimes using the resource works but other times like below, it doesn't.

INFO  js : dynamic import . . . . . . . . . . . . . . . . . . [+0ms]
      specifier = ./shreddit-overflow-menu-31f50e45.js
      resource = ./comment-client-js-4adc8893.js
      referrer_full = https://www.redditstatic.com/shreddit/en-US/comment-client-js-4adc8893.js
      normalized_specifier = ./shreddit-overflow-menu-31f50e45.js

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should look into this. I started to look into this, but the crash on 1620 (and, to a lesser extent, the logging of opaque values), kind of stopped me.

I think the resource value is always supplied by us, and, for correct caching, it should always be the full url. We should find out where we're submitting the non-full script name ./comment-client-js-4adc8893.js and fix it (maybe there's a combination of of base + url that stitch doesn't handle properly).

(It's possible the name is somehow derived by v8, and we have no choice, but that seems unlikely, and I rather make sure that, because it smells like a bug to me..and fixing it wouldn't just fix potentially weird caching issue, it would let you remove this block of code).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After fixing the crash, when I looked at this for reddit, the resource was always a full url, including for comment-client-js-cc940b94.js

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I never really crashed running this but I switched from call_arena tocontext_arena and now the resource_str works so I assume that's also how you fixed the crash?

return @constCast(resolver.getPromise().handle);
}

fn _dynamicModuleCallback(context: *JsContext, resource: []const u8, specifier: []const u8, resolver: *const v8.PromiseResolver) void {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's worth exploring whether dynamicModuleCallback can leverage the existing module function. module doesn't have its own try catch, so I think the rejection parts shouldn't be an issue:

var try_catch: TryCatch = undefined;
try_catch.init(context);
defer try_catch.deinit();

// change module to return the module so that you can evulate it, I gues
const m = self.module(....) catch |err| {
  var error_message: ?[]const u8 = null;
  if (try_catch.hasCaught()) {
      // ...
  } else switch (err) {
     // if you want to handle specific errors from module(...)
  }
}

Not having to duplicate the specifier normalization, cache management and module compilation/evaluation seems like a win.

Copy link
Contributor Author

@mookums mookums Jul 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ended up changing module() so it now returns !?v8.Promise where it returns null when the module is already in the cache and returns the evaluation result, which should always be a v8.Promise.

I ended up removing moduleNoCache() and adding the functionality to the normal module() because it felt like a lot was being duplicated across the two.

@mookums mookums force-pushed the dynamic-import branch 3 times, most recently from f521621 to 7f407f2 Compare July 10, 2025 19:34
_ = resolver.reject(ctx, error_msg.toValue());
return;
};
const new_module = context.module_cache.get(specifier).?.castToModule();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see how it's possible, but when I tried this branch with reddit, context.module_cache.get(specifier) returns null (and this code crashed). But I don't see how context.module() called with cacheable: true can return without inserting that entry...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The call_arena isn't safe to use, e.g.

 const specifier_str = jsStringToZig(context.call_arena, specifier, iso) catch {

The exact lifetime of the call_arena is a bit unspecific, but it's guaranteed to exist for at least the point where a WebAPI function is called (e.g. Window._setTimeout) to the point where data is handed to v8.

Loading a module is a lot wider than that and likely includes many WebApi calls. So imagine you have a module:

// awesome.js
console.log("hello");
// <-- should assume that the call_arena is reset at this point

Your specifier (and probably other variables) are created before this code executes, but those allocations will become invaild as soon as console.log returns.

return @constCast(resolver.getPromise().handle);
};

const referrer_full_url = blk: {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should look into this. I started to look into this, but the crash on 1620 (and, to a lesser extent, the logging of opaque values), kind of stopped me.

I think the resource value is always supplied by us, and, for correct caching, it should always be the full url. We should find out where we're submitting the non-full script name ./comment-client-js-4adc8893.js and fix it (maybe there's a combination of of base + url that stitch doesn't handle properly).

(It's possible the name is somehow derived by v8, and we have no choice, but that seems unlikely, and I rather make sure that, because it smells like a bug to me..and fixing it wouldn't just fix potentially weird caching issue, it would let you remove this block of code).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants