Deep Dive into Garfish Micro‑Frontend Architecture: Resource Loading, Sandbox, and Script Execution

This article examines Garfish's micro‑frontend implementation by dissecting resource loading, HTML parsing, sandbox environment construction, and script execution, providing detailed code analysis and comparisons with Qiankun to reveal the core mechanics behind modern micro‑frontend solutions.

ELab Team
ELab Team
ELab Team
Deep Dive into Garfish Micro‑Frontend Architecture: Resource Loading, Sandbox, and Script Execution

How to Parse Resources (HTML Entry)

Getting Resource Content

The entry URL is fetched; HTML entries are serialized and processed, while JS files instantiate a resource class that stores type, size, and code string, with caching attempts.

// Load any resource and convert to string
load(url: string, config?: RequestInit) {
  config = { mode: 'cors', ...config, ...requestConfig };
  this.loadings[url] = fetch(url, config)
    .then((res) => {
      if (res.status >= 400) {
        error(`load failed with status "${res.status}"`);
      }
      const type = res.headers.get('content-type');
      return res.text().then((code) => ({ code, type, res }));
    })
    .then(({ code, type, res }) => {
      let manager;
      const blob = new Blob([code]);
      const size = Number(blob.size);
      const ft = parseContentType(type);
      if (isJs(ft) || /.js/.test(res.url)) {
        manager = new JsResource({ url, code, size, attributes: [] });
      } else if (isHtml(ft) || /.html/.test(res.url)) {
        manager = new HtmlResource({ url, code, size });
      } else if (isCss(ft) || /.css/.test(res.url)) {
        manager = new CssResource({ url, code, size });
      } else {
        error(`Invalid resource type "${type}"`);
      }
      this.loadings[url] = null;
      currentSize += isNaN(size) ? 0 : size;
      if (!isOverCapacity(currentSize) || this.forceCaches.has(url)) {
        this.caches[url] = manager;
      }
      return manager;
    })
    .catch((e) => {
      const message = e instanceof Error ? e.message : String(e);
      error(`${message}, url: "${url}"`);
    });
  return this.loadings[url];
}

Serializing DOM Tree

HTML entry files are parsed into an AST using the himalaya library, converting the DOM into a JSON structure. A depth‑first traversal extracts link, style, and script tags for further processing.

// Query VNodes by tag names
this.queryVNodesByTagNames(['link', 'style', 'script'])

private queryVNodesByTagNames(tagNames: Array<string>) {
  const res: Record<string, Array<VNode>> = {};
  for (const tagName of tagNames) {
    res[tagName] = [];
  }
  const traverse = (vnode: VNode | VText) => {
    if (vnode.type === 'element') {
      const { tagName, children } = vnode;
      if (tagNames.indexOf(tagName) > -1) {
        res[tagName].push(vnode);
      }
      children.forEach((vnode) => traverse(vnode));
    }
  };
  this.ast.forEach((vnode) => traverse(vnode));
  return res;
}

Building Runtime Environment

Each sub‑application is instantiated inside an isolated sandbox to avoid affecting the host. The creation flow loads JS and link resources, then runs the app within the sandbox context.

// Create and run a sub‑app
private createApp(appInfo: AppInfo, opts: LoadAppOptions, manager: HtmlResource, isHtmlMode: boolean) {
  const run = (resources: ResourceModules) => {
    let AppCtor = opts.sandbox.snapshot ? SnapshotApp : App;
    if (!window.Proxy) {
      warn('Since proxy is not supported, the sandbox is downgraded to snapshot sandbox');
      AppCtor = SnapshotApp;
    }
    const app = new AppCtor(this.context, appInfo, opts, manager, resources, isHtmlMode);
    this.context.emit(CREATE_APP, app);
    return app;
  };
  const mjs = Promise.all(this.takeJsResources(manager as HtmlResource));
  const mlink = Promise.all(this.takeLinkResources(manager as HtmlResource));
  return Promise.all([mjs, mlink]).then(([js, link]) => run({ js, link }));
}

Code Execution

Scripts extracted earlier are executed inside the sandbox via execScript, which uses a with wrapper (when not in strict mode) and an evalWithEnv helper to run code in a proxied window context.

// Execute script in sandbox
execScript(code: string, url = '', options?: ExecScriptOptions) {
  try {
    (this.sandbox as Sandbox).execScript(code, url, options);
  } catch (e) {
    this.context.emit(ERROR_COMPILE_APP, this, e);
    throw e;
  }
}

// evalWithEnv implementation
export function evalWithEnv(code: string, params: Record<string, any>) {
  const keys = Object.keys(params);
  const randomValKey = '__garfish__exec_temporary__';
  const vals = keys.map((k) => `window.${randomValKey}.${k}`);
  try {
    rawWindow[randomValKey] = params;
    const evalInfo = [
      `;(function(${keys.join(',')}){`,
      `}).call(${vals[0]},${vals.join(',')});`
    ];
    const internalizeString = internFunc(evalInfo[0] + code + evalInfo[1]);
    (0, eval)(internalizeString);
  } finally {
    delete rawWindow[randomValKey];
  }
}

The analysis shows how Garfish loads resources, parses HTML, builds a sandbox, and executes scripts, highlighting differences from Qiankun and illustrating the core workflow of a micro‑frontend framework.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

micro-frontendFrontend Architecturesandboxresource-loadingscript execution
ELab Team
Written by

ELab Team

Sharing fresh technical insights

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.