在Android中和iOS中运行JavaScript函数-2
zipline-android 调用JavaScript库
接上文 在Android中运行JavaScript函数-1,以下是一个可用的通过Zipline调用JavaScript 库的例子:
1
2
// 引入依赖库
implementation("app.cash.zipline:zipline-android:1.17.0")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import app.cash.zipline.Zipline
import kotlinx.coroutines.*
import kotlinx.coroutines.test.StandardTestDispatcher
import org.junit.Assert.*
import org.junit.Test
class AstrolabeTest {
@Test
fun testAstrolabe() = runBlocking {
val testDispatcher = StandardTestDispatcher()
// 创建 Zipline 实例,传入 dispatcher 参数
val zipline = Zipline.create(dispatcher = testDispatcher)
// 获取应用程序上下文
val context = ApplicationProvider.getApplicationContext<Context>()
// 定义必要的全局变量
val initScript = """
var self = this;
var navigator = { userAgent: 'zipline' };
var console = {
log: function(message) { },
error: function(message) { },
warn: function(message) { },
info: function(message) { },
debug: function(message) { }
};
""".trimIndent()
zipline.quickJs.evaluate(initScript)
// 加载要调用的 example.min.js 文件
val jsSource = loadJsFromAssets(context, "example.min.js")
// 捕获评估 example.min.js 时的异常
try {
zipline.quickJs.evaluate(jsSource)
} catch (e: Exception) {
println("Error evaluating example.min.js: ${e.message}")
fail("Failed to evaluate example.min.js: ${e.message}")
return@runBlocking
}
// 检查 'foo' 是否已定义
val fooDefined = zipline.quickJs.evaluate("typeof foo !== 'undefined';") as Boolean
if (!fooDefined) {
println("'foo' is not defined after evaluating foo.min.js")
fail("'foo' is not defined after evaluating foo.min.js")
return@runBlocking
}
// 定义要执行的 JavaScript 脚本
val script = """
JSON.stringify(foo.bar('param1', 'parma2'));
""".trimIndent()
// 执行脚本并获取结果字符串
val result: String
try {
result = zipline.quickJs.evaluate(script) as String
} catch (e: Exception) {
println("Error executing script: ${e.message}")
fail("Failed to execute script: ${e.message}")
return@runBlocking
}
// 打印结果字符串
println("Foo#bar Result String: $result")
}
// 从 assets 中加载 JS 文件
private fun loadJsFromAssets(context: Context, fileName: String): String {
return context.assets.open(fileName).bufferedReader(Charsets.UTF_8).use { it.readText() }
}
}
经过排查,Zipline
不能成功调用js库的原因是原项目webpack打包时设置`libraryTarget: ‘umd’将库打包成 通用模块定义(Universal Module Definition,UMD) 格式,将 UMD 格式的特点是兼容多种模块系统,包括 CommonJS、AMD 和作为全局变量的方式,虽然UMD兼容性广泛,但在没有模块系统的环境下,可能无法正确地将库暴露为全局变量。
1
2
3
4
5
6
7
8
9
10
module.exports = {
mode: 'none',
entry: './src/index.ts',
output: {
path: path.resolve(__dirname, 'dist'), // 指定打包文件的目录
filename: `example.min.js`, // 打包后文件的名称
library: 'foo', // 将打包后的代码作为一个全局变量可直接调用
libraryTarget: 'var', // 此处原本为 umd
umdNamedDefine: true, // 为UMD模块命名
}...
在 Zipline(QuickJS 引擎) 的环境中:
- 不支持模块系统:Zipline 不支持 CommonJS、AMD 或 ES Modules 等模块系统。
- UMD 包裹可能无法正确执行:由于环境中不存在
define
、module.exports
等,UMD 包裹的代码可能无法正确地将库挂载到全局对象上。 - 全局对象识别问题:UMD 包裹在尝试将库挂载到全局对象时,无法正确识别全局对象(如
window
、global
、self
等)。
因此,当使用 'umd'
作为 libraryTarget
时,Zipline 环境无法正确加载库,导致 foo
变量未被定义。
将 libraryTarget
修改为 'var'
后,Webpack 会以简单的方式将库导出为全局变量:
1
2
3
var foo = (function() {
// 库代码
})();
这样,foo
变量直接在全局作用域中被定义,且不依赖任何模块系统。
iOS JavaScriptCore调用JavaScript库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import XCTest
import JavaScriptCore
class AstrolabeTests: XCTestCase {
func testAstrolabe() {
// 创建一个 JSContext
let context = JSContext()
// 处理异常
context?.exceptionHandler = { context, exception in
if let exception = exception {
print("JavaScript Exception: \(exception)")
}
}
// 设置全局对象
if let globalObject = context?.globalObject {
// 定义 self
globalObject.setValue(globalObject, forProperty: "self")
} else {
XCTFail("无法获取 JSContext 的全局对象")
return
}
// 加载 example.min.js 文件
guard let jsSourcePath = Bundle(for: type(of: self)).path(forResource: "example.min", ofType: "js") else {
XCTFail("无法找到 example.min.js 文件")
return
}
do {
// 使用指定的编码方式读取文件内容
let jsSourceContents = try String(contentsOfFile: jsSourcePath, encoding: .utf8)
context?.evaluateScript(jsSourceContents)
} catch {
XCTFail("加载 JavaScript 文件时出错: \(error)")
return
}
// 检查 foo 是否已定义
if context?.objectForKeyedSubscript("foo").isUndefined == true {
XCTFail("foo 未定义")
return
}
// 调用 foo.bar 函数并返回 JSON 字符串
let script = """
JSON.stringify(foo.bar('1','2'));
"""
if let result = context?.evaluateScript(script) {
// 获取结果字符串
if let jsonString = result.toString() {
print("Result JSON String: \(jsonString)")
} else {
XCTFail("无法将结果转换为数据")
}
} else {
XCTFail("无法获取结果字符串")
}
}
}
iOS的调用相对容易,并且支持UMD格式打包的js文件。
本文由作者按照 CC BY 4.0 进行授权