Files
monitoring-app/src/vite.ts
bipproduction 08a1054e3c Initial commit: full-stack Bun + Elysia + React template
Elysia.js API with session-based auth (email/password + Google OAuth),
role system (USER/ADMIN/SUPER_ADMIN), Prisma + PostgreSQL, React 19
with Mantine UI, TanStack Router, dark theme, and comprehensive test
suite (unit, integration, E2E with Lightpanda).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 10:12:19 +08:00

181 lines
6.0 KiB
TypeScript

import fs from 'node:fs'
import path from 'node:path'
import { TanStackRouterVite } from '@tanstack/router-vite-plugin'
import react from '@vitejs/plugin-react'
import type { Plugin } from 'vite'
import { createServer as createViteServer } from 'vite'
/**
* Custom Vite plugin: inject data-inspector-* attributes ke JSX via regex.
* enforce: "pre" → jalan SEBELUM OXC transform JSX.
*
* Karena plugin lain (OXC, TanStack) bisa mengubah source sebelum kita
* (collapse lines, resolve imports), kita baca file ASLI dari disk untuk
* line number yang akurat, lalu cross-reference dengan code yang diterima.
*/
function inspectorPlugin(): Plugin {
const rootDir = process.cwd()
return {
name: 'inspector-inject',
enforce: 'pre',
transform(code, id) {
if (!/\.[jt]sx(\?|$)/.test(id) || id.includes('node_modules')) return null
if (!code.includes('<')) return null
const cleanId = id.replace(/\?.*$/, '')
const relativePath = path.relative(rootDir, cleanId)
// Baca file asli dari disk untuk line number akurat
let originalLines: string[] | null = null
try {
originalLines = fs.readFileSync(cleanId, 'utf-8').split('\n')
} catch {}
let modified = false
let lastOrigIdx = 0
const lines = code.split('\n')
const result: string[] = []
for (let i = 0; i < lines.length; i++) {
let line = lines[i]
const jsxPattern = /(<(?:[A-Z][a-zA-Z0-9]*(?:\.[a-zA-Z][a-zA-Z0-9]*)*|[a-z][a-zA-Z0-9-]*(?:\.[a-zA-Z][a-zA-Z0-9]*)*))\b/g
let match: RegExpExecArray | null = null
while ((match = jsxPattern.exec(line)) !== null) {
const charBefore = match.index > 0 ? line[match.index - 1] : ''
if (/[a-zA-Z0-9_$.]/.test(charBefore)) continue
// Cari line number asli di file original
let actualLine = i + 1
if (originalLines) {
const afterTag = line.slice(match.index)
// Snippet: tag + atribut sampai '>' pertama, tanpa injected attrs
const snippet = afterTag.split('>')[0]
.replace(/\s*data-inspector-[^"]*"[^"]*"/g, '')
.trim()
// Tag name saja sebagai fallback (e.g. "<Button")
const tagName = match[1]
let found = false
// 1) Forward search dengan full snippet
for (let j = lastOrigIdx; j < originalLines.length; j++) {
if (originalLines[j].includes(snippet)) {
actualLine = j + 1
lastOrigIdx = j + 1
found = true
break
}
}
// 2) Fallback: forward search hanya tag name (handle multi-line collapsed)
// Penting untuk <Button\n attr="..."\n> yang di-collapse jadi 1 baris
if (!found) {
for (let j = lastOrigIdx; j < originalLines.length; j++) {
if (originalLines[j].includes(tagName)) {
actualLine = j + 1
lastOrigIdx = j + 1
found = true
break
}
}
}
// 3) Last resort: search dari awal dengan full snippet
if (!found) {
for (let j = 0; j < originalLines.length; j++) {
if (originalLines[j].includes(snippet)) {
actualLine = j + 1
lastOrigIdx = j + 1
found = true
break
}
}
}
// 4) Last resort: search dari awal dengan tag name
if (!found) {
for (let j = 0; j < originalLines.length; j++) {
if (originalLines[j].includes(tagName) && !originalLines[j].trim().startsWith('</')) {
actualLine = j + 1
lastOrigIdx = j + 1
break
}
}
}
}
const col = match.index + 1
const attr = ` data-inspector-line="${actualLine}" data-inspector-column="${col}" data-inspector-relative-path="${relativePath}"`
const insertPos = match.index + match[0].length
line = line.slice(0, insertPos) + attr + line.slice(insertPos)
modified = true
jsxPattern.lastIndex += attr.length
}
result.push(line)
}
if (!modified) return null
return result.join('\n')
},
}
}
/**
* Workaround: @vitejs/plugin-react v6 + Vite 8 middlewareMode
* inject React Refresh HMR footer 2x → "Identifier RefreshRuntime already declared".
* Plugin ini hapus duplikat setelah semua transform selesai.
*/
function dedupeRefreshPlugin(): Plugin {
return {
name: 'dedupe-react-refresh',
enforce: 'post',
transform(code, id) {
if (!/\.[jt]sx(\?|$)/.test(id) || id.includes('node_modules')) return null
const marker = 'import * as RefreshRuntime from "/@react-refresh"'
const firstIdx = code.indexOf(marker)
if (firstIdx === -1) return null
const secondIdx = code.indexOf(marker, firstIdx + marker.length)
if (secondIdx === -1) return null
const sourcemapIdx = code.indexOf('\n//# sourceMappingURL=', secondIdx)
const endIdx = sourcemapIdx !== -1 ? sourcemapIdx : code.length
return { code: code.slice(0, secondIdx) + code.slice(endIdx), map: null }
},
}
}
export async function createVite() {
return createViteServer({
root: process.cwd(),
resolve: {
alias: {
'@': path.resolve(process.cwd(), './src'),
},
},
plugins: [
TanStackRouterVite({
routesDirectory: './src/frontend/routes',
generatedRouteTree: './src/frontend/routeTree.gen.ts',
routeFileIgnorePrefix: '-',
quoteStyle: 'single',
}),
inspectorPlugin(),
react(),
dedupeRefreshPlugin(),
],
server: {
middlewareMode: true,
hmr: { port: 24678 },
allowedHosts: true,
},
appType: 'custom',
optimizeDeps: {
include: ['react', 'react-dom'],
},
})
}