Integrating CoffeeScript in Sage

Hello,

Could you please share your advice on how I can integrate CoffeeScript into my workflow with Sage.

I’d like to be able to compile *.coffee files into *.js with Gulp.

I can see, however, that /assets/script/main.js file has been built with DOM-based routing. In there, scripts go in specific places depending on when they should be fired.

Will it be sufficient to change the current main.js (to preserve the routing) into main.coffee, and then compile it back into main.js (since this is main.js that is sourced in /dist/scripts/main.js).

All advice is much appreciated.

Best,

1 Like

Hey @luqo33 - this isn’t the most perfect or robust solution, but it does work as a proof-of-concept thing and might help you out: https://github.com/JulienMelissas/sage/commits/coffee

Hi Julien! I can see there has been a lot of activity on the ‘coffee’ branch. But are there any directives on how CoffeeScript can be integrated with Sage?

Or is is it just a matter of:

  1. Copying the contents of main.js to main.coffee
  2. Adding my coffee scripts to main.coffee in appropriate places (based on where the scripts should fire)
  3. Recompiling main.coffee into /assets/script/main.js

I thought that would be the easiest way to start writing coffee. Especially sine CoffeeScript can be easily mixed with raw JS (that is already present in main.js).

Am I thinking in a good direction?

Cheers!

@luqo33 - you have to implement the changes I’m making in my branch. Just copy and paste the code changes I’ve made. You can get rid of main.js and instead use the main.coffee file I made. Or you can just download or git clone my entire coffee branch and start your project from there (but I probably won’t keep it updated).

See all of the changes from a regular version of roots here: https://github.com/JulienMelissas/sage/compare/master...JulienMelissas:coffee

Good luck!

Oh yes, I can see Julien that you added a ‘coffee’ task and to gulpfile.js, and rewrote main.js to into CofeeScript. Great work. It will sure be very helpful.

Cheers!

1 Like

Hi @JulienMelissas! Your solution has worked like a charm, I was having the same problem as well. Do you have any plans in doing a pull request to the official Sage repo with these changes? Probably in a manner that people who don’t use coffeescript can still use the default way of Sage. I personally think that having coffeescript support in Sage out of the box is as important as the the out of the box support for sass/less. What do you think?

@psymeon this would be something that would have to be handled in the upcoming yeoman generator. If we can find a way to:

  1. Automatically generate a coffee file from the main.js
  2. Have someone step up to maintain coffee support
  3. or both of the above

I could see it being worthwhile. But definitely low priority at the moment.

Personally babel solves all the problems for me that coffeescript solves: https://babeljs.io/. A lot of coffeescript hotness has been “merged into master” with ES6.

I was about to write basically what @austin is saying so… +1 to that.

I’m finally getting around to playing with Coffeescript in Sage and I’m getting jshint errors because jshint is expecting vanilla javascript, which makes sense. I tried disabling jshint in Gulp but because of the way the pipeline is written, it breaks. Am I the only one who ran into this?

Yo @alancrissey - have you tried just disabling the jshint task? You can do that by just removing the jshint task from the scripts and watch tasks.

In the jshint task’s gulp.src you can use negation globs such as: !*.coffee which means don’t include and coffee files.

Yep - but in my specific coffee branch (which might be what they’re copying off of) - we’re just compiling the coffee to js and then running jshint on that I’m pretty sure…

Oh yeah. Well shouldn’t be using JSHint anyway, use coffeelint

1 Like

When I remove the jshint task (and remove the jshint reference from the scripts task) I get this error when running gulp

stream.js:94
  throw er; // Unhandled stream error in pipe.

And I forgot to mention, I am using the updated Gulpfile from @JulienMelissas coffeescript branch.

Make sure all references to the jshint task are gone

I did, and that’s what’s puzzling me. It finishes coffeelint (put that in place of jshint), compresses images, then errors. Here’s my gulpfile if anyone’s interested:

// ## Globals
/*global $:true*/
var $           = require('gulp-load-plugins')();
var argv        = require('yargs').argv;
var browserSync = require('browser-sync');
var gulp        = require('gulp');
var lazypipe    = require('lazypipe');
var merge       = require('merge-stream');

// See https://github.com/austinpray/asset-builder
var manifest = require('asset-builder')('./assets/manifest.json');

// `path` - Paths to base asset directories. With trailing slashes.
// - `path.source` - Path to the source files. Default: `assets/`
// - `path.dist` - Path to the build directory. Default: `dist/`
var path = manifest.paths;

// `config` - Store arbitrary configuration values here.
var config = manifest.config || {};

// `globs` - These ultimately end up in their respective `gulp.src`.
// - `globs.js` - Array of asset-builder JS dependency objects. Example:
//   ```
//   {type: 'js', name: 'main.js', globs: []}
//   ```
// - `globs.css` - Array of asset-builder CSS dependency objects. Example:
//   ```
//   {type: 'css', name: 'main.css', globs: []}
//   ```
// - `globs.fonts` - Array of font path globs.
// - `globs.images` - Array of image path globs.
// - `globs.bower` - Array of all the main Bower files.
var globs = manifest.globs;

// `project` - paths to first-party assets.
// - `project.js` - Array of first-party JS assets.
// - `project.css` - Array of first-party CSS assets.
var project = manifest.getProjectGlobs();

// CLI options
var enabled = {
  // Enable static asset revisioning when `--production`
  rev: argv.production,
  // Disable source maps when `--production`
  maps: !argv.production,
  // Fail styles task on error when `--production`
  failStyleTask: argv.production
};

// Path to the compiled assets manifest in the dist directory
var revManifest = path.dist + 'assets.json';

// ## Reusable Pipelines
// See https://github.com/OverZealous/lazypipe

// ### CSS processing pipeline
// Example
// ```
// gulp.src(cssFiles)
//   .pipe(cssTasks('main.css')
//   .pipe(gulp.dest(path.dist + 'styles'))
// ```
var cssTasks = function(filename) {
  return lazypipe()
    .pipe(function() {
      return $.if(!enabled.failStyleTask, $.plumber());
    })
    .pipe(function() {
      return $.if(enabled.maps, $.sourcemaps.init());
    })
      .pipe(function() {
        return $.if('*.less', $.less());
      })
      .pipe(function() {
        return $.if('*.scss', $.sass({
          outputStyle: 'nested', // libsass doesn't support expanded yet
          precision: 10,
          includePaths: ['.'],
          errLogToConsole: !enabled.failStyleTask
        }));
      })
      .pipe($.concat, filename)
      .pipe($.pleeease, {
        autoprefixer: {
          browsers: [
            'last 2 versions', 'ie 8', 'ie 9', 'android 2.3', 'android 4',
            'opera 12'
          ]
        }
      })
    .pipe(function() {
      return $.if(enabled.rev, $.rev());
    })
    .pipe(function() {
      return $.if(enabled.maps, $.sourcemaps.write('.'));
    })();
};

// ### JS processing pipeline
// Example
// ```
// gulp.src(jsFiles)
//   .pipe(jsTasks('main.js')
//   .pipe(gulp.dest(path.dist + 'scripts'))
// ```
var jsTasks = function(filename) {
  return lazypipe()
    .pipe(function() {
      return $.if(enabled.maps, $.sourcemaps.init());
    })
    .pipe($.concat, filename)
    .pipe($.uglify)
    .pipe(function() {
      return $.if(enabled.rev, $.rev());
    })
    .pipe(function() {
      return $.if(enabled.maps, $.sourcemaps.write('.'));
    })();
};

// ### Write to rev manifest
// If there are any revved files then write them to the rev manifest.
// See https://github.com/sindresorhus/gulp-rev
var writeToManifest = function(directory) {
  return lazypipe()
    .pipe(gulp.dest, path.dist + directory)
    .pipe(function() {
      return $.if('**/*.{js,css}', browserSync.reload({stream:true}));
    })
    .pipe($.rev.manifest, revManifest, {
      base: path.dist,
      merge: true
    })
    .pipe(gulp.dest, path.dist)();
};

// ## Gulp tasks
// Run `gulp -T` for a task summary

gulp.task('coffee', function() {
  gulp.src('./assets/scripts/*.coffee')
  .pipe($.coffee({bare: true}).on('error', function(err) {
    console.error(err.message);
  }))
  .pipe(gulp.dest('./assets/scripts/'));
});

// ### Styles
// `gulp styles` - Compiles, combines, and optimizes Bower CSS and project CSS.
// By default this task will only log a warning if a precompiler error is
// raised. If the `--production` flag is set: this task will fail outright.
gulp.task('styles', ['wiredep'], function() {
  var merged = merge();
  manifest.forEachDependency('css', function(dep) {
    var cssTasksInstance = cssTasks(dep.name);
    if (!enabled.failStyleTask) {
      cssTasksInstance.on('error', function(err) {
        console.error(err.message);
        this.emit('end');
      });
    }
    merged.add(gulp.src(dep.globs, {base: 'styles'})
      .pipe(cssTasksInstance));
  });
  return merged
    .pipe(writeToManifest('styles'));
});

// ### Scripts
// `gulp scripts` - Runs CoffeeLint then compiles, combines, and optimizes Bower JS
// and project JS.
gulp.task('scripts', ['coffee', 'coffeelint'], function() {
  var merged = merge();
  manifest.forEachDependency('js', function(dep) {
    merged.add(
      gulp.src(dep.globs, {base: 'scripts'})
        .pipe(jsTasks(dep.name))
    );
  });
  return merged
    .pipe(writeToManifest('scripts'));
});

// ### Fonts
// `gulp fonts` - Grabs all the fonts and outputs them in a flattened directory
// structure. See: https://github.com/armed/gulp-flatten
gulp.task('fonts', function() {
  return gulp.src(globs.fonts)
    .pipe($.flatten())
    .pipe(gulp.dest(path.dist + 'fonts'));
});

// ### Images
// `gulp images` - Run lossless compression on all the images.
gulp.task('images', function() {
  return gulp.src(globs.images)
    .pipe($.imagemin({
      progressive: true,
      interlaced: true,
      svgoPlugins: [{removeUnknownsAndDefaults: false}]
    }))
    .pipe(gulp.dest(path.dist + 'images'));
});

// ### CoffeeLint
gulp.task('coffeelint', function() {
  return gulp.src([
    'bower.json', 'gulpfile.js'
  ].concat(project.js))
    .pipe($.coffeelint())
    .pipe($.coffeelint.reporter());
});

// ### Clean
// `gulp clean` - Deletes the build folder entirely.
gulp.task('clean', require('del').bind(null, [path.dist]));

// ### Watch
// `gulp watch` - Use BrowserSync to proxy your dev server and synchronize code
// changes across devices. Specify the hostname of your dev server at
// `manifest.config.devUrl`. When a modification is made to an asset, run the
// build step for that asset and inject the changes into the page.
// See: http://www.browsersync.io
gulp.task('watch', function() {
  browserSync({
    proxy: config.devUrl,
    snippetOptions: {
      whitelist: ['/wp-admin/admin-ajax.php'],
      blacklist: ['/wp-admin/**']
    }
  });
  gulp.watch([path.source + 'styles/**/*'], ['styles']);
  gulp.watch([path.source + 'scripts/**/*'], ['scripts']);
  gulp.watch([path.source + 'fonts/**/*'], ['fonts']);
  gulp.watch([path.source + 'images/**/*'], ['images']);
  gulp.watch(['bower.json', 'assets/manifest.json'], ['build']);
  gulp.watch('**/*.php', function() {
    browserSync.reload();
  });
});

// ### Build
// `gulp build` - Run all the build tasks but don't clean up beforehand.
// Generally you should be running `gulp` instead of `gulp build`.
gulp.task('build', ['styles', 'scripts', 'fonts', 'images']);

// ### Wiredep
// `gulp wiredep` - Automatically inject Less and Sass Bower dependencies. See
// https://github.com/taptapship/wiredep
gulp.task('wiredep', function() {
  var wiredep = require('wiredep').stream;
  return gulp.src(project.css)
    .pipe(wiredep())
    .pipe($.changed(path.source + 'styles', {
      hasChanged: $.changed.compareSha1Digest
    }))
    .pipe(gulp.dest(path.source + 'styles'));
});

// ### Gulp
// `gulp` - Run a complete build. To compile for production run `gulp --production`.
gulp.task('default', ['clean'], function() {
  gulp.start('build');
});

hmmm, from looking at your gulpfile, it looks like you haven’t actually required the gulp modules you need.
See how I’m bringing gulp-coffee in like this?

Try to require the modules you’re using :wink: and then report back.

Thanks @JulienMelissas . I’ll see if that fixes it when I get a chance to work on it again.

To be fair, you didn’t have that line in your gulpfile before, which is where I copied this from (everything minus coffeelint).

You guys should be able to use $.coffee() I would assume

Here’s my workaround
manifest.json

 //separate vendors deps from my own code
  "vendors.js": {
      "bower": ["jquery"]
  },
// concat coffee deps into main.js
 "main.js": {
      "files": [
        "scripts/main.coffee"
      ]
    }

jshint task

gulp.task('jshint', () => {
  return gulp.src(['bower.json', 'gulpfile.js'])    
   // remove jshint from my own code (coffeescript already compiling clean javascript code) 
    .pipe(jshint())
    .pipe(jshint.reporter('jshint-stylish'))
    .pipe(gulpif(enabled.failJSHint, jshint.reporter('fail')));
});

jsTasks function

var jsTasks = (filename) => {
  return lazypipe()
    .pipe(() => {
      return gulpif(enabled.maps, sourcemaps.init());
    })
    .pipe(() => {
     // check if globs is coffee
      return gulpif("*.coffee", coffee({bare:true}) );
    })
    .pipe(concat, filename)
    .pipe(uglify, {
      compress: {
        'drop_debugger': enabled.stripJSDebug
      }
    })
    .pipe(() => {
      return gulpif(enabled.rev, rev());
    })
    .pipe(() => {
      return gulpif(enabled.maps, sourcemaps.write('.', {
        sourceRoot: 'assets/scripts/'
      }));
    })();
};

edit : update snippets with better understanding of gulpif