If you feel the article useful, welcome to praise attention, but writing is not easy, without the consent of the author, prohibit any form of reprint!!

background

Webpack is especially hard to learn!!

Since version 5.0, the Webpack feature set has grown to include: Module packaging, code segmentation, load on demand, HMR, tree-shaking, file listening, Sourcemap, Module Federation, devServer, DLL, multi-process, etc. The amount of code in WebPack has grown to a staggering degree:

  • 498 JS files
  • 18862 lines of comments
  • 73548 lines of code
  • 54 Module types
  • 69 dependency types
  • 162 built-in plug-ins
  • 237 hooks

In this order of magnitude, the cost of reading, analyzing and learning the source code is very high, coupled with the lack of documentation on the Webpack website, resulting in the cost of learning and learning webPack is extremely high. To this end, the community has derived various scaffolding around Webpack, such as VUe-CLI and create-React-app, to solve the “use” problem.

However, this leads to a new problem. Most people gradually become configuration engineers in engineering, staying at the stage of “knowing how to use and match” but not knowing how to turn inside the black box, and will go blind when encountering specific problems:

  • Want to make an upgrade to the base library, there are compatibility problems can not run, directly give up
  • Want to optimize compilation performance, but don’t know the internals, can’t start

The reason is that we do not have a necessary overall understanding of the internal operating mechanism of Webpack and cannot quickly locate problems. Yes, we often cannot see the nature of the problems. What is the essence of the so-called phenomenon that we cannot see through the phenomenon? Personally, I abstract the whole huge system of Webpack into three aspects of knowledge:

  1. Build the core process
  2. The role of the loader
  3. Plugin architecture and common routines

The three work together to form the main framework of Webpack:

Understanding these three pieces of content can be regarded as a door, the most basic knowledge of Webpack, and problems encountered in the work will be able to follow the map. I should add that, as a primer, this article doesn’t go into too much detail at the WebPack code level — my energy doesn’t allow for that, so the reader doesn’t need to be overwhelmed by a bunch of text.

Core Process analysis

First of all, we need to understand one point, the core function of Webpack:

At its core, webpack is a static module bundler for modern JavaScript applications.

In other words, all kinds of resources, including images, CSS and JS, can be translated, combined, splicted and generated into BUNdler files in JS format. The animation on the homepage of the official website vividly expresses this point:

The core of this process has completed the two functions of content conversion and resource merger, and the realization includes three stages:

  1. Initialization phase:
    1. Initialization parameters: read from the configuration file, configuration object, and Shell parameters, and obtain the final parameters together with the default configurations
    2. Create a compiler object: create with the parameters obtained in the previous stepCompilerobject
    3. Initialize the build environment: this includes injecting built-in plug-ins, registering various module factories, initializing RuleSet collections, loading configured plug-ins, and so on
    4. Begin to compile: performcompilerThe object’srunmethods
    5. Determine the entrance: Based on the configurationentryFind all the entry files and callcompilition.addEntryConvert the entry file todependenceobject
  2. Construction phase:
    1. Compile module (make): according to theentryThe correspondingdependencecreatemoduleObject, callloaderTranslate the module into the standard JS content, call the JS interpreter to convert the content into an AST object, find the module that the module depends on, and then recurse this step until all the entry dependent files have been processed by this step
    2. Complete module compilation: after the last step of recursive processing of all accessible modules, we get the translated content of each module and the dependency diagram between them
  3. Generation stage:
    1. Output Resource (SEAL): Is assembled into modules based on the dependencies between entry and moduleChunkAnd then put eachChunkAdd it to the output list as a separate file. This step is the last chance to modify the output
    2. Write to a file system (emitAssets) : After determining the output content, determine the output path and file name based on the configuration, and write the output content to the file system

The single build process is carried out from top to bottom, and details will be discussed below. Before that, for those unfamiliar with the various technical terms mentioned above, please take a look at the introduction:

  • Entry: compiler entry, the starting point for WebPack compilation
  • Compiler: compiler manager, created when Webpack startscompilerObject that survives until the end of exit
  • Compilation: the manager of the single edit process, for examplewatch = trueThere is only one in the running processcompilerBut each time a file change triggers a recompilation, a new one is createdcompilationobject
  • Dependence: dependency object, on which WebPack records module dependencies
  • Module: All resources in Webpack will exist in the form of “Module” objects, and all operations, translation and merging of resources are based on “Module”
  • Chunk: When the compilation is complete and ready for output, Webpack willmoduleOrganized one by one according to specific ruleschunk, thesechunkTo some extent corresponds to the final output
  • LoaderA resource content converter is A converter that converts content A to B
  • Plugin: Events are broadcast at specific times during webPack build, and plug-ins listen for these events and intervene in the build process at specific points

The WebPack compilation process is built around these key objects. For more complete information, see the WebPack Knowledge Graph.

Initialization phase

Learning the source code of a project usually starts from the entry, and gradually finds its way out, so let’s take a look at the initialization process of WebPack:

Explain:

  1. willprocess.args + webpack.config.jsMerge into user configuration
  2. callvalidateSchemaCheck the configuration
  3. callgetNormalizedWebpackOptions + applyWebpackOptionsBaseDefaultsMerge the final configuration
  4. createcompilerobject
  5. Traversal the user-definedpluginsCollection that executes the plug-inapplymethods
  6. callnew WebpackOptionsApply().processMethod to load various built-in plug-ins

The main logic is focused on the WebpackOptionsApply class. Webpack has hundreds of built-in plug-ins, which do not need to be manually configured. WebpackOptionsApply will dynamically inject corresponding plug-ins according to the configuration content in the initialization stage, including:

  • injectionEntryOptionPluginPlug-in, processingentryconfiguration
  • According to thedevtoolValue determines which plug-in to use for subsequent processingsourcemap, optional values:EvalSourceMapDevToolPlugin,SourceMapDevToolPlugin,EvalDevToolModulePlugin
  • injectionRuntimePluginFor dynamic injection of the WebPack runtime based on code content

At this point, the compiler instance is created and the environment parameters are preset. Call compiler.compile.

/ / from webpack/lib/compiler. Js
compile(callback) {
    const params = this.newCompilationParams();
    this.hooks.beforeCompile.callAsync(params, err= > {
      // ...
      const compilation = this.newCompilation(params);
      this.hooks.make.callAsync(compilation, err= > {
        // ...
        this.hooks.finishMake.callAsync(compilation, err= > {
          // ...
          process.nextTick(() = > {
            compilation.finish(err= > {
              compilation.seal(err= >{... }); }); }); }); }); }); }Copy the code

The Webpack architecture is flexible, but at the expense of the intuitiveness of the source code, such as the initialization process described above, from creating the Compiler instance to calling the Make hook, the logical link is very long:

  • Start webpack and triggerlib/webpack.jsIn the filecreateCompilermethods
  • createCompilerMethod invocationWebpackOptionsApplyThe plug-in
  • WebpackOptionsApplyDefined in thelib/WebpackOptionsApply.jsDocuments, internal basisentryConfiguration decision injectionentryRelated plug-ins, including:DllEntryPlugin,DynamicEntryPlugin,EntryPlugin,PrefetchPlugin,ProgressPlugin,ContainerPlugin
  • EntryRelated plug-ins, such aslib/EntryPlugin.jsEntryPluginListening to thecompiler.makehook
  • lib/compiler.jscompileIntra-function callthis.hooks.make.callAsync
  • The triggerEntryPluginmakeCallback, executed in a callbackcompilation.addEntryfunction
  • compilation.addEntryThe function goes through a bunch of stuff that’s irrelevant to the main flowhookAfter that, callhandleModuleCreateFunction to start building the content

This process involves pre-embedding various plug-ins at WebPack initialization, going through 4 files and 7 jumps before getting to the theme. Foreplay is too much, and if the reader doesn’t have enough knowledge of WebPack concepts, architecture, and components, the source code reading process can be painful.

I summarize some tips and suggestions on this topic at the end of this article. If you are interested, please refer to the appendix reading module.

The construction phase

The basic flow

Have you ever thought about the question:

  • Does the Webpack compilation process parse the source code into AST? What do WebPack and Babel implement?
  • How do I identify resource dependencies during Webpack compilation?
  • Why is Webpack considered a new generation of build tools compared to grunt, gulp and other streaming build tools?

These problems, basically in the construction phase can see some clues. In the construction stage, the resources and resource dependencies were analyzed recursively from entry, and module sets and dependencies between modules were gradually built within compilation objects. The core process was as follows:

To explain, the build phase starts with the entry file:

  1. callhandleModuleCreate, based on the file typemoduleA subclass
  2. callloader-runnerThe warehouserunLoaderstranslationmoduleContent, typically translated from various resource types to JavaScript text
  3. Call Acorn to parse the JS text into an AST
  4. Iterate through the AST, triggering various hooks
    1. inHarmonyExportDependencyParserPluginPlug-in to monitorexportImportSpecifierHooks that interpret the resource dependencies corresponding to the JS text
    2. callmoduleThe object’saddDependencyAdd the dependent object tomoduleIn the dependency list
  5. After the AST traversal is complete, the AST is calledmodule.handleParseResultHandling module dependencies
  6. formoduleNew dependency, callhandleModuleCreate, control flow back to step 1
  7. After all dependencies have been resolved, the build phase ends

Module => AST => Dependences => module This requires that the final result of loaders must be a standard JavaScript syntax that can be processed by Acorn. For example, for images, the image binary needs to be converted to something like export default “data:image/ PNG; Base64, XXX “or export default “http://xxx” url format.

Compilation works recursively through this process, gradually resolving the contents of each module and module dependencies so that output can be packaged around those contents.

Example: Hierarchy

Imagine a file dependency tree like the following:

Where index.js is an entry file, which depends on a/ B files. A depends on c/ D files. After initializing the compilation environment, EntryPlugin finds the index.js file based on the entry configuration, calls the compiler. addEntry function to trigger the build process, and internally generates data structures like this:

The contents of module[index.js] and dependence objects are obtained from dependence[A.js] and dependence[B.js]. A. js, B. js, continue to call the handleParseResult function of module[index.js] according to the logic of the above flow chart, continue to process the files of A. js and B. js, recurse the above flow, and further obtain modules A and B:

C. js/ D. js dependencies are resolved from the A. js module, so we call handleParseResult on the Module [A.js] again and recurse the above process:

Once you’ve parsed all the modules at this point and found no more new dependencies, you can move on to the next step.

conclusion

Review the questions raised at the beginning of the chapter:

  • Does the Webpack compilation process parse the source code into AST? What do WebPack and Babel implement?
    • The construction phase reads the source code and parses it into an AST collection.
    • Webpack only iterates through the AST collection after reading the AST; Babel does the equivalent conversion of the source code
  • How do I identify resource dependencies during Webpack compilation?
    • Webpack traverses the AST collection process to identifyrequire/ importDetermines the module’s dependencies on other resources
  • Why is Webpack considered the next generation of build tools compared to grant, gulp and other streaming build tools?
    • Grant and Gulp only execute the developer’s predefined task flow; Webpack digs into the content of the resource and is more powerful

Generate the phase

The basic flow

The build phase revolves around Modules and the build phase revolves around chunks. After the construction phase, WebPack has enough module content and module relationship information to start generating the final resource. At the code level, we start executing the compilation.seal function:

/ / from webpack/lib/compiler. Js
compile(callback) {
    const params = this.newCompilationParams();
    this.hooks.beforeCompile.callAsync(params, err= > {
      // ...
      const compilation = this.newCompilation(params);
      this.hooks.make.callAsync(compilation, err= > {
        // ...
        this.hooks.finishMake.callAsync(compilation, err= > {
          // ...
          process.nextTick(() = > {
            compilation.finish(err= > {
              **compilation.seal**(err= >{... }); }); }); }); }); }); }Copy the code

Seal means to seal and lock, which I personally understand to be close to “putting a module in a honeypot” in the context of Webpack. The SEAL function mainly completes the transformation from Module to chunks. The core flow is as follows:

A brief overview:

  1. Build for this compilationChunkGraphObject;
  2. traversecompilation.modulesThe collection,moduleEntry/Dynamic entryRules are assigned to differentChunkObject;
  3. compilation.modulesWhen the set is traversed, it is completechunksCollection object, calledcreateXxxAssetsmethods
  4. createXxxAssetstraversemodule/chunk, the callcompilation.emitAssetsMethods useassetsInformation recordedcompilation.assetsIn the object
  5. The triggersealCallback, control flow back tocompilerobject

The key logic for this step is to organize modules into chunks according to rules. The chunk encapsulation rules built into Webpack are relatively simple:

  • entryAnd the module that entry touches to combine into onechunk
  • Modules introduced using dynamic import statements are grouped into onechunk

Chunk is the basic unit of output. By default, these chunks correspond to the final output resources one by one. According to the above rules, it can be roughly deduced that an entry will correspond to a resource, and modules introduced through dynamic import statements will also correspond to a resource.

Example: Multi-entry packaging

If there is such a configuration:

const path = require("path");

module.exports = {
  mode: "development",
  context: path.join(__dirname),
  entry: {
    a: "./src/index-a.js",
    b: "./src/index-b.js",
  },
  output: {
    filename: "[name].js",
    path: path.join(__dirname, "./dist"),
  },
  devtool: false,
  target: "web",
  plugins: [],
};
Copy the code

There are two entries in the instance configuration, corresponding to the file structure:

Index-a depends on C and dynamically introduces e; Index -b depends on c/ D. According to the above rules:

  • entryAnd the modules that entry touches, and combine them into a chunk
  • Modules introduced by dynamic import statements are grouped into one chunk

The resulting chunks structure is:

That is, chunk[a] contains two modules, index- A/C, according to the dependency relationship. Chunk [B] contains c/index- B/D modules; Chunk [e-hash] Dynamically imports the chunk corresponding to E.

I don’t know if you have noticed that chunk[a] and chunk[b] contain C at the same time. This problem may be a multi-page application in a specific business scenario. All pages depend on the same base library, so the entry corresponding to all pages will contain the base library code, which is a waste of time. To solve this problem, WebPack provides plug-ins such as CommonsChunkPlugin and SplitChunksPlugin to further optimize the chunks structure beyond the basic rules.

SplitChunksPluginThe role of

SplitChunksPlugin is a good example of the high scale of the WebPack architecture. We mentioned above that chunks are organized by entry/dynamic introduction in the main WebPack process, which inevitably leads to unnecessary repackaging. Webpack solves this problem in the form of plug-ins.

A review of the compilation.seal function code can be summarized in four steps:

  1. traversecompilation.modules, record the module andchunkRelationship between
  2. Triggers various module optimization hooks, which are optimized primarily for module dependencies
  3. traversemoduleBuilding a chunk Collection
  4. Trigger various optimization hooks

The previous steps 1-3 are implementations of the pre-processing + chunks default rule, which are beyond our scope. Here we focus on the optimizeChunks hook triggered by step 4. SplitChunksPlugin uses this hook to analyze chunks and add common chunks according to configuration rules:

module.exports = class SplitChunksPlugin {
  constructor(options = {}) {
    // ...
  }

  _getCacheGroup(cacheGroupSource) {
    // ...
  }

  apply(compiler) {
    // ...
    compiler.hooks.thisCompilation.tap("SplitChunksPlugin".(compilation) = > {
      // ...
      compilation.hooks.optimizeChunks.tap(
        {
          name: "SplitChunksPlugin".stage: STAGE_ADVANCED,
        },
        (chunks) = > {
          // ...}); }); }};Copy the code

Does that make sense? The high extensibility of WebPack plug-in architecture makes the whole compilation main process can be solidified, branch logic and detail requirements are “outsourced” to a third party to achieve, this set of rules set up a huge Webpack ecosystem, more details about the plug-in architecture are described in the plugin section below, which is skipped here.

Write to the file system

After the construction phase, the compilation knows the contents and dependencies of the resource modules, and thus what the “inputs” are. After the SEAL stage, the compilation knows the map of the output of the resource, that is, how to “output” it: which modules are “bound” to those modules and where to output it. General data structure after SEAL:

compilation = { // ... modules: [ /* ... */ ], chunks: [ { id: "entry name", files: ["output file name"], hash: "xxx", runtime: "xxx", entryPoint: {xxx} // ... }, // ... ] };Copy the code

Seal is done, and then call the compiler. EmitAssets function, function calls compiler. The internal outputFileSystem. The writeFile method will assets collection written to the file system, realize the logic is more twists and turns, But it doesn’t have much to do with the main process, so I won’t go into it here.

Resource pattern transfer

OK, the main process of logical structure has been sorted out above. Here, we re-examine the whole process from the perspective of resource form transfer to deepen our understanding:

  • compiler.makePhase:
    • entryFile todependenceObject form joincompilationThe dependency list of,dependenceObject records haveentryThe type and path of the
    • According to thedependenceCall the corresponding factory function to createmoduleObject, and then read inmoduleCorresponding file content, callloader-runnerThe content is transformed, and if there are other dependencies as a result, the dependent resource is read in, and the process is repeated until all dependencies are converted tomodule
  • compilation.sealPhase:
    • traversemoduleSet, according toentryThe way resources are configured and introduced willmoduleAssign to differentchunk
    • traversechunkCollection, callcompilation.emitAssetMethods markingchunkThe output rule is converted toassetsA collection of
  • compiler.emitAssetsPhase:
    • willassetsWrite to the file system

The Plugin parsing

There’s a lot of literature on the web that categorizes WebPack’s plug-in architecture as an “event/subscription” model, which I think is a bit biased. The subscription pattern is a loosely coupled architecture in which publishers only publish event messages at specific times, and subscribers do not or rarely interact directly with events. For example, when we use HTML events, most of the time we only trigger business logic at this time and rarely invoke context operations. The hook system of Webpack is a strong coupling architecture, which will attach enough context information when triggering the hook at a specific time. The hook callback defined by the plug-in can or can only produce side effect with the data structure and interface interaction behind these contexts, thus affecting the compilation state and subsequent process.

To learn about plug-in architecture, you need to understand three key issues:

  • WHAT: WHAT are plug-ins
  • WHEN: What hooks will trigger at what point
  • HOW: HOW do you affect compilation state in hook callbacks

What: What are plug-ins

Morphologically, a plug-in is usually a class with the apply function:

class SomePlugin {
    apply(compiler){}}Copy the code

The apply function is run with the parameter Compiler, from which hook objects can be called to register various hook callbacks, for example: Compiler. Hooks. Make tapAsync, name, is it make hook tapAsync defines the call of the hook, is constructed from the webpack plug-in architecture based on this model, the plugin developers can use this model in the hook callback, insert the specific code. Various webpack built-in objects have hooks attributes, such as the Compilation object:

class SomePlugin {
    apply(compiler) {
        compiler.hooks.thisCompilation.tap('SomePlugin'.(compilation) = > {
            compilation.hooks.optimizeChunkAssets.tapAsync('SomePlugin'.() = >{}); }}})Copy the code

The core hook logic is defined in the Tapable repository, which internally defines the following types of hooks:

const {
        SyncHook,
        SyncBailHook,
        SyncWaterfallHook,
        SyncLoopHook,
        AsyncParallelHook,
        AsyncParallelBailHook,
        AsyncSeriesHook,
        AsyncSeriesBailHook,
        AsyncSeriesWaterfallHook
 } = require("tapable");
Copy the code

Different types of hooks are called in slightly different ways depending on their parallelism, fusing, synchronous asynchrony, and so on. Plugin developers need to write different interaction logic based on these features.

When: When does the hook trigger

Now that you know the basic shape of the WebPack plug-in, you need to figure out: What hooks do WebPack trigger at what point in time? After all, there are 237 hooks in the source code, but the official website only introduces less than 100 hooks, and the description of each hook on the official website is too brief. Personally, I did not learn much after reading it, so it is necessary to talk about this topic. Let’s start with a few examples:

  • compiler.hooks.compilation
    • Timing: Triggered after compilation is started and compilation objects are created
    • Parameter: The compilation object currently compiled
    • Example: Many plug-ins get compilation instances based on this event
  • compiler.hooks.make:
    • Timing: Triggered when compilation officially begins
    • Parameter: also currently compiledcompilationobject
    • Example: webpack built-inEntryPluginImplemented based on this hookentryInitialization of a module
  • compilation.hooks.optimizeChunks
    • Timing:sealFunction,chunkTriggered when the collection is built
    • Parameters:chunksThe collection andchunkGroupsA collection of
    • Example:SplitChunksPluginThe plug-in is implemented based on this hookchunkBreak up the optimization
  • compiler.hooks.done:
    • Timing: Triggered after compilation is complete
    • Parameters:statsObject that contains various statistics during compilation
    • Example:webpack-bundle-analyzerThe plug-in implements packaging analysis based on this hook

This is my summary of the three learning elements of hooks: firing timing, passing parameters, and sample code.

trigger

The firing timing is closely related to the working process of WebPack, roughly from start to finish, and the Compiler object fires the following hooks successively:

The compilation object fires one by one:

So, by understanding the main flow of webPack work, you can basically figure out “what hooks are triggered when”.

parameter

Passing parameters associated with the hook strength of concrete, the website did not make further explanation to this aspect, I practice is directly in the source code search call statements, such as for compilation. Hooks. OptimizeTree, The call code can be found by searching the Webpack source for the iterate.optimizetree. Call keyword:

// lib/compilation.js#2297
this.hooks.optimizeTree.callAsync(this.chunks, this.modules, err= >{});Copy the code

Given the context of the code, you can tell that you are passing an optimized set of chunks and modules.

Find the sample

Webpack’s hooks vary in complexity, and I think the best way to learn is to query how they are used in other plug-ins with a purpose. For example, inside the compilation.seal function, there are a pair of seemingly dual hooks called optimizeModules and afterOptimizeModules, OptimizeModules are literally meant to optimizeModules that have already been compiled. What about afterOptimizeModules?

The only use found in the Webpack source is ProgressPlugin, which follows roughly:

compilation.hooks.afterOptimizeModules.intercept({
  name: "ProgressPlugin".call() {
    handler(percentage, "sealing", title);
  },
  done() {
    progressReporters.set(compiler, undefined);
    handler(percentage, "sealing", title);
  },
  result() {
    handler(percentage, "sealing", title);
  },
  error() {
    handler(percentage, "sealing", title);
  },
  tap(tap) {
    // p is percentage from 0 to 1
    // args is any number of messages in a hierarchical matter
    progressReporters.set(compilation.compiler, (p, ... args) = > {
      handler(percentage, "sealing", title, tap.name, ... args); }); handler(percentage,"sealing", title, tap.name); }});Copy the code

Almost as you might guess, afterOptimizeModules are designed to notify the end of optimization behavior.

Although Apply is a function, it is designed for input only, webpack does not care for output, so the plug-in can only change the compilation behavior by calling various methods of the type entity or changing the configuration information of the entity. Such as:

  • Compilation. AddModule: Add modules. You can add custom modules in addition to the existing module building rules
  • Compilation. EmitAsset: The ability to write content to a specific path

Here, the working mechanism and writing method of the plug-in has a very superficial introduction, back to carry out the details of it.

How: How does this affect compilation status

Now that we’ve solved these two problems, we can understand “how to insert certain logic into the WebPack build process,” but what’s important is how to affect the build state? Note that webPack’s plug-in architecture is very different from the usual subscribe/publish model. It is a very strongly coupled design. Webpack decides when and how to execute hooks callbacks. Within the hooks callback, side effects can be generated on Webpack by modifying the state, calling the context API, and so on.

For example, the EntryPlugin plugin:

class EntryPlugin {
  apply(compiler) {
    compiler.hooks.compilation.tap(
      "EntryPlugin".(compilation, { normalModuleFactory }) = >{ compilation.dependencyFactories.set( EntryDependency, normalModuleFactory ); }); compiler.hooks.make.tapAsync("EntryPlugin".(compilation, callback) = > {
      const { entry, options, context } = this;

      const dep = EntryPlugin.createDependency(entry, options);
      compilation.addEntry(context, dep, options, (err) = >{ callback(err); }); }); }}Copy the code

The code snippet above calls two interfaces that affect the state of the Compilation object:

  • compilation.dependencyFactories.set
  • compilation.addEntry

The main point to understand here is that WebPack passes context information to the hook callback, either as a parameter or as this (Compiler object), where methods of the context object can be called or properties of the context object can be directly modified. Side effect the original process. So in order to write plug-ins proficiently, we need to understand not only when to call, but also which apis we can use. For example:

  • compilation.addModule: Add modules that can be added to the originalmoduleAdd custom modules to build rules
  • compilation.emitAssetWrite the content to a specific path
  • compilation.addEntry: Add entry, functional and direct definitionentryConfiguration is the same
  • module.addError: Adds compilation error information
  • .

Loader is introduced

Loader functions and implementation is relatively simple, easy to understand, so a brief introduction to the line. Review where the Loader takes effect in the compilation process:

In the flow diagram, runLoaders will call a collection of loaders configured by the user to read and translate the resource. The previous content can be very strange, but the translation should theoretically output standard JavaScript text or AST objects so that WebPack can continue to handle module dependencies.

After understanding the basic logic, loader’s responsibility is clear, which is to convert content A to content B. However, there are many specific usage aspects, such as pitch, pre, post, inline and so on, which are used for various scenarios.

To help you understand, here is a supplementary example: Webpack case – Vue-Loader principle analysis.

The appendix


Source code Reading Tips

  • Easy: choose soft persimmon pinch, such as initialization process though, but the concept is relatively at least, the most clear logic, then start from here, find out the whole working process can be learned some general webpack routines, such as hook design and function, encoding rules, naming conventions, plug-ins loading logic, etc., the equivalent of into the door first
  • Learn to debug:multi-purposendbA single point of debug tracks how your application is running, and while there are many ways to debug Node, I personally recommend itndb, flexible, simple, cooperatedebuggerStatements are killer
  • Understand architecture:The WebPack architecture can be simplified to some extentcompiler + compilation + plugins, there will only be one during the webPack runcompiler; And every compile — including callscompiler.runFunction orwatch = trueWhen the file changes, one is createdcompilationObject. Understanding the design, responsibility, and collaboration of these three core objects is pretty much the core logic of WebPack
  • Catch the big and let the small go:The key to the plugin is the “hook”, which I recommend to focus on strategically and ignore tactically! After all, hooks are the key concept of Webpack and the foundation of the whole plug-in mechanism. It is impossible to bypass hooks by learning WebPack, but the corresponding logical jump is too convoluted and not intuitive. If you keep focusing on this point when looking at the code, the complexity will increase dramatically.
    • Take a close look at the tapable repository documentation, or take a cursory looktapableUnderstand synchronous hook, asynchronous hook, Promise hook, serial hook, parallel hook and other concepts, righttapableThe event model provided has a more refined perception called strategic importance
    • Don’t panic when you encounter a hook that you don’t understand. In my experience, I don’t even know what this class does. It’s really hard to understand these hooksdebuggerStatement single point debugging, by the time you’ve figured out the following logic, chances are you’ll also know what the hook means. This is called tactical neglect
  • Be curious:During the learning process, I keep strong curiosity and toughness. I am good at and dare to ask questions, and then summarize my own answers based on source code and community materials. There may be many questions, such as:
    • Loader design pre, pitch, POST, inline?
    • compilation.sealThere are a lot of optimized hooks inside the function. Why are they so detailed? What do Webpack designers expect from different hooks?
    • Why do you need so manymoduleThe subclass? When are these subclasses used?

ModuleModuleA subclass

As can be seen from the above, the core process of WebPack construction basically revolves around Module. I believe that readers who have contacted and used Webpack should have a perceptual understanding of Module, but the logic of module implementation is very complicated and heavy.

Take [email protected] for example, there are 54 subclasses that directly or indirectly inherit from Module (webpack/lib/ module.js file) :

It’s too tiring to figure out what each of these classes does, so we need to get down to the nitty-gritty: What does a Module do?

Module is the basic unit of Webpack resource processing. It can be considered that Webpack’s path parsing, reading, translating, analyzing and packaging output of resources are all carried out around Module. There are a lot of articles that say module = file, but this is not accurate. For example, the subclass AsyncModuleRuntimeModule is just a piece of built-in code, a resource and not simply equivalent to an actual file.

Webpack has strong scalability, including module processing logic. For example, the entry file is a common JS, and the NormalModule object is created first. When parsing the AST, it is found that the file also contains asynchronous loading statements, such as Requere.Ensure. The corresponding AsyncModuleRuntimeModule module is created to inject asynchronously loaded template code. The 54 Module subclasses in the class diagram above are designed for various scenarios.

Continue to output in-depth front-end technology articles, follow the wechat public account: