Webpack打包优化之体积篇

最近在项目开发中,需要将VUE前端代码打包上传至静态资源服务器上,结果静态资源服务器限制上传的资源每个文件大小不能大于1MB;然而在日常的项目开发中,我们会遇到各种第三方插件来提高效率,但随之带来的问题就是打包后的vendor.js文件体积过大,导致加载时空白页事件过长,而且首屏加载慢,给用户的体验太差。为此我们需要减小vendor.js文件的体积;

*Webpack教程

定位webpack打包慢并且文件大的原因

*webpack-bundle-analyzer

externals优化项目

vendor.js文件过大,这时候用webpack提供的externals属性可以解决以上的问题,像vue.js、vuex.js、vue-router.js这些第三方,基本不会变的;如果将它们独立出来单独加载就能利用浏览器的缓存机制,不用每次都重新加载这些第三方库文件,并且大大减小了vendor.js文件的大小;

  1. 添加配置如下

webpack的外部扩展(externals)可以有效的解决;externals配置选项提供了从输出的bundle中排除依赖的方法;相反,所创建的bundle依赖于那些存在用户环境中的依赖。防止将某些import的包打包到bundle中,而是在运行的时候再去从外部获取这些扩展依赖;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# build/webpack.base.conf.js

var path = require('path');
var utils = require('./utils');
var config = require('../config');

var vueLoaderConfig = require('./vue-loader.conf');

function resolve(dir) {
return path.join(__dirname, '..', dir);
}

module.exports = {
entry: {
app: './src/main.js'
},
output: {
path: config.build.assetsRoot,
filename: '[name].js',
publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src')
}
},
module: {
rules: [
{
test: /\.vue$/,
loader: /vue-loader/,
options: vueLoaderConfig
},
{
test: /\.js$/,
loader: 'babel-loader',
options: {
cacheDirectory: true
},
include: [resolve('src'), resolve('test')]
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('img|[name].[hash:7].[ext]')
}
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('media/[name].[hash:7].[ext]')
}
},
{
test: /\.(woff2?\eot|ttf|otf)(\.?*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
},
{
test: /\.s[a|c]ss$/,
loader: 'sass-loader',
/*options: {
incluedePaths: [resolve('src'), resolve('test')]
}*/
}
]
},
# externals中的key是后面需要require的名字,value是第三方库暴露出来的方法名
externals: { // 放到common.min.js
'vue': 'Vue',
'element-ui': 'ELEMENT',
'vuex': 'Vuex',
'vue-loader': 'VueRouter',
'axios': 'axios'
}
}

  1. 全局引入第三方的库

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    # main.js

    import Vue from 'vue'
    import App from './App'
    import router from './router'
    import axios from 'axios'
    import '@/theme/index.css'
    import ElementUI from 'element-ui'

    import Utils from './utils/utils'
    import Constant from './utils/constant'
    import request from './util/request'
    import Menus from './utils/menus'

    import Loading from './utils/loading'

    Vue.use(ElementUI);
    Vue.use(Loading);

    Vue.prototype.$axios = axios;
    Vue.prototype.$request = request;
    Vue.prototype.$menus = menus;
    Vue.prototype.$utils = Utils;
    Vue.prototype.$constant = Constant;
  2. 全局引入静态的资源

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # index.html

    <!DOCTYPE html>
    <html>
    <head></head>
    <body>
    <div id="app"></div>

    <script type="test/javascript" src="./src/assets/js/lib/common.min.js"></script>
    <script type="test/javascript" src="./src/assets/js/lib/element-ui.js"></script>
    </body>
    </html>

注释: 这里全局引入的第三方库,当然可以从CND官网进行引入,但是为了安全性和可维护性,所以将第三方库下载至本地进行路径的引用;

优化第三方库

项目中对库的使用有点儿混乱,有些安装了但是很少使用到或者根本没有用到;但是又在webpack中的vender入口指定打包,造成不必要的麻烦,所以需要评估每个第三方库是否有必要安装;

按需引入第三方库的组件

echart是一个比较大的第三方库,或许项目中我们就是用了其中的饼状图和条形图,但是引入整个第三方库会加重浏览器加载的负荷,所以这里我们按需引入响应的组件;包括ElementUI也会可以按需引入相应的组件;

路由懒加载

*路由懒加载

当打包构建应用的时候,Javascript包会变得非常的大,影响页面的加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应的组件,这样就更加高效了;

结合Vue的异步组件和webpack代码分割功能轻松实现路由的懒加载;

首先,可以讲异步组件定义为返回一个Promise的工厂函数

1
const Foo = () => Promise.resolve({/* 组件定义对象

第二,在Webpack 2中,我们可以使用动态import语法来定义代码分割点

1
import('./Foo.vue') // 返回Promise

结合两者,这就是如何定义一个能够被Webpack自动代码分割的异步组件;

1
2
3
4
5
6
7
8
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo
}
]
})

把组件按组分开

有时候我们想把某个路由下的所有组件都打包在同一个异步块中。只需要使用名字命名chunk一个特殊的注释语法来提供chunk name(需要 Webpack > 2.4)

1
2
3
const Foo = () => import(/* webpackChunkName: 'group-foo' */ './Foo.vue')
const Bar = () => import(/* webpackChunkName: 'group-foo' */ './Bar.vue')
const Baz = () => import(/* webpackChunkName: 'group-foo' */ './Baz.vue')

Webpack会将任何一个异步模块于相同的块名称组合到相应的异步块中

webpack配置文件区分开发环境和生产环境

生产环境不要包含HotModuleReplacementPluginNoEmitOnErrorsPlugin等没必要的插件;

拆开vendor

webpack默认是将依赖打包成一个文件,这样做的好处是减少资源的请求数量,但是当依赖增多的时候,体积增大,一个资源的加载速度就会变慢;所以尝试拆包;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
new webpack.optimize.CommonsChunkPlugin({
name: 'charts',
chunks: ['vendor'],
minChunks: module => module.resource.indexOf('highcharts') > -1
}),

new webpack.optimize.CommonsChunkPlugin({
name: 'utils',
chunks: ['vendor'],
minChunks: module => module.resource.indexOf('lodash') > -1
}),

new webpack.optimize.CommonsChunkPlugin({
name: 'ui',
chunks: ['vendor'],
minChunks: module => module.resource.indexOf('element-ui') > -1
})

拆包后的结果如下:

1
2
3
static/js/vender.snaskdads123.js  251KB 1 [emitted] vender
static/js/ui.sdfjafjk.js 211KB 1 [emitted] ui
static/js/charts.asfdjasdad.js 151KB 1 [emitted] charts

分离出webpack runtime代码

webpack在客户端运行时首先会加载webpack相关的代码, 例如require函数等,这部分代码会随着每次修改业务代码后发生变化;原因是这里面会包含chunk id等容易变化的信息;
如果不抽取出来将会被打包在vendor中,导致vendor每次都要被用户重新加载,vendor也就失去了它原有的意义;所以配置要分离出来,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# build/webpack.intranet.conf.js

// split vendor js into its own file
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function(module, count) {
// any required modules inside node_modules are extracted to cendor
return (
module.resource && /\.js$/.test(module.resource) && module.resource.indexOf(path.join(__dirname, '../node_modules')) === 0
)
}
}),

// extract webpack runtime and module mainfest to its own file in order to prevent wendor hash from being undated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({
name: 'mainfest',
chunks: ['vendor']
})
坚持原创技术分享,您的支持将鼓励我继续创作!