webcodr

Ubiquiti EdgeRouter X vs. MikroTik hEX

Da ich auch mal Router testen wollte und den EdgeRouter X (ER-X) eh schon besitze, habe ich mir ein vergleichbares Gerät von MikroTik besorgt, den hEX bzw. den RB350Gr3 (dritte Generation des hEX).

MikroTik ist ein Netzwerkausrüster aus Lettland. Wie Ubiquiti bieten sie professionelle Netzwerk-Hard- und Software zu bezahlbaren Preisen an. Man kann sogar Einzelteile wie Boards, Ports, Gehäuse usw. einzeln kaufen und sich damit seinen Traum-Router zusammenbauen.

Die Kontrahenten

Sie könnten zwar von außen nicht unterschiedlicher sein, ihre inneren Werte sind jedoch sehr vergleichbar. Beide bieten fünf Gigabit-Ports und können an Port 1 über 24 V Passive PoE mit Strom versorgt werden. Außerdem nutzen beide den gleichen SoC von MediaTek und damit die gleiche CPU: einen 880 MHz MIPS Dual Core (4 Threads). Preislich liegen sie mit ca. 55 - 60 Euro natürlich auch gleich auf.

Ubiquiti EdgeRouter X

Ubiquiti EdgeRouter X

Putzig, was? Der ER-X ist wirklich klein, aber davon sollte man sich nicht täuschen lassen. Er bietet fünf völlig frei konfigurierbare Gigabit-Ports. Einmal WAN, einmal LAN und drei Switch-Ports mit separatem Netz? Kein Problem. Zweimal WAN mit automatischem Fail Over? Klar. Reiner Switch-Betrieb? Und ob, auch wenn er alleine dafür zu schade ist.

Auf dem ER-X läuft eine Linux-Distribution namens EdgeOS, die auch auf allen weiteren EdgeMAX-Geräten von Ubiquiti eingesetzt wird. Auf Einschränkungen im Vergleich zu den größeren Brüdern verzichetet man dankenswerterweise.

EdgeOS bietet ein recht umfangreiches Web-Interface mit dem sich viele Aufgaben schnell und einfach erledigen lassen. Für die wichtigsten Standard-Anwendungsfälle stehen Assistenten (Wizards) bereit. Ein simples Setup für WAN mit vier LAN-Ports als Switch und PPPoE inkl. Firewall ist damit in einer Minute erledigt. IPv6 wird leider bisher vom Web-Interface kaum unterstützt, bis auf eine Option in den Wizards für ein Standard-Setup mit Firewall, das aber ohne weitere manuelle Konfiguration nicht funktioniert, kann es nur noch IPv6-Adressen für die Interfaces anzeigen.

Alles weitere inkl. der tiefgreifenderen Konfigurationsmöglichkeiten muss über das CLI erledigt werden. Klingt nun schlimmer als es ist. EdgeOS basiert auf Vyatta, einer Linux-Distribution speziell für Netzwerkgeräte. Vyatta hat ein übersichtliches, recht einfach zu erlernendes Interface. Änderungen werden nicht sofort aktiv, erst nach dem man den Befehl commit abschickt werden sie aktiv aber noch nicht gespeichert. Sollte man sich also z.B. mal bei einer Firewall-Änderung aussperren, reicht ein Neustart des ER-X und alles läuft wie zuvor. Um zu speichern wird der Befehl save genutzt.

Man muss also keine Angst vor dem CLI haben. Kaputt machen kann man nichts, sofern man nicht gleich jede Änderung speichert.

Zusätzlich bietet der EdgeRouter X via CLI zuschaltbare Hardware-Beschleunigung für NAT und IPsec (aktuell Beta). Lt. eines Mitarbeiters auf Reddit überlegt Ubiquiti derzeit außerdem Deep Packet Inspection (DPI) in Hardware zu unterstützen – da fehlt wohl noch ein passender Treiber. Damit wäre er fast auf dem Niveau des nächst größeren Bruders, dem EdgeRouter Lite (ca. 90 - 100 Euro).

MikroTik hEX

MikroTik hEX

Zugegeben, das Gehäuse wirkt im Vergleich zum ER-X etwas billig, es stört aber auch nicht. Ich habe jedenfalls noch niemanden gesehen, der Router wegen ihres Gehäuse-Designs kauft. Die Metallhülle des ER-X mag Hitze besser ableiten, aber da beide Geräte nicht sonderlich heiß werden, spielt das eine untergeordnete Rolle.

Die Ports lassen sich genauso frei konfigurieren wie bei der Konkurrenz. Selbst Port Mirroring in Hardware ist möglich, was meines Wissens nach aktuell beim ER-X nur via Software geht.

Zusammen mit dem hEX kommt eine Lizenz für RouterOS, MikroTiks Gegenstück zu EdgeOS. Es kann allerdings auch separat lizenziert und auf x86-Hardware betrieben werden. Wer sich das Web-Interface (WebFig) vorab ansehen möchte, kann das hier tun.

Der hEX kommt wird vorkonfiguriert geliefert: WAN liegt auf Port 1, die restlichen Ports sind dem Switch zugeordnet. Ein DHCP-Server, DNS-Forwarding usw. sind bereits eingerichtet. Assistenten für andere Konfigurationen gibt es aber nicht. wenn lieber selbst Hand anlegen möchte, besteht beim ersten Login die Möglichkeit einfach per Klick alle vordefinierten Einstellungen zurückzusetzen.

WebFig ist standardmäßig an LAN-Port 2 über die IP-Adresse 192.168.88.1 erreichbar. Alternativ bietet MikroTik mit WinBox ein Windows-Programm, das wie eine Art Wrapper für WebFig aussieht, sich aber durch Fenster-Unterstützung innerhalb der Software besser und schneller bedienen lässt. Für Mac-User gibt es WinBox inkl. Wine als fertiges Bundle – funktioniert bei mir bisher probemlos.

Im Gegensatz zum Web-Interface von EdgeOS kann WebFig alle Funktionen konfigurieren. Über einen Paket-Manager lassen sich außerdem weitere Möglichkeiten nachrüsten, u.A. IPv6, das als inaktives Paket mitgeliefert wird.

Die Oberfläche erschlägt einen auf den ersten Blick durch die vielen Optionen und ist etwas gewöhnungsbedürftig, wenn man vorher nur mit EdgeOS zu tun hatte. Nach ein paar Problemen komme ich aber mit WebFig ziemlich gut klar. Die grundsätzlichen Vorgänge unterscheiden sich ja nicht. Umgekehrt ist sicherlich auch EdgeOS für einen MikroTik-Nutzer erstmal sehr ungewohnt.

Das CLI von RouterOS ist ebenso so logisch und einfach strukturiert wie in EdgeOS, auch wenn natürlich die Syntax anders aussieht. Änderungen sind im Gegensatz zu EdgeOS sofort aktiv und werden direkt gespeichert. Um Probleme zu verhindern, bietet RouterOS den Safe Mode für das CLI und WebFig. Darin gemachte Änderungen werden auch sofort umgesetzt, aber erst gespeichert, wenn man das entsprechende Kommando gibt. Im Zweifelsfall reicht ein Neustart und nichts ist passiert.

In Sachen Hardware-Beschleunigung zeigt sich der hEX knausriger als der ER-X, da aktuell nur IPsec unterstützt wird. Ob da weitere Planungen anstehen, konnte ich leider nicht in Erfahrung bringen. Bleibt die Frage, ob das überhaupt geht? Offiziell unterstützt der SoC beider Geräte nur Hardware-IPsec. Es ist also wahrscheinlich, dass der ER-X zusätzliche Hardware für NAT und DPI besitzt.

Benchmark

Mein Benchmark-Szenario basiert auf iPerf3 mit einer, zehn und 100 gleichzeitigen TCP-Verbindungen und ist damit eher theoretischer Natur. Einen ausgefeilten Test mit zigtausenden HTTP-Downloads in verschiedenen Größen wie Ars Technica kann ich leider derzeit nicht bieten. Vielleicht sollte ich “routerperf” entwickeln. :D

Der Benchmark fand zwischen meinem PC und dem MacBook Pro statt. Der Windows-Rechner verfügt über eine Intel-LAN-Schnittstelle, während der Mac über einen Thunderbolt-Ethernet-Adapter mit dem LAN verbunden war. Sind also beides keine Krücken.

Als Referenzwerte dienen Durchläufe an beiden Rechnern, die über einen Cisco SoHo-Switch verbunden waren.

In allen anderen Szenarien war der Mac als Server am WAN-Port des jeweiligen Routers und der PC am entsprechenden LAN-Port in einem separaten Netz. Das NAT findet via Masquerading in IPTables statt.

Ergebnis

Benchmark Results

Das Hardware-NAT des ER-X schlägt richtig ein, während die Leistung ohne Hardware-Unterstützung ungewöhnlich inkonsistent ist. Das volle Potenzial wird erst mit mehreren Verbindungen wirklich genutzt. Der hEX dagegen skaliert in dieser Situation wie man es erwartet.

Da beide die gleiche CPU verwenden ist das Ergebnis bei nur einer Verbindung umso erstaunlicher. Es wäre durchaus möglich, dass es sich hier um einen Bug handelt. Ein ähnliches Problem gab es im Sommer mit dem UniFi Security Gateway, das auf der Hardware des EdgeRouter Lite basiert.

In der Realität dürfte die Differenz zwischen dem hEX und ER-X ein Stück kleiner ausfallen, denn nicht jede Verbindung läuft über TCP und die Paketgröße hat hier auch ein Stück mitzureden.

Fazit

Ich bin mit beiden Geräten sehr zufrieden. Für knapp 60 Euro bekommt man in beiden Fällen ein überzeugendes Produkt, das auch gehobenen Ansprüchen im Heimnetz mehr als gerecht wird.

Nur wer sich glücklich schätzen kann eine Gigabit-Internetverbindung zu besitzen, dürfte mit dem ER-X die schlauere Wahl treffen – auch wenn in der Realität der Unterschied geringer ausfallen wird. Ganz nebenbei: der hEX hat natürlich auch größere Brüder.

Letztendlich dürfte es für die meisten von uns eine reine Geschmacksfrage sein. Ich werde beide im Wechsel einsetzen und die Entwicklung beobachten. Da beide praktischerweise 24 V Passive PoE unterstützen, lassen sie sich sehr einfach tauschen. Kabel bei einem abstecken, beim anderen anstecken – fertig.

Wer Interesse daran hat den hEX oder den EdgeRouter X zu kaufen und mich unterstützen möchte, kann folgende Amazon-Links benutzen. Vielen Dank!

MikroTik hEX (RB350Gr3)

Ubiquiti EdgeRouter X

Adios, Kabel-Internet (Update)

Zum Nachtrag vom 13.1.2017

Wie im Guide zum Vigor 130 bzw. EdgeRouter X schon angedeutet, bin ich von meiner bisherigen Vodafone/Kabel Deutschland-Verbindung zur Telekom mit VDSL 100 gewechselt.

Damit halbiert sich mein Downstream, da Vodafone hier 200 Mbit/s anbietet und VDSL mit Vectoring bekanntlich nur max. 100 Mbit/s hergibt. Als Trostpflaster gibt’s aber immerhin 15 Mbit/s mehr Upstream.

Für Entscheidung war das aber alles zweitranging. In den letzten Monaten gab es immer mehr Probleme mit der Kabel-Verbindung, sei’s durch merkwürdiges Verhalten der Fritzbox 6490, zunehmender Last im Kabelsegment oder mit Routing/Peering der Kabel-Infrastruktur.

Gerade letzteres habe ich eigentlich erst richtig gemerkt, als der Vergleich zur Telekom möglich war.

Die Symptome:

  • Als der Anschluss auf 200 Mbit/s geschaltet wurde, waren stabile Downloadraten von 23 - 25 MB/s in Steam die Regel. Inzwischen sind sie nur noch die Ausnahme und nur außerhalb der Hauptlastzeiten möglich. Gilt nicht nur für Steam, generell für alle Downloads.

  • Wenn Steam die Bandbreite nicht auslasten kann, öffnet es zusätzliche TCP-Verbindungen. Können schon mal an die 50 - 70 Stück sein. Ab dem Punkt steigt die Fritzbox langsam aus, weil sie mit NAT nicht mehr hinterher kommt. Ping-Zeiten steigen deutlich an, Surfen nebenbei macht keinen Spaß mehr … der EdgeRouter X lächelt dank Hardware-NAT nur müde.

  • Teils massive Probleme mit Ping-Zeiten und Packet Loss in Battlefield 1, manchmal völlig unspielbar. Lag für mich immer an EA, bis ich ein paar Runden via VDSL gespielt habe …

  • Apple Music war richtig lahm, Streaming von Filmen aus dem iTunes Store war auf PC und Mac ein Graus bis unbrauchbar, Downloads im App Store waren mal pfeilschnell, dann wieder unglaublich langsam usw. – hatte ich alles auf Apple geschoben, aber wie schon bei Battlefield 1 lag’s an Vodafone. Gleiches gilt auch für teils extrem langsam Downloads aus dem PlayStattion Network.

  • Ich schaue gerne die Reviews von SF Debris, es war aber zunehmend schwer sich die Videos überhaupt anzusehen. Die Seite lädt im Vodafone-Netz extrem lahm und die Videos brauchen gefühlte Ewigkeit, bis mal ein bisschen matschiges 240p zu sehen ist. Über die Telekom starten die Videos sofort – in HD.

  • Vermehrt Probleme mit YouTube, manchmal so schlimm, dass selbst Videos in SD nicht mehr flüssig liefen.

  • Starke Schwankungen bei den Ping-Zeiten, auch ohne Last.

Ein Vergleich:

Vodafone

~ ❯❯❯ ping -c 100 google.de
PING google.de (172.217.21.195): 56 data bytes
64 bytes from 172.217.21.195: icmp_seq=0 ttl=53 time=21.271 ms
...
64 bytes from 172.217.21.195: icmp_seq=99 ttl=53 time=28.892 ms

--- google.de ping statistics ---
100 packets transmitted, 100 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 19.163/30.845/111.265/15.526 ms

Telekom

~ ❯❯❯ ping -c 100 google.de
PING google.de (172.217.21.163): 56 data bytes
64 bytes from 172.217.21.163: icmp_seq=0 ttl=57 time=21.731 ms
...
64 bytes from 172.217.21.163: icmp_seq=99 ttl=57 time=21.594 ms

--- google.de ping statistics ---
100 packets transmitted, 100 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 21.482/21.760/22.146/0.156 ms

Mit IPv6 schaut’s für Vodafone sogar noch etwas schlechter aus.

Diese Ping-Messungen habe ich an einem frühen Nachmittag ausgeführt, also sollte sich die Last im Segmet bzw. Netz doch in Grenzen gehalten haben.

Meine Diagnose:

  1. Die Fritzbox 6490 mag für Normalnutzer okay sein, aber nicht für mich. Den Bridge-Modus lässt Vodafone leider durch Einschnitte in FritzOS nicht mehr zu und ein reines Kabelmodem für EURODOCSIS 3.0 zu finden ist ein Ding der Unmöglichkeit.

  2. Mein zuständiges Kabel-Segment wird zunehmend überlastet. Früher war ich hier der einzige weit und breit mit einer Internet-Verbindung über Kabel. Da es zumindest bis Dezember 2016 auch keine Alternativen für anständige Bandbreiten gab, geht in dem Segment nun zunehmend die Post ab.

  3. Das interne Routing von Vodafone geht z.T. über sehr viele Hops, sowohl mit IPv4 als auch IPv6. Das ist grundsätzlich nicht weiter schlimm, aber es erhöht die Anfälligkeit für Fehler und Packet Loss.

  4. Der vermutlich schwerwiegendste Punkt: das Peering in andere Netze ist z.T. eine Katastrophe. Alle og. Probleme mit den Servern von Apple, EA, Google/YouTube, Sony usw. liegen daran. Über den Punkt kann ich nicht mehr hinwegsehen. Bandbreiten und Ping-Schwankungen sind eine Sache, aber wenn ich für mich wichtige Dienste nicht mehr richtig nutzen kann, ist der Ofen wirklich aus.

Kabel Deutschland bzw. jetzt Vodafone hatte ich fast acht Jahre ohne größere Probleme, aber da ich auch beruflich auf die Internetverbindung angewiesen bin, muss ich an der Stelle den Stecker ziehen.

Vodafone wird mit Sicherheit an den Problemen arbeiten – dauert halt. Eine Segment-Aufteilung kann sich über mehrere Monate bis ein Jahr ziehen. Internes Routing lässt sich auch nicht von heute auf morgen verbessern, ebenso das Peering.

Man hat es hier mit den Angeboten für 200 Mbit/s bzw. in vielen Haushalten auch schon 400 Mbit/s wohl übertrieben ohne die Infrastruktur dahinter auszubauen.

Zumindest im Mobilfunk-Bereich hat Vodafone in den letzten Jahren einiges deutlich verbessert. Schaffen sie im Kabelnetz hoffentlich auch noch. Konkurrenz belebt das Geschäft.

Solange bin ich aber wieder Telekom-Kunde. VDSL mit Vectoring ist nur eine Übergangslösung bis FTTH im großen Stil kommt, aber bin nicht mehr auf ein Shared Medium wie Kabel angewiesen und ab dem MSAN hängt man im BNG, dem neuen, verdammt flotten Backbone-Netz der Telekom.

Außerdem kann ich meine Wunsch-Hardware nutzen, auch wenn es nicht viele Vectoring-taugliche DSL-Modems da draußen gibt.

Für die meisten tut’s der übliche Speedport, der mir zumindest in seinem Verhalten unter Last besser gefällt als die Fritzbox. Der Vergleich hinkt natürlich etwas, da die Fritte deutlich mehr kann.

In Sachen WLAN sind sie beide ziemlich meh, besonders im Vergleich zu meinen früheren AirPorts oder jetzt UniFi Access Points – ist natürlich wieder unfair. Gibt weit teurere Consumer-Geräte, die viel schlimmer sind, siehe Ars Technica.

Nachtrag vom 13.1.2017:

Nach diesem Bericht von Golem ist mir dann auch klar, warum ich die Probleme mit Vodafone hatte. Traffic Shaping verzögert oder verwirft Pakete einfach, je nach Konfiguration bzw. Last. Das erklärt die schwankenden Ping-Zeiten, den Packet Loss in Spielen, die inkonsistenten Download-Raten usw.

Es ist zwar logisch, dass Vodafone QoS einsetzt, damit das Netz nicht komplett zum Teufel geht. Man kann aber nicht einfach den Backbone-Ausbau verschlafen oder gar sogar absichtlich verzögern, während man den Kabel-Kunden immer mehr Bandbreite anbietet und die DSL-Kunden ins Kabelnetz lockt bzw. mancherorts wohl sogar drängt.

Webpack 2 with Pug and SASS

Until now I used some Gulp tasks with Jade and SASS to create the HTML and CSS files as a base for the webcodr blog theme. A crude little setup, but it worked.

I decided to write an replacement with Webpack 2 and to share it with you: WebCodr/design GitHub repo

First steps

The original setup used the identend SASS syntax and Jade templates. Since the indented syntax is more trouble than it’s worth, I had to convert to the SCSS syntax, a superset of the CSS syntax.

Unfortunately there was a copyright claim for “Jade” and they had to rename the project to Pug. The Jade Node module is still available but unmaintained. It’s time to use Pug.

SASS to SCSS

Don’t worry, that’s quite simple. The SASS people provide a tool called sass-convert and it’s really easy to use:

sass-convert --from=sass --to=scss -R assets/styles/

Just pass the tool the format you want to convert, the new format, -R for recursive conversion and add the directory name. After sass-convert finished, you find the new SCSS files along with their SASS predecessors. You can now delete the old SASS files, adjust your build tool and you’re done.

Jade to Pug

It depends on your templates how easy or complicated this step is. My template files are rather simple, so just renaming the file extensions from .jade to .pug and using Pug instead of Jade did the job. If you are having trouble, there is a migration guide from Jade to Pug.

Using Webpack 2

Webpack 2 isn`t finished yet. The current version is 2.2.0 RC3, but even the Beta version were stable enough to use it for production purposes.

Here’s my package.json file with all necessary modules:

{
  "name": "WebCodr_Design",
  "version": "2.0.0",
  "scripts": {
    "server": "webpack-dev-server",
    "dist": "webpack --config webpack.config.prod.babel.js"
  },
  "dependencies": {},
  "devDependencies": {
    "autoprefixer": "^6.6.1",
    "babel-core": "^6.21.0",
    "babel-preset-es2015": "^6.18.0",
    "css-loader": "^0.26.1",
    "csso-loader": "^0.2.1",
    "extract-text-webpack-plugin": "^2.0.0-beta.4",
    "html-loader": "^0.4.4",
    "node-sass": "^4.1.1",
    "postcss-loader": "^1.2.1",
    "pug": "^2.0.0-beta6",
    "pug-html-loader": "^1.0.10",
    "sanitize.css": "^4.1.0",
    "sass-loader": "^4.1.1",
    "webpack": "2.2.0-rc.3",
    "webpack-dev-server": "2.2.0-rc.0"
  }
}

I wrote two little helper scripts to run a dev server on port 8080 and to build a production version of the assets, mainly the CSS file. Currently there is no JavaScript file for webcodr. But it would be really easy to add JavaScript to the Webpack config.

The Webpack config or where the magic happens

I can’t stand the old JavaScript syntax anymore, so I write all my stuff in ES2015 and that’s why Babel is present in the dev dependencies.

import webpack from 'webpack'
import path from 'path'
import autoprefixer from 'autoprefixer'
import ExtractTextPlugin from 'extract-text-webpack-plugin'

let extractStyles = new ExtractTextPlugin('[name].css')
let extractHtml = new ExtractTextPlugin('[name].html')

let config = {
  stats: {
    assets: false,
    colors: true,
    version: false,
    hash: true,
    timings: true,
    chunks: false,
    chunkModules: false
  },
  entry: {
    index: [
      path.resolve(__dirname, 'templates/index.pug')
    ],
    post: [
      path.resolve(__dirname, 'templates/post.pug')
    ],    
    'css/application': [
      path.resolve(__dirname, 'assets/styles/application.scss')
    ]
  },
  output: {
    path: path.resolve(__dirname, 'build'),
    filename: '[name].js'
  },
  module: {
    rules: [
      {
        test: /\.pug$/,
        loader: extractHtml.extract({
          loader: ['html-loader', 'pug-html-loader?pretty&exports=false']
        })
      },
      {
        test: /\.scss$/,
        loader: extractStyles.extract({
          loader: [
            {
              loader: 'css-loader'
            },
            {
              loader: 'postcss-loader'
            },
            {
              loader: 'sass-loader'
            }
          ]
        })
      }
    ]
  },
  plugins: [
    new webpack.LoaderOptionsPlugin({
      minimize: false,
      debug: true,
      options: {
        postcss: [
          autoprefixer({
            browsers: ['last 2 version', 'Explorer >= 10', 'Android >= 4']
          })
        ],
        sassLoader: {
          includePaths: [
            path.resolve(__dirname, 'node_modules/sanitize.css/')
          ]
        }
      }
    }),
    extractStyles,
    extractHtml
  ]
}

export default config

Explanations:

  • stats: Webpack 2 is really verbose, this options will help to make it shut up. ;)

  • entry: I defined three entry points for Webpack. Two for Pug files and one for my SCSS file. The property name is the filename for saving the finished file and the array item points to the source file.

  • output: Defines where to put the files and how to name them.

  • module: The module rules tell Webpack what to do with different file extensions. Since Webpack 2 is JavaScript module bundler it creates JavaScript files and we have to use a plug-in to get HTML and CSS files. The extract text plug-in looks for the content of the defined loaders and extracts it into the file you want.

    To convert Pug into HTML I had to use the Pug HTML loader and the HTML loader. The extract text plug-in now knows what to extract of the generated JavaScript files.

    The same goes for SASS. The SASS loader converts SASS into CSS, after that the PostCSS loader applies the Autoprefixer and finally the CSS loader tells the extract text plug-in what to do.

  • plugins: webpack.LoaderOptionsPlugin contains the loader config for Webpack 1 loaders. Webpack 2 compatible loaders can be configured directly with the loader definition. As you can see, PostCSS loader and SASS loader are not compatible with Webpack 2 yet.

    After the configuration of the legacy loaders, I just had to add the extraction plug-in instances and that’s it.

When Webpack is started, it will iterate through the entry points and apply the appropriate rules based on the regular expressions in the property test. Each rule applies the loaders in reverse order, so the last loader will be applied first. The loaders itself obtain their configs from the query as the Pug HTML loader does or the from the loader options plug-in, only the extract text plug-in is an exception as it needs two configurations: the loaders and where to put the extracted content.

After Webpack is finished, the files are located in the output directory. There are two files for each entry point: a HTML or CSS file and JavaScript file. As mentioned above, Webpack 2 is a JavaScript module bundler and can only create JavaScript files without help from plug-ins. Without extraction, this JavaScript files would contain the HTML or CSS code as a Webpack runtime module and could be used within JavaScript.

You could now open one of the HTML files in your browser, but there’s a better way. You would have to manually start Webpack for each change to make … meh.

yarn run server

This will start the Webpack Dev Server on Port 8080. It watches for changes and re-builds all affected files. You just have to reload the browser tab. Not comfy enough? There is a possibility of auto-reloading and even hot JavaScript module replacement which will be explored in a future blog post.

Adding JavaScript support

What do you think? Is it complicated to create JavaScript files with a JavaScript module bundler? Of course not!

Add the following rule to your config:

{
  test: /\.js$/,
  exclude: /node_modules\/*/,
  use: ['babel-loader'],
  options: {
    presets: [
      ['es2015']
    ]
  }
}

Any entry point with JavaScript files will be processed with Babel for ES2015 support. Pretty neat, huh?

Need ES2016 or ES2017? No problem, just add a suitable Babel preset with npm or yarn to your project and adjust the config.

You’re using TypeScript or CoffeeScript? No problem, just look for a compatible loader.

Conclusion

Some say Webpack is too complicated. Well, it can be quite confusing if you’re just using it without knowledge what it really does, but it’s no rocket science.

I used Webpack to replace a rather large and complex collection of Gulp tasks to build the assets for the website of a customer. For certain things like SVG sprite maps I still use Gulp, but all other tasks are done by Webpack faster and way more easy, especially things like hashes in filenames for production purposes or automatically splitting the main JavaScript file into an application file and a vendor file with certain libraries/frameworks like AngularJS.

The same goes for Karma to run the Jasmine tests. Just add the Webpack plug-in for Karma, make some small adjustments to the Webpack config, let Webpack handle the rest and you’re done.

Compared to the old Gulp tasks, the Webpack setup is easier to understand, faster and much more fun to use. New team members have not to dig into a bunch of Gulp tasks and related helper methods. A quick introduction to the Webpack config is mostly enough to understand how it works.

If you have about 1,000 lines of code with Gulp tasks and helper functions or some really small tasks and a about 150 lines of Webpack config, what do you prefer?

TL;DR

Use Webpack 2, it’s awesome. :P

VDSL via Vigor 130 und EdgeRouter X

Meine Fritzbox 6490 ging mir ja schon länger auf den Zeiger, aber seit ca. acht Wochen geht auch die Verbindung von Vodafone bzw. Kabel Deutschland zunehmend zum Teufel. Passenderweise hat die Telekom hier vor ein paar Wochen den VDSL-Ausbau inkl. Vectoring abgeschlossen. Seit dem 4.1. steht die Leitung und ich bin bisher äußerst angetan. Es halbiert sich zwar die Bandbreite auf 100 Mbit/s down (dafür gibt’s 40 statt 25 Mbit/s up), dafür ist die Leitung wesentlich stabiler. Genauers dazu schreibe ich in einem separaten Blog-Post in Kürze. Genaueres dazu hier.

Bisheriges Netzwerk

Die Internet-Verbindung wird über die Fritzbox 6490 aufgebaut, die sonst außer VoIP nichts tut. An der Fritte hängt ein Ubiquiti EdgeRouter X, der dann das eigentliche Netzwerk aufbaut. Das Netz verteilt sich vom Wohnzimmer aus über drei SoHo-Switches von Cisco und zwei Ubiquiti UniFi AC Lite Access Points an die jeweiligen Endgeräte.

Diese Konstellation führt zu doppeltem NAT. Hässlich, aber auch nicht weiter tragisch. Der EdgeRouter X kann Hardware-NAT und kostet höchstens 400 - 500 µs. Ein geringer Aufpreis für wesentlich mehr Kontrolle über mein Netzwerk. Nur die IPv6-Konfiguration zwischen Fritbox und EdgeRouter wollte einfach nicht klappen – diese unendliche Geschichte will ich hier aber nicht weiter ausbreiten, hat sich eh erledigt.

Neues Netzwerk

Mit VDSL sieht die Sache natürlich etwas anders aus. Der Telefonanschluss ist im Flur, also mussten erstmal Kabel gelegt werden … bei dem Thema sind Mietwohnungen dezent scheiße. Von Kabelkanälen hatte zum Bauzeitpunkt kein Mensch was gehört.

Zum Testen der Verbindung kam erstmal ein Speedport 724 V von der Telekom zum Einsatz. Klingt doof? So übel ist die Kiste gar nicht, allerdings ging’s auch nur mit doppeltem NAT, weil die Speedports leider keinen Modem-Betrieb mehr können und generell ist mein Nutzungsprofil doch etwas anspruchsvoller. Daher habe ich einen Vigor 130 von DrayTek gekauft und als Vectoring-taugliches VDSL-Modem im Einsatz. Der EdgeRouter wird nun einfach per PPPoE über den Vigor versorgt.

Konfiguration

Kommen wir zum Herzstück des Posts, denn die Konfiguration ist zwar grundsätzlich einfach, hat aber auch ein paar Tücken – insbesondere wenn IPv6 im Spiel ist.

DrayTek Vigor 130

Der gute Vigor wird bereits als Modem inkl. VLAN-Tagging für die Telekom vorkonfiguriert geliefert. Man muss eigentlich nur die neueste Firmware einspielen und das war’s.

Falls der Vigor wider erwarten doch nicht als Modem vorkonfiguriert geliefert wird, findet bei iDomiX Hilfe (Text und Video).

Ubiquiti EdgeRouter

Ausgangsbasis ist der EdgeRouter X mit EdgeOS 1.9.1. Es sollte grundsätzlich genauso mit einem größeren EdgeRouter und neueren EdgeOS-Versionen funktionieren.

Netzwerk-Setup

Ich gehe hierbei davon aus, dass jemand, der diesen Post liest zumindest Grundkenntnisse im Umgang eines EdgeRouters hat, also Default-IP, Default-Login-Daten, Web-Interface-Zugang usw.

Für die grundsätzliche Konfiguration bietet Ubiquiti zum Glück entsprechende Assistenten (Wizards) an, die den ganzen Vorgang vereinfachen und mir auch ersparen hier monströse Listings mit Firewall-Regeln zu posten.

Der Wizard “Basic Setup” macht grundsätzlich alles, was man braucht:

EdgeRouter Screenshot 1

Als WAN-Port habe ich mich für eth0 entschieden. Die Einwahl erfolgt über PPPoE mit den entsprechenden Login-Daten der Telekom. VLAN-Tagging ist hier nicht nötig, das übernimmt der Vigor bereits (VLAN 7). Die Default-Firewall sollte auf jeden Fall aktiviert werden, genauso wie DHCPv6 PD. Die Prefix Length ist mit /56 bereits korrekt voreingestellt und auch hier gilt, dass die Firewall an sein sollte.

Die Option “Only use one LAN” wird deaktiviert. Anschließed wird das Netz für eth1 konfiguriert. Ich nutze hier ein privates Klasse A-Netz. Für die Switching-Ports eth2, eth3 und eth4 gibt es ein separates Klasse A-Netz.

Unter “User Setup” verwende ich meine bestehenden User weiter. Sollte der EdgeRouter bisher nie konfiguriert worden sein, empfehle ich aber dringend einen neuen Nutzer mit eigenem Passwort anzulegen. Ein Router sollte nie über seine Standard-Zugangsdaten zugänglich sein.

Nach dem Speichern startet sich der EdgeRouter neu und ist anschließend über die IP 10.0.0.1 an eth1 wieder erreichbar. Im Dashboard sollte nun relativ bald unter pppoe0 die öffentliche IPv4-Adresse sichtbar sein und damit auch die Internet-Verbindung bestehen.

Grundsätzlich ist’s damit getan, wenn einem IPv4 ausreicht.

Für mein privates Netzwerk nehme ich anschließend noch ein paar Einstellungen an der Firewall vor (Port-Weiterleitungen) und am DHCP-Server vor. Wenn man zufrieden ist, sollte man ein Backup der Konfiguration machen. Das geht unter “System” -> “Back Up Config”.

IPv6

Leider unterstützt das Web-Interface bisher kaum IPv6, daher muss man ein paar Einstellungen via CLI vornehmen.

Unter macOS und Linux ist das einfach im Terminal im SSH möglich. Unter Windows bleibt einem aktuell nur das Linux Subsystem von Windows 10 oder ein SSH-Client wie PuTTY.

Beispiel:

ssh user@10.0.0.1

Anschließend wird nach dem Passwort des Users gefragt.

Bei mir sieht die Sache so aus (Login über SSH-Alias mit Key):

~ ❯❯❯ ssh edge                                                                                                                              
Welcome to EdgeOS

By logging in, accessing, or using the Ubiquiti product, you
acknowledge that you have read and understood the Ubiquiti
License Agreement (available in the Web UI at, by default,
http://192.168.1.1) and agree to be bound by its terms.

Linux 3.10.14-UBNT #1 SMP Mon Nov 14 03:56:39 PST 2016 mips
Welcome to EdgeOS
webcodr@ubnt:~$

Damit ist nun das CLI des EdgeRouters offen und man kann loslegen.

Folgendes muss eingegeben werden:

configure

set interfaces ethernet eth1 ipv6 dup-addr-detect-transmits 1
set interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 0 interface eth1 host-address '::dead:beef'
set interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 0 interface eth1 no-dns
set interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 0 interface eth1 prefix-id 42
set interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 0 interface eth1 service slaac
set interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 0 prefix-length 56
set interfaces ethernet eth0 pppoe 0 dhcpv6-pd prefix-only
set interfaces ethernet eth0 pppoe 0 dhcpv6-pd rapid-commit enable
set interfaces ethernet eth0 pppoe 0 ipv6 address autoconf
set interfaces ethernet eth0 pppoe 0 ipv6 dup-addr-detect-transmits 1
set interfaces ethernet eth0 pppoe 0 ipv6 enable
set system offload hwnat enable
commit
save

Erläuterungen:

  • configure startet das Konfiguration-System von EdgeOS/Vyatta
  • set interfaces ethernet eth1 ipv6 dup-addr-detect-transmits 1 legt die Anzahl fest, wie oft versucht wird doppelte IPv6-Addressen herauszufinden
  • set interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 0 interface eth1 host-address '::dead:beef' legt die Host-Adresse nach dem Adress-Prefix der Telekom fest. Ich finde ::dead:beef ziemlich witzig, aber hier kann sich jeder selbst austoben, solange es im Hexadezimal-Bereich liegt.
  • set interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 0 interface eth1 prefix-id 42 legt die Prefix-ID fest, die zusätzlich in die Adresse aufgenommen wird. Was außer 42 sollte es sonst sein? :D
  • set interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 0 interface eth1 service slaac SLAAC steht für Stateless Address Autoconfiguration – damit erzeugt der Port seine IP-Adresse anhand des Prefixes selbst
  • set interfaces ethernet eth0 pppoe 0 ipv6 address autoconf aktiviert die automatische IPv6-Adress-Konfiguration für das PPPoE-Interface
  • set interfaces ethernet eth0 pppoe 0 ipv6 enable aktiviert dann letztendlich IPv6
  • set system offload hwnat enable aktiviert Hardware-NAT für IPv4
  • commit wendet die neue Konfiguration an
  • save speichert die neue Konfiguration ab

Nach commit starten sich die betroffenen Interfaces neu und es erfolgt eine neue Einwahl über PPPoE. Anschließend befinden sich im Dashboard unter pppoe0 die IPv4-Adresse sowie die entsprechenden globalen und lokalen IPv6-Adressen.

Alternativ lässt sich das via CLI anschauen (außerhalb von configure):

webcodr@ubnt:~$ show interfaces
Codes: S - State, L - Link, u - Up, D - Down, A - Admin Down
Interface    IP Address                        S/L  Description
---------    ----------                        ---  -----------
...
eth1         10.0.0.1/24                       u/u  Local 2
             2003:xxxx:xxxx:xx42::dead:beef/64
...

Alle Geräte im Netzwerk sollten nun eine oder mehrere IPv6-Adressen besitzen und via IPv6 ins Internet kommen. Das lässt sich sehr einfach über die Seite ipv6-test.com herausfinden.

Zugang zum Vigor-Web-Interface

Da der Vigor auf der IP-Adresse 192.168.1.1 rumhängt, kommen wir nun leider erstmal nicht an sein Web-Interface ran. Das lässt sich aber recht einfach in EdgeOS ändern:

Dazu muss eth0 (über diesen Port der EdgeRouter ja am Vigor 130) eine IP-Adresse aus Netz des Vigors zugewiesen werden. Ich verwende hier 192.168.1.2/24.

EdgeRouter Screenshot 2

Das alleine reicht noch nicht, da die NAT bisher die Verbindung in das andere Netz nicht kennt. Unter “Firewall/NAT” -> “NAT” -> “Add Source NAT Rule”, trägt man daher folgendes ein und speichert.

EdgeRouter Screenshot 3

Anschließend sollte der Zugriff über die IP-Adresse 192.168.1.1 auf den Vigor sofort funktionieren.

So, das war’s dann eigentlich schon. Ich hoffe, diese kleine Anleitung konnte dem ein oder anderen etwas weiterhelfen.

Da nicht alles davon auf meinem Mist gewachsen ist, möchte mich an dieser Stelle noch beim Autor des Blogs TauSys bedanken. Falls jemand in og. Konfiguration noch Entertain miteinbeziehen möchte, sei ihm dieser Post wärmstens empfohlen.

Wer Interesse daran hat den Vigor 130 oder den EdgeRouter X zu kaufen und mich unterstützen möchte, kann folgende Amazon-Links benutzen. Vielen Dank!

DrayTek Vigor 130

Ubiquiti EdgeRouter X

Tastaturen

Für uns Entwickler ist die Tastatur ohne Zweifel das wichtigste Werkzeug. Ohne sie könnten wir keine Zeile Code schreiben und dennoch wird ihr selten Beachtung geschenkt. Sie ist da und funktioniert – bis man eine Tasse Kaffee darüber schüttet oder sie nach Jahren in Krümeln, anderen Essensresten bzw. Staub ersoffen ist.

Wenn sie kaputt ist, wird für ein paar Euro eine neue gekauft und gut ist. Manchmal gibt man auch etwas mehr aus, z.B. für ein Apple Keyboard oder ein besseres Modell von Logitech.

Dabei macht sich aber niemand Gedanken darüber, wie sie eigentlich funktioniert und ob da nicht gerade für uns Vieltipper Verbesserungspotenzial ist.

Die meisten Tastaturen haben eines gemeinsam: sie nutzen die sog. Rubberdome-Technik. Dabei wird beim Tastendruck eine Gumminoppe mit Kontaktpad nach unten auf die Platine gedrückt und so der Anschlag durch den Kontakt ausgelöst. Alle Noppen befinden sich auf einer Gummifolie, die einfach über die Platine gelegt wird. Kabel ans PCB, Tasten drauf, Gehäuse drumrum und fertig ist die Tastatur.

Das ist günstig, aber auch gut? Leider nein. Es gibt zwar durchaus ganz gute Vertreter dieser Gattung, aber die Schwachstellen haben sie alle gemein:

  1. Um den Anschlag auszulösen, muss man die Taste vollständig durckdrücken.
  2. Der Anschlag ist nicht über alle Tasten konsistent und verändert sich mit der Zeit.
  3. Gerade bei den billigen Vertretern, mitunter aber auch bei den teuren ist das Gehäuse nicht verwindungssteif.
  4. Geringe Haltbarheit von max. 5 Millionen Anschlägen.

Die ersten drei Punkte fallen insbesondere für Vieltipper deutlich stärker ins Gewicht, als man meinen möchte. Vollständiges Durchdrücken der Tasten lässt die Hände bzw. Finger schnell ermüden – gleiches gilt, wenn sich die Tastatur beim Tippen verwindet bzw. nach unten verbiegt. Da sich der Anschlag ungleichmäßig über die Zeit und je nach Nutzung der Tasten verändert, kann sich kein Muskelgedächtnis bilden, um schneller tippen zu können.

Und jetzt?

Wie wär’s mit einer Alternative? Mechanische Tastaturen!

Huch? Sind die nicht sauteuer, laut und sperrig? So wie früher diese Totschläger von IBM …

Hach ja, das IBM Model M. Wenn man wollte, könnte man damit wohl wirklich Einbrecher niederschlagen. Zur Selberverteidigung empfehle aber doch eher andere Gerätschaften.

Ein billiges Vergnügen sind mechanische Tastaturen wirklich nicht, aber sie sind es defintiv wert. Hier geht es schließlich um ein Werkzeug. Handwerker geben sich schließlich auch nicht mit einem Consumer-Akkuschrauber für 20 Euro zufrieden. Warum also sollten wir das bei unseren alltäglichen Helfern tun? Ob sie laut und groß sind, hängt dagegen ganz vom jeweiligen Geschmack ab.

Aha, und warum können die Dinger das nun besser?

Um die Kritikpunkte von oben direkt aufzugreifen:

  1. Mechanische Schalter müssen nicht vollständig durchgedrückt werden, um einen Anschlag auszulösen – die Hälfte genügt. Die verbreitetesten Schalter (Cherry MX) haben einen Hub von insgesamt 4 mm und lösen darum nach 2 mm aus.
  2. Der Anschlag verändert sich weder mit der Zeit noch nutzen sich verschiedene Tasten je nach Gebrauch unterschiedlich ab. So bildet sich mit der Zeit ein Muskelgedächtnis, das schnelleres und komfortableres Schreiben ermöglicht.
  3. Bei guten mechanischen Tastaturen sind die Schalter auf einer Stahlplatte angebracht, darunter befindet sich die Platine mit der sie verlötet sind. Da verwindet und verbiegt sich nichts.
  4. Mechanische Tastaturen halten Jahrzehnte durch. Die durchschnittliche Lebensdauer von Cherry-Schaltern liegt bei ca. 50 Mio. Anschlägen. Unzerstörbar sind sie aber natürlich nicht.

Je nach Schalter-Typ gibt es auch noch zusätzliche Vor- und Nachteile, so dass man sie ganz nach seinen Präferenzen aussuchen kann.

Schalter-Typen

Ich beschränke mich hierbei auf die gängen Varianten von Cherry, da sie am häufigsten verbaut werden.

  • MX blue: löst mit einem Klick-Geräusch aus und hat dazu beim Anschlag einen spürbaren Widerstand. Betätigungskraft: 60 cN
  • MX brown: löst ohne Klick aus, besitzt aber den gleichen spürbaren Widerstand beim Anschlag wie der MX blue. Betätigungskraft: 55 cN
  • MX black: linearer Schalter, löst beim Anschlag wieder einen Klick noch einen Widerstand aus. Betätigungskraft: 60 cN
  • MX red: wie MX black, nur mit geringerer Betätigungskraft: 45 cN

Darüber hinaus gibt es noch verschiedene Varianten der MX blue und MX brown mit höherer Betätigungskraft. Man trifft sie allerdings eher selten an, manche davon sind sogar regelrecht rar.

Grundsätzlich gilt es beim Kauf einer Tastatur zuerst den Schalter-Typ zu wählen. Zum Entwickeln oder Schreiben sind die MX blue definitiv die beste Wahl, da sie ein hör- und spürbares Feedback bieten und daher nach der Eingewöhnung, sehr schnelles und flüssiges Tippen ohne Ermüdungserscheinungen ermöglichen.

Nun mag natürlich so manchen das Klicken auf die Palme bringen, andere widerrum können nicht mehr ohne. Wer sich daran stört oder die Kollegen im Büro nicht zum Amoklauf treiben will, aber dennoch spüren möchte, wenn der Anschlag ausgelöst wird, sollte zum MX brown greifen. Er tippt sich ein klein wenig leichter und ist ohne den Klick erheblich leiser.

Findet man dagegen Widerstand zwecklos, sollte man einen Blick auf die MX black oder MX red werfen. Letzterer ist besonders bei Spielern sehr beliebt, da man sehr schnell mehrere Anschläge hintereinander auslösen kann, ohne wie ein Bekloppter auf die Tastatur hämmern zu müssen. Dafür neigt man u.U. eher zu Vertippern.

Grundsätzlich kann man die Schalter in drei Kategorien aufteilen: Profi-Tipper ohne Klick-Allergie, die eher wenig bis gar nicht spielen, werden mit MX blue am glücklichsten. Möchte man dagegen einen guten Allrounder zum Schreiben und Spielen, ist der MX brown das Mittel der Wahl. Wer mehr spielt als Texte tippt, dürfte mit einem MX black oder MX red am besten klarkommen.

Ich empfehle aber jedem, verschiedene Schaltertypen auszuprobieren. Es gibt genug Spieler, die sich nicht am Klicken des MX blue stören und den MX red hassen wie die Pest – und umgekehrt genauso.

Noch etwas zur Lautstärke: man kann auch mit einem nicht klickenden Schalter schön Krach machen, wenn man auf der Tastatur rumhackt wie ein Irrer. Dazu hängt der Geräuschpegel auch von der Konstruktion bzw. dem Gehäuse ab. Dazu später etwas mehr (dasKeyboard Model S vs. dasKeyboard 4 vs. Corsair K70).

Was kostet der Spaß und wo bekomme ich so ein Teil her?

Der Preis der gängigen Modelle liegt zwischen 60 und 200 Euro.

Die definitiv günstigste Wahl gibt’s von Cherry selbst mit dem MX Keyboard 3.0. Zu bekommen bei Amazon oder den gängigen Hardware-Händlern.

Ansonsten ist die Auswahl an Shops eher eingeschränkt. Die EU und insbesondere Deutschland gehören bei mechanischen Tastaturen eher zur dritten Welt.

Die bisher besten Quellen, die ich neben Amazon ausmachen konnte: GetDigital (dasKeyboard, Ducky, Filco, Leopold), CaseKing (Cherry und Ducky), The Keyboard Company UK (primär Filco und deren Zubehör).

Ansonsten stellen diverse bekannte Marken aus dem Spiele-Sektor wie Corsair, Razer, CMstorm, Func und auch Logitech mechanische Tastaturen her, die man auch in gängigen Läden und Shops bekommt.

Für professionelle Anwender rate ich zu Filco, Ducky oder dasKeyboard, da sie qualitativ etwas mehr bieten. Besonders Ducky und Filco bauen regelrechte Panzer. Grundsätzlich kann mit den anderen aber nicht viel falsch machen. Man sollte aber inzwischen aufpassen, von welchem Hersteller die Switches sind. Da gibt es leider nicht nur Unterschiede in der Schaltcharakteristik, weil inzwischen wegen abgelaufener Patente auch billigere Kopien der MX-Schalter auf dem Markt sind, zu deren Haltbarkeit es noch keine Erfahrungswerte gibt. Die einschlägigen Foren sehen die Sache allerdings weniger gelassen …

Meine Erfahrung

Ich hatte mich im November vergangenen Jahres dazu entschlossen eine mechanische Tastatur auszuprobieren und entschied mich für eine Corsair K70 mit MX brown und blauer Hintergrundbeleuchtung. Die Tastatur hing nur an meinem Windows-Rechner, an dem ich eher wenig schreibe und mehr spiele.

Abgesehen von der Lautstärke durch die offene Konstruktion ohne einen Rahmen, der die Tasten umgibt, war ich mit der K70 sehr zufrieden. Nach einem Tip im Computerbase-Forum, habe ich Gummi-O-Ringe zwischen die Tasten und Schalter montiert, was die Lautstärke deutlich dämpfte. Allerdings verändern solche Maßnahmen den Anschlag – einfach ausprobieren, die Ringe kann man sehr günstig kaufen.

Für meine Arbeit als Entwickler, die ich ausschließlich am Mac verrichte, habe ich längere Zeit eine geeignete mechanische Tastatur gesucht. Zum Testen entschied ich mich für ein dasKeyboard Model S Professional for Mac mit MX blue. Leider gibt es dieses Modell nur mit ANSI-Layout, was mich anfangs vom Kauf abgehalten hat und schließlich auch nicht damit zurecht kam. Der ständige Wechsel bringt einen nur durcheinander und das deutsche Tasten-Mapping (ISO-Layout) mit einem ANSI-Layout zu nutzen klappt auch nicht – da fehlt schließlich eine Taste.

Die MX blue dagegen haben mich auf Anhieb begeistert. Kurzum: ich habe die Tastatur gegen ein dasKeyboard 4 mit MX blue umgetauscht. Die hat zwar kein Mac-Layout, aber das lässt sich durch den Tausch der Tastenkappen und ein paar Einstellungen in OS X problemlos anpassen. Außerdem funktionieren die Media-Tasten und selbst der Lautstärkeregler ohne jegliche Anpassung mit OS X. Wozu da also noch ein spezielles Mac-Modell?

Parallel ließ mich leider die K70 im Stich. Innerhalb eines halben Jahres gingen drei LEDs der Hintergrundbeleuchtung kaputt. Nach einer Recherche in den Corsair-Forn scheint das ein verbreitetes Problem mit den blauen LEDs (gibt auch rote) bei der K70 zu sein. Corsair hat das Modell zwischenzeitlich vom Markt genommen und wird wohl demnächst eine überarbeitete Version liefern.

Amazon nahm die K70 anstandslos zurück und als Ersatz habe ich mich für ein dasKeyboard 4 mit MX brown entschieden und es bisher nicht bereut.

Zum Abrunden des Portfolios der MX-Schalter, habe ich mir noch als Abwechslung zu den beiden dasKeyboard eine Ducky Shine 3 mit MX black gegönnt. Sie wirkt noch einen Tick solider, hat ein abnehmbares Kabel und zig Beleuchtungsoptionen. Der lineare Gang und höhere Widerstand der Switches ist etwas gewöhnungsbedürftig, aber angenehm, besonders beim Spielen.

Eine weitere Anmerkung zur Lautstärke: die dasKeyboard 4 haben zwischen ihrer Deckplatte aus Aluminum und der Stahlplatte einen sehr festen Schaumstoff, der die Geräuschkulisse dämpft. Der Unterschied zum Vorgängermodell oder auch der Corsair K70 ist ziemlich gewaltig. Sogar die MX blue-Version ist leiser als die K70 samt Gummi-O-Ringen. Die Ducky liegt durch ihre sehr stabile Konstruktion auch recht gut im Vergleich. Dazu tragen aber auch die MX black sicherlich ihren Teil bei.

Generell habe ich mit keiner der Tastaturen eine schlechte Tipp-Erfahrung gemacht. Nur das ANSI-Layout und die defekten LEDs haben mich zu einem Umtausch veranlasst.

Fazit

Nach dem ich nun in einem halben Jahr drei Hersteller und drei unteschiedliche Schalter-Typen durch habe, kann ich nur sagen, dass mich mechanische Tastaturen begeistern, wenn sie nicht sogar eine Sammlerwut entfacht haben (ich hoffe nicht, das wäre ein teures Hobby).

Der Tipp-Komfort und die Zuverlässigkeit sind es mir aber wert. Schließlich sind die Tastaturen auch maßgeblich daran beteiligt, dass ich überhaupt Geld verdiene. Wenn man die Unterschiede erstmal kennt, möchte man nur ungern zu Rubberdome-Tastaturen zurückkehren. Selbst mein früher geliebtes Apple Keyboard lasse ich für eine mechanische Tastatur links liegen.

Dazu gibt es eine unglaubliche Vielfalt an Individualisierunsmöglichkeiten. Seien es nun Tastenkappen, modifizierte Federn in den Schaltern oder gar leicht verrückte Dinge wie Tastatur-Bausätze, mit denen man wirklich alles bis ins letzte Detail für sich anpassen und austüfteln kann. Interessenten zu diesen Themen sowie Kaufberatung etc. kann ich das Forum geekhack ans Herz legen.

Amplify 2.0

Ich habe Amplify grundlegend überarbeitet und mit ein paar neuen Features ausgestattet:

  • Redcarpet wurde durch kramdown ersetzt und unterstützt damit auch die Markdown-Erweiterungen, die kramdown anbietet.
  • Automatisches Verlinken von URLs
  • HTML-Sanitation
  • Das Syntax-Highlighting übernimmt nun das Gem ‘pygments.rb’. Der Umweg über Pygmentizr fällt damit intern weg und verbessert die Reaktionszeiten deutlich.
  • Das Frontend basiert nun auf AngularJS.
  • Komplett überarbeiteter Quelltext.
  • JSON-basierte API

kramdown, das automatische Verlinken und die HTML-Sanitation werden über das Gem ‘slodown’ von Hendrik Mans erledigt.

Die neue JSON-basierte API ist über eine andere URL erreichbar:

http://amplify.webcodr.de/api/2.0/transform

Ein Beispiel-Request via POST:

{
 	"source": "# Hello World!"
}

Und die entsprechende Antwort von Amplify:

{
	"html": "<h1 id=\"hello-world\">Hello World!</h1>",
 	"source": "# Hello World!"
}

Wer, wie früher, ohne JSON auskommen möchte, nutzt bitte folgende URL:

http://amplify.webcodr.de/api/1.0/transform

Die bisherige Möglichkeit einfach einen POST-Request auf die Amplify-URL abzusetzen ist nur noch aus Gründen der Kompatibilität zu bestehenden Anwendungen aktiv, wird aber langfristig deaktiviert.

Zukünftige Features werde ich außerdem nur für die JSON-basierte API 2.0 implementieren.

Des weiteren plane ich Amplify langfristig nicht mehr auf Heroku laufen zu lassen, da die App einfach zu häufig in den Schlafmodus versetzt wird und anschließend sehr lange braucht, bis sie auf Anfragen reagiert.

Ich könnte zwar einfach einen Dyno hinzubuchen, aber das ist nicht gerade billig und es gibt wirklich kostengünstigere Möglichkeiten, Ruby-Web-Applikationen zu hosten – beispielsweise bei Uberspace.de.

Aus technischer Sicht dürfte noch interessant sein, dass Amplify nun mit Capybara automatisiert getestet wird und auf Travis CI sowie testweise Circle CI (mit automatischem Heroku-Deployment) läuft.

Say Hello to Mango

Was’n das?

Finger weg! Diese Mango schmeckt nicht.

Mango ist ein Object Document Mapper für MongoDB und PHP.

Und MongoAppKit?

MongoAppKit hat ein Problem, denn es ist nicht nur ein ODM und kann noch so einiges mehr. Theoretisch kann man zwar die ODM-Komponente auch ohne den ganzen anderen Krempel nutzen, aber es bleibt eine große Abhängigkeit zu Silex, die man auch nicht so einfach wieder los wird.

Dies und das im Vergleich schlechte Handling bzw. der geringe Komfort von MongoAppKit, haben mich dazu bewogen mit Mango einen universell einsatzbaren und leicht handzuhabenden ODM zu entwickeln.

Mango wurde stark von Mongoid für Ruby inspiriert und soll dessen Funktionalität zumindest teilweise in PHP abbilden. Das ist einfacher gesagt als getan, denn Ruby bietet wesentlich elegantere Möglichkeiten diverse Probleme zu lösen, als es mit PHP derzeit machbar ist.

Los geht’s …

Installation via Composer

$ php composer.phar require webcodr/mango:*

Ein Dokument anzulegen ist ein Kinderspiel

<?php

namespace MyProject\Model;

use Mango\Document;
use Mango\DocumentInterface;

class User implements DocumentInterface
{
    use Document;

    private function addFields()
    {
        $this->addField('name', ['type' => 'String']);
        $this->addField('email', ['type' => 'String']);
        $this->addField('created_at', ['type' => 'DateTime', 'index' => true, 'default' => 'now'];
        $this->addField('updated_at', ['type' => 'DateTime', 'index' => true, 'default' => 'now'];
    }
}

Es ist lediglich nötig, dass die Model-Klasse das Interface DocumentInterface implementiert und den Trait Document einbindet. In der Hook-Methode addFields() werden anschließend noch die Felder des Dokuments deklariert.

Mango nutzt etwas Magic: Der Klassenname des Models ist gleichzeitig auch der Name der Collection (klein geschrieben). Soll die Collection anders heißen bzw. das Model eine vorhandene nutzen, muss lediglich die Methode getCollectionName() überschrieben werden.

Go, Mango, go!

<?php

use Mango\Mango;
use Mango\DocumentManager;

use Document\User;

$mango = new Mango('mongodb://localhost/galactica');
$dm = new DocumentManager($mango);
$user = new User();
$user->name = 'William Adama';
$user->email 'william.adama@bsg-75.mil';
$user->store();

Das Mango-Object erwartet eine gültige MongoDB URI, falls notwendig inkl. Benutzer, Passwort, Port usw.

Dem Document Manager kommt eine vergleichbare Aufgabe zu, wie dem Entity Manager in Doctrine2.

Für mehr Komfort holt sich eine Model-Klasse den Document Manager über eine statische Methode ab. Daher können Methoden wie store() direkt über die Model-Klasse abgewickelt werden.

Dokumente abfragen

<?php

$user = User::where(['name' => 'William Adama']);
echo $user->count(); // = 1
echo $user->first()->email; // = william.adama@bsg-75.mil

Eine Abfrage kann einfach über die statische Methode where() ausgeführt werden. Die Syntax der Abfragen entspricht derzeit noch der normalen MongoDB Query API. Für die Zukunft plane ich aber eine Abstraktionsebene für die Abfragen, vergleichbar mit Mongoid.

Eine Abfrage kit where() oder find() gibt immer ein Cursor-Objekt zurück, das anhand der aufgerufenen Methode entscheiden kann, ob der Zugriff auf den MongoCursor oder das Abfrageergebnis in Form einer Instanz von MutableMap erfolgt.

In obigem Beispiel ist count() eine Methode des Cursors, während first() schon auf der Ergebnis zugreift. Wie MongoCursor kann auch die Cursor-Klasse von Mango einfach über das Ergebnis iterieren.

Durch die dynamische Unterscheidung zwischen MongoCursor- und Datenzugriff, können auf eine Instanz der Cursor-Klasse auch alle Methoden von MutableMap angewandt werden.

Beispielsweise:

<?php

User::where()->reverse()->slice(0, 2)->each(function($document) {
    echo $document->name;
});

Natürlich macht dieser Code wenig Sinn, da man das wesentlich effizienter über die Cursor-Methoden erledigen kann. Das Beispiel soll lediglich zeigen, was möglich wäre.

Hydration

Mango sorgt automatisch dafür, dass die Dokumente im Ergebnis immer Instanzen ihrer jeweiligen Model-Klasse sind.

Die Hydration-Automatik sorgt außerdem dafür, dass die Daten intern als jeweilige Typ-Klasse von Mango gehalten werden.

Typ-Klassen halten die Daten und können sie in zwei Formaten zurückgeben. Konfiguriert man ein Feld als DateTime bekommt Mango intern beim Speichern automatisch ein MongoDate-Objekt. Greift man hingegen außerhalb von Mango auf den Wert zu, bekäme man in diesem Fall eine Instanz der Klasse DateTime zurück.

Soweit zum aktuellen Funktionsumfang von Mango. Es ist bei weitem noch nicht fertig, kann aber für kleine Projekte schon einsetzt werden. Ich verwende es selbst in der aktuellsten Version von CodrPress und es macht wesentlich mehr Spaß als MongoAppKit, ohne ein monströses Schlachtschiff wie Doctrine zu sein.

Natürlich gibt’s Mango auch bei GitHub.

Services ftw!

Wer kennt das nicht? Man findet eine nette Software-Bibliothek in einer bestimmten Sprache, die vom Server der eigenen Web-Applikation nicht unterstützt wird oder nur sehr umständlich auf andere Weise genutzt werden kann.

So erging es mir mit Tools für Markdown-Rendering und server-basiertem Syntax-Highlighting. Zwar habe ich dafür ja im Oktober die Composer-Pakete SilexMarkdown und Radiant geschrieben, die beide auf bereits vorhandenen Bibliotheken fußen.

Ich war mit beiden nie recht glücklich. Für Ruby und Python gibt es viel schönere, wesentlich umfangreichere Lösungen:

  • Pygments ist ein Python geschriebener Syntax-Highlighter, der nahezu jede relevante Sprache unterstützt – selbst esoterische Merkwürdigkeiten wie Brainfuck.

  • Redcarpet wurde in Ruby verfasst und bietet einen sehr leicht erweiter- und modifizierbaren Markdown-Renderer.

Gerade für ein Blog-System wie CodrPress liegt es nahe, beide zu kombinieren und damit zumindest teilweise GitHub flavoured Markdown zu unterstützen.

Wie bekomme ich also drei Programmiersprachen unter einen Hut, ohne dass CodrPress nur auf angepassten Server-Konfigurationen läuft? Ganz einfach: Services!

Pygmentizr

Pygmentizr ist logischerweise in Python geschrieben, um Pygments nutzen zu können.

Per POST-Anfrage auf die verlinkte URL erreicht man den eigentlichen Service, der als Parameter die Sprache und den Quelltext erwartet. Zurück kommt HTML, das nur noch per CSS hübsch gemacht werden muss.

Ein entsprechendes Stylesheet für den bekannten Monokai-Stil ist auf der Seite verlinkt oder im GitHub-Repository zu finden.

Pygmentizr bei GitHub

Amplifyr

Amplifyr nutzt Redcarpet und bindet Pygmentizr als Syntax-Highlighter ein.

Wie Pygmentizr lässt sich Amplifyr per POST-Anfrage ansprechen und gibt den in HTML konvertierten Markdown-Quelltext zurück.

Amplifyr bei GitHub

Beide Dienste laufen auf der Cloud-Plattform Heroku.

CodrPress-Integration

Um beide Services in CodrPress nutzen können, habe ich in SilexMarkdown ein paar Umbauten vorgenommen. Beim Registrieren des Service-Providers in einer Silex-Applikation, lässt sich nun ganz einfach übergeben, ob der eingebaute Renderer samt Radiant oder Amplifyr genutzt werden soll. Eine entsprechende Anleitung findet sich in der ReadMe-Datei des SilexMarkdown-Repositories bei GitHub.

Beide Dienste sind bei Heroku untergebracht und kosten mich keinen Cent. daher gebe ich die Nutzung für jeden frei. Viel Spaß!

Array almighty

Wenn man sich zulange nur mit PHP beschäftigt vergisst man schnell, dass man oft Dinge tut, die kaum in andere Sprachen übertragbar sind.

Letztens habe ich mir Scala etwas näher angesehen. Kurz am Rande: eine schöne Sprache, wenn auch die verschiedenen Syntax-Modi etwas verwirrend oder zumindest recht gewöhnungsbedürftig sind.

Vorteil PHP

Scala bietet wie fast jede andere typisierte Sprache verschiedene Listen-Klassen für diverse Nutzungsfälle. In PHP gibt es das nicht. Man hat sein Array, das jederzeit veränderlich ist, jede noch so wilde Mischung von Datentypen akzeptiert und assoziative Schlüssel erlaubt. Es ist einfach umgemein praktisch.

Vorteil Scala

Da PHP leider weit davon entfernt ist vollständig objekt-orientiert zu sein und darum ein Array leider keine Objekt ist, kann man Arrays nur mit diversen Funktionen bearbeiten.

Zwar funktioniert das einwandfrei, ist aber umständlich. Ein Array-Objekt, das entsprechende Methoden bietet, die sich am besten auch noch verkettet aufrufen lassen, wäre doch toll.

PHP goes Scala/Java/Objective-C

Daher habe ich mich ans Werk gemacht und die bereits existierende Klasse IterateableList in MongoAppKit in drei neue Klassen des Namespaces \MongoAppKit\Collection aufgeteilt: MutableMap und ArrayMap.

Die Namen orientieren sich an ihren Pendants in Scala, Java oder auch Objective-C. Während alle die SPL-Interfaces Countable und IteratorAggregate implementieren, verwendet ArrayMap zusätzlich das Interface ArrayAccess und kann damit wie ein PHP-Array verwendet werden.

Außerdem implementieren alle drei die Magic Methods __get(), __set(), __isset() und __unset. Das erleichtert z.B. die Verwendung einer Liste in Twig, in dem keine Methode mehr angesprochen werden muss, um innerhalb eines Templates auf die Inhalte zuzugreifen.

Um diverse Array-Funktionen von PHP abzubilden implementieren alle drei die Methoden:

  • first(): gibt das erste Element der Liste zurück
  • last(): gibt das letzte Element der Liste zurück
  • reverse(): dreht die Reihenfolge der Elemente innerhalb der Liste um
  • each(): wendet eine Callback-Funktion mittels array_walk auf alle Elemente an, in der auch auf die Schlüssel zugegriffen werden kann
  • map(): wendet eine Callback-Funktion mittels array_map auf alle Elemente an
  • slice(): schneidet einen Teil der Elemente heraus und gibt sie in einem neuen Listen-Objekt zurück
  • filter(): filtert die Elemente einer Liste anhand einer Callback-Funktion und gibt das Ergebnis in einem neuen Listen-Objekt zurück

Fluent Interface

Um eine Verkettung von Methodenaufrufen zu ermöglichen gibt jede Methode, die sonst keinen Rückgabewert hätte, eine Referenz auf ihre Klasse zurück. Nur slice() und filter() geben eine neue Liste mit den herausgeschnittenen bzw. gefilterten Werten zurück.

Hier ein kleines Beispiel aus CodrPress, was man damit alles anstellen kann:

<?php

Post::where()->each(function($document) use ($app) {
    $md = $document->getProperty('body');
    $html = $app['markdown']->transform($md);
    $document->set('body_html', $html)->save();
});

Die statische Methode Post::where() liefert ohne Quert alle Posts als MutableMap-Objekt zurück. Auf die Rückgabe lässt sich sofort each() anwenden, das alle Elemente der Liste iteriert und die definierte Closure auf jedes Element einzeln anwendet.

In diesem Fall wird das rohe Markdown aus dem Feld body in HTML transformiert und im Feld body_html abgespeichert.

Fazit

MutableMap und seine Sub-Klassen sparen viel Schreibarbeit durch ein simples und komfortables Fluent Interface – einer Vorgehensweise, der in PHP leider viel zu wenig Beachtung geschenkt wird.

Download

Die drei Klassen sind nicht länger Teil von MongoAppKit. Ich habe sie in ein separates GitHub-Repository und Composer-Paket ausgelegt, um eine unkomplizierte Nutzung ohne MongoAppKit zu ermöglichen. Viel Spaß!

PythonPress

Da eine neue Sprache nicht genug ist, beschäftige ich mich neben Ruby neuerdings auch noch mit Python.

Als Lernprojekt setze ich aktuell CodrPress als Python-Version um. Natürlich will ich das nicht das Rad neu erfinden, daher setze ich auf zwei Frameworks, dank denen ich sehr schnell ein vorzeigbares Ergebnis zusammenbauen konnte:

  • Flask ist ein Micro-Framework vergleichbar mit Sinatra (Ruby) oder Silex (PHP). Es kümmert sich also um alles, was man braucht, um eine Website zu bauen. Vom Routing bis hin zur Template Engine (Jinja2).

  • MongoEngine bietet einen ODM (Object Document Mapper) vergleichbar mit Mongoid (Ruby) oder meinem eigenen Projekt MongoAppKit in PHP.

Routen-Definitionen mit Flask

from flask import Flask

app = Flask(__name__)

@app.route('/hello/<name>')
def hello(name):
    return 'Hello %s!' % name

app.run()

Zu Beginn wird die Klasse Flask aus dem Package flask importiert und anschließend eine Instanz erstellt.

Im Gegensatz zu PHP oder anderen C-Syntax-Sprachen kennt Python das Schlüsselwort new nicht. Um ein neues Objekt zu instanziieren reicht es den Klassennamen samt den Klammern und ggf. den Constructor-Argumenten zu schreiben.

Es folgt die Routen-Definition. Variable Werte werden in spitze Klammern gesetzt und der anschließenden Methode mit gleichem Namen als Parameter übergeben.

Wie auch in Silex oder Sinatra wird der Rückgabewert einer Routen-Methode zurück an den Browser geschickt. In diesem Fall ist das nur ein simpler String-Wert.

Templates in Flask

Flask nutzt die Template Engine Jinja2. Wer aus der PHP-Welt Twig kennt fühlt sich sofort heimisch. Die Sprachelemente sind nahezu identisch.

Datei: ./templates/hello.html

<h1>Hello {{ name }}!</h1>  

In obiger Route müsste die Methode nun so aussehen:

from flask import Flask, render_template
...
def hello(name):
    return render_template('hello.html', name = name)
...

Nicht vergessen: render_template muss zusätzlich importiert werden!

MongoEngine

from mongoengine import *

connect('test')

class Post(Document):
    _id = ObjectIdField()
    created_at = DateTimeField()
    published = BooleanField()
    title = StringField()
    body = StringField()

Der Aufruf von connect stellt eine Verbindung zur Datenbank test her. Da keine Verbindungsdaten angegeben werden, geht MongoEngine automatisch von einem lokalen MongoDB-Server auf dem Standard-Port aus.

Die Klasse Post ist eine Sub-Klasse von Document aus MongoEngine. Anschließend werden die Felder der Klasse definiert. MongoEngine stellt für jeden von MongoDB unterstützten Datentyp entsprechende Klassen zur Verfügung.

Sofern nicht über das Attribut meta eine andere Collection definiert wird, greift MongoEngine auf den Klassennamen in Kleinbuchstaben als Collection zu.

Um ein neues Dokument von Post zu erstellen und zu speichern, reicht schon folgender Code:

post = Post()
post.published = True
post.title = 'Hello World!'
post.body = 'Hallo, ich ein Test.'
post.save()

Abfragen mit MongoEngine

Als vollständiger ODM bietet MongoEngine natürlich auch die Möglichkeit vorhandene Daten abzufragen. In folgendem Beispiel werden die letzten zehn veröffentlichten Einträge absteigend nach der Erstelldatum sortiert, in ein Array geschrieben.

posts = Post.objects(published = True).order_by('-created_at').limit(10)

Abfragen erfolgen statisch, daher ist keine Instanz nötig. Die Methode objects() enthält die Bedingungen, also in diesem Fall, dass ein Eintrag veröffentlicht wurde. order_by() erwartet den Feldnamen mit der Sortierrichtung als Präfix. Hierbei steht + für aufsteigend und - für absteigend. Zu guter letzt wird das Ergebnis mit limit() auf 10 Dokumente eingeschränkt.

Mit diesem Wissen lässt sich nun ganz schnell eine Basis-Applikation bauen, die aus der vorhandenen CodrPress-Collection Einträge ausliest und anzeigt.

Der bisherige Stand ist natürlich bei GitHub.

CodrPress

Ich bin mal wieder so wahnsinnig und arbeite an einem Blog-System. Diesmal will ich das Rad aber nicht neu erfinden und ein zweites Wordpress bauen. Stattdessen orientiert sich CodrPress an Schnitzelpress.

Da Schnitzelpress auf Ruby basiert und primär für den Einsatz auf Heroku ausgelegt ist, habe ich mich dazu entschieden mit CodrPress quasi eine PHP-Version von Schnitzelpress zu entwickeln.

Natürlich ist das wieder mal einfacher gesagt als getan, vor allem da es für diverse Ruby Gems, die Schnitzelpress nutzt, in der PHP-Welt kaum brauchbaren Ersatz gibt.

Mit Redcarpet und CodeRay hat Ruby zwei wundervolle Gems, die sich um Markdown-Rendering bzw. Syntax-Highlighting kümmern.

CodrPress basiert auf meinem Projekt MongoAppKit, das widerrum auf Silex sowie Twig setzt und seine Abhängigkeiten mit Composer regelt. Keine der PHP-basierten Lösungen, um diese zwei Ruby Gems zu ersetzen, bietet Composer-Unterstützung an und die Strukturen sind z.T. auch nicht PSR-0-kompatibel, so dass ein Autoloading der Klassen nicht möglich ist.

Daher habe ich zwei neue Projekte aus der Taufe gehoben, die genau diesen Mangel beseitigen:

SilexMarkdown

Da ich keine Lust und Zeit habe, selbst einen Markdown-Renderer zu schreiben, basiert SilexMarkdown auf php-markdown von Michel Fortin.

Ich musste es erstmal in brauchbare Struktur bringen, da das Original leider weder Namespaces nutzt und sogar zwei Klassen in einer Datei besitzt.

SilexMarkdown stellt nun eine Service-Prodiver-Klasse für Silex und eine entsprechende Twig-Extension zur Verfügung. Dazu wurde es noch mit einer Unterstützung für Code-Blöcke angereichert, um Syntax Highlighting wie in GitHub nutzen zu können.

Radiant

Die Kern-Komponente von Radiant ist ebenfalls nicht auf meinem Mist gewachsen und stammt aus dem Projekt Nijikodo von Clint Campbell.

Immerhin war die Grundlage schon mal PSR-0-kompatibel und damit auch relativ leicht Composer-tauglich zu machen.

Meine Arbeit bestand zum Großteil darin, entsprechende Unit-Tests zu schreiben und einige Fehler zu beseitigen und es in SilexMarkdown einzubinden.

Qualität

Alle genannten Projekte, also MongoAppKit, SilexMarkdown, Radiant und CodrPress werden mittels PHPUnit laufend von mir und automatisiert via Travis CI getestet. Abgesehen von SilexMarkdown beträgt die Code-Coverage zwischen 70 - 90%.

Style

Aktuell ist CodrPress mit dem vollständigen Twitter Bootstrap versehen, um auch in der Entwicklunsphase ein halbwegs ahnsehnliches Design zu haben. Später werde ich nur ein paar Komponenten aus Bootstrap nutzen, z.B. das Grid und die responiven Fähigkeiten.

Für das Syntax-Highlighting habe ich ein Farb-Theme basierend auf meinem Farbschema von PhpStorm geschrieben, das auch Radiant beiliegt. Dank einer recht einfachen Struktur kann man sich auch sehr schnell ein eigenes Theme zusammenstellen.

Ausblick

Die Frontend-Funktionen von CodrPress sind mit einer gefüllten Datenbank (Schnitzelpress-kompatibel) schon nutzbar. Homepage, Einzeldarstellung von Einträgen, eigene Seiten und das Markdown-Rendering mit Syntax-Highlighting funktionieren soweit einwandfrei.

Als nächstes werde ich einem Admin-Bereich und anschließend einem ansprechenden Design widmen.

Menlo Park, start your photocopiers ...

… oder warum Software-Patente und Patentkriege scheiße sind.

Gestern habe für die Share-Funktionen von Twitter, Google+ und Facebook jeweils ein Modul nach dem CommonJS-Standard gebaut, um sie in meinem privaten Weblog zu nutzen.

Daran ist nun nichts besonders, wenn ich nicht eine kleine Entdeckung gemacht hätte. Offenbar hat Facebook den nötigen JavaScript-Code von Twitter kopiert oder Twitter von Facebook.

Quelltext vom Twitter

!function(d, s, id) {
	var js, fjs = d.getElementsByTagName(s)[0];

	if(!d.getElementById(id)) {
		js = d.createElement(s);
		js.id = id;
		js.src = "//platform.twitter.com/widgets.js";
		fjs.parentNode.insertBefore(js, fjs);
	}
}(document, "script", "twitter-wjs");

Quelltext von Facebook:

(function(d, s, id) {
	var js, fjs = d.getElementsByTagName(s)[0];
	if(d.getElementById(id)) return;
	js = d.createElement(s);
	js.id = id;
	js.src = "//connect.facebook.net/de_DE/all.js#xfbml=1";
	fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));

Zwecks der Lesbarkeit habe ich die Funktionen entsprechend formatiert.

Selbst Nicht-Programmierern dürften die Ähnlichkeiten kaum entgehen. Die Variablennamen sind identisch und sogar die Art der URL-Angabe ohne Protokoll. Selbst das if-Conditional und damit die Methode das externe JavaScript nicht zweimal einzubinden, stimmen überein – nur die Schreibweise ist etwas anders.

Aus meiner Sicht geht dieses Vorgehen das vollkommen in Ordnung. Man muss nicht ständig das Rad neu erfinden. Gerade Programmierer tun das sehr gerne, obwohl es nur selten notwendig ist.

Twitter und Facebook sind Technologie-Vorreiter, neben Google die zwei wichtigsten im gesamten Netz – warum sollten sie also nicht gegenseitig voneinander profitieren? Auch wenn es nur um einen Code-Schnippsel geht, der externe JavaScripts lädt.

Andere Firmen (Hallo, Oracle!) holen selbst bei wesentlich geringeren Quelltext-Ähnlichkeiten gleich die Klage-Keule raus. Durch die Möglichkeit in den USA Patente auf Software zu bekommen, ist sowas sogar oft von Erfolg gekrönt …

Ich bin kein Verfechter von Open Source, auch wenn ich es grundsätzlich für eine gute Sache halte. Noch bin ich der Meinung, dass Software ein Allgemeingut wäre und jedem kostenlos zur Verfügung stehen müsse.

Jedem Entwickler muss das Recht zustehen, sein Produkt zu verkaufen und es schützen zu dürfen. Im Fall von Trivial-Patenten, geht es nicht mehr darum.

Man will nur noch der Konkurrenz schaden und Geld rausholen, obwohl man selbst oft mehr als genug hat. Firmen werden gekauft, um an die Patente zu kommen und anschließend andere Firmen mit Klagen zu überziehen.

Egal, ob Apple, Samsung, Motorola (Google), Nokia oder sonst wer. Hört endlich auf damit! Keiner eurer Kunden will Import-Verbote, absurd hohe Patentabgaben für verkaufte Geräte oder sonstige Auswüchse euer Advokaten-Armeen.

Aus Apples Sicht ist Android ein geklautes Produkt. Objektiv gesehen kann man dem sogar in Teilen zustimmen. Nur: na und?

Apple hat gute Ideen, Google hat gute Ideen. Nutzt sie, um euch gegenseitig zu verbessern und stellt diese lächerlichen Grabenkämpfe ein, die euch letztlich mehr schaden als nützen.

Responsive Bilder mit WordPress

Aktuell wage ich erste Gehversuche mit responsiven Layouts in meinem WordPress-Theme. Ziel der Übung ist ein smartphone-taugliches Layout. Leider macht einem WordPress die Arbeit bei Bildern unnötig schwer.

Automatische Bildskalierung mit CSS

Bilder müssen in responsiven Layouts automatisch mit der Breite des Anzeigegeräts skalieren. Feste Breiten würden hier zwangsläufig zu Darstellungsproblemen führen. Das klingt nun komplizierter als es ist. Mit drei Zeilen CSS lassen sich Bilder abhängig von der Breite ihres Eltern-Elements automatisch skalieren.

img {
	max-width: 100%;
}

Ein Bild darf also maximal so breit werden, wie seine vorgesehene Weite. Schrumpft das Eltern-Element durch Verkleinern des Viewports, wird das Bild automatisch mitskaliert. Wir müssen uns also um nichts weiter kümmern, da die Browser den Rest erledigen.

Problemfall WordPress

Leider klappt die automatische Skalierung in WordPress nicht. Wenn man in einem Beitrag Bilder einfügt, setzt WordPress automatisch das width- bzw. height-Attribut auf das img-Element. Sobald auch nur eines von beiden gesetzt ist, wird eine automatische Größenanpassung verhindert. Die Attribute müssen also weg.

Wie immer, gibt es dafür zig verschiedene Möglichkeiten. Beispielsweise könnte man die überflüssigen Element-Eigenschaften per jQuery-Einzeiler entfernen. Wirklich schön ist das aber nicht. Es wäre doch viel besser, wenn man WordPress dazu bringen könnte, den Quelltext gleich ohne width und height auszuliefern.

Dank des Hook-Systems in WordPress geht das mit ein paar Zeilen Code in der Datei functions.php des Themes:

add_filter('the_content', 'removeImageDimensions');

function removeImageDimensions($html) {
	return preg_replace('/(width|height)=\"\d*\"\s/', '', $html);
}

Die Funktion removeImageDimensions() entfernt per regulärem Ausdruck unsere unerwünchten Gäste width und height. Mittels add_filter() wird der WordPress-Funktion the_content() (gibt den Inhalt eines Beitrags aus) unsere neue Funktion als Ausgabefilter zugewiesen. WordPress führt nun bei jedem Aufruf von the_content() unsere neue Funktion removeImageDimensions() aus, die den Rückgabewert von the_content() entsprechend verändert.

Damit steht responsiven Bildbreiten nun nichts mehr im Weg.

Retina-Display-taugliche Icons mit CSS-Hintergrundbildern

Besitzer von Apple-Geräten mit Retina-Displays kennen das Dilemma: auf vielen Seiten sehen Hintergrundbilder, insbesondere Icons, reichlich unscharf aus. So erging es mir gleich doppelt mit meinem privaten Blog. Dank neuem iPad und MacBook Pro, vermatschen die Icons für externe Links.

Es gibt verschiedene Lösungsansätze für dieses Problem, beispielsweise Icons in vektorbasierten Formaten (SVG), die beliebig in jeder Pixeldichte skalieren können.

Da ich die verwendeten Icons nur als Bilder vorliegen habe, kommt diese Lösung nicht in Betracht. Dank CSS-Media-Queries ist das aber kein großes Problem.

Media-Queries?

Ein Media-Query ermöglicht es, innerhalb eines Stylesheets diverse Informationen zum aktuellen Anzeigegerät abzufragen. Dazu gehören unter Anderem die Minimal-Breite, die Ausrichtung (Portrait, Landscape), der Gerätetyp (Screen, Projection etc.), das Bildverhältnis oder in diesem Fall besonders wichtig, das Verhältnis zwischen vorhandenen und tatsächlich dargestellten Pixeln.

Was machen eigentlich Retina-Displays?

Retina-Displays verdoppeln die Auflösung, während die dargestellten Elemente gleich groß bleiben. Bei normalen Displays ist dieses Verhältnis 1:1. Eine Grafik mit 100 Pixeln Breite wird also mit 100 Pixeln auf dem Monitor dargestellt. Auf Apple-Geräten mit Retina-Displays verdoppeln sich die 100 Pixel auf 200 Pixel – das Verhältnis beträgt also 2:1.

Ist eine Grafik in der notwendigen Auflösung nicht verfügbar, wird das vorhandene Bild hochgerechnet und wirkt unscharf. Mit einem Media-Query können wir dem Browser eine höher aufgelöste Version zur Verfügung stellen, die genau das verhindert.

An die Arbeit

@media only screen and (min--moz-device-pixel-ratio: 2),
only screen and (-o-min-device-pixel-ratio: 2),
only screen and (-webkit-min-device-pixel-ratio: 2),
only screen and (min-device-pixel-ratio: 2) {
	#content article .content p a.external {
		background-image: url("icons/external-url-32.png");
		background-size: 16px auto;
	}
}

Da die Media-Query-Eigenschaft min-device-pixel-ratio noch nicht vollständig in allen Browsern implementiert ist, setze ich die entsprechenden Präfix-Versionen vorher ein. So kann man sichergehen, dass die proprietäre als auch die standardisierte Eigenschaft greifen – je nach aktuellem Stand der Browser-Implementation.

Mozilla greift hier zu einer recht seltsamen Präfix-Syntax, während Opera und Webkit sich an das bewährte Schema halten. Ob und wann der Internet Explorer die Eigenschaft unterstützt, konnte ich bisher nicht herausfinden.

Wie oben schon beschrieben, ist das Verhältnis von tatsächlichen Pixeln zu dargestellten Pixeln 2:1, daher wird es in der Bedingung mit einer 2 angegeben.

Innerhalb des Media-Queries kann ganz normales CSS verwendet werden. Ich tausche nun einfach das bisherige Hintergrundbild (16 x 16 Pixel) durch eine größere Version (32 x 32 Pixel) aus und setze die Größe des Hintergrundbildes auf 16 Pixel. Ansonsten würde der Browser eine falsche Annahme treffen und das Bild auf 64 x 64 Pixel hochrechnen. Damit wäre alles für die sprichwörtliche Katz.

Das war’s schon. Sofern man ein passendes Gerät hat und der Browser die Media-Query-Eigenschaft unterstützt, bekommt man nun ein schön hoch aufgelöstes Icon zu sehen, das um Welten besser aussieht als der hochgerechnete Pixelmatsch.

PHP-Autoloader nach dem PSR-0-Standard

Wenn’s um das Schreiben eines Autoloaders in PHP geht, kochen viele Entwickler gern ihr eigenes Süppchen. Sofern nun mehrere gleichzeitig aktiv sind, kann das zu Problemen führen und ggf. sogar dafür sorgen, dass eine Alternative gesucht werden muss.

Die PHP Framework Interoperability Group (kurz FIG) hat sich dieses Problems angenommen und eine Spezifikation für Autoloader entwickelt, die Interoperabilität sicherstellt.

Der PSR-0-Standard besteht aus ein paar recht simplen Regeln, die sich sehr einfach umsetzen lassen und z.T. sicher schon von vielen genutzt wurden:

  • Ein qualifizierter Namespace hat folgende Struktur \<Vendor Name>\(<Namespace>\)*<Class Name>
  • Jeder Namespace hat einen Haupt-Namespace (Vendor Name)
  • Jeder Namespace kann beliebig viele Unter-Namespaces besitzen
  • Jeder Namespace-Separator wird in einen DIRECTOR_SEPARATOR konvertiert, wenn aus dem Dateisystem geladen wird
  • Das Zeichen “_” (Underscore) wird in einen DIRECTORY_SEPARATOR konvertiert und hat keine spezielle Bedeutung.
  • Der qualifizierte Namespace inkl. Klasse bekommt die Endung “.php” angehängt, um die Datei zu laden.
  • Namespaces, Vendor Names und Klassennamen dürfen alphabetische Zeichen in jeder Kombination aus Groß- und Kleinschreibung enthalten.

Beispiel-Implementation

<?php

namespace WebCodr;

class Loader {

    public static function registerAutoloader() {
        return spl_autoload_register(array ('WebCodr\\Loader', 'load'));
    }

    public static function load($class) {
        if(substr($class, 0, 7) !== 'WebCodr') {
            return;
        }

        $libraryRoot = realpath(__DIR__ . '/../');
        $classFileName = str_replace(array('\\', '_'), DIRECTORY_SEPARATOR, $class) . '.php';
        $fileName = realpath($libraryRoot . DIRECTORY_SEPARATOR . $classFileName);

        if(is_readable($fileName)) {
            include_once($fileName);
        }
    }
}

Die Klasse stellt zwei statische Methoden bereit. Mit Loader::registerAutoloader() wird die Methode Loader::load() als SPL-Autoloader registriert.

Loader::load() prüft zuerst, ob sich die angeforderte Klasse überhaupt in Namespace WebCodr befindet. Falls dies nicht der Fall ist, wird durch den leeren Rückgabewert signalisiert, dass die Klasse mit diesem Autoloader nicht geladen werden kann und die SPL geht zum nächsten registrierten Autoloader über.

Anschließend wird der Pfad zur Klasse zusammengesetzt und die Datei mittels include_once() eingebunden. Optinal könnte man im Fehlerfall natürlich noch eine Exception werfen.

Aufruf des Autoloaders

<?php

include_once('Loader.php');
\WebCodr\Loader::registerAutoloader();

Fazit

PSR-0 ist schnell und einfach implementiert. Für neue Projekte gibt es also keinen Grund, sich nicht daran zu halten. In bestehendem Code könnte es recht aufwendig sein, den Standard umzusetzen – je nach dem, welche Benamungsschemata und Verzeichnisstrukturen bereits verwendet werden.

Man sollte den Aufwand aber nicht scheuen. Was bringt einem schon die tollste Library oder ein cooles Framework, wenn es aufgrund eines schlecht implementierten Autoloaders, kaum eingesetzt werden kann?

IHK != agile

Während der Berufsschule wird einem klassisches Projekt-Management eingetrichtert und immer wieder betont wird, wie wichtig das doch wäre. Dabei wird natürlich nach dem Wasserfallmodell vorgegangen, Gantt-Diagramme gezeichnet (wer braucht bei einer IT-Ausbildung schon Rechner?), mit Netzplänen die Dauer einzelner Schritte in Tagen ausgerechnet (von Hand wohlgemerkt, dementsprechend falsch sind die dann auch meistens) oder die Schüler auf andere Weise mit Dingen malträtiert werden, die zunehmend für die Software-Branche als unbrauchbar betrachtet werden.

Bei Software-Projekten nach dem Wasserfallmodell vorzugehen, hat sich immer wieder als nicht praktikabel herausgestellt. Für Bau-Ingenieure, bei denen sich alles vorher planen und ausrechnen lässt, mag das wunderbar funktionieren.

In der Software-Entwicklung lässt sich aber nun mal nichts vollständig planen. Schon gar nicht zeitliche Angaben, wie sie die IHK für das Projekt auf eine halbe Stunde genau will.

Unvorhergesehene Dinge treten immer auf. Dabei können ganze Lösungsansätze vollkommen eliminiert werden und die Arbeit von Tagen, manchmal sogar Wochen war vergebens.

Oft verursachen selbst keine Fehler große Verzögerungen. Darum versagen klassische Modelle. In solchen Situationen sind sie zu starr und erlauben keine angemessene Reaktion, um das Problem schnell anzugehen. Seit mittlerweile 20 Jahren werden Alternativen entwickelt bzw. setzen sich zunehmend durch.

Agil arbeiten

Als Software-Projekte immer größer bzw. komplexer wurden und damit auch die Probleme durch klassisches Projektmanagement, entstanden die Überlegungen zu agilen Entwicklungsmethoden. Sie sind deutlich flexibler und leichtgewichtiger als klassische Prozesse und widmen sich auch den sozialen Aspekten der Software-Entwicklung.

An dieser Stelle tiefer in das Thema einzusteigen, würde den Rahmen deutlich sprengen, daher verweise ich auf die Blogs meiner Kollegen Dominik Jungowski und Peter Roessler sowie Agile42.

Auch wenn ich erst seit dem Wechsel zu Chip Online mit agiler Entwicklung in Berührung gekommen bin, möchte ich nicht mehr darauf verzichten.

Ich durfte vorher oft genug erleben, wie herkömliche Methoden versagt haben und mir als Programmierer nur Steine in den Weg legten, während agile Methoden Spaß machen und darauf ausgelegt sind, dass ich meine Arbeit angenehmer und besser erledigen kann.

Genau darum verstehe ich auch nicht, warum während der Ausbildung Dinge gelehrt werden, die in jeder größeren längst Software-Schmiede abgeschafft wurden.

Zumal es auch öfter der Fall sein dürfte, dass Auszubildende eigentlich agil in ihrem Betrieb arbeiten, aber in der Berufsschule mit klassischem Projektmanagement konfrontiert werden.

Man sollte noch darauf im Lehrplan eingehen, aber als Negativbeispiel um die Vorteile agiler Entwicklung aufzuzeigen.

Ich wage sogar zu behaupten, dass ein Auszubildender aus unserem hausinternen Workshop zu agilen Methoden innerhalb eines Tages mehr mitnehmen kann, als nach den 60 Tagen Berufsschule des ersten Jahres, in denen Projektmanagement, Wasserfallmodell & Co über sie herfallen.

Übrigens: auch den Lehrern könnte etwas mehr Agilität nicht schaden. Es ließen sich ganz andere Wege beschreiten, die Spaß machen und den Schülern Wissen viel effektiver bzw. dauerhaft vermitteln können … aber das ist ein anderes Thema, über das ich evtl. in einem weiteren Eintrag genauer eingehen werde.

PHP-Tip: Limonade

Wer sich schon mal mit Symfony oder ähnlichen PHP-Frameworks beschäftigt hat, kam sicher schnell zur Erkentnis, dass das die Dinger zwar viel können und generell eine tolle Sache sind, aber hohe Einstiegshürden haben bzw. viel Einarbeitungszeit benötigen, sowie für viele Projekte einfach überdimensioniert sind.

In Ruby hätte man für solche Fälle z.B. Sinatra: übersichtlich, klein, schnell und flexibel. Mit Limonade gibt es so ein Micro-Framework nun endlich für PHP, mit dem sich auch ähnlich elegant entwickeln lässt.

Es reicht eine Datei in ein Script einzubinden, die .htaccess-Datei anzupassen und schon kann man loslegen:

<?php

require_once 'vendors/limonade.php';

// lambda function (>= PHP 5.3)
dispatch('/hello/:name', function() {
    $name = params('name');

    return "Hello, {$name}";
});

run();

Das war es schon. Den ganzen Rest erledigt Limonade und das war noch lange nicht alles, was es kann. Die Routen-Definitionen können beispielsweise auch Wildcards oder reguläre Ausdrücke enthalten. Als Callback lassen sich selbstverständlich auch Objekte bzw. deren Methoden aufrufen (auch statisch) oder man gibt in klassischer Manier einen Funktionsnamen als String an.

Dazu gibt es eine Template Engine mit partiellen Templates, Capture-Möglichkeiten, JSON-Unterstützung, Hooks und Filtern.

Alles davon lässt sich ohne große Einarbeitung sofort nutzen. Natürlich muss man die integrierte Template Engine nicht nutzen und kann auch stattdessen einfach Twig oder Smarty verwenden.

Schnellere Websites mit RequireJS

Script-Elemente sind Blocker im Rendering-Prozess. Browser arbeiten den Quelltext einer Seite von oben nach unten durch. Wenn ein script-Element auftaucht, muss es erst ausgeführt werden, bevor der Browser sich um den nachfolgenden Quelltext kümmern kann. Externe JavaScripts können aus diesem Grund massiven Einfluss auf die Ladegeschwindigkeit einer Seite haben, auch wenn die eigentliche Website schon längst vom Web-Server an den Browser ausgeliefert wurde.

Falls ein externes Script auf einem langsamen Server liegt, muss der Browser warten, bis er es komplett abgerufen und ausgeführt hat. Sollte das aufgerufene Script gar nicht mehr vorhanden sein, wartet der Browser seine Timeout-Einstellung ab, bis er weitermacht. Sicherlich hat jeder schon mal gesehen, dass eine Website nur bis einem gewissen Teil angezeigt wird und erst nach ein paar Sekunden der Rest dargestellt wird. Mit sehr hoher Wahrscheinlichkeit, war dies ein nicht mehr vorhandenes oder nur sehr langsam ladendes JavaScript.

Asynchrones Abholen findet erst nach dem Rendern der Seite statt und blockiert daher nichts. Dazu lässt sich bei konsequentem Nutzen von RequireJS der Einsatz von script-Elementen weitgehend vermeiden. Im Idealfall gibt es nur noch ein script-Element, das RequireJS lädt und ein weiteres Script startet, um die gesamte JavaScript-Funktionalität einer Seite zu initialisieren.

Konfiguration

Hier ein simples Beispiel einer Require-JS-Konfiguration mit jQuery:

(function() {
    require.config({
        paths: {
            'jquery': 'libs/jquery/jquery-1.7.1'
        }
    });

    require(['jquery'], function(jQuery){
      jQuery.noConflict();
    });
})();

Zuerst wird RequireJS so konfiguriert, dass jQuery mittels des Keywords “jquery” geladen werden kann. Im Konfigurations-Objekt “paths” wird dafür als Attributsname “jquery” und als Wert der Pfad (ausgehend vom Verzeichnis des Scripts) zu jQuery gesetzt. Anschließend wird require() aufgerufen und als erster Parameter wird ein Array mit den aufzulösenden Abhängigkeiten erwartet – hier jQuery. Man kann hier ein unter “paths” gesetztes Keyword, den Pfad oder auch externe JavaScripts über die URL angeben. Falls es sich um ein Modul handelt, wird auf die Dateiendung “.js” verzichtet.

Im zweiten Parameter wird eine Callback-Funktion definiert, deren Aufruf unmittelbar nach dem Laden der Abhängikeiten stattfindet. Als Parameter folgen hier die Rückgaben der angeforderten JavaScript-Dateien. In diesem Fall ist es ein jQuery-Objekt, das sogleich in den No-Conflict-Modus versetzt wird, um nicht mit evtl. anderen geladenen Bibliotheken zu kollidieren. Dieses Verfahren ist besser bekannt als Dependency Injection.

Module

RequireJS bietet über die Modul-Definition eine elegante Lösung, Programmbestandteile zu kapseln und Abhängigkeiten (z.B. jQuery) aufzulösen. Aktuell setze ich hier drei Module ein, die Funktionen für einzelne Beiträge (Syntax Highlighting, Lightbox etc.) übernehmen sowie das Tracking über Piwik bzw. Google Analytics starten.

Alle Module sind so aufgebaut, dass sie Abhängigkeiten in Form von Bibliotheken oder anderen Modulen erst auflösen, wenn sie gebraucht werden. Gleichzeitig stellt RequireJS sicher, dass eine Abhängikeit nur einmal geladen wird und für jedes andere Modul zur Verfügung steht.

Ein Beispiel-Modul:

define(['jquery'], function($) {
    var exports = {};

    exports.init = function() {
        $('body').append('<h1>Hello World!</h1>');
    }

    return exports;
});

Mit define() wird die Moduldefinition gestartet. Anschließend passiert das gleiche, wie in der Funktion require(). Zuerst werden die Abhängigkeiten als Array definiert, danach startet eine Dependecy Injection in die Callback-Funktion. An dieser Stelle wird ein Dollarzeichen als Parameter für die Abhängigkeit verwendet, um jQuery wie gewohnt einsetzen zu können. Anschließend wird das Objekt “exports” mit der Methode init() erstellt und zurückgegeben. Wird das Modul an anderer Stelle über require() geladen und das exports-Objekt in die Callback-Funktion injiziert, kann man in ihr die init-Methode aufrufen und damit das Modul starten. Natürlich es ist aber auch möglich, darauf zu verzichten und innerhalb der Modul-Callback-Funktion direkt Code auszuführen.

Um ein Modul an anderer Stelle mit require() zu laden, gibt man den Pfad, ausgehend vom Verzeichnis der RequireJS-Konfiguration, zum Modul an. Wie vorhin schon beschrieben, muss man hierbei auf die Dateiendung “.js” verzichten.

Zu guter letzt muss RequireJS selbst samt Konfiguration geladen werden:

<script src="js/libs/require/require.js" data-main="js/config"></script>

Ein simples Script-Element hierzu genügt – am besten unmittelbar vor dem schließenden body-Element. Zusätzlich wird mit dem Daten-Attribut “main” noch der Pfad zur Konfiguration mitgegeben. RequireJS bindet das entsprechende JavaScript automatisch ein und startet sie.

Fazit

RequireJS ist ein mächtiges Tool, das alleine schon durch die Kapselung in verschiedene Module dem Entwickler sehr viele Vorteile bringt. Durch die immer größer werdende Komplexität von Web-Sites bzw. Web-Applikationen sind andere Formen der Organisation notwendig geworden, die in anderen Programmiersprachen schon von Anfang an vorhanden waren.

Dank der Module mit ihren von RequireJS automatisch aufgelösten Abhängigkeiten durch Dependency Injection, lassen sich schnell und einfach, neue Funktionalitäten in eine Website implementieren, ohne weitere script-Elemente in den HTML-Quelltext einfügen zu müssen.

Bis auf RequireJS selbst werden alle definierten Abhängigkeiten bzw. Module asynchron geladen, ohne das Rendern der Seite zu blockieren. Da Browser asynchrone Anfragen parallel abarbeiten können, werden insgesamt alle Scripts schneller geladen und früher ausgeführt, als es bei einer traditionellen Verwendung von JavaScript möglich wäre.

Das Ergebnis ist ein großer Vorteil für alle Beteiligten. Für den Nutzer wird eine Website mit viel JavaScript deutlich schneller dargestellt, während die Funktionalitäten im Hintergrund nachgeladen werden, ohne dass man etwas davon bemerkt. Aus Sicht der Entwickler bietet RequireJS eine elegante Möglichkeit, schnelle neue Funktionen zu entwickeln, Abhängigkeiten einfach aufzulösen und durch die Module strukturierter zu arbeiten.

Abgesehen von TypeKit und Disqus laufen alle JavaScript-Bestandteile dieser Seite als Modul. Zwar lässt sich TypeKit problemlos als Modul umsetzen, nur werden die Schriften erst nach dem Rendern der Seite geladen. Für etwa eine halbe Sekunde sind die Fallback-Schriften zu sehen, erst dann die über TypeKit-Schriften dargestellt. Da das nicht Sinn und Zweck von TypeKit und Euch Leser irritiert, wird TypeKit klassisch im head-Element vor den Stylesheets geladen.

Disqus ist dagegen als Wordpress-Plug-In eingebunden und darauf würde ich auch nur sehr ungern verzichten. Ich könnte das Plug-In zwar anpassen, aber damit wäre die Update-Fähigkeit dahin. Da Disqus seine Scripts selbst asynchron lädt, sehe ich auch keine Notwendigkeit, den ganzen Aufwand zu betreiben und Module zu schreiben. Allerdings habe ich andere Plug-Ins wie die Lightbox und das Syntax Highlighting rausgeworfen und durch RequireJS-Module ersetzt, die nun fester Bestandteil des Themes sind. Geladen werden sie aber nur, wenn auch Beiträge vorhanden sind, die eine der Funktionen brauchen.

Falls ich Euch Interesse zu RequireJS geweckt habe, könnt Ihr meine Implementierung inkl. eine Ladesystems für Module aus dem HTML-Quelltext heraus im GitHub-Repository meines Wordpress-Themes anschauen oder auch einen Fork erstellen, um damit selbst entwickeln zu können. Falls jemand Ideen oder Verbesserungen hat, ich freue mich über jede Anregung und jeden Pull-Request in GitHub.

Upload-Probleme mit PHP via FastCGI

Als ich eben eine neue Galerie in mein privates Weblog hochladen wollte, begrüßte mich bei jedem Versuch ein HTTP 500, besser bekannt als Internal Server Error. Die Meldung ist absolut nichtssagend und es lässt nur über Log-Dateien rausfinden, was eigentlich passiert.

Das Problem besteht offenbar seit dem Umzug auf einen virtuellen Server bei Host Europe mit Ubuntu 10.04 LTS und Plesk zur Verwaltung. In Plesk wird PHP standardmäßig via mod_php in den Apache eingebunden. Da das aber u.U. Rechteprobleme zwischen dem Apache-User und dem FTP-User bei von PHP angelegten Dateien geben kann, lasse ich PHP via FastCGI laufen. Das braucht zwar mehr RAM, hat aber den Vorteil, dass der PHP-Prozess und FTP-Zugang über den gleichen Nutzer laufen. Im Gegensatz zu suPHP funktionieren damit auch Opcode Caches wie APC und es muss nicht für jede Anfrage auf ein Script ein neuer PHP-Prozess gestartet werden.

Nach etwas Recherche, war die Ursache aber schnell klar. Um mit FastCGI arbeiten zu können, verwendet der Apache das Modul mod_fcgid, das folgenden Fehler auslöst:

mod_fcgid: HTTP request length 1019250 (so far) exceeds MaxRequestLen
(131072)

Sprich: sämtliche HTTP-Anfragen, deren Länge mehr als 128 KB beträgt, werden durch FastCGI nicht zugelassen. Wie man sieht, war der Request knapp 1 MB groß, was bei größeren Bildern in ordentlicher Qualität schnell passiert.

Um das Limit zu erhöhen, muss man in die Modul-Konfiguration unter /etc/apache2/mods-available/fcgid.conf eingreifen und folgenden Eintrag hinzufügen bzw. entsprechend verändern:

MaxRequestLen 2097152

Damit wird die Beschränkung auf 2 MB erhöht. Sollte für die meisten Zwecke mehr als ausreichen. Anschließend muss der Apache neu gestartet werden, damit die Änderung wirksam wird.

Sublime Text 2

Als Entwickler ist man ja immer auf der Suche nach noch besseren Werkzeugen, um seine Arbeit schneller, besser und schöner zu erledigen. Das wichtigste Tool dafür ist ein guter Editor. Unter OS X setzte ich immer auf TextMate und falls kein Mac zur Verfügung stand, war Notepad++ unter Windows das Mittel der Wahl.

Leider wird TextMate kaum noch weiterentwickelt. Der letzte Release ist Monate her und TextMate 2 grenzt schon fast an Vaporware. Im Büro (sowohl früher als auch jetzt bei Chip) habe ich nun seit über einem halben Jahr auch einen Mac, daher kommt Notepad++ nicht als Alternative in Frage. Diverse Versuche mit Wine bzw. WineBottler haben sich als Schuss in den Ofen erwiesen.

Zu meinem großem Glück bin ich dann zufällig über Sublime Text 2 gestolpert. Zuerst dachte ich, es wäre nur ein billiger TextMate-Klon, aber weit gefehlt. Das Ding ist richtig toll.

Warum? Darum:

  • Läuft unter OS X, Windows und sogar Linux.
  • Kann von Haus aus eine Menge, ohne überladen zu sein (gutes Syntax-Highlighting, umfangreiche Suchfunktionen, Code-Folding, Code Completion etc.)
  • Bis ins letzte Detail konfigurierbar (nur über JSON-Files, keine GUI bisher).
  • Erweiterbar über sog. Packages, die es für so ziemlich alles gibt, von ActionScript über Git bis Zen Coding (ein Großteil der TextMate Bundles ist kompatibel!)
  • Sehr schnell und keine Probleme mit extrem langen Zeilen (TextMate gerät da teilweise unglaublich ins Stocken bishin zum Absturz).
  • Tabs (ja, die kann TextMate auch, aber nur innerhalb von Projekten). *Regelmäßige Updates.

Wie effizient man mit Sublime Text und ein paar kleinen Tools entwickeln kann, zeigt Andrey Tarantsov in einem sehr schönen Video. Da zeigt sich auch mal wieder, dass für Web-Entwickler OS X immer noch die beste Plattform ist — viele Tools gibt es für Windows schlichtweg nicht. Mal davon abgesehen, dass Arbeiten mit einem Terminal immer noch eine Qual ist. Cygwin ist hier zwar ein Segen, aber auch nicht allmächtig, da es nicht für alles wichtige kompatible Versionen gibt (Stichwort Ruby Gems …).

Da Sublime Text 2 Shareware ist und beliebig lange getestet werden kann, rate ich jedem Entwickler ihn zumindest auszuprobieren — es lohnt sich. Und wer schon immer einen TextMate-ähnlichen Editor für Windows oder Linux gesucht hat: hier ist er!