From 4aa7ad83f213cf1cba6420f4be507fb90d5930dc Mon Sep 17 00:00:00 2001 From: Maarten Breddels Date: Tue, 6 Oct 2026 10:19:51 +0200 Subject: [PATCH 1/2] perf: let nodeps.js use the host's Vuetify instead of a second copy nodeps.js is the bundle for Solara, whose page already has Vue and Vuetify. It still bundled Vuetify's components, directives and a scoped copy of Vuetify's CSS: about 2.6 MB raw and 354 KB gzip on every Solara page. The Vuetify plugin now comes from a small module. In the two nodeps builds, webpack swaps it for one that returns the host's plugin (window.vuetifyPlugin), so the components, directives and CSS are no longer bundled. The DatePicker wrapper resolves VDatePicker from the app instead of importing it, which would bundle it again. index.js and the labextension still bundle Vuetify, so Jupyter does not change. Co-Authored-By: Claude Opus 5.5 (1M context) --- js/src/VuetifyApp.js | 5 +++-- js/src/VuetifyView.js | 15 +++------------ js/src/nodepsVuetifyPlugin.js | 8 ++++++++ js/src/vuetifyPlugin.js | 13 +++++++++++++ js/webpack.config.js | 15 +++++++++++++++ 5 files changed, 42 insertions(+), 14 deletions(-) create mode 100644 js/src/nodepsVuetifyPlugin.js create mode 100644 js/src/vuetifyPlugin.js diff --git a/js/src/VuetifyApp.js b/js/src/VuetifyApp.js index 3315631a..3e829a22 100644 --- a/js/src/VuetifyApp.js +++ b/js/src/VuetifyApp.js @@ -1,5 +1,4 @@ -import { h } from "vue"; -import { VDatePicker } from "vuetify/components"; +import { h, resolveComponent } from "vue"; const appsWithComponents = new WeakSet(); @@ -28,6 +27,8 @@ const DatePicker = { props: ["modelValue"], emits: ["update:modelValue"], setup(props, { attrs, emit, slots }) { + // the app's own VDatePicker: the host's Vuetify in nodeps.js, ipyvuetify's in Jupyter + const VDatePicker = resolveComponent("VDatePicker"); return () => h( VDatePicker, diff --git a/js/src/VuetifyView.js b/js/src/VuetifyView.js index 390100c7..9c12fca1 100644 --- a/js/src/VuetifyView.js +++ b/js/src/VuetifyView.js @@ -1,10 +1,7 @@ import { VueView, createViewContext, vueRender } from "jupyter-vue"; -import "vuetify/styles"; import colors from "vuetify/lib/util/colors.mjs"; -import { createVuetify, useTheme } from "vuetify"; -import * as components from "vuetify/components"; -import * as labComponents from "vuetify/labs/components"; -import * as directives from "vuetify/directives"; +import { useTheme } from "vuetify"; +import { createVuetifyPlugin } from "./vuetifyPlugin"; import { ThemeColorsModel, ThemeModel } from "./Themes"; import { addApp } from "./VuetifyApp"; @@ -17,13 +14,7 @@ function getManagerState(widgetManager) { if (!managerState) { managerState = { initializedThemes: new WeakSet(), - vuetify: createVuetify({ - components: { - ...components, - ...labComponents, - }, - directives, - }), + vuetify: createVuetifyPlugin(), }; managerStateByWidgetManager.set(widgetManager, managerState); } diff --git a/js/src/nodepsVuetifyPlugin.js b/js/src/nodepsVuetifyPlugin.js new file mode 100644 index 00000000..f7a80d56 --- /dev/null +++ b/js/src/nodepsVuetifyPlugin.js @@ -0,0 +1,8 @@ +// nodeps.js: the host page (Solara) already has Vuetify, and shares its plugin as a global. +export const createVuetifyPlugin = () => { + if (!window.vuetifyPlugin) + throw new Error( + "jupyter-vuetify nodeps.js needs the host's window.vuetifyPlugin (Solara)" + ); + return window.vuetifyPlugin; +}; diff --git a/js/src/vuetifyPlugin.js b/js/src/vuetifyPlugin.js new file mode 100644 index 00000000..09d94b17 --- /dev/null +++ b/js/src/vuetifyPlugin.js @@ -0,0 +1,13 @@ +// Jupyter: ipyvuetify brings its own Vuetify. nodeps.js (Solara) replaces this module with +// nodepsVuetifyPlugin.js, so it does not bundle a second copy (see webpack.config.js). +import "vuetify/styles"; +import { createVuetify } from "vuetify"; +import * as components from "vuetify/components"; +import * as labComponents from "vuetify/labs/components"; +import * as directives from "vuetify/directives"; + +export const createVuetifyPlugin = () => + createVuetify({ + components: { ...components, ...labComponents }, + directives, + }); diff --git a/js/webpack.config.js b/js/webpack.config.js index db0fe295..6bd19a59 100755 --- a/js/webpack.config.js +++ b/js/webpack.config.js @@ -1,3 +1,4 @@ +var webpack = require("webpack"); var path = require("path"); var version = require("./package.json").version; @@ -114,6 +115,13 @@ module.exports = [ "vuetify", ], mode: "production", + // use the host's Vuetify plugin instead of bundling a second Vuetify + plugins: [ + new webpack.NormalModuleReplacementPlugin( + /^\.\/vuetifyPlugin$/, + "./nodepsVuetifyPlugin" + ), + ], }, { name: "nodeps-embed", @@ -143,6 +151,13 @@ module.exports = [ "vuetify", ], mode: "production", + // use the host's Vuetify plugin instead of bundling a second Vuetify + plugins: [ + new webpack.NormalModuleReplacementPlugin( + /^\.\/vuetifyPlugin$/, + "./nodepsVuetifyPlugin" + ), + ], }, { name: "embed", From 6a8cb6489e197442bfd075891a68dcfbc0455c37 Mon Sep 17 00:00:00 2001 From: Maarten Breddels Date: Tue, 6 Oct 2026 10:35:47 +0200 Subject: [PATCH 2/2] docs: explain why the DatePicker resolves VDatePicker instead of importing it Co-Authored-By: Claude Opus 5.5 (1M context) --- js/src/VuetifyApp.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/js/src/VuetifyApp.js b/js/src/VuetifyApp.js index 3e829a22..07316747 100644 --- a/js/src/VuetifyApp.js +++ b/js/src/VuetifyApp.js @@ -27,7 +27,10 @@ const DatePicker = { props: ["modelValue"], emits: ["update:modelValue"], setup(props, { attrs, emit, slots }) { - // the app's own VDatePicker: the host's Vuetify in nodeps.js, ipyvuetify's in Jupyter + // Use the VDatePicker registered in the app we render in: the host's Vuetify in + // nodeps.js (Solara), ipyvuetify's own in Jupyter. Importing it from + // "vuetify/components" would bundle VDatePicker and its dependencies into nodeps.js, + // a second partial Vuetify that cannot find the host's date setup. const VDatePicker = resolveComponent("VDatePicker"); return () => h(