mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
e0902e3c27
Adds a small LinearProgress (determinate + MUI-style dual-bar indeterminate) and uses it on workspace build progress; drops MUI and adds Tailwind keyframes + Storybook stories. - New component under `site/src/components/LinearProgress/` - `WorkspaceBuildProgress` switched off `@mui/material/LinearProgress` - Added `bar-indeterminate` / `bar-indeterminate-2` animation keyframes <img width="563" height="115" alt="image" src="https://github.com/user-attachments/assets/bdcd8584-fa2d-487c-96f6-084891a9b084" />
261 lines
7.4 KiB
TypeScript
261 lines
7.4 KiB
TypeScript
import * as path from "node:path";
|
|
import babel from "@rolldown/plugin-babel";
|
|
import { storybookTest } from "@storybook/addon-vitest/vitest-plugin";
|
|
import react, { reactCompilerPreset } from "@vitejs/plugin-react";
|
|
import { playwright } from "@vitest/browser-playwright";
|
|
import { visualizer } from "rollup-plugin-visualizer";
|
|
import type { PluginOption } from "vite";
|
|
import checker from "vite-plugin-checker";
|
|
import { defineConfig } from "vitest/config";
|
|
|
|
// We enable profiling and source maps for internal deployments (e.g. dogfood).
|
|
// The profiling build uses react-dom/profiling, which keeps optimizations but
|
|
// preserves performance instrumentation.
|
|
const isProfilingBuild = process.env.CODER_REACT_PROFILING === "true";
|
|
|
|
const compilerPreset = reactCompilerPreset();
|
|
compilerPreset.rolldown.filter = {
|
|
...compilerPreset.rolldown.filter,
|
|
id: { include: [/src\/pages\/AgentsPage\//] },
|
|
};
|
|
|
|
const plugins: PluginOption[] = [
|
|
react(),
|
|
babel({ presets: [compilerPreset] }),
|
|
checker({
|
|
typescript: true,
|
|
}),
|
|
];
|
|
|
|
if (process.env.STATS !== undefined) {
|
|
plugins.push(
|
|
visualizer({
|
|
filename: "./stats/index.html",
|
|
gzipSize: true,
|
|
}),
|
|
);
|
|
}
|
|
|
|
export default defineConfig({
|
|
plugins,
|
|
worker: {
|
|
format: "es",
|
|
},
|
|
publicDir: path.resolve(__dirname, "./static"),
|
|
build: {
|
|
outDir: path.resolve(__dirname, "./out"),
|
|
emptyOutDir: false, // We need to keep the /bin folder and GITKEEP files
|
|
sourcemap: isProfilingBuild ? true : "hidden",
|
|
rolldownOptions: {
|
|
input: {
|
|
index: path.resolve(__dirname, "./index.html"),
|
|
serviceWorker: path.resolve(__dirname, "./src/serviceWorker.ts"),
|
|
},
|
|
output: {
|
|
entryFileNames: (chunkInfo) => {
|
|
return chunkInfo.name === "serviceWorker"
|
|
? "[name].js"
|
|
: "assets/[name]-[hash].js";
|
|
},
|
|
codeSplitting: {
|
|
groups: [
|
|
{ name: "mui", test: /@mui/ },
|
|
{ name: "emotion", test: /@emotion/ },
|
|
{ name: "monaco", test: /monaco-editor/ },
|
|
{ name: "xterm", test: /@xterm/ },
|
|
{ name: "emoji-mart", test: /emoji-mart/ },
|
|
{ name: "radix-ui", test: /radix-ui/ },
|
|
],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
define: {
|
|
"process.env": {
|
|
NODE_ENV: process.env.NODE_ENV,
|
|
STORYBOOK: process.env.STORYBOOK,
|
|
},
|
|
},
|
|
server: {
|
|
port: process.env.PORT ? Number(process.env.PORT) : 8080,
|
|
headers: {
|
|
// This header corresponds to "src/api/api.ts"'s hardcoded FE token.
|
|
// This is the secret side of the CSRF double cookie submit method.
|
|
// This should be sent on **every** response from the webserver.
|
|
//
|
|
// This is required because in production, the Golang webserver generates
|
|
// this "Set-Cookie" header. The Vite webserver needs to replicate this
|
|
// behavior. Instead of implementing CSRF though, we just use static
|
|
// values for simplicity.
|
|
"Set-Cookie":
|
|
"csrf_token=JXm9hOUdZctWt0ZZGAy9xiS/gxMKYOThdxjjMnMUyn4=; Path=/; HttpOnly; SameSite=Lax",
|
|
},
|
|
proxy: {
|
|
"//": {
|
|
changeOrigin: true,
|
|
target: process.env.CODER_HOST || "http://localhost:3000",
|
|
secure: process.env.NODE_ENV === "production",
|
|
rewrite: (path) => path.replace(/\/+/g, "/"),
|
|
},
|
|
"/api": {
|
|
ws: true,
|
|
changeOrigin: true,
|
|
target: process.env.CODER_HOST || "http://localhost:3000",
|
|
secure: process.env.NODE_ENV === "production",
|
|
configure: (proxy) => {
|
|
if (process.env.CODER_SESSION_TOKEN) {
|
|
proxy.on("proxyReq", (proxyReq) => {
|
|
proxyReq.setHeader(
|
|
"Coder-Session-Token",
|
|
process.env.CODER_SESSION_TOKEN!,
|
|
);
|
|
});
|
|
}
|
|
// Vite does not catch socket errors, and stops the webserver.
|
|
// As /logs endpoint can return HTTP 4xx status, we need to embrace
|
|
// Vite with a custom error handler to prevent from quitting.
|
|
proxy.on("proxyReqWs", (proxyReq, _req, socket) => {
|
|
if (process.env.NODE_ENV === "development") {
|
|
proxyReq.setHeader(
|
|
"origin",
|
|
process.env.CODER_HOST || "http://localhost:3000",
|
|
);
|
|
if (process.env.CODER_SESSION_TOKEN) {
|
|
proxyReq.setHeader(
|
|
"Coder-Session-Token",
|
|
process.env.CODER_SESSION_TOKEN!,
|
|
);
|
|
}
|
|
}
|
|
|
|
socket.on("error", (error) => {
|
|
console.error(error);
|
|
});
|
|
});
|
|
},
|
|
},
|
|
"/swagger": {
|
|
target: process.env.CODER_HOST || "http://localhost:3000",
|
|
secure: process.env.NODE_ENV === "production",
|
|
},
|
|
"/healthz": {
|
|
target: process.env.CODER_HOST || "http://localhost:3000",
|
|
secure: process.env.NODE_ENV === "production",
|
|
},
|
|
"/serviceWorker.js": {
|
|
target: process.env.CODER_HOST || "http://localhost:3000",
|
|
secure: process.env.NODE_ENV === "production",
|
|
},
|
|
},
|
|
allowedHosts: [".coder", ".dev.coder.com"],
|
|
},
|
|
// Pre-bundle deps that Vite tends to discover late (deep MUI
|
|
// imports, Emotion). Without this, Vite re-optimizes mid-session
|
|
// which returns 504 "Outdated Optimize Dep" for every previously
|
|
// served chunk, cascading into dynamic import failures.
|
|
optimizeDeps: {
|
|
include: [
|
|
"@emotion/cache",
|
|
"@emotion/css",
|
|
"@emotion/react",
|
|
"@emotion/react/jsx-runtime",
|
|
"@emotion/styled",
|
|
"@mui/material/Autocomplete",
|
|
"@mui/material/Card",
|
|
"@mui/material/CardActionArea",
|
|
"@mui/material/CardContent",
|
|
"@mui/material/Checkbox",
|
|
"@mui/material/CircularProgress",
|
|
"@mui/material/Collapse",
|
|
"@mui/material/CssBaseline",
|
|
"@mui/material/Dialog",
|
|
"@mui/material/DialogActions",
|
|
"@mui/material/DialogContent",
|
|
"@mui/material/DialogContentText",
|
|
"@mui/material/DialogTitle",
|
|
"@mui/material/Divider",
|
|
"@mui/material/Drawer",
|
|
"@mui/material/FormControl",
|
|
"@mui/material/FormControlLabel",
|
|
"@mui/material/FormGroup",
|
|
"@mui/material/FormHelperText",
|
|
"@mui/material/FormLabel",
|
|
"@mui/material/InputAdornment",
|
|
"@mui/material/InputBase",
|
|
"@mui/material/Link",
|
|
"@mui/material/List",
|
|
"@mui/material/ListItem",
|
|
"@mui/material/ListItemText",
|
|
"@mui/material/Menu",
|
|
"@mui/material/MenuItem",
|
|
"@mui/material/MenuList",
|
|
"@mui/material/Radio",
|
|
"@mui/material/RadioGroup",
|
|
"@mui/material/Select",
|
|
"@mui/material/Skeleton",
|
|
"@mui/material/Snackbar",
|
|
"@mui/material/Stack",
|
|
"@mui/material/SvgIcon",
|
|
"@mui/material/TableRow",
|
|
"@mui/material/TextField",
|
|
"@mui/material/ToggleButton",
|
|
"@mui/material/ToggleButtonGroup",
|
|
"@mui/material/styles",
|
|
"@mui/system/createTheme",
|
|
"@mui/system/useTheme",
|
|
"@mui/x-tree-view",
|
|
],
|
|
},
|
|
resolve: {
|
|
alias: {
|
|
// In profiling builds, swap the usual reconciler for the profiling
|
|
// variant so that <Profiler> receives actual timing data.
|
|
...(isProfilingBuild
|
|
? { "react-dom/client": "react-dom/profiling" }
|
|
: {}),
|
|
},
|
|
},
|
|
test: {
|
|
silent: "passed-only",
|
|
projects: [
|
|
{
|
|
extends: true,
|
|
test: {
|
|
name: "unit",
|
|
include: [
|
|
"src/**/*.test.?(m)ts?(x)",
|
|
"scripts/**/*.test.?(m)[jt]s?(x)",
|
|
],
|
|
globals: true,
|
|
environment: "jsdom",
|
|
setupFiles: [
|
|
"@testing-library/jest-dom/vitest",
|
|
"./test/vitestSetup.ts",
|
|
],
|
|
},
|
|
},
|
|
// Storybook story tests via Playwright browser mode.
|
|
// Discovery handled by the storybookTest plugin via
|
|
// .storybook/main.ts `stories` config.
|
|
{
|
|
extends: true,
|
|
plugins: [
|
|
storybookTest({
|
|
configDir: path.join(__dirname, ".storybook"),
|
|
}),
|
|
],
|
|
test: {
|
|
name: "storybook",
|
|
browser: {
|
|
enabled: true,
|
|
headless: true,
|
|
provider: playwright(),
|
|
instances: [{ browser: "chromium" }],
|
|
},
|
|
setupFiles: [".storybook/vitest.setup.ts"],
|
|
},
|
|
},
|
|
],
|
|
},
|
|
});
|