UNPKG

amd-optimize

Version:

An AMD (i.e. RequireJS) optimizer that's stream-friendly. Made for gulp. (WIP)

238 lines (176 loc) 5.63 kB
_ = require("lodash") fs = require("fs") path = require("path") vinylFs = require("vinyl-fs") async = require("async") through = require("through2") Readable = require("stream").Readable trace = require("./trace") exportModule = require("./export") util = require("./util") firstChunk = (stream, callback) -> settled = false stream .on("data", (data) -> if not settled settled = true callback(null, data) return ).on("end", -> if not settled callback() return ).on("error", (err) -> if not settled settled = true callback(err) return ) return collectModules = (module, omitInline = true) -> # Depth-first search over the module dependency tree outputBuffer = [] collector = (currentModule) -> currentModule.deps.forEach( (depModule) -> collector(depModule) ) if not (omitInline and currentModule.isInline) and not _.any(outputBuffer, name : currentModule.name) outputBuffer.push(currentModule) collector(module) return outputBuffer mergeOptionsFile = (file, options = {}) -> return _.merge( {} Function(""" var output, requirejs = require = { config : function (options) { output = options; } }, define = function () {}; #{file.contents.toString("utf8")}; return output; """)() options ) defaultLoader = (fileBuffer, options) -> return (name, callback, asPlainFile) -> addJs = (!asPlainFile) and '.js' or '' if options.baseUrl and file = _.detect(fileBuffer, path : path.resolve(options.baseUrl, name + addJs)) callback(null, file) else if file = _.detect(fileBuffer, relative : path.join(options.baseUrl, name + addJs)) callback(null, file) else if options.loader options.loader(name, callback) else module.exports.loader()(path.join(options.baseUrl, name + addJs), callback) module.exports = rjs = (entryModuleName, options = {}) -> # Default options options = _.defaults( options, { baseUrl : "" configFile : null exclude : [] excludeShallow : [] # include : [] findNestedDependencies : false # wrapShim : true loader : null preserveComments : false } ) # Fix sloppy options if _.isString(options.exclude) options.exclude = [options.exclude] if _.isString(options.excludeShallow) options.excludeShallow = [options.excludeShallow] # Prepare config file stream if _.isString(options.configFile) or _.isArray(options.configFile) configFileStream = vinylFs.src(options.configFile) else if _.isObject(options.configFile) configFileStream = options.configFile fileBuffer = [] # Go! mainStream = through.obj( # transform (file, enc, done) -> fileBuffer.push(file) done() # flush (done) -> async.waterfall([ (callback) -> # Read and merge external options if configFileStream configFileStream.pipe( through.obj( (file, enc, done) -> options = mergeOptionsFile(file, options) done() -> callback() ) ) else callback() (callback) -> # Trace entry module trace(entryModuleName, options, null, defaultLoader(fileBuffer, options), callback) (module, callback) -> # Flatten modules list callback(null, collectModules(module)) (modules, callback) -> # Find excluded modules if _.isArray(options.exclude) async.map( options.exclude (moduleName, callback) -> trace(moduleName, options, null, defaultLoader(fileBuffer, options), callback) (err, excludedModules) -> if err callback(err) else callback(null, modules, _(excludedModules) .map((module) -> collectModules(module)) .flatten() .pluck("name") .unique() .value()) ) else callback(null, modules, []) (modules, excludedModuleNames, callback) -> # printTree(module) # Remove excluded modules modules = _.reject(modules, (module) -> return _.contains(excludedModuleNames, module.name) or _.contains(options.excludeShallow, module.name) ) # Fix and export all the files in correct order exportStream = exportModule(options) exportStream .on("data", (file) -> mainStream.push(file) ) .on("end", -> callback()) .on("error", callback) modules.forEach(exportStream.write.bind(exportStream)) exportStream.end() # Done! ], done) ) return mainStream module.exports.src = (moduleName, options) -> source = rjs(moduleName, options) process.nextTick -> source.end() return source module.exports.loader = (filenameResolver, pipe) -> (moduleName, callback) -> # console.log(filenameResolver(moduleName)) if filenameResolver filename = filenameResolver(moduleName) else filename = moduleName source = vinylFs.src(filename).pipe(through.obj()) if pipe source = source.pipe(pipe()) firstChunk(source, callback) return