Migrate djangobuilder.io from Vue 2/Vuetify 2 to Vue 3/Vuetify 3 - #259
Merged
Conversation
Vue 2 and Vuetify 2 both reached end-of-life and had no further security
patches available for their toolchain (vue, vue-template-compiler, vuetify,
babel-traverse via vite-plugin-vue2's Babel 6 chain, postcss@7 via
@vue/component-compiler-utils) - the only real fix was the major upgrade.
Toolchain:
- vue 2.7 -> 3.5, vuetify 2.2 -> 3.12, vue-router 3 -> 4.6
- vuex -> pinia (Options-API-style defineStore, keeping the same
getter/action shapes so component code changes are minimal)
- vite-plugin-vue2/@vitejs/plugin-vue2 -> @vitejs/plugin-vue +
vite-plugin-vuetify (auto-import, replacing the old unplugin-vue-components
resolver hack)
- vue-highlight.js -> @highlightjs/vue-plugin (matching djangobuilder4's
existing pattern)
- vue-gtag 1.x -> 3.x (no default export anymore; uses createGtag(options))
- Removed vue-template-compiler, babel-core, raw-loader, stylus/stylus-loader,
vue-cli-plugin-vuetify, vuetify-loader, @mdi/font, @vue/compiler-dom -
all unused once the Vue 3 toolchain replaced their functionality.
Component changes across all 29 .vue files:
- $store.getters/commit/dispatch -> a Pinia useMainStore() computed,
dropping the getters that only wrapped state access 1:1 (user, loaded,
projects, apps, models, fields, relationships) since a Pinia getter can't
share a name with a state property - these are read directly off state now.
- v-list-item-content/action/avatar (removed in Vuetify 3) restructured to
#prepend/#append template slots.
- v-btn boolean style props (text, outlined, fab) -> variant/icon props;
size shorthands (x-small, large, ...) -> the size prop.
- v-slot:activator="{ on }" + v-on="on" -> "{ props }" + v-bind="props"
(v-tooltip, v-dialog, v-menu activator API changed).
- Old *--text/*--darken/*--lighten color utility classes -> text-*/bg-*.
- Vuetify 2 hidden-*-only/and-up/and-down breakpoint classes -> d-*.
- Vue.extend(...).$mount() imperative dialog mounting -> createApp(...).mount().
- Form components (TextInput, SelectList, SelectListObjects) bind directly
to Vuetify inputs via :value/@input - switched to :model-value/
@update:model-value since Vuetify 3 dropped the old contract entirely.
v-form.validate() is now async (returns a Promise), so `if (validate())`
was always truthy - fixed to await the resolved {valid} result.
Vue.$set calls removed (Vue 3's Proxy reactivity tracks new properties
without it).
- Fixed a latent bug found along the way: several v-alert instances relied
solely on Vuetify 2's `value` prop for visibility with no v-if, which
doesn't exist in Vuetify 3 - added explicit v-if guards.
Verified: yarn build/lint/test_smoke/test_core/test_io all pass for both
djangobuilder.io and djangobuilder4, and firebase-tools CLI still loads.
Visual QA of the actual rendered UI was not possible in this session (no
browser available in this sandboxed environment) - see the note added to
docs/VUE2_TO_VUE3_MIGRATION_PLAN.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two issues that passed `vite build`, lint, and unit tests but broke the
running app - caught by actually loading it in a headless browser:
1. Trailing-slash directory imports (@/dialogs/, @/firebase_utils/) failed
to resolve in Vite 6's dev server (vite:import-analysis), 500-ing every
component that used them and rendering a blank page under `yarn dev`.
Rollup's build resolver tolerated the trailing slash so the production
build never surfaced it. These imports predate this branch (they were in
the Vue 2 code too and break master's dev server the same way now that
it's on Vite 6) - dropped the trailing slashes so both build and dev
resolve them.
2. v-subheader was removed in Vuetify 3 ("Failed to resolve component").
Replaced with v-list-subheader inside the nav v-list (MainContent), and
with plain divs in AppView where they sat outside a list and wrapped
block buttons (a list-subheader's fixed height would clip them).
Verified by loading /, /login, /about, /signup, /reset_password in headless
chromium: 0 pageerrors, 0 console errors/warnings, 0 failed requests, and
the pages render correctly (screenshots reviewed). build/lint/test still green.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Owner
Author
|
Visual QA done (headless chromium against the dev server): This surfaced two dev-server-only breakages that
So the earlier "visual QA pending" caveat is now resolved. |
Signed in as an anonymous user against a real Firebase project and drove the full create-project/app/model/field flow in a headless browser, comparing against production (dev.djangobuilder.io). Fixes: Functional (forms were completely unusable — couldn't create anything): - Form input wrappers (TextInput/SelectList/SelectListObjects/BooleanInput) bound Vuetify fields with :model-value to a static prop, making them controlled inputs that reverted typed text in Vuetify 3 (Vuetify 2's :value+@input kept an internal value). Rewrote them to hold local state (v-model) and emit 'input' on change. - Declared emits:['input'] on those wrappers. In Vue 3 the parent's @input listener otherwise falls through to the native <input> (Vue 2 didn't auto-forward listeners), so formData was set to a raw InputEvent and every validation rule threw "(v||'').indexOf is not a function". - ButtonGroupSelect emitted its default inside data(); moved to created() so the default (e.g. Django version) actually reaches the form. - Per-model speed-dial (add field/relationship, move, delete) never opened: Vuetify 3's VSpeedDial wraps VMenu and needs an activator slot with bound props + `location` (not `absolute/right/direction`). Rewrote it; kept the action labels via native title attributes. Visual parity with production: - Theme: production renders LIGHT (the Vue 2 config's `dark:true` sat at an invalid options location and was always ignored). The migration had wired up a real dark theme — switched back to the default light theme. - Router: switched createWebHistory -> createWebHashHistory. Production and all Firebase email action links use hash URLs (/#/...); history mode would have broken existing verify/reset links. - App-bar "About" was pushed to centre because Vuetify 3's v-toolbar-title grows (flex:1 1 auto); constrained .toolbar-title to size-to-content. - Cookie snackbar: Vuetify 3 replaced the `bottom right` boolean props with `location="bottom right"` (and `:timeout="-1"`); it was rendering centred. - Splash "Login or Sign Up" buttons: dead code in production (the old `user` getter returned a truthy function so `!user` was always false). Pinia made `user` real state, revealing them; removed to match production's single "Sign In". - Breakpoint helpers: my hidden-* -> d-*-flex conversion forced flex on block/inline elements, left-aligning centred titles and breaking inline breadcrumbs. Used d-*-block / d-*-inline where the element wasn't flex. - Dropped trailing-slash directory imports already covered earlier; also fixed two leftover `text--darken-1` (double-dash) classes. Verified: build/lint/test green, and a full anonymous session (create project + app + model + field, syntax-highlighted code generation, delete) runs with zero console/page errors; splash/login/about now match production. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…yout Side-by-side comparison against dev.djangobuilder.io surfaced several Vuetify-3 rendering gaps (all now fixed): - Icons: the app mixed MDI names (mdi-*) with Material Icons ligatures (add, info, home, close, delete, share, device_hub, person, lock). Vuetify 3's mdi icon set treats every name as mdi-*, so the ligatures rendered blank (missing +/trash/person/lock icons on buttons and form fields). Converted them all to mdi equivalents (mdi-plus, mdi-delete, mdi-account, mdi-lock, ...). index.html also loaded an ancient MDI font (2.5.94) via a dead CDN; bumped it to @mdi/font@7.4.47 (matching @mdi/js) and dropped the now-unused Material Icons font. - v-treeview (Project Files): Vuetify 3 rewrote it on top of v-list, so the Vuetify-2 API (item-key, v-model:open, v-slot:label) produced a tree with icons but no labels. Switched to item-title/item-value, activatable + @update:activated for file selection, and the v3 title/prepend slots (slot item is item.raw). Labels render again. - Project page layout: DirectoryView's main column had no explicit width, so next to AppView's md=4 it wrapped (Files+code on one row, Models below). Gave it md=8 -> the 3 columns (Files | code | Models) sit side by side like production. - Cookie snackbar sat above the app footer (Vuetify 3 offsets it into the layout); pinned it flush to the bottom-right corner to match prod. - Constrained .toolbar-title so the "About" link sits next to the logo (v-toolbar-title grows by default in v3). Note: the "Version undefined" footer in dev is only because the bare vite binary doesn't set npm_package_version; `yarn dev`/the production build set it (shows 2.0.0). No code change needed. Verified against dev.djangobuilder.io across landing/login/home/create- project/project-with-models/about: build, lint, and unit tests pass; a full anonymous create project+app+model+field session runs with zero console errors. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Full page-by-page comparison against dev.djangobuilder.io turned up a few more Vuetify-3 rendering gaps: - Dialog headers were invisible: every dialog used <v-card-title class="primary text-white"> but "primary" isn't a background class in Vuetify 3 (needs bg-primary), so the blue title bar rendered white-on-white (e.g. the "Add new project" header was blank). Fixed across FormDialog / MessageDialog / ConfirmDialog / DeleteDialog / UpgradeDialog / Project error dialog. - FormDialog's add/edit icon used Material ligatures -> mdi-plus / mdi-pencil. - Renamed-in-MDI-7 icon: mdi-github-circle -> mdi-github (the GitHub icons on the About page and Login were blank). - Space-separated color props (Vuetify 2 syntax) don't resolve in v3: color="grey lighten-4" -> "grey-lighten-4" (avatars), color="red darken-1" -> "red-darken-1" (splash stars). - FontAwesome icons never had a font loaded (broken on prod too): the import spinner became a v-progress-circular, and fa-dot-circle -> mdi-record-circle-outline. - VerifyAction spinner: "mdi-cached mdi-spin" as an icon name -> icon mdi-cached with the mdi-spin animation class. Verified against prod across every route (landing, login, home, create- project dialog, empty project, project with models, about): build, lint and the 5 unit tests pass; screenshots now match production. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Vue 2 and Vuetify 2 are both end-of-life with no further security patches available. This closes the last 9 Dependabot alerts tied to that toolchain (vue, vue-template-compiler, vuetify, babel-traverse, postcss@7 — all only fixable via this major upgrade).
valueprop with nov-if; a.lengthcheck on a plain object instead ofObject.keys(...).length)Full rationale and details are in the commit message.
Test plan
yarn buildpasses for both djangobuilder.io and djangobuilder4yarn lintpasses (only pre-existing warnings)yarn test_smoke,yarn test_core,yarn test_ioall passfirebase --versionconfirms firebase-tools CLI still loadsCo-Authored-By: Claude Sonnet 5 noreply@anthropic.com