Przejdź do treści

Temat 32 · 3 × 45 min

Moduły ES, npm, Vite i jakość kodu

Do tej pory twoje programy mieściły się w jednym pliku skrypt.js podpiętym do index.html. Przy liście zadań z serwerem (lekcja REST i CRUD) ten plik ma już 100 linii i miesza trzy rzeczy: rozmowę z API, rysowanie listy i obsługę formularza. Zawodowy kod dzieli się na moduły – osobne pliki, które jawnie mówią, co udostępniają i czego potrzebują. Wokół modułów wyrósł cały zestaw narzędzi: Node.js i npm (biblioteki), Vite (serwer deweloperski i budowanie), ESLint i Prettier (jakość i formatowanie), Git (historia zmian). W tej lekcji poznasz je w takiej kolejności, w jakiej używa się ich w prawdziwym projekcie.

Problem: kilka plików, jeden wspólny zasięg

Wydaje się, że wystarczy podzielić kod na pliki i podpiąć kilka <script src>. To działa, ale wszystkie klasyczne skrypty dzielą jeden zasięg globalny. Zmienna z a.js jest widoczna w b.js, a kolejność tagów decyduje, czy coś już istnieje.

Sprawdź się

Co się stanie po załadowaniu strony z tymi dwoma skryptami?

<!-- koszyk.js -->
let licznik = 0;
<!-- galeria.js -->
let licznik = 1;
console.log(licznik);

Dwa pliki napisane przez dwie osoby, obie użyły nazwy licznik – i aplikacja nie startuje. Moduły rozwiązują to u źródła: każdy plik ma własny zasięg, a na zewnątrz wychodzi tylko to, co wyraźnie wyeksportujesz.

Moduły ES: export i import

Moduł to zwykły plik .js, który używa export albo import. Są dwa rodzaje eksportu:

koszyk.js
// eksport nazwany – może być wiele w jednym pliku
export const VAT = 0.23;

export function policzBrutto(netto) {
  return Math.round(netto * (1 + VAT) * 100) / 100;
}

export function sumaKoszyka(produkty) {
  return produkty.reduce((suma, p) => suma + p.cena * p.ilosc, 0);
}

// to zostaje prywatne – nie ma export
function formatuj(kwota) {
  return kwota.toFixed(2) + ' zł';
}
Koszyk.js
// eksport domyślny – dokładnie jeden na plik, zwykle „główna rzecz” modułu
export default class Koszyk {
  #produkty = [];
  dodaj(produkt) { this.#produkty.push(produkt); }
  get produkty() { return [...this.#produkty]; }
}
main.js
import Koszyk from './Koszyk.js';                        // domyślny: dowolna nazwa, bez klamer
import { policzBrutto, sumaKoszyka } from './koszyk.js'; // nazwane: w klamrach, nazwy muszą się zgadzać
import { VAT as STAWKA_VAT } from './koszyk.js';         // zmiana nazwy przy imporcie
import * as narzedzia from './koszyk.js';                // wszystko jako obiekt: narzedzia.VAT

const koszyk = new Koszyk();
koszyk.dodaj({ nazwa: 'Kabel USB-C', cena: 29.99, ilosc: 2 });
console.log(policzBrutto(sumaKoszyka(koszyk.produkty)), STAWKA_VAT, narzedzia.VAT);

Zasady, które warto zapamiętać:

RodzajW module źródłowymPrzy imporcieUwagi
nazwanyexport function f() {} lub export { f, g }import { f, g } from './m.js'nazwa musi się zgadzać; zmiana przez as
domyślnyexport default …import Cokolwiek from './m.js'jeden na plik; nazwa dowolna
obaexport default X; export const yimport X, { y } from './m.js'domyślny zawsze pierwszy
wszystko–import * as m from './m.js'dostęp m.f, m.y

Ścieżka w from musi być ścieżką zaczynającą się od ./, ../ lub / (albo pełnym adresem URL) i w przeglądarce z rozszerzeniem .js. import { f } from 'koszyk' bez kropki przeglądarka odrzuci – taki „nagi” zapis rozumie bundler (zobaczysz go przy Vite jako import biblioteki z node_modules), a w samej przeglądarce działa tylko wtedy, gdy strona zdefiniuje mapę importów (<script type="importmap">, zobaczysz ją w lekcji 35).

Sprawdź się

Plik narzedzia.js zawiera export default function suma(a, b) { return a + b; }. Który import jest poprawny?

Podpięcie w HTML: type=“module”

index.html
<script type="module" src="main.js"></script>

Jeden tag wystarczy – przeglądarka sama pobierze Koszyk.js i koszyk.js, bo main.js je importuje. Moduł różni się od zwykłego skryptu w kilku miejscach:

  • własny zasięg – const x w module nie staje się window.x,
  • tryb ścisły ('use strict') automatycznie – literówka w nazwie zmiennej to ReferenceError, a nie cicha globalna,
  • defer domyślnie – wykonuje się po sparsowaniu HTML, więc elementy już istnieją,
  • wykonuje się raz – nawet jeśli pięć plików importuje ten sam moduł, jego kod uruchomi się jednokrotnie,
  • this na najwyższym poziomie to undefined, nie window.

Przetestuj własny zasięg bez żadnych plików – wystarczy wbudowany moduł:

Zasięg modułu vs skryptu
// to jest zwykły skrypt na końcu body
console.log('w skrypcie:', typeof zwykla, '/', typeof modulowa);
Podgląd

Zwróć uwagę na kolejność w konsoli: zwykły skrypt wypisał się przed modułem, choć stoi niżej w HTML – moduły są odroczone.

Dlaczego file:// nie działa

Otwórz index.html z type="module" dwuklikiem (adres file:///C:/…) i zobaczysz w konsoli:

Tekst
Access to script at 'file:///C:/projekt/main.js' from origin 'null' has been blocked by CORS policy

Moduły są pobierane jak zasoby sieciowe z kontrolą pochodzenia (CORS, lekcja 31), a protokół file:// nie ma „pochodzenia” – przeglądarka zgłasza origin 'null' i blokuje. Potrzebujesz serwera HTTP, dowolnego:

SposóbPolecenie / czynnośćAdres
VS Code, rozszerzenie Live Serverprawy klik na index.html → Open with Live Serverhttp://127.0.0.1:5500
Node bez instalacjinpx serve w folderze projektuhttp://localhost:3000
Vite (niżej)npm run devhttp://localhost:5173
XAMPPplik w htdocs/projekt/http://localhost/projekt/
Python (jeśli masz)python -m http.server 8000http://localhost:8000

Na egzaminie INF.03 nie używasz modułów właśnie dlatego: tam otwierasz plik bezpośrednio albo przez XAMPP, a jeden <script> wystarcza.

Node.js i npm

Node.js to środowisko uruchamiające JavaScript poza przeglądarką (silnik V8 z Chrome plus dostęp do plików i sieci). Potrzebujesz go nie po to, by pisać serwery (to lekcja 36), lecz dlatego, że wszystkie narzędzia front-endowe są napisane w JS i uruchamiane w Node. Razem z Node instaluje się npm – menedżer pakietów z rejestru npmjs.com (ponad 3 mln bibliotek).

Pobierz instalator LTS ze strony nodejs.org (jesienią 2026 r. wspierane linie LTS to 22 i 24, a od 28 października 2026 także 26) i sprawdź w terminalu:

Terminal
node -v     # np. v24.x.x albo v26.x.x
npm -v      # 11.x.x

package.json – dowód tożsamości projektu

Terminal
mkdir lista-zadan && cd lista-zadan
npm init -y                 # tworzy package.json z domyślnymi wartościami
npm install date-fns        # biblioteka do pracy z datami → dependencies
npm install -D eslint       # narzędzie tylko dla programisty → devDependencies
package.json
{
  "name": "lista-zadan",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "start": "node index.js",
    "lint": "eslint ."
  },
  "dependencies": {
    "date-fns": "^4.1.0"
  },
  "devDependencies": {
    "eslint": "^10.13.0"
  }
}

Co tu jest ważne:

  • scripts – nazwane polecenia. npm run lint uruchamia eslint ., przy czym npm sam znajdzie eslint w node_modules/.bin. npm start i npm test działają bez run.
  • dependencies – to, czego potrzebuje działająca aplikacja; devDependencies – narzędzia (linter, testy, bundler).
  • ^4.1.0 – „4.1.0 lub nowsza, ale poniżej 5.0.0”. Dokładne zainstalowane wersje zapisuje package-lock.json – dzięki niemu kolega po npm install dostanie identyczne biblioteki.
  • "type": "module" – pliki .js w tym projekcie są modułami ES także dla Node.
  • Folder node_modules bywa ogromny (setki MB). Nigdy nie wysyłasz go do Git ani kolegom – odtwarza go npm install z package.json.

npx nazwa uruchamia pakiet bez instalowania na stałe (tak startowałeś json-server). npm uninstall nazwa usuwa, npm outdated pokazuje, co można zaktualizować.

Sprawdź się

Kolega wysłał ci projekt bez folderu node_modules. Co robisz, żeby zadziałał?

Vite: serwer deweloperski i budowanie

Pisząc z modułami, chcesz: serwera HTTP (bo file:// nie działa), automatycznego przeładowania po zapisie, możliwości importowania bibliotek z node_modules „nagą” nazwą, importowania CSS i obrazów z JS, a na koniec – jednego małego pliku do wrzucenia na hosting. Wszystko to robi Vite (fr. „szybko”; aktualna wersja 8, wymaga Node 20.19+ lub 22.12+).

Terminal
npm create vite@latest lista-zadan -- --template vanilla
cd lista-zadan
npm install
npm run dev

Bez --template kreator zapyta interaktywnie o framework (Vanilla, Vue, React, Preact, Lit, Svelte, Solid, Qwik…) i wariant (JavaScript / TypeScript). vanilla to czysty JS – ten chcesz dziś; react przyda się w lekcji 35, vanilla-ts w 36.

Po npm run dev w terminalu pojawi się adres http://localhost:5173/. Struktura wygenerowanego projektu:

Tekst
lista-zadan/
├── index.html          ← punkt wejścia, ma <script type="module" src="/src/main.js">
├── src/
│   ├── main.js         ← importuje style.css, obrazki i counter.js
│   ├── counter.js      ← przykładowy moduł z eksportem nazwanym
│   ├── style.css
│   └── assets/         ← obrazki importowane z JS
├── public/             ← pliki kopiowane „jak są” (favicon, ikony)
├── package.json
└── node_modules/
src/main.js (fragment z szablonu)
import './style.css';                      // Vite pozwala importować CSS z JS
import { setupCounter } from './counter.js';

setupCounter(document.querySelector('#counter'));

Zapisz zmianę w src/counter.js – strona odświeży się sama (HMR, hot module replacement). Gdy aplikacja jest gotowa:

Terminal
npm run build      # → folder dist/ z jednym zminifikowanym JS i CSS
npm run preview    # podgląd tego, co jest w dist/, na http://localhost:4173

Zawartość dist/ wrzucasz na hosting (albo do htdocs w XAMPP) – to zwykłe pliki HTML/CSS/JS, działające bez Node. Vite na etapie build łączy wszystkie moduły w jeden plik (tzw. bundling), usuwa nieużywany kod i skraca nazwy – strona ładuje się szybciej niż z dziesięciu osobnych <script>.

ESLint: komputer czyta twój kod przed uruchomieniem

Linter analizuje kod bez uruchamiania i wskazuje błędy oraz podejrzane konstrukcje: niezadeklarowane zmienne, nieużywane importy, == zamiast ===, przypisanie w warunku. Połowę błędów z lekcji o pułapkach ESLint złapałby, zanim odświeżysz przeglądarkę. Aktualna wersja to ESLint 10 z konfiguracją w pliku eslint.config.js (tzw. flat config; stary format .eslintrc wersja 9 domyślnie ignorowała, a wersja 10 usunęła całkowicie).

Terminal
npm init @eslint/config@latest

Kreator zada kilka pytań: jak chcesz używać ESLint (sprawdzanie składni / szukanie problemów), jaki rodzaj modułów (ESM), framework (none), TypeScript (no), gdzie działa kod (Browser). Na końcu zainstaluje eslint, @eslint/js i globals i utworzy plik:

eslint.config.js
import { defineConfig } from 'eslint/config';
import globals from 'globals';
import js from '@eslint/js';

export default defineConfig([
  { files: ['**/*.js'], languageOptions: { globals: globals.browser } },
  { files: ['**/*.js'], plugins: { js }, extends: ['js/recommended'] },
  {
    rules: {
      eqeqeq: 'error',            // tylko === i !==
      'no-unused-vars': 'warn',   // ostrzeżenie zamiast błędu
      'prefer-const': 'error',    // let, którego nie zmieniasz → const
    },
  },
]);

globals.browser mówi ESLintowi, że document, window, fetch istnieją – bez tego każde document.getElementById byłoby „niezadeklarowaną zmienną”. js/recommended włącza około 60 reguł wyłapujących realne błędy. Trzy dopisane reguły to te, które najczęściej dotyczą uczniów.

Terminal
npx eslint .            # sprawdź wszystkie pliki
npx eslint . --fix      # napraw to, co da się naprawić automatycznie (np. let → const)
Sprawdź się

Ile problemów zgłosi ESLint z konfiguracją wyżej (js/recommended + eqeqeq + no-unused-vars + prefer-const) dla tego kodu?

let cena = 100;
const rabat = 10;
function policz(kwota, procent) {
if (kwota == 0) return 0;
return kwota - kwota * procnt / 100;
}
console.log(policz(cena));

W VS Code zainstaluj rozszerzenie ESLint – podkreśli problemy na czerwono w trakcie pisania, bez uruchamiania terminala.

Prettier: koniec dyskusji o formatowaniu

Prettier nie szuka błędów – przepisuje kod w jednolitym stylu: wcięcia, cudzysłowy, przecinki, długość linii. Dwie osoby w zespole zapisują plik i wygląda identycznie, a code review dotyczy logiki, nie spacji.

Terminal
npm install -D --save-exact prettier
node --eval "fs.writeFileSync('.prettierrc','{}\n')"      # pusta konfiguracja = domyślne ustawienia
npx prettier . --write                                      # sformatuj wszystko
npx prettier . --check                                      # tylko sprawdź (przydatne w CI)

Typowe ustawienia w .prettierrc, jeśli chcesz coś zmienić:

.prettierrc
{
  "singleQuote": true,
  "semi": true,
  "printWidth": 100
}

Dwie rzeczy do zapamiętania:

  • ESLint i Prettier mają się nie kłócić. Zainstaluj eslint-config-prettier i dodaj go na końcu eslint.config.js – wyłącza reguły ESLint dotyczące formatowania. ESLint pilnuje błędów, Prettier wyglądu.
  • W VS Code: rozszerzenie Prettier – Code formatter + ustawienie Format On Save. Od tej chwili formatowanie dzieje się przy Ctrl+S i nie myślisz o nim.
Terminal
npm install -D eslint-config-prettier
eslint.config.js (dodatek)
import prettier from 'eslint-config-prettier';
// … na końcu tablicy defineConfig:
//   prettier,

Git: historia projektu

Git zapisuje migawki (commity) projektu. Dzięki niemu cofasz nieudaną zmianę, widzisz, kto i kiedy zmienił linię, i pracujesz w zespole bez wysyłania projekt_final_v3_poprawiony.zip. Pobierz z git-scm.com i skonfiguruj raz:

Terminal
git config --global user.name "Imię Nazwisko"
git config --global user.email "ty@example.com"
git config --global init.defaultBranch main   # nowe repozytoria zaczynają od gałęzi main, nie master

Zanim zrobisz pierwszy commit, powiedz Gitowi, czego nie śledzić. Szablon Vite tworzy już .gitignore; w projekcie z npm init dopisz sam:

.gitignore
node_modules/
dist/
.env

Codzienny cykl pracy to pięć poleceń:

Terminal
git init                              # raz: zacznij śledzić folder
git status                            # co się zmieniło?
git add .                             # zaznacz zmiany do zapisania („do poczekalni”)
git commit -m "Dodaj formularz zadania z walidacją"   # zapisz migawkę z opisem
git log --oneline                     # historia: skrót + opis każdego commita

Komunikat commita pisz w trybie rozkazującym i konkretnie: „Napraw podwójne wysyłanie formularza”, nie „zmiany” ani „asdf”. Jeden commit = jedna logiczna zmiana. Gdy coś zepsujesz:

Terminal
git diff                              # co zmieniłem od ostatniego commita?
git restore app.js                    # cofnij plik do ostatniego commita
git checkout -b eksperyment           # nowa gałąź: testuj bez psucia main
git switch main                       # wróć na główną gałąź

Żeby mieć kopię w chmurze i pracować w zespole, załóż pusty projekt na GitHubie i połącz:

Terminal
git remote add origin https://github.com/twoj-login/lista-zadan.git
git branch -M main                    # upewnij się, że gałąź nazywa się main (domyślnie bywa master)
git push -u origin main               # wyślij; kolejne razy wystarczy git push

Kolega pobiera całość przez git clone <adres>, potem npm install – i ma działający projekt bez node_modules w paczce.

Pułapki

Ściąga

Składnia / polecenieCo robiPrzykład
export const x / export function feksport nazwanyimport { x, f } from './m.js'
export default Xeksport domyślny (jeden na plik)import Dowolna from './m.js'
import * as m from './m.js'cały moduł jako obiektm.f()
import { a as b }zmiana nazwyimport { VAT as STAWKA }
<script type="module" src="main.js">uruchom jako modułwłasny zasięg, strict, defer
npm init -ynowy package.jsonw pustym folderze
npm install nazwa / -D nazwazależność / narzędzienpm i -D vitest
npm installodtwórz node_modulespo git clone
npm run skryptpolecenie ze scriptsnpm run dev
npx nazwauruchom pakiet bez instalacjinpx json-server db.json
npm create vite@latest app -- --template vanillanowy projekt Vitecd app && npm i && npm run dev
npm run build / previewzbuduj dist/ / podejrzyjhosting dostaje dist/
npm init @eslint/config@latestutwórz eslint.config.jspotem npx eslint .
npx eslint . --fixsprawdź i naprawlet → const
npx prettier . --writesformatuj projekt--check tylko sprawdza
git init / add . / commit -m "…"śledź / zaznacz / zapiszgit log --oneline
git restore plikcofnij plikdo ostatniego commita
.gitignorepliki poza Gitnode_modules/, dist/

Zadania

Zadanie 1
łatwe

Kalkulator rat w trzech modułach

Masz kalkulator rat z lekcji 14 w jednym pliku. Podziel go na moduły:

  • obliczenia.js – eksport nazwany obliczRate(kwota, liczbaRat) (zaokrąglenie do 2 miejsc) i formatujZl(kwota) (np. "125,50 zł"),
  • ui.js – eksport domyślny: funkcja podlaczFormularz(form, wynik), która czyta pola kwota i raty, liczy i wypisuje Rata: … zł w elemencie wynik,
  • main.js – importuje i spina z HTML.

Uruchom przez Live Server lub npx serve. Sprawdź w konsoli, że typeof obliczRate na najwyższym poziomie (w konsoli DevTools) daje "undefined" – funkcja nie wyciekła do globalnego zasięgu.

Pokaż rozwiązanie
obliczenia.js
export function obliczRate(kwota, liczbaRat) {
  if (liczbaRat <= 0) return 0;              // zabezpieczenie przed dzieleniem przez zero
  return Math.round((kwota / liczbaRat) * 100) / 100;
}

export function formatujZl(kwota) {
  return kwota.toFixed(2).replace('.', ',') + ' zł';
}
ui.js
import { obliczRate, formatujZl } from './obliczenia.js';

export default function podlaczFormularz(form, wynik) {
  form.addEventListener('submit', (e) => {
    e.preventDefault();
    const kwota = parseFloat(form.elements.kwota.value);
    const raty = parseInt(form.elements.raty.value, 10);
    if (Number.isNaN(kwota) || Number.isNaN(raty) || raty <= 0) {
      wynik.textContent = 'Wprowadź poprawne dane.';
      return;
    }
    wynik.textContent = `Rata: ${formatujZl(obliczRate(kwota, raty))}`;
  });
}
main.js
import podlaczFormularz from './ui.js';

podlaczFormularz(document.getElementById('form-raty'), document.getElementById('wynik'));
index.html
<!doctype html>
<html lang="pl">
<head><meta charset="utf-8"><title>Raty</title></head>
<body>
  <form id="form-raty">
    <input name="kwota" type="number" step="0.01" placeholder="Kwota" required>
    <input name="raty" type="number" min="1" placeholder="Liczba rat" required>
    <button>Oblicz</button>
  </form>
  <p id="wynik"></p>
  <script type="module" src="main.js"></script>
</body>
</html>

W konsoli DevTools typeof obliczRate zwraca "undefined", bo funkcja żyje tylko w zasięgu modułu obliczenia.js i tam, gdzie została zaimportowana.

Zadanie 2
średnie

Lista zadań w Vite

Utwórz projekt npm create vite@latest lista-zadan -- --template vanilla. Usuń przykładowy licznik i zbuduj listę zadań z lekcji 16 w trzech modułach: stan.js (tablica zadań + funkcje dodaj, przelacz, usun – eksporty nazwane), widok.js (eksport domyślny rysuj(lista, zadania)), main.js (formularz, delegacja zdarzeń, import ./style.css). Następnie npm run build i otwórz dist/index.html przez npm run preview. Zajrzyj do dist/assets/ – ile plików JS powstało z trzech modułów?

Pokaż rozwiązanie
stan.js
let zadania = [];
let kolejneId = 1;

export function dodaj(tytul) {
  zadania.push({ id: kolejneId++, tytul, wykonane: false });
  return zadania;
}

export function przelacz(id) {
  const zadanie = zadania.find((z) => z.id === id);
  if (zadanie) zadanie.wykonane = !zadanie.wykonane;
  return zadania;
}

export function usun(id) {
  zadania = zadania.filter((z) => z.id !== id);
  return zadania;
}

export function wszystkie() {
  return zadania;
}
widok.js
export default function rysuj(lista, zadania) {
  lista.innerHTML = '';
  for (const z of zadania) {
    const li = document.createElement('li');
    li.className = z.wykonane ? 'wykonane' : '';
    li.innerHTML = `
      <input type="checkbox" data-akcja="przelacz" data-id="${z.id}" ${z.wykonane ? 'checked' : ''}>
      <span>${z.tytul}</span>
      <button data-akcja="usun" data-id="${z.id}">Usuń</button>`;
    lista.append(li);
  }
}
main.js
import './style.css';
import { dodaj, przelacz, usun, wszystkie } from './stan.js';
import rysuj from './widok.js';

document.querySelector('#app').innerHTML = `
  <form id="form-zadanie">
    <input name="tytul" placeholder="Nowe zadanie" required>
    <button>Dodaj</button>
  </form>
  <ul id="lista"></ul>`;

const form = document.getElementById('form-zadanie');
const lista = document.getElementById('lista');

form.addEventListener('submit', (e) => {
  e.preventDefault();
  rysuj(lista, dodaj(form.elements.tytul.value.trim()));
  form.reset();
});

lista.addEventListener('click', (e) => {
  const { akcja, id } = e.target.dataset;
  if (akcja === 'przelacz') rysuj(lista, przelacz(Number(id)));
  if (akcja === 'usun') rysuj(lista, usun(Number(id)));
});

rysuj(lista, wszystkie());
style.css
body { font-family: system-ui, sans-serif; max-width: 480px; margin: 40px auto; }
.wykonane span { text-decoration: line-through; color: #888; }

Po npm run build w dist/assets/ jest jeden plik index-<hash>.js (i jeden CSS) – Vite połączył trzy moduły w jeden i zminifikował. Hash w nazwie zmienia się przy każdej zmianie kodu, dzięki czemu przeglądarki użytkowników nie trzymają starej wersji w pamięci podręcznej.

Zadanie 3
średnie

Napraw to, co zgłasza ESLint

W projekcie z zadania 2 uruchom npm init @eslint/config@latest (ESM, brak frameworka, bez TypeScript, Browser) i dopisz reguły eqeqeq, prefer-const, no-unused-vars jak w lekcji. Następnie wklej do pliku zepsute.js poniższy kod, uruchom npx eslint zepsute.js i napraw wszystkie zgłoszenia bez używania eslint-disable. Zapisz, ile ich było i jakie reguły.

zepsute.js
import { dodaj, usun, przelacz } from './stan.js';

var licznik = 0;
let nazwa = 'Lista';

function pokaz(zadania) {
  for (var i = 0; i < zadania.length; i++) {
    if (zadania[i].wykonane == true) {
      licznik++;
    }
  }
  console.log(nazwa + ': ' + liczik + ' wykonanych');
}

export function dodajIpokaz(tytul) {
  const zadania = dodaj(tytul);
  pokaz(zadania);
  return zadania;
}
Pokaż rozwiązanie

ESLint zgłosi 6 problemów: usun i przelacz nieużywane (no-unused-vars ×2), licznik tylko zwiększany, a nigdy nieodczytany – bo w console.log jest literówka (no-unused-vars), nazwa nigdy nie zmieniana (prefer-const), == true (eqeqeq), liczik niezdefiniowane (no-undef). Dwa var przejdą bez uwag, bo reguła no-var nie jest w recommended – dopisz 'no-var': 'error' do rules i zgłoszeń będzie 8. Naprawiona wersja:

zepsute.js (po naprawie)
import { dodaj } from './stan.js';

let licznik = 0;
const nazwa = 'Lista';

function pokaz(zadania) {
  for (const zadanie of zadania) {
    if (zadanie.wykonane === true) {
      licznik++;
    }
  }
  console.log(`${nazwa}: ${licznik} wykonanych`);
}

export function dodajIpokaz(tytul) {
  const zadania = dodaj(tytul);
  pokaz(zadania);
  return zadania;
}

Zwróć uwagę, że literówkę liczik linter wykrył bez uruchamiania kodu – w przeglądarce dowiedziałbyś się o niej dopiero po kliknięciu, jako ReferenceError. Po npx eslint zepsute.js --fix ESLint sam zamieni let nazwa na const, ale literówki i == musisz poprawić ręcznie (zamiana == na === mogłaby zmienić działanie programu, więc --fix tego nie robi).

Zadanie 4
trudne

Pełny przepływ: Vite + ESLint + Prettier + Git

Zbuduj od zera projekt „Dziennik ocen” i doprowadź go do stanu „gotowy do pracy w zespole”:

  1. npm create vite@latest dziennik -- --template vanilla, moduły: oceny.js (dodajOcene, srednia, najlepszyPrzedmiot – eksporty nazwane, czysta logika bez DOM), widok.js, main.js.
  2. ESLint z js/recommended, eqeqeq, prefer-const, no-var + Prettier z singleQuote: true + eslint-config-prettier. W package.json dodaj skrypty lint, lint:fix, format.
  3. .gitignore z node_modules/ i dist/, git init, trzy sensowne commity: szkielet projektu, logika ocen, widok.
  4. npm run build – sprawdź, że dist/ nie trafił do Git (git status po buildzie ma być czysty).
  5. Wyślij na GitHub i podaj koledze adres – ma uruchomić projekt w trzech poleceniach.
Pokaż rozwiązanie
oceny.js
// czysta logika – zero DOM, łatwa do przetestowania w lekcji 33
export function dodajOcene(oceny, przedmiot, wartosc) {
  if (wartosc < 1 || wartosc > 6) throw new Error('Ocena musi być z zakresu 1–6');
  return [...oceny, { przedmiot, wartosc }];
}

export function srednia(oceny) {
  if (oceny.length === 0) return 0;
  const suma = oceny.reduce((s, o) => s + o.wartosc, 0);
  return Math.round((suma / oceny.length) * 100) / 100;
}

export function najlepszyPrzedmiot(oceny) {
  const sumy = {};
  for (const { przedmiot, wartosc } of oceny) {
    sumy[przedmiot] ??= { suma: 0, ile: 0 };
    sumy[przedmiot].suma += wartosc;
    sumy[przedmiot].ile++;
  }
  let najlepszy = null;
  let maks = -Infinity;
  for (const [przedmiot, { suma, ile }] of Object.entries(sumy)) {
    if (suma / ile > maks) {
      maks = suma / ile;
      najlepszy = przedmiot;
    }
  }
  return najlepszy;
}
package.json (fragment scripts)
{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "lint": "eslint .",
    "lint:fix": "eslint . --fix",
    "format": "prettier . --write"
  }
}
eslint.config.js
import { defineConfig } from 'eslint/config';
import globals from 'globals';
import js from '@eslint/js';
import prettier from 'eslint-config-prettier';

export default defineConfig([
  { files: ['**/*.js'], languageOptions: { globals: globals.browser } },
  { files: ['**/*.js'], plugins: { js }, extends: ['js/recommended'] },
  {
    rules: {
      eqeqeq: 'error',
      'prefer-const': 'error',
      'no-var': 'error',
    },
  },
  prettier, // na końcu – wyłącza reguły formatowania, bo tym zajmuje się Prettier
]);
Terminal
# instalacja narzędzi
npm init @eslint/config@latest
npm install -D --save-exact prettier
npm install -D eslint-config-prettier
node --eval "fs.writeFileSync('.prettierrc','{ \"singleQuote\": true }\n')"

# historia
git init
git add .
git commit -m "Szkielet projektu Vite z ESLint i Prettier"
# … piszesz oceny.js …
git add oceny.js
git commit -m "Dodaj logikę ocen: dodawanie, średnia, najlepszy przedmiot"
# … piszesz widok.js i main.js …
git add .
git commit -m "Dodaj widok listy ocen i formularz"

npm run build
git status        # „nothing to commit, working tree clean” – dist/ jest ignorowany

git remote add origin https://github.com/twoj-login/dziennik.git
git branch -M main
git push -u origin main

Kolega: git clone https://github.com/twoj-login/dziennik.git, cd dziennik && npm install, npm run dev. Trzy polecenia, działający projekt, identyczne wersje bibliotek dzięki package-lock.json.

Test powtórkowy

Test powtórkowy

Dziewięć pytań o moduły i narzędzia. Jedna poprawna odpowiedź.

0/0poprawnych
Sprawdź się

Plik koszyk.js zawiera export function suma() {} oraz export const VAT = 0.23. Który import jest poprawny?

Sprawdź się

Które zdanie o skrypcie z atrybutem type="module" jest prawdziwe?

Sprawdź się

Dlaczego otwarcie index.html dwuklikiem nie ładuje modułów ES?

Sprawdź się

Gdzie w package.json powinien trafić ESLint?

Sprawdź się

Które polecenie tworzy nowy projekt w Vite z czystym JavaScriptem?

Sprawdź się

Co robi polecenie npm run build w projekcie Vite?

Sprawdź się

Którą regułę ESLint złamie ten kod?

const cena = '10';
if (cena == 10) console.log('równe');
Sprawdź się

Jaka jest różnica między ESLint a Prettier?

Sprawdź się

Które polecenia w tej kolejności zapisują zmiany w historii Git?