在我的办公室里,我们正在使用gulp来构建我们的less文件。我想改进构建任务,因为在我们最近进行的一个大型项目上构建需要一秒钟的时间。我们的想法是缓存文件,只传递更改的文件。因此,我从谷歌开始,发现了javascript的增量构建,并认为用更少的成本重写它们会很容易。这是我开始的一个:https://github.com/gulpjs/gulp/blob/master/docs/recipes/incremental-builds-with-concatenate.md
在几次尝试失败后,我得到了以下代码(使用最新的引导程序分发版进行了测试):
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);
}
});
});
但这只传递更改后的文件,较少的编译器会因为缺少变量定义而失败。如果我在less任务之前管道连接concat插件,gullow就会陷入(似乎)无休止的循环。
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
附言:我正在计划添加linting,源地图,缩小,evtl。缓存破坏和稍后的自动refixer。
与Ashwell一样,我发现使用导入来确保我的所有LESS文件都可以访问它们所需的变量和mixin非常有用。我还将LESS文件与导入一起用于绑定目的。这有几个优点:
- 我可以利用LESS的功能来做一些复杂的事情,比如覆盖变量值以生成多个主题,或者在另一个LESS文件中为每个规则预先设置一个类
- 不需要concat插件
- 像Visual Studio的Web Essentials这样的工具可以提供语法帮助和输出预览,因为每个LESS文件都完全能够自己呈现
如果您想导入变量、mixin等,但又不想实际输出另一个文件的全部内容,可以使用:
@import (reference) "_colors.less";
经过几天的努力,我终于能够获得一个增量构建,它正确地重建了所有依赖于我更改的LESS文件的对象。我在这里记录了结果。这是最后一个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*@imports*(?:(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文件,然后允许每次后续更改只构建那些依赖于更改文件的文件。
因此,当我想在gump中进行增量构建时,我通过抽象出gump任务的内部过程来完成,这样我就不必担心保留缓存了。
// 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', {} );
});
});
这对我来说很好,我在所有的吞咽任务中都使用这种模式。然而,我注意到,有时gullow会在观察"更改"期间压缩路径,如果它得到一个文件。在这种情况下,我自己进行路径操作,类似于path.dirname(srcPath.replace( srcDir, outputDir ))
作为runCompile
函数的参数dest
。
编辑:刚刚意识到这可能不会解决你的"变量丢失"问题。我没有任何想法来解决这个问题,因为我大量使用导入来组织LESS文件,所以每个需要一组变量的文件都会有一个导入语句来确保它们在那里。
我们实际上可以使用gull更新和gull子代mtime执行此任务。Stripling的方法几乎是最好的,每次你运行无吞咽任务时,它都会从头开始重新编译所有内容,然后开始查看文件。如果你使用较少的样式表,这将花费你很多时间gull子代mtime类似于gulf子代,只是它做了真正的核心内容。每次文件通过gull子代mtime时,它都会检查导入中是否有任何修改,如果有,它将调整流中当前文件的mtime,从而使其通过gull更新。我觉得这样更好,因为我们甚至没有缓存任何东西。
//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"));