diff --git a/Gruntfile.js b/Gruntfile.js index adf51fb34b..2d89ad0178 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -99,7 +99,7 @@ module.exports = function (grunt) { 'public/src/**/*.js', 'node_modules/nodebb-*/**/*.js', '!node_modules/nodebb-*/node_modules/**', - 'node_modules/templates.js/lib/templates.js', + 'node_modules/benchpressjs/build/benchpress.js', '!node_modules/nodebb-*/.git/**', ], options: { diff --git a/install/web.js b/install/web.js index 16bef11509..58767b776a 100644 --- a/install/web.js +++ b/install/web.js @@ -9,6 +9,8 @@ var less = require('less'); var async = require('async'); var uglify = require('uglify-js'); var nconf = require('nconf'); +var Benchpress = require('benchpressjs'); + var app = express(); var server; @@ -35,7 +37,7 @@ web.install = function (port) { winston.info('Launching web installer on port', port); app.use(express.static('public', {})); - app.engine('tpl', require('templates.js').__express); + app.engine('tpl', Benchpress.__express); app.set('view engine', 'tpl'); app.set('views', path.join(__dirname, '../src/views')); app.use(bodyParser.urlencoded({ diff --git a/package.json b/package.json index 3e0f592185..993f5cd1e6 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "async": "2.4.1", "autoprefixer": "7.1.1", "bcryptjs": "2.4.3", + "benchpressjs": "^1.0.1", "body-parser": "^1.9.0", "bootstrap": "^3.3.7", "chart.js": "^2.4.0", @@ -93,7 +94,6 @@ "socketio-wildcard": "2.0.0", "spdx-license-list": "^3.0.1", "string": "^3.0.0", - "templates.js": "0.3.11", "toobusy-js": "^0.5.1", "uglify-js": "^3.0.11", "validator": "7.0.0", diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js index e2303c501c..98580332eb 100644 --- a/public/src/ajaxify.js +++ b/public/src/ajaxify.js @@ -328,20 +328,10 @@ $(document).ready(function () { }; ajaxify.loadTemplate = function (template, callback) { - if (templates.cache[template]) { - callback(templates.cache[template]); - } else { - $.ajax({ - url: config.relative_path + '/assets/templates/' + template + '.tpl?' + config['cache-buster'], - type: 'GET', - success: function (data) { - callback(data.toString()); - }, - error: function (error) { - throw new Error('Unable to load template: ' + template + ' (' + error.statusText + ')'); - }, - }); - } + require([config.relative_path + '/assets/templates/' + template + '.jst'], callback, function (err) { + console.error('Unable to load template: ' + template); + throw err; + }); }; function ajaxifyAnchors() { @@ -424,7 +414,9 @@ $(document).ready(function () { }); } - templates.registerLoader(ajaxify.loadTemplate); + require(['benchpress'], function (Benchpress) { + Benchpress.registerLoader(ajaxify.loadTemplate); + }); if (window.history && window.history.pushState) { // Progressive Enhancement, ajaxify available only to modern browsers @@ -432,9 +424,4 @@ $(document).ready(function () { } app.load(); - - $('[type="text/tpl"][data-template]').each(function () { - templates.cache[$(this).attr('data-template')] = $('
').html($(this).html()).text(); - $(this).parent().remove(); - }); }); diff --git a/public/src/app.js b/public/src/app.js index a45a99c5b3..993c35b6d2 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -11,7 +11,21 @@ app.cacheBuster = null; (function () { var showWelcomeMessage = !!utils.params().loggedin; - templates.setGlobal('config', config); + require(['benchpress'], function (Benchpress) { + Benchpress.setGlobal('config', config); + if (Object.defineProperty) { + Object.defineProperty(window, 'templates', { + configurable: true, + enumerable: true, + get: function () { + console.warn('[deprecated] Accessing benchpress (formerly know as templates.js) globally is deprecated. Use `require(["benchpress"], function (Benchpress) { ... })` instead'); + return Benchpress; + }, + }); + } else { + window.templates = Benchpress; + } + }); app.cacheBuster = config['cache-buster']; diff --git a/public/src/modules/helpers.js b/public/src/modules/helpers.js index e4759935b6..fbac7da1de 100644 --- a/public/src/modules/helpers.js +++ b/public/src/modules/helpers.js @@ -3,15 +3,15 @@ (function (factory) { if (typeof module === 'object' && module.exports) { var relative_path = require('nconf').get('relative_path'); - module.exports = factory(require('../utils'), require('templates.js'), require('string'), relative_path); + module.exports = factory(require('../utils'), require('benchpressjs'), require('string'), relative_path); } else if (typeof define === 'function' && define.amd) { - define('helpers', ['string'], function (string) { - return factory(utils, templates, string, config.relative_path); + define('helpers', ['benchpress', 'string'], function (Benchpress, string) { + return factory(utils, Benchpress, string, config.relative_path); }); } else { window.helpers = factory(utils, templates, window.String, config.relative_path); } -}(function (utils, templates, S, relative_path) { +}(function (utils, Benchpress, S, relative_path) { var helpers = { displayMenuItem: displayMenuItem, buildMetaTag: buildMetaTag, @@ -30,8 +30,13 @@ renderDigestAvatar: renderDigestAvatar, userAgentIcons: userAgentIcons, register: register, + __escape: identity, }; + function identity(str) { + return str; + } + function displayMenuItem(data, index) { var item = data.navigation[index]; if (!item) { @@ -270,7 +275,7 @@ function register() { Object.keys(helpers).forEach(function (helperName) { - templates.registerHelper(helperName, helpers[helperName]); + Benchpress.registerHelper(helperName, helpers[helperName]); }); } diff --git a/src/admin/search.js b/src/admin/search.js index 6ffd9db768..dfc9034658 100644 --- a/src/admin/search.js +++ b/src/admin/search.js @@ -14,10 +14,12 @@ function filterDirectories(directories) { // get the relative path return dir.replace(/^.*(admin.*?).tpl$/, '$1'); }).filter(function (dir) { + // exclude .jst files // exclude partials // only include subpaths // exclude category.tpl, group.tpl, category-analytics.tpl - return !dir.includes('/partials/') && + return !dir.endsWith('.jst') && + !dir.includes('/partials/') && /\/.*\//.test(dir) && !/manage\/(category|group|category-analytics)$/.test(dir); }); diff --git a/src/emailer.js b/src/emailer.js index cc8dbcb01c..bb9bd3d64d 100644 --- a/src/emailer.js +++ b/src/emailer.js @@ -3,7 +3,7 @@ var async = require('async'); var winston = require('winston'); var nconf = require('nconf'); -var templates = require('templates.js'); +var Benchpress = require('benchpressjs'); var nodemailer = require('nodemailer'); var sendmailTransport = require('nodemailer-sendmail-transport'); var smtpTransport = require('nodemailer-smtp-transport'); @@ -173,9 +173,9 @@ Emailer.sendViaFallback = function (data, callback) { }; function render(tpl, params, next) { - if (meta.config['email:custom:' + tpl.replace('emails/', '')]) { - var text = templates.parse(meta.config['email:custom:' + tpl.replace('emails/', '')], params); - next(null, text); + var customTemplate = meta.config['email:custom:' + tpl.replace('emails/', '')]; + if (customTemplate) { + Benchpress.compileParse(customTemplate, params, next); } else { app.render(tpl, params, next); } diff --git a/src/meta/js.js b/src/meta/js.js index 8f483779da..8082be2412 100644 --- a/src/meta/js.js +++ b/src/meta/js.js @@ -29,7 +29,7 @@ JS.scripts = { 'public/vendor/tinycon/tinycon.js', 'public/vendor/xregexp/xregexp.js', 'public/vendor/xregexp/unicode/unicode-base.js', - 'node_modules/templates.js/lib/templates.js', + 'node_modules/benchpressjs/build/benchpress.js', 'public/src/utils.js', 'public/src/sockets.js', 'public/src/app.js', diff --git a/src/meta/templates.js b/src/meta/templates.js index f6776f64e1..18991fc149 100644 --- a/src/meta/templates.js +++ b/src/meta/templates.js @@ -11,6 +11,8 @@ var nconf = require('nconf'); var plugins = require('../plugins'); var file = require('../file'); +var viewsPath = nconf.get('views_dir'); + var Templates = module.exports; Templates.compile = function (callback) { @@ -18,7 +20,6 @@ Templates.compile = function (callback) { var themeConfig = require(nconf.get('theme_config')); var baseTemplatesPaths = themeConfig.baseTheme ? getBaseTemplates(themeConfig.baseTheme) : [nconf.get('base_templates_path')]; - var viewsPath = nconf.get('views_dir'); function processImports(paths, relativePath, source, callback) { var regex = //; @@ -63,9 +64,9 @@ Templates.compile = function (callback) { var source = file.toString(); processImports(paths, relativePath, source, next); }, - function (compiled, next) { + function (source, next) { mkdirp(path.join(viewsPath, path.dirname(relativePath)), function (err) { - next(err, compiled); + next(err, source); }); }, function (compiled, next) { @@ -74,6 +75,9 @@ Templates.compile = function (callback) { ], next); }, next); }, + function (next) { + rimraf(path.join(viewsPath, '*.jst'), next); + }, function (next) { winston.verbose('[meta/templates] Successfully compiled templates.'); next(); @@ -99,7 +103,6 @@ function getBaseTemplates(theme) { function preparePaths(baseTemplatesPaths, callback) { var coreTemplatesPath = nconf.get('core_templates_path'); - var viewsPath = nconf.get('views_dir'); var pluginTemplates; async.waterfall([ function (next) { diff --git a/src/middleware/index.js b/src/middleware/index.js index 9072b0441d..511ae1ddd4 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -2,11 +2,13 @@ var async = require('async'); var path = require('path'); +var fs = require('fs'); var csrf = require('csurf'); var validator = require('validator'); var nconf = require('nconf'); var ensureLoggedIn = require('connect-ensure-login'); var toobusy = require('toobusy-js'); +var Benchpress = require('benchpressjs'); var plugins = require('../plugins'); var meta = require('../meta'); @@ -186,3 +188,33 @@ middleware.delayLoading = function (req, res, next) { // Introduces an artificial delay during load so that brute force attacks are effectively mitigated setTimeout(next, 1000); }; + +var viewsDir = nconf.get('views_dir'); +middleware.templatesOnDemand = function (req, res, next) { + var filePath = req.filePath || path.join(viewsDir, req.path); + if (!filePath.endsWith('.jst')) { + return next(); + } + + async.waterfall([ + function (cb) { + file.exists(filePath, cb); + }, + function (exists, cb) { + if (exists) { + return next(); + } + + fs.readFile(filePath.replace(/\.jst$/, '.tpl'), cb); + }, + function (source, cb) { + Benchpress.precompile({ + source: source.toString(), + minify: global.env !== 'development', + }, cb); + }, + function (compiled, cb) { + fs.writeFile(filePath, compiled, cb); + }, + ], next); +}; diff --git a/src/routes/index.js b/src/routes/index.js index a8c3eeefa7..60c97bd8d3 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -4,11 +4,12 @@ var nconf = require('nconf'); var winston = require('winston'); var path = require('path'); var async = require('async'); +var express = require('express'); + var meta = require('../meta'); var controllers = require('../controllers'); var plugins = require('../plugins'); var user = require('../user'); -var express = require('express'); var accountRoutes = require('./accounts'); var metaRoutes = require('./meta'); @@ -147,6 +148,7 @@ module.exports = function (app, middleware, hotswapIds, callback) { } app.use(middleware.privateUploads); + app.use(relativePath + '/assets/templates', middleware.templatesOnDemand); var statics = [ { route: '/assets', path: path.join(__dirname, '../../build/public') }, diff --git a/src/views/500-embed.tpl b/src/views/500-embed.tpl index 9d911e9848..b1045d431f 100644 --- a/src/views/500-embed.tpl +++ b/src/views/500-embed.tpl @@ -1,8 +1,12 @@ - \ No newline at end of file diff --git a/src/webserver.js b/src/webserver.js index a7dc0a8182..26f4820c15 100644 --- a/src/webserver.js +++ b/src/webserver.js @@ -27,7 +27,7 @@ var plugins = require('./plugins'); var flags = require('./flags'); var routes = require('./routes'); var auth = require('./routes/authentication'); -var templates = require('templates.js'); +var Benchpress = require('benchpressjs'); var helpers = require('../public/src/modules/helpers'); @@ -119,11 +119,24 @@ function setupExpressApp(app, callback) { var middleware = require('./middleware'); var relativePath = nconf.get('relative_path'); + var viewsDir = nconf.get('views_dir'); - app.engine('tpl', templates.__express); + app.engine('tpl', function (filepath, data, next) { + filepath = filepath.replace(/\.tpl$/, '.jst'); + + middleware.templatesOnDemand({ + filePath: filepath, + }, null, function (err) { + if (err) { + return next(err); + } + + Benchpress.__express(filepath, data, next); + }); + }); app.set('view engine', 'tpl'); - app.set('views', nconf.get('views_dir')); - app.set('json spaces', process.env.NODE_ENV === 'development' ? 4 : 0); + app.set('views', viewsDir); + app.set('json spaces', global.env === 'development' ? 4 : 0); app.use(flash()); app.enable('view cache'); diff --git a/src/widgets/index.js b/src/widgets/index.js index 9071989722..98ee50051b 100644 --- a/src/widgets/index.js +++ b/src/widgets/index.js @@ -2,8 +2,8 @@ var async = require('async'); var winston = require('winston'); -var templates = require('templates.js'); var _ = require('lodash'); +var Benchpress = require('benchpressjs'); var plugins = require('../plugins'); var translator = require('../translator'); @@ -93,12 +93,12 @@ function renderWidget(widget, uid, options, callback) { if (widget.data.container && widget.data.container.match('{body}')) { translator.translate(widget.data.title, function (title) { - html = templates.parse(widget.data.container, { + Benchpress.compileParse(widget.data.container, { title: title, body: html, + }, function (err, html) { + next(err, { html: html }); }); - - next(null, { html: html }); }); } else { next(null, { html: html }); diff --git a/test/search-admin.js b/test/search-admin.js index 0d52c8db75..bb0fc6ecdc 100644 --- a/test/search-admin.js +++ b/test/search-admin.js @@ -14,6 +14,15 @@ describe('admin search', function () { ]); done(); }); + it('should exclude .jst files', function (done) { + assert.deepEqual(search.filterDirectories([ + 'hfjksfd/fdsgagag/admin/gdhgfsdg/sggag.tpl', + 'dfahdfsgf/admin/hgkfds/fdhsdfh.jst', + ]), [ + 'admin/gdhgfsdg/sggag', + ]); + done(); + }); it('should exclude partials', function (done) { assert.deepEqual(search.filterDirectories([ 'hfjksfd/fdsgagag/admin/gdhgfsdg/sggag.tpl',