前端组件化、模块化、工程化整理

前端组件化、模块化、工程化都是属于思想而不是技术,但最终以技术的形式来实现。只有理解这些思想,才能更高效的开发出易维护、易扩展、性能佳的代码。

一、生活中的点滴

2017年的时候,我还用QQ,我的一个摄影师朋友,给他所在的公司拍摄线材照片,说他把所有接口接头都收集了,按角度(45度 和 90度)拍好,后面就可以PhotoShop 复用合成就行,摄影需要对光足够理解,需要很多时间调整光源,如果能够复用,那么就能省下不少时间。

可见组件化思想在社会实践中,只要用心想偷懒总是会想到的。

二、前端组件化

我理解前端组件化主要目的是:

  1. 拆分成独立的小单元,保持清晰的结构,特别是单页面应用时不拆分可能经常会有数千行代码的文件。
    多人协作开发,版本控制也容易。
  2. 复用:总有些人喜欢去复制代码,而不是剥离出公共组件来引用,一旦需求变更,就要改多处,维护成本高。减少重复代码,代码少,网络请求快,打开页面就快。
  3. 复用现成的组件开发效率高,比如 Element UI 就是可复用的组件库。

前端在划分任务时,不能只是按页面去划分,可能不同同事写了相同功能的不同代码。

三、前端模块化

我理解前端组件化主要目的是:

  1. 复用,快速利用现成模块完成具体功能,比如常说的现成的轮子。
  2. 降低耦合,可替换。比如我们调用的这个模块已经不兼容新的环境了,要替换一个,或有更先进的模块可以替代它。我们要对依赖的模块持怀疑态度,要考虑可替代方案。
  3. 代码隔离,避免命名冲突。
  4. 使代码结构更加清晰。

方法就是将一些具体的功能进行封装,更多关注接口传递的参数和期望的结果。
JavaScript 封装及调用模块有一些格式规范,比如:CommonJS,AMD,CMD,UMD,ES6 Module。

Node 应用由模块组成,采用 CommonJS 模块规范,他通过require 函数同步加载依赖的模块,exports 对象导出需要暴露的接口。

math.js
exports.add = function() {};

increment.js
var add = require('./math').add;
exports.increment = function(val) {
    return add(val, 1);
};

program.js
var inc = require('./increment').increment;
inc(1); // 2

AMD 是require.js 模块加载器在浏览器端异步加载模块的规范。

define(id?, dependencies?, factory);

math.js
define(function (){
 return {
   add: function () {}
 };
});

increment.js
define(['math'], function (math){
 return {
   inc: function (val){ return math.add(val, 1);}
 };
});

program.js
require(['increment'], function (increment){
  console.log(increment.inc(1)); // 2
});

CMD 是阿里玉伯写的 sea.js模块加载器在浏览器端异步加载模块的规范。

math.js
define(function(require, exports, module) {
  // 通过 exports 对外提供接口
  exports.add = function() {}
  // 也可以用 module.exports = ...
});

increment.js
define(function(require, exports) {
  var add = require('math').add;
  exports.inc = function(val) {
    return add(val, 1);
  };
});

program.js
define(function(require, exports, module) {
  var inc = require('increment').inc;
  console.log(inc(1)); // 2
});

UMD是能让所写的组件同时支持CommonJS、AMD 的不同调用产生的兼容性规范。

// increment.js 的 UMD简单实现,一个自调用函数
(function (global, factory) {
  if (typeof define === 'function' && define.amd) { // 处于 AMD 环境下
    define('increment', ["math"], factory)
  } else if (typeof define === 'function' && define.cmd) { // 处于 CMD 环境下
    define(function(require, exports, modules) {
      let math = require('math')
      modules.exports = factory(math)                                                                                                                                                                   
    })
  } else if (typeof module === 'object' && module.exports) { // 处于Node/CommonJS 环境
    let math = require("./math")
    module.exports = factory(math)
  } else {
    global.increment = factory(global.math)
  }
}(this, function (math) {
  let increment = {
    inc: function (val) {
      return math.add(val, 1)
    }
  }
  return increment;
}));

jQuery 实现UMD的代码片段

(function( global, factory ) {
  if ( typeof module === "object" && typeof module.exports === "object" ) {
    // 兼容 CommonJS
    module.exports = global.document ?
      factory(global, true ) : // 浏览器环境
      function( w ) { // 非浏览器环境
        if ( !w.document ) {
	  throw new Error( "jQuery requires a window with a document" );
	}
	return factory( w );
      };
    } else {
      factory(global);
  }
  // Pass this if window is not defined yet
} (typeof window !== "undefined" ? window : this, function( window, noGlobal) {
  ...
  if ( typeof define === "function" && define.amd ) { // 兼容 AMD
    define("jquery", [], function() {
      return jQuery;
    });
  }
  ...
  return jQuery;
}))

自从ES6 原生Module 出来,上面的模块化工具就逐渐淘汰。

math.js
const add = function() {}
export default { add }

increment.js
import math from './math.js'
const inc = function(val) {
   return math.add(val, 1);
}
export default { inc }

program.js
import increment from './increment.js'
console.log(increment.inc(1)); // 2

四、前端工程化

前端工程化是指通过使用工具和技术来自动化和优化前端开发的流程和效率,从而提高项目的可维护性和可扩展性。前端工程化可以包括代码规范、版本控制、构建工具、自动化测试、性能优化等方面。
更多是构建一种标准化