2014-10-29 15 views
12

Nel mio ufficio stiamo usando gulp per costruire i nostri file in meno. Volevo migliorare l'attività di compilazione in quanto è occorso un secondo per costruire un grande progetto su cui abbiamo lavorato di recente. L'idea era di mettere in cache i file e solo passare quello che è cambiato. Così ho iniziato con google e ho trovato build incrementali per javascript che pensavo sarebbe stato facile riscriverle per meno. Ecco quello che ho iniziato con: https://github.com/gulpjs/gulp/blob/master/docs/recipes/incremental-builds-with-concatenate.mdIncremental gulp less build

Dopo alcuni tentativi infruttuosi ho finito con seguente codice (testato con l'ultima distribuzione bootstrap):

var gulp   = require('gulp'); 
var less   = require('gulp-less'); 
var concat   = require('gulp-concat'); 
var remember  = require('gulp-remember'); 
var cached   = require('gulp-cached'); 

var fileGlob = [ 
    './bootstrap/**/*.less', 
    '!./bootstrap/bootstrap.less', 
    '!./bootstrap/mixins.less' 
]; 

gulp.task('less', function() { 
    return gulp.src(fileGlob) 
     .pipe(cached('lessFiles')) 
     .pipe(remember('lessFiles')) 
     .pipe(less()) 
     .pipe(gulp.dest('output')); 
}); 

gulp.task('watch', function() { 
    var watcher = gulp.watch(fileGlob, ['less']); 
    watcher.on('change', function (e) { 
     if (e.type === 'deleted') { 
      delete cached.caches.scripts[e.path]; 
      remember.forget('lessFiles', e.path); 
     } 
    }); 
}); 

Ma questo passa solo il file modificato e il meno compilatore fallisce a causa della mancanza di definizioni delle variabili. Se instrado il plugin concat prima dell'attività meno, gulp rimane bloccato in un loop (apparentemente) infinito.

gulp.task('less', function() { 
    return gulp.src(fileGlob) 
     .pipe(cached('lessFiles')) 
     .pipe(remember('lessFiles')) 
     .pipe(concat('main.less') 
     .pipe(less()) 
     .pipe(gulp.dest('output')); 
}); 

Qualcuno ha esperienza con questi plugin o sono riusciti a creare un incrementale meno costruire in un altro modo. Ecco un repository github (disordinato) per i test: https://github.com/tuelsch/perfect-less-build

PS: Sto pianificando di aggiungere linting, sourcemaps, minification, evtl. cache busting e autoprefixer in seguito.

+1

Stavo investigando la stessa identica cosa. Purtroppo non sembra essere una soluzione conveniente. Tuttavia, ho trovato un articolo che copre quell'argomento (comunque non è di alcuna utilità): http://io.pellucid.com/blog/tips-and-tricks-for-faster-front-end- build – nirazul

+0

Mentre scavavo, mi sono imbattuto in broccoli (http://www.solitr.com/blog/2014/02/broccoli-first-release/), ancora un altro "task runner". È ancora un progetto giovane, ma sembra che implementino l'idea di cui sopra come una caratteristica fondamentale. Terrò d'occhio questo. – phippu

+0

È 'memorizzato nella cache 'necessario? La rimozione dalla pipeline fa funzionare le cose? Funzionano per me, ma non so se sto facendo la tua stessa cosa. Potresti fornire il passaggio per duplicare i tuoi errori? – pgreen2

risposta

18

Come Ashwell, ho trovato utile utilizzare le importazioni per garantire che tutti i miei file LESS abbiano accesso alle variabili e ai mixin di cui hanno bisogno. Uso anche un file LESS con importazioni per scopi di aggregazione. Questo ha alcuni vantaggi:

  1. Posso sfruttare le funzionalità di LESS per eseguire operazioni complesse come la sostituzione dei valori delle variabili per produrre più temi o anteporre una classe a ogni regola in un altro file LESS.
  2. Non è necessario il plugin concat.
  3. Strumenti come Web Essentials per Visual Studio possono fornire l'aiuto della sintassi e le anteprime di output, poiché ogni file LESS è completamente in grado di essere visualizzato da solo.

Dove si desidera importare variabili, mixins, ecc, ma non si desidera, è possibile utilizzare effettivamente uscita l'intero contenuto di un altro file:

@import (reference) "_colors.less"; 

Dopo alcuni giorni di sforzi , Sono stato finalmente in grado di ottenere una build incrementale che ricostruisce correttamente tutti gli oggetti che dipendono dal file LESS che ho modificato. Ho documentato i risultati here.Questo è il gulpfile finale:

/* 
* This file defines how our static resources get built. 
* From the StaticCommon root folder, call "gulp" to compile all generated 
* client-side resources, or call "gulp watch" to keep checking source 
* files, and rebuild them whenever they are changed. Call "gulp live" to 
* do both (build and watch). 
*/ 

/* Dependency definitions: in order to avoid forcing everyone to have 
* node/npm installed on their systems, we are including all of the 
* necessary dependencies in the node_modules folder. To install new ones, 
* you must install nodejs on your machine, and use the "npm install XXX" 
* command. */ 
var gulp = require('gulp'); 
var less = require('gulp-less'); 
var LessPluginCleanCss = require('less-plugin-clean-css'), 
    cleanCss = new LessPluginCleanCss(); 
var sourcemaps = require('gulp-sourcemaps'); 
var rename = require('gulp-rename'); 
var cache = require('gulp-cached'); 
var progeny = require('gulp-progeny'); 
var filter = require('gulp-filter'); 
var plumber = require('gulp-plumber'); 
var debug = require('gulp-debug'); 

gulp.task('less', function() { 
    return gulp 
     // Even though some of our LESS files are just references, and 
     // aren't built, we need to start by looking at all of them because 
     // if any of them change, we may need to rebuild other less files. 
     .src(
     ['Content/@(Theme|Areas|Css)/**/*.less'], 
     { base: 'Content' }) 
     // This makes it so that errors are output to the console rather 
     // than silently crashing the app. 
     .pipe(plumber({ 
      errorHandler: function (err) { 
       console.log(err); 
       // And this makes it so "watch" can continue after an error. 
       this.emit('end'); 
      } 
     })) 
     // When running in "watch" mode, the contents of these files will 
     // be kept in an in-memory cache, and after the initial hit, we'll 
     // only rebuild when file contents change. 
     .pipe(cache('less')) 
     // This will build a dependency tree based on any @import 
     // statements found by the given REGEX. If you change one file, 
     // we'll rebuild any other files that reference it. 
     .pipe(progeny({ 
      regexp: /^\s*@import\s*(?:\(\w+\)\s*)?['"]([^'"]+)['"]/ 
     })) 
     // Now that we've set up the dependency tree, we can filter out 
     // any files whose 
     // file names start with an underscore (_) 
     .pipe(filter(['**/*.less', '!**/_*.less'])) 
     // This will output the name of each LESS file that we're about 
     // to rebuild. 
     .pipe(debug({ title: 'LESS' })) 
     // This starts capturing the line-numbers as we transform these 
     // files, allowing us to output a source map for each LESS file 
     // in the final stages. 
     // Browsers like Chrome can pick up those source maps and show you 
     // the actual LESS source line that a given rule came from, 
     // despite the source file's being transformed and minified. 
     .pipe(sourcemaps.init()) 
     // Run the transformation from LESS to CSS 
     .pipe(less({ 
      // Minify the CSS to get rid of extra space and most CSS 
      // comments. 
      plugins: [cleanCss] 
     })) 
     // We need a reliable way to indicate that the file was built 
     // with gulp, so we can ignore it in Mercurial commits. 
     // Lots of css libraries get distributed as .min.css files, so 
     // we don't want to exclude that pattern. Let's try .opt.css 
     // instead. 
     .pipe(rename(function(path) { 
      path.extname = ".opt.css"; 
     })) 
     // Now that we've captured all of our sourcemap mappings, add 
     // the source map comment at the bottom of each minified CSS 
     // file, and output the *.css.map file to the same folder as 
     // the original file. 
     .pipe(sourcemaps.write('.')) 
     // Write all these generated files back to the Content folder. 
     .pipe(gulp.dest('Content')); 
}); 

// Keep an eye on any LESS files, and if they change then invoke the 
// 'less' task. 
gulp.task('watch', function() { 
    return gulp.watch('Content/@(Theme|Areas|Css)/**/*.less', ['less']); 
}); 

// Build things first, then keep a watch on any changed files. 
gulp.task('live', ['less', 'watch']); 

// This is the task that's run when you run "gulp" without any arguments. 
gulp.task('default', ['less']); 

Ora possiamo semplicemente eseguire gulp live per costruire tutti i nostri file MENO una volta, e poi consentire ad ogni successiva modifica a poco costruire quei file che dipendono i file modificati.

+1

Questo è sorprendente, la progenie e l'utilizzo delle importazioni in ogni file sembrano essere le parti che mi mancavano. Lasciami eseguire alcuni test build prima di accettare questa come risposta corretta. – phippu

+0

@phippu: Felice il mio sforzo potrebbe essere in grado di aiutare gli altri. Sentiti libero di contattarmi se ti imbatti in qualche ostacolo. – StriplingWarrior

+0

@phippu: Ho aggiornato il mio post sul blog e il codice sopra con alcune linee di gestione degli errori aggiuntive necessarie per mantenere il funzionamento di 'watch' dopo che si è verificato un errore. – StriplingWarrior

3

Così, quando voglio eseguire build incrementali in gulp, lo faccio estraendo il processo interno del task gulp, in questo modo non devo preoccuparmi di mantenere una cache.

// Create a function that does just the processing 
var runCompile = function(src, dest, opts){ 
    return gulp.src(src) 
    .pipe(less(opts)) 
    .pipe(gulp.dest(dest)); 
}; 

// Leverage the function to create the task 
gulp.task('less', function(){ 
    return runCompile(fileGlob, 'output', {}); 
}); 

// Use it again in the watch task 
gulp.task('less:watch', function(){ 
    return gulp.watch(fileGlob) 
    .on("change", function(event){ 
     // might need to play with the dest dir here 
     return runCompile(event.path, 'output', {}); 
    }); 
}); 

Questo funziona perfettamente per me e io uso questo schema in tutte le mie attività di sorso. Comunque ho notato che qualche volta Gulp schiacciano i percorsi durante l'orologio "on change" se ottiene un singolo file. In tal caso eseguo personalmente la manipolazione del percorso, come path.dirname(srcPath.replace(srcDir, outputDir)) come argomento dest per la funzione .

Modifica: ho appena realizzato che probabilmente non risolverà il problema delle "variabili perse". Non ho nulla in testa per risolvere quello da quando organizzo i miei file LESS con un pesante uso di importazioni, quindi ogni file che necessiterebbe di un set di variabili avrebbe una dichiarazione di importazione che assicura che ci siano.

+1

Si potrebbe anche prendere una tacca e utilizzare qualcosa come LazyPipe, https://www.npmjs.com/package/lazypipe – ashwell

+0

Grazie per il vostro approccio, ma non vedo come questo compila solo il file che è cambiato e ancora produce un singolo file CSS. Sebbene mi piaccia l'idea con le importazioni. – phippu

+0

Sì, mi dispiace, avrei dovuto leggere la domanda un po 'di più. – ashwell

Problemi correlati