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压缩版的
  <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目录名改成其他名字,或者新建一个目录)

image3.jpg

  <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,小了一半

image4@2x.png

注:细心的同学会发现,上图中的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

image6.png

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

至此,大功告成!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,864评论 6 494
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,175评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,401评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,170评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,276评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,364评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,401评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,179评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,604评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,902评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,070评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,751评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,380评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,077评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,312评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,924评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,957评论 2 351

推荐阅读更多精彩内容