文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

一篇带给你 No.js 的模块加载器实现

2024-12-02 21:12

关注

前言:最近在 No.js 里实现了一个简单的模块加载器,本文简单介绍一下加载器的实现。

因为 JS 本身没有模块加载的概念,随着前端的发展,各种加载技术也发展了起来,早期的seajs,requirejs,现在的 webpack,Node.js等等,模块加载器的背景是代码的模块化,因为我们不可能把所有代码写到同一个文件,所以模块加载器主要是解决模块中加载其他模块的问题,不仅是前端语言,c语言、python、php同样也是这样。No.js 参考的是 Node.js的实现。比如我们有以下两个模块。module1.js

  1. const func = require("module2");func(); 

module2.js

  1. module.exports = () => { 
  2.     // some code 
  3.  

我们看看如何实现模块加载的功能。首先看看运行时执行的时候,是如何加载第一个模块的。No.js 在初始化时会通过 V8 执行 No.js文件。

  1. const { 
  2.     loader, 
  3.     process, 
  4.  
  5. } = No
  6.  
  7.  
  8.  
  9. function loaderNativeModule() { 
  10.     // 原生 JS模块列表 
  11.     const modules = [ 
  12.         { 
  13.             module: 'libs/module/index.js'
  14.             name'module' 
  15.         }, 
  16.     ]; 
  17.     No.libs = {}; 
  18.     // 初始化 
  19.     for (let i = 0; i < modules.length; i++) { 
  20.         const module = { 
  21.             exports: {}, 
  22.         }; 
  23.         loader.compile(modules[i].module).call(null, loader.compile, module.exports, module); 
  24.         No.libs[modules[i].name] = module.exports; 
  25.  
  26.         } 
  27.  
  28.  
  29.  
  30. function runMain() { 
  31.     No.libs.module.load(process.argv[1]); 
  32.  
  33.  
  34. loaderNativeModule();runMain(); 

No.js文件的逻辑主要是两个,加载原生 JS 模块和执行用户的 JS。首先来看一下如何加载原生JS模块,模块加载是通过loader.compile实现的,loader.compile是 V8 函数的封装。

  1. void No::Loader::Compile(V8_ARGS) { 
  2.     V8_ISOLATE 
  3.     V8_CONTEXT 
  4.     String::Utf8Value filename(isolate, args[0].As()); 
  5.     int fd = open(*filename, 0 , O_RDONLY); 
  6.     std::string content; 
  7.     char buffer[4096]; 
  8.     while (1) 
  9.     { 
  10.       memset(buffer, 0, 4096); 
  11.       int ret = read(fd, buffer, 4096); 
  12.       if (ret == -1) { 
  13.         return args.GetReturnValue().Set(newStringToLcal(isolate, "read file error")); 
  14.       } 
  15.       if (ret == 0) { 
  16.         break; 
  17.       } 
  18.       content.append(buffer, ret); 
  19.     } 
  20.     close(fd); 
  21.     ScriptCompiler::Source script_source(newStringToLcal(isolate, content.c_str())); 
  22.     Local params[] = { 
  23.       newStringToLcal(isolate, "require"), 
  24.       newStringToLcal(isolate, "exports"), 
  25.       newStringToLcal(isolate, "module"), 
  26.     }; 
  27.     MaybeLocal<Function> fun = 
  28.     ScriptCompiler::CompileFunctionInContext(context, &script_source, 3, params, 0, nullptr); 
  29.     if (fun.IsEmpty()) { 
  30.       args.GetReturnValue().Set(Undefined(isolate)); 
  31.     } else { 
  32.       args.GetReturnValue().Set(fun.ToLocalChecked()); 
  33.     } 
  34.  

Compile首先读取模块的内容,然后调用CompileFunctionInContext函数。CompileFunctionInContext函数的原理如下。假设文件内容是 1 + 1。执行以下代码后

  1. const ret = CompileFunctionInContext("1+1", ["require""exports""module"]) 

ret变成

  1. function (require, exports, module) { 
  2.     1 + 1; 
  3.  

所以CompileFunctionInContext的作用是把代码封装到一个函数中,并且可以设置该函数的形参列表。回到原生 JS 的加载过程。

  1. for (let i = 0; i < modules.length; i++) { 
  2.         const module = { 
  3.             exports: {}, 
  4.         }; 
  5.         loader.compile(modules[i].module).call(null, loader.compile, module.exports, module); 
  6.         No.libs[modules[i].name] = module.exports;      

首先通过loader.compile和模块内容得到一个函数,然后传入参数执行该函数。我们看看原生JS 模块的代码。

  1. class Module { 
  2.     // ... 
  3.  
  4. }; 
  5.  
  6.  
  7. module.exports = Module; 

最后导出了一个Module函数并记录到全局变量 No中。原生模块就加载完毕了,接着执行用户 JS。

  1. function runMain() { 
  2.     No.libs.module.load(process.argv[1]); 
  3.  

我们看看No.libs.module.load。

  1. static load(filename, ...args) { 
  2.     if (map[filename]) { 
  3.         return map[filename]; 
  4.     } 
  5.     const module = new Module(filename, ...args); 
  6.     return (map[filename] = module.load()); 
  7.  

新建一个Module对象,然后执行他的load函数。

  1. load() { 
  2.     const result = loader.compile(this.filename); 
  3.     result.call(this, Module.load, this.exports, this); 
  4.     return this.exports; 
  5.  

load函数最终调用loader.compile拿到一个函数,最后传入三个参数执行该函数,就可以通过module.exports拿到模块的导出内容。从中我们也看到,模块里的require、module和exports到底是哪里来的,内容是什么。

 

来源:编程杂技内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯