nodejs动态加载代码

使用场景:

将一段nodejs代码字符串执行起来

方法一:

假设存在文件bundle.js,main.js,其中bundle.js中为要执行的代码

//main.js
const fs = require('fs')
fs.readFile = util.promisify(fs.readFile)
const bundle = await fs.readFile('./bundle.js', 'utf-8') //此时的bundle为String
const m = new module.constructor()
m._compile(bundle, 'bundle.js') // 第一个参数为要执行的代码字符串,第二个参数为文件名
//此时就可以通过m.exports来调用bundle.js文件中exports出来的东西

这样做虽然简单,但有很多缺陷,很常见的就是如果你代码中require了一个包,那这种方式就无法解析require,一执行就会报错:
can not found xxx packages,

方法二:

//main.js
const fs = require('fs')
const NativeModule = require('module')
const vm = require('vm')

fs.readFile = util.promisify(fs.readFile)
const bundle = await fs.readFile('./bundle.js', 'utf-8') //此时的bundle为String

const getModuleFromString = (bundle, filename) => {
	const m = { exports: {} }
	const wrapper = NativeModule.wrap(bundle)
	const script = new vm.Script(wrapper, { 
		filename,
		displayErrors: true
	})
	const result = script.runInThisContext() // 此处可以指定代码的执行环境,此api在nodejs文档中有介绍
	result.call(m.exports, m.exports, require, m) // 执行wrapper函数,此处传入require就解决了第一种方法不能require的问题
	return m
}

const m = getModuleFromString(bundle, 'bundle.js')

上述代码中,NativeModule.wrap方法会将bundle包装成如下形式:

`(function (exports, require, module, __filename, __dirname){
	// bundle code
})`

这样就方便我们传入exports、require、module等参数,这些参数就是bundle代码执行过程中的module.exports,require,module,这样就方便了我们去定制这些参数,注意此时得到的wrapper依然是字符串的形式。然后通过vm模块的vm.Script方法去执行这段代码。

当用webpack打包一个js文件的时候,webpack会将代码中require的东西都拿出来打包到这个文件中,方便文件在前端环境下执行,但是如果是服务端渲染的时候,那么这个原本要在前端执行的文件就回在后端执行,此时没必要把require的文件全部打包进来因为nodejs中就可以直接require,所以通过webpack的externals配置externals: Object.keys(require('../package.json').dependencies)避免这些比不必要的打包(而且这可能因为一些对象被重复创建会导致一些错误)。
而在开发环境中,我们通常不会把打包后的文件输出到硬盘,而是输出到内存中(比如可以借助memory-fs模块),这时由于硬盘上不存在真正的打包后的文件,所以不能直接require获取,就需要上述方法。

你可能感兴趣的:(Node.js)