fix(vite-plugin): stop dev CSS writes from reloading server environments - #679
Merged
Merged
Conversation
In dev the plugin wrote `/* <id> <timestamp> */` into df/devup-ui/*.css on every transform, in every environment. The write is how the dev server learns that a sheet changed, but its content always differed, so a transform that added nothing still signalled a change. A module runner cannot apply CSS, so the rsc and ssr environments answered each signal with a full reload, re-imported their entries, transformed the importers again and wrote again: an endless rsc:update loop while a tab is open, and a 500 when the reload lands during the first render. Write a sheet only when its CSS changed (the transform's `css` is only set when it added styles), and return [] from `hotUpdate` for devup-ui sheets in server environments. Those only reference sheets by URL; the client environment still refreshes their contents through Vite's CSS HMR, which is also what keeps globalCss edits live. Defining `hotUpdate` makes Vite 6+ skip `handleHotUpdate`, so the devup.json reload moves into it for the client environment.
Contributor
Changepacks@devup-ui/components@0.1.58 → 0.1.59 - packages/components/package.jsonPatch
@devup-ui/vite-plugin@1.0.71 → 1.0.72 - packages/vite-plugin/package.jsonPatch
|
Codecov Report✅ All modified and coverable lines are covered by tests.
🚀 New features to boost your workflow:
|
Vite 5 has no hotUpdate hook, so removing handleHotUpdate dropped the devup.json full reload there. Vite 6+ calls handleHotUpdate only for a plugin without hotUpdate, so keeping both changes nothing on newer Vite.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
문제
vinext(Vite environment API: client / ssr / rsc)로 dev 서버를 띄우고 탭을 열어 두면, 아무것도 편집하지 않아도
rsc:update/css-update/rsc:prune가 끝없이 반복됩니다. 기동 직후 첫 요청은 500이 납니다. service-template #11과 braillify-studio #49에서 드러났고, 템플릿 main(vite 8.2.2, vinext beta.8, 1.0.69)에서도 재현됩니다.원인
격리 복사본의 플러그인 dist와 vite / plugin-rsc의
hotUpdate경로에 로그를 넣어 확인한 순서입니다.transform이 모든 환경에서 변환할 때마다df/devup-ui/devup-ui-N.css에/* <id> <Date.now()> */를 씁니다. 내용이 매번 달라서, 스타일이 그대로여도 watcher는 변경으로 받습니다. CSS 내용은load가 WASM 메모리에서 주므로 이 쓰기는 "시트가 바뀌었다"는 신호 역할만 합니다.rsc·ssr 환경은 module runner라 CSS HMR이 없습니다. Vite는 시트 변경을
page reload→program reload로 처리하고, runner는 entry를 다시 import합니다.그때 시트를 import한 모듈을 다시 변환하면서(
fetchModule경로) 1의 쓰기가 다시 일어납니다. 브라우저는 매번rsc:update를 받아?_rsc를 다시 가져옵니다.첫 렌더 중에도 같은 쓰기가 일어나면 ssr runner가 렌더 도중 reset되어 500이 납니다.
수정 (
packages/vite-plugin만)codeExtract의css는 새 스타일이 추가됐을 때만 채워지므로, 다시 변환되기만 한 모듈은 아무것도 쓰지 않습니다.hotUpdate에서 서버 환경(consumer === 'server')의 devup-ui 시트 변경에는[]를 돌려줍니다. 서버 환경은 시트를 URL로만 참조하고, 내용은 client 환경이 지금처럼 Vite CSS HMR로 갱신합니다.globalCss(기본 시트)도 이 경로로 반영되므로 서버 재시작이 필요 없습니다.hotUpdate를 정의하면 Vite 6+는 같은 플러그인의handleHotUpdate를 부르지 않습니다. 그래서 devup.json 처리(데이터 파일 재작성, invalidate, full reload)를 client 환경의hotUpdate에도 두었습니다.hotUpdate가 없는 Vite 5를 위해 main의handleHotUpdate는 그대로 남겨 fallback으로 씁니다.브라우저에 직접 갱신을 보내는 방식은 필요 없었습니다. 쓰기를 조건부로 바꾸자 기존 client CSS HMR로 충분했습니다. 반대로 쓰기 조건화만으로는 부족합니다: 서버 환경 처리를 뺀 변형은 cold start 첫 요청이 3번 모두 500이었습니다.
검증
격리 복사본(
git archive+bun install --frozen-lockfile)에 빌드한 플러그인을 넣고, 조사 메모의 측정 방법을 그대로 썼습니다(T에 편집, T+15초에 되돌림, 200ms 샘플, WS 프레임·문서 로드·?_rsc요청 집계). Windows 11, 시나리오별 1회.Cold start 첫 요청 (
df/,.vinext/,node_modules/.vite를 지우고 기동, 각 3회)/dashboard/login편집 시나리오 (적용까지 ms / toggles. toggles 1 = 한 번에 적용, 그 이상 = 깜빡임)
service-template #11 admin
/dashboard(vite 8.3.1, vinext beta.12, plugin-rsc 0.5.35)rsc:update75,?_rsc139rsc:update1,?_rsc2rsc:update59rsc:update0globalCssrsc:update125rsc:update1rsc:update142,css-update75service-template #11 front
/(서버 컴포넌트bg편집): 1.0.71은 674 / 14(14.5초 시점에 미적용), idlersc:update86 → 이 PR은 269 / 1, idle 0.braillify-studio #49 front
/loginrsc:update77rsc:update1rsc:update61rsc:update0globalCssrsc:update102rsc:update1rsc:update95일반 Vite (
apps/vite, client 전용,singleCss): 컴포넌트 새 atom 1.0.71 261 / 1 → 이 PR 249 / 1. 둘 다 문서 reload 0, idle 프레임 0.production build
apps/vite:dist가 바이트 단위로 같습니다(CSS·JS·HTML).vinext build): 클래스 이름을 정규화한 CSS 규칙 집합이 같습니다(파일 17개, 규칙 421개). 바이트 비교는 의미가 없습니다. 배포된 1.0.71로 세 번 빌드해도 전역 클래스 순서와 시트 번호가 빌드마다 달랐습니다.테스트: 같은 CSS면 쓰지 않음, 기본 시트도 바뀔 때만 씀, 서버 환경은 devup-ui 시트 변경을
[]로 넘김, 서버 환경의 소스·이름만 같은 앱 CSS·client 환경 시트는 Vite에 맡김, client 환경의 devup.json reload를 추가했습니다. 기존handleHotUpdate테스트는 그대로 둡니다.bun test5214 pass,plugin.ts커버리지 100%.#678과의 관계
git merge-tree --write-tree로 #678 head(b89e7b31)와 합쳐 보면 충돌이 없습니다. #678은configResolved(module resolver)와codeExtract직후(addWatchFile)를 고치고, 이 PR은 시트 쓰기를 고치고hotUpdate를 추가합니다.알려진 한계
page reload+program reload는 남습니다. devup-ui와 무관한 Vite module runner 기본 동작입니다.singleCss: true에서는 devup.json 테마 변경이 dev에서 반영되지 않습니다(1.0.71도 같음).writeDataFiles가 singleCss일 때 기본 시트를 쓰지 않아 캐시된 시트 모듈이 무효화되지 않습니다. 별개 문제라 이 PR에서는 고치지 않았습니다.#FF00FF는#F0F로 줄여 출력되는데 측정 코드는#ff00ff를 찾았습니다. per-file 모드에서는 1.0.71도 반영됩니다.handleHotUpdate가 그대로 devup.json을 처리합니다. 단위 테스트로만 확인했고, Vite 5 앱에서 실행해 보지는 않았습니다.resolveId가?t=로 매번 새 id를 만드는 동작은 그대로라 시트 하나에 모듈 id가 쌓입니다(admin의devup-ui.css는 49개). 루프가 없어져 더 늘지 않지만 손대지 않았습니다.