Kotlin Jupyter 支持 JavaScript 魔法标签

缘起

工作上需要了解盲水印,而这涉及傅里叶变换。我对傅里叶变换一窍不通,想着通过 Kotlin Jupyter 来一步一步用代码来演示傅里叶变换,辅助理解。然而 Kotlin Jupyter 里没有找到 3D 曲面的展示库,像 lets-plot 似乎只支持 2D 图表。

经过尝试,在 Kotlin Jupyter 只能使用 JS 实现 3D 曲面的展示。而 Kotlin Jupyter 里使用 JS 比较繁琐。你需要先将 kotlin 变量转为 JSON 字符串,在HTML()方法写要执行 html 代码,容器标签和<script>标签,

kt 复制代码
// convert to JSON
var dataList = "[" + bList.map { "[${it.first}, ${it.second}, ${it.third}]" }.joinToString(",\n") + "]";

// render to html
HTML("""
<div id="chartDom" style="width: 600px; height: 600px;"> </div>
<script type="module">
import { init } from "https://unpkg.com/echarts@5.4.3/dist/echarts.esm.min.js"
import "https://unpkg.com/echarts-gl@2.0.9/dist/echarts-gl.min.js"

var chartDom = document.getElementById('chartDom');
var myChart = echarts.init(chartDom);
myChart.setOption({
  // echart option
})
</script>
""")

Don't Repeat Yourself

可以看到上面的例子有好几处模板代码。

  • kotlin 转 Json
  • 创建容器<div>标签
  • 创建<script>标签

ipython 就支持%js直接写js,cell 执行前会拦截有%js标记的代码,转为<script />标签插入的输出 cell。

在 Kotlin Jupyter 我们也通过自定义 line magic,来生成这些模版代码。Kotlin Jupyter 已经提供给了相关的钩子(kotlin-jupyter/docs/libaries.md),我们需要做的就是写一个CodePreprocessor拦截,含有%js的代码,转为HTML函数调用。

比如下面的:

js 复制代码
%js
var hello = "hellow jupyter js"

console.log(hello)

需要转换为:

kotlin 复制代码
HTML("""
<script type="module">
var hello = "hellow jupyter js"

console.log(hello)
</script>
""")

One More Thing

但是还有更重要的一件事。我们在 Kotlin Jupyter 里写 JS 的目的是为了可视化 Kotlin 的数据,仅仅只是转换代码没有实用价值。我们需要能在 JS 里使用 Kotlin 的变量。

我们如何在 JS 里使用 kotlin 的数据呢?

我的想法是就是虚拟 import。定义@jupyter为虚拟 package,我们可以从这里 import Kotlin 的变量,编译时替换成真实的 kotlin 变量

假设,第一个 cell 定义了一个 Kotlin 变量

kt 复制代码
val foo = "bar";

后面的cell,直接 import 这个变量然后使用即可

js 复制代码
%js
import { foo } from '@jupyter';

console.log('variable from kotlin', foo)

实际的编译结果:

html 复制代码
<script type="module">
const foo = "foo"

console.log('variable from kotlin', foo)
</script>

到这里,解决的问题变成了变量从 Kotlin 世界到 JS 世界的转换。对于任意的 Kotlin 变量可以转为 JSON 吗?

根据源码 VariableState.kt#L11 可知:所有的 Kotlin 变量都被保存成Any。显然我们是无法将Any转为 JSON 字符串。

但是,如果对于我们可以缩小支持转换成 JSON 的类型范围,那么就是可以做到的。

  1. 基本类型/Array/Collection/Map
  2. 使用了 Renderable/DisplayResult 接口

根据 Kotlin/kotlinx.serialization#296 的讨论,我们在Any?实现toJsonElement方法就可以做到将任意的Collection,Map,Array,String,Boolean,Number 转为 JSON。这已经足够能够支持大多数场景了。

下面的函数就能够递归的将基础类型转为JsonElement,然后将JsonElement转为字符串就很方便了。

kt 复制代码
fun Any?.toJsonElement(): JsonElement = when(this) {
    null -> JsonNull
    is Collection<*> -> toJsonElement() // call Collection<*>.toJsonElement()
    is String -> JsonPrimitive(this) // end of recursive
    // ... ignore Map<*, *> Array<*>, other primary type
    else -> {
      throw IllegalStateException("Can't serialize unknown type: $this")
    }
}

fun Collection<*>.toJsonElement(): JsonElement {
    return JsonArray(this.map {
      it.toJsonElement() // recursively transform value to JsonElement 
    })
}

完整代码:AnyToJsonElement.kt#L5

但是,这种方式不支持类,对于类的支持需要另一种方式。实现DisplayResult或者Renderable 接口。因为DisplayResulttoJSon 方法的,通过这个方法就能获取到可以 import 的 json 对象。

kt 复制代码
when (value) {
  is DisplayResult -> {
    value.toJson()
  }
  is Renderable -> {
    value.render(notebook).toJson()
  }
}

From JavaScript To JavaScript

上面提到虚拟 import,需要 import 语句能够被编译成变量声明。我们可以通过正则表达式来将替换 import 语句替换成变量声明,但这不是一个处理代码的好方式。最好能够将 JS 代码转换成 AST,我们操作 AST 进行代码变换。

但是 Kotlin 里并没有一个好的工具了来编译 JS。常见的 JS 编译工具,比如:babel 都是 JS 写的,很难在 JVM 里使用。但是,感谢最近几年前端工具链的锈化,已经有了SWC,OXC等 Rust 写的 JS 编译器。现在,可以通过JNI,Kotlin也可以"原生"支持编译JS

但是,社区似乎没有现成 swc 的 binding。不过,写一个 binding 总比写一个JS 编译器简单。

我实现了 SWC 的 binding swc-binding

有了 SWC 以后,那我们不光可以支持 jsts/jsx/tsx 也可支持了。

参考下面的流程图:

如果给 Kotlin Kernel 的代码里包含%js magic,JavaScriptMagicCodeProcessor就会将 JS 代码处理成合法的 kotlin 代码。

JavaScriptMagicCodeProcessor处理流程如下

第一步,会将 jsx/ts/tsx 转换为正常的 JS,如果是 JS 在不会处理

第二步,操作 AST

  1. import { * } from '@jupyter'; 变量声明语句
  2. jsx/tsx 的默认导出修改为变量声明
  3. 其它操作

第三步,AST 转回代码

最后将 JS 代码包装的成 HTML 结果返回。

尾声

至此,kotlin Jupyter 支持 %js magic 的思路就梳理清楚了。

Echart 的例子截图:

实际例子,可以看一下examples/js-magic.ipynb

欢迎大家试用,反馈问题。

github: yidafu/kotlin-jupyter-js

相关推荐
weixin_4640780717 天前
Pycharm中Jupyter Notebook 插件常用快捷键
ide·jupyter·pycharm
逆羽飘扬17 天前
【JupyterLab集成】GPU性能监控可视化组件
人工智能·python·jupyter·gpu监控
The丶Star17 天前
【解决CMD命令行下无法正常打开jupyter notebook的特殊办法(关闭防火墙版)】
ide·python·jupyter
不讲魔法讲道理17 天前
(202506最新)Jupyter Notebook显示目录的导航栏
ide·python·jupyter
摘取一颗天上星️18 天前
Jupyter 是什么?基于浏览器的交互式计算环境
ide·chrome·jupyter
lyb0619 天前
关于 jupyter 找不到虚拟环境中安装好的包的问题
ide·深度学习·jupyter
路由侠内网穿透19 天前
本地部署 Jupyter 并实现外部访问(Windows 版本)
服务器·ide·windows·网络协议·tcp/ip·jupyter
mwcxz20 天前
pycharm 2025.1.1-专业版jupyter notebook远程连接
jupyter·pycharm
纪伊路上盛名在23 天前
jupyter内核崩溃
前端·数据库·jupyter·生物信息·基因组·k-mer
简放24 天前
Cursor-1.0安装Jupyter-Notebook,可视化运行.ipynb文件中Python分片代码
jupyter·ai编程·cursor