From ba5c7b25372ddc65f557618ea496a9a1872bbd0d Mon Sep 17 00:00:00 2001 From: tporadowski Date: Fri, 3 Oct 2014 16:22:47 +0200 Subject: [PATCH 1/3] Use exportsOverride to rename target folders - exportOverride can now use an extended definition that has "packageName" naming convention which is run through Handlebars with package's metadata (so one can install to i.e. "js/jquery-2.1.1" based on metadata of Bower packages) - added dependencies to Q and Handlebars --- README.md | 41 +++++++++++++++++ package.json | 6 ++- tasks/lib/bower_assets.js | 97 +++++++++++++++++++++++++++++++-------- 3 files changed, 124 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 2be200e..1d7ed0b 100644 --- a/README.md +++ b/README.md @@ -236,6 +236,47 @@ img/bootstrap-sass/glyphicons-halflings-white.png img/bootstrap-sass/glyphicons-halflings.png ``` +### Advanced usage with package renaming +You can also use an extended `"exportsOverride"` definition that allows you to further customize +package name(s) and thus resulting target folder names: + +```json +{ + "name": "simple-bower", + "version": "0.0.0", + "dependencies": { + "jquery": "*", + "bootstrap-sass-official": "*", + "requirejs": "*" + }, + "exportsOverride": { + "packageName": "{{meta.name}}-{{version.major}}.{{version.minor}}.{{version.revision}}", + "overrides": { + "bootstrap-sass-official": { + "packageName": "bootstrap-{{version_safe}}", + "overrides": { + "js": "assets/javascripts/*.js" + } + }, + "requirejs": { + "js": "require.js" + }, + "jquery": { + "js": "dist/*.js" + } + } + } +} +``` +In order to use it you must provide `packageName` property that will be evaluated with [Handlebars](http://handlebarsjs.com/) templating engine and enclose overrides in `overrides` object. `exportsOverride.packageName` provides the default naming convention and can be redefined for a specific package (see override for "bootstrap-sass-oficial" package). + +In `packageName` you can refer to: +* `meta` - metadata information as given by "bower info \" (`name`, `version`, etc.) +* `version` - provides `major`, `minor` and `revision` properties +* `version_safe` - metatdata's version with all non-dot and non-digit characters replaced with "\_" (i.e. "3.2.0+2" becomes "3.2.0\_2"). + +As a result of above configuration you will have your files installed to `js/bootstrap-3.2.0_2` ("bootstrap-sass-oficial" uses its own `packageName` convention), `js/jquery-2.1.1` and `js/requirejs-2.1.15` ("jquery" and "requirejs" fall back to main convention from `exportsOverride.packageName`). + ### Wildcard and RegExp support If you have the same override rules for multiple Bower components you can make use of simple wildcard: diff --git a/package.json b/package.json index 03936d9..107aa33 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "grunt-bower-task", "description": "Install Bower packages.", - "version": "0.4.0", + "version": "0.4.1", "homepage": "https://github.com/yatskevich/grunt-bower-task", "author": { "name": "Ivan Yatskevich", @@ -39,7 +39,9 @@ "rimraf": "~2.0.2", "wrench": "~1.4.3", "colors": "~0.6.0-1", - "async": "~0.1.22" + "async": "~0.1.22", + "q": "~1.0.1", + "handlebars": "~2.0.0" }, "devDependencies": { "grunt": "~0.4.1", diff --git a/tasks/lib/bower_assets.js b/tasks/lib/bower_assets.js index 3126805..0bb4f2d 100644 --- a/tasks/lib/bower_assets.js +++ b/tasks/lib/bower_assets.js @@ -3,6 +3,8 @@ var Emitter = require('events').EventEmitter; var path = require('path'); var grunt = require('grunt'); var packageMatcher = require('./package_matcher'); +var Q = require('q'); +var Handlebars = require('handlebars'); var Assets = function(cwd, componentsDir) { this._assets = {}; @@ -10,15 +12,24 @@ var Assets = function(cwd, componentsDir) { this._componentsDir = componentsDir; }; -Assets.prototype.addOverridden = function(override, pkg) { +Assets.prototype.addOverridden = function(override, pkg, pkgMeta, exportsOverride) { var pkgPath = path.join(this._componentsDir, pkg); - _(override).each(function(overriddenPaths, assetType) { + //take "packageName" specified for given override (or for master "exportsOverride") + // and run it through Handlebars to further customize name of the package (and thus target folder) + var tmpl = Handlebars.compile(getPackageName(override) || getPackageName(exportsOverride) || pkg); + //provide context with: + // meta - package metadata (as returned by Bower) + // version - object with "major", "minor" and "revision" properties (extracted from metadata's version) + // version_safe - metatdata's version with all non-dot and non-digit characters replaced with "_" + pkg = tmpl({ meta: pkgMeta, version: getPackageVersion(pkgMeta), version_safe: pkgMeta.version.replace(/[^0-9.]/, '_') }); + + _(getOverrides(override)).each(function(overriddenPaths, assetType) { this.addAssets(overriddenPaths, pkg, assetType, pkgPath); }, this); }; -Assets.prototype.addUntyped = function(pkgFiles, pkg) { +Assets.prototype.addUntyped = function(pkgFiles, pkg, pkgMeta) { this.addAssets(pkgFiles, pkg, '__untyped__'); }; @@ -41,6 +52,39 @@ Assets.prototype.toObject = function() { return _.clone(this._assets); }; +/** + * Checks if parameter contains an extended definition that has "packageName" + * and "overrides" properties. + */ +function isExtendedOverride(overrides) { + return overrides && _(overrides).has('packageName') && _(overrides).has('overrides'); +} + +/** + * Gets overrides from an extended definition or returns "overrides" as is. + */ +function getOverrides(overrides) { + return isExtendedOverride(overrides) && _.isObject(overrides.overrides) ? overrides.overrides : overrides; +} + +/** + * Gets "packageName" property if given an extended overrides definition or returns null. + */ +function getPackageName(overrides) { + return isExtendedOverride(overrides) ? overrides.packageName : null; +} + +/** + * Gets Bower package version from its metadata. + * + * @param pkgMeta package metadata (returned by "bower.commands.info") + * @returns object with "major", "minor" and "revision" properties (extracted from metadata's version) + */ +function getPackageVersion(pkgMeta) { + var parts = pkgMeta.version.split(/[^0-9]/).concat([0, 0, 0]); + return { major: parts[0], minor: parts[1], revision: parts[2] }; +} + var BowerAssets = function(bower, cwd) { this.bower = bower; @@ -55,11 +99,13 @@ BowerAssets.prototype.constructor = BowerAssets; BowerAssets.prototype.get = function() { var bower = this.bower; var bowerConfig = grunt.file.readJSON(path.join(this.cwd, this.config)); - var exportsOverride = bowerConfig.exportsOverride; + var exportsOverride = bowerConfig.exportsOverride || {}; var paths = bower.commands.list({paths: true}); paths.on('end', function(data) { - this.emit('end', this.mergePaths(data, exportsOverride ? exportsOverride : {})); + this.mergePaths(data, exportsOverride, bower, function(retData) { + this.emit('end', retData); + }.bind(this)); }.bind(this)); paths.on('error', function(err) { this.emit('error', err); @@ -72,27 +118,42 @@ BowerAssets.prototype.get = function() { * * @param bowerComponents - output of 'bower list' command * @param overrides - overrides coming from 'bower.json' + * @param bower - reference to Bower + * @param callback - function to be called back with 1 argument (object with assets) * * @returns assets grouped by component and type */ -BowerAssets.prototype.mergePaths = function(bowerComponents, overrides) { +BowerAssets.prototype.mergePaths = function(bowerComponents, overrides, bower, callback) { var findOverride = function(pkg) { - return _(overrides).find(function(override, override_key) { + return _(getOverrides(overrides)).find(function(override, override_key) { return packageMatcher.matches(pkg, override_key); }); }; - _(bowerComponents).each(function(pkgFiles, pkg) { - var activeOverride = findOverride(pkg); - - if (activeOverride) { - this.assets.addOverridden(activeOverride, pkg); - } else { - this.assets.addUntyped(pkgFiles, pkg); - } - }, this); - - return this.assets.toObject(); + var that = this; + Q.allSettled( + _(bowerComponents).map(function(pkgFiles, pkg) { + var deferred = Q.defer(); + + //get package metadata and pass it to assets' aggregation functions + bower.commands.info(pkg).on('end', function(pkginfo) { + var activeOverride = findOverride(pkg); + pkginfo = pkginfo.latest || pkginfo; + + if (activeOverride) { + that.assets.addOverridden(activeOverride, pkg, pkginfo, overrides); + } else { + that.assets.addUntyped(pkgFiles, pkg, pkginfo); + } + + deferred.resolve(true); + }); + + return deferred.promise; + }) + ).then(function() { + callback(that.assets.toObject()); + }); }; module.exports = BowerAssets; From ad6815ca7d3376a50771ff309ebd06cf822158f5 Mon Sep 17 00:00:00 2001 From: tporadowski Date: Sat, 4 Oct 2014 17:14:30 +0200 Subject: [PATCH 2/3] Added tests for new extended exportsOverride - added unit tests for new functionality, fixed JSHint warnings --- tasks/lib/bower_assets.js | 66 +++++++++---------- test/bower_assets_test.js | 61 +++++++++++++++++ .../components/jquery/jquery.js | 0 .../components/jquery/jquery.js | 0 .../components/lodash/lodash.js | 0 5 files changed, 94 insertions(+), 33 deletions(-) create mode 100644 test/fixtures/exportsOverrideWithPackageName/components/jquery/jquery.js create mode 100644 test/fixtures/exportsOverrideWithPackageNameMixed/components/jquery/jquery.js create mode 100644 test/fixtures/exportsOverrideWithPackageNameMixed/components/lodash/lodash.js diff --git a/tasks/lib/bower_assets.js b/tasks/lib/bower_assets.js index 0bb4f2d..007d18c 100644 --- a/tasks/lib/bower_assets.js +++ b/tasks/lib/bower_assets.js @@ -12,6 +12,39 @@ var Assets = function(cwd, componentsDir) { this._componentsDir = componentsDir; }; +/** + * Checks if parameter contains an extended definition that has "packageName" + * and "overrides" properties. + */ +function isExtendedOverride(overrides) { + return overrides && _(overrides).has('packageName') && _(overrides).has('overrides'); +} + +/** + * Gets overrides from an extended definition or returns "overrides" as is. + */ +function getOverrides(overrides) { + return isExtendedOverride(overrides) && _.isObject(overrides.overrides) ? overrides.overrides : overrides; +} + +/** + * Gets "packageName" property if given an extended overrides definition or returns null. + */ +function getPackageName(overrides) { + return isExtendedOverride(overrides) ? overrides.packageName : null; +} + +/** + * Gets Bower package version from its metadata. + * + * @param pkgMeta package metadata (returned by "bower.commands.info") + * @returns object with "major", "minor" and "revision" properties (extracted from metadata's version) + */ +function getPackageVersion(pkgMeta) { + var parts = pkgMeta.version.split(/[^0-9]/).concat([0, 0, 0]); + return { major: parts[0], minor: parts[1], revision: parts[2] }; +} + Assets.prototype.addOverridden = function(override, pkg, pkgMeta, exportsOverride) { var pkgPath = path.join(this._componentsDir, pkg); @@ -52,39 +85,6 @@ Assets.prototype.toObject = function() { return _.clone(this._assets); }; -/** - * Checks if parameter contains an extended definition that has "packageName" - * and "overrides" properties. - */ -function isExtendedOverride(overrides) { - return overrides && _(overrides).has('packageName') && _(overrides).has('overrides'); -} - -/** - * Gets overrides from an extended definition or returns "overrides" as is. - */ -function getOverrides(overrides) { - return isExtendedOverride(overrides) && _.isObject(overrides.overrides) ? overrides.overrides : overrides; -} - -/** - * Gets "packageName" property if given an extended overrides definition or returns null. - */ -function getPackageName(overrides) { - return isExtendedOverride(overrides) ? overrides.packageName : null; -} - -/** - * Gets Bower package version from its metadata. - * - * @param pkgMeta package metadata (returned by "bower.commands.info") - * @returns object with "major", "minor" and "revision" properties (extracted from metadata's version) - */ -function getPackageVersion(pkgMeta) { - var parts = pkgMeta.version.split(/[^0-9]/).concat([0, 0, 0]); - return { major: parts[0], minor: parts[1], revision: parts[2] }; -} - var BowerAssets = function(bower, cwd) { this.bower = bower; diff --git a/test/bower_assets_test.js b/test/bower_assets_test.js index 1005a19..a40be44 100644 --- a/test/bower_assets_test.js +++ b/test/bower_assets_test.js @@ -48,6 +48,7 @@ function verify(name, message, expected, test, bower) { exports.bower_assets = { setUp: function(done) { var bowerCommands = { + infoMap: {}, list: new EventEmitter() }; this.bowerCommands = bowerCommands; @@ -56,6 +57,10 @@ exports.bower_assets = { commands: { list: function() { return bowerCommands.list; + }, + info: function(pkg) { + bowerCommands.infoMap[pkg] = new EventEmitter(); + return bowerCommands.infoMap[pkg]; } }, config: { @@ -90,6 +95,7 @@ exports.bower_assets = { this.bower); this.bowerCommands.list.emit('end', {"jquery": path.normalize("components/jquery/jquery.js")}); + this.bowerCommands.infoMap["jquery"].emit('end', {"name": "jquery", version: "2.1.1"}); }, extendedComponentJson: function(test) { @@ -124,6 +130,8 @@ exports.bower_assets = { ], "jquery": path.normalize("components/jquery/jquery.js") }); + this.bowerCommands.infoMap["bootstrap-sass"].emit('end', {"name": "bootstrap-sass", "version": "3.2.0"}); + this.bowerCommands.infoMap["jquery"].emit('end', {"name": "jquery", "version": "2.1.1"}); }, overrideHonoringNativeBowerConfiguration: function(test) { @@ -159,6 +167,8 @@ exports.bower_assets = { ], "jquery": path.normalize("bo_co/jquery/jquery.js") }); + this.bowerCommands.infoMap["bootstrap"].emit('end', {"name": "bootstrap", "version": "3.2.0"}); + this.bowerCommands.infoMap["jquery"].emit('end', {"name": "jquery", "version": "2.1.1"}); }, overrideRegexBowerConfiguration: function(test) { @@ -202,6 +212,9 @@ exports.bower_assets = { path.normalize("bo_co/underscore/underscore.css") ] }); + this.bowerCommands.infoMap["bootstrap"].emit('end', {"name": "bootstrap", "version": "3.2.0"}); + this.bowerCommands.infoMap["jquery"].emit('end', {"name": "jquery", "version": "2.1.1"}); + this.bowerCommands.infoMap["underscore"].emit('end', {"name": "underscore", "version": "1.0.17"}); }, support_bower_components_folder: function(test) { @@ -227,5 +240,53 @@ exports.bower_assets = { this.bowerCommands.list.emit('end', { "jquery": path.normalize("bower_components/jquery/jquery.js") }); + this.bowerCommands.infoMap["jquery"].emit('end', {"name": "jquery", "version": "2.1.1"}); + }, + + exportsOverrideWithPackageName: function(test) { + test.expect(1); + + var expected = { + "js": { + "jquery-2.1.1": [path.normalize("components/jquery/jquery.js")] + } + }; + + verify( + 'exportsOverrideWithPackageName', + 'should use metadata of "jquery" package', + expected, + test, + this.bower); + + this.bowerCommands.list.emit('end', {"jquery": path.normalize("components/jquery/jquery.js")}); + this.bowerCommands.infoMap["jquery"].emit('end', {"name": "jquery", version: "2.1.1"}); + }, + + exportsOverrideWithPackageNameMixed: function(test) { + test.expect(1); + + var expected = { + "js": { + //no packageName + "jquery": [path.normalize("components/jquery/jquery.js")], + //uses "{{version_safe}}" + "lodash-3.2.0_2": [path.normalize("components/lodash/lodash.js")] + } + }; + + verify( + 'exportsOverrideWithPackageNameMixed', + 'should use metadata of "bootstrap" package and leave "jquery" as is', + expected, + test, + this.bower); + + this.bowerCommands.list.emit('end', { + "jquery": path.normalize("components/jquery/jquery.js"), + "lodash": path.normalize("components/lodash/lodash.js") + }); + this.bowerCommands.infoMap["jquery"].emit('end', {"name": "jquery", version: "2.1.10"}); + this.bowerCommands.infoMap["lodash"].emit('end', {"name": "lodash", version: "3.2.0+2"}); } }; diff --git a/test/fixtures/exportsOverrideWithPackageName/components/jquery/jquery.js b/test/fixtures/exportsOverrideWithPackageName/components/jquery/jquery.js new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/exportsOverrideWithPackageNameMixed/components/jquery/jquery.js b/test/fixtures/exportsOverrideWithPackageNameMixed/components/jquery/jquery.js new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/exportsOverrideWithPackageNameMixed/components/lodash/lodash.js b/test/fixtures/exportsOverrideWithPackageNameMixed/components/lodash/lodash.js new file mode 100644 index 0000000..e69de29 From 992e613455a153912a5fa00bd9e592b09f769f32 Mon Sep 17 00:00:00 2001 From: tporadowski Date: Sat, 4 Oct 2014 19:28:48 +0200 Subject: [PATCH 3/3] Added missing test configuration files - "component.json" files were not added in previous commit due to .gitignore --- .../component.json | 15 +++++++++++++++ .../component.json | 19 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 test/fixtures/exportsOverrideWithPackageName/component.json create mode 100644 test/fixtures/exportsOverrideWithPackageNameMixed/component.json diff --git a/test/fixtures/exportsOverrideWithPackageName/component.json b/test/fixtures/exportsOverrideWithPackageName/component.json new file mode 100644 index 0000000..e8a1ad3 --- /dev/null +++ b/test/fixtures/exportsOverrideWithPackageName/component.json @@ -0,0 +1,15 @@ +{ + "name": "simple-bower", + "version": "0.0.0", + "dependencies": { + "jquery": "~1.7.2" + }, + "exportsOverride": { + "packageName": "{{meta.name}}-{{version.major}}.{{version.minor}}.{{version.revision}}", + "overrides": { + "jquery": { + "js": "*.js" + } + } + } +} diff --git a/test/fixtures/exportsOverrideWithPackageNameMixed/component.json b/test/fixtures/exportsOverrideWithPackageNameMixed/component.json new file mode 100644 index 0000000..f0ce708 --- /dev/null +++ b/test/fixtures/exportsOverrideWithPackageNameMixed/component.json @@ -0,0 +1,19 @@ +{ + "name": "simple-bower", + "version": "0.0.0", + "dependencies": { + "jquery": "~1.7.2", + "lodash": "*" + }, + "exportsOverride": { + "jquery": { + "js": "*.js" + }, + "lodash": { + "packageName": "{{meta.name}}-{{version_safe}}", + "overrides": { + "js": "*.js" + } + } + } +}