JS & Node 模块化解释:AMD、UMD、CommonJS、 ESM

一、前言

传统方式下,JS 若想引入其它 JS 文件时,通常使用 <script> 语法来完成,然而引入的 JS 往往易于造成命名污染,为了解决这问题,模块化 开发的概念逐渐浮现。

本文将以完整的 Demo 将各大模块模块的概念和写法进行阐述与演示,希望对你有帮助。

二、AMD

2.1 介绍

AMD 就是为了解决命名污染而研发的,同时还支持按需加载,是第一个引入 模块化 开发的规范插件,要想使用 AMD 语法得借助一款插件 RequireJS

注意,AMD 只适用于浏览器,虽然也支持 Node,但不如 Node 自家的 CJS,后面会讲。

2.2 使用

  1. 目录结构
    在这里插入图片描述

  2. 引入 requirejs.js 插件

// index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script data-main="app.js" src="https://requirejs.org/docs/release/2.3.6/r.js"></script>
</head>
<body>
  <div id="app">
    Hello,world
  </div>
</body>
</html>

参数解释:data-main 代表 JS 入口文件,当 src 加载完插件后,会立刻执行 app.js

  1. app.js 入口文件内进行一些 AMD 配置
// app.js
requirejs.config({
  baseUrl: 'modules/', // 引入模块的基路径。
});
  1. 使用 define 定义模块
// modules/tools.js
define('tools', function() {
  const getDeviceType = function() {
    return 'Android'
  }
  return {
    getDeviceType: getDeviceType,
  }
})
  1. 使用 require 加载模块
// app.js
requirejs.config({
  baseUrl: 'modules/',
});
// 根据上述配置的 baseUrl + 'tools' 去加载这个模块。 
require(['tools'], function(tools) {
  console.log('tools', tools)
})

效果:
在这里插入图片描述

小结:define && require = AMD,更多高级 API 和配置可参考官方。

三、CommonJS

3.1 介绍

CommonJS 也常被称为 CJS,与 ADMI 一样属于模块化语法,不过它是用来兼容后端 Nodejs 语言,庆幸的是,CJS 在 Node.js 中已内置,开箱即用,无需引入插件。

3.2 使用

  1. 案例结构
    在这里插入图片描述

  2. 使用 exports.module 定义模块

// modules/tools.js
const getDeviceType = () => {
  return 'Android'
}

exports.module = {
  getDeviceType,
}
  1. 使用 require 加载模块
const tools = require('./modules/tools')
const app = () => {
  console.log('tools', tools)
}
app();
  1. 执行 node
    在这里插入图片描述

小结:exports.module && require = CJS

提示:文件后缀也可以是 .cjs,Node 会自动识别。

四、UMD

4.1 介绍

UMDAMD + CommonJS 的结合体,同时还兼容了 script 标签引入,对组件库或框架库来说,解决了以前一套代码无法多端使用的难题。UMD 模块可借助 Rollup 工具来完成。

4.2 使用

  1. 安装 rollup
npm i rollup -g
  1. 案例结构
    在这里插入图片描述
    modules/tools.js 模块代码如下:

    const getDeviceType = () => {
      return 'Android'
    }
    export { getDeviceType }
    

    注意:代码采用的是 ESM 写法,后面会讲到。

  2. 通过 rollup 将文件打包成 UMD 产物

    rollup ./modules/tools.js  --file ./modules/tools_umd.js  --format umd --name=tools
    

    解释:

    • --file 表示自定义输出产物的目录和文件名。
    • --format 表示文件的转换格式,这里我们转成 umd 即可。打包后的 tools_umd.js 将放在 modules/ 目录下,产物内容如下:
    • --name 表示兼容 script 标签,将数据挂载到指定的全局对象变量中,后续通过 window[name] 来获取模块。

构建完的产物如下:

(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
  typeof define === 'function' && define.amd ? define(['exports'], factory) :
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.tools = {}));
})(this, (function (exports) { 'use strict';

  const getDeviceType = () => {
    return 'Android'
  };
  exports.getDeviceType = getDeviceType;
}));
  1. 在浏览器引入 UMD 产物
  • 首先在 index.html 中引入 AMD 插件 requirejs,并执行 app-web.js ,如下:
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script data-main="app-web.js" src="https://requirejs.org/docs/release/2.3.6/r.js"></script>
</head>

<body>
  <div id="app">
    Hello,world
  </div>
</body>
</html>
  • app-web.js 中引入加载刚刚打包好的 UMD 模块:
requirejs.config({
  baseUrl: 'modules/',
});
require(['tools_umd'], function (tools) {
  console.log('tools', tools)
})

效果:

在这里插入图片描述

  1. 在浏览器通过 script 标签引入 UMD 产物
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <!-- <script data-main="app-web.js" src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js"></script> -->
  <script src="./modules/tools_umd.js"></script>
</head>

<body>
  <div id="app">
    Hello,world
  </div>
</body>
</html>

效果:
在这里插入图片描述

  1. 在 Node 引入 UMD 产物
const tools = require('./modules/tools_umd.js')
const app = () => {
  console.log('Tools are: ', tools)
}
app()

效果:
在这里插入图片描述

  1. 在浏览器中通过 ESM 引入 UMD 产物:

由于 UMD 仅兼容 AMD/CJS ,可使用 rollup 将 format 设置 esm 版本即可:

rollup ./modules/tools.js --file ./modules/tools_esm.js --format esm

本质上和原先的 tools.js 代码没区别:

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <!-- <script data-main="app-web.js" src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js"></script> -->
  <!-- <script src="./modules/tools_umd.js"></script> -->
  <script type="module" src="app.js"></script>
</head>

<body>
  <div id="app">
    Hello,world
  </div>
</body>

</html>
// app.js
import { getDeviceType  } from './modules/tools_esm.js';
const app = () => {
  console.log('Tool method:', getDeviceType)
}
app()

请添加图片描述

小结:

  1. UMD = AMD + CJS + Script 标签
  2. UMD 需要借助打包工具如 Webpack/Rollup
  3. 除了 Webpack/Rollup 工具,还可以结合其它工具如 Babel,可以将最新的 ES 语法转成低版本的语法,当然这是题外话,对 Babel 话题感兴趣的可参考:JS & 介绍 Babel 的使用及 presets & plugins 的概念

五、ESM

5.1 介绍

由于浏览器始终得借助于 ADMUMD 来进行模块化开发,官方实在看不下去,决定让浏览器内置一套模块化机制,俗称 ESM,目的就是减少 AMDUMD 的依赖实现统一规范,它与 CJS 一样,现代浏览器已经内置好了。

由于 ESM 技术较新,一些旧浏览器无法兼容,因此现在大多项目要借助 Webpack/Rollup 等构建工具将 ESM 打包成兼容的语法;也就说,我们可以在项目中写 ESM 代码,但在运行期间,代码会以兼容语法的方式来执行。

5.2 使用

其实在前面的 UMD 章节第 7 点我们已经使用过 ESM 语法了,除了 export 导出方式,还有其它导出的方式 export default ,它可以导出完整的对象,接下来将基于此语法作为演示。

提示:浏览器使用 ESM 的前提条件是给 script 标签加上 type="module"
另外,这里我们不再借助 Rollup 构建转换工具,现代浏览器本身就支持 ESM。

  1. 案例结构:
    请添加图片描述
  2. 原来的 index.html 内容不变:
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script type="module" src="app.js"></script>
</head>
<body>
  <div id="app">
    Hello,world
  </div>
</body>
</html>
  1. 改造 app.js
// app.js
import tools from './modules/tools.js';
const app = () => {
  console.log('Tools are:', tools)
}
app()
  1. 改造 modules/tools.js,使用 export default 方式导出
// modules/tools.js
const getDeviceType = () => {
  return 'Android'
}
const getDeviceVersion = () => {
  return '1.0.0'
}
const tools = {
  getDeviceType,
  getDeviceVersion
}
export default tools

效果:
在这里插入图片描述
5. exportexport default 支持混合使用

一个 JS 文件中,export 可以有无限个,但 export default 只能有一个。

// modules/tools.js
...
export { getDeviceType, getDeviceVersion }
export default tools
// app.js
import tools from './modules/tools.js';
import { getDeviceType } from './modules/tools.js';
const app = () => {
  console.log('Tools are:', tools)
  console.log('Method is:', getDeviceType)
}
app()

请添加图片描述

六、Nodejs 如何使用 ESM?

6.1 介绍

ESM 语法已经成为现代模块化标准规范,Nodejs12.0.0 版本就开始支持 ESM ,前提条件是,文件名后缀必须为 .mjs ,若想省略 .mjs ,可在 package.json 设置 type: "module" 即可;

提示:若 A.js 想引入 B.mjs ,也要在 package.json 设置 "type": "module" ,否则 node 运行时也会报错,如果是 A.mjs 引入 B.mjs 则不用配置。

6.2 使用

  1. 案例结构
    请添加图片描述

  2. 改造 tools.mjs ,代码如同上述的 ESM

const getDeviceType = () => {
  return 'Android'
}

const getDeviceVersion = () => {
  return '1.0.0'
}

const tools = {
  getDeviceType,
  getDeviceVersion
}

export { getDeviceType, getDeviceVersion }

export default tools
  1. 改造 app.js
import tools from './modules/tools.mjs'
const app = () => {
  console.log('Tools are:', tools)
}
app()
  1. node-esm 跟目录下执行 npm init -y,将会生成 package.json,里面我们只需新增 "type": "module" 即可
{
  "name": "node-esm",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "type": "module"
}

提示:我们可以把前面的 tools.mjs 换成 tools.js ,因为我们已经设定了 "type: "module" ,但为了方便演示,这里就不变更了。

  1. 运行
node app.js

效果:
请添加图片描述

注意:一个 .js 文件不可能同时存在 ESM 或者 CJS 两种语法,也就是你的项目只能选择其中一种模块化语法来开发,否则执行时会抛出错误。像前端常见的 Vite 脚手架,我们之所以能够写 ESM 语法而不用在当前项目设定 module 的原因是, vite 内部的 package.json 早已设定好 "type": "module",通过 vite 来执行项目时会自动解析我们写的ESM 代码。

----------------------------------------------------------------结尾----------------------------------------------------------------

完整 demo 已放到 github

参考文献:
https://requirejs.org/docs/api.html#usage
https://adostes.medium.com/authoring-a-javascript-library-that-works-everywhere-using-rollup-f1b4b527b2a9
https://blog.logrocket.com/how-to-use-ecmascript-modules-with-node-js/

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cookcyq

请作者喝杯暖暖的奶茶

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值