UNPKG

neft

Version:

Universal Platform

358 lines (301 loc) 10.5 kB
'use strict' try babel = require 'babel-core' bundle = require './bundle' parser = require './parser' bindingParser = require 'src/binding/parser' log = require 'src/log' utils = require 'src/utils' Renderer = require 'src/renderer' assert = require 'src/assert' ATTRIBUTE = 'attribute' # options {BINDING_THIS_TO_TARGET_OPTS} = bindingParser ids = idsKeys = itemsKeys = extensions = queries = null isAnchor = (obj) -> assert obj.type is ATTRIBUTE, "isAnchor: type must be an attribute" obj.name is 'anchors' or obj.name.indexOf('anchors.') is 0 isBinding = (obj) -> assert obj.type is ATTRIBUTE, "isBinding: type must be an attribute" bindingParser.isBinding obj.value getByTypeDeep = (elem, type, callback) -> if elem.type is type callback elem for child in elem.body if child.type is type callback child if child.body? getByTypeDeep child, type, callback else if child.value?.body getByTypeDeep child.value, type, callback return MODIFIERS_NAMES = __proto__: null Class: true Transition: true Animation: true PropertyAnimation: true NumberAnimation: true FontLoader: true ResourcesLoader: true anchorAttributeToString = (obj) -> assert obj.type is ATTRIBUTE, "anchorAttributeToString: type must be an attribute" if typeof obj.value is 'object' return "{}" anchor = obj.value.split '.' if anchor[0] is 'this' anchor.shift() anchor[0] = "this.#{anchor[0]}" else if anchor[0] is 'null' return 'null' useBinding = false anchor[0] = switch anchor[0] when 'this.parent', 'parent' "'parent'" when 'this.children', 'children' "'children'" when 'this.nextSibling', 'nextSibling' "'nextSibling'" when 'this.previousSibling', 'previousSibling' "'previousSibling'" else useBinding = true "#{anchor[0]}" if anchor.length > 1 anchor[1] = "'#{anchor[1]}'" r = "[#{anchor}]" if useBinding "[function(#{idsKeys}){return #{r}}, []]" else r isPublicBindingId = (id) -> id is 'this' or id is 'app' or id is 'view' or ids.hasOwnProperty(id) or id of Renderer bindingAttributeToString = (obj) -> binding = bindingParser.parse obj.value, isPublicBindingId, obj._parserOptions args = idsKeys+'' func = "function(#{args}){return #{binding.hash}}" "[#{func}, [#{binding.connections}]]" stringify = function: (elem) -> # arguments args = idsKeys+'' if args and elem.params+'' args += "," args += elem.params+'' # body {body} = elem if babel body = "(function(){#{body}})" body = babel.transform(body, presets: ['es2015']).code body = /{([^]*)}/m.exec(body)?[1] else log.warn.once 'NML functions do not support EcmaScript 6, ' + 'because babel is not installed' "function(#{args}){#{body}}" object: (elem) -> json = {} children = [] postfix = '' attrToValue = (body) -> {value} = body if body.name is 'document.query' and not MODIFIERS_NAMES[body.parent.name] if isBinding(body) throw new Error 'document.query must be a string' if value query = '' tmp = body # get value while tmp = tmp.parent for child in tmp.body if child.name is 'document.query' query = child.value.replace(/'/g, '') + ' ' + query break query = query.trim() # get ids id = '' if body.parent.parent id = ':' + body.parent.id # save query queries[query] = id else if Array.isArray(value) r = {} for child in value r[child.name] = "`#{attrToValue(child)}`" r = JSON.stringify r postfix += ", \"#{body.name}\": #{r}" return false else if value?.type is 'object' valueCode = stringify.object value value = "((#{valueCode}), new Renderer.Component.Link('#{value.id}'))" else if body.type is 'function' value = stringify.function body else if isAnchor(body) value = anchorAttributeToString(body) else if isBinding(body) value = bindingAttributeToString(body) value unless elem.id json.id = elem.id = "i#{utils.uid()}" for body in elem.body switch body.type when 'id' json.id = body.value when 'attribute' value = attrToValue body if value isnt false json[body.name] = "`#{value}`" when 'function' json[body.name] = "`#{stringify.function body}`" when 'object' children.push stringify.object(body) when 'property' json.properties ?= [] json.properties.push body.name when 'signal' json.signals ?= [] json.signals.push body.name else if stringify[body.type]? children.push stringify[body.type](body) else throw "Unexpected object body type '#{body.type}'" if not elem.parent and elem.name is 'Class' and not json.target json.target = "`view`" itemsKeys.push json.id visibleId = json.id if utils.has(idsKeys, json.id) visibleId = json.id json = JSON.stringify json, null, 4 if children.length postfix += ", children: [" for child, i in children if i > 0 postfix += ", " postfix += child postfix += "]" if postfix if json.length is 2 postfix = postfix.slice(2) json = json.slice 0, -1 json += postfix json += "}" json = json.replace /"`(.*?)`"/g, (_, val) -> JSON.parse "\"#{val}\"" rendererCtor = Renderer[elem.name.split('.')[0]] if rendererCtor? r = "Renderer.#{elem.name}.New(_c, #{json})\n" else r = "Renderer.Component.getCloneFunction(#{elem.name}, '#{elem.name}')(_c, #{json})\n" if visibleId r = "#{visibleId} = #{r}" r if: (elem) -> changes = [] body = [] for child in elem.body if child.type in ['attribute', 'function'] changes.push child else body.push child elem.type = 'object' elem.name = 'Class' elem.body = [{ type: 'attribute' name: 'when' value: elem.condition parent: elem _parserOptions: BINDING_THIS_TO_TARGET_OPTS }, { type: 'attribute' name: 'changes' value: changes parent: elem }, body...] stringify.object elem for: (elem) -> changes = [] body = [] for child in elem.body if child.type in ['attribute', 'function'] changes.push child else body.push child elem.type = 'object' elem.name = 'Class' elem.body = [{ type: 'attribute' name: 'document.query' value: elem.query parent: elem }, { type: 'attribute' name: 'changes' value: changes parent: elem }, body...] stringify.object elem getIds = (elem, ids={}) -> elems = getByTypeDeep elem, 'id', (attr) -> ids[attr.value] = attr.parent ids module.exports = (file, filename) -> elems = parser file codes = {} autoInitCodes = [] bootstrap = '' firstId = null allQueries = {} for elem, i in elems queries = {} id = elem.id ids = getIds elem idsKeys = Object.keys(ids).filter (id) -> !!id itemsKeys = [] code = "var _c = new Renderer.Component({fileName: '#{filename}'})\n" if elem.type is 'code' bootstrap += elem.body continue if typeof stringify[elem.type] isnt 'function' console.error "Unexpected block type '#{elem.type}'" continue elemCode = stringify[elem.type] elem objectsIds = idsKeys.slice() for id in itemsKeys unless utils.has(objectsIds, id) objectsIds.push id if objectsIds.length code += "var #{objectsIds}\n" objects = utils.arrayToObject objectsIds, (i, elem) -> elem, (i, elem) -> "`#{elem}`" code += '_c.item = ' code += elemCode code += "_c.itemId = '#{elem.id}'\n" code += "_c.idsOrder = #{JSON.stringify(idsKeys)}\n" code += "_c.objectsOrder = #{JSON.stringify(idsKeys).replace(/\"/g, '')}\n" code += "_c.objects = #{JSON.stringify(objects).replace(/\"`|`\"/g, '')}\n" if elem.name is 'Class' code += "_c.initAsEmptyDefinition()\n" code += "_c.item.enable()\n" autoInitCodes.push code else code += 'return _c.createItem\n' uid = 'n' + utils.uid() id ||= uid if codes[id]? id = uid codes[id] = code firstId ?= id # queries for query, val of queries val = id + val if allQueries[query]? throw new Error "document.query '#{query}' is duplicated" allQueries[query] = val if not codes._main and firstId codes._main = link: firstId bootstrap: bootstrap codes: codes autoInitCodes: autoInitCodes queries: allQueries module.exports.bundle = bundle module.exports