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.
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
