Closed kolibril13 closed 1 year ago
Add this to your vite config:
define: {
'process.env.NODE_ENV': '"production"'
},
The issue is that process
is a node global and is not in the the browser. For whatever reason, the final main.js
includes references to process.env.NODE_ENV
which is not defined. This bit of code will effectively have vite transform the the environment check blocks in main.js
from:
if (process.env.NODE_ENV !== "production") {
}
to
if ("production" !== "production") {
}
which bundlers can statically analyze and dead-code eliminate.
Thanks for the quick reply!
I've now added 'process.env.NODE_ENV': '"production"'
and run npm run build
again, now I get this new error message in the notebook:
[Open Browser Console for more detailed log - Double click to close this message]
Failed to create view for 'AnyView' from module 'anywidget' with model 'AnyModel' from module 'anywidget'
Error: Minified React error #299; visit https://reactjs.org/docs/error-decoder.html?invariant=299 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
at ye.createRoot (blob:http://localhost:8888/8a12665c-753f-4e50-9bda-b17ff0c591c0:6140:11)
at blob:http://localhost:8888/8a12665c-753f-4e50-9bda-b17ff0c591c0:6211:4
and one further observation: running npm run dev
with define: {'process.env.NODE_ENV': '"production"'}
in vite.config.js
does not show the content of the component in the browser anymore.
I can only see a blank page with this error in the console:
App.jsx:8 Uncaught Error: @vitejs/plugin-react can't detect preamble. Something is wrong. See https://github.com/vitejs/vite-plugin-react/pull/11#discussion_r430879201
at App.jsx:8:11
(anonymous) @ App.jsx:8
hmm, this could be done to the new automatic runtime in defaults in @vitejs/plugin-react
. Can you try react({ jsxRuntime: 'classic' })
?
Thanks for the suggestion, nope, that did not work. Before:
After:
with the classic runtime you must import * as React from "react";
at the top of any jsx file.
Ok, I got it sorted out. The issue is that you are bundling react in the library mode in vite. You need the process.env.NODE_ENV
to be define during the build but not during development:
// vite.config.js
import { defineConfig } from "vite";
import react from '@vitejs/plugin-react'
export default defineConfig(({ command }) => {
let define = {};
if (command === "build") {
define["process.env.NODE_ENV"] = JSON.stringify("production");
}
return {
plugins: [react()],
build: {
outDir: "hello_widget/static",
lib: {
entry: ["src/main.jsx"],
formats: ["es"],
},
},
define,
}
});
Nice, now I have App.jsx
:
import * as React from "react";
export default function App() {
return <h1>Hello Anywidget + Vite!</h1>;
}
and
// vite.config.js
import { defineConfig } from "vite";
import react from '@vitejs/plugin-react'
export default defineConfig(async ({ command }) => {
let define = {};
if (command === "build") {
define["process.env.NODE_ENV"] = JSON.stringify("production");
}
return {
plugins: [react()],
build: {
outDir: "hello_widget/static",
lib: {
entry: ["src/main.jsx"],
formats: ["es"],
},
},
define,
}
});
this way the website works now again.
What remains is the
[Open Browser Console for more detailed log - Double click to close this message]
Failed to create view for 'AnyView' from module 'anywidget' with model 'AnyModel' from module 'anywidget'
Error: Minified React error #299; visit https://reactjs.org/docs/error-decoder.html?invariant=299 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
at ye.createRoot (blob:http://localhost:8888/8afd7d22-37e7-4ebf-82c9-124781fe5554:6140:11)
at blob:http://localhost:8888/8afd7d22-37e7-4ebf-82c9-124781fe5554:6211:4
error in the widget:
This makes sense. Your main.jsx
isn't a valid anywidget module. (It doesn't export a render
function).
If you want to make a react app with Vite, I'd recommend following the docs and using the anywidget
plugin. The @vitejs/plugin-react
react plugin doesn't play well inside Jupyter and does not offer the same HMR experience as our plugin.
If you want to develop separately, I'd recommend creating a widget.jsx
that acts as the ESM entrypoint for your widget
// widget.jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
export function render({ model, el }) {
let root = ReactDOM.createRoot(el);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
return () => root.unmount();
}
and update your build with:
// vite.config.js
import { defineConfig } from "vite";
import react from '@vitejs/plugin-react'
export default defineConfig(async ({ command }) => {
let define = {};
if (command === "build") {
define["process.env.NODE_ENV"] = JSON.stringify("production");
}
return {
plugins: [react()],
build: {
outDir: "hello_widget/static",
lib: {
++ entry: ["src/widget.jsx"],
formats: ["es"],
},
},
define,
}
});
This makes sense. Your
main.jsx
doesn't export arender
function.If you want to make a react app with Vite, I'd recommend following the docs and using the
anywidget
plugin. The@vitejs/plugin-react
react plugin doesn't play well inside Jupyter and does not offer the same HMR experience as our plugin.
Nice, I will try that out and will let you know soon if I was able to get it working! :)
ok, I've now added and installed the vite plugin from https://anywidget.dev/en/bundling/#development-1 and tried to run npm run build
, but that lead to the Minified React error
again.
Getting this minimal example working with vite would be really awesome! May I ask you if you could set that up? In return, I can offer some cool anywidget apps that might come from that. I just sent you an invitation to the repo, feel free to add commits directly to main. But also if you don't have time for that, I really appreciate your help so far ✨
ohhh. I just tried to widget.jsx approach from two messages above https://github.com/manzt/anywidget/issues/185#issuecomment-1638375731 at that works!!! 🎉 Both in browser and in the widget! 🌟
and packages work as well, eg. react-qr-code! 🤩
and interactive mafs components render as well! 🎉
https://github.com/manzt/anywidget/assets/44469195/f0b4e2d3-07d6-4268-831b-02e2108e7de5
If you currently have some bandwidth, I'd be curious to hear your thoughts on this @manzt @maartenbreddels ✨
This is how I see the next step up. First, play around with ipyreact, nothing needed except a browser. If you want to package this, or deploy this in production, you probably want to build a bundle using a modern toolchain ala vite or esbuild. I haven't tried this myself yet, so thanks for showing the pitfalls :)
so thanks for showing the pitfalls :)
It's a pleasure to be on this discovery journey with the two of you! ⛵
The last step, shipping to pypi via poetry, was almost TOO easy 🏝️🤩
I just published ipymafs to pypi, feel free to try is via pip install ipymafs
!
I think I will record a video tutorial at some point about the whole process, so that it becomes easier for other people to reproduce this whole widget creation workflow and can avoid all the deadlocks that I took. :)
I still have some remaining questions. @manzt : Would you be interested in helping me polish ipymafs? That project could then also become a anywidget vite react example project.
For these questions, I would then create separate issues.
Just stumbled about the first question: How do I pass props using the anywidget+react+vite approach? I'm using the react-qr-code widget as a minimal example in this repo. My idea was to do it the exact same way like in ipyreact https://kolibril13.github.io/ipyreact-example-gallery/01-nb.html#qr-code-widget
but when I change
-- return <QRCode value="Hiiii" />
++ return <QRCode value={content} />
then the output is just white, without the rendered content.
ah, it seems like widget.jsx
needs to pass the prop as well.
With that, return <QRCode value={content} />
works now.
However, how can I sync <App content="And here is some content!!!!!!!!" />
now with the value of my traitlet?
In the below example, I want the traitlet to give the content "Hi" to the qr code, but that's currently not the case.
How do I pass props using the anywidget+react+vite approach?
You need to connect your "app" to the Jupyter Widget model. I've tried to detail the important bits of connecting JS with Python in the docs.
But, most simply:
// widget.jsx
+ <App content={model.get("content")} />
This code grabs the initial value of content
and feeds it as a prop to your component.
To perhaps anticipate your next question, "How do you update or re-render the component when content
changes?" ipyreact
takes care of setting up this data-binding for you, but anywidget requires some more setup because it's not React-specific and forwards the primitives from the Jupyter Widgets framework.
From the anywidget docs:
[Y]our widget’s
render
function is executed exactly one per output cell that displays the widget instance. Therefore,render
primarily serves two purposes:
- Initializing content to display (i.e., create and append element(s) to context.el)
- Registering event handlers to update or display model state any time it changes (i.e., passing callbacks to
model.on
)
The code in widget.jsx
only accomplishes 1 at the moment. Jupyter Widgets is based on a Model-View-Controller (MVC) architecture using backbone JS. React is not an MVC framework React is not an MVC framework.
To accomplish 2, you'll need to wire up some React hooks around the model
in your render
function.
// widget.jsx
// Connects React App component to the Backbone model
function WidgetAdapter({ model }) {
let [content, setContent] = React.useState(model.get("content"));
React.useEffect(() => {
// update content anytime the model changes (from either Python or JS)
model.on("change:content", () => setContent(model.get("content")));
}, [])
return <App content={content} />
}
export function render({ model, el }) {
let root = ReactDOM.createRoot(el);
root.render(
<React.StrictMode>
<WidgetAdapter model={model} />
</React.StrictMode>,
);
return () => root.unmount();
}
I think I will record a video tutorial at some point about the whole process, so that it becomes easier for other people to reproduce this whole widget creation workflow and can avoid all the deadlocks that I took. :)
This would be great! I've also been meaning to record some videos. Maybe we could chat sometime about how to improve the anywidget docs / create more resources.
@manzt : Would you be interested in helping me polish ipymafs?
Sure, I'll have a look.
<App content={model.get("content")} />
awesome, that works! 🎉 😊
To perhaps anticipate your next question, "How do you update or re-render the component when content changes?"
when I first saw anywidget I was already suspicious that you're a magician, but now it's confirmed! 🧙
just incorporated that hook for updating from the traitlet as well, and also works out of the box 🎉
This would be great! I've also been meaning to record some videos. Maybe we could chat sometime about how to improve the anywidget docs / create more resources.
I'm absolutely up for that, looking forward to connecting with you! My favorite communication platform is discord, my username there is the same as here: kolibril13 Otherwise, DM on Twitter would also work.
Sure, I'll have a look.
Amazing, thank you so much for your time and support, I really appreciate that.
I was thinking instead of polishing ipymafs directly, it would be more convenient to have the most simple widget possible, which is I think the react-qr-code
widget with only 4 lines of code.
As soon as that minimal example is polished, that project can serve as a best practice template on how to set up a react-vite-anywidget project.
One remaining polishing challenge:
Currently, every change in the qr code widget needs npm run build
and kernel restart, so it would be it would be a great relief if there was a way to enable HMR for the react-vite-anywidget stack.
This minimal qr-code example currently lives at https://github.com/Octoframes/anywidget-react-vite-test, feel free to add commits directly to main there.
I'm going to close this issue, in favor of #190 and our on-going discussion in anywidget-react-vite-test.
My favorite communication platform is discord, my username there is the same as here: kolibril13 Otherwise, DM on Twitter would also work.
Sounds good. I am at a conference / traveling the next couple of weeks but will reach out to find a time to chat.
I've just made an experiment to see if I can run a minimal react app with Anywidget+Vite
but I get this error:
ReferenceError: process is not defined
👇👇👇Steps to reproduce
I've followed the official vite tutorial and the tutorial from the anywidget docs in order to set up a sample project at https://github.com/Octoframes/anywidget-react-vite-test.
The setup process ran without any errors, only displaying the widget does not work. Here are the steps that I did:
npm create vite@latest
.
React
JavaScript
(note that there's also JavaScript+SWC, but I did not choose that)npm install
npm run dev
<h1>Hello Anywidget + Vite!</h1>
vite.config.js
toexport default defineConfig({ plugins: [react()], build: { outDir: "hello_widget/static", lib: { entry: ["src/main.jsx"], formats: ["es"], }, }, });
python3.11 -m venv .venv && source .venv/bin/activate
pip install "anywidget[dev]"
pip install jupyterlab
process is not defined
error will show.If you currently have some bandwidth, I'd be curious to hear your thoughts on this @manzt @maartenbreddels ✨