preface
In terms of technology, the more you learn, the less you feel. Maybe it didn’t reach a certain level. Surfing on the Internet when learning, see some big guy writing calm, clear thinking. A deep dissection of a technical point. Really produced a kind of respect and admire from the heart and respect and admire. Sometimes, just a paragraph of text can remind you, so!! Sometimes it gets chewed up and shoved into your mouth like you can’t even chew it. I have been learning Webpack for some time. I want to use this blog post to sort out webpack related knowledge system. Is also a summary of their own learning.
webpack
# Things like parsing Tapable event streams and implementations, analyzing Webpack-CLI source code, parsing the build process, implementing xx with a difficult loader or plugin are not included in this article.Copy the code
- Webpack is just a module packager
: : Webpack is used to package the scattered module code into the same JavaScript file. For the environment compatibility problems in the code, webpack can also be converted by the module loader, webpack can also split the code. The code in the application is packaged according to the need (packaging on demand), to achieve progressive loading. This solves the problem of files that are too fragmented or too large. Webpack loads any type of file in a modular way (import ‘./index.css’).
Webpack addresses the modularity of the front end as a whole, not just JavaScript. All resources can be viewed as a module.
import j1 from './index.js'
import c1 from './index.css'
import h1 from './index.html'
import p1 from './index.png'.Copy the code
The packaging process of WebPack
- Webpack-cli parses the CLI parameters and merges them with the parameters you configured to get the final configuration item.
- The Compiler object is created through the configuration items, added to the methods required by the build process, and completed with functions such as registering plug-ins. This object will be used throughout the build process.
- Find all dependencies through the entry file corresponding to the AST engine library (ACORN) + entry, and generate an AST abstract syntax tree, which can also be seen as a dependency diagram here.
- The AST syntax tree is parsed and each module is processed by different loaders based on configuration items.
- After all modules are built, write to the output directory corresponding to output.
So let’s see
How does WebPack know that this is a module and I want to package it?
Webpack triggers packaging
Webpack does not mindless package all the data in the entry file, but rather requires triggering.
-
It is well known that the import syntax of an ESModule is packaged by WebPack as a module. So what’s the other way?
-
JavaScript code: Require syntax of Commonjs specification, REQUIRE syntax of AMD
-
Non-javascript code: Non-javascript code is known to be processed by the Loader
-
CSS: When the Loader processes CSS, things like @import/ URL in the style code also trigger packaging.
When processing CSS files, we use csS-Loader, which will package the path it imported as a new module if it finds @import syntax or URL syntax. For example, background-image: url(background.png); Webpack finds the.png file and sends it to url-Loader for processing. Such as @ import url (reset. CSS); Webpack finds the.css file, triggers the resource load, and then passes the file to the CSS-Loader for processing.
-
-
-
HTML: Packaging mechanisms such as SRC are also triggered when the Loader processes HTML. If more trigger mechanisms are required, check whether the loader has exposed interfaces. If yes, configure them yourself.
Comparison between WebPack and GULp
Just one point: support for import/require syntax.
Browsers do not support import/require syntax, so how is it translated? Because WebPack provides the base code that “replaces” the import syntax. Gulp is a purely automated build tool flow. None of the basic code is provided to make it easy for users to use the modularized syntax.
So let’s see
Webpack bootstrap code
How is the base code or bootstrap webPack implemented? Or is WebPack an implementation of import/require syntax?
Ps: Here’s how to pack in mode: None. It’s different in mode: Development.
/ * * * * * * / (function (modules) { // webpackBootstrap. }) ([/* 0 - Entry file */
/ * * * / (function (module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _testA_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1); // import testA from './ testa.js'
/* harmony import */ var _testB_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2); // import testB from './ testb.js'
console.log(_testA_js__WEBPACK_IMPORTED_MODULE_0__["default"], _testB_js__WEBPACK_IMPORTED_MODULE_1__["default"]);
}),
/* 1 The first module introduced, testA*/
/ * * * / (function (module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
var t1 = 'hello1';
/* harmony default export */ __webpack_exports__["default"] = (t1);
}),
/* 2 The second module introduced, testB*/
/ * * * / (function (module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
var t2 = 'hello2';
/* harmony default export */ __webpack_exports__["default"] = (t2); })]);Copy the code
As you can see above, WebPack implements the import x from ‘./x.xx’ syntax with the __webpack_require__ method. Default means export by default. The arguments to this anonymous function are an array, and each module is a member of this array. Modules are resolved into functions, resulting in a separate scope, and there is no variable conflict between modules. In mode: Development, there are some differences, but they are basically the same. The bootstrap code provided by WebPack makes the relationships between modules clear and independent, and easier to split and combine.
Conclusion:
Putting all modules in the same file provides the base code to keep the relationships between modules as they are. All modules are treated as members of an array parameter of a giant anonymous function. Each member of the array is a function, which means webpack treats each module as a function. To maintain the privatization of modules. Starting with the first entry argument, only functions with subscript 0 will be executed first. Each module corresponds to a subscript, and WebPack handles the relationships between modules at compile time. For example, a.js import B.JS. If b.js is subscript 3, it will be in a.js, webpack.require(3). This is how WebPack maintains the relationships between modules.
This section describes how Loader works
-
Webpack can be zero-configured. The default is SRC /index.js –> dist/main.js
-
Webpack treats all modules it encounters as if they were JavaScript
- Each loader needs to export a function
- The result of the function is executed as JavaScript code (which is placed in the body of each module’s function), so the function must return standard executable JavaScript code.
- The input is what needs to be parsed
- The output is the result of the processing
1.Multiple Loaders can be used for the same resource in sequence2.Works when the module is loadedCopy the code
- Each loader needs to export a function
How the Plugin works
Loader focuses on loading resource modules and packaging the whole module
Webpack enhances WebPack automation
Plugin’s ability to clean up packaged directories, copy resource files, compress output code, and more.
Webpack’s plug-in mechanism is implemented by the hook mechanism. Similar to events in the Web, there are many links in the working process of the plug-in. To facilitate the extension of the plug-in, WebPack mounts hooks to each link, so that the operation and development of the plug-in is to extend the capabilities in the hook.
The plugin must be either a function or an object containing the apply method.
Plugins are implemented by extending functions within lifecycle hooks.
SoureMap
- Sourcemap addresses issues where the run-time code is inconsistent with the development-time code, causing debugging to fail and error messages to fail to locate.
If you need to debug jquery.js, you need to add it to the last line of the imported jquery.min.js
/ / # sourceMappingURL = jquery – 3.4.1 track. Min. The map
Tell the file to find jquery-3.4.1.min.map. This file records the mapping between the code after the transformation and the code before the transformation.
Currently, There are 12 implementations of WebPack’s sourcemap style support, each with varying levels of efficiency and effectiveness.
-
The eval mode
eval('console.log(123)') // Vm122:1 runs on a temporary VM. eval('console.log(123) //# sourceURL=./foo/bar.js') //./foo/bar.js:1./foo/bar.js // Use the sourceURL to specify the runtime name or path. Copy the code
devtools: //# sourceURL=webpack:///./ SRC /main.js? To mark the file path. Represents that the module only wants the path of the source file. The builds are the fastest, but the results are mediocre. You can only locate which file has the problem. No source map will be generated
Devtools: eval-source-map, which also uses the eval function to execute module code. In addition to helping us locate the problem file, we can also determine the row and column information. Compared with eval, the generated source map introduced in the form of dataURL is generated inside the generated JS. The sourcemap is Babel converted, not the original.
Cheap-eval-source-map devtools: cheap-eval-source-map, which adds a cheap version to the previous eval-source-map. Compare this to the previous information that can only locate rows, but not columns. But the build speed has accelerated. The principle of source map is the same as above
Cheap-modol-eval-source-map, rather than cheap-eval-source-map, maps the actual source code, not the compiled one. The xxx.js.map file is generated. Other pain cheap-eval-source-map
The inline-source-map has the same effect as the source map, but the.js.map file is placed in the last line of the compiled file as a dataURL. Import with #sourceMappingURL. The normal one is to generate.map.js files
Hidd-source-map: The sourcemap file is generated during the build process. Sourcemap is not commented into the code. This sourcemap style is usually used when developing third-party packages.
Nosource-source-map, which allows you to see the location of the error but not the source code, provides the location of the error but does not show it to you. This is a way to prevent source code exposure in a production environment.
-
Select the sourcemap for best practices
-
In a development environment: cheap-mod-eval-source-map.
-
In a production environment: None
-
In a production environment: nosource-source-map
- With little information about the code itself, choosing nosource-source-map can locate the error without exposing the source code.
-
Actual development on the path problem
- In the process of building scaffolding, the path problem was a headache, and I was unable to find a good solution, mainly due to a lack of understanding of some properties of configurable paths in WebPack. Such as publicPath, filename, where HTML is packaged.
publicPath
Webpack-packed modules are placed in the output directory by default.
- PublicPath is the url of the resource referenced in the runtime browser with publicPath as its prefix.
- The default is the empty string “”, which represents the root directory of the web site
- There is a/at the end of the publicPath value. This/cannot be omitted because it is the form of path concatenation
import icon from './icon.png'
// If there is no publicPath, icon is './[hash].png'
// icon = publicPath+ './[hahs].png'
Copy the code
- Webpack-dev-server will also default from
publicPath
As a benchmark, you use it to decide in which directory to enable the service to access the files that webPack outputs.
The resource path in the HTML page is automatically injected with the value of filename in the output. The value of filename in output. /backend/js/app.11c8b942e5dd4b3a51b5.js? 2C61C09c3e5bFF69e658 is automatically injected into index.html as SRC /href, but the packaging location is determined by path and filename together, that is, the result of filename needs to be set to the server as the root path of the project. Because only the root path can find it. If publicPath is set, all static resource paths will be prefixed with the publicPath publicPath. (No directory is generated, just add the value of publicPath before the imported path)// Set publicPath: '/ ABC '
<script src=/abc/backend/js/app.57e28a966ab9582ff286.js? 1dcc397cc95f5934ef81></script> </script> index.html path is determined by filename However, the path of the resource address in the code is determined by filename+publicPath. For loaders, some loaders have their own publiPath, but you can also set filename to replace the publicPath once and for allCopy the code
HMR
One of the most powerful features in WebPack
-
Module hot replacement
A module can be replaced in real time during the running of an application without affecting the running status of the application. Hot replacement only replaces the modified modules to the application in real time, without the need to completely refresh the application.
- Hot updates can hot update not only text files, but other types of files as well.
-
Open the HMR
-
HMR has been integrated into Webpack-dev-serve, using webpack-dev-serve –hot to start hot updates.
- We found that with hot update enabled, only the CSS works out of the box, and the JS changes still refresh the page. This is because different modules have different logic, and different logic leads to different processing processes. Each JS file needs to handle the hot update of the JS file separately for the js file (according to the logic of the page). Webpack has no way to provide a universal replacement, but vue-CLI or creant-React-app scaffolds can do HMR because they are frameworks, each of which meets certain rules, and the framework inherits HMR hot mode replacement internally.
-
So how do you provide HMR for each JS separately? Use the module.hot.accept(file path, () => {// hot replace logic}) provided by Webpack to simply show the HMR of the image below
if (module.hot) {
// The HMR of the picture
module.hot.accept('./test.png'.() = > {
img.src = background
console.log(background)
})
}
Copy the code
- Problem: If we use hot: true to enable hot mode replacement, if the replacement fails, such as an error in the code, then we fall back on the HMR setting using automatic refresh
HotOnly: true // Use HMR only, no fallback to live reloading.
webpack.DefinePlugin
- This is to inject global variables into the project. Key: value This value must be a JS code fragment.
new webpack.DefinePlugin({
// The value requires a code snippet
API_BASE_URL: JSON.stringify('https://api.example.com')})Copy the code
tree-shaking
To shake the tree is to shake the dead-code out of the code. Mode: production. Tree shaking is automatically enabled.
Realization of Tree-shaking
optimization: {
usedExports: true.// Mark the dead leaves
minimize: true // Be responsible for shaking off the marked dead leaves
}
Copy the code
- The test tree – shaking
let foo = () = > {
let x = 1
if (false) {
console.log('never readched')}let a = 3
return a
}
let baz = () = > {
var x = 'baz is running'
console.log(x)
function unused () {
return 5
}
return x
let c = x + 3
return c
}
module.exports = { // CommonJS module specification export
baz
}
export { // ESM module specification export
baz
}
Copy the code
- The result of the package
EsModule specifies packaged modules ([function (e, n, r) {
"use strict";
r.r(n);
var t;
t = "baz is running".console.log(t),
console.log("main.js running")}]); Commonjs specification packaged modules ([function (e, n, t) {(0, t(1).baz)(), console.log("main.js running")},function (e, n, t) {
e.exports = {
baz: function () {
var e = "baz is running";
return console.log(e),
e
}
}
}
]);
Copy the code
- Why do you need the esModule specification to do tree-shaking
1.ES6's module introduction is statically analyzed, so it is possible to determine exactly what code has been loaded at compile time.2.Analyze the program flow to determine which variables are not being used, referenced, and then delete the code.Copy the code
Scope Scope increased
Merge module functions
- Enabled to merge module functions, instead of one function per module, all modules are put into one function.
- Combine all modules into one function whenever possible.
- It not only improves the operation efficiency, but also reduces the volume of output code.
optimization: {
usedExports: true.// Mark the dead leaves
minimize: true.// Be responsible for shaking off the marked dead leaves
concatenateModules: true. // Enable the Scope reactor-based control
}
Copy the code
The tree – shaking and Babel
- Many sources say that tree-shaking will fail if we use Babel-Loader.
- The premise for Tree-shaking is that modules must be ESModule standard. Code packaged with WebPack must use ESM standards. Before packaging, WebPack assigns modules to different loaders for processing, and packages the results of the loader processing together. When babel-Loader processes JS, it may process esModule conversion to commonJS specification. However, after our experiments, even after using Babel-Loader, tree shaking is still carried out, because the new version of Babel-Loader has automatically turned off esModule conversion for us.
loader: 'babel-loader'.options: {
presets: [['@babel/preset-env', { modules: 'commonjs' }] // Turn on the esModule conversion to CommonJS, which will not be converted to CommonJS.]}Copy the code
- Still to be explored ~~~~~~~~~~~~~~~~~~~~~~
sideEffects
Side effects
-
Allowing us to configure to identify if our code has side effects gives tree-Shaking more space to compress.
-
Side effect: What the module does when it executes besides exporting members. For example, adding the prototype method to the XXX prototype will contaminate the XXX prototype when others introduce it.
-
Generally used to flag NPM packages for side effects
-
How to Use:
- Use sideEffects to turn this on in webpack.config.js
- Configure sideEffects: false in packjson to indicate that the code in this project does not have any sideEffects.
-
Make sure your code has no side effects before you use it. Otherwise side effect code will be deleted by mistake.
Webpack code subcontracting/cutting
- The bundle size is too large and needs to be resolved. Not every module is necessary at startup time.
- Subcontract and load on demand
- Multiple entry packing
- Dynamic import and load on demand
Multiple entry packing
- Generally suitable for multi-page applications
- Each page corresponds to a package entry
- The common parts are extracted separately
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'none'.entry: {
index: './src/index.js'.album: './src/album.js'
},
output: {
filename: '[name].bundle.js'
},
optimization: {
splitChunks: {
// Automatically extract all common modules into a separate bundle
chunks: 'all'}},module: {
rules: [{test: /\.css$/,
use: [
'style-loader'.'css-loader']]}},plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'Multi Entry'.template: './src/index.html'.filename: 'index.html'.chunks: ['index']}),new HtmlWebpackPlugin({
title: 'Multi Entry'.template: './src/album.html'.filename: 'album.html'.chunks: ['album']]}})Copy the code
Dynamic import and load on demand
-
All dynamically imported modules are automatically subcontracted
-
The logic of the code controls when a dynamic import is required, or when the module is loaded.
-
Magic note: dynamically packaged files can be renamed and flexible combinations of files can be made.
-
Import (‘ path ‘).then(data => {}) import() is the ESModule specification syntax, and this method returns a promise.
-
How is Loading On Demand WebPack packaged? This is the bootstrap code webPack provides for the import() syntax.
__webpack_require__.e = function requireEnsure(chunkId) {
var promises = [];
var installedChunkData = installedChunks[chunkId];
if(installedChunkData ! = =0) { // 0 means "already installed".
// a Promise means "currently loading".
if(installedChunkData) {
promises.push(installedChunkData[2]);
} else {
// setup Promise in chunk cache
var promise = new Promise(function(resolve, reject) {
installedChunkData = installedChunks[chunkId] = [resolve, reject];
});
promises.push(installedChunkData[2] = promise);
// start chunk loading
var script = document.createElement('script'); // Create a script tag
var onScriptComplete;
script.charset = 'utf-8';
script.timeout = 120; // Set the timeout period for script
script.src = jsonpScriptSrc(chunkId); / / set the SRC
// create error before stack unwound to get useful stacktrace later
var timeout = setTimeout(function(){
onScriptComplete({ type: 'timeout'.target: script }); // The completed logic
}, 120000);
script.onerror = script.onload = onScriptComplete;
document.head.appendChild(script); // Insert into the page}}return Promise.all(promises); // Return a promise
};
Copy the code
As you can see, the dynamic import is to create the script, then get the SRC of the script tag, and insert the created script tag into the head.
- Implement an on-demand loading of hash routes
const render = () = > {
const hash = window.location.hash || '#posts'
const mainElement = document.querySelector('.main')
mainElement.innerHTML = ' '
if (hash === '#posts') {
// mainElement.appendChild(posts())
import(/* webpackChunkName: 'components' */'./posts/posts').then(({ default: posts }) = > {
mainElement.appendChild(posts())
})
} else if (hash === '#album') {
// mainElement.appendChild(album())
import(/* webpackChunkName: 'components' */'./album/album').then(({ default: album }) = > {
mainElement.appendChild(album())
})
}
}
render()
window.addEventListener('hashchange', render)
Copy the code
Output file name hash
- In a production environment, filenames are hashed. To the client, a new file name is a new request, so there is no caching problem.
- hash
- At the project level, any change in any part of the project will change the hash value,
- chunkhash
- At the chunk level, the hash value of the files along the same path is the same. However, if the files along the same path change, the hash value of the files along the same path changes.
- For example, A.JS introduces A.SS. A.js and A.SS are on the same road.
- contenthash
-
File level hash, which changes when the file is modified based on the hash generated by the file.
-
Webpack is undeniably responsible for the evolution of the big front end to this day. Framework development now generally uses scaffolding tools that are highly out-of-the-box, but an understanding of WebPack is essential. To understand how our program has evolved from a chicken covered in feathers to a DRF chicken with a savory Orleans flavor.