前端工程化探索-项目中总结

posted @ Apr 21, 2016 • 张云龙 • 前端笔记

摘要:「不知道你的团队如何定义前端开发,据我所知,时至今日仍然有很多团队会把前端开发归类为产品或者设计岗位,虽然身份之争多少有些无谓,但我对这种偏见还是心存芥蒂,酝酿了许久,决定写一个系列的文章,试着从工程的角度系统的介绍一下我对前端,尤其是Web前端的理解。这篇文章,是转载于张云龙工程师对工程化问题的探索」

什么是前端工程化

不知道你的团队如何定义前端开发,据我所知,时至今日仍然有很多团队会把前端开发归类为产品或者设计岗位,虽然身份之争多少有些无谓,但我对这种偏见还是心存芥蒂,酝酿了许久,决定写一个系列的文章,试着从工程的角度系统的介绍一下我对前端,尤其是Web前端的理解。

只要我们还把自己的工作看作为一项软件开发活动,那么我相信读过下面的内容你也一定会有所共鸣。

前端,是一种GUI软件

现如今前端可谓包罗万象,产品形态五花八门,涉猎极广,什么高大上的基础库/框架,拽炫酷的宣传页面,还有屌炸天的小游戏……不过这些一两个文件的小项目并非是前端技术的主要应用场景,更具商业价值的则是复杂的Web应用,它们功能完善,界面繁多,为用户提供了完整的产品体验,可能是新闻聚合网站,可能是在线购物平台,可能是社交网络,可能是金融信贷应用,可能是音乐互动社区,也可能是视频上传与分享平台…… > 从本质上讲,所有Web应用都是一种运行在网页浏览器中的软件,这些软件的图形用户界面(Graphical User Interface,简称GUI)即为前端。

如此复杂的Web应用,动辄几十上百人共同开发维护,其前端界面通常也颇具规模,工程量不亚于一般的传统GUI软件:

尽管Web应用的复杂程度与日俱增,用户对其前端界面也提出了更高的要求,但时至今日仍然没有多少前端开发者会从软件工程的角度去思考前端开发,来助力团队的开发效率,更有甚者还对前端保留着”如玩具般简单“的刻板印象,日复一日,刀耕火种。

历史悠久的前端开发,始终像是放养的野孩子,原始如斯,不免让人慨叹!

前端工程的三个阶段

现在的前端开发倒也并非一无所有,回顾一下曾经经历过或听闻过的项目,为了提升其前端开发效率和运行性能,前端团队的工程建设大致会经历三个阶段:

第一阶段:库/框架选型

前端工程建设的第一项任务就是根据项目特征进行技术选型。

基本上现在没有人完全从0开始做网站,哪怕是政府项目用个jquery都很正常吧, React/Angularjs等框架横空出世,解放了不少生产力,合理的技术选型可以为项目节省许多工程量这点毋庸置疑。

第二阶段:简单构建优化

选型之后基本上就可以开始敲码了,不过光解决开发效率还不够,必须要兼顾运行性能。前端工程进行到第二阶段会选型一种构建工具,对代码进行压缩,校验,之后再以页面为单位进行简单的资源合并。

前端开发工程化程度之低,常常出乎我的意料,我之前在百度工作时是没有多少概念的,直到离开大公司的温室,去到业界与更多的团队交流才发现,能做到这个阶段在业界来说已然超出平均水平,属于“具备较高工程化程度”的团队了,查看网上形形色色的网页源代码,能做到最基本的JS/CSS压缩的Web应用都已跨入标准互联网公司行列,不难理解为什么很多前端团队对于前端工程构建的认知还仅停留在“压缩、校验、合并”这种程度。

第三阶段:JS/CSS模块化开发

分而治之是软件工程中的重要思想,是复杂系统开发和维护的基石,这点放在前端开发中同样适用。在解决了基本开发效率运行效率问题之后,前端团队开始思考维护效率,模块化是目前前端最流行的分治手段。

很多人觉得模块化开发的工程意义是复用,我不太认可这种看法,在我看来,模块化开发的最大价值应该是分治,是分治,分治!(重说三)。

不管你将来是否要复用某段代码,你都有充分的理由将其分治为一个模块。

JS模块化方案很多,AMD/CommonJS/UMD/ES6 Module等,对应的框架和工具也一大堆,说起来很烦,大家自行百度吧;CSS模块化开发基本都是在less、sass、stylus等预处理器的import/mixin特性支持下实现的。

虽然这些技术由来已久,在如今这个“言必及React”的时代略显落伍,但想想业界的绝大多数团队的工程化落后程度,放眼望去,毫不夸张的说,能达到第三阶段的前端团队已属于高端行列,基本具备了开发维护一般规模Web应用的能力。

然而,做到这些就够了么?Naive!

如何管理你的 Javascript 代码,从命名空间过度到模块加载器

今天不聊技术的问题,咱们来聊聊在前端开发中如何管理好自己的 Javascript 代码。首先,咱们先来说说一般都有哪些管理方式?我相信 seajs、requirejs 对于前端开发者而言都不陌生,不错它们都是前端代码模块化开发的利器,显然以模块化的方式去管理我们的 Javascript 代码,是很不错的选择。

不过今天不谈模块化开发,因为上面的两个工具已经做得很好了,只要到他们的官方网站找到相应的文档资料,认真学习,不需太多时日你也能掌握模块化开发了。今天咱们要谈的是在不依赖模块管理工具的前提下,如何做好自己项目的代码管理。

首先,笔者认为一个独立的 Web 项目,应该有一个顶级的命名空间,而针对这个项目开发的所有附属代码应该尽量都放到该命名空间下。如果项目特别大,咱可以根据业务模块再分二级命名空间,三级命名空间,等等。但是何谓大项目,这个就只能开发者自己去定义了。

然后,仅仅有命名空间还不够,如果你所有的代码都码在一个 js 文件中,那将会是一个悲剧。为什么这么说呢?第一,如果我们有一个 Tip 组件,功能是给指定元素添加标签提示功能,当我们需要在多个页面中使用这个组件时,你就会发现,每个页面都会引用 N 多没用的代码。第二,所有的代码都写在一个文件中,那么你的这个 js 文件,必将是一个庞然大物,几千上万行,调试起来也是相当有难度的。所以,要管好你的 js 代码,请把独立的插件、组件、公共方法,保存到独立的 js 文件中,再用我们上面准备的顶级命名空间把它们聚集到一起来。

再然后,一套合理的编码规范会让你的代码管理事半功倍。普通变量小驼峰,类名大驼峰,常量大写,私有变量加前置下划线,能很好的提高你代码的可读性和可维护性。运算符之间添加空格,代码使用 4(或 2)个空格合理缩进,可以让你的代码整齐有序,清晰易读。代码块严格使用大括号包裹(即便只有一行语句),三元运算符合理使用小括号,会让你的代码整齐,逻辑清晰。统一的组件开发模式,可以让你的代码专业而不失优雅。还有很多很多,就不再往下罗列了,这些规范在很多规范文档中已经写得非常清楚,这里给大家推荐一个个人感觉很不错的 js 规范,感兴趣的朋友还可以找谷姐、度娘勾兑勾兑,学习更多的 js 规范,让自己更专业。当然,所谓“尽信书,则不如无书”,所以,规范仅作参考,并不是严格限定,开发者可以在规范的基础上保留一点点自己的个性,但必须保证风格统一。最后,咱一起来看一个简单的代码示例,命名空间就以 SEEJS 为例了。

第一步,咱们先把我们的命名空间给弄出来,顺便加一些基础信息:

    window.SEEJS = {
        copyright: "CopyRight © MrZheng",
        version: "1.0.0"
    };

第二步,我们来定义一个组件结构:

(function(window, undefined){
    SEEJS.plugins = SEEJS.plugins || {};
    function Tips(cfg) {...}

    Tips.prototype = {
        constructor: Tips,
        init: function() {...}
    };

    SEEJS.plugins.Tips = SEEJS.plugins.Tips || Tips;
})(window);

只为举例,咱就不再多写了。最后,咱给我们的项目提供一些工具方法:

(function(window, undefined){
    var TOOLS = SEEJS.tools || {};

    TOOLS.trim = function(str) {...};
    TOOLS.hexToRgba = function(hex, alpha) {...};

    SEEJS.tools = TOOLS;
})(window); **现在我们在控制台输入 SEEJS,然后敲一下回车,就可以清晰的看到我们的代码结构了:**

SEEJS = {
    copyright: "CopyRight © MrZheng",
    plugins: {
        Tips: function(cfg) {}
    },
    tools: {
        trim: function(str) {},
        hexToRgba: function(hex, alpha) {}
    },
    version: "1.0.0"
}

以前我也是用这种方式,可当页面越来越多时会发现越来越难管理 SEEJS 下的 plugins 和 tools。

一、 多人维护时候,很容易破坏SEEJS的结构。虽然使用了 namespace 。 二、例如:Viewdata 插件依赖于 Tip插件, Viewdata的代码如下

(function(window, undefined){
    SEEJS.plugins = SEEJS.plugins || {};

    function Viewdata(cfg) {...}

    Viewdata.prototype = {
        init: function() {...},
        show: function(str) {
            new SEEJS.plugins.Tip(str);
        }
    };
    SEEJS.plugins.Tips = SEEJS.plugins.Tips || Tips;})(window); Viewdata 的 show 函数依赖于 Tip 插件,依赖关系是通过 SEEJS 关联。 我们发现 Tip 现有功能已经不能满足开发需求了,需要升级 Tip 。这个升级工作可能会修改 Tip 的 api。

一旦完成升级并修改了 Tip 的 api 就需要修改所有页面的 Tip 相关代码和 Viewdata 相关代码,因为它是通过 SEEJS.plugins 访问的。


为了解决这些问题,现在我们使用 seajs 加载文件和管理依赖关系。 通过文件目录命名空间去解决组件的加载和版本控制 // old code seajs.use(‘tip/0.0.1/tip’,function () { // 无特殊必要不会修改旧的代码}) // new code seajs.use(‘tip/0.0.2/tip’,function () { // 新的页面则使用新版本的组件}) utils 可通过约定集成到 base.js 中 base.js 包含 seajs 和 utils 目前我们全局变量是 S , S 下面有 namespace 方法。 约定所有页面临时变量全部用 S.page 自由存放,声明变量全部使用 namespace 声明。

S.namespace('page.data')

S.page.data.username = 'NimoChu';

S.namespace('page.data.ui')

seajs.use('tip/0.0.2/tip',function () {
    S.page.ui.tip = new Tip();    }))
seajs 帮我们完成了组件的加载、版本控制、依赖管理。

而全局变量 S 和 namespace 帮助我们管理和创建本页所需的变量

第四阶段

前端是一种技术问题较少、工程问题较多的软件开发领域。 当我们要开发一款完整的Web应用时,前端将面临更多的工程问题,比如: 1. 大体量:多功能、多页面、多状态、多系统; 2. 大规模:多人甚至多团队合作开发; 3. 高性能:CDN部署、缓存控制、文件指纹、缓存复用、请求合并、按需加载、同步/异步加载、移动端首屏CSS内嵌、HTTP 2.0服务端资源推送。