diff --git a/addons/ac/shaker.server.js b/addons/ac/shaker.server.js index 1525a58..397899b 100644 --- a/addons/ac/shaker.server.js +++ b/addons/ac/shaker.server.js @@ -8,25 +8,15 @@ YUI.add('mojito-shaker-addon', function (Y, NAME) { 'use strict'; - - function FakeAssetsAddon() { - this.assets = {js: [], css: [], blob: []}; - } - - FakeAssetsAddon.prototype = { - getAssets: function () { - return this.assets; - }, - addAsset: function (type, location, url) { - this.assets[type].push(url); - } - }; + var self, + pagePositions = ['top', 'shakerTop', 'bottom', 'shakerBottom', 'shakerInlineCss', 'shakerInlineJs']; function ShakerAddon(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); + this.ac = ac; + this.context = ac.context; + this.route = ac.url.find(adapter.req.url, adapter.req.method); + this._hookDone(ac, adapter); + self = this; } ShakerAddon.prototype = { @@ -35,182 +25,216 @@ YUI.add('mojito-shaker-addon', function (Y, NAME) { setStore: function (rs) { this.rs = rs; + this.title = rs.shaker.title; + this.meta = rs.shaker.meta; + this.settings = this.meta.settings; + this.posl = rs.selector.getPOSLFromContext(this.context); + this.poslStr = this.posl.join("-") + this.appResources = this.meta.app[this.poslStr].app.assets; + this.currentLocation = this.meta.currentLocation; + this.rollups = this.currentLocation && this.route ? this.meta.app[this.poslStr].rollups[this.route.name] : null; + this.inline = this.settings.inline ? this.meta.inline : null; }, - 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; - } + run: function (assets) { + var start = new Date().getTime(); + this.isHTMLFrame = true; + this._getTitle(assets); + this._initializeAssets(assets); + this._addAppResources(assets); + this._addRouteRollups(assets); + this._filterAndUpdate(assets); + console.log("time: " + ((new Date().getTime()) - start)); }, - _getFakeYUIBootstrap: function () { - var store = this.getStore(), - shakerRS = store.shaker, - fakeBS = shakerRS.fakeYUIBootstrap; - - if (fakeBS) { - return ''; - } else { - Y.log('[SHAKER] Error getting the Fake YUI BootStrap', 'error'); - } + setTitle: function (title) { + this.ac.assets.addBlob(title, 'shakerTitle'); }, - createOptimizedBootstrapBlob: function (jsList) { - var urls, - tmpl; - - if (jsList && jsList.length) { - urls = jsList.join('","'); - tmpl = ''; - return tmpl; + _getTitle: function (assets) { + // if the title was set, choose the last title that was set + if (assets.shakerTitle && assets.shakerTitle.blob) { + this.ac.instance.config.title = assets.shakerTitle.blob[assets.shakerTitle.blob.length - 1]; } }, - _init: function (ac, adapter) { - // initialize will return the shaker metadata - if (this._initializeShaker()) { - this._augmentAppAssets(ac); - } else { - Y.log('[SHAKER] Metadata not found. Application running without Shaker...', 'error'); - } - }, - - _initializeShaker: function () { - var store = this.getStore(), - shakerRS = store.shaker, - staticContext = store.getStaticContext(), - appConfig = store.getAppConfig(staticContext), - shakerConfig = appConfig && appConfig.shaker, - shakerMeta = shakerRS.meta; - this.shakerConfig = shakerConfig; - this._meta = shakerMeta; - - return shakerMeta; + _initializeAssets: function (assets) { + Y.Array.each(pagePositions, function (pagePosition) { + assets[pagePosition] = assets[pagePosition] || {}; + assets[pagePosition].css = assets[pagePosition].css || []; + assets[pagePosition].js = assets[pagePosition].js || []; + assets[pagePosition].blob = assets[pagePosition].blob || []; + }); }, - _augmentAppAssets: function (ac) { - var instance = ac.command.instance, - action = instance.action || ac.command.action || 'index', - viewObj = instance.views[action] || {}, - actionAssets = viewObj && viewObj.assets; - - ac.assets.addAssets(actionAssets); - delete viewObj.assets; + _addAppResources: function (assets) { + Y.Array.each(pagePositions, function (pagePosition) { + Y.Object.each(self.appResources[pagePosition], function (typeResources, type) { + [].push.apply(assets[pagePosition][type], typeResources || []); + }); + }); }, - checkRouteBundling: function () { - var ac = this._ac, - adapter = this._adapter, - assets = ac.assets.getAssets(), - core = this._meta.core, - command = this._command, - store = adapter.req.app.store, - url = adapter.req.url, - method = adapter.req.method, - //get the triggered route - route = ac.url.find(url, method), - //get context - strContext = store.selector.getPOSLFromContext(ac.context).join('-'), - //check if we have a bundle for that route - shakerApp = this._meta.app[strContext], - shakerBundle = shakerApp.routesBundle[route.name]; - - if (shakerBundle) { - Y.log('Bundling entry point!', 'shaker'); - // If is empty for some reason... - assets.topShaker = assets.topShaker || {js: [], css: [], blob: []}; - assets.bottomShaker = assets.bottomShaker || {js: [], css: [], blob: []}; - - // Attach the assets we collect during dispatching... - assets.topShaker.css = shakerBundle.css; - assets.bottomShaker.js = core.concat(shakerBundle.js); - return true; + _addRouteRollups: function (assets) { + // add route rollups + // do not add rollups is current location is default + if (!self.rollups) { + return; } + Y.Array.each(pagePositions, function (pagePosition) { + Y.Object.each(self.rollups.assets[pagePosition], function (typeResources, type) { + [].push.apply(assets[pagePosition][type], typeResources || []); + }); + }); }, - clientDeployment: function (meta) { - var ac = this._ac, - assets = ac.assets, - store = this.getStore(), - shakerConfig = this.shakerConfig || {}, - //collect assets - mAssets = assets.getAssets(), - collectedJSAssets = (mAssets.bottomShaker && mAssets.bottomShaker.js) || [], - // if we have the optimizeBootstrap enabled - // create a fake asset addon to collect the Mojito original generated deployment assets - assetsAddon = shakerConfig.optimizeBootstrap ? new FakeAssetsAddon() : assets, - binders = meta.binders, - inlineDynamicaLoader, - deployedFake; - - // If we are deploying to the client get all the assets required - if (ac.config.get('deploy') === true) { - ac.deploy.constructMojitoClientRuntime(assetsAddon, binders); - } - - if (shakerConfig.optimizeBootstrap) { - deployedFake = assetsAddon.getAssets(); - collectedJSAssets = collectedJSAssets.concat(deployedFake.js); - - inlineDynamicaLoader = this.createOptimizedBootstrapBlob(collectedJSAssets); - delete mAssets.bottomShaker; - - assets.addAsset('blob', 'bottomShaker', this._getFakeYUIBootstrap()); - assets.addAsset('blob', 'bottomShaker', inlineDynamicaLoader); - assets.addAsset('blob', 'bottomShaker', deployedFake.blob); - - } + _filterAndUpdate: function (assets) { + Y.Object.each(assets, function (positionResources, position) { + Y.Object.each(positionResources, function (typeResources, type) { + var i = 0, + newLocation, + isRollup = false, + inlineElement = "", + comboLocalTypeResources = [], + comboLocationTypeResources = [], + comboLoad; + + if (type === "blob") { + type = position === "shakerInlineCss" ? "css" : position === "shakerInlineJs" ? "js" : type; + } + + comboLoad = (type === "js" && self.settings.serveJs.combo) + || (type === "css" && self.settings.serveCss.combo); + + while (i < typeResources.length) { + // remove resource if found in rollup + if (self.rollups && self.rollups[type] && self.rollups[type].resources[typeResources[i]]) { + typeResources.splice(i, 1); + } else if (self.inline && self.inline[typeResources[i]] !== undefined) { + // resource is to be inlined + inlineElement += self.inline[typeResources[i]].trim(); + typeResources.splice(i, 1); + } else { + // replace asset with new location if available + newLocation = self.currentLocation && self.currentLocation && self.currentLocation.resources[typeResources[i]]; + isRollup = self.rollups && self.rollups[type] && self.rollups[type].rollups.indexOf(typeResources[i]) !== -1; + + // don't combo load rollups + if (comboLoad && !isRollup) { + // get local resource to comboload + if (self.settings.serveLocation === "local" || !newLocation) { + comboLocalTypeResources.push(newLocation || typeResources[i]); + } else { + // get location resources to comboload + comboLocationTypeResources.push(newLocation); + } + // remove resource since it will appear comboloaded + typeResources.splice(i, 1); + } else { + typeResources[i] = newLocation || typeResources[i]; + i++; + } + } + } + + // create inline asset + if (position === "shakerInlineCss" && inlineElement) { + typeResources.push(""); + return; + } else if (position === "shakerInlineJs" && inlineElement) { + typeResources.push(""); + return; + } + + // add comboload resources + if (comboLoad && comboLocalTypeResources.length !== 0) { + typeResources.push(self._comboload(comboLocalTypeResources, true)); + } + if (comboLoad && comboLocationTypeResources.length !== 0) { + typeResources.push(self._comboload(comboLocationTypeResources, false)); + } + }); + }); }, - run: function (meta) { - var routeFound; - if (this._meta.app) { - routeFound = this.checkRouteBundling(); + _comboload: function (resourcesArray, isLocal) { + var comboSep = "~", + comboBase = "/combo~", + locationComboConfig = self.currentLocation && self.currentLocation.yuiConfig && self.currentLocation.yuiConfig.groups && + self.currentLocation.yuiConfig.app; + if (!isLocal) { + comboBase = locationComboConfig && locationComboConfig.comboBase || comboBase; + comboSep = locationComboConfig && locationComboConfig.comboSep || comboSep; } - this.clientDeployment(meta); + // if just one resource return it, otherwise return combo url + return resourcesArray.length === 1 ? resourcesArray[0] : comboBase + resourcesArray.join(comboSep); }, - renderListAsHtmlAssets: function (list, type) { - var i, - data = '', - url; - - if ('js' === type) { - for (i = 0; i < list.length; i += 1) { - // TODO: Fuly escape any HTML chars in the URL to avoid trivial - // attribute injection attacks. See owasp-esapi reference impl. - url = encodeURI(list[i]); - data += '\n'; - } - } else if ('css' === type) { - for (i = 0; i < list.length; i += 1) { - // TODO: Escape any HTML chars in the URL to avoid trivial - // attribute injection attacks. See owasp-esapi reference impl. - url = encodeURI(list[i]); - data += '\n'; - } - } else if ('blob' === type) { - for (i = 0; i < list.length; i += 1) { - // NOTE: Giant security hole...but used by everyone who uses - // Mojito so there's not much we can do except tell authors of - // Mojito applications to _never_ use user input to generate - // blob content or populate config data. Whatever goes in here - // can't be easily encoded without the likelihood of corruption. - data += list[i] + '\n'; - } - } else { - Y.log('Unknown asset type "' + type + '". Skipped.', 'warn', NAME); + _hookDone: function (ac, adapter) { + console.log("hook done"); + var self = this, + originalDone = 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._shakerDone.apply(self, [this, originalDone].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. + */ + _shakerDone: function (selfContext, done, data, meta) { + var self = this, + args; + + if (!self.isHTMLFrame && self.inline) { + Y.Array.each(["shakerInlineCss", "shakerInlineJs"], function (position) { + var positionResources = meta.assets[position], + inlineElement = "", + type = position === "shakerInlineCss" ? "css" : "js"; + + Y.Array.each(positionResources && positionResources.blob, function (resource) { + // do not add inline asset if already in rollup + if (self.rollups && self.rollups[type] && self.rollups[type].resources[resource]) { + return; + } + if (self.inline[resource]) { + inlineElement += self.inline[resource]; + } + }); + + if (type === "css" && inlineElement) { + inlineElement = ""; + //console.log(inlineElement); + if (typeof data === 'string') { + data = inlineElement + data; + } else if (data instanceof Array) { + data.splice(0, 0, inlineElement); + } + } else if (type === "js" && inlineElement) { + inlineElement = ""; + //console.log(inlineElement); + if (typeof data === 'string') { + data += inlineElement; + } else if (data instanceof Array) { + data.push(inlineElement); + } + } + // empty inline resources + // do not delete this position (causes resources to appear twice for some reason) + meta.assets[position].blob = []; + }); } - return data; - } + // Restore the original arguments and call the real ac.done with the modified data. + args = [].slice.apply(arguments).slice(2); + args[0] = data; + done.apply(selfContext, args); + } }; Y.mojito.addons.ac.shaker = ShakerAddon; @@ -223,4 +247,4 @@ YUI.add('mojito-shaker-addon', function (Y, NAME) { 'mojito-deploy-addon', 'mojito-url-addon' ] -}); +}); \ No newline at end of file diff --git a/addons/rs/shaker.server.js b/addons/rs/shaker.server.js index 85b1e89..acf7329 100644 --- a/addons/rs/shaker.server.js +++ b/addons/rs/shaker.server.js @@ -1,22 +1,8 @@ -/* - * Copyright (c) 2012-2013, Yahoo! Inc. All rights reserved. - * Copyrights licensed under the New BSD License. - * See the accompanying LICENSE file for terms. - */ - -/*jslint anon:true, sloppy:true, nomen:true*/ -/*global YUI*/ - YUI.add('addon-rs-shaker', function (Y, NAME) { var libpath = require('path'), libfs = require('fs'), - //bootstrap - BOOTSTRAP_DIR = '../../lib/bootstrap/', - BOOTSTRAP_YUI_OVERRIDE = 'yui-bootstrap-override', - BOOTSTRAP_YUI_INLINE = 'yui-bootstrap-inline-min', - //inline (defined in core as well) - INLINE_SELECTOR = 'shaker-inline'; + self; function RSAddonShaker() { RSAddonShaker.superclass.constructor.apply(this, arguments); @@ -28,181 +14,225 @@ YUI.add('addon-rs-shaker', function (Y, NAME) { Y.extend(RSAddonShaker, Y.Plugin.Base, { initializer: function (config) { + self = this; this.rs = config.host; this._poslCache = {}; // context: POSL this.appRoot = config.appRoot; this.mojitoRoot = config.mojitoRoot; this.appConfig = config.host.getStaticAppConfig() || {}; - this.shakerConfig = this.appConfig.shaker || {}; + //this.shakerConfig = this.appConfig.shaker || {}; var yuiRS = this.rs.yui, store = this.rs, shakerConfig = this.shakerConfig; - if (!this.initilized) { - //first read the shaker metadata - this.meta = this.rs.config.readConfigSimple(libpath.join(this.appRoot, 'shaker-meta.json')); + // Augments the view with assets + if (!process.shakerCompiler) { + if (!this.initilized) { + //first read the shaker metadata + this.meta = this.rs.config.readConfigSimple(libpath.join(this.appRoot, 'shaker-meta.json')); + + if (this.meta && !Y.Object.isEmpty(this.meta)) { + Y.log('Metadata loaded correctly.', 'info', 'Shaker'); + Y.log('Preloading store', 'info', 'mojito-store'); + } else { + Y.log('Metadata not found.', 'warn', 'Shaker'); + } + } - if (this.meta && !Y.Object.isEmpty(this.meta)) { - Y.log('Metadata loaded correctly.', 'info', 'Shaker'); - Y.log('Preloading store', 'info', 'mojito-store'); - } else { - Y.log('Metadata not found.', 'warn', 'Shaker'); + // get settings + // TODO: validate settings + this.meta.settings = this.appConfig.shaker && this.appConfig.shaker.settings; + // get location + this.meta.currentLocation = this.meta.locations[this.meta.settings.serveLocation]; + + // if the current location is set to something other than default + // hook into getAppConfig in order to set custom yui configuration + if (this.meta.currentLocation) { + this.rs._appConfigCache = {}; + this.beforeHostMethod('getAppConfig', this.getAppConfig, this); } - } - /* - * AOP HOOKS: - * We need to hook some events on the store, - * but we will have to do different hooks depending if we are on build time or in runtime - * The reason is that there are some hook that are not needeed on runtime or viceversa - */ + // populate meta data with app and rollup resources for each posl + var routes = Y.Object.keys(self.meta.app["*"].rollups);debugger; + Y.Object.each(self.meta.app, function (poslResources, poslStr) { + var posl = poslStr.split("-"); + poslResources.app = poslResources.app || {}; + poslResources.app.assets = self._positionResources(self.getAppResources(self.meta, posl)); + Y.Array.each(routes, function (route) { + poslResources.rollups = poslResources.rollups || {}; + poslResources.rollups[route] = self.getRollupResources(self.meta, posl, route); + var typeResources = {}; + typeResources.js = poslResources.rollups[route].js && poslResources.rollups[route].js.rollups || []; + typeResources.css = poslResources.rollups[route].css && poslResources.rollups[route].css.rollups || []; + poslResources.rollups[route].assets = self._positionResources(typeResources); + }); + }); - if (shakerConfig.optimizeBootstrap) { - this.beforeHostMethod('makeResourceVersions', this.makeResourceVersions, this); - } - //on build time we need this to reconfigure the url of where the assets come from... - if (shakerConfig.comboCDN) { - this.beforeHostMethod('resolveResourceVersions', this.resolveResourceVersions, this); + this.onHostEvent('resolveMojitDetails', this.resolveMojitDetails, this); } + this.beforeHostMethod('resolveResourceVersions', this.resolveResourceVersions, this); - this.beforeHostMethod('parseResourceVersion', this.parseResourceVersion, this); - // This hooks are for runtime - if (!process.shakerCompile) { + }, - //alter the url for the seeds or augment it if necesary... - if (shakerConfig.comboCDN || shakerConfig.optimizeBootstrap) { - Y.Do.after(this.alterAppSeedFiles, yuiRS, 'getAppSeedFiles', this); - } - //alter bootstrap config - //Y.Do.after(function (){console.log(Y.Do.currentRetVal);}, yuiRS, 'getAppGroupConfig', this); + _positionResources: function (resources) { + var positionedResources = { + top: {}, + shakerTop: {}, + bottom: {} + }, + shakerResources = {}, + shakerInline = {}, + resource; + + // separate inline resources + // dont separate inline resources if no inline resources or no current location + // allow inline if default location with inline specified + if (!Y.Object.isEmpty(self.meta.inline) && self.meta.settings.inline) { + Y.Object.each(resources, function (typeResources, type) { + shakerResources[type] = []; + + if (!self.meta.settings.serveCss && type === "css" || + !self.meta.settings.serveJs && type === "js") { + return; + } - // Augments the view with assets - this.onHostEvent('resolveMojitDetails', this.resolveMojitDetails, this); + shakerInline[type] = { + blob: [] + }; + Y.Array.each(typeResources, function (resource) { + if (self.meta.inline[resource] !== undefined) { + shakerInline[type].blob.push(resource); + } else { + shakerResources[type].push(resource); + } + }); + }); + positionedResources.shakerInlineCss = shakerInline.css; + positionedResources.shakerInlineJs = shakerInline.js; + } else { + shakerResources = resources; } - }, - destructor: function () { - // TODO: needed to break cycle so we don't leak memory? - this.rs = null; - }, + // add css assets to proper position + if (self.meta.settings.serveCss) { + positionedResources[self.meta.settings.serveCss.position].css = shakerResources.css || []; + } - /* - * We need to add the synthetic bootstrap items - */ - makeResourceVersions: function () { - var store = this.rs, - yuiRS = store.yui; - this.addOptimizedBootstrap(store, yuiRS); + // add js assets to proper position + if (self.meta.settings.serveJs) { + positionedResources[self.meta.settings.serveJs.position].js = shakerResources.js || []; + } + return positionedResources; }, - /* - * Add the synthetic resources for the optimized bootstrap - * On runtime we can access the new synthethic files - */ - addOptimizedBootstrap: function (store, yuiRS) { - var relativePath = libpath.join(__dirname, BOOTSTRAP_DIR), - bootstrapResources = [ - BOOTSTRAP_YUI_OVERRIDE, - BOOTSTRAP_YUI_INLINE - ]; - - Y.Array.each(bootstrapResources, function (item) { - var content = libfs.readFileSync(relativePath + item + '.js', 'utf8'), - res = { - source: {}, - mojit: 'shared', - type: 'yui-module', - subtype: 'synthetic', - name: item, - affinity: 'client', - selector: '*', - yui: { - name: item - } - }; + getAppConfig: function (ctx) { + var appConfig, + key, + ycb; - // this is how mojito creates synthetic resources when the server start, so wejust replicate it. - res.id = [res.type, res.subtype, res.name].join('-'); - res.source.pkg = store.getAppPkgMeta(); - res.source.fs = store.makeResourceFSMeta(__dirname, 'app', '../../lib/bootstrap/', item + '.js', true); + ctx = this.rs.blendStaticContext(ctx); + key = JSON.stringify(ctx || {}); + + if (this.rs._appConfigCache[key]) { + return JSON.parse(this.rs._appConfigCache[key]); + } - yuiRS.resContents[item] = content; - store.addResourceVersion(res); + ycb = this.rs._appConfigYCB.read(ctx); - // We save in the shaker addon the content of the hook - // because on runtime we want to pick it syncronously - if (item === BOOTSTRAP_YUI_INLINE) { - this.fakeYUIBootstrap = content; + appConfig = Y.mojito.util.blend(this.rs._fwConfig.appConfigBase, this.rs._config.appConfig); + appConfig = Y.mojito.util.blend(appConfig, ycb); + + Y.mix(appConfig.yui.config, self.meta.currentLocation.yuiConfig, true, null, 0, true);/*{ + "groups": { + "app": { + "base": "/", + "comboBase": "/combo~", + "comboSep": "~", + "combine": true, + "root": "/static/app1/assets/compiled/" + } } + }, true, null, 0, true);*/ + + this.rs._appConfigCache[key] = JSON.stringify(appConfig); - }, this); + return appConfig; }, - _resolveSeedResourceURL: function (moduleList) { - var store = this.rs, - yuiRS = store.yui, - seed = moduleList, - files, - i; - - for (i = 0; i < seed.length; i += 1) { - if (yuiRS.yuiModulesDetails.hasOwnProperty(seed[i])) { - seed[i] = yuiRS.yuiModulesDetails[seed[i]].url; - } else if (yuiRS.appModulesDetails.hasOwnProperty(seed[i])) { - seed[i] = yuiRS.appModulesDetails[seed[i]].url; - } else { - Y.log('Couldnt find module for seed. Optmized bootstrap may fail', 'warn', 'Shaker'); - } - } + getMojitResources: function (meta, posl, mojit, action) { + return this._getResources(posl, function (poslStr) { + return meta.app[poslStr].mojits && meta.app[poslStr].mojits[mojit] && meta.app[poslStr].mojits[mojit][action]; + }); + }, - return seed; + getAppResources: function (meta, posl) { + return this._getResources(posl, function (poslStr) { + return meta.app[poslStr].app; + }); }, - /* - * When comboLoad is active we need to change the seed to point to the CDN... - * We rely on the mapping we have on the Shaker metadata. - * Also if optimizeBootstrap is set to true we need to augment the seed files to create our own seed. - * - * NOTE: - * I have to implement _resolveSeedResourceURL, because the original method @getAppSeedFiles - * pulls the seed directly from configuration or it creates it's own, - * so we cannot hook directly those files. - */ - alterAppSeedFiles: function () { - var i, - newUrl, - resources, - shakerConfig = this.shakerConfig, - cdnUrls = this.meta && this.meta.cdnModules, - currentSeed = Y.Do.currentRetVal; - - if (shakerConfig.optimizeBootstrap) { - - resources = this._resolveSeedResourceURL([BOOTSTRAP_YUI_OVERRIDE]); - //the first element has to be the yui-override - currentSeed.unshift(resources[0]); - } + getRollupResources: function (meta, posl, route) { + return this._getResources(posl, function (poslStr) { + return meta.app[poslStr].rollups && meta.app[poslStr].rollups[route]; + }); + }, - // We need to change the url to point to the generated in CDN... - if (shakerConfig.comboCDN && cdnUrls) { - for (i in currentSeed) { - if (currentSeed.hasOwnProperty(i)) { - newUrl = cdnUrls[currentSeed[i]]; - if (newUrl) { - currentSeed[i] = newUrl; + _getResources: function (posl, getMetaResources) { + // TODO remove resources type based on config/shaker runtime settings + var poslStr = posl.join("-"), + resources = { + css: null, + js: null + }, + continueSearching = true, + metaResources; + + // if posl does not exist in meta then use default + // posl should always exist in meta to ensure a more general posl is used instead of just jumping to '*' + poslStr = this.meta.app[poslStr] ? poslStr : "*"; + + while (continueSearching) { + // get app or mojit resources for the posl inside the metadata + metaResources = getMetaResources(poslStr); + if (metaResources) { + // assume metaResources contains all the type resources required + continueSearching = false; + Y.Object.each(resources, function (typeResources, type) { + // this resource has not been found + if (!typeResources) { + resources[type] = metaResources[type]; } - } + // type resource not found so must continue searching for it + if (!resources[type]) { + continueSearching = true; + return; + } + }); + } + if (posl.length === 1) { + break; } + // remove the minor selector from the posl and search within more general posl + posl.splice(posl.length-2, 1); + poslStr = posl.join('-'); } + // remove type resources if empty + Y.Object.each(resources, function (typeResources, type) { + if (!typeResources) { + //delete resources[type]; + resources[type] = []; + } + }); + return resources; }, /* * Change the URL's of the Store so we get the comboLoad from CDN. */ - resolveResourceVersions: function (cdnUrls) { + resolveResourceVersions: function (urlMap) { var r, res, ress, @@ -212,10 +242,9 @@ YUI.add('addon-rs-shaker', function (Y, NAME) { meta, urls = {}; - //get the CDN URL mapping - cdnUrls = cdnUrls || this.meta.cdnModules; + urlMap = urlMap || this.meta && this.meta.currentLocation && this.meta.currentLocation.resources; - if (!cdnUrls) { + if(!urlMap) { return; } @@ -225,93 +254,43 @@ YUI.add('addon-rs-shaker', function (Y, NAME) { for (m = 0; m < mojits.length; m += 1) { mojit = mojits[m]; + ress = this.rs.getResourceVersions({mojit: mojit}); for (r = 0; r < ress.length; r += 1) { res = ress[r]; //Change the url - if (res.yui && cdnUrls[res.url]) { - res.url = cdnUrls[res.url]; + if (res.yui && urlMap[res.url]) { + res.url = urlMap[res.url]; } } } }, - /* - * Converting inlines files to be readable by the store. - * parseResourceVersion: - * AOP hook! - */ - parseResourceVersion: function (source, type, subtype) { - var basename, - tmpBasename, - inline; - - if (type === 'asset') { - basename = source.fs.basename.split('.'); - inline = basename.pop(); - - if (inline === INLINE_SELECTOR) { - // Add the inline property to source, since we don't have access to the resource itself yet. - source.inline = true; - // put back the basename without the INLINE_SELECTOR so mojito doesnt skip the file. - basename[0] = basename[0] + '-' + INLINE_SELECTOR; - source.fs.basename = basename.join('.'); - } - } - }, - /* * Augment the view spec with the Shaker computed assets. * Will be merged on the action-context module (either on the client or in the server). */ resolveMojitDetails: function (e) { - var env = e.args.env, + var mojit = e.args.type, + views = e.mojitDetails.views, posl = e.args.posl, - mojitName = e.args.type, - ress = e.args.ress, - details = e.mojitDetails, - strContext = posl.join('-'), - isFrame = mojitName.indexOf('ShakerHTMLFrameMojit') !== -1, - shakerMeta = this.meta, - shakerBase, - frameActionMeta, - actionMeta, - css, - resource, - i; - - if (Y.Object.isEmpty(this.meta)) { + self = this; + + // skip mojits not in metadata + if (!this.meta.app["*"].mojits[mojit]) { return; } - // If the mojit is the ShakerHTMLFrame, we are going to put the common assets there. - if (isFrame) { - shakerBase = shakerMeta.app[strContext]; - shakerBase = shakerBase && shakerBase.app; - frameActionMeta = { - css: shakerBase - }; + Y.Object.each(views, function (view, action) { + var resources = self.getMojitResources(self.meta, posl, mojit, action); - } else { - // Check if on the nested meta we have all the info we need... - shakerBase = shakerMeta.app[strContext]; - shakerBase = shakerBase && shakerBase.mojits[mojitName]; - } - - Y.Object.each(details.views, function(view, name) { - actionMeta = (isFrame ? frameActionMeta : shakerBase && shakerBase[name]) || {css: [], blob: []}; - view.assets = { - topShaker: { - css: actionMeta.css - }, - inlineShaker: { - blob: actionMeta.blob - } - }; + view.assets = self._positionResources(resources); }); + } }); + RSAddonShaker.appResources = {}; Y.namespace('mojito.addons.rs').shaker = RSAddonShaker; }, '0.0.1', { diff --git a/bin/mojito-shake b/bin/mojito-shake index beb26ce..565912c 100755 --- a/bin/mojito-shake +++ b/bin/mojito-shake @@ -1,15 +1,4 @@ #!/usr/bin/env node -/* - * Copyright (c) 2011-2012, Yahoo! Inc. All rights reserved. - * Copyrights licensed under the New BSD License. - * See the accompanying LICENSE file for terms. - */ - -/*jslint anon:true, sloppy:true*/ - -var libmojito = require('mojito'), - utils = require('mojito/lib/management/utils'), - libpath = require('path'); /* * A command is expected to export the following: * run - The function that executes the command. The signature is: @@ -24,7 +13,6 @@ var libmojito = require('mojito'), * hasValue - True is this option requires a value. Optional; default false. */ - /* * Creates a map keyed by both short and long option names, to simplify lookup * of option info from command line args. @@ -88,20 +76,14 @@ function parseArgs(args, optionInfo) { return { params: params, options: options, errors: errors }; } -// ---------- Start of mainline code ---------- + function main() { - var args = process.argv.slice(2), - command, + var utils, + command = require('../commands/shake'), + args = process.argv.slice(2), argInfo; - try { - command = require('../commands/shake'); - } catch (e) { - utils.error('Invalid command!'); - return; - } - if (args.length === 0) { argInfo = { command: 'help', params: [] }; } else { @@ -109,23 +91,18 @@ function main() { } if (argInfo.errors && argInfo.errors.length > 0) { + utils = require('mojito/lib/management/utils') argInfo.errors.forEach(function(e) { utils.log(e); }); - utils.error('Invalid command line.', "Try 'mojito help '."); + //utils.error('Invalid command line.', "Try 'mojito help '."); return; } - command.run(argInfo.params, argInfo.options, function(err) { - if (err) { - utils.error(err); - } else { - utils.success('mojito done.'); - } - }); + command.run(argInfo.params, argInfo.options); } // Execute the main() function. Note that this occurs as part of the -// require/import process so simply importing/require()ing the cli.js will cause +// require/import process so simply importing/requiring the cli.js will cause // the main() operation to be invoked. main(); diff --git a/commands/shake.js b/commands/shake.js index f3606de..1108ae6 100755 --- a/commands/shake.js +++ b/commands/shake.js @@ -4,8 +4,10 @@ * See the accompanying LICENSE file for terms. */ -start = require('mojito/lib/app/commands/start'); -Shaker = require('mojito-shaker/lib/shaker').Shaker; +var utils = require('mojito/lib/management/utils'), + mojitoStart = require('mojito/lib/app/commands/start'), + ShakerCompiler = require('../lib/compiler').ShakerCompiler; + /** * Convert a CSV string into a context object. * @param {string} s A string of the form: 'key1:value1,key2:value2'. @@ -35,10 +37,9 @@ function contextCsvToObject(s) { * Standard usage string export. */ exports.usage = '\nShaker Options:\n' + - '\t--context A comma-separated list of key:value pairs that define the' + - ' base\n' + - '\t context used to read configuration files\n' + - '\t--run Run Mojito Server after running Shaker\n'; + ' --context A comma-separated list of key:value pairs that define the base\n' + + ' context used to read configuration files (e.g. "environment:dev")\n' + + ' --run Run the Mojito server after running the Shaker compiler\n'; /** * Standard options list export. @@ -73,7 +74,7 @@ exports.options = [ * @param {object} opts Options/flags for the command. * @param {function} callback An optional callback to invoke on completion. */ -exports.run = function(params, options, callback) { +exports.run = function(params, options) { options = options || {}; var context = {}, debug = options.debug || 0; @@ -86,15 +87,26 @@ exports.run = function(params, options, callback) { console.log(this.usage); return; } - - var shaker = new Shaker({context: context, debugLevel: debug}); - shaker.run(function (err, data) { - if (options.run) { - delete options.run; - start.run(params, options, callback); + + // shaker compiler + process.shakerCompiler = true; + + var compiler = new ShakerCompiler(context); + compiler.compile(function (err) { + if (err) { + utils.error(err); } else { - callback(err, data); + utils.success('Shaker done.'); + if (options.run) { + delete options.run; + mojitoStart.run(params, options, function (err) { + if (err) { + utils.error(err); + } else { + utils.success('Mojito done.'); + } + }); + } } }); - //if we do async stuff move callback }; diff --git a/index.js b/index.js index ace0a92..3d81f41 100644 --- a/index.js +++ b/index.js @@ -1 +1 @@ -module.exports = require('./lib/shaker'); \ No newline at end of file +module.exports = require('./lib/compiler'); diff --git a/lib/compiler.js b/lib/compiler.js index 3144413..22e391d 100644 --- a/lib/compiler.js +++ b/lib/compiler.js @@ -9,8 +9,7 @@ var validateConfig = require('./config.js').validateConfig, mojitrollup: require('./rollups/mojitrollup.js').mojitrollup }, locations: { - local: require('./locations/local.js').local, - mobstor: require('shaker-mobstor').mobstor + local: require('./locations/local.js').location } }, typeHierarchy = { @@ -23,13 +22,23 @@ var validateConfig = require('./config.js').validateConfig, }; function ShakerCompiler(context) { -debugger; this.compile = function (done) { var resources = new ShakerResources(context), config = resources.shakerConfig; config.appConfig = resources.appConfig; + // initialize locations + Y.Object.each(config.locations, function (locationConfig, location) { + if (!modules.locations[location]) { + try { + modules.locations[location] = require('mojito-shaker-' + location).location; + } catch (e) { + console.log(e); + } + } + }); + applyTasks(config, resources.appResources, function (err) { applyRollups(config, resources.appResources, resources.organizedResources, function (err) { applyLocations(config.locations, config.appConfig, resources.appResources, function (err) { @@ -480,7 +489,7 @@ debugger; // 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)); +require('fs').writeFile("shaker-meta.json", JSON.stringify(organizedResources)); } @@ -507,7 +516,7 @@ require('fs').writeFile("shakerResources.json", JSON.stringify(organizedResource locations[locationName].resources[resource.url] = resource.locations[locationName]; //} }); - +debugger; resources.resolveResourceVersions(locations[locationName].resources); loaderResources = resources.getLoaderResources(); diff --git a/lib/locations/local.js b/lib/locations/local.js index 9a856e2..73d61bf 100644 --- a/lib/locations/local.js +++ b/lib/locations/local.js @@ -3,7 +3,7 @@ var fs = require('fs'); var basedir = 'assets/compiled'; -exports.local = function (config) { +exports.location = function (config) { var root = "/" + config.appConfig.prefix + "/" + config.appConfig.appName + "/" + basedir + "/"; this.yuiConfig = { "groups": { diff --git a/lib/resources.js b/lib/resources.js index 13bc2cc..79f7fe9 100644 --- a/lib/resources.js +++ b/lib/resources.js @@ -40,8 +40,8 @@ function ShakerResources(context) { // TODO remove posl if no resoruces - require('fs').writeFile("appResources.json", JSON.stringify(self.appResources)); - require('fs').writeFile("organizedResources.json", JSON.stringify(self.organizedResources)); + //require('fs').writeFile("appResources.json", JSON.stringify(self.appResources)); + //require('fs').writeFile("organizedResources.json", JSON.stringify(self.organizedResources)); @@ -387,8 +387,7 @@ function ShakerResources(context) { function getShakerConfig(context) { var config = store.getAppConfig(context || {}); - // TODO: change to shaker - return config.shaker2 || {}; + return config.shaker || {}; }; function getShakerConfigByContext(mojitName, context) { @@ -416,7 +415,6 @@ function ShakerResources(context) { } function getStoreConfigs() { - debugger; var appConfig = store.getAppConfig(context); return { diff --git a/lib/rollups/mojitrollup.js b/lib/rollups/mojitrollup.js index 452d937..16faf90 100644 --- a/lib/rollups/mojitrollup.js +++ b/lib/rollups/mojitrollup.js @@ -95,6 +95,5 @@ exports.mojitrollup = function (config, resources) { }); }); }); - debugger; return rollups; } diff --git a/mojits/ShakerHTMLFrameMojit/controller.server.js b/mojits/ShakerHTMLFrameMojit/controller.server.js index 2778aee..44378a0 100644 --- a/mojits/ShakerHTMLFrameMojit/controller.server.js +++ b/mojits/ShakerHTMLFrameMojit/controller.server.js @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2012, Yahoo! Inc. All rights reserved. + * Copyright (c) 2011-2013, Yahoo! Inc. All rights reserved. * Copyrights licensed under the New BSD License. * See the accompanying LICENSE file for terms. */ @@ -9,17 +9,74 @@ /*global YUI*/ -YUI.add('ShakerHTMLFrameMojit', function(Y, NAME) { +YUI.add('HTMLFrameMojit', function (Y, NAME) { + 'use strict'; Y.namespace('mojito.controllers')[NAME] = { - index: function(ac) { + index: function (ac) { this.__call(ac); }, - __call: function(ac) { + __call: function (ac) { + this._renderChild(ac, function (data, meta) { + + ac.shaker.run(meta.assets); + + // meta.assets from child should be piped into + // the frame's assets before doing anything else. + ac.assets.addAssets(meta.assets); + + if (ac.config.get('deploy') === true) { + ac.deploy.constructMojitoClientRuntime(ac.assets, + meta.binders); + } + + // we don't care much about the views specified in childs + // and for the parent, we have a fixed one. + + meta.view = meta.view || {}; + + meta.view.name = 'index'; + + // 1. mixing bottom and top fragments from assets into + // the template data, along with title and mojito version. + // 2. mixing meta with child metas, along with some extra + // headers. + + ac.done( + Y.merge(data, ac.assets.renderLocations(), { + + title: ac.shaker.title || ac.config.get('title') || 'Powered by Mojito', + mojito_version: Y.mojito.version + + }), + Y.mojito.util.metaMerge(meta, { + + http: { + headers: { + 'content-type': 'text/html; charset="utf-8"' + } + } + + }, true) + ); + + }); + + }, + + /** + * Renders a child mojit based on a config called "child" and + * the "assets" collection specified in the specs. + * @method _renderChild + * @protected + * @param {Object} ac Action Context Object. + * @param {Function} callback The callback. + */ + _renderChild: function (ac, callback) { // Grab the "child" from the config an add it as the // only item in the "children" map. var child = ac.config.get('child'), @@ -37,55 +94,19 @@ YUI.add('ShakerHTMLFrameMojit', function(Y, NAME) { assets: ac.config.get('assets') }; - Y.log('executing ShakerHTMLFrameMojit child', 'mojito', 'qeperf'); // Now execute the child as a composite - ac.composite.execute(cfg, function(data, meta) { - - // Make sure we have meta - meta.http = meta.http || {}; - meta.http.headers = meta.http.headers || {}; - - // Make sure our Content-type is HTML - meta.http.headers['content-type'] = - 'text/html; charset="utf-8"'; - - // Set the default data - data.title = ac.config.get('title') || - 'Powered by Mojito ' + Y.mojito.version; - data.mojito_version = Y.mojito.version; - - data.enableDynamicTitle = ac.config.get('enableDynamicTitle'); - - // Add all the assets we have been given to our local store - ac.assets.addAssets(meta.assets); - - // SHAKER RUNTIME! - // NOTE: We move the deployment of the client to within Shaker addon... - ac.shaker.run(meta); - - // Attach assets found in the "meta" to the page - Y.Object.each(ac.assets.getAssets(), function(types, location) { - if (!data[location]) { - data[location] = ''; - } - Y.Object.each(types, function(assets, type) { - data[location] += ac.shaker.renderListAsHtmlAssets(assets, type); - }); - }); - - meta.view = {name: 'index'}; - - Y.log('ShakerHTMLFrameMojit done()', 'mojito', 'qeperf'); - - ac.done(data, meta); - }); + ac.composite.execute(cfg, callback); } + }; }, '0.1.0', {requires: [ - 'mojito-composite-addon', + 'mojito', + 'mojito-util', 'mojito-assets-addon', + 'mojito-deploy-addon', 'mojito-config-addon', + 'mojito-composite-addon', 'mojito-shaker-addon' ]}); diff --git a/mojits/ShakerHTMLFrameMojit/definition.json b/mojits/ShakerHTMLFrameMojit/definition.json index 4ec523a..df95660 100644 --- a/mojits/ShakerHTMLFrameMojit/definition.json +++ b/mojits/ShakerHTMLFrameMojit/definition.json @@ -1,8 +1,5 @@ [ { - "settings" : ["master"], - "yui": { - "sharedYUIInstance" : true - } + "settings" : ["master"] } ] diff --git a/mojits/ShakerHTMLFrameMojit/views/index.hb.html b/mojits/ShakerHTMLFrameMojit/views/index.hb.html new file mode 100644 index 0000000..2dc0c9b --- /dev/null +++ b/mojits/ShakerHTMLFrameMojit/views/index.hb.html @@ -0,0 +1,27 @@ + + + + +{{#meta}} + +{{/meta}} +{{^meta}} + +{{/meta}} + +{{title}} + +{{{shakerInlineCss}}} +{{{top}}} +{{{shakerTop}}} + + + + +{{{child}}} + +{{{shakerInlineJs}}} +{{{bottom}}} +{{{shakerBottom}}} + + diff --git a/mojits/ShakerHTMLFrameMojit/views/index.iphone.hb.html b/mojits/ShakerHTMLFrameMojit/views/index.iphone.hb.html new file mode 100644 index 0000000..3156b6b --- /dev/null +++ b/mojits/ShakerHTMLFrameMojit/views/index.iphone.hb.html @@ -0,0 +1,24 @@ + + + + +{{#meta}} + +{{/meta}} +{{^meta}} + +{{/meta}} + +{{title}} + +{{{top}}} + + + + +{{{child}}} + +{{{bottom}}} + + + diff --git a/package.json b/package.json index aa083d8..cf8d123 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,14 @@ { "name": "mojito-shaker", - "version": "3.1.3", + "version": "4.0.0", "description": "Compiles and deploys asset rollups for Mojito applications.", "author": "shaker-users@yahoo-inc.com", "contributors": [ + "Albert Jimenez ", "Diego Ferreiro ", - "Stephen Murphy ", - "Brett Stimmerman ", "Julien Lecomte ", - "Albert Jimenez " + "Stephen Murphy ", + "Brett Stimmerman " ], "dependencies": { "async": "0.1.x",