前端模块化

110 阅读3分钟

模块化的历史

无模块化 ->IIFE(模块化雏形) -> CommonJS(代码层面到框架层面的优化) -> AMD(从同步加载到异步加载的优化) -> CMD(按需加载优化) -> ESModule

0. 无模块化

  • 污染全局作用域, 不利于大型项目的开发以及多人团队的共建。

1.IIFE(语法侧的优化)

主要应用的是作用域进行模块化

const iifeModule = ((dependencyModule1, dependencyModule2) => {
  let count = 0;
  const increase = () => ++count;
  const reset = () => {
    count = 0;
  };

  return {
    increase,
    reset,
  };
})(dependencyModule1, dependencyModule2);
iifeModule.increase();
iifeModule.reset();

2. CJS - Commonjs

  • node.js 制定
  • 特征:

通过 module + exports 去对外暴露接口 通过 require 来调用其他模块

  • 模块组织方式main.js 文件

  • 优点: CommonJS 率先在服务端实现了,从框架层面解决依赖、全局变量污染的问题

  • 缺点:主要针对了服务端的解决方案。对于异步拉取依赖的处理整合不是那么的友好。

// 引入部分
const dependencyModule1 = require(./dependencyModule1);
const dependencyModule2 = require(./dependencyModule2);

// 处理部分
let count = 0;
const increase = () => ++count;
const reset = () => {
  count = 0;
}
// 暴露接口部分
exports.increase = increase;
exports.reset = reset;

module.exports = {
  increase, reset
}

模块使用方式

const { increase, reset } = require("./main.js");

increase();
reset();

3.AMD

  • AMD(Asynchronous module definition)异步的模块定义
  • 通过异步加载 + 允许制定回调函数
  • 经典实现框架是:require.js
  • 通过define来定义一个模块,然后require进行加载
/*
  define
  params(define的参数): 模块名,依赖模块,工厂方法
   */
define(id, [depends], callback);
require([module], callback);

模块定义方式

define("amdModule", ["dependencyModule1", "dependencyModule2"], (
  dependencyModule1,
  dependencyModule2
) => {
  // 业务逻辑
  // 处理部分
  let count = 0;
  const increase = () => ++count;
  const reset = () => {
    count = 0;
  };

  return {
    increase,
    reset,
  };
});

引入模块:

require(["amdModule"], (amdModule) => {
  amdModule.increase();
});

在 AMDmodule 中想兼容已有的CJS代码

  define('amdModule', [], (require, export, module) => {
    // 引入部分
    const dependencyModule1 = require(./dependencyModule1);
    const dependencyModule2 = require(./dependencyModule2);

    // 处理部分
    let count = 0;
    const increase = () => ++count;
    const reset = () => {
      count = 0;
    }
    // 做一些跟引入依赖相关事宜……

    export.increase = increase();
    export.reset = reset();
  })

  define('otherModule', [], require => {
    const otherModule = require('amdModule');
    otherModule.increase();
    otherModule.reset();
  })

3.UMD

  • 兼容 AMD&CJS/如何判断 CJS 和 AMD
  (define('amdModule', [], (require, export, module) => {
    // 引入部分
    const dependencyModule1 = require(./dependencyModule1);
    const dependencyModule2 = require(./dependencyModule2);

    // 处理部分
    let count = 0;
    const increase = () => ++count;
    const reset = () => {
      count = 0;
    }
    // 做一些跟引入依赖相关事宜……

    export.increase = increase();
    export.reset = reset();
  }))(
    // 目标是一次性区分CommonJSorAMD
    typeof module === "object"
    && module.exports
    && typeof define !== "function"
      ? // 是 CJS
        factory => module.exports = factory(require, exports, module)
        // factory 代表要执行的函数 即回调中要执行的函数  将module.exports 等于 要执行的函数
      : // 是AMD
        define
  )

4.CMD

  • 按需加载,支持动态引入依赖文件
  • 主要应用的框架 sea.js
  • 优点:按需加载,依赖就近
  • 缺点: 依赖于打包,加载逻辑存在于每个模块中,扩大模块体积
// 引入require
var fs = require('fs'); //同步
require.async('./module3', function (m3) {}) //异步

define("module", (require, exports, module) => {
  let $ = require("jquery");
  // jquery相关逻辑

  let dependencyModule1 = require("./dependecyModule1");
  // dependencyModule1相关逻辑
});

5.ES6 模块化

  • 引入关键字 —— import

  • 导出关键字 —— export

  • 优点(重要性):通过一种最统一的形态整合了 js 的模块化

  • 缺点(局限性):本质上还是运行时的依赖分析(未进行预编译)

模块引入、导出和定义的地方

// 引入区域
import dependencyModule1 from "./dependencyModule1.js";
import dependencyModule2 from "./dependencyModule2.js";

// 实现代码逻辑
let count = 0;

// 导出区域
// 一种方式
export const increase = () => ++count;
export const reset = () => {
  count = 0;
};

// 一种方式
const increase = () => ++count;
const reset = () => {
  count = 0;
};
export default {
  increase,
  reset,
};

模板引入的地方

<script type="module" src="esModule.js"></script>

node 中:

import { increase, reset } from "./esModule.mjs";
increase();
reset();

import esModule from "./esModule.mjs";
esModule.increase();
esModule.reset();

动态模块

import("./esModule.js").then((dynamicEsModule) => {
  dynamicEsModule.increase();
});