От пясъчника към проект
Накратко: Vite прави от компонентите истински проект: npm create vite създава папките, npm run build събира dist/ за публикуване, а VITE_ променливите на средата влизат в кода на сайта и са публични.
Проблем
Работи в пясъчника, не работи в браузъра
Магазинът ти работи в пясъчника. Копираш файловете в папка, пускаш я на сървър и отваряш страницата. Тя е празна, а конзолата казва:
Uncaught SyntaxError: Unexpected token '<'Браузърът разбира JavaScript, а <App /> е JSX. Дори без JSX следва друга грешка: import { createRoot } from 'react-dom/client' не е път до файл и браузърът не знае къде е react-dom.
Пясъчникът в уроците е вършил тази работа вместо теб. Той превежда JSX на JavaScript в страницата на урока и знае откъде да вземе библиотеката. В истински проект това прави инструмент.
Идеята
Vite: сървър за разработка и build
Vite е инструментът, с който се правят повечето нови проекти на React и Vue. Празен проект се прави с една команда:
npm create vite@latest shop -- --template reactcd shopnpm installnpm run dev--template избира шаблона: react или react-ts (с TypeScript). Двете тирета -- преди него казват на npm, че флагът е за create-vite, а не за самия npm. npm install сваля библиотеките в папка node_modules.
Vite има две работи, а package.json ги дава като скриптове:
npm run devпуска сървър за разработка наhttp://localhost:5173. Той превежда всеки файл, когато браузърът го поиска. Запишеш ли файл, страницата се обновява веднага.npm run buildправи готовия сайт в папкаdist/. Това е build: всичко, преведено и събрано в обикновени файлове.npm run previewпускаdist/наhttp://localhost:4173, за да видиш build-а, преди да го публикуваш.
Идеята
Папките и правилата
Шаблонът дава малко. Ето същият магазин, когато порасне:
shop/ index.html входът: <script type="module" src="/src/main.jsx"> package.json библиотеките и скриптовете dev, build, preview vite.config.js настройките на Vite (plugin-ът за React) public/ файлове, които се копират както са (favicon.svg) src/ main.jsx createRoot(…).render(<App />) App.jsx components/ ProductCard.jsx, CartSummary.jsx pages/ CatalogPage.jsx, ProductPage.jsx (за рутера) context/ CartContext.jsx (общото състояние) api/ products.js: функциите с fetch assets/ картинки, които кодът внася с importindex.html, public/ и src/main са на Vite. Другите папки са договорка в екипа, а не правило: Vite не ги иска. Договорката помага да намериш нещо, без да го търсиш. Тестовете от предишния урок стоят до компонента: ProductCard.test.<Only when="react">jsx</Only><Only when="vue">js</Only>.
Линтер и форматиране. Линтерът чете кода и хваща грешки, преди да го пуснеш: hook в if, забравена променлива. Форматирането (например с Prettier) подрежда интервалите и кавичките еднакво за целия екип, за да не се спори за тях в преглед на кода. Шаблонът на React идва с линтер: npm run lint пуска oxlint с правилото за hooks.
Виж го
Какво прави build
Как няколко файла с JSX и import стават сайт, който браузърът разбира.
import { createRoot } from 'react-dom/client';import App from './App.jsx';import './index.css';
createRoot(document.getElementById('root')).render(<App />);
// App.jsximport ProductCard from './components/ProductCard.jsx';import hero from './assets/hero.png';
// npm run build → dist/// dist/index.html// dist/assets/index-[hash].js// dist/assets/index-[hash].css// dist/assets/hero-[hash].pngСтъпка по стъпка, с думи
- Build-ът тръгва от index.html. Той зарежда main.jsx със <script type="module">, а там започва приложението.
- Vite следва всеки import, файл по файл: App, ProductCard, CSS-а, картинката. Файл, който никой не внася, не влиза.
- Целият JavaScript, заедно с библиотеката от node_modules, се събира в един файл и се свива: без интервали, с къси имена.
- Излизат обикновени файлове, които браузърът разбира: един .js, един .css и картинката. Всяко име носи хеш, сметнат от съдържанието.
- dist/index.html вече сочи новите имена. Цялата папка dist/ е сайтът: публикуваш нея, както е.
- Сменяш ProductCard и пускаш build пак. JavaScript-ът е друг, затова името му е ново. CSS-ът не е сменен и пази името си.
- Затова браузърът може да пази файл с хеш завинаги: новата версия винаги е с ново име. Посетителят тегли само каквото е сменено.
dist/ е готовият сайт: един HTML файл и папка assets/. Сървър не му трябва, само хостинг, който раздава файлове. Тя не е в git (шаблонът я слага в .gitignore): правиш я наново при всяко публикуване.
Идеята
TypeScript в компонентите
В пясъчника можеш да подадеш на ProductCard каквото и да е: product={42}. Грешката се вижда чак при рисуването. TypeScript казва какво приема компонентът и хваща грешката в редактора, преди да пуснеш нищо:
type Product = { id: number; name: string; price: number };
type Props = { product: Product; onAdd: (product: Product) => void;};
export default function ProductCard({ product, onAdd }: Props) { // …}Файлът е .tsx. Ако App забрави onAdd, редакторът казва Property 'onAdd' is missing in type … but required in type 'Props'.
Едно нещо изненадва: Vite само маха типовете, без да ги проверява. Така е бърз. Проверката е отделна: в шаблона с TypeScript npm run build е tsc -b && vite build. Ако типовете не са наред, build-ът спира, преди Vite да започне.
Пробвай
Намери грешката
Редакторът подчертава един ред с червено. Кой?
Избери реда с грешката.
Ред 12: product.prise. Типът Product няма такова поле, а TypeScript казва Property 'prise' does not exist on type 'Product'. Did you mean 'price'?. Без TypeScript product.prise е undefined и цената излиза грешна чак на екрана.
Идеята
Променливите на средата са публични
Адресът на API-то е различен на твоя компютър и на истинския сайт. Затова не се пише в кода, а във файл .env в корена на проекта:
VITE_API_URL=https://api.shop.exampleconst API_URL = import.meta.env.VITE_API_URL;Vite дава на кода само променливите, които започват с VITE_. При npm run build той вписва стойността в JavaScript-а: в dist/assets/index-….js пише https://api.shop.example като обикновен текст. Всеки, който отвори сайта, може да го прочете в инструментите на браузъра.
Затова в VITE_ променлива никога не слагаш тайна: ключ за плащания, парола за базата, ключ за API, който струва пари. Няма значение, че .env не е в git. Тайната е в бъндъла. Тайните живеят на сървъра, а страницата праща заявка към твоето API (урокът за тайните от „Сигурност за програмисти“).
Пробвай
Намери грешката
Функцията плаща поръчка. Кой ред пуска тайна в света?
Избери реда с грешката.
Ред 2: ключът за плащания е във VITE_ променлива, значи е в JavaScript-а на сайта. Всеки може да го вземе и да плаща от твое име. Страницата праща orderId на твоето API, а сървърът вика плащанията с ключа, който само той знае.
Идеята
Публикуване и адресите на рутера
dist/ е само файлове, затова става всеки хостинг за статични сайтове. Публикуването е: npm run build и копиране на dist/. Много хостинги могат да го правят сами при всеки push в git.
Остава една подробност от урока за рутирането. Истинският сайт ползва BrowserRouter, а адресите са като /product/7. Клиент отваря такъв адрес директно (от връзка в имейл или с презареждане). Браузърът иска от хостинга /product/7, а в dist/ такъв файл няма: само index.html и assets/. Хостингът казва 404.
Решението е правило на хостинга: всеки адрес, който не е файл, връща index.html. Приложението тръгва, а рутерът чете адреса и рисува ProductPage. Това се казва SPA fallback. Всеки хостинг го пише по своему, например в nginx:
location / { try_files $uri $uri/ /index.html;}npm run dev и npm run preview го правят сами, затова грешката се вижда чак на истинския хостинг. Провери го веднага след първото публикуване: отвори директно адрес на продукт.
Идеята
Следващата стъпка: Next.js и Nuxt
Всичко в курса е приложение в браузъра: сървърът дава празен index.html и JavaScript, а страницата се рисува при клиента. Докато JavaScript-ът не дойде и не тръгне, клиентът не вижда нищо.
Рендерирането на сървъра (server-side rendering, SSR) рисува първия HTML на сървъра. Страницата се вижда по-бързо, а търсачките я четат без JavaScript. После същите компоненти се събуждат в браузъра и стават интерактивни. Това правят рамките над библиотеките: Next.js. Те добавят и рутиране по файлове, и код, който върви само на сървъра (там може да има тайни).
Компонентите, props, състоянието и ефектите са същите. Затова този курс е основата, а рамката е следващият урок, който ще учиш сам.
По-точно, за любопитните
Сървърът за разработка не събира файловете. При npm run dev браузърът зарежда всеки модул поотделно, а Vite го превежда при поискване. Затова тръгва за миг дори при голям проект. Събирането в един файл е само при build.
Малките картинки се вграждат. Картинка под 4 KB, внесена с import, не става файл: Vite я слага в JavaScript-а като data: адрес (настройката е build.assetsInlineLimit). Файловете от public/ никога не се пипат и не получават хеш, затова ги викаш с пълния път: /favicon.svg.
.env файловете. .env.local е само за твоя компютър (шаблонът го пази извън git с *.local). .env.production важи при build, а .env.development при npm run dev. import.meta.env.DEV и import.meta.env.PROD казват в кой режим е кодът. Всички стойности са текст: VITE_PAGE_SIZE=20 дава '20', а не 20.
Сайт в подпапка. Ако сайтът е на https://example.com/shop/, Vite трябва да го знае: base: '/shop/' във vite.config.js. Иначе index.html сочи /assets/… в корена и файловете не се намират.
npm run preview не е за истинския сайт. Той е за проверка на build-а при теб. Истинският сайт се дава от хостинг или от сървър като nginx.
StrictMode в main.jsx. Шаблонът обвива <App /> в <StrictMode>. При разработка той рисува всеки компонент два пъти, а ефектите пуска, почиства и пуска пак, за да изкара наяве ефекти без почистване. В build-а това го няма.
Упражнение
От празна папка до build
Подреди командите: нов проект, библиотеките, сървърът за разработка, build и проверката на build-а.
npm run buildcd shopnpm run devnpm create vite@latest shop -- --template reactnpm installnpm run preview
Тест
Провери се
Свързани уроци
Раздел „Свързани уроци“- Нужно отпреди: Данни от API в компонент · Общо състояние · Рутиране · Тестове на компоненти
- Продължава в: Витрината на магазина
- Задачи по темата: Цяло приложение