Une version optimisée pour PHP de JSMin

Les Javascripts permettent de faire tout pleins de choses sur un site web, c’est cool. Mais à force d’empiler les librairies, les frameworks et autres fonctions, on peut vite se retrouver avec deux kilo-tonnes de script, ce qui n’est ni très agréable à charger et ni très léger pour la bande passante.

Heureusement, plusieurs librairies (plus ou mois efficaces) existent et permettent de compresser les scripts. JSMin est l’une d’entre elle.

A l’origine, JSMin est un petit programme écrit en C par Douglas Crokford. Il prend un fichier Javascript et supprime tout ce qui n’est strictement nécessaire, c’est-à-dire :

  • les commentaires (/* */ et //),
  • les lignes vides et retour à la ligne,
  • les espaces (sauf dans les chaines de caractères et les expressions régulières).

Contrairement à d’autres packer, JSMin ne brouille pas le code, c’est-à-dire que le fichier compressé reste lisible, débuggable et modifiable (même si une petite ré-indentation est nécessaire), ce qui est quand même un avantage très appréciable.

Il existe une version PHP de JSMin, mais, comme son auteur l’indique, il s’agit d’une « conversion simple et brutale du C vers le PHP », entendez par là qu’elle n’est pas du tout optimisée et donc super lente. Evidemment, la version C est théoriquement utilisable en PHP via la fonction shell_exec, mais c’est totalement impensable sur un hébergement mutualisé.

Récemment, j’ai dû mettre en place un système de compression et de concaténation des javascripts « à la volée » ; c’est-à-dire que les javascripts sont compressés la première fois qu’un utilisateur les demande, puis mis en cache (vaguement inspiré de ce post). Dès qu’un fichier est mis à jour, le cache est regénéré. Ainsi, je conserve les versions commentées et indentées pour développer, et je n’ai pas à me soucier de les compresser avant de les mettre en ligne. C’est magique.

Mais, la version originale de JSMin en PHP met parfois plus de 3 secondes à compresser certains fichiers, ce qui est bien trop long. Donc je n’ai pas pû m’empêcher de faire un tour dans le code pour l’optimiser. J’ai remplacé toutes les boucles de lecture caractère par caractère (typique du C) par des fonctions natives PHP comme strpos et strlen.

J’ai également viré toute la partie qui concernait les fichiers (dans la version originale, il est possible de passer un nom de fichier à JSMin pour qu’il le lise directement), parceque en PHP c’est beaucoup plus rapide de lire d’abord le fichier (par une fonction comme file_get_contents) et de travailler sur la chaine de caractère représentant le contenu.

Le gain en vitesse dépend fortement de la taille et du contenu du fichier ; dans mon cas c’est entre 10% et 50% plus rapide.

Utilisation

Le constructeur prend 2 paramètres : le code à compresser (string) et, si besoin, un tableau de commentaires à ajouter en début de fichier (array ou rien). Ensuite il suffit d’appeller la fonction minify().

Exemple :

$code = file_get_contents('foo.js');
 $compressor = new MyJsMin($code, array('Hello', 'World'));
 echo $compressor->minify();

Le fichier foo.js contient :

function foo(bar) {
 bar += 1; alert('hello world !');
} 

Le résultat sera :

// hello
// world
 function foo(bar) {bar+=1;alert('hello world !');}

Sources

Test en ligne

Vous pouvez tester les performances du script grace à cette page (en anglais, parceque c’est la classe internationnale).

Comments

14 Responses to “Une version optimisée pour PHP de JSMin”
  1. tmtisfree says:

    J’ai essayé ta version (j’utilisais l’original en php avant de découvrir la tienne), elle ne me renvoie rien. Un pb mineur probablement.

  2. Rémi says:

    Ah tiens, c’est étonnant ça. Pourtant sur la page de test elle fonctionne niquel… Es-tu sûr de l’initialiser correctement ? Comme je l’ai expliqué, j’ai viré le code qui s’occupe des fichiers, donc si tu lui passes un nom de fichier elle ne fera rien.

  3. tmtisfree says:

    Oui, ta page de test fonctionne.

    Voilà comment je l’utilise : je l’inclus avec un include en début de page, puis j’utilise ton exemple en <head> :

    <?php
    $code = file_get_contents(‘js/prototype.js’);
    $comp = new JSMin($code);
    echo $comp->minify();
    ?>

    Le fichier js est lu puis le serveur s’arrête de renvoyer après la première ligne.
    Comme dit plus haut, si j’inclus l’original, le js est renvoyé correctement. Je ne comprend pas non plus. Peut-être un pb de charset.

  4. tmtisfree says:

    Je vais consulter le fichier de log de php.

  5. Rémi says:

    Ma classe s’appelle MyJsMin ! Je corrige l’article.

  6. tmtisfree says:

    Effectivement, ca va beaucoup mieux avec le bon nom :). J’aurai pu regarder de plus près. Corrige aussi le bout de code dans le fichier lui-même au alentours de la ligne 74.
    Testé sur Apache 2.0.55/PHP 5.1.6 et Apache 2.0.59/PHP 5.2.1.

    Merci pour ton code et bonne continuation.
    Bye,
    TMTisFree

  7. tmtisfree says:

    En essayant de généraliser sur toutes les librairies js que j’utilise, certaines refuse de passer à la moulinette : prototype.js et overlib.js en particulier. Une idée ?

  8. Rémi says:

    Rha ! C’est à cause des strings sur plusieurs lignes… Vu que je n’utilise jamais cette syntaxe pour mes JS je n’avais pas vu ce bug. Merci pour l’info, je vais voir s’il est possible de le corriger proprement.

  9. Rémi says:

    Voila, j’ai mis en ligne une nouvelle version qui fonctionne avec prototype. Malheureusement, le gain en performance est moins flagrant (entre 5% et 20% ; dans certains cas c’est même moins rapide, argh !).

  10. tmtisfree says:

    Merci, je vais l’essayer.
    J’ai optimisé mes sites hier soir (sans jsmin pour l’instant donc !), résultat : temps de chargement divisé par 2. Je vais faire quelques tests avec ta nouvelles version.

  11. Laurent says:

    Quid des commentaires conditionnels de IE ?

    /*@cc_on
    if ( @_jscript_version == 5.6 )
    document.execCommand(“BackgroundImageCache”, false, true);
    @*/

  12. Rémi says:

    La version originale ne les supporte pas. Mais il y a des méthodes plus “propres” pour mettre du code spécifique à IE…

  13. BSO HQ says:

    Performances des packers javascript

    Les librairies et frameworks javascript sont de plus en plus nombreux et de plus en plus complets. Ca, c’est une bonne nouvelle, mais la conséquence logique est que leur taille augmente jusqu’à un point qui commence à devenir problémati……

  14. Cyrille37 says:

    Bonjour,

    Le traitement de prototype.js fonctionne bien avec jsmin.php mais pas avec ton MyJsMin.php.

    MyJsMin lève une exception “MyJsMinException UnterminatedStringLiteral” lorsqu’il parse l’une des expressions régulières définies dans prototype.js :

    attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/

    Il semble qu’il plante car il pense être entré dans une chaîne de caractère et que celle-ci n’est pas terminée.

    Il s’agit de la version:
    Prototype JavaScript framework, version 1.6.0.2

    Cyrille.

Speak Your Mind

Tell us what you're thinking...
and oh, if you want a pic to show with your comment, go get a gravatar!