前端工程化


目前,Web业务逻辑日益复杂化和多元化,前端发开已经从以WebPage模式为主转变成以WebApp 为主,已经不再像过去那样,拼凑页面,依赖 jQuery和其社区下的多元化的插件。

开发工程复杂了之后就产生了许多问题,比如:如何进行高效的多人协作?如何保证项目的可维护性?如何提高项目的开发质量?

我认为前端工程化主要应该从模块化组件化规范化自动化四个方面来思考。

模块化

简单来说,模块化就是将一个大文件拆分成相互依赖的小文件,再进行统一的拼装和加载。只有这样,才有多人协作的可能。

JS 的模块化

在 ES6 之前,JavaScript一直没有模块系统,这对开发大型复杂的前端工程造成了巨大的障碍。对此社区制定了一些模块加载方案,如CommonJS、AMD、CMD和UMD等,某些框架也会有自己模块系统,比如Angular1.x。

  • CommonJS 是服务器端模块的规范,Node.js采用了这个规范。 可以将 Javascript 按照 Node 模块的方式定义,例子:

    var $ = require('jquery');
    function myFunc(){};
    module.exports = myFunc;
    

    CommonJS 加载模块是同步的,所以只有加载完成才能执行后面的操作。像Node.js主要用于服务器的编程,加载的模块文件一般都已经存在本地硬盘,所以加载起来比较快,不用考虑异步加载的方式,所以CommonJS规范比较适用。但如果是浏览器环境,要从服务器加载模块,这是就必须采用异步模式。所以就有了 AMD、CMD 解决方案。

  • AMD(Asynchronous Module Definition): 异步模块定义,可以异步的加载或依赖其他模块,支持的库如 Require.js, Sea.js ,例子:

    //通过数组引入依赖 ,回调函数通过形参传入依赖
    define(['someModule1', ‘someModule2’], function (someModule1, someModule2) {
    
      function foo () {
          /// someing
          someModule1.test();
      }
    
      return {foo: foo}
    });
    
  • CMD是 SeaJS 在推广过程中对模块定义的规范化产出,CMD和AMD的区别有:

    1. 对于依赖的模块AMD是提前执行,CMD是延迟执行。不过RequireJS从2.0开始,也改成可以延迟执行(根据写法不同,处理方式不通过)。
    2. CMD推崇依赖就近,AMD推崇依赖前置
//AMD
define(['./a','./b'], function (a, b) {

    //依赖一开始就写好
    a.test();
    b.test();
});

//CMD
define(function (requie, exports, module) {

    //依赖可以就近书写
    var a = require('./a');
    a.test();

    ...
    //软依赖
    if (status) {

        var b = requie('./b');
        b.test();
    }
});
  • UMD是AMD和CommonJS的糅合

AMD 浏览器第一的原则发展 异步加载模块。CommonJS 模块以服务器第一原则发展,选择同步加载,它的模块无需包装(unwrapped modules)。这迫使人们又想出另一个更通用的模式UMD(Universal Module Definition)。希望解决跨平台的解决方案。UMD先判断是否支持 Node.js的模块(exports)是否存在,存在则使用Node.js模块模式。再判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。

(function (window, factory) {
    if (typeof exports === 'object') {

        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {

        define(factory);
    } else {

        window.eventUtil = factory();
    }
})(this, function () {
    //module ...
});

现在ES6已经在语言层面上规定了模块系统,完全可以取代现有的CommonJS和AMD规范,而且使用起来相当简洁,并且有静态加载的特性。

import Vue from "vue"

function Func(){
    //my func here
}

export Func

规范确定了,然后就是模块的打包和加载问题:

  1. 用Webpack+Babel将所有模块打包成一个文件同步加载;
  2. 用SystemJS+Babel分模块异步加载;
  3. 将两者结合在一起。

CSS处理

CSS 处理包含了 CSS 预处理和 CSS 模块化

CSS 预处理就是使用像 SASS、LESS、Stylus 等这样的预处理工具来优化 css 的开发过程,最终同样汇编成原生的 css

虽然 SASS、LESS、Stylus 等预处理器实现了CSS的文件拆分,但没有解决CSS模块化的一个重要问题:选择器的全局污染问题。

为了避免全局选择器的冲突,各厂都制定了自己的 CSS 命名规则:

  • BEM风格;
  • Bootstrap风格;
  • Semantic UI风格;

等等,但这毕竟都是比较弱的约束,随着项目的增长,这些问题会越来越严重。

从工具层面上,社区又创造出Shadow DOM、CSS in JS和CSS Modules三种解决方案。

  • Shadow DOM是WebComponents的标准。它能解决全局污染问题,但目前很多浏览器不兼容,对我们来说还很久远;

  • CSS in JS是彻底抛弃CSS,使用JS或JSON来写样式。这种方法很激进,不能利用现有的CSS技术,而且处理伪类等问题比较困难;

  • CSS Modules仍然使用CSS,只是让JS来管理依赖。它能够最大化地结合CSS生态和JS模块化能力,目前来看是最好的解决方案。Vue的scoped style也属于这一种。

组件化

组件化是基于模块化,在设计层面上,对用户界面(UI)的拆分。

从 UI 拆分下来的每个包含模板(HTML)+样式(CSS)+逻辑(JS)功能完备的结构单元,我们称之为组件。

其实,组件化更重要的是一种分治思想。

页面上所有的东西都可以看作组件。页面是个大型组件,可以拆成若干个中型组件,然后中型组件还可以再拆,拆成若干个小型组件,小型组件也可以再拆,直到拆成DOM元素为止。DOM元素可以看成是浏览器自身的组件,作为组件的基本单元。

传统前端框架/类库的思想是先组织DOM,然后把某些可复用的逻辑封装成组件来操作DOM,是DOM优先(典型的就是 jQuery )。

而组件化框架/类库的思想是先来构思组件,然后用DOM这种基本单元结合相应逻辑来实现组件,是组件优先。这是两者本质的区别。

其实组件化不是什么新鲜的东西,以前的客户端框架,像WinForm、WPF、Android等,它们从诞生的那天起就是组件化的。而前端领域发展曲折,是从展示页面为主的WebPage模式走过来的,近两年才从客户端框架经验中引入了组件化思想。其实我们很多前端工程化的问题都可以从客户端那里寻求解决方案。

目前市面上的组件化框架很多,主要的有Vue、React、Angular 等

规范化

模块化和组件化确定了开发模型,而这些东西的实现就需要规范去落实。

规范化其实是工程化中很重要的一个部分,项目初期规范制定的好坏会直接影响到后期的开发质量。

常见的规范化内容有:

  • 目录结构规范
  • 编码规范
  • 前后端接口规范
  • 文档规范
  • 组件管理
  • 仓库管理规范(Git、SVN 等)

自动化

自动化的核心其实就是:任何简单机械的重复劳动都应该让机器去完成。

常见的有:

  • 自动化构建
  • 自动化测试
  • 自动化部署

自动化工具有: gulp、webpack、docker等

当然每个项目团队的实际情况不一样,不一定会全部遵守整个流程,但是了解和掌握前端工程化,对你以后组织项目会有很大的帮助。

参考: