记一次react项目优化的过程
优化前,用uglifyjs-webpack-plugin插件压缩js后得到的大小,实际大小1.3M,如图:
以上图形化界面,可下载webpack-bundle-analyzer插件包查看。
可以发现打包出来的main.js内主要包含了两部分:node_modules包及js业务代码,所以第一步就是要拆分node_modules包及业务代码:
//业务代码与node_modules分离
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: ({ resource }) => (
resource &&
resource.indexOf('node_modules') >= 0 &&
resource.match(/\.js$/)
),
}),
打包效果:
image2.png
第二步:分离node_modules包中比较大的包,并通过CDN或以静态文件的方式引入到项目中,在webpack中用externals指定即可,如:
externals: {
'react': 'React',
'react-dom': 'ReactDOM',
'redux': 'Redux',
'redux-thunk': 'ReduxThunk',
'react-redux': 'ReactRedux',
'redux-form': 'ReduxForm',
'immutable': 'Immutable',
'babel-polyfill': 'window', // polyfill 直接写 {} 也是可以的,
'transit-js': 'transit',
}
分离后在externals中所指定的包就不会打包的vendor.js中。
1.在index.html中通过CDN的方式引入在externals中指定过的包,如:
//注:以下插件包需找项目中对应版本的包且是min.js压缩版的
<script src="https://cdn.bootcss.com/babel-polyfill/6.2.0/polyfill.min.js"></script>
<script src="https://cdn.bootcss.com/react/16.4.0/umd/react.production.min.js"></script>
<script src="https://cdn.bootcss.com/react-dom/16.4.0/umd/react-dom.production.min.js"></script>
<script src="http://cdn.cognitect.com/transit/transit-0.8.861-min.js"></script>
<script src="https://cdn.bootcss.com/redux-thunk/2.2.0/redux-thunk.min.js"></script>
<script src="https://cdn.bootcss.com/redux/3.7.2/redux.min.js"></script>
<script src="https://cdn.bootcss.com/react-redux/5.0.7/react-redux.min.js"></script>
<script src="https://cdn.bootcss.com/redux-form/7.0.1/redux-form.min.js"></script>
<script src="https://cdn.bootcss.com/immutable/3.8.1/immutable.min.js"></script>
什么是CDN?
cdn(内容分发网络)的作用是加速网络传输,通过将资源部署到服务器,来加快资源到获取速度。目前用到的CDN主要是由Bootstrap 中文网支持并维护的前端开源项目免费 CDN 服务。
2.以静态文件的方式引入资源包
在当前项目的public目录下创建新的目录resource,将以上cdn引用包全都保存到resource目录(注:通过create-react-app创建的react项目中public目录下默认会有static目录,这里不能将js到静态文件放到static目录,在index.html中直接引入static目录下到文件会报错 Unexpected token <,所以要么将static目录名改成其他名字,或者新建一个目录)
<script src="/resource/polyfill.min.js"></script>
<script src="/resource/react.production.min.js"></script>
<script src="/resource/react-dom.production.min.js"></script>
<script src="/resource/transit-0.8.861-min.js"></script>
<script src="/resource/redux-thunk.min.js"></script>
<script src="/resource/redux.min.js"></script>
<script src="/resource/react-redux.min.js"></script>
<script src="/resource/redux-form.min.js"></script>
<script src="/resource/immutable.min.js"></script>
既然用CDN加速来加载引入的包,为什么还要用静态文件的方式引入呢?
答案当然是本地引入文件会更快,毕竟cdn也是服务环境,请求服务环境的资源也是耗时间的,可参考cdn加速与js引入文件的比较
分离后vendor.js的大小:从原来的1.06M减少到584.42kb,小了一半
注:细心的同学会发现,上图中的antd-mobile包为什么没有通过js引用?
原因是antd-mobile包在经过按需打包后所得大小是147kb,而通过cdn引入的antd-mobile竟然有373kb,远超出了打包所得大小,所以不建议通过js引入,
可参考cdn antd-mobile
3.第三步-拆分js业务代码,按需加载
可看上图image2,右侧蓝色部分,业务代码实际大小只有93kb,但在页面初次加载资源时会将整个js业务代码都加载进来,特别是在网络环境差时,会加载的比较慢
业务代码主要分三个模块:买家账户(放account目录),卖家(放seller目录),登录、产品等页面(放sys目录),再加上公共代码(common\component目录),总共分四个块,项目中用到react-router-dom路由,接下来就按路由的方式拆分代码,所用工具react-loadable
//目录结构
Projects
--js
--common
--component
--App.web.js
--pages
--index.web.js
--account
--sys
--seller
index.web.js
//App.web.js
import {
AsyncSys,
AsyncSeller,
AsyncAccount,
} from './pages/index.web'
import {Route, Redirect, Switch } from 'react-router-dom'
class Root extends Component {
render() {
return (
<div>
<Switch>
<Route exact path="/" render={() => (
<Redirect to="/Sys/Product"/>
)}/>
<Route path="/Sys" component={AsyncSys}/>
<Route path="/Account" component={AsyncAccount}/>
<Route path="/SellerList" component={AsyncSeller}/>
</Switch>
</div>
)
}
}
//./pages/index.web.js 这里按路由拆分
import Loadable from 'react-loadable';
import AsyncJSLoading from '../component/AsyncJSLoading'
const AsyncSys = Loadable({
loader: () => import(/* webpackChunkName: 'sys' */'./sys/index.web'),
loading: AsyncJSLoading
});
const AsyncSeller = Loadable({
loader: () => import(/* webpackChunkName: 'seller' */'./seller/index.web'),
loading: AsyncJSLoading
});
const AsyncAccount = Loadable({
loader: () => import(/* webpackChunkName: 'account' */'./account/index.web'),
loading: AsyncJSLoading
});
export {
AsyncSys,
AsyncAccount,
AsyncSeller,
}
//./sys/index.web.js
import {Route, Switch} from 'react-router-dom';
import Product from './ProductDetails.web';
import Login from './Login.web';
import SupplyData from './SupplyData.web';
export default class extends React.Component {
render() {
return (
<div>
<Switch location={this.props.location}>
<Route path={`${this.props.match.path}/Login`} component={Login}/>
<Route path={`${this.props.match.path}/Product`} component={Product}/>
<Route path={`${this.props.match.path}/SupplyData`} component={SupplyData}/>
</Switch>
</div>
)
}
}
//./seller/index.web.js 、./account/index.web.js 与 sys/index.web.js写法一样,这里不列举了
3点注意:
1.在 pages/index.web.js中通过import分割相应模块,模块内的文件在当前分割js中不可再次引用,否则分割失效,如:
//./pages/index.web.js
import Loadable from 'react-loadable';
import AsyncJSLoading from '../component/AsyncJSLoading'
import Login from './sys/Login.web' //再次引入sys目录下的文件
const AsyncSys = Loadable({
loader: () => import(/* webpackChunkName: 'sys' */'./sys/index.web'),
loading: AsyncJSLoading
});
2.import() 会返回一个Promise,对于不支持Promise浏览器需要在页面上注入Promise polyfill,如:
<script src="https://cdn.bootcss.com/babel-polyfill/6.2.0/polyfill.min.js"></script>
另外import()语法还没有加入到ECMAScript标准里,所以项目中需要安装一个Babel插件 babel-plugin-syntax-dynamic-import,并且将其加入.babelrc中:
{
plugins: [
"syntax-dynamic-import"
],
}
3./* webpackChunkName: 'sys' */ 的含义是为动态生成的Chunk赋予一个名称,以便我们追踪和调试代码
//filename中的name 就是 webpackChunkName所指定的名称
output: {
path: paths.appBuild,
filename: 'resource/js/[name].[chunkhash:8].js',
chunkFilename: 'resource/js/[name].[chunkhash:8].chunk.js',
publicPath: publicPath,
},
最后看看分割的结果:按预期分成功了四大块:main.js 主要是公共代码、account.js 是买家模块、seller.js 是卖家模块、sys.js 是 登录、产品页部分
image5.png
第四步-gzip压缩
什么是gzip压缩?
参考
服务器开启GZIP
探索HTTP传输中gzip压缩的秘密
const CompressionWebpackPlugin = require('compression-webpack-plugin');
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp('\\.(js|css)$'),
threshold: 10240,
minRatio: 0.8
})
)
压缩前:最大的文件vendor.js 589kb
压缩后:vendor.js 154kb
image7.png
至此,大功告成!
网友评论