From abffc29128b661e1cd29efeb349641a6fc58b1fb Mon Sep 17 00:00:00 2001
From: Peter Jaszkowiak
Date: Thu, 24 Aug 2017 17:26:50 -0600
Subject: [PATCH] Use Benchpress (#5901)
* Use Benchpress
* Use Benchpress.compileParse
* Error for template load failure
* Use benchpressjs package
* Compile templates on demand
* Fix user settings page
* Fix admin search to exclude `.jst` files
* Fix 500-embed
So ajaxify can still show an error if the server goes down
---
Gruntfile.js | 2 +-
install/web.js | 4 +++-
package.json | 2 +-
public/src/ajaxify.js | 27 +++++++--------------------
public/src/app.js | 16 +++++++++++++++-
public/src/modules/helpers.js | 15 ++++++++++-----
src/admin/search.js | 4 +++-
src/emailer.js | 8 ++++----
src/meta/js.js | 2 +-
src/meta/templates.js | 11 +++++++----
src/middleware/index.js | 32 ++++++++++++++++++++++++++++++++
src/routes/index.js | 4 +++-
src/views/500-embed.tpl | 18 +++++++++++-------
src/webserver.js | 21 +++++++++++++++++----
src/widgets/index.js | 8 ++++----
test/search-admin.js | 9 +++++++++
16 files changed, 128 insertions(+), 55 deletions(-)
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',