diff --git a/packages/anywidget/__tests__/widget.test.ts b/packages/anywidget/__tests__/widget.test.ts index 572e3b4c..ec0d79a7 100644 --- a/packages/anywidget/__tests__/widget.test.ts +++ b/packages/anywidget/__tests__/widget.test.ts @@ -3,34 +3,34 @@ import * as baseManager from "@jupyter-widgets/base-manager"; import { afterEach, expect, it } from "vitest"; import { page, userEvent } from "vitest/browser"; -import create_anywidget from "../src/widget.js"; +import createAnywidget from "../src/widget.js"; -let anywidget = create_anywidget(widgets); -let num_comms = 0; +let anywidget = createAnywidget(widgets); +let numComms = 0; class MockComm implements widgets.IClassicComm { - comm_id = `mock-${num_comms++}`; + comm_id = `mock-${numComms++}`; target_name = "dummy"; - #on_open: ((x?: unknown) => void) | null = null; - #on_close: ((x?: unknown) => void) | null = null; + #onOpen: ((x?: unknown) => void) | null = null; + #onClose: ((x?: unknown) => void) | null = null; on_open(fn: () => void): void { - this.#on_open = fn; + this.#onOpen = fn; } on_close(fn: (x: unknown) => void): void { - this.#on_close = fn; + this.#onClose = fn; } on_msg(): void {} open(): string { - this.#on_open?.(); + this.#onOpen?.(); return ""; } close(): string { - this.#on_close?.(); + this.#onClose?.(); return ""; } @@ -84,13 +84,13 @@ export default { render }; `; async function createWidget(options: { - widget_manager: Manager; + widgetManager: Manager; esm: string; css?: string; state?: Record; }): Promise> { - let { widget_manager, esm, css, state = {} } = options; - let model_options = { + let { widgetManager, esm, css, state = {} } = options; + let modelOptions = { model_name: "AnyModel", model_module: "anywidget", model_module_version: "0.1.0", @@ -98,18 +98,18 @@ async function createWidget(options: { view_module: "anywidget", view_module_version: "0.1.0", } as const; - let model = await widget_manager.new_widget( + let model = await widgetManager.new_widget( { - ...model_options, + ...modelOptions, model_id: widgets.uuid(), }, { - _model_name: model_options.model_name, - _model_module: model_options.model_module, - _model_module_version: model_options.model_module_version, - _view_name: model_options.view_name, - _view_module: model_options.view_module, - _view_module_version: model_options.view_module_version, + _model_name: modelOptions.model_name, + _model_module: modelOptions.model_module, + _model_module_version: modelOptions.model_module_version, + _view_name: modelOptions.view_name, + _view_module: modelOptions.view_module, + _view_module_version: modelOptions.view_module_version, _esm: esm, _anywidget_id: "anywidget-test", ...(css ? { _css: css } : {}), @@ -125,9 +125,9 @@ afterEach(() => { }); it("creates an anywidget", async () => { - let widget_manager = new Manager(); + let widgetManager = new Manager(); let model = await createWidget({ - widget_manager, + widgetManager, esm: _esm, }); expect(model).toBeInstanceOf(anywidget.AnyModel); @@ -136,7 +136,7 @@ it("creates an anywidget", async () => { }); it("renders view", async () => { - let widget_manager = new Manager(); + let widgetManager = new Manager(); let esm = `\ function render({ model, el }) { el.innerText = "Hello, world"; @@ -144,16 +144,16 @@ function render({ model, el }) { export default { render }; `; let model = await createWidget({ - widget_manager, + widgetManager, esm: esm, }); - let view = await widget_manager.create_view(model); + let view = await widgetManager.create_view(model); document.body.appendChild(view.el); await expect.element(page.getByText("Hello, world")).toBeInTheDocument(); }); it("renders view with styles", async () => { - let widget_manager = new Manager(); + let widgetManager = new Manager(); let esm = `\ function render({ model, el }) { el.classList.add("basic-test"); @@ -167,11 +167,11 @@ export default { render }; } `; let model = await createWidget({ - widget_manager, + widgetManager, esm: esm, css: css, }); - let view = await widget_manager.create_view(model); + let view = await widgetManager.create_view(model); document.body.appendChild(view.el); await expect.element(page.getByText("Hello, world")).toBeInTheDocument(); expect( @@ -180,7 +180,7 @@ export default { render }; }); it("updates view on model changes", async () => { - let widget_manager = new Manager(); + let widgetManager = new Manager(); let esm = `\ function render({ model, el }) { let button = document.createElement("button"); @@ -197,11 +197,11 @@ function render({ model, el }) { export default { render }; `; let model = await createWidget({ - widget_manager, + widgetManager, esm: esm, state: { value: 0 }, }); - let view = await widget_manager.create_view(model); + let view = await widgetManager.create_view(model); document.body.appendChild(view.el); await expect.element(page.getByText("count is 0")).toBeInTheDocument(); model.set("value", 10); @@ -212,7 +212,7 @@ export default { render }; }); it("performs HMR update for _esm", async () => { - let widget_manager = new Manager(); + let widgetManager = new Manager(); let esm = `\ function render({ model, el }) { el.innerText = "hello. " + model.get("value"); @@ -223,11 +223,11 @@ function render({ model, el }) { export default { render }; `; let model = await createWidget({ - widget_manager, + widgetManager, esm: esm, state: { value: 0 }, }); - let view = await widget_manager.create_view(model); + let view = await widgetManager.create_view(model); document.body.appendChild(view.el); await expect.element(page.getByText("hello. 0")).toBeInTheDocument(); model.set("value", 10); @@ -245,7 +245,7 @@ export default { render }; }); it("performs HMR update for _css", async () => { - let widget_manager = new Manager(); + let widgetManager = new Manager(); let esm = `\ function render({ model, el }) { el.classList.add("anywidget-hmr-test") @@ -254,11 +254,11 @@ function render({ model, el }) { export default { render }; `; let model = await createWidget({ - widget_manager, + widgetManager, esm: esm, css: `.anywidget-hmr-test { background-color: lightgreen }`, }); - let view = await widget_manager.create_view(model); + let view = await widgetManager.create_view(model); document.body.appendChild(view.el); await expect.element(page.getByText("hi")).toBeInTheDocument(); expect( diff --git a/packages/anywidget/src/binding.ts b/packages/anywidget/src/binding.ts index 88d16c22..cc65722b 100644 --- a/packages/anywidget/src/binding.ts +++ b/packages/anywidget/src/binding.ts @@ -2,16 +2,16 @@ import type { Experimental, Host } from "@anywidget/types"; import type { DOMWidgetModel, DOMWidgetView } from "@jupyter-widgets/base"; import type { AnyWidget } from "./load.ts"; -import { INITIALIZE_MARKER, model_proxy } from "./model-proxy.ts"; -import { type Awaitable, promise_with_resolvers, safe_cleanup } from "./util.ts"; +import { INITIALIZE_MARKER, modelProxy } from "./model-proxy.ts"; +import { type Awaitable, promiseWithResolvers, safeCleanup } from "./util.ts"; -function is_safe_cleanup_function(x: unknown): x is () => Awaitable { +function isSafeCleanupFunction(x: unknown): x is () => Awaitable { return typeof x === "function"; } export class WidgetBinding { #controller: AbortController | undefined; - #widget_def: AnyWidget | undefined; + #widgetDef: AnyWidget | undefined; #exports: unknown; #model: DOMWidgetModel; ready: Promise; @@ -19,42 +19,42 @@ export class WidgetBinding { constructor(model: DOMWidgetModel) { this.#model = model; - this.#resolvers = promise_with_resolvers(); + this.#resolvers = promiseWithResolvers(); this.ready = this.#resolvers.promise; } async bind( - widget_def: AnyWidget, + widgetDef: AnyWidget, { experimental }: { experimental: Experimental }, ): Promise { - if (this.#widget_def === widget_def) return; + if (this.#widgetDef === widgetDef) return; - if (this.#widget_def && this.#widget_def !== widget_def) { + if (this.#widgetDef && this.#widgetDef !== widgetDef) { this.#controller?.abort(); - this.#resolvers = promise_with_resolvers(); + this.#resolvers = promiseWithResolvers(); this.ready = this.#resolvers.promise; } - this.#widget_def = widget_def; + this.#widgetDef = widgetDef; this.#controller = new AbortController(); let signal = this.#controller.signal; let model = this.#model; model.off(null, null, INITIALIZE_MARKER); - let result = await widget_def.initialize?.({ - model: model_proxy(model, INITIALIZE_MARKER), + let result = await widgetDef.initialize?.({ + model: modelProxy(model, INITIALIZE_MARKER), signal, experimental, }); if (signal.aborted) { - await safe_cleanup(is_safe_cleanup_function(result) ? result : undefined, "esm update"); + await safeCleanup(isSafeCleanupFunction(result) ? result : undefined, "esm update"); return; } - if (is_safe_cleanup_function(result)) { - signal.addEventListener("abort", () => safe_cleanup(result, "esm update")); + if (isSafeCleanupFunction(result)) { + signal.addEventListener("abort", () => safeCleanup(result, "esm update")); this.#exports = undefined; } else if (typeof result === "object" && result !== null) { this.#exports = result; @@ -65,26 +65,26 @@ export class WidgetBinding { this.#resolvers.resolve(this.#exports); } - async create_view( + async createView( view: DOMWidgetView, { signal, experimental, host }: { signal: AbortSignal; experimental: Experimental; host: Host }, ): Promise<(() => void) | undefined> { await this.ready; - if (!this.#widget_def?.render) return; + if (!this.#widgetDef?.render) return; let controller = new AbortController(); let combined = AbortSignal.any([signal, controller.signal]); - let cleanup = await this.#widget_def.render({ - model: model_proxy(this.#model, view), + let cleanup = await this.#widgetDef.render({ + model: modelProxy(this.#model, view), el: view.el, signal: combined, host, experimental, }); if (combined.aborted) { - await safe_cleanup(cleanup, "dispose view - already aborted"); + await safeCleanup(cleanup, "dispose view - already aborted"); return; } - combined.addEventListener("abort", () => safe_cleanup(cleanup, "dispose view - aborted")); + combined.addEventListener("abort", () => safeCleanup(cleanup, "dispose view - aborted")); return () => controller.abort(); } @@ -95,14 +95,14 @@ export class WidgetBinding { destroy(): void { this.#controller?.abort(); this.#controller = undefined; - this.#widget_def = undefined; + this.#widgetDef = undefined; } } class BindingManager { #bindings = new Map(); - get_or_create(model: DOMWidgetModel): WidgetBinding { + getOrCreate(model: DOMWidgetModel): WidgetBinding { let binding = this.#bindings.get(model); if (!binding) { binding = new WidgetBinding(model); diff --git a/packages/anywidget/src/host.ts b/packages/anywidget/src/host.ts index be25fc3f..2c27949f 100644 --- a/packages/anywidget/src/host.ts +++ b/packages/anywidget/src/host.ts @@ -3,43 +3,43 @@ import type { DOMWidgetModel, DOMWidgetView } from "@jupyter-widgets/base"; import { BINDINGS } from "./binding.ts"; import { invoke } from "./invoke.ts"; -import { model_proxy } from "./model-proxy.ts"; -import { parse_widget_ref } from "./widget-ref.ts"; +import { modelProxy } from "./model-proxy.ts"; +import { parseWidgetRef } from "./widget-ref.ts"; -export function create_host(model: DOMWidgetModel, { signal }: { signal: AbortSignal }): Host { +export function createHost(model: DOMWidgetModel, { signal }: { signal: AbortSignal }): Host { let host: Host = { - // @ts-expect-error - model_proxy returns AnyModel; generic T is erased at runtime + // @ts-expect-error - modelProxy returns AnyModel; generic T is erased at runtime async getModel(ref) { - let model_id = parse_widget_ref(ref); - let child_model = await model.widget_manager.get_model(model_id); + let modelId = parseWidgetRef(ref); + let childModel = await model.widget_manager.get_model(modelId); let context = Symbol("anywidget.host.getModel"); - signal.addEventListener("abort", () => child_model.off(null, null, context)); - return model_proxy(child_model, context); + signal.addEventListener("abort", () => childModel.off(null, null, context)); + return modelProxy(childModel, context); }, // @ts-expect-error - generic T is erased at runtime, exports typed as unknown async getWidget(ref) { - let model_id = parse_widget_ref(ref); - let child_model = await model.widget_manager.get_model(model_id); - let child_binding = BINDINGS.get(child_model); - if (!child_binding) { - throw new Error(`[anywidget] No binding found for widget ${model_id}`); + let modelId = parseWidgetRef(ref); + let childModel = await model.widget_manager.get_model(modelId); + let childBinding = BINDINGS.get(childModel); + if (!childBinding) { + throw new Error(`[anywidget] No binding found for widget ${modelId}`); } let exports = await Promise.race([ - child_binding.ready, + childBinding.ready, new Promise((_, reject) => AbortSignal.timeout(10000).addEventListener("abort", () => - reject(new Error(`[anywidget] Timed out waiting for widget ${model_id} to initialize`)), + reject(new Error(`[anywidget] Timed out waiting for widget ${modelId} to initialize`)), ), ), ]); return { exports, - async render({ el, signal: view_signal }) { - let child_view_signal = view_signal ?? signal; + async render({ el, signal: viewSignal }) { + let childViewSignal = viewSignal ?? signal; // Create a minimal view-like object for the binding // oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- intentionally creating a partial DOMWidgetView for child rendering - let child_view = { - model: child_model, + let childView = { + model: childModel, el, $el: { empty() { @@ -47,11 +47,11 @@ export function create_host(model: DOMWidgetModel, { signal }: { signal: AbortSi }, }, } as unknown as DOMWidgetView; - await child_binding.create_view(child_view, { - signal: child_view_signal, + await childBinding.createView(childView, { + signal: childViewSignal, experimental: { // @ts-expect-error - bind isn't working - invoke: invoke.bind(null, child_model), + invoke: invoke.bind(null, childModel), }, host, }); diff --git a/packages/anywidget/src/load.ts b/packages/anywidget/src/load.ts index 5e4e1b4c..f8d325a1 100644 --- a/packages/anywidget/src/load.ts +++ b/packages/anywidget/src/load.ts @@ -12,19 +12,18 @@ export interface AnyWidgetModule { default?: AnyWidget | (() => AnyWidget | Promise); } -function is_href(str: string): str is `https://${string}` | `http://${string}` { +function isHref(str: string): str is `https://${string}` | `http://${string}` { return str.startsWith("http://") || str.startsWith("https://"); } -async function load_css_href(href: string, anywidget_id: string): Promise { - let prev = document.querySelector(`link[id='${anywidget_id}']`); +async function loadCssHref(href: string, anywidgetId: string): Promise { + let prev = document.querySelector(`link[id='${anywidgetId}']`); // Adapted from https://github.com/vitejs/vite/blob/d59e1acc2efc0307488364e9f2fad528ec57f204/packages/vite/src/client/client.ts#L185-L201 // Swaps out old styles with new, but avoids flash of unstyled content. // No need to await the load since we already have styles applied. if (prev) { - // oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- Node.cloneNode() returns Node; we know prev is HTMLLinkElement so the clone is too - let newLink = prev.cloneNode() as HTMLLinkElement; + let newLink = prev.cloneNode() as unknown as HTMLLinkElement; newLink.href = href; newLink.addEventListener("load", () => prev?.remove()); newLink.addEventListener("error", () => prev?.remove()); @@ -42,29 +41,29 @@ async function load_css_href(href: string, anywidget_id: string): Promise }); } -function load_css_text(css_text: string, anywidget_id: string): void { - let prev = document.querySelector(`style[id='${anywidget_id}']`); +function loadCssText(cssText: string, anywidgetId: string): void { + let prev = document.querySelector(`style[id='${anywidgetId}']`); if (prev) { // replace instead of creating a new DOM node - prev.textContent = css_text; + prev.textContent = cssText; return; } let style = Object.assign(document.createElement("style"), { - id: anywidget_id, + id: anywidgetId, type: "text/css", }); - style.appendChild(document.createTextNode(css_text)); + style.appendChild(document.createTextNode(cssText)); document.head.appendChild(style); } -export async function load_css(css: string | undefined, anywidget_id: string): Promise { - if (!css || !anywidget_id) return; - if (is_href(css)) return load_css_href(css, anywidget_id); - return load_css_text(css, anywidget_id); +export async function loadCss(css: string | undefined, anywidgetId: string): Promise { + if (!css || !anywidgetId) return; + if (isHref(css)) return loadCssHref(css, anywidgetId); + return loadCssText(css, anywidgetId); } -async function load_esm(esm: string): Promise { - if (is_href(esm)) { +async function loadEsm(esm: string): Promise { + if (isHref(esm)) { return await import(/* webpackIgnore: true */ /* @vite-ignore */ esm); } let url = URL.createObjectURL(new Blob([esm], { type: "text/javascript" })); @@ -73,9 +72,9 @@ async function load_esm(esm: string): Promise { return mod; } -function warn_render_deprecation(anywidget_id: string): void { +function warnRenderDeprecation(anywidgetId: string): void { console.warn(`\ -[anywidget] Deprecation Warning for ${anywidget_id}: Direct export of a 'render' will likely be deprecated in the future. To migrate ... +[anywidget] Deprecation Warning for ${anywidgetId}: Direct export of a 'render' will likely be deprecated in the future. To migrate ... Remove the 'export' keyword from 'render' ----------------------------------------- @@ -100,10 +99,10 @@ To learn more, please see: https://github.com/manzt/anywidget/pull/395. `); } -export async function load_widget(esm: string, anywidget_id: string): Promise { - let mod = await load_esm(esm); +export async function loadWidget(esm: string, anywidgetId: string): Promise { + let mod = await loadEsm(esm); if (mod.render) { - warn_render_deprecation(anywidget_id); + warnRenderDeprecation(anywidgetId); return { async initialize() {}, render: mod.render, diff --git a/packages/anywidget/src/model-proxy.ts b/packages/anywidget/src/model-proxy.ts index ed75f8dd..d78e3915 100644 --- a/packages/anywidget/src/model-proxy.ts +++ b/packages/anywidget/src/model-proxy.ts @@ -14,7 +14,7 @@ export let INITIALIZE_MARKER = Symbol("anywidget.initialize"); * `context`, so we can gracefully unsubscribe from events * added by user-defined hooks. */ -export function model_proxy(model: DOMWidgetModel, context: unknown): AnyModel { +export function modelProxy(model: DOMWidgetModel, context: unknown): AnyModel { // oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- DOMWidgetModel.get/set/on/off have wider signatures than AnyModel, so bound versions don't narrow cleanly; the shape is structurally compatible return { get: model.get.bind(model), diff --git a/packages/anywidget/src/runtime.ts b/packages/anywidget/src/runtime.ts index c06e33ce..14a02d29 100644 --- a/packages/anywidget/src/runtime.ts +++ b/packages/anywidget/src/runtime.ts @@ -3,11 +3,11 @@ import type { DOMWidgetModel, DOMWidgetView } from "@jupyter-widgets/base"; import * as solid from "solid-js"; import { BINDINGS } from "./binding.ts"; -import { create_host } from "./host.ts"; +import { createHost } from "./host.ts"; import { invoke } from "./invoke.ts"; -import { type AnyWidget, load_css, load_widget } from "./load.ts"; +import { type AnyWidget, loadCss, loadWidget } from "./load.ts"; import { observe } from "./observe.ts"; -import { assert, promise_with_resolvers, type Result, throw_anywidget_error } from "./util.ts"; +import { assert, promiseWithResolvers, type Result, throwAnywidgetError } from "./util.ts"; interface State { [key: string]: unknown; @@ -18,12 +18,12 @@ interface State { export class Runtime { // @ts-expect-error - Set synchronously in constructor. - #widget_result: solid.Accessor>; + #widgetResult: solid.Accessor>; #signal: AbortSignal; ready: Promise; constructor(model: DOMWidgetModel, options: { signal: AbortSignal }) { - let resolvers = promise_with_resolvers(); + let resolvers = promiseWithResolvers(); this.ready = resolvers.promise; this.#signal = options.signal; this.#signal.throwIfAborted(); @@ -31,7 +31,7 @@ export class Runtime { AbortSignal.timeout(2000).addEventListener("abort", () => { resolvers.reject(new Error("[anywidget] Failed to initialize model.")); }); - let binding = BINDINGS.get_or_create(model); + let binding = BINDINGS.getOrCreate(model); let experimental: Experimental = { // @ts-expect-error - invoke.bind loses generic type parameter invoke: invoke.bind(null, model), @@ -39,14 +39,14 @@ export class Runtime { let dispose = solid.createRoot((dispose) => { // DOMWidgetModel is untyped by trait shape; we know the anywidget traits, so narrow to AnyModel for type-safe `.get()` access // oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- see above - let typed_model = model as unknown as AnyModel; - let id = typed_model.get("_anywidget_id"); - let css = observe(typed_model, "_css", { signal: this.#signal }); - let esm = observe(typed_model, "_esm", { signal: this.#signal }); - let [widget_result, set_widget_result] = solid.createSignal>({ + let typedModel = model as unknown as AnyModel; + let id = typedModel.get("_anywidget_id"); + let css = observe(typedModel, "_css", { signal: this.#signal }); + let esm = observe(typedModel, "_esm", { signal: this.#signal }); + let [widgetResult, setWidgetResult] = solid.createSignal>({ status: "pending", }); - this.#widget_result = widget_result; + this.#widgetResult = widgetResult; solid.createEffect( solid.on(css, () => console.debug(`[anywidget] css hot updated: ${id}`), { defer: true }), @@ -55,23 +55,23 @@ export class Runtime { solid.on(esm, () => console.debug(`[anywidget] esm hot updated: ${id}`), { defer: true }), ); solid.createEffect(() => { - return load_css(css(), id); + return loadCss(css(), id); }); solid.createEffect(() => { - load_widget(esm(), id) + loadWidget(esm(), id) .then(async (widget) => { await binding.bind(widget, { experimental }); - set_widget_result({ status: "ready", data: widget }); + setWidgetResult({ status: "ready", data: widget }); resolvers.resolve(); }) - .catch((error) => set_widget_result({ status: "error", error })); + .catch((error) => setWidgetResult({ status: "error", error })); }); return dispose; }); } - async create_view(view: DOMWidgetView, options: { signal: AbortSignal }): Promise { + async createView(view: DOMWidgetView, options: { signal: AbortSignal }): Promise { let model = view.model; let signal = AbortSignal.any([this.#signal, options.signal]); // either model or view destroyed signal.throwIfAborted(); @@ -82,30 +82,31 @@ export class Runtime { // @ts-expect-error - invoke.bind loses generic type parameter invoke: invoke.bind(null, model), }; - let host = create_host(model, { signal }); + let host = createHost(model, { signal }); let dispose = solid.createRoot((dispose) => { solid.createEffect(() => { // Clear all previous event listeners from this hook. model.off(null, null, view); view.$el.empty(); - let result = this.#widget_result(); + let result = this.#widgetResult(); if (result.status === "pending") { return; } if (result.status === "error") { - throw_anywidget_error(result.error); + throwAnywidgetError(result.error); + return; } let controller = new AbortController(); solid.onCleanup(() => controller.abort()); Promise.resolve() .then(() => - binding.create_view(view, { + binding.createView(view, { signal: AbortSignal.any([signal, controller.signal]), experimental, host, }), ) - .catch((error) => throw_anywidget_error(error)); + .catch((error) => throwAnywidgetError(error)); }); return () => dispose(); }); diff --git a/packages/anywidget/src/util.ts b/packages/anywidget/src/util.ts index 7816a869..65979b16 100644 --- a/packages/anywidget/src/util.ts +++ b/packages/anywidget/src/util.ts @@ -20,7 +20,7 @@ export function assert(condition: unknown, message: string): asserts condition { if (!condition) throw new Error(message); } -export async function safe_cleanup( +export async function safeCleanup( fn: void | (() => Awaitable) | undefined, kind: string, ): Promise { @@ -34,15 +34,15 @@ export async function safe_cleanup( * You can fully inspect the entire stack trace in the console interactively, * but the initial error message is cleaned up to be more user-friendly. */ -export function throw_anywidget_error(source: unknown): never { +export function throwAnywidgetError(source: unknown): never { if (!(source instanceof Error)) { // Don't know what to do with this. throw source; } let lines = source.stack?.split("\n") ?? []; - let anywidget_index = lines.findIndex((line) => line.includes("anywidget")); - let clean_stack = anywidget_index === -1 ? lines : lines.slice(0, anywidget_index + 1); - source.stack = clean_stack.join("\n"); + let anywidgetIndex = lines.findIndex((line) => line.includes("anywidget")); + let cleanStack = anywidgetIndex === -1 ? lines : lines.slice(0, anywidgetIndex + 1); + source.stack = cleanStack.join("\n"); console.error(source); throw source; } @@ -52,7 +52,7 @@ export function throw_anywidget_error(source: unknown): never { * * Trevor(2025-03-14): Should be able to remove once more stable across browsers. */ -export function promise_with_resolvers(): PromiseWithResolvers { +export function promiseWithResolvers(): PromiseWithResolvers { let resolve!: (value: T | PromiseLike) => void; let reject!: (reason?: unknown) => void; let promise = new Promise((res, rej) => { diff --git a/packages/anywidget/src/widget-ref.ts b/packages/anywidget/src/widget-ref.ts index 58d2d8ab..72b863c7 100644 --- a/packages/anywidget/src/widget-ref.ts +++ b/packages/anywidget/src/widget-ref.ts @@ -1,6 +1,6 @@ export let WIDGET_REF_PREFIX = "anywidget:"; -export function parse_widget_ref(ref: unknown): string { +export function parseWidgetRef(ref: unknown): string { if (typeof ref === "string" && ref.startsWith(WIDGET_REF_PREFIX)) { return ref.slice(WIDGET_REF_PREFIX.length); } diff --git a/packages/anywidget/src/widget.ts b/packages/anywidget/src/widget.ts index 871eeed3..bb451970 100644 --- a/packages/anywidget/src/widget.ts +++ b/packages/anywidget/src/widget.ts @@ -86,7 +86,7 @@ export default function ({ async render(): Promise { let runtime = RUNTIMES.get(this.model); assert(runtime, "[anywidget] Runtime not found."); - await runtime.create_view(this, { signal: this.#controller.signal }); + await runtime.createView(this, { signal: this.#controller.signal }); } remove(): void { this.#controller.abort("[anywidget] View destroyed."); diff --git a/packages/create-anywidget/__tests__/index.test.js b/packages/create-anywidget/__tests__/index.test.js index 2ad51d58..19fe9572 100644 --- a/packages/create-anywidget/__tests__/index.test.js +++ b/packages/create-anywidget/__tests__/index.test.js @@ -1,7 +1,7 @@ // @ts-check import { beforeAll, describe, expect, test } from "vite-plus/test"; -import { gather_files } from "../create.js"; +import { gatherFiles } from "../create.js"; describe("create-anywidget", () => { test.each( @@ -13,9 +13,9 @@ describe("create-anywidget", () => { "template-react-ts", ]), )(`%s`, async (template) => { - const files = await gather_files(template, { + const files = await gatherFiles(template, { name: "ipyfoo", - pkg_manager: "npm", + pkgManager: "npm", }); expect(files).toMatchSnapshot(); }); @@ -39,9 +39,9 @@ describe("create-anywidget (Bun)", () => { "template-react-ts", ]), )(`%s`, async (template) => { - const files = await gather_files(template, { + const files = await gatherFiles(template, { name: "ipyfoo", - pkg_manager: "bun", + pkgManager: "bun", }); expect(files).toMatchSnapshot(); }); diff --git a/packages/create-anywidget/create.js b/packages/create-anywidget/create.js index 0e7cf91f..57a35506 100644 --- a/packages/create-anywidget/create.js +++ b/packages/create-anywidget/create.js @@ -7,12 +7,12 @@ import snakecase from "just-snake-case"; let __dirname = path.dirname(url.fileURLToPath(import.meta.url)); /** @param {any} obj */ -function json_dumps(obj) { +function jsonDumps(obj) { return JSON.stringify(obj, null, "\t"); } /** @param {string} path */ -async function read_json(path) { +async function readJson(path) { return fs.readFile(path, "utf-8").then(JSON.parse); } @@ -20,22 +20,22 @@ async function read_json(path) { * pnpm will help us keep package versions in sync over time, along with dependabot, * so we lookup the version from `package.json` to use for those in our templates. * - * @param {{ dependencies: string[], dev_dependencies: string[]}} template + * @param {{ dependencies: string[], devDependencies: string[]}} template */ -async function get_dependency_versions(template) { - let rook_pkg = await read_json(path.join(__dirname, "package.json")); - let lookup = rook_pkg.devDependencies; +async function getDependencyVersions(template) { + let rootPkg = await readJson(path.join(__dirname, "package.json")); + let lookup = rootPkg.devDependencies; // The "workspace:" is not published to npm, so if present, we are working locally. if (Object.values(lookup).some((v) => /^workspace:/.test(String(v)))) { - let overrides = await gather_workspace_overrides(); + let overrides = await gatherWorkspaceOverrides(); for (let name of Object.keys(lookup)) { lookup[name] = overrides[name] ?? lookup[name]; } } /** @param {string[]} deps */ - function create_pkg_entry(deps) { + function createPkgEntry(deps) { /** @type {Record} */ let entry = {}; for (let dep of deps) { @@ -48,25 +48,25 @@ async function get_dependency_versions(template) { return entry; } return { - dependencies: create_pkg_entry(template.dependencies), - devDependencies: create_pkg_entry(template.dev_dependencies), + dependencies: createPkgEntry(template.dependencies), + devDependencies: createPkgEntry(template.devDependencies), }; } /** @returns {Promise>} */ -async function gather_workspace_overrides() { +async function gatherWorkspaceOverrides() { let dirs = await fs.readdir(path.join(__dirname, "..")); let entries = dirs .filter((dir) => dir !== "create-anywidget") .map(async (dir) => { - let pkg = await read_json(path.join(__dirname, "..", dir, "package.json")); + let pkg = await readJson(path.join(__dirname, "..", dir, "package.json")); return [pkg.name, `~${pkg.version}`]; }); return Promise.all(entries).then(Object.fromEntries); } /** @param {string} name */ -let pyproject_toml = (name) => +let pyprojectToml = (name) => `\ [build-system] requires = ["hatchling"] @@ -94,8 +94,8 @@ dev = ["watchfiles", "jupyterlab"] * @param {string} name * @param {string} npm */ -let pyproject_toml_with_hatch_jupyter_builder = (name, npm) => - `${pyproject_toml(name)}\n +let pyprojectTomlWithHatchJupyterBuilder = (name, npm) => + `${pyprojectToml(name)}\n [tool.hatch.build] only-packages = true artifacts = ["src/${name}/static/*"] @@ -218,7 +218,7 @@ in the notebook. /** @param {string} name */ let notebook = (name) => - json_dumps({ + jsonDumps({ cells: [ { cell_type: "code", @@ -273,7 +273,7 @@ let styles = (name) => `; /** @param {string} name */ -let widget_react_ts = (name) => +let widgetReactTs = (name) => `\ import * as React from "react"; import { createRender, useModelState } from "@anywidget/react"; @@ -294,7 +294,7 @@ export default { render }; `; /** @param {string} name */ -let widget_react = (name) => +let widgetReact = (name) => `\ import * as React from "react"; import { createRender, useModelState } from "@anywidget/react"; @@ -315,7 +315,7 @@ export default { render }; `; /** @param {string} name */ -let widget_vanilla = (name) => +let widgetVanilla = (name) => `\ import "./widget.css"; @@ -337,7 +337,7 @@ export default { render }; `; /** @param {string} name */ -let widget_vanilla_ts = (name) => +let widgetVanillaTs = (name) => `\ import type { RenderProps } from "@anywidget/types"; import "./widget.css"; @@ -365,12 +365,12 @@ function render({ model, el }: RenderProps) { export default { render }; `; -function css_declaration() { +function cssDeclaration() { return `declare module "*.css";\n`; } -function get_tsconfig() { - return json_dumps({ +function getTsconfig() { + return jsonDumps({ include: ["js"], compilerOptions: { target: "ES2020", @@ -395,72 +395,72 @@ function get_tsconfig() { }); } -/** @type {Record string }[], dependencies: string[], dev_dependencies: string[] }>} */ -const bundled_templates = { +/** @type {Record string }[], dependencies: string[], devDependencies: string[] }>} */ +const bundledTemplates = { "template-react": { - entry_point: "js/widget.jsx", + entryPoint: "js/widget.jsx", files: [ - { path: "js/widget.jsx", render: widget_react }, + { path: "js/widget.jsx", render: widgetReact }, { path: "js/widget.css", render: styles }, ], dependencies: ["@anywidget/react", "react", "react-dom"], - dev_dependencies: [], + devDependencies: [], }, "template-react-ts": { - entry_point: "js/widget.tsx", + entryPoint: "js/widget.tsx", files: [ - { path: "js/widget.tsx", render: widget_react_ts }, + { path: "js/widget.tsx", render: widgetReactTs }, { path: "js/widget.css", render: styles }, - { path: "js/css.d.ts", render: css_declaration }, - { path: "tsconfig.json", render: get_tsconfig }, + { path: "js/css.d.ts", render: cssDeclaration }, + { path: "tsconfig.json", render: getTsconfig }, ], dependencies: ["@anywidget/react", "react", "react-dom"], - dev_dependencies: ["@types/react", "@types/react-dom", "typescript"], + devDependencies: ["@types/react", "@types/react-dom", "typescript"], }, "template-vanilla": { - entry_point: "js/widget.js", + entryPoint: "js/widget.js", files: [ - { path: "js/widget.js", render: widget_vanilla }, + { path: "js/widget.js", render: widgetVanilla }, { path: "js/widget.css", render: styles }, ], dependencies: [], - dev_dependencies: [], + devDependencies: [], }, "template-vanilla-ts": { - entry_point: "js/widget.ts", + entryPoint: "js/widget.ts", files: [ - { path: "js/widget.ts", render: widget_vanilla_ts }, + { path: "js/widget.ts", render: widgetVanillaTs }, { path: "js/widget.css", render: styles }, - { path: "js/css.d.ts", render: css_declaration }, - { path: "tsconfig.json", render: get_tsconfig }, + { path: "js/css.d.ts", render: cssDeclaration }, + { path: "tsconfig.json", render: getTsconfig }, ], dependencies: [], - dev_dependencies: ["@anywidget/types", "typescript"], + devDependencies: ["@anywidget/types", "typescript"], }, }; /** - * @param {(typeof bundled_templates)[keyof typeof bundled_templates]} template - * @param {{ build_dir: string, typecheck: boolean, pkg_manager: string }} options + * @param {(typeof bundledTemplates)[keyof typeof bundledTemplates]} template + * @param {{ buildDir: string, typecheck: boolean, pkgManager: string }} options */ -async function generate_package_json(template, { build_dir, typecheck, pkg_manager }) { +async function generatePackageJson(template, { buildDir, typecheck, pkgManager }) { /** @type {Record} */ let scripts = { dev: "npm run build -- --sourcemap=inline --watch", }; /** @type {string[]} */ - let dev_extra = []; - if (pkg_manager === "bun") { - scripts.build = `bun build ${template.entry_point} --minify --format=esm --outdir=${build_dir} --asset-naming=[name].[ext]`; + let devExtra = []; + if (pkgManager === "bun") { + scripts.build = `bun build ${template.entryPoint} --minify --format=esm --outdir=${buildDir} --asset-naming=[name].[ext]`; } else { - scripts.build = `esbuild ${template.entry_point} --minify --format=esm --bundle --outdir=${build_dir}`; - dev_extra.push("esbuild"); + scripts.build = `esbuild ${template.entryPoint} --minify --format=esm --bundle --outdir=${buildDir}`; + devExtra.push("esbuild"); } - let { dependencies, devDependencies } = await get_dependency_versions({ + let { dependencies, devDependencies } = await getDependencyVersions({ dependencies: template.dependencies, - dev_dependencies: [...template.dev_dependencies, ...dev_extra], + devDependencies: [...template.devDependencies, ...devExtra], }); if (typecheck) { @@ -470,16 +470,16 @@ async function generate_package_json(template, { build_dir, typecheck, pkg_manag } /** - * @param {(typeof bundled_templates)[keyof typeof bundled_templates]} template - * @param {{ name: string, pkg_manager: string }} options + * @param {(typeof bundledTemplates)[keyof typeof bundledTemplates]} template + * @param {{ name: string, pkgManager: string }} options */ -async function render_template(template, { name, pkg_manager }) { - let build_dir = `src/${name}/static`; +async function renderTemplate(template, { name, pkgManager }) { + let buildDir = `src/${name}/static`; let tsconfig = template.files.find((file) => file.path.includes("tsconfig.json")); - let package_json = await generate_package_json(template, { - build_dir, + let packageJson = await generatePackageJson(template, { + buildDir, typecheck: !!tsconfig, - pkg_manager, + pkgManager, }); let files = template.files.map((file) => ({ path: file.path, @@ -490,17 +490,17 @@ async function render_template(template, { name, pkg_manager }) { { path: `example.ipynb`, content: notebook(name) }, { path: `.gitignore`, content: gitignore([`src/${name}/static`]) }, - { path: `package.json`, content: json_dumps(package_json) }, + { path: `package.json`, content: jsonDumps(packageJson) }, { path: `pyproject.toml`, - content: pyproject_toml_with_hatch_jupyter_builder(name, pkg_manager), + content: pyprojectTomlWithHatchJupyterBuilder(name, pkgManager), }, { path: `src/${name}/__init__.py`, content: __init__(name) }, ...files, ]; } -let deno_json = { +let denoJson = { lock: false, compilerOptions: { checkJs: true, @@ -516,7 +516,7 @@ let deno_json = { }; /** @param {string} name */ -let widget_esm = (name) => +let widgetEsm = (name) => `\ import confetti from "https://esm.sh/canvas-confetti@1"; @@ -543,39 +543,39 @@ export default { render }; /** * @param {TemplateType} type - * @param {{ name: string, pkg_manager: string }} options + * @param {{ name: string, pkgManager: string }} options */ -export async function gather_files(type, { name, pkg_manager }) { +export async function gatherFiles(type, { name, pkgManager }) { if (type === "template-vanilla-deno-jsdoc") { return [ { path: `README.md`, content: readme(name, type) }, { path: `example.ipynb`, content: notebook(name) }, - { path: `pyproject.toml`, content: pyproject_toml(name) }, - { path: `deno.json`, content: json_dumps(deno_json) }, + { path: `pyproject.toml`, content: pyprojectToml(name) }, + { path: `deno.json`, content: jsonDumps(denoJson) }, { path: `.gitignore`, content: gitignore() }, { path: `src/${name}/__init__.py`, content: __init__(name) }, - { path: `src/${name}/static/widget.js`, content: widget_esm(name) }, + { path: `src/${name}/static/widget.js`, content: widgetEsm(name) }, { path: `src/${name}/static/widget.css`, content: styles(name) }, ]; } - if (type in bundled_templates) { - return render_template(bundled_templates[type], { name, pkg_manager }); + if (type in bundledTemplates) { + return renderTemplate(bundledTemplates[type], { name, pkgManager }); } throw new Error(`Unknown template type: ${String(type)}`); } /** @typedef {{ content: string, path: string }} File */ -// oxlint-disable-next-line typescript-eslint/no-redundant-type-constituents -- template-vanilla-deno-jsdoc is not in bundled_templates -/** @typedef {keyof typeof bundled_templates | "template-vanilla-deno-jsdoc"} TemplateType */ +// oxlint-disable-next-line typescript-eslint/no-redundant-type-constituents -- template-vanilla-deno-jsdoc is not in bundledTemplates +/** @typedef {keyof typeof bundledTemplates | "template-vanilla-deno-jsdoc"} TemplateType */ /** * @param {string} target - * @param {{ name: string, template: TemplateType, pkg_manager: string }} options + * @param {{ name: string, template: TemplateType, pkgManager: string }} options */ export async function create(target, options) { - const files = await gather_files(options.template, { + const files = await gatherFiles(options.template, { name: snakecase(options.name), - pkg_manager: options.pkg_manager, + pkgManager: options.pkgManager, }); const promises = files.map(async (file) => { let location = path.resolve(target, file.path); diff --git a/packages/create-anywidget/index.js b/packages/create-anywidget/index.js index 01c5480d..481cc713 100755 --- a/packages/create-anywidget/index.js +++ b/packages/create-anywidget/index.js @@ -31,7 +31,7 @@ let p = new Proxy(_p, { }); // https://github.com/withastro/astro/blob/fca6892f8d6a30ceb1e04213be2414dd4cb4d389/packages/create-astro/src/actions/context.ts#L110-L115 -function detect_package_manager() { +function detectPackageManager() { // @ts-expect-error if (typeof Bun !== "undefined") return "bun"; if (!process.env.npm_config_user_agent) return; @@ -98,8 +98,8 @@ if (p.isCancel(framework)) { process.exit(1); } -let pkg_manager = detect_package_manager() ?? "npm"; -let bundler = pkg_manager === "bun" ? "bun" : "esbuild"; +let pkgManager = detectPackageManager() ?? "npm"; +let bundler = pkgManager === "bun" ? "bun" : "esbuild"; /** @type {string | symbol} */ let template = await p.select({ @@ -144,7 +144,7 @@ if (p.isCancel(template)) { await create(cwd, { name: path.basename(path.resolve(cwd)), template, - pkg_manager, + pkgManager, }).catch((err) => { console.error("Error writing files:", err); process.exit(1); @@ -161,7 +161,7 @@ if (relative !== "") { } if (template !== "template-vanilla-deno-jsdoc") { - console.log(` ${i++}: ${bold(cyan(`${pkg_manager} install`))}`); + console.log(` ${i++}: ${bold(cyan(`${pkgManager} install`))}`); } console.log( @@ -169,7 +169,7 @@ console.log( ); if (template !== "template-vanilla-deno-jsdoc") { - console.log(` ${i++}: ${bold(cyan(`${pkg_manager} run dev`))}`); + console.log(` ${i++}: ${bold(cyan(`${pkgManager} run dev`))}`); console.log(`\nTo close the dev server, hit ${bold(cyan("Ctrl-C"))}`); } diff --git a/packages/deno/src/jupyter_paths.ts b/packages/deno/src/jupyter-paths.ts similarity index 84% rename from packages/deno/src/jupyter_paths.ts rename to packages/deno/src/jupyter-paths.ts index 9a24cf67..433c8ef6 100644 --- a/packages/deno/src/jupyter_paths.ts +++ b/packages/deno/src/jupyter-paths.ts @@ -13,7 +13,7 @@ function assert(condition: unknown, message: string): asserts condition { * * Looks for a python executable in the PATH, and uses the directory. */ -async function guess_sys_prefix(): Promise { +async function guessSysPrefix(): Promise { let dirs = (Deno.env.get("PATH") ?? "").split(":"); let pathext = Deno.build.os === "windows" ? (Deno.env.get("PATHEXT") ?? "").split(";") : [""]; for (let dir of dirs) { @@ -37,7 +37,7 @@ async function guess_sys_prefix(): Promise { * * @ref https://test-jupyter.readthedocs.io/en/rtd-theme/projects/system.html#data-files */ -export function user_data_dir(): string { +export function userDataDir(): string { if (Deno.build.os === "windows") { let appdata = Deno.env.get("APPDATA"); assert(appdata, "APPDATA environment variable not set"); @@ -58,7 +58,7 @@ export function user_data_dir(): string { * * @ref https://test-jupyter.readthedocs.io/en/rtd-theme/projects/system.html#data-files */ -export function system_data_dirs(): Array { +export function systemDataDirs(): Array { if (Deno.build.os === "windows") { let programdata = Deno.env.get("PROGRAMDATA"); assert(programdata, "PROGRAMDATA environment variable not set"); @@ -77,19 +77,19 @@ export function system_data_dirs(): Array { * 2. Otherwise, use the user data directory. * 3. Otherwise, use the system data directory. */ -export async function find_data_dir(): Promise { - let sys_prefix = await guess_sys_prefix(); - if (sys_prefix) { - return path.resolve(sys_prefix, "share", "jupyter"); +export async function findDataDir(): Promise { + let sysPrefix = await guessSysPrefix(); + if (sysPrefix) { + return path.resolve(sysPrefix, "share", "jupyter"); } - let user_dir = user_data_dir(); + let userDir = userDataDir(); try { - let stat = await Deno.stat(user_dir); + let stat = await Deno.stat(userDir); if (stat.isDirectory) { - return user_dir; + return userDir; } } catch { // Fine, we'll use the system data directory. } - return system_data_dirs()[0]; + return systemDataDirs()[0]; } diff --git a/packages/deno/src/mod.ts b/packages/deno/src/mod.ts index a376f287..fe237353 100644 --- a/packages/deno/src/mod.ts +++ b/packages/deno/src/mod.ts @@ -3,13 +3,13 @@ * @module */ -import { remove_buffers } from "./utilities.ts"; +import { removeBuffers } from "./utilities.ts"; let COMMS = new WeakMap(); // TODO: We need to get this version from somewhere. Needs to match packages/anywidget/package.json#version const ANYWIDGET_SEMVER_VERSION: string = "~0.9.*"; -let jupyter_broadcast: Broadcast = (() => { +let jupyterBroadcast: Broadcast = (() => { try { return Deno.jupyter.broadcast; } catch { @@ -17,7 +17,7 @@ let jupyter_broadcast: Broadcast = (() => { } })(); -let init_promise_symbol = Symbol("init_promise"); +let INIT_PROMISE_SYMBOL = Symbol("init_promise"); type Broadcast = (typeof Deno)["jupyter"]["broadcast"]; @@ -37,28 +37,28 @@ type Mimebundle = { */ interface TestingInternals { /** Broadcast a message to the front end. Stubbed in testing. */ - jupyter_broadcast: Broadcast; + jupyterBroadcast: Broadcast; /** Get the comm for a model */ - get_comm(model: object): Comm; + getComm(model: object): Comm; /** Get the init promise for a model */ - get_init_promise(model: Model): Promise | undefined; + getInitPromise(model: Model): Promise | undefined; /** The version of anywidget used. */ version: string; } /** @private */ export const _internals: TestingInternals = { - jupyter_broadcast, - get_comm(model: object): Comm { + jupyterBroadcast, + getComm(model: object): Comm { let comm = COMMS.get(model); if (!comm) { throw new Error("No comm found for model"); } return comm; }, - get_init_promise(model: Model): Promise | undefined { + getInitPromise(model: Model): Promise | undefined { // @ts-expect-error - We have tagged this symbol onto the model privately - return model[init_promise_symbol]; + return model[INIT_PROMISE_SYMBOL]; }, get version() { return ANYWIDGET_SEMVER_VERSION; @@ -67,15 +67,15 @@ export const _internals: TestingInternals = { class Comm { #id: string; - #anywidget_version: string; - #protocol_version_major: number; - #protocol_version_minor: number; + #anywidgetVersion: string; + #protocolVersionMajor: number; + #protocolVersionMinor: number; - constructor({ anywidget_version }: { anywidget_version?: string }) { + constructor({ anywidgetVersion }: { anywidgetVersion?: string }) { this.#id = crypto.randomUUID(); - this.#anywidget_version = anywidget_version ?? ANYWIDGET_SEMVER_VERSION; - this.#protocol_version_major = 2; - this.#protocol_version_minor = 1; + this.#anywidgetVersion = anywidgetVersion ?? ANYWIDGET_SEMVER_VERSION; + this.#protocolVersionMajor = 2; + this.#protocolVersionMinor = 1; } /** The id of the comm. */ @@ -85,8 +85,8 @@ class Comm { /** Send a message to the front end to initialize the widget. */ init(data: Record = {}): Promise { - let { state, buffers, buffer_paths } = remove_buffers(data); - return _internals.jupyter_broadcast( + let { state, buffers, bufferPaths } = removeBuffers(data); + return _internals.jupyterBroadcast( "comm_open", { comm_id: this.id, @@ -95,36 +95,36 @@ class Comm { state: { _model_module: "anywidget", _model_name: "AnyModel", - _model_module_version: this.#anywidget_version, + _model_module_version: this.#anywidgetVersion, _view_module: "anywidget", _view_name: "AnyView", - _view_module_version: this.#anywidget_version, + _view_module_version: this.#anywidgetVersion, _view_count: null, ...state, }, - buffer_paths: buffer_paths, + buffer_paths: bufferPaths, }, }, { buffers: buffers, metadata: { - version: `${this.#protocol_version_major}.${this.#protocol_version_minor}.0`, + version: `${this.#protocolVersionMajor}.${this.#protocolVersionMinor}.0`, }, }, ); } /** Send a state update to the front end. */ - send_state(data: Record): Promise { - let { state, buffers, buffer_paths } = remove_buffers(data); - return _internals.jupyter_broadcast( + sendState(data: Record): Promise { + let { state, buffers, bufferPaths } = removeBuffers(data); + return _internals.jupyterBroadcast( "comm_msg", { comm_id: this.id, data: { method: "update", state: state, - buffer_paths: buffer_paths, + buffer_paths: bufferPaths, }, }, { @@ -137,8 +137,8 @@ class Comm { mimebundle(): Mimebundle { return { "application/vnd.jupyter.widget-view+json": { - version_major: this.#protocol_version_major, - version_minor: this.#protocol_version_minor, + version_major: this.#protocolVersionMajor, + version_minor: this.#protocolVersionMinor, model_id: this.id, }, }; @@ -206,7 +206,7 @@ type HTMLElement = typeof globalThis extends { : unknown; // TODO: more robust serialization of render function (with context?) -function to_esm({ imports = "", render }: Pick, "imports" | "render">) { +function toEsm({ imports = "", render }: Pick, "imports" | "render">) { return `${imports}\nexport default { render: ${render.toString()} }`; } @@ -256,23 +256,23 @@ export interface WidgetOptions { */ export function widget(options: WidgetOptions): Model { let { state, render, imports, version } = options; - let comm = new Comm({ anywidget_version: version }); - let init_promise = comm.init({ ...state, _esm: to_esm({ imports, render }) }); + let comm = new Comm({ anywidgetVersion: version }); + let initPromise = comm.init({ ...state, _esm: toEsm({ imports, render }) }); let model = new Model(state); for (let key in state) { // @ts-expect-error - TS can't infer this is correctly keyof ChangeEvents model.on(`change:${key}`, () => { - comm.send_state({ [key]: model.get(key) }); + comm.sendState({ [key]: model.get(key) }); }); } let obj = new Proxy(model, { get(target, prop, receiver) { - if (prop === init_promise_symbol) { - return init_promise; + if (prop === INIT_PROMISE_SYMBOL) { + return initPromise; } if (prop === Symbol.for("Jupyter.display")) { return async () => { - await init_promise; + await initPromise; return comm.mimebundle(); }; } diff --git a/packages/deno/src/uninstall.ts b/packages/deno/src/uninstall.ts index 8d630264..8ce47162 100644 --- a/packages/deno/src/uninstall.ts +++ b/packages/deno/src/uninstall.ts @@ -10,21 +10,21 @@ import * as path from "@std/path"; -import { find_data_dir } from "./jupyter_paths.ts"; +import { findDataDir } from "./jupyter-paths.ts"; -let data_dir = await find_data_dir(); +let dataDir = await findDataDir(); -await Deno.readTextFile(path.join(data_dir, "labextensions/anywidget/package.json")) +await Deno.readTextFile(path.join(dataDir, "labextensions/anywidget/package.json")) .then((contents) => { const { version } = JSON.parse(contents); console.log(`Uninstalling anywidget@${version}...`); }) .catch(() => {}); -await Deno.remove(path.join(data_dir, "labextensions/anywidget"), { +await Deno.remove(path.join(dataDir, "labextensions/anywidget"), { recursive: true, }).catch(() => {}); -await Deno.remove(path.join(data_dir, "nbextensions/anywidget"), { +await Deno.remove(path.join(dataDir, "nbextensions/anywidget"), { recursive: true, }).catch(() => {}); diff --git a/packages/deno/src/utilities.ts b/packages/deno/src/utilities.ts index f67aa06f..94ff69f5 100644 --- a/packages/deno/src/utilities.ts +++ b/packages/deno/src/utilities.ts @@ -4,21 +4,21 @@ * @param state - The input state object containing potential Uint8Array values. * The modified (JSON-serializable) state, extracted buffers, and buffer paths. */ -export function remove_buffers>( +export function removeBuffers>( state: T, ): { state: { [K in keyof T]: T[K] extends Uint8Array ? null : T[K] }; buffers: Array; - buffer_paths: Array<[string]>; + bufferPaths: Array<[string]>; } { let buffers: Array = []; - let buffer_paths: Array<[string]> = []; + let bufferPaths: Array<[string]> = []; let out: Record = {}; for (let key in state) { if (state[key] instanceof Uint8Array) { out[key] = null; buffers.push(state[key]); - buffer_paths.push([key]); + bufferPaths.push([key]); } else { out[key] = state[key]; } @@ -27,6 +27,6 @@ export function remove_buffers>( // @ts-expect-error - we know the type state: out, buffers, - buffer_paths, + bufferPaths, }; } diff --git a/packages/deno/test.ts b/packages/deno/test.ts index b7e66459..8853b268 100644 --- a/packages/deno/test.ts +++ b/packages/deno/test.ts @@ -2,24 +2,24 @@ import { expect } from "@std/expect"; import * as mock from "@std/testing/mock"; import { _internals, widget } from "./src/mod.ts"; -import { remove_buffers } from "./src/utilities.ts"; +import { removeBuffers } from "./src/utilities.ts"; Deno.test("widget() initializes the front end", async () => { - let jupyter_broadcast = mock.spy(_internals, "jupyter_broadcast"); + let jupyterBroadcast = mock.spy(_internals, "jupyterBroadcast"); try { let model = widget({ state: { value: 0 }, imports: "BLAH", render: async ({ model: _model, el: _el }) => {}, }); - let init_promise = _internals.get_init_promise(model); - await init_promise; - mock.assertSpyCalls(jupyter_broadcast, 2); - mock.assertSpyCall(jupyter_broadcast, 0, { + let initPromise = _internals.getInitPromise(model); + await initPromise; + mock.assertSpyCalls(jupyterBroadcast, 2); + mock.assertSpyCall(jupyterBroadcast, 0, { args: [ "comm_open", { - comm_id: _internals.get_comm(model).id, + comm_id: _internals.getComm(model).id, target_name: "jupyter.widget", data: { state: { @@ -38,11 +38,11 @@ Deno.test("widget() initializes the front end", async () => { }, ], }); - mock.assertSpyCall(jupyter_broadcast, 1, { + mock.assertSpyCall(jupyterBroadcast, 1, { args: [ "comm_msg", { - comm_id: _internals.get_comm(model).id, + comm_id: _internals.getComm(model).id, data: { buffer_paths: [], method: "update", @@ -58,24 +58,24 @@ Deno.test("widget() initializes the front end", async () => { ], }); } finally { - jupyter_broadcast.restore(); + jupyterBroadcast.restore(); } }); Deno.test("model.set() sends change events to the front end", async () => { - let jupyter_broadcast = mock.spy(_internals, "jupyter_broadcast"); + let jupyterBroadcast = mock.spy(_internals, "jupyterBroadcast"); try { let model = widget({ state: { value: 0 }, render: async ({ model: _model, el: _el }) => {}, }); - await _internals.get_init_promise(model); + await _internals.getInitPromise(model); model.set("value", 1); - mock.assertSpyCall(jupyter_broadcast, 2, { + mock.assertSpyCall(jupyterBroadcast, 2, { args: [ "comm_msg", { - comm_id: _internals.get_comm(model).id, + comm_id: _internals.getComm(model).id, data: { buffer_paths: [], method: "update", state: { value: 1 } }, }, { @@ -84,23 +84,23 @@ Deno.test("model.set() sends change events to the front end", async () => { ], }); } finally { - jupyter_broadcast.restore(); + jupyterBroadcast.restore(); } }); Deno.test("Explicit anywidget version overrides the default", () => { - let jupyter_broadcast = mock.spy(_internals, "jupyter_broadcast"); + let jupyterBroadcast = mock.spy(_internals, "jupyterBroadcast"); let version = "VERSION"; let model = widget({ state: { value: 0 }, render: async () => {}, version: version, }); - mock.assertSpyCall(jupyter_broadcast, 0, { + mock.assertSpyCall(jupyterBroadcast, 0, { args: [ "comm_open", { - comm_id: _internals.get_comm(model).id, + comm_id: _internals.getComm(model).id, target_name: "jupyter.widget", data: { state: { @@ -121,8 +121,8 @@ Deno.test("Explicit anywidget version overrides the default", () => { }); }); -Deno.test("remove_buffers extracts buffers from message", () => { - let result = remove_buffers({ +Deno.test("removeBuffers extracts buffers from message", () => { + let result = removeBuffers({ a: new Uint8Array([1, 2, 3]), b: "string", c: new Uint8Array([4, 5, 6]), @@ -143,6 +143,6 @@ Deno.test("remove_buffers extracts buffers from message", () => { }, }, buffers: [new Uint8Array([1, 2, 3]), new Uint8Array([4, 5, 6])], - buffer_paths: [["a"], ["c"]], + bufferPaths: [["a"], ["c"]], }); });