diff --git a/Gruntfile.js b/Gruntfile.js index 275e7b9..844bc7d 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,8 +1,10 @@ var _ = require('underscore'); + module.exports = function(grunt) { + // Load required NPM tasks. // You must first run `npm install` in the project's root directory to get these dependencies. grunt.loadNpmTasks('grunt-contrib-concat'); @@ -10,33 +12,28 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-contrib-copy'); grunt.loadNpmTasks('grunt-contrib-compress'); grunt.loadNpmTasks('grunt-contrib-clean'); - grunt.loadNpmTasks('grunt-contrib-watch'); // Very useful for development. See README. + var fileIndex = require('./files.js'); // lists of source/dependency files + var loaderUtils = require('./build/loader.js'); + // read config files, and combine into one "meta" object var packageConfig = grunt.file.readJSON('package.json'); var componentConfig = grunt.file.readJSON('component.json'); var pluginConfig = grunt.file.readJSON('fullcalendar.jquery.json'); var meta = _.extend({}, packageConfig, componentConfig, pluginConfig); - - var config = { // this will eventually get passed to grunt.initConfig + // this will eventually get passed to grunt.initConfig + var config = { meta: meta, // do this primarily for templating (<%= %>) - - // initialize multitasks - concat: {}, + concat: {}, // initialize multitasks... uglify: {}, copy: {}, compress: {}, - clean: {}, - watch: {} // we will add watch tasks whenever we do concats, so files get re-concatenated upon save + clean: {} }; - // files that the demos might need in the distributable - var depFiles = require('./build/deps.js'); - - /* Important Top-Level Tasks ----------------------------------------------------------------------------------------------------*/ @@ -44,125 +41,51 @@ module.exports = function(grunt) { grunt.registerTask('dist', 'Create a distributable ZIP file', [ 'clean:build', - 'submodules', + 'concat', 'uglify', - 'copy:deps', + 'copy:dependencies', 'copy:demos', 'copy:misc', 'compress' ]); - grunt.registerTask('dev', 'Build necessary files for developing and debugging', 'submodules'); - grunt.registerTask('submodules', 'Build all FullCalendar submodules', [ - 'main', - 'gcal' - ]); - - - /* Main FullCalendar Submodule + /* Concatenate Submodules ----------------------------------------------------------------------------------------------------*/ - grunt.registerTask('main', 'Build the main FullCalendar submodule', [ - 'concat:mainJs', - 'concat:mainCss', - 'concat:mainPrintCss' - ]); + _.each(fileIndex.fullcalendar, function(submodule, name) { - // JavaScript + if (submodule.js) { + config.concat[name + '-js'] = { + options: { + process: true // replace template variables + }, + src: submodule.js, + dest: 'build/out/fullcalendar/' + name + '.js' + }; + } - config.concat.mainJs = { - options: { - process: true // replace template variables - }, - src: [ - 'src/intro.js', - 'src/defaults.js', - 'src/main.js', - 'src/Calendar.js', - 'src/Header.js', - 'src/EventManager.js', - 'src/date_util.js', - 'src/util.js', - 'src/basic/MonthView.js', - 'src/basic/BasicWeekView.js', - 'src/basic/BasicDayView.js', - 'src/basic/BasicView.js', - 'src/basic/BasicEventRenderer.js', - 'src/agenda/AgendaWeekView.js', - 'src/agenda/AgendaDayView.js', - 'src/agenda/AgendaView.js', - 'src/agenda/AgendaEventRenderer.js', - 'src/common/View.js', - 'src/common/DayEventRenderer.js', - 'src/common/SelectionManager.js', - 'src/common/OverlayManager.js', - 'src/common/CoordinateGrid.js', - 'src/common/HoverListener.js', - 'src/common/HorizontalPositionCache.js', - 'src/outro.js' - ], - dest: 'build/out/fullcalendar/fullcalendar.js' - }; + if (submodule.css) { + config.concat[name + '-css'] = { + options: { + process: true // replace template variables + }, + src: submodule.css, + dest: 'build/out/fullcalendar/' + name + '.css' + }; + } - config.watch.mainJs = { - files: config.concat.mainJs.src, - tasks: 'concat:mainJs' - }; + if (submodule.printCss) { + config.concat[name + '-print-css'] = { + options: { + process: true // replace template variables + }, + src: submodule.printCss, + dest: 'build/out/fullcalendar/' + name + '.print.css' + }; + } - // CSS - - config.concat.mainCss = { - options: { - process: true // replace template variables - }, - src: [ - 'src/main.css', - 'src/common/common.css', - 'src/basic/basic.css', - 'src/agenda/agenda.css' - ], - dest: 'build/out/fullcalendar/fullcalendar.css' - }; - - config.watch.mainCss = { - files: config.concat.mainCss.src, - tasks: 'concat:mainCss' - }; - - // CSS (for printing) - - config.concat.mainPrintCss = { - options: { - process: true // replace template variables - }, - src: 'src/common/print.css', - dest: 'build/out/fullcalendar/fullcalendar.print.css' - }; - - config.watch.mainPrintCss = { - files: config.concat.mainPrintCss.src, - tasks: 'concat:mainPrintCss' - }; - - - /* Google Calendar Submodule - ----------------------------------------------------------------------------------------------------*/ - - grunt.registerTask('gcal', 'Build the Google Calendar submodule', 'concat:gcalJs'); - - config.concat.gcalJs = { - options: { - process: true // replace template variables - }, - src: 'src/gcal/gcal.js', - dest: 'build/out/fullcalendar/gcal.js' - }; - - config.watch.gcalJs = { - files: config.concat.gcalJs.src, - tasks: 'concat:gcalJs' - }; + }); /* Minify the JavaScript @@ -173,7 +96,7 @@ module.exports = function(grunt) { preserveComments: 'some' // keep comments starting with /*! }, expand: true, - src: 'build/out/fullcalendar/fullcalendar.js', + src: 'build/out/fullcalendar/*.js', ext: '.min.js' } @@ -181,12 +104,14 @@ module.exports = function(grunt) { /* Copy Dependencies ----------------------------------------------------------------------------------------------------*/ - config.copy.deps = { + config.copy.dependencies = { expand: true, flatten: true, - src: depFiles, - dest: 'build/out/jquery/' // all depenencies will go in the jquery/ directory for now - // (because we only have jquery and jquery-ui) + src: [ + fileIndex['jquery'].js, + fileIndex['jquery-ui'].js + ], + dest: 'build/out/jquery/' }; @@ -195,11 +120,15 @@ module.exports = function(grunt) { config.copy.demos = { options: { - // while copying demo files over, rewrite "); // all dependencies are in jquery/ for now - } - return tags.join("\n"); - } - /* Copy Misc Files ----------------------------------------------------------------------------------------------------*/ @@ -272,11 +166,11 @@ module.exports = function(grunt) { grunt.registerTask('component', 'Build the FullCalendar component for the Bower package manager', [ 'clean:component', - 'submodules', + 'concat', 'uglify', // we want the minified JS in there 'copy:component', - 'copy:componentReadme', - 'componentConfig' + 'copy:component-readme', + 'component.json' ]); config.copy.component = { @@ -286,12 +180,12 @@ module.exports = function(grunt) { dest: 'build/component/', }; - config.copy.componentReadme = { + config.copy['component-readme'] = { src: 'build/component-readme.md', dest: 'build/component/readme.md' }; - grunt.registerTask('componentConfig', function() { + grunt.registerTask('component.json', function() { grunt.file.write( 'build/component/component.json', JSON.stringify( diff --git a/build/deps.js b/build/deps.js deleted file mode 100644 index 13cb07e..0000000 --- a/build/deps.js +++ /dev/null @@ -1,53 +0,0 @@ - -/* - * This file defines the JS dependencies required to run a barebones FullCalendar example. - * - * Additionally, if run from Node (i.e. the build system), this file will serve as a module that - * exports the dependency file list. - * - * Additionally, if run from a browser, this file will write a \n"); - } - -} \ No newline at end of file diff --git a/build/loader.js b/build/loader.js new file mode 100644 index 0000000..a1568b5 --- /dev/null +++ b/build/loader.js @@ -0,0 +1,312 @@ +(function() { + + +/* + * Modules that will be implicitly included + */ +var DEFAULT_MODULES = [ 'jquery', 'jquery-ui', 'fullcalendar'/*the submodule*/ ]; + + +/* + * Generate HTML script/link tags for loading resources. + * + * @param {String} root + * Path to the project or distributable's root directory from the HTML file running this script. + * + * @param {Object} fileIndex + * File location information for each module/submodule (everything in files.js). + * + * @param {String} moduleString + * A comma-separated string with names of additional modules to load. + * + * @param {string} [buildType] + * unspecified - use source JS/CSS files + * "concat" - use concatenated JS/CSS files + * "min" - use minified JS files, concatenated CSS files + * "dist" - use minified JS files, concatenated CSS files, and write paths for distributable + */ +function buildTags(root, fileIndex, moduleString, buildType) { + + var extraModuleNames = moduleString ? moduleString.split(',') : []; + var moduleNames = DEFAULT_MODULES.concat(extraModuleNames); + + var jsPaths = []; + var cssPaths = []; + var printCssPaths = []; + + var buildRoot; + if (buildType == 'dist') { + buildRoot = root; // in the distributable, the build root IS the root + } + else { + buildRoot = root + '/build/out'; + } + + each(moduleNames, function(moduleName) { + + var submodule = fileIndex.fullcalendar[moduleName]; + var module = submodule || fileIndex[moduleName]; + + if (module) { + + var js = arrayify(module.js); + var css = arrayify(module.css); + var printCss = arrayify(module.printCss); + + if (buildType && submodule) { + // + // paths for a "built" fullcalendar submodule + // + if (js.length) { + jsPaths.push(buildRoot + '/fullcalendar/' + moduleName + (buildType == 'concat' ? '' : '.min') + '.js'); + } + if (css.length) { + cssPaths.push(buildRoot + '/fullcalendar/' + moduleName + '.css'); + } + if (printCss.length) { + printCssPaths.push(buildRoot + '/fullcalendar/' + moduleName + '.print.css'); + } + } + else if (buildType == 'dist' && !submodule) { + // + // 3rd-party paths in the distributable (they all go in jquery/ for now) + // + each(js, function(path) { + jsPaths.push(buildRoot + '/jquery/' + basename(path)); + }); + each(css, function(path) { + cssPaths.push(buildRoot + '/jquery/' + basename(path)); + }); + each(printCss, function(path) { + printCssPaths.push(buildRoot + '/jquery/' + basename(path)); + }); + } + else { + // + // paths for development + // + each(js, function(path) { + if (!/(intro|outro)\.js$/.test(path)) { // don't write syntactically incorrect intro.js/outro.js + jsPaths.push(root + '/' + path); + } + }); + each(css, function(path) { + cssPaths.push(root + '/' + path); + }); + each(printCss, function(path) { + printCssPaths.push(root + '/' + path); + }); + } + } + }); + + return [].concat( + map(cssPaths, buildCssTag), + map(printCssPaths, buildPrintCssTag), + map(jsPaths, buildJsTag) + ) + .join('\n'); +} + + +/* When run in a browser... +====================================================================================================*/ + + +/* + * Writes script/link tags for resources. + * Attributes can be added to loader.js's script tag to change its behavior: + * data-modules="..." (a comma-separated list of additional modules to load) + * data-debug="true" + */ +function run() { + + var thisScript = getLastScript(); + var src = thisScript.getAttribute('src'); + var cwd = dirname(src); + var projectRoot = dirname(cwd); + var moduleString = thisScript.getAttribute('data-modules'); + var debugEnabled = !!thisScript.getAttribute('data-debug'); + var buildType = getQueryStringVar('build'); + + if (debugEnabled) { + buildType = buildType || getCookieVar('build'); // fall back to cookie + initDebug(buildType); + } + + loadScript(projectRoot + '/files.js', function(fileIndex) { + document.write( + buildTags(projectRoot, fileIndex, moduleString, buildType) + ); + }); +} + + +/* + * Installs a build-switching " + + "" + + "" + + "" + + "" + + "" + ); + + var select = form.find('select') + .on('change', function() { + var val = select.val(); + if (!val) { + select.remove(); // erases from resulting query string + } + document.cookie = 'build=' + val; // save cookie + form.submit(); + }) + .val(currentBuildType || ''); + + $('body').append(form); + }; +} + + +/* HTML-building Utilities +====================================================================================================*/ + + +function buildJsTag(path) { + return "" +} + + +function buildCssTag(path) { + return ""; +} + + +function buildPrintCssTag(path) { + return ""; +} + + +/* DOM Utilities +====================================================================================================*/ + +/* + * Loads an external JS file in the global scope, and calls `callback` when done. + * If the JS file is a CommonJS module, the module's "exports" will be given to the callback. + */ +function loadScript(path, callback) { + var funcName = ('_scriptCallback' + Math.random()).replace('.', ''); + document.write(""); + document.write(""); + document.write(""); + window[funcName] = function() { + removeLastScript(); + removeLastScript(); + removeLastScript(); + var exports = module.exports; + delete window.module; + delete window.exports; + delete window[funcName]; + if (callback) { + callback(exports); + } + }; +} + + +function removeLastScript() { + var script = getLastScript(); + script.parentNode.removeChild(script); +} + + +function getLastScript() { + var scripts = document.getElementsByTagName('script'); + return scripts[scripts.length - 1]; +} + + +function getQueryStringVar(name) { + var qs = extractQueryString(window.location.href); + var match = new RegExp(name + '=([^&]*)').exec(qs); + if (match) { + return match[1]; + } +} + + +function getCookieVar(name) { + var match = new RegExp(name + '=([^;]*)').exec(document.cookie); + if (match) { + return match[1]; + } +} + + +/* File Path Utilities +====================================================================================================*/ + + +function basename(path) { + var match = /.*\/(.*)/.exec(path); + return match ? match[1] : path; +} + + +function dirname(path) { + var match = /(.*)\//.exec(path); + return match ? match[1] : '.'; +} + + +function extractQueryString(url) { + var match = /\?(.*)/.exec(url); + return match ? match[1] : ''; +} + + +/* Language Utilities +====================================================================================================*/ + + +function each(a, f) { + for (var i=0; i