为什么需要模块化

commonjs模块原理与实现 - 图1
早期javaScipt模块这一概念,都是通过script标签引入js文件代码。当然这写基本简单需求没有什么问题,但当我们的项目越来越庞大时,我们引入的js文件就会越多,这时就会出现以下问题。

  • js文件作用域都是顶层,这会造成变量污染
  • js文件多,变得不好维护
  • js文件依赖问题,稍微不注意顺序引入错,代码全报错。

    1. <-- A同学的模块 -->
    2. <script>
    3. var var1 = 'var1'
    4. </script>
    5. <-- B同学的模块 -->
    6. <script>
    7. var var1 = 'var1'
    8. </script>

    后来为了防止全局的污染。前端同学们可以使用自执行函数(IIFE)实现简单的模块化系统。

    1. // 模块1
    2. var module1 = (function () {
    3. var var1 = 1;
    4. var fun2 = function(first, second) {
    5. return first + second;
    6. }
    7. return {
    8. var1: var1,
    9. fun2: fun2
    10. }
    11. })()
    12. // 模块二
    13. var module2 = (function (module) {
    14. var var1 = 1;
    15. var result = module.fun2(var1, module.var1)
    16. return {
    17. var1: var1,
    18. result: result,
    19. }
    20. })(module1)

    但这个模块化系统还有一些缺陷。文件的顺序必须确定。模块不过是否使用都会执行。这样不仅要求开发人员必须遵守模块的开发顺序,例如module2依赖module1。module2必须放在module1之后编写。而且有一些模块运行时并不需要,导致不必要的开销。为了解决这个问题。出现了commonjs规范。commonjs规范解决了以上几个问题。

  • 自执行函数(IIFE)包装模块,防止变量污染全局。

  • 动态加载模块,需要使用到该模块的时候才去执行和加载模块。
  • 模块的使用不依赖位置的定义。

    commonjs规范

    规范解决了如何编写模块以便在基于浏览器的环境中互操作。暗示,该规范定义了模块系统必须提供的最低功能,以支持可互操作的模块。

  • 模块是单例的。

  • 不应引入模块范围内的新自由变量。
  • 执行必须是懒惰的。

    模块的定义

    define是一个全局函数,用来定义模块,一个模块是用define关键字定义的,它是一个函数。注意,在common.js中一个模块是一个文件。不可以在一个文件中多次使用define。
    1. define(factory);
  1. define函数接受一个参数,即模块工厂。
  2. factory可以是一个函数或其他有效的值。
  3. 如果factory是函数,则函数的前三个参数(如果指定)必须按“require”、“exports”和“module”的顺序排列。
  4. 如果factory不是函数,则模块的导出设置为该对象。

    1. define(function(require, exports, module) {
    2. exports.add = function(first, second) {
    3. return first + second;
    4. };
    5. console.assert(module.id === "foo");
    6. });
    7. define({
    8. foo: "bar"
    9. });
    10. define([
    11. 'foo',
    12. 'bar'
    13. ]);
    14. define('foo bar');

    模块的上下文

    在一个模块中,有三个自由变量:require,exportsmodule

    1. define(function(require, exports, module) {
    2. exports.add = function (first,second) {
    3. return first + second;
    4. }
    5. module.exports.result = 1;
    6. });

    require函数

  5. require是一个函数参数为模块的文件名,可以不写后缀名称。

foo.js

  1. define(function(require, exports, module) {
  2. exports.add = function (first,second) {
  3. return first + second;
  4. }
  5. module.exports.result = 1;
  6. });

bar.js

  1. var {add, result} =require('foo');
  2. console.assert(add(1, result), 2);
  1. require 接受一个模块标识符。
  2. require 返回外部模块的导出 API。
  3. 如果请求的模块无法返回,require则应返回 null。
  1. require.async是一个函数, 该函数用于在模块內部异步加载模块,并在加载完成后执行指定的回调。

foo.js

  1. define(function(require, exports, module) {
  2. exports.add = function (first,second) {
  3. return first + second;
  4. }
  5. module.exports.result = 1;
  6. });

bar.js

  1. // bar.js
  2. require.async('foo.js', function ({add, result }) {
  3. console.assert(add(1, result), 2);
  4. })
  1. require.async 接受一个模块标识符列表和一个可选的回调函数。
  2. 回调函数接收模块导出作为函数参数,按照与第一个参数中的顺序相同的顺序列出。
  3. 如果请求的模块无法返回,则回调应相应地接收 null。

exports对象

在一个模块中,有一个名为“exports”的自由变量,它是一个对象,模块在执行时可以将其 API 添加到该对象中。

module对象

  1. module.uri: 模块的完整解析 uri。
  2. module.dependencies: 模块所需的模块标识符列表。
  3. module.exports:模块的导出 API。它与exports对象相同。

    模块标识符

  4. 模块标识符是并且必须是文字字符串。

  5. 模块标识符可能没有像.js.
  6. 模块标识符应该是破折号连接的字符串,例如foo-bar.
  7. 模块标识符可以是相对路径,例如./foo和../bar。

从本质上看,commonjs规范使用了javaScript的函数作用域的特性。函数外的变量不能访问函数内的变量。模块的加载使用的是函数的调用,但不同的是,模块向外部暴露函数和变量不是通过函数调用的返回值,而是通过使用对象传递引用关系,在函数执行的时候传递一个对象。模块内的逻辑可以往这个对象设置暴露的属性。外部通过传递进来的对象就能获取当前函数作用域内的导出的函数和变量。

在javaScript实现。

// 模块的定义
function module(exports) {
  exports.add = function (first, second) {
    return first + second;
  }
  exports.result = 1;
}

const exports= {};
module1(exports);

const { add, result} = exports;
console.assert(add(result, 1) === 2);

使用v8实现。

void module (v8::Local<v8::Object> exports) {
    v8::Isolate* isolate = v8::Isolate::GetCurrent();
    v8::Local<v8::Context> context = isolate->GetCurrentContext();
    // 设置 add函数
    exports->Set(context, v8::String::NewFromUtf8Literal(isolate, "add"),
                 v8::Function::New(context, [](const v8::FunctionCallbackInfo<v8::Value> &info) -> void {
                   v8::Isolate* isolate = info.GetIsolate();
                   // 参数校验
                   if (info.Length() != 2 || !info[0]->IsNumber() && !info[1]->IsNumber()) {
                     isolate->ThrowException(v8::String::NewFromUtf8Literal(isolate, "参数错误"));
                     return;
                   }
                   float first = info[0].As<v8::Number>()->Value();
                   float second = info[1].As<v8::Number>()->Value();
                   float result = first + second;
                   info.GetReturnValue().Set(v8::Number::New(isolate, result));
                 }).ToLocalChecked()).FromJust();
    // 设置属性result = 1;
    exports->Set(context, v8::String::NewFromUtf8Literal(isolate, "result"), v8::Number::New(isolate, 1)).FromJust();
}

TEST_F(Environment, module_commonjs_test) {
  v8::Isolate* isolate = getIsolate();
  v8::HandleScope handleScope(isolate);
  v8::Local<v8::Context> context = v8::Context::New(isolate);
  v8::Context::Scope context_scope(context);
  v8::Local<v8::Object> exports = v8::Object::New(isolate);
  module(exports);
  // 获取moudle函数执行暴露的变量和函数
  v8::Local<v8::Function> add = exports->Get(context, v8::String::NewFromUtf8Literal(isolate, "add")).ToLocalChecked().As<v8::Function>();
  v8::Local<v8::Number> result = exports->Get(context, v8::String::NewFromUtf8Literal(isolate, "result")).ToLocalChecked().As<v8::Number>();
  v8::Local<v8::Value> argv[] =  { v8::Number::New(isolate, 1), result};
  EXPECT_TRUE(add->Call(context, context->Global(), 2, argv).ToLocalChecked().As<v8::Number>()->Value() == 2);
};

浏览器中实现commonjs

commonjs_browser技术梳理

按照浏览器commonjs规范实现的commonjs在浏览器器的运行时。项目地址:commonjs_brower。编写一个模块加载器首先下。需要解决的事情是模块的循环依赖问题。什么叫循环依赖。
image.png
在上图中,首先会执行main.js。当执行发现需要加载bar.js文件。会停止下面的代码执行去加载bar.js,加载完成后去执行bar.js代码。执行后遇到需要加载foo.js模块。接着停止当前执行的代码去加载foo.js文件。当加载完成foo.js后的执行代码。当开始执行遇到需要加载bar.js 文件。在这里有一个问题。在bar中去加载foofoo又继续调用bar 。会导致一个死循环,两个不同的模块互相加载。
image.png
解决循环引用的关系。可以使用缓存的方式。在加载bar.js 的时候。把该模块缓存下载,当foo.js 再次请求bar.js 的时候。判断bar.js 已经存在缓存中,所以不在执行加载执行的流程,直接使用缓存的代码。

commonjs_browser的使用

由于commonjs规定中没有规范主模块的加载方式。所以在 commonjs_browser 在全局函数 define 挂载了启动主模块函数 use() 。下面是测试模块目录。

|- utils |- utils.js |- bar.js |- foo.js |- common_browser.js |- index.html

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="./common_browser.js"></script>
    <script>
        window.onload = function () {
            // 加载主模块
            define.use('bar');
        }
    </script>
</head>
<body>

</body>
</html>

bar.js

// 定义bar模块
define(function (require, exports, module) {
   // 加载foo模块
   const { result } = require('./foo');
   console.assert(result === 2);
});

foo.js

// 定义foo模块
define(function (require, exports, module) {
  const { add } = require('./utils/utils');
  module.exports.result = add(1,1);
});

utils.js

define(function (require, exports, module) {
  // 导出add函数
  exports.add = function (first,second) {
    return first + second;
  };
});

commonjs_browser的源码解析

下图是整个工程目录。使用了webpack5作为构建工具,TypeScript作为开发语言。最后会打包成一个umd的包,在lib目录下。在全局映入中会向全局环境中注入define全局函数。
image.png

|- src |- define.ts // 定义模块的实现,包括以函数为参数的模块加载和普通有效值 |- main.ts // 入口模块。包括组装全局函数define()和defined.use() 。 defined.use()作为主模块的加载。只能额使用一次。多次使用不会只会执行第一次注册的函数 |- module.ts // commonjs的module的实现 |- require.ts // commonjs的require的实现。可以直接通过require(‘’)同步加载模块,或者通过 require.async()异步加载模块。可以省略文件标识符。

commonjs_browser启动流程.png
main.ts

import { defineFun,defineValue, use } from "./define";

/**
 * @description 全局导出函数,参数可以为任何的有效值
 * @param module
 */
const define = function (module:any) {
    if (!module && module !== 0) {
        throw new Error('需要一个参数');
    }
    // 只需要做函数和其他类型的区别。
    // 函数会作为模块去执行
    // 其他有效值默认只会导出处理
    if (typeof module === 'function') {
        defineFun(module as Function);
    } else {
        defineValue(module);
    }
}

// 把主模块加载函数use挂载到define函数上
define.use = use;

export default define;

define.ts


import requireModule  from "./require";
import Module from './module'

/**
 * @description 定义模块
 * @param moduleCallBack{Function}
 */
export const defineFun = function (moduleCallBack: Function) {
    const module = new Module(requireModule.currentModuleId);
    const exports = module.exports;
    requireModule.cache[requireModule.currentModuleId] = module;
    moduleCallBack(requireModule, exports, module);
}

/**
 * @description 定义数据
 * @param moduleValue
 */
export const defineValue = function (moduleValue: any) {
    const module = new Module(requireModule.currentModuleId);
    module.exports = moduleValue;
    requireModule.cache[requireModule.currentModuleId] = module;
}

/**
 * @description 使用主模块
 * @param moduleIdentify{string}
 */
export const use = function (moduleIdentify:string) {
    requireModule.currentModuleId = location.origin;
    requireModule(moduleIdentify);
}

require.ts

import Module from "./module";
/**
 * @description 根据标识符获取绝对路径
 * @param moduleIdentify
 */
const getAbsoluteURL = function (moduleIdentify:string):string {
    // 如果模块省略了后缀名称,自动添加模块后拽
    if (!moduleIdentify.endsWith('.js')) {
        moduleIdentify+= '.js';
    }

    let baseURL = new URL(requireModule.currentModuleId);
    if (baseURL.pathname.includes('.')) {
        // 获取文件的目录
        const index = baseURL.pathname.lastIndexOf('/');
        const pathname =baseURL.pathname.slice(index);
        baseURL = new URL(pathname, location.origin);
    }
    return new URL(moduleIdentify, baseURL).href;
}

const requireModule = function (moduleIdentify:string):object {
   const absoluteURL = getAbsoluteURL(moduleIdentify);
    // 查找父模块,把当前模块的绝对路径设置到夫模块的依赖中
    const parentModule = requireModule.cache[requireModule.currentModuleId] as Module;
    if (parentModule) {
        parentModule.addDepend(absoluteURL);
    }
   // 如果模块已经加载,直接从缓存中获取。
   if (requireModule.cache[absoluteURL]) {
       return requireModule.cache[absoluteURL];
   }
    requireModule.currentModuleId= absoluteURL;
    // 因为require 需要同步获取模块。在次使用了ajax的同步请求资源
    // 然后再使用 script标签去执行javaScipt代码
    const http = new XMLHttpRequest();
    http.open("GET", absoluteURL, false);
    http.send();
    const response = http.response;
    const script = document.createElement('script');
    script.textContent = response;
    document.querySelector('head').appendChild(script);
    return requireModule.cache[absoluteURL] ?  requireModule.cache[absoluteURL].exports : null;
}

/**
 * @description 异步加载模块
 * @param moduleIdentify
 * @param callback
 */
const async = function (moduleIdentify:string, callback: Function) {
    const absoluteURL = getAbsoluteURL(moduleIdentify);
    // 查找父模块,把当前模块的绝对路径设置到夫模块的依赖中
    const parentModule = requireModule.cache[requireModule.currentModuleId] as Module;
    if (parentModule) {
        parentModule.addDepend(absoluteURL);
    }
    if (requireModule.cache[absoluteURL]) {
        callback(callback(requireModule.cache[absoluteURL]));
        return;
    }
    requireModule.currentModuleId= absoluteURL;
    const script = document.createElement('script');
    script.src = absoluteURL;
    script.async = true;
    const loadSuccess = () => {
        callback( requireModule.cache[absoluteURL] ?  requireModule.cache[absoluteURL].exports : null);
        clear();
    }
    const loadError = () => {
        callback(null);
        clear();
        // 移除脚本,清空模块缓存,移除父模块的依赖
        document.removeChild(script);
        requireModule.cache[absoluteURL] = undefined;
        parentModule.removeDepend(absoluteURL);
    }
    const clear = () => {
        script.removeEventListener('load', loadSuccess);
        script.removeEventListener('error', loadError);
    }
    script.addEventListener('load', loadSuccess);
    script.addEventListener('error', loadError);
}

requireModule.async = async;
// 以模块的绝对路径为key, 以模块的构建对象 module为value。作为缓存存放在cache对象里面,防止重复加载
requireModule.cache = {};
// 记录当前处理的模块的绝对路径 当主模块执行的时候, currentModuleId  为主模块的绝对路径。
requireModule.currentModuleId = '';

export default requireModule;

module.ts

/**
 * @description commonjs 模块
 */
export default class Module{
    private _id: string;
    private _exports: object = {};
    private _dependencies:string[] = [];

    constructor(id) {
        this._id = id;
    }
    get uri () {
        return this._id;
    }
    get exports () {
        return this._exports;
    }
    set exports (value: object) {
        this._exports = value;
    }
    get dependencies () {
        return this._dependencies;
    }

    /**
     * @description 添加依赖
     * @param depend
     */
    addDepend(depend:string) {
        // 防止重复添加
       if (!this._dependencies.includes(depend)) {
           this._dependencies.push(depend);
       }
    }

    /**
     * @description 移除依赖。
     * @param depend
     */
    removeDepend (depend:string) {
        const index = this._dependencies.findIndex((dependItem:string) => {
            return dependItem === depend;
        });
        if (index !== -1) {
            this._dependencies.splice(index, 1);
        }
    }
}

使用v8实现commonjs规范

在使用v8实现commonjs规范中。主模块直接通过命令行参数传递到程序中。可以直接通过main 函数的argv参数获取。通过getcwd 函数获取到当前执行命令行的目录。通过参数传递进的路径和命令行的路径。最终得出一个绝对路径。这个绝对路径作为主模块的路径。项目地址:commonjs_server 。使用clion开发工具。wsl 开发环境。c++17版本。和上面使用javaScript创建的原理类似。

  1. define函数: 在上下文创建后,创建一个define的函数,然后向全局上下文的全局代理对象注入全局的define函数。当javaScript代码使用define函数。会调用到c++层的define函数。define函数通过javaScript传递过来的参数类型做出不同的处理。
    • 非有效的javaScript值: 抛出异常
    • 函数:构建一个叫modle的v8类型的对象并设置 uri, exports, dependencies属性。再创建一个叫exports的v8类型的对象作为module对象exports属性的值,接着再创建一个叫require的v8函数。然后再创建一个叫async的v8函数。并设置到require函数的属性async上。把创建的这3个v8对象作为参数传递到函数中,并执行。
    • 有效的javaScript的值:构建一个v8对象。并设置它的属性exports为当前值。

最后把改模块以改文件模块的绝对路径为key,module对象为value。保存在一个全局的持久化句柄对象上作为缓存。

  1. require函数: 在程序启动后,通过把该函数构建成v8类型的函数,然后加载主模块。加载完成后执行编译,并执行。 ```cpp

    include

    include “v8.h”

    include “libplatform/libplatform.h”

    include

    include

    include

    include

    include

// 当前模块的绝对路径 v8::Persistent currentModuleId; // 模块缓存 key为模块的文件全路径,value 为 module对象 v8::Persistent cache;

/**

  • 技术路径path相对于 dir的绝对路径
  • @param path 文件路径
  • @param dir_name 目录
  • @return */ std::string getAbsolutePath(const std::string& path,
                         const std::string& dir) {
    
    std::string absolute_path; // 判断是否为绝对路径。在linux 上下。文件以 / 开头 if ( path[0] == ‘/‘) {
     absolute_path = path;
    
    } else {
     absolute_path = dir + '/' + path;
    
    } std::replace(absolute_path.begin(), absolute_path.end(), ‘\‘, ‘/‘); std::vector segments; std::istringstream segment_stream(absolute_path); std::string segment; while (std::getline(segment_stream, segment, ‘/‘)) {
     if (segment == "..") {
         segments.pop_back();
     } else if (segment != ".") {
         segments.push_back(segment);
     }
    
    } std::ostringstream os; std::copy(segments.begin(), segments.end() - 1,
           std::ostream_iterator<std::string>(os, "/"));
    
    os << *segments.rbegin(); return os.str(); }

/**

  • 读取文件
  • @param path
  • @return / v8::Local readFile (std::string& path) { v8::Isolate isolate = v8::Isolate::GetCurrent(); // 读取主模块文件 std::ifstream in(path.c_str()); // 如果打开文件失败 if (!in.is_open()) {
     return v8::Local<v8::String>();
    
    } std::string source; char buffer[256]; // 如果没有读取到文件结束符位置。 while(!in.eof()){
     in.getline(buffer,256);
     source.append(buffer);
    
    }; return v8::String::NewFromUtf8(isolate, source.c_str()).ToLocalChecked(); }

/**

  • 判断字符串是否以某个 后缀为结尾
  • @param str
  • @param suffix
  • @return */ inline bool has_suffix(const std::string &str, const std::string &suffix){ return str.size() >= suffix.size() &&
        str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0;
    
    } /**
  • require 函数的实现,用于模块的获取。
  • @param info */ void require(const v8::FunctionCallbackInfo &info) {

    // 参数校验。如果没有参数传递。返回null if (!info.Length() || !info[0]->IsString()) {

     info.GetReturnValue().SetNull();
     return;
    

    } v8::Isolate* isolate = info.GetIsolate(); v8::Local context = isolate->GetCurrentContext(); v8::HandleScope handleScope(isolate);

    v8::Local moduleCache = v8::Local::New(isolate, cache); v8::Local moduleId = v8::Local::New(isolate, currentModuleId); std::string modulePath(*v8::String::Utf8Value(isolate, info[0].As())); if (!has_suffix(modulePath, std::string(“.js”))) {

     modulePath.append(".js");
    

    }

    std::string moduleDir(*v8::String::Utf8Value(isolate, moduleId)); std::string parentModuleId = moduleDir; // 获取夫模块的目录 if (moduleDir.find(std::string(“.”)) != -1) {

     int index = moduleDir.find_last_of("/");
     moduleDir = moduleDir.substr(0, index);
    

    } // 父模块id std::string moduleAbsolutePath = getAbsolutePath(modulePath, moduleDir);

    // 查找缓存 v8::Local module = moduleCache->Get(context, v8::String::NewFromUtf8(isolate, moduleAbsolutePath.c_str()).ToLocalChecked()).ToLocalChecked(); // 如果命中缓存,直接使用缓存 // 这里注意的是,在javaScript 获取一个属性的时候如果属性不存在,返回的是 undefined if (!module.IsEmpty() && !module->IsUndefined()){

     info.GetReturnValue().Set(module);
     return;
    

    } // 读取原文件 v8::Local source = readFile(moduleAbsolutePath);

    if (source.IsEmpty()) {

     info.GetReturnValue().SetNull();
     return;
    

    }

    // 构建脚本 v8::Local script = v8::Script::Compile(context, source).ToLocalChecked();

    // 把当前文件作为当前模块id currentModuleId.Reset(isolate, v8::String::NewFromUtf8(isolate, moduleAbsolutePath.c_str()).ToLocalChecked()); // 执行模块 script->Run(context).ToLocalChecked(); // 从缓存模块中获取 module = moduleCache->Get(context, v8::String::NewFromUtf8(isolate, moduleAbsolutePath.c_str()).ToLocalChecked()).ToLocalChecked(); if (!module.IsEmpty() && !module->IsUndefined()) {

     v8::Local<v8::Object> exports = module.As<v8::Object>()->Get(context, v8::String::NewFromUtf8Literal(isolate, "exports")).ToLocalChecked().As<v8::Object>();
     if (!exports.IsEmpty() && !exports->IsUndefined()) {
         v8::Local<v8::Object> parentModule = moduleCache->Get(context, v8::String::NewFromUtf8(isolate, parentModuleId.c_str()).ToLocalChecked()).ToLocalChecked().As<v8::Object>();
         // 获取父模块。把当前模块设置到夫模块的依赖项中。
         if (!parentModule.IsEmpty() && !parentModule->IsUndefined()) {
             // 获取模块的依赖数组
             v8::Local<v8::Array> dependencies = parentModule->Get(context, v8::String::NewFromUtf8Literal(isolate, "dependencies")).ToLocalChecked().As<v8::Array>();
             int length = dependencies->Length();
             bool isFind = false;
             // 防止重复添加
             for (int index = 0; index < length; ++index) {
                 v8::Local<v8::String> depend = dependencies->Get(context, index).ToLocalChecked().As<v8::String>();
                 if (depend->StrictEquals(v8::String::NewFromUtf8(isolate, moduleAbsolutePath.c_str()).ToLocalChecked())){
                     isFind = true;
                     break;
                 }
             }
             if (!isFind) {
                 // 把当前模块添加到父模块中
                 dependencies->Set(context, length, v8::String::NewFromUtf8(isolate, moduleAbsolutePath.c_str()).ToLocalChecked()).FromJust();
             }
         }
         info.GetReturnValue().Set(exports);
         return;
     }
    

    } info.GetReturnValue().SetNull(); }

/**

  • 异步获取模块
  • 在commonjs 文件中使用
  • define(function(require, export, module) {
  • require.async(‘path’, (module1) => {
  • });
  • const module2 = require(‘path’);
  • })
  • @param info */ void async(const v8::FunctionCallbackInfo &info) {

    // 参数校验。async必须两个参数,第一个为字符串路径。第二个为回调函数。 if (info.Length() != 2 || !info[0]->IsString() && !info[1]->IsFunction()) {

     info.GetReturnValue().SetNull();
     return;
    

    } v8::Isolate* isolate = info.GetIsolate(); v8::HandleScope handleScope(isolate); v8::Local context = isolate->GetCurrentContext();

    // 把参数设置到对象params。用于创建微任务队列的回调函数。把该对象作为参数 v8::Local params = v8::Object::New(isolate); params->Set(context, v8::String::NewFromUtf8Literal(isolate, “modulePath”), info[0]).FromJust(); params->Set(context, v8::String::NewFromUtf8Literal(isolate, “callBack”), info[1]).FromJust();

    // 存放在微任务队列中。 isolate->EnqueueMicrotask(v8::Function::New(context, -> void {

     v8::Isolate* isolate = info.GetIsolate();
     v8::HandleScope handleScope(isolate);
     v8::Local<v8::Context> context = isolate->GetCurrentContext();
     v8::Local<v8::Object> params = info.Data().As<v8::Object>();
     v8::Local<v8::Function> requireFun = v8::Function::New(context, require).ToLocalChecked();
     v8::Local<v8::Value> args[] = { params->Get(context, v8::String::NewFromUtf8Literal(isolate, "modulePath")).ToLocalChecked() };
     v8::Local<v8::Value> result = requireFun->Call(context, context->Global(), 1, args).ToLocalChecked();
     v8::Local<v8::Function> callBack = params->Get(context, v8::String::NewFromUtf8Literal(isolate, "callBack")).ToLocalChecked().As<v8::Function>();
    
     // 执行async 函数的回调
     if (result.IsEmpty() || result->IsUndefined()) {
         v8::Local<v8::Value> argv[] = { v8::Null(isolate) };
         callBack->Call(context, context->Global(), 1, argv).ToLocalChecked();
     } else {
         v8::Local<v8::Value> argv[] = { result };
         callBack->Call(context, context->Global(), 1, argv).ToLocalChecked();
     }
    

    }, params).ToLocalChecked()); }

/**

  • 全局对象define 的实现, 参数 require, export, module由c++负责创建和执行。
  • 1: define(function(require, export, module) {
  • })
  • 2:define(value)
  • @param info / void define(const v8::FunctionCallbackInfo &info) { v8::Isolate isolate = info.GetIsolate(); v8::Local context = isolate->GetCurrentContext(); v8::HandleScope handleScope(isolate); // 参数校验,define的参数必须为一个函数或者为一个有效的javaScript值。 if (!info.Length() || info[0]->IsUndefined() || info[0]->IsNull()) {

     info.GetReturnValue().Set(isolate->ThrowException(v8::String::NewFromUtf8Literal(isolate, "需要一个参数")));
     return;
    

    }

    // 把正在执行的持久化moduleId 和模块缓存对象本地化。 v8::Local moduleCache = v8::Local::New(isolate, cache); v8::Local moduleId = v8::Local::New(isolate, currentModuleId);

    // 构建 module对象, module.exports对象 v8::Local module = v8::Object::New(isolate); module->Set(context, v8::String::NewFromUtf8Literal(isolate, “uri”), moduleId).FromJust(); // 把模块设置到缓存里面 moduleCache->Set(context, moduleId, module).FromJust(); if (info[0]->IsFunction()) {

     v8::Local<v8::Object> exports = v8::Object::New(isolate);
     // 设置module对象的 exports和 dependencies属性。exports为对象。 dependencies为数组。
     module->Set(context, v8::String::NewFromUtf8Literal(isolate, "exports"), exports).FromJust();
     module->Set(context, v8::String::NewFromUtf8Literal(isolate, "dependencies"), v8::Array::New(isolate)).FromJust();
    
     v8::Local<v8::Function> moduleCallBack = info[0].As<v8::Function>();
     // 构建require 函数
     v8::Local<v8::Function> requireFun =  v8::Function::New(context, require).ToLocalChecked();
     // 为require 函数增加 async 函数属性
     requireFun->Set(context, v8::String::NewFromUtf8Literal(isolate, "async"), v8::Function::New(context, async).ToLocalChecked()).FromJust();
     v8::Local<v8::Value> argv[] = { requireFun, exports, module};
     // 执行define 的参数回调
     moduleCallBack->Call(context, context->Global(), 3, argv).ToLocalChecked();
    

    } else {

     module->Set(context, v8::String::NewFromUtf8Literal(isolate, "exports"), info[0]).FromJust();
    

    } }

/**

  • 启动函数,参数0 为程序的名称 参数二为主模块的路径。可以是相对路径,绝对路径
  • @param args
  • @param argv
  • @return / int main(int args, char* argv) { // 如果没有入口文件 if (argv[1] == nullptr) {

     return 1;
    

    } char workDirBuffer[255]; // linux 获取工作目录 getcwd(workDirBuffer,sizeof(workDirBuffer));

    // 初始化v8 v8::V8::InitializeICUDefaultLocation(argv[0]); v8::V8::InitializeExternalStartupData(argv[0]); std::unique_ptr platform = v8::platform::NewDefaultPlatform(); v8::V8::InitializePlatform(platform.get()); v8::V8::Initialize(); v8::Isolate::CreateParams create_params; create_params.array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator(); v8::Isolate* isolate = v8::Isolate::New(create_params);

    {

     v8::Isolate::Scope isolate_scope(isolate);
     v8::HandleScope handleScope(isolate);
    
     // 设置微任务队列策略 显示调用
     isolate->SetMicrotasksPolicy(v8::MicrotasksPolicy::kExplicit);
     v8::Local<v8::Context> context = v8::Context::New(isolate);
     v8::Context::Scope context_scope(context);
    
     // 初始化当前模块ID和缓存模块
     currentModuleId.Reset(isolate, v8::String::NewFromUtf8(isolate, workDirBuffer).ToLocalChecked());
     cache.Reset(isolate, v8::Object::New(isolate));
    
     // 设置全局函数 define
     context->Global()->Set(context, v8::String::NewFromUtf8Literal(isolate, "define"), v8::Function::New(context, define).ToLocalChecked()).FromJust();
     // 设置全局函数用于打印结果
     context->Global()->Set(context, v8::String::NewFromUtf8Literal(isolate, "print"),
                            v8::Function::New(context, [](const v8::FunctionCallbackInfo <v8::Value> &info) -> void {
                                v8::Isolate *isolate = info.GetIsolate();
                                v8::Local<v8::Context> context = isolate->GetCurrentContext();
                                // 如果是普通的对象,直接转成JSON字符串。如果是其他类型强制转换成字符串输出。
                                if (!info[0]->IsNull() && info[0]->IsObject() && !info[0]->IsFunction()) {
                                    std::cout << *v8::String::Utf8Value(isolate, v8::JSON::Stringify(context, info[0]).ToLocalChecked()) << std::endl;
                                } else {
                                    std::cout << *v8::String::Utf8Value(isolate, info[0].As<v8::String>()) << std::endl;
                                }
                            }).ToLocalChecked()).FromJust();
     // 创建require 函数
     v8::Local<v8::Function> requireFun = v8::Function::New(context, require).ToLocalChecked();
     v8::Local<v8::Value> args[] = { v8::String::NewFromUtf8(isolate, argv[1]).ToLocalChecked() };
     // 加载主模块
     requireFun->Call(context, context->Global(), 1, args).ToLocalChecked();
     //情况微任务队列。
     isolate->PerformMicrotaskCheckpoint();
    

    } isolate->Dispose(); v8::V8::Dispose(); v8::V8::ShutdownPlatform(); delete create_params.array_buffer_allocator; return 0; } ```