前言

了解了webpack的基本配置之后,接下来应该关注其优化策略。webpack的优化策略对我们的程序有着极大的性能提升。这是我记录学习webpack优化的文章,纪录的同时也希望分享给大家。

DLL动态链接库

在开发的过程中,我们会引入像react、react-dom这样的库,这样的库基本上每一次打包时它们的内容都不会改变,所以我们引入动态链接库。将它们打包一次,在之后的构建过程中它们就不会被打包,打包的模块也会使用动态链接库里面的代码而不是去node_modules中取,这样我们的构建速度就会大大提升。下面让我们来看看webpack中怎么构建动态链接库。

打包

我们新建一个webpack.dll.config.js的文件,配置如下

const path = require('path')
const DllPlugin = require('webpack/lib/DllPlugin')
module.exports = {
    entry:{
        //将react、react-dom放到动态链接库中
        react:['react','react-dom']
    },
    output:{
        //输出的文件名称,[name]指的是当前动态链接库的名称,即react
        filename:'[name].dll.js',
        //输出到dist目录下
        path:path.resolve(__dirname,'dist'),
        //存动态链接库的全局变量名称,即_dll_react,加上_dll_防止全局变量冲突
        library:'_dll_[name]',
    },
    plugins:[
        new DllPlugin({
            //动态链接库的全局变量名称,需要和library一致
            name:'_dll_[name]',
            path:path.join(__dirname,'dist','[name].manifest.json')
        })
    ]
} 复制代码

下面执行命令npx webpack --config webpack.dll.config.js,dist目录下多了两个文件,分别是react.dll.js和react.manifest.jsonreact.dll.js里面包含React的基础运行环境,即react、react-dom模块。react.manifest. json用于描述在动态链接库文件中包含哪些模块。

然后再新建一个webpack.config.js文件,在打包出来的chunk块中声明需要引入的动态链接库,具体配置如下

const path = require('path')
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin')
module.exports = {
    entry: {
        main: './main.js' },
    output:{
        filename:'bundle.js',
        path:path.resolve(__dirname,'dist')
    },
    module:{
        rules:[

        ]
    },
    plugins:[
        new DllReferencePlugin({
            manifest:require('./dist/react.manifest.json')
        })
    ]
} 复制代码

执行命令npx webpack,在dist目录下生成bundle.js。

引入

然后我们在dist目录下新建一个index.html文件,引入打包构建出来的文件和动态链接库。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    
    <script src="./react.dll.js"></script>
    <script src="./buncld.js"></script>
</body>
</html> 复制代码

HappyPack

当文件数量变多时,webpack构建速度慢的问题会变得特别明显。但是运行在node之上的webpack是单线程的,不能同时处理多个任务。而HappyPack可以让webpack做到这一点,它将任务分解成多个子进程去并发执行。由于JavaScript是单线程模型,所以想要发挥多核CPU的作用只能通过多进程而不能通过多线程来实现。 接入HappyPack的代码如下

const path = require('path')
const HappyPack = require('happypack');
module.exports = {
    entry: {
        main: './main.js' },
    output: {
        filename: '[name].js',
        path: path.resolve(__dirname, 'dist')
    },
    module: {
        rules: [{ test: /\.js$/,
                //以babel为id,转交给happypack处理
                use: ['happypack/loader?id=babel'],
            },
            { test: /\.css$/,
                //以css为id,转交给happypack处理
                use: ['happypack/loader?id=css']
            }

        ]
    },
    plugins: [
        new HappyPack({
            id: 'babel',
            loaders: ['babel-loader']
        }),
        new HappyPack({
            id: 'css',
            loaders: ['style-loader','css-loader']
        })
    ]
} 复制代码

执行构建命令npx webpack从命令行的输出可以看出HappyPack已经生效

Happy[babel]: Version: 5.0.1. Threads: 3
Happy[babel]: All set; signaling webpack to proceed.
Happy[css]: Version: 5.0.1. Threads: 3
Happy[css]: All set; signaling webpack to proceed. 复制代码

压缩代码

为了提升网页的加载速度,可以对资源进行压缩。

压缩JavaScript

这里我们会用到UglifyJS,它通过去掉无效代码、去掉日志输出、缩短变量名,从而来优化我们的代码。 简单的配置如下

const UglifyJSPlugin = require('webpack/lib/optimize/UglifyJsPlugin')
module.exports = {
    //以上省略。。。
    plugins: [
        //压缩输出的 JavaScript 代码
        new UglifyJSPlugin({
            compress: {
                //在 UglifyJS 删除没有用到的代码时不输出警告
                warnings: false,
                //删除所有 console 语句, 可以兼容IE浏览器
                drop_console: true,
                //内嵌己定义但是只用到一次 的变量
                collapse_vars: true,
                //提取出现了多次但是没有定义成变量去 用的静态值
                reduce_vars: true,
                output: {
                    //最紧凑的输出
                    beautify: false,
                    //删除所有注释
                    comments: false,
                }
            }
        })
    ] 复制代码

压缩CSS

CSS也可以向JavaScript一样被压缩,这里用到的工具是cssnano。cssnano的意义不仅仅是删除空格,它可以理解CSS代码。例如margin:10px 20px 10px 20px会被压缩为margin:10px 20px基本配置如下

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextPlugin = require('extract-text-plugin')
module.exports = {
    //以上省略。。。。
    module: {
        rules: [{ test: /\.css$/,
            use: [ExtractTextPlugin.extract({
                use: ['style-loader', 'css-loader?minimize']
            })]
        }]
    },
    plugins:[
        new HtmlWebpackPlugin({
            template:'./index.html',
            filename:'index.html' }),
        new ExtractTextPlugin({
            filename: '[name]_[contenthash:8].css' })
    ]
} 复制代码

提取公共代码

如果将多个页面的公共代码抽离成单独的文件,就能优化一些问题。例如相同的资源被重复加载,浪费用户的流量和服务器的成本。每个页面要加载的资源太大,导致网页首屏加载缓慢,影响用户体验。 基本配置如下,此配置主要针对多页面。单页就不存在于公共代码这一说法了。

import 'react' import 'react-dom' import './index.css' //每个页面都用到的样式
const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin')
module.exports = {
    module: {
        entry: './main.js',
        rules: [{ test: /\.css$/,
            use: [ExtractTextPlugin.extract({
                use: ['style-loader', 'css-loader?minimize']
            })]
        }]
    },
    plugins: [
        new CommonsChunkPlugin({
            //从已有的common和base两个现成的chunks中提取公共部分 
            chunks: ['common', 'base'],
            //将公共部分放到base中 这样配置之后 common会变小,因为公共部分都跑到了base里,而base不变
            name: 'base' })
    ]
} 复制代码

为了能使网页运行,以网页A为例,除了打包出来的A页面的JavaScript代码还需引入公共部分代码

<script src= 'base.js'></script>
<script src='common.js'></script>
<script src='a.js'></script> 复制代码

按需加载

单页应用首次渲染缓慢,一个很重要的原因是一次性加载了所有功能对应的代码。这个时候如果采用按需加载,我们网站的性能将会大大提升。 在webpack里,按需加载可以这样来写。例如我们只打包出了一个bundle.js。在bundle.js中,有这么一段代码

window.document.getElementById('button').addEventListener('click',()=>{
    import('./show').then(show=>{
        show('webpack')
    })
}) 复制代码

在show.js中

module.exports= function (content) {
    window.aleat(`hello ${content}`)
} 复制代码

webpack中内置了import语句的支持,当遇到这样的语句时,首先会生成一个新的chunk,然后触发import的时候再去加载这个chunk,返回的是一个Promise对象,在加载成功时使用then方法进行下面的操作,为了让webpack正确打包chunk,配置文件中需加入

output:{
    //从entry打包生成的chunk
    filename:'[name].js',
    //动态加载生成的chunk
    chunkFileName:'[name].js' } 复制代码

最后

webpack的优化配置还有很多,这里只记录了我平时常用的,具体详情还请查阅官方文档。