首页 > 代码库 > 前端构建

前端构建

后端有构建工具,比如Java的ant、maven,前端也有构建工具,比如现在非常流行的grunt。
前端构建,是在项目部署上线前,将js css html 图片合并压缩的过程,使用模块化开发时,又多了个规范化过程。

Seajs是非常好用,好用的代价是SPM构建打包起来很麻烦,文档不全,没好的实例。

demo地址 https://github.com/sprying/ys7
一、项目需求
我们要做成什么?
充分利用缓存,并减少请求数。

方案一:提炼出相同功能的组件,并存独立文件(dialog.js),随着业务的复杂化,组件越来越多,导致一次url访问请求数很多。特别是用户第一次进入页面时,要加载所有的资源,要等几十个资源请求load后,才发ajax请求。虽然组件配合缓存可能会减少页面的流量,但与繁多的请求相比,这种性能上提升几乎为0。(现在使用的)

方案二:一个页面一个js,这是另外个极端,不能充分利用浏览器缓存。

方案三:
均衡方案,开发时还是每个的功能,一个Js。但是上线时,合并一些组件到页面Js中,将请求数降低到理想值,同时还引入些公用Js、以利用缓存。

于是要考虑将组件划分成两类:要合并的Js,不要合并的Js。不要合并的Js,一种是通过script标签来的,每个页面都要用;另一种是通过Seajs动态加载。具体划分如下:

1)按照使用调用情况分:必加载组件、绝大地方都使用的组件、只有几个页面使用到的js、一个页面对应的js
2)按照业务分:基础、标准组件(比如弹出窗,backbone跟业务无关的)、业务组件、页面Js。

综合考虑,我们将结构分成如下,并采用业务命名区分

基础模块:所有的页面都需要,弄成一个请求,script标签引入。采用合并或者combo请求,比如jquery json2
标准模块:外面引入的插件,自己写的插件,比如backbone,dialog
业务模块:少数页面用到的业务Js,比如wifi
页面模块:页面对应的Js,比如add2.jsp对应的是add.js的入口。

当请求数还是偏多,协调标准模块和业务模块。

二、打包几个关键流程
Seajs是采用Cmd规范,很简约,跟nodeJs采用的Amd很像。打包的第一步是从简约变复杂。

打包的第一步

Seajs一个文件一个模块,我们最常见的写法define(function(require,exports){})

通过cmd规范的transport插件,变成define(id,[dependences],function(require,exports,module){})

打包的第二步,合并依赖模块,有三种配置,一种是self,不合并;一种relactive(默认),合并相对路径的依赖;一种all,合并所有依赖

打包的第三步,合并好后压缩。

这只是构建一个前端项目最原始的几个步骤。在项目里面,要考虑css、图片、组件层次问题,还要详细配置。

三、其它要考虑的
1)版本控制,版本控制当然为了充分利用浏览器缓存,可将js css 设置成一年或者更久。

a、合并后Js文件名+MD5,在seaConfig.js配置别名(配置由grunt自动生成)
b、基础组件,通过1.1.0类似的来实现版本控制
c、至于seaConfig.js不设置缓存,或者直接写人html中

2)构建时可以合并Js,发起请求时也可以combo合并文件;对于页面必须的,非模块化Js,可采用combo请求,服务器端可以配置combo插件。

3)如何简单切换开发模式和线上模式
在Jsp中配置模式,Jsp->html后

三、实际打包
1)其实到现在,最关键的是?打包工具怎么知道哪些require要合并,哪些不用?想到有两种方法,
一种是通过配置文件来管理,一种是通过制定开发规范。我们有这种需求,难道其它使用Seajs的人没有?
果不其然,grunt-cmd-concat出现了,专门解决这个问题。它是通过规范,插件里面对不同规范作不同的处理。
require(‘‘)相对路径,会将依赖合并,require(‘dialog/1.2.0/dialog‘),类似这种的不会合并。

2)SPM2默认会将依赖的css文件合并到js,最终插到页面style标签中。这时css里面涉及url的路径要注意,之前用的相对路径会有问题,要用绝对路径。阿里涉及图片的直接用一个静态服务器用绝对路径,当然没这方面顾虑。
可是项目主管说,每个组件必须有自己的imgs目录。那只能使css不合并到js中,要做如下工作:
a、页面模块和业务组件的css要合并成一个,
b、图片都放到一个文件夹下
合并前
-------------------------------
--app/
--------shareDetail/
---------------------- shareDetail.js
---------------------- share.css
----------------------imgs/
--------------------------------------unJoin.png
--components/
--------wifi/
---------------------- wifi.js
---------------------- wifi.css
----------------------imgs/
--------------------------------------wifi_connect.png
shareDetail.js里面引入require(‘./share.css‘),
wifi.js里面引入require(‘./wifi.css‘),

合并后
-------------------------------
--app/
--------shareDetail/
---------------------- shareDetail.js
---------------------- share.css
---------------------- imgs/
--------------------------------------unJoin.png
--------------------------------------wifi_connect.png
shareDetail.js里面引入require(‘./share.css‘),
wifi.js里面引入require(‘./share.css‘)

Css、imgs合并,要分析出require相对路径的业务组件名,比如wifi,然后将wifi/imgs合并到相应的shareDetail/imgs;
这里我写了个grunt-cmd-concatFiles和grunt-cmd-concatCss

3)流程
规范化(transport)Js,合并(cmd-concat)Js依赖,压缩(uglify)Js,重命名(md5)Js,生成配置文件
依据依赖关系合并(concatCss)Css,压缩(cssmin)Css
依据依赖关系合并(concatFiles)图像文件夹,压缩(imagemin)图像



4)关键配置
a)
        ......
        transport : {
            options : {
                ......
                paths:[‘src‘]
            },
        ........

paths:[‘src‘] 运行transport时,要保证require的文件能找到,相对路径、绝对路径很明了
类似require(‘modules/dialog/0.0.1/dialog‘),实际路径是paths + ,这里的paths值跟seaConfig.js里的base相同。

b)
        ......
        transport : { 
            options : {
                ......
                paths:[‘src‘] 
            },
            app : { 
                options : {
                    idleading : ‘apps/‘
                },
                ........

idleading : ‘apps/‘ 打包后的文件放在新位置后,define的路径要变化,插件的idleading参数可配置define的路径。如果你transport后文件没有执行,很可能idleading设置有误。

c)
开发环境与生产环境。开发环境下跑的所有Js和css甚至图片都是未处理的;生产环境下都是合并压缩后的Js、css。
seajs里面的require可能有三种类型,绝对路径、相对路径、name。
在打包情况下的结果:
绝对路径在打包前后的都不会变化,目前没这种场景,所以没有使用绝对路径场景。
相对路径,最终路径 = 外层Js的路径 + 相对路径,最外层Js就是seajs.use(‘../../static/shareDetail/shareDetail.js‘)
类似require(‘modules/dialog/0.0.1/dialog‘)路径,最终路径 = seajs.data.base + name; name路径在打包前后不能变化,打包前后如有变动,就要变seajs.data.base



四、具体命令
安装nodeJs环境 

安装grunt构建工具
npm install grunt
npm install -g grunt-cli

安装SPM及其打包插件build
npm install spm -g
spm plugin install build
npm install spm-build -g

期间按照它的Demo,发现有许多问题

构建要做的事

如果突然发现要用什么grunt插件,
npm install grunt --save-dev

五、调试
使用时,有疑问,网上文档缺乏,周围没人知道这块,download的demo可能因插件老旧而不能正常使用。
这时,一方面输入打包命令时 加--verbose,打印出详细的过程,
另一方面迫切希望能调试下源码。
一开始,我也是人肉log(grunt.log.writeln)调试。后来发现了也可以使用node调试。

启动调试过程
先安装插件
npm install -g node-inspector

打开命令行,启动调试端口
node-inspector


打开另一个命令行窗口
node --debug-brk $(which grunt) task
node --debug-brk C:\Users\fangyingchun\AppData\Roaming\npm\node_modules\grunt-cli\bin\grunt concat:app
node --debug-brk C:\Users\fangyingchun\AppData\Roaming\npm\node_modules\spm-build\bin\spm-build 

最后打开chrome,输入网址 
http://127.0.0.1:8080/debug?port=5858,进行调试

六、相关网址
https://github.com/seajs/seajs
http://nodejs.org/api/
http://gruntjs.com/
http://docs.spmjs.org/doc/
https://github.com/spmjs

freemark
spm depoy
前端服务部署与文件发布
ast


  在js里面不能出现a-- 而要用a=a-1来代替(原因,这个打包工具将它看成注释)。至于自增和自减的逻辑,自己去学习吧
How can I run a grunt task from within a grunt task?
http://stackoverflow.com/questions/15284556/how-can-i-run-a-grunt-task-from-within-a-grunt-task

开发规则
页面组件中的css不能调用其它组件的图片
引入依赖的基础组件使用别名
模块化开发,引入依赖模块时,不要使用绝对路径
在src目录中的Js都要是未压缩的,压缩交给构建工具统一处理