webpack优化(1)-打包篇
页面的首次加载时间对用户体验影响很大,有研究表明,如果首次加载超过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结构
###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结构也变为
HPS
可以看到除了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结构变为:
HPS
页面首次加载的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,图片)添加摘要信息。 关于原因,可参考 大公司里怎样开发和部署前端代码? ####解决:
- 对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;}})]
};
转载于:https://my.oschina.net/dang4/blog/702275
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
