Using your own multi-page blog as an example, execute the NPM run build, deploy the package code and visit the project, and you will find that the performance is very poor and the page will wait for a long blank time. As you can see in the figure below, the total load time is nearly 12s, which is an intolerable performance problem that urgently needs to be addressed.
Client loading diagram:
As you can see in the figure above, the main culprit for slow load times is the Vendor file, which holds all the third-party dependencies in the project. And as can be seen from the following figure, this file is also used by the management terminal, with a volume of 1.2m.
Loading diagram on the management terminal
As can be seen from the following package file structure diagram, vendor mainly includes dependencies highlight, Mavon-Editor, vue, vue-moment, marked, vue-router, iView, etc. In practice, mavon-Editor and iView are only used on the management side, while highlight and marked are only used on the client side, so one key point of optimization is to separate third-party dependencies. However, it should be taken into account when separating dependencies, even if the dependency files are still stored on the server after separation. However, relatively speaking, many mature class libraries will provide CDN nodes, and it is faster to access CDN than to access their own servers. Therefore, the first step of optimization is to introduce related dependencies in the form of CDN.
The introduction of the CDN
For VUE, VUex, VUE-Router, vuE-moment, highlight’S CDN can be imported by the client, but not by the management. Therefore, you need two different templates of HTML, configured as follows:
tpl/index.html
tpl/admin.html
With CDN, WebPack no longer needs to package these dependencies, but webPack tracks all dependencies by default, so you need to configure filtered dependencies. Open webpack.base.conf.js and add the following configuration:
externals: {
'vue': 'Vue',
'vue-router': 'VueRouter',
'vuex': 'Vuex',
'vue-moment': 'VueMoment',
'highlight.js': 'hljs',
},
Copy the code
Key is the node module name and value is a reference to the module in the project. Then run NPM run build –report and look at the packaged vendor file, as shown below. None of the dependencies in externals have been packaged. With the loss of highlight.js, vendor has more than halved in size to around 500KB.
But that’s not enough. The vendor file is still large, and mavon-Editor is half of it. Since mavon-Editor is only used on the administrative side, marked is lighter and only used on the client side to parse markdown. Therefore, further separation of vendor is needed. Webpack provides us with a plug-in, CommonsChunkPlugin, that collects all synchronized dependencies in a project for code splitting.
The code segment
In webpack.prod.conf.js, modify CommonsChunkPlugin configuration as follows:
new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', minChunks (module) { return ( module.resource && /\.js$/.test(module.resource) && module.resource.indexOf( path.join(__dirname, '.. /node_modules') ) === 0 && module.resource.indexOf('mavon-editor') < 0 && module.resource.indexOf('marked') < 0 ) } }),Copy the code
The default configuration collects all dependencies into vendor. The collected modules can be customized through the minChunks hook function, which returns a Boolean value. Webpack tracks all dependencies and calls the hook, collecting only those that return true, so we can remove non-common modules from dependencies, and the packaged vendor is the one used by both the client page and the management page. The culled marked and mavon-Editor are collected in their respective chunks, marked is packaged into the client, and Mavon-Editor is packaged into the manager. The diagram below:
However, you can see that the manage file is still large and mainly contains mavon-Editor, which is only used in article routes. Similarly, marked in the client is only used in detail routes. The next step, then, is to make the files load on demand.
Asynchronous load route
For the problem mentioned above, if the route import mode is changed to asynchronous import, webPack will package the asynchronous file separately as a new chunk and load it in the page only when needed. For example, change the route import mode to asynchronous route import as follows:
const Catagory = () => import('.. /pages/Catagory'); const Avator = () => import('.. /pages/Avator') const Comment = () => import('.. /pages/Comment') const Detail = () => import('.. /pages/Detail') const Article = () => import('.. /pages/Article')Copy the code
After the asynchronous route is used, the volume of manager and client is further reduced, and one more package file is added for each asynchronous route. These files are dynamically loaded.
After the above process, the code between multiple pages has been decoupled, and its dependent large files have been successfully separated. At this point, the page loads significantly faster. However, as you can see, the mavon-Editor dependency alone is over 250 KB, and no matter where it is packaged, it will affect the loading of the corresponding page. Next we turn on gzip compression to further reduce the size of dependencies.
Gzip compression
Generally speaking, gzip is enabled only by the server, but the server needs to compress the file according to the set compression level and then return the compressed file to the browser. In this case, webPack’s CompressionWebpackPlugin helps generate gzip zip files. If the compressed file is stored on the server in advance, the server can directly read the. Gz file on the server to save the compression time. In vue-cli2, simply change the productionGzip configuration item in config/index.js to true.
As you can see from the screenshot above, the size of the file has been further reduced. The 0.41 js file that contains mavon-editor has been reduced from over 300 KB to 107kb. Next, we enable gzip in nginx as follows:
gzip_static on;
gzip_min_length 5k;
gzip_buffers 4 16k;
gzip_comp_level 4;
gzip_types text/plain application/javascript text/css application/xml text/javascript application/x-httpd-php;
gzip_vary on;
keepalive_timeout 65;
Copy the code
When gzip_static is enabled, nginx reads pre-compressed GZ files, which reduces CPU consumption for each request for gzip compression, which is why webPack is required to compress files. All configurations can be explained by referring to the Nginx Gzip module enablement and configuration instructions.
Cache acceleration
With Nginx, caching is enabled by default, and responses to static files are etagged, as shown below. In this way, as long as the static files on the server are not changed when the browser reloads, it will be read from the cache, further optimizing the page response speed.
pre-rendered
The problem of the above solution is to reduce the size of JS resources to speed up the loading. This solution is fast enough for the first screen rendering under good network conditions, but ultimately, rendering depends on the execution logic of JS after loading, and this approach is not conducive to SEO. So to further improve the first screen loading scheme and two, one is a pre-rendered, one is the SSR, the rendering of a service, the latter solution is more complicated, I will in future articles were analyzed, and the rendering of a service than pre-rendered, dynamic joining together the data, the main advantage is that can be returned, as part of a document This allows for more SEO friendly features and dynamic sharing. Since my blog does not have these requirements and does not want to increase the load on the server considering the condition of the server (single-core 1GB), it will further speed up the loading of the first screen by using pre-rendering without considering SSR.
Prerendering relies on a prerender-spa-plugin, which is first introduced in webpack.prod.conf.js as follows:
const PrerenderSPAPlugin = require('prerender-spa-plugin')
Copy the code
Then, add the following plug-in configuration to your plugins:
new PrerenderSPAPlugin( path.join(__dirname, '.. /nginx/blog'), ['/'], {captureAfterTime: 50000, // Ignore packaging error ignoreJSErrors true, phantomOptions: '--web-security=false', maxAttempts: 10, } )Copy the code
When configured this way, the generated index.html package contains the pre-rendered DOM structure, so the first screen rendering is much faster. The chunk file loaded asynchronously is inserted into the head tag with an async property like this:
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Blog - SilentPort</title>
<link href="/static/css/client.6b5a982c3673872dbaa1a8891279d36d.css" rel="stylesheet">
<script type="text/javascript" charset="utf-8" async="" src="/static/js/3.c925dfc72043d1d1d5ac.js"></script>
</head>
Copy the code
The runtime manifest file is located at the bottom of the body. Since async causes the loading and rendering of subsequent document elements and the loading and execution of the current script to occur in parallel (asynchronously), the script script will be executed before the manifest. This will generate a webpackJsonp is not defined error. Therefore, before deployment, we need to manually change async to defer, which will also be carried out in parallel (asynchronously) with the loading of the current script script during the loading of subsequent document elements, but the execution of the current script script should be completed after all elements are parsed and before the DOMContentLoaded event is triggered. This ensures that scripts are executed after the manifest.
Optimize here, deploy online, you can experience flying first screen experience.