commonjs

jiang_xin_yu發表於2024-04-17

Commonjs

什麼是 CommonJs

  1. CommonJs 是 js 模組化的社群規範

模組化產生的原因

  1. 隨著前端頁面複雜度的提升,依賴的第三方庫的增加,導致的 js 依賴混亂,全域性變數的汙染,和命名衝突
  2. 單個 js 檔案內容太多,導致了維護困難,拆分成為多個檔案又會發生第一點描述的問題
  3. v8 引擎的出現,讓 js 有了媲美編譯型語言的執行速度,大大激勵了前端開發者

CommonJS 的使用環境

  1. nodejs 實現了 CommonJS 模組化規範

CommonJs 有哪些規定

  1. 每一個檔案就是一個模組
  2. 模組中的變數和函式不會汙染全域性(解決了全域性汙染和命名衝突)
  3. 提供給外部使用的內容需要匯出
  4. 使用其他模組的內容需要匯入 (模組的匯入和匯出共同解決了 js 依賴混亂的問題)
  5. 模組不會重複載入,模組第一次匯入後會將第一次匯入的結果快取,下次匯入時,直接使用快取的結構
  6. 省略一些細碎的內容在下面程式碼中提及.....

commonJS 語法

  1. 匯入
//這是匯入一個模組,module.js;commonjs中規定require匯入模組時可以省略.js字尾
const module1 = require("./module1");
//如果沒有尋找到dir.js檔案,而發現了dir路徑,則尋找dir路徑下package.json 的main屬性指定的檔案
//如果package.json未指定路徑,則觸發預設規則 依次查詢查詢 index.js index.json
const module2 = require("./dir");
//如果require不是相對路徑,則會去node_module中尋找該模組,重複module1 和module2 的步驟
//如果沒有node_modules 或node_modules 中不存在模組則繼續向上級目錄尋找node_modules,直到根目錄
const module3 = require("module3");
  1. 匯出
module.exports = {
  //這裡輸入匯出的內容
};

//這也是匯出
exports.a = "a";

//注意 module.exports匯出和exports[屬性名]匯出不可共存
//module.exports會覆蓋掉exports匯出的內容

簡易實現類 nodejs 模組化環境

const fs = require("fs");
const Path = require("path");
const vm = require("vm");
const ModuleStack = [];

function isRootDirectory(path) {
  // Windows 根路徑
  const windowsRootDirectory = /^[a-zA-Z]:\\$/;
  // Unix/Linux 根路徑/
  const unixRootDirectory = /^\//;

  return windowsRootDirectory.test(path) || unixRootDirectory.test(path);
}

function isRelativeDirectory(path) {
  //匹配 ../ 或者 ./開頭的路徑
  const relativeDirectory = /^(\.\.\/|\.\/).+/;
  return relativeDirectory.test(path);
}
// 計算node_modules路徑
let computePaths = (dirname) => {
  let paths = [];
  let path = dirname;
  let node_modules = "./node_modules";
  while (
    !isRootDirectory(path) ||
    !paths.includes(Path.resolve(path, node_modules))
  ) {
    paths.push(Path.resolve(path, node_modules));
    path = Path.resolve(path, "../");
  }
  return paths;
};

function myRequire(path) {
  let truelyPath;
  if (isRelativeDirectory(path)) {
    // 獲取真實路徑
    truelyPath = Path.resolve(__dirname, path);
  } else {
    //獲取可能的node_modules路徑
    let paths = computePaths(__dirname);
    for (const item of paths) {
      truelyPath = Path.resolve(item, path);
      if (fs.existsSync(truelyPath)) {
        break;
      }
    }
    if (!truelyPath) {
      throw new Error("Can't find module " + path);
    }
  }
  // 如果快取中有,直接返回
  if (myRequire.cache[truelyPath]) {
    return myRequire.cache[truelyPath].exports;
  }
  // 讀取檔案內容
  const content = fs.readFileSync(path, "utf-8");
  // 包裝程式碼
  const wrapper = [
    "(function (exports, require, module, __filename, __dirname) { \n",
    "\n})",
  ];
  // 拼接程式碼
  const wrapperContent = wrapper[0] + content + wrapper[1];

  // 獲取檔案路徑和檔名
  let dirname = Path.dirname(truelyPath);
  let filename = truelyPath;
  let parentModule =
    ModuleStack.length > 0 ? ModuleStack[ModuleStack.length - 1] : null;
  // 模組物件
  const Module = {
    id: Object.keys(myRequire.cache).length > 0 ? filename : ".",
    path: dirname,
    exports: {},
    parent: parentModule,
    filename: filename,
    loaded: false,
    children: [],
    paths: computePaths(dirname),
  };
  if (parentModule) {
    parentModule.children.push(Module);
  }
  //模組入棧
  ModuleStack.push(Module);
  // 需要執行的函式
  const moduleScope = vm.runInThisContext(wrapperContent);
  // 執行程式碼
  moduleScope.call(
    Module.exports,
    Module.exports,
    myRequire,
    Module,
    filename,
    dirname
  );
  // 標記模組已載入
  Module.loaded = true;
  //模組出棧
  ModuleStack.pop();
  // 快取模組
  myRequire.cache[truelyPath] = Module;
  return Module.exports;
}

myRequire.cache = Object.create(null);

模組化的意義

  1. 解決在模組化出現之前的js依賴混亂,全域性汙染命名衝突的問題
  2. 模組化的出現讓js程式碼可以拆分為多個模組共同協作,單個js檔案過長的問題,降低了維護難度。
  3. 模組化的出現讓js開發大型專案出現了可能

ps:當前內容為學習commonjs理解,內容正確性請謹慎甄別。

相關文章