preface
One of the most noticeable aspects of daily development is the amount of time it takes to compile and package a project, especially if the deployment is packaged more frequently.
For example, a previous project I worked on “cold start” took about 86 seconds to compile:
Can you stand it? Obviously not, so I decided to optimize it, otherwise it would be too bad for the development experience. Here are some things I did during the optimization process.
Analysis time module
View vue-CLI built-in configuration
Vue-cli-service inspect can be used to easily view the built-in configuration content of VUe-CLI. Enter: Vue inspect – mode production webpack. Config. Production. Js, will be in the project and the SRC directory to generate webpack. At the same level config. Production. Js file.
Analysis tools
To get time-consuming modules, you need some analysis tools, such as:
- speed-measure-webpack-plugin
- webpack-bundle-analyzer
speed-measure-webpack-plugin
This plugin can measure the speed of web package construction and output the compilation time of each module, which can help us find the time-consuming module better.
The vue. Config. Js configuration
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
module.exports = { ... .configureWebpack: (config) = >{... config.plugins.push(newSpeedMeasurePlugin(), ); }};Copy the code
Restart the
From the output below, you can easily see the corresponding time consuming module.
webpack-bundle-analyzer
This plugin visualizes the size of a web package output file and provides an interactive, scalable tree diagram.
The vue. Config. Js configuration
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
+ const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = { ... .configureWebpack: (config) = >{... config.plugins.push(new SpeedMeasurePlugin(),
+ newBundleAnalyzerPlugin() ); }};Copy the code
Restart the
From the output below, you can easily see the size of each module when packed.
To optimize the
Thread-loader – Enables multi-threading optimization
According to the compilation time output of speed-measure-webpack-plugin, the following loaders have a large proportion of compilation time:
- vue-loader
- ts-loader
- babel-loader
- image-webpack-loader
- postcss-loader
This section can be optimized with thread-loader because it can put the time-consuming content into a separate thread for execution, but not for all loaders because this process is also expensive.
Note: Use thread-loader only for time-consuming operations. Otherwise, using thread-loader may lead to a longer project build time because each worker is an independent Node.js process, which costs about 600ms. It also restricts cross-process data exchange and so on.
To recap, the “cold start” time of the previous project without using Thread-Loader is about 86 seconds:
After thread-loader is used for babel-loader only, the “cold start” time of the project is about 78 seconds:
It takes longer for other Laoder to use thread-Loader to find, so the best result after trying is obtained
Hard-source-webpack-plugin — Uses cache optimization
There are several ways to cache in Webpack:
cache-loader
hard-source-webpack-plugin
CacheDirectory flag for babel-loader
All of these caching methods have initial startup overhead, meaning they make “cold starts” longer, but secondary starts save a lot of time.
Vue-cli has built-in cacheDirectory flags for cache-loader and babel-loader. The corresponding configurations are as follows:
By default, babel-Laoder, TS-Loader, vue-loader have been cached during the “cold start”.
Configure the hard-source-webpack-plugin first:
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
+ const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
module.exports = { ... .configureWebpack: (config) = >{... config.plugins.push(// Provide an intermediate cache for the module. The cache path is node_modules/. Cache /hard-source
// Resolve undetected configuration changes
+ new HardSourceWebpackPlugin({
root: process.cwd(),
directories: [].environmentHash: {
root: process.cwd(),
directories: [].// The main reason for configuring files is to solve the problem that cache does not take effect due to configuration update
// Plugin will rebuild part of the cache if the packet changes after configuration
files: ['package.json'.'yarn.lock']}}),new SpeedMeasurePlugin(),
newBundleAnalyzerPlugin(), ); }};Copy the code
The “cold start” and “secondary start” times after using the hard-source-webpack-plugin are as follows:
Reduce packing volume
Reduce js code size
In the packaged dist directory, look for console.log. The result is as follows:
You can see that the default configuration in vue-CLI does not remove console.log statements from js files, so this is something that can be further optimized.
Here you can use uglifyjs-webpack-plugin or terser-webpack-plugin to delete comments and compress JS code. The specific configuration can be directly linked to refer to.
Compress images
For some image pixel without high requirements of image resources for compression, here can be through image-webpack-plugin or image-minimizer-webpack-plugin image resource compression, specific configuration can be directly linked to refer to.
External extension — externals & CDN
The externals option is used to prevent import packages from being packaged into the bundle, and instead to obtain external dependencies at runtime
In simple terms, the JS should have been packaged in the bundle. Now, by configuring the externals option, we can make it a resource outside the bundle, namely a CDN resource, and request this resource when the code runs.
For example, the following is the configuration of externals in the project:
chainWebpack: (config) = >{...// Import resources through CDN
config.externals({
echarts: 'echarts'.nprogress: 'NProgress'}); }Copy the code
DllPlugin plugin – Optimizes packaging time
The DllPlugin is responsible for packaging and breaking down bundles of libraries that are relatively stable (such as vue/ React family buckets) so that they do not need to be packaged again the next time they are packaged, thus greatly increasing build speed.
In WebPack4, DllPlugin has been integrated, so we only need to configure it
- Create a DLL. Js file for simple configuration
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
vendor: ['echarts'.'element-ui'.'vue/dist/vue.esm.js'.'vue-router'.'vuex'],},output: {
path: path.join(__dirname, 'target'),
filename: '[name].js'.library: '[name]_[hash]',},plugins: [
new webpack.DllPlugin({
// The name attribute of DllPlugin needs to be consistent with libary
name: '[name]_[hash]'.// Specify the current directory
path: path.join(__dirname, '. '.'[name]-manifest.json'),
// Context needs to be consistent with webpack.config.js
context: __dirname,
}),
],
};
Copy the code
- in
package.json
Configuration in filescript
Script:"dll": "webpack --config ./dll.js"
- The installation
webpack-cli
Because the dependency was not installed in the original dependency and is required to run the script commandwebpack-cli
- Executing script commands
npm run dll
To generate thevendor-manifest.json
File. This file is used to makeDllReferencePlugin
Be able to map to corresponding dependencies
- in
vue.config.js
In the configurationDllReferencePlugin
Plug-ins that link to packaged dependencies
const { pathResolve } = require('./build/utils.js'); // eslint-disable-line
const devConfig = require('./build/webpack.dev.conf.js'); // eslint-disable-line
const buildConfig = require('./build/webpack.prod.conf.js');
// Analysis tools
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
// Resource cache
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
// Remove stable third-party libraries to avoid repeated packaging
const DllReferencePlugin = require('webpack').DllReferencePlugin;
// Public function
const { versionSet } = require('./build/utils'); // eslint-disable-line
// Whether it is a development environment
const isDevelopment = process.env.NODE_ENV == 'development';
const vueWebpackConfig = () = > {
let envConfig = {};
if (isDevelopment) {
/ / development
envConfig = devConfig;
} else {
/ / build
versionSet();
envConfig = buildConfig;
}
const vueConfig = {
// Environment configuration. envConfig,productionSourceMap: isDevelopment, // Whether sourcdeMap is generated when the production package is built
// Extend the WebPack configuration
chainWebpack: (config) = > {
// ============ Configure the alias ============
config.resolve.alias
.set('@build', pathResolve('.. /build')) // Build the directory
.set(The '@', pathResolve('.. /src'))
.set('@api', pathResolve('.. /src/api'))
.set('@utils', pathResolve('.. /src/utils'))
.set('@views', pathResolve('.. /src/views'));
// ============ SVG processing ============
const svgRule = config.module.rule('svg');
// Clear all existing loaders.
// If you do not do this, the following loader will be appended to the existing loader for this rule.
svgRule.uses.clear();
// Add the loader to be replaced
svgRule.use('svg-sprite-loader').loader('svg-sprite-loader').options({
symbolId: 'icon-[name]'});// ============ compressed image ============
config.module
.rule('images')
.use('image-webpack-loader')
.loader('image-webpack-loader')
.options({ bypassOnDebug: true })
.end();
// ============ package analysis tool ============
if(! isDevelopment) {if (process.env.npm_config_report) {
config.plugin('webpack-bundle-analyzer').use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin).end();
config.plugins.delete('prefetch'); }}// ============ CDN resource import ============
config.externals({
// echarts: 'echarts',
nprogress: 'NProgress'}); },configureWebpack: (config) = > {
// Try to ensure that the file suffixes in the project are accurate
config.resolve.extensions = ['.ts'.'.js'.'.vue'.'.json'];
/ / processing Babel - loader
config.module.rules[12].use.unshift({
loader: 'thread-loader'}); config.plugins.push(// Provide an intermediate cache for the module. The cache path is node_modules/. Cache /hard-source
new HardSourceWebpackPlugin({
root: process.cwd(),
directories: [].environmentHash: {
root: process.cwd(),
directories: [].// The main reason for configuring files is to solve the problem that the cache does not take effect due to configuration updates. Plugin will rebuild part of the cache after configuration changes
files: ['package.json'.'yarn.lock'],}}),/ / DllReferencePlugin plug-in
new DllReferencePlugin({
context: __dirname,
// Manifest is the JSON file we packaged in step 2
manifest: require('./vendor-manifest.json'),}),// Analysis tools
new SpeedMeasurePlugin(),
newBundleAnalyzerPlugin(), ); }};return vueConfig;
};
module.exports = vueWebpackConfig();
Copy the code
Other optimization
resolve.alias & resolve.extensions
Resolve. Alias is an alias used to create import or require to make module introduction easier
For example, here are some aliases defined in the project:
chainWebpack: (config) = > {
// Configure the alias
config.resolve.alias
.set('@build', pathResolve('.. /build')) // Build the directory
.set(The '@', pathResolve('.. /src'))
.set('@api', pathResolve('.. /src/api'))
.set('@utils', pathResolve('.. /src/utils'))
.set('@views', pathResolve('.. /src/views'));
}
Copy the code
The resolve.extensions are specified as the corresponding file suffix to ensure useless look-ups and recursion when looking for modules, i.e. the file suffix in this configuration should be as few as possible.
Reduce unnecessary parsing — module.noparse
NoParse prevents Webpack from parsing any files that match a given regular expression. Ignored files should not contain import, require, define calls, or any other import mechanism. Build performance can be improved by ignoring large libraries.
For example, the configuration of module.noParse in vue-CLI is as follows:
Code level optimization
According to the contents of Webpack-bundle-Analyzer, it can be found that some JS and CSS modules are relatively large in size. At this time, we can find the corresponding file combing logic and optimize the code, such as encapsulating JS logic and extracting CSS styles. The most basic optimization is to write less repetitive styles and logic, which can also avoid some ineffective double compilations.
The last
These are some of the vue-CLI (Webapck based) optimizations you should try if you haven’t done them before.
Finally, take a look at the changes in production packaging and construction time before and after optimization: