Menyelesaikan Isu Kebergantungan Pekeliling dalam ESrojects

王林
Lepaskan: 2024-09-03 21:04:32
asal
1041 orang telah melayarinya

Resolving Circular Dependency Issues in ESrojects

Panduan Mengenalpasti dan Memperbaiki Ketergantungan Pekeliling dalam Projek JavaScript Menggunakan Madge dan ESLint.

TL;DR

  • Adalah disyorkan untuk menggunakan ESLint dengan peraturan untuk menyemak kebergantungan bulat dalam projek anda.
  • Jika sasaran binaan anda ialah ES5, sebaik sahaja modul mengeksport pemalar, modul itu tidak seharusnya mengimport modul lain.

Gejala Isu

Apabila menjalankan projek, pemalar yang dirujuk akan dikeluarkan sebagai tidak ditentukan.

Contohnya: FOO yang dieksport daripada utils.js diimport dalam index.js dan nilainya dicetak sebagai tidak ditentukan.

// utils.js

// import other modules…
export const FOO = 'foo';

// ...
Salin selepas log masuk
// index.js

import { FOO } from './utils.js';
// import other modules…

console.log(FOO); // `console.log` outputs `undefined`

// ...
Salin selepas log masuk

Berdasarkan pengalaman, isu ini mungkin disebabkan oleh pergantungan bulat antara index.js dan utils.js.

Langkah seterusnya ialah mengenal pasti laluan pergantungan bulat antara dua modul untuk mengesahkan hipotesis.

Mencari Kebergantungan Pekeliling

Pasang Alat Madge

Terdapat banyak alatan tersedia dalam komuniti untuk mencari kebergantungan bulat. Di sini, kami menggunakan Madge sebagai contoh.

Madge ialah alat pembangun untuk menjana graf visual kebergantungan modul anda, mencari kebergantungan bulat dan menyediakan maklumat berguna yang lain.

Langkah 1: Konfigurasikan Madge

// madge.js
const madge = require("madge");
const path = require("path");
const fs = require("fs");
madge("./index.ts", {
  tsConfig: {
    compilerOptions: {
      paths: {
        // specify path aliases if using any
      },
    },
  },
})
  .then((res) => res.circular())
  .then((circular) => {
    if (circular.length > 0) {
      console.log("Found circular dependencies: ", circular);
      // save the result into a file
      const outputPath = path.join(__dirname, "circular-dependencies.json");
      fs.writeFileSync(outputPath, JSON.stringify(circular, null, 2), "utf-8");
      console.log(`Saved to ${outputPath}`);
    } else {
      console.log("No circular dependencies found.");
    }
  })
  .catch((error) => {
    console.error(error);
  });
Salin selepas log masuk

Langkah 2: Jalankan Skrip

node madge.js
Salin selepas log masuk

Selepas menjalankan skrip, tatasusunan 2D diperoleh.

Tatasusunan 2D menyimpan semua kebergantungan bulat dalam projek. Setiap sub-tatasusunan mewakili laluan kebergantungan bulat tertentu: fail pada indeks n merujuk fail pada indeks n + 1, dan fail terakhir merujuk fail pertama, membentuk kebergantungan bulat.

Perlu ambil perhatian bahawa Madge hanya boleh mengembalikan kebergantungan bulat langsung. Jika dua fail membentuk pergantungan bulat tidak langsung melalui fail ketiga, ia tidak akan disertakan dalam output Madge.

Berdasarkan situasi projek sebenar, Madge mengeluarkan fail hasil dengan lebih 6,000 baris. Fail keputusan menunjukkan bahawa pergantungan pekeliling yang disyaki antara kedua-dua fail tidak dirujuk secara langsung. Mencari pergantungan tidak langsung antara dua fail sasaran adalah seperti mencari jarum dalam timbunan jerami.

Menulis Skrip untuk Mencari Ketergantungan Pekeliling Tidak Langsung

Seterusnya, saya meminta ChatGPT untuk membantu menulis skrip untuk mencari laluan pergantungan bulatan langsung atau tidak langsung antara dua fail sasaran berdasarkan fail hasil.

/**
 * Check if there is a direct or indirect circular dependency between two files
 * @param {Array<string>} targetFiles Array containing two file paths
 * @param {Array<Array<string>>} references 2D array representing all file dependencies in the project
 * @returns {Array<string>} Array representing the circular dependency path between the two target files
 */
function checkCircularDependency(targetFiles, references) {
  // Build graph
  const graph = buildGraph(references); // Store visited nodes to avoid revisiting
  let visited = new Set(); // Store the current path to detect circular dependencies
  let pathStack = [];
  // Depth-First Search
  function dfs(node, target, visited, pathStack) {
    if (node === target) {
      // Found target, return path
      pathStack.push(node);
      return true;
    }
    if (visited.has(node)) {
      return false;
    }
    visited.add(node);
    pathStack.push(node);
    const neighbors = graph[node] || [];
    for (let neighbor of neighbors) {
      if (dfs(neighbor, target, visited, pathStack)) {
        return true;
      }
    }
    pathStack.pop();
    return false;
  }
  // Build graph
  function buildGraph(references) {
    const graph = {};
    references.forEach((ref) => {
      for (let i = 0; i < ref.length; i++) {
        const from = ref[i];
        const to = ref[(i + 1) % ref.length]; // Circular reference to the first element
        if (!graph[from]) {
          graph[from] = [];
        }
        graph[from].push(to);
      }
    });
    return graph;
  }
  // Try to find the path from the first file to the second file
  if (dfs(targetFiles[0], targetFiles[1], new Set(), [])) {
    // Clear visited records and path stack, try to find the path from the second file back to the first file
    visited = new Set();
    pathStack = [];
    if (dfs(targetFiles[1], targetFiles[0], visited, pathStack)) {
      return pathStack;
    }
  }
  // If no circular dependency is found, return an empty array
  return [];
}
// Example usage
const targetFiles = [
  "scene/home/controller/home-controller/grocery-entry.ts",
  "../../request/api/home.ts",
];
const references = require("./circular-dependencies");
const circularPath = checkCircularDependency(targetFiles, references);
console.log(circularPath);
Salin selepas log masuk

Menggunakan output tatasusunan 2D daripada Madge sebagai input skrip, keputusan menunjukkan bahawa memang terdapat pergantungan bulat antara index.js dan utils.js, yang terdiri daripada rantaian yang melibatkan 26 fail.

Punca Punca

Sebelum menyelesaikan masalah, kita perlu memahami punca utama: Mengapa pergantungan bulat menyebabkan pemalar yang dirujuk tidak ditentukan?

Untuk mensimulasikan dan memudahkan masalah, mari kita anggap rantai pergantungan bulat adalah seperti berikut:

index.js → component-entry.js → request.js → utils.js → component-entry.js

Memandangkan kod projek akhirnya digabungkan oleh Webpack dan disusun ke dalam kod ES5 menggunakan Babel, kita perlu melihat struktur kod yang digabungkan.

Contoh Kod Gabungan Webpack

(() => {
  "use strict";

  var e,
    __modules__ = {
      /* ===== component-entry.js starts ==== */
      148: (_, exports, __webpack_require__) => {
        // [2] define the getter of `exports` properties of `component-entry.js`
        __webpack_require__.d(exports, { Cc: () => r, bg: () => c });
        // [3] import `request.js`
        var t = __webpack_require__(595);
        // [9]
        var r = function () {
            return (
              console.log("A function inside component-entry.js run, ", c)
            );
          },
          c = "An constants which comes from component-entry.js";
      },
      /* ===== component-entry.js ends ==== */

      /* ===== request.js starts ==== */
      595: (_, exports, __webpack_require__) => {
        // [4] import `utils.js`
        var t = __webpack_require__(51);
        // [8]
        console.log("request.js run, two constants from utils.js are: ", t.R, ", and ", t.b);
      },
      /* ===== request.js ends ==== */

      /* ===== utils.js starts ==== */
      51: (_, exports, __webpack_require__) => {
        // [5] define the getter of `exports` properties of `utils.js`
        __webpack_require__.d(exports, { R: () => r, b: () => t.bg });
        // [6] import `component-entry.js`, `component-entry.js` is already in `__webpack_module_cache__`
        // so `__webpack_require__(148)` will return the `exports` object of `component-entry.js` immediately
        var t = __webpack_require__(148);
        var r = 1001;
        // [7] print the value of `bg` exported by `component-entry.js`
        console.log('utils.js,', t.bg); // output: 'utils, undefined'
      },
      /* ===== utils.js starts ==== */
    },

    __webpack_module_cache__ = {};

  function __webpack_require__(moduleId) {
    var e = __webpack_module_cache__[moduleId];

    if (void 0 !== e) return e.exports;

    var c = (__webpack_module_cache__[moduleId] = { exports: {} });

    return __modules__[moduleId](c, c.exports, __webpack_require__), c.exports;
  }

  // Adds properties from the second object to the first object
  __webpack_require__.d = (o, e) => {
    for (var n in e)
      Object.prototype.hasOwnProperty.call(e, n) &&
        !Object.prototype.hasOwnProperty.call(o, n) &&
        Object.defineProperty(o, n, { enumerable: !0, get: e[n] });
  },

  // [0]
  // ======== index.js starts ========
  // [1] import `component-entry.js`
  (e = __webpack_require__(148/* (148 is the internal module id of `component-entry.js`) */)),
  // [10] run `Cc` function exported by `component-entry.js`
  (0, e.Cc)();
  // ======== index.js ends ========
})();
Salin selepas log masuk

Dalam contoh, [nombor] menunjukkan susunan pelaksanaan kod.

Versi ringkas:

function lazyCopy (target, source) {
  for (var ele in source) {
    if (Object.prototype.hasOwnProperty.call(source, ele)
      && !Object.prototype.hasOwnProperty.call(target, ele)
    ) {
      Object.defineProperty(target, ele, { enumerable: true, get: source[ele] });
    }
  }
}

// Assuming module1 is the module being cyclically referenced (module1 is a webpack internal module, actually representing a file)
var module1 = {};
module1.exports = {};
lazyCopy(module1.exports, { foo: () => exportEleOfA, print: () => print, printButThrowError: () => printButThrowError });
// module1 is initially imported at this point

// Assume the intermediate process is omitted: module1 references other modules, and those modules reference module1

// When module1 is imported a second time and its `foo` variable is used, it is equivalent to executing:
console.log('Output during circular reference (undefined is expected): ', module1.exports.foo); // Output `undefined`

// Call `print` function, which can be executed normally due to function scope hoisting
module1.exports.print(); // 'print function executed'

// Call `printButThrowError` function, which will throw an error due to the way it is defined
try {
  module1.exports.printButThrowError();
} catch (e) {
  console.error('Expected error: ', e); // Error: module1.exports.printButThrowError is not a function
}

// Assume the intermediate process is omitted: all modules referenced by module1 are executed

// module1 returns to its own code and continues executing its remaining logic
var exportEleOfA = 'foo';
function print () {
  console.log('print function executed');
}
var printButThrowError = function () {
  console.log('printButThrowError function executed');
}

console.log('Normal output: ', module1.exports.foo); // 'foo'
module1.exports.print(); // 'print function executed'
module1.exports.printButThrowError(); // 'printButThrowError function executed'
Salin selepas log masuk

Proses Himpunan Modul Webpack

Semasa fasa analisis AST, Webpack mencari penyata import dan eksport ES6. Jika fail mengandungi pernyataan ini, Webpack menandakan modul sebagai jenis "harmoni" dan melakukan transformasi kod yang sepadan untuk eksport:

https://github.com/webpack/webpack/blob/c586c7b1e027e1d252d68b4372f08a9bce40d96c/lib/dependencies/HarmonyExportInitFragment.js#L161

https://github.com/webpack/webpack/blob/c586c7b1e027e1d252d68b4372f08a9bce40d96c/lib/RuntimeTemplate.js#L164

Ringkasan Punca Punca

  1. Simptom isu: Modul mengimport pemalar, tetapi nilai sebenar tidak ditentukan apabila dijalankan.

  2. Syarat untuk isu itu berlaku:

    • Projek ini digabungkan oleh Webpack dan disusun ke dalam kod ES5 (tidak diuji dengan berkas lain).
    • Modul A mentakrifkan foo malar dan mengeksportnya, dan modul ini mempunyai kebergantungan bulat dengan modul lain.
    • Modul B mengimport foo daripada Modul A dan berjalan semasa proses pemulaan modul.
    • Modul A dan Modul B mempunyai kebergantungan bulat.
  3. Punca punca:

    • In Webpack's module system, when a module is first referenced, Webpack initializes its exports using property getters and stores it in a cache object. When the module is referenced again, it directly returns the exports from the cache object.
    • let variables and const constants are compiled into var declarations, causing variable hoisting issues. When used before their actual definition, they return undefined but do not throw an error.
    • Function declarations are hoisted, allowing them to be called normally.
    • Arrow functions are compiled into var foo = function () {}; and function expressions do not have function scope hoisting. Therefore, they throw an error when run instead of returning undefined.

How to Avoid

ESLint

We can use ESLint to check for circular dependencies in the project. Install the eslint-plugin-import plugin and configure it:

// babel.config.js

import importPlugin from 'eslint-plugin-import';

export default [
  {
    plugins: {
      import: importPlugin,
    },
    rules: {
      'import/no-cycle': ['error', { maxDepth: Infinity }],
    },
    languageOptions: {
      "parserOptions": {
        "ecmaVersion": 6, // or use 6 for ES6
        "sourceType": "module"
      },
    },
    settings: {
      // Need this to let 'import/no-cycle' to work
      // reference: https://github.com/import-js/eslint-plugin-import/issues/2556#issuecomment-1419518561
      "import/parsers": {
        espree: [".js", ".cjs", ".mjs", ".jsx"],
      }
    },
  },
];
Salin selepas log masuk

Atas ialah kandungan terperinci Menyelesaikan Isu Kebergantungan Pekeliling dalam ESrojects. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

sumber:dev.to
Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan