详解小程序循环require之坑

作者:没有角的牛 时间:2024-02-24 03:38:29 

1. 循环require

在JavaScript中,模块之间可能出现相互引用的情况,例如现在有三个模块,他们之间的相互引用关系如下,大致的引用关系可以表示为 A -> B -> C -> A,要完成模块A,它依赖于模块C,但是模块C反过来又依赖于模块A,此时就出现了循环require。


// a.js
const B = require('./b.js');

console.log('B in A', B);
const A = {
 name: 'A',
 childName: B.name,
};
module.exports = A;


// b.js
const C = require('./c.js');

console.log('C in B', C);
const B = {
 name: 'B',
 childName: C.name,
}
module.exports = B;


// c.js
const A = require('./a.js');

console.log('A in C', A);
const C = {
 name: 'C',
 childName: A.name,
};
module.exports = C;

那JS引擎会一直循环require下去吗?答案是不会的,如果我们以a.js为入口执行程序,C在引用A时,a.js已经执行,不会再重新执行a.js,因此c.js获得的A对象是一个空对象(因为a.js还没执行完成)。

2. 小程序中的坑

在正常情况下,JS引擎是可以解析循环require的情形的。但是在一些低版本的小程序中,居然出现程序一直循环require的情况,最终导致栈溢出而报错,实在是天坑。

那如何解决呢,很遗憾,目前并未找到完美的方法来解决,只能找到程序中的循环require的代码,并进行修改。为了快速定位程序中的循环引用,写了一段NodeJs检测代码来检测进行检测。


const fs = require('fs');
const path = require('path');
const fileCache = {};
const requireLink = [];

if (process.argv.length !== 3) {
console.log(`please run as: node ${__filename.split(path.sep).pop()} file/to/track`);
return;
}

const filePath = process.argv[2];
const absFilePath = getFullFilePath(filePath);
if (absFilePath) {
resolveRequires(absFilePath, 0);
} else {
console.error('file not exist:', filePath);
}

/**
* 递归函数,解析文件的依赖
* @param {String} file 引用文件的路径
* @param {Number} level 文件所在的引用层级
*/
function resolveRequires(file, level) {
requireLink[level] = file;
for (let i = 0; i < level; i ++) {
 if (requireLink[i] === file) {
  console.log('**** require circle detected ****');
  console.log(requireLink.slice(0, level + 1));
  console.log();
  return;
 }
}
const requireFiles = getRequireFiles(file);
requireFiles.forEach(file => resolveRequires(file, level + 1));
}

/**
* 获取文件依赖的文件
* @param {String} filePath 引用文件的路径
*/
function getRequireFiles(filePath) {
if (!fileCache[filePath]) {
 try {
  const fileBuffer = fs.readFileSync(filePath);
  fileCache[filePath] = fileBuffer.toString();
 } catch(err) {
  console.log('read file failed', filePath);
  return [];
 }
}
const fileContent = fileCache[filePath];

// 引入模块的几种形式
const requirePattern = /require\s*\(['"](.*?)['"]\)/g;
const importPattern1 = /import\s+.*?\s+from\s+['"](.*?)['"]/g;
const importPattern2 = /import\s+['"](.*?)['"]/g;

const requireFilePaths = [];
const baseDir = path.dirname(filePath);
let match = null;
while ((match = requirePattern.exec(fileContent)) !== null) {
 requireFilePaths.push(match[1]);
}
while ((match = importPattern1.exec(fileContent)) !== null) {
 requireFilePaths.push(match[1]);
}
while ((match = importPattern2.exec(fileContent)) !== null) {
 requireFilePaths.push(match[1]);
}

return requireFilePaths.map(fp => getFullFilePath(fp, baseDir)).filter(fp => !!fp);
}

/**
* 获取文件的完整绝对路径
* @param {String} filePath 文件路径
* @param {String} baseDir 文件路径的相对路径
*/
function getFullFilePath(filePath, baseDir) {
if (baseDir) {
 filePath = path.resolve(baseDir, filePath);
} else {
 filePath = path.resolve(filePath);
}

if (fs.existsSync(filePath)) {
 const stat = fs.statSync(filePath);
 if (stat.isDirectory() && fs.existsSync(path.join(filePath, 'index.js'))) {
  return path.join(filePath, 'index.js');
 } else if (stat.isFile()){
  return filePath;
 }
} else if (fs.existsSync(filePath + '.js')) {
 return filePath + '.js';
}

return '';
}

来源:https://segmentfault.com/a/1190000018426421

标签:小程序,循环,require
0
投稿

猜你喜欢

  • 用Dreamweaver MX制作导航下拉菜单

    2009-05-29 18:37:00
  • Python使用sqlite3第三方库读写SQLite数据库的方法步骤

    2024-01-23 06:31:39
  • Python中的并发编程asyncio库入门使用

    2021-02-13 05:03:52
  • python属于跨平台语言码

    2023-12-23 02:10:32
  • pycharm打包py项目为.exe可执行文件的两种方式

    2022-03-26 11:51:51
  • Python数据分析之使用matplotlib绘制折线图、柱状图和柱线混合图

    2023-09-16 23:18:09
  • Python列表生成器的循环技巧分享

    2023-10-06 21:24:28
  • Python基于scrapy采集数据时使用代理服务器的方法

    2022-06-03 08:37:04
  • Python中处理字符串之endswith()方法的使用简介

    2023-02-25 09:05:36
  • (X)HTML的文档结构

    2008-06-30 12:25:00
  • Django Rest framework解析器和渲染器详解

    2021-06-30 20:46:25
  • Oracle 数据库自动存储管理-安装配置

    2009-05-24 19:15:00
  • 价值3亿美元的按钮[译]

    2009-03-18 19:39:00
  • 浅谈用VSCode写python的正确姿势

    2022-11-20 20:52:06
  • Pycharm配置anaconda环境图文教程

    2023-11-10 02:18:20
  • JavaScript实现淘宝网图片的局部放大功能

    2023-08-25 05:44:56
  • Python虚拟环境的创建和包下载过程分析

    2023-01-02 12:46:10
  • python绘制多个曲线的折线图

    2021-08-01 13:58:52
  • python海龟绘图之画国旗实例代码

    2022-03-06 14:57:23
  • Python使用selenium + headless chrome获取网页内容的方法示例

    2023-05-02 05:27:37
  • asp之家 网络编程 m.aspxhome.com