Total Pageviews

Monday, 19 May 2014

前端开发工具集:Generator-clam

对原理不关心,直接跳到使用指南

什么是 Generator-Clam

阿里航旅前端基础设施:

Web 前端项目开发需要依赖一揽子辅助工具和约定,来保持代码的快速流转和可维护性。
一套完整的前端技术基础设施包括六个方面:
  1. 前端框架(Kissy)
  2. 代码骨架(Clam)
  3. 构建工具(Grunt)
  4. 服务和环境(Grunt-flexcombo)
  5. 模块规范和公共代码仓库(PI)
  6. 项目的开源开发模式(Git)
Clam 就是让整个过程简化的脚手架工具,让前端工程师快速进入项目角色,不必担心项目交接成本、和脏代码影响心情。宏观上看,Clam 从整体上降低了项目流转成本,前端同学的相互补位更加灵活,让前端同学将精力放在核心代码和产品体验提升上。

为什么开源这么重要!!!

Web 项目大致分三类:
  1. 结构精密,业务架构性强的 Web 应用,又称 OPOA(One Page One App),比如旅行计划
  2. 结构松散,需求变更频繁的页面级开发,比如旅行首页淘宝首页
  3. 组件代码,代码发布出来就是要被别的页面引用的。
对于第一类项目,技术架构伴随业务架构生长,技术设计天然有发挥的空间,项目维护期需求增减也有规律可循。相比之下,第二类项目的组织难度要高很多,类似旅行首页天猫首页淘宝首页, 本质上是一个信息聚合页,比如淘宝首页便民中心区域的充值模块和机票模块是由不同团队提供的代码,和淘首页主干代码的合并和联调无法做到脱产,即,机票模 块代码更新,必须拉上淘首页开发同学一同跟进。再进一步讲,天猫首页、淘首页、旅行首页本质上不是“开源”的。维护这类页面的同学必然成为瓶颈,任何修改 必须主干代码维护者在场操作。
这类项目在航旅事业部、整个阿里占绝大部分,有两个直接的影响:
  1. 接手别人的项目成本高,前端开发资源无法形成有效互补,表现在“前端资源永远不够用”
  2. 跨业务的合作项目成本高,2013年酒店类目接入天猫和淘主站,不一致的编程约定、环境、代码结构,导致一半的项目时间浪费在这上。最后的表现还是“前端资源不够用”。
项目闭源同样影响组件代码的传播,如果没有有效的开源策略,组件开发者无法主动跟踪调试每个引用它的代码的页面,也阻碍优秀代码传播的更远、生长的更加健壮。
对于第二类项目,让我们再多了解一些被人熟知的真相:

1. Web 前端技术的特点

表现层代码隶属Web技术栈最顶层,受需求变化影响最直接,大量改版项目也导致页面代码频繁重写。因此一个由零散的信息碎片拼装而成的页面,天然不成架构。这会导致代码无法顺利流转、项目交接困难。
因此,项目和关键功能碎片化之后更要开源(企业内部开源),让你的代码可以被“种”在大量的页面、角落中,并可以自己生长(被fork二次开发)。让代码单元以多种形态(服务、组件、模块)运行在线上。

2. 互联网开发周期特点


以下结论摘自《编写可维护的JavaScript》
  1. 软件声明周期中80%的成本消耗在了维护上
  2. 几乎所有的软件维护者都不是它的最初作者
  3. 编码规范提高可读性,让工程师快速理解新的代码
  4. 源码应当作为产品来一部分来发布,是完整可打包的。
很少有人会打开代码编辑器从零开始写代码,多数时间你面对的是写好的代码... 正如我在Yahoo常说的:“当你开始工作时,不是在给你自己写代码,而是为后来人写代码”
——N.C.Zakas
不幸的是,我们在日常项目中痛苦的适应着这种局面,痛苦的对已有的线上脏代码打补丁,而这些线上代码几乎全无文档、无规范、无交接人。因此开源会:
  1. 将重要的、通用的功能用版本管理工具收纳起来
  2. 源码的结构、构建方式、约定,作为技术文档的一部分沉淀下来
  3. 更方便、透明的代码合并,一定程度用代码描述功能需求
  4. debug 源码代替直接 debug 线上代码

3. Web项目特点

互联网产品不是一次性完工,是经过很多次迭代、不同项目的代码堆积而成的。因此产品的技术架构本身要支持模块拼装,模块以项目为单位,可装可卸。最大的难题在于:
  1. 集成测试
  2. 依赖真实环境的开发(本地测试)
  3. 真实环境中的debug某个项目(单元)

这种样子的产品要求代码的可抽离性要高,这时每个小项目(单元)的Demo就显得意义不大了(本地Demo仅用作开发过程的管理、归档、历史版本迭代用),因为线上环境是最好的 Demo。

项目(Assets)前端代码发布流程

基本流程


前端工程师拥有大部分的HTML的发布权限和所有的JS/CSS/IMG的发布权限。HTML片段参与组成线上页面。基于HTML和CSS/JS分离的原则,前端工程师会将更多的时间用户修补JS/CSS而非HTML。因此JS和CSS的重要性大于HTML。

CDN 的 Combo 服务

Combo是淘系CDN提供的基础服务,动态输出CDN里的颗粒文件。比如:
http://g.tbcdn.cn/path/??a.js,b.js
将输出a.jsb.js两个文件

Generator-Clam 遵循的原则

Generator-Clam我们简称ClamClam志在为前端工程师提供更简单和一致的项目开发体验。Clam 遵循这些易于理解的原则:
  1. Server 服务随源码携带
  2. Assets 的源码和目标代码同时发布,比如a-min.js一定存在其源码a.js
  3. CDN 代码非覆盖式发布,发布基于项目版本x.y.z,而非文件版本,比如线上代码为0.1.2/a.js而非a-0.1.2.js
  4. 项目均包含描述文件Gruntfile.js,用于组织 Grunt 构建和定义本地 Server 服务
  5. 共享代码通过bower install {moduleName}的形式安装和更新
  6. 项目结构代码骨架统一:Pages+Mods+widgets
这些原则将直接指导我们应对快速的需求变更和代码流转,同时能遵循一致的规范和自然形成沉淀。

Generator-Clam 模块化开发

模块定义

Generator-Clam 非常激进的提供了从底层架构层面对于前端模块化开发的支持。先来了解一下Generator-Clam对于前端项目的理解。
传统上前端工作里“项目”的概念远没有后台软件开发领域里那么清晰,这主要是由于以往的客户端页面较简单,不需要太多“项目”层面的支持。随着现在客户端功能的越趋复杂,有必要系统的来引入一套针对前端业务特点构建的架构模式。
Clam 里对于项目的定义是一个完整的前端应用,或其中一个相对独立的某一个业务场景。如:一个单页富应用,可以作为一个 Clam 项目;或者业务耦合度较高,用户使用路径很近的一组页面,也可以作为一个 Clam 项目。
对于一个 Clam 项目的具体页面,除了页面自身的html模板,样式和脚本外,它还可以引用一组模块,其中每个模块都有其独立的html模板,样式和脚本文件。同时页面上还可以存在一些通用的组件,

  1. 一个Clam项目由若干页面Page、模块Module、和组件Widget构成;
  2. 其中Page和Module可以由html,css,js等组成;Widget对外提供的代码通常只有JS/CSS;
  3. Page可以在其html里包含Module的html文件来使用模块;
  4. Module通常是与业务关系较密切的独立功能块,比如一个订票网站的常用联系人模块;
  5. Module应做到尽可能独立,最理想的情况是完全独立于页面;
  6. Widget是与具体业务耦合较松,复用性更强的功能块,如日历组件;
  7. Page的html部分通常是用来包含模块html或为组件提供容器的。
比如一个典型的Clam项目结构如下:
.hello_pro
    ├── build/
    ├── Gruntfile.js
    └── src
        ├── config.js
        ├── mods
        │   └── def
        │       ├── demo.html
        │       ├── index.css
        │       ├── index.html
        │       └── index.js
        └── pages
            └── abc
                ├── index.css
                ├── index.html
                └── index.js
其中src/pages/abc/index.html内容:
<!DOCTYPE HTML>
<html>
<head>
    <meta charset="utf-8" />
    <title> H5Test - Abc demo </title>
    <script src="http://g.tbcdn.cn/kissy/k/1.4.0/seed-min.js"></script>
    <link rel="stylesheet" href="index.css"/>
    <script src="../??config.js"></script>
</head>
<body>
    <!--#include virtual="../../mods/def/index.html" -->
    <script>
        KISSY.use('h5-test/abc/index', function(S, Abc) {
            new Abc();
        }); 
    </script>
</body>
</html>
因为页面中需给JS传参,通过new Abc({JSONData})将数据传递给Abc模块,不要通过全局变量给JS传参。
src/mods/def/index.html内容为:
<link href="../mods/def/index.css" rel="stylesheet" />
模块正文
<script>
    KISSY.use('h5-test/def/index', function(S, Def) {
        new Def();
    }); 
</script>
启动本地demo服务grunt demo,浏览器绑定HTTP服务127.0.0.1:8080,访问demo/pages/abc/index.html
<!DOCTYPE HTML>
<html>
<head>
    <meta charset="utf-8" />
    <title> H5Test - Abc demo </title>
    <script src="http://g.tbcdn.cn/kissy/k/1.4.0/seed-min.js"></script>
    <link rel="stylesheet" href="index.css"/>
    <script src="../??config.js"></script>
</head>
<body>
    <link href="../mods/def/index.css" rel="stylesheet" />
    模块正文
    <script>
        KISSY.use('h5-test/def/index', function(S, Def) {
            new Def();
        }); 
    </script>
    <script>
        KISSY.use('h5-test/abc/index', function(S, Abc) {
            new Abc();
        }); 
    </script>
</body>
</html>
Clam 推荐将模块所有的外部依赖都在模块html文件内引入,这样可以做到模块的完全独立。同样的,你毋需担心引入诸如jQuery,CSS Reset等文件带来的麻烦。Clam会在项目build阶段做排重处理,确保你最终打包后的页面一个资源只引入一次。

模块在线上环境中的拼装和组合

除了业务架构本身就很强的Web富应用开发外,前端开发最大的挑战在于“门户”型页面的架构、和组队开发。
实际上,定义清楚模块是万里长征的第一步,任何项目初始状态的模块划分始终是最清晰。在互联网开发实际场景中,页面信息碎片化过程中渐渐失去了结 构,如果模块代码没有真正独立(成单独仓库)、而是长在项目的某个目录中,松散的页面信息结构最终导致项目代码失控。而每个模块独占一个项目,强行将代码 独立,不仅要求开发者具备更强的代码抽象能力,更从根本上避免“弱约定”的弊病("弱约定"很容易被紧急多变的需求击碎)。
这是我们无法在项目中完全高保真还原Demo的根本原因:互联网信息碎片化总会导致代码的熵变大。因此,往往开发者拿到一段“局部”代码,去 debug 它在另外一个项目中嵌入后的页面。因此,HTML和CSS/JS的分离尤为重要。我们不要纠结 Demo 无法高保真的还原。

无论如何,前端项目中的Demo都无法做到高保真还原

项目的构建

Clam 项目构建基于Grunt,构建任务作为插件配置到Gruntfile.js中。这些任务包括:
  • Grunt-combohtml
    • 合并SSI的html,抽取页面中分散的JS和CSS,合并好后输出;
    • 解析原html中的juicer模板,生成vm、tms或者php。
  • Grunt-kmc,JS库代码依赖KISSY,使用KISSY打包工具来解析源码中JS的依赖关系,生成map.js或者静态合并。
  • Grunt-uglify、Grunt-cssmin、Grunt-replace、Grunt-less,Grunt-css_combo
  • Grunt-mytps,上传本地图片到tps服务器,依赖python(默认不开启,开发者自行配置)
  • Grunt-toascii,把文件中的非英文字符转码成对应的ascii码(默认不开启,开发者自行配置)
  • Grunt-cssimage,对css文件中的图片进行压缩替换,支持远程图片抓取(默认不开启,开发者自行配置)
  • Grunt-tms,将Juicer模板文件转换为TMS格式。

基于 NodeJS 的本地 Server

本地 Server 跟随代码安装和启动,以 Grunt 插件形式提供。

本地服务原理


Grunt-flexcombo 是一款基于NodeJS的轻服务,便携且易于配置。用于淘系环境中的Demo服务和自定义虚机等场景。
Grunt-flexcomboflex-combo 的grunt插件版本,Grunt-Flexcombo 原理介绍
本地服务的启动
基于Clam生成的项目目录运行grunt debug,将会启用flexcombo服务,会在本地启动两个 Server 服务,两个服务分属两个端口proxyport(反向代理服务)和port(Flexcombo 模拟 CDN 环境)
  • 反向代理服务:用于启用本地虚机
  • flexcombo服务:映射 CDN Combo 请求中的某个文件到本地:http://cdn/??a.js,b.js

启动服务后,绑定设备,以下两种方法取其中一个,推荐第二种:
  1. 将cdn配向本机127.0.0.1 g.tbcdn.cn a.tbcdn.cn
  2. 将浏览器或者设备HTTP代理配置到本机的反向代理服务的端口
比如在手机终端设置代理方法:

然后可以直接通过g.tbcdn.cn域名来预览本地文件
http://g.tbcdn.cn/{group}/{project}
推荐使用proxyHosts中的虚机名来访问本地文件列表
http://demo.com/
flexcombo服务可以配合watch和你Gruntfile.js中的构建命令,完成代码调试,比如在Gallery中的代码调试,更多内容参照:grunt-flexcombo 配置方法

Flexcombo 服务启动后如何映射 Combo URL 里的文件

我们通过一个案例来说明原理:
1,将示例代码检出
将仓库检出到任意目录:
git clone https://github.com/jayli/grunt-flexcombo.git
2,补全node_modules
进入到刚检出的目录中:
cd grunt-flexcombo/test
npm install
3,启动本地服务
test目录中执行
sudo grunt demo
这时服务启动。
4,打开浏览器,绑定HTTP代理到本机

5,浏览器中访问这个URL
http://g.tbcdn.cn/??test/index.js,kissy/k/1.4.0/seed.js
可以看到本地文件test/index.js和线上文件kissy/k/1.4.0/seed.js一并输出。这时,在命令行窗口可以看到请求log.

from https://github.com/jayli/generator-clam