使用场景:
将一段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获取,就需要上述方法。