文档章节

webpack优化(1)-打包篇

 铛铛铛铛
发布于 2016/06/28 16:33
字数 2397
阅读 134
收藏 2

页面的首次加载时间对用户体验影响很大,有研究表明,如果首次加载超过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
作品 0
南京
Webpack学习笔记 - 入门篇

如果你已经读了 Webpack学习笔记 - 体验篇,是不是有想进一步了解 的冲动。 没有。那是因为包子太大,你还没有咬馅儿呢。 Webpackr的工作方式 把项目当做一个整体,通过一个给定的入口文件 ...

他山之石
2016/12/28
32
0
手摸手,带你用合理的姿势使用webpack4(上)

本文作者来自 华尔街见闻技术团队 - 花裤衩 前几天 webpack 作者 Tobias Koppers 发布了一篇新的文章 webpack 4.0 to 4.16: Did you know?(需翻墙),总结了一下发布以来,做了哪些调整和优化...

花裤衩
08/07
0
0
Webpack 打包之体积优化

谈及如今欣欣向荣的前端圈,不仅有各类框架百花齐放,如, , 等等,就打包工具而言,发展也是如火如荼,百家争鸣;从早期的王者, ,到后来赢得宝座的 , 以及独树一帜的 , 以及下一代打包神...

jeffjade
2017/08/10
0
0
Webpack 打包优化之体积篇 | 晚晴幽草轩

谈及如今欣欣向荣的前端圈,不仅有各类框架百花齐放,如, , 等等,就打包工具而言,发展也是如火如荼,百家争鸣;从早期的王者, ,到后来赢得宝座的 , 以及独树一帜的 , 以及下一代打包神...

05/17
0
0
带你用合理的姿势使用webpack4(上)

前几天 webpack 作者 Tobias Koppers 发布了一篇新的文章 webpack 4.0 to 4.16: Did you know?(需翻墙),总结了一下发布以来,做了哪些调整和优化。 并且说自己正在着手开发 。 翻译成中文就...

大灰狼的小绵羊哥哥
10/12
0
0

没有更多内容

加载失败,请刷新页面

加载更多

HashTable

Hashtable 是一个散列表,它存储的内容是键值对(key-value)映射 Hashtable 继承于Dictionary,实现了Map、Cloneable、java.io.Serializable接口 Hashtable 的函数都是同步的,这意味着它是线...

职业搬砖20年
23分钟前
1
0
Linux系统状态查看命令1

10月23日任务 10.1 使用w查看系统负载 10.2 vmstat命令 10.3 top命令 10.4 sar命令 10.5 nload命令 查看系统负载 w命令 # 第一行:当前系统时间,系统启动时间,登录的用户,系统负载:1分钟...

robertt15
38分钟前
2
0
缓存那些事

前言 一般而言,现在互联网应用(网站或App)的整体流程,可以概括如图1所示,用户请求从界面(浏览器或App界面)到网络转发、应用服务再到存储(数据库或文件系统),然后返回到界面呈现内容...

Skqing
47分钟前
2
0
nginx开启stub_status模块配置方法

nginx开启stub_status模块配置方法 2017年12月13日 15:57:29 ly_dengle 阅读数:3765 标签: stub_statusnginxnginx开启stub_status模块 更多 个人分类: 软件工具php 版权声明:本文为博主原...

linjin200
54分钟前
3
0
挑逗 Java 程序员的那些 Scala 绝技

有个问题一直困扰着 Scala 社区,为什么一些 Java 开发者将 Scala 捧到了天上,认为它是来自上帝之吻的完美语言;而另外一些 Java 开发者却对它望而却步,认为它过于复杂而难以理解。同样是 ...

joymufeng
57分钟前
154
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部