10. Jul 2014 |
- min Lesezeit
Da ich hierbei immer wieder über die selben Problem gestolpert bin, möchte ich kurz zusammenfassen, was ich herausgefunden habe. Ich hoffe, dass es vielleicht auch dem einen oder anderen in Zukunft etwas Zeit bei der Fehlersuche erspart.
Ich weiß und ich bin auch heilfroh, dass Flash auf dem absteigenden Ast ist. Mir ist es erst vor Kurzem auf die Füße gefallen, als ich für MyTripMap ´ZeroClipboard´ eingebunden habe. Fürs Kopieren in die Systemzwischenablage brauchen wir leider ein kleines SWF :/
Theoretisch gibt es zwischen Theorie und Praxis keinen Unterschied. Praktisch leider schon... So ähnlich ist es leider immer wieder mit dem Production- und Development-Mode. Der häufigste Unterschied ist, dass man im Production-Mode Assets nicht erst beim Request kompiliert - schließlich ändern sie sich da auch nicht ständig - sondern dass diese schon beim Deployment vorkompiliert und so im Betrieb direkt aus dem Dateisystem ausgeliefert werden. Anderseits möchte man in der lokalen Entwickungsumgebung nicht in riesigen und am Ende auch noch minifizierten Dateien Frontend-Debugging betreiben müssen.
Leider ist dieser sinnvolle Unterschied aber auch die häufigste Ursache, weswegen eine Rails-Applikation oft lokal im Development-Mode einwandfrei funktioniert aber auf dem Live-System dann Zicken mit dem Frontend macht.
Auch wenn oft ein Apache oder nginx die statischen Assets ausliefert, ohne dass dabei die Rails-Applikation behelligt wird, schadet es nicht, wenn man auch in der Rails-Konfiguration die Mime-Types korrekt registiert. Insbesondere, wenn man seine Applikation auf Heroku hosted ist dies sogar unabdingbar, da in diesem Fall die Rails-Applikation die Assets ausliefert. (Selbst bei Verwendung eines CDNs passiert das zumindest einmal.)
Das Problem hierbei ist nämlich, dass Rails im Header als Fallback Mime-Type text/html
setzt, sofern für die Dateiendung der Mime-Type nicht explizit registriert wurde. Dies wiederum hat verständlicherweise zur Folge, dass der eine oder andere Browser diese Resourcen nicht richtig oder auch gar nicht interpretiert.
Zum Registrieren der benötigten Mime-Types erweitert man config/initializers/mime_types.rb
wie folgt:
<span class="dt">Mime</span>::<span class="dt">Type</span>.register <span class="st">"image/x-icon"</span>, <span class="st">:ico</span>
<span class="dt">Mime</span>::<span class="dt">Type</span>.register <span class="st">"application/font-woff"</span>, <span class="st">:woff</span>
<span class="dt">Mime</span>::<span class="dt">Type</span>.register <span class="st">"application/font-eot"</span>, <span class="st">:eot</span>
<span class="dt">Mime</span>::<span class="dt">Type</span>.register <span class="st">"application/font-ttf"</span>, <span class="st">:ttf</span>
<span class="dt">Mime</span>::<span class="dt">Type</span>.register <span class="st">"application/x-shockwave-flash"</span>, <span class="st">:swf</span>
Für Javascript, CSS und die wichtigsten Bildformate sind die Mime-Types natürlich schon standardmäßig registriert.
Das Production-System hat man - wie schon erwähnt - normalerweise so konfiguriert, dass die Assets nicht nach Bedarf kompiliert werden, sondern vorkompiliert und abgelegt sein sollten.
Referenziert man nun beispielsweise in einem View-Template eine nicht vorkompilierte Assets-Datei mit Hilfe von assets_path
oder dergleichen, wird dies im Normalfall mit einer Exception und damit einem 500 Internal server error
quittiert. Der User sieht also nicht die gewünschte Seite, sondern eine böse Fehlermeldung. Im Log findet man dann etwas wie ActionView::Template::Error (modernizr.js isn't precompiled)
Um dies zu vermeiden, muss man Rails explizit sagen, welche Assets zusätzlich zu application.js
,
application.css
und allen Bilddateien vorkompiliert werden sollen. Dies macht man für Javascript- oder CSS-Dateien wie folgt in config/application.rb
oder config/environments/production.rb
:
config.assets.precompile +=<span class="ot"> %w(</span><span class="st"> lib.js modernizr.js print.css </span><span class="ot">)</span>
Dummerweise funktioniert das für Font- oder Flash-Dateien nicht so einfach, weil Rails (4.0.x) bzw. Sprockets bei der Angabe der Dateinamen solche ignoriert, die keine Standard-Assets-Dateiendung (.js, .css, .png, jpg, etc.) haben. Hinzu kommt, dass Rails bei den Nicht-Standard-Assets keine foobar.swf isn't precompiled
Exception also auch keinen 500er produziert. Bei Bild-, Font- oder Flash-Dateien sind die Assets-Path-Helper nicht so streng und werfen keine Exception, sondern geben einen Fallback-Pfad unter public
aus. Letzteres führt dazu, dass das View-Template und damit die Seite normal gerendert werden kann, der Browser dann aber für die zusätzliche Resource eine URL abfragt, die einfach mit einem 404 Not found
antwortet. Das heißt, die Seite wird eigentlich korrekt ausgeliefert, nur die Font- oder Flash-Assets werden nicht gefunden.
Zwar gibt es bei Font- oder Flash-Dateien ähnlich wie bei Bildern nicht viel zu kompilieren und man könnte diese Dateien einfach wirklich direkt in public
ablegen. Bei Gems oder (über bower) unter vendor/assets
eingebundene 3rd-Party-Frontend-Libraries will man aber keine manuelle Kopie der Fonts oder SWFs anlegen und manuell refrenzieren.
Auch um das Browser-Caching dieser Dateien zu verbessern, ist es ratsam solche Dateien durch die Assets-Pipline verarbeiten zu lassen. So werden sie ordentlich und mit MD5-Fingerprint im Namen, zusätzlich auch noch gezipped in public/assets
abgelegt und korrekt im `public/assets/manifest.json' eingetragen. Denn nur dann stimmt der Ablageort mit der von Assest-Path-Helper-Methoden generierten Referenzen überein.
Zum Glück kann man Rails bzw. Sprockets doch dazu bringen, diese Nichstandard-Assets vorzukompilieren. Um das zu erreichen, nutzt man die Möglichkeit bei der Angabe der Dateinamen Regular-Expressions zu verwenden. Bei mir sieht die entsprechende Erweiterung in config/application.rb
wie folgt aus:
config.assets.precompile <<<span class="ot"> %r(.+.(swf|eot|svg|ttf|woff)$)</span>
Damit nehme ich zwar alle Font- und Flash-Dateien mit, das schadet aber im Zweifel nicht, da ich beispielsweise nicht für jeden neuen Font einen zusätzlichen Eintrag schreiben möchte.
Den Trick habe ich übrigens im bootstrap-sass
-Gem gefunden. Dieses nutzt natürlich eine spezifischere Regex um den Icon-Font durch die Assets-Pipline zu schleusen.
Die Rails Assets-Pipeline ist ein wichtiges Tool zur Verwaltung "statischer" Assets. Mit zwei kleinen Kniffen ist auch das Handling von Font- und Flash-Dateien kein großes Problem.
config/initializers/mime_types.rb
<span class="dt">Mime</span>::<span class="dt">Type</span>.register <span class="st">"image/x-icon"</span>, <span class="st">:ico</span>
<span class="dt">Mime</span>::<span class="dt">Type</span>.register <span class="st">"application/font-woff"</span>, <span class="st">:woff</span>
<span class="dt">Mime</span>::<span class="dt">Type</span>.register <span class="st">"application/font-eot"</span>, <span class="st">:eot</span>
<span class="dt">Mime</span>::<span class="dt">Type</span>.register <span class="st">"application/font-ttf"</span>, <span class="st">:ttf</span>
<span class="dt">Mime</span>::<span class="dt">Type</span>.register <span class="st">"application/x-shockwave-flash"</span>, <span class="st">:swf</span>
config/application.rb
:
config.assets.precompile <<<span class="ot"> %r(.+.(swf|eot|svg|ttf|woff)$)</span>
Ich hoffe ich konnte euch mit diesen kleinen Tricks ein bisschen helfen. Sollte dem so sein, oder wenn ihr eine besser Lösung, sonstige Anmerkungen habt, freue ich mich über entsprechende Kommentare.
So long... ...and may the source be with you!
Im Übrigen habe ich mir tatsächlich angewöhnt, die Konfiguration der eigentlich umgebungsunabhängigen, zusätzlich zu kompilierenden Assets in config/application.rb
zu konfigurieren. Das macht das Debuggen solcher Probleme lokal im Develepment-Mode doch wesentlich einfacher:
$ <span class="kw">rake</span> assets:precomile
Dann kann man schell mal schauen, ob wirklich alles, was auch im Production-Mode benötigt wird, vorkompiliert wurde.
Danach immer alle Assets löschen, sonst wundert ihr euch, wenn Änderung am Javascript oder CSS im Development-Mode einfach nicht automatisch neu angezeigt werden.
$ <span class="kw">rake</span> assets:clobber