朋友们,美好的一天!我向您介绍Dixin 撰写的文章“了解(所有)JavaScript模块格式和工具”。在创建应用程序时,通常希望将代码分为部分,逻辑或功能块(模块)。但是,JavaScript最初没有模块支持。这导致了各种模块化技术的出现。本文讨论了在JavaScript中使用模块的所有基本概念,模板,库,语法和工具。IIFE模块:JS模块模板
通过在JS中定义变量,我们将其定义为全局变量。这意味着该变量将在当前页面上加载的所有JS文件中可用:
let count = 0
const increase = () => ++count
const reset = () => {
count = 0
console.log(' .')
}
increase()
reset()
为了避免污染全局名称空间,可以使用匿名函数: (() => {
let count = 0
})
瞧,没有更多的全局变量了。但是,该函数内部的代码未执行。IIFE:立即函数表达
为了在函数内部执行代码f
,必须使用()
how 来调用它f()
。要在匿名函数内部执行代码,(() => {})
也应使用()
。看起来像这样(() => {})()
: (() => {
let count = 0
})()
这称为IIFE(立即称为函数表达式)。一个模块可以定义如下:
const iifeCounterModule = (() => {
let count = 0
return {
increase: () => ++count,
reset: () => {
count = 0
console.log(' .')
}
}
})()
iifeCounterModule.increase()
iifeCounterModule.reset()
我们将模块代码包装在IIFE中。匿名函数返回一个对象。这将替换导出接口。仅有一个全局变量-模块的名称(或其名称空间)。随后,可以使用模块的名称来调用它(导出)。这称为JS模块模板。进口杂质
定义模块时,可能需要一些依赖关系。使用模块化模板时,每个从属模块都是全局变量。依赖模块可以在匿名函数中定义,也可以作为参数传递给它:
const iifeCounterModule = ((dependencyModule1, dependencyModule2) => {
let count = 0
return {
increase: () => ++count,
reset: () => {
count = 0
console.log(' .')
}
}
})(dependencyModule1, dependencyModule2)
流行的库(例如jQuery)的早期版本使用了此模板(jQuery的最新版本使用了UMD模块)。打开模块:打开JS模块模板
开放模块模板由Christian Heilmann创造。该模板也是IIFE,但是重点在于将所有接口定义为匿名函数内的局部变量:
const revealingCounterModule = (() => {
let count = 0
const increase = () => ++count
const reset = () => {
count = 0
console.log(' .')
}
return {
increase,
reset
}
})()
revealingCounterModule.increase()
revealingCounterModule.reset()
此语法使您更容易理解每个接口负责什么(或其作用)。CJS模块:CommonJS模块或Node.js模块
CommonJS(最初称为ServerJS)是用于定义和使用模块的模板。它内置在Node.js中。默认情况下,每个JS文件都是一个CJS。变量module
还exports
提供模块(文件)的导出。该功能require
提供了加载和使用模块的功能。以下代码演示了CommonJS语法中计数器模块的定义:
const dependencyModule1 = require('./dependencyModule1')
const dependencyModule2 = require('./dependencyModule2')
let count = 0
const increase = () => ++count
const reset = () => {
count = 0
console.log(' .')
}
exports.increase = increase
exports.reset = reset
module.exports = {
increase,
reset
}
这是使用此模块的方式:
const {
increase,
reset
} = require('./commonJSCounterModule')
increase()
reset()
const commonJSCounterModule = require('./commonJSCounterModule')
commonJSCounterModule.increase()
commonJSCounterModule.reset()
在Node.js运行时(引擎)中,通过将文件内的代码包装到一个函数中来使用此模板,变量exports, module
和函数作为参数传递给该函数require
:
(function(exports, require, module, __filename, __dirname) {
const dependencyModule1 = require('./dependencyModule1')
const dependencyModule2 = require('./dependencyModule2')
let count = 0
const increase = () => ++count
const reset = () => {
count = 0
console.log(' .')
}
module.exports = {
increase,
reset
}
return module.exports
}).call(thisValue, exports, require, module, filename, dirname)
(function(exports, require, module, __filename, __dirname) {
const commonJSCounterModule = require('./commonJSCounterModule')
commonJSCounterModule.increase()
commonJSCounterModule.reset()
}).call(thisValue, exports, require, module, filename, dirname)
AMD模块或RequireJS模块
AMD(异步模块定义)是用于定义和使用模块的模板。它在RequireJS库中使用。AMD包含一个define
模块定义函数,该函数接受模块名称,依赖项名称和工厂功能:
define('amdCounterModule', ['dependencyModule1', 'dependencyModule2'], (dependencyModule1, dependencyModule2) => {
let count = 0
const increase = () => ++count
const reset = () => {
count = 0
console.log(' .')
}
return {
increase,
reset
}
})
它还包含require
使用模块的功能:
require(['amdCounterModule'], amdCounterModule => {
amdCounterModule.increase()
amdCounterModule.reset()
})
require
AMD与require
CommonJS的不同之处在于,它以模块的名称和模块本身作为函数的参数。动态加载
功能define
也有不同的用途。它需要一个回调函数,并将类似于CommonJS的require
函数传递给该函数。在回调函数中,调用require来动态加载模块:
define(require => {
const dynamicDependencyModule1 = require('dependencyModule1')
const dynamicDependencyModule2 = require('dependencyModule2')
let count = 0
const increase = () => ++count
const reset = () => {
count = 0
console.log(' .')
}
return {
increase,
reset
}
})
来自CommonJS模块的AMD模块
上述功能define
,此外require
,可以采取的变量exports
和作为参数module
。因此,define
来自CommonJS的代码可以在内部执行:
define((require, exports, module) => {
const dependencyModule1 = require('dependencyModule1')
const dependencyModule2 = require('dependencyModule2')
let count = 0
const increase = () => ++count
const reset = () => {
count = 0
console.log(' .')
}
exports.increase = increase
exports.reset = reset
})
define(require => {
const counterModule = require('amdCounterModule')
counterModule.increase()
counterModule.reset()
})
UMD模块:通用模块定义或UmdJS模块
UMD(通用模块定义)-一组模板,用于确保模块在不同运行时环境中的运行。适用于AMD的UMD(RequireJS)和浏览器
以下代码在AMD(RequireJS)和浏览器中都提供了该模块:
((root, factory) => {
if (typeof define === 'function' && define.amd) {
define('umdCounterModule', ['dependencyModule1', 'dependencyModule2'], factory)
} else {
root.umdCounterModule = factory(root.dependencyModule1, root.dependencyModule2)
}
})(typeof self !== undefined ? self : this, (dependencyModule1, dependencyModule2) => {
let count = 0
const increase = () => ++count
const reset = () => {
count = 0
console.log(' ')
}
return {
increase,
reset
}
})
看起来很复杂,但这只是IIFE。匿名函数确定是否有define
来自AMD / RequireJS 的函数。- 如果
define
检测到,则通过它调用工厂函数。 - 如果
define
未找到,则直接调用工厂函数。此时,参数root
是浏览器的Window对象。它从全局变量(Window对象的属性)接收从属模块。当factory
模块返回时,它也成为全局变量(Window对象的属性)。
适用于AMD(RequireJS)和CommonJS(Node.js)的UMD
以下代码在AMD(RequireJS)和CommonJS(Node.js)中提供了该模块: (define => define((require, exports, module) => {
const dependencyModule1 = require("dependencyModule1")
const dependencyModule2 = require("dependencyModule2")
let count = 0
const increase = () => ++count
const reset = () => {
count = 0
console.log("Count is reset.")
}
module.export = {
increase,
reset
}
}))(
typeof module === 'object' && module.exports && typeof define !== 'function'
?
factory => module.exports = factory(require, exports, module)
:
define)
别担心,这只是IIFE。调用匿名函数时,其参数为“求值”。评估参数以确定执行环境(由变量module
和exports
CommonJS / Node.js 的存在以及define
AMD / RequireJS 的功能定义)。- 如果运行时是CommonJS / Node.js,则匿名函数参数将手动创建function
define
。 - 如果运行时是AMD / RequireJS,则匿名函数的参数是
define
该环境中的函数。执行匿名功能可确保该功能正常工作define
。在匿名函数内部,将调用一个函数来创建模块define
。
ES模块:ECMAScript2015或ES6模块
2015年,JS规范的版本6引入了新的模块化语法。这称为ECMAScript 2015(ES2015)或ECMAScript 6(ES6)。新语法的基础是关键字import
and export
。以下代码演示了将ES模块用于命名和默认(默认)导入/导出的用法:
import dependencyModule1 from './dependencyModule1.mjs'
import dependencyModule2 from './dependencyModule2.mjs'
let count = 0
export const increase = () => ++count
export const reset = () => {
count = 0
console.log(' .')
}
export default {
increase,
reset
}
要在浏览器中使用模块文件,请添加标签<script>
并将其标识为模块:<script type="module" src="esCounterModule.js"></script>
。要在Node.js中使用此模块,请将其扩展名更改为.mjs
:
import {
increase,
reset
} from './esCounterModule.mjs'
increase()
reset()
import esCounterModule from './esCounterModule.mjs'
esCounterModule.increase()
esCounterModule.reset()
为了在浏览器中向后兼容,您可以添加<script>
带有属性的标签nomodule
: <脚本nomodule>
警报(“不支持。”)
</ script>
ES动态模块:ECMAScript2020或ES11动态模块
JS 2020规范的最新11版本引入了import
用于动态使用ES模块的内置功能。该函数返回一个Promise,因此您可以将模块用于then
:
import('./esCounterModule.js').then(({
increase,
reset
}) => {
increase()
reset()
})
import('./esCounterModule.js').then(dynamicESCounterModule => {
dynamicESCounterModule.increase()
dynamicESCounterModule.reset()
})
由于该函数import
返回promise,因此可以使用关键字await
:
(async () => {
const {
increase,
reset
} = await import('./esCounterModule.js')
increase()
reset()
const dynamicESCounterModule = await import('./esCounterModule.js')
dynamicESCounterModule.increase()
dynamicESCounterModule.reset()
})
系统模块:SystemJS模块
SystemJS是一个库,用于在旧版浏览器中支持ES模块。例如,以下模块是使用ES6语法编写的:
import dependencyModule1 from "./dependencyModule1.js"
import dependencyModule2 from "./dependencyModule2.js"
dependencyModule1.api1()
dependencyModule2.api2()
let count = 0
export const increase = function() {
return ++count
}
export const reset = function() {
count = 0
console.log("Count is reset.")
}
export default {
increase,
reset
}
该代码在不支持ES6语法的浏览器中不起作用。解决此问题的一种方法是使用System.register
SystemJS库接口转换代码:
System.register(['./dependencyModule1', './dependencyModule2'], function(exports_1, context_1) {
'use strict'
var dependencyModule1_js_1, dependencyModule2_js_1, count, increase, reset
var __moduleName = context_1 && context_1.id
return {
setters: [
function(dependencyModule1_js_1_1) {
dependencyModule1_js_1 = dependencyModule1_js_1_1
},
function(dependencyModule2_js_1_1) {
dependencyModule2_js_1 = dependencyModule2_js_1_1
}
],
execute: function() {
dependencyModule1_js_1.default.api1()
dependencyModule2_js_1.default.api2()
count = 0
exports_1('increase', increase = function() {
return ++count
})
exports_1('reset', reset = function() {
count = 0
console.log(' .')
})
exports_1('default', {
increase,
reset
})
}
}
})
新的模块化ES6语法已消失。但是该代码在较旧的浏览器中可以正常工作。可以使用Webpack,TypeScript等自动完成此转换。动态模块加载
SystemJS还包含import
用于动态导入的功能:
System.import('./esCounterModule.js').then(dynamicESCounterModule => {
dynamicESCounterModule.increase()
dynamicESCounterModule.reset()
})
Webpack模块:CJS,AMD和ES模块的编译和组装
Webpack是一个模块构建器。它的编译器将CommonJS,AMD和ES模块组合到一个平衡的模块化模板中,并将所有代码收集到一个文件中。例如,在以下3个文件中,使用不同的语法定义了3个模块:
define('amdDependencyModule1', () => {
const api1 = () => {}
return {
api1
}
})
const dependencyModule2 = require('./commonJSDependencyModule2')
const api2 = () => dependencyModule1.api1()
exports.api2 = api2
import dependencyModule1 from './amdDependencyModule1'
import dependencyModule2 from './commonJSDependencyModule2'
let count = 0
const increase = () => ++count
const reset = () => {
count = 0
console.log(' .')
}
export default {
increase,
reset
}
以下代码演示了此模块的用法:
import counterModule from './esCounterModule'
counterModule.increase()
counterModule.reset()
Webpack能够将这些文件(尽管它们是不同的模块化系统)组合到一个文件中main.js
: 根
dist
main.js(位于src文件夹中的文件的汇编)
src
amdDependencyModule1.js
commonJSDependencyModule2.js
esCounterModule.js
index.js
webpack.config.js
由于Webpack基于Node.js,因此它使用CommonJS的模块化语法。在webpack.config.js
: const path = require('path')
module.exports = {
entry: './src/index.js',
mode: 'none',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
},
}
要编译和构建,必须运行以下命令: npm安装webpack webpack-cli --save-dev
npx webpack --config webpack.config.js
结果,Webpack将创建文件main.js
。main.js
格式化以下代码以提高可读性: (function(modules) {
var installedModules = {}
function require(moduleId) {
if (installedModules[moduleId]) {
return installedModules[moduleId].exports
}
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
}
modules[moduleId].call(module.exports, module, module.exports, require)
module.l = true
return module.exports
}
require.m = modules
require.c = installedModules
require.d = function(exports, name, getter) {
if (!require.o(exports, name)) {
Object.defineProperty(exports, name, {
enumerable: true,
get: getter
})
}
}
require.r = function(exports) {
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, {
value: 'Module'
})
}
Object.defineProperty(exports, '__esModule', {
value: true
})
}
require.t = function(value, mode) {
if (mode & 1) value = require(value)
if (mode & 8) return value
if ((mode & 4) && typeof value === 'object' && value && value.__esModule) return value
var ns = Object.create(null)
require.r(ns)
Object.defineProperty(ns, 'default', {
enumerable: true,
value: value
})
if (mode & 2 && typeof value !== 'string')
for (var key in value) require.d(ns, key, function(key) {
return value[key]
}.bind(null, key))
return ns
}
require.n = function(module) {
var getter = module && module.__esModule ?
function getDefault() {
return module['default']
} :
function getModuleExports() {
return module
}
require.d(getter, 'a', getter)
return getter
}
require.o = function(object, property) {
return Object.prototype.hasOwnProperty.call(object, property)
}
require.p = ''
return require(require.s = 0)
})([
function(module, exports, require) {
'use strict'
require.r(exports)
var esCounterModule = require(1)
esCounterModule['default'].increase()
esCounterModule['default'].reset()
},
function(module, exports, require) {
'use strict'
require.r(exports)
var amdDependencyModule1 = require.n(require(2))
var commonJSDependencyModule2 = require.n(require(3))
amdDependencyModule1.a.api1()
commonJSDependencyModule2.a.api2()
let count = 0
const increase = () => ++count
const reset = () => {
count = 0
console.log(' .')
}
exports['default'] = {
increase,
reset
}
},
function(module, exports, require) {
var result!(result = (() => {
const api1 = () => {}
return {
api1
}
}).call(exports, require, exports, module),
result !== undefined && (module.exports = result))
},
function(module, exports, require) {
const dependencyModule1 = require(2)
const api2 = () => dependencyModule1.api1()
exports.api2 = api2
}
])
同样,这就是IIFE。4个文件中的代码将转换为4个函数的数组。并将此数组作为参数传递给匿名函数。Babel模块:ES模块的翻译
Babel是另一个在较旧的浏览器中支持ES6 +代码的传输器。可以将上述ES6 +模块转换为Babel模块,如下所示:
Object.defineProperty(exports, '__esModule', {
value: true
})
exports['default'] = void 0
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {
'default': obj
}
}
var dependencyModule1 = _interopRequireDefault(require('./amdDependencyModule1'))
var dependencyModule2 = _interopRequireDefault(require('./commonJSDependencyModule2'))
dependencyModule1['default'].api1()
dependencyModule2['default'].api2()
var count = 0
var increase = function() {
return ++count
}
var reset = function() {
count = 0
console.log(' .')
}
exports['default'] = {
increase: increase,
reset: reset
}
以下是中的代码index.js
,演示了此模块的用法:
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {
'default': obj
}
}
var esCounterModule = _interopRequireDefault(require('./esCounterModule.js'))
esCounterModule['default'].increase()
esCounterModule['default'].reset()
这是默认的转译。Babel还知道如何与其他工具一起使用。Babel和SystemJS
SystemJS可以用作Babel的插件: npm install --save-dev @ babel /插件转换模块-systemjs
该插件应添加到babel.config.json
: {
'plugins':['@ babel / plugin-transform-modules-systemjs'],
'预设':[
[
'@ babel / env',
{
“目标”:{
'ie':'11'
}
}
]
]
}
现在,Babel可以与SystemJS一起移植CommonJS / Node.js,AMD / RequireJS和ES模块: npx babel src --out-dir库
结果: 根
LIB
amdDependencyModule1.js(使用SystemJS进行翻译)
commonJSDependencyModule2.js(使用SystemJS编译)
esCounterModule.js(使用SystemJS进行翻译)
index.js(使用SystemJS编译)
src
amdDependencyModule1.js
commonJSDependencyModule2.js
esCounterModule.js
index.js
babel.config.json
AMD,CommonJS和ES模块的整个语法都转换为SystemJS语法:
System.register([], function(_export, _context) {
'use strict'
return {
setters: [],
execute: function() {
define('amdDependencyModule1', () => {
const api1 = () => {}
return {
api1
}
})
}
}
})
System.register([], function(_export, _context) {
'use strict'
var dependencyModule1, api2
return {
setters: [],
execute: function() {
dependencyModule1 = require('./amdDependencyModule1')
api2 = () => dependencyModule1.api1()
exports.api2 = api2
}
}
})
System.register(['./amdDependencyModule1', './commonJSDependencyModule2'], function(_export, _context) {
var dependencyModule1, dependencyModule2, count, increase, reset
return {
setters: [function(_amdDependencyModule) {
dependencyModule1 = _amdDependencyModule.default
}, function(_commonJSDependencyModule) {
dependencyModule2 = _commonJSDependencyModule.default
}],
execute: function() {
dependencyModule1.api1()
dependencyModule1.api2()
count = 0
increase = () => ++count
reset = () => {
count = 0
console.log(' .')
}
_export('default', {
increase,
reset
})
}
}
})
System.register(['./esCounterModule'], function(_export, _context) {
var esCounterModule
return {
setters: [function(_esCounterModule) {
esCounterModule = _esCounterModule.default
}],
execute: function() {
esCounterModule.increase()
esCounterModule.reset()
}
}
})
TypeScript模块:CJS,AMD,ES和SystemJS模块的转译
TypeScript支持所有形式的JS语法,包括ES6。期间transpilation,所述ES6模块的语法可以保存或转换为另一种格式,包括CommonJS的/ Node.js的,AMD / RequireJS,UMD / UmdJS或SystemJS根据transpilation中的设置tsconfig.json
: {
'compilerOptions':{
'module':'ES2020'//无,CommonJS,AMD,System,
UMD,ES6,ES2015,ESNext
}
}
例如:
import dependencyModule from './dependencyModule'
dependencyModule.api()
let count = 0
export const increase = function() {
return ++count
}
var __importDefault = (this && this.__importDefault) || function(mod) {
return (mod && mod.__esModule) ? mod : {
'default': mod
}
}
exports.__esModule = true
var dependencyModule_1 = __importDefault(require('./dependencyModule'))
dependencyModule_1['default'].api()
var count = 0
exports.increase = function() {
return ++count
}
var __importDefault = (this && this.__importDefault) || function(mod) {
return (mod && mod.__esModule) ? mod : {
'default': mod
}
}
define(['require', 'exports', './dependencyModule'], function(require, exports, dependencyModule_1) {
'use strict'
exports.__esModule = true
dependencyModule_1 = __importDefault(dependencyModule_1)
dependencyModule_1['default'].api()
var count = 0
exports.increase = function() {
return ++count
}
})
var __importDefault = (this & this.__importDefault) || function(mod) {
return (mod && mod.__esModule) ? mod : {
'default': mod
}
}
(function(factory) {
if (typeof module === 'object' && typeof module.exports === 'object') {
var v = factory(require, exports)
if (v !== undefined) module.exports = v
} else if (typeof define === 'function' && define.amd) {
define(['require', 'exports', './dependencyModule'], factory)
}
})(function(require, exports) {
'use strict'
exports.__esModule = true
var dependencyModule_1 = __importDefault(require('./dependencyModule'))
dependencyModule_1['default'].api()
var count = 0
exports.increase = function() {
return ++count
}
})
System.register(['./dependencyModule'], function(exports_1, context_1) {
'use strict'
var dependencyModule_1, count, increase
car __moduleName = context_1 && context_1.id
return {
setters: [
function(dependencyModule_1_1) {
dependencyModule_1 = dependencyModule_1_1
}
],
execute: function() {
dependencyModule_1['default'].api()
count = 0
exports_1('increase', increase = function() {
return ++count
})
}
}
})
TypeScript支持的模块化ES语法称为外部模块。内部模块和命名空间
TypeScript也有关键字module
和namespace
。它们被称为内部模块: module Counter {
let count = 0
export const increase = () => ++count
export const reset = () => {
count = 0
console.log(' .')
}
}
namespace Counter {
let count = 0
export const increase = () => ++count
export const reset = () => {
count = 0
console.log(' .')
}
}
两者都转置为JS对象: var Counter;
(function(Counter) {
var count = 0
Counter.increase = function() {
return ++count
}
Counter.reset = function() => {
count = 0
console.log(' .')
}
})(Counter || (Counter = {}))
TypeScript模块和名称空间可以通过分隔符具有多个嵌套级别.
: module Counter.Sub {
let count = 0
export const increase = () => ++count
}
namespace Counter.Sub {
let count = 0
export const increase = () => ++count
}
子模块和子命名空间被转换为对象属性: var Counter;
(function(Counter) {
var Sub;
(function(Sub) {
var count = 0
Sub.increase = function() {
return ++count
}
})(Sub = Counter.Sub || (Counter.Sub = {}))
})(Counter || (Counter = {}))
TypeScript模块和名称空间也可以在语句中使用export
: module Counter {
let count = 0
export module Sub {
export const increase = () => ++count
}
}
module Counter {
let count = 0
export namespace Sub {
export const increase = () => ++count
}
}
上面的代码还转换为子模块和子命名空间: var Counter;
(function(Counter) {
var count = 0
var Sub;
(function(Sub) {
Sub.increase = function() {
return ++count
}
})(Sub = Counter.Sub || (Counter.Sub = {}))
})(Counter || (Counter = {}))
结论
欢迎使用JS,它有10多种系统/调制格式/名称空间:- IIFE模块:JS模块模板
- 打开模块:打开JS模块模板
- CJS模块:CommonJS模块或Node.js模块
- AMD模块:异步模块定义或RequireJS模块
- UMD模块:通用模块定义或UmdJS模块
- ES模块:ECMAScript2015或ES6模块
- ES动态模块:ECMAScript2020或ES11动态模块
- 系统模块:SystemJS模块
- Webpack模块:CJS,AMD和ES模块的编译和组装
- Babel模块:ES模块的翻译
- TypeScript模块和名称空间
幸运的是,JS当前具有标准的内置工具,可与Node.js和所有现代浏览器支持的模块一起使用。对于较旧的浏览器,您可以使用新的模块化ES语法,并使用Webpack / Babel / SystemJS / TypeScript将其转换为兼容的语法。感谢您的时间。我希望它花得很好。