diff --git a/addons/ac/shakerInline.server.js b/addons/ac/shakerInline.server.js deleted file mode 100644 index 736d453..0000000 --- a/addons/ac/shakerInline.server.js +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (c) 2011-2012, Yahoo! Inc. All rights reserved. - * Copyrights licensed under the New BSD License. - * See the accompanying LICENSE file for terms. - */ - -YUI.add('shaker-inline-addon', function(Y, NAME) { - - function ShakerInlineAddon(command, adapter, ac) { - this._ac = ac; // the future action context of the mojit (not attached yet if mojit created dynamically) - this._adapter = adapter; // where the functions done and error live before attach them to the ac. - this._command = command; //all the configuration for the mojit - this._init(ac, adapter); - } - - ShakerInlineAddon.prototype = { - namespace: 'shakerInline', - - setStore: function (rs) { - this.rs = rs; - }, - /* - * getter for abstract the access of the store, since the accesors to the store may change - * in the next version of Mojito... - */ - getStore: function () { - if (this.rs) { - return this.rs; - } else { - // dirty fallback version to access the store - this.rs = this._adapter.req.app.store; - return this.rs; - } - }, - - _init: function (ac, adapter) { - this._hookDoneMethod(ac, adapter); - this._inlineShared = []; - }, - _hookDoneMethod: function (ac, adapter) { - var self = this, - doneMethod = adapter.done; - - adapter.done = function () { - // We don't know for sure how many arguments we have, - // so we have to pass through the hook references plus all the original arguments. - self._handleDone.apply(self, [this, doneMethod].concat([].slice.apply(arguments))); - }; - }, - /* - * The first two arguments are the real context and method of Mojito, that we pass artificially on @_hookDoneMethod - * The rest are the original arguments that are being passed by Mojito. - * We have to do like this since we need to modify the arguments but we don't know how many we got. - */ - _handleDone: function (selfContext, done, data, meta) { - var assets = meta.assets, - inlineShaker = assets.inlineShaker; - - if (!inlineShaker.blob) { - inlineShaker.blob = []; - } - //add the gathered inlineShared - inlineShaker.blob = inlineShaker.blob.concat(this._inlineShared); - - if (typeof data === 'string') { - data += inlineShaker.blob.join(''); - } else if (data instanceof Array) { - data.concat(inlineShaker.blob); - } - - // Remove the inlineAssets from meta, since we just added to the view - // (if not we would get duplicates) - delete assets.inlineShaker; - - // Restore the original arguments and call the real ac.done with the modified data. - args = [].slice.apply(arguments).slice(2); - done.apply(selfContext, args); - }, - /* - */ - inlineFile: function (name) { - var store = this.getStore(), - ac = this._ac, - shakerRS = store.shaker, - shakerMeta = shakerRS.meta, - context = ac.context, - strContext = store.selector.getPOSLFromContext(ac.context).join('-'), - inlineShared = (shakerMeta && shakerMeta.app[strContext].inlineShared) || {}; - - if (inlineShared[name]) { - this._inlineShared.push(inlineShared[name]); - return true; - } - } - }; - //we just add it to ac in case at some point we will have an API - Y.mojito.addons.ac.shakerInline = ShakerInlineAddon; - -}, '0.0.1', {requires: ['mojito']}); diff --git a/lib/bootstrap/yui-bootstrap-core.js b/lib/bootstrap/yui-bootstrap-core.js deleted file mode 100644 index cbc9eb0..0000000 --- a/lib/bootstrap/yui-bootstrap-core.js +++ /dev/null @@ -1,237 +0,0 @@ -// During the build process, the content of this file should be output in the -// same bundle as the one containing the definition of the YUI global object. -// Obviously, because of the way JavaScript code is evaluated, the definition -// of the YUI global object should appear first in that bundle (after the -// content of yui-bootstrap-override.js however...), followed by the following -// modules, which are all used needed by this file: -// - oop -// - yui-later -// - event-custom-base -// - features -// - array-extras -// - loader-base -// - loader-rollup -// - loader-yui3 -// - -// The content of this file should appear last in that bundle. - -// The purpose of this file is to fix the standard behavior of a YUI instance -// when the bootstrap config is set to false, which I consider broken. Indeed: -// YUI({bootstrap: false}).use('xxx', function (Y) {...}); -// will result in the callback being invoked immediately, even if the required -// modules are not available! The following code will prevent this from -// happening *and* will automatically invoke the callback as soon as the -// required modules become available (in case you manage the loading of your -// JavaScript bundles separately) - -// For more information, please refer to: http://slidesha.re/gDT7Ni - -// A ticket was filed against the YUI library itself to fix this behavior -// at the library level: http://yuilibrary.com/projects/yui3/ticket/2531788 - -/*jslint browser: true, nomen: true, plusplus: true */ -/*global _YUI */ - -YUI.GlobalConfig = { - - // Disable the loader. If one of the required modules (see below) is - // missing, I'd rather have an error (which we'll easily spot early in the - // release process) than not noticing anything and having the loader - // invoked for nothing... - bootstrap: false, - - debug: false - -}; - -// The actual list of modules needed is: -// - yui-base (contains the definition -// - oop -// - yui-later -// - event-custom-base -// - loader -// - features -// - array-extras -// However, it is more efficient (faster) to use the wildcard here, -// especially since we know that all those modules are available -// (they are supposed to be in the same bundle, preceding this file) - -YUI().use('*', function (Y) { - 'use strict'; - - // _YUI is a reference to our fake YUI object, defined inline in the SRP. - if (window._YUI) { - Y.mix(YUI.Env, _YUI.Env, false, null, 0, true); - } - - var // Used to throttle calls to Y.use when new modules become available. - timer, - - // Pending calls to Y.use() - pending = [], - - // Save a copy to the original _setup YUI instance method - setup = YUI.prototype._setup, - - // Loader instance, used for dependency resolution. Note that we use - // our own loader instance, and we do not use Y.Env._loader for this. - // The reason: it does not work. Why is that? I have no idea, but - // I like to leave the shared loader instance alone. - loader = new Y.Loader(Y.config); - - // Now, override the _setup YUI instance method. If the "bootstrap" config - // was set to false (see docs), we override the default (broken if you ask - // me...) use method to prevent the callback from being invoked if required - // modules are not available. - - YUI.prototype._setup = function () { - var YUIinstance = this; - - // Call the original _setup method first. - setup.apply(YUIinstance, arguments); - - // Now, customize this instance based on the value of the bootstrap - // configuration value. Note: Since _setup is invoked multiple times, - // we use the bootstrapFix property to avoid attaching multiple - // callbacks to the use() method! - if (!YUIinstance.config.bootstrap && !YUIinstance.bootstrapFix) { - - YUIinstance.bootstrapFix = true; - - // Checks whether all the YUI modules listed as parameters when - // calling Y.use() are available. If a module is missing, the - // actual call to Y.use() will be delayed until the module - // becomes available. - - Y.before(function () { - - var args = Y.Array(arguments, 0, true), - callback = args[args.length - 1], - required = [], - missing = [], - name, - module, - i, - l; - - if (args[0] === '*') { - Y.error('Wildcard not supported'); - } - - if (typeof callback === 'function') { - args.pop(); - } else { - Y.error('Missing callback'); - } - - for (i = 0, l = args.length; i < l; i++) { - name = args[i]; - if (!YUI.Env.mods[name]) { - // The specified module must not have been bound to the - // YUI object just yet... - missing.push(name); - } else { - module = loader.moduleInfo[name]; - if (module) { - // The specified module was previously added to the YUI object. - // Use the loader to compute the list of required modules. - required = required.concat(loader.getRequires(module)); - } else { - // We don't know anything about this module. - // It must not have been added to the YUI object just yet. - missing.push(name); - } - } - } - - // Weed out any duplicates... - required = Y.Array.unique(required); - - // Figure out whether we have any missing modules in the list of required modules. - for (i = 0, l = required.length; i < l; i++) { - name = required[i]; - // Note: we ignore style sheets here, b/c we have no choice. - module = loader.moduleInfo[name]; - if ((!module || module.type === 'js') && !YUI.Env.mods[name]) { - missing.push(name); - } - } - - if (missing.length) { - // Queue the call. - pending.push([YUIinstance].concat(args).concat(callback)); - // Do not proceed... - Y.log('[Y.use] Missing modules: ' + missing.join(',') + ' [' + YUIinstance.id + ']'); - return new Y.Do.Prevent(); - } - - // Attach these modules to the YUI instance. Note: The loader - // should return the list of required modules already sorted. - // Also attach the yui-log module, or debug messages will never show... - YUIinstance._attach(['yui-log'].concat(required)); - // And proceed... - Y.log('[Y.use] Invoking Y.use(' + args.join(',') + ') [' + YUIinstance.id + ']'); - - }, YUIinstance, 'use'); - } - }; - - // Checks whether the availability of a YUI module may unblock a pending - // call to Y.use(). Note that the execution is throttled because a single - // fuse module may contain numerous calls to YUI.add(). - - Y.after(function (name, fn, version, details) { - Y.log('Module ' + name + ' was added.'); - - // Let our own loader instance know that this module was added... - if (!loader.moduleInfo[name]) { - loader.addModule(details || {}, name); - } - - if (timer) { - timer.cancel(); - } - - timer = Y.later(0, null, function () { - var i, l, args, YUIinstance, modules; - - for (i = 0, l = pending.length; i < l; i++) { - args = pending.shift(); - - // The YUI instance is in first position... - YUIinstance = args.shift(); - - // Output some debug information. - modules = args.concat(); // concat dups the array so we can pop it next... - modules.pop(); - Y.log('[YUI.add] Invoking Y.use("' + modules.join('","') + '", function () {...}) [' + YUIinstance.id + ']'); - - // We're not sure whether or not the necessary modules are - // available at this point in time, but that's ok because - // Y.use will compute the dependencies and put this back - // in the pending queue if needed. We won't end up in an - // infinite loop b/c of the way this loop has been written, - // and b/c we use shift/push to manipulate the pending queue. - YUIinstance.use.apply(YUIinstance, args); - } - }); - - }, YUI, 'add'); - - // Copy a few things from the fake YUI object... - - YUI.OnloadHandlerQueue = _YUI.OnloadHandlerQueue; - YUI.SimpleLoader = _YUI.SimpleLoader; - - // Process pending Y.use() calls! - - Y.each(_YUI.Env.pending, function (args) { - var cfg = args.shift(), - YUIinstance = YUI(cfg); - YUIinstance.use.apply(YUIinstance, args); - }); - - // A bit of cleanup won't hurt... - - delete window._YUI; -}); diff --git a/lib/bootstrap/yui-bootstrap-inline-min.js b/lib/bootstrap/yui-bootstrap-inline-min.js deleted file mode 100644 index 108b7ee..0000000 --- a/lib/bootstrap/yui-bootstrap-inline-min.js +++ /dev/null @@ -1 +0,0 @@ -(function(){YUI=function(a){return{use:function(){var b=Array.prototype.slice.call(arguments);YUI.Env.pending.push([a].concat(b))}}},YUI.config={doc:document},YUI.Env={mods:{},pending:[],add:function(a,b,c,d){a&&a.addEventListener?a.addEventListener(b,c,d):a&&a.attachEvent&&a.attachEvent("on"+b,c)},remove:function(a,b,c,d){if(a&&a.removeEventListener)try{a.removeEventListener(b,c,d)}catch(e){}else a&&a.detachEvent&&a.detachEvent("on"+b,c)}},YUI.add=function(a,b,c,d){YUI.Env.mods[a]={name:a,fn:b,version:c,details:d||{}}},YUI.applyConfig=function(a){YUI.pendingApplyConfig=a},YUI.merge=function(a,b){var c;for(c in b)b.hasOwnProperty(c)&&(a[c]=b[c])},YUI.onYUIReady=function(){var a=this.pendingApplyConfig||{},b=this.Env.pending.shift(),c=b.shift()||YUI();this.merge(YUI.Env.mods,_YUI.Env.mods),c.applyConfig(a),c.use.apply(c,b)};var a=window,b=document,c=YUI.Env.add,d=YUI.Env.remove,e=/MSIE/.test(navigator.userAgent),f=function(){function e(){setTimeout(function(){var c,f;for(c=0,f=b.length;f>c;c++)b[c]();d(a,"load",e)},0)}var b=[];return c(a,"load",e),{add:function(a){b.push(a)}}}(),g=function(){function h(){d--,0>=d&&!g&&(g=!0,setTimeout(function(){"undefined"!=typeof _YUI&&_YUI.onYUIReady()},100))}function i(a,c){var f,g,d=b.createElement(a);"script"===a&&(e?d.onreadystatechange=function(){/loaded|complete/.test(this.readyState)&&(this.onreadystatechange=null),h()}:d.onload=d.onerror=h);for(f in c)c.hasOwnProperty(f)&&(g=c[f],d.setAttribute(f,g));return d}function j(){var e,g,h,f=a.length,j=b.getElementsByTagName("head")[0];for(e=0;f>e;e++)g=a[e],"js"===g.type&&d++;for(e=0;f>e;e++){if(g=a[e],"css"===g.type)h=i("link",{href:g.url,rel:"stylesheet",type:"text/css"});else{if("js"!==g.type)continue;h=i("script",{src:g.url})}j.appendChild(h)}c=!0,a=[]}function k(b){var d=Array.prototype.slice.call(arguments,1),e=0,f=d.length;for(e=0;f>e;e++)a.push({type:b,url:d[e]});c&&j()}var a=[],c=!1,d=0,g=!1;return f.add(j),{js:function(){var a=Array.prototype.slice.call(arguments);k.apply(null,["js"].concat(a))},css:function(){var a=Array.prototype.slice.call(arguments);k.apply(null,["css"].concat(a))}}}();YUI.OnloadHandlerQueue=f,YUI.SimpleLoader=g})(); diff --git a/lib/bootstrap/yui-bootstrap-inline.js b/lib/bootstrap/yui-bootstrap-inline.js deleted file mode 100644 index 519cfb5..0000000 --- a/lib/bootstrap/yui-bootstrap-inline.js +++ /dev/null @@ -1,285 +0,0 @@ -// During the build process, the content of this file should be output in the -// same bundle as the one containing the definition of the YUI global object. -// Obviously, because of the way JavaScript code is evaluated, the definition -// of the YUI global object should appear first in that bundle (after the -// content of yui-override.js however...) -// The content of this file should appear last in that bundle. - -// The purpose of this file is to fix the standard behavior of a YUI instance -// YUI().use('xxx', function (Y) {...}); -// will result in the callback being invoked immediately, even if the required -// modules are not available! The following code will prevent this from -// happening *and* will automatically invoke the callback as soon as the -// required modules become available (in case you manage the loading of your -// JavaScript bundles separately) - -// This file is not used by shaker, the yui-fake-inline-min.js will be used instead. - -// This optimization is based on the work of: -// @jlecomte for Yahoo! Search -// @rgrove (https://github.com/rgrove/lazyload/) - -/*jslint nomen: true, plusplus: true, continue: true */ -/*globals _YUI */ - -(function () { - - YUI = function (cfg) { - return { - use: function () { - var args = Array.prototype.slice.call(arguments); - YUI.Env.pending.push([cfg].concat(args)); - } - }; - }; - - YUI.config = { - doc: document - }; - - YUI.Env = { - - mods: {}, - - pending: [], - - add: function (el, type, fn, capture) { - if (el && el.addEventListener) { - el.addEventListener(type, fn, capture); - } else if (el && el.attachEvent) { - el.attachEvent('on' + type, fn); - } - }, - - remove: function (el, type, fn, capture) { - if (el && el.removeEventListener) { - try { - el.removeEventListener(type, fn, capture); - } catch (ex) { - /* ignore */ - } - } else if (el && el.detachEvent) { - el.detachEvent('on' + type, fn); - } - } - }; - - YUI.add = function (name, fn, version, details) { - YUI.Env.mods[name] = { - name: name, - fn: fn, - version: version, - details: details || {} - }; - }; - - YUI.applyConfig = function (config) { - YUI.pendingApplyConfig = config; - }; - - YUI.merge = function (obj1, obj2) { - var attr; - for (attr in obj2) { - if (obj2.hasOwnProperty(attr)) { - obj1[attr] = obj2[attr]; - } - } - }; - - YUI.onYUIReady = function () { - var config = this.pendingApplyConfig || {}, - args = this.Env.pending.shift(), - instance = args.shift() || YUI(); - - this.merge(YUI.Env.mods, _YUI.Env.mods); - instance.applyConfig(config); - instance.use.apply(instance, args); - }; - - var - - //----------------------------------------------------------------------------- - // A few shorthands... - //----------------------------------------------------------------------------- - - w = window, - d = document, - add = YUI.Env.add, - remove = YUI.Env.remove, - - //----------------------------------------------------------------------------- - // Some basic browser sniffing... - //----------------------------------------------------------------------------- - - ie = /MSIE/.test(navigator.userAgent), - - //----------------------------------------------------------------------------- - // A simplistic queue allowing us to use a single 'load' event handler - // and execute functions in the order they were added to the queue, - // regardless of the browser. - //----------------------------------------------------------------------------- - - OnloadHandlerQueue = (function () { - - var queue = []; - - function onLoad(e) { - // Use a setTimeout to not interfere with RTB measurements! - setTimeout(function () { - var i, l; - - for (i = 0, l = queue.length; i < l; i++) { - queue[i](); - } - - remove(w, 'load', onLoad); - }, 0); - } - - add(w, 'load', onLoad); - - return { - add: function (fn) { - queue.push(fn); - } - }; - - }()), - - //----------------------------------------------------------------------------- - // A tiny JavaScript and CSS loader - //----------------------------------------------------------------------------- - - SimpleLoader = (function () { - - var queue = [], - windowLoaded = false, - jsRequestsPending = 0, - done = false; - - function decrementRequestPending() { - jsRequestsPending--; - if (jsRequestsPending <= 0 && !done) { - done = true; - setTimeout(function () { - if (typeof _YUI !== 'undefined') { - // Remember that we decrement jsRequestsPending - // on error as well! This means that _YUI may be - // undefined! - _YUI.onYUIReady(); - } - }, 100); - } - } - - function createNode(name, attrs) { - var node = d.createElement(name), key, value; - - if (name === 'script') { - if (ie) { - node.onreadystatechange = function () { - if (/loaded|complete/.test(this.readyState)) { - this.onreadystatechange = null; - } - decrementRequestPending(); - }; - } else { - node.onload = node.onerror = decrementRequestPending; - } - } - - for (key in attrs) { - if (attrs.hasOwnProperty(key)) { - value = attrs[key]; - node.setAttribute(key, value); - } - } - - return node; - } - - function flush() { - var i, l = queue.length, asset, node, - head = d.getElementsByTagName('head')[0]; - - // Compute jsRequestsPending first! - for (i = 0; i < l; i++) { - asset = queue[i]; - if (asset.type === 'js') { - jsRequestsPending++; - } - } - - for (i = 0; i < l; i++) { - asset = queue[i]; - - if (asset.type === 'css') { - - node = createNode('link', { - href: asset.url, - rel: 'stylesheet', - type: 'text/css' - }); - - } else if (asset.type === 'js') { - - node = createNode('script', { - src: asset.url - }); - - } else { - continue; - } - - head.appendChild(node); - } - - windowLoaded = true; - queue = []; - } - - function load(type) { - var urls = Array.prototype.slice.call(arguments, 1), - i = 0, - l = urls.length; - - for (i = 0; i < l; i++) { - queue.push({ - type: type, - url: urls[i] - }); - } - - if (windowLoaded) { - flush(); - } - } - - OnloadHandlerQueue.add(flush); - - return { - - js: function () { - var args = Array.prototype.slice.call(arguments); - load.apply(null, ['js'].concat(args)); - }, - - css: function () { - var args = Array.prototype.slice.call(arguments); - load.apply(null, ['css'].concat(args)); - } - }; - - }()); - - //----------------------------------------------------------------------------- - // Make some of the functionality exposed in this file available to code that - // will reside outside of this closure. Since we don't want to clobber the - // global scope, let's just bind stuff to the YUI object. We'll have to copy - // those things to the real YUI object once it is available! - //----------------------------------------------------------------------------- - - YUI.OnloadHandlerQueue = OnloadHandlerQueue; - YUI.SimpleLoader = SimpleLoader; - -}()); diff --git a/lib/bootstrap/yui-bootstrap-override.js b/lib/bootstrap/yui-bootstrap-override.js deleted file mode 100644 index f3782a4..0000000 --- a/lib/bootstrap/yui-bootstrap-override.js +++ /dev/null @@ -1,4 +0,0 @@ -if (typeof YUI !== 'undefined') { - _YUI = YUI; - YUI = undefined; -} \ No newline at end of file diff --git a/lib/compiler.js b/lib/compiler.js new file mode 100644 index 0000000..3144413 --- /dev/null +++ b/lib/compiler.js @@ -0,0 +1,530 @@ +var validateConfig = require('./config.js').validateConfig, + ShakerResources = require('./resources.js').ShakerResources, + Y = require('yui').YUI({useSync: true}).use('base-base'), + async = require('async'), + gear = require('gear'), + path = require('path'), + modules = { + rollups: { + mojitrollup: require('./rollups/mojitrollup.js').mojitrollup + }, + locations: { + local: require('./locations/local.js').local, + mobstor: require('shaker-mobstor').mobstor + } + }, + typeHierarchy = { + "yui-module": "js", + "controller": "yui-module", + "binder": "yui-module", + "asset": "*", + "addon": "yui-module", + "view": "html" + }; + +function ShakerCompiler(context) { +debugger; + this.compile = function (done) { + var resources = new ShakerResources(context), + config = resources.shakerConfig; + + config.appConfig = resources.appConfig; + + applyTasks(config, resources.appResources, function (err) { + applyRollups(config, resources.appResources, resources.organizedResources, function (err) { + applyLocations(config.locations, config.appConfig, resources.appResources, function (err) { + updateLoader(config, resources, function (err) { + constructMetadata(config, Y.mix(resources.appResources, resources.loaderResources), resources.organizedResources); + done(); + }) + }); + }); + }); + } + + /*this.run = function (done) { + var appResources = shakerResources.appResources, + organizedResources = shakerResources.organizedResources, + shakerConfig = shakerResources.shakerConfig; + + shakerConfig.appConfig = shakerResources.appConfig; + + compile(shakerConfig, shakerResources, function () { + //done(); + }); + return; + + require('fs').readFile('appResources.json', 'utf8', function (err, data) { + appResources = JSON.parse(data); + require('fs').readFile('organizedResources.json', 'utf8', function (err2, data2) { + organizedResources = JSON.parse(data2); + + // link each resources' require to allResouces + Y.Object.each(appResources, function (resource, url) { + Y.Object.each(resource.requires, function (requiredResource, requiredUrl) { + resource.requires[requiredUrl] = appResources[requiredUrl]; + }); + Y.Object.each(resource.dependencies, function (requiredResource, requiredUrl) { + resource.dependencies[requiredUrl] = appResources[requiredUrl]; + }); + }); + // link organized resources to appResources + Y.Object.each(organizedResources.app, function (appResources, context) { + + Y.Object.each(appResources.app, function (typeResources, type) { + Y.Object.each(typeResources, function (resource, url) { + typeResources[url] = appResources[url]; + }); + }); + + Y.Object.each(appResources.mojits, function (mojit) { + Y.Object.each(mojit, function (mojitResources, action) { + var view = mojitResources.view, + controller = mojitResources.controller, + binder = mojitResources.binder, + assets = mojitResources.assets; + + mojitResources.view = appResources[view.url]; + + if (controller) { + mojitResources.controller = appResources[controller.url]; + Y.Object.each(controller.dependencies, function (dependency, url) { + controller.dependencies[url] = appResources[url]; + }); + } + if (binder) { + mojitResources.binder = appResources[binder.url]; + Y.Object.each(binder.dependencies, function (dependency, url) { + binder.dependencies[url] = appResources[url]; + }); + } + Y.Object.each(assets, function (assetGroup, type) { + Y.Object.each(assetGroup, function (resource, url) { + assetGroup[url] = appResources[url]; + }); + }); + }); + }); + }); + + Y.Object.each(organizedResources.mojito, function (mojitoResource, url) { + organizedResources.mojito[url] = appResources[url]; + }); + + compile(config, appResources, organizedResources, function () { + //done(); + }); + }); + }); + + };*/ + + function configToGearTasks(config) { + var gearTasks = {}; + Y.Object.each(config.tasks, function (tasks, type) { + var prevTask = 'readResource'; + gearTasks[type] = { + tasks: {} + }; + Y.Object.each(tasks, function (taskOptions, taskName) { + if (taskOptions === false) { + return; + } + + taskOptions = !Y.Lang.isObject(taskOptions) ? {} : taskOptions; + + gearTasks[type].tasks[taskName] = { + task: [taskName, taskOptions] + }; + if (prevTask) { + gearTasks[type].tasks[taskName].requires = prevTask; + } + prevTask = taskName; + }); + gearTasks[type].lastTask = prevTask; + }); + + return gearTasks; + } + + // TODO: remove resources that are empty from both appResources and organized resources + + function applyTasks(config, appResources, done) { + var count = 0; + var registry = new gear.Registry({dirname: path.resolve(__dirname, '../', 'node_modules', 'gear-lib', 'lib')}); + + registry.load({tasks: { + 'readResource': function (resource, blob, done) { + resource.read(function (err) { + done(err, new gear.Blob(resource.content, {name: resource.path})); + }); + }, + 'updateResource': function (resource, blob, done) { + resource.content = blob.result; + done(null); + } + }}); + + var gearTasks = configToGearTasks(config); + + // put all resources into an array, since async requires this + var resourcesArray = []; + Y.Object.each(appResources, function (resource) { + resourcesArray.push(resource); + }); + async.forEach(resourcesArray, function (resource, callback) { + + // get the most specific task type for this resource + var type = resource.type; + while (!gearTasks[type]) { + type = typeHierarchy[type]; + if (!type) { + break; + } + if (type === "*") { + type = resource.subtype; + + break; + } + } + + var queue = new gear.Queue({registry: registry}); + var newGearTasks = gearTasks[type] ? Y.clone(gearTasks[type].tasks) : {}; + + //queue.read("/homes/jimenez/shaker_v4/app1/yui_modules/app-yui-module.client.js") + + queue.read("/dev/null"); + //queue.readResource(resource); + newGearTasks['readResource'] = { + task: ['readResource', resource] + }; + newGearTasks['updateResource'] = { + task: ['updateResource', resource], + requires: gearTasks[type] && gearTasks[type].lastTask || "readResource" + }; + /*newGearTasks['join'] = { + requires: 'updateResource' + }*/ + + queue.tasks(newGearTasks); + + queue.run(function (err, results) { + var error, + errorType, + errorTask, + errorMsg; + + if (err) { + console.log(err); + return; + error = err.error; + errorType = error.error.type ? "a " + error.error.type.toLowerCase() : "an"; + errorTask = error.task; + errorMsg = "There was " + errorType + " error when applying " + errorTask + " to " + resource.path + ":\n" + error.error.message; + if (error.error.stop) { + callback(errorMsg); + } else { + console.log(errorMsg); + } + } + callback(null); + }); + }, function (err) { + done(err); + }); + } + + function applyRollups(config, appResources, organizedResources, done) { + var rollups, + i = 0; + + Y.Object.each(config.routeRollups, function (rollupConfig, rollupName) { + var rollupModule = modules.rollups[rollupName]; + rollups = rollupModule(rollupConfig, organizedResources); + // merge rollups + }); + + // filter out content that don't have any particular resource that belongs to a posl + // add rollups to organizedResources + Y.Object.each(organizedResources.app, function (poslResources, posl) { + var contextPOSLArray = posl.split("-"), + minorSelector = contextPOSLArray[contextPOSLArray.length-2]; + + // check which rollups belong in posl + poslResources.rollups = {}; + Y.Object.each(rollups[posl], function (routeRollups, route) { + poslResources.rollups[route] = {}; + + Y.Object.each(routeRollups, function (rollup, type) { + var belongsInPOSL = posl === "*"; + Y.Object.each(rollup.resources, function (resource, url) { + belongsInPOSL |= resource.selector === minorSelector; + }); + if (belongsInPOSL) { + poslResources.rollups[route][type] = { + rollups: {}, + resources: {} + }; + // create new resources for rollups and add to appResources + Y.Array.each(rollup.rollups, function (rollupContent, index) { + var rollupName = ["route", type, route, posl, index].join("_"); + appResources[rollupName] = { + content: rollupContent, + basename: rollupName, + subtype: type, + type: "rollup", + url: rollupName, + resources: rollup.resources //TODO: just for debugging + } + poslResources.rollups[route][type].rollups[rollupName] = appResources[rollupName]; + Y.mix(poslResources.rollups[route][type].resources, rollup.resources); + }); + } + }); + if (Y.Object.isEmpty(poslResources.rollups[route])) { + delete poslResources.rollups[route]; + } + }); + if (Y.Object.isEmpty(poslResources.rollups)) { + delete poslResources.rollups; + } + + // determine if app resources belong in posl + Y.Object.each(poslResources.app, function (typeResources, type) { + if (config.appResources && !config.appResources[type]) { + return; + } + var belongsInPOSL = posl === "*"; + Y.Object.each(typeResources, function (resource, url) { + belongsInPOSL |= resource.selector === minorSelector; + }); + if (!belongsInPOSL) { + delete poslResources.app[type]; + } + }); + if (Y.Object.isEmpty(poslResources.app)) { + delete poslResources.app; + } + + // determine if each mojit belongs in posl + Y.Object.each(poslResources.mojits, function (mojitResources, mojit) { + Y.Object.each(mojitResources, function (actionResources, action) { + var resources = {}, + controller = actionResources.controller, + view = actionResources.view, + binder = actionResources.binder, + assets = actionResources.assets; + + // add view + /*if (config.mojitResources.views) { + resources["js"][view.url] = view; + } + // add controller and dependencies + if (controller && config.mojitResources.controllers) { + resources["js"][controller.url] = controller; + Y.mix(resources["js"], controller.dependencies); + } + // add binder and dependencies + if (binder && config.mojitResources.binders) { + resources["js"][binder.url] = binder; + Y.mix(resources["js"], binder.dependencies); + }*/ + + // add assets + Y.Object.each(assets, function (assetGroup, type) { + resources[type] = resources[type] || {}; + + Y.Object.each(assetGroup, function (resource, url) { + resources[type][url] = resource; + /*// make css asset inline if specified by config + // TODO: delete resource if inline is empty + if (posl === "*" && type === "css" && config.mojitResources.inlineCss) { + resource.inline = true; + }*/ + }); + }); + + if (Y.Object.isEmpty(resources["js"])) { + delete resources["js"]; + } + + // check if the resources belong in posl + Y.Object.each(resources, function (typeResources, type) { + var belongsInPOSL = posl === "*"; + Y.Object.each(typeResources, function (resource, url) { + belongsInPOSL |= resource.selector === minorSelector; + }); + if (!belongsInPOSL) { + delete resources[type]; + } + }); + if (!Y.Object.isEmpty(resources)) { + // now resources are organized by type + mojitResources[action] = resources; + } else { + delete mojitResources[action]; + } + }); + if (Y.Object.isEmpty(mojitResources)) { + delete poslResources.mojits[mojit]; + } + }); + if (Y.Object.isEmpty(poslResources.mojits)) { + delete poslResources.mojits; + } + + }); + + done(null); + } + + function applyLocations(locationsConfig, appConfig, resources, done) { + var locationArray = Object.keys(locationsConfig), + resourcesArray = [], + i, + crypto = require('crypto'), + locationYUIConfigs = {}; + + // place all resources into an array, since async requires this + for (i in resources) { + /*// skip resources that are inline + if (resources[i].inline) { + continue; + }*/ + // add location attribute, which will be used to store the location after an upload + resources[i].locations = {}; + // add a getHash function to each resource which can be used to retrive the hash of the resource + resources[i].getHash = function () { + var hash = crypto.createHash('md5').update(this.content).digest('hex'); + // save hash for future calls to getHash + this.getHash = function () { + return hash; + } + return hash; + } + resourcesArray.push(resources[i]); + } + + // for each location type, upload all resources and get their new locations + async.forEach(locationArray, function (locationName, locationCallback) { + var locationConfig = locationsConfig[locationName]; + locationConfig.appConfig = appConfig; + var locationModule = new modules.locations[locationName](locationConfig); + locationYUIConfigs[locationName] = locationModule.yuiConfig; + async.forEach(resourcesArray, function (resource, resourceCallback) { + // upload the resource and get new location + locationModule.store(resource, function (err, resourceLocation) { + resource.locations[locationName] = resourceLocation; + resourceCallback(err); + }); + }, function (err) { + locationCallback(err); + }); + }, function (err) { + done(err, locationYUIConfigs); + }); + } + + function constructMetadata(config, appResources, organizedResources) { + var resources = [], + resourcesByType = {}; + // construct final meta data file + + // construct inline map + organizedResources.inline = {}; + Y.Object.each(appResources, function (resource) { + if (resource.inline) { + organizedResources.inline[resource.url] = resource.content; + } + }); + if (Y.Object.isEmpty(organizedResources.inline)) { + delete organizedResources.inline; + } + + // mojito + organizedResources.mojito = Object.keys(organizedResources.mojito); + + Y.Object.each(organizedResources.app, function (poslResources, posl) { + + // app + Y.Object.each(poslResources.app, function (typeResources, type) { + poslResources.app[type] = Object.keys(typeResources); + }); + + // mojits + Y.Object.each(poslResources.mojits, function (mojitResources, mojit) { + Y.Object.each(mojitResources, function (actionResources, action) { + Y.Object.each(actionResources, function (typeResources, type) { + actionResources[type] = Object.keys(typeResources); + }); + }); + }); + + // rollups + Y.Object.each(poslResources.rollups, function (routeRollup, route) { + Y.Object.each(routeRollup, function (typeResources, type) { + routeRollup[type].rollups = Object.keys(typeResources.rollups); + // replace resource with true + Y.Object.each(routeRollup[type].resources, function (resource, id) { + routeRollup[type].resources[id] = true; + }) + + }); + }); + }); + + console.log("All Resources: " + Object.keys(appResources).length); + //console.log("Used Resources: " + Object.keys(usedResources).length); + //console.log("Inline Resources: " + Object.keys(organizedResources.inline).length); + //console.log("Location Resources: " + Object.keys(organizedResources.locations.local.resources).length); + +// TODO check if write files, sometimes no permission +// TODO check it can write file first before compiling +require('fs').writeFile("shakerResources.json", JSON.stringify(organizedResources)); + + } + + function updateLoader(config, resources, done) { + var locations = resources.organizedResources.locations = {}, + locationsArray = Y.Object.keys(config.locations); + + // TODO loader files for different locations + // update resource store + + // construction locations map, mapping app url to their corresponding location url + // TODO: this needs to be in an async + Y.Array.each(locationsArray, function (locationName) { + var loaderResources, + locationConfig = {}; + + locations[locationName] = { + resources: {}, + yuiConfig: {} + }; + Y.Object.each(resources.appResources, function (resource) { + // also upload inline file, incase disabled in runtime + //if (!resource.inline) { + locations[locationName].resources[resource.url] = resource.locations[locationName]; + //} + }); + + resources.resolveResourceVersions(locations[locationName].resources); + loaderResources = resources.getLoaderResources(); + + locationConfig[locationName] = config.locations[locationName]; + // apply tasks to loader resources and upload to locations + applyTasks(config, loaderResources, function (err) { + applyLocations(locationConfig, config.appConfig, loaderResources, function (err, locationYUIConfigs) { + locations[locationName].yuiConfig = locationYUIConfigs[locationName] || {}; + Y.Object.each(loaderResources, function (resource) { + locations[locationName].resources[resource.url] = resource.locations[locationName]; + }); + done(err); + }); + }); + + }); + } +} + +exports.ShakerCompiler = ShakerCompiler; \ No newline at end of file diff --git a/lib/config.js b/lib/config.js new file mode 100644 index 0000000..ca686b4 --- /dev/null +++ b/lib/config.js @@ -0,0 +1,73 @@ +exports.validateConfig = function () { + var config = + { + tasks: { + binders: { + lint: true, + minify: { + level: 5 + } + }, + controllers: { + lint: true, + minify: false + }, + css: { + lint: true, + minify: { + level: 2 + } + }, + views: { + htmlToJs: true + } + }, + + rollups: { + js: { + jsRollup: { + "device:iphone": { + "search-home-page": { + highCoverageMojits: [ + "SearchDirect.index", + "NewsTopicDD.index", + "AlsoTry.index", + "Footer.index" + ], + split: 3 + } + } + } + } + }, + + locations: { + mobstor: { + expiration: 1000, + comboUrl: "/combo?", + comboDelimiter: "&" + }, + amazon: { + expiration: 1000 + }, + local: { + comboUrl: "/combo~", + comboDelimiter: "~" + } + }, + + settings: { + serveLocation: 'mobstor', + serveJs: { + method: 'combo', + location: 'top' + }, + serveCss: { + method: 'inline' + }, + serveViews: true, + optimizeBootstrap: true + } + } + return config; +}; \ No newline at end of file diff --git a/lib/locations/local.js b/lib/locations/local.js new file mode 100644 index 0000000..9a856e2 --- /dev/null +++ b/lib/locations/local.js @@ -0,0 +1,40 @@ +var Y = require('yui').YUI({useSync: true}).use('base-base'); +var fs = require('fs'); +var basedir = 'assets/compiled'; + + +exports.local = function (config) { + var root = "/" + config.appConfig.prefix + "/" + config.appConfig.appName + "/" + basedir + "/"; + this.yuiConfig = { + "groups": { + "app": { + "base": "/", + "comboBase": "/combo~", + "comboSep": "~", + "combine": true, + "root": root + } + } + }; + + // create compile directory under assets + fs.mkdir(basedir); + + this.store = function (resource, done) { + var hash = resource.getHash(), + filename = resource.basename + "_" + hash + "." + resource.subtype, + filepath = basedir + '/' + filename; + + // check if file exists, if so do not write file + fs.exists(filepath, function (exists) { + var url = root + filename; + if (exists) { + done(null, url); + } else { + require('fs').writeFile(filepath, resource.content, function (err) { + done(err, url); + }); + } + }); + }; +}; diff --git a/lib/resources.js b/lib/resources.js new file mode 100644 index 0000000..13bc2cc --- /dev/null +++ b/lib/resources.js @@ -0,0 +1,745 @@ +var Y = require('yui').YUI({useSync: true}).use('base-base'), + fs = require('fs'); + +function ShakerResources(context) { + var self = this, + store = makeStore({ + root: process.cwd(), + context: context + }), + libutils = require('./utils'), + Y = require('yui').YUI({useSync: true}), + shakerConfig = getShakerConfig(context); + + + self.shakerConfig = shakerConfig; + self.organizedResources = { + mojito: {}, + app: {} + }; + self.appResources = {}; + self.loaderResources = {}; + + self.appConfig = { + appName: store.url.config.appName, + prefix: store.url.config.prefix + + } + + getResources(); + + // give each app resource a read function + Y.Object.each(self.appResources, function (resource) { + resource.read = function (callback) { + fs.readFile(resource.path, 'utf8', function (err, content) { + resource.content = content; + callback(err); + }); + }; + }); + + + // TODO remove posl if no resoruces + require('fs').writeFile("appResources.json", JSON.stringify(self.appResources)); + require('fs').writeFile("organizedResources.json", JSON.stringify(self.organizedResources)); + + + + this.resolveResourceVersions = function (resourcesMap) { + store.resolveResourceVersions(resourcesMap); + } + + this.getLoaderResources = function () { + var ress, + m, + mojit, + mojits= [], + context = getStoreConfigs().context, + yuiModules = store.yui.getYUIURLDetails(), + seedModules, + moduleRess = {}; + + //mojito doesnt provide default lang in build time. + context.lang = context.lang || 'en'; + seedModules = store.yui.getAppSeedFiles(context); + + function processRess(ress) { + var r, + res; + for (r = 0; r < ress.length; r += 1) { + res = ress[r]; + if (res.type === 'view') { + res.yui = {name: res.url}; + moduleRess[res.url] = res; + } + if ('common' !== res.affinity.affinity) { + continue; + } + if (res.yui && res.yui.name) { + moduleRess[res.yui.name] = res; + } + } + for (r = 0; r < ress.length; r += 1) { + res = ress[r]; + if ('client' !== res.affinity.affinity) { + continue; + } + if (res.yui && res.yui.name) { + moduleRess[res.yui.name] = res; + } + } + } + + ress = store.getResourceVersions({}); + processRess(ress); + + mojits = store.listAllMojits(); + mojits.push('shared'); + for (m = 0; m < mojits.length; m += 1) { + mojit = mojits[m]; + ress = store.getResourceVersions({mojit: mojit}); + processRess(ress); + } + + for (m = 0; m < seedModules.length; m += 1) { + ress = yuiModules[seedModules[m]]; + + if (ress) { + moduleRess[seedModules[m]] = ress; + } + } + + // get loader resources + for (m in moduleRess) { + ress = moduleRess[m]; + if (m.indexOf("loader") === 0) { + self.loaderResources[m] = { + basename: m, + url: ress.url, + path: ress.source.fs.fullPath, + type: ress.type, + subtype: "js", + selector: ress.selector, + rawResource: ress, + read: function (done) { + var thisResource = this; + store.getResourceContent(store.makeStaticHandlerDetails(thisResource.rawResource), function (err, content) { + thisResource.content = content.toString(); + done(err); + }); + } + } + + } + } + return self.loaderResources; + } + + function getResources() { + + var metadata = {}, + mojits = getMojits(), + contexts = getContexts(), + mojit, + context, + contextPOSL, + contextPOSLStr, + contextPOSLs = {}, + contextPOSLArray, + //minorSelector, + m, + c, + shakenMojit = {}, + shakenApp = {}; + + // get all context POSLs + for (c = 0; c < contexts.length; c++) { + context = contexts[c]; + contextPOSLArray = getPOSLFromContext(context); + contextPOSLStr = contextPOSLArray.join("-"); + if (contextPOSLs[contextPOSLStr]) { + continue; + } + contextPOSLs[contextPOSLStr] = { + context: context, + contextPOSLStr: contextPOSLStr//, + //minorSelector: contextPOSLArray[contextPOSLArray.length-2] + } + } + + for (c in contextPOSLs) { + contextPOSL = contextPOSLs[c]; + context = contextPOSL.context; + //minorSelector = contextPOSL.minorSelector; + + self.organizedResources.app[c] = { + app: {}, + mojits: {} + }; + for (m = 0; m < mojits.length; m++) { + mojit = mojits[m]; + shakenMojit = shakeMojitByContext(mojit, context); + addMojitResources(mojit, shakenMojit, c);//, minorSelector); + } + + shakeAppResourcesByContext(contextPOSL);//, minorSelector); + } + + shakeMojito(); + + +//debugger; + + function getDependencies(resource) { + var dependencies = {}, + dependency; + + if (resource.dependencies) { + return resource.dependencies; + } + for (var dependencyUrl in resource.requires) { + dependency = resource.requires[dependencyUrl]; + dependencies[dependency.url] = dependency; + dependencies = Y.merge(dependencies, getDependencies(dependency)); + } + return dependencies; + } + + function storeYUIModule(rawResource, yuiResources) { + var module, + resource; + + if (self.appResources[rawResource.url]) { + return self.appResources[rawResource.url]; + } + + resource = { + basename: rawResource.source.fs.basename, + url: rawResource.url, + path: rawResource.source.fs.fullPath, + type: rawResource.type, + subtype: "js", + requires: {}, + selector: rawResource.selector + }; + self.appResources[rawResource.url] = resource; + + + try { + module = store.yui._makeYUIModuleConfig('client', rawResource); // TODO: try catch + } catch (e) { + console.log("\n\n\n\n\nError: " + e); + return resource; + } + + for (var i in module.requires) { + var requiredResourceName = module.requires[i]; + var requiredResource = yuiResources[requiredResourceName]; + if (requiredResource) { + resource.requires[requiredResource.url] = self.appResources[requiredResource.url] || + storeYUIModule(requiredResource, yuiResources); + } + } + + resource.dependencies = getDependencies(resource); + return resource; + } + + + + function addMojitResources(mojit, shakenMojit, contextPOSL) {//, minorSelector) { + var resource, + mojitResources = {}, + //mojitBelongsInPOSL, + controllerResources = [], + //mojitBelongsInPOSL = contextPOSL === "*", + d, + b, + a, + dependencies, + rawResource, + action, + assetAction; + + // Get view resources + for (action in shakenMojit.views) { + rawResource = shakenMojit.views[action]; + //mojitBelongsInPOSL |= rawResource.selector === minorSelector; + resource = self.appResources[rawResource.url] || { + basename: rawResource.source.fs.basename, + url: rawResource.url, + path: rawResource.source.fs.fullPath, + type: rawResource.type, + subtype: "html", + selector: rawResource.selector + + }; + self.appResources[rawResource.url] = resource; + + mojitResources[action] = {}; + if (!Y.Object.isEmpty(shakenMojit.assets)) { + mojitResources[action].assets = {}; + } + mojitResources[action].view = resource; + + } + + // Get controller resources + if (!Y.Object.isEmpty(shakenMojit.controller)) { + rawResource = shakenMojit.controller; + //mojitBelongsInPOSL |= rawResource.selector === minorSelector; + resource = storeYUIModule(rawResource, shakenMojit.yuiResources); + for (action in shakenMojit.views) { + mojitResources[action].controller = resource; + } + } + + // Get binder resources + for (b in shakenMojit.binders) { + rawResource = shakenMojit.binders[b]; + action = rawResource.name; + //mojitBelongsInPOSL |= rawResource.selector === minorSelector; + resource = storeYUIModule(rawResource, shakenMojit.yuiResources); + mojitResources[action].binder = resource; + } + + // Get assets resources + for (a in shakenMojit.assets) { + rawResource = shakenMojit.assets[a]; + //mojitBelongsInPOSL |= rawResource.selector === minorSelector; + assetAction = null; + for (action in shakenMojit.views) { + // check if the asset belongs to a specific action + if (rawResource.name.indexOf(action) === 0) { + assetAction = action; + break; + } + } + resource = self.appResources[rawResource.url] || { + basename: rawResource.source.fs.basename, + url: rawResource.url, + path: rawResource.source.fs.fullPath, + type: rawResource.type, + subtype: rawResource.subtype, + selector: rawResource.selector + }; + + var pathArray = resource.path.split("/"); + var containingDir = pathArray[pathArray.length-2]; + + // determine if resource is not to be inclucded + + // make resource inline if specified by config or name + if (resource.basename.indexOf("-void") !== -1 || containingDir.indexOf("void") !== -1) { + continue; + } + + self.appResources[rawResource.url] = resource; + if (resource.basename.indexOf("-inline") !== -1 || (resource.subtype === "css" && shakerConfig.inlineCss) + || (resource.subtype === "js" && shakerConfig.inlineJs) + || containingDir.indexOf("inline") !== -1) { + resource.inline = true; + } + + if (assetAction) { + mojitResources[assetAction].assets[resource.subtype] = mojitResources[assetAction].assets[resource.subtype] || {}; + mojitResources[assetAction].assets[resource.subtype][rawResource.url] = resource; + } else { + for (action in shakenMojit.views) { + mojitResources[action].assets[resource.subtype] = mojitResources[action].assets[resource.subtype] || {}; + mojitResources[action].assets[resource.subtype][rawResource.url] = resource; + } + } + } + // sort assets for each action + for (action in shakenMojit.views) { + sortAssets(mojitResources[action]); + } + + /* + if (!mojitBelongsInPOSL) { + return; + }*/ + self.organizedResources.app[contextPOSL].mojits[mojit] = mojitResources; + } + + } + + function getContexts() { + return store.selector._listUsedContexts(); + } + + function getMojits() { + return store.listAllMojits(); + } + + function getPOSLFromContext(context) { + return store.selector.getPOSLFromContext(context); + } + + function makeStore(cfg) { + + process.shakerCompiler = true; + var store = require('/homes/jimenez/shaker_v4/app1/node_modules/mojito/lib/store.js'); + return store.createStore(cfg); + } + + + function getShakerConfig(context) { + var config = store.getAppConfig(context || {}); + // TODO: change to shaker + return config.shaker2 || {}; + }; + + function getShakerConfigByContext(mojitName, context) { + context = context || {}; + var filter = { + type:'config', + name: 'shaker' + }, + configResources, + shakerConfigResource, + config; + + if (mojitName !== 'shared') { + filter.mojit = mojitName; + } + configResources = store.getResourceVersions(filter), + shakerConfigResource = configResources.length ? configResources[0] : null; + + if (shakerConfigResource) { + config = store.config.readConfigYCB(shakerConfigResource.source.fs.fullPath, context); + return config.shaker || null; + } else { + return null; + } + } + + function getStoreConfigs() { + debugger; + var appConfig = store.getAppConfig(context); + + return { + prefix: store.url.config.prefix, + appName: appConfig.staticHandling.appName, + context: context || {} + }; + } + + function shakeMojito () { + var context = getStoreConfigs().context, + rawResources = store.getResourceVersions({mojit:'shared'}), + rawResource, + resource, + i; + + for (i in rawResources) { + rawResource = rawResources[i]; + if ((rawResource.source.pkg.name === 'mojito' || rawResource.source.pkg.name === 'mojito-shaker') && + (rawResource.affinity.affinity === 'common' || rawResource.affinity.affinity === 'client') && + rawResource.source.fs.ext === '.js') { + resource = self.appResources[rawResource.url] || { + basename: rawResource.source.fs.basename, + url: rawResource.url, + path: rawResource.source.fs.fullPath, + type: rawResource.type, + subtype: "js", + selector: rawResource.selector + }; + self.appResources[rawResource.url] = resource; + + self.organizedResources.mojito[rawResource.url] = resource; + } + } + } + + function shakeLoader() { + var ress, + m, + mojit, + mojits= [], + context = getStoreConfigs().context, + yuiModules = store.yui.getYUIURLDetails(), + seedModules, + moduleRess = {}; + + //mojito doesnt provide default lang in build time. + context.lang = context.lang || 'en'; + seedModules = store.yui.getAppSeedFiles(context); + + function processRess(ress) { + var r, + res; + for (r = 0; r < ress.length; r += 1) { + res = ress[r]; + if (res.type === 'view') { + res.yui = {name: res.url}; + moduleRess[res.url] = res; + } + if ('common' !== res.affinity.affinity) { + continue; + } + if (res.yui && res.yui.name) { + moduleRess[res.yui.name] = res; + } + } + for (r = 0; r < ress.length; r += 1) { + res = ress[r]; + if ('client' !== res.affinity.affinity) { + continue; + } + if (res.yui && res.yui.name) { + moduleRess[res.yui.name] = res; + } + } + } + + ress = store.getResourceVersions({}); + processRess(ress); + + mojits = store.listAllMojits(); + mojits.push('shared'); + for (m = 0; m < mojits.length; m += 1) { + mojit = mojits[m]; + ress = store.getResourceVersions({mojit: mojit}); + processRess(ress); + } + + for (m = 0; m < seedModules.length; m += 1) { + ress = yuiModules[seedModules[m]]; + + if (ress) { + moduleRess[seedModules[m]] = ress; + } + } + + // get loader resources + for (m in moduleRess) { + ress = moduleRess[m]; + if (m.indexOf("loader") === 0) { + self.loaderResources[m] = { + basename: m, + url: ress.url, + path: ress.source.fs.fullPath, + type: ress.type, + subtype: "js", + selector: ress.selector, + rawResource: ress, + read: function (done) { + var thisResource = this; + store.getResourceContent(store.makeStaticHandlerDetails(thisResource.rawResource), function (err, content) { + thisResource.content = content.toString(); + done(err); + }); + } + } + + } + } + + + + return self.loaderResources; + } + + function getPOSLFromContext(ctx) { + return store.selector.getPOSLFromContext(ctx); + }; + + // takes a list of resources and returns only those that match a context + function filterResourceVersionsByContext(ctx, ress) { + var posl = getPOSLFromContext(ctx), + bySelector = {}, // selector: id: resource + out = {}, // id: resource + r, + res, + s, + selector; + for (r = 0; r < ress.length; r += 1) { + res = ress[r]; + if (!bySelector[res.selector]) { + bySelector[res.selector] = {}; + } + bySelector[res.selector][res.id] = res; + } + for (s = posl.length; s > 0; s -= 1) { + selector = posl[s - 1]; + for (r in bySelector[selector]) { + if (bySelector[selector].hasOwnProperty(r)) { + out[r] = bySelector[selector][r]; + } + } + } + return Y.Object.values(out); + }; + + function shakeMojitByContext(mojitName, context) { + //get the resources for that mojit + var rawResources, + //transform yui modules on a hash object using key as yui-name + yuiResources, + mojitResources = { + binders: {}, + langs:{}, + assets:{}, + views: {}, + yui_modules: {} + }; + + function getMojitResourcesByContext(mojitName, ctx) { + var mojitResources = store.getResourceVersions({mojit: mojitName}), + mojitResourcesFilterIterator = function (item) { + //remove mojito Framework + shared (app level) assets + return !(item.source.pkg.name === 'mojito' || item.source.pkg.name === 'mojito-shaker' || + (item.mojit === 'shared' && item.type === 'asset')) && + (item.affinity.affinity === 'common' || item.affinity.affinity === 'client') + }, + sharedResources = store.getResourceVersions({mojit: 'shared'}), + sharedResourcesFilterIterator = function (item) { + return !(item.source.pkg.name === 'mojito' || item.source.pkg.name === 'mojito-shaker' || + item.type === 'asset') && + (item.affinity.affinity === 'common' || item.affinity.affinity === 'client') + }; + + // Return the resources in the "shared" mojit too, since Mojito considers those as part of each mojit. + mojitResources = mojitResources.filter(mojitResourcesFilterIterator); + mojitResources = filterResourceVersionsByContext(ctx, mojitResources); + sharedResources = sharedResources.filter(sharedResourcesFilterIterator); + sharedResources = filterResourceVersionsByContext(ctx, sharedResources); + return sharedResources.concat(mojitResources); + }; + + rawResources = getMojitResourcesByContext(mojitName, context); + + function getYUIModuleNamesFromResources(rawResources) { + var cleanList = {}; + yuiModules = rawResources.forEach(function(item, key){ + if (item.yui && item.yui.name) { + cleanList[item.yui.name] = item; + } + }); + return cleanList; + } + + mojitResources.yuiResources = getYUIModuleNamesFromResources(rawResources); + + function matchDependencies(sortedNeededYui, yuiModules) { + var matched = {}; + Y.Array.each(sortedNeededYui, function (name, index) { + if (yuiModules[name]) { + matched[name] = yuiModules[name]; + } + }); + return matched; + } + + // group all dependencies + rawResources.forEach(function(rawResource) { + if (rawResource.mojit === "shared") { + return; + } + switch(rawResource.type) { + case 'controller': + // we create some properties due to the way _precomputeYUIDependencies works. + mojitResources.controller = rawResource; + break; + case 'view': + mojitResources.views[rawResource.name] = rawResource; + break; + + case 'binder': + mojitResources.binders[rawResource.yui.name] = rawResource; + break; + + case 'asset': + mojitResources.assets[rawResource.name] = rawResource; + break; + case 'yui-lang': + mojitResources.langs[rawResource.yui.name] = store.yui._makeYUIModuleConfig('client', rawResource); + mojitResources.langs[rawResource.yui.name].lang = rawResource.yui.lang; + } + }); + + return mojitResources; + + } + + function sortAssets(assets) { + Y.Object.each(assets, function (typeAssets, type) { + var sortedKeys = Object.keys(typeAssets).sort(); + Y.Array.each(sortedKeys, function (key) { + var asset = typeAssets[key]; + delete typeAssets[key]; + typeAssets[key] = asset; + }); + }); + } + + function shakeAppResourcesByContext(contextPOSL) {//, minorSelector) { + var appResources, + assets = {}, + i, + rawResource, + resource; + //mojitBelongsInPOSL = contextPOSL.contextPOSLStr === "*"; + + function getAppResourcesByContext(context) { + var rawResources = store.getResourceVersions({mojit:'shared'}), + appResourcesFilterIterator = function (item) { + //remove mojito Framework + app autoloads (included in the mojits already) + return item.source.pkg.name !== 'mojito' && item.type !== 'yui-module' && + (item.affinity.affinity === 'common' || item.affinity.affinity === 'client') + }; + rawResources = rawResources.filter(appResourcesFilterIterator); + return filterResourceVersionsByContext(context, rawResources); + }; + + appResources = getAppResourcesByContext(contextPOSL.context); + for (i in appResources) { + rawResource = appResources[i]; + // get asset type resources and ignore resources under assets/compiled + if (rawResource.type === 'asset' && rawResource.source.fs.fullPath.indexOf("assets/compiled") === -1) { + //mojitBelongsInPOSL |= rawResource.selector === minorSelector; + resource = self.appResources[rawResource.url] || { + basename: rawResource.source.fs.basename, + url: rawResource.url, + path: rawResource.source.fs.fullPath, + type: 'asset', + subtype: rawResource.subtype, + selector: rawResource.selector + }; + + var pathArray = resource.path.split("/"); + var containingDir = pathArray[pathArray.length-2]; + + // determine if resource is not to be inclucded + + // make resource inline if specified by config or name + if (resource.basename.indexOf("-void") !== -1 || containingDir.indexOf("void") !== -1) { + continue; + } + + self.appResources[rawResource.url] = resource; + if (resource.basename.indexOf("-inline") !== -1 || (resource.subtype === "css" && shakerConfig.inlineCss) + || (resource.subtype === "js" && shakerConfig.inlineJs) + || containingDir.indexOf("inline") !== -1) { + resource.inline = true; + } + assets[rawResource.subtype] = assets[rawResource.subtype] || {}; + assets[rawResource.subtype][rawResource.url] = resource; + } + } + + // sort assets + sortAssets(assets); + + //if (mojitBelongsInPOSL) { + self.organizedResources.app[contextPOSL.contextPOSLStr].app = assets; + /*} else { + delete self.organizedResources.app[contextPOSL.contextPOSLStr].app; + }*/ + } +} + + +exports.ShakerResources = ShakerResources; \ No newline at end of file diff --git a/lib/rollups/mojitrollup.js b/lib/rollups/mojitrollup.js new file mode 100644 index 0000000..452d937 --- /dev/null +++ b/lib/rollups/mojitrollup.js @@ -0,0 +1,100 @@ +var Y = require('yui').YUI({useSync: true}).use('base-base'); +exports.mojitrollup = function (config, resources) { + var mojitoResources = resources.mojito; + var rollups = {}; + Y.Object.each(resources.app, function (poslResources, posl) { + rollups[posl] = {}; + var appResources = poslResources.app; + + // get resources for each route + Y.Object.each(config.rollups, function (mojits, route) { + rollups[posl][route] = { + js: { + rollups: [], + resources: {} + }, + css: { + rollups: [], + resources: {} + } + } + + // add mojito resources + Y.Object.each(mojitoResources, function (mojitoResource, url) { + if (rollups[posl][route][mojitoResource.subtype]) { + rollups[posl][route][mojitoResource.subtype].resources[url] = mojitoResource; + } + }); + + // add app resources + Y.Object.each(appResources, function (typeResources, type) { + Y.Object.each(typeResources, function (appResource, url) { + if (rollups[posl][route][appResource.subtype]) { + rollups[posl][route][appResource.subtype].resources[url] = appResource; + } + }); + }); + + // add mojit resources + Y.Array.each(mojits, function (mojit) { + var mojitParts = mojit.split("."), + mojitName = mojitParts[0], + mojitAction = mojitParts[1], + resources = {}, + mojitResources, + controller, + view, + binder, + assets; + + if (!poslResources.mojits[mojitName] || !(poslResources.mojits[mojitName] && poslResources.mojits[mojitName][mojitAction])) { + // TODO: perhaps a warning + return; + } + + mojitResources = poslResources.mojits[mojitName][mojitAction], + controller = mojitResources.controller, + view = mojitResources.view, + binder = mojitResources.binder, + assets = mojitResources.assets; + + // add view + //resources[view.url] = view; + // add controller and dependencies + if (controller) { + resources[controller.url] = controller; + Y.mix(resources, controller.dependencies); + } + // add binder and dependencies + if (binder) { + resources[binder.url] = binder; + Y.mix(resources, binder.dependencies); + } + + // add assets + Y.Object.each(assets, function (assetGroup, type) { + Y.Object.each(assetGroup, function (resource, url) { + resources[url] = resource; + }); + }); + + Y.Object.each(resources, function (resource, url) { + if (rollups[posl][route][resource.subtype]) { + rollups[posl][route][resource.subtype].resources[url] = resource; + } + }); + }); + + // rollup content for each type of rollup + Y.Object.each(rollups[posl][route], function (rollup, type) { + var mergedContent = ""; + Y.Object.each(rollup.resources, function (resource) { + mergedContent += resource.content; + }); + rollup.rollups.push(mergedContent); + }); + }); + }); + debugger; + return rollups; +} diff --git a/mojits/ShakerHTMLFrameMojit/views/index.iphone.mu.html b/mojits/ShakerHTMLFrameMojit/views/index.iphone.mu.html deleted file mode 100644 index 5b7640c..0000000 --- a/mojits/ShakerHTMLFrameMojit/views/index.iphone.mu.html +++ /dev/null @@ -1,33 +0,0 @@ - - - -{{#meta}} - -{{/meta}} -{{^meta}} - -{{/meta}} - -{{{prefetch}}} - -{{#enableDynamicTitle}} - {{dynamicTitle}} -{{/enableDynamicTitle}} -{{^enableDynamicTitle}} - {{title}} -{{/enableDynamicTitle}} - -{{{topShaker}}} -{{{top}}} - - - -{{{child}}} - -{{{inlineShaker}}} -{{{bottomShaker}}} - -{{{bottom}}} - - - diff --git a/mojits/ShakerHTMLFrameMojit/views/index.mu.html b/mojits/ShakerHTMLFrameMojit/views/index.mu.html deleted file mode 100644 index 3d15d59..0000000 --- a/mojits/ShakerHTMLFrameMojit/views/index.mu.html +++ /dev/null @@ -1,33 +0,0 @@ - - - -{{#meta}} - -{{/meta}} -{{^meta}} - -{{/meta}} -{{{prefetch}}} - -{{#enableDynamicTitle}} - {{dynamicTitle}} -{{/enableDynamicTitle}} -{{^enableDynamicTitle}} - {{title}} -{{/enableDynamicTitle}} - -{{{topShaker}}} -{{{top}}} - - - -{{{child}}} - -{{{inlineShaker}}} -{{{bottomShaker}}} - -{{{bottom}}} -{{{postfetch}}} - - -