如何写一个 JS 运行时

前言:随着 Node.js 的出现和不断发展,其他新的 JS 运行时也穷出不断,Deno、Just、Bun等等。本文简单介绍一下如何写一个 JS 运行时,相比操作系统、编译器来说,写一个 JS 运行时理论上并不是一个难的事情,但是写一个优秀且功能齐全的运行时并不是一个容易的事情。

JS 引擎

写一个 JS 运行时,首先就必须需要一个 JS 引擎来处理 JS,大部分的 JS 运行时都是基于 V8的,当然你也可以使用其他的 JS 引擎。所以首先需要选择一个 JS 引擎,然后下载代码,编译成功。有了 JS 引擎,就可以通过它提供的一些 API 实现一个可以执行 JS 代码的软件。

int main(int argc, char* argv[]) {
  setvbuf(stdout, nullptr, _IONBF, 0);
  setvbuf(stderr, nullptr, _IONBF, 0);
  v8::V8::InitializeICUDefaultLocation(argv[0]);
  v8::V8::InitializeExternalStartupData(argv[0]);
  std::unique_ptr<Platform> platform = platform::NewDefaultPlatform();
  v8::V8::InitializePlatform(platform.get());
  v8::V8::Initialize();
  Isolate::CreateParams create_params;
  create_params.array_buffer_allocator = ArrayBuffer::Allocator::NewDefaultAllocator();
  Isolate* isolate = Isolate::New(create_params);
  {
    Isolate::Scope isolate_scope(isolate);
    HandleScope handle_scope(isolate);
    Local<ObjectTemplate> global = ObjectTemplate::New(isolate);
    Local<Context> context = Context::New(isolate, nullptr, global);
    Context::Scope context_scope(context);
    Local<Object> globalInstance = context->Global();
    globalInstance->Set(context, String::NewFromUtf8Literal(isolate, "No", 
    NewStringType::kNormal), No);
    // 设置全局属性global指向全局对象
    globalInstance->Set(context, String::NewFromUtf8Literal(isolate, 
      "global", 
      NewStringType::kNormal), globalInstance).Check();
    {
      // 打开文件
      int fd = open(argv[1], 0, O_RDONLY);
      struct stat info;
      // 取得文件信息
      fstat(fd, &info);
      // 分配内存保存文件内容
      char *ptr = (char *)malloc(info.st_size + 1);
      // ptr[info.st_size] = '\0';
      read(fd, (void *)ptr, info.st_size);
      // 要执行的js代码
      Local<String> source = String::NewFromUtf8(isolate, ptr,
                          NewStringType::kNormal,
                          info.st_size).ToLocalChecked();

      // 编译
      Local<Script> script = Script::Compile(context, source).ToLocalChecked();
      // 解析完应该没用了,释放内存
      free(ptr);
      // 执行
      Local<Value> result = script->Run(context).ToLocalChecked();
    }
  }

  // Dispose the isolate and tear down V8.
  isolate->Dispose();
  v8::V8::Dispose();
  v8::V8::ShutdownPlatform();
  delete create_params.array_buffer_allocator;
  return 0;
}

拓展功能

有了 JS 引擎,我们只能使用 JS 语言本身提供的一些能力,可以做的事情不多,比如网络、文件、进程能力都没有。但是幸运的是,JS 引擎提供了拓展能力,我们可以使用 JS 引擎提供的 API 拓展网络、文件这些功能。在之前代码的基础上增加以下代码。

Local<ObjectTemplate> global = ObjectTemplate::New(isolate);
Local<Context> context = Context::New(isolate, nullptr, global);
Context::Scope context_scope(context);
// 所有拓展功能挂到这个对象中
Local<Object> No = Object::New(isolate);
No::Console::Init(isolate, No);
Local<Object> globalInstance = context->Global();
// 再把这个对象挂载到全局变量
globalInstance->Set(context, String::NewFromUtf8Literal(isolate, "No", 
NewStringType::kNormal), No);


void No::Console::log(V8_ARGS) {
    V8_ISOLATE
    String::Utf8Value str(isolate, args[0]);
    Log(*str);
}

void No::Console::Init(Isolate* isolate, Local<Object> target) {
  Local<ObjectTemplate> console = ObjectTemplate::New(isolate);
  setMethod(isolate, console, "log", No::Console::log);
  setObjectValue(isolate, target, "console", console->NewInstance(isolate->GetCurrentContext()).ToLocalChecked());
}

以上代码在 JS 的全局变量上挂载了一个变量 No,然后在 No 变量上挂载我们需要拓展的功能,比如上面的 console.log。这样我们就可以直接在 JS 里使用 console.log 了。

事件循环

有了之前的基础后,接下来我们就需要实现一个事件循环,因为有些拓展功能的 API,是同步执行的,但是有些是不能同步执行的,比如文件、网络。所以我们需要一个事件循环来处理异步的任务。事件循环本质上是一个生产者 / 消费者模型,在这个模型中,最重要的是当没有任务消费的时候,如何处理。通常使用的是阻塞 / 唤醒的机制,通常是使用事件驱动模块实现这种机制。如果我们只支持 Linux,那么就可以选择 epoll,如何是 Mac,那么就可以选择 kqueue,基本上,大多数操作系统都提供了这种机制,如果我们支持多操作系统,那么就需要封装好各个操作系统提供的 API,当然如果为了方便,我们可以直接使用 Libuv。如果你只想支持比较新版本的 Linux,可以使用真正的异步 IO 框架 io_uring。

void No::io_uring::RunIOUring(struct io_uring_info *io_uring_data) {
    struct io_uring* ring = &io_uring_data->ring;
    struct io_uring_cqe* cqe;
    struct request* req;
    while(io_uring_data->stop != 1 && io_uring_data->pending != 0) {
        // 提交请求给内核
	    int count = io_uring_submit_and_wait(ring, 1);
      	// 处理每一个完成的请求
        while (1) { 
            io_uring_peek_cqe(ring, &cqe);
            if (cqe == NULL)
                break;
            --io_uring_data->pending;
            // 拿到请求上下文
            req = (struct request*) (uintptr_t) cqe->user_data;
            req->res = cqe->res;
            io_uring_cq_advance(ring, 1);
            // 执行回调
            if (req->cb != nullptr) {
                req->cb((void *)req);
            }
        }
    }
}

模块加载器

有了上面的基础后,基本上实现了一个 JS 运行时了。可以在 JS 里使用到各种各样的拓展功能,比如建立 TCP 连接,读写文件。但是还有一个重要的部分需要实现,那就是模块加载器,内置的功能可以通过挂载到全局变量的方式来实现,这样用户就不需要通过模块加载器的方式来使用拓展功能,但是用户的 JS,还是需要一个模块加载器。实现模块加载器之后,架子就搭建得差不多了。剩下的事情就是取决于需要支持什么功能。

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());
    }
}

如果你有兴趣,可以参考我之前的一些实践。
https://github.com/theanarkh/js_runtime_loader
https://github.com/theanarkh/No.js

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 好的,关于一个可以运行的网站页面,我可以提供一些基本的步骤和工具。 1. 确定网站页面的目的和内容:首先,需要确定网站页面的目的,是用于展示产品或服务,还是用于发布文章或信息。然后,确定网站页面的内容,包括文字、图片、视频等。 2. 设计网站页面的布局和风格:根据网站页面的目的和内容,设计网站页面的布局和风格,包括颜色、字体、图标等。 3. 编网站页面的代码:使用HTML、CSS和JavaScript等编网站页面的代码,包括页面的结构、样式和交互效果。 4. 测试网站页面的兼容性和性能:测试网站页面在不同浏览器和设备上的兼容性,以及页面的加载速度和响应间。 5. 发布网站页面到服务器:将编好的网站页面上传到服务器,确保可以通过URL访问到网站页面。 对于编网站页面的代码,可以使用一些工具和框架,如Bootstrap、jQuery、React等,来加快开发速度和提高代码质量。同,还需要了解一些基本的网络技术和安全知识,以确保网站页面的安全性和可靠性。 希望这些信息对你有所帮助,如果需要更详细的指导,可以进一步咨询专业的网站开发人员或者参考相关的网站开发教程。 ### 回答2: 回答这个问题需要提供更多的具体信息,因为创建一个可以运行的网站页面涉及到很多方面的内容。然而,我可以提供给你一个简单的例子。 假设我们要创建一个简单的网站页面,用于展示一本书的信息。以下是一个可以运行的网站页面的示例代码: ```html <!DOCTYPE html> <html> <head> <title>书籍详情</title> </head> <body> <h1>书籍详情页面</h1> <div class="book-info"> <h2>书名:《JavaScript高级程序设计》</h2> <p>作者:Nicholas C. Zakas</p> <p>出版日期:2012年</p> <p>出版社:人民邮电出版社</p> <p>ISBN:9787115335503</p> </div> </body> </html> ``` 这是一个基本的HTML页面,标题是"书籍详情",页面内容包含一个书籍信息的区域。在这个例子中,我们展示了一本书的标题、作者、出版日期、出版社和ISBN号码。 当你将这段代码保存为一个HTML文件(例如book.html),并通过浏览器打开它,你将看到一个简单的网页,显示了书籍的信息。 当然,这只是一个非常简单的示例,一个真正的可运行网站页面需要更多的功能和交互性。构建一个完整的、实用的网站页面通常需要使用HTML、CSS和JavaScript等多种技术来实现。同,后端技术,如数据库和服务器端语言也经常用于网站的开发。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值