|
| 1 | +# Vite 8 + Rolldown Upgrade — Status & Testing Guide |
| 2 | + |
| 3 | +**Branch:** `feat/AG-17612-vite-dynamic-imports-and-esm-externals` |
| 4 | +**Date:** 2026-02-24 |
| 5 | + |
| 6 | +--- |
| 7 | + |
| 8 | +## Current Status |
| 9 | + |
| 10 | +All code changes are complete in octane. TypeScript compiles cleanly, lint passes, and all 71 tests pass. The changes have **not yet been tested** with a real MFE build — the local prod server was serving stale assets from a previous build. |
| 11 | + |
| 12 | +--- |
| 13 | + |
| 14 | +## What Changed (Summary) |
| 15 | + |
| 16 | +| File | Change | |
| 17 | +|------|--------| |
| 18 | +| `packages/gdu/package.json` | `vite` bumped from `^7.0.0` to `^8.0.0-beta.0`; added `peerDependencyMeta` for `@vanilla-extract/vite-plugin` | |
| 19 | +| `packages/gdu/config/vite/types.ts` | Renamed `Rollup*` types to `Rolldown*`; added `oxc` config type alongside existing `esbuild` type; `minify` type widened to `boolean \| string` | |
| 20 | +| `packages/gdu/config/vite/vite.config.ts` | `rollupOptions` renamed to `rolldownOptions`; `minify: 'esbuild'` changed to `minify: true` (OXC minifier); added `oxc` config block for JSX transform; kept `esbuild` config for `pure` + `legalComments` (deprecated but functional in Vite 8) | |
| 21 | +| `packages/gdu/commands/build/buildSPA-vite.ts` | `esmExternalRequirePlugin` imported from `'vite'` instead of `'rolldown/plugins'`; **fixed bug** where `external: undefined` was set when the plugin loaded (would have bundled all externals); simplified merge config | |
| 22 | +| `packages/gdu/commands/start/runSPA-vite.ts` | Added `oxc: base.oxc` for JSX in dev; `esbuild.pure` cleared for dev mode | |
| 23 | +| `.changeset/vite-dynamic-imports-esm-externals.md` | Updated to `minor` bump with Vite 8 upgrade description | |
| 24 | + |
| 25 | +--- |
| 26 | + |
| 27 | +## Key Architectural Decisions |
| 28 | + |
| 29 | +### OXC vs esbuild coexistence |
| 30 | + |
| 31 | +Vite 8 introduces `oxc` as the new transform engine and deprecates `esbuild`. However, both can coexist: |
| 32 | + |
| 33 | +- **`oxc`** handles JSX/TS transforms (the `jsx: { runtime: 'automatic', importSource: 'react' }` config) |
| 34 | +- **`esbuild`** (deprecated but functional) handles the renderChunk pass for `pure` (console stripping) and `legalComments: 'none'` |
| 35 | + |
| 36 | +There is no direct OXC equivalent of esbuild's `pure: ['console.log', ...]` option. Rolldown's minifier has `dropConsole: boolean` in `CompressOptions`, but that drops **all** console methods including `console.error`. Our config selectively strips `log`, `info`, `debug`, and `warn` while preserving `error`. |
| 37 | + |
| 38 | +**Note:** With `minify: true` (OXC minifier), the esbuild renderChunk plugin runs for target downlevelling but with `treeShaking: false`, so the `pure` annotations may not actually strip console calls in the final output. If console stripping is critical, consider either: |
| 39 | +1. Changing `minify` to `'esbuild'` (keeps esbuild as the minifier) |
| 40 | +2. Using `define` to replace console methods with no-ops: `'console.log': '(()=>{})'` |
| 41 | + |
| 42 | +### The external removal bug fix |
| 43 | + |
| 44 | +The previous code had: |
| 45 | +```ts |
| 46 | +...(hasEsmExternalPlugin ? { external: undefined } : {}) |
| 47 | +``` |
| 48 | +This would set `external` to `undefined` when `esmExternalRequirePlugin` loaded successfully — effectively removing all externals from the Rolldown config and causing them to be bundled into the output. The `esmExternalRequirePlugin` is designed to **coexist** with `external`, not replace it. The plugin handles CJS `require()` shims while `external` keeps the modules out of the bundle. |
| 49 | + |
| 50 | +--- |
| 51 | + |
| 52 | +## How to Test with fmo-booking |
| 53 | + |
| 54 | +### Step 1: Build gdu in octane |
| 55 | + |
| 56 | +```bash |
| 57 | +cd ~/Documents/GitHub/octane |
| 58 | +yarn workspace gdu build |
| 59 | +``` |
| 60 | + |
| 61 | +### Step 2: Copy gdu build to MFE |
| 62 | + |
| 63 | +```bash |
| 64 | +cd ~/Documents/GitHub/mfe |
| 65 | +yarn gdu:local |
| 66 | +``` |
| 67 | + |
| 68 | +This runs `.scripts/copy-gdu.js` which: |
| 69 | +1. Builds gdu in the sibling `octane` repo (`yarn workspace gdu build`) |
| 70 | +2. Copies `octane/packages/gdu/dist/` to `mfe/node_modules/gdu/dist/` |
| 71 | +3. Copies `octane/packages/gdu/entry/` to `mfe/node_modules/gdu/entry/` |
| 72 | +4. Copies `octane/packages/babel-preset/` to `mfe/node_modules/@autoguru/babel-preset/` |
| 73 | + |
| 74 | +**Important:** The `copy-gdu.js` script runs its own `yarn workspace gdu build` internally, so you don't strictly need Step 1 separately. However, running it first lets you catch TypeScript errors before copying. |
| 75 | + |
| 76 | +### Step 3: Ensure Vite 8 is installed in MFE |
| 77 | + |
| 78 | +The MFE's `node_modules/gdu/node_modules/vite` (or the hoisted `node_modules/vite`) must be Vite 8. Since `gdu:local` only copies the `dist/` and `entry/` directories, it does **not** update `node_modules/vite`. |
| 79 | + |
| 80 | +You need to ensure the MFE has Vite 8 available. The simplest approach: |
| 81 | + |
| 82 | +```bash |
| 83 | +cd ~/Documents/GitHub/mfe |
| 84 | + |
| 85 | +# Check current vite version |
| 86 | +node -e "console.log(require('vite/package.json').version)" |
| 87 | + |
| 88 | +# If it's still 7.x, you may need to manually update or add a resolutions override |
| 89 | +# in the MFE root package.json: |
| 90 | +# "resolutions": { "vite": "8.0.0-beta.15" } |
| 91 | +# Then run: |
| 92 | +yarn install |
| 93 | +``` |
| 94 | + |
| 95 | +### Step 4: Build fmo-booking |
| 96 | + |
| 97 | +```bash |
| 98 | +cd ~/Documents/GitHub/mfe |
| 99 | +APP_ENV=dev_au yarn workspace @autoguru/fmo-booking build:mfe |
| 100 | +``` |
| 101 | + |
| 102 | +### Step 5: Verify the build output |
| 103 | + |
| 104 | +```bash |
| 105 | +# Check build manifest exists |
| 106 | +cat apps/fmo-booking/dist/dev_au/build-manifest.json | jq . |
| 107 | + |
| 108 | +# Check the main JS bundle imports externals from esm.sh (not bundled inline) |
| 109 | +head -5 apps/fmo-booking/dist/dev_au/main-*.js |
| 110 | + |
| 111 | +# Check for external import specifiers in the bundle |
| 112 | +grep -c 'esm.sh' apps/fmo-booking/dist/dev_au/main-*.js |
| 113 | + |
| 114 | +# Check bundle size (should be significantly smaller than 2.6 MB) |
| 115 | +du -sh apps/fmo-booking/dist/dev_au/main-*.js |
| 116 | +``` |
| 117 | + |
| 118 | +**What to look for:** |
| 119 | +- The main JS should have `import` statements pointing to `esm.sh` URLs for react, react-dom, @datadog/*, etc. |
| 120 | +- These modules should NOT be bundled inline |
| 121 | +- Bundle size should be significantly smaller than 2.6 MB |
| 122 | + |
| 123 | +### Step 6: Test with the local prod server |
| 124 | + |
| 125 | +```bash |
| 126 | +# Kill any existing server on port 8080 |
| 127 | +lsof -ti :8080 | xargs kill -9 2>/dev/null |
| 128 | + |
| 129 | +# Start fresh server (reads build manifest at startup) |
| 130 | +node .scripts/mfe-local-prod-server.js fmo-booking dev au 8080 |
| 131 | +``` |
| 132 | + |
| 133 | +Then open: http://localhost:8080/au/custom-fleet/booking |
| 134 | + |
| 135 | +**Important:** The local prod server caches `build-manifest.json` at startup. If you rebuild, you **must** restart the server to pick up the new asset hashes. |
| 136 | + |
| 137 | +--- |
| 138 | + |
| 139 | +## What Was Tried During Investigation |
| 140 | + |
| 141 | +### Browser debugging session |
| 142 | + |
| 143 | +Navigated to `http://localhost:8080/au/custom-fleet/booking?status=COMPLETED&...` and found: |
| 144 | + |
| 145 | +1. **Page was blank** — only the environment banner and empty `<div id="__app__">` rendered |
| 146 | +2. **Network tab showed 4 requests**, all returning HTTP 200: |
| 147 | + - The HTML document |
| 148 | + - `main-DZvqnZTf.css` (200) |
| 149 | + - `main-Cm3RdaoF.js` (200) |
| 150 | + - `favicon.ico` (200) |
| 151 | +3. **The JS file was returning HTML** — `curl http://localhost:8080/main-Cm3RdaoF.js` returned the SPA fallback HTML, not JavaScript |
| 152 | +4. **Root cause:** The build manifest on disk contained `main-B8JrHicf.js` but the server was serving HTML referencing `main-Cm3RdaoF.js` — the server had been started with an older build and the manifest was cached at startup. The hash mismatch meant `express.static` couldn't find the file, so the catch-all `app.get('*')` returned HTML instead |
| 153 | + |
| 154 | +### Verification completed |
| 155 | + |
| 156 | +- `tsc --noEmit` — passes (no type errors) |
| 157 | +- `yarn lint` — passes |
| 158 | +- `yarn test` — all 71 tests pass (11 suites) |
| 159 | +- `yarn install` — resolved Vite 8.0.0-beta.15 + Rolldown 1.0.0-rc.5 successfully |
| 160 | +- `esmExternalRequirePlugin` — confirmed exported from `vite` in v8 (`typeof vite.esmExternalRequirePlugin === 'function'`) |
| 161 | +- Rolldown `OutputOptions.paths` — confirmed supported (for external URL rewriting) |
| 162 | + |
| 163 | +--- |
| 164 | + |
| 165 | +## Known Risks |
| 166 | + |
| 167 | +1. **Vite 8 is beta** (`8.0.0-beta.15`) — may have undiscovered bugs |
| 168 | +2. **`@vanilla-extract/vite-plugin`** doesn't list `vite@8` in peer deps — added `peerDependencyMeta` override to suppress warnings. Rolldown supports Rollup plugins so it should work, but untested with this specific combination |
| 169 | +3. **`esbuild.pure` may not strip console calls** with `minify: true` (OXC) — the esbuild renderChunk plugin runs for target downlevelling but with `treeShaking: false`. Verify by checking if `console.log` appears in production bundles |
| 170 | +4. **Rolldown output format differences** — subtle differences between Rollup and Rolldown ES module output could cause runtime issues. Verify by checking import/export shapes in the bundle |
0 commit comments