Webpack Entry Configuration and Compilation Queue Control
Webpack determines which files become modules by processing the entry configuration—accepting strings, arrays, objects, or functions—through EntryOptionPlugin, which creates the appropriate SingleEntry, MultiEntry, or DynamicEntry plugin; these plugins enqueue entries via compilation.addEntry, while a Semaphore limits concurrent module processing to the default of 100.
This article explains how Webpack treats every file as a module and how the entry configuration determines which files are added to the compilation queue and transformed into modules.
Overview of entry types : Webpack supports four forms of entry – a string, an array of strings, an object, or a dynamic function (synchronous or returning a Promise). The entry value is ultimately processed by compilation.addEntry, which enqueues the entry file for compilation. Subsequent imports are added to the queue via addModuleDependencies until all files are processed.
{
entry: './demo.js'
}
// array form
{ entry: ['./demo1.js', './demo2.js'] }
// object form
{ entry: { app: './demo.js' } }
// function form
{ entry: () => './demo.js' }
// async function form
{ entry: () => new Promise(resolve => resolve('./demo.js')) }Compilation queue control – Semaphore : Both _addModuleChain and addModuleDependencies call this.semaphore.acquire. The semaphore implementation (found in lib/util/Semaphore.js) limits concurrent processing.
class Semaphore {
constructor(available) {
// available is the maximum concurrency
this.available = available;
this.waiters = [];
this._continue = this._continue.bind(this);
}
acquire(callback) {
if (this.available > 0) {
this.available--;
callback();
} else {
this.waiters.push(callback);
}
}
release() {
this.available++;
if (this.waiters.length > 0) {
process.nextTick(this._continue);
}
}
_continue() {
if (this.available > 0 && this.waiters.length > 0) {
this.available--;
const callback = this.waiters.pop();
callback();
}
}
}The semaphore exposes two methods: acquire: requests a resource; if a slot is free it runs the callback immediately, otherwise the callback is queued. release: frees a resource, increments the available count, and processes any waiting callbacks.
The default concurrency is 100 (defined in Compilation.js as new Semaphore(options.parallelism || 100)).
Entry processing flow : Webpack’s startup file webpack.js applies EntryOptionPlugin, which registers the entryOption hook. Depending on the entry type, it creates the appropriate plugin:
const SingleEntryPlugin = require("./SingleEntryPlugin");
const MultiEntryPlugin = require("./MultiEntryPlugin");
const DynamicEntryPlugin = require("./DynamicEntryPlugin");
function itemToPlugin(context, item, name) {
if (Array.isArray(item)) {
return new MultiEntryPlugin(context, item, name);
}
return new SingleEntryPlugin(context, item, name);
}
module.exports = class EntryOptionPlugin {
apply(compiler) {
compiler.hooks.entryOption.tap("EntryOptionPlugin", (context, entry) => {
if (typeof entry === "string" || Array.isArray(entry)) {
itemToPlugin(context, entry, "main").apply(compiler);
} else if (typeof entry === "object") {
for (const name of Object.keys(entry)) {
itemToPlugin(context, entry[name], name).apply(compiler);
}
} else if (typeof entry === "function") {
new DynamicEntryPlugin(context, entry).apply(compiler);
}
return true;
});
}
};All three entry plugins share two responsibilities:
Register a compilation hook to set dependencyFactories (mapping a dependency type to a factory).
Register a make hook that calls compilation.addEntry, which eventually invokes _addModuleChain to start the real compilation.
SingleEntryPlugin simply maps SingleEntryDependency to normalModuleFactory and adds a single entry:
apply(compiler) {
compiler.hooks.compilation.tap("SingleEntryPlugin", (compilation, { normalModuleFactory }) => {
compilation.dependencyFactories.set(SingleEntryDependency, normalModuleFactory);
});
compiler.hooks.make.tapAsync("SingleEntryPlugin", (compilation, callback) => {
const { entry, name, context } = this;
const dep = SingleEntryPlugin.createDependency(entry, name);
compilation.addEntry(context, dep, name, callback);
});
}
static createDependency(entry, name) {
const dep = new SingleEntryDependency(entry);
dep.loc = name;
return dep;
}MultiEntryPlugin handles an array of entries. It registers two factories ( MultiEntryDependency → multiModuleFactory, SingleEntryDependency → normalModuleFactory) and creates a MultiEntryDependency that contains multiple SingleEntryDependency objects.
static createDependency(entries, name) {
return new MultiEntryDependency(
entries.map((e, idx) => {
const dep = new SingleEntryDependency(e);
dep.loc = `${name}:${100000 + idx}`;
return dep;
}),
name
);
}The multiModuleFactory.create method receives the first dependency and builds a MultiModule whose build method simply marks the module as built.
build(options, compilation, resolver, fs, callback) {
this.built = true;
this.buildMeta = {};
this.buildInfo = {};
return callback();
}DynamicEntryPlugin supports entry functions that may return a value synchronously or a Promise. It invokes the entry function, then processes the result using the same logic as the other plugins.
compiler.hooks.make.tapAsync("DynamicEntryPlugin", (compilation, callback) => {
const addEntry = (entry, name) => {
const dep = DynamicEntryPlugin.createDependency(entry, name);
return new Promise((resolve, reject) => {
compilation.addEntry(this.context, dep, name, err => {
if (err) return reject(err);
resolve();
});
});
};
Promise.resolve(this.entry()).then(entry => {
if (typeof entry === "string" || Array.isArray(entry)) {
addEntry(entry, "main").then(() => callback(), callback);
} else if (typeof entry === "object") {
Promise.all(Object.keys(entry).map(name => addEntry(entry[name], name)))
.then(() => callback(), callback);
}
});
});In summary, Webpack’s entry handling starts from the EntryOptionPlugin, creates the appropriate entry plugin based on the configuration type, registers dependency factories, and adds the entry (or entries) to the compilation queue. The queue is processed sequentially, with the Semaphore limiting concurrency, and each file is eventually turned into a module.
The article’s author is Cui Jing, a senior front‑end engineer at Didi, and the team is recruiting senior front‑end developers (contact: [email protected]).
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.
