UNPKG

muffin.io

Version:

A full stack development tool for creating modern webapps

273 lines (230 loc) 7.7 kB
# # Variables # loader = @ modules = {} inProgressModules = {} justDefinedModules = {} config = {} head = document.getElementsByTagName('head')[0] # # Global methods # window.require = (deps, callback) -> # The global require function only supports asynchronous module loading. # require ['module/1/path', 'module/2/path'], (module1, module2) -> {} # The local require function supports both asynchronous and synchronous (preloaded) module loading. return unless isArray(deps) loadAll deps, -> args = [] for path in deps exports = evaluate(modules[path]) args.push exports callback.apply(loader, args) window.define = (path, deps, factory) -> module = inProgressModules[path] ? {path} # Set module dependencies and factory function base = baseOfPath(path) module.deps = (normalize(p, base) for p in deps) module.factory = factory inProgressModules[path] = module if /\.(html|htm|json|css)$/.test(path) # If it's a text file wrapper, evaluate right away. module.exports = factory() delete module.factory # Inject CSS right away if /\.css$/.test(path) then injectCSS(module.exports, path) # Load dependencies after all other modules in the file are defined. # Some dependencies might be among those modules. justDefinedModules[path] = module # Load configurations require.config = (cfg) -> config = cfg # Undefine a module require.undef = (path) -> delete modules[path] # # Loader # # Fetch a JavaScript module asynchronously from its path fetchJS = (path, callback) -> tag = document.createElement('script') tag.type = 'text/javascript' tag.async = true tag.src = (if /\.js$/.test(path) then path else path + '.js') <? if (settings.env === 'development') { ?> tag.src += "?_#{(new Date()).getTime()}" # Add cache buster <? } ?> done = false tag.onload = tag.onreadystatechange = (e) -> readyState = tag.readyState if !done and (!readyState or /^(complete|loaded)$/.test(readyState)) done = true # Handle memory leak in IE tag.onload = tag.onreadystatechange = null head.removeChild(tag) callback() head.appendChild(tag) # Fetch a text file (.html, .json, .css, ...) fetchText = (path, callback) -> xhr = createXHR() <? if (settings.env === 'development') { ?> path += "?_#{(new Date()).getTime()}" # Add cache buster <? } else if (settings.env === 'production') { ?> path = path + '?_' + "<?- (new Date()).getTime() ?>" <? } ?> xhr.open 'GET', path, true xhr.onreadystatechange = -> if /^(complete|loaded|4)$/.test(xhr.readyState) callback(xhr.responseText) xhr.send(null) createXHR = -> if window.ActiveXObject # Microsoft failed to properly implement the XMLHttpRequest # in IE7 (can't request local files), so we use the ActiveXObject # when it is available. Additionally XMLHttpRequest can be disabled # in IE7/IE8 so we need a fallback. createStandardXHR() ? createActiveXHR() else # For all other browsers, use the standard XMLHttpRequest object createStandardXHR() createStandardXHR = -> try return new window.XMLHttpRequest() createActiveXHR = -> try return new window.ActiveXObject('Microsoft.XMLHTTP') # Inject CSS with a style tag injectCSS = (css, path) -> # Skip if already applied return if getElementByAttributeValue('data-path', path) tag = document.createElement('style') tag.type = 'text/css' tag.setAttribute('data-path', path) if tag.styleSheet? # IE workaround tag.styleSheet.cssText = css else tag.innerHTML = css head.appendChild(tag) # Load a module load = (path, callback) -> # Skip if the module is already loaded if modules[path] callback(modules[path]) return # Skip if the module is being loaded if inProgressModules[path] inProgressModules[path].callbacks ?= [] inProgressModules[path].callbacks.push callback return # Otherwise, fetch the module from its path inProgressModules[path] = {path, callbacks: [callback]} if /\.(html|htm|json|css)$/.test(path) fetchText path, (text) -> module = inProgressModules[path] module.exports = text if /\.css/.test(path) then injectCSS(text, path) didLoadModule(module) else fetchJS path, -> for p, module of justDefinedModules # Get around the stupid JavaScript scoping issue using blocks. # Note that if we use "loadAll module.deps -> didLoadModules(module)", # didLoadModules will not call on the original module, but the module in the current loop. loadDeps = (m) -> loadAll m.deps, -> didLoadModule(m) loadDeps(module) delete justDefinedModules[p] # Load multiple modules and trigger callback after all loaded. loadAll = (deps, callback) -> if deps?.length completed = 0 done = (module) -> completed++ if completed >= deps.length callback() load(path, done) for path in deps else callback() didLoadModule = (module) -> path = module.path # If it's a traditional script, evaluate it here. if moduleFormatFromPath(path) is 'script' module.factory.call(window) delete module.factory if config.exports[path] module.exports = window[config.exports[path]] # Save the module in memory modules[path] = module # Fire all the callbacks unless module.callbacks console.error "Failed to load module #{module.path}" cbk(module) for cbk in module.callbacks delete module.callbacks delete inProgressModules[path] # Evaluate a module by running its factory function evaluate = (module) -> if module.factory? module.exports = {} path = module.path base = baseOfPath(path) # Handle relative paths with a local require function localRequire = (deps, callback) -> if isArray(deps) _deps = (normalize(p, base) for p in deps) # Call the global require return require(_deps, callback) else if typeof deps is 'string' # Synchronous require: # module = require 'module/path' p = normalize(deps, base) m = modules[p] if m return evaluate(m) else console.log "module #{p} not found" return null for own prop, value of require localRequire[prop] = value if moduleFormatFromPath(path) is 'module' module.factory.call(window, localRequire, module.exports, module) else module.factory.call(window) delete module.factory return module.exports # # Helpers # isArray = (obj) -> Object.prototype.toString.call(obj) == '[object Array]' baseOfPath = (path) -> path.split('/')[0...-1].join('/') # Convert relative path to full path normalize = (path, base=null) -> parts = path.split('/') if path.charAt(0) is '.' and base baseParts = base.split('/') switch parts[0] when '.' path = baseParts.concat(parts[1..]).join('/') when '..' path = baseParts[0...-1].concat(parts[1..]).join('/') else if config.aliases[path] path = config.aliases[path] else if config.aliases[parts[0]] alias = config.aliases[parts[0]] path = [alias].concat(parts[1..]).join('/') # Strip the .js suffix path = path.replace(/\.js$/, '') return path # Get module format moduleFormatFromPath = (path) -> if /\.(html|htm|json|css)$/.test(path) format = 'text' else if path in config.scripts format = 'script' else format = 'module' return format # Get any one element by a certain attribute value getElementByAttributeValue = (attribute, value) -> for element in document.getElementsByTagName('*') return element if element.getAttribute(attribute) is value