mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-02 10:08:45 +09:00
## prettier -> oxfmt (0.58.0)
- .oxfmtrc.json generated with `oxfmt --migrate=prettier`: trailingComma
es5, printWidth 80 (oxfmt defaults to 100), sortPackageJson disabled,
.prettierignore patterns folded in (file deleted).
- ignorePatterns additionally exclude yml/yaml/toml/jsonc and
packages/cli/templates/** so oxfmt covers exactly the file set prettier
used to format (ts,tsx,js,json,css,md) and never touches user-facing
scaffolding artifacts.
- root `format` script, nano-staged pre-commit hook and the CI check
(`pnpm oxfmt --check`, replacing `prettier --cache --check` and its
actions/cache step — oxfmt checks the whole repo in ~0.7s) now run
oxfmt.
- every generator script that ran `prettier --write` on __generated__
output now runs `oxfmt` (css-data, icons, html-data, postgrest, sdk,
sdk-components-*, apps/builder tailwind preflight).
- the four scripts that used prettier's JS `format()` API write raw
source instead and their package.json scripts pipe the generated file
through the oxfmt CLI: project-build generate-docs +
generate-runtime-operation-contracts, cli generate-docs, apps/builder
generate-server-only-api-metadata. oxfmt rejects paths containing "..",
so cross-package generated files are formatted via a subshell cd into
the owning package.
- one-time reformat: oxfmt's canonical output differs from prettier on 8
repo source files (binary-operator continuation indent, import() call
wrapping, sub-pixel spacing of empty for-clauses); oxfmt honors existing
`// prettier-ignore` comments so intentional layouts are kept.
- prettier remains ONLY as a cli devDependency for the
prettier/standalone HTML normalizer inside html-to-jsx.test.ts, and
inside fixtures/ + packages/cli/templates/ (ships to scaffolded user
projects; out of scope here).
- editor config switched from esbenp.prettier-vscode to oxc.oxc-vscode
(devcontainer extension list, .vscode editor.defaultFormatter).
## oxlint 1.56 -> 1.73
- oxlint 1.73 rules-of-hooks special-cases hooks NAMED useEffectEvent:
their results may only be called from effects. Our
app/shared/hook-utils/effect-event.ts is a stable-callback helper
(useEvent pattern), intentionally called from event handlers and passed
as props. The rule is disabled via .oxlintrc.json overrides for the five
files doing this (product code untouched); note those files lose
hook-order linting entirely until the rule becomes configurable.
## TypeScript 5.8.2 -> 7.0.2 (native compiler)
- `tsc` is now the Go-based native binary; all `tsgo` script usages
(workspace packages and fixtures) renamed back to `tsc` and
@typescript/native-preview removed (the package is being phased out).
- fixes for TS7 changed defaults and stricter checks:
- `types` now defaults to [] -> base tsconfig sets "types": ["node"]
(restores the implicit-@types/node behavior of TS5).
- `rootDir` now defaults to ./ -> every tsconfig.dts.json sets
"rootDir": "./src" (TS5011 otherwise).
- import assertions removed -> cli/src/cli.ts uses `with { type: "json"
}` (TS2880).
- stricter isolatedDeclarations inference -> explicit annotations in
css-data bin/property-filter.ts (TS9010) and image image-dev.stories.tsx
(TS9013).
- declaration serialization limit -> project-build builder-runtime.ts
runtimeOperationContracts gets an explicit type (TS7056); the annotation
preserves the narrow RuntimeOperationId union.
- TS7 ships no stable programmatic API:
- react-sdk (component-generator tests drive the compiler API) aliases
its typescript devDependency to @typescript/typescript6@^6.0.2; its bin
is `tsc6`, so the package's typecheck still runs native TS7 tsc from the
workspace root.
- generate-arg-types keeps typescript 5.8.2 for react-docgen-typescript.
- fixtures/ and packages/cli/templates/ keep typescript 5.8.2
(user-facing); fixture scripts still work because plain `tsc` resolves
to their local dependency.
## vite -> rolldown-vite 7.3.1 (oxc bundler)
- global pnpm override `"vite": "npm:rolldown-vite@^7.3.1"`: the builder
app (remix v2 vite plugin works unmodified), every package vite build
and the storybook build now bundle on rolldown/oxc.
- vitest is scoped BACK to vanilla vite via `vitest>vite`,
`vite-node>vite` and `@vitest/mocker>vite` overrides because vitest
3.1's runner does not understand rolldown's SSR transform
(__vite_ssr_exportName__ ReferenceError in every suite).
- those overrides pin vite EXACTLY to 6.3.4: letting ^6.3.4 float to
6.4.3 breaks cli prebuild tests (dynamic import of a generated
app/routes/$.tsx fails to resolve under 6.4.3's loader).
## unrelated cleanup requested during review
- apps/builder commands.ts imports duplicate-tokens-group statically
instead of via dynamic import. The circular dependency the dynamic
import guarded against only exists through the command-panel barrel
(command-panel.tsx > groups/index.ts > commands-group.tsx >
commands.ts); importing the group module directly is cycle-free
(verified with madge), matching the neighboring wrap-group and
convert-group imports.
## verification
- pnpm lint, oxfmt --check: clean.
- pnpm -r typecheck (TS7 native) and pnpm dts: clean.
- pnpm -r test: all suites pass (builder 2093 tests, cli 630, react-sdk
45, project-build 11, ...).
- builder app build, full workspace build, cli build and storybook build
all succeed on rolldown-vite.
## Description
1. What is this PR about (link the issue and add a short description)
## Steps for reproduction
1. click button
2. expect xyz
## Code Review
- [ ] hi @kof, I need you to do
- conceptual review (architecture, feature-correctness)
- detailed review (read every line)
- test it on preview
## Before requesting a review
- [ ] made a self-review
- [ ] added inline comments where things may be not obvious (the "why",
not "what")
## Before merging
- [ ] tested locally and on preview environment (preview dev login:
0000)
- [ ] updated [test
cases](https://github.com/webstudio-is/webstudio/blob/main/apps/builder/docs/test-cases.md)
document
- [ ] added tests
- [ ] if any new env variables are added, added them to `.env` file
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
183 lines
6.1 KiB
TypeScript
183 lines
6.1 KiB
TypeScript
import path, { resolve } from "node:path";
|
|
import { defineConfig, loadEnv, type CorsOptions } from "vite";
|
|
import { vitePlugin as remix } from "@remix-run/dev";
|
|
import { vercelPreset } from "@vercel/remix/vite";
|
|
import type { IncomingMessage } from "node:http";
|
|
import pc from "picocolors";
|
|
|
|
import {
|
|
getAuthorizationServerOrigin,
|
|
isBuilderUrl,
|
|
} from "./app/shared/router-utils/origins";
|
|
import { readFileSync, existsSync } from "node:fs";
|
|
import fg from "fast-glob";
|
|
|
|
const rootDir = ["..", "../..", "../../.."]
|
|
.map((dir) => path.join(__dirname, dir))
|
|
.find((dir) => existsSync(path.join(dir, ".git")));
|
|
|
|
const hasPrivateFolders =
|
|
fg.sync([path.join(rootDir ?? "", "packages/*/private-src/*")], {
|
|
ignore: ["**/node_modules/**"],
|
|
}).length > 0;
|
|
|
|
const conditions = hasPrivateFolders
|
|
? ["webstudio-private", "webstudio"]
|
|
: ["webstudio"];
|
|
|
|
export default defineConfig(({ mode }) => {
|
|
if (mode === "development") {
|
|
// Enable self-signed certificates for development service 2 service fetch calls.
|
|
// This is particularly important for secure communication with the oauth.ws.token endpoint.
|
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
}
|
|
|
|
const env = loadEnv(mode, __dirname, "");
|
|
const multiplayerRelayProxyTarget =
|
|
process.env.COLLAB_RELAY_PROXY_TARGET ??
|
|
env.COLLAB_RELAY_PROXY_TARGET ??
|
|
"http://127.0.0.1:1999";
|
|
|
|
return {
|
|
plugins: [
|
|
remix({
|
|
presets: [vercelPreset()],
|
|
future: {
|
|
v3_lazyRouteDiscovery: false,
|
|
v3_relativeSplatPath: false,
|
|
v3_singleFetch: false,
|
|
v3_fetcherPersist: false,
|
|
v3_throwAbortReason: false,
|
|
},
|
|
}),
|
|
{
|
|
name: "request-timing-logger",
|
|
configureServer(server) {
|
|
server.middlewares.use((req, res, next) => {
|
|
// Some dev proxies can forward HTTP/2 pseudo-headers such as
|
|
// ":method". Remix converts Node headers to Fetch Headers, where
|
|
// those names are invalid and crash before the request reaches app
|
|
// code. Vite still needs a normal req.url, so preserve ":path"
|
|
// before removing the pseudo-headers.
|
|
if (req.url === undefined) {
|
|
const path = req.headers[":path"];
|
|
if (typeof path === "string") {
|
|
req.url = path;
|
|
}
|
|
}
|
|
for (const header of Object.keys(req.headers)) {
|
|
if (header.startsWith(":")) {
|
|
delete req.headers[header];
|
|
}
|
|
}
|
|
// Pre-bundled dep chunks must never be served from a stale browser
|
|
// cache. Vite uses ?v= to bust the cache, but some browsers (or
|
|
// service workers) still serve cached chunks with old hashes,
|
|
// causing duplicate-React errors during hydration. no-store removes
|
|
// the files from the browser cache entirely.
|
|
if (req.url?.includes("/node_modules/.vite/deps/")) {
|
|
res.setHeader("Cache-Control", "no-store");
|
|
}
|
|
const start = Date.now();
|
|
res.on("finish", () => {
|
|
const duration = Date.now() - start;
|
|
if (
|
|
!(
|
|
req.url?.startsWith("/@") ||
|
|
req.url?.startsWith("/app") ||
|
|
req.url?.includes("/node_modules")
|
|
)
|
|
) {
|
|
console.info(
|
|
`[${req.method}] ${req.url} - ${duration}ms : ${pc.dim(req.headers.host)}`
|
|
);
|
|
}
|
|
});
|
|
next();
|
|
});
|
|
},
|
|
},
|
|
],
|
|
resolve: {
|
|
conditions: [...conditions, "browser", "development|production"],
|
|
alias: [
|
|
{
|
|
find: "~",
|
|
replacement: resolve("app"),
|
|
},
|
|
|
|
// before 2,899.74 kB, after 2,145.98 kB
|
|
{
|
|
find: "@supabase/node-fetch",
|
|
replacement: resolve("./app/shared/empty.ts"),
|
|
},
|
|
|
|
// rolldown resolves tslib to its CJS build (our resolve.conditions
|
|
// omit "module") and the generated interop leaves `default`
|
|
// undefined, crashing the server bundle at boot. Point it at the
|
|
// ESM build instead.
|
|
{
|
|
find: /^tslib$/,
|
|
replacement: "tslib/tslib.es6.mjs",
|
|
},
|
|
],
|
|
},
|
|
ssr: {
|
|
resolve: {
|
|
conditions: [...conditions, "node", "development|production"],
|
|
},
|
|
},
|
|
define: {
|
|
"process.env.NODE_ENV": JSON.stringify(mode),
|
|
},
|
|
server: {
|
|
// Service-to-service OAuth token call requires a specified host for the wstd.dev domain
|
|
host: "wstd.dev",
|
|
proxy: {
|
|
"/collab-relay": {
|
|
target: multiplayerRelayProxyTarget,
|
|
ws: true,
|
|
},
|
|
},
|
|
https: {
|
|
key: readFileSync("../../https/privkey.pem"),
|
|
cert: readFileSync("../../https/fullchain.pem"),
|
|
},
|
|
cors: ((
|
|
req: IncomingMessage,
|
|
callback: (error: Error | null, options: CorsOptions | null) => void
|
|
) => {
|
|
// Handle CORS preflight requests in development to mimic Remix production behavior
|
|
if (req.method === "OPTIONS" || req.method === "POST") {
|
|
if (req.headers.origin != null && req.url != null) {
|
|
const url = new URL(req.url, `https://${req.headers.host}`);
|
|
|
|
// Allow CORS for /builder-logout path when requested from the authorization server
|
|
if (url.pathname === "/builder-logout" && isBuilderUrl(url.href)) {
|
|
return callback(null, {
|
|
origin: getAuthorizationServerOrigin(url.href),
|
|
preflightContinue: false,
|
|
credentials: true,
|
|
});
|
|
}
|
|
}
|
|
|
|
if (req.method === "OPTIONS") {
|
|
// Respond with method not allowed for other preflight requests
|
|
return callback(null, {
|
|
preflightContinue: false,
|
|
optionsSuccessStatus: 405,
|
|
});
|
|
}
|
|
}
|
|
|
|
// Disable CORS for all other requests
|
|
return callback(null, {
|
|
origin: false,
|
|
});
|
|
}) as never,
|
|
},
|
|
envPrefix: ["GITHUB_", "PUBLIC_"],
|
|
};
|
|
});
|