How Mako Redefines Web Bundling: Architecture, Performance, and Plugins
Mako is a Rust‑based web bundler that offers zero‑config, production‑grade speed, hot‑module replacement, code‑splitting and module concatenation, and its architecture—spanning entry, compiler, load, parse, transform, dependency analysis, module creation, and plugin orchestration—is detailed with benchmarks and code examples.
Overview
Mako is a Rust‑based web bundler that supports zero‑configuration bundling of JavaScript/TypeScript, CSS, Less, CSS Modules, React, images, fonts, WASM and Node polyfills. The core bundling logic runs in Rust and uses the piscina thread‑pool library for parallel compilation.
Key Features
Zero‑configuration : a single entry file is enough; all supported asset types are handled automatically.
Fast builds : Rust core and parallel compilation outperform many Rust bundlers and Webpack.
Hot Module Replacement (HMR) : integrated React Fast Refresh updates only the changed component.
Code splitting : dynamic imports generate separate chunks.
Module concatenation : similar to Webpack’s scope hoisting to reduce bundle size.
Performance
Benchmarks (see https://github.com/umijs/benchmark) compare cold start, root HMR, leaf HMR and incremental builds. Mako consistently shows lower build times than competing tools and Webpack.
Build Pipeline Architecture
The compilation process is divided into distinct modules:
Entry
Compiler
Build
Load
Parse
Transform
Analyze Deps + Resolve
Create Module
Generate
Load Stage
The Load module reads files from disk and converts them into internal Content variants. Supported types include virtual modules, raw imports, JavaScript/TypeScript, CSS, Markdown/MDX, SVG (via svgr), TOML, WASM, XML, YAML, JSON and generic assets.
impl Load {
pub fn load(file: &File, context: Arc<Context>) -> Result<Content> {
// plugin first
let content = context.plugin_driver.load(&PluginLoadParam { file }, &context)?;
if let Some(content) = content { return Ok(content); }
// handle virtual modules, ?raw, js, css, md/mdx, svg, toml, wasm, xml, yaml, json, assets …
}
}Parse Stage
Parsing converts source files into a ModuleAst using SWC. Plugins can replace the default parser to support custom syntax.
impl Parse {
pub fn parse(file: &File, context: Arc<Context>) -> Result<ModuleAst> {
let ast = context.plugin_driver.parse(&PluginParseParam { file }, &context)?;
if let Some(ast) = ast { return Ok(ast); }
// default JS/TS parsing via SWC
// default CSS handling …
}
}Transform Stage
Transform runs a series of SWC visitors that perform resolver injection, helper positioning, symbol‑conflict fixing, URL asset handling, worker module processing, TypeScript/TSX stripping, React Fast Refresh integration, environment‑variable replacement, import resolution, provider injection, CSS Modules handling, dynamic‑import conversion and Node‑specific mocks.
impl Transform {
pub fn transform(ast: &mut ModuleAst, file: &File, context: Arc<Context>) -> Result<()> {
let mut visitors: Vec<Box<dyn VisitMut>> = vec![
Box::new(resolver(...)),
Box::new(FixHelperInjectPosition::new()),
// … other visitors …
];
ast.transform(&mut visitors, &mut folders, file, true, context.clone())?;
Ok(())
}
}Analyze Deps + Resolve
After transformation, the dependency graph is built. Mako switched from its original resolver to oxc_resolver to fix edge‑case bugs while still using SWC for parsing.
Create Module
Metadata from previous stages is merged into a Module instance, which includes module ID, entry flag, source‑map chain, async detection and a raw hash for watch mode.
pub fn build_module(file: &File, parent_resource: Option<ResolverResource>, context: Arc<Context>) -> Result<Module> {
// load → parse → transform → analyze deps → create Module
let module = Module::new(module_id, is_entry, Some(info));
Ok(module)
}Plugin System
Mako defines a Plugin trait with lifecycle callbacks (e.g., load, parse, transform_js, after_generate_chunk_files, etc.). The PluginDriver registers built‑in plugins (11 types) and external JavaScript plugins, then schedules each lifecycle method during the build.
pub trait Plugin: Any + Send + Sync {
fn name(&self) -> &str;
fn modify_config(&self, _config: &mut Config, _root: &Path, _args: &Args) -> Result<()> { Ok(()) }
fn load(&self, _param: &PluginLoadParam, _context: &Arc<Context>) -> Result<Option<Content>> { Ok(None) }
// … other lifecycle methods …
}Build Execution
The Compiler::build method spawns parallel tasks using a Rayon thread pool, collects results via channels, aggregates errors and returns the set of built module IDs.
impl Compiler {
pub fn build(&self, files: Vec<File>) -> Result<HashSet<ModuleId>> {
let (rs, rr) = channel::<Result<Module>>();
for file in files { /* spawn build task */ }
// collect results, handle errors, return module IDs
}
}Usage Options
Directly invoke the Rust crate (core logic exported, not yet published on crates.io).
Use the npm package to run Mako from Node.js.
Execute the provided CLI, which loads the native module compiled via napi and runs the build pipeline.
Conclusion
Mako follows a Rollup‑style plugin architecture rather than aiming for Webpack loader compatibility. Projects that heavily rely on Webpack’s ecosystem may prefer alternatives such as Rspack, while Mako provides a fast, Rust‑backed solution for modern front‑end builds.
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.
Architect
Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.
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.
