Fundamentals 28 min read

Modular Programming and Dynamic Linking in JavaScript and WebAssembly

This article explains the principles of modular programming, examines JavaScript and asm.js module systems, and details how WebAssembly extends these concepts with import/export sections and dynamic linking mechanisms, while also reviewing current proposals and future trends for module linking and component models.

ByteDance Web Infra
ByteDance Web Infra
ByteDance Web Infra
Modular Programming and Dynamic Linking in JavaScript and WebAssembly

Modular programming is a software design pattern that decomposes a program into independent, replaceable modules with well‑defined interfaces, improving design clarity, implementation ease, testing, maintenance, reuse, and low coupling. Languages such as JavaScript, Python, Rust, Java, and even C++20 support module systems.

JavaScript offers several module specifications—CommonJS, AMD, UMD, and ECMAScript modules (ESM). The article focuses on CommonJS, showing how module.exports and require() expose and import module functionality, illustrated by a square.js example that defines a Square class and a calculator.js that loads it.

let wrap = function(script) {
  return Module.wrapper[0] + script + Module.wrapper[1];
};

const wrapper = [
  '(function (exports, require, module, __filename, __dirname) { ',
  '\n});',
];

Node.js wraps each module in an anonymous function to create a private scope, exposing exports , module , and require for linking. The loader invokes Module.prototype.require , which calls Module._load to handle caching and actual loading.

Module.prototype.require = function(id) {
  requireDepth++;
  try {
    return Module._load(id, this, false);
  } finally {
    requireDepth--;
  }
};

The loading process checks Module._cache , creates a new Module instance if needed, and then calls module.load(filename) , which selects the appropriate extension handler (e.g., .js ) to compile and execute the module code.

Module.prototype.load = function(filename) {
  const extension = findLongestRegisteredExtension(filename);
  Module._extensions[extension](this, filename);
  this.loaded = true;
};

For WebAssembly, the article describes how its module format builds on the same import/export concepts, defining import and export sections for functions, tables, memories, and globals. It shows three linking scenarios: WebAssembly → JavaScript, JavaScript → WebAssembly, and WebAssembly → WebAssembly via JavaScript re‑exports.

// Load a shared WebAssembly module and expose its exports to JS
JSModule = {};
let instance = wasmLoad(__dirname + "/lib/shared-module.wasm", JSModule);
let memory = instance.exports.memory;
let sp = instance.exports.stack_pointer;
let fn_fib = instance.exports.fib;
let fn_distance = instance.exports.distance;
let tbl = instance.exports.indirect_function_table;

When a user module depends on host‑provided symbols, a JavaScript object (e.g., JSModule ) supplies the required functions, tables, memory, and globals, enabling the WebAssembly instance to resolve its imports.

// Define host functions and objects for a user WebAssembly module
function fib(num) { /* recursive implementation */ }
function distance(n1, n2) { return Math.abs(n1 - n2); }
const fn_table = new WebAssembly.Table({initial:2, maximum:2, element:"anyfunc"});
const importMemory = new WebAssembly.Memory({initial:256, maximum:32768});
const sp = new WebAssembly.Global({value:"i32", mutable:true}, 5243920);
JSModule = {
  share_ctx: { stack_pointer: sp, fib, distance, indirect_function_table: fn_table, memory: importMemory },
  env: { print: console.log.bind(console) }
};
let instance = wasmLoad(__dirname + "/lib/user-module.wasm", JSModule);

The article then surveys emerging proposals such as ECMAScript Module Integration, which aims to make WebAssembly instantiation declarative (e.g., import {fib} from "./shared-module.wasm" ), and the Module Linking and Component Model proposals that seek a host‑agnostic, composable ecosystem for WebAssembly modules.

In summary, the piece outlines the evolution from traditional JavaScript module systems to sophisticated WebAssembly dynamic linking, highlights current limitations, and points to future standardization efforts that could simplify module composition across languages and runtimes.

JavaScriptWebAssemblyDynamic Linkingasm.jsModular Programming
ByteDance Web Infra
Written by

ByteDance Web Infra

ByteDance Web Infra team, focused on delivering excellent technical solutions, building an open tech ecosystem, and advancing front-end technology within the company and the industry | The best way to predict the future is to create it

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.