模块化规范的出现

2023/6/20 moduleESM

# 规范化的出现

除了模块加载的问题以外,Webpack 究竟解决了什么问题?这几种通过约定实现模块化的方式,不同的开发者在实施的过程中会出现一些细微的差别,因此,为了统一不同开发者、不同项目之间的差异,我们就需要制定一个行业标准去规范模块化的实现方式

再加上我们刚刚提到的模块加载的问题,我们现在的需求就是两点:

  • 一个统一的模块化标准规范
  • 一个可以自动加载模块的基础库、

提到模块化规范,首先会想到的就是 CommonJS 规范,它是 Node.js 中所遵循的模块规范,该规范约定,一个文件就是一个模块,每个模块都有单独的作用域,通过 module.exports 导出成员,再通过 require 函数载入模块

现如今的前端开发者应该对其有所了解,但是如果我们想要在浏览器端直接使用这个规范,那就会出现一些新的问题。

CommonJS 约定的是以同步的方式加载模块,因为 Node.js 执行机制是在服务启动时加载模块,执行过程中只是使用模块,所以这种方式不会有问题。

但是如果要在浏览器端也使用这种同步的加载模式,就会引起大量同步的模块请求,浏览器要等待请求回来之后才可以继续解析模块,也就是说,模块请求会造成浏览器 JS 解析过程的阻塞,导致浏览器运行效率低下

所以在早期制定前端模块化标准时,并没有直接选择 CommonJS 规范,而是专门为浏览器端重新设计了一个规范,叫做 AMDAsynchronous Module Definition) 规范,即异步模块定义规范。同期还推出了一个非常出名的库,叫做 Require.js,它除了实现了 AMD 模块化规范,本身也是一个非常强大的模块加载器。

AMD 规范中约定每个模块通过 define() 函数定义,这个函数默认可以接收两个参数

  • 第一个参数是一个数组,用于声明此模块的依赖项
  • 第二个参数是一个函数,参数与前面的依赖项一一对应,每一项分别对应依赖项模块的导出成员,这个函数的作用就是为当前模块提供一个私有空间。如果在当前模块中需要向外部导出成员,可以通过 return 的方式实现。

AMD规范定义一个模块

define(['module1',...,'modulen'],function(param)	{
    //todo:定义模块内容

    //返回模块对象
    return 模块;
});

除此之外, 通常是在主模块开头部分,会使用RequireJS库提供的requirejs.config()requirejs()接口分别实现子模块的路径配置与非AMD模块到AMD模块的映射、子模块的加载和主模块内容的定义

AMD规范载入一个模块

//配置子模块及其路径
requirejs.config({
  baseUrl: "js", //相对根目录的路劲
  //默认相对于当前main.js主模块的相对路径
  paths: {
    dataService: "./modules/dataService",
    alert: "./modules/alert",
    jquery: "./libs/jquery-3.6.1", //jquery默认支持amd规范
    angular: "./libs/angular", //angular.js-非amd规范的模块
  },
  shim: {
    //配置非amd规范的js脚本库-将当前非amd文件规范化为AMD模块
    angular: {
      exports: "angular",
    },
  },
});
//引入子模块
requirejs(["alert", "jquery", "angular"], function (alert, $, angular) {
  alert.showMsg();
  const domTitle = $("#title")[0].innerText;
  console.log(domTitle);
  console.log(angular);
});

目前绝大多数第三方库都支持 AMD 规范,但是它使用起来相对复杂,而且当项目中模块划分过于细致时,就会出现同一个页面对 js 文件的请求次数过多的情况,从而导致效率降低。在当时的环境背景下,AMD 规范为前端模块化提供了一个标准,但这只是一种妥协的实现方式,并不能成为最终的解决方案

同期出现的规范还有淘宝的 Sea.js,只不过它实现的是另外一个标准,叫作 CMD,这个标准类似于 CommonJS,在使用上基本和 Require.js 相同,可以算上是重复的轮子 随着前端技术的发展,Sea.js 后来也被 Require.js 兼容了

# 模块化的标准规范

尽管上面介绍的这些方式和标准都已经实现了模块化,但是都仍然存在一些让开发者难以接受的问题

随着技术的发展,JavaScript 的标准逐渐走向完善,可以说,如今的前端模块化已经发展得非常成熟了,而且对前端模块化规范的最佳实践方式也基本实现了统一

  • Node.js 环境中,我们遵循 CommonJS 规范来组织模块
  • 在浏览器环境中,我们遵循 ES Modules 规范

而且在最新的 Node.js 提案中表示,Node 环境也会逐渐趋向于 ES Modules 规范,也就是说作为现阶段的前端开发者,应该重点掌握 ES Modules 规范

因为 CommonJS 属于内置模块系统,所以在 Node.js 环境中使用时不存在环境支持问题,只需要直接遵循标准使用 requiremodule 即可

但是对于 ES Modules 规范来说,情况会相对复杂一些。我们知道 ES Modules 是 ECMAScript 2015(ES6)中才定义的模块系统,也就是说它是近几年才制定的标准,所以肯定会存在环境兼容的问题。在这个标准刚推出的时候,几乎所有主流的浏览器都不支持。但是随着 Webpack 等一系列打包工具的流行,这一规范才开始逐渐被普及

经过几年的迭代, ES Modules 已发展成为现今最主流的前端模块化标准。相比于 AMD 这种社区提出的开发规范,ES Modules 是在语言层面实现的模块化,因此它的标准更为完善也更为合理。而且目前绝大多数浏览器都已经开始能够原生支持 ES Modules 这个特性了,所以说在未来几年,它还会有更好的发展,短期内应该不会有新的轮子出现了

综上所述,**如何在不同的环境中去更好的使用 ES Modules **是我们前端开发者需要重点考虑的问题

# 出现了新的问题

模块化可以帮助我们更好地解决复杂应用开发过程中的代码组织问题,但是随着模块化思想的引入,又产生了一些新的问题

  • 兼容性问题
  • 性能问题
  • 其他的文件也需要模块化

首先,我们所使用的 ES Modules 规范本身就存在环境兼容问题,尽管现如今主流浏览器的最新版本都支持这一特性,但是目前还无法保证用户的浏览器使用情况。所以我们还需要解决兼容问题

其次,模块化的方式划分出来的模块文件过多,而前端应用又是运行在浏览器中的,每一个文件都需要单独从服务器请求回来。零散的模块文件必然会导致浏览器的频繁发送网络请求,影响应用的工作效率

最后,随着应用日益复杂,在前端应用开发过程中不仅仅只有 JavaScript 代码需要模块化,HTML 和 CSS 这些资源文件也会面临需要被模块化的问题。而且从宏观角度来看,这些文件也都应该看作前端应用中的一个模块,只不过这些模块的种类和用途跟 JavaScript 不同

# 模块打包工具的出现

对于开发过程而言,模块化肯定是必要的,所以我们需要在前面所说的模块化实现的基础之上引入更好的方案或者工具,去解决上面提出的 3 个问题,让我们的应用在开发阶段继续享受模块化带来的优势,又不必担心模块化对生产环境所产生的影响

接下来我们可以先对这个更好的方案或者工具提出一些设想:

1️⃣ 它需要具备编译代码的能力,也就是将我们开发阶段编写的那些包含新特性的代码转换为能够兼容大多数环境的代码,解决我们所面临的环境兼容问题

2️⃣ 能够将散落的模块再打包到一起,这样就解决了浏览器频繁请求模块文件的问题。这里需要注意,只是在开发阶段才需要模块化的文件划分,因为它能够帮我们更好地组织代码,到了实际运行阶段,这种划分就没有必要了

3️⃣ 它需要支持不同种类的前端模块类型,也就是说可以将开发过程中涉及的样式、图片、字体等所有资源文件都作为模块使用,这样我们就拥有了一个统一的模块化方案,所有资源文件的加载都可以通过代码控制,与业务代码统一维护,更为合理

针对上面第一、第二个设想,我们可以借助 Gulp 之类的构建系统配合一些编译工具和插件去实现,但是对于第三个可以对不同种类资源进行模块化的设想,就很难通过这种方式去解决了

💥 所以 Webpack 出现了,上述的三个设想也是 Webpack 的特点

  1. 打包你的代码,并去除你代码里面用不到的部分,提取用到的部分,这就是tree shaking
  2. 配合各种loader让你可以使用sass,less,ts,es新语法... 并且解析各种各样的文件
  3. 帮你对于打包之后的项目通过使用plugin进行各种优化操作(比如说压缩,混淆代码...)

# 写在最后

虽然 Webpack 发展到今天,它的功能已经非常强大了,但依然改变不了它的初衷是解决模块化

你可以看到,Webpack 官方的 Slogan 仍然是:A bundler for javascript and friends(一个 JavaScript 和周边的打包工具)

从另外一个角度来看,Webpack 从一个“打包工具”,发展成现在开发者眼中整个前端项目的“构建系统”,表面上似乎只是称呼发生了变化,但是这背后却透露出来一个信号:模块化思想是非常伟大的,伟大到可以帮你“统治”前端整个项目。这也足以见得模块化思想背后还有很多值得我们思考的内容

总的来说,我们可以把 Webpack 看作现代化前端应用的“管家”,这个“管家”所践行的核心理论就是“模块化”,也就是说Webpack 以模块化思想为核心,帮助我们这些前端开发者们更好地管理整个前端工程

How to love
Lil Wayne