增量减少构建

时间:2014-10-29 10:50:47

标签: javascript gulp gulp-less

在我的办公室,我们正在使用gulp来构建我们较少的文件。我想改进构建任务,因为它花了一秒钟来构建我们最近工作的大型项目。想法是缓存文件,只传递更改的文件。所以我从谷歌开始,发现javascript的增量版本,并认为很容易以较少的方式重写它们。这是我开始使用的那个:https://github.com/gulpjs/gulp/blob/master/docs/recipes/incremental-builds-with-concatenate.md

经过几次不成功的尝试后,我最终得到了以下代码(使用最新的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);
        }
    });
});

但是这只传递了更改的文件,因为缺少变量定义,编译器失败了。如果我在较少的任务之前管道concat插件,gulp卡在(看似)无限循环中。

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

是否有人使用这些插件或设法以其他方式创建增量较少的构建。这是一个(杂乱的)github存储库,用于测试:https://github.com/tuelsch/perfect-less-build

PS:我打算加入linting,sourcemaps,minification,evtl。缓存清除和autoprefixer稍后。

3 个答案:

答案 0 :(得分:19)

像Ashwell一样,我发现使用导入确保所有LESS文件都可以访问他们需要的变量和mixin是很有用的。我还使用带导入的LESS文件进行捆绑。这有一些好处:

  1. 我可以利用LESS的功能来执行复杂的操作,例如覆盖变量值以生成多个主题,或者将类添加到另一个LESS文件中的每个规则。
  2. 不需要concat插件。
  3. Web Essentials for Visual Studio等工具可以提供语法帮助和输出预览,因为每个LESS文件都可以完全自行呈现。
  4. 你想导入变量,混合等等,但你不想实际输出另一个文件的全部内容,你可以使用:

    @import (reference) "_colors.less";
    

    经过几天的努力,我终于能够获得一个增量构建,正确地重建依赖于我更改的LESS文件的所有对象。我记录了结果here。这是最终的gulpfile:

    /*
     * 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']);
    

    现在我们可以简单地运行gulp live来构建我们所有的LESS文件,然后允许每个后续更改只构建那些依赖于已更改文件的文件。

答案 1 :(得分:2)

因此,当我想在gulp中进行增量构建时,我通过抽象出gulp任务的内部过程来做到这一点,这样我就不用担心保留缓存了。

// 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', {} );
    });
});

这对我很有用,我在整个gulp任务中使用这种模式。然而,我注意到有时候gulp会在观看期间压制路径"在变化"如果它获得一个文件。在这种情况下,我将自己的路径操作,例如path.dirname(srcPath.replace( srcDir, outputDir ))作为dest函数的参数runCompile

编辑:刚刚意识到这可能不会解决你失去的变量"问题。由于我组织了大量使用导入的LESS文件,因此我无法解决这个问题,所以每个需要一组变量的文件都会有一个导入语句,确保它们在那里

答案 2 :(得分:2)

我们实际上可以使用 gulp-newergulp-progeny-mtime 来完成这项任务。每次运行 gulp less 任务时,Stripling 的方法几乎是最好的,它会从头开始重新编译所有内容,然后开始观察文件。如果您使用大量较少的样式表,这将花费您很多时间。 gulp-progeny-mtimegulp-progeny 类似,只是它做的是真正的核心任务。每次文件通过 gulp-progeny-mtime 时,它都会检查导入中的任何修改,如果是,它将调整流中当前文件的 mtime,从而使其通过 >gulp-newer。我觉得这更好,因为我们甚至没有缓存任何东西。

   //Compile less for deployment 
   gulp.task("less", () => {
      return gulp
        .src(["static/less/**/*.less"])
        .pipe(progenyMtime())
        .pipe(
          plumber({
            errorHandler: function (err) {
              log(chalk.bgRed.white.bold(err.message));
            },
          })
        )
        .pipe(filter(["**/*.less", "!**/_*.less", "!static/less/includes*/**"]))
        .pipe(newer({ dest: "static/css/", ext: ".css" }))
        .pipe(debug({ title: "LESS" }))
        .pipe(
          less({
            plugins: [cleanCss, autoprefix],
          })
        )
        .pipe(gulp.dest("static/css/"));
    });

    //Watch changes is less and compile if changed.
    gulp.task("watch-less", () => {
      return gulp.watch("static/less/**/*.less", gulp.series("less"));
    });
    
    //Compile all less files on first run ( if changed ) then compile only modified files from next run
    gulp.task("live-less", gulp.series("less", "watch-less"));