为 Duktape 添加 JavaScript 模块化加载能力

Duktape 是一个体积小巧、可移植性高、适合嵌入到各种环境中的 JavaScript 引擎。

最近需要将 protobuf.js 移植到 Duktape 里边运行起来,所以需要解决 JavaScript 模块化加载问题,也就是要支持 require, module.exports 语法。我们通过 modSearch 函数来实现模块化加载:

实现 modSearch 函数

Implementing a native modSearch() function这篇 guide 里边有说通过在 native 实现 modSearch 函数就可以在 JavaScript 里通过require的时候加载到别的模块。

我在 c 层实现 modSearch 函数如下:

//
// main.c
// duktape
//
// Created by faywong on 16/3/18.
// Copyright © 2016年 faywong. All rights reserved.
//
#include <stdio.h>
#include "duktape.h"
#include "fileio.h"
duk_ret_t my_mod_search(duk_context *ctx) {
/*
* index 0: id (string)
* index 1: require (object)
* index 2: exports (object)
* index 3: module (object)
*/
printf("fun: %s in, id: %s\n", __FUNCTION__, duk_require_string(ctx, 0));
const char *id = duk_require_string(ctx, 0);
duk_pop_n(ctx, duk_get_top(ctx));
const int FILE_PATH_LEN = 1024;
char file[FILE_PATH_LEN];
memset(file, 0, FILE_PATH_LEN);
snprintf(file, FILE_PATH_LEN, "/Users/faywong/%s.js", id);
duk_push_string_file(ctx, file);
return 1;
}
/*
* Register Duktape.modSearch
*/
void register_mod_search(duk_context *ctx) {
duk_eval_string(ctx, "(function (fun) { Duktape.modSearch = fun; })");
duk_push_c_function(ctx, my_mod_search, 4 /*nargs*/);
duk_call(ctx, 1);
duk_pop(ctx);
}
int main(int argc, const char * argv[]) {
duk_context *ctx = duk_create_heap_default();
if (ctx) {
register_mod_search(ctx);
register_fileio(ctx);
duk_eval_file(ctx, "/Users/faywong/test.js");
printf("result is: %s\n", duk_safe_to_string(ctx, -1));
duk_pop(ctx);
}
return 0;
}

test.js 用以验证实现的模块化加载功能是否正常,内容如下:

var ByteBuffer = require('bytebuffer');
var test = new ByteBuffer(10);
print('step 1, ByteBuffer ok: ' + test.toString());
var ProtoBuf = require('protobuf');
print('step 2, ProtoBuf ok: ' + (typeof ProtoBuf));
print('step 3, typeof ProtoBuf.loadProtoFile: ' + (typeof ProtoBuf.loadProtoFile));
var builder = ProtoBuf.loadProtoFile('/Users/faywong/complex.proto');
print('step 4, typeof builder: ' + (typeof builder));
Game = builder.build("Game"),
Car = Game.Cars.Car;
// OR: Construct with values from an object, implicit message creation (address) and enum values as strings:
var car = new Car({
"model": "Rustywq",
"vendor": {
"name": "Iron Inc.",
"address": {
"country": "US"
}
},
"speed": "SUPERFAST" // also equivalent to "speed": 2
});
// OR: It's also possible to mix all of this!
// Afterwards, just encode your message:
var buffer = car.encode();
print('step 5, typeof buffer: ' + (typeof buffer) + ' toString(): ' + buffer.toString());

其中:

  • register_mod_search 函数用于向 Duktape 注册一个用于加载 JavaScript 模块的函数 my_mod_search,该函数有四个入参,分别为模块 id、发起 require 的模块、本模块的 exports 对象、本模块的 module 对象,该函数加载 /Users/faywong 目录下以 id 为主文件名(比如在 test.js 中 require 到的 bytebuffer, protobuf)的 JavaScript 文件并将文件内容返回给 Duktape

  • 为了方便,test.js 中 require 的其他 JavaScript 模块被笔者放在了自己的家目录下:

    /Users/faywong/bytebuffer.js
    /Users/faywong/protobuf.js
    /Users/faywong/test.js