Download Game! Currently 58 players and visitors. Last logged in:NarcosisIberiamsspVihaanSirda

Blitzer's Blog >> 72045

Back to blogs index
Posted: 05 Sep 2026 12:21 [ permalink ]
Here is the complete solution to port your Node.js LPC Virtual Machine into a
standalone browser environment.
To achieve this cleanly, the solution is split into two files:
template.html: The static frontend shell containing the CSS, UI, and the
runtime shims (require, fs, sys.stdout, etc.).
build_web.js: The Node.js compiler script that recursively reads the VM
directories, bundles the source code, strips out the Node-specific terminal
logic from harness.js, and injects everything into the HTML template.  
1. The HTML Template (template.html)
This file contains the retro-terminal styling, the Virtual File System (VFS)
shims, the require() stub, and the bootstrap logic that initializes the VM
when the page loads.

==

2. The Build Script (build_web.js)
This script crawls the target directories, packages all standard Node.js and
LPC files into a dictionary, patches out the Node-specific process.argv loop
in harness.js, and outputs the finished index.html.

==

const fs = require('fs');
const path = require('path');

function walkSync(dir, filelist = []) {
    if (!fs.existsSync(dir)) return filelist;
    fs.readdirSync(dir).forEach(file => {
        const filepath = path.join(dir, file);
        if (fs.statSync(filepath).isDirectory()) {
            filelist = walkSync(filepath, filelist);
        } else {
            filelist.push(filepath);
        }
    });
    return filelist;
}

console.log("Packaging LPC VM...");

let vfs = {};
const dirsToScan = ['core', 'efuns.d', 'gamelib', 'tests'];

/ Recursively map required directories
dirsToScan.forEach(dir => {
    walkSync(dir).forEach(file => {
        // Normalize paths for VFS dictionary (POSIX format internally)
        let key = file.split(path.sep).join('/');
        vfs[key] = fs.readFileSync(file, 'utf8');
        console.log(` -> Cached: ${key}`);
    });
});

/ Cache global dependencies
if (fs.existsSync('efuns.js')) {
    vfs['efuns.js'] = fs.readFileSync('efuns.js', 'utf8');
}

if (fs.existsSync('harness.js')) {
    let harnessSrc = fs.readFileSync('harness.js', 'utf8');
    
    // AST / Module Patch: Swap out the auto-executing runVM() call
    // so it safely exports the GDCompiler class to our browser CommonJS shim!
    harnessSrc = harnessSrc.replace(/^runVM\(\);/m, 'module.exports =
GDCompiler;');
    vfs['harness.js'] = harnessSrc;
}

/ Generate the final static HTML bundle
if (!fs.existsSync('template.html')) {
    console.error("ERROR: template.html is missing. Cannot build frontend.");
    process.exit(1);
}

let templateData = fs.readFileSync('template.html', 'utf8');
const vfsInjectionString = `window.__VFS = ${JSON.stringify(vfs)};`;
const finalHtml = templateData.replace('/* VFS_INJECTION_POINT */',
vfsInjectionString);

fs.writeFileSync('index.html', finalHtml);
console.log("n SUCCESS: index.html has been generated. Open it in a browser to
use the REPL.");


==

Usage Instructions
Ensure both build_web.js and template.html reside in your ~/Downloads/Remote/LP
C/GEM directory.
Run node build_web.js.
Open index.html in your web browser.