From 09adca8c82c585b6f6f769f090c75278d2e82637 Mon Sep 17 00:00:00 2001 From: Trevor Manz Date: Sat, 31 Aug 2024 13:59:24 -0400 Subject: [PATCH 1/7] docs: Add AFM section --- docs/src/consts.ts | 4 + .../pages/en/anywidget-front-end-module.md | 228 ++++++++++++++++++ 2 files changed, 232 insertions(+) create mode 100644 docs/src/pages/en/anywidget-front-end-module.md diff --git a/docs/src/consts.ts b/docs/src/consts.ts index caa3dbd4..a1c46de5 100644 --- a/docs/src/consts.ts +++ b/docs/src/consts.ts @@ -53,6 +53,10 @@ export const SIDEBAR: Sidebar = { { text: "Community", link: "en/community" }, ], Learn: [ + { + text: "Anywidget Front-End Module", + link: "en/anywidget-front-end-module", + }, { text: "Jupyter Widgets: The Good Parts", link: "en/jupyter-widgets-the-good-parts", diff --git a/docs/src/pages/en/anywidget-front-end-module.md b/docs/src/pages/en/anywidget-front-end-module.md new file mode 100644 index 00000000..e63495f0 --- /dev/null +++ b/docs/src/pages/en/anywidget-front-end-module.md @@ -0,0 +1,228 @@ +--- +title: "Anywidget Front-End Module (AFM)" +description: "A specification for portable widgets based on ECMAScript modules." +layout: ../../layouts/MainLayout.astro +--- + +## What is AFM? + +The **Anywidget Front-End Module (AFM)** specification defines a standard for +creating portable widget front-end code. Our vision is to enable widget reuse +**beyond Jupyter**, including other computational notebooks and standalone web +applications. AFM is oriented around a minimal set of APIs we identified as +essential for integration with _host platforms_, boiling down to: + +- Bidirectional communication with a host (e.g., Jupyter) +- Modifying output areas (DOM manipulation) (e.g., a notebook output cell) + +## Core Concepts + +### Front-End Module + +The Anywidget Front-end Module is widget front-end code authored by a widget +developer. It contains the front-end logic of a widget, which is defined by +implementing various [lifecycle methods](#lifecycle-methods) to control the +widget's behavior. AFM is a web-standard ECMAScript module (ESM) can be +authored simply as a text file or generated from a more complex front-end +toolchain. + +### Host platform + +The web-based environment in which a widget is embedded. It is responsible for +loading AFM and calling [lifecycle methods](#lifecycle-methods) with required +the platform APIs. + +The `anywidget` Python library provides the necessary glue code to make all +Jupyter-like environments (e.g., Jupyter Notebook, JupyterLab, Google Colab, VS +Code) an AFM-compatible host platform. The +[marimo](https://github.com/marimo-team/marimo) project is a _native_ host +platform. + +## Anywidget Front-end Module (AFM) + +An Anywidget Front-End Module (AFM) is an [ECMAScript +module](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) +that defines a widget's behavior through [lifecycle +methods](#lifecycle-methods). + +```js +export default { + initialize({ model }) { + // Set up shared state or event handlers. + return () => { + // Optional: Called when the widget is destroyed. + } + }, + render({ model, el }) { + // Render the widget's view into the el HTMLElement. + return () => { + // Optional: Called when the view is destroyed. + } + } +} +``` + +A host platform is expected to: + +- Load this module. +- Call the lifecycle methods, passing in dependencies (`model` & `el`). + +All browsers support ESM, so loading the module is supported natively across +web-based environments. It is then the host platform's responsibility to +implement the required [`model` interface](#model-interface) and provide an +output DOM element (`el`). This simple mechanism allows new host platforms to +be implemented as long as they adhere to these requirements. + +### Lifecycle Methods + +The widget lifecycle in AFM follows a Model-View pattern, consisting of two +main phases: + +- **Model Initialization**: Occurs when a widget is first created, setting up +the model and any shared state. +- **View Rendering**: Happens each time a widget needs to be displayed, +potentially multiple times for a single widget instance. + +AFM exports methods that correspond to these lifecycle phases. The default +export object specifies one or more widget lifecycle hooks: + +- `initialize({ model })`: Executed once per widget instance during model +initialization. It has access to `model` to setup non-view event handlers or +state to share across views. + +- `render({ model, el })`: Executed once per view during view rendering. It has +access to both the `model` and an `el` DOM element. Used to setup event +handlers or access state specific to that view. + +Each method MAY return a **cleanup function** which will be called when the +widget is destroyed or the view is removed. + +#### Additional Setup Logic + +The default export may also be a function that returns this interface. This +option can be useful to setup some front-end specific state for the lifecycle +of the widget: + +```js +export default async () => { + let extraState = {}; + return { + initialize({ model }) { /* ... */ }, + render({ model, el }) { /* ... */ }, + } +} +``` + +Here, `initialize` and `render` both will have access to `extraState` during the +lifetime of the widget. + +### Model interface + +The `model` interface in AFM is loosely based on traditional Jupyter Widgets but +defines a _narrower_ subset of APIs. This approach maintains familiarity for +widget developers while requiring host platforms to implement a small subset of +APIs to be a proper host. + +The simplified interface is: + +```typescript +/** + * The model interface for an Anywidget Front-End Module + * @see {https://github.com/manzt/anywidget/tree/main/packages/types} for complete types + */ +interface AnyModel { + /** Get a property value from the model + * @param key The key of the property to get + * @returns The value of the property + */ + get(key: string): any; + /** + * Set a property value in the model + * @param key The key of the property to set + * @param value The value to set + */ + set(key: string, value: any): void; + /** + * Remove an event listener + * @param eventName The name of the event to stop listening to + * @param callback The callback function to remove + */ + off(eventName?: string | null, callback?: Function | null): void; + /** + * Add an event listener for custom messages + * @param eventName Must be "msg:custom" + * @param callback The function to call when a custom message is received + */ + on(eventName: "msg:custom", callback: (msg: any, buffers: DataView[]) => void): void; + /** + * Add an event listener for property changes + * @param eventName The name of the event, in the format "change:propertyName" + * @param callback The function to call when the property changes + */ + on(eventName: `change: ${string}`, callback: Function): void; + /** + * Commit changes to sync with the backend + */ + save_changes(): void; + /** + * Send a custom message to the backend + * @param content The content of the message + * @param callbacks Optional callbacks for the message + * @param buffers Optional binary buffers to send with the message + */ + send(content: any, callbacks?: any, buffers?: ArrayBuffer[] | ArrayBufferView[]): void; +} +``` + +This interface can be implemented without any dependencies and does not need to +extend from [Jupyter Widget's patch of +BackboneJS](https://github.com/jupyter-widgets/ipywidgets/blob/main/packages/base/src/backbone-patch.ts). +For instance, marimo's `model` implementation uses [no third-party +dependencies](https://github.com/marimo-team/marimo/blob/7f3023ff0caef22b2bf4c1b5a18ad1899bd40fa3/frontend/src/plugins/impl/anywidget/AnyWidgetPlugin.tsx#L161-L267). + +## Framework Bridges + +AFM intentionally does not prescribe specific models for state management or UI +rendering. While many front-end tools exist to help with authoring UIs (e.g., +React, Svelte, Vue) we strongly believe that incorporating these +non-web-standard pieces at the specification level would be a mistake. Our goal +is to create a solution for reusable widgets that aligns with the web's strong +backwards compatibility garantuees. + +Instead of baking framework support into the specification, we envision support +for UI frameworks through: + +- **Framework bridges**: Libraries that provide idiomatic APIs for popular +frameworks while adhering to the AFM specification. +- **Developer tooling**: Simple build processes that can compile +framework-specific code into standard AFM. + +This approach gives anywidget developers the option to use their preferred +tools and frameworks while ensuring that the final output is a web-standard +JavaScript. + +For example, using the `@anywidget/react` bridge looks like this: + +```jsx +// index.jsx +import * as React from "react"; +import { useModelState, createRender } from "@anywidget/react"; + +function Counter() { + let [count, setCount] = useModelState("count"); + return ; +} + +export default { + render: createRender(Counter) +}; +``` + +The bridge provides an idiomatic +[_hook_](https://react.dev/reference/react/hooks) for model state +(`useModelState`) and `createRender` function wraps a React component to +adhere to the AFM specification. + +By maintaining this separation between frameworks and the core specification, +we ensure that AFM remains flexible, future-proof, and aligned with the +long-term evolution of web standards. From 4dba8b264202edeaed3f52e9d992afac742a3989 Mon Sep 17 00:00:00 2001 From: Trevor Manz Date: Sat, 31 Aug 2024 14:00:45 -0400 Subject: [PATCH 2/7] rename /afm --- docs/src/consts.ts | 2 +- docs/src/pages/{en/anywidget-front-end-module.md => afm.md} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename docs/src/pages/{en/anywidget-front-end-module.md => afm.md} (100%) diff --git a/docs/src/consts.ts b/docs/src/consts.ts index a1c46de5..cf3586ef 100644 --- a/docs/src/consts.ts +++ b/docs/src/consts.ts @@ -55,7 +55,7 @@ export const SIDEBAR: Sidebar = { Learn: [ { text: "Anywidget Front-End Module", - link: "en/anywidget-front-end-module", + link: "en/afm", }, { text: "Jupyter Widgets: The Good Parts", diff --git a/docs/src/pages/en/anywidget-front-end-module.md b/docs/src/pages/afm.md similarity index 100% rename from docs/src/pages/en/anywidget-front-end-module.md rename to docs/src/pages/afm.md From a54e242466f25eac7e24fec183b4b4ef3321e4d0 Mon Sep 17 00:00:00 2001 From: Trevor Manz Date: Sat, 31 Aug 2024 15:28:34 -0400 Subject: [PATCH 3/7] Update afm.md Co-authored-by: Nezar Abdennur --- docs/src/pages/afm.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/pages/afm.md b/docs/src/pages/afm.md index e63495f0..f745566c 100644 --- a/docs/src/pages/afm.md +++ b/docs/src/pages/afm.md @@ -67,7 +67,7 @@ A host platform is expected to: - Load this module. - Call the lifecycle methods, passing in dependencies (`model` & `el`). -All browsers support ESM, so loading the module is supported natively across +All browsers support ECMAScript modules, so loading the module is supported natively across web-based environments. It is then the host platform's responsibility to implement the required [`model` interface](#model-interface) and provide an output DOM element (`el`). This simple mechanism allows new host platforms to From de058ce788dbb98f6418eec91d43e1143e29e716 Mon Sep 17 00:00:00 2001 From: Trevor Manz Date: Sat, 31 Aug 2024 15:29:26 -0400 Subject: [PATCH 4/7] Update afm.md Co-authored-by: Nezar Abdennur --- docs/src/pages/afm.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/pages/afm.md b/docs/src/pages/afm.md index f745566c..688efe30 100644 --- a/docs/src/pages/afm.md +++ b/docs/src/pages/afm.md @@ -8,7 +8,7 @@ layout: ../../layouts/MainLayout.astro The **Anywidget Front-End Module (AFM)** specification defines a standard for creating portable widget front-end code. Our vision is to enable widget reuse -**beyond Jupyter**, including other computational notebooks and standalone web +within and **beyond Jupyter**, including other computational notebooks and standalone web applications. AFM is oriented around a minimal set of APIs we identified as essential for integration with _host platforms_, boiling down to: From 6d4b6adc368b190d27aa99e1e3fe0dd8e6d3fc7e Mon Sep 17 00:00:00 2001 From: Trevor Manz Date: Sat, 31 Aug 2024 15:29:31 -0400 Subject: [PATCH 5/7] Update afm.md Co-authored-by: Nezar Abdennur --- docs/src/pages/afm.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/pages/afm.md b/docs/src/pages/afm.md index 688efe30..aa4731ab 100644 --- a/docs/src/pages/afm.md +++ b/docs/src/pages/afm.md @@ -35,7 +35,7 @@ the platform APIs. The `anywidget` Python library provides the necessary glue code to make all Jupyter-like environments (e.g., Jupyter Notebook, JupyterLab, Google Colab, VS Code) an AFM-compatible host platform. The -[marimo](https://github.com/marimo-team/marimo) project is a _native_ host +[marimo](https://github.com/marimo-team/marimo) project is an example of a _native_ host platform. ## Anywidget Front-end Module (AFM) From b942857425d86ef5967e479536c3926b39b54b02 Mon Sep 17 00:00:00 2001 From: Trevor Manz Date: Sat, 31 Aug 2024 15:29:39 -0400 Subject: [PATCH 6/7] Update afm.md Co-authored-by: Nezar Abdennur --- docs/src/pages/afm.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/pages/afm.md b/docs/src/pages/afm.md index aa4731ab..cac2d2c8 100644 --- a/docs/src/pages/afm.md +++ b/docs/src/pages/afm.md @@ -187,7 +187,7 @@ rendering. While many front-end tools exist to help with authoring UIs (e.g., React, Svelte, Vue) we strongly believe that incorporating these non-web-standard pieces at the specification level would be a mistake. Our goal is to create a solution for reusable widgets that aligns with the web's strong -backwards compatibility garantuees. +backwards compatibility guarantees. Instead of baking framework support into the specification, we envision support for UI frameworks through: From 5545263e55708fdeffb06696796f5147f1d594cc Mon Sep 17 00:00:00 2001 From: Trevor Manz Date: Sat, 31 Aug 2024 15:29:45 -0400 Subject: [PATCH 7/7] Update afm.md Co-authored-by: Nezar Abdennur --- docs/src/pages/afm.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/pages/afm.md b/docs/src/pages/afm.md index cac2d2c8..95fbd1f0 100644 --- a/docs/src/pages/afm.md +++ b/docs/src/pages/afm.md @@ -10,7 +10,7 @@ The **Anywidget Front-End Module (AFM)** specification defines a standard for creating portable widget front-end code. Our vision is to enable widget reuse within and **beyond Jupyter**, including other computational notebooks and standalone web applications. AFM is oriented around a minimal set of APIs we identified as -essential for integration with _host platforms_, boiling down to: +essential for integration with [_host platforms_](#host-platform), boiling down to: - Bidirectional communication with a host (e.g., Jupyter) - Modifying output areas (DOM manipulation) (e.g., a notebook output cell)