Skip to content

VFS Support #3

Description

@MarketingPip

Is there a way we can hard monkey patch os.FileRead?

Options to do this could be:

// Manually force-register a fake package
packages["os"] = &compiler.Archive{
    ImportPath: "os",
    // You would need to populate this with the compiled 
    // declarations of your custom ReadFile
}

or update main in complie.go:

// 1. Define your mock OS archive globally or fetch it once
var mockOSArchive *compiler.Archive 

// ... inside main ...
importContext := &compiler.ImportContext{
    Packages: make(map[string]*types.Package),
    Import: func(path string) (*compiler.Archive, error) {
        // HIJACK: If anything asks for "os", give them our fake one
        if path == "os" && mockOSArchive != nil {
            return mockOSArchive, nil
        }

        if pkg, found := packages[path]; found {
            return pkg, nil
        }
        pkgsToLoad[path] = struct{}{}
        return &compiler.Archive{}, nil
    },
}

Dropping this here since @hatemhosny - will think this is useful.

import { compile, format } from 'https://cdn.jsdelivr.net/npm/@live-codes/go2js';
  import { vol } from "https://cdn.jsdelivr.net/npm/memfs@4.6.0/+esm";

  // ---------------------------
  // 1. Load JSON into memfs
  // ---------------------------
  vol.fromJSON({
    "/etc/hello.txt": "Hello from the JSON VFS!",
    "/data/config.json": JSON.stringify({ version: "1.0", status: "ready" })
  });

  // ---------------------------
  // 2. Expose VFS to Go
  // ---------------------------
  window.VFS = {
    readFile: (path) => vol.readFileSync(path, "utf8"),
    writeFile: (path, data) => vol.writeFileSync(path, data, "utf8"),
    stat: (path) => vol.statSync(path),
    readDir: (path) => vol.readdirSync(path),
    mkdirAll: (path) => vol.mkdirSync(path, { recursive: true }),
    remove: (path) => vol.unlinkSync(path),

    // Simple open/read/close
    open: (path) => vol.openSync(path, "r"),
    read: (fd, len) => {
    const buf = new Uint8Array(len);
    const bytes = vol.readSync(fd, buf, 0, len, null);
    return new TextDecoder().decode(buf.slice(0, bytes));
    }
    ,
    close: (fd) => vol.closeSync(fd)
  };

  // ---------------------------
  // 3. Go code (os-compatible API)
  // ---------------------------
  const goCode = `
package main

import (
  "fmt"
  "io"
  "os"
  "syscall/js"
  "time"
)

// ---- vfs/os-compatible types ----
type File struct{ fd int }

type fileInfo struct {
  name  string
  size  int64
  mode  os.FileMode
  isDir bool
  mtime time.Time
}

func (fi fileInfo) Name() string       { return fi.name }
func (fi fileInfo) Size() int64        { return fi.size }
func (fi fileInfo) Mode() os.FileMode  { return fi.mode }
func (fi fileInfo) ModTime() time.Time { return fi.mtime }
func (fi fileInfo) IsDir() bool        { return fi.isDir }
func (fi fileInfo) Sys() interface{}   { return nil }

// ---- VFS API ----
func ReadFile(path string) ([]byte, error) {
  val := js.Global().Get("VFS").Call("readFile", path)
  return []byte(val.String()), nil
}

func WriteFile(path string, data string) error {
  js.Global().Get("VFS").Call("writeFile", path, data)
  return nil
}

func Stat(path string) (os.FileInfo, error) {
  st := js.Global().Get("VFS").Call("stat", path)
  return fileInfo{
    name:  st.Get("name").String(),
    size:  int64(st.Get("size").Int()),
    mode:  os.FileMode(st.Get("mode").Int()),
    isDir: st.Get("isDirectory").Bool(),
    mtime: time.Unix(0, int64(st.Get("mtimeMs").Int())*int64(time.Millisecond)),
  }, nil
}

func ReadDir(path string) ([]os.DirEntry, error) {
  arr := js.Global().Get("VFS").Call("readDir", path)
  out := make([]os.DirEntry, 0, arr.Length())
  for i := 0; i < arr.Length(); i++ {
    name := arr.Index(i).String()
    out = append(out, dirEntry{name: name})
  }
  return out, nil
}

type dirEntry struct{ name string }

func (d dirEntry) Name() string               { return d.name }
func (d dirEntry) IsDir() bool                { return false }
func (d dirEntry) Type() os.FileMode          { return 0 }
func (d dirEntry) Info() (os.FileInfo, error) { return nil, nil }

func Open(path string) (*File, error) {
  fd := js.Global().Get("VFS").Call("open", path).Int()
  return &File{fd: fd}, nil
}

func (f *File) Read(p []byte) (int, error) {
  s := js.Global().Get("VFS").Call("read", f.fd, len(p)).String()
  n := copy(p, []byte(s))
  if n == 0 {
    return 0, io.EOF
  }
  return n, nil
}


func (f *File) Close() error {
  js.Global().Get("VFS").Call("close", f.fd)
  return nil
}

// ---- main ----
func main() {
  data, _ := ReadFile("/etc/hello.txt")
  fmt.Println("ReadFile:", string(data))

  f, _ := Open("/etc/hello.txt")
  buf := make([]byte, 128)
  n, _ := f.Read(buf)
  fmt.Println("Open+Read:", string(buf[:n]))
  f.Close()
}
`;

  // ---------------------------
  // 4. Compile & run
  // ---------------------------
  format(goCode)
    .then((formatted) => compile(formatted))
    .then((js) => eval(js))
    .catch(console.error);

Activity

  1. MarketingPip commented on Feb 2, 2026

    @MarketingPip
    Author

    Compile GH packages example (not fully working - would need proper AST parser) - not sure what gopherjs can all handle:

    async function startGoRuntime(code) {
      const { compile, format } = await import('https://cdn.jsdelivr.net/npm/@live-codes/go2js');
      
      // 1. Define our Global Monkey Patch for "os"
      // We create a "virtual" archive for the os package
      const mockOsCode = `
        package os
        import "fmt"
        func ReadFile(path string) ([]byte, error) {
            fmt.Println("PATCH: Intercepted ReadFile for:", path)
            return []byte("This data came from the JS Monkey Patch!"), nil
        }
        func Open(path string) (interface{}, error) { return nil, nil }
        func MkdirAll(path string, perm uint32) error { return nil }
        func Remove(path string) error { return nil }
      `;
    
    }
    
    /**
     * Fetches raw .go files from GitHub API and combines them
     */
    /**
     * Extracts github.com import paths from Go source code
     * Handles both: import "github.com/user/repo" 
     * and import ( "github.com/user/repo" )
     */
    function parseGithubImports(source) {
        // Matches strings starting with github.com inside quotes
        const importRegex = /"github\.com\/([\w-]+\/[\w-]+)(?:\/[\w-]+)*"/g;
        const imports = new Set();
        let match;
    
        while ((match = importRegex.exec(source)) !== null) {
            // match[1] captures the 'owner/repo' part
            imports.add(`github.com/${match[1]}`);
        }
        return Array.from(imports);
    }
    
    async function fetchAndCompileGithub(pkgPath, visited = new Set()) {
        if (visited.has(pkgPath)) return "";
        visited.add(pkgPath);
    
        const parts = pkgPath.replace('github.com/', '').split('/');
        const [owner, repo] = parts;
        const apiUrl = `https://api.github.com/repos/${owner}/${repo}/contents/`;
        
        const response = await fetch(apiUrl);
        const files = await response.json();
        
        // 1. Filter for valid Go files
        const goFiles = files.filter(f => f.name.endsWith('.go') && !f.name.endsWith('_test.go'));
        
        let currentPkgSource = "";
        for (const file of goFiles) {
            const res = await fetch(file.download_url);
            let text = await res.text();
            
            // Strip package declaration and any "use strict" or JS-like artifacts
            // This regex removes 'package name' and any comments before it
            text = text.replace(/^[\s\S]*?package\s+\w+\n?/, ""); 
            currentPkgSource += text + "\n";
        }
    
        // 2. Extract and resolve dependencies (Recursive)
        const importRegex = /"github\.com\/([\w-]+\/[\w-]+)(?:\/[\w-]+)*"/g;
        let match;
        let dependencySource = "";
    
        while ((match = importRegex.exec(currentPkgSource)) !== null) {
            const depPath = `github.com/${match[1]}`;
            dependencySource += await fetchAndCompileGithub(depPath, visited);
        }
    
        // 3. Assemble the Virtual Package
        // We wrap everything in a single 'package main' so the compiler is happy
        const finalGoSource = `package main\n\nimport "fmt"\n` + dependencySource + currentPkgSource;
    
        // 4. Compile ONLY the final result
        // If we are at the root of the recursion, compile to JS
        if (visited.size === 1) {
            const { compile } = await import('https://cdn.jsdelivr.net/npm/@live-codes/go2js');
            console.log("Compiling final Go source to JS...");
            return await compile(finalGoSource);
        }
    
        return finalGoSource;
    }////
    
    // --- TEST RUN ---
    const myProg = `
    package main
    import (
    	"fmt"
    	"os"
    )
    func main() {
    	data, _ := os.ReadFile("/any/path/at/all.txt")
    	fmt.Println("Result:", string(data))
    }
    `;
    
    console.log(await fetchAndCompileGithub('github.com/tidwall/gjson'))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions