Open5

ブラウザーで動くNode.jsを自作する

Godai HoriGodai Hori

require関数

鬼門となるのが、Node.js固有の関数であるrequire関数
これはmemfsとかでメモリ上でファイルを管理することで解決できそう!
以下思いついたコード

export function runCode(filePath: string, fs: IFs) {
  function customRequire(filePath: string) {
    let tmpFilePath = filePath;
    // 拡張子がない場合、様々な拡張子を試す
    if (!/\.[^/.]+$/.test(filePath)) {
      let flag = false;
      for (const ext of [".js", ".mjs", ".cjs"]) {
        try {
          tmpFilePath = trimFirstDot(filePath) + ext;
          if (fs.existsSync(tmpFilePath)) {
            flag = true;
            break;
          }
        } catch (e) {
          console.log(e);
          // ファイルが見つからなかった場合、次の拡張子を試す
        }
      }
      if (!flag) {
        throw new Error(`Cannot find module '${filePath}'`);
      }
    }

    const exports = {};
    const module = { exports };

    const code = fs.readFileSync(tmpFilePath, "utf8");

    const wrappedCode = code;

    const script = new Function("require", "module", "exports", wrappedCode);

    script(customRequire, module, exports);

    return module.exports;
  }

  customRequire(filePath);
}

例えば以下のようなファイルがあったときに

const files = {
  "/main.js": `
      const a = require('./hello');
      a.hello();
  `,
  "/hello.js": `
      module.exports = {
          hello: () => console.log('hello')
      };
  `,
};

以下のように実行できる

const { fs } = memfs(files, "/");
runCode("/main.js", fs);
Godai HoriGodai Hori

npm install

次に鬼門になるのがブラウザーで動かすnpm install
ただこれはnaruway氏がnpmにCORSが設定されていないことを発見してライブラリを作っている

https://github.com/naruaway/npm-in-browser

同じようにmemfsを使って仮想メモリ上にファイルを展開しているので上記のやり方と相性が良さそう

Godai HoriGodai Hori

ダウンロードしたソースコードの展開

import { Volume } from 'memfs';
import JSZip from 'jszip';

async function downloadAndUnzipToMemfs(url, vol) {
    // ZIPファイルをダウンロード
    const response = await fetch(url);
    const blob = await response.blob();

    // JSZipでZIPファイルを読み込み
    const zip = await JSZip.loadAsync(blob);

    // ZIPファイル内の各ファイルをmemfsに書き込み
    for (const [filename, zipEntry] of Object.entries(zip.files)) {
        if (!zipEntry.dir) {
            const content = await zipEntry.async('nodebuffer');
            vol.writeFileSync('/' + filename, content);
        }
    }
}
Godai HoriGodai Hori

node_modules内のパスはどうやって解決しようかな