相信每一款产品,都有很多零碎的H5页面,有的是用于浏览器打开,有的是用来嵌在终端的APP中。这些页面的大多存在方式,都是互相不关联,是一个又一个的松散组织的零散页面。Webpack是不是这些页面的正确打包、构建方式呢?
原有项目打包的简要介绍
首先有一点是需要提前说明的,就是我们产品和大多数前端架构不一样的地方:
- CSS不是由我们来写,也不是由我们来发布,所以针对CSS、Image静态资源的存放是不由我们控制的。
- 页面是用Node直出的,所以我们一个页面会有部分的Node服务器代码。(什么是直出)
- 我们的页面都是用zepto手撸的,并没有用vue等框架。
正是由于我们项目的特殊性,目前没有找到和我们相匹配的构建方式,所以探索过程也并不容易。
H5项目目录结构
整个H5项目目录结构大致是:
.
├── common
├── lib
├── modules
│ ├── page1
│ ├── page2
│ ├── page3
│ └── page4
├── tmpl
- common:业务相关库
- lib:业务不相关基础设施库
- modules:各种页面项目存放目录
- tmpl:直出基础模板目录
注:其中对于第三方库我们是另外存储的,直接发布CDN,然后通过
script
标签引用;其中lib/common库是服务器端、浏览器端通用的同构库。单页面的目录结构
单个页面的目录结构如下:
.
├── src
│ ├── _config.js // jsc配置文件
│ ├── ar.js
│ ├── busiConfig.js
│ ├── capacity_purchase.js
│ ├── dom.js
│ ├── mgr.js
│ ├── tmpl
│ │ ├── body.tmpl.html
│ │ └── iap.tmpl.html
│ ├── view.js
│ └── vm.js
├── sync
│ ├── config.js
│ ├── loader.js
│ └── sync.js
├── tmpl.js
├── index.js
src
src目录是异步使用的库,也就是浏览器端代码,是需要被打包的。
其中比较另类的有两个:
- tmpl文件夹,这个文件夹是存放编译前模板的,编译(编译是指我们自己一个工具先进行预处理)出来成js函数,填入对应数据,输出html字符串,这个是浏览器端和服务器端共用的。
- vm.js文件,这个是处理数据的工具函数,无状态。也是浏览器和服务器端都使用(直出使用这个来处理拉到的数据,格式化成页面需要用到的展示数据;浏览器端ajax拉到的数据也要用其处理)。
sync
sync目录存放的是服务器端代码,Node服务器拿到request。先从业务后台拉取需要的数据,填到模板函数中,输出html字符串,然后封装返回response。
编译,jsc
jsc是团队内部的一个打包&编译工具,src/_config.js就是它的配置文件,做这样两件事:
- 把src目录下面所有的文件都打包到
index.js
之中,然后在浏览器中加载运行时将拆包推入seajs模块系统中。(没错,加载这个文件之前,要加载seajs,虽然seajs已死。这也是我决心要更新一套构建工具的原因,面向未来编程嘛)
- 把src/tmpl目录下的模板文件打包出来一份输出为单独的tmpl.js,以供服务器端代码使用,因为直出渲染时也依靠这个来输出html字符串。
CSS、Image静态资源哪去了?
正如上文所说,样式是由专门的重构同学做的,发布流程目前也不在我们手中。所以我们的工作很纯粹,是纯粹的Javascript程序员。
使用Webpack打包的尝试
为什么首先尝试Webpack?
两个原因:
- 装X(有人说为什么不用rollup,那么我就说了,别着急,马上就来尝试)
- 用上ES6 module,面向未来就是要面向真正的未来。browsify插件太少,不热,插件少,自然不予考虑。
尝试效果如何
比“差强人意”差一点,这句话没毛病吧?说人话的话,就是妥协太多,最终虽然达到了生产环境的发布要求,但是有太多需要妥协的事情了,而妥协的事情一多,就表明不合适,这和谈恋爱是一个道理。虽然花费了很多时间去探索,但是学经济的人都知道,那都是沉没成本,所以最后我选择不妥协,要再去找个更好的。废话不说那么多,我们来看看哪里妥协了。
改版换血概览
├── build
│ ├── lib.dev.js
│ ├── lib.js
│ ├── vendor.dev.js
│ └── vendor.js
├── lib
├── modules
│ ├── src
│ │ ├── _config.js
│ │ ├── entry.dev.js // webpack dev build
│ │ ├── entry.js // webpack production build
│ │ ├── index.js
│ │ └── tmpl
│ │ └── index.tmpl.html
│ └── sync
│ ├── config.js
│ ├── data_processor.js
│ ├── loader.js
│ ├── sync.js
│ └── tmpl.js
│
├── tmpl
├── node_modules // npm
├── package.json // npm
├── manifest.dev.json // webpack
├── manifest.json // webpack
├── webpack.config.js // webpack
├── webpack.dirvars.js // webpack
├── webpack.dll.config.js // webpack
└── webpack.dll.dev.config.js // webpack
先从基础库说起
使用了Webpack之后,我的库只划分为两个:vendor(第三方库,如vue、zepto)、lib(common + lib)。
- __发现改版之后去掉了common库,把common库(业务基础库)和lib库(基础工具库)合在了一起,这就是妥协点一。__
Webpack对于基础库的打包定制能力不强,对于自制库(包括common、lib)只能有一个CommonsChunkPlugin的解决方案。而且这种解决方案的弊病是:组织库的方式是按照引用次数自动归为库文件(比如对a.js文件,项目中有超过5次引用,就认为这个是一个库文件),而非依据目录来定义库。这种约束不强,库文件初期容易经常变更。
- __第三方库打包方式也不适合这种多页松散的场景。__
第三方库使用DllReferencePlugin这个来实现。简要介绍下这个插件的作用:你可以指定['vue', 'zepto']两个第三方库,然后生成对应的vendor.js以及manifest.json。vendor.js是浏览器中加载的打包好的两个第三方库文件,manifest.json是依赖记录,可以在打包页面的时候告诉页面,vue库已经被包含在vendor库中了,你可以不用把vue打进来了。听起来是很不错,但是问题在于:
1.__这个vendor.js不能热插拔,丢失了的话页面会有报错。所以对于你的每一个页面来说,都必须要加载这个vendor。__
2.打包这个第三方库dll,也要起一个Webpack才行,配上生产环境和开发环境两套配置,你可以看到项目下面全部是Webpack的配置文件。
再说打包好的文件存放目录
不能自定义,对于我这种服务器端和浏览器代码都有的项目结构,理想的存放方式是
├── 项目A
│ ├── 浏览器代码
│ │ ├── 原始src文件
│ │ ├── 打包好的bundle.js
│ └── 服务器代码
│ ├── 原始commonjs模块
│ └── 编译好的tmpl.js
然而Webpack要一股脑扔到build目录下,虽然有奇技淫巧:比如让entry的name是一个路径可以达到存放到相应目录下的效果。但是一是不爽,二是服务器端模板文件需要编译一遍放在服务器端代码目录下面,这个实现不了(具体比较复杂)
再说模板编译
我需要写一个插件,将jsc第二个功能抽离出来,即将模板文件编译成js函数文件。但是这个需要再起一个Webpack,再起一个Webpack!!!
加上页面的、dll的、模板文件编译的,一共需要三个Webpack。光配置文件一大堆,一点都不优雅。
Webpack对于这些页面力不从心的原因
需要作出妥协的几点:
- 库打包差强人意,无法层次分明地区分:第三方库vendor、业务基础库common、基础工具库lib。
- 库无法热插拔,每个页面必须同时且顺序加载:vendor、lib、entry。
- 存放目录无法自定义。
- 每个Webpack是从一个页面进入出发的,需要启动多个Webpack才能满足服务器端和浏览器端两种打包or编译需求。
另外几点:
- 最强的部分用不到:抽离css、image这种。
- 缺点全让我碰上了。
__我也自我反思过了,当初确实是不太了解,没有仔细思考这个工具的定位。在我探索之后,我才明白,这个东西完全不适合我的场景。并不是她不优秀,而是我们真的不合适。__
未来之路在哪里
Gulp + rollup or
Gulp + Other ES6 module pack tool
皮皮虾,我们走!