Skip to content

Instantly share code, notes, and snippets.

@AriPerkkio
Last active March 17, 2023 07:07
Show Gist options
  • Save AriPerkkio/50562378aef3448a234e9ce6658c4c37 to your computer and use it in GitHub Desktop.
Save AriPerkkio/50562378aef3448a234e9ce6658c4c37 to your computer and use it in GitHub Desktop.
isolated vm + dynamic import
/*
$ node --watch --no-warnings --experimental-vm-modules --experimental-import-meta-resolve vm.mjs
*/
import vm from "node:vm";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { JSDOM } from "jsdom";
const server = {
async fetchModule(filename) {
if (filename.startsWith("node:")) return "externalize";
const code = readFileSync(fileURLToPath(filename), "utf8");
return this.transform(code);
},
async transform(code) {
return `// @transformed\n${code}`;
},
};
const html = `<!DOCTYPE html>
<html>
<body>
<div>Initial HTML</div>
</body>
</html>`;
const dom1 = new JSDOM(html, { runScripts: "dangerously" });
const dom2 = new JSDOM(html, { runScripts: "dangerously" });
const script = `
'use strict';
(async () => {
await import("./setup-file.js");
const { default: chalk } = await import("chalk");
console.log(chalk.blue('Running chalk!'));
})()
`.trim();
const context1 = dom1.getInternalVMContext();
const context2 = dom2.getInternalVMContext();
context1.console = { log: (...args) => console.log("[Context 1]:", ...args) };
context2.console = { log: (...args) => console.log("[Context 2]:", ...args) };
await vm.runInContext(script, context1, createOptions(context1));
await vm.runInContext(script, context2, createOptions(context2));
await vm.runInContext(script, context1, createOptions(context1));
console.log("\ncontext1\n", context1.document.body.outerHTML);
console.log("\ncontext2\n", context2.document.body.outerHTML);
console.log("\nglobalThis\n", globalThis.document);
/*
*/
function createOptions(context) {
const moduleCache = new Map();
async function load(
filename,
{ identifier } = { identifier: import.meta.url }
) {
if (moduleCache.has(filename)) {
return moduleCache.get(filename);
}
const name = await import.meta.resolve(filename, identifier);
const options = {
context,
identifier: name,
importModuleDynamically,
};
const code = await server.fetchModule(name);
const mod =
code === "externalize"
? await externalize(options)
: new vm.SourceTextModule(code, options);
moduleCache.set(filename, mod);
return mod;
}
async function externalize(options) {
const mod = await import(options.identifier);
const externalized = new vm.SyntheticModule(
Object.keys(mod),
() =>
Object.keys(mod).forEach((key) =>
externalized.setExport(key, mod[key])
),
options
);
return externalized;
}
async function importModuleDynamically(specifier) {
const m = await load(specifier);
if (m.status === "unlinked") {
await m.link(load);
}
if (m.status === "linked") {
await m.evaluate();
}
return m;
}
return { importModuleDynamically };
}
{
"name": "repro",
"version": "1.0.0",
"main": "index.js",
"dependencies": {
"chalk": "^5.2.0",
"jsdom": "^21.1.1"
}
}
const div = document.createElement("div");
div.appendChild(document.createTextNode("Setup complete!"));
document.body.appendChild(div);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment