LeeJim/better-fe

开始拥抱gulp[翻译]

Opened this issue · 0 comments

原文地址:http://qiulijun.com/2016/09/23/getting-started-with-gulp/

前端工作流中常用的构建工具除了grunt,还有一个gulp(当然现在还有webpack)。gulp是一个直观的,执行代码覆盖配置(code-over-configuration)的,基于nodejs流的构建工具,而且执行很快。

既然有了grunt,为啥还要来学习gulp呢?这是一个好问题,用过grunt的都知道,每次写Gruntfile.js的时候,都要先写一大片的配置文件,而gulpgrunt最大的差别在于,gulp是执行代码覆盖配置的。这样的好处在于,gulpfile.js很容易写,而且阅读起来清晰明了,并且容易维护。

gulp使用node.js的流(stream),这让gulp构建的时候不需要写入临时的文件/目录到硬盘上。如果你想要学习关于流(stream)的更多知识,可以看这篇文章(写得很好)

gulp允许将你输入的源文件使用管道(pipe),让文件流经一堆的插件,最后输出出来。而不是像grunt那样,为每一个grunt都写一些配置信息和输入输出路径。让我们看下使用gruntgulp写的Sass编译:

Grunt:

sass: {
  dist: {
    options: {
      style: 'expanded'
    },
    files: {
      'dist/assets/css/main.css': 'src/styles/main.scss',
    }
  }
},

autoprefixer: {
  dist: {
    options: {
      browsers: [
        'last 2 version', 'safari 5', 'ie 8', 'ios 6', 'android 4'
      ]
    },
    src: 'dist/assets/css/main.css',
    dest: 'dist/assets/css/main.css'
  }
},

grunt.registerTask('styles', ['sass', 'autoprefixer']);

Grunt需要为每个插件各自写配置信息,并且指定输入输出的路径。比如,我们输入一个文件到Sass插件,执行完就要保存输出的文件。然后我们再将sass的输出文件传递给Autoprefixer输入,然后我们再将文件输出保存起来。

gulp:

gulp.task('sass', function() {
  return sass('src/styles/main.scss', { style: 'expanded' })
    .pipe(autoprefixer('last 2 version', 'safari 5', 'ie 8', 'ios 6', 'android 4'))
    .pipe(gulp.dest('dist/assets/css'))
});

使用gulp的时候,我们只需要输入一个文件,然后经过Sass插件修改,然后经过Autoprefixer插件修改,最后输出一个文件。这样会让我们的构建速度加快,因为我们避免了多次不必要的读取和写入。

安装Gulp

在我们钻研配置任务信息之前,我们需要安装gulp

$ npm install gulp -g

以上命令会在全局环境上安装gulp,让我们可以使用gulp命令行。然后,我们需要在项目文件夹内安装本地的gulpcd到你的项目路径,然后运行一下命令(执行命令前需确保项目下有package.json这个文件)

$ npm install gulp --save-dev

以上命令会在项目内安装本地的gulp并保存到package.json里的devDependencies

安装gulp插件

我们需要安装一些插件来完成以下的任务:

通过以下命令来安装这些插件:

npm install gulp-ruby-sass gulp-autoprefixer gulp-cssnano gulp-jshint gulp-concat gulp-uglify gulp-imagemin gulp-notify gulp-rename gulp-livereload gulp-cache del --save-dev

这将会安装全部需要的插件并将它们保存到package.jsondevDependencies里面。你可以在这里找到gulp的全部插件。

加载插件

之后,我们要创建一个文件夹gulpfile.js并加载这些插件:

var gulp = require('gulp'),
    sass = require('gulp-ruby-sass'),
    autoprefixer = require('gulp-autoprefixer'),
    cssnano = require('gulp-cssnano'),
    jshint = require('gulp-jshint'),
    uglify = require('gulp-uglify'),
    imagemin = require('gulp-imagemin'),
    rename = require('gulp-rename'),
    concat = require('gulp-concat'),
    notify = require('gulp-notify'),
    cache = require('gulp-cache'),
    livereload = require('gulp-livereload'),
    del = require('del');

这个时候,看起来是不是好像需要写的东西比grunt还多?其实,gulp插件和grunt插件有略微的不同——gulp插件的理念是:每个插件只需做一件事然后把这件事做好就可以了(翻译得好渣,原文是they are designed to do one thing and one thing well)比如,gruntimagemin使用缓存来避免压缩已经压缩过的图片;而gulp则需要cache插件来协助完成这样的任务,当然cache插件也可以缓存其他的文件。这就给你的构建任务添加了许多灵活性,很酷是吧?

我们同样可以像grunt那样自动加载所有已安装的插件,不过为了这篇文章,我们就将坚持纯手工工艺!

创建任务

Compile Sass, Autoprefix and minify

首先,我们先配置Sass编译,然后使用Autoprefixer添加厂商前缀,这时可以先输出到一个目的地。之后,再将文件流传递给cssnao压缩成一个.min版本,最后再输出到另一个目的地,最后的最后调用notify提示我们任务完成了:

gulp.task('styles', function() {
  return sass('src/styles/main.scss', { style: 'expanded' })
    .pipe(autoprefixer('last 2 version'))
    .pipe(gulp.dest('dist/assets/css'))
    .pipe(rename({suffix: '.min'}))
    .pipe(cssnano())
    .pipe(gulp.dest('dist/assets/css'))
    .pipe(notify({ message: 'Styles task complete' }));
});

继续往下讲之前,有一点东西需要解释一下:

gulp.task('styles', function() {...});

这里的gulp.taskAPI是用来创建任务的。我们在命令行工具中可以使用$ gulp styles运行上面的任务。

return sass('src/styles/main.scss', { style: 'expanded' })

这里是一个新的gulp-rubu-sassAPI,我们用来定义源文件并可以添加一些参数配置;而在其他的许多插件中,我们将会用gulp.srcAPI来代替(在文章的下部分你将会看到)这同样可以使用glob pattern,比如:/**/*.scss来匹配多个文件。(以下未翻译:By returning the stream it makes it asynchronous, ensuring the task is fully complete before we get a notification to say it’s finished.)

.pipe(autoprefixer('last 2 version'))

我们通过.pipe()来导数据流到一个插件。通常我们可以在各个插件的GitHubPage找到各自的options信息。为了方便大家,我已经在上面粘贴了它们的地址。管道(Pipes)是可链式调用的,因此你可以尽可能地添加插件到文件流中。

.pipe(gulp.dest('dist/assets/css'));

这里的gulp.destAPI我们是用来设置输出路径的。一个任务可以有多个输出路径的,上面的例子就是一个用来输出expanded version(正常大小版本),另一个输出minifed version(压缩版本)

我建议去看下gulpAPI文档来更好地理解这些方法,它并不像听起来那么吓人!

JSHint, concat, and minify JavaScript

希望你现在对如何创建一个gulp任务有一个很好的idea,接下来我们将设置scripts任务去检测,合并和压缩js文件:

gulp.task('scripts', function() {
  return gulp.src('src/scripts/**/*.js')
    .pipe(jshint('.jshintrc'))
    .pipe(jshint.reporter('default'))
    .pipe(concat('main.js'))
    .pipe(gulp.dest('dist/assets/js'))
    .pipe(rename({suffix: '.min'}))
    .pipe(uglify())
    .pipe(gulp.dest('dist/assets/js'))
    .pipe(notify({ message: 'Scripts task complete' }));
});

这里我们就是用gulp.srcAPI来指定我们的输入文件。有一件事情需要注意的是,我们需要为JSHint指定一个reporter,我使用的是适合大部分人使用的默认reporter,你可以在JSHint官网找到更多的信息。

Compress Images

接下来,我们设置图片压缩。

gulp.task('images', function() {
  return gulp.src('src/images/**/*')
    .pipe(imagemin({ optimizationLevel: 3, progressive: true, interlaced: true }))
    .pipe(gulp.dest('dist/assets/img'))
    .pipe(notify({ message: 'Images task complete' }));
});

这里我们将拿到一些图片,然后导到imagemin插件。我们可以做得更好一点,就是使用缓存来避免重复压缩已经压缩过的图片——这只需要我们之前已经安装好的gulp-cache插件。为了实现这个,我们需要改变这一行:

.pipe(imagemin({ optimizationLevel: 3, progressive: true, interlaced: true }))

改成:

.pipe(cache(imagemin({ optimizationLevel: 5, progressive: true, interlaced: true })))

现在就只用新的图片和改变过的图片会被压缩了,nice吧?

Clean up!

在部署之前,清除输出目录再执行构建任务是一个好想法——为了避免一些在原目录也就是输出目录已经删除的文件还遗留在输出目录:

gulp.task('clean', function() {
    return del(['dist/assets/css', 'dist/assets/js', 'dist/assets/img']);
});

这里我们不需要使用gulp的插件,因为我们可以利用node模块的优点,我们使用return来确保在退出之前完成任务(翻译得有点渣,原文奉上:We don’t need to use a gulp plugin here as we can take advantage of Node modules directly within gulp. We use a return to ensure the task finishes before exiting.)

The default task

我们定义的default任务可以直接使用$ gulp运行,比如:

gulp.task('default', ['clean'], function() {
    gulp.start('styles', 'scripts', 'images');
});

需要注意到的是,我们添加了一个数组到gulp.task上,在这里我们可以定义任务依赖。在这里例子里,clean任务将会在gulp.start之前先运行。gulp里的任务是并发的,所以无法确定各个任务的完成顺序,所以我们需要确保clean任务完成之后才开始其他的任务。

注意

建议是的不要在依赖任务中使用gulp.start,不过在这个脚本里为了确保clean完全完成,这似乎是最好的选择(原文:It’s advised against using gulp.start in favour of executing tasks in the dependency arrary, but in this scenario to ensure clean fully completes, it seems the best option)

watch

监听我们的文件,然后当它们修改的时候执行相应的任务。首先我们要创建一个新的任务,然后使用gulp.watchAPI来开始监听文件。

gulp.task('watch', function() {

  // Watch .scss files
  gulp.watch('src/styles/**/*.scss', ['styles']);

  // Watch .js files
  gulp.watch('src/scripts/**/*.js', ['scripts']);

  // Watch image files
  gulp.watch('src/images/**/*', ['images']);

});

我们可以通过gulp.watchAPI来指定我们需要监听的文件,然后通过依赖数组来定义需要执行的任务。现在我们可以运行$ gulp watch,然后修改一下对应监听目录下的文件,就将会执行对应的任务。

LiveReload

gulp同样可以在文件修改的时候刷新页面,我们需要修改我们的watch任务来配置LiveReload服务:

gulp.task('watch', function() {

  // Create LiveReload server
  livereload.listen();

  // Watch any files in dist/, reload on change
  gulp.watch(['dist/**']).on('change', livereload.changed);

});

为了让实现这个梦想,你需要安装和启用LiveReload的浏览器插件。或者你也可以手动的添加这些东西

合并所有代码

现在你拥有了一个完整的gulpfile了, 它来自于这里

/*!
 * gulp
 * $ npm install gulp-ruby-sass gulp-autoprefixer gulp-cssnano gulp-jshint gulp-concat gulp-uglify gulp-imagemin gulp-notify gulp-rename gulp-livereload gulp-cache del --save-dev
 */

// Load plugins
var gulp = require('gulp'),
    sass = require('gulp-ruby-sass'),
    autoprefixer = require('gulp-autoprefixer'),
    cssnano = require('gulp-cssnano'),
    jshint = require('gulp-jshint'),
    uglify = require('gulp-uglify'),
    imagemin = require('gulp-imagemin'),
    rename = require('gulp-rename'),
    concat = require('gulp-concat'),
    notify = require('gulp-notify'),
    cache = require('gulp-cache'),
    livereload = require('gulp-livereload'),
    del = require('del');

// Styles
gulp.task('styles', function() {
  return sass('src/styles/main.scss', { style: 'expanded' })
    .pipe(autoprefixer('last 2 version'))
    .pipe(gulp.dest('dist/styles'))
    .pipe(rename({ suffix: '.min' }))
    .pipe(cssnano())
    .pipe(gulp.dest('dist/styles'))
    .pipe(notify({ message: 'Styles task complete' }));
});

// Scripts
gulp.task('scripts', function() {
  return gulp.src('src/scripts/**/*.js')
    .pipe(jshint('.jshintrc'))
    .pipe(jshint.reporter('default'))
    .pipe(concat('main.js'))
    .pipe(gulp.dest('dist/scripts'))
    .pipe(rename({ suffix: '.min' }))
    .pipe(uglify())
    .pipe(gulp.dest('dist/scripts'))
    .pipe(notify({ message: 'Scripts task complete' }));
});

// Images
gulp.task('images', function() {
  return gulp.src('src/images/**/*')
    .pipe(cache(imagemin({ optimizationLevel: 3, progressive: true, interlaced: true })))
    .pipe(gulp.dest('dist/images'))
    .pipe(notify({ message: 'Images task complete' }));
});

// Clean
gulp.task('clean', function() {
  return del(['dist/styles', 'dist/scripts', 'dist/images']);
});

// Default task
gulp.task('default', ['clean'], function() {
  gulp.start('styles', 'scripts', 'images');
});

// Watch
gulp.task('watch', function() {

  // Watch .scss files
  gulp.watch('src/styles/**/*.scss', ['styles']);

  // Watch .js files
  gulp.watch('src/scripts/**/*.js', ['scripts']);

  // Watch image files
  gulp.watch('src/images/**/*', ['images']);

  // Create LiveReload server
  livereload.listen();

  // Watch any files in dist/, reload on change
  gulp.watch(['dist/**']).on('change', livereload.changed);

});

我也使用grunt写了一份配置文件来完成同样的东西,你可以对比一些有什么不一样。拿好了,不送了