Panduan Mengenalpasti dan Memperbaiki Ketergantungan Pekeliling dalam Projek JavaScript Menggunakan Madge dan ESLint.
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'; // ...
// index.js import { FOO } from './utils.js'; // import other modules… console.log(FOO); // `console.log` outputs `undefined` // ...
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.
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.
// 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); });
node madge.js
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.
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);
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.
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.
(() => { "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 ======== })();
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'
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
Simptom isu: Modul mengimport pemalar, tetapi nilai sebenar tidak ditentukan apabila dijalankan.
Syarat untuk isu itu berlaku:
Punca punca:
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"], } }, }, ];
Atas ialah kandungan terperinci Menyelesaikan Isu Kebergantungan Pekeliling dalam ESrojects. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!