diff --git a/Alloy/commands/compile/index.js b/Alloy/commands/compile/index.js index a88322f9c..cd2244662 100755 --- a/Alloy/commands/compile/index.js +++ b/Alloy/commands/compile/index.js @@ -3,6 +3,7 @@ var path = require('path'), wrench = require('wrench'), vm = require('vm'), uglifyjs = require('uglify-js'), + jsonlint = require('jsonlint'), sourceMapper = require('./sourceMapper'), styler = require('./styler'), _ = require("../../lib/alloy/underscore")._, @@ -131,13 +132,19 @@ module.exports = function(args, program) { logger.debug('----- BASE RUNTIME FILES -----'); U.installPlugin(path.join(alloyRoot,'..'), paths.project); - // Copy in all assets, libs, and Alloy runtime files + // copy in all lib resources from alloy module U.updateFiles(path.join(alloyRoot, 'lib'), paths.resources); - wrench.mkdirSyncRecursive(path.join(paths.resourcesAlloy, CONST.DIR.COMPONENT), 0777); - wrench.mkdirSyncRecursive(path.join(paths.resourcesAlloy, CONST.DIR.WIDGET), 0777); - U.updateFiles(path.join(paths.app,CONST.DIR.ASSETS), paths.resources); - U.updateFiles(path.join(paths.app,CONST.DIR.LIB), paths.resources); - U.updateFiles(path.join(paths.app,'vendor'), paths.resources); + + // create runtime folder structure for alloy + _.each(['COMPONENT','WIDGET','RUNTIME_STYLE'], function(type) { + var p = path.join(paths.resourcesAlloy, CONST.DIR[type]); + wrench.mkdirSyncRecursive(p, 0777); + }); + + // Copy in all developer assets, libs, and additional resources + _.each(['ASSETS','LIB','VENDOR'], function(type) { + U.updateFiles(path.join(paths.app,CONST.DIR[type]), paths.resources); + }); // copy in test specs if not in production if (alloyConfig.deploytype !== 'production') { @@ -292,6 +299,7 @@ function parseAlloyComponent(view,dir,manifest,noView) { __MAPMARKER_CONTROLLER_CODE__: '', }, widgetDir = dirname ? path.join(CONST.DIR.COMPONENT,dirname) : CONST.DIR.COMPONENT, + widgetStyleDir = dirname ? path.join(CONST.DIR.RUNTIME_STYLE,dirname) : CONST.DIR.RUNTIME_STYLE, state = { parent: {}, styles: [] }, files = {}; @@ -327,9 +335,12 @@ function parseAlloyComponent(view,dir,manifest,noView) { } files[fileType] = baseFile; }); - files.COMPONENT = path.join(compileConfig.dir.resourcesAlloy,CONST.DIR.COMPONENT); - if (dirname) { files.COMPONENT = path.join(files.COMPONENT,dirname); } - files.COMPONENT = path.join(files.COMPONENT,viewName+'.js'); + + _.each(['COMPONENT','RUNTIME_STYLE'], function(fileType) { + files[fileType] = path.join(compileConfig.dir.resourcesAlloy,CONST.DIR[fileType]); + if (dirname) { files[fileType] = path.join(files[fileType],dirname); } + files[fileType] = path.join(files[fileType],viewName+'.js'); + }); // we are processing a view, not just a controller if (!noView) { @@ -517,14 +528,17 @@ function parseAlloyComponent(view,dir,manifest,noView) { // prep the controller paths based on whether it's an app // controller or widget controller var targetFilepath = files.COMPONENT; + var runtimeStylePath = files.RUNTIME_STYLE; if (manifest) { wrench.mkdirSyncRecursive(path.join(compileConfig.dir.resourcesAlloy, CONST.DIR.WIDGET, manifest.id, widgetDir), 0777); + wrench.mkdirSyncRecursive(path.join(compileConfig.dir.resourcesAlloy, CONST.DIR.WIDGET, manifest.id, widgetStyleDir), 0777); CU.copyWidgetResources( [path.join(dir,CONST.DIR.ASSETS), path.join(dir,CONST.DIR.LIB)], compileConfig.dir.resources, manifest.id ); targetFilepath = path.join(compileConfig.dir.resourcesAlloy, CONST.DIR.WIDGET, manifest.id, widgetDir, viewName + '.js'); + runtimeStylePath = path.join(compileConfig.dir.resourcesAlloy, CONST.DIR.WIDGET, manifest.id, widgetStyleDir, viewName + '.js'); } // generate the code and source map for the current controller @@ -541,6 +555,15 @@ function parseAlloyComponent(view,dir,manifest,noView) { } } }, compileConfig); + + // write the compiled style array to a runtime module + var relativeStylePath = path.relative(compileConfig.dir.project,runtimeStylePath); + logger.info(' created: "' + relativeStylePath + '"'); + wrench.mkdirSyncRecursive(path.dirname(runtimeStylePath), 0777); + fs.writeFileSync( + runtimeStylePath, + 'module.exports = ' + JSON.stringify(state.styles) + ); } function findModelMigrations(name, inDir) { @@ -643,6 +666,7 @@ function optimizeCompiledCode() { 'app.js', 'alloy/CFG.js', 'alloy/controllers/', + 'alloy/styles/', 'alloy/backbone.js', 'alloy/underscore.js' ]; diff --git a/Alloy/commands/compile/sourceMapper.js b/Alloy/commands/compile/sourceMapper.js index 357fc304a..686efb5a1 100644 --- a/Alloy/commands/compile/sourceMapper.js +++ b/Alloy/commands/compile/sourceMapper.js @@ -116,18 +116,18 @@ exports.generateCodeAndSourceMap = function(generator, compileConfig) { })); ast.print(stream); - // create the generated code + // write the generated controller code var outfile = target.filepath; var relativeOutfile = path.relative(compileConfig.dir.project,outfile); wrench.mkdirSyncRecursive(path.dirname(outfile), 0777); fs.writeFileSync(outfile, stream.toString()); logger.info(' created: "' + relativeOutfile + '"'); - // create source map for the generated file + // write source map for the generated file var mapDir = path.join(compileConfig.dir.project,CONST.DIR.MAP); outfile = path.join(mapDir,relativeOutfile) + '.' + CONST.FILE_EXT.MAP; relativeOutfile = path.relative(compileConfig.dir.project,outfile); wrench.mkdirSyncRecursive(path.dirname(outfile), 0777); fs.writeFileSync(outfile, sourceMap.toString()); logger.debug(' map: "' + relativeOutfile + '"'); -}; \ No newline at end of file +}; diff --git a/Alloy/commands/compile/styler.js b/Alloy/commands/compile/styler.js index e3fbae834..ff29be637 100644 --- a/Alloy/commands/compile/styler.js +++ b/Alloy/commands/compile/styler.js @@ -141,6 +141,11 @@ exports.sortStyles = function(style, opts) { U.die('Invalid style specifier "' + key + '"'); } var newKey = match[2]; + + // skip any invalid style entries + if (newKey === 'undefined' && !match[1]) { continue; } + + // get the style key type switch(match[1]) { case '#': obj.isId = true; diff --git a/Alloy/common/constants.js b/Alloy/common/constants.js index 4ea6a92d5..9272c9bf2 100755 --- a/Alloy/common/constants.js +++ b/Alloy/common/constants.js @@ -75,6 +75,7 @@ exports.FILE_EXT = { exports.DIR = { VIEW: 'views', STYLE: 'styles', + RUNTIME_STYLE: 'styles', CONTROLLER: 'controllers', MODEL: 'models', MODELCODE: 'models', @@ -84,7 +85,8 @@ exports.DIR = { WIDGET: 'widgets', LIB: 'lib', COMPONENT: 'controllers', - MAP: 'build/map' + MAP: 'build/map', + VENDOR: 'vendor' }; // constants identifying JS reserved words diff --git a/test/apps/testing/ALOY-612/controllers/index.js b/test/apps/testing/ALOY-612/controllers/index.js new file mode 100644 index 000000000..8aaaac257 --- /dev/null +++ b/test/apps/testing/ALOY-612/controllers/index.js @@ -0,0 +1,16 @@ +$.index.open(); + +var style = require('alloy/styles/index'); +var i, len; +for (i = 0, len = $.index.children.length; i < len; i++) { + var child = $.index.children[i]; + child.addEventListener('click', function(e) { + var id = e.source.id; + _.each(style, function(o) { + if (o.key === id && o.isId) { + // print each style that applies by ID to the source + Ti.API.info(JSON.stringify(o)); + } + }); + }); +} \ No newline at end of file diff --git a/test/apps/testing/ALOY-612/styles/app.tss b/test/apps/testing/ALOY-612/styles/app.tss new file mode 100644 index 000000000..c10f4ab81 --- /dev/null +++ b/test/apps/testing/ALOY-612/styles/app.tss @@ -0,0 +1,14 @@ +"Window": { + backgroundColor: '#fff', + fullscreen: false +} + +"Label": { + color: '#000', + height: Ti.UI.SIZE, + width: Ti.UI.SIZE +} + +"Button": { + color: '#500' +} \ No newline at end of file diff --git a/test/apps/testing/ALOY-612/styles/index.tss b/test/apps/testing/ALOY-612/styles/index.tss new file mode 100644 index 000000000..205c08b04 --- /dev/null +++ b/test/apps/testing/ALOY-612/styles/index.tss @@ -0,0 +1,41 @@ +"Button": { + top: '15dp' +} + +"#index": { + layout: 'vertical' +} + +"#index[platform=android]": { + exitOnClose: true +} + +"#info": { + top: '15dp', + textAlign: 'center', + width: '250dp', + font: { + fontSize: '18dp', + fontWeight: 'normal' + } +} + +"#button1": { + height: '70dp', + width: '250dp', + borderRadius: 8, + borderWidth: 2, + borderColor: '#500' +} + +"#button2": { + width: '100dp' +} + +"#button3": { + height: '40dp', + width: '200dp', + borderRadius: 32, + borderWidth: 1, + borderColor: '#0f0' +} \ No newline at end of file diff --git a/test/apps/testing/ALOY-612/views/index.xml b/test/apps/testing/ALOY-612/views/index.xml new file mode 100644 index 000000000..0f23885fc --- /dev/null +++ b/test/apps/testing/ALOY-612/views/index.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/test/lib/testUtils.js b/test/lib/testUtils.js index 579bea231..306b6e451 100644 --- a/test/lib/testUtils.js +++ b/test/lib/testUtils.js @@ -150,6 +150,11 @@ exports.addMatchers = function() { toBeJavascript: toBeJavascript, toBeJavascriptFile: toBeJavascriptFile, toBeTssFile: toBeTssFile, + toHaveNoUndefinedStyles: function() { + return !_.find(this.actual, function(o) { + return o.key === 'undefined' && o.isApi + }); + }, toHaveSameContentAs: function(expected) { return fs.readFileSync(this.actual,'utf8') === fs.readFileSync(expected,'utf8'); }, diff --git a/test/specs/compile.js b/test/specs/compile.js index 2ff827eb2..444b05356 100644 --- a/test/specs/compile.js +++ b/test/specs/compile.js @@ -27,6 +27,8 @@ var alloyRoot = path.join(__dirname,'..','..'), // The alloy command test suite describe('alloy compile', function() { + TU.addMatchers(); + // Iterate through each test app and make sure it compiles for all platforms _.each(wrench.readdirSyncRecursive(paths.apps), function(file) { if (process.env.app && file !== process.env.app) { return; } @@ -76,6 +78,29 @@ describe('alloy compile', function() { }); }); + it('has no undefined style entries', function() { + var hrDir = path.join(paths.harness,'Resources'); + var cPaths = [ + path.join(hrDir,'alloy','styles'), + path.join(hrDir,platform.titaniumFolder,'alloy','styles') + ]; + + _.each(cPaths, function(cPath) { + if (!fs.existsSync(cPath)) { return; } + var files = wrench.readdirSyncRecursive(cPath); + _.each(files, function(file) { + var fullpath = path.join(cPath,file); + if (!fs.statSync(fullpath).isFile() || + !/\.js$/.test(fullpath)) { + return; + } + + var json = require(fullpath); + expect(json).toHaveNoUndefinedStyles(); + }); + }); + }); + var genFolder = path.join(paths.apps,file,GEN_FOLDER,platform.platform); if (!fs.existsSync(genFolder)) { return; } var hrFolder = path.join(paths.harness,'Resources');