2019年末的某天,我在某个科技网站的报道中似乎听到了:神级程序员Fabrice Bellard手撸了一个类V8引擎,编译后不到1M,精简后可低至500k。当时马上到搜索引擎中寻找,找到了QuickJS的主页链接 bellard.org/quickjs 。抽空快速看了一下文档,立刻惊为天人,这不就是一个微型NodeJs核心吗?当时就打算入手试试,但手上没时间,就一直拖到了今天。
这2天刚好休息,利用空闲时间动手了解了一下,以下做一个简单的实操教程,稍后有空将会制作成视频教程。
注:以下为萧鸣本人全程实操,所有程序调用及编译都正常通过,并且得到最后结果,确保不浪费大家宝贵的时间。
QuickJS是什么?
QuickJS是一个小型且可嵌入的Javascript引擎。它支持 ES2020规范,包括模块,异步生成器,代理和BigInt。它可选地支持数学扩展,例如大十进制浮点数(BigDecimal),大二进制浮点数(BigFloat)和运算符重载。
内置标准std的实现、os模块文件与系统操作模块的实现、多线程Worker、正则表达式、完整的模块引入机制,以及自定义动态链接库的引入。
它的qjsc编译器,可以将你的js文件编译成单个可执行文件,通过优化后,体积可以缩小至500k左右,所以它非常适合使用在嵌入式设备,或者嵌入到其他项目中去。
QuickJS是基于MIT协议发布的,大家可以访问它的官网并下载源码进行研究学习
神级作者
1972年生的FabriceBellard,神级程序员之一。
说说他的作品你就明白为什么称为神级了:
FFmpeg(流媒体和视频制作哪个不是用它做底层,20年前年幼的我看到各种解霸,感觉人家做媒体解码软件的太牛了,结果现在一看,bin文件夹下赫然躺着FFmpeg)
QEMU 最牛的模拟器谁用谁知道
他也是最快圆周率算法贝拉公式、TCCBOOT和TCC(微型C编译器)
好像还抽空搞了一个 TinyOpenGL
还用js写了一个可以运行在浏览器上的jsLinux(请自行查阅)
安装方式:源码安装(Linux与MacOS)
使用make命令来安装.可以不使用make install,直接在当前文件夹下可以找到编译后的文件
make && make install
mac下可以直接用homebrew安装
brew install quickjs
安装成功之后,我们可以直接执行qjs命令,我们可以看到我们进入了一个交互式编程界面,熟悉Chorme控制台或者Python编程的同学,应该理解在里面进行编码实验。
交互式编程界面环境中,默认导入了std和os模块,可以直接使用,在js文件中,则需要导入模块后使用。
使用qjs来执行一个js文件
编辑一个js文件,保存为test.js
/* 引入os模块 */
import * as os from 'os';
/* setTimeout方法在os模块中*/
os.setTimeout(() => {
console.log("hello 萧鸣!")
}, (500));
保存文件,我们用qjs来解释执行它
> qjs test.js
得到如下执行结果
用qjsc将test.js 编译成单个可执行文件
必须将源码中的quickjs.h、quickjs-libc.h、libquickjs.a 三个文件拷贝到test.js同目录下
如果怕麻烦,可以把你的文件拷贝到源码中进行编译
> qjsc test.js -o test
> ls
> ./test
这种编译成单个二进制文件的形式,不依赖任何外部环境与程序,去除一些当前项目不需要的库(比如正则库等)可以极大减小二级制文件的体积,可以精简至500k左右。
而且因为QuickJS里内置了Worker,以及可以通过exec来调用外部程序,我们可以用它来做很多事情,比如文件监听、进程守护等。
内置模块快速预览(具体请查询官网)
std模块:
包含了常用的evalScript、loadScript、loadFile、open、popen、fdopen、tmpfile、puts、printf、sprintf、strerror、gc、getenv、urlGet、parseExtJSON
包装了libc的stdin、stdout、stderr。
Error,包含了常见的枚举对象,并可以自定义其他错误代码:EINVAL、EIO、EACCES、EEXIST、ENOSPC、ENOSYS、EBUSY、ENOENT、EPERM、EPIPE
FILE原型:
close、puts、printf、flush、seek、tell、tello、eof、error、clearerr、read、write、getline、readAsString、getByte、putByte
os模块:提供了操作系统特定的 低级文件访问、信号、计时器、异步IO、线程 方法
open、close、seek、read、write、isatty、ttyGetWinSize、ttySetRaw、remove、rename、realpath、getcwd、chdir、mkdir、stat、lstat、utimes、symlink、readlink、readdir、setReadHandler、setWriteHandler、signal、kill、exec、waitpid、WNOHANG、dup、dup2、pipe、sleep、setTimeout、clearTimeout、platform、Worker
Unicode
QuickJS实现了特定的Unicode库,不用依赖外部的大型Unicode库(比如ICU)。所有的Unicode表都经过了压缩来提高访问速度(果然神级程序员)。
如何自己扩展 QuickJS C API
C API的设计简单有效,C API在 quickjs.h中进行定义
JS数据类型的C定义
以下结构定义在quickjs.h文件中
typedef struct JSValue {
/*用来存放指针或数组*/
JSValueUnion u;
/*标记 undefined 等*/
int64_t tag;
} JSValue;
typedef union JSValueUnion {
/*指针*/
void *ptr;
/*整型*/
int32_t int32;
/*浮点*/
double float64;
} JSValueUnion;
编写一个C模块
关键点在于定义API函数的入口、定义初始化的回调函数并暴露处理、定义初始化模块方法(系统自动调用,这个函数名称只能遵循qjs的规则)
/* 创建文件 test.c */
#include "quickjs.h"
#include "stdio.h"
#include "stdlib.h"
#define len(x) (sizeof(x) / sizeof((x)[0]))
/* 功能函数,实现你的具体功能的C函数 */
static double test_add(int a, double b)
{
return a + b;
}
/* *ctx=上下文;this_val=this对象;argc=参数个数; *argv=参数列表*/
static JSValue js_test_add(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
int a;
double b;
if (JS_ToInt32(ctx, &a, argv[0])){ return JS_EXCEPTION; }
if (JS_ToFloat64(ctx, &b, argv[1])){ return JS_EXCEPTION; }
return JS_NewFloat64(ctx, test_add(a, b));
}
/* js函数入口名称及列表*/
static const JSCFunctionListEntry js_test_funcs[] = {
/* 表示入口名称是testAdd,2个参数,调用内部哪个函数*/
JS_CFUNC_DEF("testAdd", 2, js_test_add),
};
/* 初始化的回调方法(由系统调用),将函数入口列表暴露出来*/
static int js_test_init(JSContext *ctx, JSModuleDef *m)
{
return JS_SetModuleExportList(ctx, m, js_test_funcs, len(js_test_funcs));
}
/* 模块初始化,由系统调用,名称和参数不可修改,遵循qjs的规则*/
JSModuleDef *js_init_module(JSContext *ctx, const char *module_name)
{
JSModuleDef *module;
module = JS_NewCModule(ctx, module_name, js_test_init);
if (!module){ return NULL; }
JS_AddModuleExportList(ctx, module, js_test_funcs, len(js_test_funcs));
return module;
}
gcc test.c libquickjs.a -fPIC -shared -o test.so
萧鸣注: 这里会碰到一个坑,可能在编译期间会提示你:
libquickjs.a无法创建动态链接库,要使用 -fPIC进行编译
what?我不是用了 -fPIC参数了吗?实际上这里说的是整个qjs源码make时要加入-fPIC参数
我们找到源码里的makefile,打开找到以下内容
HOST_CC=gcc
CC=$(CROSS_PREFIX)gcc
CFLAGS=-g -Wall -MMD -MF $(OBJDIR)/$(@F).d
然后在 CFLAGS 这行增加参数 -fPIC, 然后重新make即可
CFLAGS=-g -fPIC -Wall -MMD -MF $(OBJDIR)/$(@F).d
在js脚本中调用这个动态链接库
import { testAdd} from 'test.so'
console.log(`萧鸣测试 testAdd(200,10.5)`)
console.log(`结果等于: ${testAdd(200, 10.5)}`)
执行后得到以下结果
总结
经过本人亲手试验, QuickJS可以灵活地使用在一些嵌入式程序之中,因为体积小可以嵌入到单片机中使用,通过自己扩展c方法,提供js对单片机的控制。
同时也可以参考cocos2d-x之类游戏引擎,利用嵌入的Quickjs来操纵底层的图形图像接口,来做一个自己的游戏引擎。
也可以嵌入到一些软件中去,用js来调用软件内部的功能。
也可以嵌入到安卓和苹果app中去,形成自己动态的脚本配置。
总之只有想不到没有做不到。
其他:
后续更多对于QuickJS的尝试,比如将QuickJS嵌入到nginx中去,直接在nginx中提供js脚本提供web服务等,将在后续的视频教程中奉献给大家。
本文暂时没有评论,来添加一个吧(●'◡'●)