Webpack打包优化

1、前言

       对于使用vue开发项目的FE来讲,打包上线这个环节相信你们都不陌生。本文主要是介绍如何经过webpack(实践版本:webpack4.16.5)的配置来提升打包构建速度以及减少包的体积。javascript

2、优化策略  

 css,js,html压缩

使用optimize-css-assets-webpack-plugin 来压缩css。在webpack4的配置项中,需在optimization下的minimizer对象中去实例化使用。css

// 压缩css
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')

// 在optimization.minimizer中引用
new OptimizeCSSAssetsPlugin()复制代码

使用MiniCssExtractPlugin来抽离内联css到外联文件html

// 抽离内联css到外部css文件
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
// 在optimization.minimizer中引用
new MiniCssExtractPlugin({  
    filename: utils.assetsPath('css/[name].[contenthash:8].css'),  
    chunkFilename: utils.assetsPath('css/[name].[contenthash:8].css')
})复制代码

使用uglifyjs-webpack-plugin来压缩js,重点属性parallel,用来开启多进程并行执行js压缩,大大提升构建速度。vue

//压缩js
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

//在optimization.minimizer中引用
new UglifyJsPlugin({  
    uglifyOptions: {    
        warnings: false,    
        mangle: {      
            safari10: true    
        },    
        compress: {      
            drop_debugger: false,      
            drop_console: true, //console      
            pure_funcs: ['console.log'] // 移除console    
        },    
        output:{      
             // 去掉注释内容      
             comments: false    
        }  
    },  
    sourceMap: false,  
    cache: true,  
    parallel: true
})复制代码

使用html-webpack-plugin来压缩html代码,并实现自动化注入脚本以及样式文件,对于文件名中包含哈希的Webpack捆绑包尤为有用。java

// 压缩html
const HtmlWebpackPlugin = require('html-webpack-plugin')

// 在plugins中引入
new webpack.HtmlWebpackPlugin({    filename:path.resolve(__dirname, '../dist/index.html'),
    template: 'index.html',    inject: true,
    favicon: resolve('favicon.ico'),
    title:'标题',
    minify: {  
        removeComments: true,  
        collapseWhitespace: true,  
        removeAttributeQuotes: true
    } })
    
 复制代码

拆包

webpack4.x中使用splitChunks来进行拆包,抽离第三方依赖库。默认状况下,webpack将会基于如下条件自动分割代码块:node

  • 新的代码块被共享或者来自node_modules文件夹
  • 新的代码块大于30kb(在min+giz以前)
  • 按需加载代码块的请求数量应该<=5
  • 页面初始化时加载代码块的请求数量应该<=3

默认配置以下:webpack

splitChunks: {
    chunks: "async",
    minSize: 30000, // 模块的最小体积
    minChunks: 1, // 模块的最小被引用次数
    maxAsyncRequests: 5, // 按需加载的最大并行请求数
    maxInitialRequests: 3, // 一个入口最大并行请求数
    automaticNameDelimiter: '~', // 文件名的链接符
    name: true,
    cacheGroups: { // 缓存组
        vendors: {
            test: /[\\/]node_modules[\\/]/,
            priority: -10
        },
        default: {
            minChunks: 2,
            priority: -20,
            reuseExistingChunk: true
        }
    }
}复制代码

能够经过自定义配置项修改配置,如像这样抽离第三方依赖库(如单独将elementUI打包)ios

splitChunks: {  
    chunks: 'all',  
    cacheGroups: {    
        lib: {      
            name: 'chunks-libs',      
            test: /[\\/]node_modules[\\/]/,      
            priority: 10,      
            chunks: 'initial' // 只打包初始时依赖的第三方    
        },    
       elementUI: {      
            name: 'chunk-elementUI', // 单独将 elementUI 拆包      
            priority: 20, // 权重要大于 libs 和 app 否则会被打包进 libs 或者 app      
            test: /[\\/]node_modules[\\/]element-ui[\\/]/,      
            chunks: 'all'    
       }  
    }
}复制代码

通常还需配合runtimeChunk使用(runtimeChunk的具体做用)。在抽取 Webpack 运行时代码的时候,要指定 runtimeChunk 属性:git

  • true:表示每一个入口都抽取一个公共的运行时代码(适用于单入口)
  • 'single':表示多个入口抽取一个公共的运行时代码,通常使用这种方式(适用于多入口)
// optimization.runtimeChunk 具体使用规则  详见 
//https://webpack.js.org/configuration/optimization/#optimizationruntimechunk

runtimeChunk: true复制代码

Happypack

因为 JavaScript 是单线程模型,在webpack构建过程当中,咱们须要使用Loader对js,css,图片,字体等文件作转换操做,而且转换的文件数据量也是很是大的,且这些转换操做不能并发处理文件,而是须要一个个文件进行处理,HappyPack的基本原理是将这部分任务分解到多个子进程中去并行处理,子进程处理完成后把结果发送到主进程中,从而减小总的构建时间。(ps:对file-loader和url-loader支持很差,因此这两个loader就不须要换成HappyPack)github

Happypack运行机制


Happy使用方式

// 多进程并发执行loader  默认为单线程
const HappyPack = require('happypack')
const os = require('os')// 根据系统的内核数量 指定线程池个数 也能够其余数量
const happyThreadPool = HappyPack.ThreadPool({size: os.cpus().length})//插件中引入
plugins:[
    new HappyPack({ 
        // 基础参数设置  
        id: 'babel', // 上面loader?后面指定的id  
        loaders: ['babel-loader?cacheDirectory'], // 实际匹配处理的loader  这里加入了缓存控制 
        threadPool: happyThreadPool,    
        verbose: true
    })
]


// loader处理器中使用 经过id匹配
modules: {
     test: /\.js$/,      
     use:'happypack/loader?id=babel',  
     exclude: /node_modules/, // 排除不处理的目录  
     include: [    
        resolve('src'), resolve('test'),   
        resolve('mock'),    
        resolve('node_modules/webpack-dev-server/client')  
    ]
}复制代码

缓存与增量构建

缓存构建:webpack构建中,通常须要使用许多loader来预处理文件,以babel-loader为例。能够经过设置cacheDirectorycacheDirectory=true来达到缓存的目的。

{  
    test: /\.js$/,  
    loader: 'babel-loader?cacheDirectory',  
    exclude: /node_modules/, // 排除不处理的目录  
    include: [    
        resolve('src'),    
        resolve('test'),    
        resolve('mock'),    
        resolve('node_modules/webpack-dev-server/client')  
    ]
}复制代码

cacheDirectory对loader转译后的结果进行缓存,以后的webpack进行构建时,都会去尝试读取缓存来避免高耗能的babel从新转译过程。


增量构建:使用增量构建而不是全量构建有利于构建速度的提高。全量构建即每次从新构建都须要从新编译一次(包括未修改部分),而增量构建对于未修改的部分不会再从新编译,对于rebuild可以大大提升编译速度。对于开发阶段,可使用webpack-dev-server来达到增量编译的目的,对于生产阶段,能够经过给生成的文件添加hash(或chunkhash 或contenthash )来实现增量构建。

output: {  
    path: config.build.assetsRoot,  
    filename: utils.assetsPath('js/[name].[chunkhash:8].js'),  
    chunkFilename: utils.assetsPath('js/[name].[chunkhash:8].js')
}复制代码

优化模块查找路径

经过配置resolve.modules来告诉webpack解析模块时应该搜索的目录。默认配置采用向上递归搜索的方式去寻找,设置特定搜索目录有助于webpack更快搜索到目标。

resolve: {  
    extensions: ['.js', '.vue', '.json'],  
    alias: {    
        '@': resolve('src')  
    },  
    modules: [    
        resolve('src'),    
        resolve('node_modules')  
    ]}
}复制代码

DllPlugin和DllReferencePlugin

dll全称为动态连接库,先经过dllPlugin生成清单文件(这个文件包含了从 requireimport 的request到模块 id 的映射),而后经过DllReferencePlugin引用该清单文件,将依赖的名称映射到模块的 id 上。这样每次打包时.先去查找清单里中是否已经存在这个依赖,若是已经存在,则不打包,若是还没存在,则须要打包。与经过externals 的方式引入第三方库相似,dll主要用于那些没有能够在<script>标签中引入的资源的模块(纯npm包)。

使用方式

新建一个webpack.dll.conf.js文件,并执行webpack --config ./build/webpack.dll.conf.js会在static/js文件夹下生成dll.vendor.js以及在根目录下生成mainfest.json

//webpack.dll.conf.js
const webpack = require('webpack');
const path = require('path');
const vendors = [  'vue',  'vue-router',  'vuex',  'axios'];
module.exports = {  
    output: {    
         path: path.resolve(__dirname, '../static/js'),    
         filename: 'dll.[name].js',    library: '[name]'  
    }, 
    entry: {    vendor: vendors,  }, 
    plugins: [    
         new webpack.DllPlugin({      
             path: 'manifest.json',      
             name: '[name]',      
             context: __dirname    
         })  
    ]
}复制代码

在项目配置文件(如webpack.base.conf.js)中经过DllReferencePlugin引入

// build/webpack.base.conf.js
 const manifest = require('../manifest.json')    
    
 // 插件中引入
 plugins: [
   new webpack.DllReferencePlugin({
       mainfest
    })
 ]复制代码

而后在index.html手动引入该文件

<script type="text/javascript" src="/static/js/dll.vendor.js"></script>复制代码

externals配合cdn加载第三方库

externals的做用是从打包的bundle文件中排除依赖。通俗点讲,就是在项目中经过import引入的依赖在打包的时候不会打包到bundle包中去,而是经过script的方式去访问这些依赖。与Dll不一样的是,要去维护cdn

// build/webpack.base.conf.js
// externals 对象中的   key表明第三方依赖名(同package.json包中依赖名)
// value表明暴露给外部使用的别名  这里element-ui的别名为ELEMENT
module.exports ={
    externals: {  
        'vue':'Vue',  
        'vue-router':'VueRouter',  
        'axios':'axios',  
        'vuex':'Vuex',  
        'element-ui':'ELEMENT'
    }
}复制代码

在index.html文件中经过cdn方式引入

// head中引入
<link href="https://cdn.bootcss.com/element-ui/2.4.6/theme-chalk/index.css" rel="stylesheet">
// body中引入
<script type="text/javascript" src="/static/js/dll.vendor.js"></script>
<script src="https://cdn.bootcss.com/vue/2.5.17/vue.min.js"></script>
<script src="https://cdn.bootcss.com/element-ui/2.4.6/index.js"></script>
<script src="https://cdn.bootcss.com/vue-router/3.0.6/vue-router.min.js"></script>
<script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script>
<script src="https://cdn.bootcss.com/vuex/3.0.1/vuex.min.js"></script>复制代码

在相关文件中移除相关引入的依赖

// main.js
//经过cdn的方式注释相关依赖

// import Vue from "vue";
// import ElementUI from "element-ui";
//  Vue.use(ElementUI);


// store/index.js
// 注释掉vuex import引入方式

// import Vue from 'vue';
// import Vuex from 'vuex';
// Vue.use(Vuex);


// router/router.js
// 注释掉vue-router
 
// import Vue from 'vue';
// import Router from 'vue-router';


//使用externals中vue-router暴露的全局对象名VueRouter进行实例化
const router = new VueRouter({   
     mode: "history",    
     scrollBehavior: () => ({y: 0}),       
    routes: [otherRouter, ...constantRouterMap]
});复制代码


分析工具

查看 webpack 打包后全部组件与组件间的依赖关系,能够经过打包分析工具来实现。

webpack-bundle-analyzer

 webpack-bundle-analyzer是一个webpack打包分析插件,它将打包的依赖关系以树形图的方式呈现,直观方便。

plugins:[
    new BundleAnalyzerPlugin({  
        analyzerPort: 8080,  
        generateStatsFile: false
    })
]复制代码

在插件配置项中引入并经过cnpm run build --report来生成可视化分析图,效果图以下:

官方分析工具

webpack提供的一个官方工具,可查看你的项目版本信息,有多少modules,多少chunks,中间有多少错误信息、有多少警告,各个模块之间的依赖关系等。

使用方式

首先经过webpack --config ./build/webpack.prod.conf.js --json > stats.json 生成json文件(ps:若是使用了Happypack,需删除该json文件前两行,不然不是标准的json),而后经过官网分析工具地址将该文件上传,如图所示:


具体分析结果能够经过点击相应的模块查看详情。


总结

webpack优化并无一个通用方案,这里我只是列出了我使用过的一些策略,具体业务需具体分析,对症下药。但愿本文能帮助到一些有须要的小伙伴~

相关参考资料:

Webpack

花裤衩-手摸手webpack4

Webpack Analyse

HappyPack

webpack打包分析与性能优化

相关文章
相关标签/搜索