Webpack’s event flow and plug-in mechanism
We have talked about the operation principle of Webpack and loader. In this article, we will talk about the mechanism of Webpack event flow and plug-in. This series is divided into three parts
- Webpack5 packaging analysis
- Webpack loader implementation
- Webpack’s event flow and plugin principles
Webpack event flow
What is the event flow of Webpack, here to introduce the simple webpack inside a paragraph.
Webpack is like a production line that goes through a series of processes to convert source files into output. Each process on this production line has a single responsibility. There are dependencies between multiple processes. Only after the current process is completed can the next process be handled. A plug-in is like a function that plugs into a production line and works with resources on the production line at a specific time. Webpack organizes this complex production line through Tapable. Webpack broadcasts events as it runs, and the plug-in only needs to listen for the events it cares about to join the production line and change how the production line works. Webpack’s event flow mechanism ensures the orderliness of plug-ins and makes the whole system very extensible. — Wu Haolin, “Simple Webpack”
How WebPack works
In the first article, we implemented a simple Webpack by ourselves. We use a picture to understand the overall operation mechanism of WebPack from the start to the packaging of resources.There are several key steps
- When we run Webpack, it will read your incoming configuration file (webpack.config.js) and run it
compile.run
To initialize the intrinsic build parameters (entry files, etc.), and then start analyzing the module (Environment hook function) - Next comes the entryOption phase, where WebPack starts reading configured entry entries and recursively iterates through all entry files (entryOption hooks)
- After WebPack enters the entry file, it starts analyzing the dependencies, and proceeds
compilation
Process. (compilation
Hook function)- Compile the file contents using the configured Loader (
buildModule
), we can get the module’s resource(resource path) from the parameter module of the incoming event callback (buildModule
Hook function)
compiler.hooks.compilation.tap('compilation'.(compilation , compilationParams) = > { compilation.hooks.buildModule.tap('sourceMap'.(module) = > { console.log(module.resource) // Resource path})})Copy the code
- The files processed by loader will be parsed through Acorn to generate ast syntax tree (normalModuleLoader) to analyze the dependencies of files, and then form the dependency array to repeat the above module analysis process (Webpackage 5 has been abandoned
normalModuleLoader
This hook)
- Compile the file contents using the configured Loader (
- Emit phase: All files have been compiled and converted, including the final output of the resource. We can retrieve the required data from the compilation.assets of the incoming event callback, including the output of the resource, Chunk of code, and so on.
The diagram below is a comparison of the overall operation mechanism and the main plug-in diagramThere is also a more detailed module-based running flow diagram based on WebPack (dotted lines represent repeated executions)
Plug-in development for Webpack
We mainly use 2 plugins
- Use MD mode to list the file packaging resources
- Analyze the components that the file depends on through plug-ins
First we need to know how the plug-in for WebPack needs to be developed. It requires two conditions
- Must be a class
- To expose an Apply method, WebPack executes the method at initialization and passes in one
compiler
, the source code is like this
if (Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
if (typeof plugin === "function") {
plugin.call(compiler, compiler);
} else{ plugin.apply(compiler); }}}Copy the code
List package resource plug-ins
As we can see from the above figure, we can fetch the packaged resource after it is packaged and then add a new file.
- By using the emit hook function, we can use the emit hook function in the callback function
compilation
Get the parameters inassets
The resource list - Then add a resource to process the process through WebPack
class FileListPlugin {
constructor({ filename = 'index.md' }) {
this.filename = filename;
}
apply(compiler) {
compiler.hooks.emit.tapAsync('FileListPlugin'.(compilation, cb) = > {
let assets = compilation.assets;
let content = '## 文件 资源大小\r\n';
Object.entries(assets).forEach(([key, value]) = > {
content += `${key} ${value.size()}\r\n`;
});
assets[this.filename] = {
size() {
return content.length;
},
source() {
returncontent; }}; cb(); }); }}module.exports = FileListPlugin;
Copy the code
The list of components used
In today’s component mentality, a component can be used by multiple pages, sometimes we modify a component without knowing whether it will affect other pages, so we developed a plug-in to count the pages used by the component. The usage is different in webpack4 and webpack5, and we are now analyzing based on webpack5.
- As you can see from the flowchart above, each asset has a module phase
NormalModuleFactory
, all we can do after the module is processed by loaderafterResolve
To analyze the module - After analyzing module dependencies, you can pass
done
Hooks and so on output the parsed content
class NormalModuleFactory {
constructor() {
this.dependencies = {}
this.entry = ' '
this.workDictory = ""
}
apply(compiler) {
compiler.hooks.normalModuleFactory.tap('NormalModuleFactory'.(nmf) = > {
nmf.hooks.afterResolve.tapAsync('nmf'.(result, callback) = > {
let { request, contextInfo, context } = result
if(! request.includes('node_modules')) {
if(! contextInfo.issuer) {console.log('entry', path.normalize(request))
this.entry = request
this.workDictory = context
} else if(! ['react'.'react-dom'].includes(request)){
// There is a dependency contextInfo which is the context in which the resource is requested
const requestPath = path.relative(this.workDictory, contextInfo.issuer)
// './component/Hello requestPath: src/index.js
if(! requestPath.includes("node_modules")) {
const resourcePath = path.join(context, request)
console.log(requestPath, request)
request = path.relative(this.workDictory, resourcePath)
if(!this.dependencies[request]) {
this.dependencies[request] = [requestPath]
} else {
this.dependencies[request].push(requestPath)
}
}
}
}
callback()
})
})
compiler.hooks.done.tap('NormalModuleFactory'.() = > {
console.log(this.dependencies)
})
}
}
module.exports = NormalModuleFactory
Copy the code
After executing NPX webpack, we can get this content, key is the path of the component being used, value is the page or component using this component, and we can analyze the specific style and component dependency based on the path matching according to the project content.
{
'src\\base\\a.js': [ 'src\\index.js' ],
'src\\component\\Hcc': [ 'src\\index.js' ],
'src\\component\\Hello': [ 'src\\index.js', 'src\\component\\Hcc.jsx' ],
'src\\index.less': [ 'src\\index.js' ],
'src\\base\\b': [ 'src\\base\\a.js' ],
'src\\pic\\hcc.jpg': [ 'src\\index.less' ]
}
Copy the code
Refer to the article
- Explore the webpack plugin mechanism
- Uncover the WebPack plug-in workflow and principles