我想实现一个函数,当目标模块已下载时,直接通过 require 引入,否则去npm下载,完成后再引入。 原先的代码是这样的:
const execa = require('execa');
const requireDynamic = async moduleName => {
try {
return require(moduleName);
} catch {
await execa('npm', ['install', moduleName]);
return require(moduleName);
}
};
但是这样会报错,提示模块不存在。 而我在模块安装结束后用下面这段代码确认模块已经安装,模块文件夹是存在的:
const fs = require('fs-extra');
const path = require('path');
console.log(fs.pathExists(path.resolve(process.cwd(), 'node_modules', moduleName)));
我怀疑模块下载成功后没能立即允许引入,所以开了定时器setInterval
来确认什么时候可以引入,但是一直都不可以。
后来我先找到这个模块的package.json
,然后在main
字段找到模块的入口文件,引入这个文件,是可以的。
const execa = require('execa');
const path = require('path');
const requireDynamic = async moduleName => {
try {
return require(moduleName);
} catch {
await execa('npm', ['install', moduleName]);
const modulePath = path.resolve(process.cwd(), 'node_modules', moduleName);
const { main } = require(path.resolve(modulePath, 'package.json'));
return require(path.resolve(modulePath, main));
}
};
我就是没明白,npm模块从下载,解压,还要经过什么流程,才可以被正常引入。希望有好心人解答下,谢谢了。
目测 require 缓存造成的,catch 语句里面先删除一次缓存试试
await execa('npm', ['install', moduleName]);
delete require.cache[moduleName];
return require(moduleName);
@xiaoxiaojx 不是,删除缓存后还是会提示模块不存在。
@linzb93 打扰了
webstorm里可以看到require实现的源码 楼主可以试试看
@xiaoxiaojx 我找到解决方案了。
delete require.cache[]
数组里面填写的不能是moduleName,因为之前require失败,缓存里面找不到moduleName对应的地址。
应该改成path.resolve(process.cwd(), 'node_modules', moduleName)
就可以成功删除了。
其实没必要这样操作,如果你的目的只是不想多下载一次,可以使用pnpm,每个版本的包只会下载一次。写成这样,如果原理了解不够透彻或者测试不够充分,容易留下隐患
@zurmokeeper 是有些依赖不是跨平台的,在另一个平台安装会报错。所以这些依赖的安装就不会通过先写入package.json再install。