If you find the article useful, you are welcome to like and follow it, but writing is really not easy. Reprinting in any form is prohibited without the author's consent! ! !
background
Webpack is particularly hard to learn! ! !
After version 5.0, the Webpack feature set has become very large, including: module packaging, code segmentation, on-demand loading, HMR, Tree-shaking, file monitoring, sourcemap, Module Federation, devServer, DLL, multi-process, etc. To achieve these functions, the amount of code in webpack has reached an astonishing level:
- 498 JS files
- 18862 line comment
- 73548 lines of code
- 54 module types
- 69 dependency types
- 162 built-in plugins
- 237 hooks
Under this order of magnitude, the cost of reading, analyzing, and learning the source code is very high, coupled with the unclear documentation on the webpack official website, resulting in extremely high costs of learning and getting started with webpack. To this end, the community has derived various scaffolds around Webpack, such as vue-cli , create-react-app , to solve the problem of "use".
But this has led to a new problem. Most people gradually become configuration engineers in terms of engineering, staying at the stage of "can use and match" but don't know how to turn in the black box, and they will be blind when they encounter specific problems. Up:
- I want to upgrade the basic library, but I can’t run due to compatibility issues, so I just give up.
- I want to optimize the compilation performance, but I don’t know the internal principles and I can’t get started
The reason is that there is no necessary overall understanding of the internal operating mechanism of webpack, and the problem can not be quickly located. Yes, even the essence of the problem is often not seen. The so-called cannot see the essence through the phenomenon, what is the essence? I personally abstract the entire huge system of webpack into three aspects of knowledge:
- build core processes
- role loader of
- plugin architecture and common routines
The three work together to form the main framework of webpack:
After understanding these three pieces of content, even if you enter the door, you have the most basic understanding of Webpack, and you can find out by the picture if you encounter any problems in your work. I should add that as an introductory tutorial, this article will not expand too many webpack code-level details-my energy does not allow it, so readers do not need to read a bunch of text to produce a particularly large psychological burden.
Core process analysis
First of all, we have to understand one point, the core function of Webpack:
At its core, webpack is a static module bundler for modern JavaScript applications.
That is, various types of resources, including pictures, css, js, etc., are translated, combined, spliced, and generated into bundler files in JS format. official website home page animation is vividly expressed this:
The core of this process completes the content conversion + resource merging , and the realization includes three stages:
Initialization phase:
- initialization parameters : read from the configuration file, configuration object, and Shell parameters, and combine with the default configuration to get the final parameters
- create a compiler object : use the parameters obtained in the
Compiler
step to create a 06095019a05c35 object - Initialize the compilation environment : Including injecting built-in plug-ins, registering various module factories, initializing RuleSet collection, loading configuration plug-ins, etc.
- start compiling : execute the
run
method of thecompiler
- determine the entrance : Find out all the entry files
entry
in the configuration,compilition.addEntry
convert the entry files into thedependence
object
Construction phase:
- compilation module (make) : Create a
module
object based on theentry
corresponding todependence
, call theloader
to translate the module into standard JS content, call the JS interpreter to convert the content into an AST object, find out which module the module depends on, and recurse the original Step until all the files dependent on the entry have been processed in this step - completes the module compilation : After recursively processing all reachable modules in the previous step, the translated content of each module and the dependency diagram
- compilation module (make) : Create a
Generation stage:
- output resource (seal) : According to the dependency between the entry and the module, assemble into a
Chunk
that contains multiple modules, and thenChunk
into a separate file and add it to the output list. This step can be modified Last chance to output content - Write into the file system (emitAssets) : After determining the output content, determine the output path and file name according to the configuration, and write the file content to the file system
- output resource (seal) : According to the dependency between the entry and the module, assemble into a
The single build process is executed in order from top to bottom. The details will be discussed below. Before that, students who are not familiar with the various technical terms mentioned above can take a look at the introduction:
Entry
: Compilation entry, starting point for webpack compilationCompiler
: Compiler manager, after webpack is started, it will createcompiler
object, which will survive until the end and exitCompilation
: a single editing process manager, such aswatch = true
, there is only onecompiler
running process, but every time a file change triggers a recompilation, a newcompilation
object will be createdDependence
: dependent objects, webpack records the dependencies between modules based on this typeModule
: All resources inside webpack will exist in the form of "module" objects, and all operations, translation, and merging of resources are performed with "module" as the basic unitChunk
: When compiled ready for output, webpack willmodule
organized according to specific rules as a onechunk
, thesechunk
some extent correspond with the final outputLoader
: The resource content converter is actually a converter that converts content A to BPlugin
: During the webpack building process, corresponding events will be broadcast at specific times. The plug-in listens to these events and intervenes in the compilation process at a specific point in time.
The webpack compilation process revolves around these key objects. For more detailed and complete information, please refer to Webpack Knowledge Graph .
Initialization phase
Basic process
Learning the source code of a project usually starts from the entrance, and slowly finds out the routine according to the diagram, so let's take a look at the initialization process of webpack:
explain:
process.args + webpack.config.js
into user configuration- Call
validateSchema
verify the configuration - Call
getNormalizedWebpackOptions + applyWebpackOptionsBaseDefaults
merge the final configuration - Create
compiler
object - Traverse the user-defined
plugins
collection and execute theapply
method of the plug-in - Call
new WebpackOptionsApply().process
method, load various built-in plug-ins
The main logic is concentrated in the WebpackOptionsApply
class. Webpack has hundreds of plug-ins built in. These plug-ins do not need to be manually configured. WebpackOptionsApply
will dynamically inject the corresponding plug-ins according to the configuration content during the initialization phase, including:
- Inject the
EntryOptionPlugin
plug-in, handle theentry
configuration - The
devtool
value determination processing that the subsequent plugsourcemap
, optional values:EvalSourceMapDevToolPlugin
,SourceMapDevToolPlugin
,EvalDevToolModulePlugin
- Inject
RuntimePlugin
, which is used to dynamically inject the webpack runtime according to the code content
At this point, the compiler
instance is created, and the corresponding environment parameters are also preset, and then the compiler.compile
function is called:
// 取自 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 => {...});
});
});
});
});
});
}
The Webpack architecture is very flexible, but at the cost of sacrificing the intuitiveness of the source code. For example, the initialization process mentioned above, from creating compiler
instance of 06095019a0616c to calling the make
hook, the logical link is very long:
- Start webpack and trigger the
createCompiler
method in thelib/webpack.js
file createCompiler
method internally calls theWebpackOptionsApply
pluginWebpackOptionsApply
defined in thelib/WebpackOptionsApply.js
file, internally according to theentry
configuration, it is decided to inject theentry
related plug-ins, including:DllEntryPlugin
,DynamicEntryPlugin
,EntryPlugin
,PrefetchPlugin
,ProgressPlugin
ContainerPlugin
Entry
related plugins, such aslib/EntryPlugin.js
ofEntryPlugin
monitorcompiler.make
hooklib/compiler.js
in thecompile
function ofthis.hooks.make.callAsync
- Trigger
EntryPlugin
ofmake
callbacks, perform callbackcompilation.addEntry
function compilation.addEntry
hook
that has nothing to do with the main process, then call thehandleModuleCreate
function to formally start building the content
This process needs to pre-bury various plug-ins when webpack is initialized. After 4 files, 7 jumps before entering the theme, the foreplay is too much. If the reader does not have enough understanding of the concept, architecture, and components of webpack, The source code reading process will be painful.
Regarding this issue, I summarized some tips and suggestions at the end of the article. Those who are interested can slide to the appendix reading module.
Construction phase
Basic process
Have you ever thought about such a problem:
- Will the Webpack compilation process parse the source code into AST? What do webpack and babel achieve respectively?
- How to identify the dependence of resources on other resources during Webpack compilation?
- Compared to streaming build tools such as grunt and gulp, why is webpack considered a new generation of build tools?
These problems can basically be seen in the construction phase. Construction of phase from entry
start recursive resolution resource dependency and resources, in compilation
gradually constructed within the object module
collection and module
dependencies between, core processes:
To explain, the build phase starts with the entry file:
- Call
handleModuleCreate
module
subclass according to the file type - Call loader-runner warehouse
runLoaders
translatemodule
content, usually from various resource types into JavaScript text - Call acorn to parse JS text into AST
Traverse the AST, trigger various hooks
exportImportSpecifier
hook in theHarmonyExportDependencyParserPlugin
plug-in, and interpret the resource dependencies corresponding to the JS text- Call
module
object toaddDependency
the dependent object to themodule
dependency list
- After the AST traversal is completed, call
module.handleParseResult
process module dependencies - For
module
new dependence, callinghandleModuleCreate
, control flows back to the first step - After all dependencies are resolved, the build phase ends
In this process, the data flow module => ast => dependences => module
, first transfer to AST and then find dependencies from AST. This requires loaders
final result must be processed can be processed acorn standard JavaScript syntax, for example, pictures, images need to be converted from binary to similar export default "data:image/png;base64,xxx"
such base64 format or export default "http://xxx"
such url format.
compilation
processed recursively according to this process, and the content of each module and the module
gradually parsed, and then the content can be packaged and output later.
Example: hierarchical progression
If there is a file dependency tree as shown in the figure below:
Among them, index.js
is the entry
file, which depends on the a/b file; a depends on the c/d file. After initializing the compilation environment, EntryPlugin
finds the index.js
file according to the entry
configuration, and calls the compilation.addEntry
function to trigger the build process. After the build is completed, the internal data structure will be generated:
At this time module[index.js]
and the corresponding dependent objects dependence[a.js]
and dependence[b.js]
. The OK, which give the next clue: a.js, b.js, according to the above logic flowchart continues to call module[index.js]
of handleParseResult
function, processing continues a.js, b.js file, the above-described recursive process, further a, b Module:
From the a.js module, the c.js/d.js dependency is parsed, so continue to call module[a.js]
of handleParseResult
, and then recurse the above process:
After analyzing all the modules here, and finding that there are no more new dependencies, you can continue to advance and enter the next step.
to sum up
Recall the questions mentioned at the beginning of the chapter:
Will the Webpack compilation process parse the source code into AST? What do webpack and babel achieve respectively?
- The source code will be read during the build phase and parsed into an AST collection.
- Webpack only traverses the AST collection after reading the AST; babel performs equivalent conversion of the source code
How to identify the dependence of resources on other resources during Webpack compilation?
- In the process of Webpack traversing the AST collection, it recognizes
require/ import
and determines the dependency of the module on other resources
- In the process of Webpack traversing the AST collection, it recognizes
Compared with streaming build tools such as grant and gulp, why is webpack considered a new generation build tool?
- Grant and Gulp only execute the task flow predefined by the developer; while webpack handles the content of the resource in-depth and is more powerful in function
Generation phase
Basic process
The construction phase revolves around module
, and the generation phase revolves around chunks
. After the construction phase, webpack gets enough module content and module relationship information, and then it starts to generate the final resources. At the code level, it starts to execute the function compilation.seal
// 取自 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 => {...});
});
});
});
});
});
}
seal
originally intended to be sealed and locked. I personally understand that in the context of , it is close to 16095019a068b3 "Put the module into the honeypot" . seal
function mainly completes module
to chunks
, the core process:
Simply sort out:
ChunkGraph
object compiled this time;- Traverse the
compilation.modules
collection, and assignmodule
to differentChunk
objects according toentry/dynamically introduced rules;
compilation.modules
traversing the 06095019a0694f collection, get the completechunks
collection object, call thecreateXxxAssets
methodcreateXxxAssets
traversesmodule/chunk
and calls thecompilation.emitAssets
method to record theassets
compilation.assets
object- Trigger
seal
callback, control flow returns tocompiler
object
The key logic of this step is to chunks
module
according to the rules. The built-in chunk
packaging rules of webpack are relatively simple:
entry
and the modules reached by entry are combined into achunk
- Modules introduced using dynamic introduction statements, each combined into a
chunk
chunk
is the basic unit of output. By default, these chunks
correspond to the final output resource one-to-one. According to the above rules, it can be roughly deduced that a entry
will be packaged out corresponding to a resource, and the module introduced through the dynamic introduction statement also Corresponding resources will be packaged out, let's look at an example.
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: [],
};
There are two entries in the example configuration, the corresponding file structure:
index-a
depends on c and dynamically introduces e; index-b
depends on c/d. According to the rules stated above:
-
entry
and the modules reached by entry, combined into a chunk - uses the modules introduced by the dynamic import statement, and each is combined into a chunk
The generated chunks
structure is:
I.e. according to the dependencies, chunk[a]
contains index-a/c
two modules; chunk[b]
contains c/index-b/d
three modules; chunk[e-hash]
dynamic introduced e
corresponding chunk.
I don’t know if you have noticed that chunk[a]
and chunk[b]
contain c at the same time. When this problem is put into a specific business scenario, it may be that a multi-page application, all pages rely on the same basic library, then the entry
corresponding to all these pages will contain it. Basic library code, isn't it a waste? In order to solve this problem, webpack provides some plug-ins such as CommonsChunkPlugin
and SplitChunksPlugin
, which further optimize the structure of chunks
The role of SplitChunksPlugin
SplitChunksPlugin
is an excellent example of webpack highly scalable architecture, we said above, the main flow webpack which is based entry / dynamic organization introduced in both cases
chunks
, which will inevitably lead to some unnecessary repetition package, webpack through plug-ins Form to solve this problem.
Looking back compilation.seal
the code of the 06095019a06c0c function, roughly it can be sorted into four steps:
- Traverse
compilation.modules
and record the relationship between thechunk
- Trigger various module optimization hooks, this step is mainly optimized for module dependencies
- Traverse
module
build a chunk collection - Trigger various optimization hooks
The above 1-3 are the implementation of the preprocessing + chunks default rule, which is beyond the scope of our discussion. Here we focus on the optimizeChunks
hook triggered in the fourth step. At this time, the logic of the main process has been run, and the chunks
set SplitChunksPlugin
is used. This hook analyzes chunks
collection and adds some general chunks according to the 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) => {
// ...
}
);
});
}
};
Understand? The high scalability of the webpack plug-in architecture allows the entire compilation process to be solidified. The branch logic and detailed requirements are "outsourced" to be implemented by a third party. This set of rules sets up a huge webpack ecosystem. More about the plug-in architecture For more details, the following plugin
has a detailed introduction, so skip it here.
Write to the file system
After the construction phase, compilation
the content and dependencies of the resource module, and knows what the "input" is; and after the seal
phase is processed, compilation
knows the map of the resource output, that is, knows how to "output": which modules Where to "bind" with those modules to output. seal
approximate data structure after 06095019a06dc8:
compilation = {
// ...
modules: [
/* ... */
],
chunks: [
{
id: "entry name",
files: ["output file name"],
hash: "xxx",
runtime: "xxx",
entryPoint: {xxx}
// ...
},
// ...
],
};
seal
end of 06095019a06e00, the function compiler.outputFileSystem.writeFile
method assets
compiler.emitAssets
function to write the 06095019a06e03 collection to the file system. The realization logic is relatively tortuous, but it has nothing to do with the main process, so I won't expand it here.
Resource form circulation
OK, the logic level structure of the main process has been sorted out above, here combined with the resource form circulation perspective to re-examine the entire process to deepen understanding:
compiler.make
stage:entry
filedependence
Eligibility formcompilation
dependency list,dependence
the object is recordedentry
information type, path, etc.- According to
dependence
call the corresponding factory function to create themodule
object, and then read in the file content correspondingmodule
loader-runner
to convert the content, if the conversion result has other dependencies, continue to read the dependent resources, repeat this process until all dependencies are converted tomodule
compilation.seal
stage:- Traverse the
module
module
to differentchunk
according to theentry
configuration and the way of introducing resources - Traversing
chunk
set, callcompilation.emitAsset
method for markingchunk
output rules, i.e. intoassets
set
- Traverse the
compiler.emitAssets
stage:assets
to the file system
Plugin analysis
Many information on the Internet classifies the plug-in architecture of webpack as an "event/subscription" model. I think this generalization is biased. The subscription mode is a loosely coupled architecture. The publisher only publishes event messages at a specific time. Subscribers do not or rarely interact directly with the event. For example, when we usually use HTML events, it is often only at this time. Trigger business logic, rarely call context operations. The webpack's hook system is a strongly coupled architecture. It will be accompanied by sufficient context information when the hook is triggered at a specific time. In the hook callback defined by the plug-in, it can and can only interact with the data structure and interface behind these contexts to produce The side effect , which in turn affects the compilation status and subsequent processes.
To learn the plug-in architecture, you need to understand three key issues:
- WHAT: What is a plug-in
- WHEN: What hook will be triggered at what time
- HOW: How affect the compilation status in the hook callback?
What: What is a plug-in
From the morphological point of view, the plug-in is usually a class apply
class SomePlugin {
apply(compiler) {
}
}
apply
function will get the parameter compiler
when it runs. From this, you can call the hook
object to register various hook callbacks, for example: compiler.hooks.make.tapAsync
, where make
is the hook name, and tapAsync
defines how the hook is called. Webpack's plug-in architecture is based on this model. Built, plug-in developers can use this mode to insert specific code in the hook callback. Various built-in objects of hooks
have 06095019a07117 attributes, such as compilation
objects:
class SomePlugin {
apply(compiler) {
compiler.hooks.thisCompilation.tap('SomePlugin', (compilation) => {
compilation.hooks.optimizeChunkAssets.tapAsync('SomePlugin', ()=>{});
})
}
}
The core logic of the hook is defined in the Tapable warehouse, and the following types of hooks are defined internally:
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");
Different types of hooks will have slightly different calling methods according to their parallelism, fusing method, synchronization and asynchrony. Plug-in developers need to write different interaction logics according to these characteristics. This part is also very rich. Let’s talk later.
When: When will the hook be triggered
After understanding the basic form of webpack plugins, the next question needs to be clarified: at what time will webpack trigger which hooks? I think this piece is the most knowledgeable part. After all, there are 237 hooks in the source code, but the official website only introduces less than 100, and the official website’s description of each hook is too short, personally I haven’t read it. It's so rewarding, so it's necessary to start talking about this topic. Let's look at a few examples:
compiler.hooks.compilation
:- Timing: Triggered after the compilation is started and the compilation object is created
- Parameters: the currently compiled compilation object
- Example: Many plugins obtain compilation instances based on this event
compiler.hooks.make
:- Timing: Triggered when compiling officially starts
- Parameters: also the current compiled
compilation
object - Example: The built-in
EntryPlugin
implements the initialization of theentry
module based on this hook
compilation.hooks.optimizeChunks
:- Timing: In the
seal
function, it will be triggered after thechunk
- Parameters:
chunks
collection andchunkGroups
collection - Example:
SplitChunksPlugin
plug-in realizeschunk
split optimization based on this hook
- Timing: In the
compiler.hooks.done
:- Timing: Triggered after compilation is complete
- Parameters:
stats
object, including various statistical information during the compilation process - Example:
webpack-bundle-analyzer
plug-in realizes package analysis based on this hook
These are the three learning elements of the hook I summarized: trigger timing, passing parameters, and sample code.
Trigger timing
The timing of the trigger is closely related to the webpack working process. Generally, from start to finish, the compiler
object triggers the following hooks successively:
The compilation
object is triggered successively:
Therefore, if you understand the main process of webpack work mentioned earlier, you can basically figure out "when will the hooks be triggered".
parameter
Passing parameters are strongly related to specific hooks. The official website does not provide further explanation on this aspect. My approach is to directly search for the call statement in the source code. For example, for compilation.hooks.optimizeTree
, you can search for the hooks.optimizeTree.call
keyword in the webpack source code to find the call code :
// lib/compilation.js#2297
this.hooks.optimizeTree.callAsync(this.chunks, this.modules, err => {
});
Combined with the context where the code is located, it can be determined that the optimized collections chunks
and modules
Find example
Webpack's hooks vary in complexity. I think the best way to learn is to query how to use these hooks in other plugins with a purpose. For example, in compilation.seal
internal functions have optimizeModules
and afterOptimizeModules
pair looks dual hook, optimizeModules
literally can be understood for optimization has compiled the modules
, that afterOptimizeModules
it?
The only use searched from the webpack source code is ProgressPlugin
, and the general logic is as follows:
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);
}
});
Basically, it can be guessed that the afterOptimizeModules
is to notify the end of the optimization behavior.
apply
is a function, it only has input from the design, and webpack does not care output. Therefore, the plug-in can only call various methods of the type entity to change the configuration information of the entity and change the compilation behavior. E.g:
- compilation.addModule : add a module, you can add custom modules in addition to the original module construction rules
- compilation.emitAsset : The literal translation is "submit assets", the function can understand to write the content to a specific path
At this point, the working mechanism and writing of the plug-in have been introduced in a very superficial way. Let's go back and talk about it in detail.
How: How to affect compilation status
After solving the above two problems, we will be able to understand "how to insert specific logic into the webpack compilation process", and then the key point is how to affect the compilation status? To emphasize, webpack's plug-in system is very different from the usual subscription/publishing model. It is a very strongly coupled design. Webpack determines when and how to execute hooks callbacks; and inside the hooks callbacks, you can pass Modifying the state, calling the context api, etc. will generate side effect .
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);
});
});
}
}
The above code snippet calls two interfaces that affect the state of the compilation
compilation.dependencyFactories.set
compilation.addEntry
The specific meaning of the operation can be ignored first. The point to understand here is that webpack will pass the context information this
(compiler object). In the callback, you can call the method of the context object or directly modify the properties of the context object. In this way, a side effect is generated on the original process. So if you want to write a plug-in skillfully, in addition to understanding the timing of the call, you also need to understand which APIs we can use, for example:
compilation.addModule
: Add modules, you can add custom modules in addition to themodule
compilation.emitAsset
: Literally translated as "submit assets", the function can understand and write the content to a specific pathcompilation.addEntry
: Add entry, the function is the same as the configuration ofentry
module.addError
: Add compilation error message- ...
Loader introduction
The role and implementation of Loader are relatively simple and easy to understand, so just a brief introduction. Recall where the loader takes effect in the compilation process:
In the flowchart, runLoaders
will call the loader set configured by the user to read and translate resources. The previous content can be strange, but in theory, standard JavaScript text or AST objects should be output after translation, so that webpack can continue to process module dependencies.
After understanding this basic logic, the loader's responsibilities are clearer. It is nothing more than converting content A into content B, but it is quite specific at the level of specific usage. There are concepts such as pitch, pre, post, inline and so on. Cope with various scenarios.
To help understand, here is an example: Webpack case-analysis of the principle of vue-loader .
appendix
Source code reading skills
- avoid the heavy and light: pick the soft persimmon, for example, although the initialization process is winding, it is relatively the least conceptual and logical. From here to understand the entire working process, you can learn some common routines of webpack, such as hooks Design and function, coding rules, naming conventions, loading logic of built-in plug-ins, etc., are equivalent to entering a door first
- learn to debug: use the
ndb
single-point debugging function to track the running of the program. Although there are many ways to debug node, I personally recommendndb
, which is flexible and simple. It is a big killerdebugger
- understand architecture: to some extent can be webpack architecture reduced to
compiler + compilation + plugins
, process webpack run there will be only acompiler
; and each compilation - including callingcompiler.run
function orwatch = true
files change when, will create acompilation
object. Understand the design, responsibilities, and collaboration of these three core objects, and you can almost understand the core logic of webpack small: plugin The key is "hooks", I suggest to pay attention to it strategically and ignore it tactically! After all, hooks are the key concept of webpack and the foundation of the entire plug-in mechanism. It is impossible to bypass hooks when learning webpack, but the corresponding logical jumps are too circumscribed and unintuitive. When looking at the code, it is complicated. Sex will increase dramatically, my experience is:
- Take a careful look at the documentation of the tapable repository, or take a rough look at
tapable
, understand the concepts of synchronous hooks, asynchronous hooks, promise hooks, serial hooks, parallel hooks, etc., and have a finer understanding of the event model provided bytapable
Call strategic importance - Don’t panic when you encounter hooks that you don’t understand. In my experience, I don’t even know what this class does. It’s too difficult to understand these hooks. It’s better to skip the meaning of the hook itself and see which plugins use it. Then go to the plug-in and add the
debugger
follow-up logic, there is a high probability that you will also know the meaning of the hook. This is called tactical neglect.
- Take a careful look at the documentation of the tapable repository, or take a rough look at
maintain curiosity: maintain strong curiosity and resilience during the learning process, be good at \& dare to ask questions, and then summarize your own answers based on the source code and community information. There may be many questions, such as:
- Why do loader design pre, pitch, post, inline?
compilation.seal
function has designed many optimized hooks. Why do we need to distinguish them so finely? What do webpack designers expect from different hooks?- Why do you need so many
module
subcategories? When are these subclasses used?
Module
and Module
subclasses
As can be seen from the above, the core process of the webpack construction phase basically revolves around module
. I believe that readers who have contacted and used module
should already have a perceptual understanding of module
, but the logic to implement 06095019a07a70 is very complicated and heavy. of.
Take webpack\@5.26.3 as an example, there are 54 subclasses Module
( webpack/lib/Module.js
Unable to copy loading content
It is too tiring to understand the role of these types one by one. We need to grasp the essence: module
the role of 06095019a07aae?
module
is the basic unit of webpack resource processing. It can be considered that webpack's path analysis, reading, translation, analysis, and packaging output of resources are all carried out around the module. There are many articles that say module = file . In fact, this statement is not accurate. For example, the subclass AsyncModuleRuntimeModule
is just a piece of built-in code, which is a resource and cannot be simply equivalent to the actual file.
Webpack is very extensible, including the processing logic of the module. For example, the entry file is an ordinary js. At this time, the NormalModule object is first created. When parsing the AST, it is found that the file also contains asynchronous loading statements, such as requere.ensure
, then the corresponding The local will create the AsyncModuleRuntimeModule
module and inject the asynchronously loaded template code. The 54 module subclasses in the above class diagram are all designed to adapt to various scenarios.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。