For the basics, see webpack4 basics

Webpack author is from c#, a lot of code is OOP mode, can be used for reference

Tapable

Webpack 4 rewrites Tapable and is the core of webPack’s plug-in organization. It provides individual plug-in hooks that execute these mounted methods when an event is raised. Webapck plugins must have the apply() method, which webpack attaches to events when called, much like EventEmitter’s $on in NodeJS

class Car {
	constructor() {
		this.hooks = {
			accelerate: new SyncHook(["newSpeed"]),
			brake: new SyncHook(),
			calculateRoutes: new AsyncParallelHook(["source"."target"."routesList"])}; }, setSpeed(newSpeed) {this.hooks.accelerate.call(newSpeed); }}Copy the code

As shown in the code above, three hook functions are registered during the instantiation process and are triggered when a method is called on the instance. Here are the six main Tapable instances in Webpack, all of which inherit Tapable and define some hooks of their own

Compiler

Top-level instances, initial configuration, providing global hooks like Done, compilation. Other Tapable instances need to be accessed through it, such as

    compiler.hooks.compilation.tap(
      "myFirstWebpackPlugin",
      (compilation, params) => {
        compilation.hooks.seal.tap()
      }
    );
Copy the code

Compilation

Created by Compiler, the entire build is done here, doing dependency graph building, optimizing resources, rendering runtime code, etc. The following four examples occur in this phase.

Resolver

When you request a module, you send the module name or relative address to the module parser, which resolves the absolute address to look for the module, see if it exists, and return the corresponding module information, including the context and so on. A request can carry query parameters like a network request, and Resolver will return additional information. In Webpack4, the instance of Resolver is extracted and a package, enhanced resolve, is issued separately. The abstraction can facilitate users to implement their own Resolver

ModuleFactory

A module factory is an instance that is responsible for building a module. The two types of NormalModuleFactory and ContextModuleFactory are introduced. The difference between the two is that the latter is used to parse dynamic import(). The module factory is mainly used to create a NormalModule object in memory by taking the source code of the Resolver successfully parsed request out of the file.

Parser

Parser is mainly used to parse code into AST abstract syntax 🌲. You can look at the AST to see what the code looks like. Webpack uses the Acorn parser by default, and Babel is Babylon. Parser converts code strings from objects returned by the ModuleFactory into AST and parses them. If it finds module references like Import or require or define, it adds those references (dependencies) to the object of the current module. In this way, each module object contains information about its module as well as its dependencies. Webpack fires events not only at module declarations, but even when a variable is parsed. The following three hook functions can be seen in webpack/lib/ parser. js

    varDeclaration: new HookMap((a)= > new SyncBailHook(["declaration")),varDeclarationLet: new HookMap((a)= > new SyncBailHook(["declaration")),varDeclarationConst: new HookMap((a)= > new SyncBailHook(["declaration")),Copy the code

Template

Responsible for generating run-time code

/ / the source code
// index.js
    var multiply  = require('./multiply')
    var sum = (a,b) = >{
        return a+b;
    }
    module.exports = sum;
// multiply.js
    module.exports = (a, b) = > a*b

// Generated runtime
[
/* 0 */
/ * * * / (function(module, exports, __webpack_require__) {
        var multiply  = __webpack_require__(1)
        var sum = (a,b) = >{
            return a+b;
        }
        module.exports = sum;
/ * * * / }),
/ * 1 * /
/ * * * / (function(module, exports) {
        module.exports = (a, b) = > a*b
/ * * * /})];Copy the code

As shown in the above code, there are three templates, which are responsible for chunk, module and dependency respectively. Chunk is an array containing multiple modules, which is the form of the external array. A module is the part of the module that is surrounded by immediate functions. Dependency converts the original import, require, and other reference module parts to __webpack_require__.

The working process

Now I only see the module building part. The result is to use vscode debugging tools to break more points ~~

After introducing these six examples, the following is a general description of the workflow of Webpack. Webpack does a lot of work, and only the main ones are selected here. The parentheses are the file locations of the source code, and the context is node_modules/webpack. This process is based on Webpackage 4.30.0.

  1. The first step is to read in the configuration file. Webpack4 has default configurationoptions = new WebpackOptionsDefaulter().process(options);The user’s configuration takes precedence. Compiler to createcompiler = new Compiler(options.context);Bind the plugin part of the configuration
    for (const plugin of options.plugins) {
        if (typeof plugin === "function") {
            plugin.call(compiler, compiler);
        } else{ plugin.apply(compiler); }}Copy the code

Options = new WebpackOptionsApply().process(options, compiler); (lib/webpack.js) 2. Then generate different packing templates according to the packing destination (Web, node, electron etc.)

    switch (options.target) {
        case "web":
            JsonpTemplatePlugin = require("./web/JsonpTemplatePlugin"); . break;case "webworker":...Copy the code

This is because browser-side modules that request asynchronous loading will insert

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

This part is to add the entry configuration to call the entryOption hook. (lib/WebpackOptionsApply.js) 3. Calling different classes based on different interface types, WebPack is full of classes

// lib/EntryOptionPlugin.js
    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);
    }
Copy the code

The example here is a single-file entry that binds (or is invoked by the Compiler after compilation is created) callbacks to the compilation hook to specify the mode-generation method that the Compiler currently relies on.

    compiler.hooks.compilation.tap(
        "SingleEntryPlugin", (compilation, { normalModuleFactory }) => { compilation.dependencyFactories.set( SingleEntryDependency, normalModuleFactory ); });Copy the code

4. Create compilation (lib/ compiler.js).

    const compilation = this.createCompilation();
Copy the code
// lib/SingleEntryPlugin.js
    compiler.hooks.make.tapAsync(
        "SingleEntryPlugin",
        (compilation, callback) => {
            const { entry, name, context } = this;
            constdep = SingleEntryPlugin.createDependency(entry, name); compilation.addEntry(context, dep, name, callback); });Copy the code

This is an asynchronous hook that was previously registered, but is called before the compilation has been created. The compilation is created and passed in, which creates a dependency for the entry. 5. Start the addEntry() method, call _addModuleChain in the addEntry method, and create the module moduleFactory.create with the current entry file. Module to create good post-processing of the current module dependencies enclosing processModuleDependencies. Dependencies are created and then resolved in turn. (lib/Compilation. Js)

The resources

Mainly Sean’s webpack-Plugins

contribute-to-webpack