Build Your Own Mini Webpack: A Step‑by‑Step Guide to Single‑Entry Bundling
This article walks through creating a minimal webpack implementation that bundles a single entry file, covering prerequisite knowledge, initialization parameters, compilation steps, loader handling, dependency resolution, chunk creation, and file generation, complete with code examples and explanations.
Introduction
Webpack is a widely used bundling tool that packages many files so they can run in a browser. This guide implements a simple version of webpack that supports bundling a single entry file, without plugins or code splitting.
Prerequisite Knowledge
Consider the following demo: an index.js that requires a.js, which in turn requires b.js. Using the simplest webpack configuration, index.js is set as the entry point.
// index.js
require('./a.js');
console.log('entry load');
// a.js
require("./b.js");
const a = 1;
console.log("a load");
module.exports = a;
// b.js
console.log("b load");
const b = 1;
module.exports = b;The bundled output is an immediately‑executed function that defines each module and implements a custom __webpack_require__ to replace the original require calls, enabling synchronous module loading in the browser.
Initialization Parameters
According to the Node API documentation, webpack exposes a webpack function that accepts a configuration object and returns a compiler instance. The compiler provides a run method to start the compilation.
const webpack = require('webpack');
const compiler = webpack(options);
compiler.run((err, stats) => {
// compilation callback
});In our mini‑webpack we expose a similar function that receives user options (entry, output, etc.).
// mini-webpack/core/index.js
function webpack(options) {
const compiler = new Compiler(options);
return compiler;
}
module.exports = webpack;Compilation Process
Read the entry file and pass it through matching loaders to obtain transformed code.
Compile the transformed code.
Replace each require with the custom __webpack_require__, record dependencies, and recursively process them.
After all modules are processed, organize the results starting from the entry file.
Entry File Loader Handling
// mini-webpack/compiler.js
const fs = require('fs');
class Compiler {
constructor(options) {
this.options = options || {};
this.modules = new Set();
}
run(callback) {
const entryChunk = this.build(path.join(process.cwd(), this.options.entry));
}
build(modulePath) {
let originCode = fs.readFileSync(modulePath);
originCode = this.dealWidthLoader(modulePath, originCode.toString());
return this.dealDependencies(originCode, modulePath);
}
// Pass source code through matching loaders
dealWidthLoader(modulePath, originCode) {
[...this.options.module.rules].reverse().forEach(item => {
if (item.test(modulePath)) {
const loaders = [...item.use].reverse();
loaders.forEach(loader => originCode = loader(originCode));
}
});
return originCode;
}
}
module.exports = Compiler;Entry File Processing
The entry file’s dependencies are collected, and each require is replaced with __webpack_require__ using Babel’s AST traversal.
// Inside Compiler class
dealDependencies(code, modulePath) {
const fullPath = path.relative(process.cwd(), modulePath);
const module = { id: fullPath, dependencies: [] };
const ast = parser.parse(code, { sourceType: "module", ast: true });
traverse(ast, {
CallExpression: (nodePath) => {
const node = nodePath.node;
if (node.callee.name === "require") {
const requirePath = node.arguments[0].value;
const moduleDirName = path.dirname(modulePath);
const fullPath = path.relative(path.join(moduleDirName, requirePath), requirePath);
node.callee = t.identifier("__webpack_require__");
node.arguments = [t.stringLiteral(fullPath)];
const exitModule = [...this.modules].find(item => item.id === fullPath);
if (!exitModule) {
module.dependencies.push(fullPath);
}
}
},
});
const { code: compilerCode } = generator(ast);
module._source = compilerCode;
return module;
}Dependency Processing
After handling the entry file, its recorded dependencies are recursively built and added to the module set.
// Continuation of dealDependencies
module._source = compilerCode;
module.dependencies.forEach(dependency => {
const depModule = this.build(dependency);
this.modules.add(depModule);
});
return module;Chunk Creation
// mini-webpack/compiler.js (continued)
buildChunk(entryName, entryModule) {
return {
name: entryName,
entryModule: entryModule,
modules: this.modules,
};
}File Generation
With all modules compiled, the final step writes the bundled code to the output path, preserving the basic structure of a real webpack bundle.
// mini-webpack/compiler.js (continued)
run(callback) {
const entryModule = this.build(path.join(process.cwd(), this.options.entry));
const entryChunk = this.buildChunk("entry", entryModule);
this.generateFile(entryChunk);
}
generateFile(entryChunk) {
const code = this.getCode(entryChunk);
if (!fs.existsSync(this.options.output.path)) {
fs.mkdirSync(this.options.output.path);
}
fs.writeFileSync(
path.join(this.options.output.path, this.options.output.filename.replace("[name]", entryChunk.name)),
code
);
}
getCode(entryChunk) {
return `
(() => {
// webpackBootstrap
var __webpack_modules__ = {
${Array.from(entryChunk.modules).map(module => `"${module.id}": (module, __unused_webpack_exports, __webpack_require__) => {${module._source}}`).join(',')}
};
var __webpack_module_cache__ = {};
function __webpack_require__(moduleId) {
var cachedModule = __webpack_module_cache__[moduleId];
if (cachedModule !== undefined) {
return cachedModule.exports;
}
var module = (__webpack_module_cache__[moduleId] = { exports: {} });
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
return module.exports;
}
var __webpack_exports__ = {};
(() => {
${entryChunk.entryModule._source}
})();
})()
`;
}Running the generated bundle in a browser produces the expected output, demonstrating a functional minimal webpack that handles a single entry file. Real webpack is far more complex, but this implementation captures its core bundling idea.
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.
