UNPKG

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
# * 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()