前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >No.js 的模块加载器实现

No.js 的模块加载器实现

作者头像
theanarkh
发布2021-10-11 12:17:09
7880
发布2021-10-11 12:17:09
举报
文章被收录于专栏:原创分享原创分享

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

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

代码语言:javascript
复制
const func = require("module2");func();

module2.js

代码语言:javascript
复制
module.exports = () => {
    // some code}

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

代码语言:javascript
复制
const {
    loader,
    process,} = No;
function loaderNativeModule() {
    // 原生 JS模块列表
    const modules = [
        {
            module: 'libs/module/index.js',
            name: 'module'
        },
    ];
    No.libs = {};
    // 初始化
    for (let i = 0; i < modules.length; i++) {
        const module = {
            exports: {},
        };
        loader.compile(modules[i].module).call(null, loader.compile, module.exports, module);
        No.libs[modules[i].name] = module.exports;        }}

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

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

代码语言:javascript
复制
void No::Loader::Compile(V8_ARGS) {
    V8_ISOLATE
    V8_CONTEXT
    String::Utf8Value filename(isolate, args[0].As<String>());
    int fd = open(*filename, 0 , O_RDONLY);
    std::string content;
    char buffer[4096];
    while (1)
    {
      memset(buffer, 0, 4096);
      int ret = read(fd, buffer, 4096);
      if (ret == -1) {
        return args.GetReturnValue().Set(newStringToLcal(isolate, "read file error"));
      }
      if (ret == 0) {
        break;
      }
      content.append(buffer, ret);
    }
    close(fd);
    ScriptCompiler::Source script_source(newStringToLcal(isolate, content.c_str()));
    Local<String> params[] = {
      newStringToLcal(isolate, "require"),
      newStringToLcal(isolate, "exports"),
      newStringToLcal(isolate, "module"),
    };
    MaybeLocal<Function> fun =
    ScriptCompiler::CompileFunctionInContext(context, &script_source, 3, params, 0, nullptr);
    if (fun.IsEmpty()) {
      args.GetReturnValue().Set(Undefined(isolate));
    } else {
      args.GetReturnValue().Set(fun.ToLocalChecked());
    }}

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

代码语言:javascript
复制
const ret = CompileFunctionInContext("1+1", ["require", "exports", "module"])

ret变成

代码语言:javascript
复制
function (require, exports, module) {
    1 + 1;}

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

代码语言:javascript
复制
for (let i = 0; i < modules.length; i++) {
        const module = {
            exports: {},
        };
        loader.compile(modules[i].module).call(null, loader.compile, module.exports, module);
        No.libs[modules[i].name] = module.exports;     
}

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

代码语言:javascript
复制
class Module {
    // ...};
module.exports = Module;

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

代码语言:javascript
复制
function runMain() {
    No.libs.module.load(process.argv[1]);}

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

代码语言:javascript
复制
static load(filename, ...args) {
    if (map[filename]) {
        return map[filename];
    }
    const module = new Module(filename, ...args);
    return (map[filename] = module.load());}

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

代码语言:javascript
复制
load() {
    const result = loader.compile(this.filename);
    result.call(this, Module.load, this.exports, this);
    return this.exports;}

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

后记:就简单介绍到这,有兴趣的可以参考

https://github.com/theanarkh/js_runtime_loader,另外分支里实现了一个Function的版本。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-09-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程杂技 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档