UNPKG

foam-framework

Version:
512 lines (480 loc) 16.1 kB
/** * @license * Copyright 2015 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ CLASS({ package: 'foam.build', name: 'BuildApp', imports: [ 'log', 'error' ], requires: [ 'foam.dao.NodeFileDAO as FileDAO', 'foam.dao.File', 'foam.core.dao.OrDAO', 'node.dao.ModelFileDAO', 'foam.build.WebApplication' ], properties: [ { name: 'appDefinition' }, { name: 'controller', help: 'Name of the main controller/model to create', }, { model_: 'StringArrayProperty', name: 'coreFiles', adapt: function(_, s) { if ( typeof s === 'string' ) return s.split(','); return s; } }, { name: 'defaultView', help: "Default view of the controller to use. If not set, the controller will be used as view (if it is one), or DetailView will be used" }, { name: 'targetPath', help: "Directory to write output files to. Will be created if it doesn't exist.", required: true }, { model_: 'BooleanProperty', name: 'precompileTemplates', help: 'True to precompile templates of models loaded from the ModelDAO.', defaultValue: false }, { model_: 'BooleanProperty', name: 'includeFoamCSS', defaultValue: false }, { model_: 'StringProperty', name: 'icon' }, { model_: 'StringProperty', name: 'version' }, { model_: 'StringArrayProperty', name: 'resources' }, { model_: 'BooleanProperty', name: 'appcacheManifest', defaultValue: false }, { model_: 'StringArrayProperty', name: 'extraFiles', help: 'Extra files to both load during the build process, and include in the built image.', adapt: function(_, s) { if ( typeof s === 'string' ) return s.split(','); return s; } }, { model_: 'StringArrayProperty', name: 'extraBuildFiles', help: 'Extra files to load during the build process, but NOT include in the built image.', adapt: function(_, s) { if ( typeof s === 'string' ) return s.split(','); return s; } }, { model_: 'StringArrayProperty', name: 'extraModels', help: 'Extra models to include in the image regardless of if they were arequired or not.', adapt: function(_, s) { if ( typeof s === 'string' ) return s.split(','); return s; }, factory: function() { return ['foam.ui.FoamTagView']; } }, { model_: 'StringArrayProperty', name: 'blacklistModels', help: 'Models to unconditionally exclude from the image, even if they are listed as required.', adapt: function(_, s) { if ( typeof s === 'string' ) return s.split(','); return s; } }, { model_: 'BooleanProperty', name: 'outputManifest', defaultValue: false, help: 'Set to true to write out a MANIFEST file listing all included models.' }, { name: 'htmlFileName', help: 'Name of the main html file to produce.', defaultValue: 'main.html' }, { model_: 'StringArrayProperty', name: 'htmlHeaders' }, { name: 'formatter', factory: function() { return { __proto__: JSONUtil.compact, formatFunction: function(f) { var s = f.code.toString(); if ( s.startsWith('function ' + f.name + '(') ) return s; return s.replace(/function ([^\(]*)\(/, 'function ' + f.name + '(') }, keys_: {}, keyify: JSONUtil.prettyModel.keyify, outputObject_: function(out, obj, opt_defaultModel) { var first = true; out('{'); if ( obj.model_.id !== opt_defaultModel ) { this.outputModel_(out, obj); first = false; } if ( Template.isInstance(obj) ) var isTemplate = true; var properties = obj.model_.getRuntimeProperties(); for ( var key in properties ) { var prop = properties[key]; if ( ! this.p(prop) && ( ! isTemplate || prop.name !== 'code' ) ) continue; if ( prop.name === 'documentation' ) continue; if ( prop.name in obj.instance_ ) { var val = obj[prop.name]; if ( Array.isArray(val) && ! val.length ) continue; if ( ! first ) out(','); out(this.keyify(prop.name), ': '); if ( prop.name === 'methods' ) { out('['); var ff = true; for ( var i = 0 ; i < val.length ; i++ ) { if ( ! ff ) out(','); out(this.formatFunction(val[i])); ff = false; } out(']'); } else { if ( Array.isArray(val) && prop.subType ) { this.outputArray_(out, val, prop.subType); } else { this.output(out, val); } } first = false; } } out('}'); } }; } }, { name: 'path', factory: function() { return require('path'); } }, { name: 'fileDAO', factory: function() { return this.FileDAO.create(); } }, { model_: 'StringArrayProperty', name: 'extraClassPaths', help: 'List of extra .js hierarchies to load models from. Paths will be checked in the order given, finally falling back to the main FOAM js/ hierarchy.', adapt: function(_, s) { if ( typeof s === 'string' ) return s.split(','); return s; } }, { model_: 'StringProperty', name: 'locale' }, // TODO(markdittmer): Remove "i18nMessagesPath" when all build processes // no longer require it. { model_: 'StringProperty', name: 'i18nMessagesPath' }, { model_: 'StringProperty', name: 'i18nTranslationsPath' }, { model_: 'StringArrayProperty', name: 'i18nMessages', adapt: function(_, s) { if (typeof s === 'string') return s.split(','); return s; } }, { model_: 'StringArrayProperty', name: 'i18nTranslations', adapt: function(_, s) { if (typeof s === 'string') return s.split(','); return s; } }, { model_: 'StringProperty', name: 'jsFileName', getter: function() { return 'foam' + (this.locale ? '_' + this.locale : '') + '.js'; } }, { model_: 'StringProperty', name: 'manifestFileName', getter: function() { return 'app' + (this.locale ? '_' + this.locale : '') + '.manifest'; } }, { name: 'localizedHTMLFileName_', getter: function() { var match = this.htmlFileName.match(/[.][^.]*$/g); var ext = match ? match[0] : ''; var baseName = this.htmlFileName.slice(0, this.htmlFileName.length - ext.length); return baseName + (this.locale ? '_' + this.locale : '') + ext; } }, { model_: 'StringProperty', name: 'delegate' } ], methods: { execute: function() { for ( var i = 0; i < this.extraClassPaths.length ; i++ ) { this.X.ModelDAO = this.OrDAO.create({ delegate: this.ModelFileDAO.create({ classpath: this.extraClassPaths[i] }), primary: this.X.ModelDAO }); } if ( this.appDefinition ) { this.X.ModelDAO.find(this.appDefinition, { put: function(d) { this.copyFrom(d); this.execute_(); }.bind(this), error: function() { console.log("App definition failed"); this.execute_(); }.bind(this) }); } else { this.execute_(); } }, execute_: function() { if ( this.delegate ) { this.X.arequire(this.delegate)(function(DelegateModel) { DelegateModel.create({ builder: this }).buildApp(); }.bind(this)); } else { this.buildApp(); } }, buildApp: function() { if ( ! this.targetPath ) { this.error("targetPath is required"); process.exit(1); } if ( ! this.controller ) { this.error("controller is required"); process.exit(1); } var extraBuildFiles = this.extraBuildFiles.concat(this.extraFiles); for ( var i = 0 ; i < extraBuildFiles.length ; i++ ) { var path = this.getFilePath(extraBuildFiles[i]); require(path); } var view = this.defaultView ? this.X.arequire(this.defaultView) : anop; var seq = [view]; for ( var i = 0; i < this.extraModels.length ; i++ ) { seq.push(this.X.arequire(this.extraModels[i])); } aseq( aseq.apply(null, seq), this.X.arequire(this.controller))(this.buildModel.bind(this)); }, buildCoreJS_: function(ret) { var i = 0; var self = this; var corejs = ''; var file; var myfiles = this.coreFiles.length ? this.coreFiles : files ; myfiles = myfiles.concat(this.extraFiles); awhile( function() { return i < myfiles.length; }, aif( function() { file = myfiles[i++]; if ( Array.isArray(file) ) { if ( ! file[0] || file[1] == IN_NODEJS || // Exclude nodejs files file[1] == IN_CHROME_APP || // Exclude chrome app files file[0] == '../js/foam/core/bootstrap/BrowserFileDAO' // Exclude BrowserFileDAO, use more portable IE11ModelDAO ) return false; file = file[0]; } return true; }, aseq( function(ret) { var path = this.getFilePath(file); this.fileDAO.find(path, { put: ret, error: function() { self.error.apply(["Error reading file: ", path].concat(arguments)); } }); }.bind(this), function(ret, file) { corejs += '\n'; corejs += file.contents; ret(); })))(function() { ret(corejs); }); }, buildAppJS_: function(ret) { var models = {}; var visited = {}; var error = this.error; var self = this; function add(require) { if ( visited[require] ) return; visited[require] = true; var model = X.lookup(require); if ( ! model ) { error("Could not load model: ", require); } if ( model.package && self.blacklistModels.indexOf(model.id) == -1 ) { models[model.id] = model; } model.getAllRequires().forEach(add); }; add(this.controller); if ( this.defaultView ) add(this.defaultView); for ( var i = 0; i < this.extraModels.length ; i++ ) { add(this.extraModels[i]); } var contents = ''; var ids = Object.keys(models); if ( this.outputManifest ) { this.fileDAO.put(this.File.create({ path: this.targetPath + this.path.sep + 'MANIFEST', contents: ids.join('\n') })); } for ( var i = 0; i < ids.length; i++ ) { var model = models[ids[i]]; if ( this.precompileTemplates ) { function precompile(model) { for ( var j = 0 ; j < model.templates.length ; j++ ) { var t = model.templates[j]; // It's safe to remove leading and trailing whitespace from CSS. if ( t.name === 'CSS' ) t.template = t.template.split('\n').map(function(s) { return s.trim(); }).join('\n'); t.code = TemplateUtil.compile(t, model); t.clearProperty('template'); } model.models.forEach(precompile) } precompile(model); } contents += 'CLASS('; var formatter = this.precompileTemplates ? this.formatter : JSONUtil.compact; contents += formatter.where(NOT_TRANSIENT).stringifyObject(models[ids[i]], 'Model'); contents += ')\n'; } ret(contents); }, buildModel: function(model) { if ( ! model ) { this.error('Could not find model: ', this.controller); } this.log('Building ', model.id); this.log('Target is: ', this.targetPath); this.log(this.precompileTemplates ? '' : 'NOT ', 'pre-compiling templates.'); var self = this; aseq( function(ret) { var file = this.File.create({ path: this.targetPath + this.path.sep + this.localizedHTMLFileName_, contents: this.HTML() }); console.log('Writing: ', file.path); this.fileDAO.put(file, { put: ret, error: function() { self.error('ERROR writing file: ', file.path); process.exit(1); } }); }.bind(this), aif(this.appcacheManifest, function(ret) { var file = this.File.create({ path: this.targetPath + this.path.sep + this.manifestFileName, contents: this.MANIFEST() }); console.log('Writing: ', file.path); this.fileDAO.put(file, { put: ret, error: function() { this.error("ERROR writing file: ", file.path); process.exit(1); } }); }.bind(this)), apar( function(ret) { this.buildCoreJS_(ret); }.bind(this), function(ret) { this.buildAppJS_(ret); }.bind(this)), function(ret, corejs, appjs) { var file = this.File.create({ path: this.targetPath + this.path.sep + this.jsFileName, contents: corejs + appjs }); console.log('Writing: ', file.path); this.fileDAO.put(file, { put: ret, error: function() { self.error('ERROR writing file: ', file.path); process.exit(1); } }); }.bind(this) )( function(){ process.exit(0); }); }, getFilePath: function(file) { var path = file; if ( path.slice(-3) !== '.js' ) path += '.js'; if ( path.charAt(0) !== this.path.sep ) path = FOAM_BOOT_DIR + this.path.sep + path; return path; } }, templates: [ function HTML() {/*<html<% if ( this.appcacheManifest ) { %> manifest="app.manifest"<% } %>><head><meta charset="utf-8"><%= this.htmlHeaders.join('') %><% if ( this.includeFoamCSS ) { %><link rel="stylesheet" type="text/css" href="foam.css"/><% } %><% if ( this.icon ) { %><link rel="icon" sizes="128x128" href="<%= this.icon %>"/><% } %><script src="%%jsFileName"></script></head><body style="margin:0px"><foam model="<%= this.controller %>"<% if ( this.defaultView ) { %> view="<%= this.defaultView %>"<% } %>></foam></body></html>*/}, function MANIFEST() {/*CACHE MANIFEST # version <%= this.version %> <% if ( this.appDefinition ) { %># hash: <%= this.appDefinition.hashCode() %><% } %> CACHE: %%jsFileName %%localizedHTMLFileName_ <% if ( this.includeFoamCSS ) { %>foam.css<% } %> <% for ( var i = 0 ; i < this.resources.length ; i++ ) { %><%= this.resources[i] %> <% } %> NETWORK: * */} ] });