webpack打包优化
更新日期:
问题描述:
丁丁租房,在8月初,开发大版本的重构。我也是在7.30号,加人丁丁,也莫名其妙的成为了第一个丁丁的前端。因为没有历史包袱,所以,当时决定用react+webpack,虽然从来没有接触过react和webpack。
在经过大概3周的开发后,代理商系统(http://daili.zufangzi.com/#/),和400系统(http://400.zufangzi.com/#/)开发完成。因为经验的缺乏、人力太少、时间太短,两个系统存在很多不完美的地方。其中,最大的问题是:webpack打包后,形成两个主要的js文件——entry.js 和 common.js ,都比较大,网络不好的时候,加载很慢。在上线当天,老板,就提出了,优化的问题……
以代理商为例:
代理商系统,目前线上两个js文件的大小:
问题分析和解决:
想要达到的结果:尽量减少两个js文件的大小、尽量按需加载
1. react-router 按需加载
项目用react-router进行路由的管理,如下:router.js
import React from 'react';
import ReactRouter from 'react-router';
// 主入口
import App from './page/App';
// 房源
import storageHouseInfo from './page/house/storageHouse/storageHouse';
import houseList from './page/house/houseList';
import houseShow from './page/house/houseShow/houseShow';
import houseModify from './page/house/houseModify/houseModify';
import picEdit from './page/house/editpic';
// 账户管理
import accountModify from './page/account/modify';
import noAuth from './page/noAuth/noAuth';
// 定义整个页面的路由结构
require.ensure([], function (require) {
var Router = ReactRouter.Route;
var routes = (
<Router path="/" handler={App}>
<Router path="house/storage" name="subMenuId=housePutInFeature" handler={storageHouseInfo}/>
<Router path="house/list" name="subMenuId=houseManage" handler={houseList}/>
<Router path="house/show" name="subMenuId=houseShow" handler={houseShow}/>
<Router path="house/modify" name="subMenuId=houseModify" handler={houseModify}/>
<Router path="house/editpic" name="subMenuId=editpic" handler={picEdit}/>
<Router path="account/modify" name="subMenuId=accountManagement" handler={accountModify}/>
<Router path="noauth" name="subMenuId=noAuth" handler={noAuth}/>
</Router>
);
ReactRouter.run(routes, ReactRouter.HashLocation, (Root) => {
React.render(<Root/>, document.body);
});
});
这样,导致的问题,所有的业务代码,都会直接打到entry.js里。所有,要能够使handler异步加载。
asyncPage.js:
/**
*异步加载模块
*/
var React = require('react');
var handlers = {};
var AsyncRoute = function(req) {
return React.createClass({
getInitialState: function() {
return {
myComponent: handlers[req]
};
},
statics: {
// willTransitionTo,react-router生命周期: 当一个handler 将要被渲染的时候被调用
// willTranstionFrom是当一个被激活路由将要跳出的时候给你提供了中断跳出的方法
willTransitionTo: function (transition, params, query, callback) {
var deffer = new Promise(function(resolve, reject) {
require.ensure([], function() {
var Comp = handlers[req] || require(req);
handlers[req] = Comp;
resolve();
});
});
deffer.then(function() {
callback();
});
}
},
render: function() {
return (<this.state.myComponent />);
}
});
};
module.exports = AsyncRoute;
router.js里的更改:
import React from 'react';
import ReactRouter from 'react-router';
var AsyncRoute = require("./asyncPage");
var routerConf = require("./routerConf");
// 主入口
import App from './page/App';
// 房源
// import storageHouseInfo from './page/house/storageHouse/storageHouse';
//import houseList from './page/house/houseList';
//import houseShow from './page/house/houseShow/houseShow';
//import houseModify from './page/house/houseModify/houseModify';
//import picEdit from './page/house/editpic';
// 账户管理
//import accountModify from './page/account/modify';
//import noAuth from './page/noAuth/noAuth';
// 定义整个页面的路由结构
// 异步加载handler
require.ensure([], function (require) {
var Router = ReactRouter.Route;
var routes = (
<Router path="/" handler={App}>
<Router path={routerConf['housePutInFeature']} name="subMenuId=housePutInFeature" handler={AsyncRoute('./page/house/storageHouse/storageHouse.js')}/>
<Router path={routerConf['houseManage']} name="subMenuId=houseManage" handler={AsyncRoute('./page/house/houseList/index.js')}/>
<Router path={routerConf['houseShow']} name="subMenuId=houseShow" handler={AsyncRoute('./page/house/houseShow/houseShow.js')}/>
<Router path={routerConf['houseModify']} name="subMenuId=houseModify" handler={AsyncRoute('./page/house/houseModify/houseModify.js')}/>
<Router path={routerConf['editpic']} name="subMenuId=editpic" handler={AsyncRoute('./page/house/editpic/index.js')}/>
<Router path={routerConf['accountManagement']} name="subMenuId=accountManagement" handler={AsyncRoute('./page/account/modify/index.js')}/>
<Router path={routerConf['noAuth']} name="subMenuId=noAuth" handler={AsyncRoute('./page/noAuth/noAuth.js')}/>
</Router>
);
ReactRouter.run(routes, ReactRouter.HashLocation, (Root) => {
React.render(<Root/>, document.body);
});
});
webpack.config.js中的修改:
output: {
path: '/',
// path: __dirname + "/release",
publicPath: '/agent-asset/',
// filename: '[name].[hash].js',
filename: '[name].js',
chunkFilename: 'entry.biz.[hash].js' // 加上这句!!嘻嘻……
}
通过上述的修改,webpack在打包时,会自动给我们分出一个entry.biz.hash.js,这个js文件,会在登录点菜单时,按需加载。
2. jquery、moment等用cdn
3. 删除react-bootsrtap、react-router-bootstrap等用处不大的依赖,需要的,比如react-router-bootstrap的NavItemLink,自己实现。
4. 依赖的组件库,antd,从entry.js中抽出antd,放入common.js中
在webpack.config.js中,如下更改。
entry: {
common: ['react', 'antd'],
entry: [
'webpack-dev-server/client?http://localhost:8080',
'webpack/hot/only-dev-server',
'./src/router'
]
},
结论
在经过上面四步后,
登录时加载的entry.js,从917k减少到了141k,但是common.js因为加入了antd,从281k增加到了469k……entry.biz.js会在登录点击菜单后进行加载。
结果……虽然,比之前好点……但是还是不够完美……后面待续……
参考
http://segmentfault.com/a/1190000002801128
这个,是后来发现的,也不错~但是没有实验过。https://github.com/QianmiOpen/react-async-router