webpack
One of the best features of WebPack is that you can import any type of file from the Loader in addition to JavaScript.
Webpack Core Concepts:
Entry
(Entry) : Webpack performs the first step in the build starting with Entry, which can be abstracted into input.Module
Modules: In Webpack everything is a module, a module corresponds to a file. Webpack recursively finds all dependent modules starting from the configured Entry.Chunk
(Code block) : A Chunk is composed of multiple modules for code merging and splitting.Loader
(Module converter) : used to convert the original content of the module into new content as required.Plugin
(Extension) : Events are broadcast at specific times in the Webpack build process, and plug-ins can listen for these events and change the output
Webpack executes the process
Webpack executes the following process from start to finish:
- Initialization: Resolve webPack configuration parameters, production
Compiler
The instance - Registered plug-in: invoking a plug-in
apply
Method passed to the plug-incompiler
Instance, and the plug-in calls the API provided by Webpack through compiler, allowing the plug-in to listen for all subsequent event nodes. - Start compiling: Read the entry file
- Parsing files: Use
loader
Parse files into abstract syntax treesAST
- Generate dependency graph: Find dependencies for each file (traversal)
- Output: Generated from converted code
chunk
- Generate the last packaged file
Ps: Because WebPack dynamically loads all dependencies based on dependency diagrams, each module can explicitly state its dependencies and avoid packing unused modules.
Babel
Babel is a toolchain for converting ECMAScript 2015+ version code into backward compatible JavaScript syntax so that it can run in current and older browsers or other environments:
The main function
- The syntax conversion
- through
Polyfill
Way to add missing features to the target environment (through@babel/polyfill
Module) - Source code conversion (
codemods
)
The main module
@babel/parser
: is responsible for parsing code into an abstract syntax tree@babel/traverse
: a tool to walk through an abstract syntax tree, where we can parse specific nodes and then do some operations@babel/core
: Code conversion, such as ES6 code to ES5 mode
Webpack packages the results
In a typical application or site built using WebPack, there are three main types of code:
- Source code: source code written by you or your team.
- Dependencies: Your source code will depend on any third party
library
Or”vendor
“Code. - Management Documents:
webpack
的runtime
usemanifest
Manage the interaction of all modules.
Runtime: Connects the loading and parsing logic required by modules when they interact. This includes links to loaded modules in the browser and execution logic for lazily loaded modules.
Manifest: When the compiler starts executing, parsing, and mapping the application, it retains the detailed essentials of all modules. This data set is called the “Manifest”, and when packaged and sent to the browser, modules are parsed and loaded at runtime through the Manifest. No matter which module syntax you choose, those import or require statements are now converted to the webpack_require method, which points to the module identifier. Using the manifest data, Runtime will be able to query module identifiers and retrieve the corresponding modules behind them.
Among them:
import
或require
The statement is converted to__webpack_require__
- Asynchronous imports are converted to
require.ensure
(Use the Promise wrapper in Webpack 4)
To compare
gulp
Task runner: Used to automate common development tasks such as checking items (Lint), building (build), testing (test)webpack
Bundler: Helps you take JavaScript and style sheets ready for deployment and convert them into a usable format for your browser. For example, JavaScript can compress, split chunks, and lazily load,
Webpack optimization
DllPlugin + DllReferencePlugin
To greatly reduce build time, separate packaging.
Both the DllReferencePlugin and the DLL plugin DllPlugin are used in the _ plus _ webpack setup.
DllPlugin this plugin creates a DLL only bundle(DLL-only -bundle) in an additional separate Webpack setup. The plugin generates a file named manifest.json, which is used to map the DLLReferencePlugin to related dependencies.
webpack.vendor.config.js
new webpack.DllPlugin({
context: __dirname,
name: "[name]_[hash]",
path: path.join(__dirname, "manifest.json"})),Copy the code
webpack.app.config.js
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require("./manifest.json"),
name: "./my-dll.js",
scope: "xyz".sourceType: "commonjs2"
})
Copy the code
CommonsChunkPlugin
By stripping out the common modules, the resultant files can be loaded once at the beginning and stored in the cache for later use. This gives a speed boost because the browser quickly removes the common code from the cache, rather than loading a larger file every time a new page is visited.
If the public file is extracted from a file, then when the user visits a web page, loads the public file, and then visits other pages that rely on the public file, the file is directly used in the browser’s cache, and the public file is transferred only once.
entry: {
vendor: ["jquery"."other-lib"], // Specify the third-party library app:"./entry"
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
// filename: "vendor.js"// (give chunk a different name) minChunks: Infinity, // (with more and more entry chunks, // this configuration ensures that no other modules will be packed into Vendor Chunk)})] // Packaged file <script SRC ="vendor.js" charset="utf-8"></script>
<script src="app.js" charset="utf-8"></script>
Copy the code
UglifyJSPlugin
Basically, scaffolding includes this plug-in, which analyzes the JS code syntax tree and understands the meaning of the code to optimize for removing invalid code, removing log input code, shortening variable names, and so on.
const UglifyJSPlugin = require('webpack/lib/optimize/UglifyJsPlugin'); / /... plugins: [ new UglifyJSPlugin({ compress: { warnings:false// Drop useless code without warning drop_console:true// Delete all console statements in IE Collapse_vars:true, // Inline variable reduce_vars that are defined but used only once:true}, output: {beautify: {beautify: {beautify: {beautify: {beautify: {beautify:false// The most compact output with no Spaces and no tabs comments:false, // delete all comments}})]Copy the code
ExtractTextPlugin + PurifyCSSPlugin
The ExtractTextPlugin extracts text (CSS) from bundles into separate files. The PurifyCSSPlugin purifies CSS.
module.exports = {
module: {
rules: [
{
test: /\.css$/,
loader: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
{
loader: 'css-loader',
options: {
localIdentName: 'purify_[hash:base64:5]',
modules: true
}
}
]
})
}
]
},
plugins: [
...,
new PurifyCSSPlugin({
purifyOptions: {
whitelist: ['*purify*']}})]};Copy the code
DefinePlugin
DefinePlugin automatically detects environmental changes and is efficient.
In front-end development, different configurations are required in different application environments. For example, the API Mocker of the development environment, data forgery in the testing process, and debugging information printing. If you manually process this configuration information, it is cumbersome and error prone.
Global constants configured using DefinePlugin
Note that because this plug-in performs text substitution directly, the given value must contain the actual quotes inside the string itself. In general, there are two ways to achieve this effect, using ‘ ‘production’ ‘, or using json.stringify (‘production’).
New webpack.defineplugin ({// Of course, the configuration file should be context-specific when running the Node server // Run the configuration in the test environment simulated below'process.env':JSON.stringify('dev'),
WP_CONF: JSON.stringify('dev'),}),Copy the code
Test DefinePlugin: write
if (WP_CONF === 'dev') {
console.log('This is dev');
} else {
console.log('This is prod');
}
Copy the code
When packaged, WP_CONF === ‘dev’ will compile to false
if (false) {
console.log('This is dev');
} else {
console.log('This is prod');
}
Copy the code
Clear unreachable code
When the DefinePlugin plugin is used, packaged code has a lot of redundancy. You can clear unreachable code with UglifyJsPlugin.
[
new UglifyJsPlugin({
uglifyOptions: {
compress: {
warnings: false// Remove warning dead_code:true// Remove unreachable code}, warnings:false}})]Copy the code
The final packing code will become console.log(‘This is prod’)
Attached Uglify documentation: github.com/mishoo/Ugli…
Use DefinePlugin to distinguish the environment + UglifyJsPlugin to remove unreachable code to reduce the size of the packaged code
HappyPack
HappyPack can enable multi-process Loader conversion to split tasks into sub-processes and send the results to the main process.
use
exports.plugins = [
new HappyPack({
id: 'jsx',
threads: 4,
loaders: [ 'babel-loader' ]
}),
new HappyPack({
id: 'styles',
threads: 2,
loaders: [ 'style-loader'.'css-loader'.'less-loader']})]; exports.module.rules = [ {test: /\.js$/,
use: 'happypack/loader? id=jsx'
},
{
test: /\.less$/,
use: 'happypack/loader? id=styles'},]Copy the code
ParallelUglifyPlugin
ParallelUglifyPlugin enables multi-process compression of JS files
import ParallelUglifyPlugin from 'webpack-parallel-uglify-plugin';
module.exports = {
plugins: [
new ParallelUglifyPlugin({
test,
include,
exclude,
cacheDir,
workerCount,
sourceMap,
uglifyJS: {
},
uglifyES: {
}
}),
],
};
Copy the code
BundleAnalyzerPlugin
Webpack packages the result analysis plug-in
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
}
Copy the code
Externals
This is not a plugin, it is a configuration option for WENpack
The externals configuration option provides the method to “exclude dependencies from the output bundle”. Instead, the bundles created depend on those dependencies that exist in the consumer’s environment. This feature is usually most useful to library developers, but there are a variety of applications that use it.
entry: {
entry: './src/main.js',
vendor: ['vue'.'vue-router'.'vuex'}, externals: {externals: {externals: {externals: {externals: {externals: {externals: {externals: {externals:}'echarts',}Copy the code
test & include & exclude
Speed up file search by reducing the scope
The sample
{
test: /\.css$/,
include: [
path.resolve(__dirname, "app/styles"),
path.resolve(__dirname, "vendor/styles")]}Copy the code
Webpack HMR principle analysis
Hot Module Replacement (HMR)
Contains the following contents:
- Hot update figure
- Description of hot update procedure
Step 1: Webpack watches the file system into memory
Webpack-dev-middleware calls webpack’s API to the file system Watch. When a file changes, Webpack recompiles the file and saves it to memory.
Webpack packs bundle.js files into memory. The reason for not generating files is that accessing code in memory is faster than accessing files in the file system, and it also reduces the overhead of writing code to files.
All thanks to memory-FS, a dependent library for Webpack-dev-Middleware, Webpack-dev-middleware replaces webpack’s original outputFileSystem with a MemoryFileSystem instance, so the code is exported to memory.
The source code for webpack-dev-Middleware is as follows:
// compiler // webpack-dev-middleware/lib/Shared.js var isMemoryFs = ! compiler.compilers && compiler.outputFileSystem instanceof MemoryFileSystem;if(isMemoryFs) {
fs = compiler.outputFileSystem;
} else {
fs = compiler.outputFileSystem = new MemoryFileSystem();
}
Copy the code
Step 2: devServer notifies the browser that the file has changed
When devServer is started, SockJS establishes a webSocket long connection between the server side and the browser side to inform the browser of the various stages of webPack compilation and packaging. The most important step is that webpack-dev-server calls webpack API to listen for the done event of compile. When compile is complete, Webpack-dev-server sends the hash value of the compiled and packaged new module to the browser using the _sendStatus method.
// webpack-dev-server/lib/Server.js
compiler.plugin('done', (stats) => {// stats.hash is the latest packaged filehashValue of enclosing _sendStats (enclosing sockets, stats. ToJson (clientStats)); this._stats = stats; }); . Server.prototype._sendStats =function (sockets, stats, force) {
if(! force && stats && (! stats.errors || stats.errors.length === 0) && stats.assets && stats.assets.every(asset => ! asset.emitted) ) {return this.sockWrite(sockets, 'still-ok'); } // Calling the sockWrite method willhashThe value is sent to the browser via websocket this.sockWrite(Sockets,'hash', stats.hash);
if (stats.errors.length > 0) { this.sockWrite(sockets, 'errors', stats.errors); }
else if (stats.warnings.length > 0) { this.sockWrite(sockets, 'warnings', stats.warnings); } else { this.sockWrite(sockets, 'ok'); }};Copy the code
Step 3: webpack-dev-server/client responds to the server message
Webpack-dev-server modifies the Entry property in the WebPack configuration to add webpack-dev-client code so that the final bundle.js file will receive the code for webSocket messages.
Webpack-dev-server /client stores the hash value temporarily after receiving the hash message of type OK. After receiving the message of type OK, the reload operation is performed on the application.
In the reload operation, webpack-dev-server/client decides whether to refresh the browser or do a hot update (HMR) to the code based on the hot configuration. The code is as follows:
// webpack-dev-server/client/index.js
hash: function msgHash(hash) {
currentHash = hash;
},
ok: function msgOk() {/ /... reloadApp(); } / /...function reloadApp() {/ /...if (hot) {
log.info('[WDS] App hot update... ');
const hotEmitter = require('webpack/hot/emitter');
hotEmitter.emit('webpackHotUpdate', currentHash);
// ...
} else {
log.info('[WDS] App updated. Reloading... '); self.location.reload(); }}Copy the code
Step 4: Webpack receives the latest hash value validation and requests the module code
First, webpack/hot/dev-server (dev-server) listens for the webpackHotUpdate message sent by webpack-dev-server/client. Call webpack/lib/HotModuleReplacement runtime (hereinafter referred to as HMR runtime) method of the check, detect whether there are new updates.
In the process of the check will use webpack/lib/JsonpMainTemplate runtime (hereinafter referred to as the json runtime) hotDownloadManifest and two of the method HotDownloadUpdateChunk.
HotDownloadManifest is a call to AJAX to request the server whether there is an updated file, if there is an updated file list back to the browser. This method returns the latest hash value.
HotDownloadUpdateChunk requests the latest module code via JSONP and sends it back to HMR Runtime, which does further processing based on the new module code, either refreshing the page or hot updating the module. This method returns the code block corresponding to the latest hash value.
Finally, the new code block is returned to HMR Runtime for module hot update.
Ps: why is the code for updating the module not sent directly to the browser via websocket in step 3, but obtained via JSONP?
Dev-server /client is only responsible for message passing and not for new module fetching. This should be done by HMR Runtime. HMR Runtime should be the place to fetch new code. Another interesting thing about using Webpack-hot-middleware is that it’s possible to use webpack-hot-middleware in conjunction with Webpack without using webpack-dev-server. Instead of using a Websocket, it uses an EventSource. In summary, new module code should not be placed in webSocket messages in the HMR workflow.
Step 5: HotModuleReplacement. The runtime for hot update module
This step is a key step in the overall module hot update (HMR), and module hot updates occur in the hotApply method in THE HMR Runtime
// webpack/lib/HotModuleReplacement.runtime
function hotApply() {/ /... var idx; var queue = outdatedModules.slice();while(queue.length > 0) {
moduleId = queue.pop();
module = installedModules[moduleId];
// ...
// remove module from cache
delete installedModules[moduleId];
// when disposing there is no need to call dispose handler
delete outdatedDependencies[moduleId];
// remove "parents" references from all children
for(j = 0; j < module.children.length; j++) {
var child = installedModules[module.children[j]];
if(! child)continue;
idx = child.parents.indexOf(moduleId);
if(idx >= 0) { child.parents.splice(idx, 1); }}} //... // insert new codefor(moduleId in appliedUpdate) {
if(Object.prototype.hasOwnProperty.call(appliedUpdate, moduleId)) { modules[moduleId] = appliedUpdate[moduleId]; }} / /... }Copy the code
If an error occurs during the hot update process, the hot update will revert to the refresh browser. This part of the code is in the dev-server code, the brief code is as follows:
module.hot.check(true).then(function(updatedModules) {
if(! updatedModules) {return window.location.reload();
}
// ...
}).catch(function(err) {
var status = module.hot.status();
if(["abort"."fail"].indexOf(status) >= 0) { window.location.reload(); }});Copy the code
Step 6: What does the business code need to do?
After replacing the old module with the new module code, our business code cannot know that the code has changed. That is to say, when the hello.js file is modified, we need to call the HMR Accept method in the index.js file to add the processing function after the module update. Insert the return value of the Hello method into the page in time. The following code
// index.js
if(module.hot) {
module.hot.accept('./hello.js'.function() {
div.innerHTML = hello()
})
}
Copy the code