CSS 전달 최적화
라우트 소유와 선택적 가지치기로 관련 없는 CSS를 줄이고 프로덕션 출력, 런타임 동작, 탐색 비용을 검증합니다.
이 페이지의 내용
#개요
상호작용 후 나타나는 상태까지 각 페이지가 필요한 CSS를 받게 하세요. 전달 경로를 추적하고 기능 스타일을 소유 진입점으로 옮긴 뒤 빌드가 가능한 용도를 알고 있을 때만 네이티브 규칙을 가지치기하세요. 출력 에셋과 동작을 비교한 후 회귀 검사 예산을 정하세요.
이 가이드는 같은 탐색 메뉴를 공유하는 Overview와 Reports 페이지를 다룹니다. Reports만 보고서 패널을 렌더링합니다. 목표는 Overview가 패널 규칙을 전혀 다운로드하지 않고 Reports는 준비 및 키보드 포커스 상태를 유지하는 것입니다.
먼저 프레임워크의 설치를 완료하세요. 라우트 단위 스타일은 로컬 스타일 소유와 정의를, 클래스 이름 스캔은 발견 가능한 클래스 문자열과 소스 예외를 설명합니다. 일상적인 컴포넌트 작업에는 이 작성 가이드를 사용하고 전달 비용이나 누락 에셋 조사에는 이 작업 흐름을 사용하세요.
설치된 Master CSS 버전, CSS 진입점, 렌더링 모드, 가지치기 정책을 기록하세요. 전달 리팩터링 중에는 기존 모드를 유지하세요. 아래 재현 가능한 예제는 Vite와 점진적 렌더링으로 초기 CSS, 이후 유틸리티, 네이티브 가지치기가 별개임을 보여 줍니다. 공개 패키지와 프리셋 토큰만 사용합니다.
#각 페이지가 받는 내용 확인하기
현재 소스를 빌드하고 출력과 잠금 파일, 도구 버전을 함께 보관하세요. 기능을 쓰는 페이지와 쓰지 않는 페이지를 모두 선택하고 직접 로드 및 두 페이지 사이 탐색을 포함하세요. 공유 에셋이 이미 캐시에 있으면 두 번째 페이지 비용이 달라질 수 있습니다.
각 HTML 스타일시트 링크에서 CSS import를 추적하고 해석된 에셋을 페이지마다 한 번씩만 계산하세요. 인라인 style 블록은 별도로 기록하세요. 모듈 import도 검사하세요. 공유 레지스트리로 가져온 컴포넌트는 마크업이 없어도 CSS를 로드할 수 있습니다. 런타임 스크립트, 프로그램, hydration 데이터는 스타일시트와 따로 기록하세요.
서로 다른 세 종류의 근거를 사용하세요:
| 근거 | 기록할 내용 | 입증하는 내용 |
|---|---|---|
| 빌드 출력 | 고유 외부 파일, import 깊이, 원본 및 gzip/Brotli 바이트, 인라인 CSS | 빌드가 전달하는 에셋과 기록한 압축 설정에서 결정되는 크기 |
| 브라우저 요청 | 응답, 인코딩, 전송 바이트, 캐시 재사용, 요청 시작자 | 이번 탐색에서 실제 가져온 내용 |
| 브라우저 동작 | 페인트·상호작용 시간, 계산 스타일, 관련 상태 | 페이지 작동 여부와 테스트 조건의 성능 |
Network 패널은 요청과 의존 관계를 보여 줍니다. 변경 전후에 같은 브라우저, 뷰포트, 콘텐츠, 제한 설정을 사용해 콜드 로드와 웜 탐색을 따로 측정하세요. 시간 측정을 반복하고 중앙값과 개별 표본을 모두 보관하세요. 압축 파일 크기만으로 빠른 페인트를 입증할 수는 없습니다.
Coverage는 조사할 기능의 단서를 제공할 수 있습니다. 한 기록에서 쓰이지 않은 규칙도 닫힌 대화상자, 포커스 상태, 다른 뷰포트, 외부 콘텐츠에 필요할 수 있습니다. 제거 전에 소유와 가능한 상태를 확인하세요.
#기능 규칙에 로컬 소유자 지정하기
공통 JavaScript 진입점이 탐색 메뉴와 보고서 스타일을 모두 가져온다고 가정해 보세요:
import './style.css'import './reports.css'이제 두 페이지 모두 보고서 규칙을 받습니다. Overview DOM에 보고서가 없어도 import는 바뀌지 않습니다. 두 번째 import를 Reports만 로드하는 보고서 진입점으로 옮기고 공유 탐색 메뉴는 공통 스타일시트에 유지하세요.
import 이동으로 전달 경로 변경
보고서 진입점이 기능 import를 소유합니다. 공유 탐색은 두 페이지 모두에 도착하고 보고서 규칙은 Reports에만 도착합니다. 블록은 실측 바이트가 아닌 규칙 그룹을 뜻합니다.
텍스트 설명
- 공통 import. main.js가 탐색과 보고서 스타일을 가져오면 Overview에 보고서 마크업이 없어도 두 페이지가 두 그룹을 모두 받습니다.
- 로컬 import. reports.css를 reports.js로 옮깁니다. 두 페이지는 공통 진입점을 유지하며 Reports만 reports.js를 불러와 보고서 규칙을 받습니다.
#두 페이지 예제 재현하기
빈 디렉터리에 다음 파일을 만드세요. npm install로 공개 패키지를 설치하고 npm run build를 실행한 뒤 npm run preview로 결과를 제공하세요. Master CSS 릴리스는 일치시켜 사용하고 비교를 위해 생성된 잠금 파일을 보관하세요.
- package.json
- vite.config.js
- index.html
- reports
- index.html
- src
- style.css
- main.js
- reports.css
- reports.js
Vite 다중 페이지 빌드는 HTML 진입점 두 개를 사용합니다. 둘 다 초기 마크업을 포함하므로 Vite HTML 파이프라인이 사전 렌더링할 수 있습니다. 사용자 지정 요청 시점 SSR 앱에는 프레임워크 서버 통합이 필요합니다. 이 예제는 Vite에 요청 시점 렌더러를 제공하지 않습니다.
전체 두 페이지 프로젝트
{ "name": "css-delivery-example", "private": true, "type": "module", "scripts": { "dev": "vite", "build": "vite build", "preview": "vite preview" }, "dependencies": { "@master/css": "2.0.0" }, "devDependencies": { "@master/css-vite": "2.0.0", "vite": "^8" }}import { defineConfig } from 'vite'import masterCSS from '@master/css-vite'export default defineConfig({ plugins: [masterCSS({ mode: 'progressive' })], build: { rolldownOptions: { input: ['index.html', 'reports/index.html'] } }})<!doctype html><html lang="en"><head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Overview</title> <script type="module" src="/src/main.js"></script></head><body class="p-lg"> <nav class="site-nav" aria-label="Main"><a href="/">Overview</a><a href="/reports/">Reports</a></nav> <main><h1>Overview</h1><p>The overview does not render a report.</p></main></body></html><!doctype html><html lang="en"><head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Reports</title> <script type="module" src="/src/main.js"></script> <script type="module" src="/src/reports.js"></script></head><body class="p-lg"> <nav class="site-nav" aria-label="Main"><a href="/">Overview</a><a href="/reports/">Reports</a></nav> <main> <h1>Reports</h1> <button id="refresh" type="button">Refresh report</button> <section id="report" class="report report-idle" data-state="idle" tabindex="0">Report waiting</section> </main></body></html>공통 진입점이 하나의 전체 Master CSS 진입점을 가져옵니다. 탐색 규칙은 공개 간격 토큰을 사용합니다:
import './style.css'@import '@master/css';@layer components { .site-nav { display: flex; gap: var(--spacing-md); }}보고서 모듈은 자체 스타일시트를 가져옵니다. 완전한 클래스 문자열이 유한한 상태 후보를 나타내며 속성 선택자와 포커스 규칙도 패널과 함께 유지합니다:
import './reports.css'const states = { idle: 'report-idle', ready: 'report-ready' }const report = document.querySelector('#report')document.querySelector('#refresh').addEventListener('click', () => { report.classList.replace(states.idle, states.ready) report.dataset.state = 'ready' report.textContent = 'Report ready'})@prune native;@layer components { .report { padding: var(--spacing-lg); border: 1px solid currentColor; } .report[data-state="ready"] { border-width: 3px; } .report:focus-visible { outline: 2px solid currentColor; outline-offset: 4px; } .report-idle { opacity: .65; } .report-ready { opacity: 1; } .report-debug { outline: 8px solid red; }}/와 /reports/를 직접 열고 출력 CSS import를 추적하세요. Reports만 .report를 받아야 합니다. 보고서를 갱신한 뒤 키보드로 패널에 포커스하세요. 테두리는 3px, 불투명도는 1이 되고 키보드 포커스 윤곽선이 표시됩니다.
@prune native 표시는 이 파일을 가지치기 대상으로 지정하지만 제거에는 클래스 사용 정보를 제공하는 빌드 경로도 필요합니다. 현재 Vite 로컬 스타일시트 변환은 프로젝트 정의로 보고서 규칙을 컴파일하며 가지치기용 사용 집합을 제공하지 않으므로 .report-debug가 로컬 출력에 남습니다. 소유 범위 개선은 여전히 유효합니다. 다음 절은 소스의 지시문을 더 작은 프로덕션 파일의 증거로 취급하지 않고 공개 CLI로 가지치기를 따로 검증합니다.
#기능을 가져오지 않고 정의 공유하기
로컬 스타일시트는 전체 진입점을 다시 가져오지 않고 프로젝트 토큰과 믹스인을 사용할 수 있습니다. 통합이 정의 맥락을 제공합니다. 정의가 진입점 그래프 밖에 있다면 @reference를 사용하세요. 참조 파일의 네이티브 규칙을 불러오지 않고 정의를 제공합니다.
네이티브 선택자는 로드 후에도 일반 문서 범위를 유지합니다. 라우트 소유는 전달을 제어하며 선택자 일치나 프레임워크가 탐색 후 CSS를 유지할지는 제어하지 않습니다. 공유 값은 토큰에, 공유 역할은 네이티브 클래스에, 반복 선언은 믹스인에 두고 독립적인 로컬 선택에는 유틸리티를 사용하세요. 스타일 재사용에서 이 설계 결정을 설명합니다.
#알려진 사용 집합만 가지치기
네이티브 CSS는 기본적으로 유지됩니다. 불필요한 import를 먼저 제거한 뒤 클래스와 상태를 파악할 수 있는 파일을 검토하세요. 경계를 정하는 동안 프로젝트 전체 pruneNativeCSS는 끄고 개별 소스만 @prune native;로 활성화하세요.
가지치기는 선언 파일에 적용되고 해당 루트의 클래스 사용 범위를 사용하며 가져온 파일로 자동 전파되지 않습니다. 패키지 CSS는 자체 활성화가 필요합니다. 소비자의 프로젝트 전체 설정이 모든 패키지 스타일을 가지치기 가능하게 만들지는 않습니다. 프로젝트 소유 스타일시트 전체의 가지치기를 의도하고 가능한 사용을 모두 포함했을 때만 pruneNativeCSS: true를 검토하세요. 전체 범위 규칙은 가지치기 Reference에서 다룹니다.
이 예제의 마크업에는 report와 report-idle이 있고 JavaScript 맵에는 report-ready도 있습니다. 완전한 이름 덕분에 스캐너 기반 빌드는 두 상태 규칙을 모두 유지할 수 있습니다. report를 찾으면 준비나 포커스가 아직 활성화되지 않았어도 속성 및 포커스 선택자 분기를 유지합니다. 클래스 스캔은 선택자가 도달 가능한 DOM 관계에 일치함을 입증하지 않으며 유지된 규칙도 한 번도 일치하지 않을 수 있습니다.
알려진 클래스는 나중 선택자 상태도 유지
근거는 초기 DOM 스크린샷이 아닌 가능한 클래스 집합입니다. 유한 외부 집합은 안전 목록으로 보완할 수 있고 알 수 없는 집합은 보존해야 합니다.
텍스트 설명
- 알려진 사용. 보고서 마크업과 전체 idle/ready 맵이 report, report-idle, report-ready를 제공합니다. 스캐너 기반 빌드는 report[data-state="ready"]와 report:focus-visible을 포함한 규칙을 유지합니다.
- 알 수 없는 사용. 범위가 열린 CMS 클래스는 소스 스캔으로 없음을 입증할 수 없습니다. 소유 네이티브 스타일시트를 보존하고 CMS 콘텐츠가 나타날 수 있는 곳에서 불러오세요.
클래스 선택자가 없는 규칙은 계속 사용할 수 있습니다. 가지치기는 초기 DOM 스냅샷이 아닌 클래스 사용 정보를 기준으로 합니다.
네이티브 가지치기는 클래스 없는 규칙을 유지
소스는 @prune native를 활성화합니다. 유지된 클래스 없는 선택자도 일반 CSS 일치와 캐스케이드 규칙을 따릅니다.
텍스트 설명
- body와 :root에는 클래스 선택자가 없으므로 네이티브 가지치기가 규칙을 유지합니다.
- 이 예제에서는 사용하는 .card 규칙은 남고 사용하지 않는 .unused 클래스 규칙은 제거됩니다.
- 네이티브 가지치기는 클래스 사용에 기반하며 브라우저로 모든 선택자를 분석하거나 유지한 모든 규칙이 현재 일치한다고 보장하지 않습니다.
#가지치기 단계 독립 검증하기
CLI로 네이티브 가지치기 재현하기
스캔한 사용 집합을 제공하는 빌드 경로로 이 파일을 검사하려면 npm install -D @master/css-cli@2.0.0로 일치하는 공개 CLI를 설치하세요. 보고서 소스를 일회용 검사 디렉터리에 복사하세요:
mkdir pruning-checkcp src/reports.css src/reports.js reports/index.html pruning-check/해당 디렉터리에 이 진입점을 추가하세요:
@import '@master/css';@import './reports.css';프로젝트 루트에서 실행하세요:
cd pruning-checknpx master-css generate 'index.html' 'reports.js' --strict -o output.cssoutput.css에서 import를 따라가세요. 보고서 규칙과 필요한 간격 리소스는 유지되고 .report-debug는 없습니다. 이 검사는 해당 입력에 대한 CLI 출력을 입증하며 앱의 Vite 파이프라인을 바꾸지 않습니다. 가지치기를 강제하려고 두 번째 전체 진입점을 앱에 가져오지 마세요. 관련 없는 공유 전달이 다시 생깁니다. 프로덕션 통합의 실제 출력이 의도한 필터링을 보여 줄 때만 가지치기를 사용하세요.
#외부 및 불확실한 소스 보호하기
위젯이 추가하는 클래스가 유한하다면 가지치기 파일에 완전한 이름을 나열하세요:
@prune native;@safelist "report-exported";@layer components { .report-exported { border-style: double; }}안전 목록은 후보를 제공하며 DOM에 클래스를 추가하지는 않습니다. 범위가 열린 CMS나 벤더 소스는 완전한 목록을 추측하지 말고 소유 파일의 네이티브 규칙을 보존하세요:
@preserve native;@layer components { .cms-callout { border-inline-start: 3px solid currentColor; }}CMS 콘텐츠가 나타날 수 있는 곳마다 해당 파일을 불러오세요. @preserve native는 해당 파일의 네이티브 가지치기를 재정의합니다. 네이티브 전달을 보존하는 것이며 소스 형식을 바이트 단위로 유지하거나 사용하지 않는 모든 유틸리티를 출력에 강제 포함하지는 않습니다.
선택자와 함께 애니메이션과 변수도 검토하세요. 활성화된 파일에서 관리 키프레임은 온디맨드 정의가 됩니다. 유지된 애니메이션 선언과 변수 의존성은 필요한 정의를 남길 수 있습니다. 외부 JavaScript만 제공하는 이름에는 정확한 @safelist keyframes "report-enter"; 항목이 필요할 수 있습니다. 조건·레이어 래핑과 익명 레이어 및 상대 URL 제한을 포함한 키프레임 계약을 따르세요. 임의로 작성한 네이티브 선택자는 런타임 정의가 아닙니다.
#렌더링 모드와 가지치기 구분하기
렌더링 모드는 클래스가 언제 CSS가 되는지와 브라우저가 새 규칙을 생성할 수 있는지를 다룹니다. 네이티브 가지치기는 스타일시트 빌드에서 대상 네이티브 규칙 중 무엇을 남길지 다룹니다. 점진적 렌더링은 가지치기한 네이티브 스타일시트를 사용하면서 초기 페이지 CSS를 전달하고 이후 유틸리티 클래스도 지원할 수 있습니다.
렌더링과 네이티브 가지치기는 독립적인 선택
두 열 모두 초기 CSS를 제공할 수 있습니다. 점진적 행은 후속 유틸리티도 지원합니다. 가지치기는 대상 네이티브 출력을 바꾸며 렌더링 모드를 바꾸지 않습니다.
텍스트 설명
- 네이티브 정책. 보존은 네이티브 규칙을 유지합니다. 지원되는 가지치기 경로는 알려진 사용 집합으로 대상 규칙을 필터링합니다. 행렬은 정책 설명이며 모든 통합 경로가 사용 집합을 제공한다는 보장이 아닙니다.
- 렌더링 모드. 정적 렌더링은 새 브라우저 규칙을 생성하지 않습니다. 점진적 렌더링은 두 네이티브 정책 모두에서 초기 CSS를 제공하고 이후 유효한 유틸리티를 생성할 수 있습니다.
- 경계. 런타임 생성은 제거된 임의의 네이티브 선택자를 복원하지 않습니다. 관리 키프레임은 별도의 정의 및 의존성 계약을 따릅니다.
정적 추출에는 소스 집합 안의 완전한 클래스 이름이 필요하고 네이티브 가지치기에는 사용할 수 있는 네이티브 이름이 필요합니다. 전체 리터럴 맵은 두 경우 모두 유한한 후보를 검토 가능하게 합니다. 런타임과 점진적 모드는 시작 후 구성되는 새 유효 유틸리티 이름도 처리하지만 유효성과 생성 비용은 여전히 중요합니다. 안정적인 규칙 하나로 충분한 연속 변화 값에는 CSS 변수를 사용하세요.
런타임 생성은 가지치기로 제거한 임의의 네이티브 선택자를 재구성할 수 없습니다. 나중에 report-ready를 추가해 동작하려면 네이티브 규칙이 유지되고 로드되어 있어야 합니다. 관리 키프레임은 자체 정의와 의존성 계약이 있으므로 일반 선택자 처리에서 동작을 추론하지 마세요.
예제에서는 다음을 따로 검증하세요:
- JavaScript 실행 전 응답 소스를 확인하세요.
style#master-css에는 초기p-lg유틸리티가 있으며 네이티브 보고서 스타일시트는 Reports에만 연결됩니다. - Reports의 보고서를 갱신하세요. 유지된 네이티브 상태 규칙은 새 유틸리티 생성 없이 적용됩니다.
- 독립적인 런타임 검사를 위해 Reports를 다시 로드하세요. 보고서를 갱신하기 전에 페이지 hydration 매니페스트에 없는 새 유효 유틸리티를 개발자 도구에서 추가하세요:
const report = document.querySelector('#report')report.classList.add('(letter-spacing:0.123em)')계산된 자간과 document.querySelector('style#master-css').sheet.cssRules를 검사하세요. 기본 점진적 시작 정책과 유효한 hydration 데이터에서는 미포함 클래스가 지연 엔진/프로그램 경로를 트리거합니다. 페이지 매니페스트가 이미 포함한 클래스로는 이 경로의 동작을 입증할 수 없습니다. style 요소의 텍스트 내용을 실시간 CSSOM 스냅샷으로 사용하지 마세요.
HTML, CSS, hydration 데이터, 런타임 리소스는 같은 빌드의 것을 유지하세요. 통합은 개발 시 동작이 다를 수 있으므로 프로덕션과 개발을 모두 테스트하세요. 시작 옵션, hydration 실패, 프레임워크 지원은 렌더링 모드를 참고하세요.
#비용과 동작 비교 후 예산 설정하기
한 번에 하나의 변경만 검토하세요. 전달 소유를 먼저 정하고 지원되는 곳에서 가지치기한 뒤 측정이 뒷받침할 때 에셋 그룹화를 조정하세요. 이 예제에서는 main.js가 reports.css를 가져오는 버전과 위 버전을 비교하세요. Overview에는 보고서 규칙이 없어지고 Reports는 외형과 상호작용 상태를 유지해야 합니다.
작아진 출력도 동작 검사 필요
두 검사는 서로 다른 사실을 입증합니다. 누락된 기능도 파일을 작게 만들 수 있으므로 바이트 절감만으로 변경을 통과시킬 수 없습니다.
텍스트 설명
- 출력 근거. 같은 빌드와 압축 방법으로 Overview와 Reports를 비교하세요. 외부 import와 인라인 CSS를 추적하고 파일, 깊이, 바이트, 캐시 재사용을 기록하세요.
- 동작 근거. 두 라우트를 직접 열고 사이를 이동하며 준비, 포커스, 테마, 반응형 상태를 확인하세요. 점진적 페이지는 초기 CSS와 새 런타임 유틸리티를 따로 검증하세요.
- 예산. 의도한 전달 변경과 동작 유지가 모두 검증된 후에만 프로젝트별 상한을 저장하세요. 시간 표본은 결정론적인 바이트 한도와 구분합니다.
| 검사 | Overview | Reports |
|---|---|---|
| 직접 프로덕션 로드 | 탐색과 페이지 타이포그래피 유지, 보고서 규칙 없음 | 패널과 필요한 모든 네이티브 상태 도착 |
| 상호작용 | 탐색 계속 작동 | 갱신과 키보드 포커스에 의도한 스타일 유지 |
| 탐색과 캐시 | 새로 가져온 CSS 에셋과 재사용한 에셋 따로 계산 | Overview에서 이동 및 새 직접 로드 테스트 |
| 점진적 출력 | 런타임 전에 초기 유틸리티 CSS 존재 | 초기 CSS와 실제 새 유틸리티 모두 동작 |
기준선과 같은 방법으로 고유 외부 CSS 바이트, gzip/Brotli 바이트, 인라인 CSS, 파일 수, import 깊이를 기록하세요. 선택자뿐 아니라 테마 변수, 조건 규칙, 의사 요소, 애니메이션 의존성도 검사하세요. 전달 이동, 미사용 규칙 제거, 중복 제거, 필요한 의존성 수정 등 각 출력 차이를 설명하세요.
로컬 파일 하나가 여러 출력 조각을 만들 수 있으므로 소스 파일 수는 요청 수가 아닙니다. 이 작은 예제의 테스트 빌드에서는 import를 옮기면 Overview 전달 CSS는 줄지만 Reports 조각은 늘어납니다. 더 좁은 소유 범위를 입증할 뿐 모든 라우트의 성능 향상을 뜻하지는 않습니다. 최적화로 받아들이기 전에 절충을 비교하세요. 작은 라우트 번들은 콜드 로드를 줄여도 다음 페이지가 새로 가져올 바이트를 늘릴 수 있습니다. 안정적인 에셋 공유는 재사용을 높이고 과도한 분할은 요청과 import 체인을 늘립니다. 페이지별로도, 전체 탐색 순서로도 비교하세요. 적절한 번들러나 통합이 올바른 URL, 조건, 레이어, 캐시 식별로 에셋을 조립하게 하세요. 파일 수를 줄이려고 임의의 CSS를 연결하지 마세요.
동작 검증을 통과한 후에만 실측 페이지별 상한을 프로젝트의 기존 검사에 저장하세요. 기능 페이지와 기능을 받지 않아야 하는 페이지를 모두 포함하세요. 모든 앱에 맞는 고정 KB 한도는 없습니다. 새 기능이 예산 변경을 정당화할 수 있지만 추가한 동작과 실측 비용을 검토에서 명시해야 합니다. 시간 표본은 결정론적인 바이트 상한과 구분하고 작은 시간 변동을 속도 향상 주장으로 바꾸지 마세요.
에이전트와 작업한다면 이 가이드, 대상 라우트, 기존 모드, 허용 범위를 제공하세요. 기준선, 소스 변경, 프로덕션 출력, 실제로 확인한 상태를 요청하세요. 매니페스트 검사나 깨끗한 lint 결과는 유용한 소스 근거이지만 빌드나 브라우저 검증을 대신하지는 않습니다.









