Keep gulp running with gulp-plumber

Posted in General on July 14, 2016 by manhhomienbienthuy Comments
Keep gulp running with gulp-plumber

Nếu bạn đang sử dụng gulp, có thể bạn sẽ thích cách làm việc của nó (stream) và nó rất đơn giản, mọi thứ được thể hiện trong code. Thực sự gulp rất tuyệt vời và tôi cũng đang dùng nó cho các công việc liên quan đến front-end của mình. Tuy nhiên, tôi gặp một vấn đề nhỏ với gulp: gulp watch sẽ chết khi gặp lỗi nào đó. Trong bài viết này, tôi sẽ trình bày một cách giữ cho gulp chạy liên tục, bằng cách sử dụng một plugin của nó: gulp-plumber

Trong bài viết này, tôi coi như bạn đã hiểu về gulp rồi. Nếu chưa, rất hoan nghênh các bạn đọc các bài viết khác giới thiệu về gulp trước khi chúng ta tiếp tục.

Gulp watch: build liên tục khi có thay đổi

Giả sử chúng ta có một gulp task như sau dùng để compile SCSS thành CSS:

gulp.task('stylesheet', () => {
    return gulp
        .src('static/scss/main.scss')
        .pipe(sass())
        .pipe(autoprefixer(config.autoprefixer))
        .pipe(gulp.dest('static/css'));
});

Nếu bạn đang làm việc với các task liên quan đến front-end như trên, chắc chắn bạn sẽ biết đến gulp watch. Bởi với task trên, mỗi lần muốn xem kết quả công việc mình đang làm, bạn sẽ phải gọi gulp task lại rồi chờ kết quả. Nhưng gulp watch có thể giúp chúng ta, nó sẽ theo dõi các file và sẽ gọi task (một hoặc một vài task tùy config) mỗi khi có sự thay đổi.

Ví dụ, chúng ta sẽ compile SCSS mỗi khi chúng ta thay đổi điều gì đó:

gulp.task('watch', ['stylesheet'], () => {
    gulp.watch('static/scss/*.scss', ['stylesheet']);
});

Trong trường hợp này, gulp watch sẽ compile cho chúng ta lần đầu tiên, sau đó theo dõi các file và tiếp tục compile nếu có thay đổi.

$ gulp watch
[13:36:53] Using gulpfile ~/gulpfile.js
[13:36:53] Starting 'stylesheet'...
[13:36:58] Finished 'stylesheet' after 5.1 s
[13:36:58] Starting 'watch'...
[13:36:58] Finished 'watch' after 22 ms
[13:37:22] Starting 'stylesheet'...
[13:37:22] Finished 'stylesheet' after 373 ms
[13:37:23] Starting 'stylesheet'...
[13:37:23] Finished 'stylesheet' after 189 ms

Vấn đề

Nhưng có một vấn đề, tôi chẳng phải pro lắm khi code SCSS và thỉnh thoảng tôi gặp những lỗi ngu si kiểu như thế này:

$white = #fff;

Tất nhiên, chúng ta không thể compile với lỗi như vậy:

$ gulp
[14:46:15] Using gulpfile ~/gulpfile.js
[14:46:15] Starting 'stylesheet'...

events.js:141
      throw er; // Unhandled 'error' event
      ^
Error: static/scss/_others.scss
Error: expected ':' after $white in assignment statement
        on line 221 of static/scss/_others.scss
>> $white = #fff;
   ^

    at options.error (.../gulp-sass/node_modules/node-sass/lib/index.js:271:32)

Đây chẳng phải vấn đề gì lớn lắm, có lỗi thì sửa thôi. Và lại compile lại từ đầu. Kể cả chúng ta có vài task đi chăng nữa, thì việc tốn thêm vài giây để compile cũng làm chậm trễ dự án gì.

Mọi chuyện sẽ trở thành vấn đề khi chúng ta sử dụng gulp watch, ví dụ mọi thứ đều vẫn rất ngon lành cho đến khi bạn code một dòng như trên:

$ gulp watch
...
[14:52:10] Starting 'stylesheet'...

events.js:141
      throw er; // Unhandled 'error' event
      ^
Error: static/scss/_others.scss
Error: expected ':' after $white in assignment statement
        on line 221 of static/scss/_others.scss
>> $white = #fff;
   ^

    at options.error (.../gulp-sass/node_modules/node-sass/lib/index.js:271:32)

Đến đây, nếu bạn để ý và nhận ra mình vừa code sai, thì mọi chuyện cũng đã quá muộn. Lúc này, bạn chỉ có thể sửa lại lỗi trên code của mình rồi chạy lại gulp watch. Một vài lần thì không sao, nhưng nhiều hơn thì khá là khó chịu đó. Đặc biệt, là nếu bạn không để ý và tin tưởng gulp watch sẽ tự động hóa mọi việc cho chúng ta, rất có thể bạn không nhận ra nó đã dừng lại từ lâu và đang tự hỏi mình tại sao code nhiều thế mà chưa thấy thay đổi gì.

Vì vậy chúng ta cần giải pháp nào đó để giải quyết vấn đề này.

Giải quyết lỗi như thế nào?

Vấn đề thì như vậy, bay giờ cần giải quyết sao đây? Trước hết, gulp cũng giống như các cơ chế stream khác của node, chúng ta có thể bắt các event lỗi trong gulp task:

gulp.task('stylesheet', () => {
    return gulp
        .src('static/scss/main.scss')
        .pipe(sass())
        .on('error', function (error) {
          console.log(error.toString());
        })
        .pipe(autoprefixer(config.autoprefixer))
        .pipe(gulp.dest('static/css'));
});

Tuy nhiên có vẻ như cơ chế xử lý lỗi của gulp, không được đánh giá cao, vì vậy, chúng ta nên sử dụng một plugin của gulp là gulp-plumber.

Sử dụng gulp-plumber

Sau một thời gian tìm kiếm, tôi biết đến gulp-plumber với câu giới thiệu đầy ấn tượng:

Prevent pipe breaking caused by errors from gulp plugins

gulp-plumber chính là những gì chúng ta cần để xử lý lỗi với gulp. Thử cài đặt và sử dụng nó xem sao:

gulp.task('stylesheet', () => {
    return gulp
        .src('static/scss/main.scss')
        .pipe(plumber())
        .pipe(sass())
        .pipe(autoprefixer(config.autoprefixer))
        .pipe(gulp.dest('static/css'));
});

Bây giờ, mỗi khi có lỗi, chúng ta có thể nhận được kết quả như sau:

$ gulp
[15:42:18] Using gulpfile ~/gulpfile.js
[15:42:18] Starting 'stylesheet'...
[15:42:18] Starting 'javascript'...
[15:42:19] Plumber found unhandled error:
 Error in plugin 'gulp-sass'
Message:
    static/scss/_others.scss
Error: expected ':' after $white in assignment statement
        on line 221 of static/scss/_others.scss
>> $white = #fff;
   ^

Details:
    formatted: Error: expected ':' after $white in assignment statement
        on line 221 of static/scss/_others.scss
>> $white = #fff;
   ^

    column: 1
    line: 221
    file: /home/naa/Works/django/aprigi/static/scss/_others.scss
    status: 1
    messageFormatted: static/scss/_others.scss
Error: expected ':' after $white in assignment statement
        on line 221 of static/scss/_others.scss
>> $white = #fff;
   ^

    messageOriginal: expected ':' after $white in assignment statement
    relativePath: static/scss/_others.scss
[15:42:19] Finished 'javascript' after 16 μs

Như ví dụ trên, chúng ta có thể thấy rằng kể cả task compile SCSS có lỗi thì các task khác (ở đây là javascript) vẫn có thể chạy bình thường. Tuy nhiên, như đã nói ở trên, thêm vài giây chạy thêm một task cũng không làm dự án chậm mấy nên ý nghĩa của gulp-plumber chưa phải là nhiều.

Thử với gulp-watch xem sao:

$ gulp watch
[15:53:07] Using gulpfile ~/gulpfile.js
[15:53:07] Starting 'stylesheet'...
[15:53:08] Finished 'stylesheet' after 1.22 s
[15:53:08] Starting 'watch'...
[15:53:08] Finished 'watch' after 20 ms
[15:53:17] Starting 'stylesheet'...
[15:53:17] Plumber found unhandled error:
 Error in plugin 'gulp-sass'
Message:
    static/scss/_others.scss
Error: expected ':' after $white in assignment statement
        on line 221 of static/scss/_others.scss
>> $white = #fff;
   ^

Details:
    formatted: Error: expected ':' after $white in assignment statement
        on line 221 of static/scss/_others.scss
>> $white = #fff;
   ^

    column: 1
    line: 221
    file: /home/naa/Works/django/aprigi/static/scss/_others.scss
    status: 1
    messageFormatted: static/scss/_others.scss
Error: expected ':' after $white in assignment statement
        on line 221 of static/scss/_others.scss
>> $white = #fff;
   ^

    messageOriginal: expected ':' after $white in assignment statement
    relativePath: static/scss/_others.scss

Quả thật là biết trêu ngươi, gulp bắt được lỗi và không chết, nhưng nó chẳng chạy tiếp và đơ như quân cơ ở đó. Vậy cũng chắc khác gì. Thậm chí còn mất công hơn do chúng ta phải tắt gulp đi bật lại.

Sau một thời gian tìm hiểu, thì tôi biết thêm rằng, gulp task cần nhận được tín hiệu end để biết rằng task đã kết thúc. Và khi chúng ta sử dụng gulp-plumber, tín hiệu end sẽ không bao giờ xuất hiện khi có lỗi, do đó gulp sẽ cứ chờ end nơi cuối con đường và đơ ở đó luôn.

Rất may, gulp-plumber cho phép chúng ta tự viết hàm xử lý khi gặp lỗi, do đó, chúng ta có thể phát tín hiệu end cho gulp:

gulp.task('stylesheet', () => {
    return gulp
        .src('static/scss/main.scss')
        .pipe(plumber({
              errorHandler: function (error) {
                  console.log(error.toString());
                  this.emit('end');
              })
        )
        .pipe(sass())
        .pipe(autoprefixer(config.autoprefixer))
        .pipe(gulp.dest('static/css'));
});

Thử kiểm tra lại với gulp watch:

$ gulp watch
[16:11:13] Using gulpfile ~/gulpfile.js
[16:11:13] Starting 'stylesheet'...
[16:11:15] Finished 'stylesheet' after 1.86 s
[16:11:15] Starting 'watch'...
[16:11:15] Finished 'watch' after 28 ms
[16:11:32] Starting 'stylesheet'...
Error in plugin 'gulp-sass'
Message:
    static/scss/_others.scss
Error: expected ':' after $white in assignment statement
        on line 221 of static/scss/_others.scss
>> $white = #fff;
   ^

Details:
    formatted: Error: expected ':' after $white in assignment statement
        on line 221 of static/scss/_others.scss
>> $white = #fff;
   ^

    column: 1
    line: 221
    file: .../static/scss/_others.scss
    status: 1
    messageFormatted: static/scss/_others.scss
Error: expected ':' after $white in assignment statement
        on line 221 of static/scss/_others.scss
>> $white = #fff;
   ^

    messageOriginal: expected ':' after $white in assignment statement
    relativePath: static/scss/_others.scss
[16:11:32] Finished 'stylesheet' after 33 ms

Vậy là tín hiệu end đã hoạt động, gulp watch vẫn tiếp tục chạy. Sửa lại lỗi và xem gulp watch compile cho chúng ta:

[16:11:37] Starting 'stylesheet'...
[16:11:37] Finished 'stylesheet' after 267 ms

Đây đúng là điều mà chúng ta cần. Gulp watch vẫn tiếp tục chạy khi mà lỗi xảy ra.

Kết luận

Gulp là một công cụ tuyệt vời, tuy nhiên cơ chế xử lý khi gặp lỗi của nó chưa được phát triển đầy đủ lắm. Và gulp-plumber đã ra đời để giải quyết vấn đề đó. Nó không chỉ giúp xử lý lỗi của các task, mà còn có thể xử lý giúp gulp watch tiếp tục chạy và build cho chúng ta. Chỉ có một lưu ý nhỏ, đó là cần phải phát tín hiệu end (this.emit('end');) để gulp có thể dừng task hiện tại lại và tiếp tục quá trình xử lý của nó.

I apologise for any typos. If you notice a problem, please let me know.

Thank you all for your attention.