Pliki statyczne w Systemie Zapisów - iiuni/projektzapisy GitHub Wiki

Overview

W Zapisach korzystamy z TypeScriptu i sass + frameworków jak vue.js czy React. Kompilacja assetów do formatu nadającego się dla przeglądarki odbywa się przy użyciu Webpacka. Webpack uruchamiany jest w ramach node.js. Dzięki temu procesowi:

  1. Możemy korzystać z technologii przyjemniejszych niż te natywnie dostępne w przeglądarkach (czysty ES6/CSS)
  2. Nie trzeba się nadmiernie przejmować przy używaniu nowych technologii; wszystkie cztery nowoczesne przeglądarki (Edge, Safari, Chrome, FF) powinny sobie radzić z naszym frontowym kodem spokojnie do kilku wersji wstecz.

Motywacja

System staticfiles w Django jest dziś naszym zdaniem niewystarczający do komfortowej pracy nad aplikacjami działającymi w przeglądarkach, ponieważ w Django te pliki faktycznie są statyczne. Istnieje projekt pipeline, ale jest to dość niszowa opcja: brak dobrze działającego wsparcia dla TypeScriptu (trzeba to robić samemu), korzystanie z vue.js prawdopodobnie w ogóle nie byłoby możliwe. Webpack jest znacznie bardziej popularnym i w związku z tym kompletnym rozwiązaniem.

Terminologia

Nazwa pliki statyczne, choć rozpowszechniona w Django, wydaje się być o tyle nietrafna, że, jak wspomniano wyżej, większość tych plików nie jest statyczna i wymaga procesu kompilacji. W związku z tym będą one tutaj nazywane zbiorczo assetami.

Integracja z Django: szybki przegląd

Dawny system plików statycznych został przebudowany, dlatego zachęcamy do przeczytania poniższego krótkiego opisu.

  • Pliki statyczne (rawfiles) - Mowa tu o wszelkich plikach niewymagających kompilacji, jak np. obrazki. Do ich serwowania korzystamy z systemu staticfiles. Przechowujemy je w podkatalogu static/<nazwa aplikacji>/ w folderze aplikcji która z danego pliku korzysta. Aby móc zagnieździć taki plik w Djangowym templatcie, korzystamy z wbudowanego tagu {% static "nazwa_aplikacji/plik" %}.
  • Pliki kompilowane - Wszelkie pliki Javascript, Typescript, Vue, CSS, SCSS itp. Do ich kompilacji wykorzystujemy Webpacka, a integrację zapewnia paczka django-webpack-loader. Korzystanie z niej jest bardzo proste:
  1. W folderze danej aplikacji tworzymy folder assets. To tutaj będziemy przechowywać wszelkie kompilowane pliki danej aplikacji.

  2. W pliku webpack_resources/asset-defs.js, w dziale aplikacji oznaczonym odpowiednim komentarzem, umieszczamy definicję naszego bundle'a. Z zasady oznaczamy go wedle następującej zasady: nazwa_aplikacji-nazwa_bundle'a.

  3. W pliku HTML ładujemy zdefiniowany przez django-webpack-loader template tag: {% load render_bundle from webpack_loader %}.

  4. Przy pomocy taga wczytujemy bundle w następujący sposób: {% render_bundle "nazwa_aplikacji-nazwa_bundlea" %}. Wygenerowane zostaną wówczas tagi <script> i <style>, które wczytają nasze skompilowane assety.

Szczegóły

Webpack jest dość niskopoziomowym systemem i sam nie narzuca żadnej konwencji; w związku z tym system opisany poniżej jest zaprojektowany przez nas. Jeżeli pojawi się jakaś propozycja ulepszenia go, można to bez problemu zrobić.

Po uruchomieniu kompilacji Webpack sprawdzi zawartość pliku webpack_resources/asset-defs.js. Format tego pliku jest następujący:

const path = require("path");

const AssetDefs = {
    // Nazwa_aplikacji app
    "nazwa_aplikacji-nazwa_bundlea1": [
        path.resolve("apps/nazwa_aplikacji/assets/example1.ts"),
        path.resolve("apps/nazwa_aplikacji/assets/example2.js")
    ],
    "nazwa_aplikacji-nazwa_bundlea2": [
        path.resolve("apps/nazwa_aplikacji/assets/example3.scss")
    ]

    // ...
}

module.exports = AssetDefs;

Eksportujemy obiekt zawierający klucze będące nazwami bundle'i. Podane ścieżki muszą być relatywne względem folderu zapisy. Poniżej podano objaśnienie obu sekcji.

bundle

Bundle to nic innego jak zbiór plików .ts, .js, .scss czy .css. Definicja każdego z nich składa się z tablicy ze ścieżkami do plików z których się składa. Do tablicy wystarczy wpisać tylko entry points (punkty wejściowe); wszystkie pliki importowane pośrednio lub bezpośrednio przez jakikolwiek punkt wejściowy zostaną automatycznie dodane do bundle. Do nazwy bundle wedle zasady dodajemy z przodu nazwę aplikacji Django, do której ten bundle należy, oraz myślnik (-).

Bundle można potem wczytać w szablonie Django w następujący sposób:

  1. Należy załadować (oczywiście tylko raz w danym pliku) odpowiedni tag: {% load render_bundle from webpack_loader %}
  2. W odpowiednim miejscu w kodzie należy wczytać bundle: {% render_bundle "nazwa_aplikacji-nazwa_bundlea" %}. W wyniku tego wywołania zostaną wygenerowane tagi HTML <script> oraz <link> wczytujące odpowiednio skompilowany kod JS oraz CSS. Uwaga: są to standarowe tagi HTML i w związku z tym należy przestrzegać normalnych zasad ich umieszczania w kodzie (wzorcowo powinno się to robić w sekcji <head> pliku HTML).

Dla przykładu, w aplikacji news, chcemy zadeklarować bundle o nazwie test-bundle. Robimy to w pliku webpack_resources/asset-defs.js w następujący sposób:

const path = require("path");

const AssetDefs = {
    // ...

    // news app
    "news-test-bundle": [
        path.resolve("apps/news/assets/myscript.ts"),
    ],

    // ...
}

module.exports = AssetDefs;

w myscript.ts:

import { memoize } from "lodash";
import { someUtility } from "./utils.ts";

Pełna nazwa bundle to news-test-bundle (to jej należy użyć w szablonie Django). Należeć do niego będzie zarówno myscript.ts, utils.ts, jak i kod z lodash implementujący funkcję memoize. Wystarczy jednak zdefiniować tylko punkt wejściowy/korzeń drzewa zależności. By załączyć skompilowany bundle w template, należy napisać:

{% render_bundle "news-test-bundle" %}

Pliki statyczne rawfiles

Pliki, które nie wymagają przetwarzania w ramach procesu kompilacji. Opcja ta służy do pracy z obrazami czy dokumentami (pdf).

rawfiles umieszczamy w katalogu <katalog aplikacji>/static/<nazwa aplikacji>. Dla przykładu, jeśli chcemy użyć pliku img.png w aplikacji news, umieszczamy go w lokalizacji: apps/news/static/news/img.png.

By go później użyć w szablonie, np. by stworzyć bezpośredni odnośnik, należy skorzystać ze static: {% static "news/img.png" %}.

Kompilacja

Podczas kompilacji każdy bundle kompilowany jest do jednego pliku dla każdego typu plików wejściowych. Innymi słowy, pliki .ts należące do jednego bundle zostaną skompilowane do jednego pliku .js, zaś pliki .scss - do jednego pliku .css. Folderem wyjściowym dla kompilacji jest zapisy/compiled_assets. Skompilowane bundles zostaną wypisane bezpośrednio do tego folderu.

WAŻNE: jak wspominano wcześniej, do nazwy każdego bundle z zasady dodajemy z przodu nazwę aplikacji do której należy. Dla przykładu: dla aplikacji news określono w asset-defs.js bundle o nazwie main; jego pełna nazwa, którą należy zadeklarować i użyć później w szablonie, to news-main.

Uruchamianie kompilacji

Wykonaj polecenie run server. Uruchomi to serwer Django i kompilator assetów naraz. Kompilator assetów automatycznie wykryje wszelkie zmiany i przeprowadzi ponowną kompilację tylko zmienionego kodu, co z reguły trwa nie dłużej, niż 1-2 sekundy.


Informacje szczegółowe

UWAGA: Opcje podane tutaj są potrzebne wyłącznie do zaawansowanych zastosowań (jak choćby konfiguracja fabfile, która opisuje, w jaki sposób zbudować produkcyjną wersję assetów); do użytku standardowego nie ma potrzeby zapoznawać się z nimi.

Kompilację uruchamia się skryptami zdefniowanymi w package.json. Dostępne są cztery tryby:

  • yarn dev:watch - uruchom w trybie development (brak minifikacji i optymalizacji, generowanie sourcemap), tryb watch (automatyczna, inkrementalna kompilacja przy wykryciu zmian w plikach). Tryb ten jest uruchomiony jako usługa systemowa na wirtualnej maszynie developerskiej.
  • yarn dev - jak wyżej, ale bez trybu watch (proces kompilatora kończy pracę się po przetworzeniu plików)
  • yarn dev:tc - jak wyżej, ale ze sprawdzaniem typów przez Typescript. Domyślnie uruchomione podczas automatycznych testów na Travis-ci.
  • yarn build - skompiluj projekt przy ustawieniach produkcyjnych (brak sourcemap, minifikacja oraz optymalizacja skryptów i styli). Domyślnie uruchomione przy deploy'ach (wrzucaniu nowej wersji kodu na maszynę produkcyjną).

Skrypt run.py w głównym katalogu uruchamia serwer Django oraz Webpack w trybie devw jak powyżej; tego rozwiązania należy używać przy codziennej pracy. Skrypt w obecnej konfiguracji jest uruchamiany automatycznie.

Szczegół implementacyjny: W settings.py STATICFILES_DIRS ustawione jest na compiled_assets. Chodzi o to, by:

  1. Przy DEBUG = True serwer Django odczytywał i udostępniał skompilowane assety z tego katalogu
  2. Podczas deploy za pomocą fabfile.py można było standardowo zebrać wszystkie pliki używając collectstatic. Warto zwrócić uwagę, że nie można po prostu ustawić STATIC_ROOT na compiled_assets, bo istnieją również inne pliki statyczne z aplikacji wbudowanych w Django, np. contrib.auth. Dlatego niezbędne jest uruchomienie collectstatic, które pozbiera pliki zarówno z compiled_assets, jak i pozostałych aplikacji w systemie, i zbierze je w jednym miejscu.

Szczegół techniczny: W Ubuntu yarn kryje się pod komendą yarnpkg.

Podziękowania

Pierwszą konfigurację z Webpackiem i bundle'ami stworzył w Systemie Zapisów @user-45-20. Istotne uproszczenia zostały wprowadzone (#962) przez @basiekjusz i @jasiekmarc.

⚠️ **GitHub.com Fallback** ⚠️