欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

详解小程序循环require之坑

程序员文章站 2023-12-24 19:22:09
1. 循环require 在javascript中,模块之间可能出现相互引用的情况,例如现在有三个模块,他们之间的相互引用关系如下,大致的引用关系可以表示为 a -&...

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 '';
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

上一篇:

下一篇: