webpack优化(1)-打包篇
webpack优化(1)-打包篇
铛铛铛铛 发表于1年前
webpack优化(1)-打包篇
  • 发表于 1年前
  • 阅读 73
  • 收藏 1
  • 点赞 0
  • 评论 0

腾讯云 十分钟定制你的第一个小程序>>>   

摘要: webpack打包生成的文件很大,经过合理配置,可以有效降低打包文件的大小,优化页面加载时间。

页面的首次加载时间对用户体验影响很大,有研究表明,如果首次加载超过8秒,那么用户将有极大可能放弃访问这个网站。

减少HTTP请求数与缩减资源文件的大小是减少页面加载时间的主要手段。

在使用webpack打包时,进行必要的优化,缩减js文件和css文件的大小,能够有效提高页面加载时间。

下图中是目前项目中的首页加载网络情况,其中webpack生成的打包文件bundle.js有2.9MB大。而下载js文件和css文件的请求数达到9个。 输入图片说明

项目中使用的webpack.config.js文件如下,可以看到只进行了简单的文件合并,未作文件压缩等处理。 其中react,jquery,backbone,underscore,echarts,laydate都是通过外部引用的方式,未通过webpack打包。

原始的webpack.config.js

var path = require('path');

module.exports = {
	entry: [
		__dirname + '/src/main.jsx'
	],
	output: {
		path: './dist/',
		publicPath: "./dist/",
		filename: 'bundle.js'
	},
	externals: {
		underscore: "underscore",
		backbone: "Backbone",
		jquery: "jQuery"
	},
	module: {
		loaders: [{
			test: /\.jsx?$/,
			loader: 'babel',
			exclude: /node_modules/,
			query: {
				presets: ['react', 'es2015']
			}
		}, {
			test: /\.less$/,
			loader: 'style-loader!css-loader!less-loader',
			exclude: /node_modules/
		}, {
			test: /\.css$/,
			loader: 'style-loader!css-loader',
			exclude: /node_modules/
		}, {
			test: /\.(png|jpg|gif)$/,
			loader: 'url-loader',
			exclude: /node_modules/,
			query: {
				limit: 8192
			}
		}]
	}
};

页面的HTML结构

<html>
<head>
	<meta charset="UTF-8"/>
    <title></title>
</head>
<body>
<div id='root'>
</div>

<script src="./lib/react.min.js"></script>
<script src="./lib/react-dom.min.js"></script>
<script src="./lib/jquery-2.1.4.min.js"></script>
<script src="./lib/underscore-min.js"></script>
<script src="./lib/backbone-min.js"></script>
<script src="./lib/echarts-all.js"></script>
<link rel="stylesheet" type="text/css" href="./lib/laydate/skins/default/laydate.css">
<script type="text/javascript" src="./lib/laydate/laydate.js" ></script>
<script src="./dist/bundle.js"></script>
</body>
</html>

###Step 1 ####问题: 打包生成的bundle.js中实际已经包含了react,但是页面仍然引用了react.min.js,造成了代码重复 ####解决: 通过设置externals,可以指定外部依赖的库,webpack将不会对externals中指定的库进行打包。 我们将react添加进externals

externals: {
    underscore: "underscore",
    backbone: "Backbone",
    jquery: "jQuery",
    react:'React',
    'react-dom':'ReactDOM'
}

这样,生成的bundle.js从2.9MB降低到了2.23MB

###Step 2

####问题: 打包生成的bundle.js未进行压缩混淆c ####解决: 使用webpack.optimize.UglifyJsPlugin压缩代码

plugins:[
    new webpack.optimize.UglifyJsPlugin({
        compress:{
            warnings:false
        }
    })//代码压缩与混淆
]

经过压缩与混淆处理后,bundle.js缩小到1.4M

###Step 3 ####问题: 现代浏览器能够并行加载CSS文件与JS文件,为提高加载速度,将所有css从bundle.js中分离 ####解决: 使用ExtractTextPlugin将css分开压缩,同时修改原来的css与less分开处理的方式,将css与less统一处理

var ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
    module: {
		loaders: [{
			test: /\.jsx?$/,
			loader: 'babel',
			exclude: /node_modules/,
			query: {
				presets: ['react', 'es2015']
			}
		},{
			test: /\.(less|css)$/,
			loader: ExtractTextPlugin.extract("style-loader", "css-loader!less-loader"),
			exclude: /node_modules/
		},{
			test: /\.(png|jpg|gif)$/,
			loader: 'url-loader',
			exclude: /node_modules/,
			query: {
				limit: 8192
			}
		}]
	}
}

经过处理,生成的bundle.js大小为928KB,生成的styles.css文件的大小为798KB ###Step 4 ####问题: 外部库文件react,backbone,jquery,underscore,echarts是分开加载的,将库文件打包合并到一个js文件中。一方面可以减少HTTP请求数;另一方面,库文件基本是不变的,将库文件分开打包,可以有效利用缓存。 ####解决: 首先,不再通过externals的方式使用外部引用库,而通过npm统一管理react,react-dom,backbone,underscore,jquery,echarts模块 然后,通过CommonsChunkPlugin来合并所有的库文件到libs.js

var prodConfig = {
	entry: {
		bll:__dirname + '/src/main.jsx',
		vendor:['react','react-dom',"backbone","echarts"]//第三方模块
	},
	output: {
		path: './dist/',
		publicPath: "./",
		filename: '[name].js',
		chunkFilename: 'js/[id].bundle.js'
	},
	externals: {

	},
	plugins:[
		//代码压缩与混淆
		new UglifyJsPlugin({
			compress:{
				warnings:false
			}
		}),
		//提取css文件
		new ExtractTextPlugin("styles.css"),
		//提取公用组件
		new CommonsChunkPlugin({
			name:"vendor",
			filename:"libs.js",
			minChunks: Infinity
		})
	]
};

页面的HTML结构也变为

<html>
<head>
	<meta charset="UTF-8"/>
    <title>HPS</title>
</head>
<body>
<div id='root'>
</div>
<link rel="stylesheet" type="text/css" href="./lib/laydate/skins/default/laydate.css">
<script type="text/javascript" src="./lib/laydate/laydate.js" ></script>
<link rel="stylesheet" href="dist/styles.css">
<script src="./dist/libs.js"></script>
<script src="./dist/bll.js"></script>
</body>
</html>

可以看到除了laydate(一个日期插件)外,其他的库都打包到了libs.js,而业务逻辑全部打包到bll.js 生成的bll.js大小为928KB(实际上就是原来的bundle.js),libs.js的大小为798KB。 到此,页面需要引用的js包括laydate.js,libs.js,bll.js,需要应用的css有styles.css。 首页,HTTP请求数变为4个,其中styles.css是能够与js文件并发下载的。

###Step 5 ####问题: 在externals中去掉jquery,而将jquery作为一个node module来使用,造成了一个问题,即$,jQuery不再是window下的变量。而原来的代码中直接使用全局变量$,而不是通过var $ = require('jquery')的方式使用jquery。这样会造成$为undefined的错误。

####解决: 使用插件webpack.ProviePlugin将jquery模块导入到所有模块中,这样在所有模块中都可以直接使用$,jQuery,或者window.jQuery。

        new webpack.ProvidePlugin({
		   $: "jquery",
		   jQuery: "jquery",
		   "window.jQuery": "jquery"
		})

NOTE: 这里的配置主要是从不改业务代码的角度来考虑;从模块化的角度考虑,我更倾向的是在需要使用jquery的模块加上

var $ = require('jquery') 

###Step 6 ####问题: laydate插件仍然使用script外部引用的方式,从减少HTTP请求数考虑,我们将其合并入libs.js ####解决: 首先,使用exports-loader将laydate导出为模块,然后通过设置alias,这样通过

var laydate = require('laydate');

就可以在模块中使用laydate。

{
    resolve: {
		alias: {
			laydate:path.join(__dirname,'./lib/laydate/laydate.js')
		}
	},
    module: {
		loaders: [{
          test:require.resolve("./lib/laydate/laydate.js"),
          loader:"exports-loader?laydate"
        }]
    }
}

通过上面的配置,laydate.js将被合并到libs.js中。 这样还存在一个问题,即laydate.js找不到其需要的css和图片等信息。 我们要做的是将laydate需要的need与skins文件夹拷贝到libs.js所在的目录。 使用transfer-webpack-plugin可以进行文件拷贝

    plugins:[
		//拷贝文件
		new TransferWebpackPlugin([{
			from:'./lib/laydate/skins',
			to:'js/skins'
		},{
			from:'./lib/laydate/need',
			to:'js/need'
		}])
	]

打包后的 libs.js:816KB bll.js:933KB styles.css:817KB HTML结构变为:

<html>
<head>
	<meta charset="UTF-8"/>
    <title>HPS</title>
</head>
<body>
<div id='root'>
</div>
<link rel="stylesheet" href="dist/styles.css">
<script src="./dist/libs.js"></script>
<script src="./dist/bll.js"></script>
</body>
</html>

页面首次加载的HTTP数减少到3,其中有一个是css文件~ ###Step 7 ####问题: 默认使用 require('echarts') 得到的是已经加载了所有图表和组件的 echarts 包,因此体积会比较大。echarts支持按需加载的方式。 例如,使用柱状图:

// 引入 ECharts 主模块
var echarts = require('echarts/lib/echarts');
// 引入柱状图
require('echarts/lib/chart/bar');
// 引入提示框和标题组件
require('echarts/lib/component/tooltip');
require('echarts/lib/component/title');

####解决: 可参考echarts官网的在webpack中使用echarts中的介绍。 由于页面实际只使用了line,gauge,bar三种图表类型。 经过按需加载处理,打包后的libs.js大小变为560KB ###Step 8 ####问题: 从最大限度利用缓存,加快页面加载速度的角度考虑,需要对所有静态资源(js,css,图片)添加摘要信息。 关于原因,可参考 大公司里怎样开发和部署前端代码? ####解决:

  1. 对js文件添加摘要信息,通过[chunkhash]来解决,后面的:8代表输入8位长度的摘要,默认16
output: {
		path: 'dist/',
		publicPath: "./",
		filename: 'js/[name].[chunkhash:8].js',
		chunkFilename: 'js/[id].bundle.js'
	},
plugins:[
		//提取公用组件
		new CommonsChunkPlugin({
			name:"vendor",
			filename:"js/libs.[chunkhash:8].js",
			minChunks: Infinity
		})
]

2.对css文件添加摘要信息,同样适用chunkhash:8解决

plugins:[
		//提取css文件
		new ExtractTextPlugin("css/styles.[chunkhash:8].css")
]

3.对图片添加摘要信息 通过url-loader的query可以设置输出的图片的文件名(默认为32位hash),我们将所有输出的图片保存到images目录中。

module: {
		loaders: [{
			test: /\.(less|css)$/,
			loader: ExtractTextPlugin.extract("style-loader", "css-loader!less-loader",{
				publicPath:'../'
			}),
			exclude: /node_modules/
		},{
			test: /\.(png|jpg|gif)$/,
			loader: 'url-loader',
			exclude: /node_modules/,
			query: {
				limit: 8192,
				name:'images/[name].[hash:6].[ext]'
			}
		}]
	}

###Step:9 ####问题: 由于,文件摘要信息经常变化,因此HTML文档需要自动生成

####解决: 使用html-webpack-plugin,html-webpack-plugin能够自动将打包的css和js文件填到html文件中。 插件可使用参数: title: html标题 template:用来生成HTML文档的模板文件 minify:是否进行最小化操作 faviron:网站ico的路径 chunksSortMode:chunk填入HTML文档的顺序

new HtmlWebpackPlugin({
      title:'hps',
      template:'src/index.ejs',
      minify:false,
      favicon:'src/images/favicon.ico',
      chunksSortMode:function(a,b){
        var orderMap = {
          'vendor':1,
          'bll':2
        };
        var aName = a.names[0];
        var bName = b.names[0];
        return orderMap[aName]&&orderMap[bName]? orderMap[aName]-orderMap[bName] : -1;
      }
    }

###小结:

最后生成的包的结构为

dist
  --images
    --***.png
    --***.jpg
    --...
  --styles
    --styles.********.js
  --js
    --libs.********.js
    --libs.********.js
  --favicon.ico
  --index.html

原来的生成的结构为:

dist
  --bundle.js
  --***.png
  --***.jpg
  --...
lib
  --echarts.min.js
  --backbone.min.js
  --...
index.html

可以看到,优化后生成的文档结构更加清晰。部署时需要的所有文件都放在dist中。

完整的配置代码为

var prodConfig = {
	entry: {
		bll:__dirname + '/src/main.jsx',
		vendor:['react','react-dom',"backbone",
			"echarts/lib/chart/bar",
			"echarts/lib/chart/line",
			"echarts/lib/chart/gauge",
			"echarts/lib/component/legend",
			"echarts/lib/component/tooltip"
		]//第三方模块
	},
	output: {
		path: 'dist/',
		publicPath: "./",
		filename: 'js/[name].[chunkhash:8].js',
		chunkFilename: 'js/[id].bundle.js'
	},
	resolve: {
		alias: {
			root: path.join(__dirname, "src"),
			bll: path.join(__dirname, "src/bll"),
			widget: path.join(__dirname, "src/widget"),
			util: path.join(__dirname, "src/utils"),
			model: path.join(__dirname, "src/models"),
			collection: path.join(__dirname, "src/collections"),
			laydate:path.join(__dirname,'lib/laydate/laydate.js')
		}
	},
	externals: {},
	module: {
		loaders: [{
			test: /\.jsx?$/,
			loader: 'babel',
			exclude: /node_modules|lib/,
			query: {
				presets: ['react', 'es2015']
			}
		},{
			test: /\.(less|css)$/,
			loader: ExtractTextPlugin.extract("style-loader", "css-loader!less-loader",{
				publicPath:'../'
			}),
			exclude: /node_modules/
		},{
			test: /\.(png|jpg|gif)$/,
			loader: 'url-loader',
			exclude: /node_modules/,
			query: {
				limit: 8192,
				name:'images/[name].[hash:6].[ext]'
			}
		},{
      test:require.resolve("./lib/laydate/laydate.js"),
      loader:"exports-loader?laydate"
    }]
	},
	plugins:[
		//代码压缩与混淆
		new UglifyJsPlugin({
			compress:{
				warnings:false
			}
		}),
		//提取css文件
		new ExtractTextPlugin("css/styles.[chunkhash:8].css"),
		//提取公用组件
		new CommonsChunkPlugin({
			name:"vendor",
			filename:"js/libs.[chunkhash:8].js",
			minChunks: Infinity
		}),
		//将$,jQuery,window.jQuery变量导入到所有模块中
		new webpack.ProvidePlugin({
		   $: "jquery",
		   jQuery: "jquery",
		   "window.jQuery": "jquery"
		}),
		//拷贝文件
		new TransferWebpackPlugin([{
			from:'lib/laydate/skins',
			to:'js/skins'
		},{
			from:'lib/laydate/need',
			to:'js/need'
		}]),
		//生成HTML文件
		new HtmlWebpackPlugin({
      title:'hps',
      template:'src/index.ejs',
      minify:false,
      favicon:'src/images/favicon.ico',
      chunksSortMode:function(a,b){
        var orderMap = {
          'vendor':1,
          'bll':2
        };
        var aName = a.names[0];
        var bName = b.names[0];
        return orderMap[aName]&&orderMap[bName]? orderMap[aName]-orderMap[bName] : -1;
      }
    })
	]
};
共有 人打赏支持
粉丝 0
博文 7
码字总数 10429
×
铛铛铛铛
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: