JS/TS專案裡的Module都是什麼?

華為雲開發者社群發表於2022-04-15
摘要:在日常進行JS/TS專案開發的時候,經常會遇到require某個依賴和module.exports來定義某個函式的情況。就很好奇Modules都代表什麼和有什麼作用呢。

本文分享自華為雲社群《JS/TS專案裡的Module都是什麼?都有幾種形式?loaders和bundlers的區別是什麼?》,作者: gentle_zhou 。

在日常進行JS/TS專案開發的時候,經常會遇到require某個依賴和module.exports來定義某個函式的情況。再加上在日常審視程式碼的時候,發現tsconfig.json檔案裡有一個"compilerOptions",裡面關於module引入的是"commonjs",就很好奇Modules都代表什麼和有什麼作用呢。

什麼是Module?

一個Module(模組)顧名思義就是一段可以重複利用的程式碼(通常是一個特性,或則一些特性的集合;可以是一個檔案或則多個檔案/資料夾的集合),它封裝了內部程式碼實現的細節並曝露一個公開的API,讓其他程式碼可以輕易地載入和使用。

為什麼我們需要Modules?

技術上來說,其實完成一個JS/TS專案,我們並不需要模組,直接上手寫程式碼也是可以的。但就像在JAVA、Python軟體專案裡,不引入依賴一樣,會導致程式設計師們重複寫很多相同的程式碼。

引入Module,為的就是可以應對JS/TS專案的程式碼越來越龐大,越來越複雜的情形。我們需要使用軟體工程的方法,來管理JS/TS專案的業務邏輯。

在JS/TS專案中,模組應該允許我們實現以下功能:

  • 抽象程式碼:將功能委託給專門的庫,這樣我們就不必瞭解它們內部實際如何實現的(無論多複雜)
  • 封裝程式碼:如果我們不想再更改程式碼了,可以將程式碼隱藏在模組中
  • 重用程式碼:避免反覆編寫相同的程式碼
  • 管理依賴:在不重寫程式碼的情況下,輕鬆改變依賴關係

幾種常見的Module形式

在 ES6 Module 出現之前,在ES5時期,JS並沒有提供一個官方的定義模組的規則;因此JavaScript 社群裡的天才程式設計師們嘗試了各種形式來定義模組,以達到“在現有的執行環境下,可以實現模組效果”的目的。

一些非常有名的模組形式:

  • CommonJS
    CommonJS形式是用在Node.js環境裡的,我在文章開頭提到的require和module.exports就是CommonJS裡用來定義依賴和模組的:
  var dep1 = require('./dep1');  
  module.exports = function(){  // ...}
  • Asynchronous Module Definition (AMD)
    AMD(官方github連結)則是用在瀏覽器中的,顧名思義這個形式是非同步的,其中用define函式來定義模組:
  // 一個依賴陣列&一個工廠函式以引數的形式呼叫define函式
  define(['dep1', 'dep2'], function (dep1, dep2) {
  //通過返回一個值來定義模組值
  return function () {};
  });
  • Universal Module Definition (UMD)
    UMD則是可以用在瀏覽器和Node.js中,是通用的:
 (function (root, factory) {
    if (typeof define === 'function' && define.amd) {
      // AMD. 以同步模組的方式註冊.
        define(['b'], factory);
    } else if (typeof module === 'object' && module.exports) {
      // Node節點. 不能和嚴格意義上的CommonJS一起使用,但是類似CommonJS的環境裡是支援使用module.expoerts的,就像node.
      module.exports = factory(require('b'));
    } else {
      // 瀏覽器 globals (根節點是window)
      root.returnExports = factory(root.b);
    }
  }(this, function (b) {  
    // 返回一個值來定義module export;這裡返回的是一個物件,但是模組其實可以返回一個函式作為exported value.
    return {};
  }));

以及現在出現的官方ES6 模組形式,一種原生的模組形式。它用export來輸出模組的公開API:

// 輸出函式
export function sayHello(){  
  console.log('Hello');
}

我們可以使用import和as來引入部分程式碼到模組裡:

import { sayHello as say } from './lib';

say(); // 輸出Hello

或則直接在一開始引入整個模組:

import * as lib from './lib';

lib.sayHello();  // 輸出 Hello

Module loaders和Module bundlers的區別

兩者都是為了讓我們編寫模組化JS/TS應用的時候更方便快捷。

Module loaders

模組載入器用來解析並載入以特定模組格式編寫的模組,通常是一些庫;可以載入、解釋和執行使用特定模組格式/語法定義的JavaScript模組,比如AMD或Common JS。

在編寫模組化JS/TS應用程式時,通常每個模組都有一個檔案。因此,當編寫由數百個模組組成的應用程式時,要確保所有檔案都以正確的順序包含進去可能會非常痛苦。所以,如果有載入器會為你負責依賴管理,確保所有模組在應用程式執行時被載入,那會輕鬆容易很多。

模組載入器是在執行時(runtime)執行的:

  • 在瀏覽器中載入模組載入器
  • 告訴模組載入器載入哪個主應用檔案
  • 模組載入器下載並解析主應用檔案
  • 模組載入器根據需要去下載檔案

如果你試著在瀏覽器的開發人員控制檯中開啟network選項卡,將看到許多檔案是按需由模組載入器載入的:

JS/TS專案裡的Module都是什麼?

一些流行的模組載入器的例子如下:

  • Require JS: AMD格式的模組載入器
  • System JS: AMD, Common JS, UMD或System.register格式的模組載入器

Module bundlers

模組繫結器相當於是模組載入器的替代品;基本上,它們做的事情是一樣的(管理和載入相互依賴的模組)。

但模組繫結器和載入器不同的地方是,它並非是在執行時執行的,而是作為應用程式構建的一部分執行(在build的時候執行);而且它是在瀏覽器中載入的。因此,繫結器在執行程式碼之前會將所有模組合併到一個檔案/bundle中(比如叫bundle.js),而不是在程式碼執行時再去載入出現的依賴項。比如現在流行的兩個bundlers:Webpack(AMD,Common JS, es6模組的bundler)和Browserify(Common JS模組的bundler)。

什麼時候更適合用哪個呢?

這個問題的答案取決於JS/TS應用程式的結構與大小。

使用bundler的主要優點是,它讓瀏覽器需要下載的檔案變少了很多,這可以給我們的應用程式帶來效能上的優勢(因為減少了載入所需的時間);但是取決於應用程式的模組數量,並不是說用bundler就一定是最好的。對於那種大型應用(有很多模組),模組載入器可以提供更好的效能,因為bundler在一開始載入一個巨大的單檔案會阻礙應用的啟動。

如何選取,其實只需要我們進行測試比較一下即可~

參考連結

  1. https://v8.dev/features/modules
  2. https://www.geeksforgeeks.org/node-js-modules/
  3. https://www.jvandemo.com/a-10-minute-primer-to-javascript-modules-module-formats-module-loaders-and-module-bundlers/
  4. https://stackoverflow.com/questions/38864933/what-is-difference-between-module-loader-and-module-bundler-in-javascript

 

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章