shampoo
Version:
The ultimate Grunt Browserify task. Run once or watch files for changes. Uses a cache for super speed (instant builds). CoffeeScript support built in. Alias mappings. Shimming. Everything in one easy to use package.
200 lines (172 loc) • 7.53 kB
text/coffeescript
# * ShampooJS
# * https://github.com/thesunny/shampoojs
# *
# * Copyright (c) 2014 Sunny Hirai
# * Licensed under the MIT license.
#
"use strict"
mkdirp = require 'mkdirp'
fs = require 'fs'
path = require 'path'
coffeeify = require 'coffeeify'
_ = require 'lodash'
glob = require 'glob'
util = require '../lib/util'
resolveShimOptions = (options) ->
_.each options, (value, key) ->
value.path = path.resolve(value.path)
processMap = (map) ->
aliasMap = {}
shimMap = {}
_.each map, (value, key) ->
if _.isString(value)
aliasMap[key] = value
else
shimMap[key] = value
{aliasMap: aliasMap, shimMap: shimMap}
module.exports = (grunt) ->
# Please see the Grunt documentation for more information regarding task
# creation: http://gruntjs.com/creating-tasks
grunt.registerMultiTask "shampoo", "The ultimate CommonJS build tool for development and production", ->
# Runs this in asynchronous mode. Sets up a function to call when we want
# to exit.
quit = @async()
# get options with defaults
options = @options(
watch: true
debug: false
brfs: false
aliasMappings: null
shim: null
extensions: []
)
numBundles = @files.length
completedBundles = 0
@files.forEach (file) ->
sourcePaths = file.orig.src
destPath = util.normalizeBuildExtension(util.prefix(file.orig.dest))
# Create the destination directory
mkdirp.sync path.dirname(destPath)
# Adds CoffeeScript file extension to the list of supported extensions
# automatically. ShampooJS has CoffeeScript support built in. Note that
# we still need to add the `coffeeify` module to make CoffeeScript work.
browserifyOptions = {
extensions: [".coffee"]
}
# Create a browserify instance that accepts both .js and .coffee files.
# Note that Watchify returns a Browserify instance except that it caches
# making Browserify run a lot faster after the first bundle.
if options.watch
browserify = (require 'watchify')(browserifyOptions)
else
browserify = (require 'browserify')(browserifyOptions)
# # Create a watchify instance that accepts both .js and .coffee files.
# # Watchify returns a Browserify instance except that it caches making
# # Browserify run a lot faster after the first bundle.
# watchify = watchifyModule(watchifyOptions)
# Add all the source paths the .files configuration.
flattenedSourcePaths = []
_.each sourcePaths, (sourcePath) ->
expandedSourcePaths = glob.sync sourcePath
# if the glob doesn't expand, it will actually return an empty Array
# instead of the sourcePath. The code below fixes this.
if expandedSourcePaths.length == 0
expandedSourcePaths = [util.prefix(sourcePath)]
_.each expandedSourcePaths, (expandedSourcePath) ->
expandedSourcePath = util.prefix(expandedSourcePath)
flattenedSourcePaths.push expandedSourcePath
browserify.add expandedSourcePath
# Add the CoffeeScript transform. The CoffeeScript transform is added
# first so that the other transforms are working on top of JavaScript
# code instead of CoffeeScript code.
browserify.transform(coffeeify)
# Process maps separates our single map configuration into two separate
# maps. One is an alias map which just maps a name to a path. The other
# is a shimMap which maps a name to a path and a variable that needs to
# be pulled out of it. The aliasMap is fed to `aliasify` and the
# shimMap is fed to `browserify-shim`.
maps = processMap(options.map)
aliasMap = maps.aliasMap
shimMap = maps.shimMap
# Add browserify-shim only if there are any shim maps
if !_.isEmpty(shimMap)
shim = require 'browserify-shim'
shimOptions = resolveShimOptions(shimMap)
resolveShimOptions(shimOptions)
shim(browserify, shimOptions)
# Add alias support using aliasify plugin if there are any alias maps
if !_.isEmpty(aliasMap)
aliasify = require("aliasify").configure
aliases: aliasMap
browserify.transform(aliasify)
# Add BRFS support if requested
if options.brfs
browserify.transform('brfs')
bundleOptions = {
# The default is to include the source maps
debug: options.sourceMaps || true
# These are actually the default values but added here for clarity
insertGlobals: false # don't auto insert globals
detectGlobals: true # only insert if a global is referenced
}
# If we are in watch mode, then we don't need to ever quit so the
# function just does nothing (noop). If we are not in watch mode, then
# we need to terminate once all the bundles are completed. The method
# then just counts the completed bundles and calls quit() when the
# number of completed bundles matches the total number of bundles.
if options.watch
completeBundle = ->
else
completeBundle = ->
completedBundles++
quit() if completedBundles == numBundles
# Create a bundle function that gets called whenever a file is updated.
# We put it in a separate function because we also need to call the
# bundle once manually and also separately from the callback. If we don't
# call it once immediately, then watchify doesn't keep the process open
# (i.e. it immediately exits instead of watching)
bundle = ->
browserify.bundle bundleOptions, (err, src) ->
if err
grunt.log.error err
completeBundle()
else
fs.writeFile destPath, src, (err) ->
if err
grunt.log.error err
completeBundle()
else
console.log " DONE: #{destPath}."
console.log ""
completeBundle()
# Let the world know that we have started.
console.log ""
console.log "----------"
console.log "Shampoo JS"
console.log "----------"
# watch for file changes. When there is one, then we call bundle()
if options.watch
browserify.on "update", (ids) ->
console.log "Shampooing changes:" # #{id.join(', ')}
_.each ids, (id) ->
relativePath = util.rootPath(util.normalizePathDelimiter(path.relative(process.cwd(), id)))
console.log " FILE: #{relativePath}"
# console.log ""
bundle()
console.log ""
console.log "Watching #{sourcePaths} and dependencies forever..."
console.log ""
console.log " CTRL+C to quit."
console.log ""
else
console.log ""
console.log "Not watching. Running once."
console.log ""
# Sometimes the bundle will be working against watchify (i.e. the process
# will keep looking for file changes forever). Even when we are using
# watchify, we will still want to run the bundle at the very beginning.
console.log "Shampooing:" # #{sourcePaths.join(', ')}
_.each flattenedSourcePaths, (path) ->
console.log " FILE: #{path}"
# console.log ""
bundle()