[eric] apps: the template vite config loads as ESM (.mts) and deps ride a vendored lockfile, so a fresh app boots on the bundled node 20 instead of dying on ESM-only plugins

This commit is contained in:
ciregenz
2026-08-09 18:17:44 -07:00
parent 09df1c2d17
commit d7ed3cda2d
5 changed files with 6234 additions and 9 deletions
+1 -1
View File
@@ -35,7 +35,7 @@ def p_safe_build_config(fe: str) -> tuple[list[str], Optional[str]]:
Returns (extra build args, temp-config path to delete) or ([], None)."""
cfg_name = next(
(n for n in ("vite.config.ts", "vite.config.js", "vite.config.mjs")
(n for n in ("vite.config.mts", "vite.config.ts", "vite.config.js", "vite.config.mjs")
if os.path.exists(os.path.join(fe, n))),
None,
)
+1 -1
View File
@@ -341,7 +341,7 @@ class AppRuntime:
[WinError 2] on `bash run.sh` and the preview never started. We only
take this path when vite is actually present (node_modules linked);
otherwise fall back to bash so behavior is unchanged everywhere else.
vite.config.ts reads FRONTEND_PORT / BACKEND_PORT from the environment."""
vite.config.mts reads FRONTEND_PORT / BACKEND_PORT from the environment."""
if os.name == "nt" and self.port is None:
node = env.get("OPENSWARM_NODE_PATH") or shutil.which("node")
vite_bin = os.path.join(
+1 -1
View File
@@ -23,7 +23,7 @@ def dist_index(workspace_path: str) -> str:
def p_newest_source_mtime(workspace_path: str) -> float:
newest = 0.0
fe = os.path.join(workspace_path, "frontend")
for probe in ("package.json", "vite.config.ts", "index.html"):
for probe in ("package.json", "vite.config.mts", "vite.config.ts", "index.html"):
p = os.path.join(fe, probe)
if os.path.isfile(p):
newest = max(newest, os.path.getmtime(p))
File diff suppressed because it is too large Load Diff
@@ -6,11 +6,16 @@ import terminal from 'vite-plugin-terminal';
import path from 'path';
import os from 'os';
import fs from 'fs';
import crypto from 'crypto';
import { fileURLToPath } from 'url';
// ESM config (.mts): the packaged app's bundled node 20 cannot require() ESM-only plugins like @tailwindcss/vite, so the config itself must load as ESM.
const here = path.dirname(fileURLToPath(import.meta.url));
// Shared, hash-keyed vite optimization cache. Every webapp-template
// workspace shares its node_modules/ via a symlink to OpenSwarm's warm
// cache, AND now shares the optimized-deps output via this cache too —
// keyed on the hash of vite.config.ts + package.json so a real config
// keyed on the hash of vite.config.mts + package.json so a real config
// or dep bump invalidates automatically. First workspace ever opened
// pays the ~1015s MUI pre-bundle; every subsequent workspace reuses
// the same `.vite-cache/deps/` and boots in under a second.
@@ -25,12 +30,10 @@ import fs from 'fs';
// 3. Vite's own metadata.json swap is atomic, so concurrent boots
// don't corrupt the cache.
function sharedViteCacheDir(): string {
const here = __dirname;
let digest = 'fallback';
try {
const crypto = require('crypto') as typeof import('crypto');
const hasher = crypto.createHash('sha256');
for (const f of ['vite.config.ts', 'package.json']) {
for (const f of ['vite.config.mts', 'package.json']) {
const p = path.join(here, f);
if (fs.existsSync(p)) hasher.update(fs.readFileSync(p));
}
@@ -67,8 +70,8 @@ export default defineConfig(({ mode }) => {
],
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
'@toolui': path.resolve(__dirname, 'src/toolui'),
'@': path.resolve(here, 'src'),
'@toolui': path.resolve(here, 'src/toolui'),
},
// Force single instances of React and emotion — even if anything
// tries to resolve them from a deeper node_modules path (which