主机页面与 IFrame 中运行的 JupyterLite 实例之间的通信#

当 JupyterLite 实例通过 IFrame 嵌入到网站中时,可能需要在主机页面和实例之间建立通信通道。

在以下内容中,我们将构建一个前端扩展,允许位于 IFrame 中的 JupyterLite 实例接收和处理由主机页面上的按钮触发的主题更改命令。该实例还可以向主机页面发送消息。

此扩展首先为 JupyterLab 构建,然后为 JupyterLite 构建。

创建开发环境并初始化项目#

最好为扩展开发创建一个特定环境。有多种方法可供选择。这里我们使用 conda,这是一个包和环境管理器。conda 的安装过程 在此

按照 JupyterLab 教程 创建环境

conda create -n jupyterlab-iframe-ext --override-channels --strict-channel-priority -c conda-forge -c nodefaults jupyterlab=4 nodejs=20 git copier=7 jinja2-time jupyterlite-core
conda activate jupyterlab-iframe-ext

在您的工作区中创建一个目录,移动到该目录,然后使用 copier 生成扩展模板

copier copy https://github.com/jupyterlab/extension-template .
Select kind:
1 - frontend
2 - server
3 - theme
Choose from 1, 2, 3 [1]: 1
author_name [My Name]:
author_email [[email protected]]:
labextension_name [myextension]: jupyterlab-iframe-bridge-example
python_name [jupyterlab-iframe-bridge-example]: jupyterlab-iframe-bridge-example
project_short_description [A JupyterLab extension.]: Communication between a host page and an instance of JupyterLab located in an IFrame
has_settings [n]: n
has_binder [n]: n
test [y]: n
repository [https://github.com/github_username/jupyterlab-iframe-bridge-example]:

最后,将依赖项和扩展(目前为空)安装到环境中,然后从 JupyterLab 创建一个指向源代码的符号链接,以避免每次修改后都运行 pip install

cd jupyterlab-iframe-bridge-example
pip install -ve .
jupyter labextension develop --overwrite .

扩展开发#

使用文本编辑器修改文件 jupyterlab-iframe-bridge-example/src/index.ts

由于宿主页面会要求 IFrame 更改主题,因此请导入支持主题管理的插件。

import { JupyterFrontEnd, JupyterFrontEndPlugin } from '@jupyterlab/application';

import { IThemeManager } from '@jupyterlab/apputils';

还需要修改 plugin 对象的代码。

/**
 * Initialization data for the jupyterlab-iframe-bridge-example extension.
 */
const plugin: JupyterFrontEndPlugin<void> = {
  id: 'jupyterlab-iframe-bridge-example:plugin',
  autoStart: true,
  requires: [IThemeManager],
  activate: (app: JupyterFrontEnd, themeManager: IThemeManager) => {
    console.log('JupyterLab extension jupyterlab-iframe-bridge-example is activated!');

    /* Incoming messages management */
    window.addEventListener('message', (event) => {
      if (event.data.type === 'from-host-to-iframe') {
        console.log('Message received in the iframe:', event.data);

        if (themeManager.theme === 'JupyterLab Dark') {
          themeManager.setTheme('JupyterLab Light');
        } else {
          themeManager.setTheme('JupyterLab Dark');
        }
      }
    });

    /* Outgoing messages management */
    const notifyThemeChanged = (): void => {
      const message = { type: 'from-iframe-to-host', theme: themeManager.theme };
      window.parent.postMessage(message, '*');
      console.log('Message sent to the host:', message);
    };
    themeManager.themeChanged.connect(notifyThemeChanged);
  },
};

export default plugin;

themeManager 对象实现了 IThemeManager 接口,其 文档 列出了可访问的属性和方法。

需要区分两种情况:接收来自宿主页面的消息和向宿主页面发送消息。

对于第一种情况,我们使用 themeManager.theme 属性来识别当前主题,并使用 themeManager.setTheme 方法来更改主题。当 IFrame 接收类型为 from-host-to-iframe 的消息时,将触发主题更改。

对于第二种情况,使用 themeManager.themeChanged 属性。当主题实际更改时,会触发此信号。它用于通过 postMessage 方法 (文档) 通知宿主页面。

从 JupyterLab 扩展到 JupyterLite 扩展#

首先安装之前代码中显示的依赖项。

jlpm add @jupyterlab/apputils
jlpm add @jupyterlab/application

构建 JupyterLab 扩展。

jlpm run build

JupyterLab 扩展已创建!以下命令检查它是否在环境中正确加载。

jupyter labextension list

移动到应该测试扩展的目录,并运行此扩展的构建以用于 JupyterLite。

mkdir examples
cd examples
jupyter lite build --output-dir lite

以下行显示扩展已成功构建。

...
federated_extensions:copy:ext:jupyterlab-iframe-bridge-example
.  pre_build:federated_extensions:copy:ext:jupyterlab-iframe-bridge-example
...

一个包含 JupyterLite 工作所需的所有内容的 jupyterlab-iframe-bridge-example/examples/lite/ 目录已创建(注意我们的扩展在 extensions 子目录中)。

测试扩展#

要测试包含 JupyterLite 的宿主页面和 IFrame 之间的通信,请创建一个文件 jupyterlab-iframe-bridge-example/examples/index.html。使用以下代码编辑此文件。

<html>
  <title>Example bridge between a host app and JupyterLite</title>
  <body>
    <script type="text/javascript">
      /* Outgoing messages */
      function toggle() {
        window.frames.jupyterlab.postMessage({ type: 'from-host-to-iframe' });
      }

      /* Incoming messages */
      window.addEventListener('message', (event) => {
        if (event.data.type === 'from-iframe-to-host') {
          document.getElementById('chosenTheme').innerText = event.data.theme;
        }
      });
    </script>
    <h2>Below is a JupyterLite site running in an IFrame</h2>
    <p>
      Click the following button sends a message to the JupyterLab IFrame to toggle the
      theme.
    </p>
    <p>The IFrame indicates that the current theme is: <em id="chosenTheme"></em></p>
    <input type="button" value="Toggle the JupyterLab Theme" onclick="toggle()" />
    <iframe
      name="jupyterlab"
      src="lite/"
      width="100%"
      height="600px"
      sandbox="allow-scripts allow-same-origin"
    ></iframe>
  </body>
</html>

当用户单击按钮时, toggle 函数通过 postMessage 方法向 IFrame 发送消息。此消息被我们的扩展拦截,该扩展更改主题。此外,当宿主页面收到来自 IFrame 的通知有效主题更改的消息时,它会将其显示给用户。

为了可视化此过程,请从 examples 目录启动一个最小服务器。

cd examples
python -m http.server -b 127.0.0.1

在浏览器中,在地址 http://127.0.0.1:8000,您应该能够注意到宿主页面和 IFrame 之间的通信(如果需要,请刷新浏览器)。

jupyterlite-bridge-iframe

此外,浏览器控制台应显示类似于以下内容的消息。

image