JS/TS項目里的Module都是什么?


摘要:在日常進行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選項卡,將看到許多文件是按需由模塊加載器加載的:

一些流行的模塊加載器的例子如下:

  • 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

 

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM