温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

JavaScript模块化方案和工具都有哪些

发布时间:2021-09-30 16:53:20 来源:亿速云 阅读:112 作者:柒染 栏目:web开发

这篇文章给大家介绍JavaScript模块化方案和工具都有哪些,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

模块化是大型前端项目的必备要素。JavaScript 从诞生至今,出现过各种各样的模块化方案,让我们一起来盘点下吧。

IIFE 模块

默认情况下,在浏览器宿主环境里定义的变量都是全局变量,如果页面引用了多个这样的 JavaScript 文件,很容易造成命名冲突。

// 定义全局变量 let count = 0; const increase = () => ++count; const reset = () => {     count = 0;     console.log("Count is reset."); };  // 使用全局变量 increase(); reset();

为了避免全局污染,可以用匿名函数包裹起来,这就是最简单的 IIFE 模块(立即执行的函数表达式):

// 定义 IIFE 模块 const iifeCounterModule = (() => {     let count = 0;     return {         increase: () => ++count,         reset: () => {             count = 0;             console.log("Count is reset.");         }     }; })();  // 使用 IIFE 模块 iifeCounterModule.increase(); iifeCounterModule.reset();

IIFE 只暴露了一个全局的模块名,内部都是局部变量,大大减少了全局命名冲突。

每个 IIFE 模块都是一个全局变量,这些模块通常有自己的依赖。可以在模块内部直接使用依赖的全局变量,也可以把依赖作为参数传给 IIFE:

// 定义带有依赖的 IIFE 模块 const iifeCounterModule = ((dependencyModule1, dependencyModule2) => {     let count = 0;     return {         increase: () => ++count,         reset: () => {             count = 0;             console.log("Count is reset.");         }     }; })(dependencyModule1, dependencyModule2);

一些流行的库在早期版本都采用这模式,比如大名鼎鼎的 jQuery(最新版本也开始用 UMD 模块了,后面会介绍)。

还有一种 IIFE,在 API 声明上遵循了一种格式,就是在模块内部提前定义了这些 API 对应的变量,方便 API 之间互相调用:

// Define revealing module. const revealingCounterModule = (() => {     let count = 0;     const increase = () => ++count;     const reset = () => {         count = 0;         console.log("Count is reset.");     };      return {         increase,         reset     }; })();  // Use revealing module. revealingCounterModule.increase(); revealingCounterModule.reset();

CommonJS 模块(Node.js 模块)

CommonJS 最初叫 ServerJS,是由 Node.js 实现的模块化方案。默认情况下,每个 .js  文件就是一个模块,模块内部提供了一个module和exports变量,用于暴露模块的 API。使用 require  加载和使用模块。下面这段代码定义了一个计数器模块:

// 定义 CommonJS 模块: commonJSCounterModule.js. const dependencyModule1 = require("./dependencyModule1"); const dependencyModule2 = require("./dependencyModule2");  let count = 0; const increase = () => ++count; const reset = () => {     count = 0;     console.log("Count is reset."); };  exports.increase = increase; exports.reset = reset; // 或者这样: module.exports = {     increase,     reset };

使用这个模块:

// 使用 CommonJS 模块 const { increase, reset } = require("./commonJSCounterModule"); increase(); reset(); // 或者这样: const commonJSCounterModule = require("./commonJSCounterModule"); commonJSCounterModule.increase(); commonJSCounterModule.reset();

在运行时,Node.js 会将文件内的代码包裹在一个函数内,然后通过参数传递exports、module变量和require函数。

// Define CommonJS module: wrapped commonJSCounterModule.js. (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("Count is reset.");     };      module.exports = {         increase,         reset     };      return module.exports; }).call(thisValue, exports, require, module, filename, dirname);  // Use CommonJS module. (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  这个库实现。它通过define函数定义模块,并接受模块名和依赖的模块名作为参数。

// 定义 AMD 模块 define("amdCounterModule", ["dependencyModule1", "dependencyModule2"],        (dependencyModule1, dependencyModule2) => {     let count = 0;     const increase = () => ++count;     const reset = () => {         count = 0;         console.log("Count is reset.");     };      return {         increase,         reset     }; });

也用 require加载和使用模块:

require(["amdCounterModule"], amdCounterModule => {     amdCounterModule.increase();     amdCounterModule.reset(); });

跟 CommonJS 不同,这里的 requrie接受一个回调函数,参数就是加载好的模块对象。

AMD 的define函数还可以动态加载模块,只要给它传一个回调函数,并带上 require参数:

// Use dynamic AMD module. define(require => {     const dynamicDependencyModule1 = require("dependencyModule1");     const dynamicDependencyModule2 = require("dependencyModule2");      let count = 0;     const increase = () => ++count;     const reset = () => {         count = 0;         console.log("Count is reset.");     };      return {         increase,         reset     }; });

AMD 模块还可以给define传递module和exports,这样就可以在内部使用 CommonJS 代码:

// 定义带有 CommonJS 代码的 AMD 模块 define((require, exports, module) => {     // CommonJS 代码     const dependencyModule1 = require("dependencyModule1");     const dependencyModule2 = require("dependencyModule2");      let count = 0;     const increase = () => ++count;     const reset = () => {         count = 0;         console.log("Count is reset.");     };      exports.increase = increase;     exports.reset = reset; });  // 使用带有 CommonJS 代码的 AMD 模块 define(require => {     // CommonJS 代码     const counterModule = require("amdCounterModule");     counterModule.increase();     counterModule.reset(); });

UMD 模块

UMD(通用模块定义),是一种支持多种环境的模块化格式,可同时用于 AMD 和 浏览器(或者 Node.js)环境。

兼容 AMD 和浏览器全局引入:

((root, factory) => {     // 检测是否存在 AMD/RequireJS 的 define 函数     if (typeof define === "function" && define.amd) {         // 如果是,在 define 函数内调用 factory         define("umdCounterModule", ["deependencyModule1", "dependencyModule2"], factory);     } else {         // 否则为浏览器环境,直接调用 factory         // 导入的依赖是全局变量(window 对象的属性)         // 导出的模块也是全局变量(window 对象的属性)         root.umdCounterModule = factory(root.deependencyModule1, root.dependencyModule2);     } })(typeof self !== "undefined" ? self : this, (deependencyModule1, dependencyModule2) => {     // 具体的模块代码     let count = 0;     const increase = () => ++count;     const reset = () => {         count = 0;         console.log("Count is reset.");     };      return {         increase,         reset     }; });

看起来很复杂,其实就是个 IIFE。代码注释写得很清楚了,可以看看。

下面来看兼容 AMD 和 CommonJS(Node.js)模块的 UMD:

(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     }; }))(// 判断 CommonJS 里的 module 变量和 exports 变量是否存在     // 同时判断 AMD/RequireJS 的define 函数是否存在     typeof module === "object" && module.exports && typeof define !== "function"         ? // 如果是 CommonJS/Node.js,手动定义一个 define 函数             factory => module.exports = factory(require, exports, module)         : // 否则是 AMD/RequireJS,直接使用 define 函数             define);

同样是个 IIFE,通过判断环境,选择执行对应的代码。

ES 模块(ES6 Module)

前面说到的几种模块格式,都是用到了各种技巧实现的,看起来眼花缭乱。终于,在 2015 年,ECMAScript 第 6  版(ES 2015,或者 ES6 )横空出世!它引入了一种全新的模块格式,主要语法就是 import和epxort关键字。来看 ES6 怎么定义模块:

// 定义 ES 模块:esCounterModule.js 或 esCounterModule.mjs. import dependencyModule1 from "./dependencyModule1.mjs"; import dependencyModule2 from "./dependencyModule2.mjs";  let count = 0; // 具名导出: export const increase = () => ++count; export const reset = () => {     count = 0;     console.log("Count is reset."); }; // 默认导出 export default {     increase,     reset };

浏览器里使用该模块,在 script标签上加上type="module",表明引入的是 ES 模块。在 Node.js 环境中使用时,把扩展名改成  .mjs。

// Use ES module. //浏览器: <script type="module" src="esCounterModule.js"></script> or inline.  // 服务器:esCounterModule.mjs import { increase, reset } from "./esCounterModule.mjs"; increase(); reset(); // Or import from default export: import esCounterModule from "./esCounterModule.mjs"; esCounterModule.increase(); esCounterModule.reset();

浏览器如果不支持,可以加个兜底属性:

<script nomodule>     alert("Not supported."); </script>

ES 动态模块(ECMAScript 2020)

2020 年最新的 ESCMA 标准11版中引入了内置的 import函数,用于动态加载 ES  模块。import函数返回一个 Promise,在它的then回调里使用加载后的模块:

// 用 Promise API 加载动态 ES 模块 import("./esCounterModule.js").then(({ increase, reset }) => {     increase();     reset(); });  import("./esCounterModule.js").then(dynamicESCounterModule => {     dynamicESCounterModule.increase();     dynamicESCounterModule.reset(); });

由于返回的是 Promise,那肯定也支持await用法:

// 通过 async/await 使用 ES 动态模块 (async () => {     // 具名导出的模块     const { increase, reset } = await import("./esCounterModule.js");     increase();     reset();     // 默认导出的模块     const dynamicESCounterModule = await import("./esCounterModule.js");     dynamicESCounterModule.increase();     dynamicESCounterModule.reset(); })();

各平台对import、export和动态import的兼容情况如下:

JavaScript模块化方案和工具都有哪些

image.png

JavaScript模块化方案和工具都有哪些

image.png

System 模块

SystemJS 是一个 ES 模块语法转换库,以便支持低版本的 ES。例如,下面的模块是用 ES6 语法定义的:

// 定义 ES 模块 import dependencyModule1 from "./dependencyModule1.js"; import dependencyModule2 from "./dependencyModule2.js"; dependencyModule1.api1(); dependencyModule2.api2();  let count = 0; // Named export: export const increase = function () { return ++count }; export const reset = function () {     count = 0;     console.log("Count is reset."); }; // Or default export: export default {     increase,     reset }

如果当前的运行环境(比如旧浏览器)不支持 ES6 语法,上面的代码就无法运行。一种方案是把上面的模块定义转换成 SystemJS 库的一个 API,  System.register:

// Define SystemJS module. System.register(["./dependencyModule1.js", "./dependencyModule2.js"],                  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;             // Named export:             exports_1("increase", increase = function () { return ++count };             exports_1("reset", reset = function () {                 count = 0;                 console.log("Count is reset.");             };);             // Or default export:             exports_1("default", {                 increase,                 reset             });         }     }; });

这样,import/export关键字就不见了。Webpack、TypeScript 等可以自动完成这样的转换(后面会讲)。

SystemJS 也支持动态加载模块:

// Use SystemJS module with promise APIs. System.import("./esCounterModule.js").then(dynamicESCounterModule => {     dynamicESCounterModule.increase();     dynamicESCounterModule.reset(); });

Webpack 模块(打包 AMD,CJS,ESM)

Webpack 是个强大的模块打包工具,可以将 AMD、CommonJS 和 ES Module  格式的模块转换并打包到单个 JS 文件。

Babel 模块

Babel 是也个转换器,可将 ES6+ 代码转换成低版本的 ES。前面例子中的计数器模块用 Babel 转换后的代码是这样的:

// Babel. Object.defineProperty(exports, "__esModule", {     value: true }); exports["default"] = void 0; function _interopRequireDefault(obj)           { return obj && obj.__esModule ? obj : { "default": obj }; }  // Define ES module: esCounterModule.js. 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("Count is reset."); };  exports["default"] = {     increase: increase,     reset: reset };

引入该模块的index.js将会转换成:

// Babel. function _interopRequireDefault(obj)           { return obj && obj.__esModule ? obj : { "default": obj }; }  // Use ES module: index.js var esCounterModule = _interopRequireDefault(require("./esCounterModule.js")); esCounterModule["default"].increase(); esCounterModule["default"].reset();

以上是 Babel 的默认转换行为,它还可以结合其他插件使用,比如前面提到的 SystemJS。经过配置,Babel 可将 AMD、CJS、ES  Module 转换成 System 模块格式。

TypeScript 模块

TypeScript 是 JavaScript 的超集,可以支持所有 JavaScript 语法,包括 ES6  模块语法。它在转换时,可以保留 ES6 语法,也可以转换成 AMD、CJS、UMD、SystemJS 等格式,取决于配置:

{     "compilerOptions": {         "module": "ES2020", // None, CommonJS, AMD, System, UMD, ES6, ES2015, ES2020, ESNext.     } }

TypeScript 还支持 module和namespace关键字,表示内部模块。

module Counter {     let count = 0;     export const increase = () => ++count;     export const reset = () => {         count = 0;         console.log("Count is reset.");     }; }  namespace Counter {     let count = 0;     export const increase = () => ++count;     export const reset = () => {         count = 0;         console.log("Count is reset.");     }; }

都可以转换成 JavaScript 对象:

var Counter; (function (Counter) {     var count = 0;     Counter.increase = function () { return ++count; };     Counter.reset = function () {         count = 0;         console.log("Count is reset.");     }; })(Counter || (Counter = {}));

随着标准化推进,Node.js  和最新的现代浏览器都开始支持 ES 模块格式。如果要在旧环境中使用模块化,可以通过 Webpack、Babel、TypeScript、SystemJS  等工具进行转换。

关于JavaScript模块化方案和工具都有哪些就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI