Skip to content
This repository has been archived by the owner on May 29, 2019. It is now read-only.

chore(build): make custom builds work (from browser) #2892

Closed
wants to merge 8 commits into from
28 changes: 27 additions & 1 deletion Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,13 @@ module.exports = function(grunt) {
})
);

var moduleFileMapping = _.clone(modules, true);
moduleFileMapping.forEach(function (module) {
delete module.docs;
});

grunt.config('moduleFileMapping', moduleFileMapping);

var srcFiles = _.pluck(modules, 'srcFiles');
var tpljsFiles = _.pluck(modules, 'tpljsFiles');
//Set the concat task to concatenate the given src modules
Expand All @@ -331,7 +338,7 @@ module.exports = function(grunt) {
grunt.config('concat.dist_tpls.src', grunt.config('concat.dist_tpls.src')
.concat(srcFiles).concat(tpljsFiles));

grunt.task.run(['concat', 'uglify']);
grunt.task.run(['concat', 'uglify', 'makeModuleMappingFile', 'makeRawFilesJs']);
});

grunt.registerTask('test', 'Run tests on singleRun karma server', function () {
Expand All @@ -351,6 +358,25 @@ module.exports = function(grunt) {
}
});

grunt.registerTask('makeModuleMappingFile', function () {
var _ = grunt.util._;
var moduleMappingJs = 'dist/assets/module-mapping.json';
var moduleMappings = grunt.config('moduleFileMapping');
var moduleMappingsMap = _.object(_.pluck(moduleMappings, 'name'), moduleMappings);
var jsContent = JSON.stringify(moduleMappingsMap);
grunt.file.write(moduleMappingJs, jsContent);
grunt.log.writeln('File ' + moduleMappingJs.cyan + ' created.');
});

grunt.registerTask('makeRawFilesJs', function () {
var _ = grunt.util._;
var jsFilename = 'dist/assets/raw-files.json';
var genRawFilesJs = require('./misc/raw-files-generator');

genRawFilesJs(grunt, jsFilename, _.flatten(grunt.config('concat.dist_tpls.src')),
grunt.config('meta.banner'));
});

function setVersion(type, suffix) {
var file = 'package.json';
var VERSION_REGEX = /([\'|\"]version[\'|\"][ ]*:[ ]*[\'|\"])([\d|.]*)(-\w+)*([\'|\"])/;
Expand Down
211 changes: 200 additions & 11 deletions misc/demo/assets/app.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* global FastClick, smoothScroll */
angular.module('ui.bootstrap.demo', ['ui.bootstrap', 'plunker', 'ngTouch'], function($httpProvider){
FastClick.attach(document.body);
delete $httpProvider.defaults.headers.common['X-Requested-With'];
Expand All @@ -8,7 +9,39 @@ angular.module('ui.bootstrap.demo', ['ui.bootstrap', 'plunker', 'ngTouch'], func
location.replace(el.id);
});
}
}]);
}]).factory('buildFilesService', function ($http, $q) {

var moduleMap;
var rawFiles;

return {
getModuleMap: getModuleMap,
getRawFiles: getRawFiles,
get: function () {
return $q.all({
moduleMap: getModuleMap(),
rawFiles: getRawFiles(),
});
}
};

function getModuleMap() {
return moduleMap ? $q.when(moduleMap) : $http.get('assets/module-mapping.json')
.then(function (result) {
moduleMap = result.data;
return moduleMap;
});
}

function getRawFiles() {
return rawFiles ? $q.when(rawFiles) : $http.get('assets/raw-files.json')
.then(function (result) {
rawFiles = result.data;
return rawFiles;
});
}

});

var builderUrl = "http://50.116.42.77:3001";

Expand All @@ -18,10 +51,11 @@ function MainCtrl($scope, $http, $document, $modal, orderByFilter) {
templateUrl: 'buildModal.html',
controller: 'SelectModulesCtrl',
resolve: {
modules: function() {
return $http.get(builderUrl + "/api/bootstrap").then(function(response) {
return response.data.modules;
});
modules: function(buildFilesService) {
return buildFilesService.getModuleMap()
.then(function (moduleMap) {
return Object.keys(moduleMap);
});
}
}
});
Expand All @@ -35,7 +69,7 @@ function MainCtrl($scope, $http, $document, $modal, orderByFilter) {
};
}

var SelectModulesCtrl = function($scope, $modalInstance, modules) {
var SelectModulesCtrl = function($scope, $modalInstance, modules, buildFilesService) {
$scope.selectedModules = [];
$scope.modules = modules;

Expand All @@ -55,12 +89,122 @@ var SelectModulesCtrl = function($scope, $modalInstance, modules) {
$modalInstance.dismiss();
};

$scope.download = function (selectedModules) {
var downloadUrl = builderUrl + "/api/bootstrap/download?";
angular.forEach(selectedModules, function(module) {
downloadUrl += "modules=" + module + "&";
$scope.isOldBrowser = function () {
return isOldBrowser;
};

$scope.build = function (selectedModules, version) {
/* global JSZip, saveAs */
var moduleMap, rawFiles;

buildFilesService.get().then(function (buildFiles) {
moduleMap = buildFiles.moduleMap;
rawFiles = buildFiles.rawFiles;

generateBuild();
});
return downloadUrl;

function generateBuild() {
var srcModuleNames = selectedModules
.map(function (module) {
return moduleMap[module];
})
.reduce(function (toBuild, module) {
addIfNotExists(toBuild, module.name);

module.dependencies.forEach(function (depName) {
addIfNotExists(toBuild, depName);
});
return toBuild;
}, []);

var srcModules = srcModuleNames
.map(function (moduleName) {
return moduleMap[moduleName];
});

var srcModuleFullNames = srcModules
.map(function (module) {
return module.moduleName;
});

var srcJsContent = srcModules
.reduce(function (buildFiles, module) {
return buildFiles.concat(module.srcFiles);
}, [])
.map(getFileContent)
.join('\n')
;

var jsFile = createNoTplFile(srcModuleFullNames, srcJsContent);

var tplModuleNames = srcModules
.reduce(function (tplModuleNames, module) {
return tplModuleNames.concat(module.tplModules);
}, []);

var tplJsContent = srcModules
.reduce(function (buildFiles, module) {
return buildFiles.concat(module.tpljsFiles);
}, [])
.map(getFileContent)
.join('\n')
;

var jsTplFile = createWithTplFile(srcModuleFullNames, srcJsContent, tplModuleNames, tplJsContent);

var zip = new JSZip();
zip.file('ui-bootstrap-custom-' + version + '.js', rawFiles.banner + jsFile);
zip.file('ui-bootstrap-custom-' + version + '.min.js', rawFiles.banner + uglify(jsFile));
zip.file('ui-bootstrap-custom-tpls-' + version + '.js', rawFiles.banner + jsTplFile);
zip.file('ui-bootstrap-custom-tpls-' + version + '.min.js', rawFiles.banner + uglify(jsTplFile));

saveAs(zip.generate({type: 'blob'}), 'ui-bootstrap-custom-build.zip');
}

function createNoTplFile(srcModuleNames, srcJsContent) {
return 'angular.module("ui.bootstrap", [' + srcModuleNames.join(',') + ']);\n' +
srcJsContent;
}

function createWithTplFile(srcModuleNames, srcJsContent, tplModuleNames, tplJsContent) {
var depModuleNames = srcModuleNames.slice();
depModuleNames.unshift('"ui.bootstrap.tpls"');

return 'angular.module("ui.bootstrap", [' + depModuleNames.join(',') + ']);\n' +
'angular.module("ui.bootstrap.tpls", [' + tplModuleNames.join(',') + ']);\n' +
srcJsContent + '\n' + tplJsContent;

}

function addIfNotExists(array, element) {
if (array.indexOf(element) == -1) {
array.push(element);
}
}

function getFileContent(fileName) {
return rawFiles.files[fileName];
}

function uglify(js) {
/* global UglifyJS */

var ast = UglifyJS.parse(js);
ast.figure_out_scope();

var compressor = UglifyJS.Compressor();
var compressedAst = ast.transform(compressor);

compressedAst.figure_out_scope();
compressedAst.compute_char_frequency();
compressedAst.mangle_names();

var stream = UglifyJS.OutputStream();
compressedAst.print(stream);

return stream.toString();
}
};
};

Expand Down Expand Up @@ -90,3 +234,48 @@ var DownloadCtrl = function($scope, $modalInstance) {
$modalInstance.dismiss();
};
};

/*
* The following compatibility check is from:
*
* Bootstrap Customizer (http://getbootstrap.com/customize/)
* Copyright 2011-2014 Twitter, Inc.
*
* Licensed under the Creative Commons Attribution 3.0 Unported License. For
* details, see http://creativecommons.org/licenses/by/3.0/.
*/
var isOldBrowser;
(function () {

var supportsFile = (window.File && window.FileReader && window.FileList && window.Blob);
function failback() {
isOldBrowser = true;
}
/**
* Based on:
* Blob Feature Check v1.1.0
* https://github.com/ssorallen/blob-feature-check/
* License: Public domain (http://unlicense.org)
*/
var url = window.webkitURL || window.URL; // Safari 6 uses "webkitURL".
var svg = new Blob(
['<svg xmlns=\'http://www.w3.org/2000/svg\'></svg>'],
{ type: 'image/svg+xml;charset=utf-8' }
);
var objectUrl = url.createObjectURL(svg);

if (/^blob:/.exec(objectUrl) === null || !supportsFile) {
// `URL.createObjectURL` created a URL that started with something other
// than "blob:", which means it has been polyfilled and is not supported by
// this browser.
failback();
} else {
angular.element('<img/>')
.on('load', function () {
isOldBrowser = false;
})
.on('error', failback)
.attr('src', objectUrl);
}

})();
4 changes: 4 additions & 0 deletions misc/demo/assets/uglifyjs.js

Large diffs are not rendered by default.

14 changes: 12 additions & 2 deletions misc/demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
<meta name="google-site-verification" content="7lc5HyceLDqpV_6oNHteYFfxDJH7-S3DwnJKtNUKcRg" />

<script src="//cdnjs.cloudflare.com/ajax/libs/fastclick/0.6.7/fastclick.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.0.0/FileSaver.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jszip/2.4.0/jszip.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/<%= ngversion %>/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/<%= ngversion %>/angular-touch.min.js"></script>
<script src="ui-bootstrap-tpls-<%= pkg.version%>.min.js"></script>
Expand Down Expand Up @@ -273,10 +275,15 @@ <h4>
</h4>
</div>
<div class="modal-body">
<div ng-show="isOldBrowser()">
Your current browser doesn't support creating custom builds.
Please take a second to <a href="http://browsehappy.com/">upgrade to a
more modern browser</a> (other than Safari).
</div>
<div ng-show="buildErrorText">
<h4 style="text-align: center;">{{buildErrorText}}</h4>
</div>
<div ng-hide="buildErrorText">
<div ng-hide="buildErrorText || isOldBrowser()">
<% modules.forEach(function(module,i) { %>
<% if (i % 3 === 0) {%>
<div class="btn-group" style="width: 100%;">
Expand All @@ -297,7 +304,9 @@ <h4 style="text-align: center;">{{buildErrorText}}</h4>
</div>
<div class="modal-footer">
<a class="btn btn-default" ng-click="cancel()">Close</a>
<a class="btn btn-primary" ng-disabled="!selectedModules.length" ng-href="{{selectedModules.length ? download(selectedModules) : ''}}">
<a class="btn btn-primary" ng-hide="isOldBrowser()"
ng-disabled="isOldBrowser() !== false && !selectedModules.length"
ng-click="selectedModules.length && build(selectedModules, '<%= pkg.version %>')">
<i class="glyphicon glyphicon-download-alt"></i> Download {{selectedModules.length}} Modules
</a>
</div>
Expand All @@ -317,5 +326,6 @@ <h4 style="text-align: center;">{{buildErrorText}}</h4>

</script>
<script src="assets/smoothscroll-angular-custom.js"></script>
<script src="assets/uglifyjs.js"></script>
</body>
</html>
41 changes: 41 additions & 0 deletions misc/raw-files-generator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*!
* Forked from:
* Bootstrap Grunt task for generating raw-files.min.js for the Customizer
* http://getbootstrap.com
* Copyright 2014 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/

/* jshint node: true */

'use strict';
var fs = require('fs');

function getFiles(filePaths) {
var files = {};
filePaths
.forEach(function (path) {
files[path] = fs.readFileSync(path, 'utf8');
});
return files;
}

module.exports = function generateRawFilesJs(grunt, jsFilename, files, banner) {
if (!banner) {
banner = '';
}

var filesJsObject = {
banner: banner,
files: getFiles(files),
};

var filesJsContent = JSON.stringify(filesJsObject);
try {
fs.writeFileSync(jsFilename, filesJsContent);
}
catch (err) {
grunt.fail.warn(err);
}
grunt.log.writeln('File ' + jsFilename.cyan + ' created.');
};