source: spip-zone/_plugins_/scssphp/trunk/scssphp_fonctions.php @ 115258

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

Mise a jour de scssphp, qui inclue une petite optimisation du cache: sur var_mode=css on ne refresh un cache qu'une seule fois et on peut ensuite l'utiliser, ce qui permet de ne parser les fichiers modifies qu'une seule fois
Accelere sensiblement la mise a jour si on a plusieurs scss independante mais qui inclue des morceaux en commun (variables, mixins…)

A noter que le var_mode=css reste utile dans un seul scenario : si on duplique une feuille existante pour la surcharger dans le path, il faudra passer par là pour que le compilateur voit le nouveau fichier et le prenne en compte.
Autrement toute modification d'un fichier utilise dans la compilation provoquera la mise a jour de la css compilee avec un simple var_mode=calcul

File size: 11.1 KB
Line 
1<?php
2/*
3 * Plugin Scss
4 * Distribue sous licence MIT
5 *
6 */
7use Leafo\ScssPhp\Compiler;
8
9if (!defined('_ECRIRE_INC_VERSION')) {
10        return;
11}
12
13
14function scss_cache_dir() {
15        static $cache_dir;
16        if (is_null($cache_dir)) {
17                $cache_dir = sous_repertoire (_DIR_VAR, 'cache-scss');
18                $cache_dir = sous_repertoire($cache_dir, 'compile');
19        }
20        return $cache_dir;
21}
22
23/**
24 * Compiler des styles inline SCSS en CSS
25 *
26 * @param string $style
27 *   contenu du .scss
28 * @param array $contexte
29 *   file : chemin du fichier compile
30 *          utilise en cas de message d'erreur, et pour le repertoire de reference des @import
31 * @return string
32 */
33function scss_compile($style, $contexte = array()) {
34        include_spip('lib/scssphp/scss.inc');
35        spip_timer('scss_compile');
36
37        $import_dirs = _chemin();
38
39        $cache_options = array(
40                'cache_dir' => scss_cache_dir(),
41                // il faut prefixer avec une empreinte du import_dirs qui change le resultat
42                'prefix' => 'scssphp_'. substr(md5(json_encode($import_dirs)),0,4) . '_',
43                'force_refresh' => false,
44        );
45
46        if (defined('_VAR_MODE') and
47                (_request('var_mode') == 'css' or in_array(_VAR_MODE, array('css', 'recalcul'))) ) {
48                $cache_options['force_refresh'] = 'once';
49        }
50
51        // le compilateur Leafo\ScssPhp\Compiler compile le contenu
52        $scss = new Compiler($cache_options);
53        $scss->setFormatter("Leafo\ScssPhp\Formatter\Expanded");
54
55        // lui transmettre le path qu'il utilise pour les @import
56        $scss->setImportPaths(_chemin());
57
58        // pouvoir importer des @import 'css/truc' sur fichier 'css/truc.scss.html'
59        $scss->addImportPath(function($path) {
60                // SPIP 3.0+
61                if (function_exists('produire_fond_statique')) {
62                        if ($f = find_in_path($path . '.scss.html')) {
63                                $f = produire_fond_statique($path . '.scss', array('format' => 'scss'));
64                                $f = supprimer_timestamp($f);
65                                return $f;
66                        }
67                }
68                return null;
69        });
70        // pipeline : scss_variables
71        // Surcharger des variables depuis un plugin ou une configuration
72        // les variables sont un tableau 'variable'=>'scss value'
73        // ex : 'header'=> '(background:pink,color:white)'
74        $scss_vars = pipeline('scss_variables',array());
75        $scss->setVariables($scss_vars);
76
77        // Inline source maps
78        // http://leafo.github.io/scssphp/docs/#source-maps
79        // https://github.com/leafo/scssphp/wiki/Source-Maps
80        if (defined('_SCSS_SOURCE_MAP') and '_SCSS_SOURCE_MAP' == true) {
81                $scss->setSourceMap(Compiler::SOURCE_MAP_INLINE);
82                $scss->setSourceMapOptions(array(
83                        // This value is prepended to the individual entries in the 'source' field.
84                        'sourceRoot' => '',
85                        // an optional name of the generated code that this source map is associated with.
86                        'sourceMapFilename' => null,
87                        // url of the map
88                        'sourceMapURL' => null,
89                        // absolute path to a file to write the map to
90                        'sourceMapWriteTo' => null,
91                        // output source contents?
92                        'outputSourceFiles' => false,
93                        // base path for filename normalization
94                        'sourceMapRootpath' => '/',
95                        // base path for filename normalization
96                        // difference between file & url locations, removed from ALL source files in .map
97                        'sourceMapBasepath' => '/local/cache-scss/'
98          ));
99        }
100
101        try {
102                $out = $scss->compile($style, isset($contexte['file']) ? $contexte['file'] : null);
103                spip_log('scss_compile compile '.(isset($contexte['file'])?$contexte['file']:substr($style,0,100)).' :: '.spip_timer('scss_compile'), 'scssphp');
104        } catch (exception $ex) {
105                // en cas d'erreur, on retourne du vide...
106                spip_log('SCSS Compiler fatal error:'.$ex->getMessage(), 'scssphp'._LOG_ERREUR);
107                $display_file = '';
108                if (isset($contexte['file'])) {
109                        $display_file = $contexte['file'];
110                        if (strpos($ex->getMessage(), '.scss') !== false) {
111                                $display_file = basename($display_file);
112                        }
113                        $display_file= " fichier $display_file";
114                }
115                erreur_squelette(
116                        'SCSS : Echec compilation'
117                        . $display_file
118                        . '<br />' . $ex->getMessage()
119                );
120                return '';
121        }
122
123        if ($files = $scss->getParsedFiles()
124          AND count($files)){
125                $files = array_keys($files);
126                $l = strlen(_DIR_RACINE);
127                $lr = strlen(_ROOT_RACINE);
128                foreach($files as $k=>$file){
129                        if ($l and strncmp($file,_DIR_RACINE,$l)==0){
130                                $files[$k] = substr($file,$l);
131                        }
132                        if ($lr and strncmp($file,_ROOT_RACINE,$lr)==0){
133                                $files[$k] = substr($file,$lr);
134                        }
135                }
136                $out = "/*\n#@".implode("\n#@",$files)."\n*"."/\n" . $out;
137        }
138        return $out;
139}
140
141/**
142 * Transformer du SCSS en CSS
143 * Peut prendre en entree
144 * - un fichier .css ou .scss :
145 *   [(#CHEMIN{messtyles.scss.css}|scss_css)]
146 *   la sortie est un chemin vers un fichier CSS
147 * - des styles inline,
148 *   pour appliquer dans une feulle scss calculee :
149 *   #FILTRE{scss_css}
150 *   la sortie est du style inline
151 *
152 * @param string $source
153 * @return string
154 */
155function scss_css($source) {
156        static $chemin = null;
157
158        // Si on n'importe pas, est-ce un fichier ?
159        if (
160                !preg_match(',[\s{}],', $source)
161                and preg_match(',\.(scss|css)$,i', $source, $r)
162                and file_exists($source)
163        ) {
164                static $done = array();
165                // ne pas essayer de compiler deux fois le meme fichier dans le meme hit
166                // si on a echoue une fois, on echouera pareil
167                if (isset($done[$source])) {
168                        return $done[$source];
169                }
170
171                if (is_null($chemin)) {
172                        $chemin = _chemin();
173                        $chemin = md5(serialize($chemin));
174                }
175
176                $f = basename($source, $r[0]);
177                $f = sous_repertoire(_DIR_VAR, 'cache-scss')
178                . preg_replace(
179                        ',(.*?)(_rtl|_ltr)?$,',
180                        "\\1-cssify-" . substr(md5("$source-scss-$chemin"), 0, 7) . "\\2",
181                        $f,
182                        1
183                )
184                . '.css';
185
186                # si la feuille compilee est plus recente que la feuille source
187                # l'utiliser sans rien faire, sauf si il y a un var_mode
188                # dans ca cas on passe par la compilation qui utilise un cache et est donc rapide si rien de change
189                $changed = false;
190                if (@filemtime($f) < @filemtime($source)){
191                        $changed = true;
192                }
193
194                // si pas change ET pas de var_mode du tout, rien a faire (performance)
195                if (!$changed
196                        AND !defined('_VAR_MODE'))
197                        return $f;
198
199                $contenu = false;
200                if (!lire_fichier($source, $contenu)) {
201                        return $source;
202                }
203
204                // compiler le SCSS si besoin (ne pas generer une erreur si source vide
205                if (!$contenu) {
206                        $contenu = "/* Source $source : vide */\n";
207                } else {
208                        $contenu = scss_compile($contenu, array('file'=>$source));
209                }
210
211                // si erreur de compilation on renvoit un commentaire, et il y a deja eu un log
212                if (!$contenu) {
213                        $contenu = "/* Compilation $source : vide */\n";
214                }
215
216                // passer la css en url absolue (on ne peut pas le faire avant, car c'est du SCSS, pas des CSS)
217                $contenu = urls_absolues_css($contenu, $source);
218
219                // ecrire le fichier destination, en cas d'echec renvoyer la source
220                // on ecrit sur un fichier
221                if (ecrire_fichier($f.'.last', $contenu, true)) {
222                        if ($changed or md5_file($f) != md5_file($f.'.last')) {
223                                @copy($f.'.last', $f);
224                                // eviter que PHP ne reserve le vieux timestamp
225                                if (version_compare(PHP_VERSION, '5.3.0') >= 0) {
226                                        clearstatcache(true, $f);
227                                } else {
228                                        clearstatcache();
229                                }
230                        }
231
232                        return $done[$source] = $f;
233                } else {
234                        return $done[$source] = $source;
235                }
236        }
237
238        $source = scss_compile($source);
239
240        if (!$source) {
241                return '/* Erreur compilation SCSS : cf scss.log */';
242        } else {
243                return $source;
244        }
245}
246
247
248/**
249 * injecter l'appel au compresseur sous la forme de filtre
250 * pour intervenir sur l'ensemble du head
251 * du squelette public
252 *
253 * @param string $flux
254 * @return string
255 */
256function scss_insert_head($flux) {
257        $flux .= '<'
258                .'?php header("X-Spip-Filtre: '
259                .'scss_cssify_head'
260                .'"); ?'.'>';
261        return $flux;
262}
263
264
265/**
266 * Attraper automatiquement toutes les .scss ou .scss.css du head
267 * les compiler, et les remplacer par leur css compilee
268 *
269 * @param string $head
270 * @return void
271 */
272function scss_cssify_head($head) {
273        $url_base = url_de_base();
274        $balises = extraire_balises($head, 'link');
275        $files = array();
276
277        foreach ($balises as $s) {
278                if (
279                        extraire_attribut($s, 'rel') === 'stylesheet'
280                        and (!($type = extraire_attribut($s, 'type')) or $type == 'text/css')
281                        and $src = extraire_attribut($s, 'href')
282                        // format .scss.css ou .scss avec un eventuel timestamp ?123456
283                        and preg_match(',\.(scss\.css|scss)(\?\d+)?$,', $src)
284                        and $src = preg_replace(',\?\d+$,', '', $src)
285                        and $src = preg_replace(",^$url_base,", _DIR_RACINE, $src)
286                        and file_exists($src)
287                ) {
288                        $files[$s] = $src;
289                }
290        }
291
292        if (!count($files)) {
293                return $head;
294        }
295
296        foreach ($files as $s => $scssfile) {
297                $cssfile = scss_css($scssfile);
298                $m = @filemtime($cssfile);
299                $s2 = inserer_attribut($s, 'href', "$cssfile?$m");
300                $head = str_replace($s, $s2, $head);
301        }
302
303        return $head;
304}
305
306/*
307 * Prise en charge de la balise #CSS{style.css}
308 * Regles :
309 * - cherche un .css ou un .css.html ou un .scss comme feuille de style
310 * - si un seul des 3 trouve dans le chemin il est renvoye (et compile au passage si .scss)
311 * - si un .css.html et un .css trouves dans le chemin, c'est le .css.html qui est pris
312 * (surcharge d'un statique avec une css calculee)
313 * - si un .scss et un (.css ou .css.html) on compare la priorite du chemin des deux trouves :
314 *   le plus prioritaire des 2 est choisi
315 *   si priorite equivalente on choisi le (.css ou .css.html) qui est le moins couteux a produire
316 *   permet d'avoir dans le meme dossier le .scss et sa version compilee .css : cette derniere est utilisee
317 *
318 * #CSS{style.css} renvoie dans tous les cas un fichier .css qui est soit :
319 * - un .scss compile en .css
320 * - un .css statique
321 * - un .css.html calcule en .css
322 */
323if (!function_exists('balise_CSS')) {
324        function balise_CSS($p) {
325                $_css = interprete_argument_balise(1, $p);
326                $p->code = "timestamp(direction_css(scss_select_css($_css)))";
327                $p->interdire_scripts = false;
328                return $p;
329        }
330}
331
332/**
333 * Selectionner de preference la feuille .scss (en la compilant)
334 * et sinon garder la .css classiquement
335 *
336 * @param string $css_file
337 * @return string
338 */
339function scss_select_css($css_file) {
340        if (
341                function_exists('scss_css')
342                and substr($css_file, -4) == '.css'
343        ) {
344                $scss_file = substr($css_file, 0, -4).'.scss';
345                $scss_or_css = scss_find_scss_or_css_in_path($scss_file, $css_file);
346
347                if (substr($scss_or_css, -5) == '.scss') {
348                        return scss_css($scss_or_css);
349                } else {
350                        return $scss_or_css;
351                }
352        }
353
354        return find_in_path($css_file);
355}
356
357/**
358 * Faire un find_in_path en cherchant un fichier .scss ou .css
359 * et en prenant le plus prioritaire des deux
360 * ce qui permet de surcharger un .css avec un .scss ou le contraire
361 * Si ils sont dans le meme repertoire, c'est le .css qui est prioritaire,
362 * par soucis de rapidite
363 *
364 * @param string $scss_file
365 * @param string $css_file
366 * @return string
367 */
368function scss_find_scss_or_css_in_path($scss_file, $css_file) {
369        $s = find_in_path($scss_file);
370        $c = $f = trouver_fond($css_file);
371
372        if (!$c) {
373                $c = find_in_path($css_file);
374        }
375
376        if (!$s) {
377                return ($f ? produire_fond_statique($css_file, array('format' => 'css')) : $c);
378        } elseif (!$c) {
379                return $s;
380        }
381
382        // on a un scss et un css en concurence
383        // prioriser en fonction de leur position dans le path
384        $path = creer_chemin();
385        foreach ($path as $dir) {
386                // css prioritaire
387                if (strncmp($c, $dir . $css_file, strlen($dir . $css_file)) == 0) {
388                        return ($f ? produire_fond_statique($css_file, array('format'=>'css')) : $c);
389                }
390                if ($s == $dir . $scss_file) {
391                        return $s;
392                }
393        }
394
395        // on ne doit jamais arriver la !
396        spip_log('Resolution chemin scss/css impossible', 'scssphp' . _LOG_CRITIQUE);
397        debug_print_backtrace();
398        die('Erreur fatale, je suis perdu');
399}
Note: See TracBrowser for help on using the repository browser.