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

Commit 390f2bf

Browse files
committed
fix(build): make custom builds on demo site work
Use approach from Twitter Bootstrap. Most of the code is from their customizer. - use modules in mappings file - generate concatenated JS - generate tpl and non-tpl file with banner - generate zip file for custom builds - add browser compatibility message - speed up page by deferring loading of files - only load the files when needed. Cache them after they're loaded. Fixes #2960 Fixes #2847 Fixes #2625 Fixes #2489 Fixes #2357 Fixes #2176 Closes #2892
1 parent 32c4704 commit 390f2bf

File tree

5 files changed

+284
-14
lines changed

5 files changed

+284
-14
lines changed

Gruntfile.js

+27-1
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,13 @@ module.exports = function(grunt) {
322322
})
323323
);
324324

325+
var moduleFileMapping = _.clone(modules, true);
326+
moduleFileMapping.forEach(function (module) {
327+
delete module.docs;
328+
});
329+
330+
grunt.config('moduleFileMapping', moduleFileMapping);
331+
325332
var srcFiles = _.pluck(modules, 'srcFiles');
326333
var tpljsFiles = _.pluck(modules, 'tpljsFiles');
327334
//Set the concat task to concatenate the given src modules
@@ -331,7 +338,7 @@ module.exports = function(grunt) {
331338
grunt.config('concat.dist_tpls.src', grunt.config('concat.dist_tpls.src')
332339
.concat(srcFiles).concat(tpljsFiles));
333340

334-
grunt.task.run(['concat', 'uglify']);
341+
grunt.task.run(['concat', 'uglify', 'makeModuleMappingFile', 'makeRawFilesJs']);
335342
});
336343

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

361+
grunt.registerTask('makeModuleMappingFile', function () {
362+
var _ = grunt.util._;
363+
var moduleMappingJs = 'dist/assets/module-mapping.json';
364+
var moduleMappings = grunt.config('moduleFileMapping');
365+
var moduleMappingsMap = _.object(_.pluck(moduleMappings, 'name'), moduleMappings);
366+
var jsContent = JSON.stringify(moduleMappingsMap);
367+
grunt.file.write(moduleMappingJs, jsContent);
368+
grunt.log.writeln('File ' + moduleMappingJs.cyan + ' created.');
369+
});
370+
371+
grunt.registerTask('makeRawFilesJs', function () {
372+
var _ = grunt.util._;
373+
var jsFilename = 'dist/assets/raw-files.json';
374+
var genRawFilesJs = require('./misc/raw-files-generator');
375+
376+
genRawFilesJs(grunt, jsFilename, _.flatten(grunt.config('concat.dist_tpls.src')),
377+
grunt.config('meta.banner'));
378+
});
379+
354380
function setVersion(type, suffix) {
355381
var file = 'package.json';
356382
var VERSION_REGEX = /([\'|\"]version[\'|\"][ ]*:[ ]*[\'|\"])([\d|.]*)(-\w+)*([\'|\"])/;

misc/demo/assets/app.js

+200-11
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* global FastClick, smoothScroll */
12
angular.module('ui.bootstrap.demo', ['ui.bootstrap', 'plunker', 'ngTouch'], function($httpProvider){
23
FastClick.attach(document.body);
34
delete $httpProvider.defaults.headers.common['X-Requested-With'];
@@ -8,7 +9,39 @@ angular.module('ui.bootstrap.demo', ['ui.bootstrap', 'plunker', 'ngTouch'], func
89
location.replace('#' + el.id);
910
});
1011
}
11-
}]);
12+
}]).factory('buildFilesService', function ($http, $q) {
13+
14+
var moduleMap;
15+
var rawFiles;
16+
17+
return {
18+
getModuleMap: getModuleMap,
19+
getRawFiles: getRawFiles,
20+
get: function () {
21+
return $q.all({
22+
moduleMap: getModuleMap(),
23+
rawFiles: getRawFiles(),
24+
});
25+
}
26+
};
27+
28+
function getModuleMap() {
29+
return moduleMap ? $q.when(moduleMap) : $http.get('assets/module-mapping.json')
30+
.then(function (result) {
31+
moduleMap = result.data;
32+
return moduleMap;
33+
});
34+
}
35+
36+
function getRawFiles() {
37+
return rawFiles ? $q.when(rawFiles) : $http.get('assets/raw-files.json')
38+
.then(function (result) {
39+
rawFiles = result.data;
40+
return rawFiles;
41+
});
42+
}
43+
44+
});
1245

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

@@ -18,10 +51,11 @@ function MainCtrl($scope, $http, $document, $modal, orderByFilter) {
1851
templateUrl: 'buildModal.html',
1952
controller: 'SelectModulesCtrl',
2053
resolve: {
21-
modules: function() {
22-
return $http.get(builderUrl + "/api/bootstrap").then(function(response) {
23-
return response.data.modules;
24-
});
54+
modules: function(buildFilesService) {
55+
return buildFilesService.getModuleMap()
56+
.then(function (moduleMap) {
57+
return Object.keys(moduleMap);
58+
});
2559
}
2660
}
2761
});
@@ -35,7 +69,7 @@ function MainCtrl($scope, $http, $document, $modal, orderByFilter) {
3569
};
3670
}
3771

38-
var SelectModulesCtrl = function($scope, $modalInstance, modules) {
72+
var SelectModulesCtrl = function($scope, $modalInstance, modules, buildFilesService) {
3973
$scope.selectedModules = [];
4074
$scope.modules = modules;
4175

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

58-
$scope.download = function (selectedModules) {
59-
var downloadUrl = builderUrl + "/api/bootstrap/download?";
60-
angular.forEach(selectedModules, function(module) {
61-
downloadUrl += "modules=" + module + "&";
92+
$scope.isOldBrowser = function () {
93+
return isOldBrowser;
94+
};
95+
96+
$scope.build = function (selectedModules, version) {
97+
/* global JSZip, saveAs */
98+
var moduleMap, rawFiles;
99+
100+
buildFilesService.get().then(function (buildFiles) {
101+
moduleMap = buildFiles.moduleMap;
102+
rawFiles = buildFiles.rawFiles;
103+
104+
generateBuild();
62105
});
63-
return downloadUrl;
106+
107+
function generateBuild() {
108+
var srcModuleNames = selectedModules
109+
.map(function (module) {
110+
return moduleMap[module];
111+
})
112+
.reduce(function (toBuild, module) {
113+
addIfNotExists(toBuild, module.name);
114+
115+
module.dependencies.forEach(function (depName) {
116+
addIfNotExists(toBuild, depName);
117+
});
118+
return toBuild;
119+
}, []);
120+
121+
var srcModules = srcModuleNames
122+
.map(function (moduleName) {
123+
return moduleMap[moduleName];
124+
});
125+
126+
var srcModuleFullNames = srcModules
127+
.map(function (module) {
128+
return module.moduleName;
129+
});
130+
131+
var srcJsContent = srcModules
132+
.reduce(function (buildFiles, module) {
133+
return buildFiles.concat(module.srcFiles);
134+
}, [])
135+
.map(getFileContent)
136+
.join('\n')
137+
;
138+
139+
var jsFile = createNoTplFile(srcModuleFullNames, srcJsContent);
140+
141+
var tplModuleNames = srcModules
142+
.reduce(function (tplModuleNames, module) {
143+
return tplModuleNames.concat(module.tplModules);
144+
}, []);
145+
146+
var tplJsContent = srcModules
147+
.reduce(function (buildFiles, module) {
148+
return buildFiles.concat(module.tpljsFiles);
149+
}, [])
150+
.map(getFileContent)
151+
.join('\n')
152+
;
153+
154+
var jsTplFile = createWithTplFile(srcModuleFullNames, srcJsContent, tplModuleNames, tplJsContent);
155+
156+
var zip = new JSZip();
157+
zip.file('ui-bootstrap-custom-' + version + '.js', rawFiles.banner + jsFile);
158+
zip.file('ui-bootstrap-custom-' + version + '.min.js', rawFiles.banner + uglify(jsFile));
159+
zip.file('ui-bootstrap-custom-tpls-' + version + '.js', rawFiles.banner + jsTplFile);
160+
zip.file('ui-bootstrap-custom-tpls-' + version + '.min.js', rawFiles.banner + uglify(jsTplFile));
161+
162+
saveAs(zip.generate({type: 'blob'}), 'ui-bootstrap-custom-build.zip');
163+
}
164+
165+
function createNoTplFile(srcModuleNames, srcJsContent) {
166+
return 'angular.module("ui.bootstrap", [' + srcModuleNames.join(',') + ']);\n' +
167+
srcJsContent;
168+
}
169+
170+
function createWithTplFile(srcModuleNames, srcJsContent, tplModuleNames, tplJsContent) {
171+
var depModuleNames = srcModuleNames.slice();
172+
depModuleNames.unshift('"ui.bootstrap.tpls"');
173+
174+
return 'angular.module("ui.bootstrap", [' + depModuleNames.join(',') + ']);\n' +
175+
'angular.module("ui.bootstrap.tpls", [' + tplModuleNames.join(',') + ']);\n' +
176+
srcJsContent + '\n' + tplJsContent;
177+
178+
}
179+
180+
function addIfNotExists(array, element) {
181+
if (array.indexOf(element) == -1) {
182+
array.push(element);
183+
}
184+
}
185+
186+
function getFileContent(fileName) {
187+
return rawFiles.files[fileName];
188+
}
189+
190+
function uglify(js) {
191+
/* global UglifyJS */
192+
193+
var ast = UglifyJS.parse(js);
194+
ast.figure_out_scope();
195+
196+
var compressor = UglifyJS.Compressor();
197+
var compressedAst = ast.transform(compressor);
198+
199+
compressedAst.figure_out_scope();
200+
compressedAst.compute_char_frequency();
201+
compressedAst.mangle_names();
202+
203+
var stream = UglifyJS.OutputStream();
204+
compressedAst.print(stream);
205+
206+
return stream.toString();
207+
}
64208
};
65209
};
66210

@@ -90,3 +234,48 @@ var DownloadCtrl = function($scope, $modalInstance) {
90234
$modalInstance.dismiss();
91235
};
92236
};
237+
238+
/*
239+
* The following compatibility check is from:
240+
*
241+
* Bootstrap Customizer (http://getbootstrap.com/customize/)
242+
* Copyright 2011-2014 Twitter, Inc.
243+
*
244+
* Licensed under the Creative Commons Attribution 3.0 Unported License. For
245+
* details, see http://creativecommons.org/licenses/by/3.0/.
246+
*/
247+
var isOldBrowser;
248+
(function () {
249+
250+
var supportsFile = (window.File && window.FileReader && window.FileList && window.Blob);
251+
function failback() {
252+
isOldBrowser = true;
253+
}
254+
/**
255+
* Based on:
256+
* Blob Feature Check v1.1.0
257+
* https://github.com/ssorallen/blob-feature-check/
258+
* License: Public domain (http://unlicense.org)
259+
*/
260+
var url = window.webkitURL || window.URL; // Safari 6 uses "webkitURL".
261+
var svg = new Blob(
262+
['<svg xmlns=\'http://www.w3.org/2000/svg\'></svg>'],
263+
{ type: 'image/svg+xml;charset=utf-8' }
264+
);
265+
var objectUrl = url.createObjectURL(svg);
266+
267+
if (/^blob:/.exec(objectUrl) === null || !supportsFile) {
268+
// `URL.createObjectURL` created a URL that started with something other
269+
// than "blob:", which means it has been polyfilled and is not supported by
270+
// this browser.
271+
failback();
272+
} else {
273+
angular.element('<img/>')
274+
.on('load', function () {
275+
isOldBrowser = false;
276+
})
277+
.on('error', failback)
278+
.attr('src', objectUrl);
279+
}
280+
281+
})();

misc/demo/assets/uglifyjs.js

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

misc/demo/index.html

+12-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
<meta name="google-site-verification" content="7lc5HyceLDqpV_6oNHteYFfxDJH7-S3DwnJKtNUKcRg" />
1010

1111
<script src="//cdnjs.cloudflare.com/ajax/libs/fastclick/0.6.7/fastclick.min.js"></script>
12+
<script src="//cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.0.0/FileSaver.min.js"></script>
13+
<script src="//cdnjs.cloudflare.com/ajax/libs/jszip/2.4.0/jszip.min.js"></script>
1214
<script src="//ajax.googleapis.com/ajax/libs/angularjs/<%= ngversion %>/angular.min.js"></script>
1315
<script src="//ajax.googleapis.com/ajax/libs/angularjs/<%= ngversion %>/angular-touch.min.js"></script>
1416
<script src="ui-bootstrap-tpls-<%= pkg.version%>.min.js"></script>
@@ -273,10 +275,15 @@ <h4>
273275
</h4>
274276
</div>
275277
<div class="modal-body">
278+
<div ng-show="isOldBrowser()">
279+
Your current browser doesn't support creating custom builds.
280+
Please take a second to <a href="http://browsehappy.com/">upgrade to a
281+
more modern browser</a> (other than Safari).
282+
</div>
276283
<div ng-show="buildErrorText">
277284
<h4 style="text-align: center;">{{buildErrorText}}</h4>
278285
</div>
279-
<div ng-hide="buildErrorText">
286+
<div ng-hide="buildErrorText || isOldBrowser()">
280287
<% modules.forEach(function(module,i) { %>
281288
<% if (i % 3 === 0) {%>
282289
<div class="btn-group" style="width: 100%;">
@@ -297,7 +304,9 @@ <h4 style="text-align: center;">{{buildErrorText}}</h4>
297304
</div>
298305
<div class="modal-footer">
299306
<a class="btn btn-default" ng-click="cancel()">Close</a>
300-
<a class="btn btn-primary" ng-disabled="!selectedModules.length" ng-href="{{selectedModules.length ? download(selectedModules) : ''}}">
307+
<a class="btn btn-primary" ng-hide="isOldBrowser()"
308+
ng-disabled="isOldBrowser() !== false && !selectedModules.length"
309+
ng-click="selectedModules.length && build(selectedModules, '<%= pkg.version %>')">
301310
<i class="glyphicon glyphicon-download-alt"></i> Download {{selectedModules.length}} Modules
302311
</a>
303312
</div>
@@ -317,5 +326,6 @@ <h4 style="text-align: center;">{{buildErrorText}}</h4>
317326

318327
</script>
319328
<script src="assets/smoothscroll-angular-custom.js"></script>
329+
<script src="assets/uglifyjs.js"></script>
320330
</body>
321331
</html>

misc/raw-files-generator.js

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*!
2+
* Forked from:
3+
* Bootstrap Grunt task for generating raw-files.min.js for the Customizer
4+
* http://getbootstrap.com
5+
* Copyright 2014 Twitter, Inc.
6+
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
7+
*/
8+
9+
/* jshint node: true */
10+
11+
'use strict';
12+
var fs = require('fs');
13+
14+
function getFiles(filePaths) {
15+
var files = {};
16+
filePaths
17+
.forEach(function (path) {
18+
files[path] = fs.readFileSync(path, 'utf8');
19+
});
20+
return files;
21+
}
22+
23+
module.exports = function generateRawFilesJs(grunt, jsFilename, files, banner) {
24+
if (!banner) {
25+
banner = '';
26+
}
27+
28+
var filesJsObject = {
29+
banner: banner,
30+
files: getFiles(files),
31+
};
32+
33+
var filesJsContent = JSON.stringify(filesJsObject);
34+
try {
35+
fs.writeFileSync(jsFilename, filesJsContent);
36+
}
37+
catch (err) {
38+
grunt.fail.warn(err);
39+
}
40+
grunt.log.writeln('File ' + jsFilename.cyan + ' created.');
41+
};

0 commit comments

Comments
 (0)