Към съдържанието
wannacode

От пясъчника към проект

Накратко: 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 react
cd shop
npm install
npm 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/ картинки, които кодът внася с import

index.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.jsx
import 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
Стъпка по стъпка, с думи
  1. Build-ът тръгва от index.html. Той зарежда main.jsx със <script type="module">, а там започва приложението.
  2. Vite следва всеки import, файл по файл: App, ProductCard, CSS-а, картинката. Файл, който никой не внася, не влиза.
  3. Целият JavaScript, заедно с библиотеката от node_modules, се събира в един файл и се свива: без интервали, с къси имена.
  4. Излизат обикновени файлове, които браузърът разбира: един .js, един .css и картинката. Всяко име носи хеш, сметнат от съдържанието.
  5. dist/index.html вече сочи новите имена. Цялата папка dist/ е сайтът: публикуваш нея, както е.
  6. Сменяш ProductCard и пускаш build пак. JavaScript-ът е друг, затова името му е ново. CSS-ът не е сменен и пази името си.
  7. Затова браузърът може да пази файл с хеш завинаги: новата версия винаги е с ново име. Посетителят тегли само каквото е сменено.

dist/ е готовият сайт: един HTML файл и папка assets/. Сървър не му трябва, само хостинг, който раздава файлове. Тя не е в git (шаблонът я слага в .gitignore): правиш я наново при всяко публикуване.

Идеята

TypeScript в компонентите

В пясъчника можеш да подадеш на ProductCard каквото и да е: product={42}. Грешката се вижда чак при рисуването. TypeScript казва какво приема компонентът и хваща грешката в редактора, преди да пуснеш нищо:

ProductCard.tsx
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 да започне.

Пробвай

Намери грешката

Редакторът подчертава един ред с червено. Кой?

Избери реда с грешката.

Идеята

Променливите на средата са публични

Адресът на API-то е различен на твоя компютър и на истинския сайт. Затова не се пише в кода, а във файл .env в корена на проекта:

Терминал
VITE_API_URL=https://api.shop.example
const 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 (урокът за тайните от „Сигурност за програмисти“).

Пробвай

Намери грешката

Функцията плаща поръчка. Кой ред пуска тайна в света?

Избери реда с грешката.

Идеята

Публикуване и адресите на рутера

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-а.

  1. npm run build
  2. cd shop
  3. npm run dev
  4. npm create vite@latest shop -- --template react
  5. npm install
  6. npm run preview

Тест

Провери се

1. Какво прави npm run build в проект с Vite?
2. Защо в имената в dist/assets/ има хеш (index-DSXtc1GT.js)?
3. Ключът за плащания е във VITE_PAYMENT_KEY в .env, а .env не е в git. Тайна ли е?
4. Сайтът е публикуван. Клиент отваря директно /product/7 и получава 404 от хостинга. Какво липсва?
5. Проектът е с TypeScript. vite build минава, а npm run build пада с грешка в тип. Защо?

Създаден от Иво Гергов като част от кампанията на Evo Tech за достъпно обучение по програмиране ·