文档章节

webpack优化(1)-打包篇

 铛铛铛铛
发布于 2016/06/28 16:33
字数 2397
阅读 124
收藏 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 打包优化之体积篇 | 晚晴幽草轩

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

05/17
0
0
Webpack 打包之体积优化

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

jeffjade
2017/08/10
0
0
手摸手,带你用合理的姿势使用webpack4(上)

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

花裤衩
08/07
0
0
webpack学习笔记——入门篇:通俗理解webpack

前言 (在过去)对我这种渣渣来说,在项目里面一旦配置好了webpack之后,就再也不想碰这玩意儿了,因为实在是太多的坑。。。使用一个插件可能要把config文件改个十几二十遍,还得不断地跑起来...

Eason_Wong
07/16
0
0
彻底解决Webpack打包性能问题

这几天写腾讯实习生 Mini 项目的时候用上了 react 全家桶,当然同时引入了 Webpack 作为打包工具。但是开发过程中遇到一个很棘手的问题就是,react 加上 react-router、material-ui、superag...

黑魔法
2016/07/29
1K
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

maven坐标和依赖

一、maven坐标详解 <groupId>com.fgt.club</groupId><artifactId>club-common-service-facade</artifactId><version>3.0.0</version><packaging>jar</packaging> maven的坐标元素说......

老韭菜
今天
1
0
springmvc-servlet.xml配置表功能解释

问:<?xml version="1.0" encoding="UTF-8" ?> 答: xml version="1.0"表示是此xml文件的版本是1.0 encoding="UTF-8"表示此文件的编码方式是UTF-8 问:<!DOCTYPE beans PUBLIC "-//SPRING//......

隐士族隐逸
今天
1
0
基于TP5的微信的公众号获取登录用户信息

之前讲过微信的公众号自动登录的菜单配置,这次记录一下在TP5项目中获取自动登录的用户信息并存到数据库的操作 基本的流程为:微信设置自动登录的菜单—>访问的URL指定的函数里获取用户信息—...

月夜中徘徊
今天
0
0
youTrack

package jetbrains.teamsys.license.runtime; 计算lis package jetbrains.ring.license.reader; 验证lis 安装后先不要生成lis,要把相关文件进行替换 ring-license-checker-1.0.41.jar char......

max佩恩
今天
1
0
12.17 Nginx负载均衡

Nginx负载均衡 下面的dig看到可以返回2个IP,就是解析出来的IP,这样我们可以做负载均衡。 dig www.qq.com 1.vim /usr/local/nginx/conf/vhost/fuzai.conf 2.添加如下配置 upstream qq //定义...

芬野de博客
今天
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部