bem
Version:
530 lines (399 loc) • 15.8 kB
JavaScript
'use strict';
var Q = require('q'),
U = require('../util'),
PATH = require('../path'),
FS = require('fs'),
BlockNode = require('./block').BlockNodeName,
fileNodes = require('./file'),
BemCreateNode = require('./create'),
BemBuildNode = require('./build'),
BemDeclNode = require('./decl'),
BorschikNode = require('./borschik'),
MagicNode = require('./magic'),
registry = require('../nodesregistry'),
LOGGER = require('../logger'),
BundleNodeName = exports.BundleNodeName = 'BundleNode';
/* jshint -W106 */
exports.__defineGetter__(BundleNodeName, function() {
return registry.getNodeClass(BundleNodeName);
});
/* jshint +W106 */
registry.decl(BundleNodeName, BlockNode, /** @lends BundleNode.prototype */ {
nodeType: 6,
make: function() {
return this.ctx.arch.withLock(this.alterArch(), this);
},
alterArch: function() {
var ctx = this.ctx;
return function() {
// create real node for page
var arch = ctx.arch,
bundleNode;
if (arch.hasNode(this.path)) {
bundleNode = arch.getNode(this.path);
} else {
bundleNode = new fileNodes.FileNode({
root: this.root,
path: this.path
});
arch.setNode(bundleNode, arch.getParents(this).filter(function(p) {
return !(arch.getNode(p) instanceof MagicNode.MagicNode);
}), this);
}
// generate targets for page files
var optTechs = this.getOptimizerTechs();
this.getTechs().map(function(tech) {
var techNode = this.createTechNode(tech, bundleNode, this);
if (techNode && ~optTechs.indexOf(tech)) {
this.createOptimizerNode(tech, techNode, bundleNode);
}
}, this);
return Q.when(this.takeSnapshot('after alterArch BundleNode ' + this.getId()));
};
},
lastModified: function() {
return Q.resolve(0);
},
createTechNode: function(tech, bundleNode, magicNode) {
var f = 'create-' + tech + '-node';
f = typeof this[f] === 'function'? f : 'createDefaultTechNode';
LOGGER.fdebug('Using %s() to create node for tech %s', f, tech);
return this[f].apply(this, arguments);
},
createOptimizerNode: function(tech, sourceNode, bundleNode) {
var f = 'create-' + tech + '-optimizer-node';
f = typeof this[f] === 'function'? f : 'createDefaultOptimizerNode';
LOGGER.fdebug('Using %s() to create optimizer node for tech %s', f, tech);
return this[f].apply(this, arguments);
},
getBundlePath: function(tech) {
return this.level.getPath(this.getNodePrefix(), tech);
},
getTechs: function() {
return [
'bemjson.js',
'bemdecl.js',
'deps.js',
'bemhtml',
'css',
'ie.css',
'js',
'html'
];
},
/**
* Returns an array of the techs to build in a forked process. The techs not in the list will be built inprocess.
* @return {Array}
*/
getForkedTechs: function() {
return [
'bemhtml'
];
},
getOptimizerTechs: function() {
return this.getTechs();
},
cleanup: function() {
var arch = this.ctx.arch;
if (!arch.hasNode(this.path)) return;
arch.removeTree(this.path);
},
/**
* Constructs array of levels to build tech from.
*
* @param {String} tech Tech name.
* @return {Array} Array of levels.
*/
getLevels: function(tech) {
return (this.level.getConfig().bundleBuildLevels || [])
.concat([PATH.resolve(this.root, PATH.dirname(this.getNodePrefix()), 'blocks')]);
},
/**
* Checks that dependencies for specified node are met (appropriate nodes exist in the arch) or that node's file is
* already exists on the file system.
* @param node
* @return {Node} Specified node if dependencies are met, FileNode if not but file does exist, null otherwise.
*/
useFileOrBuild: function(node) {
var deps = node.getDependencies();
for(var i = 0, l = deps.length; i < l; i++) {
var d = deps[i];
if (!this.ctx.arch.hasNode(d)) {
LOGGER.fverbose('Dependency %s is required to build %s but does not exist, checking if target already built', d, node.getId());
if (!PATH.existsSync(node.getPath())) {
LOGGER.fwarn('%s will not be built because dependency file %s does not exist', node.path, d);
return null;
}
return new fileNodes.FileNode({
root: this.root,
path: node.getId()
});
}
}
return node;
},
/**
* Create a bem build node, add it to the arch, add
* dependencies to it. Then create a meta node and link
* it to the build node.
*
* @param {String} techName
* @param {String} techPath
* @param {String} declPath
* @param {String} bundleNode
* @param {String} magicNode
* @param {Boolean} [forked]
* @return {Node}
*/
setBemBuildNode: function(techName, techPath, declPath, bundleNode, magicNode, forked) {
var arch = this.ctx.arch,
buildNode = new BemBuildNode.BemBuildNode({
root: this.root,
bundlesLevel: this.level,
levels: this.getLevels(techName),
declPath: declPath,
techPath: techPath,
techName: techName,
output: this.getNodePrefix(),
forked: forked === undefined? ~this.getForkedTechs().indexOf(techName): forked
});
var tech = this.level.getTech(techName, techPath),
metaNode;
/* techs-v2: don't create meta node */
if (tech.API_VER !== 2) metaNode = buildNode.getMetaNode();
// Set bem build node to arch and add dependencies to it
arch.setNode(buildNode)
.addChildren(buildNode, buildNode.getDependencies());
// Add file aliases to arch and link with buildNode as parents
buildNode.getFiles().forEach(function(f) {
if (buildNode.getId() === f) return;
var alias = new fileNodes.FileNode({ path: f, root: this.root });
arch.setNode(alias).addParents(buildNode, alias);
}, this);
bundleNode && arch.addParents(buildNode, bundleNode);
magicNode && arch.addChildren(buildNode, magicNode);
/* techs-v2: don't add dependency on meta node */
if (metaNode) {
// Set bem build meta node to arch
arch.setNode(metaNode)
.addParents(metaNode, buildNode)
.addChildren(metaNode, metaNode.getDependencies());
}
return buildNode;
},
/**
* Create a bem create node, add it to the arch,
* add dependencies to it.
*
* @param {String} techName
* @param {String} techPath
* @param {String} bundleNode
* @param {String} magicNode
* @param {Boolean} [force]
* @param {Boolean} [forked]
* @return {Node | undefined}
*/
setBemCreateNode: function(techName, techPath, bundleNode, magicNode, force, forked) {
var arch = this.ctx.arch,
node = this.useFileOrBuild(new BemCreateNode.BemCreateNode({
root: this.root,
level: this.level,
item: this.item,
techPath: techPath,
techName: techName,
force: force,
forked: forked === undefined? ~this.getForkedTechs().indexOf(techName): forked
}));
if (!node) return;
// Set bem create node to arch and add dependencies to it
arch.setNode(node)
.addChildren(node, node.getDependencies());
// Add file aliases to arch and link with node as parents
node.getFiles && node.getFiles().forEach(function(f) {
if (node.getId() === f) return;
var alias = new fileNodes.FileNode({ path: f, root: this.root });
arch.setNode(alias).addParents(node, alias);
}, this);
bundleNode && arch.addParents(node, bundleNode);
magicNode && arch.addChildren(node, magicNode);
return node;
},
/**
* Create file node, add it to the arch, add dependencies to it.
*
* @param {String} tech
* @param {String} bundleNode
* @param {String} magicNode
* @return {Node | undefined}
*/
setFileNode: function(tech, bundleNode, magicNode) {
var arch = this.ctx.arch,
filePath = this.getBundlePath(tech);
if (!PATH.existsSync(PATH.resolve(this.root, filePath))) return;
var node = new fileNodes.FileNode({
root: this.root,
path: filePath
});
arch.setNode(node);
bundleNode && arch.addParents(node, bundleNode);
magicNode && arch.addChildren(node, magicNode);
return node;
},
createDefaultTechNode: function(tech, bundleNode, magicNode) {
return this.setBemBuildNode(
tech,
this.level.resolveTech(tech),
this.getBundlePath('deps.js'),
bundleNode,
magicNode);
},
createDefaultOptimizerNode: function(tech, sourceNode, bundleNode) {},
createBorschikOptimizerNode: function(tech, sourceNode, bundleNode) {
var files = sourceNode.getFiles? sourceNode.getFiles() : [sourceNode.path];
LOGGER.fdebug('Creating borschik nodes for %s', files);
return files.map(function(file) {
var node = new (registry.getNodeClass('BorschikNode'))({
root: this.root,
input: file,
tech: tech,
forked: true
});
this.ctx.arch
.setNode(node)
.addParents(node, bundleNode)
.addChildren(node, sourceNode);
return node;
}, this);
},
'create-bemjson.js-node': function(tech, bundleNode, magicNode) {
return this.setFileNode.apply(this, arguments);
},
'create-bemdecl.js-node': function(tech, bundleNode, magicNode) {
return this.setBemCreateNode(
tech,
this.level.resolveTech(tech),
bundleNode,
magicNode);
},
'create-deps.js-node': function(tech, bundleNode, magicNode) {
return this.setBemBuildNode(
tech,
this.level.resolveTech(tech),
this.getBundlePath('bemdecl.js'),
bundleNode,
magicNode);
},
'create-html-node': function(tech, bundleNode, magicNode) {
return this.setBemCreateNode(
tech,
this.level.resolveTech(tech),
bundleNode,
magicNode);
},
'create-js-optimizer-node': function(tech, sourceNode, bundleNode) {
return this.createBorschikOptimizerNode('js', sourceNode, bundleNode);
},
'create-priv.js-optimizer-node': function(tech, sourceNode, bundleNode) {
return this['create-js-optimizer-node'].apply(this, arguments);
},
'create-bemhtml-optimizer-node': function(tech, sourceNode, bundleNode) {
return this['create-js-optimizer-node'].apply(this, arguments);
},
'create-bemhtml.js-optimizer-node': function(tech, sourceNode, bundleNode) {
return this['create-bemhtml-optimizer-node'].apply(this, arguments);
},
'create-css-optimizer-node': function(tech, sourceNode, bundleNode) {
return this.createBorschikOptimizerNode('css-fast', sourceNode, bundleNode);
},
'create-ie.css-optimizer-node': function(tech, sourceNode, bundleNode) {
var nodes = this['create-css-optimizer-node'].apply(this, arguments);
this.ctx.arch.link(this.getBundlePath('css'), nodes);
return nodes;
},
'create-ie6.css-optimizer-node': function(tech, sourceNode, bundleNode) {
var nodes = this['create-ie.css-optimizer-node'].apply(this, arguments);
this.ctx.arch.link(this.getBundlePath('ie.css'), nodes);
return nodes;
},
'create-ie7.css-optimizer-node': function(tech, sourceNode, bundleNode) {
return this['create-ie6.css-optimizer-node'].apply(this, arguments);
},
'create-ie8.css-optimizer-node': function(tech, sourceNode, bundleNode) {
return this['create-ie6.css-optimizer-node'].apply(this, arguments);
},
'create-ie9.css-optimizer-node': function(tech, sourceNode, bundleNode) {
return this['create-ie.css-optimizer-node'].apply(this, arguments);
}
});
var MergedBundleNodeName = exports.MergedBundleNodeName = 'MergedBundleNode';
/* jshint -W106 */
exports.__defineGetter__(MergedBundleNodeName, function() {
return registry.getNodeClass(MergedBundleNodeName);
});
/* jshint +W106 */
registry.decl(MergedBundleNodeName, BundleNodeName, /** @lends MergedBundleNode.prototype */ {
make: function() {
var path = PATH.resolve(this.root, this.path);
if (!PATH.existsSync(path)) FS.mkdirSync(path);
return this.__base();
},
/**
* Overriden. Creates BemDecl node linked to the deps.js nodes of the bundles within containing level.
* @param tech
* @param bundleNode
* @param magicNode
* @return {*}
*/
'create-deps.js-node': function(tech, bundleNode, magicNode) {
var ctx = this.ctx,
arch = ctx.arch,
levelNode = arch.getNode(PATH.relative(this.root, this.level.dir)),
depsTech = this.level.getTech('deps.js').getTechName(),
bundles = arch.getChildren(levelNode)
.filter(function(b) {
var n = arch.getNode(b);
return n instanceof exports.BundleNode && n !== this;
}, this)
.map(function(b) {
return U.getNodeTechPath(this.level, arch.getNode(b).item, depsTech);
}, this);
return this.setBemDeclNode(
tech,
this.level.resolveTech(tech),
bundleNode,
magicNode,
'merge',
bundles);
},
/**
* Creates BemDecl node which maps to 'bem decl [cmd] [decls]'.
* @param techName
* @param techPath
* @param bundleNode
* @param magicNode
* @param cmd Command to execute (merge or substract).
* @param decls Declaration paths to execute command on.
* @param [force]
* @return {Node | undefined}
*/
setBemDeclNode: function(techName, techPath, bundleNode, magicNode, cmd, decls, force) {
var arch = this.ctx.arch,
node = this.useFileOrBuild(new BemDeclNode.BemDeclNode({
root: this.root,
level: this.level,
item: this.item,
techPath: techPath,
techName: techName,
cmd: cmd,
decls: decls,
force: force
}));
if (!node) return;
// Set bem create node to arch and add dependencies to it
arch.setNode(node)
.addChildren(node, node.getDependencies());
bundleNode && arch.addParents(node, bundleNode);
magicNode && arch.addChildren(node, magicNode);
return node;
}
});