UNPKG

made-view

Version:

components html preprocessor use for made-build

644 lines (556 loc) 17.5 kB
/** * Made-View Compiler * @author: SimonHao * @date: 2015-12-16 14:13:46 */ 'use strict'; var Parser = require('made-view-parser'); var mid = require('made-id'); var fs = require('fs'); var extend = require('extend'); var addwith = require('./with'); var utils = require('./util'); var tags = require('./tag'); var character_parser = require('character-parser'); function Compiler(ast, options, transform){ this.options = extend({ entry: 'view.jade', ext: '.jade', basedir: process.cwd(), filename: __filename, instance: '', instance_through: true, model: 'dev', pretty: true, indent: '\n', }, options); this.filename = this.options.filename; this.transform = transform || {}; this.ast = ast; this.instance = this.options.instance; this.sid = mid.sid(this.filename, this.options); this.block = {}; this.buf = []; this.last_text_buf = []; this.indent_stack = this.options.indent; } Compiler.prototype = { constructor: Compiler, compile: function(){ this.visit(this.ast); return addwith('__made_locals', this.buf.join('\n'), ['__made_buf', '__made_block', '__made_dep', '__made_view']); }, /** * 弹出错误 */ error: function(msg){ console.error('Compiler error from file "', this.filename, '"'); console.error(msg); throw new Error('Compiler Error'); }, /** * 缓存文本 */ buffer_text: function(str, interpolate){ if(!str) return; if(interpolate){ this.interpolate(str, true); }else{ this.last_text_buf.push(utils.stringify(str, true)); } }, /** * 缓存代码片段 */ buffer_code: function(exp){ this.clear_text_buf(); this.buf.push(exp); }, /** * 将代码缓存指定的位置的末尾 */ buffer_code_last: function(exp, inline){ if(inline){ this.last_text_buf.push(exp); }else{ this.buf.push(this.buf.pop() + exp); } }, /** * 清除文本缓冲区 */ clear_text_buf: function(){ var text = ''; if(this.last_text_buf.length){ text = this.last_text_buf.join(''); this.last_text_buf = []; if(text !== ''){ this.buf.push('__made_buf.push("' + text + '");'); } } }, /** * 用来查找 "}" */ detect_bracket: function(str){ return character_parser.parseUntil(str, '}'); }, /** * 从字符串中解析id, instance */ detect_path: function(str){ var match = str.split(':'); var module_id = match[0]; var module_instance = match[1]; var instance = []; if(this.sid && this.options.instance_through) instance.push(this.sid); if(this.instance && this.options.instance_through) instance.push(this.instance); if(module_instance) instance.push(module_instance); var info = { filename: mid.path(module_id, this.options), instance: instance.join('-') }; return info; }, /** * 处理插入表达式 */ interpolate: function(str, inline){ var match = /(\\)?([#!]){((?:.|\n)*)$/.exec(str); if(match){ this.buffer_code_last(utils.stringify(str.substring(0, match.index), true), inline); if(match[1]){ // escape this.buffer_code_last(utils.stringify(match[2] + '{', true), inline); this.interpolate(match[3], inline); }else{ var rest = match[3]; var range = this.detect_bracket(rest); var code = (('!' !== match[2] && inline) ? '__made_view.encode' : '') + '(' + range.src + ')'; this.buffer_code_last('"+' + code + '+"', inline); this.interpolate(rest.substring(range.end + 1), inline); } }else{ this.buffer_code_last(utils.stringify(str, true), inline); } }, indent: function(){ this.indent_stack += ' '; }, outdent: function(){ this.indent_stack = this.indent_stack.substring(0, (this.indent_stack.length - 2)); }, visit: function(node, inline){ var visitor = 'visit_' + node.type; if(visitor in this){ this[visitor](node, inline); }else{ this.error('Unknow Node Type :' + node.type); } }, visit_nodes: function(nodes, inline){ var self = this; if(!Array.isArray(nodes)) return; nodes.forEach(function(node){ self.visit(node, inline); }); }, visit_tags: function(nodes, inline){ if(!Array.isArray(nodes)) return; this.indent(); this.visit_nodes(nodes, inline); this.outdent(); }, visit_file: function(file){ this.visit_nodes(file.nodes); this.clear_text_buf(); }, visit_doctype: function(node){ this.buffer_text('<!DOCTYPE ' + node.val + '>'); }, visit_tag: function(node){ var self = this; if(!tags.inline(node.name) && this.options.pretty){ this.buffer_text(this.indent_stack); } this.buffer_text('<' + node.name); var property_list = node.property_list || []; var class_list = []; property_list.forEach(function(property){ if(property.name === 'class'){ class_list.push(property); }else if(property.name === 'id'){ self.buffer_text(' '); self.visit_id_property(property); }else if(property.name in self.transform){ self.buffer_text(' '); self.visit_transform_property(property, node.name); }else{ self.buffer_text(' '); self.visit_property(property); } }); this.visit_class_property(class_list, node.class_list); if(tags.self_closeing(node.name)){ this.buffer_text('>'); }else{ this.buffer_text('>'); this.buffer_text(node.text, true); this.visit_tags(node.nodes); if(!tags.inline(node.name) && this.options.pretty && Array.isArray(node.nodes) && node.nodes.length){ this.buffer_text(this.indent_stack); } this.buffer_text('</' + node.name + '>'); } }, visit_property: function(node){ if(node.boolean){ this.visit_boolean_property(node); }else{ this.visit_normal_property(node); } }, visit_class_property: function(nodes, class_list){ var self = this; var class_list = class_list || []; var nodes = nodes || []; if(class_list.length || nodes.length){ this.buffer_text(' class="'); } if(class_list.length){ for(var i = 0; i < (class_list.length - 1); i++){ this.visit_class(class_list[i]); this.buffer_text(' '); } this.visit_class(class_list[i]); } if(class_list.length && nodes.length){ this.buffer_text(' '); } if(nodes.length){ this.buffer_code_last('"+(', true); nodes.forEach(function(node){ self.visit(node.val, true); }); this.buffer_code_last(')+"', true); } if(class_list.length || nodes.length){ this.buffer_text('"'); } }, visit_class: function(node){ this.buffer_text((this.sid?this.sid+'-':'') + node.val); }, visit_transform_property: function(node, tag_name){ var transform_result; if(node.val && node.val.type === 'json_string'){ transform_result = this.transform[node.name](node.val.val, tag_name, this.options); if(transform_result){ this.buffer_text(node.name + '="' + transform_result + '"'); }else{ this.visit_property(node); } }else{ this.visit_property(node); } }, visit_id_property: function(node){ if(node.val && node.val.type === 'json_string'){ this.buffer_text('id=' + utils.stringify((this.sid?this.sid+'-':'') + (this.instance?this.instance+'-':'') + node.val.val)); }else{ this.visit_property(node); } }, visit_boolean_property: function(node){ this.buffer_text(node.name); }, visit_normal_property: function(node){ this.buffer_text(node.name + '="'); this.buffer_code_last('"+', true); this.visit(node.val, true); this.buffer_code_last('+"', true); this.buffer_text('"'); }, visit_json_ident: function(node, inline){ if(inline){ this.buffer_code_last('__made_view.encode(' + node.val + ')', inline); }else{ this.buffer_code_last(node.val, inline); } }, visit_json_array: function(node, inline){ this.buffer_code_last('[', inline); for(var i = 0; i < (node.element.length - 1); i++){ this.visit(node.element[i], inline); this.buffer_code_last(',', inline); } this.visit(node.element[i], inline); this.buffer_code_last(']', inline); }, visit_json_number: function(node, inline){ this.buffer_code_last(node.val, inline); }, visit_json_reference_list: function(node, inline){ if(inline){ this.buffer_code_last('__made_view.encode(' + node.ident.val, inline) this.visit_nodes(node.list, inline); this.buffer_code_last(')', inline); }else{ this.buffer_code_last(node.ident.val, inline); this.visit_nodes(node.list, inline); } }, visit_json_literal: function(node, inline){ this.buffer_code_last(node.val, inline); }, visit_json_object: function(node, inline){ this.buffer_code_last('{', inline); for(var i = 0; i < (node.member.length - 1); i++){ this.visit(node.member[i], inline); this.buffer_code_last(',', inline); } this.visit(node.member[i], inline); this.buffer_code_last('}', inline); }, visit_json_member: function(node, inline){ this.buffer_code_last(node.name + ':', inline); this.visit(node.val, inline); }, visit_json_string: function(node, inline){ this.buffer_code_last('"', inline); this.interpolate(node.val, inline); this.buffer_code_last('"', inline); }, visit_block: function(node){ this.block[node.name] = true; this.buffer_code('__made_view.block(__made_block,' + utils.stringify(node.name) + ',function(){'); this.visit_nodes(node.nodes); this.buffer_code('});'); }, visit_extends: function(node){ var self = this; var path_info = this.detect_path(node.id); if(!fs.existsSync(path_info.filename)){ this.error('Extend components not exists: ' + node.id); } var str = fs.readFileSync(path_info.filename, 'utf-8'); var ast = new Parser(str, path_info.filename).parse(); var compiler = new Compiler(ast, extend({}, this.options, { filename: path_info.filename, instance: path_info.instance, indent: this.indent_stack, instance_through: true, }), this.transform); var code = compiler.compile(); this.buffer_code('(function(__made_locals, __made_block){'); this.buffer_code(code); if(this.options.dep){ this.buffer_code(';__made_dep.push({'); this.buffer_code('filename:' + utils.stringify(path_info.filename) + ','); this.buffer_code('instance:' + utils.stringify(path_info.instance) + ','); this.buffer_code('type:"extends",'); this.buffer_code('entry:' + (node.entry ? "true" : "false") +','); this.buffer_code('options:__made_locals'); this.buffer_code('});'); } this.buffer_code('})({'); var options_list = node.option || []; for(var i = 0; i < (options_list.length - 1); i++){ this.visit_option(options_list[i]); this.buffer_code_last(','); } this.visit_option(options_list[i]); this.buffer_code('},{'); this.visit_content(node.content, compiler.block, path_info.filename); this.buffer_code('});') }, visit_option: function(node){ if(!node) return; if(node.name in this.transform && node.val && node.val.type === 'json_string'){ this.visit_transform_option(node); }else{ this.visit_normal_option(node); } }, visit_transform_option: function(node){ var transform_result = this.transform[node.name](node.val.val, '__option', this.options); if(transform_result){ node.val.val = transform_result; } this.visit_normal_option(node); }, visit_normal_option: function(node){ if(node){ this.buffer_code_last(node.name + ':'); this.visit(node.val); } }, visit_content: function(content_list, block_list, filename){ var self = this; var content_list = content_list || []; var content_table = {}; content_list.forEach(function(content){ content_table[content.block] = content_table[content.block] || {before:[],replace:[],after:[]}; content_table[content.block][content.type].push(content); }); Object.keys(content_table).forEach(function(block){ if(!(block in block_list)){ self.error('There no Block "' + block + '" from extends file' + filename); } self.buffer_code(block + ':['); if(content_table[block].before.length){ self.visit_content_list(content_table[block].before); }else{ self.buffer_code('null'); } self.buffer_code(','); if(content_table[block].replace.length){ self.visit_content_list(content_table[block].replace); }else{ self.buffer_code('null'); } self.buffer_code(','); if(content_table[block].after.length){ self.visit_content_list(content_table[block].after); }else{ self.buffer_code('null'); } self.buffer_code('],'); }); }, visit_content_list: function(content_list){ var self = this; this.buffer_code('function(){'); content_list.forEach(function(content){ self.visit_tags(content.nodes); }); this.buffer_code('}'); }, visit_include: function(node){ var self = this; var path_info = this.detect_path(node.id); if(!fs.existsSync(path_info.filename)){ this.error('Include components not exists: ' + node.id); } var str = fs.readFileSync(path_info.filename, 'utf-8'); var ast = new Parser(str, path_info.filename).parse(); var compiler = new Compiler(ast, extend({}, this.options, { filename: path_info.filename, instance: path_info.instance, indent: this.indent_stack, instance_through: true, }), this.transform); var code = compiler.compile(); this.buffer_code('(function(__made_locals, __made_block){'); this.buffer_code(code); if(this.options.dep){ this.buffer_code(';__made_dep.push({'); this.buffer_code('filename:' + utils.stringify(path_info.filename) + ','); this.buffer_code('instance:' + utils.stringify(path_info.instance) + ','); this.buffer_code('type:"include",'); this.buffer_code('entry:' + (node.entry ? "true" : "false") +','); this.buffer_code('options:__made_locals'); this.buffer_code('});'); } this.buffer_code('})({'); var options_list = node.option || []; for(var i = 0; i < (options_list.length - 1); i++){ this.visit_option(options_list[i]); this.buffer_code_last(','); } this.visit_option(options_list[i]); this.buffer_code('},{});') }, visit_comment: function(node){ this.buffer_text(this.indent_stack + '<!--' + node.val + '-->'); }, visit_code: function(node){ this.buffer_code(node.val); }, visit_origin: function(node){ this.buffer_text(this.indent_stack + ' ' + node.val); }, visit_text: function(node){ this.buffer_text(node.val); }, visit_case: function(node){ this.buffer_code('switch(' + node.expr + '){'); this.visit_nodes(node.nodes); this.buffer_code('}'); }, visit_when: function(node){ this.buffer_code('case ' + node.expr + ':'); this.visit_nodes(node.nodes); this.buffer_code('break;'); }, visit_default: function(node){ this.buffer_code('default:'); this.visit_nodes(node.nodes); }, visit_if: function(node){ this.buffer_code('if(' + node.expr + '){'); this.visit_nodes(node.nodes); this.buffer_code('}'); }, visit_else: function(node){ this.buffer_code('else{'); this.visit_nodes(node.nodes); this.buffer_code('}'); }, visit_elseif: function(node){ this.buffer_code('else if(' + node.expr + '){'); this.visit_nodes(node.nodes); this.buffer_code('}'); }, visit_while: function(node){ this.buffer_code('while(' + node.expr + '){'); this.visit_nodes(node.nodes); this.buffer_code('}'); }, visit_each: function(node){ this.buffer_code('__made_view.each(' + node.data + ',function(' + node.value + (node.index?','+node.index : '') + '){'); this.visit_nodes(node.nodes); this.buffer_code('});'); }, visit_custom_tag: function(node){ if(node.text || (Array.isArray(node.nodes) && node.nodes.length)){ this.visit_custom_tag_extend(node); }else{ this.visit_custom_tag_include(node); } }, visit_custom_tag_extend: function(node){ var extend_node = { type: 'extends', option: node.option, entry: true, id: node.name, content:[] }; var content_node = { type: 'replace', block: 'content', nodes: [] }; if(node.text){ content_node.nodes.push({ type: 'text', val: node.text }); } if(Array.isArray(node.nodes) && node.nodes.length){ content_node.nodes = content_node.nodes.concat(node.nodes); } extend_node.content.push(content_node); this.visit_extends(extend_node, true); }, visit_custom_tag_include: function(node){ var include_node = { type: 'include', option: node.option, entry: true, id: node.name }; this.visit_include(include_node, true); } }; module.exports = Compiler;