使用Node的Modules构建复杂的Gulpfile

Published on 2017 - 01 - 15

Why use plain node.js modules?

A common misunderstanding and topic of confusion for gulp beginners is when and why to use plain node.js modules in place of using or creating a new gulp plugin. Generally, the best practice is that if you can use plain node.js modules, then you should use plain node.js modules.

Gulp was built on the Unix philosophy that we can pull together many smaller, single-purpose applications to perform more complex actions. With this philosophy we are never duplicating work or creating redundant code. Additionally, it is easier to test the expectations of each smaller application than it would be to test a large collection of duplicated code.

The gulp team spends a lot of time ensuring that their plugin ecosystem maintains the highest quality. Part of ensuring this is making sure that no gulp plugin deviates from this core philosophy. Any gulp plugins that don't follow it are blacklisted and will not be shown to other users in the official gulp plugin search. This is very important to remember when looking for plugins to use, and if you ever plan on creating a plugin yourself. Don't duplicate work that has already been done. If you would like to help improve a plugin, contact the maintainer and work together to improve it for everyone!

If we all decided to create our own version of every plugin, then the ecosystem would be inundated with duplication, which would only confuse users and damage the overall perception of gulp as a tool.

Static server

For quick and easy distribution, having the ability to spin up a small file server can be a great time saver and will prevent the need to run larger server software such as Apache or Nginx.

For this task, instead of using a gulp plugin we are going to use the Connect middleware framework module. Middleware is a small layer that allows us to build additional functionality into our applications, or in this case our gulp tasks.

Connect itself only acts as the framework to pull in additional functionality, so in addition to Connect we will need to install the plugin that we wish to use. To spin up a static server, we will be using the serve-static node.js module.

Installing modules

Installing plain node.js modules is exactly the same process as installing gulp plugins because, despite the gulp focus, gulp plugins are still node.js modules at heart. The modules we will be using for this specific task are connect and serve-static.

To install connect and serve-static, we will run the following command:

npm install connect serve-static --save-dev

Including modules

As you might expect, we will include any plain node.js modules in the same way that we included our gulp plugins from the previous chapter. We will be adding these two to the bottom of our code.

// Modules & Plugins
var gulp = require('gulp');
var concat = require('gulp-concat');
var myth = require('gulp-myth');
var uglify = require('gulp-uglify');
var jshint = require('gulp-jshint');
var imagemin = require('gulp-imagemin');
var connect = require('connect'); // Added
var serve = require('serve-static'); // Added

Writing a server task

Once our node.js modules have been installed and included, we can begin writing our new task. We will introduce some more advanced node.js-specific syntax, but it will most likely feel somewhat familiar to the tasks we created in the previous chapter.

Our server task will look like this:

gulp.task('server', function() {
  return connect().use(serve(__dirname))
    .listen(8080)
    .on('listening', function() {
      console.log('Server Running: View at http://localhost:8080');
      });
});

The first thing you will notice is that aside from our main .task() wrapper method we don't actually use gulp at all in this task. It's literally a wrapper to label and run the node.js code that resides within.

Let's take a moment to discuss this code to better understand what it does:

First we include our connect() function. Next, we will use its .use() method and pass it to our serve() module. In the serve() module we will pass it to the directory we wish to serve from, in our case __dirname, which is used by node.js to output the name of the directory that the currently executing script resides in. Next we assign port 8080 to listen for requests. Finally, we use the .on() method to check whether our server is successfully listening, and then we log a small command to announce that the server is running as expected.

Compared to the gulp tasks we've created so far, not a lot has changed. The only difference is that we are using the methods for the plain node.js module instead of gulp's built-in methods such as .src() and .dest(). That's because in this case we aren't actually using gulp to modify any data. We are only using it to label, organize, and control the use of a plain node.js module within a gulp task. The server doesn't have any use for modifying our data or using streams, it simply exists as a way to serve the files to a browser.

Finally, if you would like, you can include this task inside your default task so that this task is run by default.

When added, your default task should now look like this:

For Gulp 4.0+

// Default Task
   gulp.task('default', gulp.parallel('styles', 'scripts', 'images', 'server', 'watch'));

For Gulp 3.9

// Default Task
   gulp.task('default', ['styles', 'scripts', 'images', 'server', 'watch']);

BrowserSync

As web developers, we spend a lot of time interacting with our browsers. Whether we are debugging our code, resizing our windows, or simply refreshing our pages, we often perform a lot of repetitive tasks in order to do our jobs.

In this section, we will explore ways to eliminate browser refreshes and make some other handy improvements to our browser experience. To do this, we will use an incredible node.js module called BrowserSync.

BrowserSync is one of the most impressive tools I have ever used. Upon first use, it will truly wow you with what it is capable of doing. Unlike similar tools that only handle browser refreshing, BrowserSync will additionally sync up every action that is performed on your pages across any device on your local network.

This process allows you to have multiple devices viewing the same project simultaneously and maintains actions, such as scrolling, in sync across them all. It's really quite impressive and can save you a ton of time when developing, especially if you're working on responsive designs.

Installing BrowserSync

To use BrowserSync, we first need to install it. The process is the same as all of the other plugins and modules that we have installed previously.

To install BrowserSync run the following command:

npm install browser-sync --save-dev

As always, we will include our –-save-dev flag to ensure that it is added to our development dependencies list.

Including BrowserSync

Once installed, we can add the module to our project by adding it to our list of requires at the top of our gulpfile.

The module/plugin to be included in the code is as follows:

// Modules & Plugins
var gulp = require('gulp');
var concat = require('gulp-concat');
var myth = require('gulp-myth');
var uglify = require('gulp-uglify');
var jshint = require('gulp-jshint');
var imagemin = require('gulp-imagemin');
var connect = require('connect');
var serve = require('serve-static');
var browsersync = require('browser-sync'); // Added

Writing the BrowserSync task

Now, let's create a small task that we can call anytime we need to communicate any changes we make to our browsers.

The code for the BrowserSync task is as follows:

gulp.task('browsersync', function(cb) {
    return browsersync({
        server: {
            baseDir:'./'
        }
    }, cb);
});

In this task, we have simply called our browsersync module and provided it with our base project directory as the location to create the server instance.

As a final step, we need to add some additional information to our watch task to let BrowserSync know when to reload our browsers:

For Gulp 4.0+

// Watch Task
  gulp.task('watch', function() {
       gulp.watch('app/css/*.css', gulp.series('styles', browsersync.reload));
       gulp.watch('app/js/*.js', gulp.series('scripts', browsersync.reload));
       gulp.watch('app/img/*', gulp.series('images', browsersync.reload));
});

For Gulp 3.9

// Watch Task
  gulp.task('watch', function() {
       gulp.watch('app/css/*.css', ['styles', browsersync.reload]);
       gulp.watch('app/js/*.js', ['scripts', browsersync.reload]);
       gulp.watch('app/img/*', ['images', browsersync.reload]);
});

Now, we need our watch methods to run two items instead of one. So, we have added in the .series method to execute our tasks in a specified order. In the series method, we will pass in our task name first, and then include a reference to the .reload method of our browsersync task. This will allow our tasks to complete before communicating any changes to our source files and instruct BrowserSync to refresh our browsers.

If you would like this task to run by default, be sure that you also include it to your default task like so:

For Gulp 4.0+

// Default Task
gulp.task('default', gulp.parallel('styles', 'scripts', 'images', 'browsersync', 'watch'));

For Gulp 3.9

// Default Task
gulp.task('default', ['styles', 'scripts', 'images', 'browsersync', 'watch']);

As soon as gulp runs our browsersync task, it will immediately create a server and open a new browser window pointing to http://localhost:3000, which is the default port that BrowserSync uses. Once this has been completed, everything that runs on that page will be automatically refreshed if you update your code.

Additionally, you will be given an external URL that you can visit on other devices, such as a phone or tablet, as long as they are all on the same network. Once you have visited that URL, all of your actions will be kept in sync and any time you make changes to your code, all of the devices will refresh to show those changes automatically. It even tracks your scrolling movement on every single device, so if you decide to scroll up on your phone, the website will also scroll up on every other device, including your laptop or computer. It's an incredibly neat and helpful tool.

It is worth noting that using both a static server and BrowserSync are unnecessary as they serve a similar purpose. It's really dependent on which suits your project best. In most cases, I would suggest using BrowserSync due to the added features that it provides.

Browserify

As you have now experienced when creating a gulpfile and writing tasks, the way node.js breaks code into modules is very clean and natural. With node.js we can assign an entire module to a variable by using node.js' require() function.

This pattern is actually based on a specification called CommonJS and it is a truly fantastic way to organize and modularize code. Browserify is a tool that was created to leverage that exact same specification so that you can write all of your JavaScript code that way. Not only will you be able to modularize your own project code, but you now have the ability to use modules from npm in your non-node.js JavaScript. It's quite remarkable.

The goal of this task is to use Browserify so that we can write our JavaScript files using the CommonJS spec that node.js uses to include and modularize various pieces of our application. Additionally, we will also be able to use many other node.js modules in our projects on the client side without having to run them on a server.

Installing modules

We will use both the browserify and vinyl-source-stream modules for this task. Many node.js modules operate using node.js streams, but gulp uses a virtual file format called vinyl to process files. So, to interact with modules such as Browserify, we must convert the stream into a format that we can use by including the vinyl-source-steam module.

To install these modules, run the following command:

npm install browserify vinyl-source-stream --save-dev

Including modules

Once we have installed our modules, we can add them to our gulpfile by appending them to our list of requires, like this:

// Modules & Plugins
var gulp = require('gulp');
var concat = require('gulp-concat');
var myth = require('gulp-myth');
var uglify = require('gulp-uglify');
var jshint = require('gulp-jshint');
var imagemin = require('gulp-imagemin');
var connect = require('connect');
var serve = require('serve-static');
var browsersync = require('browser-sync');
var browserify = require('browserify'); // Added
var source = require('vinyl-source-stream'); // Added

Writing the Browserify task

As with all of our other tasks, we always start with our main task wrapper method and provide our task with a name. In this task, we will blend new methods from our Browserify module with some of gulp's methods that you are already familiar with.

The code to run Browserify as a gulp task look like this:

gulp.task('browserify', function() {
  return browserify('./app/js/app.js')
    .bundle()
    .pipe(source('bundle.js'))
    .pipe(gulp.dest('dist'));
});

To better understand what is happening in this task, let's break it down into steps:

  1. First, we pass our main JavaScript application that requires our modules to browserify().
  2. We then run Browserify's built-in .bundle() method, which will bundle our source file and its dependencies into a single file.
  3. The file then gets passed to our first .pipe() method, which uses vinyl-source-stream to convert the node.js stream into a vinyl stream, and then we provide the bundle with the name bundle.js.
  4. Once our file has been bundled, processed, and named, we finally pass it to our final pipe, which uses the .dest() method to output the file.

The only really confusing portion of this task is understanding the difference between node.js streams and gulp streams. BKnowing when and how to work around the differences in streams will make your life a lot easier when using plain node.js modules inside of gulp tasks.

While this task sets you up to use Browserify inside gulp, you should take some time to really understand how Browserify works and how to make use of it in your projects. To learn more about Browserify, be sure to check out the official site at http://browserify.org.

Reference