As front-end engineering continues to evolve, so do the build tools. Webpack has gradually become the little prince of front-end construction tools by virtue of its powerful expansion ability and the concept of everything module. With the continuous iteration of Webpack4, we enjoy the pleasure brought by the continuous improvement of construction efficiency and the comfort of continuous reduction of configuration. Maybe you can proficiently use Webpack for project construction. You might want to read the Webpack source code, but when you look at it, it’s too complicated to do anything about it. Please don’t worry, this article will explain the plugin mechanism in Webpack in detail, as well as the event flow mechanism principle and workflow principle, to help you easily explore the mysteries of Webpack.
Before reading this article, we hope that you have mastered the basic configuration of Webpack and can build a webPack-based front-end automation system on your own, so this article will not teach you how to configure or use Webpack. We will skip over the basic concepts and get straight to the topic.
Plugin
introduce
First, plugin. I think plugin is used to extend the functionality of WebPack. However, compared to loader, it seems to be more “omnipotent” and very flexible. Next, we start from the configuration of plugin, analyze how plugin is embedded in webpack, start to make a very simple plugin, learn in practice. Finally, from this small example, dive into the webpack event stream to take a look at the “core engine” of the webpack driver => tapable.
Let’s start with the basics. How to configure a plugin
plugins: [
new WebpackPluginXXXXX({
/ /... param}),]Copy the code
Very straightforward, create a plugin object and put it in an array… What rules are embedded within the Plugin to be executed in webPack? Take a look below.
Write a simple plugin
Website document: teach you how to write a plug-in webpack.js.org/contribute/…
Write a simple plugin and let it run.
class MyPlugin {
apply(compiler) {
compiler.hooks.run.tap("myPlugin", compiler=> {
console.log("The plugin I wrote is running!!"); }); }}module.exports = { MyPlugin };
Copy the code
Then we’ll start a new project and test it
Note: I’m running Node version 10.16.0 and WebPack version 4.41.2
Then put it in the configuration file.
const { MyPlugin } = require("./plugin/MyPlugin");
module.exports = {
entry: {
app: "./src/index.js"
},
plugins: [new MyPlugin()]
};
Copy the code
Ok, look at the effect.
compiler
compiler
tapable
tapable
AsyncSeriesHook
Is the plug-in class? So I tried two other ways of writing it.
const MyPlugin2 = {
apply(compiler) {
compiler.hooks.run.tap("myPlugin", compilation => {
console.log("The second plug-in I wrote is also running!!"); }); }};function MyPlugin3() {}
MyPlugin3.prototype.apply = function(compiler) {
compiler.hooks.run.tap("myPlugin", compilation => {
console.log("The third plugin I wrote is also running!!");
});
};
module.exports = { MyPlugin, MyPlugin2, MyPlugin3 };
Copy the code
Then add the following to the configuration file and run it.
plugins: [new MyPlugin(), MyPlugin2, new MyPlugin3()]
Copy the code
It turned out that all three worked, but the second was definitely not recommended.
Ok, let’s write a slightly useful plugin that inserts a comment after the packaging is complete.
const { ConcatSource } = require("webpack-sources");
class MyPlugin {
apply(compiler) {
// Compilation hook after creation, execute the plug-in.
compiler.hooks.compilation.tap("BannerPlugin", compilation => {
// Optimize all chunk resources (assets). Assets are stored in compilation.assets.
// Each Chunk has a files attribute that points to all files created by that Chunk.
/ / additional resources (asset) is stored in the compilation. AdditionalChunkAssets.
compilation.hooks.optimizeChunkAssets.tap("BannerPlugin", chunks => {
for (const chunk of chunks) {
for (const file of chunk.files) {
compilation.updateAsset(file, old => {
return new ConcatSource("/ *! I'll add a line of comment here */"."\n", old); }); }}}); }); }}module.exports = { MyPlugin };
Copy the code
Then you can see the effect in packaging.
This is a webpack ConcatSource combined resources See more = > www.npmjs.com/package/web…
In the example above, we used the Compiler compilation hook and the Compiler optimizeChunkAssets hook.
Compilation
Compilation
Compilation
When I want to write some plug-ins of my own, it is essential to look at the documentation, and I am wondering what the red box in the picture above is. To understand this, take a closer look at WebPack’s core library, Tapable, the command hub that handles WebPack’s complex traffic.
Tapable
After a long period of learning, I found that the principle of Tapable, which I had previously regarded as elegant, is actually not complicated. Its design mode is one of the most commonly used design modes of the front end, observer mode. It’s kind of like NodeJS Events, which registers an event and then fires when it’s appropriate, so Events, just to remind you, one listens and one fires.
const EventEmitter = require('events');
const myEmitter = new EventEmitter();
// The first argument to on is the event name, which emit can then use to trigger the method.
// The second argument to on is the callback function, which is the execution method of this event
myEmitter.on('newListener', (param1,param2) => {
console.log("newListener",param1,param2)
});
// The first argument to emit is the name of the event to fire
The second subsequent argument to emit is the argument to the callback function.
myEmitter.emit('newListener'.111.222);
Copy the code
However, the requirements of Webpack may not only be supported by Events, there must be more complex requirements, so what functions does this upgraded version of Events provide?
We found tapable in the NPM library and found that the Tapable library exposes a number of Hook classes that provide hooks for plug-ins to mount.
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");
Copy the code
SyncHook
This is the most common synchronized hook and is typical because all hook constructors take an optional argument, a list of parameter names as an array of strings. Tap is the method to bind the hook. The first parameter is the event name and the second parameter is the event callback function.
const hook = new SyncHook(["arg1"."arg2"."arg3"]);
// Bind events to the Webapck event stream
hook.tap('plugin1', (arg1, arg2, arg3) => console.log(arg1, arg2, arg3))
hook.tap('plugin2', (arg1, arg2, arg3) => console.log(arg1, arg2, arg3))
// Execute the bound event
hook.call(1.2.3)
/ / 1, 2, 3
/ / 1, 2, 3
Copy the code
Sync
SyncHook: Sequential execution, the most basic and most commonly used hook. SyncBailHook: This hook allows early exit. When any mounted hook returns any function, the following hooks will stop running. WaterfallHook: Similar to reduce, if the result of the previous Hook function is result! If == undefined, result will be the first argument to the next Hook function. LoopHook: execute Hook repeatedly until all functions result === undefined.
Here’s an example of using SyncBailHook, but I’m not going to try it all out. If you’re interested, try it out for yourself.
const hook1 = new SyncBailHook(["arg1"]);
hook1.tap("A", arg => {console.log('A function param:${arg}`)});
hook1.tap("B", arg => arg);
hook1.tap("C", arg => {console.log('C function param:${arg}`)});
hook1.tap("D", arg => {console.log('D function param:${arg}`)});
hook1.call("sync");
// function param: sync
Copy the code
The top one is synchronous. Now let’s look at asynchronous hooks.
AsyncHook
AsyncParalle* stands for asynchronous parallel execution hook AsyncSeries* stands for asynchronous serial execution hook
The figure above is an asynchronous serial hook. Let’s explain how to use it in detail
Asynchronous hooks add a lot of functionality over synchronous interfaces. 1. From the perspective of the message publisher method, we can get the callback after the execution of all subscribers.
hook.callAsync(name,callback)
Copy the code
2. Several monitoring methods are added from the perspective of message recipients
TapAsync (name,callback) // Async(Promise) hook. TapPromise (name) // Async(Promise) hook.Copy the code
After learning the basic usage of Tapable think about the following run to see your own understanding of the Tapable plugin ~
const testHook = new AsyncSeriesHook(["name"]);
testHook.tap("plugin1".function(name) {
console.log(name + "I am plugin1");
});
testHook.tapAsync("plugin2".function(name, cb) {
console.log(name + "I am plugin2");
setTimeout(() => {
console.log("Asynchronous callback to plugin2 starts executing");
cb();
}, 2000);
});
testHook.tapPromise("plugin3".function(name, cb) {
console.log(name + "I am plugin3");
return new Promise(resolve => setTimeout(resolve, 1000));
});
testHook.callAsync("hello", () => {
console.log("over");
});
Copy the code
Answer: Hello I’m plugin1 Hello I’m plugin2 Async callbacks to plugin2 start hello I’m plugin3 over
1. AsyncSeriesHook is a serial asynchronous function that supports three types of listening event methods. 2. Tap is a synchronous method, so the first print should be “hello I am plugin1” 3. I am plugin2, go to the second hook and wait two seconds for the callback to trigger. Print the async callback of plugin2 to execute 4. Then go to the third hook, “Hello I am plugin3”, and one second later the Promise resolve method executes 5. When all listeners are done it says “over”
Use plug-ins to solve real problems
I’ll tell you about our small tool carefree, which is a Web real machine testing solution based on the WebPack plug-in and server Whistle and is independent of Wifi hotspots
carefree.jd.com/#/
After understanding webpack event flow and learning tapable usage, we may have a deeper understanding of various hooks when developing Webpack plug-in. Let’s take a look at the function flow analysis of Webpack and see how Webpack is implemented
Webpack workflow parsing
There are two ways to start Webpack:
- Can be in
Terminal
Run directly in the terminal, this way is the fastest, out of the box.- Also through
require('webpack')
The introduction of the way to implement, this way is the most flexible, we can controlWebpackStartup timing can also be passedWebpackThe exposed hook does something during its lifetime.
In order to debug and understand the source code, we use the second method to start Webpack (note that we are using Webpack 5.0.0-beta.9, so the source code is also the same). We create a new startup file named index.js in the root directory:
const webpack = require(webpack);
const config = require("./webpack.config.js"); // Our own webPack configuration
const compiler = webpack(config);
// Since the second parameter callback was not passed when webpack was started, we need to manually execute run to start compiling
compiler.run((err, stats) = > {
if (err) {
console.error(err);
} else {
console.log(stats); }});Copy the code
The Webpack execution process is actually a serial process, so here’s an overview. The diagram below:
We can see that the whole running process can be simply divided into three major stages, namely initialization, compilation and output. Then, here is a detailed introduction of what will happen in these three stages:
I. Initialization stage:
Everything starts with const Compiler = webpack(config).
Webpack function source (lib/webpack.js) :
const webpack = (options, callback) = > {
The options parameter is the parameter of the local configuration file
let compiler;
// The initialization phase begins
compiler = createCompiler(options);
// If the callback function is passed in, the callback function is automatically started; otherwise, the user needs to run manually
if (callback) {
compiler.run((err, stats) = > {
compiler.close(err2= > {
callback(err || err2, stats);
});
});
}
return compiler;
};
Copy the code
1. Initialization parameters:
When Webpack first runs, it will createCompiler and pass in the user’s custom configuration parameter Options. Then it will read and merge the parameters from the configuration file and Shell we wrote to get the final parameter Options.
Simplified pseudocode (lib/webpack.js) :
const createCompiler = options= > {
// Initialize parameters: concatenate the user's local configuration file with the built-in parameters of Webpack
options = newWebpackOptionsDefaulter().process(options); . }Copy the code
2. Instantiated Compiler:
Initialize the Compiler instance with the parameters obtained in the previous step. Compiler is the Webpack commander responsible for running through the packaging production line. The complete Webpack environment information is included in the Compiler instance, and there is only one Compiler instance globally.
Simplified pseudocode (lib/webpack.js) :
const createCompiler = options= >{...// Use options to instantiate compiler, responsible for file listening and start compiling;
const compiler = newCompiler(options.context); . }Copy the code
3. Mount user-defined plug-ins:
Start mounting plugins that we used in our configuration file, which determine whether they are functions or not, and call the apply method of the object if it is called directly (which is why Webpack officially restricts our plug-in to calling either way). At the same time, a reference to the Compiler instance is passed to the plug-in to facilitate the Hook invocation through the compiler inside the plug-in, so that the plug-in can be executed at any event node and the configuration of the Webpack environment can be obtained.
Simplified pseudocode (lib/webpack.js) :
const createCompiler = options= >{...// Mount our own plugins
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
4. Mount the built-in plug-in and process entry files:
After the user – defined plug-in is mounted, start to mount the plug-in built in Webpack and register the built-in plug-in with different hooks.
Simplified pseudocode (lib/webpack.js) :
const createCompiler = options= >{...// Mount the built-in WebPack plug-in
compiler.options = newWebpackOptionsApply().process(options, compiler); . }Copy the code
The job of the WebpackOptionsApply class is to initialize the built-in plug-ins, which will load different plug-ins based on many different conditions.
Streamlined pseudo code (lib/WebpackOptionsApply. Js) :
class WebpackOptionsApply extends OptionsApply {
constructor() {
super(a); } process(options, compiler) {// When the incoming configuration information meets the requirements, the logic related to the configuration item is processed
if(options.target) {
new OnePlugin().apply(compiler);
}
if(options.devtool) {
new AnotherPlugin().apply(compiler);
}
new JavascriptModulesPlugin().apply(compiler);
newJsonModulesPlugin().apply(compiler); .// Register entryOption plug-in
new EntryOptionPlugin().apply(compiler);
// Trigger entry-option: Reads the entry configuration, finds all entry files, and hangs make Hook for each entry filecompiler.hooks.entryOption.call(options.context, options.entry); .// Trigger afterPlugins: invoke the apply method of all built-in and custom plug-inscompiler.hooks.afterPlugins.call(compiler); .// Trigger afterResolvers: initializes the resolver according to the configuration: the resolver is responsible for finding the file in the specified path in the file system
compiler.hooks.afterResolvers.call(compiler);
returnoptions; }}Copy the code
If entry is an array, it means multiple entries, so it will loop through each entry processing. If entry is a function, it means asynchronous loading entry, so use plugin for asynchronous loading processing. DynamicEntryPlugin actually has one more operation than EntryPlugin that uses Promises to load entry files asynchronously.
Simplified pseudocode (lib/ entryoptionplugin.js) :
module.exports = class EntryOptionPlugin {
apply(compiler) {
compiler.hooks.entryOption.tap("EntryOptionPlugin", (context, entry) => {
const applyEntryPlugins = (entry, name) = > {
if (typeof entry === "string") {
new EntryPlugin(context, entry, name).apply(compiler);
} else if (Array.isArray(entry)) {
// If there are multiple entries, all entries will be traversed
for (const item ofentry) { applyEntryPlugins(item, name); }}};if (typeof entry === "string" || Array.isArray(entry)) {
applyEntryPlugins(entry, "main");
} else if (typeof entry === "object") {
for (const name of Object.keys(entry)) { applyEntryPlugins(entry[name], name); }}else if (typeof entry === "function") {
// If it is an asynchronous load entry, use asynchronous load processing
new DynamicEntryPlugin(context, entry).apply(compiler);
}
return true; }); }};Copy the code
Entry files are processed using EntryPlugin, attaching the Compilation hook to each entry file, attaching a module factory to each entry file, and then attaching a Make hook to each entry file, waiting for the compilation phase to use the module factory to convert the entry file and its dependencies into JS modules.
Simplified pseudocode (lib/ entryplugin.js) :
class EntryPlugin {
constructor(context, entry, name) {
this.context = context;
this.entry = entry;
this.name = name;
}
apply(compiler) {
// Register the compilation hook for each entry file
compiler.hooks.compilation.tap(
"EntryPlugin", (compilation, { normalModuleFactory }) => { compilation.dependencyFactories.set( EntryDependency, normalModuleFactory ); });// Register make hooks for each entry file
compiler.hooks.make.tapAsync("EntryPlugin", (compilation, callback) => {
const { entry, name, context } = this;
constdep = EntryPlugin.createDependency(entry, name); compilation.addEntry(context, dep, name, err => { callback(err); }); }); }}Copy the code
After the process function is executed, Webpack registers all the Hook messages it cares about and waits for them to be fired one by one during subsequent compilations.
The above is the initialization stage of Webpack. The main tasks of this stage are to integrate configuration parameters options, initialize Compiler objects, mount all plugins and register all hooks. There are also some minor incidents such as the introduction of the Node file system FS plug-in (mainly in preparation for the subsequent output of packaged files) and the initialization of resolver(to find files in the specified path in the file system).
Ii. Compilation Stage:
1. Run:
Compile. Run lets the compile phase run. The run function defines a post-compile callback function that generates the compiled content into a file. As you can see, the first step is to check whether the compilation is successful or not. If it is not successful, the done event is triggered to end the compilation. If successful, start packing files. The beforeRun and Run events are then emitted which bind the file read object and enable the CachePlugin. The entry file is then read and this.compile() is executed after obtaining the contents of the entry file.
Simplified pseudocode (lib/Compiler.js) :
class Compiler {...// The entire run process
run(callback) {
const onCompiled = (err, compilation) = > {
// Failed to compile
if (this.hooks.shouldEmit.call(compilation) === false) {
const stats = new Stats(compilation);
stats.startTime = startTime;
stats.endTime = Date.now();
this.hooks.done.callAsync(stats, () => {
this.hooks.afterDone.call(stats);
});
return;
}
process.nextTick((a)= > {
// Output file
this.emitAssets(compilation, () => {
// Trigger the done event
this.hooks.done.callAsync(stats, () => {
// Triggers the callback passed in when we manually execute the run function
callback(stats);
// Triggers the afterDone event
this.hooks.afterDone.call(stats);
});
});
});
};
// Trigger the beforeRun event to bind the file read object
this.hooks.beforeRun.callAsync(this, () = > {// The run event is triggered to enable the compilation of the cache plug-in CachePlugin, improving compilation efficiency
this.hooks.run.callAsync(this, () = > {// Read the contents of the entry file (readFile)
this.readRecords((a)= > {
// Start compiling and pass in the compiled callback function
this.compile(onCompiled); })})})}}Copy the code
This.pile () initializes the ModuleFactory and stores the loader configuration in preparation for parsing and converting the module. The compile event is then triggered to tell the plug-in that a new compilation is about to begin.
Simplified pseudocode (lib/Compiler.js) :
class Compiler {
newCompilationParams() {
const params = {
normalModuleFactory: this.createNormalModuleFactory(),
contextModuleFactory: this.createContextModuleFactory()
};
return params;
}
compile(callback) {
// Initialize the module factory
const params = this.newCompilationParams(); . }}Copy the code
2. Initialize compilation:
Wait a minute, I think we’re missing one very important object, the Compilation, and that’s the Compiler. We mentioned Compiler earlier, it’s responsible for the whole production line, it’s like a commander, directing all the work of the Webpack, so the Compilation focuses on the production of a product, and every time we compile, A Compilation is reinitialized, which contains information about the environment for the current Compilation, and then the make hook is triggered to actually start the Compilation.
Simplified pseudocode (lib/Compiler.js) :
class Compiler {
compile(callback) {
...
// The compile event is triggered to tell the plug-in that a new compile is about to start
this.hooks.compile.call(params);
// Initialize the compilation. The compilation object represents a single version build and resource generation process
const compilation = this.newCompilation(params);
// Trigger the make event to start compiling
this.hooks.make.callAsync(compilation, () => {
...
})
}
createCompilation() {
return new Compilation(this);
}
newCompilation(params) {
const compilation = this.createCompilation();
compilation.name = this.name;
compilation.records = this.records;
this.hooks.thisCompilation.call(compilation, params);
this.hooks.compilation.call(compilation, params);
returncompilation; }}Copy the code
3. Start compiling:
The compile phase enters the home field, first handing over each entry file to the Compilation and then executing addEntry.
Simplified pseudocode (lib/ entryplugin.js) :
compiler.hooks.make.tapAsync("EntryPlugin", (compilation, callback) => {
const { entry, name, context } = this; // This is the entry file
const dep = EntryPlugin.createDependency(entry, name);
// Send the import file to the compilation
compilation.addEntry(context, dep, name, err => {
callback(err);
});
});
Copy the code
In addEntry, addModuleChain is executed to handle each entry file. The module factory is then used to convert the entry file into a NormalModule instance. The procedure call chain is very long, and the execution process has been simplified for ease of understanding.
Simplified pseudocode (lib/ entryplugin.js) :
addEntry(context, entry) {
...
this._addModuleChain(context, entry);
}
_addModuleChain(context, dependency) {
...
const Dep = dependency.constructor;
// Create the module factory for the entry file
const moduleFactory = this.dependencyFactories.get(Dep);
// Create an entry module
moduleFactory.create(
{
contextInfo: {
issuer: "".compiler: this.compiler.name
},
context: context,
dependencies: [dependency]
},
({ module= > {})// Run loader after creation
this.buildModule(module); }); } buildModule(module) {...Build will execute dubuild to run each loader
module.build(
this.options,
this.this.resolverFactory.get("normal".module.resolveOptions),
this.inputFileSystem
);
}
Copy the code
After the entry module is built, doBuild will be executed, which is to select the appropriate loader to load the resource. The goal is to convert this resource to A JS module (because WebPack only recognizes JS modules). Finally, the loaded source file is returned for further processing.
Simplified pseudocode (lib/ normalModule.js) :
const { runLoaders } = require("loader-runner");
doBuild(options, compilation, resolver, fs, callback) {
// runLoader method imported from package 'loader-runner'
runLoaders({
resource: this.resource, // The resource may be a js file, a CSS file, or an img file
loaders: this.loaders
}, (err, result) => {
const source = result[0];
const sourceMap = result.length >= 1 ? result[1] : null;
const extraInfo = result.length >= 2 ? result[2] : null; . })}Copy the code
We need to use Parser(Acorn) to convert the JS code of the entry file into a standard AST (abstract syntax tree), and then analyze the AST. Add the module to module.dependencies and perform a recursive analysis on the dependencies of all modules. This will generate a complete dependency tree (moduleGraph).
Streamlined pseudo code (lib/javascript/JavascriptParser. Js) :
parse(code, options) {
// Call the third-party plugin 'acorn' to parse the JS module
let ast = acorn.parse(code);
// Omit some code
if(this.hooks.program.call(ast, comments) === undefined) {
this.detectStrictMode(ast.body);
this.prewalkStatements(ast.body);
this.blockPrewalkStatements(ast.body);
// Webpack iterates through the ast.body and writes all the module's dependencies to 'module.dependencies'
this.walkStatements(ast.body); }}Copy the code
At this point, all the source code is compiled and stored in memory (compilation.modules), waiting to be packaged and exported.
The above is the compilation stage of Webpack, the main task of this stage is to initialize the module factory, initialize compilation, then call loader to compile, transform AST, and generate dependency atlas. There are also some minor incidents such as binding the file read object and calling the Cache plug-in (compiling the Cache to improve compilation efficiency).
Iii. Output Stage:
1. Reorganize source code:
Make after the event, began to implement the callback compilation. The seal (), start packaging module, here will perform compilation. CreateChunkAssets method (at the time of execution will first read the cache in whether have the same hash of resources, if any, Otherwise, the system continues to execute the logic generated by the module and stores it in the cache.) Generates chunk resources to be outputted. GetRenderManifest is called to get a list of outputs, each containing a resource and information that needs to be packaged for output. Then the AST will be converted back to JS code and the corresponding template will be used for stitching, and then the stitching content will be saved in the compiler. assets according to the file name for later file output.
Condensed pseudocode (lib/ compiler.js) :
createChunkAssets(callback) {
// Get an output list containing information about each resource that needs to be output
let manifest = this.getRenderManifest();
for (const fileManifest of manifest) {
// Convert the AST back to JS code and concatenate the code according to the template
source = fileManifest.render();
// Put the final contents of the code into compilation.assets to prepare the generated file
this.emitAsset(file, source, assetInfo); }}Copy the code
2. Output completed:
After the seal execution, all the modules are packaged and saved in compilation. assets, and it’s time to output them as files. This is followed by a series of callback callbacks, which eventually lead us to the compiler.emitAssets method body, which fires the EMIT event and outputs the file to the specified folder according to the path property of the output configuration of the webpack.config.js file. At this point, you can see the packaged file in Dist.
Simplified pseudocode (lib/Compiler.js) :
emitAssets(compilation, callback) {
let outputPath;
this.hooks.emit.callAsync(compilation, () => {
// Find the output file path
outputPath = compilation.getPath(this.outputPath, {});
// Output compilation.assets to the specified path
mkdirp(this.outputFileSystem, outputPath, compilation.getAssets()); })}Copy the code
The above is the output stage of Webpack. The main task of this stage is to get the transformed results and dependencies, combine modules into chunks, and then use corresponding templates according to the type of Chunk to generate the final output file content, and finally output the content to the hard disk.
Engineering application of Webpack
Webpack focuses on modular builds, and many VUE and React projects are packaged based on it. You can build a scaffolding for your team to build against the VUE and React stack with workflows such as development, testing, and rollout, but it can take a few days to build from scratch. Gaea-cli is a scaffold building tool based on WebPack and Node.js. It also includes a complete front-end workflow of development, testing, packaging, etc., with a reasonable default configuration of WebPack. It also exposes the WebPack configuration file to allow users to configure additional plug-ins themselves. You can also choose your favorite UI framework when initializing your project from the command line, such as NutUI, ElementUI, etc. You can also configure different Webpack plug-ins to provide more functionality for your scaffolding, such as CareFree, image compression, PWA, Smock, etc. You can also choose whether you want to support Ts, Vuex, Eslint, etc. These build tools allow you to create a vue/React scaffolding with easy selection and zero configuration, so you can focus on writing your app instead of spending days with configuration issues.
conclusion
The overall architecture of Webpack is a situation of plug-ins. Event flow is realized through Tapable, and its entire workflow is spliced together through events, and event flow can make plug-ins execute in different process stages. The event flow mechanism of Webpack ensures the orderliness of plug-ins and makes the whole system very expansibility.
Due to the single-threaded barrier of Node.js, Webpack construction is always slow. (This is also the reason why Happypack is so popular. Here, Happypack uses node.js’s native cluster module to open up multi-process construction, and WebPack4 has been integrated). Loader -> js(String) -> AST -> js(String)
Nowadays, front-end projects use the idea of modularization to develop, Webpack is also just for the development of modular automation construction tools, coupled with its strong scalability to make it a very wide range of scenarios, not fire also not good!