webpack刚出现时gulp如日中天,现在webpack更新到2.x版本,gulp逐渐淡出我们的视线,聊webpack的人越来越多,直到最近发现Vue官方文档里到处都是基于webpack的讲解,仿佛webpack已经成为了打包器的事实标准,作为一个仍然不准备使用webpack的前端,我必须认真打量一下自己的处境了。
背景知识
模块化开发
一切还要从模块化说起,在距今仅仅几年之前的前端页面里,还会在底部发现一个jQuery+一个js文件的链接,随着这几年规范的进步、硬件、网络的发展、人们对体验的重视,各种因素导致前端脚本越写越多,代码越来越复杂,仿佛一夜之间那个玩具般的代码组织方式无法满足前端开发需求了,于是js模块化横空出世,以横扫之势迅速被前端社区接受,依托加载器实现的js模块化开发和加载,大大提升了前端开发体验,seajs便是在那个背景下杀出来的优秀加载器之一。
模块打包
模块化开发固然好,但在加载上有点问题,因为前端性能优化向来很看重减少请求数量,模块化加载无疑是背道而驰,要解决这个问题,大家想到了将模块打包,开发时是分开的,发布前用打包器合到一起,这样页面的js请求数可以降到1
,很好,从此打包器开始流行,webpack就是一个打包器,在webpack之前大家用的最多的是gulp,gulp是一个比打包器更底层的任务管理器,可以压缩代码,预编译代码,处理图片,当然也可以完成打包。
webpack的反杀
既然gulp被webpack反杀了,一定是有不如人的地方,以gulp为代表的传统打包最大的问题是解决不了按需打包,就更别说按需加载了,因为传统的打包思路是遍历源文件 => 匹配规则 => 打包/处理
,也就是说只要被规则命中了,即便是程序用不到的模块也会被无脑打包,根本原因是按需这个事无法被规则描述,只能被程序逻辑描述。其实在webpack之前不是没有解决方案,百度fis最得意的地方就是解决了这个问题,并且是理论上堪称完美的解决方案,我感觉其最大的缺点是需要后端配合,然而你懂的,后端通常不鸟这种需求,百度fis也就不了了之了。
那webpack是怎么解决按需问题的呢?前面说了,按需只能被程序逻辑描述,webpack的打包思路就是从程序逻辑入手:入口文件 => 分析代码 => 找出依赖 => 打包
,这样代码里不出现的模块就不可能被打进包里,甚至还可以实现按需加载,这就是webpack最有价值的地方。
打包过程中还有一个仅次于按需的需求,那就是分包,之所以说webpack或者说所有的打包器都特别适合SPA应用,是因为SPA只有一个入口,基本不需要分包或者分包需求很简单,但在网站类应用上,我们讨论的不是要不要分包,而是怎样最不浪费的分包,将加载总量降到最低。这个通常来说不是大问题,只要配合好文件目录结构的划分,多数分包需求都可以用规则描述,但在这方面webpack更进了一步,可以在业务代码中依托特定的语法标记出需要分包的模块,这就使分包的实现能在一定程度上自动化了,没毛病。
webpack的其他功能我觉得没必要再说了,都不是刚需,有则锦上添花,没有也无所谓。
需求分析
业务场景
不谈场景的选型都是耍流氓。
假设有这样一个网站项目,兼容IE8,基于jQuery开发,脚本模块化,模块分成业务模块、插件模块、类库模块,类库模块以jQuery为主,业务模块即每个页面的业务代码,也是脚本入口,插件模块主要实现各种前端展示效果,为了更好的代码复用,已经将很多插件封装好并沉淀为一个插件库,可以覆盖项目里几乎所有的展示需求,这个库以一个文件夹的形式存在。
那么问题来了,这么多模块,请求数居高不下,怎么办。
无脑回答,打包啊。
打包分析
那我们就来看这个项目该怎么打包,重点放在插件模块上,首先会遇到传统打包的老大难按需问题,因为插件都在一个文件夹里,规则只能全部匹配,结果就是打出来一个大而无当的包。你说手动把不用的插件移除不行么,行。但实际情况是,展示类页面经常更新,可能今天用到插件1,明天就改用插件2,或者增加了一个页面要用插件3,你让开发者自己去统计到底用了哪个没用哪个吗,那是历史的倒退,不行。
别人解决不了webpack可以,这是webpack首当其冲的优势,按需,不是问题。
再往下看,网站上的所有展示效果都通过插件实现,最终整个项目用到的插件总数比较可观,也就是说即便按需了,这个插件包也不会小,而插件虽说相对固化,但毕竟是展示类插件,更新是免不了的,任意一个插件发生了增删改,整个插件包都要重打,客户端重新加载,代价有点大,不划算,并且我们知道,这个需求对于展示型网站来说,绝对是个真需求。
整体打包不行,那每个页面对应一个自己的插件包呢,各改各的,互不干扰,嗯,有点道理。
但是,实际情况要复杂的多,这也是展示型网站最烦人的地方,页面间还是有不少共用插件的,头部至少有个导航、下拉菜单、搜索框吧,底部来个选项卡不过分吧,幻灯片插件很多页面都有可以吗,这些公共插件每个页面打一份真的好吗。要是将公用插件单独拎出来呢,好啊,那不就又是人肉维护依赖关系了么,还是不好。
说一千道一万,关键在于打包需求不稳定,这种情况是打包中最难解决的,webpack也没有办法。但百度fis有!fis从一个更高的维度看问题,直接拿到了资源依赖表,千变万化的打包需求瞬间变得可控了,可惜,用户少,生态就小,生态小,就没戏了。
现在的局面,在不考虑fis的情况下,已经不是怎么打包的问题了,而是根本不适合打包。
解决方案
模块本地存储
那么问题又变成,无法打包的情况下如何减少模块化应用请求数。
不打包就得用加载器,加载器自身不可避免的要占一个请求,但加载器也为模块请求优化提供了可能,现在至少有两种可行的方案,一是配合服务端的请求合并,二是模块本地存储。服务端请求合并方案这里略去不谈。
本地存储方案在纯前端就可以实现,通过扩展加载器将请求到的模块代码缓存到localStorage
,这样用户初次访问会全量请求,但之后的访问将只加载模块加载器、缓存模块目录、和业务模块三个文件,模块目录文件用于记录将要缓存的模块及其版本号,实现缓存更新,实际开发中可以跟加载器合并成一个文件,从而将脚本请求数降到2
。
下面我们就要算一笔账了,虽然打包方案极难完美实现,但如果我们财大气粗,上CDN加速呢,虽然包经常更新,但有了CDN加载速度也能保证,理论上每个页面的脚本请求数可以降到1
,但实际中总要提取第三方类库吧,请求数同样是2
,好,那对比双方就是:
普通请求加载器 + 业务代码
VS CDN加持下的公共包 + 页面包
:
下面讨论的前提是,CDN再快也有极限,一个CDN大包在一个小文件面前,不会有很大优势。下面看看双方的代码体积,加载器用的seajs
,页面的业务代码百行左右,这种情况下的2个请求,我认为上CDN的钱可以省。在非首次加载情况下,我认为本地存储方案处于无敌水平。
本地存储方案最大的问题是首次加载,大批的插件请求怎么办。无法根除,但可以缓解。比如在前端开发时多考虑模块懒加载,尽量提升首屏展示速度,这是前端的看家本领。实在不行咱也可以给插件上CDN嘛,反正只有首次加载走流量,就算上了CDN都比其他方案省好多钱。
因此最终选定的方案是,seajs + 本地存储。
webpack不是银弹
以上项目需求均来自真实经历,这些需求我相信在前端圈子里仍然占有很大的比例,总结起来就是,我们有一个丰富又多变的组件库,这时候webpack解决不了问题。
在兼容性方面,webpack一直是面向最新的标准,自身的很多特性需要依赖polyfill
才能向下兼容,甚至有的特性最新的浏览器都还没有原生兼容,用起来难免有点折腾。
技术圈都是追新的,仿佛现在的前端都不好意思再聊jQuery技术栈了,即便说起来也都是jQuery已经过时了
的论调,我只想说,浏览器市占率摆在那,展示类项目的需求摆在那,这些应用场景还确确实实的存在着,现实世界的任何一个2C产品可能都有一个高到让你不屑的兼容性要求。
最后webpack的侵入性较强,这是我个人最介意的一点,某些高级特性需要依赖独特的语法才能实现,也就是说需要在一定程度上面向webpack编程,一旦离开了webpack,你的源码无法运行。
后记
唯一不变的是变化。
模块加载在未来大概率会被浏览器原生支持,到那时会不会有新的优化方案?
打包这件事,怎么看都像一个补丁,未来的HTTP2
会不会彻底使其成为伪需求?
IE8迟早淘汰,Vue/React的市场会无限增大吗,SEO怎么做,Nodejs服务端渲染是答案吗?
这真是个动荡的年代啊。
没错,文中提到的项目就是Flow-UI,感谢关注。
前端路上原创技术文章,转载请注明出处:https://refined-x.com/2017/06/16/Webpack是答案吗/
不甘平庸的你,快来跟我一起充电吧,关注看风景,获取更多精彩内容。