解读vue-server-renderer源码并在react中的实现 (3)

上面逻辑也比较清晰明了,通过createBundleRunner方法来解析入口文件的字符串代码,vue server-main.js入口文件返回是一个Promise函数,Promise返回的是new Vue(),所以解析出来的结果就new Vue实例。

通过RenderContext等实例解析返回的new Vue实例,获取到对应的html字符串。

通过source-map模块对错误进行正确的文件路径映射。

这样就实现了在内存中执行文件中的代码,返回html,达到ssr的效果。这次文章的重点是如何执行那段入口文件的 字符串 代码。

我们来到createBundleRunner方法,来看看里面到底是如何实现的。

function createBundleRunner (entry, files, basedir, runInNewContext) { var evaluate = compileModule(files, basedir, runInNewContext); if (runInNewContext !== false && runInNewContext !== 'once') { // 这块runInNewContext不传false 跟 once这两个选项的话,每次都会生成一个新的上下文环境,我们共用一个上下文global就行。所以这块就不考虑 } else { var runner; var initialContext; return function (userContext) { // void 0 === undefined, 因为undefined可被重新定义,void没法重新定义,所以用void 0 肯定是undefined if ( userContext === void 0 ) userContext = {}; return new Promise(function (resolve) { if (!runner) { // runInNewContext: false, 所以这里上下文就是指的global var sandbox = runInNewContext === 'once' ? createSandbox() : global; // 通过调用evaluate方法返回入口文件的函数。代码实现: evaluate = compileModule(files, basedir, runInNewContext) // 去到compileModule方法看里面是如何实现的 /* vue官方demo的server-main.js文件,返回的时一个Promise函数,所以runner就是这个函数。 export default context => { return new Promise((resolve) => { const { app } = createApp() resolve(app) }) } */ // 传入入口文件名,返回入口函数。 runner = evaluate(entry, sandbox); } // 执行promise返回 app,至此app就得到了。 resolve(runner(userContext)); }); } } } // 这个方法返回了evaluateModule方法,也就是上面evaluate方法 // evaluate = function evaluateModule(filename, sandbox, evaluatedFiles) {} function compileModule (files, basedir, runInNewContext) { var compiledScripts = {}; // filename: 依赖的文件名,例如 server.bundle.js 或 server.bundle.js依赖的 1.server.bundle.js 文件 // 在通过vue-ssr-server-bundle.json中的files字段获取这个文件名对应的文件内容 类似:"module.exports = 10"字符串 // 通过node的module模块来包裹这段代码,代码其实很简单粗暴,封装成了一个函数,传入我们熟知的commonjs规范中的require、exports等等变量 /* Module.wrapper = [ '(function (exports, require, module, __filename, __dirname, process, global) { ', '\n});' ]; Module.wrap = function(script) { return Module.wrapper[0] + script + Module.wrapper[1]; }; 结果: function (exports, require, module, __filename, __dirname, process, global) { module.exports = 10 } */ // 通过vm模块创建沙盒环境,来执行这段js代码。 function getCompiledScript (filename) { if (compiledScripts[filename]) { return compiledScripts[filename] } var code = files[filename]; var wrapper = require('module').wrap(code); var script = new require('vm').Script(wrapper, { filename: filename, displayErrors: true }); compiledScripts[filename] = script; return script } function evaluateModule (filename, sandbox, evaluatedFiles) { if ( evaluatedFiles === void 0 ) evaluatedFiles = {}; if (evaluatedFiles[filename]) { return evaluatedFiles[filename] } // 获取这个执行这段代码的沙盒环境 var script = getCompiledScript(filename); // 沙盒环境使用的上下文 runInThisContext => global var compiledWrapper = runInNewContext === false ? script.runInThisContext() : script.runInNewContext(sandbox); var m = { exports: {}}; var r = function (file) { file = path$1.posix.join('.', file); // 当前js依赖的打包文件,存在,继续创建沙盒环境执行 if (files[file]) { return evaluateModule(file, sandbox, evaluatedFiles) } else { return require(file) } }; // 执行函数代码。注意webpack要打包成commonjs规范的,不然这里就对不上了。 compiledWrapper.call(m.exports, m.exports, r, m); // 获取返回值 var res = Object.prototype.hasOwnProperty.call(m.exports, 'default') ? m.exports.default : m.exports; evaluatedFiles[filename] = res; // 返回结果 return res } return evaluateModule }

createBundleRunner函数里的实现其实也不多。就是创建一个沙盒环境来执行获取到的代码

整个逻辑核心思路如下

通过拦截webpack assets 生成一个json文件,包含所有js文件数据

通过入口文件到生成好的json文件里面取出来那段字符串代码。

通过require('module').wrap把字符串代码转换成函数形式的字符串代码,commonjs规范

通过require('vm')创建沙盒环境来执行这段代码,返回结果。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zygfyf.html