perf(studio): upgrade to Vite 8 + auto-install bun for faster frontend builds (#4522)

* perf(studio): upgrade to Vite 8 + auto-install bun for 3x faster frontend builds

* fix(studio): make bun-to-npm fallback actually reachable

setup.sh used run_quiet() for the bun install attempt, but run_quiet
calls exit on failure. This killed the script before the npm fallback
could run, making the "falling back to npm" branch dead code.

Replace the run_quiet call with a direct bun invocation that captures
output to a temp file (same pattern, but returns instead of exiting).

Also clean up partial node_modules left by a failed bun install before
falling back to npm, in both setup.sh and build.sh. Without this, npm
inherits a corrupted node_modules tree from the failed bun run.

* fix(studio): restore commonjsOptions for dagre CJS interop

The previous commit removed build.commonjsOptions, assuming Vite 8's
Rolldown handles CJS natively. While optimizeDeps.include covers the
dev server (pre-bundling), it does NOT apply to production builds.

The resolve.alias still points @dagrejs/dagre to its .cjs.js entry,
so without commonjsOptions the production bundle fails to resolve
the CJS default export. This causes "TypeError: e is not a function"
on /chat after build (while dev mode works fine).

Restore the original commonjsOptions block to fix production builds.

* fix(studio): use motion/react instead of legacy framer-motion import

* fix(studio): address PR review findings for Vite 8 + bun upgrade

Fixes:
  - Remove bun.lock from repo and add to .gitignore (npm is source of truth)
  - Use & bun install *> $null pattern in setup.ps1 for reliable $LASTEXITCODE
  - Add Remove-Item node_modules before npm fallback in setup.ps1
  - Print bun install failure log in setup.sh before discarding
  - Add Refresh-Environment after npm install -g bun in setup.ps1
  - Tighten Node version check to ^20.19.0 || >=22.12.0 (Vite 8 requirement)
  - Add engines field to package.json
  - Use string comparison for _install_ok in build.sh
  - Remove explicit framer-motion ^11.18.2 from package.json (motion pulls
    framer-motion ^12.38.0 as its own dependency — the old pin caused a
    version conflict)

* Fix Colab Node bypass and bun.lock stale-build trigger

Gate the Colab Node shortcut on NODE_OK=true so Colab
environments with a Node version too old for Vite 8 fall
through to the nvm install path instead of silently proceeding.

Exclude bun.lock from the stale-build probe in both setup.sh
and setup.ps1 so it does not force unnecessary frontend rebuilds
on every run.

---------

Co-authored-by: Daniel Han <danielhanchen@gmail.com>
Co-authored-by: Shine1i <wasimysdev@gmail.com>
This commit is contained in:
Etherll 2026-03-25 13:27:41 +02:00 committed by GitHub
parent be2cd7087a
commit d69d60ff19
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 131 additions and 2510 deletions

View file

@ -29,7 +29,22 @@ _restore_gitignores() {
}
trap _restore_gitignores EXIT
npm install
# Use bun for install if available (faster), fall back to npm.
_install_ok=false
if command -v bun &>/dev/null; then
if bun install; then
_install_ok=true
else
echo "⚠ bun install failed, falling back to npm"
rm -rf node_modules
fi
fi
if [ "$_install_ok" != "true" ]; then
if ! npm install; then
echo "❌ ERROR: package install failed" >&2
exit 1
fi
fi
npm run build # outputs to studio/frontend/dist/
_restore_gitignores

View file

@ -11,6 +11,7 @@ pnpm-debug.log*
lerna-debug.log*
node_modules
bun.lock
dist
dist-ssr
test/

File diff suppressed because it is too large Load diff

View file

@ -3,6 +3,9 @@
"private": true,
"version": "0.0.0",
"type": "module",
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
@ -35,7 +38,7 @@
"@streamdown/code": "1.0.2",
"@streamdown/math": "1.0.2",
"@streamdown/mermaid": "1.0.2",
"@tailwindcss/vite": "^4.1.18",
"@tailwindcss/vite": "^4.2.2",
"@tanstack/react-router": "^1.159.10",
"@tanstack/react-table": "^8.21.3",
"@toolwind/corner-shape": "^0.0.8-3",
@ -48,7 +51,6 @@
"cmdk": "^1.1.1",
"date-fns": "^4.1.0",
"dexie": "^4.3.0",
"framer-motion": "^11.18.2",
"js-yaml": "^4.1.1",
"katex": "^0.16.28",
"lucide-react": "^0.577.0",
@ -80,13 +82,13 @@
"@types/node": "^24.10.1",
"@types/react": "^19.2.5",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.1",
"@vitejs/plugin-react": "^6.0.1",
"eslint": "^9.39.1",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.26",
"globals": "^16.5.0",
"typescript": "~5.9.3",
"typescript-eslint": "^8.55.0",
"vite": "^7.3.1"
"vite": "^8.0.1"
}
}

View file

@ -36,7 +36,7 @@ import {
useAuiEvent,
useAuiState,
} from "@assistant-ui/react";
import { motion } from "framer-motion";
import { motion } from "motion/react";
import {
ArrowDownIcon,
ArrowUpIcon,

View file

@ -728,16 +728,22 @@ if ($IsPipInstall) {
Write-Host "[OK] Running from pip install - frontend already bundled, skipping Node/npm check" -ForegroundColor Green
} else {
# setup.sh installs Node LTS (v22) via nvm. We enforce the same range here:
# Node >= 20, npm >= 11.
# Vite 8 requires Node ^20.19.0 || >=22.12.0, npm >= 11.
$NeedNode = $true
try {
$NodeVersion = (node -v 2>$null)
$NpmVersion = (npm -v 2>$null)
if ($NodeVersion -and $NpmVersion) {
$NodeMajor = [int]($NodeVersion -replace 'v','').Split('.')[0]
$NodeParts = ($NodeVersion -replace 'v','').Split('.')
$NodeMajor = [int]$NodeParts[0]
$NodeMinor = [int]$NodeParts[1]
$NpmMajor = [int]$NpmVersion.Split('.')[0]
if ($NodeMajor -ge 20 -and $NpmMajor -ge 11) {
# Vite 8: ^20.19.0 || >=22.12.0
$NodeOk = ($NodeMajor -eq 20 -and $NodeMinor -ge 19) -or
($NodeMajor -eq 22 -and $NodeMinor -ge 12) -or
($NodeMajor -ge 23)
if ($NodeOk -and $NpmMajor -ge 11) {
Write-Host "[OK] Node $NodeVersion and npm $NpmVersion already meet requirements." -ForegroundColor Green
$NeedNode = $false
} else {
@ -761,6 +767,24 @@ if ($IsPipInstall) {
}
Write-Host "[OK] Node $(node -v) | npm $(npm -v)" -ForegroundColor Green
# ── bun (optional, faster package installs) ──
# Installed via npm — Node is already guaranteed above. Works on all platforms.
if (-not (Get-Command bun -ErrorAction SilentlyContinue)) {
Write-Host " Installing bun (faster frontend package installs)..." -ForegroundColor DarkGray
$prevEAP_bun = $ErrorActionPreference
$ErrorActionPreference = "Continue"
npm install -g bun 2>&1 | Out-Null
$ErrorActionPreference = $prevEAP_bun
Refresh-Environment
if (Get-Command bun -ErrorAction SilentlyContinue) {
Write-Host "[OK] bun installed ($(bun --version))" -ForegroundColor Green
} else {
Write-Host "[OK] bun install skipped (npm will be used instead)" -ForegroundColor DarkGray
}
} else {
Write-Host "[OK] bun already installed ($(bun --version))" -ForegroundColor Green
}
}
# ============================================
@ -844,10 +868,10 @@ if ($IsPipInstall) {
if ($NewerFile) { break }
}
}
# Also check all top-level files (package.json, bun.lock, vite.config.ts, index.html, etc.)
# Also check all top-level files (package.json, vite.config.ts, index.html, etc.)
if (-not $NewerFile) {
$NewerFile = Get-ChildItem -Path $FrontendDir -File -ErrorAction SilentlyContinue |
Where-Object { $_.LastWriteTime -gt $DistTime } |
Where-Object { $_.Name -ne "bun.lock" -and $_.LastWriteTime -gt $DistTime } |
Select-Object -First 1
}
if (-not $NewerFile) {
@ -882,26 +906,47 @@ if ($NeedFrontendBuild -and -not $IsPipInstall) {
$WalkDir = Split-Path $WalkDir -Parent
}
# npm writes warnings to stderr; lower ErrorActionPreference so PS doesn't
# treat them as terminating errors (same pattern as the pip section below).
# Use bun if available (faster install), fall back to npm.
# Bun is used only as package manager; Node runs the actual build (Vite 8).
$prevEAP_npm = $ErrorActionPreference
$ErrorActionPreference = "Continue"
Push-Location $FrontendDir
npm install 2>&1 | Out-Null
if ($LASTEXITCODE -ne 0) {
$UseBun = $null -ne (Get-Command bun -ErrorAction SilentlyContinue)
if ($UseBun) {
Write-Host " Using bun for package install (faster)" -ForegroundColor DarkGray
& bun install *> $null
$bunExit = $LASTEXITCODE
if ($bunExit -ne 0) {
Write-Host " [WARN] bun install failed (exit $bunExit), falling back to npm" -ForegroundColor Yellow
if (Test-Path "node_modules") {
Remove-Item "node_modules" -Recurse -Force -ErrorAction SilentlyContinue
}
$UseBun = $false
}
}
if (-not $UseBun) {
& npm install *> $null
$npmExit = $LASTEXITCODE
if ($npmExit -ne 0) {
Pop-Location
$ErrorActionPreference = $prevEAP_npm
foreach ($gi in $HiddenGitignores) { Rename-Item -Path "$gi._twbuild" -NewName (Split-Path $gi -Leaf) -Force -ErrorAction SilentlyContinue }
Write-Host "[ERROR] npm install failed (exit code $LASTEXITCODE)" -ForegroundColor Red
Write-Host "[ERROR] npm install failed (exit code $npmExit)" -ForegroundColor Red
Write-Host " Try running 'npm install' manually in frontend/ to see errors" -ForegroundColor Yellow
exit 1
}
npm run build 2>&1 | Out-Null
if ($LASTEXITCODE -ne 0) {
}
# Always use npm to run the build (Node runtime — avoids bun Windows runtime issues)
& npm run build *> $null
$buildExit = $LASTEXITCODE
if ($buildExit -ne 0) {
Pop-Location
$ErrorActionPreference = $prevEAP_npm
foreach ($gi in $HiddenGitignores) { Rename-Item -Path "$gi._twbuild" -NewName (Split-Path $gi -Leaf) -Force -ErrorAction SilentlyContinue }
Write-Host "[ERROR] npm run build failed (exit code $LASTEXITCODE)" -ForegroundColor Red
Write-Host "[ERROR] npm run build failed (exit code $buildExit)" -ForegroundColor Red
exit 1
}
Pop-Location

View file

@ -69,6 +69,7 @@ _NEED_FRONTEND_BUILD=true
if [ -d "$SCRIPT_DIR/frontend/dist" ]; then
# Check all top-level files (package.json, bun.lock, vite.config.ts, index.html, etc.)
_changed=$(find "$SCRIPT_DIR/frontend" -maxdepth 1 -type f \
! -name 'bun.lock' \
-newer "$SCRIPT_DIR/frontend/dist" -print -quit 2>/dev/null)
# Check src/ and public/ recursively (|| true guards against set -e when dirs are missing)
if [ -z "$_changed" ]; then
@ -85,12 +86,18 @@ else
NEED_NODE=true
if command -v node &>/dev/null && command -v npm &>/dev/null; then
NODE_MAJOR=$(node -v | sed 's/v//' | cut -d. -f1)
NODE_MINOR=$(node -v | sed 's/v//' | cut -d. -f2)
NPM_MAJOR=$(npm -v | cut -d. -f1)
if [ "$NODE_MAJOR" -ge 20 ] && [ "$NPM_MAJOR" -ge 11 ]; then
# Vite 8 requires Node ^20.19.0 || >=22.12.0
NODE_OK=false
if [ "$NODE_MAJOR" -eq 20 ] && [ "$NODE_MINOR" -ge 19 ]; then NODE_OK=true; fi
if [ "$NODE_MAJOR" -eq 22 ] && [ "$NODE_MINOR" -ge 12 ]; then NODE_OK=true; fi
if [ "$NODE_MAJOR" -ge 23 ]; then NODE_OK=true; fi
if [ "$NODE_OK" = true ] && [ "$NPM_MAJOR" -ge 11 ]; then
echo "✅ Node $(node -v) and npm $(npm -v) already meet requirements. Skipping nvm install."
NEED_NODE=false
else
if [ "$IS_COLAB" = true ]; then
if [ "$IS_COLAB" = true ] && [ "$NODE_OK" = true ]; then
echo "✅ Node $(node -v) and npm $(npm -v) detected in Colab."
# In Colab, just upgrade npm directly - nvm doesn't work well
if [ "$NPM_MAJOR" -lt 11 ]; then
@ -150,6 +157,20 @@ fi
echo "✅ Node $(node -v) | npm $(npm -v)"
# ── Install bun (optional, faster package installs) ──
# Uses npm to install bun globally — Node is already guaranteed above,
# avoids platform-specific installers, PATH issues, and admin requirements.
if ! command -v bun &>/dev/null; then
echo " Installing bun (faster frontend package installs)..."
if npm install -g bun > /dev/null 2>&1 && command -v bun &>/dev/null; then
echo "✅ bun installed ($(bun --version))"
else
echo " bun install skipped (npm will be used instead)"
fi
else
echo "✅ bun already installed ($(bun --version))"
fi
# ── 5. Build frontend ──
cd "$SCRIPT_DIR/frontend"
@ -174,7 +195,27 @@ _restore_gitignores() {
}
trap _restore_gitignores EXIT
run_quiet "npm install" npm install
# Use bun for install if available (faster), fall back to npm.
# Build always uses npm (Node runtime — avoids bun runtime issues on some platforms).
# NOTE: We intentionally avoid run_quiet for the bun install attempt because
# run_quiet calls exit on failure, which would kill the script before the npm
# fallback can run. Instead we capture output manually and only show it on failure.
if command -v bun &>/dev/null; then
echo " Using bun for package install (faster)"
_bun_log=$(mktemp)
if bun install >"$_bun_log" 2>&1; then
rm -f "$_bun_log"
else
echo " ⚠️ bun install failed, falling back to npm"
echo " bun install output:"
sed 's/^/ | /' "$_bun_log" >&2
rm -f "$_bun_log"
rm -rf node_modules
run_quiet "npm install" npm install
fi
else
run_quiet "npm install" npm install
fi
run_quiet "npm run build" npm run build
_restore_gitignores