Files
gochat/frontend/vite.config.ts
T
rogee 1535e6cd55 Decouple frontend from Rails, establish pnpm workspace dev workflow
Strip the Chatwoot frontend down to a standalone Vite + Vue 3 SPA that talks
directly to the GoChat Go backend — no Ruby/Rails required.

Deleted from frontend/ (all Rails-only):
- Ruby: Gemfile, Gemfile.lock, config.ru, Capfile, Rakefile, bin/ (binstubs),
  config/ (application/boot/environment/routes/initializers/environments/locales),
  app/helpers/ (*.rb), app/views/ (ERB/jbuilder), app/assets/ (administrate scss)
- Enterprise overlay (enterprise/ — pure Ruby/jbuilder, zero JS)
- Infra: Procfile*, docker-compose*, docker/, deployment/, swagger/, .circleci/,
  .devcontainer/, .qlty/, .codegraph/, .windsurf/, .github/, .vscode/, .husky/
- Lint/config: .rubocop.yml, .scss-lint.yml, .rspec, rubocop/, .bundler-audit.yml,
  .annotaterb.yml, .all-contributorsrc, crowdin.yml, histoire.config.ts, etc.
- Stale frontend/pnpm-lock.yaml (replaced by root workspace lockfile)

Kept (frontend build essentials):
- app/javascript/ (all Vue SPA source — dashboard/widget/sdk/portal/superadmin/survey/v3)
- package.json, vite.config.ts, tailwind.config.js, postcss.config.js
- theme/, vitest.setup.js, .prettierrc

Key decoupling changes:
- vite.config.ts: removed vite-plugin-ruby, added explicit rollupOptions.input
  for all 7 entrypoints + dev-server proxy (/api,/platform,/cable,/health → GoChat)
- index.html: created static SPA entry that injects window.chatwootConfig /
  window.globalConfig (previously done by Rails ERB vueapp.html.erb)
- package.json: renamed @gochat/frontend, removed vite-plugin-ruby/histoire/husky,
  scripts now use plain vite dev/build
- tailwind.config.js: removed enterprise ERB content paths
- markdownEmbeds.js: relocated markdown_embeds.yml into app/javascript/dashboard/config/

Root pnpm workspace:
- package.json: dev:backend (air), dev:frontend (vite), dev (concurrently)
- pnpm-workspace.yaml: frontend as workspace package
- .gitignore: node_modules/, frontend/dist/, frontend build artifacts

Verified: pnpm install + vite build succeeds (4446 modules, all 7 entrypoints
produce JS+CSS chunks in dist/).
2026-07-07 15:37:24 +08:00

161 lines
4.9 KiB
TypeScript

/// <reference types="vitest" />
/**
* GoChat frontend Vite config — decoupled from Rails.
*
* Originally Chatwoot used vite-plugin-ruby which auto-discovers entrypoints
* from app/javascript/entrypoints/ via config/vite.json. We replaced it with
* explicit rollupOptions.input so the frontend builds as a standalone Vite app
* that talks to the GoChat Go backend (no Ruby/Rails required).
*
* Entry points (mirrors app/javascript/entrypoints/):
* dashboard — agent dashboard SPA (mounted at #app via /app/*)
* widget — customer-facing web widget
* portal — help-center portal
* sdk — standalone SDK (library mode, built separately)
* superadmin — super admin panel
* survey — CSAT survey form
* v3app — v3 login/signup flow
*/
import { defineConfig } from 'vite';
import path from 'path';
import vue from '@vitejs/plugin-vue';
import yaml from '@rollup/plugin-yaml';
const isLibraryMode = process.env.BUILD_MODE === 'library';
const isTestMode = process.env.TEST === 'true';
const vueOptions = {
template: {
compilerOptions: {
isCustomElement: (tag: string) => ['ninja-keys'].includes(tag),
},
},
};
const resolveAliases = {
vue: 'vue/dist/vue.esm-bundler.js',
components: path.resolve(__dirname, './app/javascript/dashboard/components'),
next: path.resolve(__dirname, './app/javascript/dashboard/components-next'),
v3: path.resolve(__dirname, './app/javascript/v3'),
dashboard: path.resolve(__dirname, './app/javascript/dashboard'),
helpers: path.resolve(__dirname, './app/javascript/shared/helpers'),
shared: path.resolve(__dirname, './app/javascript/shared'),
survey: path.resolve(__dirname, './app/javascript/survey'),
widget: path.resolve(__dirname, './app/javascript/widget'),
assets: path.resolve(__dirname, './app/javascript/dashboard/assets'),
};
// Entrypoints that vite-plugin-ruby used to auto-discover
const entrypoints = {
dashboard: path.resolve(__dirname, './app/javascript/entrypoints/dashboard.js'),
widget: path.resolve(__dirname, './app/javascript/entrypoints/widget.js'),
portal: path.resolve(__dirname, './app/javascript/entrypoints/portal.js'),
superadmin: path.resolve(__dirname, './app/javascript/entrypoints/superadmin.js'),
superadmin_pages: path.resolve(__dirname, './app/javascript/entrypoints/superadmin_pages.js'),
survey: path.resolve(__dirname, './app/javascript/entrypoints/survey.js'),
v3app: path.resolve(__dirname, './app/javascript/entrypoints/v3app.js'),
};
let plugins = [vue(vueOptions), yaml()];
if (isLibraryMode) {
plugins = [];
} else if (isTestMode) {
plugins = [vue(vueOptions), yaml()];
}
export default defineConfig({
plugins,
css: {
preprocessorOptions: {
scss: {
api: 'modern-compiler',
},
},
},
// Dev server proxies API + WebSocket to the GoChat Go backend
server: {
port: 3036,
proxy: {
'/api': {
target: process.env.VITE_API_HOST || 'http://127.0.0.1:3000',
changeOrigin: true,
},
'/platform': {
target: process.env.VITE_API_HOST || 'http://127.0.0.1:3000',
changeOrigin: true,
},
'/cable': {
target: process.env.VITE_API_HOST || 'http://127.0.0.1:3000',
ws: true,
changeOrigin: true,
},
'/health': {
target: process.env.VITE_API_HOST || 'http://127.0.0.1:3000',
changeOrigin: true,
},
},
},
build: {
outDir: 'dist',
rollupOptions: {
input: isLibraryMode ? undefined : entrypoints,
output: {
// [NOTE] when not in library mode, no new keys will be added or overwritten
...(isLibraryMode
? {
dir: 'dist/sdk',
entryFileNames: (chunkInfo: { name: string }) => {
if (chunkInfo.name === 'sdk') {
return 'js/sdk.js';
}
return '[name].js';
},
}
: {}),
inlineDynamicImports: isLibraryMode, // Disable code-splitting for SDK
},
},
lib: isLibraryMode
? {
entry: path.resolve(__dirname, './app/javascript/entrypoints/sdk.js'),
formats: ['iife'] as const, // IIFE format for single file
name: 'sdk',
}
: undefined,
},
resolve: {
alias: resolveAliases,
},
test: {
environment: 'jsdom',
include: ['app/**/*.{test,spec}.?(c|m)[jt]s?(x)'],
coverage: {
reporter: ['lcov', 'text'],
include: ['app/**/*.js', 'app/**/*.vue'],
exclude: [
'app/**/*.@(spec|stories|routes).js',
'**/specs/**/*',
'**/i18n/**/*',
],
},
globals: true,
outputFile: 'coverage/sonar-report.xml',
pool: 'threads',
poolOptions: {
threads: {
singleThread: false,
},
},
server: {
deps: {
inline: ['tinykeys', '@material/mwc-icon'],
},
},
setupFiles: ['fake-indexeddb/auto', 'vitest.setup.js'],
mockReset: true,
clearMocks: true,
},
});