Previous articles:
- Relearn webpack4 principle analysis
- Relearn webpack4 basics
- Re-learn webpack4 loader development
- Relearn webpack4 plugin development
- Relearn webpack4 packaging libraries and components
- Relearn webpack4 build speed and volume optimization
background
There are many front-end cache schemes, such as HTTP negotiation cache, PWA hijacking, FROM Memory cache, from Disk cache, App container cache, etc. This article is based on the webpack perspective of the cache scheme, modern browsers support better and better cache, this scheme is now obsolete. But the design idea is worth learning, so let’s get started…
Before we start, let’s take a look at the browser cache strategy
- The from disk cache: Similarly, the resource is fetched from disk and has been loaded at some previous time. The server will not be requested but the resource will not be released when the page is closed because it is stored on disk. The next open will still be from disk cache.
In the browser developer tools Network Size bar will appear in three cases:
- from memory cache
- from disk cache
- Resource size (e.g. 100.6K)
Principle of Three-level caching
- 1. Search for the memory. If the memory exists, load the memory.
- 2. If it is not found in the memory, obtain it from the hard disk. If it exists in the hard disk, load it from the hard disk.
- 3. If it is not found in the hard disk, make a network request;
- 4. The loaded resources are cached to the hard disk and memory.
HTTP status code and differences
- 200 form memory cache
If no request is made to the server, the resource is loaded and cached in the memory, and the cache is directly read from the memory. When the browser is closed, the data will not exist (the resource is freed). When the same page is opened again, the from memory cache will not appear.
- 200 from disk cache
If no request is made to the server, the resource has been loaded at a previous time and the cache is directly read from the hard disk. After the browser is closed, the data still exists. The resource will not be released when the page is closed.
- 200 Indicates the resource size
Request server
- 304 Not Modified Indicates the size of a negotiation packet
Requests the server, finds that the data has not been updated, and the server returns this status code. The data is then read from the cache, known as a negotiated cache
Execution Order (Chrome)
Access -> 200 -> Exit browser -> second entry -> 200(from disk cache) -> refresh -> 200(from memory cache)
Cache plug-in target
- Front-end cache: Stores the packaged Runtime, Vendor, and index files in localStorage and updates them incrementally
- The server is requested to obtain resources during the first load and the script stored in localStorage is requested during the second load
- When there is an incremental update, localStorage is also an incremental update
Production environment HTML template
- Use placeholders
- Dynamic prefetch
- Dynamic polyfill
- Introduce basic project libraries, such as React and React – DOM, through CDN
<! --injectcss--> <! --injectjs-->Copy the code
<! DOCTYPEhtml>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-dns-prefetch-control" content="on">
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no,width=device-width">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="format-detection" content="telephone=no, email=no">
<meta name="renderer" content="webkit"><meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="HandheldFriendly" content="true"><meta name="MobileOptimized" content="320">
<meta name="screen-orientation" content="portrait"><meta name="x5-orientation" content="portrait">
<meta name="full-screen" content="yes">
<meta name="x5-fullscreen" content="true">
<meta name="browsermode" content="application">
<meta name="x5-page-mode" content="app">
<meta name="msapplication-tap-highlight" content="no">
<link rel="dns-prefetch" href="<%= htmlWebpackPlugin.options.prefetch %>">
<link rel="dns-prefetch" href="//cdn.bootcss.com">
<link rel="shortcut icon" href="./assets/images/favicon.png" type="image/x-icon">
<style>* {margin:0;padding:0; }html.body{height:100%; }#pre-loading{position:absolute;width:100%;height:100%;z-index:10;transition:opacity 500ms;background:#f5f5f9; }#pre-loading.loaded{opacity:0; }#pre-loading.end{display:none; }.spline{position:absolute;width:80px;height:50px;left:0;text-align:center;z-index:11;left:50%;top:50%;margin: -25px 0 0 -40px; }.spline>div{display:inline-block;width:6px;height:100%;background:rgb(74.76.91); -webkit-animation:strachdelay 1.2 s infinite ease-in-out;animation:strachdelay 1.2 s infinite ease-in-out;margin:0 2px; }.spline .line2{-webkit-animation-delay:0.1 s; }.spline .line3{-webkit-animation-delay:0.2 s; }.spline .line4{-webkit-animation-delay:0.3 s; }.spline .line5{-webkit-animation-delay:0.4 s; }@-webkit-keyframes strachdelay{0%.40%.100%{-webkit-transform:scaleY(.4); }20%{-webkit-transform:scaleY(1); }}@keyframes strachdelay{0%.40%.100%{transform:scaleY(.4); }20%{transform:scaleY(1); }}</style>
<! --injectcss-->
<title>xxxx</title>
</head>
<body>
<div id="app"></div>
<div id="pre-loading">
<div class="spline">
<div class="line1"></div>
<div class="line2"></div>
<div class="line3"></div>
<div class="line4"></div>
<div class="line5"></div>
</div>
</div>
<script src="https://polyfill.io/v3/polyfill.js"></script>
<script src="https://cdn.bootcss.com/react/16.7.0/umd/react.production.min.js"></script>
<script src="https://cdn.bootcss.com/react-dom/16.7.0/umd/react-dom.production.min.js"></script>
<script src="https://cdn.bootcss.com/react-router-dom/4.2.2/react-router-dom.min.js"></script>
<script src="https://cdn.bootcss.com/mobx/5.1.0/mobx.umd.min.js"></script>
<script src="https://cdn.bootcss.com/mobx-react/5.2.5/index.min.js"></script>
<script src="https://cdn.bootcss.com/axios/0.18.0/axios.js"></script>
<! --injectjs-->
</body>
</html>
Copy the code
Webpack configuration key points
Webpack3 Configuration essentials
- externals
- code split
- html-webpack-plugin
-
- Inject: false, give the work of injecting chunks into the template to custom plug-ins
-
- Inject the prefetch variable
output: {
path: path.resolve(__dirname, '.. /dist'),
publicPath: publicPath,
filename: 'scripts/[name].[chunkhash:5].js',},devtool: false.externals: {
'react': 'React'.'react-dom': 'ReactDOM'.'react-router-dom': 'ReactRouterDOM'.'axios': 'axios'.'mobx': 'mobx'.'mobx-react': 'mobxReact'
},
// Remove function js
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor'.minChunks: function(module){
return module.context && module.context.indexOf("node_modules")! = = -1; }}),new webpack.optimize.CommonsChunkPlugin({
name: 'runtime'.minChunks: Infinity
}),
new HtmlWebpackPlugin({
filename: 'index.html'.template: path.join(__dirname, '.. /app/.html'),
minify: {// Compress HTML files
removeComments:true.// Remove comments from HTML
collapseWhitespace:true.// Remove whitespace and newline characters
removeEmptyAttributes:true,},prefetch:
cf.assetsPublicPath[cf.assetsPublicPath.length - 1] = ="/"
? cf.assetsPublicPath.substr(0, cf.assetsPublicPath.length - 1)
: cf.assetsPublicPath,
chunks: ['vendor'.'runtime'.'index'].inject: false
}),
// Put the plug-in after the HtmlWebpackPlugin because you are processing output files
new htmlAfterWebpackPlugin(),
Copy the code
Webpack4 Configuration essentials
- externals
- code split
- html-webpack-plugin
-
- Inject: false, give the work of injecting chunks into the template to custom plug-ins
-
- Inject the prefetch variable
externals: {
'react': 'React'.'react-dom': 'ReactDOM'.'react-router-dom': 'ReactRouterDOM'.'axios': 'axios'.'mobx': 'mobx'.'mobx-react': 'mobxReact'
},
/** * optimizations include code split * and the code split of the manifest is extracted into a separate runtimeChunk configuration */
optimization: {
splitChunks: {
chunks: "all".cacheGroups: {
// Extract the code in node_modules
vendors: {
test: /[\\/]node_modules[\\/]/,
name: "vendor".chunks: "all",},commons: {
// Async set to extract common code from asynchronous code
chunks: "async".name: "commons-async"./** * minSize defaults to 30000 * To really split the code as we set it * you need to reduce minSize */
minSize: 0.// Common code for at least two chunks
minChunks: 2,},styles: {
name: "index".test: /\.(css|scss)$/,
chunks: "all".enforce: true,}}},/ / / * *
// * corresponding to minchunks: Infinity
// * Extract the WebPack runtime code
// * Set true or name
/ / * /
runtimeChunk: {
name: "runtime",},minimizer: [
new UglifyJsPlugin({
cache: true.parallel: true.uglifyOptions: {
compress: {
warnings: false.drop_debugger: true.drop_console: false,}}}),new OptimizeCSSAssetsPlugin({}),
],
},
plugins: [
/ / CSS
new MiniCssExtractPlugin({
filename: `${cf.css}/[name].[contenthash:5].css`,}).// css: tree-shaking
new PurgecssPlugin({
paths: glob.sync(`${CssTreeShakPaths}/ * * / * `, { nodir: true})}),new OptimizeCSSAssetsPlugin({
assetNameRegExp: /\.css$/g,
cssProcessor: require("cssnano"),}),// Enable caching
new HardSourceWebpackPlugin(),
new HtmlWebpackPlugin({
filename: "index.html".template: resolve(__dirname, ".. /src/index_pro.html"),
title: "Background Management System".prefetch:
cf.assetsPublicPath[cf.assetsPublicPath.length - 1] = ="/"
? cf.assetsPublicPath.substr(0, cf.assetsPublicPath.length - 1)
: cf.assetsPublicPath,
minify: {
collapseWhitespace: true.removeAttributeQuotes: true,}}),// Put the plug-in after the HtmlWebpackPlugin because you are processing output files
new htmlAfterWebpackPlugin(),
// Code packaging analysis
// new BundleAnalyzerPlugin({ analyzerPort: 3011 }),].Copy the code
htmlAfterWebpackPlugin.js
Step 1: Verify that the HTML-webpack-plugin hook can get chunks,
- Webpack3 and WebPack4 have different hooks, and other logic is the same
- Get the CDN addresses of CSS and JS through chunks generated on the EMIT
- Replace placeholders with the resource’s CDN address, template
- Ensure the order in which js is loaded
const HtmlWebpackPlugin = require("html-webpack-plugin");
// Save the chunks for WebPack4
let ASSETS = {};
// Sort js, in order to ensure that the order of loading, to prevent some htmlWebpackPlugin uneasy pattern of cards
function sortByOrders(arrs) {
arrs = arrs.sort((mapf, maps) = > {
let fIndex = 3, sIndex = 3;
if (mapf.indexOf('runtime') > -1) {
fIndex = 0;
} else if (mapf.indexOf('vendor') > -1) {
fIndex = 1;
} else if (mapf.indexOf('index') > -1) {
fIndex = 2;
}
if (maps.indexOf('runtime') > -1) {
sIndex = 0;
} else if (maps.indexOf('vendor') > -1) {
sIndex = 1;
} else if (maps.indexOf('index') > -1) {
sIndex = 2;
}
return fIndex > sIndex;
});
return arrs;
}
function cssHelp(arrs){
let _cssHtml = ' ';
/ / deal with CSS
for(let i=0; i<arrs.length; i++){ _cssHtml +='<link rel="stylesheet" type="text/css" href="' + arrs[i] + '" / >'
}
return _cssHtml;
}
function jsHelp(arrs){
let maps = {};
let reg= /.+\/((\w|-)+)\.((\w{5})\.) ? \w+.js$/;
arrs = sortByOrders(arrs);
let _html = ' ';
console.log(arrs);
for(let i=0; i<arrs.length; i++){ _html +='<script src="' + arrs[i] + '"></script>';
}
return _html;
}
class htmlAfterWebpackPlugin {
apply(compiler) {
if (compiler.hooks) {
// wepback4
compiler.hooks.compilation.tap(
"htmlAfterWebpackPlugin".(compilation) = > {
HtmlWebpackPlugin.getHooks(
compilation
).beforeAssetTagGeneration.tapAsync(
"htmlAfterWebpackPlugin".// <-- Set a meaningful name here for stacktraces
(data, cb) = > {
ASSETS = data.assets;
cb(null, data); }); HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync("htmlAfterWebpackPlugin".// <-- Set a meaningful name here for stacktraces
(data, cb) = > {
console.log(ASSETS);
let { html } = data;
html = html.replace("<! --injectcss-->", cssHelp(ASSETS.css));
html = html.replace("<! --injectjs-->", jsHelp(ASSETS.js));
data.html = html;
cb(null, data); }); }); }else {
// webpack3
compiler.plugin("compilation".(compilation) = > {
compilation.plugin(
"html-webpack-plugin-before-html-processing".(data, cb) = > {
let assets = data.assets;
let html = data.html;
html = html.replace("<! --injectcss-->", cssHelp(assets.css));
html = html.replace("<! --injectjs-->", jsHelp(assets.js));
data.html = html;
cb(null, data); }); }); }}}module.exports = htmlAfterWebpackPlugin;
Copy the code
Step 2: Implement caching
- Modify the code from the first step
- CSS is still handled the way it used to be, mainly dealing with JS
- First access: Store JS in localStorage
- On the second visit, get the JS resource from localStorage
- Webpack incrementally updates resources to localStorage (reduces resource acquisition costs for update tape)
Modify the jsHelp method
function jsHelp(arrs) {
let maps = {};
let reg = /.+\/((\w|-)+)\.((\w{5})\.) ? \w+.js$/;
arrs = sortByOrders(arrs);
arrs = sortByOrders(arrs);
arrs.forEach((arr) = > {
let regRet = reg.exec(arr);
if (regRet) {
regRet = regRet[1]; maps[regRet] = arr; }});console.log(Object.keys(maps));
return `
<script>
var jsMap = The ${JSON.stringify(maps)}; var createScript = function(content) { var scriptDom = document.createElement('script'); scriptDom.innerHTML = content; document.body.appendChild(scriptDom); } var getScript = function(key) { axios.get(jsMap[key]).then(function(value) { localStorage.setItem(key, jsMap[key]); localStorage.setItem(jsMap[key], value.data); If (key === 'index'){setTimeout(function(){createScript(value.data); }, 0); }else{ createScript(value.data); }}); }; var jsMapKeys = Object.keys(jsMap); for(var i=0; i<jsMapKeys.length; i++){ var key = jsMapKeys[i]; (function(vkey) { var val = localStorage.getItem(vkey); if (! val) { getScript(vkey); } else { if (val === jsMap[vkey]) { var scriptValue = localStorage.getItem(jsMap[vkey]); scriptValue && createScript(scriptValue); } else { localStorage.removeItem(val); getScript(vkey); } } }(key)); } </script> `;
}
Copy the code
The resulting HTML looks like this
<! DOCTYPEhtml>
<html>
<head>
<meta charset=utf-8>
<meta http-equiv=x-dns-prefetch-control content=on>
<meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no,minimal-ui">
<meta http-equiv=X-UA-Compatible content="ie=edge">
<meta name=screen-orientation content=portrait>
<meta content=yes name=apple-mobile-web-app-capable>
<meta name=apple-mobile-web-app-status-bar-style content=black-translucent>
<meta name=format-detection content="telephone=no">
<meta name=full-screen content=yes>
<meta name=x5-fullscreen content=true>
<title>Background management system</title>
<link rel=dns-prefetch href=/ / 127.0.0.1:8080>
<link rel=dns-prefetch href=//cdn.bootcss.com>
<link rel=stylesheet href=https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css>
<link rel=stylesheet href=https://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css>
<! -- Insert CSS -->
<link rel="stylesheet" type="text/css" href="/ / 127.0.0.1:8080 / CSS/index ddc73. CSS" />
</head>
<body>
<div id=app></div>
<script src=https://polyfill.io/v3/polyfill.js></script>
<script src=https://cdn.bootcss.com/react/16.7.0/umd/react.production.min.js></script>
<script src=https://cdn.bootcss.com/react-dom/16.7.0/umd/react-dom.production.min.js></script>
<script src=https://cdn.bootcss.com/react-router-dom/4.2.2/react-router-dom.min.js></script>
<script src=https://cdn.bootcss.com/mobx/5.1.0/mobx.umd.min.js></script>
<script src=https://cdn.bootcss.com/mobx-react/5.2.5/index.min.js></script>
<script src=https://cdn.bootcss.com/axios/0.18.0/axios.js></script>
<! Insert dynamic script -->
<script>
var jsMap = {
"runtime": "/ / 127.0.0.1:8080 / js/runtime. 15 e89. Js." "."vendor": "/ / 127.0.0.1:8080 / js/vendor. 5 cc2c. Js." "."index": "/ / 127.0.0.1:8080 / js/index bdb95. Js." "};var createScript = function (content) {
var scriptDom = document.createElement('script');
scriptDom.innerHTML = content;
document.body.appendChild(scriptDom);
}
var getScript = function (key) {
axios.get(jsMap[key]).then(function (value) {
localStorage.setItem(key, jsMap[key]);
localStorage.setItem(jsMap[key], value.data);
// Ensure that the trailing js is inserted last
if (key === 'index') {
setTimeout(function () {
createScript(value.data);
}, 0);
} else{ createScript(value.data); }}); };var jsMapKeys = Object.keys(jsMap);
for (var i = 0; i < jsMapKeys.length; i++) {
var key = jsMapKeys[i];
(function (vkey) {
var val = localStorage.getItem(vkey);
if(! val) { getScript(vkey); }else {
if (val === jsMap[vkey]) {
var scriptValue = localStorage.getItem(jsMap[vkey]);
scriptValue && createScript(scriptValue);
} else {
localStorage.removeItem(val);
getScript(vkey);
}
}
}(key));
}
</script>
</body>
</html>
Copy the code
Okay? Look at the curative effect
For the first time,
The second time
The end of the whole
It doesn’t really work anymore, but it did help me a lot in the early days. It made me feel good to brag about other people.
After a week, I reorganized my thoughts on using webpack4 in the past few years. As a farewell gift to webpack4, I hope we don’t see each other again.
Webpack 5 is beckon to us, read webPack 5 source code, come to a conclusion: all webpack techniques and add-ons, loader before V5… All abandoned, MY f * * king heart broken, no more jokes…
Stay up for it: he’s coming, he’s coming, he’s coming with the green hat (webpack5)
It’s late at night, it’s time to go home and get some sleep
Join us at ❤️
Bytedance Xinfoli team
Nice Leader: Senior technical expert, well-known gold digger columnist, founder of Flutter Chinese community, founder of Flutter Chinese community open source project, well-known developer of Github community, author of dio, FLY, dsBridge and other well-known open source projects
We look forward to your joining us to change our lives with technology!!
Recruitment link: job.toutiao.com/s/JHjRX8B