diff --git a/.gitignore b/.gitignore index c3499f13f7..f5e5f3d209 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,6 @@ CHANGELOG.md /core/test/functional/*.png config.js + +# Built asset files +/core/built \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js index 146465aaa7..d68e9d4741 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -40,6 +40,16 @@ var path = require('path'), files: ['<%= paths.adminAssets %>/sass/**/*'], tasks: ['sass:admin'] }, + concat: { + files: [ + 'core/client/*.js', + 'core/client/helpers/*.js', + 'core/client/models/*.js', + 'core/client/tpl/*.js', + 'core/client/views/*.js' + ], + tasks: ['concat'] + }, livereload: { files: [ // Theme CSS @@ -49,11 +59,7 @@ var path = require('path'), // Admin CSS '<%= paths.adminAssets %>/css/*.css', // Admin JS - 'core/client/*.js', - 'core/client/helpers/*.js', - 'core/client/models/*.js', - 'core/client/tpl/*.js', - 'core/client/views/*.js' + 'core/built/scripts/*.js' ], options: { livereload: true @@ -343,6 +349,122 @@ var path = require('path'), tagMessage: '<%= buildType %> Release %VERSION%', pushTo: "origin build" } + }, + + concat: { + dev: { + files: { + "core/built/scripts/vendor.js": [ + "core/shared/vendor/jquery/jquery.js", + "core/shared/vendor/jquery/jquery-ui-1.10.3.custom.min.js", + "core/client/assets/lib/jquery-utils.js", + "core/client/assets/lib/uploader.js", + "core/shared/vendor/underscore.js", + "core/shared/vendor/backbone/backbone.js", + "core/shared/vendor/handlebars/handlebars-runtime.js", + "core/shared/vendor/moment.js", + + "core/client/assets/vendor/icheck/jquery.icheck.min.js", + + "core/shared/vendor/jquery/jquery.ui.widget.js", + "core/shared/vendor/jquery/jquery.iframe-transport.js", + "core/shared/vendor/jquery/jquery.fileupload.js", + + "core/client/assets/vendor/codemirror/codemirror.js", + "core/client/assets/vendor/codemirror/addon/mode/overlay.js", + "core/client/assets/vendor/codemirror/mode/markdown/markdown.js", + "core/client/assets/vendor/codemirror/mode/gfm/gfm.js", + "core/client/assets/vendor/showdown/showdown.js", + "core/client/assets/vendor/showdown/extensions/ghostdown.js", + "core/shared/vendor/showdown/extensions/github.js", + "core/client/assets/vendor/shortcuts.js", + "core/client/assets/vendor/validator-client.js", + "core/client/assets/vendor/countable.js", + "core/client/assets/vendor/to-title-case.js", + "core/client/assets/vendor/packery.pkgd.min.js", + "core/client/assets/vendor/jquery.hammer.min.js" + ], + + "core/built/scripts/helpers.js": [ + "core/client/init.js", + + "core/client/mobile-interactions.js", + "core/client/toggle.js", + "core/client/markdown-actions.js", + "core/client/helpers/index.js" + ], + + "core/built/scripts/templates.js": [ + "core/client/tpl/hbs-tpl.js" + ], + + "core/built/scripts/models.js": [ + "core/client/models/**/*.js" + ], + + "core/built/scripts/views.js": [ + "core/client/views/**/*.js", + "core/client/router.js" + ] + } + }, + prod: { + files: { + "core/built/scripts/ghost.js": [ + "core/shared/vendor/jquery/jquery.js", + "core/shared/vendor/jquery/jquery-ui-1.10.3.custom.min.js", + "core/client/assets/lib/jquery-utils.js", + "core/client/assets/lib/uploader.js", + "core/shared/vendor/underscore.js", + "core/shared/vendor/backbone/backbone.js", + "core/shared/vendor/handlebars/handlebars-runtime.js", + "core/shared/vendor/moment.js", + + "core/client/assets/vendor/icheck/jquery.icheck.min.js", + + "core/shared/vendor/jquery/jquery.ui.widget.js", + "core/shared/vendor/jquery/jquery.iframe-transport.js", + "core/shared/vendor/jquery/jquery.fileupload.js", + + "core/client/assets/vendor/codemirror/codemirror.js", + "core/client/assets/vendor/codemirror/addon/mode/overlay.js", + "core/client/assets/vendor/codemirror/mode/markdown/markdown.js", + "core/client/assets/vendor/codemirror/mode/gfm/gfm.js", + "core/client/assets/vendor/showdown/showdown.js", + "core/client/assets/vendor/showdown/extensions/ghostdown.js", + "core/shared/vendor/showdown/extensions/github.js", + "core/client/assets/vendor/shortcuts.js", + "core/client/assets/vendor/validator-client.js", + "core/client/assets/vendor/countable.js", + "core/client/assets/vendor/to-title-case.js", + "core/client/assets/vendor/packery.pkgd.min.js", + "core/client/assets/vendor/jquery.hammer.min.js", + + "core/client/init.js", + + "core/client/mobile-interactions.js", + "core/client/toggle.js", + "core/client/markdown-actions.js", + "core/client/helpers/index.js", + + "core/client/tpl/hbs-tpl.js", + + "core/client/models/**/*.js", + + "core/client/views/**/*.js", + + "core/client/router.js" + ] + } + } + }, + + uglify: { + prod: { + files: { + "core/built/scripts/ghost.min.js": "core/built/scripts/ghost.js" + } + } } }; @@ -654,6 +776,8 @@ var path = require('path'), "shell:bourbon", "sass:admin", "handlebars", + "concat", + "uglify", "bump:build", "updateCurrentPackageInfo", "changelog", @@ -666,6 +790,8 @@ var path = require('path'), "shell:bourbon", "sass:admin", "handlebars", + "concat", + "uglify", "bump:build", "updateCurrentPackageInfo", "changelog", @@ -677,6 +803,8 @@ var path = require('path'), "shell:bourbon", "sass:admin", "handlebars", + "concat", + "uglify", "changelog", "copy:build", "compress:build" @@ -692,7 +820,7 @@ var path = require('path'), // Prepare the project for development // TODO: Git submodule init/update (https://github.com/jaubourg/grunt-update-submodules)? - grunt.registerTask("init", ["shell:bourbon", "sass:admin", 'handlebars']); + grunt.registerTask("init", ["shell:bourbon", "default"]); // Run unit tests grunt.registerTask("test-unit", ['setTestEnv', 'loadConfig', "mochacli:all"]); @@ -706,8 +834,10 @@ var path = require('path'), // Generate Docs grunt.registerTask("docs", ["groc"]); + // TODO: Production build task that minifies with uglify:prod + // When you just say "grunt" - grunt.registerTask("default", ['sass:admin', 'handlebars']); + grunt.registerTask("default", ['sass:admin', 'handlebars', 'concat']); }; module.exports = configureGrunt; diff --git a/core/client/assets/vendor/shortcuts.js b/core/client/assets/vendor/shortcuts.js index 1704005d82..32ce8a5331 100644 --- a/core/client/assets/vendor/shortcuts.js +++ b/core/client/assets/vendor/shortcuts.js @@ -220,4 +220,4 @@ shortcut = { else if(ele.removeEventListener) ele.removeEventListener(type, callback, false); else ele['on'+type] = false; } -} \ No newline at end of file +}; \ No newline at end of file diff --git a/core/ghost.js b/core/ghost.js index d37364e67f..2f014b194e 100644 --- a/core/ghost.js +++ b/core/ghost.js @@ -2,7 +2,7 @@ // Defines core methods required to build the application // Module dependencies -var config = require('./../config'), +var config = require('../config'), when = require('when'), express = require('express'), errors = require('./server/errorHandling'), @@ -340,11 +340,28 @@ Ghost.prototype.initPlugins = function (pluginsToLoad) { // Initialise Theme or admin Ghost.prototype.initTheme = function (app) { var self = this, - hbsOptions; + oneYear = 31536000000; + + app.set('view engine', 'hbs'); + // return the correct mime type for woff files + express['static'].mime.define({'application/font-woff': ['woff']}); + + // Serve the assets of the current theme + app.use(express['static'](self.paths().activeTheme)); + + // Serve shared assets and images + app.use('/shared', express['static'](path.join(__dirname, '/shared'))); + app.use('/content/images', express['static'](path.join(__dirname, '/../content/images'))); + + // Serve our built scripts; can't use /scripts here because themes already are + app.use("/built/scripts", express['static'](path.join(__dirname, '/built/scripts'), { + // Put a maxAge of one year on built scripts + maxAge: oneYear + })); + return function initTheme(req, res, next) { - app.set('view engine', 'hbs'); - // return the correct mime type for woff files - express['static'].mime.define({'application/font-woff': ['woff']}); + + var hbsOptions; if (!res.isAdmin) { @@ -369,9 +386,7 @@ Ghost.prototype.initTheme = function (app) { app.use('/public', express['static'](path.join(__dirname, '/client/assets'))); app.use('/public', express['static'](path.join(__dirname, '/client'))); } - app.use(express['static'](self.paths().activeTheme)); - app.use('/shared', express['static'](path.join(__dirname, '/shared'))); - app.use('/content/images', express['static'](path.join(__dirname, '/../content/images'))); + next(); }; }; diff --git a/core/server/helpers/index.js b/core/server/helpers/index.js index e3de0b76a3..dcd6d1f173 100644 --- a/core/server/helpers/index.js +++ b/core/server/helpers/index.js @@ -3,13 +3,17 @@ var _ = require('underscore'), downsize = require('downsize'), when = require('when'), hbs = require('express-hbs'), + packageInfo = require('../../../package.json'), errors = require('../errorHandling'), models = require('../models'), coreHelpers; coreHelpers = function (ghost) { - var paginationHelper; + var paginationHelper, + scriptTemplate = _.template(""), + isProduction = process.env.NODE_ENV === "production", + version = encodeURIComponent(packageInfo.version); /** * [ description] @@ -317,6 +321,32 @@ coreHelpers = function (ghost) { return ret; }); + // A helper for inserting the javascript tags with version hashes + ghost.registerThemeHelper("ghostScriptTags", function () { + var scriptFiles = []; + + if (isProduction) { + scriptFiles.push("ghost.min.js"); + } else { + scriptFiles = [ + "vendor.js", + "helpers.js", + "templates.js", + "models.js", + "views.js" + ]; + } + + scriptFiles = _.map(scriptFiles, function (fileName) { + return scriptTemplate({ + name: fileName, + version: version + }); + }); + + return scriptFiles.join(""); + }); + // ## Template driven helpers // Template driven helpers require that their template is loaded before they can be registered. diff --git a/core/server/views/default.hbs b/core/server/views/default.hbs index 446bb643fd..bccf5638ac 100644 --- a/core/server/views/default.hbs +++ b/core/server/views/default.hbs @@ -36,63 +36,10 @@ - - - - - - - + {{{ghostScriptTags}}} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {{{block "bodyScripts"}}} + diff --git a/core/test/unit/admin_spec.js b/core/test/unit/admin_spec.js index 5a82fd832c..e0de048fdb 100644 --- a/core/test/unit/admin_spec.js +++ b/core/test/unit/admin_spec.js @@ -90,7 +90,8 @@ describe('Admin Controller', function() { }); it('should send correct path to image when today is in Sep 2013', function(done) { - clock = sinon.useFakeTimers(1378585460681); // Sat Sep 07 2013 21:24:20 GMT+0100 (BST) + // Sat Sep 07 2013 21:24 + clock = sinon.useFakeTimers(new Date(2013, 8, 7, 21, 24).getTime()); sinon.stub(res, 'send', function(data) { data.should.equal('/content/images/2013/Sep/IMAGE.jpg'); return done(); @@ -100,7 +101,8 @@ describe('Admin Controller', function() { }); it('should send correct path to image when today is in Jan 2014', function(done) { - clock = sinon.useFakeTimers(1388707200000); // Wed Jan 03 2014 00:00:00 GMT+0000 (GMT) + // Jan 1 2014 12:00 + clock = sinon.useFakeTimers(new Date(2014, 0, 1, 12).getTime()); sinon.stub(res, 'send', function(data) { data.should.equal('/content/images/2014/Jan/IMAGE.jpg'); return done(); @@ -110,7 +112,8 @@ describe('Admin Controller', function() { }); it('can upload two different images with the same name without overwriting the first', function(done) { - clock = sinon.useFakeTimers(1378634224614); // Sun Sep 08 2013 10:57:04 GMT+0100 (BST) + // Sun Sep 08 2013 10:57 + clock = sinon.useFakeTimers(new Date(2013, 8, 8, 10, 57).getTime()); fs.exists.withArgs('content/images/2013/Sep/IMAGE.jpg').yields(true); fs.exists.withArgs('content/images/2013/Sep/IMAGE-1.jpg').yields(false); @@ -123,7 +126,8 @@ describe('Admin Controller', function() { }); it('can upload five different images with the same name without overwriting the first', function(done) { - clock = sinon.useFakeTimers(1378634224614); // Sun Sep 08 2013 10:57:04 GMT+0100 (BST) + // Sun Sep 08 2013 10:57 + clock = sinon.useFakeTimers(new Date(2013, 8, 8, 10, 57).getTime()); fs.exists.withArgs('content/images/2013/Sep/IMAGE.jpg').yields(true); fs.exists.withArgs('content/images/2013/Sep/IMAGE-1.jpg').yields(true); fs.exists.withArgs('content/images/2013/Sep/IMAGE-2.jpg').yields(true); diff --git a/package.json b/package.json index 1690fb5aab..95e116dc97 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,8 @@ "grunt-bump": "~0.0.11", "grunt-contrib-copy": "~0.4.1", "grunt-contrib-compress": "~0.5.2", + "grunt-contrib-concat": "~0.3.0", + "grunt-contrib-uglify": "~0.2.4", "grunt-groc": "~0.3.0", "grunt-mocha-cli": "~1.0.6", "grunt-express-server": "~0.4.2",