灏天阁

浅谈模块化

· Yin灏

什么是模块

  • 将一个复杂的程序按照一定规则封装成几个块,并进行组合在一起;
  • 块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信;

为什么要使用模块化

  • 私有的作用域,避免命名冲突,减少命名空间污染
  • 更好的分离, 按需加载
  • 更高复用性
  • 高可维护性

模块化的发展历史

函数时期

function fn() {}

命名空间时期:模块化的思路雏形初显

var student = {
  name: "luyi",
  getScore: function () {},
};

student.name;
student.getScore;

闭包时期

// 递增的小栗子
function test() {
  let count = 1;
  return function () {
    count++;
    console.log(count);
  };
}
var a = test();
a();

解决依赖问题:此处已有现代模块化(vue)的感觉了,模块各自独立,模块之间有依赖(global, $),逻辑层操作视图层

(function (global, $) {
  var name = "Chenluo Notes";
  let data = "www.baidu.com";

  // 操作数据的函数
  function doSth() {
    //用于暴露有函数
    console.log(`foo() ${data}`);
    $("body").css("background", "red");
  }

  function getName() {
    return name;
  }

  global.student = { name, doSth, getName };
})(window, jquery);

console.log(window.student);

CJS 规范

var module = {
  exports: {},
}(function (modules, exports, require) {
  const $ = require("../juery.min.js");
  var name = "Chenluo Notes";

  function getName() {}

  modules.exports = { name, getScore };

  exports.name = name;
  // exports = {name, getScore}
})(modules, modules.exports, require);

// require 函数,以一个路径作为参数,函数的功能是,读取路径指向的 js 文件的地址,
// 然后把文件读出来,以 js 的形式解析。

时代的车轮滚滚向前~~~

函数 => 命名空间 => 闭包 => 解决依赖 => cjs 模块化规范

可以看出,技术的发展和迭代都是为了解决人们的一些痛点,然后不断进行创新迭代优化。就如最近大火的 ChartGPT, 不也是为了解放劳动力,提高生产力。让资源更集中,查询更便捷,想法落地更快速。

当然,也引发了一些其他的问题~~~ 毕竟,未知的总是让人焦虑。

貌似有点跑偏,让我们接着奏乐接着舞(学模块化)!!!

image.png

什么是模块化规范

模块化规范是为了解决传统用 script 标签引入 js 文件的一些痛点~~~

比如:

<!-- 传统引入js模块的方法 -->
<script src="./A.js"></script>
<script src="./B.js"></script>

一,没有依赖关系

  • A 和 B 两个 js 我想 B 先执行完再执行 A
  • 我要拿到 B 返回的数据,放到 A 里去执行

二,不能按需解析

  • 我要用到 A 时,A 才会被解析(函数调用,接口请求)

基于以上两点,再实际开发和维护过程中,就比较痛苦

所以模块化规范就诞生啦!!!

utils 工具库想必有开发经验的小伙伴肯定不陌生,这算是最常见的模块化。

每个项目大家都约定熟成的封装一些公共的工具函数在 utils 里,方便在各个页面使用,提高开发效率。俗称造轮子。

utils 在 vue 项目中一般使用 ESM 规范导入,为了更好的进行模块之间的依赖,和按需解析。下文中引入的是一个文件,如果 utils 文件夹下面有多个文件,彼此之间通过 ESM 进行依赖,做一些逻辑处理,效果会更明显点。

// 引用方式: esm规范
import { isURL } from "@/utils";
console.log(isURL("https://juejin.cn"));

// 引用方式: cjs规范
let { timestampToHMS } = require("../../utils/index.js");
console.log(timestampToHMS(211312));

模块化规范有哪些

AMD, CMD, CJS(common js), ESM(es 模块), UMD,一般共五种模块化规范

AMD 是一个已经过时的规范,和 CMD 一样都是异步的

一,AMD 模块化规范, RequireJS

define("a", function () {
  console.log("a load");
  return {
    run: function () {
      console.log("a run");
    },
  };
});

define("b", function () {
  console.log("b load");
  return {
    run: function () {
      console.log("b run");
    },
  };
});

require(["a", "b"], function (a, b) {
  console.log("main run"); // 🔥
  a.run();
  b.run();
});

// 依赖前置,引用的时候就执行方法
// a load
// b load
// main run
// a run
// b run

二,CMD 模块化规范, sea.js

// CMD sea.js
define("a", function (require, exports, module) {
  console.log("a load");
  exports.run = function () {
    console.log("a run");
  };
});

define("b", function (require, exports, module) {
  console.log("b load");
  exports.run = function () {
    console.log("b run");
  };
});

define("main", function (require, exports, module) {
  console.log("main run");
  var a = require("a");
  a.run();
  var b = require("b");
  b.run();
});

seajs.use("main");

// 依赖后置
// main run
// a load
// a run
// b load
// b run

三,CJS 模块化规范,主要用于服务端编程, nodejs 最早实现了它,是同步的

(function (modules, exports, require) {
  // require 函数,以一个路径作为参数,函数的功能是,读取路径指向的 js 文件的地址,
  // 然后把文件读出来,以 js 的形式解析。

  const $ = require("../juery.min.js");
  var name = "张三";

  function getName() {}

  modules.exports = { name, getScore };

  exports.name = name;
  // exports = {name, getScore} 不能这样用,会使引用断了
})(modules, modules.exports, require);

四,ESM 模块化规范,现代浏览器规范

import XXX from 'xxx';
export const xxxx;
export default xxxxxxx;
<!-- 
    或者
    浏览器中实现了它,ES6规范是浏览器的JS引擎识别JS代码的一种规则 
-->
<script type="module">
  import XXX from "xxx";
</script>

五,UMD 模块化规范, 通用模块定义,配置了多个模块,兼容 AMD CMD CJS script 浏览器标签

延伸一点点

  • 当一个 UMD 模块在 ESM 环境中使用时,可以把它当作普通的 JS 库来使用,通过 script 标签引入即可。
  • 而当一个 ESM 模块在 UMD 环境中使用时,则需要使用一些兼容策略,例如将 ESM 模块编译成 UMD 规范,并将其作为 JS 库来使用。

模块化和工程化的关系

提到工程化,不得不提 webpack 和 rollup。

他们两个都是为了把项目中的高阶语法(ES6 写法)打包成浏览器可以识别的低级语法(IIFE),也就是小伙伴们 vue 项目中 dist 目录下的文件。

上面的五种模块化规范,webpack 和 rollup 都可以进行配置打包。

下面是小白会遇到的三个疑问(不要问我为什么知道)

一,打包后,通过配置生成的这五种规范,浏览器可以解析吗?如果不可以,为什么 webpack/rollup 中可以这样配置?

ESM 规范是浏览器的亲儿子,也是我们上文一直提到的。ESM 规范是可以被浏览器解析的。

  • 可以直接在 html 文件中 script type=“module"使用 improt export
  • 也可以直接在服务器运行的浏览器环境中直接使用 es6 语法,但是不同的浏览器对模块化规范的支持程度有所不同。

那其他几种规范存在的意义是什么呢

  • CJS 是给服务器使用的,主要用于服务端编程,因为同步意味着阻塞可能。如果非要在浏览器中解析,也可以通过 Browserify 来把 CJS 的代码转成普通 js(不带 require)
  • AMD 和 CMD 是为了解决浏览器端异步加载的问题,需要通过模块加载器来让浏览器解析。AMD(requireJS), CMD(seajs)
  • UMD 综合了 CJS 和 AMD 规范的优点,兼容各种运行环境,可以在浏览器和 Node.js 环境中运行。UMD 规范支持多种模块加载器,可以满足不同项目的需求,比较灵活。

二,开发过程中,webpack 可以解析这五种规范吗?

  • 当然可以,项目中不论是 import(ESM),require(CJS)等写法,webpack 都可以解析,并且可以打包成你指定的模块化规范。

三,webpack 和 rollup 有啥区别呢?

  • Rollup 是提供一个充分利用 ESM 各项特性的高效打包器,更加专注于打包 JavaScript 库和组件。主要优势在于可以生成非常小的包,因为它采用 ES6 模块语法并利用 Tree shaking 技术进行优化。这使得与其生成的包一起使用的代码量最小化。
  • Webpack 的设计目标是支持复杂的应用程序打包。它可以将许多不同类型的代码(包括 JavaScript、CSS、图片等)打包为一个或多个文件,同时还支持功能强大的开发工作流,如热替换、代码分割和动态导入。

- Book Lists -