Style z bundlerem - Webpack cz. 3

Po konfiguracji prostego SPA za pomocą Webpacka przechodzimy do stylowania z wykorzystaniem tego bundlera. Zobacz, jak sonfigurować projekt, tak by css i sass budował nam się w locie.

Ostatnio skonfigurowaliśmy aplikację frontową zgodnie z istotą Single Page Application. Zrobiliśmy to za pomocą Webpacka. Dziś pójdziemy o krok dalej i tym razem zintegrujemy popularne rozwiązania do stylowania w projekcie, tak by pracowało się lepiej. Przebrniemy przez konfigurację tych rozwiązań, ucząc się działania powyższego bundlera. Jest to ścisła kontynuacja ostatniego wpisu, więc jeżeli go jeszcze nie przeczytałeś, stanowczo nalegam!

cssy z Webpackiem

Jeśli korzystałeś kiedykolwiek z Create-React-App, to z pewnością natknąłeś się na zintegrowanego już Sassa. Dziś zrobimy to samo. Z tą różnicą, że zupełnie sami, a pomoże nam w tym jedynie dostępna w Internecie dokumentacja.

Zacznijmy jednak od czystego CSSa. Zainstalujmy na początek dwa nowe loadery:

npm install -D style-loader css-loader 

Za pomocą style-loader możemy określić, w jaki sposób Webpack będzie wstrzykiwał nam style do projektu. css-loader natomiast ogarnia nam cssową składnię, taką jak @import, czy też url(), tak by zapewnić działanie na starszych przeglądarkach. Do configu webpacka wrzućmy nowy obiekt to tablicy rules:

// webpack.config.js
// ..
module: {
     // ..
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
// ..

Mam nadzieję, że wiesz, co tutaj się zadziało. Jeżeli nie, koniecznie przeczytaj ostatni wpis o Webpacku! Stwórzmy plik index.css oraz zaimportujmy go w index.tsx dokładnie, tak jak to robimy z plikami javascriptowymi:

/* src/index.css */

body {
    background-color: black;
}
// src/index.tsx
// ...
import './index.css';
// ...

I gotowe! Możesz już używać CSSa w projekcie. To rozwiązanie jest jednak dalekie od ideału. Jeżeli zbudujesz projekt, to możesz podejrzeć, w jaki sposób twoje style są wstrzykiwanie. Domyślnie będą one widniały w aplikacji w postaci tagu style w HTML, co jest nie zawsze (a powiedziałbym nawet, że rzadko), pożądanym rozwiązaniem. W opcjach style-loader możemy, co prawda, zmienić sposób wstrzykiwania CSS, ale docelowo chcemy, by nasz css budował się w osobnych, scachowanych plikach. W tym celu użyjemy następujący plugin:

npm install -D mini-css-extract-plugin

Z configu usuwamy style-loader, którego zastąpi nam loader z pluginu:

// webpack.config.js
// ...
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
// ...
module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
      {
        test: /\.css$/i,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
    ],
  },
// ...
  plugins: [
    new MiniCssExtractPlugin(),
    new HtmlWebpackPlugin({
      template: 'src/index.html',
    }),
  ],
// ...

Tym sposobem generujemy osobny bundle dla css w postaci jednego zresolvowanego pliku. Śmiało. Przetestuj!

Sass z Webpackiem

No dobrze. Mamy już ogarniętego cssa, to teraz ogarnijmy scssa. W tym celu potrzebujemy sass-loader:

npm install -D sass-loader

I oczywiście dodamy go do configu. Dodaj go u siebie bez mojej pomocy. Na pewno już potrafisz! No dobrą sprawę to wszystko, czego potrzebujemy w kontekście używania plików scss w projekcie. Musimy jeszcze zmienić wyrażenie regularne do sprawdzanie plików z podanym rozszerzeniem na następujące:

// webpack.config.js
test: /\.(sa|sc|c)ss$/

Znajdzie to wszystkie pliki z rozszerzeniami, takimi jak: sass, scss, css. Możemy je używać nawet zamiennie. Mamy już to co chcieliśmy. Ale wspomnijmy jeszcze o jednej fajnej rzeczy.

CSS Modules

Być może używałeś kiedyś CSS Modules. Nie jest to żadna biblioteka, a bardziej wynalezione rozwiązanie do stylowania w JS,  za pomocą którego można na przykład warunkowo generować dane style. Jest to oczywiście rozwiązanie, które umożliwia nam Webpack. CSS Modules polega na tym, że importujemy style o podanych klasach do pliku JS w postaci zmiennych i możemy zrobić z nimi co tylko zechcemy. Aby skonfigurować proste, własne moduły css, musimy - a jakże - pobawić się w konfigurację. Tym razem skorzystamy z loadera, którego już posiadany, czyli css-loader:

// webpack.config.js
// ...
use: [
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            options: {
              esModule: true,
              modules: {
                namedExport: true,
                localIdentName: 'foo__[name]__[local]',
              },
            },
          },
// ...

Sam ten loader udostępnia nam możliwość importu danych klas jako moduły i można to osiągnąć w powyższy sposób. Zanim jednak zaczniemy to robić, musimy zadbać jeszcze o jedną rzecz. Pamiętasz, że w projekcie używamy zasadniczo TypeScripta? Aby zapobiec niechcianym skargom edytora, zadeklarujmy moduły o podanych rozszerzeniach:

// declaration.d.ts
declare module '*.scss';
declare module '*.sass';
declare module '*.css';

Teraz edytor będzie akceptował wszystkie te pliki podczas importów, takich jak:

// app.tsx
// ...
import { fancy } from './index.module.scss';

const App: FC = () => {
  return (
    <BrowserRouter>
      <Routes>
        <Route
          path='/'
          element={
            <NavLink to='/about' className={fancy}>
              Navigate
            </NavLink>
          }
// ...

Końcowa zawartość pliku webpack.config.js:

// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const path = require('path');

module.exports = {
  entry: './src/index.tsx',
  output: {
    path: path.resolve(__dirname, 'build'),
    filename: 'index.bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
      {
        test: /\.(sa|sc|c)ss$/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            options: {
              esModule: true,
              modules: {
                namedExport: true,
                localIdentName: 'foo__[name]__[local]',
              },
            },
          },
          'sass-loader',
        ],
      },
    ],
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.jsx', '.js'],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'index.bundle.css',
    }),
    new HtmlWebpackPlugin({
      template: 'src/index.html',
    }),
  ],
};

Zakończenie

Był to wpis ściśle poświęcony konfiguracji styli w Webpacku. Dość szczątkowo. Dość pobieżnie. Nie poruszyłem tematu cachowania styli, chunków, sourcemap i wielu więcej. Dlaczego tak? Nie jest moim celem pisanie wielorozdziałowej książki na temat danego narzędzia (od tego jest dokumentacja), a raczej wprowadzenie do tematu i pokazanie, co i w jaki sposób działa. Daję tutaj bardziej wędkę, aniżeli gotową rybę. Wstęp i podstawę, do czegoś, do czego ty, jako samodzielny/a programista/ka, musisz dojść samemu, przeszukując dostępne źródła dostępne na każdym kroku po wpisaniu kilku słów w Google. Praca programisty w dużej mierze polega na poszukiwaniu, próbowaniu, testowaniu różnych podejść i osiąganiu tego, co chce się osiągnąć. A przypominam, że Webpack prawdopodobnie zrobi wszystko, co chcesz, jeśli go dobrze wykorzystasz.

Jeszcze jedno. Koniecznie wejdź w pierwszy link ze źródeł i zobacz, jakie jeszcze cuda można robić w kontekście budowania stylów w projekcie. Możemy dowolnie skonfigurować to, w jaki sposób końcowy, produkcyjny projekt, będzie czytał nasze style. Możemy dodać do tego co dziś zrobiliśmy minifikację, cache na development mode, sourcemapy, chunki, wiele bundlów css, postcss-loader (nawet bardziej zaawansowany od tych dziś wspomnianych), czy odseparowane foldery. Możliwości jest wiele, a jeszcze więcej jest podejść na powyższe rzeczy. Możemy się tym bawić do woli, a w komercyjnym świecie, kiedy przyjdzie taki czas, nie straszny nam Webpack będzie :).

Źródła

© Damian Kalka 2022
Wszelkie prawa zastrzeżone