近期对公司前端项目的文件组织结构和编译打包方式做了一些调整,记录如下。
1. 文件结构总览
1.1 开发环境说明
随着项目逐渐庞大,考虑到代码的组织维护以及项目架构的可扩展性,采用前后端分离的部署方案。前端项目作为独立的项目维护,由后台提供Restful API进行交互。
前端项目采用了AngularJS框架,Jade模板编写网页,基于Nodejs环境使用bower进行依赖管理,使用gulp编译和打包。
1.2 项目文件总体结构
app:使用gulp编译打包生成的路径;
master:jade、js、less/sass 等源文件;
server:前端动态加载内容,如左侧菜单栏;
vendor:依赖组件。
2. 存在问题
2.1 首次访问加载缓慢
当前使用的编译方案中,将所有的JS脚本concat到app.js中,所有的第三方组件concat到base.js中,CSS样式也是同样整合到一个app.css文件中输出。虽然AngularJS的特点能够在Single Page应用中完全发挥,但同时导致用户首次访问页面时,需要下载所有的js和css,在网络状况不佳或服务器性能下降时体验相当差。如果是作为后台管理的Web应用,也许尚能容忍;作为购物网站的话,访问量最高的首页加载慢,这是不能忍受的。
2.2 文件编排混乱
在以往的文件编排中,只是将网页文件、脚本文件和样式文件分别存放,其内部之间没有分级或者分类,不同模块的文件混杂在一起。这导致在发现Bug后需要定位到代码时,查找相应的代码文件变得困难。有的时候Bug代码比较新,维护人员能够清楚地定位到代码位置,但是对于比较久远的Bug,就不那么好找了。
3. 新的方案
针对上述两个问题,对前端项目结构进行调整。
3.1 网页文件
鉴于之前的项目文件结构将网页文件Jade集中放在一个文件夹中,不易于查找,导致开发和维护困难度增加,因此将jade文件分模块存放,每个模块独立文件夹。以商品模块“item”为例,在“master/jade/views”目录下的“item”文件夹中,存放该模块的网页文件,组织关系自行维护。
3.2 JS脚本
3.2.1 模块化
JS文件采用横向和纵向两种划分方式结合。
对于公共的基础模块,如loadingbar、ui-router等,不会与其他模块产生依赖,模块内部实现完整的功能,因此按照每个模块独立文件夹的形式存放。在图中红线以下部分,以ui-route组件为例,其使用的JS脚本均存放在routes文件夹内。
开发人员的JS脚本存放在custom文件夹内,顶层沿用controllers、directives、services的划分,下层进一步按照模块划分,如item模块的controller独立存放于item文件夹下。 directive和service尽量做到抽象通用,鉴于文件数量少且出错率低,暂不做进一步分类。
4. 输出目录结构
编译生成的文件与源代码master目录下的结构相同。
以前的编译过程中,将所有的JS脚本打包成一个app.js,导致首次加载缓慢,为了解决该问题,JS文件还是按照模块编译,将公用的脚本和库编译成app.js和base.js,各功能模块的JS脚本各自编译,存放在本模块的文件夹内。模块内的脚本编译成一个JS文件。如item模块,包含的3个JS脚本编译成一个item.js。
这样做出于两个原因:
1、只将必要的公用依赖编译到app.js和base.js中,减小这两个文件的体积;
2、将模块内部的脚本编译成一个JS文件,访问页面时动态加载脚本,同时保证只下载一次所需的功能脚本,减少网络延迟。
5. 示例
将文件分类编译只是完成了第一步,还需要完成在网页加载时才下载相应的脚本和样式文件。“ocLazyload”是保证按需加载的核心,组件注入后可在配置route.config.js时使用。
基本思路是将脚本和样式文件当做组件注入到网页绑定的Controller中。
以增加 item模块为例,该模块实现商品管理功能。
1 、首先创建 item.jade页面,引用itemController ;
2 、创建item.controller.js文件,定义 itemController;
3 、在lazyload.constants.js中指定 itemController对应的脚本文件;
4 、在route.config.js中,定义新的菜单注入 itemController;
5 、至此,新建模块加入到项目中。
6. 结束
此方案实测可行,首页加载公共组件和脚本,功能页面加载相应的功能脚本和样式。即使是JS和CSS脚本超大或网络环境恶劣的情况下,也不会导致网页显示混乱,页面是在依赖的资源全部下载后进行跳转的。在浏览器开启缓存时,刷新也不会重新下载脚本和样式文件。