传统方式下,JS 若想引入其它 JS 文件时,通常使用 语法来完成,然而引入的 JS 往往易于造成命名污染,为了解决这问题,
模块化
开发的概念逐渐浮现。
本文将以完整的 Demo 将各大模块模块的概念和写法进行阐述与演示,希望对你有帮助。
AMD
就是为了解决命名污染而研发的,同时还支持按需加载,是第一个引入 模块化
开发的规范插件,要想使用 AMD
语法得借助一款插件 RequireJS
。
注意,AMD 只适用于浏览器,虽然也支持 Node,但不如 Node 自家的 CJS,后面会讲。
// index.html
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
<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
。
app.js
入口文件内进行一些 AMD 配置// app.js
requirejs.config({
baseUrl: 'modules/', // 引入模块的基路径。
});
define
定义模块// modules/tools.js
define('tools', function() {
const getDeviceType = function() {
return 'Android'
}
return {
getDeviceType: getDeviceType,
}
})
require
加载模块// app.js
requirejs.config({
baseUrl: 'modules/',
});
// 根据上述配置的 baseUrl + 'tools' 去加载这个模块。
require(['tools'], function(tools) {
console.log('tools', tools)
})
小结:define && require
= AMD,更多高级 API 和配置可参考官方。
CommonJS
也常被称为 CJS
,与 ADMI
一样属于模块化语法,不过它是用来兼容后端 Nodejs
语言,庆幸的是,CJS
在 Node.js 中已内置,开箱即用,无需引入插件。
// modules/tools.js
const getDeviceType = () => {
return 'Android'
}
exports.module = {
getDeviceType,
}
require
加载模块const tools = require('./modules/tools')
const app = () => {
console.log('tools', tools)
}
app();
小结:exports.module && require
= CJS
提示:文件后缀也可以是
.cjs
,Node 会自动识别。
UMD
是 AMD
+ CommonJS
的结合体,同时还兼容了 script 标签引入,对组件库或框架库来说,解决了以前一套代码无法多端使用的难题。UMD
模块可借助 Rollup
工具来完成。
rollup
npm i rollup -g
const getDeviceType = () => {
return 'Android'
}
export { getDeviceType }
注意:代码采用的是 ESM 写法,后面会讲到。
通过 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;
}));
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>Documenttitle>
<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)
})
效果:
script
标签引入 UMD 产物DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
<script src="./modules/tools_umd.js">script>
head>
<body>
<div id="app">
Hello,world
div>
body>
html>
const tools = require('./modules/tools_umd.js')
const app = () => {
console.log('Tools are: ', tools)
}
app()
由于 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>Documenttitle>
<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()
小结:
AMD
+ CJS
+ Script 标签
Webpack/Rollup
Webpack/Rollup
工具,还可以结合其它工具如 Babel,可以将最新的 ES 语法转成低版本的语法,当然这是题外话,对 Babel 话题感兴趣的可参考:JS & 介绍 Babel 的使用及 presets & plugins 的概念由于浏览器始终得借助于 ADM
或 UMD
来进行模块化开发,官方实在看不下去,决定让浏览器内置一套模块化机制,俗称 ESM
,目的就是减少 AMD
或 UMD
的依赖实现统一规范,它与 CJS 一样,现代浏览器已经内置好了。
由于 ESM 技术较新,一些旧浏览器无法兼容,因此现在大多项目要借助 Webpack/Rollup 等构建工具将 ESM 打包成兼容的语法;也就说,我们可以在项目中写 ESM 代码,但在运行期间,代码会以兼容语法的方式来执行。
其实在前面的 UMD
章节第 7 点我们已经使用过 ESM 语法了,除了 export
导出方式,还有其它导出的方式 export default
,它可以导出完整的对象,接下来将基于此语法作为演示。
提示:浏览器使用 ESM 的前提条件是给 script 标签加上
type="module"
另外,这里我们不再借助 Rollup 构建转换工具,现代浏览器本身就支持 ESM。
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
<script type="module" src="app.js">script>
head>
<body>
<div id="app">
Hello,world
div>
body>
html>
app.js
// app.js
import tools from './modules/tools.js';
const app = () => {
console.log('Tools are:', tools)
}
app()
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. export
与 export 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()
ESM
语法已经成为现代模块化标准规范,Nodejs
从 12.0.0
版本就开始支持 ESM
,前提条件是,文件名后缀必须为 .mjs
,若想省略 .mjs
,可在 package.json
设置 type: "module"
即可;
提示:若
A.js
想引入B.mjs
,也要在package.json
设置"type": "module"
,否则 node 运行时也会报错,如果是A.mjs
引入B.mjs
则不用配置。
const getDeviceType = () => {
return 'Android'
}
const getDeviceVersion = () => {
return '1.0.0'
}
const tools = {
getDeviceType,
getDeviceVersion
}
export { getDeviceType, getDeviceVersion }
export default tools
app.js
import tools from './modules/tools.mjs'
const app = () => {
console.log('Tools are:', tools)
}
app()
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"
,但为了方便演示,这里就不变更了。
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/