每次更改完样式,需要手工刷新页面才行,如果用过Bracket等开发工具的会知道,有一个功能是动态展示,只要保存,就会自动更新页面显示,Gulp通过Browsersync这个插件也可以实现该功能。

  1. 安装Browsersync和修改Gulp任务
  2. WebStorm中配置项目
  3. 结构化Gulp任务文件
  4. 处理Gulp异常

安装Browsersync和修改Gulp任务

首先还是安装这个包,有点大,可能需要下载一会:

npm install browser-sync --save-def

之后需要修改gulpfile.js,来使用这个包(这里遇到了.start is not a function错误,由于还没有深入gulp,只好先改成3.9.1版。),然后使用了例子的文件:

var gulp = require('gulp'),
watch = require('gulp-watch'),
postcss = require('gulp-postcss'),
autoprefixer = require('autoprefixer'),
cssvars = require('postcss-simple-vars'),
nested = require('postcss-nested'),
cssImport = require('postcss-import'),
browserSync = require('browser-sync').create();

gulp.task('default', function() {
  console.log("Hooray - you created a Gulp task.");
});

gulp.task('html', function() {
  console.log("Imagine something useful being done to your HTML here.");
});

gulp.task('styles', function() {
  return gulp.src('./app/assets/styles/styles.css')
    .pipe(postcss([cssImport, cssvars, nested, autoprefixer]))
    .pipe(gulp.dest('./app/temp/styles'));
});

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

  browserSync.init({
    server: {
      baseDir: "app"
    }
  });

  watch('./app/index.html', function() {
    browserSync.reload();
  });

  watch('./app/assets/styles/**/*.css', function() {
    gulp.start('cssInject');
  });

});

gulp.task('cssInject', ['styles'], function() {
  return gulp.src('./app/temp/styles/styles.css')
    .pipe(browserSync.stream());
});

这里有几个地方需要学习一下:

  1. 导入的时候是导入require('browser-sync').create(),不是直接导包。Node.js的导入包的设置与具体的包有关。
  2. 如果要更新HTML页面,在监听HTML文件的任务里需要使用browserSync.reload();
  3. 监听CSS文件,这里使用到了任务以来,任务的链条是这样的
    1. 监听到CSS源文件发生变动
    2. 应该执行cssInject任务–》这个任务依赖于'styles'(作为第二个参数,任务数组的一个元素传入),所以会先执行'styles'任务
    3. 执行cssInject任务,将生成的style.css文件通过管道传递给browserSync的流,即去更新页面。

这里的新知识就是关于任务依赖的参数。被当做依赖传入的任务,会先得到执行,执行完毕之后,再执行当前任务。

browserSync需要先初始化,其中有个server参数是指的项目根目录,当前路径是运行gulpfile.js的路径,所以设置为相对路径'app'

然后启动watch任务,实际上browserSync是一个小的浏览器,采用推送方式自动更新,可以看到命令行中的提示:

>gulp watch
[18:20:40] Using gulpfile D:\Coding\frontendworkflow\gulpfile.js
[18:20:40] Starting 'watch'...
[18:20:40] Finished 'watch' after 31 ms
[Browsersync] Access URLs:
 ---------------------------------------
       Local: http://localhost:3000
    External: http://192.168.100.85:3000
 ---------------------------------------
          UI: http://localhost:3001
 UI External: http://localhost:3001
 ---------------------------------------
[Browsersync] Serving files from: app

此时无论修改index.html还是所有的css文件,保存后都会立刻反应在页面上。

仔细观察的话,会发现每次页面刷新,右上角都会提示连接到browsersync,想关掉这个提示需要加一行配置:

  browserSync.init({
    notify: false,
    server: {
      baseDir: "app"
    }
  });

browsersync的功能很强大,比如如果另外开启一个浏览器,在地址栏输入http://localhost:3000/,滚动其中一个浏览器窗口,另外一个也会滚动。样式也会同步更新,便于看浏览器兼容性。

http://localhost:3001/则是browsersync的管理面板,有兴趣可以继续研究。

WebStorm中配置项目

这是我自己琢磨出来的,在WebStorm中配置普通的前端工程化项目按照如下步骤:

  1. 创建一个Node.js项目,或者普通的Web项目也可以。然后到File | Settings | Languages & Frameworks | Node.js and NPM
    中设置本机Node.js和NPM的地址,之后可以在左侧的External Libraries中看到Node.js Core,说明配置成功。
  2. 遇到代码检测错误,可以在拼写错误的地方按Alt+Enter,在提示中选择启用Node.js拼写支持,即可写Node.js的特有代码。
  3. 右键在左侧项目目录中点击gulpfile.js,可以看到IDE认出了这是一个gulp任务文件,菜单中最下边有Show Gulp Tasks的选项,点击后可以打开Gulp面板,列出所有任务,可以启动任意任务,比从命令行启动或者terminal窗口启动占用一个窗口要方便。
  4. 从Git新下来项目的时候,IDE会提示检测到NPM,可以直接让其运行NPM来安装依赖。

结构化Gulp任务文件

现在的gulpfile.js文件已经有点多了,导入文件,监听html和监听css的任务都在一起。这个文件也是可以通过分成小文件来有效的实现结构化管理。

在项目目录下新建gulp目录,然后在其中创建tasks目录。

要将任务分离出去,创建一个与任务同名的js文件,比如styles.js,将在gulpfile.js中的以下部分剪切过去,另外还需要导入对应的包。styles.js的内容像这样:

let gulp = require('gulp'),
    postcss = require('gulp-postcss'),
    autoprefixer = require('autoprefixer'),
    cssvars = require('postcss-simple-vars'),
    nested = require('postcss-nested'),
    cssImport = require('postcss-import');

    gulp.task('styles', function() {
    return gulp.src('./app/assets/styles/styles.css')
        .pipe(postcss([cssImport, cssvars, nested, autoprefixer]))
        .pipe(gulp.dest('./app/temp/styles'));
});

再创建watch.jswatch任务分离出去:

var gulp = require('gulp'),
    browserSync = require('browser-sync').create(),
    watch = require('gulp-watch');

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

    browserSync.init({
        notify: false,
        server: {
            baseDir: "app"
        }
    });

    watch('./app/index.html', function () {
        browserSync.reload();
    });

    watch('./app/assets/styles/**/*.css', function () {
        gulp.start('cssInject');
    });

});

gulp.task('cssInject', ['styles'], function () {
    return gulp.src('./app/temp/styles/styles.css')
        .pipe(browserSync.stream());
});

原来的文件里两个dummy 任务都可以删除,导入也没有什么用了,所以可以全部删除。很显然不能是一个空文件,其实像CSS文件一样,剩下要做的就是导入几个任务文件:

require('./gulp/tasks/styles');
require('./gulp/tasks/watch');

这样就有效分离了gulp的各个任务文件。而运行的命令不变,依然是gulp watch。而且WebStorm依然可以解析该Gulp文件并启动任务。

处理Gulp异常

在学习的过程中,肯定会发现,如果CSS保存了错误的指令,postcss在编译的时候会报错,然后watch任务也停了下来,必须手工启动,browsersync也会失去连接,这显然比较麻烦。

CSS语法错误是很常见的错误,如果不希望工作流被打断,很显然需要处理编译中的异常,以让程序不会停止。由于编译过程在styles任务中执行,所以来修改一下styles.js加入处理异常的功能:

gulp.task('styles', function () {
    return gulp.src('./app/assets/styles/styles.css')
        .pipe(postcss([cssImport, cssvars, nested, autoprefixer]))
        .on('error', function (error) {
            console.log(error);
            this.emit('end');
        })
        .pipe(gulp.dest('./app/temp/styles'));
});

在完成编译工作的过程里,加上了一个on函数,第一个参数是类型,用字符串'error'表示出现异常,第二个参数是一个处理函数,参数可以传入错误对象,然后打印出来。

函数不需要返回值,但是需要通过this.emit('end')抛出一个类似事件的东西,告诉流没有异常,而是正常结束。

这样我们在编译CSS文件的过程里,遇到错误不会中止,同时又可以告诉我们哪里出了错误。比如我们故意使用一个不存在的CSS变量:

[20:37:24] Starting 'styles'...
{ CssSyntaxError: D:\Coding\frontendworkflow\app\assets\styles\modules\_large-hero.css:5:5: Undefined variable $color
    ......
  fileName:
   'D:\\Coding\\frontendworkflow\\app\\assets\\styles\\modules\\_large-hero.css',
    lineNumber: 5 }
[20:37:24] Finished 'styles' after 111 ms
[20:37:24] Starting 'cssInject'...
[Browsersync] 1 file changed (styles.css)
[20:37:24] Finished 'cssInject' after 1.45 ms

会发现打印出了编译出错的地方,然而任务并没有结束,browsersync也一样更新正常。现在不会中断的,反应式的前端工具流就搭建完成了。