Released 2023-07-14

Modified 2023-07-14

Tech

3189Words (16min read)

深入理解 Webpack:JavaScript 的模組化

Title Image

Photo by Kama on Webpack

即日起(2023/07/14)正式移除自我規範的「前言、正文與後記」文章格式啦!學習如何更好的開頭與結尾 紀錄一下

導言 - Lead paragraph

在軟體開發的世界中,程式碼的可重用性和組織性是非常重要的。

當我們在開發大型應用程式時,我們需要能夠有效地管理和維護程式碼,並且讓不同的程式碼模塊之間能夠互相協作。

而這就是模組化編程的概念所在。

在本文中,我們將探討 JavaScript 中的模組化編程,並提供一些範例來說明這個概念,而最後會延伸回 Webpack。

模組化是什麼?

模組化編程是一種使程式碼更有結構並提高重用性的編程方式,也是一種重要的程式設計概念。

它將程式碼分解為小的、可重用且獨立的模組/單元,每個模組/單元負責一部分的功能或任務,並將這些模組/單元組合在一起構成完整的應用程式。

統整只看一句話進行理解的話,也就是:

「模組化編程將程式碼分成獨立且可重用的模組」

這樣的結構性安排可以提高程式碼的可讀性、可維護性和可擴展性,等同於開發者能夠更容易地管理、測試和維護程式碼。

這些模塊可以在程式碼中的不同位置引入和使用,從而實現程式碼的組合和協作。

模組化編程的好處

  1. 組織性
    將程式碼分割成模塊,每個模塊都有明確的職責和功能,使程式碼更易於理解和維護。
  2. 可重用性
    模塊化的程式碼可以被多個地方重複使用,從而減少了程式碼的重寫量,提高了開發效率。
  3. 作用域隔離
    每個模塊都有自己的作用域,模組中的變數和函數在模組外部是不可見的,這樣可以避免命名衝突和全局作用域的污染。
  4. 加載優化
    模塊化的程式碼可以按需加載,從而減少了初始加載時間和資源消耗。

JavaScript 模組化的方式

在 JavaScript 中,有多種方式可以實現模組化編程。

以下是其中一些常用的方法:

命名空間模式(Namespace Pattern)

使用全局對象來創建命名空間,將相關的函數、變數和對象添加到該命名空間中。 這樣可以避免全局作用域的污染,並將相關的程式碼組織在一起。

簡要說明:

  • 將相關的功能封裝在一個全局對象中,以避免命名衝突。
  • 使用命名空間來組織模組。
var MyModule = MyModule || {};

MyModule.func1 = function() {
  // 函數實現
};

MyModule.func2 = function() {
  // 函數實現
};

var MyNamespace = {
  module1: {
    // 模組 1 相關功能
  },
  module2: {
    // 模組 2 相關功能
  },
  // ...
};
自執行函數模式(Immediately Invoked Function Expression,IIFE)
  • 使用自執行函數創建模組的私有作用域,防止變數污染。
  • 將模組的公共介面返回,以便其他程式碼可以訪問。
var MyModule = (function() {
  var privateVar = '私有變數';

  function privateFunc() {
    // 私有函數實現
  }

  function publicFunc() {
    // 公開函數實現
  }

  return {
    publicVar: '公共變數',
    publicFunc: publicFunc
  };
})();

MyModule.publicFunc();

看不懂?沒關係或許結合下方,兩個例子一起看會比較好理解:

var MyModule = (function() {
  // 私有變數和函數
  var privateVar = '私有變數';

  function privateFunc() {
    console.log('私有函數');
  }

  // 導出的公共介面
  return {
    publicMethod: function() {
      console.log('公共方法');
    },
    publicVar: '公共變數'
  };
})();
CommonJS
  • 用於服務器端 JavaScript 的模組化規範。
  • 使用 requiremodule.exports 實現模組的引入和導出。
  • 在伺服器端 JavaScript (Node.js) 中常見,支援同步模組載入。
// module.js
var privateVar = '私有變數';

function privateFunc() {
  // 私有函數實現
}

function publicFunc() {
  // 公開函數實現
}

module.exports = {
  publicVar: '公共變數',
  publicFunc: publicFunc
};

// main.js
var MyModule = require('./module.js');
MyModule.publicFunc();

再簡單一點的使用範例:

// 模組定義
var module1 = require("./module1");
var module2 = require("./module2");

// 模組導出
module.exports = {
  // ...
};
ES 模組(ES Modules, ESM)
  • 官方內建於現代 JavaScript 的模組系統,是 ECMAScript 的官方標準。
  • 使用 importexport 關鍵字進行模組的引入和導出。
  • 可以在瀏覽器和伺服器端使用,並逐漸取代了其他模組化編程方法。
// module.js
var privateVar = '私有變數';

function privateFunc() {
  // 私有函數實現
}

export function publicFunc() {
  // 公開函數實現
}

// main.js
import { publicFunc } from './module.js';
publicFunc();

一時轉不過來?看看下方直接使用的例子:

// module.js
// 模組導出
export function myFunc() {
  // ...
}

// main.js
// 模組引入
import { myFunc } from "./myModule";
AMD(Asynchronous Module Definition)
  • 是一個在瀏覽器中運行的非同步模組載入的規範。
  • 通常與 RequireJS 搭配使用。
  • 使用 define 函數定義模組,並使用 require 函數進行模組的引入。
// module.js
define(['dependency1', 'dependency2'], function(dep1, dep2) {
  // 模組實現
  return {
    // 導出的功能
  };
});

// main.js
require(['module'], function(module) {
  // 使用模組
});
RequireJS
  • 基於 AMD 規範。
  • 提供了一種簡潔的方式來定義和使用模組。
  • 支援非同步加載模組,避免阻塞瀏覽器,並支援動態模組的載入。
  • 可以自動解析模組之間的依賴關係,確保模組按正確的順序加載。
  • 支援模組打包和優化,減少請求數量和加載時間。
  • 可以在瀏覽器環境中使用,並與其他 AMD 模組配合使用。
// module.js
// 模組定義
define(["dependency1", "dependency2"], function(dep1, dep2) {
  // 模組實現
  return {
    // 導出的功能
  };
});

// main.js
// 模組引入
require(["module"], function(module) {
  // 使用模組
});

UMD(Universal Module Definition)
  • 是一種通用的模組定義規範,可以在瀏覽器和伺服器端運行。
  • 支援多種模組系統,包括 AMD、CommonJS 和全局變數等。
  • 通常用於可以在不同環境中運行的模組,並保持一致的介面。
(function(root, factory) {
  if (typeof define === "function" && define.amd) {
    // 支援 AMD
    define(["dependency1", "dependency2"], factory);
  } else if (typeof exports === "object" && typeof module === "object") {
    // 支援 CommonJS
    module.exports = factory(require("dependency1"), require("dependency2"));
  } else {
    // 全局變數
    root.MyModule = factory(root.Dependency1, root.Dependency2);
  }
})(this, function(dep1, dep2) {
  // 模組實現
  return {
    // 導出的功能
  };
});
SystemJS
  • 是一個通用的模組載入器,支援多種模組規範,包括 AMD、CommonJS、ES 模組等。
  • 可以在瀏覽器和伺服器端運行,並具有動態載入和非同步載入的能力。
  • 用於實現模組的載入和執行。
// 模組引入
System.import("module").then(function(module) {
  // 使用模組
});

// 模組定義
System.register("module", ["dependency1", "dependency2"], function(exports, dep1, dep2) {
  // 模組實現
  return {
    // 導出的功能
  };
});

模組化編程的範例

現在假設我們正在開發一個電子商務網站,需要處理用戶購物車的相關功能。

我們可以將這些功能組織成不同的模組,並根據其職責和功能對其進行命名。

// cart.js
const Cart = {
  items: [],
  addItem: function(item) {
    this.items.push(item);
    console.log("Added item to cart:", item);
  },
  removeItem: function(item) {
    const index = this.items.indexOf(item);
    if (index !== -1) {
      this.items.splice(index, 1);
      console.log("Removed item from cart:", item);
    }
  },
  getTotalPrice: function() {
    let totalPrice = 0;
    for (const item of this.items) {
      totalPrice += item.price;
    }
    return totalPrice;
  }
};

export default Cart;

於上方例子,我們建立惹一個名為 Cart 的模組,該模組負責處理購物車相關的功能。

它包含了一個項目列表 items,以及添加項目、移除項目和計算總價的方法。

// product.js
const Product = {
  name: "Example Product",
  price: 10.99
};

export default Product;

另外我們還建立了一個名為 Product 的模組,該模組代表一個產品。

每個產品都有自己的名稱和價格等屬性。

模組的使用與重用

延續上方,我們可以在其他地方使用這些模組,並且輕鬆地重用它們。

// main.js
import Cart from "./cart.js";
import Product from "./product.js";

const item1 = { name: "Product 1", price: 9.99 };
const item2 = { name: "Product 2", price: 14.99 };

Cart.addItem(item1);
Cart.addItem(item2);
console.log("Total price:", Cart.getTotalPrice());

console.log("Product name:", Product.name);
console.log("Product price:", Product.price);

上方我們先從 cart.js 引入了 Cart 模組,並使用它的方法來操作購物車。

同樣地,我們從 product.js 引入了 Product 模組,並取得了產品的屬性。

這種模組化的結構使得程式碼更具組織性和可讀性,我們可以獨立地開發和測試每個模組,並在需要時重複使用它們。

模組化編程與 Webpack 的應用

前情提要:Webpack 基礎是什麼?
深入理解 Webpack:重要概念統整與基本配置

簡潔提及一下,Webpack 讓能夠使用現代的模組化語法,例如 ES 模組,並將它們進行打包。

透過 Webpack 可以輕鬆地管理模組之間的相依性、解析模組路徑、自動載入模組等。

若將上面模組化編程的範例進行重寫類似的:

// cart.js
import { addItem, removeItem, getTotalPrice } from "./cartUtils.js";

const item1 = { name: "Product 1", price: 9.99 };
const item2 = { name: "Product 2", price: 14.99 };

addItem(item1);
addItem(item2);
console.log("Total price:", getTotalPrice());
// cartUtils.js
export function addItem(item) {
  // 加入購物車的邏輯
}

export function removeItem(item) {
  // 移除購物車中的項目的邏輯
}

export function getTotalPrice() {
  // 計算購物車總價的邏輯
}

上方例子中,使用了 ES 模組語法來定義 cartUtils.js 模組,並將其中的方法導入到 cart.js 模組中進行使用。

Webpack 可以將這些模組進行打包,並生成一個或多個最終的 JavaScript 檔案。

對比 Webpack 與其他常見的模組化編程的方法

Webpack 模組化編程其他模組化編程方法
工具和功能提供強大的打包、優化和擴展功能可能較為基本,需使用其他工具進行支援
資源管理可以處理 JavaScript、CSS、圖像等各種資源較少內建的資源管理功能
模組解析可以解析不同類型的模組,並處理相依性可能需要手動處理模組的相依性
開發效率提供自動化打包和開發伺服器等功能,提高開發效率需要手動進行打包和其他開發任務
社群支援擁有龐大的社群和生態系統,提供大量的插件和資源社群支援較少,可能較難找到適合的插件和資源
廣泛應用在現代前端開發中廣泛使用,支援各種框架和工具使用情況相對較少,可能在特定領域或專案中應用

結論 - Concluding

模組化編程是提高程式碼可重用性和組織性的一種重要方法,它可以將程式碼拆分為獨立的模組,每個模組都負責特定的功能。

通過將程式碼組織成模組,可提高程式碼的可讀性、可維護性和可擴展性,且模組的重用性使我們能夠在不同的場景中輕鬆地重用程式碼,並維護程式碼的一致性和品質。

而在現代 JavaScript 開發中,Webpack 的出現完美地解決了這些問題,並為模組化編程提供了強大的工具和功能。

Webpack 的模組打包和擴展功能使得我們能夠輕鬆地管理和組織模組化的程式碼,同時提供了優化和優化載入的能力。

除了打包功能,Webpack 還提供了各種豐富的插件和工具,可以處理 JavaScript、CSS、圖像等各種資源,使開發者能夠更高效地開發和維護應用程式。Webpack 的模組化編程能力與現代前端框架(如React、Angular、Vue等)以及其他工具和技術的整合,使得開發更具彈性和擴展性。

如果還沒有使用過 Webpack,強烈建議學習並導入它到專案中。透過 Webpack 的模組化編程,可以更好地組織和管理程式碼,並享受到它帶來的開發效率和維護性的提升。

官方文件也都蠻清楚明瞭的,可以看下方的參考資料了解更多。


結尾老話,想了解更多或是資訊版本錯誤,一切以官方檔案為主YO

這只是筆記!筆記!筆記!因為很重要所以說三次

參考資料 - Reference

[1] JavaScript modules - JavaScript | MDN

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules

[2] JavaScript Modules - W3Schools

https://www.w3schools.com/js/js_modules.asp

[3] Modules | webpack

https://webpack.js.org/concepts/modules/

深入理解 Webpack:JavaScript 的模組化

https:///en/blog/79

Author

Kama

Read more from Tech
lightbox-image
Toggle Button - Red Pandas Icons by svgrepo.com

Copyrights © 2023 Kama, All Rights Reserved.