Created
April 24, 2016 11:17
-
-
Save a-x-/aad7596e2a31bf20f988db1893805a77 to your computer and use it in GitHub Desktop.
Parallel bemhtml client rendering in WebWorker Raw
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Begin: worker context function | |
| var workerFunction = () => { | |
| postMessage('bemhtml worker is ready'); | |
| // Begin: Standart bemhtml client code | |
| // see worker code at file end | |
| var BEMHTML; | |
| (function(global) { | |
| var buildBemXjst = function(exports, __bem_xjst_libs__){ | |
| /// ------------------------------------- | |
| /// --------- BEM-XJST Runtime Start ---- | |
| /// ------------------------------------- | |
| var BEMHTML = function(module, exports) { | |
| (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.BEMHTML = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ | |
| function ClassBuilder(options) { | |
| this.modDelim = options.mod || '_'; | |
| this.elemDelim = options.elem || '__'; | |
| } | |
| exports.ClassBuilder = ClassBuilder; | |
| ClassBuilder.prototype.build = function build(block, elem) { | |
| if (elem === undefined) | |
| return block; | |
| else | |
| return block + this.elemDelim + elem; | |
| }; | |
| ClassBuilder.prototype.buildModPostfix = function buildModPostfix(modName, | |
| modVal) { | |
| var res = this.modDelim + modName; | |
| if (modVal !== true) res += this.modDelim + modVal; | |
| return res; | |
| }; | |
| ClassBuilder.prototype.buildBlockClass = function buildBlockClass(name, | |
| modName, | |
| modVal) { | |
| var res = name; | |
| if (modVal) res += this.buildModPostfix(modName, modVal); | |
| return res; | |
| }; | |
| ClassBuilder.prototype.buildElemClass = function buildElemClass(block, | |
| name, | |
| modName, | |
| modVal) { | |
| var res = this.buildBlockClass(block) + this.elemDelim + name; | |
| if (modVal) res += this.buildModPostfix(modName, modVal); | |
| return res; | |
| }; | |
| ClassBuilder.prototype.split = function split(key) { | |
| return key.split(this.elemDelim, 2); | |
| }; | |
| },{}],2:[function(require,module,exports){ | |
| var utils = require('./utils'); | |
| function Context(bemhtml) { | |
| this._bemhtml = bemhtml; | |
| this.ctx = null; | |
| this.block = ''; | |
| // Save current block until the next BEM entity | |
| this._currBlock = ''; | |
| this.elem = null; | |
| this.mods = {}; | |
| this.elemMods = {}; | |
| this.position = 0; | |
| this._listLength = 0; | |
| this._notNewList = false; | |
| // Used in `OnceMatch` check to detect context change | |
| this._onceRef = {}; | |
| } | |
| exports.Context = Context; | |
| Context.prototype._flush = null; | |
| Context.prototype.isArray = utils.isArray; | |
| Context.prototype.isSimple = utils.isSimple; | |
| Context.prototype.isShortTag = utils.isShortTag; | |
| Context.prototype.extend = utils.extend; | |
| Context.prototype.identify = utils.identify; | |
| Context.prototype.xmlEscape = utils.xmlEscape; | |
| Context.prototype.attrEscape = utils.attrEscape; | |
| Context.prototype.jsAttrEscape = utils.jsAttrEscape; | |
| Context.prototype.BEM = {}; | |
| Context.prototype.isFirst = function isFirst() { | |
| return this.position === 1; | |
| }; | |
| Context.prototype.isLast = function isLast() { | |
| return this.position === this._listLength; | |
| }; | |
| Context.prototype.generateId = function generateId() { | |
| return utils.identify(this.ctx); | |
| }; | |
| Context.prototype.reapply = function reapply(ctx) { | |
| return this._bemhtml.run(ctx); | |
| }; | |
| },{"./utils":7}],3:[function(require,module,exports){ | |
| var utils = require('./utils'); | |
| var Template = require('./tree').Template; | |
| var PropertyMatch = require('./tree').PropertyMatch; | |
| var CompilerOptions = require('./tree').CompilerOptions; | |
| var Match = require('./match').Match; | |
| function Entity(bemhtml, block, elem, templates) { | |
| this.bemhtml = bemhtml; | |
| this.block = null; | |
| this.elem = null; | |
| this.jsClass = null; | |
| // `true` if entity has just a default renderer for `def()` mode | |
| this.canFlush = true; | |
| // Compiler options via `xjstOptions()` | |
| this.options = {}; | |
| // "Fast modes" | |
| this.def = new Match(this); | |
| this.tag = new Match(this); | |
| this.attrs = new Match(this); | |
| this.mod = new Match(this); | |
| this.js = new Match(this); | |
| this.mix = new Match(this); | |
| this.bem = new Match(this); | |
| this.cls = new Match(this); | |
| this.content = new Match(this); | |
| // "Slow modes" | |
| this.rest = {}; | |
| // Initialize | |
| this.init(block, elem); | |
| this.initModes(templates); | |
| } | |
| exports.Entity = Entity; | |
| Entity.prototype.init = function init(block, elem) { | |
| this.block = block; | |
| this.elem = elem; | |
| // Class for jsParams | |
| this.jsClass = this.bemhtml.classBuilder.build(this.block, this.elem); | |
| }; | |
| function contentMode() { | |
| return this.ctx.content; | |
| } | |
| Entity.prototype.initModes = function initModes(templates) { | |
| /* jshint maxdepth : false */ | |
| for (var i = 0; i < templates.length; i++) { | |
| var template = templates[i]; | |
| for (var j = template.predicates.length - 1; j >= 0; j--) { | |
| var pred = template.predicates[j]; | |
| if (!(pred instanceof PropertyMatch)) | |
| continue; | |
| if (pred.key !== '_mode') | |
| continue; | |
| template.predicates.splice(j, 1); | |
| this._initRest(pred.value); | |
| // All templates should go there anyway | |
| this.rest[pred.value].push(template); | |
| break; | |
| } | |
| if (j === -1) | |
| this.def.push(template); | |
| // Merge compiler options | |
| for (var j = template.predicates.length - 1; j >= 0; j--) { | |
| var pred = template.predicates[j]; | |
| if (!(pred instanceof CompilerOptions)) | |
| continue; | |
| this.options = utils.extend(this.options, pred.options); | |
| } | |
| } | |
| }; | |
| Entity.prototype._initRest = function _initRest(key) { | |
| if (key === 'tag' || | |
| key === 'attrs' || | |
| key === 'js' || | |
| key === 'mix' || | |
| key === 'bem' || | |
| key === 'cls' || | |
| key === 'content' || | |
| key === 'default') { | |
| if (key === 'default') | |
| this.rest[key] = this.def; | |
| else | |
| this.rest[key] = this[key]; | |
| } else { | |
| if (!this.rest.hasOwnProperty(key)) | |
| this.rest[key] = new Match(this); | |
| } | |
| }; | |
| Entity.prototype.setDefaults = function setDefaults() { | |
| // Default .content() template for applyNext() | |
| if (this.content.count !== 0) | |
| this.content.push(new Template([], contentMode)); | |
| // .def() default | |
| if (this.def.count !== 0) { | |
| // `.xjstOptions({ flush: true })` will override this | |
| this.canFlush = this.options.flush || false; | |
| var self = this; | |
| this.def.push(new Template([], function defaultBodyProxy() { | |
| return self.defaultBody(this); | |
| })); | |
| } | |
| }; | |
| Entity.prototype.prepend = function prepend(other) { | |
| // Prepend to the slow modes, fast modes are in this hashmap too anyway | |
| var keys = Object.keys(this.rest); | |
| for (var i = 0; i < keys.length; i++) { | |
| var key = keys[i]; | |
| if (!other.rest[key]) | |
| continue; | |
| this.rest[key].prepend(other.rest[key]); | |
| } | |
| // Add new slow modes | |
| keys = Object.keys(other.rest); | |
| for (var i = 0; i < keys.length; i++) { | |
| var key = keys[i]; | |
| if (this.rest[key]) | |
| continue; | |
| this._initRest(key); | |
| this.rest[key].prepend(other.rest[key]); | |
| } | |
| }; | |
| // NOTE: This could be potentially compiled into inlined invokations | |
| Entity.prototype.run = function run(context) { | |
| if (this.def.count !== 0) | |
| return this.def.exec(context); | |
| return this.defaultBody(context); | |
| }; | |
| Entity.prototype.defaultBody = function defaultBody(context) { | |
| var tag = context.ctx.tag; | |
| if (tag === undefined) | |
| tag = this.tag.exec(context); | |
| var js; | |
| if (context.ctx.js !== false) | |
| js = this.js.exec(context); | |
| var bem = this.bem.exec(context); | |
| var cls = this.cls.exec(context); | |
| var mix = this.mix.exec(context); | |
| var attrs = this.attrs.exec(context); | |
| var content = this.content.exec(context); | |
| // Default content | |
| if (this.content.count === 0 && content === undefined) | |
| content = context.ctx.content; | |
| return this.bemhtml.render(context, | |
| this, | |
| tag, | |
| js, | |
| bem, | |
| cls, | |
| mix, | |
| attrs, | |
| content); | |
| }; | |
| },{"./match":5,"./tree":6,"./utils":7}],4:[function(require,module,exports){ | |
| var inherits = require('inherits'); | |
| var Tree = require('./tree').Tree; | |
| var PropertyMatch = require('./tree').PropertyMatch; | |
| var Entity = require('./entity').Entity; | |
| var Context = require('./context').Context; | |
| var ClassBuilder = require('./class-builder').ClassBuilder; | |
| var utils = require('./utils'); | |
| function BEMHTML(options) { | |
| this.options = options || {}; | |
| this.entities = null; | |
| this.defaultEnt = null; | |
| // Current tree | |
| this.tree = null; | |
| // Current match | |
| this.match = null; | |
| // Create new Context constructor for overriding prototype | |
| this.contextConstructor = function ContextChild(bemhtml) { | |
| Context.call(this, bemhtml); | |
| }; | |
| inherits(this.contextConstructor, Context); | |
| this.context = null; | |
| this.classBuilder = new ClassBuilder(this.options.naming || {}); | |
| // Execution depth, used to invalidate `applyNext` bitfields | |
| this.depth = 0; | |
| // Do not call `_flush` on overridden `def()` mode | |
| this.canFlush = false; | |
| // oninit templates | |
| this.oninit = null; | |
| // Initialize default entity (no block/elem match) | |
| this.defaultEnt = new Entity(this, '', '', []); | |
| this.defaultElemEnt = new Entity(this, '', '', []); | |
| } | |
| module.exports = BEMHTML; | |
| BEMHTML.locals = Tree.methods.concat('local', 'applyCtx', 'applyNext', 'apply'); | |
| BEMHTML.prototype.compile = function compile(code) { | |
| var self = this; | |
| function applyCtx() { | |
| return self._run(self.context.ctx); | |
| } | |
| function applyCtxWrap(ctx, changes) { | |
| // Fast case | |
| if (!changes) | |
| return self.local({ ctx: ctx }, applyCtx); | |
| return self.local(changes, function() { | |
| return self.local({ ctx: ctx }, applyCtx); | |
| }); | |
| } | |
| function apply(mode, changes) { | |
| return self.applyMode(mode, changes); | |
| } | |
| function localWrap(changes) { | |
| return function localBody(body) { | |
| return self.local(changes, body); | |
| }; | |
| } | |
| var tree = new Tree({ | |
| refs: { | |
| applyCtx: applyCtxWrap, | |
| local: localWrap | |
| } | |
| }); | |
| // Yeah, let people pass functions to us! | |
| var templates = this.recompileInput(code); | |
| var out = tree.build(templates, [ | |
| localWrap, | |
| applyCtxWrap, | |
| function applyNextWrap(changes) { | |
| if (changes) | |
| return self.local(changes, applyNextWrap); | |
| return self.applyNext(); | |
| }, | |
| apply | |
| ]); | |
| // Concatenate templates with existing ones | |
| // TODO(indutny): it should be possible to incrementally add templates | |
| if (this.tree) { | |
| out = { | |
| templates: out.templates.concat(this.tree.templates), | |
| oninit: this.tree.oninit.concat(out.oninit) | |
| }; | |
| } | |
| this.tree = out; | |
| // Group block+elem entities into a hashmap | |
| var ent = this.groupEntities(out.templates); | |
| // Transform entities from arrays to Entity instances | |
| ent = this.transformEntities(ent); | |
| this.entities = ent; | |
| this.oninit = out.oninit; | |
| }; | |
| BEMHTML.prototype.recompileInput = function recompileInput(code) { | |
| var out = code.toString(); | |
| var args = BEMHTML.locals; | |
| // Reuse function if it already has right arguments | |
| if (typeof code === 'function' && code.length === args.length) | |
| return code; | |
| // Strip the function | |
| out = out.replace(/^function[^{]+{|}$/g, ''); | |
| // And recompile it with right arguments | |
| out = new Function(args.join(', '), out); | |
| return out; | |
| }; | |
| BEMHTML.prototype.groupEntities = function groupEntities(tree) { | |
| var res = {}; | |
| for (var i = 0; i < tree.length; i++) { | |
| // Make sure to change only the copy, the original is cached in `this.tree` | |
| var template = tree[i].clone(); | |
| var block = null; | |
| var elem; | |
| elem = undefined; | |
| for (var j = 0; j < template.predicates.length; j++) { | |
| var pred = template.predicates[j]; | |
| if (!(pred instanceof PropertyMatch)) | |
| continue; | |
| if (pred.key === 'block') | |
| block = pred.value; | |
| else if (pred.key === 'elem') | |
| elem = pred.value; | |
| else | |
| continue; | |
| // Remove predicate, we won't much against it | |
| template.predicates.splice(j, 1); | |
| j--; | |
| } | |
| // TODO(indutny): print out the template itself | |
| if (block === null) | |
| throw new Error('block("...") not found in one of the templates'); | |
| var key = this.classBuilder.build(block, elem); | |
| if (!res[key]) | |
| res[key] = []; | |
| res[key].push(template); | |
| } | |
| return res; | |
| }; | |
| BEMHTML.prototype.transformEntities = function transformEntities(entities) { | |
| var wildcardElems = []; | |
| var keys = Object.keys(entities); | |
| for (var i = 0; i < keys.length; i++) { | |
| var key = keys[i]; | |
| // TODO(indutny): pass this values over | |
| var parts = this.classBuilder.split(key); | |
| var block = parts[0]; | |
| var elem = parts[1]; | |
| if (elem === '*') | |
| wildcardElems.push(block); | |
| entities[key] = new Entity(this, block, elem, entities[key]); | |
| } | |
| // Merge wildcard block templates | |
| if (entities.hasOwnProperty('*')) { | |
| var wildcard = entities['*']; | |
| for (var i = 0; i < keys.length; i++) { | |
| var key = keys[i]; | |
| if (key === '*') | |
| continue; | |
| entities[key].prepend(wildcard); | |
| } | |
| this.defaultEnt.prepend(wildcard); | |
| this.defaultElemEnt.prepend(wildcard); | |
| } | |
| // Merge wildcard elem templates | |
| for (var i = 0; i < wildcardElems.length; i++) { | |
| var block = wildcardElems[i]; | |
| var wildcardKey = this.classBuilder.build(block, '*'); | |
| var wildcard = entities[wildcardKey]; | |
| for (var i = 0; i < keys.length; i++) { | |
| var key = keys[i]; | |
| if (key === wildcardKey) | |
| continue; | |
| var entity = entities[key]; | |
| if (entity.block !== block) | |
| continue; | |
| if (entity.elem === undefined) | |
| continue; | |
| entities[key].prepend(wildcard); | |
| } | |
| this.defaultElemEnt.prepend(wildcard); | |
| } | |
| // Set default templates after merging with wildcard | |
| for (var i = 0; i < keys.length; i++) { | |
| var key = keys[i]; | |
| entities[key].setDefaults(); | |
| this.defaultEnt.setDefaults(); | |
| this.defaultElemEnt.setDefaults(); | |
| } | |
| return entities; | |
| }; | |
| BEMHTML.prototype._run = function _run(context) { | |
| var res; | |
| if (context === undefined || context === '' || context === null) | |
| res = this.runEmpty(); | |
| else if (utils.isArray(context)) | |
| res = this.runMany(context); | |
| else if (utils.isSimple(context)) | |
| res = this.runSimple(context); | |
| else | |
| res = this.runOne(context); | |
| return res; | |
| }; | |
| BEMHTML.prototype.run = function run(json) { | |
| var match = this.match; | |
| var context = this.context; | |
| this.match = null; | |
| this.context = new this.contextConstructor(this); | |
| this.canFlush = this.context._flush !== null; | |
| this.depth = 0; | |
| var res = this._run(json); | |
| if (this.canFlush) | |
| res = this.context._flush(res); | |
| this.match = match; | |
| this.context = context; | |
| return res; | |
| }; | |
| BEMHTML.prototype.runEmpty = function runEmpty() { | |
| this.context._listLength--; | |
| return ''; | |
| }; | |
| BEMHTML.prototype.runMany = function runMany(arr) { | |
| var out = ''; | |
| var context = this.context; | |
| var prevPos = context.position; | |
| var prevNotNewList = context._notNewList; | |
| if (prevNotNewList) { | |
| context._listLength += arr.length - 1; | |
| } else { | |
| context.position = 0; | |
| context._listLength = arr.length; | |
| } | |
| context._notNewList = true; | |
| if (this.canFlush) { | |
| for (var i = 0; i < arr.length; i++) | |
| out += context._flush(this._run(arr[i])); | |
| } else { | |
| for (var i = 0; i < arr.length; i++) | |
| out += this._run(arr[i]); | |
| } | |
| if (!prevNotNewList) | |
| context.position = prevPos; | |
| return out; | |
| }; | |
| BEMHTML.prototype.runSimple = function runSimple(context) { | |
| this.context._listLength--; | |
| var res = ''; | |
| if (context && context !== true || context === 0) | |
| res += context; | |
| return res; | |
| }; | |
| BEMHTML.prototype.runOne = function runOne(json) { | |
| var context = this.context; | |
| var oldCtx = context.ctx; | |
| var oldBlock = context.block; | |
| var oldCurrBlock = context._currBlock; | |
| var oldElem = context.elem; | |
| var oldMods = context.mods; | |
| var oldElemMods = context.elemMods; | |
| if (json.block || json.elem) | |
| context._currBlock = ''; | |
| else | |
| context._currBlock = context.block; | |
| context.ctx = json; | |
| if (json.block) { | |
| context.block = json.block; | |
| if (json.mods) | |
| context.mods = json.mods; | |
| else | |
| context.mods = {}; | |
| } else { | |
| if (!json.elem) | |
| context.block = ''; | |
| else if (oldCurrBlock) | |
| context.block = oldCurrBlock; | |
| } | |
| context.elem = json.elem; | |
| if (json.elemMods) | |
| context.elemMods = json.elemMods; | |
| else | |
| context.elemMods = {}; | |
| var block = context.block || ''; | |
| var elem = context.elem; | |
| // Control list position | |
| if (block || elem) | |
| context.position++; | |
| else | |
| context._listLength--; | |
| // To invalidate `applyNext` flags | |
| this.depth++; | |
| var key = this.classBuilder.build(block, elem); | |
| var restoreFlush = false; | |
| var ent = this.entities[key]; | |
| if (ent) { | |
| if (this.canFlush && !ent.canFlush) { | |
| // Entity does not support flushing, do not flush anything nested | |
| restoreFlush = true; | |
| this.canFlush = false; | |
| } | |
| } else { | |
| // No entity - use default one | |
| ent = this.defaultEnt; | |
| if (elem !== undefined) | |
| ent = this.defaultElemEnt; | |
| ent.init(block, elem); | |
| } | |
| var res = ent.run(context); | |
| context.ctx = oldCtx; | |
| context.block = oldBlock; | |
| context.elem = oldElem; | |
| context.mods = oldMods; | |
| context.elemMods = oldElemMods; | |
| context._currBlock = oldCurrBlock; | |
| this.depth--; | |
| if (restoreFlush) | |
| this.canFlush = true; | |
| return res; | |
| }; | |
| BEMHTML.prototype.render = function render(context, | |
| entity, | |
| tag, | |
| js, | |
| bem, | |
| cls, | |
| mix, | |
| attrs, | |
| content) { | |
| var ctx = context.ctx; | |
| if (tag === undefined) | |
| tag = 'div'; | |
| if (!tag) | |
| return this.renderNoTag(context, js, bem, cls, mix, attrs, content); | |
| var out = '<' + tag; | |
| var ctxJS = ctx.js; | |
| if (ctxJS !== false) { | |
| if (js === true) | |
| js = {}; | |
| if (js) { | |
| if (ctxJS !== true) | |
| js = utils.extend(ctxJS, js); | |
| } else if (ctxJS === true) { | |
| js = {}; | |
| } else { | |
| js = ctxJS; | |
| } | |
| } | |
| var jsParams; | |
| if (js) { | |
| jsParams = {}; | |
| jsParams[entity.jsClass] = js; | |
| } | |
| var isBEM = bem; | |
| if (isBEM === undefined) { | |
| if (ctx.bem === undefined) | |
| isBEM = entity.block || entity.elem; | |
| else | |
| isBEM = ctx.bem; | |
| } | |
| isBEM = !!isBEM; | |
| if (cls === undefined) | |
| cls = ctx.cls; | |
| var addJSInitClass = entity.block && jsParams && !entity.elem; | |
| if (!isBEM && !cls) { | |
| return this.renderClose(out, context, tag, attrs, isBEM, ctx, content); | |
| } | |
| out += ' class="'; | |
| if (isBEM) { | |
| var mods = ctx.elemMods || ctx.mods; | |
| if (!mods && ctx.block) | |
| mods = context.mods; | |
| out += entity.jsClass; | |
| out += this.buildModsClasses(entity.block, entity.elem, mods); | |
| var totalMix = mix; | |
| if (ctx.mix) { | |
| if (totalMix) | |
| totalMix = [].concat(totalMix, ctx.mix); | |
| else | |
| totalMix = ctx.mix; | |
| } | |
| if (totalMix) { | |
| var m = this.renderMix(entity, totalMix, jsParams, addJSInitClass); | |
| out += m.out; | |
| jsParams = m.jsParams; | |
| addJSInitClass = m.addJSInitClass; | |
| } | |
| if (cls) | |
| out += ' ' + cls; | |
| } else { | |
| if (cls) | |
| out += cls; | |
| } | |
| if (addJSInitClass) | |
| out += ' i-bem"'; | |
| else | |
| out += '"'; | |
| if (isBEM && jsParams) | |
| out += ' data-bem=\'' + utils.jsAttrEscape(JSON.stringify(jsParams)) + '\''; | |
| return this.renderClose(out, context, tag, attrs, isBEM, ctx, content); | |
| }; | |
| BEMHTML.prototype.renderClose = function renderClose(prefix, | |
| context, | |
| tag, | |
| attrs, | |
| isBEM, | |
| ctx, | |
| content) { | |
| var out = prefix; | |
| // NOTE: maybe we need to make an array for quicker serialization | |
| attrs = utils.extend(attrs, ctx.attrs); | |
| if (attrs) { | |
| var name; // TODO: do something with OmetaJS and YUI Compressor | |
| /* jshint forin : false */ | |
| for (name in attrs) { | |
| var attr = attrs[name]; | |
| if (attr === undefined) | |
| continue; | |
| // TODO(indutny): support `this.reapply()` | |
| out += ' ' + name + '="' + | |
| utils.attrEscape(utils.isSimple(attr) ? | |
| attr : | |
| this.reapply(attr)) + | |
| '"'; | |
| } | |
| } | |
| if (utils.isShortTag(tag)) { | |
| out += '/>'; | |
| if (this.canFlush) | |
| out = context._flush(out); | |
| } else { | |
| out += '>'; | |
| if (this.canFlush) | |
| out = context._flush(out); | |
| // TODO(indutny): skip apply next flags | |
| if (content || content === 0) | |
| out += this.renderContent(content, isBEM); | |
| out += '</' + tag + '>'; | |
| } | |
| if (this.canFlush) | |
| out = context._flush(out); | |
| return out; | |
| }; | |
| BEMHTML.prototype.renderMix = function renderMix(entity, | |
| mix, | |
| jsParams, | |
| addJSInitClass) { | |
| var visited = {}; | |
| var context = this.context; | |
| var js = jsParams; | |
| var addInit = addJSInitClass; | |
| visited[entity.jsClass] = true; | |
| // Transform mix to the single-item array if it's not array | |
| if (!utils.isArray(mix)) | |
| mix = [ mix ]; | |
| var classBuilder = this.classBuilder; | |
| var out = ''; | |
| for (var i = 0; i < mix.length; i++) { | |
| var item = mix[i]; | |
| if (item === undefined) | |
| continue; | |
| if (typeof item === 'string') | |
| item = { block: item, elem: undefined }; | |
| var hasItem = item.block || item.elem; | |
| var block = item.block || item._block || context.block; | |
| var elem = item.elem || item._elem || context.elem; | |
| var key = classBuilder.build(block, elem); | |
| var classElem = item.elem || | |
| item._elem || | |
| (item.block ? undefined : context.elem); | |
| if (hasItem) | |
| out += ' ' + classBuilder.build(block, classElem); | |
| out += this.buildModsClasses(block, classElem, item.elemMods || item.mods); | |
| if (item.js) { | |
| if (!js) | |
| js = {}; | |
| js[classBuilder.build(block, item.elem)] = | |
| item.js === true ? {} : item.js; | |
| if (!addInit) | |
| addInit = block && !item.elem; | |
| } | |
| // Process nested mixes | |
| if (!hasItem || visited[key]) | |
| continue; | |
| visited[key] = true; | |
| var nestedEntity = this.entities[key]; | |
| if (!nestedEntity) | |
| continue; | |
| var oldBlock = context.block; | |
| var oldElem = context.elem; | |
| var nestedMix = nestedEntity.mix.exec(context); | |
| context.elem = oldElem; | |
| context.block = oldBlock; | |
| if (!nestedMix) | |
| continue; | |
| for (var j = 0; j < nestedMix.length; j++) { | |
| var nestedItem = nestedMix[j]; | |
| if (!nestedItem.block && | |
| !nestedItem.elem || | |
| !visited[classBuilder.build(nestedItem.block, nestedItem.elem)]) { | |
| nestedItem._block = block; | |
| nestedItem._elem = elem; | |
| mix = mix.slice(0, i + 1).concat( | |
| nestedItem, | |
| mix.slice(i + 1) | |
| ); | |
| } | |
| } | |
| } | |
| return { | |
| out: out, | |
| jsParams: js, | |
| addJSInitClass: addInit | |
| }; | |
| }; | |
| BEMHTML.prototype.buildModsClasses = function buildModsClasses(block, | |
| elem, | |
| mods) { | |
| if (!mods) | |
| return ''; | |
| var res = ''; | |
| var modName; | |
| for (modName in mods) { | |
| if (!mods.hasOwnProperty(modName)) | |
| continue; | |
| var modVal = mods[modName]; | |
| if (!modVal && modVal !== 0) continue; | |
| if (typeof modVal !== 'boolean') | |
| modVal += ''; | |
| var builder = this.classBuilder; | |
| res += ' ' + (elem ? | |
| builder.buildElemClass(block, elem, modName, modVal) : | |
| builder.buildBlockClass(block, modName, modVal)); | |
| } | |
| return res; | |
| }; | |
| BEMHTML.prototype.renderContent = function renderContent(content, isBEM) { | |
| var context = this.context; | |
| var oldPos = context.position; | |
| var oldListLength = context._listLength; | |
| var oldNotNewList = context._notNewList; | |
| context._notNewList = false; | |
| if (isBEM) { | |
| context.position = 1; | |
| context._listLength = 1; | |
| } | |
| var res = this._run(content); | |
| context.position = oldPos; | |
| context._listLength = oldListLength; | |
| context._notNewList = oldNotNewList; | |
| return res; | |
| }; | |
| BEMHTML.prototype.renderNoTag = function renderNoTag(context, | |
| js, | |
| bem, | |
| cls, | |
| mix, | |
| attrs, | |
| content) { | |
| // TODO(indutny): skip apply next flags | |
| if (content || content === 0) | |
| return this._run(content); | |
| return ''; | |
| }; | |
| BEMHTML.prototype.local = function local(changes, body) { | |
| var keys = Object.keys(changes); | |
| var restore = []; | |
| for (var i = 0; i < keys.length; i++) { | |
| var key = keys[i]; | |
| var parts = key.split('.'); | |
| var value = this.context; | |
| for (var j = 0; j < parts.length - 1; j++) | |
| value = value[parts[j]]; | |
| restore.push({ | |
| parts: parts, | |
| value: value[parts[j]] | |
| }); | |
| value[parts[j]] = changes[key]; | |
| } | |
| var res = body.call(this.context); | |
| for (var i = 0; i < restore.length; i++) { | |
| var parts = restore[i].parts; | |
| var value = this.context; | |
| for (var j = 0; j < parts.length - 1; j++) | |
| value = value[parts[j]]; | |
| value[parts[j]] = restore[i].value; | |
| } | |
| return res; | |
| }; | |
| BEMHTML.prototype.applyNext = function applyNext() { | |
| return this.match.exec(this.context); | |
| }; | |
| BEMHTML.prototype.applyMode = function applyMode(mode, changes) { | |
| var match = this.match.entity.rest[mode]; | |
| if (!match) | |
| return; | |
| if (!changes) | |
| return match.exec(this.context); | |
| var self = this; | |
| // Allocate function this way, to prevent allocation at the top of the | |
| // `applyMode` | |
| var fn = function localBody() { | |
| return match.exec(self.context); | |
| }; | |
| return this.local(changes, fn); | |
| }; | |
| BEMHTML.prototype.exportApply = function exportApply(exports) { | |
| var self = this; | |
| exports.apply = function apply(context) { | |
| return self.run(context); | |
| }; | |
| // Add templates at run time | |
| exports.compile = function compile(templates) { | |
| return self.compile(templates); | |
| }; | |
| var sharedContext = {}; | |
| exports.BEMContext = this.contextConstructor; | |
| sharedContext.BEMContext = exports.BEMContext; | |
| for (var i = 0; i < this.oninit.length; i++) { | |
| var oninit = this.oninit[i]; | |
| oninit(exports, sharedContext); | |
| } | |
| }; | |
| },{"./class-builder":1,"./context":2,"./entity":3,"./tree":6,"./utils":7,"inherits":8}],5:[function(require,module,exports){ | |
| var utils = require('./utils'); | |
| var PropertyMatch = require('./tree').PropertyMatch; | |
| var OnceMatch = require('./tree').OnceMatch; | |
| var WrapMatch = require('./tree').WrapMatch; | |
| var PropertyAbsent = require('./tree').PropertyAbsent; | |
| var CustomMatch = require('./tree').CustomMatch; | |
| function MatchProperty(template, pred) { | |
| this.template = template; | |
| this.key = pred.key; | |
| this.value = pred.value; | |
| } | |
| MatchProperty.prototype.exec = function exec(context) { | |
| return context[this.key] === this.value; | |
| }; | |
| function MatchNested(template, pred) { | |
| this.template = template; | |
| this.keys = pred.key; | |
| this.value = pred.value; | |
| } | |
| MatchNested.prototype.exec = function exec(context) { | |
| var val = context; | |
| for (var i = 0; i < this.keys.length - 1; i++) { | |
| val = val[this.keys[i]]; | |
| if (!val) | |
| return false; | |
| } | |
| return val[this.keys[i]] === this.value; | |
| }; | |
| function MatchAbsent(template, pred) { | |
| this.template = template; | |
| this.key = pred.key; | |
| } | |
| MatchAbsent.prototype.exec = function exec(context) { | |
| return !context[this.key]; | |
| }; | |
| function MatchCustom(template, pred) { | |
| this.template = template; | |
| this.body = pred.body; | |
| } | |
| MatchCustom.prototype.exec = function exec(context) { | |
| return this.body.call(context); | |
| }; | |
| function MatchOnce(template) { | |
| this.template = template; | |
| this.once = null; | |
| } | |
| MatchOnce.prototype.exec = function exec(context) { | |
| var res = this.once !== context._onceRef; | |
| this.once = context._onceRef; | |
| return res; | |
| }; | |
| function MatchWrap(template) { | |
| this.template = template; | |
| this.wrap = null; | |
| } | |
| MatchWrap.prototype.exec = function exec(context) { | |
| var res = this.wrap !== context.ctx; | |
| this.wrap = context.ctx; | |
| return res; | |
| }; | |
| function MatchTemplate(mode, template) { | |
| this.mode = mode; | |
| this.predicates = new Array(template.predicates.length); | |
| this.body = template.body; | |
| var postpone = []; | |
| for (var i = 0, j = 0; i < this.predicates.length; i++, j++) { | |
| var pred = template.predicates[i]; | |
| if (pred instanceof PropertyMatch) { | |
| if (utils.isArray(pred.key)) | |
| this.predicates[j] = new MatchNested(this, pred); | |
| else | |
| this.predicates[j] = new MatchProperty(this, pred); | |
| } else if (pred instanceof PropertyAbsent) { | |
| this.predicates[j] = new MatchAbsent(this, pred); | |
| } else if (pred instanceof CustomMatch) { | |
| this.predicates[j] = new MatchCustom(this, pred); | |
| // Push OnceMatch and MatchWrap later, they should not be executed first. | |
| // Otherwise they will set flag too early, and body might not be executed | |
| } else if (pred instanceof OnceMatch) { | |
| j--; | |
| postpone.push(new MatchOnce(this)); | |
| } else if (pred instanceof WrapMatch) { | |
| j--; | |
| postpone.push(new MatchWrap(this)); | |
| } else { | |
| // Skip | |
| j--; | |
| } | |
| } | |
| // Insert late predicates | |
| for (var i = postpone.length - 1; i >= 0; i--) | |
| this.predicates[i + j] = this.predicates[i]; | |
| for (var i = 0; i < postpone.length; i++) | |
| this.predicates[i] = postpone[i]; | |
| j += postpone.length; | |
| if (this.predicates.length !== j) | |
| this.predicates.length = j; | |
| } | |
| exports.MatchTemplate = MatchTemplate; | |
| function Match(entity) { | |
| this.entity = entity; | |
| this.bemhtml = this.entity.bemhtml; | |
| this.templates = []; | |
| // applyNext mask | |
| this.mask = [ 0 ]; | |
| // We are going to create copies of mask for nested `applyNext()` | |
| this.maskSize = 0; | |
| this.maskOffset = 0; | |
| this.count = 0; | |
| this.depth = -1; | |
| this.thrownError = null; | |
| } | |
| exports.Match = Match; | |
| Match.prototype.clone = function clone(entity) { | |
| var res = new Match(entity); | |
| res.templates = this.templates.slice(); | |
| res.mask = this.mask.slice(); | |
| res.maskSize = this.maskSize; | |
| res.count = this.count; | |
| return res; | |
| }; | |
| Match.prototype.prepend = function prepend(other) { | |
| this.templates = other.templates.concat(this.templates); | |
| this.count += other.count; | |
| while (Math.ceil(this.count / 31) > this.mask.length) | |
| this.mask.push(0); | |
| this.maskSize = this.mask.length; | |
| }; | |
| Match.prototype.push = function push(template) { | |
| this.templates.push(new MatchTemplate(this, template)); | |
| this.count++; | |
| if (Math.ceil(this.count / 31) > this.mask.length) | |
| this.mask.push(0); | |
| this.maskSize = this.mask.length; | |
| }; | |
| Match.prototype.tryCatch = function tryCatch(fn, ctx) { | |
| try { | |
| return fn.call(ctx); | |
| } catch (e) { | |
| this.thrownError = e; | |
| } | |
| }; | |
| Match.prototype.exec = function exec(context) { | |
| var save = this.checkDepth(); | |
| var template; | |
| var bitIndex = this.maskOffset; | |
| var mask = this.mask[bitIndex]; | |
| var bit = 1; | |
| for (var i = 0; i < this.count; i++) { | |
| if ((mask & bit) === 0) { | |
| template = this.templates[i]; | |
| for (var j = template.predicates.length - 1; j >= 0; j--) { | |
| var pred = template.predicates[j]; | |
| /* jshint maxdepth : false */ | |
| if (!pred.exec(context)) | |
| break; | |
| } | |
| // All predicates matched! | |
| if (j === -1) | |
| break; | |
| } | |
| if (bit === 0x40000000) { | |
| bitIndex++; | |
| mask = this.mask[bitIndex]; | |
| bit = 1; | |
| } else { | |
| bit <<= 1; | |
| } | |
| } | |
| if (i === this.count) | |
| return undefined; | |
| var oldMask = mask; | |
| var oldMatch = this.bemhtml.match; | |
| this.mask[bitIndex] |= bit; | |
| this.bemhtml.match = this; | |
| this.thrownError = null; | |
| var out; | |
| if (typeof template.body === 'function') | |
| out = this.tryCatch(template.body, context); | |
| else | |
| out = template.body; | |
| this.mask[bitIndex] = oldMask; | |
| this.bemhtml.match = oldMatch; | |
| this.restoreDepth(save); | |
| var e = this.thrownError; | |
| if (e !== null) { | |
| this.thrownError = null; | |
| throw e; | |
| } | |
| return out; | |
| }; | |
| Match.prototype.checkDepth = function checkDepth() { | |
| if (this.depth === -1) { | |
| this.depth = this.bemhtml.depth; | |
| return -1; | |
| } | |
| if (this.bemhtml.depth === this.depth) | |
| return this.depth; | |
| var depth = this.depth; | |
| this.depth = this.bemhtml.depth; | |
| this.maskOffset += this.maskSize; | |
| while (this.mask.length < this.maskOffset + this.maskSize) | |
| this.mask.push(0); | |
| return depth; | |
| }; | |
| Match.prototype.restoreDepth = function restoreDepth(depth) { | |
| if (depth !== -1 && depth !== this.depth) | |
| this.maskOffset -= this.maskSize; | |
| this.depth = depth; | |
| }; | |
| },{"./tree":6,"./utils":7}],6:[function(require,module,exports){ | |
| var assert = require('minimalistic-assert'); | |
| var inherits = require('inherits'); | |
| function Template(predicates, body) { | |
| this.predicates = predicates; | |
| this.body = body; | |
| } | |
| exports.Template = Template; | |
| Template.prototype.wrap = function wrap() { | |
| var body = this.body; | |
| for (var i = 0; i < this.predicates.length; i++) { | |
| var pred = this.predicates[i]; | |
| body = pred.wrapBody(body); | |
| } | |
| this.body = body; | |
| }; | |
| Template.prototype.clone = function clone() { | |
| return new Template(this.predicates.slice(), this.body); | |
| }; | |
| function MatchBase() { | |
| } | |
| exports.MatchBase = MatchBase; | |
| MatchBase.prototype.wrapBody = function wrapBody(body) { | |
| return body; | |
| }; | |
| function Item(tree, children) { | |
| this.conditions = []; | |
| this.children = []; | |
| for (var i = children.length - 1; i >= 0; i--) { | |
| var arg = children[i]; | |
| if (arg instanceof MatchBase) | |
| this.conditions.push(arg); | |
| else if (arg === tree.boundBody) | |
| this.children[i] = tree.queue.pop(); | |
| else | |
| this.children[i] = arg; | |
| } | |
| } | |
| function OnceMatch() { | |
| MatchBase.call(this); | |
| } | |
| inherits(OnceMatch, MatchBase); | |
| exports.OnceMatch = OnceMatch; | |
| function WrapMatch(refs) { | |
| MatchBase.call(this); | |
| this.refs = refs; | |
| } | |
| inherits(WrapMatch, MatchBase); | |
| exports.WrapMatch = WrapMatch; | |
| WrapMatch.prototype.wrapBody = function wrapBody(body) { | |
| var applyCtx = this.refs.applyCtx; | |
| if (typeof body !== 'function') { | |
| return function inlineAdaptor() { | |
| return applyCtx(body); | |
| }; | |
| } | |
| return function wrapAdaptor() { | |
| return applyCtx(body.call(this)); | |
| }; | |
| }; | |
| function ReplaceMatch(refs) { | |
| MatchBase.call(this); | |
| this.refs = refs; | |
| } | |
| inherits(ReplaceMatch, MatchBase); | |
| exports.ReplaceMatch = ReplaceMatch; | |
| ReplaceMatch.prototype.wrapBody = function wrapBody(body) { | |
| var applyCtx = this.refs.applyCtx; | |
| if (typeof body !== 'function') { | |
| return function inlineAdaptor() { | |
| return applyCtx(body); | |
| }; | |
| } | |
| return function replaceAdaptor() { | |
| return applyCtx(body.call(this)); | |
| }; | |
| }; | |
| function ExtendMatch(refs) { | |
| MatchBase.call(this); | |
| this.refs = refs; | |
| } | |
| inherits(ExtendMatch, MatchBase); | |
| exports.ExtendMatch = ExtendMatch; | |
| ExtendMatch.prototype.wrapBody = function wrapBody(body) { | |
| var applyCtx = this.refs.applyCtx; | |
| var local = this.refs.local; | |
| if (typeof body !== 'function') { | |
| return function inlineAdaptor() { | |
| var changes = {}; | |
| var keys = Object.keys(body); | |
| for (var i = 0; i < keys.length; i++) | |
| changes['ctx.' + keys[i]] = body[keys[i]]; | |
| return local(changes)(function preApplyCtx() { | |
| return applyCtx(this.ctx); | |
| }); | |
| }; | |
| } | |
| return function localAdaptor() { | |
| var changes = {}; | |
| var obj = body.call(this); | |
| var keys = Object.keys(obj); | |
| for (var i = 0; i < keys.length; i++) | |
| changes['ctx.' + keys[i]] = obj[keys[i]]; | |
| return local(changes)(function preApplyCtx() { | |
| return applyCtx(this.ctx); | |
| }); | |
| }; | |
| }; | |
| function CompilerOptions(options) { | |
| MatchBase.call(this); | |
| this.options = options; | |
| } | |
| inherits(CompilerOptions, MatchBase); | |
| exports.CompilerOptions = CompilerOptions; | |
| function PropertyMatch(key, value) { | |
| MatchBase.call(this); | |
| this.key = key; | |
| this.value = value; | |
| } | |
| inherits(PropertyMatch, MatchBase); | |
| exports.PropertyMatch = PropertyMatch; | |
| function PropertyAbsent(key) { | |
| MatchBase.call(this); | |
| this.key = key; | |
| } | |
| inherits(PropertyAbsent, MatchBase); | |
| exports.PropertyAbsent = PropertyAbsent; | |
| function CustomMatch(body) { | |
| MatchBase.call(this); | |
| this.body = body; | |
| } | |
| inherits(CustomMatch, MatchBase); | |
| exports.CustomMatch = CustomMatch; | |
| function Tree(options) { | |
| this.options = options; | |
| this.refs = this.options.refs; | |
| this.boundBody = this.body.bind(this); | |
| var methods = this.methods('body'); | |
| for (var i = 0; i < methods.length; i++) { | |
| var method = methods[i]; | |
| // NOTE: method.name is empty because of .bind() | |
| this.boundBody[Tree.methods[i]] = method; | |
| } | |
| this.queue = []; | |
| this.templates = []; | |
| this.initializers = []; | |
| } | |
| exports.Tree = Tree; | |
| Tree.methods = [ | |
| 'match', 'once', 'wrap', 'elemMatch', 'block', 'elem', 'mode', 'mod', | |
| 'elemMod', 'def', 'tag', 'attrs', 'cls', 'js', 'jsAttr', | |
| 'bem', 'mix', 'content', 'replace', 'extend', 'oninit', | |
| 'xjstOptions' | |
| ]; | |
| Tree.prototype.build = function build(templates, apply) { | |
| var methods = this.methods('global').concat(apply); | |
| methods[0] = this.match.bind(this); | |
| templates.apply({}, methods); | |
| return { | |
| templates: this.templates.slice().reverse(), | |
| oninit: this.initializers | |
| }; | |
| }; | |
| function methodFactory(self, kind, name) { | |
| var method = self[name]; | |
| var boundBody = self.boundBody; | |
| if (kind !== 'body') { | |
| if (name === 'replace' || name === 'extend' || name === 'wrap') { | |
| return function wrapExtended() { | |
| return method.apply(self, arguments); | |
| }; | |
| } | |
| return function wrapNotBody() { | |
| method.apply(self, arguments); | |
| return boundBody; | |
| }; | |
| } | |
| return function wrapBody() { | |
| var res = method.apply(self, arguments); | |
| // Insert body into last item | |
| var child = self.queue.pop(); | |
| var last = self.queue[self.queue.length - 1]; | |
| last.conditions = last.conditions.concat(child.conditions); | |
| last.children = last.children.concat(child.children); | |
| if (name === 'replace' || name === 'extend' || name === 'wrap') | |
| return res; | |
| return boundBody; | |
| }; | |
| } | |
| Tree.prototype.methods = function methods(kind) { | |
| var out = new Array(Tree.methods.length); | |
| for (var i = 0; i < out.length; i++) { | |
| var name = Tree.methods[i]; | |
| out[i] = methodFactory(this, kind, name); | |
| } | |
| return out; | |
| }; | |
| // Called after all matches | |
| Tree.prototype.flush = function flush(conditions, item) { | |
| var subcond; | |
| if (item.conditions) | |
| subcond = conditions.concat(item.conditions); | |
| else | |
| subcond = item.conditions; | |
| for (var i = 0; i < item.children.length; i++) { | |
| var arg = item.children[i]; | |
| // Go deeper | |
| if (arg instanceof Item) { | |
| this.flush(subcond, item.children[i]); | |
| // Body | |
| } else { | |
| var template = new Template(conditions, arg); | |
| template.wrap(); | |
| this.templates.push(template); | |
| } | |
| } | |
| }; | |
| Tree.prototype.body = function body() { | |
| var children = new Array(arguments.length); | |
| for (var i = 0; i < arguments.length; i++) | |
| children[i] = arguments[i]; | |
| var child = new Item(this, children); | |
| this.queue[this.queue.length - 1].children.push(child); | |
| if (this.queue.length === 1) | |
| this.flush([], this.queue.shift()); | |
| return this.boundBody; | |
| }; | |
| Tree.prototype.match = function match() { | |
| var children = new Array(arguments.length); | |
| for (var i = 0; i < arguments.length; i++) { | |
| var arg = arguments[i]; | |
| if (typeof arg === 'function') | |
| arg = new CustomMatch(arg); | |
| assert(arg instanceof MatchBase, 'Wrong .match() argument'); | |
| children[i] = arg; | |
| } | |
| this.queue.push(new Item(this, children)); | |
| return this.boundBody; | |
| }; | |
| Tree.prototype.once = function once() { | |
| return this.match(new OnceMatch()); | |
| }; | |
| Tree.prototype.wrap = function wrap() { | |
| return this.def().match(new WrapMatch(this.refs)); | |
| }; | |
| Tree.prototype.xjstOptions = function xjstOptions(options) { | |
| this.queue.push(new Item(this, [ | |
| new CompilerOptions(options) | |
| ])); | |
| return this.boundBody; | |
| }; | |
| Tree.prototype.block = function block(name) { | |
| return this.match(new PropertyMatch('block', name)); | |
| }; | |
| Tree.prototype.elemMatch = function elemMatch() { | |
| return this.match.apply(this, arguments); | |
| }; | |
| Tree.prototype.elem = function elem(name) { | |
| return this.match(new PropertyMatch('elem', name)); | |
| }; | |
| Tree.prototype.mode = function mode(name) { | |
| return this.match(new PropertyMatch('_mode', name)); | |
| }; | |
| Tree.prototype.mod = function mod(name, value) { | |
| return this.match(new PropertyMatch([ 'mods', name ], value)); | |
| }; | |
| Tree.prototype.elemMod = function elemMod(name, value) { | |
| return this.match(new PropertyMatch([ 'elemMods', name ], value)); | |
| }; | |
| Tree.prototype.def = function def() { return this.mode('default'); }; | |
| Tree.prototype.tag = function tag() { return this.mode('tag'); }; | |
| Tree.prototype.attrs = function attrs() { return this.mode('attrs'); }; | |
| Tree.prototype.cls = function cls() { return this.mode('cls'); }; | |
| Tree.prototype.js = function js() { return this.mode('js'); }; | |
| Tree.prototype.jsAttr = function jsAttr() { return this.mode('jsAttr'); }; | |
| Tree.prototype.bem = function bem() { return this.mode('bem'); }; | |
| Tree.prototype.mix = function mix() { return this.mode('mix'); }; | |
| Tree.prototype.content = function content() { return this.mode('content'); }; | |
| Tree.prototype.replace = function replace() { | |
| return this.def().match(new ReplaceMatch(this.refs)); | |
| }; | |
| Tree.prototype.extend = function extend() { | |
| return this.def().match(new ExtendMatch(this.refs)); | |
| }; | |
| Tree.prototype.oninit = function oninit(fn) { | |
| this.initializers.push(fn); | |
| }; | |
| },{"inherits":8,"minimalistic-assert":9}],7:[function(require,module,exports){ | |
| /** | |
| * Pattern for acceptable names of elements and modifiers | |
| * @const | |
| * @type String | |
| */ | |
| /* jshint unused : false */ | |
| var NAME_PATTERN = '[a-zA-Z0-9-]+'; | |
| var toString = Object.prototype.toString; | |
| exports.isArray = Array.isArray; | |
| if (!exports.isArray) { | |
| exports.isArray = function isArrayPolyfill(obj) { | |
| return toString.call(obj) === '[object Array]'; | |
| }; | |
| } | |
| exports.xmlEscape = function(str) { | |
| return (str + '') | |
| .replace(/&/g, '&') | |
| .replace(/</g, '<') | |
| .replace(/>/g, '>'); | |
| }; | |
| exports.attrEscape = function(str) { | |
| return (str + '') | |
| .replace(/&/g, '&') | |
| .replace(/"/g, '"'); | |
| }; | |
| exports.jsAttrEscape = function(str) { | |
| return (str + '') | |
| .replace(/&/g, '&') | |
| .replace(/'/g, '''); | |
| }; | |
| exports.extend = function extend(o1, o2) { | |
| if (!o1 || !o2) | |
| return o1 || o2; | |
| var res = {}; | |
| var n; | |
| for (n in o1) | |
| if (o1.hasOwnProperty(n)) | |
| res[n] = o1[n]; | |
| for (n in o2) | |
| if (o2.hasOwnProperty(n)) | |
| res[n] = o2[n]; | |
| return res; | |
| }; | |
| var SHORT_TAGS = { // хэш для быстрого определения, является ли тэг коротким | |
| area: 1, base: 1, br: 1, col: 1, command: 1, embed: 1, hr: 1, img: 1, | |
| input: 1, keygen: 1, link: 1, meta: 1, param: 1, source: 1, wbr: 1 | |
| }; | |
| exports.isShortTag = function isShortTag(t) { | |
| return SHORT_TAGS.hasOwnProperty(t); | |
| }; | |
| exports.isSimple = function isSimple(obj) { | |
| if (!obj || obj === true) return true; | |
| return typeof obj === 'string' || typeof obj === 'number'; | |
| }; | |
| var uniqCount = 0; | |
| var uniqId = +new Date(); | |
| var uniqExpando = '__' + uniqId; | |
| var uniqPrefix = 'uniq' + uniqId; | |
| function getUniq() { | |
| return uniqPrefix + (++uniqCount); | |
| } | |
| exports.getUniq = getUniq; | |
| exports.identify = function identify(obj, onlyGet) { | |
| if (!obj) | |
| return getUniq(); | |
| if (onlyGet || obj[uniqExpando]) | |
| return obj[uniqExpando]; | |
| var u = getUniq(); | |
| obj[uniqExpando] = u; | |
| return u; | |
| }; | |
| },{}],8:[function(require,module,exports){ | |
| if (typeof Object.create === 'function') { | |
| // implementation from standard node.js 'util' module | |
| module.exports = function inherits(ctor, superCtor) { | |
| ctor.super_ = superCtor | |
| ctor.prototype = Object.create(superCtor.prototype, { | |
| constructor: { | |
| value: ctor, | |
| enumerable: false, | |
| writable: true, | |
| configurable: true | |
| } | |
| }); | |
| }; | |
| } else { | |
| // old school shim for old browsers | |
| module.exports = function inherits(ctor, superCtor) { | |
| ctor.super_ = superCtor | |
| var TempCtor = function () {} | |
| TempCtor.prototype = superCtor.prototype | |
| ctor.prototype = new TempCtor() | |
| ctor.prototype.constructor = ctor | |
| } | |
| } | |
| },{}],9:[function(require,module,exports){ | |
| module.exports = assert; | |
| function assert(val, msg) { | |
| if (!val) | |
| throw new Error(msg || 'Assertion failed'); | |
| } | |
| assert.equal = function assertEqual(l, r, msg) { | |
| if (l != r) | |
| throw new Error(msg || ('Assertion failed: ' + l + ' != ' + r)); | |
| }; | |
| },{}]},{},[4])(4) | |
| });; | |
| return module.exports || | |
| exports.BEMHTML; | |
| }({}, {}); | |
| /// ------------------------------------- | |
| /// --------- BEM-XJST Runtime End ------ | |
| /// ------------------------------------- | |
| var api = new BEMHTML({"wrap":false}); | |
| /// ------------------------------------- | |
| /// ------ BEM-XJST User-code Start ----- | |
| /// ------------------------------------- | |
| api.compile(function(match, once, wrap, elemMatch, block, elem, mode, mod, elemMod, def, tag, attrs, cls, js, jsAttr, bem, mix, content, replace, extend, oninit, xjstOptions, local, applyCtx, applyNext, apply) { | |
| /* begin: /Users/invntrm/Ya/Projects/fiji/libs/islands/common.blocks/i-bem/__i18n/_dummy/i-bem__i18n_dummy_yes.bemhtml.js */ | |
| /* global oninit */ | |
| oninit(function() { | |
| (function(global, bem_) { | |
| if(bem_.I18N) { | |
| return; | |
| } | |
| /** | |
| * Points to global context | |
| * @global | |
| */ | |
| global.BEM = bem_; | |
| /** | |
| * `BEM.I18N` API stub | |
| */ | |
| var i18n = bem_.I18N = function(keyset, key) { | |
| return key; | |
| }; | |
| i18n.keyset = function() { return i18n }; | |
| i18n.key = function(key) { return key }; | |
| i18n.lang = function() { return }; | |
| })(this, typeof BEM === 'undefined' ? {} : BEM); | |
| }); | |
| /* end: /Users/invntrm/Ya/Projects/fiji/libs/islands/common.blocks/i-bem/__i18n/_dummy/i-bem__i18n_dummy_yes.bemhtml.js */ | |
| /* begin: /Users/invntrm/Ya/Projects/fiji/libs/islands/common.blocks/domik/domik.bemhtml.js */ | |
| block('domik')( | |
| def().match(function() { return !Object(this.ctx.mods).logo; })(function() { | |
| var lang; | |
| if(this.ctx.lang) { | |
| lang = this.ctx.lang; | |
| } else if(BEM.blocks && BEM.blocks['i-global']) { | |
| lang = BEM.blocks['i-global'].param('lang'); | |
| } else if(this['i-global']) { | |
| lang = this['i-global'].lang; | |
| } | |
| return applyNext({ | |
| 'ctx.mods': this.extend(this.ctx.mods || {}, { | |
| logo: ['en', 'tr'].indexOf(lang) !== -1 ? 'en' : 'ru' | |
| }) | |
| }); | |
| }), | |
| content()(function() { | |
| return [ | |
| { | |
| elem: 'roof', | |
| content: [ | |
| {elem: 'roof-border'}, | |
| {elem: 'roof-body'}, | |
| {elem: 'logo'} | |
| ] | |
| }, | |
| { | |
| elem: 'content', | |
| content: this.ctx.content | |
| } | |
| ]; | |
| }), | |
| elem('roof-border').tag()('i'), | |
| elem('roof-body').tag()('i'), | |
| elem('logo').tag()('i') | |
| ); | |
| /* end: /Users/invntrm/Ya/Projects/fiji/libs/islands/common.blocks/domik/domik.bemhtml.js */ | |
| /* begin: /Users/invntrm/Ya/Projects/fiji/libs/islands/common.blocks/i-global/i-global.bemhtml.js */ | |
| oninit(function(exports) { | |
| var ctxProto = exports.BEMContext.prototype, | |
| // Параметры по умолчанию | |
| glob = exports['i-global'] = ctxProto['i-global'] = ctxProto.extend({ | |
| lang: 'ru', | |
| tld: 'ru', | |
| }, ctxProto['i-global']); | |
| // Параметры доступные в JS | |
| glob._public = { | |
| id: true, | |
| lang: true, | |
| tld: true, | |
| }; | |
| /** | |
| * Делает один или несколько параметров доступными или недоступными в JS. | |
| * | |
| * ``` | |
| * bh.lib.makePublic('param'); | |
| * bh.lib.makePublic('param', false); | |
| * bh.lib.makePublic({first: true, second: false}); | |
| * ``` | |
| * | |
| * @param {String|Object<String,Boolean>} param Имя параметра или объект именами параметров в качестве ключей | |
| * и значениями `true` или `false` | |
| * @param {Boolean} [flag] Устанавливаемое значение доступности параметра | |
| */ | |
| glob.makePublic = function(param, flag) { | |
| if(typeof param === 'object') { | |
| glob._public = ctxProto.extend(glob._public, param); | |
| return; | |
| } | |
| if(typeof flag === 'undefined') { | |
| flag = true; | |
| } | |
| glob._public[param] = flag; | |
| }; | |
| /** | |
| * Проверяет доступен ли параметр в JS. | |
| * | |
| * @param {String} param Имя параметра | |
| * @returns {Boolean} Доступность параметра | |
| */ | |
| glob.isPublic = function(param) { | |
| return Boolean(glob._public[param]); | |
| }; | |
| /** | |
| * Изменяет локаль. | |
| * | |
| * @param {String} tld Код локали, например `ru` или `tr` | |
| */ | |
| glob.setTld = function(tld) { | |
| var xYaDomain = tld === 'tr' ? 'yandex.com.tr' : 'yandex.' + tld, | |
| yaDomain = ['ua', 'by', 'kz'].indexOf(tld) !== -1 ? 'yandex.ru' : xYaDomain; | |
| glob.tld = tld; | |
| }; | |
| ctxProto._iGlobalBackup = { | |
| _backup: null, | |
| exists: function() { | |
| return Boolean(this._backup); | |
| }, | |
| save: function() { | |
| this._backup = ctxProto.extend({}, glob); | |
| }, | |
| restore: function() { | |
| // Нельзя просто заменить ссылку на i-global, некоторые блоки сохраняют её при инициализации шаблонов. | |
| // Приходится удалять и перезаписывать содержимое. | |
| var p; | |
| for(p in glob) { | |
| delete glob[p]; | |
| } | |
| for(p in this._backup) { | |
| glob[p] = this._backup[p]; | |
| } | |
| } | |
| }; | |
| }); | |
| block('i-global')( | |
| tag()(''), | |
| def()(function() { | |
| var params = this.ctx.params || {}, | |
| iGlobal = this['i-global']; | |
| if(!this._iGlobalBackup.exists()) { | |
| this._iGlobalBackup.save(); | |
| } else if(this.ctx.reset !== false) { | |
| this._iGlobalBackup.restore(); | |
| } | |
| if(params.tld && params.tld !== iGlobal.tld) { | |
| iGlobal.setTld(params.tld); | |
| } | |
| for(var p in params) { | |
| iGlobal[p] = params[p]; | |
| } | |
| return ''; | |
| }) | |
| ); | |
| /* end: /Users/invntrm/Ya/Projects/fiji/libs/islands/common.blocks/i-global/i-global.bemhtml.js */ | |
| /* begin: /Users/invntrm/Ya/Projects/fiji/sakhalin/blocks-common/i-global/__params/i-global__params.bemhtml.js */ | |
| oninit(function(exports) { | |
| var glob = exports['i-global']; | |
| [ | |
| 'serpid', | |
| 'query', | |
| ].forEach(function(param) { | |
| glob.makePublic(param); | |
| }); | |
| }); | |
| // // | |
| // // | |
| // // | |
| // // | |
| // >>> templates here <<< // | |
| // // | |
| // // | |
| // // | |
| // // | |
| /* begin: /Users/invntrm/Ya/Projects/fiji/sakhalin/blocks-common/serp-url/serp-url.bemhtml.js */ | |
| /** | |
| * greenurl | |
| * | |
| * @param {String} url – ссылка на ролик на хостинге | |
| * @param {String} content – человекочитаемый текст для greenurl'а | |
| * @param {Array} [mix] | |
| * @param {String} [counter] – данные для счётчика | |
| */ | |
| block('serp-url')( | |
| content()(function() { | |
| return { | |
| elem: 'item', | |
| content: { | |
| elem: 'link', | |
| mix: this.ctx.linkMix, | |
| url: this.ctx.url, | |
| counter: this.ctx.counter, | |
| content: this.ctx.content | |
| } | |
| }; | |
| }), | |
| elem('item').tag()('span'), | |
| elem('link')( | |
| tag()('a'), | |
| attrs()(function() { | |
| var attrs = { | |
| target: '_blank', | |
| href: this.ctx.url | |
| }; | |
| this.ctx.counter && (attrs.onmousedown = this.ctx.counter); | |
| return attrs; | |
| }) | |
| ) | |
| ); | |
| /* end: /Users/invntrm/Ya/Projects/fiji/sakhalin/blocks-common/serp-url/serp-url.bemhtml.js */ | |
| oninit(function(exports, context) { | |
| var BEMContext = exports.BEMContext || context.BEMContext; | |
| // Provides third-party libraries from different modular systems | |
| BEMContext.prototype.require = function(lib) { | |
| return __bem_xjst_libs__[lib]; | |
| }; | |
| });; | |
| }); | |
| api.exportApply(exports); | |
| /// ------------------------------------- | |
| /// ------ BEM-XJST User-code End ------- | |
| /// ------------------------------------- | |
| return exports; | |
| }; | |
| var defineAsGlobal = true; | |
| if(typeof module === "object" && typeof module.exports === "object") { | |
| exports["BEMHTML"] = buildBemXjst({}, {}); | |
| defineAsGlobal = false; | |
| } | |
| if(typeof modules === "object") { | |
| modules.define("BEMHTML", [], function(provide) { | |
| provide(buildBemXjst({}, {})); | |
| }); | |
| defineAsGlobal = false; | |
| } | |
| if(defineAsGlobal) { | |
| BEMHTML = buildBemXjst({}, {}); | |
| global["BEMHTML"] = BEMHTML; | |
| } | |
| })(this); | |
| // End: Standart bemhtml client code | |
| onmessage = function(e) { | |
| e.data && postMessage({ id: e.data.id, html: BEMHTML.apply(e.data.bemjson) }); | |
| }; | |
| }; | |
| // End: worker context function | |
| // Begin: window context stuff | |
| var createWorker = fn => { | |
| var workerFunctionBlob = new Blob(['('+fn.toString()+')()'], { type: "text/javascript" }); | |
| var worker = new Worker(URL.createObjectURL(workerFunctionBlob)); | |
| URL.revokeObjectURL(workerFunctionBlob); // free fake blob url | |
| return worker; | |
| }; | |
| var worker = createWorker(workerFunction); | |
| var callbacks = {}; | |
| worker.onmessage = function(e) { | |
| if (!e.data || !e.data.id || !callbacks[e.data.id]) return; | |
| callbacks[e.data.id].callback(e.data.html); | |
| callbacks[e.data.id] = undefined; | |
| } | |
| worker.postMessage('Start the worker, compile bemhtml'); | |
| // TODO: add memoization with retating bemjson collection | |
| var BemhtmlWorkerApply = function(bemjson, callback) { | |
| var id = Math.random() * 1e16; | |
| callbacks[id] = { callback: callback }; | |
| worker.postMessage({ bemjson: bemjson, id: id }); | |
| }; | |
| // End: window context stuff | |
| // ---- | |
| // Example: | |
| BemhtmlWorkerApply({ block: 'domik' }, html => { console.log('ololo', html); }) | |
| // perf | |
| // classic: 0.1ms..0.2ms | |
| console.time('domik result'); | |
| BEMHTML.apply({ block: 'domik' }); | |
| console.timeEnd('domik result'); | |
| // parallel: fire: 0.1ms, result: 0.5..1ms (x10 slower) | |
| console.time('domik result');console.time('domik fire') | |
| BemhtmlWorkerApply({ block: 'domik' }, html => { console.timeEnd('domik result'); console.log('ololo', html); }) | |
| console.timeEnd('domik fire'); |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Круто, но
Выгоды от этого не обнаружилось.
Рис. Профайлинг этой балалайки на одном из самых больших шаблонов,
который используется на клиенте в Яндекс.Картинках, который я смог найти:
Непосредственно шаблонизация выполняется и так очень эффективно