目前团队中新的 Web 项目基本都采用了 Vue 或 React ,加上 RN,这些都属于比较重量级的框架,然而对于小型 Web 页面,又显得过大。早期的一些项目则使用了较原始的 HTML 页面构建技术,但业务逻辑基本无法复用。
近半年做过几个小型 Web 页面,在不断学习前端知识的同时,也在重构并摸索小型 Web 项目可能的更好解决方案。本文则对之前的工作进行一次整体描述。
目标和定位
单论小型 Web 页面,其相对于 Vue/React 等项目最大不同是不需要支持 SPA 这种比较重的形式,以 MVP(Minimum Viable Product) 为原则,小页面只要满足需求,做到够用即可。所以在对现有原始 Web 页面进行重构时,会将以下两个方面作为最高优先级:
不断提高项目的重用性、可维护性;
不断提高前端性能,这里主要是加载性能;
对于第一点,组件化代码结构是当前最可行的思路;对于第二点,在做到第一点的前提下,极少的第三方依赖,良好的打包方法,是必须要做到的。
项目结构演化历程
本文所描述的小型 Web 页项目结构和打包方法是经过若干次项目重构才得到的。
第一版
第一版项目基本上以最原始的 HTML+JS+CSS 为基准。为了让项目代码更好维护,首先考虑到的是有两个点需要做:
使页面内容具有维护性,需要采用 JS 模板;
由于业务复杂,分工较细,接口繁多,需要将数据接入层 DAL(Data Access Layer) 单独分离出来;
对于第一个问题,最后选择 Mustache 库。原因是它语法极简,容易学习,同时该类型语法有广大用户群体,当然同样流行的还有 underscore/ejs 类型的模板语法。为了保证内容页面的无逻辑性和简单,故 Mustache 的高级版 Handlebars 未被使用。
第二个问题是对公司业务和项目代码有所了解后所下的结论。相当于是对现有代码的重构,主要目的是进行职责分离,将复杂多变的接口隔离出来,让剩下的代码专心解决业务问题。
开始敲代码后,才发现另一个比较严重的问题:我需要把管理内容模板的代码单独分离出来,使其不会影响主要的业务逻辑,于是想到了 MVP(Model View Presenter) 模式。简单讲,这个就是 MVVM 模式去掉 View-ViewModel 双向数据绑定后的一个弱化版。如下图所示:
在小型 Web 页面中,一般是没有 Model 层的。页面中的 Presenter 部分只负责通过参数控制界面的渲染,并以组件的方式对外公开 View 层事件。按照这个思路,第一版项目结构就基本出来了,见下图:
引入 Webpack 2+
第一版项目结构已经足以应付小型 Web 页面的需求了,同时也不会带来较多的复杂性。但是原始 Web 页面天生就不利于模块化开发,同时存在一个根本性问题:
代码解耦会使项目文件结构清晰,职责分离,有利于维护;
打包结果需要将相关代码压缩到单个文件中,便于提高加载性能;
在 Web 页面开发中,这两者形成一个悖论。所以需要引入一个打包机制,将项目代码和打包文件进行解耦。年老的 Gulp 和 Grunt 就不看了,现存项目中用的较多的是 FIS3 和 Webpack 1.x。前者国产,使用起来也非常方便,后者难度高一些,但是跟其他国外开源项目一样,他们总能把一个软件的 50% —— 文档做的很好。(其实还有很多可以吐槽的地方,但看了之后感觉相对更踏实)
在看了 Webpack 2.x 的文档之后,基本就确定使用该打包机制了,它有如下优点让人欲罢不能:
原生支持 import 语法。这样就彻底摆脱文件结构不好管理的问题了,面向对象、模块化什么的统统都可以引进来,终于可以舒舒服服写代码了;
支持 Tree Shaking。本来这是 rollup 打包机制独有的特点,现在 Webpack 也有了;
Webpack 的配置文件虽然复杂,但了解之后再配合插件机制,会发现它潜力很大,使用也相当灵活;
在引入 Webpack 2.x 后,不同功能均以单个文件的形式进行分开,各模块之间接口也变得非常明确,但还有待改进。
参考 Vue 项目结构
加入打包机制后,JavaScript 文件已经解耦的不错了,但是模板还都放在了首页中,样式也都放在了一个文件中,依赖关系混乱,不方便管理。改良它的一个好方法是参考其他优秀项目,比如 Vue 就有一套很好的项目组织结构,直接借鉴就行了。新的项目主要变化如下:
真正将组件分离出来。组件内容采用 Mustache 模板,样式采用 Less 语法,JS 部分则控制组件的渲染逻辑,尽量不关联业务逻辑,三者合一就相当于一个 .vue 文件了。通过修改 Webpack 配置或使用合适的插件,该方式可以同样支持其他模板和 CSS 语法,比如 ejs 或者 SCSS;
选择支持多页面入口,而没有采用路由功能。这样可以简化 SPA 中复杂的 URL 结构,同时打包结果也不用附带路由逻辑。这样还有一个好处是后期引入简单版的 SSR 也会很方便,路由就是 nginx 的事儿了;
对于该部分项目结构的详细描述直接看下文。结构图如下: