Replies: 8 comments
-
Since you're approaching a stage of wanting to have a component library to work with, I personally would go with creating a shared ES Module. I recommend trying out
Example files
import React from "https://esm.sh/react@18.2.0";
export default function App() {
return (
<>
<h1>Hi</h1>
<p>My name is appster</p>
</>
)
}
import React from "https://esm.sh/react@18.2.0"
import Component from "./component.tsx"
export function render() {
return <Component />
}
// main.tsx
import React2 from "https://esm.sh/react@18.2.0";
// component.tsx
import React from "https://esm.sh/react@18.2.0";
function App() {
return /* @__PURE__ */ React.createElement(
React.Fragment,
null,
/* @__PURE__ */ React.createElement("h1", null, "Hi"),
/* @__PURE__ */ React.createElement("p", null, "My name is appster"),
);
}
// main.tsx
function render() {
return /* @__PURE__ */ React2.createElement(App, null);
}
export { render }; |
Beta Was this translation helpful? Give feedback.
-
I am using esbuild and doing exactly what you describe. But how would I expose two different 'render' functions to be used by two different anywidget Python classes from that bundle? You can't, because the exported function is required to be called 'render', so a bundle can only export one function called 'render'. |
Beta Was this translation helpful? Give feedback.
-
Thanks for your suggestion on enhancing anywidget's API to allow dynamic determination of renderer function names. While this is an interesting idea, currently anywidget operates with a "one widget per file" approach for simplicity and clarity. For your use case, I recommend continuing to use esbuild --bundle --outdir=dist a-widget.js b-widget.js // shared.js
export function sharedFunction() { /* Shared logic */ }
// a-widget.js
import { sharedFunction } from "./shared.js";
export function render({ model, el }) { /* Widget A specific logic */ }
// b-widget.js
import { sharedFunction } from "./shared.js";
export function render({ model, el }) { /* Widget B specific logic */ } |
Beta Was this translation helpful? Give feedback.
-
Thanks. That's what I'm doing now. Guess I'll continue down that path. For reference, the motivation is partly due to VS Code and how convoluted loading JS files for widgets is there (https://github.com/microsoft/vscode-jupyter/wiki/Component:-IPyWidgets#loading-3rd-party-source), so just the one fetch for my collection of widgets would have been preferred, but that's not a blocker, and not your issue to solve :-) |
Beta Was this translation helpful? Give feedback.
-
Ah ok I see, great to know the use case! Thanks for understanding. Unfortunately a current hard limitation of anywidget is that a widget entry-point cannot use relative imports. This means that widgets must be bundled separately, even if they share dependencies. With full ESM support, in theory the above could work without a bundler at all (and there would only be one browser request for |
Beta Was this translation helpful? Give feedback.
-
I think I arrived at a reasonable solution for this that allows me to continue bundling everything into one JS bundle for many AnyWidget components (similar to how In my Python code I create the widget classes pointing to the same JS and CSS bundles, but with a unique 'component' value on the model for each widget. class Chart(anywidget.AnyWidget):
_esm = pathlib.Path(__file__).parent / "static" / "widget.js"
_css = pathlib.Path(__file__).parent / "static" / "widget.css"
component = traitlets.Unicode("Chart").tag(sync=True)
chartData = traitlets.Dict(sampleDataDict).tag(sync=True)
fidelity = traitlets.Float(0.5).tag(sync=True)
# etc.
class Table(anywidget.AnyWidget):
_esm = pathlib.Path(__file__).parent / "static" / "widget.js"
_css = pathlib.Path(__file__).parent / "static" / "widget.css"
component = traitlets.Unicode("Table").tag(sync=True)
# etc. I implement my various components in their own JS/TSX files, and then in the required export 'render' function I delegate to the appropriate component render function based on the type set on the model, e.g. in simplified form: widget.tsximport "./widget.css";
import { render as prender} from "preact";
import { ReTable } from "./reTable";
import { SpaceChart } from "./spaceChart";
import type { AnyModel } from "@anywidget/types";
type RenderArgs = {
model: AnyModel;
el: HTMLElement;
}
export function render({ model, el }: RenderArgs) {
let componentType = model.get("component");
switch (componentType) {
case "SpaceChart":
renderChart({ model, el });
break;
case "ReTable":
renderTable({ model, el });
break;
default:
throw new Error(`Unknown component type ${componentType}`);
}
}
function renderChart({ model, el }: RenderArgs) {
const onChange = () => {
const chartData = model.get("chartData");
prender(<SpaceChart chartData={chartData}></SpaceChart>, el)
}
onChange();
model.on("change:chartData", onChange);
}
function renderTable({ model, el }: RenderArgs) {
const onChange = () => {
const tableData = model.get("tableData");
prender(<ReTable estimatesData={tableData}></ReTable>, el)
};
onChange();
model.on("change:tableData", onChange);
}
// etc. That seems to be working for me as desired after a bit of prototyping. Build is very quick and simple, and my resulting .whl containing a few simple widgets is basically just one .py file, one .js file, and one .css file. |
Beta Was this translation helpful? Give feedback.
-
I'm currently using @billti's workaround #382 (comment), which is working fine. I'm wondering if a simple way to support it "officially" in anywidget could be to add a class Chart(anywidget.AnyWidget):
_esm = pathlib.Path(__file__).parent / "static" / "widget.js"
_esm_render_function = "renderChart"
_css = pathlib.Path(__file__).parent / "static" / "widget.css"
...
class Table(anywidget.AnyWidget):
_esm = pathlib.Path(__file__).parent / "static" / "widget.js"
_esm_render_function = "renderTable"
_css = pathlib.Path(__file__).parent / "static" / "widget.css"
... |
Beta Was this translation helpful? Give feedback.
-
For my use case I want to reuse the front-end anywidget code as a standalone JavaScript library (I'm exporting additional functions beyond render). I would like to have access to all of the front-end code from multiple widgets in a single library so I can just make one import (e.g., on ObservableHQ). It sounds like this will be possible using @billti's workaround #382 (comment) |
Beta Was this translation helpful? Give feedback.
-
Love the project. Thanks for building it!
I have a number of components that will share code, but I still want to ideally ship one .js file containing my components (and their shared code). Being that anywidget expects the Python class for a Widget to contain something like
_esm = pathlib.Path("index.js")
and for that file to export arender
function, that doesn't seem possible however.Would it be possible to be able configure the render function to use from the esm module, e.g.
_esm_renderer = "render_Foo"
for example. (Or something more elegant). That way I could have multiple components use the same .js file but use a different renderer function for each.Beta Was this translation helpful? Give feedback.
All reactions