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

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

refactoring du compresseur :
Separer par groupes fonctionnel :

  • minifier pour tout ce qui concerne la minification css ou js d'un fichier
  • concatener pour le regroupement de plusieurs fichiers en un (au passage, la fonction est generalisee et les specificites css/js sont simplement gerees par les callbacks passees en appel)
  • embarquer pour tout ce qui concerne(ra) l'inclusion des images en base64 dans les css (a developper)

On rationalise les nommage

File size: 9.8 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(",\s([^{}]*){},Ums"," ",$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 * Compacter du javascript plus intensivement
185 * grace au google closure compiler
186 *
187 * @param string $content
188 * @param bool $file
189 * @return string
190 */
191function minifier_encore_js($content,$file=false) {
192        # Closure Compiler n'accepte pas des POST plus gros que 200 000 octets
193        # au-dela il faut stocker dans un fichier, et envoyer l'url du fichier
194        # dans code_url ; en localhost ca ne marche evidemment pas
195        if ($file) {
196                $nom = $content;
197                lire_fichier($nom, $content);
198                $dest = dirname($nom).'/'.md5($content).'.js';
199                if (file_exists($dest))
200                        if (filesize($dest))
201                                return $dest;
202                        else
203                                return $nom;
204        }
205
206        if (!$file AND strlen($content)>200000)
207                return $content;
208
209        include_spip('inc/distant');
210
211        $datas=array(
212                'output_format' => 'text',
213                'output_info' => 'compiled_code',
214                'compilation_level' => 'SIMPLE_OPTIMIZATIONS', // 'SIMPLE_OPTIMIZATIONS', 'WHITESPACE_ONLY', 'ADVANCED_OPTIMIZATIONS'
215        );
216        if (!$file OR strlen($content) < 200000)
217                $datas['js_code'] = $content;
218        else
219                $datas['url_code'] = url_absolue($nom);
220
221        $cc = recuperer_page('http://closure-compiler.appspot.com/compile',
222                $trans=false, $get_headers=false,
223                $taille_max = null,
224                $datas,
225                $boundary = -1);
226
227        if ($cc AND !preg_match(',^\s*Error,', $cc)) {
228                spip_log('Closure Compiler: success');
229                $cc = "/* $nom + Closure Compiler */\n".$cc;
230                if ($file){
231                        ecrire_fichier ($dest, $cc, true);
232                        ecrire_fichier ("$dest.gz", $cc, true);
233                        $content = $dest;
234                }
235                else
236                        $content = &$cc;
237        } else {
238                if ($file)
239                        ecrire_fichier ($dest, '', true);
240        }
241        return $content;
242}
243
244
245/**
246 * Une callback applicable sur chaque balise link
247 * @param string $contenu
248 * @param  string $balise
249 * @return string
250 */
251function callback_minifier_css_file($contenu, $balise){
252        return minifier_css($contenu, extraire_attribut($balise,'media'));
253}
254
255/**
256 * Une callback applicable sur chaque balise script
257 * @param string $contenu
258 * @param  string $balise
259 * @return string
260 */
261function callback_minifier_js_file($contenu, $balise){
262        return minifier_js($contenu);
263}
264
265
266/**
267 * Minification additionnelle :
268 * experimenter le Closure Compiler de Google
269 * @param string $nom
270 *   nom d'un fichier a minifier encore plus
271 * @param string $format
272 *   format css ou js
273 * @return string
274 */
275function callback_minifier_encore(&$nom, $format) {
276        # Closure Compiler n'accepte pas des POST plus gros que 200 000 octets
277        # au-dela il faut stocker dans un fichier, et envoyer l'url du fichier
278        # dans code_url ; en localhost ca ne marche evidemment pas
279        if (
280        $GLOBALS['meta']['auto_compress_closure'] == 'oui'
281        AND $format=='js'
282        ) {
283                $nom = minifier_encore_js($nom,true);
284        }
285        return $nom;
286}
Note: See TracBrowser for help on using the repository browser.