webpack打包优化

记一次react项目优化的过程
优化前,用uglifyjs-webpack-plugin插件压缩js后得到的大小,实际大小1.3M,如图:

image.png

以上图形化界面,可下载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压缩版的
  
  
  
  
  
  
  
  
  

什么是CDN?
cdn(内容分发网络)的作用是加速网络传输,通过将资源部署到服务器,来加快资源到获取速度。目前用到的CDN主要是由Bootstrap 中文网支持并维护的前端开源项目免费 CDN 服务。

2.以静态文件的方式引入资源包
在当前项目的public目录下创建新的目录resource,将以上cdn引用包全都保存到resource目录(注:通过create-react-app创建的react项目中public目录下默认会有static目录,这里不能将js到静态文件放到static目录,在index.html中直接引入static目录下到文件会报错 Unexpected token <,所以要么将static目录名改成其他名字,或者新建一个目录)

image3.jpg

  
  
  
  
  
  
  
  
  

既然用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 (
            
( )}/>
) } }
//./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 (
            
) } } //./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,如:


另外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

image6.png

压缩后:vendor.js 154kb
image7.png

至此,大功告成!

你可能感兴趣的:(webpack打包优化)