JSI
react-native v0.67.3
node v14.21.2
JSI全称JavaScript Interface,JavaScript接口层, 旨在抹平JS 虚拟机/引擎(JSCore/V8/quick js)等APi差异,封装统一的JS 虚拟机API,https://github.com/react-native-community/discussions-and-proposals/issues/91
N-API
类似JSI的概念在JSI中也有体现,叫做Node-API。官方的解释如下
Node-API(以前的N-API)是一个用于构建本地插件的API。它独立于底层的JavaScript运行时(例如,V8),并作为Node.js本身的一部分进行维护。该API将在Node.js的各个版本中保持应用二进制接口(ABI)的稳定性。它的目的是使附加组件不受底层JavaScript引擎变化的影响,并允许为一个主要版本编译的模块在后来的Node.js主要版本上运行而无需重新编译。
JSIRuntime
JSI定一个runtime protocol,以实现不同的runtime,JSI runtime 目前实现引擎是JSCRuntime
JSI Runtime提供的公开方法
class JSI_EXPORT Runtime {
public:
virtual ~Runtime();
virtual Value evaluateJavaScript(
const std::shared_ptr<const Buffer>& buffer,
const std::string& sourceURL) = 0;
virtual std::shared_ptr<const PreparedJavaScript> prepareJavaScript(
const std::shared_ptr<const Buffer>& buffer,
std::string sourceURL) = 0;
virtual Value evaluatePreparedJavaScript(
const std::shared_ptr<const PreparedJavaScript>& js) = 0;
/// \return the global object
virtual Object global() = 0;
};
JSI runtime提供基本的方法如evaluateJavaScript、获取global对象
prepare
另外JSI runtime 提供prepare的功能,把js data 存在内存中
prepare的使用
TEST_P(JSITest, PreparedJavaScriptSourceTest) {
rt.evaluateJavaScript(std::make_unique<StringBuffer>("var q = 0;"), "");
auto prep = rt.prepareJavaScript(std::make_unique<StringBuffer>("q++;"), "");
EXPECT_EQ(rt.global().getProperty(rt, "q").getNumber(), 0);
rt.evaluatePreparedJavaScript(prep);
EXPECT_EQ(rt.global().getProperty(rt, "q").getNumber(), 1);
rt.evaluatePreparedJavaScript(prep);
EXPECT_EQ(rt.global().getProperty(rt, "q").getNumber(), 2);
}
调用prepare 是返回一个SourceJavaScriptPreparation对象,里面有buffer 和 sourceURL的成员变量
evaluatePreparedJavaScript则是把SourceJavaScriptPreparation读取出来并且执行
JSIExecutor
JSIExecutor 是对JSI的封装,JSIExecutor继承自JSExecutor,原RCTObjcExecutor也实现了JSExecutor
class RN_EXPORT JSExecutor {
public:
/**
* Prepares the JS runtime for React Native by installing global variables.
* Called once before any JS is evaluated.
*/
virtual void initializeRuntime() = 0;
/**
* Execute an application script bundle in the JS context.
*/
virtual void loadBundle(
std::unique_ptr<const JSBigString> script,
std::string sourceURL) = 0;
/**
* Add an application "RAM" bundle registry
*/
virtual void setBundleRegistry(
std::unique_ptr<RAMBundleRegistry> bundleRegistry) = 0;
/**
* Register a file path for an additional "RAM" bundle
*/
virtual void registerBundle(
uint32_t bundleId,
const std::string &bundlePath) = 0;
/**
* Executes BatchedBridge.callFunctionReturnFlushedQueue with the module ID,
* method ID and optional additional arguments in JS. The executor is
* responsible for using Bridge->callNativeModules to invoke any necessary
* native modules methods.
*/
virtual void callFunction(
const std::string &moduleId,
const std::string &methodId,
const folly::dynamic &arguments) = 0;
/**
* Executes BatchedBridge.invokeCallbackAndReturnFlushedQueue with the cbID,
* and optional additional arguments in JS and returns the next queue. The
* executor is responsible for using Bridge->callNativeModules to invoke any
* necessary native modules methods.
*/
virtual void invokeCallback(
const double callbackId,
const folly::dynamic &arguments) = 0;
virtual void setGlobalVariable(
std::string propName,
std::unique_ptr<const JSBigString> jsonValue) = 0;
virtual void *getJavaScriptContext() {
return nullptr;
}
/**
* Returns whether or not the underlying executor supports debugging via the
* Chrome remote debugging protocol.
*/
virtual bool isInspectable() {
return false;
}
/**
* The description is displayed in the dev menu, if there is one in
* this build. There is a default, but if this method returns a
* non-empty string, it will be used instead.
*/
virtual std::string getDescription() = 0;
virtual void handleMemoryPressure(__unused int pressureLevel) {}
virtual void destroy() {}
virtual ~JSExecutor() {}
virtual void flush() {}
static std::string getSyntheticBundlePath(
uint32_t bundleId,
const std::string &bundlePath);
};
} // namespace react
} // namespace facebook
initializeRuntime
与js 的交互协议在JSIExecutor内的initializeRuntime函数注入的
比如关于invoke的注入是
runtime_->global().setProperty(
*runtime_,
"nativeFlushQueueImmediate",
Function::createFromHostFunction(
*runtime_,
PropNameID::forAscii(*runtime_, "nativeFlushQueueImmediate"),
1,
[this](
jsi::Runtime &,
const jsi::Value &,
const jsi::Value *args,
size_t count) {
if (count != 1) {
throw std::invalid_argument(
"nativeFlushQueueImmediate arg count must be 1");
}
callNativeModules(args[0], false);
return Value::undefined();
}));
可以支持set 函数
其函数在JSCRuntime内的实现是一个JSObject
jsi::Function JSCRuntime::createFunctionFromHostFunction(
const jsi::PropNameID &name,
unsigned int paramCount,
jsi::HostFunctionType func) {
JSObjectRef funcRef = JSObjectMake(
ctx_,
hostFunctionClass,
new HostFunctionMetadata(this, func, paramCount, stringRef(name)));
return createObject(funcRef).getFunction(*this);
}
using HostFunctionType = std::function<
Value(Runtime& rt, const Value& thisVal, const Value* args, size_t count)>;
void JSCRuntime::setPropertyValue(
jsi::Object &object,
const jsi::PropNameID &name,
const jsi::Value &value) {
JSValueRef exc = nullptr;
JSObjectSetProperty(
ctx_,
objectRef(object),
stringRef(name),
valueRef(value),
kJSPropertyAttributeNone,
&exc);
checkException(exc);
}
loadBundle
loadBundle的顺序是
- evaluateJavaScript
- flush
setBundleRegistry
RAM Bundle才支持
注入nativeRequire方法,并且保存RAMBundleRegistry参数到内存
nativeRequire方法的实现
- 调用RAMBundleRegistry::getModule的方法
- evaluateJavaScript得到的module的代码
registerBundle
如果存在bundleRegistry_(RAMBundleRegistry),则调用其registerBundle。没有则evaluateJavaScript
其中RAMBundleRegistry有一个map保存着bundleid和bundlepath的映射关系
JSIDynamic
valueFromDynamic
cpp的对象(folly::dynamic)转换为JS 对象(Value)
Value
整个JSI通过Value来表示各种数据类型,Value能够转换为其他的类型 Object String 等
Object的常见方法
Value getProperty(Runtime& runtime, const char* name) const;
template <typename T>
void setProperty(Runtime& runtime, const char* name, T&& value);
JSValueRef
在JSCRuntime中的valueRef方法,会最终把jsi的Value转换为JSValueRef
JSValueRef JSCRuntime::valueRef(const jsi::Value &value) {
// I would rather switch on value.kind_
if (value.isUndefined()) {
return JSValueMakeUndefined(ctx_);
} else if (value.isNull()) {
return JSValueMakeNull(ctx_);
} else if (value.isBool()) {
return JSValueMakeBoolean(ctx_, value.getBool());
} else if (value.isNumber()) {
return JSValueMakeNumber(ctx_, value.getNumber());
} else if (value.isSymbol()) {
return symbolRef(value.getSymbol(*this));
} else if (value.isString()) {
return JSValueMakeString(ctx_, stringRef(value.getString(*this)));
} else if (value.isObject()) {
return objectRef(value.getObject(*this));
} else {
// What are you?
abort();
}
}
JSINativeModules
createModule方法
首先从js侧获取到__fbGenNativeModule,详细见NativeModules.js
function genModule(
config: ?ModuleConfig,
moduleID: number,
): ?{
name: string,
module?: {...},
...
} {
const [moduleName, constants, methods, promiseMethods, syncMethods] = config;
if (!constants && !methods) {
// Module contents will be filled in lazily later
return {name: moduleName};
}
const module = {};
methods &&
methods.forEach((methodName, methodID) => {
const isPromise =
(promiseMethods && arrayContains(promiseMethods, methodID)) || false;
const isSync =
(syncMethods && arrayContains(syncMethods, methodID)) || false;
const methodType = isPromise ? 'promise' : isSync ? 'sync' : 'async';
module[methodName] = genMethod(moduleID, methodID, methodType);
});
Object.assign(module, constants);
if (module.getConstants == null) {
module.getConstants = () => constants || Object.freeze({});
}
return {name: moduleName, module};
}
// export this method as a global so we can call it from native
global.__fbGenNativeModule = genModule;
然后调用__fbGenNativeModule函数,传入ModuleConfig和moduleId,这两个的来源自m_moduleRegistry
__fbGenNativeModule函数会返回module到native侧,完成createModule的调用
m_moduleRegistry的数据源在RCTCxxBridge的_buildModuleRegistryUnlocked进行构建,这里会把_moduleDataByID(RCTModuleData)转换为RCTNativeModule
getModule方法,从m_objects中查找module
HostObject
实现该接口就可以在JS Runtime注册一个对象
class JSI_EXPORT HostObject {
virtual Value get(Runtime&, const PropNameID& name);
virtual void set(Runtime&, const PropNameID& name, const Value& value);
virtual std::vector<PropNameID> getPropertyNames(Runtime& rt);
};
这几个方法需要子类实现,可以看mmkv的例子
Object提供HostObject 转 Object的方法
class JSI_EXPORT Object : public Pointer {
static Object createFromHostObject(
Runtime& runtime,
std::shared_ptr<HostObject> ho) {
return runtime.createObject(ho);
}
template <typename T = HostObject>
std::shared_ptr<T> getHostObject(Runtime& runtime) const;
template <typename T = HostObject>
std::shared_ptr<T> asHostObject(Runtime& runtime) const;
};
Runtime也有相关方法
virtual Object createObject(std::shared_ptr<HostObject> ho) = 0;
virtual std::shared_ptr<HostObject> getHostObject(const jsi::Object&) = 0;
virtual HostFunctionType& getHostFunction(const jsi::Function&) = 0;
virtual bool isHostObject(const jsi::Object&) const = 0;
virtual bool isHostFunction(const jsi::Function&) const = 0;
virtual Function createFunctionFromHostFunction(
const PropNameID& name,
unsigned int paramCount,
HostFunctionType func) = 0;