Gulp.js is a simple task/build runner to process front-end stuff, which utilizes pipes for streaming data that needs to be processed.

As gulp.js is running on Node.js, various Node.js-based packages - including JSforce - can be used in build scripts.

In this article, we'll show how to deploy a force.com package using gulp, which can contain Force.com metadata objects like custom object/field definitions, Apex classes, Visualforce pages, or CSS/JavaScript/image files as static resource archive.

(All files including build script and source codes of this article is available from here)

Example

First, we assume a project with following directory structure :

├── gulpfile.coffee
├── package.json
├── pkg
│   ├── package.xml
│   ├── pages
│   │   ├── MyAppPage.page
│   │   └── MyAppPage.page-meta.xml
│   └── staticresources
│       ├── MyApp.resource
│       └── MyApp.resource-meta.xml
└── src
    ├── images
    │   └── salesforce.jpg
    ├── scripts
    │   └── main.coffee
    └── styles
        └── main.less

In this project it has the pkg/ directory - a package of Force.com metadata objects for example Apex classes or Visualforce pages. The src/ directory includes all static files to be built and archived to a zipped static resource file by gulp.

The gulpfile.coffee is the script which describes building and deploying tasks in CoffeeScript (you can use normal JavaScript if you like, of course).

Building Static Files

Like other general frontend development, you can build JavaScript or CSS files in gulp tasks. In this example we compile CoffeeScript source code to JavaScript, bundle it with all dependencies using browserify, and uglify it to minify the content. For CSS, we compile from LESS stylesheets. Other static files like HTML or images are just copied to temporary build directory.

When all static files are built, they will be zipped as a static resource file in pkg/staticresources directory.

gulp = require "gulp"
zip = require "gulp-zip"
less = require "gulp-less"
streamify = require "gulp-streamify"
uglify = require "gulp-uglify"
minify = require "gulp-minify-css"
browserify = require "browserify"
source = require "vinyl-source-stream"

# Building CSS files from LESS source
gulp.task "css", ->
  gulp.src "./src/styles/main.less"
    .pipe less()
    .pipe minify()
    .pipe gulp.dest "./build/css"

# Compile and bundle JS file from CoffeeScript source code
gulp.task "js", ->
  browserify
    entries: [ "./src/scripts/main.coffee" ]
    extensions: [ ".coffee" ]
  .bundle()
  .pipe source "main.js"
  .pipe streamify uglify()
  .pipe gulp.dest "./build/js"

# Copy all static files in src directory to temporary build directory
gulp.task "statics", ->
  gulp.src [ "./src/**/*.html", "./src/images/**/*" ], base: "./src"
    .pipe gulp.dest "./build"

# Zip all built files as a static resource file
gulp.task "zip", [ "css", "js", "statics" ], ->
  gulp.src "./build/**/*"
    .pipe zip("MyApp.resource")
    .pipe gulp.dest "./pkg/staticresources"

# Build
gulp.task "build", [ "zip" ]

Deploying to Salesforce

As all frontend files are built, now the time to upload and deploy the Force.com package to Salesforce. The deploy task first zips package directory, pipe it to forceDeploy stream which accepts zipped file contents and pass it to JSforce Metadata#deploy() API call.

through2 = require "through2"
jsforce = require "jsforce"

###
# Returns a stream pipe for deploying zipped package to Salesforce
###
forceDeploy = (username, password) ->
  through2.obj (file, enc, callback) ->
    conn = new jsforce.Connection()
    conn.login username, password
    .then ->
      conn.metadata.deploy(file.contents).complete(details: true)
    .then (res) ->
      if res.details?.componentFailures
        console.error res.details?.componentFailures
        return callback(new Error('Deploy failed.'))
      callback()
    , (err) ->
      console.error(err)
      callback(err)

###
# Deploying package to Salesforce
###
gulp.task "deploy", ->
  gulp.src "./pkg/**/*", base: "."
    .pipe zip("pkg.zip")
    .pipe forceDeploy(process.env.SF_USERNAME, process.env.SF_PASSWORD)


# Default entry point
gulp.task "default", [ "build", "deploy" ]

Running Gulp

Now you can build and deploy all files to Salesforce by following command :

$ SF_USERNAME=xxxx@yourdomain.com SF_PASSWORD=yourpassword gulp

If you have installed foreman, you can prepare .env file in your project with above credentials and execute following :

$ foreman run gulp

Of course you can combine gulp.watch to watch file changes and automatically deploy to Salesforce when a change happens.

gulp.task "watch", ->
  gulp.watch "./src/**/*", [ "build" ]
  gulp.watch "./pkg/**/*", [ "deploy" ]

Use gulp watch to start watching file changes.

$ foreman run gulp watch
[20:57:46] Requiring external module coffee-script/register
[20:57:49] Using gulpfile /tmp/gulp-jsforce-example/gulpfile.coffee
[20:57:49] Starting 'watch'...
[20:57:49] Finished 'watch' after 44 ms