From 2caee365a9921ce68e1ff33fefda916831090745 Mon Sep 17 00:00:00 2001 From: blond Date: Mon, 18 Apr 2016 16:22:08 +0300 Subject: [PATCH 01/11] refactor: change the format of level introspection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Example of new format:** ``` { button__text: [ { entity: { block: ‘button’, elem: ‘text’ }, tech: ‘css’, path: ‘path/to/file.ext’, level: ‘path/to/level’ }, /* ... */ ], /* ... */ } ``` With this introspection format to easily get a list of files named entity. **What has been done?** * Use `bem-walk` to scan levels. * Use `bem-naming` to get id of BEM entity. * Rewrite `levels` tech with `build-flow`. * Use promises with `node.buildState` to avoid scanning the same levels multiple times. --- package.json | 3 +- techs/levels.js | 239 ++++++++++++++++++++++++++++++++---------------- 2 files changed, 160 insertions(+), 82 deletions(-) diff --git a/package.json b/package.json index 3300f1c..a035d06 100644 --- a/package.json +++ b/package.json @@ -44,11 +44,13 @@ }, "dependencies": { "bem-naming": "1.0.1", + "bem-walk": "1.0.0-1", "clear-require": "1.0.1", "enb-async-require": "1.0.1", "enb-require-or-eval": "1.0.2", "inherit": "2.2.3", "js-yaml": "3.5.2", + "lodash": "4.11.1", "vow": "0.4.12" }, "devDependencies": { @@ -57,7 +59,6 @@ "istanbul": "0.4.2", "jscs": "2.8.0", "jshint": "2.9.1", - "lodash": "4.0.0", "matcha": "0.6.1", "mocha": "2.3.4", "mock-enb": "0.3.2", diff --git a/techs/levels.js b/techs/levels.js index cc8a813..5c8e483 100644 --- a/techs/levels.js +++ b/techs/levels.js @@ -1,11 +1,11 @@ var path = require('path'), - inherit = require('inherit'), vow = require('vow'), + stringifyEntity = require('bem-naming').stringify, + walk = require('bem-walk'), + uniqBy = require('lodash').uniqBy, enb = require('enb'), vfs = enb.asyncFS || require('enb/lib/fs/async-fs'), - BaseTech = enb.BaseTech || require('enb/lib/tech/base-tech'), - Level = require('../lib/levels/level'), - Levels = require('../lib/levels/levels'); + buildFlow = enb.buildFlow || require('enb/lib/build-flow'); /** * @class LevelsTech @@ -40,85 +40,162 @@ var path = require('path'), * }]); * }; */ -module.exports = inherit(BaseTech, { - getName: function () { - return 'levels'; - }, - - init: function () { - this.__base.apply(this, arguments); - this._levelConfig = this.getRequiredOption('levels'); - this._sublevelDirectories = this.getOption('sublevelDirectories', ['blocks']); - this._target = this.node.unmaskTargetName(this.getOption('target', '?.levels')); - }, - - getTargets: function () { - return [this._target]; - }, - - build: function () { - var _this = this, - root = this.node.getRootDir(), - target = this._target, - levelList = [], - levelsToCache = [], - levelsIndex = {}, - cache = this.node.getNodeCache(target), - levelConfig = _this._levelConfig; - - for (var i = 0, l = levelConfig.length; i < l; i++) { - var levelInfo = levelConfig[i]; - - levelInfo = typeof levelInfo === 'object' ? levelInfo : { path: levelInfo }; - - var levelPath = path.resolve(root, levelInfo.path), - levelKey = 'level:' + levelPath; - if (levelsIndex[levelPath]) { - continue; - } - levelsIndex[levelPath] = true; - if (!this.node.buildState[levelKey]) { - var level = new Level(levelPath); - if (levelInfo.check === false) { - var blocks = cache.get(levelPath); - if (blocks) { - level.loadFromCache(blocks); - } else { - levelsToCache.push(level); - } - } - this.node.buildState[levelKey] = level; - } - levelList.push(this.node.buildState[levelKey]); - } +module.exports = buildFlow.create() + .name('levels') + .target('target', '?.levels') + .defineRequiredOption('levels') + .defineOption('sublevelDirectories', ['blocks']) + .saver(function () {}) + .needRebuild(function () { + return true; + }) + .builder(function () { + var target = this._target, + node = this.node, + scan = this.scanLevel.bind(this); - return vfs.listDir(path.join(_this.node.getRootDir(), _this.node.getPath())) - .then(function (listDir) { - return _this._sublevelDirectories.filter(function (path) { - return listDir.indexOf(path) !== -1; - }); - }) - .then(function (sublevels) { - return vow.all(sublevels.map(function (path) { - var sublevelPath = _this.node.resolvePath(path); - if (!levelsIndex[sublevelPath]) { - levelsIndex[sublevelPath] = true; - levelList.push(new Level(sublevelPath)); - } - })); + return this.getLevels() + .then(function (levels) { + return vow.all(levels.map(scan)) + .then(function (introspections) { + return [levels, introspections]; + }); }) - .then(function () { - return vow.all(levelList.map(function (level) { - return level.load(); - })) - .then(function () { - levelsToCache.forEach(function (level) { - cache.set(level.getPath(), level.getBlocks()); + .spread(function (levels, introspections) { + node.resolveTarget(target, { + levels: levels.map(function (level) { return level.path; }), + introspection: this.mergeIntrospections(introspections) + }); + }, this); + }) + .methods({ + /** + * Returns levels for current bundle. + * + * @returns {{path: string, check: boolean}[]} + */ + getLevels: function () { + var sourceLevels = this._initLevels(this._levels); + + return this._findSublevels() + .then(function (sublevels) { + return uniqBy(sourceLevels.concat(sublevels), 'path'); + }); + }, + /** + * Merges introspection from several levels. + * + * @param {object[]} introspections + * @returns {object} + */ + mergeIntrospections: function (introspections) { + var result = {}; + + introspections.forEach(function (introspection) { + Object.keys(introspection).forEach(function (id) { + result[id] = (result[id] || []).concat(introspection[id] || []); + }); + }); + + return result; + }, + /** + * Scans specified level. + * + * If level has `check: false` option and was scanned previously then introspection will be taken from cache. + * + * If the level of need for several bundles then it will be scanned only once. + * + * @param {{path: string, check: boolean}[]} level + * @returns {promise} + */ + scanLevel: function (level) { + var node = this.node, + cache = node.getNodeCache(this._target), + state = node.buildState, + scan = this._forceScanLevel.bind(this), + levelpath = level.path, + key = 'level:' + levelpath, + promise = state[key]; + + if (promise) { return promise; } + + if (level.check === false) { + var data = cache.get(key); + + promise = data ? vow.resolve(data) + : scan(levelpath) + .then(function (introspection) { + cache.set(key, introspection); + + return introspection; }); - _this.node.resolveTarget(target, new Levels(levelList)); - }); + } else { + promise = scan(levelpath); + } + + state[key] = promise; + + return promise; + }, + /** + * Processes the `levels` option. + * + * @returns {{path: string, check: boolean}[]} + */ + _initLevels: function () { + var root = this.node.getRootDir(); + + return this._levels.map(function (level) { + var levelpath = typeof level === 'object' ? level.path : level; + + return { + path: path.resolve(root, levelpath), + check: level.hasOwnProperty('check') ? level.check : true + }; }); - }, + }, + /** + * Finds special levels for current bundle. + * + * @returns {{path: string, check: boolean}[]} + */ + _findSublevels: function () { + var dir = path.join(this.node.getDir()), + patterns = this._sublevelDirectories; + + return vfs.listDir(dir) + .then(function (basenames) { + return basenames + .filter(function (basename) { + return patterns.indexOf(basename) !== -1; + }) + .map(function (basename) { + return { path: path.join(dir, basename), check: false }; + }); + }); + }, + /** + * Scans specified level. + * + * The cache of current node will be ignored and scan happen again. + * + * @param {string} levelpath - path to level. + * @returns {promise} + */ + _forceScanLevel: function (levelpath) { + return new vow.Promise(function (resolve, reject) { + var data = {}; - clean: function () {} -}); + walk([levelpath]) + .on('data', function (file) { + var id = stringifyEntity(file.entity); + + (data[id] || (data[id] = [])).push(file); + }) + .on('error', reject) + .on('end', function () { resolve(data); }); + }); + } + }) + .createTech(); From 8f34ce76f03f7ed106c0d2db3b3c0a5dc3ecb87b Mon Sep 17 00:00:00 2001 From: blond Date: Mon, 18 Apr 2016 16:23:17 +0300 Subject: [PATCH 02/11] test: fix tests for `levels` tech --- test/techs/levels.test.js | 304 +++++++++++++++++++++++++++----------- 1 file changed, 215 insertions(+), 89 deletions(-) diff --git a/test/techs/levels.test.js b/test/techs/levels.test.js index b3b6f6d..8a8b8d3 100644 --- a/test/techs/levels.test.js +++ b/test/techs/levels.test.js @@ -1,7 +1,6 @@ var path = require('path'), mockFs = require('mock-fs'), - naming = require('bem-naming'), TestNode = require('mock-enb/lib/mock-node'), Tech = require('../utils/techs').levels; @@ -19,9 +18,21 @@ describe('techs: levels', function () { } } }, - files = ['blocks/block/block.ext']; + expected = { + block: [ + { + entity: { block: 'block' }, + tech: 'ext', + path: path.resolve('./blocks/block/block.ext'), + level: path.resolve('./blocks') + } + ] + }; - return hasFiles(scheme, files); + return assert(scheme, { + levels: [path.resolve('blocks')], + introspection: expected + }); }); it('must detect block dir in level', function () { @@ -32,9 +43,21 @@ describe('techs: levels', function () { } } }, - dirs = ['blocks/block/block.ext']; + expected = { + block: [ + { + entity: { block: 'block' }, + tech: 'ext', + path: path.resolve('./blocks/block/block.ext'), + level: path.resolve('./blocks') + } + ] + }; - return hasDirs(scheme, dirs); + return assert(scheme, { + levels: [path.resolve('blocks')], + introspection: expected + }); }); it('must detect boolean mod file of block in level', function () { @@ -47,9 +70,21 @@ describe('techs: levels', function () { } } }, - files = ['blocks/block/_bool-mod/block_bool-mod.ext']; + expected = { + 'block_bool-mod': [ + { + entity: { block: 'block', modName: 'bool-mod', modVal: true }, + tech: 'ext', + path: path.resolve('./blocks/block/_bool-mod/block_bool-mod.ext'), + level: path.resolve('./blocks') + } + ] + }; - return hasFiles(scheme, files); + return assert(scheme, { + levels: [path.resolve('blocks')], + introspection: expected + }); }); it('must detect boolean mod dir of block in level', function () { @@ -62,9 +97,21 @@ describe('techs: levels', function () { } } }, - dirs = ['blocks/block/_bool-mod/block_bool-mod.ext']; + expected = { + 'block_bool-mod': [ + { + entity: { block: 'block', modName: 'bool-mod', modVal: true }, + tech: 'ext', + path: path.resolve('./blocks/block/_bool-mod/block_bool-mod.ext'), + level: path.resolve('./blocks') + } + ] + }; - return hasDirs(scheme, dirs); + return assert(scheme, { + levels: [path.resolve('blocks')], + introspection: expected + }); }); it('must detect mod file of block in level', function () { @@ -77,9 +124,21 @@ describe('techs: levels', function () { } } }, - files = ['blocks/block/_mod-name/block_mod-name_mod-val.ext']; + expected = { + 'block_mod-name_mod-val': [ + { + entity: { block: 'block', modName: 'mod-name', modVal: 'mod-val' }, + tech: 'ext', + path: path.resolve('./blocks/block/_mod-name/block_mod-name_mod-val.ext'), + level: path.resolve('./blocks') + } + ] + }; - return hasFiles(scheme, files); + return assert(scheme, { + levels: [path.resolve('blocks')], + introspection: expected + }); }); it('must detect mod dir of block in level', function () { @@ -92,9 +151,21 @@ describe('techs: levels', function () { } } }, - dirs = ['blocks/block/_mod-name/block_mod-name_mod-val.ext']; + expected = { + 'block_mod-name_mod-val': [ + { + entity: { block: 'block', modName: 'mod-name', modVal: 'mod-val' }, + tech: 'ext', + path: path.resolve('./blocks/block/_mod-name/block_mod-name_mod-val.ext'), + level: path.resolve('./blocks') + } + ] + }; - return hasDirs(scheme, dirs); + return assert(scheme, { + levels: [path.resolve('blocks')], + introspection: expected + }); }); it('must detect elem file of block in level', function () { @@ -107,9 +178,21 @@ describe('techs: levels', function () { } } }, - files = ['blocks/block/__elem-name/block__elem-name.ext']; + expected = { + 'block__elem-name': [ + { + entity: { block: 'block', elem: 'elem-name' }, + tech: 'ext', + path: path.resolve('./blocks/block/__elem-name/block__elem-name.ext'), + level: path.resolve('./blocks') + } + ] + }; - return hasFiles(scheme, files); + return assert(scheme, { + levels: [path.resolve('blocks')], + introspection: expected + }); }); it('must detect elem dir of block in level', function () { @@ -122,9 +205,21 @@ describe('techs: levels', function () { } } }, - dirs = ['blocks/block/__elem-name/block__elem-name.ext']; + expected = { + 'block__elem-name': [ + { + entity: { block: 'block', elem: 'elem-name' }, + tech: 'ext', + path: path.resolve('./blocks/block/__elem-name/block__elem-name.ext'), + level: path.resolve('./blocks') + } + ] + }; - return hasDirs(scheme, dirs); + return assert(scheme, { + levels: [path.resolve('blocks')], + introspection: expected + }); }); it('must detect boolean mod file of elem in level', function () { @@ -139,9 +234,21 @@ describe('techs: levels', function () { } } }, - files = ['blocks/block/__elem-name/_bool-mod/block__elem-name_bool-mod.ext']; + expected = { + 'block__elem-name_bool-mod': [ + { + entity: { block: 'block', elem: 'elem-name', modName: 'bool-mod', modVal: true }, + tech: 'ext', + path: path.resolve('./blocks/block/__elem-name/_bool-mod/block__elem-name_bool-mod.ext'), + level: path.resolve('./blocks') + } + ] + }; - return hasFiles(scheme, files); + return assert(scheme, { + levels: [path.resolve('blocks')], + introspection: expected + }); }); it('must detect boolean mod dir of elem in level', function () { @@ -156,9 +263,21 @@ describe('techs: levels', function () { } } }, - dirs = ['blocks/block/__elem-name/_bool-mod/block__elem-name_bool-mod.ext']; + expected = { + 'block__elem-name_bool-mod': [ + { + entity: { block: 'block', elem: 'elem-name', modName: 'bool-mod', modVal: true }, + tech: 'ext', + path: path.resolve('./blocks/block/__elem-name/_bool-mod/block__elem-name_bool-mod.ext'), + level: path.resolve('./blocks') + } + ] + }; - return hasDirs(scheme, dirs); + return assert(scheme, { + levels: [path.resolve('blocks')], + introspection: expected + }); }); it('must detect mod file of elem in level', function () { @@ -173,9 +292,23 @@ describe('techs: levels', function () { } } }, - files = ['blocks/block/__elem-name/_mod-name/block__elem-name_mod-name_mod-val.ext']; + expected = { + 'block__elem-name_mod-name_mod-val': [ + { + entity: { block: 'block', elem: 'elem-name', modName: 'mod-name', modVal: 'mod-val' }, + tech: 'ext', + path: path.resolve( + './blocks/block/__elem-name/_mod-name/block__elem-name_mod-name_mod-val.ext' + ), + level: path.resolve('./blocks') + } + ] + }; - return hasFiles(scheme, files); + return assert(scheme, { + levels: [path.resolve('blocks')], + introspection: expected + }); }); it('must detect mod dir of elem in level', function () { @@ -190,9 +323,23 @@ describe('techs: levels', function () { } } }, - dirs = ['blocks/block/__elem-name/_mod-name/block__elem-name_mod-name_mod-val.ext']; + expected = { + 'block__elem-name_mod-name_mod-val': [ + { + entity: { block: 'block', elem: 'elem-name', modName: 'mod-name', modVal: 'mod-val' }, + tech: 'ext', + path: path.resolve( + './blocks/block/__elem-name/_mod-name/block__elem-name_mod-name_mod-val.ext' + ), + level: path.resolve('./blocks') + } + ] + }; - return hasDirs(scheme, dirs); + return assert(scheme, { + levels: [path.resolve('blocks')], + introspection: expected + }); }); it('must detect block files in levels', function () { @@ -208,12 +355,27 @@ describe('techs: levels', function () { } } }, - files = [ - 'level-1/block/block.ext', - 'level-2/block/block.ext' - ]; + expected = { + block: [ + { + entity: { block: 'block' }, + tech: 'ext', + path: path.resolve('./level-1/block/block.ext'), + level: path.resolve('./level-1') + }, + { + entity: { block: 'block' }, + tech: 'ext', + path: path.resolve('./level-2/block/block.ext'), + level: path.resolve('./level-2') + } + ] + }; - return hasFiles(scheme, files); + return assert(scheme, { + levels: [path.resolve('level-1'), path.resolve('level-2')], + introspection: expected + }); }); it('must detect block dirs in levels', function () { @@ -229,38 +391,31 @@ describe('techs: levels', function () { } } }, - dirs = [ - 'level-1/block/block.ext', - 'level-2/block/block.ext' - ]; - - return hasDirs(scheme, dirs); - }); - - it('must handle full paths', function () { - mockFs({ - blocks: { - block: { - 'block.ext': '' - } - }, - bundle: {} - }); - - var bundle = new TestNode('bundle'), - levelDirname = path.resolve('blocks'); - - return bundle.runTech(Tech, { levels: [levelDirname] }) - .then(function (levels) { - var files = getEntityFiles(levels, 'block', 'files'); + expected = { + block: [ + { + entity: { block: 'block' }, + tech: 'ext', + path: path.resolve('./level-1/block/block.ext'), + level: path.resolve('./level-1') + }, + { + entity: { block: 'block' }, + tech: 'ext', + path: path.resolve('./level-2/block/block.ext'), + level: path.resolve('./level-2') + } + ] + }; - files[0].name.must.be('block.ext'); - files.must.have.length(1); + return assert(scheme, { + levels: [path.resolve('level-1'), path.resolve('level-2')], + introspection: expected }); }); }); -function getLevels(fsScheme) { +function assert(fsScheme, expected) { var levels = Object.keys(fsScheme), bundle; @@ -269,37 +424,8 @@ function getLevels(fsScheme) { bundle = new TestNode('bundle'); - return bundle.runTech(Tech, { levels: levels }); -} - -function getEntityFiles(levels, entity, filetype) { - var notation = naming.parse(entity); - - if (notation.elem) { - return levels.getElemEntities(notation.block, notation.elem, notation.modName, notation.modVal)[filetype]; - } else { - return levels.getBlockEntities(notation.block, notation.modName, notation.modVal)[filetype]; - } -} - -function has(fsScheme, filenames, filetype) { - return getLevels(fsScheme) - .then(function (levels) { - filenames.forEach(function (filename, i) { - var basename = path.basename(filename).split('.')[0], - fullname = path.resolve(filename), - files = getEntityFiles(levels, basename, filetype); - - files.must.have.length(filenames.length); - files[i].fullname.must.be(fullname); - }); + return bundle.runTech(Tech, { levels: levels }) + .then(function (data) { + return data.must.eql(expected); }); } - -function hasFiles(fsScheme, filenames) { - return has(fsScheme, filenames, 'files'); -} - -function hasDirs(fsScheme, filenames) { - return has(fsScheme, filenames, 'dirs'); -} From 816c4983a790352f09841a938566be65a6b9c901 Mon Sep 17 00:00:00 2001 From: blond Date: Mon, 18 Apr 2016 16:25:21 +0300 Subject: [PATCH 03/11] refactor: remove `level` and `levels` classes Now the `bem-walk` is used to scan. --- lib/levels/level.js | 219 ------------------------------------------- lib/levels/levels.js | 215 ------------------------------------------ 2 files changed, 434 deletions(-) delete mode 100644 lib/levels/level.js delete mode 100644 lib/levels/levels.js diff --git a/lib/levels/level.js b/lib/levels/level.js deleted file mode 100644 index d8bf4be..0000000 --- a/lib/levels/level.js +++ /dev/null @@ -1,219 +0,0 @@ -var inherit = require('inherit'), - fs = require('fs'), - vow = require('vow'), - path = require('path'); - -module.exports = inherit(/** @lends Level.prototype */{ - /** - * @constructs Level - * @classdesc Model of level. - * - * @param {String} levelPath — path to level. - */ - __constructor: function (levelPath) { - this._path = levelPath; - this.blocks = {}; - this._loadDeferred = vow.defer(); - }, - - /** - * Load level from cache. - */ - loadFromCache: function (data) { - this.blocks = data; - this._loadDeferred.resolve(this); - }, - - /** - * Returns blocks info. - * - * @returns {Object} - */ - getBlocks: function () { - return this.blocks; - }, - - /** - * Checks whether there is a block with the specified name. - * - * @param {String} blockName - * @returns {Boolean} - */ - hasBlock: function (blockName) { - return this.blocks[blockName]; - }, - - /** - * Returns absolute path to level. - * - * @returns {String} - */ - getPath: function () { - return this._path; - }, - - /** - * Processes file, adds it to `blocks` filed. - * - * @param {String} filename - * @param {Object} stat node.js Stat - * @param {String} parentElementName - * @param {String} elementName - * @param {String} modName - * @private - */ - _processFile: function (filename, stat, parentElementName, elementName, modName) { - var requiredBaseNameWithoutExt = (parentElementName ? parentElementName + '__' : '') + - elementName + (modName ? '_' + modName : ''), - baseName = filename.split(path.sep).slice(-1)[0], - baseNameParts = baseName.split('.'), - baseNameWithoutExt = stat.isDirectory() ? - baseNameParts.slice(0, baseNameParts.length - 1).join('.') : - baseNameParts[0], - rl = requiredBaseNameWithoutExt.length, - modVal, - processFile = baseNameWithoutExt.indexOf(requiredBaseNameWithoutExt) === 0 && ( - modName ? - (rl === baseNameWithoutExt.length) || baseNameWithoutExt.charAt(rl) === '_' : - baseNameWithoutExt === requiredBaseNameWithoutExt - ); - if (processFile) { - var suffix = stat.isDirectory() ? baseNameParts.pop() : baseNameParts.slice(1).join('.'), - fileInfo = { - name: baseName, - fullname: filename, - suffix: suffix, - mtime: stat.mtime.getTime(), - isDirectory: stat.isDirectory() - }; - if (fileInfo.isDirectory) { - fileInfo.files = filterFiles(fs.readdirSync(filename)).map(function (subFilename) { - var subFullname = path.join(filename, subFilename), - subStat = fs.statSync(subFullname); - return { - name: subFilename, - fullname: subFullname, - suffix: subFilename.split('.').slice(1).join('.'), - mtime: subStat.mtime.getTime(), - isDirectory: subStat.isDirectory() - }; - }); - } - var blockName = parentElementName || elementName, - block = this.blocks[blockName] || (this.blocks[blockName] = { - name: blockName, - files: [], - dirs: [], - elements: {}, - mods: {} - }), - destElement; - if (parentElementName) { - destElement = block.elements[elementName] || (block.elements[elementName] = { - name: elementName, - files: [], - dirs: [], - mods: {} - }); - } else { - destElement = block; - } - var collectionKey = fileInfo.isDirectory ? 'dirs' : 'files'; - if (modName) { - if (!modVal) { - if (rl !== baseNameWithoutExt.length) { - modVal = baseNameWithoutExt.substr(rl + 1); - } else { - modVal = '*'; - } - } - var mod = destElement.mods[modName] || (destElement.mods[modName] = {}), - modValueFiles = (mod[modVal] || (mod[modVal] = { files: [], dirs: [] }))[collectionKey]; - modValueFiles.push(fileInfo); - } else { - destElement[collectionKey].push(fileInfo); - } - } - }, - - /** - * Loads files and directories in modifier directory. - * - * @param {String} parentElementName - * @param {String} elementName - * @param {String} modName - * @param {String} modDirPath - * @private - */ - _loadMod: function (parentElementName, elementName, modName, modDirPath) { - var _this = this; - filterFiles(fs.readdirSync(modDirPath)).forEach(function (filename) { - var fullname = path.join(modDirPath, filename), - stat = fs.statSync(fullname); - _this._processFile(fullname, stat, parentElementName, elementName, modName); - }); - }, - - /** - * Loads files and directories in element or block directory (if `parentElementName` is not specifed). - * - * @param {String} parentElementName - * @param {String} elementName - * @param {String} elementDirPath - * @param {String} containsElements - * @private - */ - _loadElement: function (parentElementName, elementName, elementDirPath, containsElements) { - var _this = this, - requiredBaseNameWithoutExt = (parentElementName ? parentElementName + '__' : '') + elementName; - filterFiles(fs.readdirSync(elementDirPath)).forEach(function (filename) { - var fullname = path.join(elementDirPath, filename), - stat = fs.statSync(fullname); - if (stat.isDirectory()) { - if (containsElements && filename.substr(0, 2) === '__') { - _this._loadElement(elementName, filename.substr(2), fullname, false); - } else if (filename.charAt(0) === '_') { - _this._loadMod(parentElementName, elementName, filename.substr(1), fullname); - } else if (filename.indexOf('.') !== -1 && filename.indexOf(requiredBaseNameWithoutExt + '.') === 0) { - _this._processFile(fullname, stat, parentElementName, elementName); - } else if (containsElements) { - _this._loadElement(elementName, filename, fullname, false); - } - } else if (stat.isFile()) { - _this._processFile(fullname, stat, parentElementName, elementName); - } - }); - }, - - /** - * Loads a level. - * - * The structure of blocks, elements and modifiers stored in `blocks` filed. - */ - load: function () { - var deferred = this._loadDeferred, - promise = deferred.promise(); - if (promise.isFulfilled()) { - return promise; - } - - var _this = this, - levelPath = this._path; - - filterFiles(fs.readdirSync(levelPath)).forEach(function (blockDir) { - var blockDirPath = path.join(levelPath, blockDir); - if (fs.statSync(blockDirPath).isDirectory()) { - _this._loadElement(null, blockDir, blockDirPath, true); - } - }); - deferred.resolve(this); - - return promise; - } -}); - -function filterFiles(filenames) { - return filenames.filter(function (filename) { - return filename.charAt(0) !== '.'; - }); -} diff --git a/lib/levels/levels.js b/lib/levels/levels.js deleted file mode 100644 index 55ffcc7..0000000 --- a/lib/levels/levels.js +++ /dev/null @@ -1,215 +0,0 @@ -var inherit = require('inherit'); - -module.exports = inherit(/** @lends Levels.prototype */{ - /** - * @constructs Levels - * @classdesc Works with list of levels. - * @see {@link Level} - * - * @param {Level[]} items — levels instances. - */ - __constructor: function (items) { - this.items = items; - }, - - /** - * Returns info about blocks. - * - * @param {String} blockName - * @returns {Object[]} - */ - getBlocks: function (blockName) { - var block, - blocks = []; - for (var i = 0, l = this.items.length; i < l; i++) { - block = this.items[i].blocks[blockName]; - if (block) { - blocks.push(block); - } - } - return blocks; - }, - - /** - * Returns info about elements. - * - * @param {String} blockName - * @param {String} elemName - * @returns {Object[]} - */ - getElems: function (blockName, elemName) { - var block, - elements = []; - for (var i = 0, l = this.items.length; i < l; i++) { - block = this.items[i].blocks[blockName]; - if (block && block.elements[elemName]) { - elements.push(block.elements[elemName]); - } - } - return elements; - }, - - /** - * Returns files and directories for block or modifier of block. - * - * @param {String} blockName - * @param {String} modName - * @param {String} modVal - * @returns {{files: Object[], dirs: Object[]}} - */ - getBlockEntities: function (blockName, modName, modVal) { - var block, - files = [], - dirs = [], - blocks = this.getBlocks(blockName); - - modVal || (modVal = '*'); - - for (var i = 0, l = blocks.length; i < l; i++) { - block = blocks[i]; - if (modName) { - var mods = block.mods[modName], - res = mods && (mods[modVal] || mods['*']); - - if (res) { - files = files.concat(res.files); - dirs = dirs.concat(res.dirs); - } - } else { - files = files.concat(block.files); - dirs = dirs.concat(block.dirs); - } - } - return { files: files, dirs: dirs }; - }, - - /** - * Returns files and directories for element or modifier of element. - * - * @param {String} blockName - * @param {String} elemName - * @param {String} modName - * @param {String} modVal - * @returns {{files: Object[], dirs: Object[]}} - */ - getElemEntities: function (blockName, elemName, modName, modVal) { - var elem, - files = [], - dirs = [], - elems = this.getElems(blockName, elemName); - - modVal || (modVal = '*'); - - for (var i = 0, l = elems.length; i < l; i++) { - elem = elems[i]; - if (modName) { - var mods = elem.mods[modName], - res = mods && (mods[modVal] || mods['*']); - - if (res) { - files = files.concat(res.files); - dirs = dirs.concat(res.dirs); - } - } else { - files = files.concat(elem.files); - dirs = dirs.concat(elem.dirs); - } - } - return { files: files, dirs: dirs }; - }, - - /** - * Returns files for block or modifier of block. - * - * @param {String} blockName - * @param {String} modName - * @param {String} modVal - * @returns {Object[]} - */ - getBlockFiles: function (blockName, modName, modVal) { - return this.getBlockEntities(blockName, modName, modVal).files; - }, - - /** - * Returns files for element or modifier of element. - * - * @param {String} blockName - * @param {String} elemName - * @param {String} modName - * @param {String} modVal - * @returns {Object[]} - */ - getElemFiles: function (blockName, elemName, modName, modVal) { - return this.getElemEntities(blockName, elemName, modName, modVal).files; - }, - - /** - * Returns files by declaration. - * - * @param {String} blockName - * @param {String} elemName - * @param {String} modName - * @param {String} modVal - * @returns {Object[]} - */ - getFilesByDecl: function (blockName, elemName, modName, modVal) { - if (elemName) { - return this.getElemFiles(blockName, elemName, modName, modVal); - } else { - return this.getBlockFiles(blockName, modName, modVal); - } - }, - - /** - * Returns files by suffix. - * - * @param {String} suffix - * @returns {Object[]} - */ - getFilesBySuffix: function (suffix) { - var files = []; - this.items.forEach(function (level) { - var blocks = level.blocks; - Object.keys(blocks).forEach(function (blockName) { - files = files.concat(getFilesInElementBySuffix(blocks[blockName], suffix)); - }); - }); - return files; - }, - - /** - * Returns value of block modifier. - * - * @param {String} blockName - * @param {String} modName - * @returns {String[]} - */ - getModValues: function (blockName, modName) { - var modVals = []; - this.items.forEach(function (level) { - var blockInfo = level.blocks[blockName]; - if (blockInfo && blockInfo.mods && blockInfo.mods[modName]) { - modVals = modVals.concat(Object.keys(blockInfo.mods[modName])); - } - }); - return modVals; - } -}); - -function getFilesInElementBySuffix(element, suffix) { - var files = element.files.filter(function (f) { return f.suffix === suffix; }), - mods = element.mods, - elements = element.elements; - Object.keys(mods).forEach(function (modName) { - var mod = mods[modName]; - Object.keys(mod).forEach(function (modVal) { - files = files.concat(mod[modVal].files.filter(function (f) { return f.suffix === suffix; })); - }); - }); - if (elements) { - Object.keys(elements).forEach(function (elemName) { - files = files.concat(getFilesInElementBySuffix(elements[elemName], suffix)); - }); - } - return files; -} From 2659cdcf58a76bcc75ea9c35fd10dc50083c382b Mon Sep 17 00:00:00 2001 From: blond Date: Mon, 18 Apr 2016 16:35:47 +0300 Subject: [PATCH 04/11] refactor: support new introspection format for `files` tech --- techs/files.js | 157 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 106 insertions(+), 51 deletions(-) diff --git a/techs/files.js b/techs/files.js index adcc64e..94c937b 100644 --- a/techs/files.js +++ b/techs/files.js @@ -1,10 +1,15 @@ -var inherit = require('inherit'), +var path = require('path'), + + inherit = require('inherit'), vow = require('vow'), - deps = require('../lib/deps/deps'), enb = require('enb'), - BaseTech = enb.BaseTech || require('enb/lib/tech/base-tech'), asyncRequire = require('enb-async-require'), clearRequire = require('clear-require'), + stringifyEntity = require('bem-naming').stringify, + + deps = require('../lib/deps/deps'), + vfs = enb.asyncFs, + BaseTech = enb.BaseTech || require('enb/lib/tech/base-tech'), FileList = enb.FileList || require('enb/lib/file-list'); /** @@ -77,61 +82,46 @@ module.exports = inherit(BaseTech, { dirsTarget = this._dirsTarget; return this.node.requireSources([this._depsFile, this._levelsTarget]) - .spread(function (data, levels) { + .spread(function (data, levelsInfo) { return requireSourceDeps(data, depsFilename) .then(function (sourceDeps) { var fileList = new FileList(), dirList = new FileList(), - levelPaths = levels.items.map(function (level) { - return level.getPath(); - }), - hash = {}; - - for (var i = 0, l = sourceDeps.length; i < l; i++) { - var dep = sourceDeps[i], - entities; - - if (dep.elem) { - entities = levels.getElemEntities(dep.block, dep.elem, dep.mod, dep.val); - } else { - entities = levels.getBlockEntities(dep.block, dep.mod, dep.val); - } - - slice(entities.files.filter(filter)).forEach(fileList.addFiles.bind(fileList)); - slice(entities.dirs.filter(filter)).forEach(dirList.addFiles.bind(dirList)); - } - - function slice(files) { - var slices = levelPaths.map(function () { return []; }), - uniqs = {}; - - files.forEach(function iterate(file) { - var filename = file.fullname; - - levelPaths.forEach(function (levelPath, index) { - if (!uniqs[filename] && filename.indexOf(levelPath) === 0) { - slices[index].push(file); - uniqs[filename] = true; - } + levels = levelsInfo.levels, + introspection = levelsInfo.introspection, + uniqs = {}; + + return vow.all(sourceDeps.map(function (entity) { + var id = stringifyEntity(entity); + + if (uniqs[id]) { return []; } + uniqs[id] = true; + + var files = introspection[id] || []; + var slices = slice(files, levels); + + return vow.all(slices.map(function (slice) { + return vow.all(slice.map(getFileInfo)); + })); + })) + .then(function (res) { + res.forEach(function (slices) { + slices.forEach(function (slice) { + var files = []; + var dirs = []; + + slice.forEach(function (file) { + file.isDirectory ? dirs.push(file) : files.push(file); + }); + + fileList.addFiles(files); + dirList.addFiles(dirs); }); }); - return slices; - } - - function filter(file) { - var filename = file.fullname; - - if (hash[filename]) { - return false; - } - - hash[filename] = true; - return true; - } - - _this.node.resolveTarget(filesTarget, fileList); - _this.node.resolveTarget(dirsTarget, dirList); + _this.node.resolveTarget(filesTarget, fileList); + _this.node.resolveTarget(dirsTarget, dirList); + }); }); }); }, @@ -139,6 +129,60 @@ module.exports = inherit(BaseTech, { clean: function () {} }); +/** + * Slices files into sub lists by levels. + * + * @returns {object[]} + */ +function slice(files, levels) { + var slices = levels.map(function () { return []; }); + + files.forEach(function iterate(file) { + levels.forEach(function (level, index) { + if (file.level === level) { + slices[index].push(file); + } + }); + }); + + return slices; +} + +/** + * Returns info about file for ENB FileList. + * + * @param {object} file - info about file. + * @returns {promise} + */ +function getFileInfo(file) { + var filename = file.path; + + return vfs.stat(filename) + .then(function (stats) { + var isDirectory = stats.isDirectory(); + var info = { + fullname: filename, + name: path.basename(filename), + suffix: file.tech, + mtime: stats.mtime, + isDirectory: isDirectory + }; + + if (!isDirectory) { + return info; + } + + return vfs.listDir(filename) + .then(function (basenames) { + info.files = basenames.map(function (basename) { + return FileList.getFileInfo(path.join(filename, basename)); + }); + + return info; + }); + }); +} + function requireSourceDeps(data, filename) { return (data ? vow.resolve(data) : ( clearRequire(filename), @@ -150,5 +194,16 @@ function requireSourceDeps(data, filename) { } return Array.isArray(sourceDeps) ? sourceDeps : sourceDeps.deps; + }) + .then(function (sourceDeps) { + return sourceDeps.map(function (dep) { + var entity = { block: dep.block }; + + dep.elem && (entity.elem = dep.elem); + dep.mod && (entity.modName = dep.mod); + dep.val && (entity.modVal = dep.val); + + return entity; + }); }); } From ad7fdcb6a10e92b0f06f76f234153fb2256d64e8 Mon Sep 17 00:00:00 2001 From: blond Date: Mon, 18 Apr 2016 16:39:33 +0300 Subject: [PATCH 05/11] refactor: support new introspection format for `levels-to-bemdecl` tech --- techs/levels-to-bemdecl.js | 63 +++++++++++++------------------------- 1 file changed, 22 insertions(+), 41 deletions(-) diff --git a/techs/levels-to-bemdecl.js b/techs/levels-to-bemdecl.js index cc3663b..51c01f0 100644 --- a/techs/levels-to-bemdecl.js +++ b/techs/levels-to-bemdecl.js @@ -1,5 +1,6 @@ var inherit = require('inherit'), enb = require('enb'), + parseEntity = require('bem-naming').parse, vfs = enb.asyncFS || require('enb/lib/fs/async-fs'), BaseTech = enb.BaseTech || require('enb/lib/tech/base-tech'), deps = require('../lib/deps/deps'), @@ -57,24 +58,37 @@ module.exports = inherit(BaseTech, { bemdeclFormat = this._bemdeclFormat, cache = node.getNodeCache(target); - return node.requireSources([this._source]).spread(function (levels) { + return node.requireSources([this._source]).spread(function (levelsInfo) { if (cache.needRebuildFile('bemdecl-file', bemdeclFilename)) { var resDeps = [], decl = [], data, str; - levels.items.forEach(function (level) { - Object.keys(level.blocks).forEach(function (name) { - var block = level.blocks[name]; + Object.keys(levelsInfo.introspection).forEach(function (name) { + var entity = parseEntity(name); + resDeps.push({ block: entity.block }); + + if (entity.elem) { resDeps.push({ - block: name + block: entity.block, + elem: entity.elem }); + } - processMods(resDeps, name, block.mods); - processElems(resDeps, name, block.elements); - }); + if (entity.modName) { + var dep = { + block: entity.block, + mod: entity.modName + }; + + dep.elem = entity.elem; + + entity.modVal && (dep.val = entity.modVal); + + resDeps.push(dep); + } }); if (bemdeclFormat === 'deps') { @@ -105,36 +119,3 @@ module.exports = inherit(BaseTech, { }); } }); - -function processElems(deps, block, elems) { - if (elems) { - Object.keys(elems).forEach(function (elemName) { - deps.push({ - block: block, - elem: elemName - }); - - processMods(deps, block, elems[elemName].mods, elemName); - }); - } -} - -function processMods(deps, block, mods, elem) { - if (mods) { - Object.keys(mods).forEach(function (modName) { - var vals = Object.keys(mods[modName]); - - vals.forEach(function (val) { - var dep = { - block: block, - mod: modName, - val: val === '*' ? true : val - }; - - elem && (dep.elem = elem); - - deps.push(dep); - }); - }); - } -} From 7c15693bafc0497a82de9c6712a3cf34f0db4bd9 Mon Sep 17 00:00:00 2001 From: blond Date: Mon, 18 Apr 2016 16:39:45 +0300 Subject: [PATCH 06/11] refactor: support new introspection format for `deps` tech --- lib/deps/deps-resolver.js | 51 +++++++++++++++++++++++---------------- techs/deps.js | 19 +++++++++++---- 2 files changed, 44 insertions(+), 26 deletions(-) diff --git a/lib/deps/deps-resolver.js b/lib/deps/deps-resolver.js index 7f8a9e4..e5c27bf 100644 --- a/lib/deps/deps-resolver.js +++ b/lib/deps/deps-resolver.js @@ -2,6 +2,7 @@ var inherit = require('inherit'), vm = require('vm'), vow = require('vow'), vfs = require('enb/lib/fs/async-fs'), + stringifyEntity = require('bem-naming').stringify, yaml = require('js-yaml'); function DepsError(message) { @@ -18,8 +19,8 @@ module.exports = inherit(/** @lends DepsResolver.prototype */{ * * @param {Level[]} levels */ - __constructor: function (levels) { - this.levels = levels; + __constructor: function (introspection) { + this.introspection = introspection; this.declarations = []; this.resolved = {}; this.declarationIndex = {}; @@ -34,7 +35,6 @@ module.exports = inherit(/** @lends DepsResolver.prototype */{ * @returns {Array} */ normalizeDep: function (dep, blockName, elemName) { - var levels = this.levels; if (typeof dep === 'string') { return [{ name: dep }]; } else { @@ -92,9 +92,7 @@ module.exports = inherit(/** @lends DepsResolver.prototype */{ } Object.keys(modObj).forEach(function (modName) { var modVals = modObj[modName]; - if (modVals === '*') { - modVals = levels.getModValues(dep.block || blockName, modName); - } + if (!Array.isArray(modVals)) { modVals = [modVals]; } @@ -175,15 +173,22 @@ module.exports = inherit(/** @lends DepsResolver.prototype */{ getDeps: function (decl) { var _this = this, mustDecls, - files; - if (decl.elem) { - files = this.levels.getElemFiles(decl.name, decl.elem, decl.modName, decl.modVal); - } else { - files = this.levels.getBlockFiles(decl.name, decl.modName, decl.modVal); - } - files = files.filter(function (file) { - return file.suffix === 'deps.js' || file.suffix === 'deps.yaml'; + files, + introspection = this.introspection, + entity = { block: decl.name }; + + decl.elem && (entity.elem = decl.elem); + decl.modName && (entity.modName = decl.modName); + decl.modVal && (entity.modVal = decl.modVal); + + var id = stringifyEntity(entity); + + files = (introspection[id] || []).filter(function (file) { + var suffix = file.tech; + + return suffix === 'deps.js' || suffix === 'deps.yaml'; }); + var mustDepIndex = {}, shouldDepIndex = {}; mustDepIndex[declKey(decl)] = true; @@ -213,13 +218,17 @@ module.exports = inherit(/** @lends DepsResolver.prototype */{ var shouldDeps = []; function keepWorking(file) { - return vfs.read(file.fullname, 'utf8').then(function (depContent) { - if (file.suffix === 'deps.js') { + var filename = file.path; + + return vfs.read(filename, 'utf8').then(function (depContent) { + var suffix = file.tech; + + if (suffix === 'deps.js') { var depData; try { depData = vm.runInThisContext(depContent); } catch (e) { - throw new Error('Syntax error in file "' + file.fullname + '": ' + e.message); + throw new Error('Syntax error in file "' + filename + '": ' + e.message); } depData = Array.isArray(depData) ? depData : [depData]; depData.forEach(function (dep) { @@ -258,13 +267,13 @@ module.exports = inherit(/** @lends DepsResolver.prototype */{ } } }); - } else if (file.suffix === 'deps.yaml') { + } else if (suffix === 'deps.yaml') { var depYamlStructure = yaml.safeLoad(depContent, { - filename: file.fullname, + filename: filename, strict: true }); if (!Array.isArray(depYamlStructure)) { - throw new Error('Invalid yaml deps structure at: ' + file.fullname); + throw new Error('Invalid yaml deps structure at: ' + filename); } _this.normalizeDeps(depYamlStructure, decl.name, decl.elem).forEach(function (nd) { var key = declKey(nd), @@ -291,7 +300,7 @@ module.exports = inherit(/** @lends DepsResolver.prototype */{ } }).fail(function (err) { if (err instanceof DepsError) { - err.message += ' in file "' + file.fullname + '"'; + err.message += ' in file "' + filename + '"'; } throw err; }); diff --git a/techs/deps.js b/techs/deps.js index b2e5dd5..1f9ea67 100644 --- a/techs/deps.js +++ b/techs/deps.js @@ -1,10 +1,13 @@ var inherit = require('inherit'), vow = require('vow'), enb = require('enb'), - vfs = enb.asyncFS || require('enb/lib/fs/async-fs'), - BaseTech = enb.BaseTech || require('enb/lib/tech/base-tech'), asyncRequire = require('enb-async-require'), clearRequire = require('clear-require'), + _ = require('lodash'), + + FileList = enb.FileList, + vfs = enb.asyncFS || require('enb/lib/fs/async-fs'), + BaseTech = enb.BaseTech || require('enb/lib/tech/base-tech'), DepsResolver = require('../lib/deps/deps-resolver'), deps = require('../lib/deps/deps'); @@ -66,8 +69,14 @@ module.exports = inherit(BaseTech, { declFilename = this.node.resolvePath(this._declFile); return this.node.requireSources([this._levelsTarget, this._declFile]) - .spread(function (levels, sourceDeps) { - var depFiles = levels.getFilesBySuffix('deps.js').concat(levels.getFilesBySuffix('deps.yaml')); + .spread(function (levelsInfo, sourceDeps) { + var introspection = levelsInfo.introspection, + depFiles = _(introspection) + .map(function (files) { return files; }) + .flatten() + .filter(function (file) { return file.tech === 'deps.js' || file.tech === 'deps.yaml'; }) + .map(function (file) { return FileList.getFileInfo(file.path); }) + .value(); if (cache.needRebuildFile('deps-file', targetFilename) || cache.needRebuildFile('decl-file', declFilename) || @@ -75,7 +84,7 @@ module.exports = inherit(BaseTech, { ) { return requireSourceDeps(sourceDeps, declFilename) .then(function (sourceDeps) { - var resolver = new DepsResolver(levels), + var resolver = new DepsResolver(introspection), decls = resolver.normalizeDeps(sourceDeps); return resolver.addDecls(decls) From 6c005aa5fab7a04306059fcbb288e0e5aaad42d5 Mon Sep 17 00:00:00 2001 From: blond Date: Mon, 18 Apr 2016 16:39:53 +0300 Subject: [PATCH 07/11] refactor: support new introspection format for `deps-old` tech --- exlib/deps-old.js | 38 ++++++++++++++++++++++++++------------ techs/deps-old.js | 24 +++++++++++++++++------- 2 files changed, 43 insertions(+), 19 deletions(-) diff --git a/exlib/deps-old.js b/exlib/deps-old.js index 41e5f8f..3530fa9 100644 --- a/exlib/deps-old.js +++ b/exlib/deps-old.js @@ -5,10 +5,14 @@ * Заимствованный. */ -var inherit = require('inherit'); -var vowFs = require('enb/lib/fs/async-fs'); var vm = require('vm'); -var Vow = require('vow'); + +var enb = require('enb'); +var vow = require('vow'); +var inherit = require('inherit'); +var stringifyEntity = require('bem-naming').stringify; + +var vowFs = enb.asyncFs || require('enb/lib/fs/async-fs'); var MustDeps = require('./must-deps'); module.exports.OldDeps = (function () { @@ -185,13 +189,13 @@ module.exports.OldDeps = (function () { var depsCount1 = this.getCount(); var depsCount2; - return Vow.when(this.expandOnceByFS()) + return vow.when(this.expandOnceByFS()) .then(function again(newDeps) { depsCount2 = newDeps.getCount(); if (depsCount1 !== depsCount2) { depsCount1 = depsCount2; - return Vow.when(newDeps.expandOnceByFS(), again); + return vow.when(newDeps.expandOnceByFS(), again); } return newDeps.clone(_this); @@ -228,7 +232,7 @@ module.exports.OldDeps = (function () { return newDeps; }); } else { - return Vow.fulfill(newDeps); + return vow.fulfill(newDeps); } }, @@ -243,20 +247,30 @@ module.exports.OldDeps = (function () { var _this = this; var tech = this.tech; - var files = tech.levels.getFilesByDecl(item.item.block, item.item.elem, item.item.mod, item.item.val) + var dep = item.item; + var entity = { block: dep.block }; + + dep.elem && (entity.elem = dep.elem); + dep.mod && (entity.modName = dep.mod); + dep.val && (entity.modVal = dep.val); + + var id = stringifyEntity(entity); + var files = (tech.levels[id] || []) .filter(function (file) { - return file.suffix === 'deps.js'; + return file.tech === 'deps.js'; }); - var promise = Vow.fulfill(); + var promise = vow.fulfill(); files.forEach(function (file) { + var filename = file.path; + promise = promise.then(function () { - return vowFs.read(file.fullname, 'utf8').then(function (content) { + return vowFs.read(filename, 'utf8').then(function (content) { try { - _this.parse(vm.runInThisContext(content, file.fullname), item); + _this.parse(vm.runInThisContext(content, filename), item); } catch (e) { - throw new Error('Syntax error in file "' + file.fullname + '": ' + e.message); + throw new Error('Syntax error in file "' + filename + '": ' + e.message); } }); }); diff --git a/techs/deps-old.js b/techs/deps-old.js index dd5e308..c9bf5bb 100644 --- a/techs/deps-old.js +++ b/techs/deps-old.js @@ -1,10 +1,14 @@ -var inherit = require('inherit'), - vow = require('vow'), +var vow = require('vow'), + inherit = require('inherit'), enb = require('enb'), - vfs = enb.asyncFS || require('enb/lib/fs/async-fs'), - BaseTech = enb.BaseTech || require('enb/lib/tech/base-tech'), asyncRequire = require('enb-async-require'), clearRequire = require('clear-require'), + _ = require('lodash'), + + vfs = enb.asyncFS || require('enb/lib/fs/async-fs'), + FileList = enb.FileList, + BaseTech = enb.BaseTech || require('enb/lib/tech/base-tech'), + OldDeps = require('../exlib/deps-old').OldDeps, deps = require('../lib/deps/deps'); @@ -73,8 +77,14 @@ module.exports = inherit(BaseTech, { strictMode = this._strict; return node.requireSources([this._levelsTarget, this._declFile]) - .spread(function (levels, sourceDeps) { - var depFiles = levels.getFilesBySuffix('deps.js'); + .spread(function (levelsInfo, sourceDeps) { + var introspection = levelsInfo.introspection, + depFiles = _(introspection) + .map(function (files) { return files; }) + .flatten() + .filter(function (file) { return file.tech === 'deps.js'; }) + .map(function (file) { return FileList.getFileInfo(file.path); }) + .value(); if (cache.needRebuildFile('deps-file', targetFilename) || cache.needRebuildFile('decl-file', declFilename) || @@ -82,7 +92,7 @@ module.exports = inherit(BaseTech, { ) { return requireSourceDeps(sourceDeps, declFilename) .then(function (sourceDeps) { - return (new OldDeps(sourceDeps, strictMode).expandByFS({ levels: levels })) + return (new OldDeps(sourceDeps, strictMode).expandByFS({ levels: introspection })) .then(function (resolvedDeps) { var resultDeps = resolvedDeps.getDeps(), loopPaths = resolvedDeps.getLoops().mustDeps.map(function (loop) { From 7ce5227ee152cf29a9d0201c1799bdd50fa0488b Mon Sep 17 00:00:00 2001 From: blond Date: Mon, 18 Apr 2016 16:40:36 +0300 Subject: [PATCH 08/11] style: fix lint configs --- .jscsrc | 2 -- .jshintrc | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.jscsrc b/.jscsrc index 98d818c..407724d 100644 --- a/.jscsrc +++ b/.jscsrc @@ -18,7 +18,6 @@ "disallowSpacesInNamedFunctionExpression": { "beforeOpeningRoundBrace": true }, - "requireMultipleVarDecl": true, "requireBlocksOnNewline": 1, "disallowPaddingNewlinesInBlocks": true, "disallowSpacesInsideArrayBrackets": "nested", @@ -27,7 +26,6 @@ "disallowQuotedKeysInObjects": "allButReserved", "disallowSpaceAfterObjectKeys": true, "requireCommaBeforeLineBreak": true, - "requireOperatorBeforeLineBreak": true, "disallowSpaceAfterPrefixUnaryOperators": true, "disallowSpaceBeforePostfixUnaryOperators": true, "requireSpaceBeforeBinaryOperators": true, diff --git a/.jshintrc b/.jshintrc index 31717cd..e04404d 100644 --- a/.jshintrc +++ b/.jshintrc @@ -15,5 +15,6 @@ "node": true, "expr": true, - "sub": true + "sub": true, + "laxbreak": true } From a06b6e33d26deb1035c74d987068b389b5f54653 Mon Sep 17 00:00:00 2001 From: blond Date: Fri, 22 Apr 2016 13:41:16 +0300 Subject: [PATCH 09/11] node: remove support Node.js 0.10 and 0.12 --- .travis.yml | 2 -- package.json | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 937f8ab..ff72bef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,6 @@ language: node_js matrix: include: - - node_js: "0.10" - - node_js: "0.12" - node_js: "4" env: COVERALLS=1 - node_js: "5" diff --git a/package.json b/package.json index a035d06..e9d6526 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "Marat Dulin " ], "engines": { - "node": ">= 0.10.0" + "node": ">= 4" }, "peerDependencies": { "enb": ">=0.15.0 <2.0.0" From 8b9db3935256b26153acdaad6e3b554722f60c93 Mon Sep 17 00:00:00 2001 From: blond Date: Mon, 25 Apr 2016 12:28:26 +0300 Subject: [PATCH 10/11] fixup: reduce memory consumption For large projects assembly could fail with error: ``` Segmentation fault: 11 ``` --- .jshintrc | 1 + exlib/deps-old.js | 3 +- lib/deps/deps-resolver.js | 5 +- lib/introspection.js | 52 ++++++++++++++ techs/deps-old.js | 12 +--- techs/deps.js | 12 +--- techs/files.js | 28 ++------ techs/levels-to-bemdecl.js | 51 +++++--------- techs/levels.js | 81 +++++++++------------- test/techs/deps-by-tech-to-bemdecl.test.js | 2 +- test/techs/levels-to-bemdecl.test.js | 7 -- test/techs/levels.test.js | 25 ++++++- 12 files changed, 141 insertions(+), 138 deletions(-) create mode 100644 lib/introspection.js diff --git a/.jshintrc b/.jshintrc index e04404d..dd2eb65 100644 --- a/.jshintrc +++ b/.jshintrc @@ -13,6 +13,7 @@ "unused": true, "node": true, + "esversion": 6, "expr": true, "sub": true, diff --git a/exlib/deps-old.js b/exlib/deps-old.js index 3530fa9..d84a2a5 100644 --- a/exlib/deps-old.js +++ b/exlib/deps-old.js @@ -254,8 +254,7 @@ module.exports.OldDeps = (function () { dep.mod && (entity.modName = dep.mod); dep.val && (entity.modVal = dep.val); - var id = stringifyEntity(entity); - var files = (tech.levels[id] || []) + var files = tech.levels.getFilesByEntity(entity) .filter(function (file) { return file.tech === 'deps.js'; }); diff --git a/lib/deps/deps-resolver.js b/lib/deps/deps-resolver.js index e5c27bf..bd0c9f7 100644 --- a/lib/deps/deps-resolver.js +++ b/lib/deps/deps-resolver.js @@ -2,7 +2,6 @@ var inherit = require('inherit'), vm = require('vm'), vow = require('vow'), vfs = require('enb/lib/fs/async-fs'), - stringifyEntity = require('bem-naming').stringify, yaml = require('js-yaml'); function DepsError(message) { @@ -181,9 +180,7 @@ module.exports = inherit(/** @lends DepsResolver.prototype */{ decl.modName && (entity.modName = decl.modName); decl.modVal && (entity.modVal = decl.modVal); - var id = stringifyEntity(entity); - - files = (introspection[id] || []).filter(function (file) { + files = introspection.getFilesByEntity(entity).filter(function (file) { var suffix = file.tech; return suffix === 'deps.js' || suffix === 'deps.yaml'; diff --git a/lib/introspection.js b/lib/introspection.js new file mode 100644 index 0000000..425da6d --- /dev/null +++ b/lib/introspection.js @@ -0,0 +1,52 @@ +'use strict'; + +const stringifyEntity = require('bem-naming').stringify; +const parseEntity = require('bem-naming').parse; + +module.exports = class Introspection { + constructor (levels, introspections) { + this._levels = levels; + this._introspections = introspections; + } + getLevels () { + return this._levels; + } + getEntities () { + const entities = []; + + this._introspections.forEach(introspection => { + Object.keys(introspection).forEach(id => { + const entity = parseEntity(id); + + entities.push(entity); + }); + }); + + return entities; + } + getFilesByEntity (entity) { + const id = stringifyEntity(entity); + + let files = []; + this._introspections.forEach(introspection => { + files = files.concat(introspection[id] || []); + }); + + return files; + } + getFilesByTechs (techs) { + const files = []; + + this._introspections.forEach(introspection => { + Object.keys(introspection).forEach(id => { + const file = introspection[id]; + + if (techs.indexOf(file.tech) !== -1) { + files.push(file); + } + }); + }); + + return files; + } +}; diff --git a/techs/deps-old.js b/techs/deps-old.js index c9bf5bb..8b8c356 100644 --- a/techs/deps-old.js +++ b/techs/deps-old.js @@ -3,7 +3,6 @@ var vow = require('vow'), enb = require('enb'), asyncRequire = require('enb-async-require'), clearRequire = require('clear-require'), - _ = require('lodash'), vfs = enb.asyncFS || require('enb/lib/fs/async-fs'), FileList = enb.FileList, @@ -77,14 +76,9 @@ module.exports = inherit(BaseTech, { strictMode = this._strict; return node.requireSources([this._levelsTarget, this._declFile]) - .spread(function (levelsInfo, sourceDeps) { - var introspection = levelsInfo.introspection, - depFiles = _(introspection) - .map(function (files) { return files; }) - .flatten() - .filter(function (file) { return file.tech === 'deps.js'; }) - .map(function (file) { return FileList.getFileInfo(file.path); }) - .value(); + .spread(function (introspection, sourceDeps) { + var depFiles = introspection.getFilesByTechs(['deps.js', 'deps.yaml']) + .map(file => FileList.getFileInfo(file.path)); if (cache.needRebuildFile('deps-file', targetFilename) || cache.needRebuildFile('decl-file', declFilename) || diff --git a/techs/deps.js b/techs/deps.js index 1f9ea67..9684108 100644 --- a/techs/deps.js +++ b/techs/deps.js @@ -3,7 +3,6 @@ var inherit = require('inherit'), enb = require('enb'), asyncRequire = require('enb-async-require'), clearRequire = require('clear-require'), - _ = require('lodash'), FileList = enb.FileList, vfs = enb.asyncFS || require('enb/lib/fs/async-fs'), @@ -69,14 +68,9 @@ module.exports = inherit(BaseTech, { declFilename = this.node.resolvePath(this._declFile); return this.node.requireSources([this._levelsTarget, this._declFile]) - .spread(function (levelsInfo, sourceDeps) { - var introspection = levelsInfo.introspection, - depFiles = _(introspection) - .map(function (files) { return files; }) - .flatten() - .filter(function (file) { return file.tech === 'deps.js' || file.tech === 'deps.yaml'; }) - .map(function (file) { return FileList.getFileInfo(file.path); }) - .value(); + .spread(function (introspection, sourceDeps) { + var depFiles = introspection.getFilesByTechs(['deps.js', 'deps.yaml']) + .map(file => FileList.getFileInfo(file.path)); if (cache.needRebuildFile('deps-file', targetFilename) || cache.needRebuildFile('decl-file', declFilename) || diff --git a/techs/files.js b/techs/files.js index 94c937b..7cb2cbd 100644 --- a/techs/files.js +++ b/techs/files.js @@ -82,13 +82,11 @@ module.exports = inherit(BaseTech, { dirsTarget = this._dirsTarget; return this.node.requireSources([this._depsFile, this._levelsTarget]) - .spread(function (data, levelsInfo) { + .spread(function (data, introspection) { return requireSourceDeps(data, depsFilename) .then(function (sourceDeps) { var fileList = new FileList(), dirList = new FileList(), - levels = levelsInfo.levels, - introspection = levelsInfo.introspection, uniqs = {}; return vow.all(sourceDeps.map(function (entity) { @@ -97,8 +95,9 @@ module.exports = inherit(BaseTech, { if (uniqs[id]) { return []; } uniqs[id] = true; - var files = introspection[id] || []; - var slices = slice(files, levels); + var slices = introspection._introspections.map(function (levelIntrospection) { + return levelIntrospection[id] || []; + }); return vow.all(slices.map(function (slice) { return vow.all(slice.map(getFileInfo)); @@ -129,25 +128,6 @@ module.exports = inherit(BaseTech, { clean: function () {} }); -/** - * Slices files into sub lists by levels. - * - * @returns {object[]} - */ -function slice(files, levels) { - var slices = levels.map(function () { return []; }); - - files.forEach(function iterate(file) { - levels.forEach(function (level, index) { - if (file.level === level) { - slices[index].push(file); - } - }); - }); - - return slices; -} - /** * Returns info about file for ENB FileList. * diff --git a/techs/levels-to-bemdecl.js b/techs/levels-to-bemdecl.js index 51c01f0..1d89ce3 100644 --- a/techs/levels-to-bemdecl.js +++ b/techs/levels-to-bemdecl.js @@ -1,11 +1,11 @@ var inherit = require('inherit'), enb = require('enb'), - parseEntity = require('bem-naming').parse, + asyncRequire = require('enb-async-require'), + clearRequire = require('clear-require'), + vfs = enb.asyncFS || require('enb/lib/fs/async-fs'), BaseTech = enb.BaseTech || require('enb/lib/tech/base-tech'), - deps = require('../lib/deps/deps'), - asyncRequire = require('enb-async-require'), - clearRequire = require('clear-require'); + deps = require('../lib/deps/deps'); /** * @class LevelsToBemdeclTech @@ -58,45 +58,28 @@ module.exports = inherit(BaseTech, { bemdeclFormat = this._bemdeclFormat, cache = node.getNodeCache(target); - return node.requireSources([this._source]).spread(function (levelsInfo) { + return node.requireSources([this._source]).spread(function (introspection) { if (cache.needRebuildFile('bemdecl-file', bemdeclFilename)) { - var resDeps = [], - decl = [], - data, - str; - - Object.keys(levelsInfo.introspection).forEach(function (name) { - var entity = parseEntity(name); - - resDeps.push({ block: entity.block }); - - if (entity.elem) { - resDeps.push({ - block: entity.block, - elem: entity.elem - }); - } - - if (entity.modName) { + var resDeps = introspection.getEntities().map(function (entity) { var dep = { - block: entity.block, - mod: entity.modName + block: entity.block }; - dep.elem = entity.elem; - + entity.elem && (dep.elem = entity.elem); + entity.modName && (dep.mod = entity.modName); entity.modVal && (dep.val = entity.modVal); - resDeps.push(dep); - } - }); + return dep; + }), + data, + str; if (bemdeclFormat === 'deps') { - decl = resDeps; - data = { deps: decl }; - str = 'exports.deps = ' + JSON.stringify(decl, null, 4) + ';\n'; + data = { deps: resDeps }; + str = 'exports.deps = ' + JSON.stringify(resDeps, null, 4) + ';\n'; } else { - decl = deps.toBemdecl(resDeps); + var decl = deps.toBemdecl(resDeps); + data = { blocks: decl }; str = 'exports.blocks = ' + JSON.stringify(decl, null, 4) + ';\n'; } diff --git a/techs/levels.js b/techs/levels.js index 5c8e483..9f393fe 100644 --- a/techs/levels.js +++ b/techs/levels.js @@ -1,11 +1,16 @@ -var path = require('path'), - vow = require('vow'), - stringifyEntity = require('bem-naming').stringify, - walk = require('bem-walk'), - uniqBy = require('lodash').uniqBy, - enb = require('enb'), - vfs = enb.asyncFS || require('enb/lib/fs/async-fs'), - buildFlow = enb.buildFlow || require('enb/lib/build-flow'); +'use strict'; + +const path = require('path'); + +const vow = require('vow'); +const enb = require('enb'); +const walk = require('bem-walk'); +const stringifyEntity = require('bem-naming').stringify; +const uniqBy = require('lodash').uniqBy; + +const vfs = enb.asyncFS || require('enb/lib/fs/async-fs'); +const buildFlow = enb.buildFlow || require('enb/lib/build-flow'); +const Introspection = require('../lib/introspection'); /** * @class LevelsTech @@ -50,9 +55,9 @@ module.exports = buildFlow.create() return true; }) .builder(function () { - var target = this._target, - node = this.node, - scan = this.scanLevel.bind(this); + const target = this._target; + const node = this.node; + const scan = this.scanLevel.bind(this); return this.getLevels() .then(function (levels) { @@ -62,10 +67,9 @@ module.exports = buildFlow.create() }); }) .spread(function (levels, introspections) { - node.resolveTarget(target, { - levels: levels.map(function (level) { return level.path; }), - introspection: this.mergeIntrospections(introspections) - }); + const levelPaths = levels.map(function (level) { return level.path; }); + + node.resolveTarget(target, new Introspection(levelPaths, introspections)); }, this); }) .methods({ @@ -75,30 +79,13 @@ module.exports = buildFlow.create() * @returns {{path: string, check: boolean}[]} */ getLevels: function () { - var sourceLevels = this._initLevels(this._levels); + const sourceLevels = this._initLevels(this._levels); return this._findSublevels() .then(function (sublevels) { return uniqBy(sourceLevels.concat(sublevels), 'path'); }); }, - /** - * Merges introspection from several levels. - * - * @param {object[]} introspections - * @returns {object} - */ - mergeIntrospections: function (introspections) { - var result = {}; - - introspections.forEach(function (introspection) { - Object.keys(introspection).forEach(function (id) { - result[id] = (result[id] || []).concat(introspection[id] || []); - }); - }); - - return result; - }, /** * Scans specified level. * @@ -110,14 +97,14 @@ module.exports = buildFlow.create() * @returns {promise} */ scanLevel: function (level) { - var node = this.node, - cache = node.getNodeCache(this._target), - state = node.buildState, - scan = this._forceScanLevel.bind(this), - levelpath = level.path, - key = 'level:' + levelpath, - promise = state[key]; - + const node = this.node; + const cache = node.getNodeCache(this._target); + const state = node.buildState; + const scan = this._forceScanLevel.bind(this); + const levelpath = level.path; + const key = 'level:' + levelpath; + + let promise = state[key]; if (promise) { return promise; } if (level.check === false) { @@ -144,10 +131,10 @@ module.exports = buildFlow.create() * @returns {{path: string, check: boolean}[]} */ _initLevels: function () { - var root = this.node.getRootDir(); + const root = this.node.getRootDir(); return this._levels.map(function (level) { - var levelpath = typeof level === 'object' ? level.path : level; + const levelpath = typeof level === 'object' ? level.path : level; return { path: path.resolve(root, levelpath), @@ -161,8 +148,8 @@ module.exports = buildFlow.create() * @returns {{path: string, check: boolean}[]} */ _findSublevels: function () { - var dir = path.join(this.node.getDir()), - patterns = this._sublevelDirectories; + const dir = path.join(this.node.getDir()); + const patterns = this._sublevelDirectories; return vfs.listDir(dir) .then(function (basenames) { @@ -185,11 +172,11 @@ module.exports = buildFlow.create() */ _forceScanLevel: function (levelpath) { return new vow.Promise(function (resolve, reject) { - var data = {}; + const data = {}; walk([levelpath]) .on('data', function (file) { - var id = stringifyEntity(file.entity); + const id = stringifyEntity(file.entity); (data[id] || (data[id] = [])).push(file); }) diff --git a/test/techs/deps-by-tech-to-bemdecl.test.js b/test/techs/deps-by-tech-to-bemdecl.test.js index 465c747..0e5bfdf 100644 --- a/test/techs/deps-by-tech-to-bemdecl.test.js +++ b/test/techs/deps-by-tech-to-bemdecl.test.js @@ -11,7 +11,7 @@ var path = require('path'), depsTech = techs.deps, depsByTechToBemdecl = techs.depsByTechToBemdecl; -describe('techs: deps', function () { +describe('techs: deps-by-tech-to-bemdecl', function () { afterEach(function () { mockFs.restore(); }); diff --git a/test/techs/levels-to-bemdecl.test.js b/test/techs/levels-to-bemdecl.test.js index 1446970..8bdeb44 100644 --- a/test/techs/levels-to-bemdecl.test.js +++ b/test/techs/levels-to-bemdecl.test.js @@ -49,7 +49,6 @@ describe('techs: levels-to-bemdecl', function () { } }, bemdecl = [ - { name: 'block' }, { name: 'block', mods: [{ name: 'bool-mod', vals: [{ name: true }] }] } ]; @@ -67,7 +66,6 @@ describe('techs: levels-to-bemdecl', function () { } }, bemdecl = [ - { name: 'block' }, { name: 'block', mods: [{ name: 'mod-name', vals: [{ name: 'mod-val' }] }] } ]; @@ -85,7 +83,6 @@ describe('techs: levels-to-bemdecl', function () { } }, bemdecl = [ - { name: 'block' }, { name: 'block', elems: [{ name: 'elem-name' }] } ]; @@ -105,8 +102,6 @@ describe('techs: levels-to-bemdecl', function () { } }, bemdecl = [ - { name: 'block' }, - { name: 'block', elems: [{ name: 'elem-name' }] }, { name: 'block', elems: [ { name: 'elem-name', mods: [{ name: 'bool-mod', vals: [{ name: true }] }] } ] } @@ -128,8 +123,6 @@ describe('techs: levels-to-bemdecl', function () { } }, bemdecl = [ - { name: 'block' }, - { name: 'block', elems: [{ name: 'elem-name' }] }, { name: 'block', elems: [{ name: 'elem-name', mods: [{ name: 'mod-name', vals: [{ name: 'mod-val' }] }] diff --git a/test/techs/levels.test.js b/test/techs/levels.test.js index 8a8b8d3..db5f19e 100644 --- a/test/techs/levels.test.js +++ b/test/techs/levels.test.js @@ -426,6 +426,29 @@ function assert(fsScheme, expected) { return bundle.runTech(Tech, { levels: levels }) .then(function (data) { - return data.must.eql(expected); + var actual = { + levels: data._levels, + introspection: mergeIntrospections(data._introspections) + }; + + actual.must.eql(expected); }); } + +/** + * Merges introspection from several levels. + * + * @param {object[]} introspections + * @returns {object} + */ +function mergeIntrospections(introspections) { + var result = {}; + + introspections.forEach(function (introspection) { + Object.keys(introspection).forEach(function (id) { + result[id] = (result[id] || []).concat(introspection[id] || []); + }); + }); + + return result; +} From 07698ef7b92b25cb57340e8af51675434c1825e1 Mon Sep 17 00:00:00 2001 From: blond Date: Wed, 27 Apr 2016 10:36:59 +0300 Subject: [PATCH 11/11] fixup --- lib/introspection.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/introspection.js b/lib/introspection.js index 425da6d..13edd66 100644 --- a/lib/introspection.js +++ b/lib/introspection.js @@ -35,18 +35,20 @@ module.exports = class Introspection { return files; } getFilesByTechs (techs) { - const files = []; + const res = []; this._introspections.forEach(introspection => { Object.keys(introspection).forEach(id => { - const file = introspection[id]; + const files = introspection[id]; - if (techs.indexOf(file.tech) !== -1) { - files.push(file); - } + files.forEach(file => { + if (techs.indexOf(file.tech) !== -1) { + res.push(file); + } + }); }); }); - return files; + return res; } };