source: spip-zone/_core_/plugins/compresseur/inc/compresseur_minifier.php @ 43971

Last change on this file since 43971 was 43971, checked in by cedric@…, 9 years ago

debug de la gestion du recalcul dans la concatenation des fichiers (&var_mode=recalcul dans une url ne doit pas empecher de retomber sur le bon nom de fichier compresse obtenu sans ce var_mode)
respect de l'ordre du html pour le calcul du nom

Gestion plus smart du google closure compiler :

  • prise en compte des callbacks dans le nom du fichier compresse, ce qui permet de differencier le nom final dans les cas avec ou sans closure compiler
  • la fonction minifier_encore_js n'est appelee que lorsque le head est modifie (ou recalcul explicite)
  • modification de la fonction minifier_encore_js : si son second argument est une chaine, on l'utilise comme nom de fichier cible pour y recopier le js minifie, et on renvoie ce nom la, ce qui permet a l'appelant de fixer le nom final
File size: 10.4 KB
Line 
1<?php
2
3/***************************************************************************\
4 *  SPIP, Systeme de publication pour l'internet                           *
5 *                                                                         *
6 *  Copyright (c) 2001-2011                                                *
7 *  Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James  *
8 *                                                                         *
9 *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
10 *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
11\***************************************************************************/
12
13if (!defined("_ECRIRE_INC_VERSION")) return;
14
15/**
16 * Minifier un contenu CSS
17 * Si $options est vide
18 *      on utilise la methode regexp simple
19 * Si $options est une chaine non vide
20 *  elle definit un media a appliquer a la css
21 *      si la css ne contient aucun @media ni @import, on encapsule tout dans "@media $option {...}" et on utilise regexp
22 *  sinon on utilise csstidy pour ne pas faire d'erreur, mais c'est 12 fois plus lent
23 * Si $options sous forme de array()
24 *      on pass par csstidy pour parser le code
25 *  et produire un contenu plus compact et prefixe eventuellement par un @media
26 * options disponibles :
27 *  string media : media qui seront utilises pour encapsuler par @media
28 *        les selecteurs sans media
29 *  string template : format de sortie parmi 'low','default','high','highest'
30 * @param string $contenu  contenu css
31 * @param mixed $options options de minification
32 * @return string
33 */
34function minifier_css ($contenu, $options='') {
35        if (is_string($options) AND $options){
36                if ($options=="all") // facile : media all => ne rien preciser
37                        $options = "";
38                elseif (
39                                        strpos($contenu,"@media")==false
40                        AND strpos($contenu,"@import")==false
41                        ){
42                        $contenu = "@media $options {\n$contenu\n}\n";
43                        $options="";
44                }
45                else
46                        $options = array('media'=>$options);
47        }
48        if (!is_array($options)){
49
50                // nettoyer la css de tout ce qui sert pas
51                // pas de commentaires
52                $contenu = preg_replace(",/\*.*\*/,Ums","",$contenu);
53                $contenu = preg_replace(",\s//[^\n]*\n,Ums","",$contenu);
54                // espaces autour des retour lignes
55                $contenu = str_replace("\r\n","\n",$contenu);
56                $contenu = preg_replace(",\s+\n,ms","\n",$contenu);
57                $contenu = preg_replace(",\n\s+,ms","\n",$contenu);
58                // pas d'espaces consecutifs
59                $contenu = preg_replace(",\s(?=\s),Ums","",$contenu);
60                // pas d'espaces avant et apres { ; ,
61                $contenu = preg_replace("/\s?({|;|,)\s?/ms","$1",$contenu);
62                // supprimer les espaces devant : sauf si suivi d'une lettre (:after, :first...)
63                $contenu = preg_replace("/\s:([^a-z])/ims",":$1",$contenu);
64                // supprimer les espaces apres :
65                $contenu = preg_replace("/:\s/ms",":",$contenu);
66                // pas d'espaces devant }
67                $contenu = preg_replace("/\s}/ms","}",$contenu);
68
69                // ni de point virgule sur la derniere declaration
70                $contenu = preg_replace("/;}/ms","}",$contenu);
71                // pas d'espace avant !important
72                $contenu = preg_replace("/\s!\s?important/ms","!important",$contenu);
73                // passser les codes couleurs en 3 car si possible
74                // uniquement si non precedees d'un [="'] ce qui indique qu'on est dans un filter(xx=#?...)
75                $contenu = preg_replace(";([:\s,(])#([0-9a-f])(\\2)([0-9a-f])(\\4)([0-9a-f])(\\6)(?=[^\w\-]);i","$1#$2$4$6",$contenu);
76                // remplacer font-weight:bold par font-weight:700
77                $contenu = preg_replace("/font-weight:bold/ims","font-weight:700",$contenu);
78                // remplacer font-weight:normal par font-weight:400
79                $contenu = preg_replace("/font-weight:normal/ims","font-weight:400",$contenu);
80
81                // enlever le 0 des unites decimales
82                $contenu = preg_replace("/0[.]([0-9]+em)/ims",".$1",$contenu);
83                // supprimer les declarations vides
84                $contenu = preg_replace(",(^|})([^{}]*){},Ums","$1",$contenu);
85                // zero est zero, quelle que soit l'unite
86                $contenu = preg_replace("/([^0-9.]0)(em|px|pt|%)/ms","$1",$contenu);
87
88                // renommer les couleurs par leurs versions courtes quand c'est possible
89                $colors = array(
90                        'source'=>array('black','fuchsia','white','yellow','#800000','#ffa500','#808000','#800080','#008000','#000080','#008080','#c0c0c0','#808080','#f00'),
91                        'replace'=>array('#000' ,'#F0F'   ,'#FFF' ,'#FF0'  ,'maroon' ,'orange' ,'olive'  ,'purple' ,'green'  ,'navy'   ,'teal'   ,'silver' ,'gray'   ,'red')
92                );
93                foreach($colors['source'] as $k=>$v){
94                        $colors['source'][$k]=";([:\s,(])".$v."(?=[^\w\-]);ms";
95                        $colors['replace'][$k] = "$1".$colors['replace'][$k];
96                }
97                $contenu = preg_replace($colors['source'],$colors['replace'],$contenu);
98
99                // raccourcir les padding qui le peuvent (sur 3 ou 2 valeurs)
100                $contenu = preg_replace(",padding:([^\s;}]+)\s([^\s;}]+)\s([^\s;}]+)\s(\\2),ims","padding:$1 $2 $3",$contenu);
101                $contenu = preg_replace(",padding:([^\s;}]+)\s([^\s;}]+)\s(\\1)([;}!]),ims","padding:$1 $2$4",$contenu);
102
103                // raccourcir les margin qui le peuvent (sur 3 ou 2 valeurs)
104                $contenu = preg_replace(",margin:([^\s;}]+)\s([^\s;}]+)\s([^\s;}]+)\s(\\2),ims","margin:$1 $2 $3",$contenu);
105                $contenu = preg_replace(",margin:([^\s;}]+)\s([^\s;}]+)\s(\\1)([;}!]),ims","margin:$1 $2$4",$contenu);
106
107                $contenu = trim($contenu);
108
109                return $contenu;
110        }
111        else {
112                // compression avancee en utilisant csstidy
113                // beaucoup plus lent, mais necessaire pour placer des @media la ou il faut
114                // si il y a deja des @media ou des @import
115
116                // modele de sortie plus ou moins compact
117                $template = 'high';
118                if (isset($options['template']) AND in_array($options['template'],array('low','default','high','highest')))
119                        $template = $options['template'];
120                // @media eventuel pour prefixe toutes les css
121                // et regrouper plusieurs css entre elles
122                $media = "";
123                if (isset($options['media']))
124                        $media = "@media ".$options['media']." ";
125
126                include_spip("lib/csstidy/class.csstidy");
127                $css = new csstidy();
128
129                // essayer d'optimiser les font, margin, padding avec des ecritures raccourcies
130                $css->set_cfg('optimise_shorthands',2);
131                $css->set_cfg('template',$template);
132                $css->parse($contenu);
133                return $css->print->plain($media);
134        }
135}
136
137
138/**
139 * Compacte du javascript grace a Dean Edward's JavaScriptPacker
140 *
141 * Bench du 15/11/2010 sur jQuery.js :
142 * JSMIN (https://github.com/rgrove/jsmin-php/)
143 *      61% de la taille initiale / 2 895 ms
144 * JavaScriptPacker
145 *  62% de la taille initiale / 752 ms
146 *
147 * Closure Compiler
148 *  44% de la taille initiale / 3 785 ms
149 *
150 * JavaScriptPacker + Closure Compiler
151 *  43% de la taille initiale / 3 100 ms au total
152 *
153 * Il est donc plus rapide&efficace
154 * - de packer d'abord en local avec JavaScriptPacker
155 * - d'envoyer ensuite au closure compiler
156 * Cela permet en outre d'avoir un niveau de compression decent si closure
157 * compiler echoue
158 *
159 * Dans cette fonction on ne fait que le compactage local,
160 * l'appel a closure compiler est fait une unique fois pour tous les js concatene
161 * afin d'eviter les requetes externes
162 *
163 * @param  $flux
164 * @param  $url_base
165 * @return array
166 */
167function minifier_js($flux) {
168        if (!strlen($flux))
169                return $flux;
170
171        include_spip('lib/JavascriptPacker/class.JavaScriptPacker');
172        $packer = new JavaScriptPacker($flux, 0, true, false);
173
174        // en cas d'echec (?) renvoyer l'original
175        if (!strlen($t = $packer->pack())) {
176                spip_log('erreur de minifier_js');
177                return $flux;
178        }
179        return $t;
180}
181
182
183/**
184 * Minification additionnelle :
185 * Compacter du javascript plus intensivement
186 * grace au google closure compiler
187 *
188 * @param string $content
189 *  contenu a compresser
190 * @param bool $file
191 *  indique si $content est ou non un fichier, et retourne un fichier dans ce dernier cas
192 *  si $file est une chaine, c'est un nom de ficher sous lequel on ecrit aussi le fichier destination
193 * @return string
194 */
195function minifier_encore_js($content,$file=false) {
196        # Closure Compiler n'accepte pas des POST plus gros que 200 000 octets
197        # au-dela il faut stocker dans un fichier, et envoyer l'url du fichier
198        # dans code_url ; en localhost ca ne marche evidemment pas
199        if ($file) {
200                $nom = $content;
201                lire_fichier($nom, $content);
202                $dest = dirname($nom).'/'.md5($content.$file).'.js';
203                if (file_exists($dest) AND (!is_string($file) OR file_exists($file)))
204                        if (filesize($dest))
205                                return is_string($file)?$file:$dest;
206                        else {
207                                spip_log("minifier_encore_js: Fichier $dest vide",_LOG_INFO);
208                                return $nom;
209                        }
210        }
211
212        if (!$file AND strlen($content)>200000)
213                return $content;
214
215        include_spip('inc/distant');
216
217        $datas=array(
218                'output_format' => 'text',
219                'output_info' => 'compiled_code',
220                'compilation_level' => 'SIMPLE_OPTIMIZATIONS', // 'SIMPLE_OPTIMIZATIONS', 'WHITESPACE_ONLY', 'ADVANCED_OPTIMIZATIONS'
221        );
222        if (!$file OR strlen($content) < 200000)
223                $datas['js_code'] = $content;
224        else
225                $datas['url_code'] = url_absolue($nom);
226
227        $cc = recuperer_page('http://closure-compiler.appspot.com/compile',
228                $trans=false, $get_headers=false,
229                $taille_max = null,
230                $datas,
231                $boundary = -1);
232
233        if ($cc AND !preg_match(',^\s*Error,', $cc)) {
234                spip_log('Closure Compiler: success');
235                $cc = "/* $nom + Closure Compiler */\n".$cc;
236                if ($file){
237                        ecrire_fichier ($dest, $cc, true);
238                        ecrire_fichier ("$dest.gz", $cc, true);
239                        $content = $dest;
240                  if (is_string($file)){
241                          ecrire_fichier ($file, $cc, true);
242                          ecrire_fichier ("$file.gz", $cc, true);
243                    $content = $file;
244                  }
245                }
246                else
247                        $content = &$cc;
248        } else {
249                if ($file){
250                        spip_log("minifier_encore_js:Echec appel Closure Compiler. Ecriture fichier $dest vide",_LOG_INFO_IMPORTANTE);
251                        ecrire_fichier ($dest, '', true);
252                }
253        }
254        return $content;
255}
256
257
258/**
259 * Une callback applicable sur chaque balise link
260 * @param string $contenu
261 * @param  string $balise
262 * @return string
263 */
264function callback_minifier_css_file($contenu, $balise){
265        return minifier_css($contenu, extraire_attribut($balise,'media'));
266}
267
268/**
269 * Une callback applicable sur chaque balise script
270 * @param string $contenu
271 * @param  string $balise
272 * @return string
273 */
274function callback_minifier_js_file($contenu, $balise){
275        return minifier_js($contenu);
276}
277
278
279/**
280 * minifier du HTML
281 *
282 * @param string $flux
283 * @return string
284 */
285function minifier_html($flux){
286        // si pas de contenu ni de balise html, ne rien faire
287        if (!strlen($flux) OR strpos($flux,"<")===FALSE)
288                return $flux;
289
290        static $options = null;
291        if (is_null($options)){
292                $options = array();
293                if ($GLOBALS['meta']['auto_compress_css'] == 'oui')
294                        $options['cssMinifier'] = 'minifier_css';
295                if ($GLOBALS['meta']['auto_compress_js'] == 'oui')
296                        $options['jsMinifier'] = 'minifier_js';
297                include_spip('lib/minify_html/class.minify_html');
298        }
299        return Minify_HTML::minify($flux,$options);
300}
Note: See TracBrowser for help on using the repository browser.