Building a Front‑End JavaScript Module Executor Using Node's Module._compile Logic
This article explains how to dynamically load and execute a CommonJS‑style JavaScript module in a browser by reproducing Node's internal Module._compile workflow, creating a sandbox with with, Proxy, and Symbol.unscopables, and providing a complete ES6 class implementation with examples.
Dynamic Module Loading in the Front‑End
If you are given a code snippet as a string and need to import it as a module at runtime in the browser, you must emulate the Node.js module system.
module.exports = {
name: 'ConardLi',
action: function(){
console.log(this.name);
}
};Node Environment Execution
In Node we usually use the Module class, whose private _compile method can dynamically load a module:
export function getRuleFromString(code) {
const myModule = new Module('my-module');
myModule._compile(code, 'my-module');
return myModule.exports;
}The _compile source shows three steps: create a wrapper function, compile it with vm.runInThisContext , and invoke the wrapper with the proper arguments.
Module.prototype._compile = function(content, filename) {
// strip shebang
content = internalModule.stripShebang(content);
// 1. create wrapper
var wrapper = Module.wrap(content);
// 2. compile wrapper in current context
var compiledWrapper = vm.runInThisContext(wrapper, {
filename: filename,
lineOffset: 0,
displayErrors: true
});
var dirname = path.dirname(filename);
var require = internalModule.makeRequireFunction(this);
var depth = internalModule.requireDepth;
// 3. run wrapper
var result = compiledWrapper.call(this.exports, this.exports, require, this, filename, dirname);
return result;
};Creating the Wrapper
The wrapper simply surrounds the module code with a function that receives exports, require, module, __filename, __dirname :
Module.wrap = function(script) {
return Module.wrapper[0] + script + Module.wrapper[1];
};
Module.wrapper = [
'(function (exports, require, module, __filename, __dirname) { ',
'\n});'
];This isolates each module's scope, solving JavaScript's global‑scope problem.
Implementing the Module in the Browser
Because browsers lack the vm module, we must build our own sandbox. Several techniques are combined:
eval – executes code in the current scope but is unsafe.
new Function() – compiles code without closing over the surrounding scope.
with – creates a semi‑sandbox by preferring properties from a supplied object.
Proxy – intercepts property access to make every lookup succeed.
Symbol.unscopables – prevents the sandbox from hiding built‑in identifiers.
Sandbox via with and new Function
function compileCode(src) {
src = 'with (sandbox) {' + src + '}';
return new Function('sandbox', src);
}Proxy to Force Property Presence
function compileCode(code) {
code = 'with (sandbox) {' + code + '}';
const fn = new Function('sandbox', code);
return (sandbox) => {
const proxy = new Proxy(sandbox, {
has() { return true; }
});
return fn(proxy);
};
}Handling Symbol.unscopables
function compileCode(code) {
code = 'with (sandbox) {' + code + '}';
const fn = new Function('sandbox', code);
return (sandbox) => {
const proxy = new Proxy(sandbox, {
has() { return true; },
get(target, key, receiver) {
if (key === Symbol.unscopables) return undefined;
return Reflect.get(target, key, receiver);
}
});
return fn(proxy);
};
}Global‑Variable Whitelist
const ALLOW_LIST = ['console'];
function compileCode(code) {
code = 'with (sandbox) {' + code + '}';
const fn = new Function('sandbox', code);
return (sandbox) => {
const proxy = new Proxy(sandbox, {
has(target, key) {
if (!ALLOW_LIST.includes(key)) return true;
return false;
},
get(target, key, receiver) {
if (key === Symbol.unscopables) return undefined;
return Reflect.get(target, key, receiver);
}
});
return fn(proxy);
};
}Final Implementation
const ALLOW_LIST = ['console'];
export default class Module {
exports = {};
wrapper = [
'return (function (exports, module) { ',
'\n});'
];
wrap(script) {
return `${this.wrapper[0]}${script}${this.wrapper[1]}`;
}
runInContext(code) {
code = `with (sandbox) { ${code} }`;
const fn = new Function('sandbox', code);
return (sandbox) => {
const proxy = new Proxy(sandbox, {
has(target, key) {
if (!ALLOW_LIST.includes(key)) return true;
return false;
},
get(target, key, receiver) {
if (key === Symbol.unscopables) return undefined;
return Reflect.get(target, key, receiver);
}
});
return fn(proxy);
};
}
compile(content) {
const wrapper = this.wrap(content);
const compiledWrapper = this.runInContext(wrapper)({});
compiledWrapper.call(this.exports, this.exports, this);
}
}Test Execution
function getModuleFromString(code) {
const scanModule = new Module();
scanModule.compile(code);
return scanModule.exports;
}
const module = getModuleFromString(`
module.exports = {
name: 'ConardLi',
action: function(){
console.log(this.name);
}
};
`);
module.action(); // ConardLiThe example demonstrates that a string‑based CommonJS module can be safely evaluated in the browser with isolated scope, mimicking Node's module system.
Sohu Tech Products
A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.