Minification de fichiers JavaScript


19 février 2013

Le problème

Considérant un dossier de fichiers JS dont un sous-ensemble est quotidiennement modifié, le problème de leur minification se pose : je veux que ces fichiers minimisés puissent être générés automatiquement en utilisant yui-compressor (pour l'instant). En Bash, ça donne :

find . -maxdepth 1 -name "*js" | while read f; do
    [[ `stat -c %Y minified/${f%.js}-min.js` -gt `stat -c %Y $f` ]] && continue;
    echo $f
    yui-compressor --type js --charset utf-8 -v -o minified/${f%.js}-min.js $f;
done 2> minified.log

Le meilleur compresseur

Même si le script précédent fonctionne, YUI-Compressor n'est pas terrible (ou je ne sais pas l'utiliser). Je vais donc m'orienter vers le Google Closure Compiler qui est plus lent à l'exécution (10x) mais BEAUCOUP plus intéressant. A noter que l'on peut utiliser deux modes pour ce minificateur : le mode par défaut (sans option particulière) et le mode ADVANCED_OPTIMIZATIONS, qui donne des résultats très intéressants. Par contre, le code à minimiser/obfusquer doit être adapté. Ainsi, pour utiliser le mode ADVANCED_OPTIMIZATIONS et obtenir ainsi des scripts extrêmement petits et obfusqués, il faut :

  • ajouter, selon le format jsDoc, le fait qu'une fonction est un constructeur : ainsi, elle pourra utiliser "this" sans warning de la part de Google Closure. D'autres types d'annotations sont disponibles.
  • ajouter window["MonConstructeur"] = MonConstructeur;
  • si le code utilise un hash externe il faut utiliser la notation des tableaux a.["nom-du-membre"] plutôt que la notation objet : a.nomDuMembre
  • on n'écrit PLUS JAMAIS :
    MonObjet.prototype = { ... }; MonObjet.prototype.constructor = MonObjet;
    mais on va déclarer une par une toutes les propriétés d'un objet, aussi bien les méthodes que les variables comme indiqué par les 4 points suivants
  • pour les fonctions publiques :
    MonObjet.prototype["NomDeLaFonctionPublique"] = function(...) { ... Corps de la fonction publique ... };
  • pour les fonctions privées :
    MonObjet.prototype.NomDeLaFonctionPrivée = function(...) { ... Corps de la fonction privée ... };
  • pour les variables publiques :
    MonObjet.prototype["NomDeLaVariablePublique"] = ...;
  • pour les variables privées :
    MonObjet.prototype.NomDeLaVariablePrivée = ...;

L'appel au compilateur/minificateur, installé dans mon répertoire utilisateur, se fait avec la ligne ci-dessous, avec $f contenant le chemin du fichier JS à compresser :
java -jar ~/closure/compiler.jar --charset UTF-8 --compilation_level ADVANCED_OPTIMIZATIONS --js "$f" --js_output_file ${f%.js}-min.js

Résultats

...

Véritable type JS


16 novembre 2012

Un post très intéressant de Angus Croll m'a donné envie de m'inspirer de sa fonction permettant de déterminer le type d'un objet JS en utilisant la propriété interne [[class]] (suivant la spec officielle). Ainsi la fonction suivante :

Object.prototype.kindOf = function()        { return ({}).toString.call(Obj).slice(8, -1); }
Object.prototype.isKindOf = function(targetKind)        { return ({}).toString.call(Obj).slice(8, -1) == targetKind; }
Object.defineProperty(Object.prototype, "kindOf", {enumerable:false});
Object.defineProperty(Object.prototype, "isKindOf", {enumerable:false});
//this == global object
this.kindOf = function(obj) { return obj.kindOf(); }

On peut donc l'utiliser comme suit :

if (kindOf(obj) == "Array") ...     //Utilise l'objet global, syntaxe identique à typeof
if (obj.isKindOf("Array"))  ...     //Syntaxe objet incorporant la comparaison
/* ... */
switch (MonObjet.kindOf())      {   //Syntaxe objet
    case    "Object": ...
    case    "Number": ...
    case    "CustomConstructor": ...
    ....
}

A noter que String::slice est beaucoup plus rapide que la RegExp et que la passage en minuscule ne m'apportant rien je l'ai supprimé. Cf mes profiles.

L'abbréviation ({}) en lieu et place de object.prototype est intéressante : un nouvel objet (le {}) utilisera toujours la fonction toString de Object.prototype. Donc on crée un objet temporaire que l'on va utiliser pour accéder à la fonction toString ce qui a pour conséquence : 1) d'ajouter un objet en mémoire 2) de diminuer le nombre de lookups car Object.prototype.tooString entraîne 2 lookups et ({}).toString un seul. Conclusion : moins de temps machine et plus de mémoire instantanée. Du Heisenberg informatique classique...

Et au fait : pourquoi ({}) est l'équivalent de Object.prototype ? En fait, les deux ne sont pas équivalents, seules les références à toString sont identiques, ce que l'on peut vérifier en testant l'expression ({}).toString === Object.prototype.toString. La notation est étrange mais surtout parce qu'il y a des parenthèses autour de l'objet littéral. Pourquoi ces parenthèses ? Parce que si on écrit {}.toString, cette écriture est ambigue : les accolades représentent un bloc de code vide ou un objet littéral ? Un "." juste après un bloc de code vide ne fonctionne pas... On lève cette ambiguité en ajoutant les parenthèses autour des accolades : cette fois il ne peut plus s'agir d'un bloc de code vide.

Lecteur web de flux RSS


6 novembre 2012

Présentation

Lisant beaucoup d'actualités sur Internet, je me suis intéressé aux flux RSS et ai réalisé un site permettant de constituer ma propre liste de lecture. Ainsi, je me rends sur une page web qui gère tous mes abonnements RSS et me permet de lire de ces sites depuis un endroit unique : plus besoin de s'embêter à ouvrir des tonnes de pages, à subir des pubs, ça diminue la bande passante utilisée et ça tient compte de ce que j'ai déjà lu !

Accès à la démo...

Vous pouvez tester la chose sur un compte démo ou même vous créer un compte si le site vous plaît :)

La technique

La totalité de l'application est réalisée en javascript et PHP : le javascript qui gère la mise en page et les interactions Ajax avec le serveur. C'est le serveur qui télécharge les documents XML, les RSS, depuis les sites externes et qui les analyse avant de les renvoyer à l'application JS.

Tout cela fonctionne bien et m'a fait prendre conscience que je pouvais aller encore plus loin avec une application iOS que je suis en train de faire et une couche serveur qui va pousser encore plus l'analyse. Cette application devrait sortir avant fin mars 2015.

Droits harmonieux des fichiers d'un serveur


4 novembre 2012

Pour disposer d'un dépôt de fichier possèdant des droit pas trop laxistes et pas trop restrictifs, j'utilise INotify sur mes serveurs web. �a permet de ne pas tout mettre en root (si, si, j'en connais beaucoup qui font ça) et de ne pas tout mettre en 0777 (�a c'est pour Magento et ses problèmes de droits imbéciles). Pour mettre ça en place, il suffit d'utiliser INotify et d'exécuter le script qui suit et tout est magique :)

#!/bin/bash

(inotifywait -mqr --format "%w%f" --excludei ".*\.log" -e create /home/www |
        while read item; do
                echo "$item";
                DirName=`dirname "$item"`;
                #Tous les éléments fils de /home/www doivent avoir www-data comme propriétaire (Apache) et www-coder comme groupe ou le groupe du dossier parent
                ParentGroup=`stat -c '%G' "$DirName"`
                chown www-data:$ParentGroup "$item";
                #
                chmod 0460 "$item";
                #
                [[ -d "$item" ]] && chmod ug+x "$item";
                #Si le dossier parent possède un droit en écriture pour le propriétaire, alors on règle le même droit pour le nouveau fichier (dossiers d'uploads, logs etc)
                IsParentFolderWritable=`stat -c '%A' "$DirName" | cut -c 3`;
                [[ $IsParentFolderWritable == "w" ]] && chmod u+w "$item";
        done
) &

Le résultat ? Dès qu'un fichier est créé dans un (sous-)dossier quelconque de mon espace regroupant tous mes sites web, il possède automatiquement les droits 460. Pour un dossier ça sera 570. Le propriétaire est toujours www-data (L'utilisateur exécutant Apache) et le groupe est toujours www-coder. Ainsi si l'utilisateur crée un fichier avec un touch ou à l'aide d'un IDE comme Kate ou Coda et que cet utilisateur fait partie du groupe www-coder, toutes les opérations sont transparentes et il n'aura pas besoin de faire le moindre chmod/chown. Liberté...

�a n'est pas tout : par défaut Apache ne peut pas modifier un nouveau fichier et ne peut pas non plus créer quoi que ce soit dans un nouveau dossier (le propriétaire, Apache, peut juste lire l'élément). C'est important car ça évite beaucoup de problèmes). Que faire si on a un dossier "upload" dans lequel Apache doit pouvoir écrire des choses ? Il suffit de faire un chmod u+w sur ce dossier et tous les fichiers/dossiers qui seront créés disposeront également de ce droit. Magique je vous dit :)

L'étape d'après est que chaque site sous /home/www puisse avoir un groupe différent et que chaque dossier/fichier créé à l'intérieur au lieu d'être affecté à www-data:www-coder puisse être affecté au groupe de son parent. Ainsi, chaque fichier continuera d'avoir pour propriétaire www-data mais aura codersite1 ou codersite2 comme groupe. La granularité est (?) optimale.

Faire la suite avec ma nouvelle version :
#!/bin/bash

killCommand()       {
    kill $INotifyPID
    rm -f $PipePath
    exit
}

handleItem()        {
    item=$1
    echo "$item";
    DirName=`dirname "$item"`;
    #Tous les éléments fils de /home/www doivent avoir www-data comme propriétaire (Apache) et www-coder comme gro ou le groupe du dossier parent
    ParentGroup=`stat -c '%G' "$DirName"`
    chown www-data:$ParentGroup "$item";
    #
    chmod 0460 "$item";
    #
    [[ -d "$item" ]] && chmod ug+x "$item";
    #Si le dossier parent possède un droit en écriture pour le propriétaire, alors on règle le même droit pour le nouveau fichier
    IsParentFolderWritable=`stat -c '%A' "$DirName" | cut -c 3`;
    [[ $IsParentFolderWritable == "w" ]] && chmod u+w "$item";
}

PipePath="/tmp/adjustWWW.pipe"
mkfifo $PipePath

inotifywait -mqr --format "%w%f" --excludei ".*\.log" -e create,close_write /home/www > $PipePath &
INotifyPID=$!
trap "killCommand" 2 3 15

while read item; do
    handleItem $item &
done < $PipePath

killCommand
Source : http://kerlinux.org/2010/08/utilisation-de-inotifywait-dans-des-scripts-shell/

Comparaison de dates en (My)SQL


1 novembre 2012

Il arrive souvent qu'on veuille extraire des enregistrements d'une table en fonction d'un critère temporel issu d'une autre table, ex : toutes les lignes créés après la date X. L'écriture la plus simple et la plus explicite pour cela ressemble à celle qui suit. L'exemple choisi est celui d'un commerce qui stocke ses factures liées à des clients et qui veut obtenir toutes les factures qui devraient être payées, c'est-à-dire celle dont le délai de paiement est dépassé. La "difficulté" ici est que le délai de paiement dépend du client.

select f.montant from Factures as f join Clients as c on f.client=c.id where f.dateDeCreation + INTERVAL c.delai DAY > now();
1 2 3 4 5 6 7 8 9 10 11 12..... 18 19 20