Modules
JavaScript modules let you organize code into reusable pieces. With jsrun, you can provide JavaScript modules to your runtime and use them with standard import statements.
Why Use Modules?
Instead of cramming everything into one big eval:
- Organize: Split code into logical pieces
- Reuse: Import the same module from multiple scripts
- Standard: Use ES6
import/exportsyntax JavaScript developers expect
Static Modules
The simplest way is to register modules upfront with add_static_module():
import asyncio
from jsrun import Runtime
async def main():
with Runtime() as runtime:
# Register a module
runtime.add_static_module("math", """
export function add(a, b) {
return a + b;
}
export const PI = 3.14159;
""")
# Use it with dynamic import
result = await runtime.eval_async("""
(async () => {
const { add, PI } = await import('math');
return add(10, 5) * PI;
})()
""")
print(result) # 47.12385
asyncio.run(main())
Dynamic imports with eval
Since eval() and eval_async() don't support top-level await, wrap your import in an async IIFE: (async () => { ... })()
Loading from Files
Load modules from your filesystem:
with Runtime() as runtime:
# Read a .js file
with open("utils.js") as f:
code = f.read()
runtime.add_static_module("utils", code)
Custom Module Resolution
For more control, use set_module_resolver() to intercept import statements:
async def main():
with Runtime() as runtime:
def resolver(specifier: str, referrer: str):
# Handle custom URL schemes
if specifier.startswith("lib:"):
return specifier
# Let other imports fall through
return None
runtime.set_module_resolver(resolver)
The resolver receives:
- specifier: The string in the
importstatement ("math","./utils", etc.) - referrer: The URL of the importing module
Return the resolved module URL, or None to let other resolvers handle it.
Custom Module Loader
Use set_module_loader() to fetch module code dynamically:
async def main():
with Runtime() as runtime:
async def loader(specifier: str):
if specifier.startswith("lib:"):
# Load from database, network, etc.
module_name = specifier[4:] # Remove "lib:"
return f"export const name = '{module_name}';"
raise ValueError(f"Unknown module: {specifier}")
runtime.set_module_loader(loader)
def resolver(specifier: str, referrer: str):
if specifier.startswith("lib:"):
return specifier
return None
runtime.set_module_resolver(resolver)
# Now you can import custom modules
result = await runtime.eval_async("""
(async () => {
const mod = await import('lib:mylib');
return mod.name;
})()
""")
print(result) # "mylib"
asyncio.run(main())
Evaluating Modules Directly
Instead of using import inside eval, you can evaluate a module directly with eval_module_async():
async def main():
with Runtime() as runtime:
runtime.add_static_module("entry", """
export const result = 42;
export function greet(name) {
return `Hello, ${name}!`;
}
""")
# Evaluate the module and get its namespace
namespace = await runtime.eval_module_async("entry")
print(namespace["result"]) # 42
print(namespace["greet"]("World")) # "Hello, World!"
asyncio.run(main())
This is cleaner when you want to evaluate a module once and access its exports from Python.
Practical Examples
Organizing Utilities
import asyncio
from jsrun import Runtime
async def main():
with Runtime() as runtime:
runtime.add_static_module("string-utils", """
export function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
export function reverse(str) {
return str.split('').reverse().join('');
}
""")
runtime.add_static_module("array-utils", """
export function sum(arr) {
return arr.reduce((a, b) => a + b, 0);
}
export function average(arr) {
return sum(arr) / arr.length;
}
""")
result = await runtime.eval_async("""
(async () => {
const { capitalize } = await import('string-utils');
const { average } = await import('array-utils');
return {
title: capitalize('hello world'),
avg: average([1, 2, 3, 4, 5])
};
})()
""")
print(result) # {'title': 'Hello world', 'avg': 3.0}
asyncio.run(main())
Virtual File System
async def main():
with Runtime() as runtime:
# Simulate a virtual filesystem
virtual_fs = {
"config.js": "export const API_URL = 'https://api.example.com';",
"auth.js": "export function isAuthenticated() { return true; }",
}
async def loader(specifier: str):
# Strip file:/// prefix to get original name
name = specifier.replace("file:///", "")
if name in virtual_fs:
return virtual_fs[name]
raise ValueError(f"Module not found: {specifier}")
runtime.set_module_loader(loader)
runtime.set_module_resolver(lambda spec, ref: f"file:///{spec}" if spec in virtual_fs else None)
result = await runtime.eval_async("""
(async () => {
const { API_URL } = await import('config.js');
const { isAuthenticated } = await import('auth.js');
return { url: API_URL, auth: isAuthenticated() };
})()
""")
print(result) # {'url': 'https://api.example.com', 'auth': True}
asyncio.run(main())
Loading Third-Party Libraries
You can fetch JavaScript libraries from CDNs and load them as modules:
import requests
import asyncio
async def main():
with Runtime() as runtime:
# Fetch a library from CDN
response = requests.get("https://cdn.jsdelivr.net/npm/ms@2.1.3/+esm")
runtime.add_static_module("ms", response.text)
result = await runtime.eval_async("""
(async () => {
const ms = await import('ms');
return ms.default('2 days'); // Convert to milliseconds
})()
""")
print(result) # 172800000
asyncio.run(main())
Preloading libraries in snapshots
For better performance, you can pre-load CDN libraries using Snapshots to avoid repeated fetching and initialization across multiple runtimes.
Module Resolution Order
When JavaScript tries to import "something", the resolution happens in this order:
- Custom resolver (if set with
set_module_resolver()) - Static modules (registered with
add_static_module()) - Error if no match found
Limitations
- No file system access: JavaScript can't read files directly. You must provide all code via modules or bindings.
- No Node.js built-ins: Modules like
fs,http,patharen't available unless you polyfill them. - No CommonJS: Only ES6 modules (
import/export) are supported, notrequire().
Next Steps
- Learn about Bindings to expose Python functions JavaScript modules can call
- Explore Snapshots to pre-load modules for faster startup
- Check out the API Reference for complete module methods