记一次前端工程构建

需求背景

我所在的项目组主要负责公司的A产品A1模块的界面开发。通过上半年紧锣密鼓、加班加点地开发以后,终于在7月份在国内的L局点成功上线。当时那个激动啊,苦逼的生活终于过去了,你们都跟我high起来!但是到了下半年,因为公司市场人员的给力表现,又在海外开拓了D局点和T局点,真是喜(yu)大(ku)普(wu)奔(lei)啊!javascript

因为L局点的需求尚未明确,因此L局点的事情先按住不表,先说说D局点的需求。
其实,客户的实际要求也很少,对于界面来讲,无非是总体风格要与客户现有的产品保持一致。因此最终预计的工做量就是换换主题色而已,一切都是如此的easy,你们接着high!css

简单修改以后就给一线发了一个联调版本。html

在一线将版本安装完成以后,与客户联调过程当中(咱们的产品是要嵌入到客户系统中,做为客户系统的一个模块工做),客户对界面提了不少意见(该局点的客户都比较严(jiao)谨(zhen))。前端

目前来看,页面的国际化文件要准备两份(国内的L局点中文+英文,海外的D局点英文+德文),样式要准备两套(L局点一套,D局点一套)。若是客户再对页面结构有必定的要求,好比要变动页面的布局、元素等,那么html也要准备两份了。
最终分析来看,最好是能再作一套页面出来。java

按照公司如今的策略,代码分支只能有一个,作出来的版本不能有任何对于局点的定制,也就是说这个版本要在全部局点都能安装使用。webpack

如今问题来了,如何在一套代码中支持两套页面呢?git

方案制定

咱们知道,一旦在页面中引用一个静态资源,那么这个资源的路径就定死了,不能改动。好比,咱们的页面上要使用到公司的logo,这个logo图片的路径一旦肯定,就不能再修改了,可是D局点要求将logo换成客户的logo。angularjs

既然公司不容许在作版本的时候进行任何的定制,那么怎么解决这个问题,想了半天,对现有代码目录作一下调整,最终肯定了以下方案:github

webapp  |
        | public |
                 |  base  |
                          | js
                          | theme |
                                  | default   |
                                              | css
                                              | image 
                                              | font  
                                  | skin2     |
                 |  uiwidget
                 
                 | custom | js
                          | theme |
                                  | default   |
                                              | css
                                              | image 
                                              | font
                                  | skin2     |
                 | pageL  |  js
                          | theme |
                                  | default   |
                                              | css
                                              | image 
                                              | font
                                  | skin2     |
                 | pageD  |  js
                          | theme |
                                  | default   |
                                              | css
                                              | image 
                                              | font
                                  | skin2     |
  • public目录 存放全部对外开放的静态资源文件,public之外的其余目录外界不可访问。
  • base目录 主要放置与后台的数据接口模块,也就是公共Model层。另外还放置公共的、框架性的样式文件以及一些工具类。
  • uiwidget目录 放置组件库
  • custom目录 页面最终的工做目录
  • pageL目录 L局点要求的页面
  • pageD目录 D局点要求的页面

剩余要作的:
一是在打包的时候将pageL的内容拷贝到custom目录中,打一个L局点的ui包,再将pageD的内容拷贝到custom目录中,打一个D局点的UI包。最终将这两个ui包打入一个war包中。
二是新增一个脚本,在安装完成以后,根据局点决定使用哪一个ui包,删除无用的ui包。web

先说明一下为什么要这样划分目录。
除了页面变更之外,D局点的另外一个重要的需求就是要对公司产品的功能进行一些裁剪,把不须要的功能去掉。所以,不须要的接口也要通通屏蔽掉。因此,此处将全部页面目录都规划到public下,方便后面接口管理。
其余页面的划分,主要仍是基于以下图方案的考虑:

对于页面来讲,后台的接口是不会变得,因此将与后台的交互模块独立出来,放进base/js目录中,做为公共的Model层并对页面暴露公共的数据API。其余还有一些公共的css、图片等都放到base/theme目录下,做为基础样式。另外,一些公共的utils工具也能够放到base/js目录下。
PageL/PageD 就是根据具体页面具体开发了。咱们组使用AngularJS进行开发,使用RequireJS进行JS模块化管理,具体的开发内容不在本篇范围内,按住不表。

下面就要说说具体的构建过程了。

工程构建

前端的朋友都知道,出于对页面性能以及用户体验的考虑,一般要对进行静态资源进行压缩、合并、指定缓存策略等,具体措施网上有不少的讨论,google一下能够翻不少页,这里也按住不表。
咱们组采起的措施主要是将css、js文件压缩合并,并对每一个文件进行签名并配置永久缓存。图片的话,主要是将图标类的进行合并,而后经过css sprite进行分割。

在构建工程时,以前一直使用的是公司内别人基于gulp开发的的maven插件进行构建,最后由maven负责打包。每次须要修改什么东西的时候都要求助于工具开发者,实在是有一种寄人篱下的感受。并且功能实在有限,集成到maven中又缺少足够的灵活性,已经适应不了蓬勃的需求变化啦。
如今前端构建工具这么多,何不本身搞呢,后面也能自由修改和管理。因而就google了一翻。

如今构建工具备不少,主流的主要有gulpgruntwebpack等,因而我选择了grunt。没有什么别的缘由,就是由于grunt的logo比较酷炫。看下图:

前面说了,咱们对grunt的需求主要就是js、css压缩合并,静态资源文件签名。另外还有一点须要注意下,就是文件签名后,文件名前面会多一串hash值,因此全部的原来引用这些资源的地方都要修改成签名后的文件名。RequireJS的路径配置也要修改。

看了一下grunt的插件列表,基本知足要求,惟独没有一个靠谱的替换文件名的插件(也有多是我没有找到)。怎么办,本身动手丰衣足食。

综合考虑了一下,决定采用以下grunt工做流。

clean:build copy  cssmin  filerev replaceRefrence mkRequirePath clean:package uglify

其中clean、copy、cssmin、filerev和uglify都有现成的grunt插件,并且除了filerev之外都是grunt团队出品的插件,应该比较可靠。其余的两个,replaceRefrence和mkRequirePath,就是我本身开发的了。

replaceRefrence的想法很简单,就是经过filerev插件生成的签名先后的文件映射grunt.filerev.summary和正则表达式来替换目标目录中的每一个文件中的引用。正则表达式也很简单,就是匹配某一路径开头,以具体的文件后缀(.js.html.css.png等)结尾的文件路径字符串。正则表达式的简要形式以下:

var regExp = /(\/start\/)(\.js|\.html|\.css|\.png|\.jpg|\.gif)/g

其中start就是路径开头。
有了正则表达式,而后经过String.replace(regExp, function(){})将匹配到的字符串替换掉。具体样例以下:

var fileData = fs.readFileSync(filePath, {encoding:"utf8"});
var newFileData = fileData.replce(regExp, function (str) {
    return fileMap[str] ? fileMap[str] : str;
});

注意: 这里的fileMap是grunt.filerev.summary通过必定的处理以后的结果。

经过以上方法就能够将html、css、js文件中引用到的其余文件替换成签名后的文件了。

完成上面的工做以后,就只剩一个任务了,就是如何让RequireJS认识这些签名后的文件?个人想法是,经过

requirejs.config({
    paths: {
        module1: "module1/js",
        module2: "module2/js"
    }
});

的方式,将签名后的文件路径配置进来。
思路与replaceRefrence同样,仍是经过grunt.filerev.summary来作。具体的思路以下:
如上面的代码所示,咱们通常都会在paths中设置某个路径的快捷方式,即

require("module1/a.js") 等同于 module1/js/a.js

因此,咱们只须要将这些原始的路径配置拿到以后,根据这些原始配置与grunt.filerev.summary中的具体路径进行一次匹配处理,就能够将grunt.filerev.summary输出做为RequireJS的路径配置对象了。

具体的方法以下:
原来grunt.filerev.summary中一条记录多是

"module1/js/a.js": "module1/js/1234567.a.js"

如今咱们知道,代码中经过

require("module1/a")

加载的js模块,最终要别识别成

module1/js/1234567.a.js

那咱们只须要生成这样一条记录:

"module1/a": "module1/js/1234567.a.js"

具体作法就是用原始路径配置中的kv对中的v匹配grunt.filerev.summary每条记录中的key,若是key以v开头,则将key作以下处理:

key = k + key.substring(v.length);

这样就生成了正确的路径配置了。
最后一步要作的就是将生成的路径配置保存为一个manifest.js文件,插入到RequireJS的下方,如:

<script type="text/javascript" src="path1/require.js"></script>
<script type="text/javascript" src="path2/manifest-2015-11-15.js"></script>

到这里,整个构建过程就基本OK了。

相关文章
相关标签/搜索