Kategorien
Laravel

#Kapitel 5: Javascript und Css

Nachdem wir Laravel Views und Componenten kennengelernt haben, wollen wir der Seite natürlich auch Javascript und CSS hinzufügen. Klingt eigentlich einfach, jedoch wollte ich den Laravel-Weg beschreiten und bin da über einiges gestolpert.

asset() nutzen

In der (Master) View könnte doch ganz einfach folgendes gemacht werden:

<script src="{{ asset('js/app.js') }}" defer></script> 
<!-- Styles --> 
<link href="{{ asset('css/app.css') }}" rel="stylesheet">

Im HTML erscheint das dann auch sofort ausformuliert. Natürlich existieren die Ordner und die Dateien noch nicht. Das ist schnell erledigt und funktioniert im Anschluss wunderbar:

Ist das jedoch der Laravel Weg? In einer erste Recherche bin ich darauf gestoßen, dass die Resourcen-Dateien wie z. B. JS und CSS in den Ordner /storage/app/public gehören. Da der Webserver jedoch nur den Ordner /public im direkten Zugriff hat, hab ich ein wenig gerätselt wie der Zugriff dann funktioniert. Folgendes ging erstmal nicht:

<script src="{{ asset('storage/public/js/app.js') }}" defer></script> 
<!-- Styles --> 
<link href="{{ asset('storage/public/css/app.css') }}" rel="stylesheet">

Erst dachte ich mir, dass ein virtueller Ordner Webserver-seitig erstellt werden könnte. Laravel bringt dafür eine Lösung natürlich mit, nämlich symbolischer Links und einen passenden artisan-Befehl dazu:

php artisan storage:link

Wird dieser Befehl ausgeführt, wird die Konfiguration aus /config/filesystems.php verwendet, um den symbolischen Link zu erzeugen. Im Standard-Projekt sieht die entsprechende Stelle folgendermaßen aus:

/*
     |--------------------------------------------------------------------------
     | Symbolic Links
     |--------------------------------------------------------------------------
     |
     | Here you may configure the symbolic links that will be created when the
     | storage:link Artisan command is executed. The array keys should be
     | the locations of the links and the values should be their targets.
     |
     */
 'links' => [     public_path('storage') => storage_path('app/public'), ],

Das soll mir recht sein. Also raus damit:

Im public-Ordner scheint auch gleich der Link:

Es fehlt nur noch die Anpassung der View, in meinem Fall passe ich das Markup in der master.blade.php an:

<script src="{{ asset('storage/js/app.js') }}" defer></script>
<!-- Styles -->
<link href="{{ asset('storage/css/app.css') }}" rel="stylesheet">

Ein wenig mehr Infos dazu gibt es natürlich in der Dokumentation: https://laravel.com/docs/8.x/filesystem#the-public-disk

Das funktioniert also auch. Ich möchte natürlich nicht jegliches CSS oder Javascript neu erfinden, sondern auf bereits Bestehendes zurückgreifen z. B. im Bereich CSS auf Boostrap. Ich fand dazu einen ersten Hinweis in der Dokumentation „Compiling Assets (Mix)“.

Compiling Assets (Mix)

Der Grundgedanke ist, dass man z. B. CSS ja auch wie andere Sprachen kompilieren könnte. Im einfachsten Fall macht man aus einer CSS Anweisung eine minimale Version oder aus mehreren CSS-Dateien eine einzelne, minimierte CSS-Datei. Die heutigen Frameworks bringen jedoch noch mehr mit. Dazu gehört auch das Variablen verwendet werden können (z. B. für die verwendeten Farben), die dann im Kompilierungsprozess verwendet werden, um CSS zu erzeugen um nur eine Funktion aus einem ganzen Strauß von zusätzlichen Funktionalitäten zu nennen. Das gleiche gilt auch für JS-Dateien. Laut Dokumentation soll „Mix“ das alles zum Kinderspiel machen.

Voraussetzungen

Bevor Mix ausführt werden kann, muss Node.js und NPM installiert werden. Ein ganz schöner Brocken, dafür natürlich auch sehr mächtig. Ich werden auf die Installation jedoch nicht eingehen. Überprüfen kann man das mit folgenden Befehlen:

node -v und npm -v geben die installierte Version aus

Installation

Im Prinzip ist alles im Standard schon vorhanden, jedoch müssen noch die Abhängigkeiten installiert werden:

npm install

Eigentlich sollte nun alles vorbereitet sein und wir können das Kompilieren beginnen:

npm run dev

Theoretisch sollte man eine ähnliche Ausgabe wie folgende erhalten:

Wir sehen, dass zwei Dateien erzeugt wurden (im public/css und public/js Ordner).

Die View muss selbstverständlich entsprechend angepasst werden – aber das können wir mittlerweile im Schlaf…

Kleiner Wow-Effekt

npm kann das Projekt überwachen. Sobald Änderungen durch den Entwickler vorgenommen werden, wird eine Kompilierung der Skripte (JS und CSS) angestoßen. Die Überwachung startet man mit dem Befehl:

npm run watch

Dreh und Angelpunkt ist die Datei webpack.mix.js, die im Wurzelverzeichnis des Projekts liegt. Der Inhalt ist übersichtlich und sagt eigentlich schon alles aus, was wir wissen müssen:

const mix = require('laravel-mix');
 /*
  |--------------------------------------------------------------------------
  | Mix Asset Management
  |--------------------------------------------------------------------------
  |
  | Mix provides a clean, fluent API for defining some Webpack build steps
  | for your Laravel applications. By default, we are compiling the CSS
  | file for the application as well as bundling up all the JS files.
  |
  */
 mix.js('resources/js/app.js', 'public/js')
     .postCss('resources/css/app.css', 'public/css', [
         //
     ]);

In dieser Anweisung werden die Ordner und Dateien definiert, die beim Kompilieren berücksichtigt werden sollen.

Eine Änderung an resources/css/app.css und speichern führt zu einem sofortigen kompilieren:

Ja, webpack ist schon cool und ziemlich mächtig.

Und was ist mit Bootstrap?

Klar, ich wollte Bootstrap im Projekt einbinden. Ich fang mal mit dem leichtesten an, nämlich die View mit einem kleinen Beispiel zu füllen:

<div class="alert alert-primary" role="alert">
Bootstrap works!
</div>

Wenn wir anschließend alles richtig machen, sollte eine schöne Bootstrap Alert-Box zu sehen sein.

Da wir NPM verwenden können, nutzen wir das für unsere Zwecke:

npm install bootstrap

Die Ausgabe muss beachtet werden:

Diese Abhängigkeiten also am besten auch gleich installieren:

npm install jquery
npm install popper.js

Der Kompiler muss wissen, welche Module er laden soll. Hier war ich mir nicht sicher, an welcher Stelle die Anweisung eingefügt werden soll. Ich habe mich für /resources/js/app.js entschieden und folgende Ergänzung vorgenommen:

require('./bootstrap');
/* Customization */
import 'bootstrap';

Das Kompilieren sollte problemlos durchlaufen:

Das Javascript ist drin (1.08MB). Nun fehlt noch das CSS.

Ich habe gelesen, dass man am besten gleich die SASS Variante verwenden sollte, damit man alle Vorteile genießen kann. Dazu lege ich in Ordner /resources/sass an und erzeuge dort eine app.scss Datei erstmal mit leerem Inhalt. Im Anschluss passe ich die webpack.mix.js Datei an, damit Mix von der SCSS-Datei Kenntnis bekommt:

mix.js('resources/js/app.js', 'public/js')
     .sass('resources/sass/app.scss')
     .postCss('resources/css/app.css', 'public/css', [
         //
     ]);

Beim Ausführen von npm run dev hagelt es aber eine deftige Fehlermeldung:

Ok, mein Fehler… in der webpack.mix.js fehlt in der Anweisung der zweite Parameter (Ausgabeort). Das schnell anpassen:

mix.js('resources/js/app.js', 'public/js')
     .sass('resources/sass/app.scss', 'public/css')
     .postCss('resources/css/app.css', 'public/css', [
         //
     ]);

Und dann ging es:

War es das jetzt? Die Ausgabe sagt was anderes:

Die Antwort liegt auch in der webpack.mix.js. Die Anweisung postCss überschreibt das Bootstrap CSS. Im Prinzip müsste im ersten Schritt die SASS-Datei als CSS kompiliert werden und diese dann mit der app.css kombiniert werden.

In der Kürze der Zeit habe ich folgende Lösung gefunden:

mix.js('resources/js/app.js', 'public/js')
     .sass('resources/sass/app.scss', 'public/css/sass.app.css')
     .styles(['public/css/sass.app.css', 'resources/css/app.css'], 'public/css/app.css');

Die sass()-Anweisung erzeugt die Datei public/css/sass.app.css und diese wird durch die styles()-Anweisung mit der /resources/css/app.css zu unserer public/css/app.css gebunden. Das einzig unschöne ist, dass die eigentlich nicht genutzte sass.app.css Datei im public-Ordner stehen bleibt. Immerhin… es geht in die richtige Richtung.

Feinschliff

Die Überlegung ist, dass man das individuelle CSS in der app.scss einbinden kann und somit die überflüssige sass.app.css entfällt. Dazu kann man das @use-Schlüsselwort verwenden. Die app.scss muss also leicht angepasst werden. Zu beachten ist, dass @use vor allen anderen Anweisungen stehen muss:

// Variables
@use 'variables';
// Bootstrap
@use '~bootstrap/scss/bootstrap';
// Custom
@use 'resources/css/app.css';
// Fonts
@import url('https://fonts.googleapis.com/css?family=Nunito');

Neu ist auch die Anweisung @use ‚variables‘, die dazu verwendet wird die Bootstrap Sass-Variablen setzen zu können. Im Ordner /resources/sass muss die Datei _variables.scss angelegt werden (der Unterstrich ist kein Tippfehler). Vorerst hat die Datei keinen Inhalt.

So vorbereitet kann ich nun die webpack.mix.js anpassen:

// ...
mix
  .js('resources/js/app.js', 'public/js')
  .sass('resources/sass/app.scss', 'public/css/app.css');

Das Kompilieren erzeugt erfolgreich die public/css/app.css Datei.

Somit ist dieses Kapitel abgeschlossen. Für kleine Projekte bzw. Seiten kann das CSS/JS direkt eingebunden werden. Sobald das Projekt größer wird, finde ich den SASS-Ansatz empfehlenswert. Alternativ zu Bootstrap könnte Tailwind CSS verwendet werden. Dies ist in der Doku ganz gut erklärt, so dass ich das nicht ausprobieren werden.

Update: Bootstrap Variablen setzen

Nachdem ich eine Bootstrap Variable verändern wollte habe ich gemerkt das die Anpassung nicht greift. Das liegt daran, dass ich von den @import-Anweisungen auf die @use-Anweisungen umgestiegen bin. Auf Stackoverflow habe ich den entsprechenden Hinweis dazu gefunden:

@use only makes names available in the current stylesheet, as opposed to globally.

Die finale Version sieht also folgendermaßen aus:

// Bootstrap
 @use '~bootstrap/scss/bootstrap' with (
   $body-bg:#aaa,
   $body-color:#c00
 );
 // Custom
 @use 'resources/css/app.css';
 // Fonts
 @import url('https://fonts.googleapis.com/css?family=Nunito');

Die Datei _variables.scss kann entfernt werden.