Author: Cui Jing

The introduction

For WebPack, every file is a Module, and this article shows you how WebPack starts with the definition of entry in the configuration, follows through to find all the files, and converts them into Modules.

The overview

Webpack entry. The entry argument is a single-entry string, a single-entry array, a multi-entry object, or a dynamic function. Whatever it is, the compilation.addEntry method is called, and this method does _addModuleChain, Adds the entry file to the queue to compile. The files in the queue are then processed one by one, and other files introduced into the queue are added to the compile queue through addModuleDependencies. Finally, when the contents of the compile queue are processed, the file to Module conversion is complete.

Above is a rough outline, we will fill in the details one by one. Let’s start with the overall flow of control for compilation — control of the compilation queue.

Compile queue control — Semaphore

Both _addModuleChain and addModuleDependencies call this.semaphore.acquire, which is implemented in lib/util/ semaphore.js. Let’s look at the implementation

class Semaphore {
	constructor(available) {
	   // Available indicates the maximum number of concurrent requests
		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) {
			if (this.waiters.length > 0) {
				this.available--;
				const callback = this.waiters.pop(); callback(); }}}}Copy the code

There are only two ways to expose yourself:

  1. Acquire: Apply for processing resources. If there are idle resources (that is, the number of concurrent resources), processing will be executed immediately, and the idle resources are reduced by 1. Otherwise, it is stored in a wait queue.
  2. Release: Releases resources. In Acquire, the callback method is called, where the resource needs to be released and the idle resource is incremented by one. At the same time, it checks whether there is any content to be processed and continues processing if there is

The Semaphore class borrows the Semaphore concept for controlling the use of resources in multithreaded environments. The number of concurrent requests is defined by available. What is the default value? It can be found in compiler.js

this.semaphore = new Semaphore(options.parallelism || 100);
Copy the code

The default concurrency is 100. Note that concurrency is only used in code design, not to be confused with the single-threaded nature of JS. The overall compilation process is shown as follows

From the entry to _addModuleChain

Entry in the webPack official website configuration guide can be in the following forms:

  • String: indicates a string, for example
{
  entry: './demo.js'
}
Copy the code
  • [string]: array of type string, for example
{
  entry: ['./demo1.js'.'./demo2.js']}Copy the code
  • Object, for example
{
  entry: {
    app: './demo.js'}}Copy the code
  • Function to dynamically return an entry, for example
{
  entry: (a)= > './demo.js'
}
/ / or
{
  entry: (a)= > new Promise((resolve) = > resolve('./demo.js'))}Copy the code

Where are these processed? In the webpack.js startup file, options will be processed first, with the following sentence

compiler.options = new WebpackOptionsApply().process(options, compiler);
Copy the code

Entry configuration is processed during process

// webPackOptionsapply.js file
new EntryOptionPlugin().apply(compiler);
compiler.hooks.entryOption.call(options.context, options.entry);
Copy the code

So what does EntryOptionsPlugin do

const SingleEntryPlugin = require("./SingleEntryPlugin");
const MultiEntryPlugin = require("./MultiEntryPlugin");
const DynamicEntryPlugin = require("./DynamicEntryPlugin");

const 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) => {
		   // The string type is new SingleEntryPlugin
		   // Array type is new MultiEntryPlugin
			if (typeof entry === "string" || Array.isArray(entry)) {
				itemToPlugin(context, entry, "main").apply(compiler);
			} else if (typeof entry === "object") {
			    // For the object type, each item is iterated over
				for (const name of Object.keys(entry)) { itemToPlugin(context, entry[name], name).apply(compiler); }}else if (typeof entry === "function") {
			    // function type is DynamicEntryPlugin
				new DynamicEntryPlugin(context, entry).apply(compiler);
			}
			return true; }); }};Copy the code

An entryOption event handler is registered in EntryOptionsPlugin, which instantiates and executes different EntryPlugins according to different types of entry values (each /functioin in string/array/ Object) : String corresponds to SingleEntryPlugin; Array corresponds to MultiEntryPlugin; Function corresponds to DynamicEntryPlugin. For the object type, each key is iterated as an entry point and SingleEntryPlugin or MultiEntryPlugin is selected depending on the type string/array. Here we mainly analyze: SingleEntryPlugin, MultiEntryPlugin, DynamicEntryPlugin

A horizontal comparison of these three plugins does two things:

  1. The Compilation event callback (which will be triggered before the make event below) is registered and set during compilationdependencyFactories
compiler.hooks.compilation.tap('xxEntryPlugin', (compilation, { normalModuleFactory }) => {
  / /...
  compilation.dependencyFactories.set(...)
})
Copy the code
  1. Register the make event callback, call the addEntry method during the make phase, and enter_addModuleChainEnter the formal compilation stage.
compiler.hooks.make.tapAsync('xxEntryPlugin',(compilation, callback) => {
  // ...
  compilation.addEntry(...)
})
Copy the code

In conjunction with webpack’s packaging process, let’s start with the compile method in Compiler.js and see what the compilation events and make event callbacks do

XxxEntryPlugin back to call in the event of compilation to set up the compilation. DependencyFactories, Ensure that the corresponding moduleFactory is retrieved from the Dependency in the _addModuleChain callback phase later.

Dependency is generated based on the different Entry configuration in the make event callback, and then addEntry is called and passed in.

According to the different dependency in the _addModuleChain callback, then execute multiModuleFactory. Create or normalModuleFacotry. Create.

Dependency was mentioned repeatedly in the previous steps, and there will be many more in the next article. In the webpack/lib/dependencies folder, you can find a variety of dependency dependencies. Dependency and Module have the following relationship structure:

module: {
  denpendencies: [
    dependency: {
      / /...
      module: // Dependent module, also null}}}]Copy the code

Webpack also treats entry files as entry dependencies, so xxEntryDependency is generated in the xxEntryPlugin above. Dependency in a Module stores dependencies on other files, exported content, and so on. Later in this article, you will see dependency maps generated by chunks relying on dependency maps and methods and saved information generated by the final file replacing statements such as import in the source file with executable JS statements in the final output.

After looking at what each entryPlugin has in common, let’s go into each plugin vertically and compare the differences.

SingleEntryPlugin

The SingleEntryPlugin logic is simple: Will SingleEntryDependency and normalModuleFactory associated, so later the create method performs normalModuleFactory. The create method.

apply(compiler) {
	compiler.hooks.compilation.tap(
		"SingleEntryPlugin",
		(compilation, { normalModuleFactory }) => {
		   // SingleEntryDependency corresponds to normalModuleFactorycompilation.dependencyFactories.set( SingleEntryDependency, normalModuleFactory ); }); compiler.hooks.make.tapAsync("SingleEntryPlugin",
		(compilation, callback) => {
			const { entry, name, context } = this;

			const dep = SingleEntryPlugin.createDependency(entry, name);
			// dep 的 constructor 为 SingleEntryDependencycompilation.addEntry(context, dep, name, callback); }); }static createDependency(entry, name) {
	const dep = new SingleEntryDependency(entry);
	dep.loc = name;
	return dep;
}
Copy the code

MultiEntryPlugin

Compared to the SingleEntryPlugin above,

  1. In compilation, dependencyFactories set two corresponding values
MultiEntryDependency: multiModuleFactory
SingleEntryDependency: normalModuleFactory
Copy the code
  1. CreateDependency: Treat each value in entry as a SingleEntryDependency.
static createDependency(entries, name) {
	return new MultiEntryDependency(
		entries.map((e, idx) = > {
			const dep = new SingleEntryDependency(e);
			// Because entrypoints are not dependencies found in an
			// existing module, we give it a synthetic id
			dep.loc = `${name}:The ${100000 + idx}`;
			return dep;
		}),
		name
	);
}
Copy the code

3.multiModuleFactory.create

In the second step, by MultiEntryPlugin createDependency generated dep, the structure is as follows:

{
  dependencies: []module: MultiModule
  / /...
}
Copy the code

Dependencies is an array containing multiple SingleentryDependencies. This dep will as a parameter to multiModuleFactory. The create method, namely the data in the code below. Dependencies [0]

// multiModuleFactory.create
create(data, callback) {
	const dependency = data.dependencies[0];
	callback(
		null.new MultiModule(data.context, dependency.dependencies, dependency.name)
	);
}
Copy the code

The new MultiModule is generated in create, and the build method in MultiModule is executed in callback,

build(options, compilation, resolver, fs, callback) {
	this.built = true; // Mark that compilation is complete
	this.buildMeta = {};
	this.buildInfo = {};
	return callback();
}
Copy the code

This method sets the compilation completion variable to true, and then directly enters the successful callback. At this point, the entry is compiled into a module that only has dependencies. Since each item in createDependency is treated as a SingleEntryDependency, each item in Dependencies is a SingleEntryDependency. Then we enter the dependency processing phase on this Module, and the multiple files we configured in Entry are added to the compilation chain as dependencies and treated as SingleEntryDependency.

In general, for multi-file entry, it can be simply understood that webpack first converts the entry into the following form:

import './demo1.js'
import './demo2.js'
Copy the code

And then you do something with it.

DynamicEntryPlugin

Dynamic entry configuration supports synchronous mode and asynchronous mode whose return value is Promise type. Therefore, when processing addEntry, the entry function is called first, and then the logic of String, array, and Object is entered according to the returned result type.

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((a)= > callback(), callback);
			} else if (typeof entry === "object") {
				Promise.all(
					Object.keys(entry).map(name= > {
						return addEntry(entry[name], name);
					})
				).then((a)= >callback(), callback); }}); });Copy the code

So the only difference between dynamic entry and others is that there is an extra layer of function call.

Once the entry is found, it is time to convert the file to Module. The transition module process will be described in detail in the next article.