webpack性能优化

441 阅读4分钟

这是我参与更文挑战的第6天,活动详情查看:更文挑战

对CSS文件进行代码分割

场景

使用style-loader打包后会自动帮我们在html文件中加入<style>并将CSS往里填充,当CSS代码成千上万时,将会产生庞大的html。我们能不能在打包时将css分离出来,然后使用link标签进行引入呢?

在webpack中如何进行css的代码分割?

这块我们要借助webpack官网提供的一个插件,mini-css-extract-plugin这个插件就可以帮助我们对webpack中引入的css文件进行代码分割,不过这个插件有一个缺陷就是不支持模块热更新,这就意味着,如果我们在开发环境中使用这个插件,改变了css的样式,此时视图不会自动更新,我们需要手动刷新页面,如果在开发环境使用此插件的话效率就不是很高了,对于这个插件的使用我们在打包线上环境时使用即可;

配置

安装

npm add mini-css-extract-plugin

引入

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
    module: {
        rules: [
            {
                test: /\.css$/,
		use: [
                    MiniCssExtractPlugin.loader, 
                    {
                        loader: 'css-loader',
                        options: {
                            importLoaders: 2
                        }
                    },
                    'css-loader',
                    'postcss-loader'
                ]
            }
        ]
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: '[name].css'
        })
    ]
}
  • 将原本的style-loader替换成MiniCssExtractPlugin.loader
  • 配置了plugins后就能对css进行抽离,filename是对打包后的css进行命名
  • image.png

按上述这样配置就能实现css代码分离了,但是分离出来的css代码是还没有进行压缩过的,我们希望分离出来的css还要进行压缩,应该怎么处理呢?

optimize-css-assets-webpack-plugin这个插件可以帮助我们压缩css,配置如下:

const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
module.exports = {
    optimization: {
        minimizer: [new OptimizeCSSAssetsPlugin({})]
    }
}

减少loader的执行频率

rules: [{ 
    test: /\.js$/, 
    exclude: /node_modules/, 
    loader: 'babel-loader',
}]

如果遇到的是JS模块,上述配置写了exclude:/node_modules/也就是,不是node_modules里面的JS模块才会去使用babel loader,如果外面不写exclude的话,遇到JS模块时会将node_modules里的代码也进行一次编译,这就会降低打包的速度,其实这种引入的第三方库已经是编译好了的,我们完全没有必要在对它进行一次编译,在这里加入exlcude就可以很好的提高JS的打包速度;

使用DllReferencePlugin插件进一步提高打包速度

场景

现在我们已经做了代码分割的配置,第三方引入的模块会被打包到vendors中,以下是我们使用的代码:

import React, { Component } from 'react';
import ReactDom from 'react-dom';

class App extends Component {
    render() {
        return (
            <div>
                 <div>To see you in my dreams</div>
            </div>
        )
    }
}

ReactDom.render(<App />, document.getElementById('root'));

我对此代码进行打包,结果如下: image.png 耗时是1089ms,vendors的体积分别是131kib以及137kib,接下来我改动上述代码在引入一个第三方模块进行打包:

import React, { Component } from 'react';
import ReactDom from 'react-dom';
import _ from 'lodash';

class App extends Component {
    render() {
        return (
            <div>
                 <div>{_.join(['To see yo', 'in my dream'], ' ')}</div>
            </div>
        )
    }
}

ReactDom.render(<App />, document.getElementById('root'));

image.png 耗时是1235ms,vendors的体积分别是664kib以及771kib,我再次进行打包耗时为1198ms,我刚才已经打包过一次为什么再次打包耗时还是接近1200ms呢?

这是因为我们代码里引入了第三方模块,每一次重新打包的时候,它都要重新去分析我们引入的第三方库,最终把它们打包到我们的项目之中。那其实我们知道像这种第三方模块,它实际上代码是不会变的。所以我们可以把它单独打包,生成一个文件,然后只在第一次打包的时候,去分析这里面的代码,之后再去做打包的时候,我们直接用上一次分析好的结果就可以了,这样就可以提高打包速度;

配置

首先我们新建一个webpack.dll.js的文件,里面专门用于打包第三方库,配置如下:

const path = require('path');
const webpack = require('webpack');

module.exports = {
    mode: 'production',
    entry: {
        vendors: ['lodash','react', 'react-dom','jquery']
    },
    output: {
        filename: '[name].dll.js',
        path: path.resolve(__dirname, '../dll'),
        library: '[name]'
    },
    plugins: [
        new webpack.DllPlugin({
            name: '[name]',
            path: path.resolve(__dirname, '../dll/[name].manifest.json'),
        })
    ]
}
  • entryoutput就是入口和出口这个就不多说了,library就是往window添加一个全局变量vendors
  • new webpack.DllPlugin就是生成下面的文件,作用是把所有的第三方库依赖打包到一个的dll文件里面就是vendors.dll.js,还会生成一个名为 manifest.json文件。该manifest.json的作用是用来让DllReferencePlugin 映射到相关的依赖上去的。
vendors.dll.js
vendors.manifest.json

接着再来配置webpack.config.js

const path = require('path');
const fs = require('fs');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
const webpack = require('webpack');

const plugins = [
    new HtmlWebpackPlugin({
        template: 'src/index.html'
    }),
    new CleanWebpackPlugin(['dist'], {
        root: path.resolve(__dirname, '../')
    })
];

const files = fs.readdirSync(path.resolve(__dirname, '../dll'));

files.forEach(file => {
    if (/.*\.dll.js/.test(file)) {
        plugins.push(new AddAssetHtmlWebpackPlugin({
            filepath: path.resolve(__dirname, '../dll', file)
        }))
    }
    if (/.*\.manifest.json/.test(file)) {
        plugins.push(new webpack.DllReferencePlugin({
            manifest: path.resolve(__dirname, '../dll', file)
        }))
    }
})

module.exports = {
    plugins
}
  • fs引进dll文件下的内容,就是引入vendors.dll.js,vendors.manifest.json
  • 接着遍历dll文件下的内容,匹配后缀为dll.js,使用AddAssetHtmlWebpackPluginvendors.dll.js以script的形式插入html中;
  • new webpack.DllReferencePlugin的作用是什么呢?
    • 它的意思是,在我们进行打包的时候,发现了我们引入了第三方模块的时候,这个插件就会到vendors.manifest.json里面去帮我们找映射关系;

按照上述配置后,打包出的两次结果:

image.png

image.png

两次打包vendors.dll.js的体积都是一样的,由于我们做了配置打包时就不会去分析第三方的模块了,而是直接引用我们第一次打包的vendors.dll.js 打包速度也明显提示了;

总结

webpack的配置很多,详细配置还请翻阅文档进行学习;