UNPKG

amd-optimize

Version:

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

240 lines (156 loc) 6.1 kB
_ = require("lodash") fs = require("fs") path = require("path") async = require("async") VinylFile = require("vinyl") parse = require("./parse") util = require("./util") class Module constructor : (@name, @file, @deps = []) -> @name = util.fixModuleName(@name) @isShallow = false @isShimmed = false @isAnonymous = false @isInline = false @hasDefine = false @astNodes = [] module.exports = traceModule = (startModuleName, config, allModules = [], fileLoader, callback) -> foundModuleNames = [] textFiles = {} resolveModuleName = (moduleName, relativeTo = "") -> isText = (moduleName.indexOf('text!') != -1) # get rid of text! prefix if (isText) moduleName = moduleName.replace('text!', '') # deal with module path prefixes if config.paths and !config.paths[moduleName] slashIdx = moduleName.indexOf("/") if slashIdx > 0 eligiblePath = config.paths[moduleName.substr(0, slashIdx)]; if eligiblePath moduleName = eligiblePath + moduleName.substr(slashIdx) relativeToFileName = resolveModuleFileName(relativeTo) if moduleName[0] == "." moduleName = util.fixModuleName(path.join(path.dirname(relativeToFileName), moduleName)) if config.map and config.map[relativeTo] and config.map[relativeTo][moduleName] moduleName = config.map[relativeTo][moduleName] # add resolved name to list of text files if (isText) textFiles[moduleName] = true return moduleName resolveModuleFileName = (moduleName) -> if config.paths and config.paths[moduleName] moduleName = config.paths[moduleName] if /!|^exports$|^require$|^module$|^empty:/.test(moduleName) return else return moduleName resolveModules = (moduleNames, callback) -> async.mapSeries(moduleNames, resolveModule, callback) return resolveInlinedModule = (moduleName, deps, astNode, vinylFile, callback) -> async.waterfall([ (callback) -> resolveModules(deps, callback) (modules, callback) -> module = new Module(moduleName, vinylFile, _.compact(modules)) module.hasDefine = true module.isInline = true module.astNodes.push(astNode) emitModule(module) callback() ], callback) return resolveModule = (moduleName, callback) -> module = _.detect(allModules, name : moduleName) if module callback(null, module) return fileName = resolveModuleFileName(moduleName) if not fileName module = new Module(moduleName) module.isShallow = true callback(null, emitModule(module)) return if _.contains(foundModuleNames, moduleName) callback(new Error("Circular dependency detected. Module '#{moduleName}' has been processed before.")) return else foundModuleNames.push(moduleName) module = null isTextFile = !!textFiles[moduleName] # console.log("Resolving", moduleName, fileName) async.waterfall([ (callback) -> fileLoader(fileName, callback, isTextFile) (file, callback) -> if arguments.length == 1 callback = file file = null if file callback(null, file) else callback(new Error("No file for module '#{moduleName}' found.")) (file, callback) -> file.stringContents = file.contents.toString("utf8") if (isTextFile) file.stringContents = 'define(function(){ return ' + JSON.stringify(file.stringContents) + '; });' module = new Module(moduleName, file) callback(null, file) parse.bind(null, config) (file, definitions, callback) -> if _.filter(definitions, (def) -> return def.method == "define" and def.moduleName == undefined).length > 1 callback(new Error("A module must not have more than one anonymous 'define' calls.")) return module.hasDefine = _.any(definitions, (def) -> return def.method == "define" and (def.moduleName == undefined or def.moduleName == moduleName) ) async.mapSeries( definitions (def, callback) -> def.deps = def.deps.map( (depName) -> resolveModuleName(depName, def.moduleName ? moduleName) ) if def.method == "define" and def.moduleName != undefined and def.moduleName != moduleName async.waterfall([ (callback) -> resolveInlinedModule(def.moduleName, def.deps, def.node, file, callback) (callback) -> callback(null, []) ], callback) else module.astNodes.push(def.node) resolveModules(def.deps, callback) return callback ) (unflatModules, callback) -> callback(null, _.compact(_.flatten(unflatModules))) (depModules, callback) -> module.deps.push(depModules...) module.isAnonymous = true async.waterfall([ (callback) -> additionalDepNames = null if config.shim and shim = config.shim[module.name] if module.hasDefine console.log("[warn]", "Module '#{module.name}' is shimmed even though it has a proper define.") module.isShimmed = true if shim.exports module.exports = shim.exports if _.isArray(shim) additionalDepNames = shim else if shim.deps additionalDepNames = shim.deps if additionalDepNames resolveModules(additionalDepNames, callback) else callback(null, []) (depModules, callback) -> module.deps.push(depModules...) callback(null, emitModule(module)) ], callback) return ], callback) return emitModule = (module) -> if not _.any(allModules, name : module.name) allModules.push(module) return module resolveModule(startModuleName, callback) return