ucode icon indicating copy to clipboard operation
ucode copied to clipboard

Runtime module loading support

Open jow- opened this issue 5 months ago • 6 comments

This update introduces runtime module import support and extends the bytecode format to handle exported symbols from precompiled modules.

  • Runtime Module Imports: The compiler now supports a function-like import() statement for dynamically loading modules during execution. Unlike require(), imported code follows module semantics (strict mode, no top-level returns, and allowed exports). A new -cmodule compile flag enables precompiling module files into bytecode.

  • Export Symbol Encoding: The bytecode format now includes an export symbol section, allowing the runtime to reconstruct namespace objects when loading precompiled modules.

Together, these changes lay the groundwork for dynamic module loading in ucode.

jow- avatar Oct 20 '25 11:10 jow-

Am I missing something? import() seems to return an empty object, while I would have expected the export. a.uc:

function X()
{
    print( 'in X()\n' );
}
export { X };

b.uc:

const a = import( 'a' );
print( a );
a.X();
$ ucode b.uc
{ }
Type error: left-hand side is not a function
In b.uc, line 3, byte 5:

 `a.X();`
      ^-- Near here

IdWV avatar Oct 20 '25 14:10 IdWV

That should work, will check

jow- avatar Oct 20 '25 15:10 jow-

Should work now

jow- avatar Oct 22 '25 17:10 jow-

OK, I confirm it works now. And I can also import() a compiled module, and use it. But is it by design that 'import from' doesn't work in a compiled module? ucode complains about the she-bang. (Should it /have/ a she-bang when compiled as module?)

IdWV avatar Oct 24 '25 11:10 IdWV

See the latest commit in this branch, it will address this issue by implicitly compiling static imports of precompiled modules into dynamic runtime imports (no code changes required on your end).

jow- avatar Nov 07 '25 16:11 jow-

OK, thanks. After some tests I come to this truth table:

import from import() require()
dynamic library Works Works Works
script Works Works Only works with 'return { exports };', in which case 'export' is forbidden.
compiled script Compile error on 'export' Compile error on 'export' Compile error on 'export', but 'return { exports };' works
'module compiled' script Works Works Works, but compile error on 'return { exports };'. So only the module script body is executed

Futher, to my surprise I can have more 'export {};' blocks in a script, which get merged. Nice.
I can't execute a script containing an 'export {};', it can't be compiled either. But if I 'module compile' it, it can be both imported, and executed. In the latter case, is there a way to detect that, and have a way to be both a utility and a library, comparable to nodejs

if (require.main === module) {
  // do something
}

?

IdWV avatar Nov 09 '25 13:11 IdWV

@jow- Is there anything which is keeping you from merging this?

IdWV avatar Jan 09 '26 14:01 IdWV

I was somewhat held up by your request for a way to check whether code is running as module or standalone and I couldn't come up with some simple or clean API.

I am going to merge this for now.

jow- avatar Jan 16 '26 23:01 jow-

Ah, thanks. It was more a question than a request. Meanwhile I found an answer:

function somefunction( arg )
{
    print( arg, '\n' );
}

export { somefunction };

function main()
{
    somefunction( 'locally called'  );
}

if( 'module,uc' == pop( split( global.SCRIPT_NAME, '/' ) ) )
{
    main();
}

The only (?) downside is that you have to know the name of the module inside the module.

IdWV avatar Jan 17 '26 08:01 IdWV

You could also do something like:

function runsAsModule() {
  try {
    die();
  }
  catch(e) {
    return e.stacktrace[1].function === "module";
  }

  return false;
}

jow- avatar Jan 17 '26 12:01 jow-