From 04d31fe1d4f09fef60ee0c07a3982d30fd12992c Mon Sep 17 00:00:00 2001 From: Peter Jaszkowiak Date: Sun, 15 Jul 2018 00:12:37 -0600 Subject: [PATCH] Precompile all templates - Benchpress compilation is 33x faster now - Native module with JS fallback and pre-built binaries - Dev template build is <1sec now - Minified template build is ~5sec (uglify accounts for almost all) --- install/package.json | 2 +- src/emailer.js | 24 +++++++++-------- src/meta/templates.js | 47 +++++++++++++++++++++++++++++++-- src/middleware/index.js | 57 ----------------------------------------- src/routes/index.js | 1 - src/webserver.js | 10 +------- test/controllers.js | 6 +++-- 7 files changed, 65 insertions(+), 82 deletions(-) diff --git a/install/package.json b/install/package.json index e29846770c..5db98b4e62 100644 --- a/install/package.json +++ b/install/package.json @@ -22,7 +22,7 @@ "async": "2.6.1", "autoprefixer": "^8.5.2", "bcryptjs": "2.4.3", - "benchpressjs": "^1.2.2", + "benchpressjs": "^1.2.3", "body-parser": "^1.18.2", "bootstrap": "^3.3.7", "chart.js": "^2.7.1", diff --git a/src/emailer.js b/src/emailer.js index 184eb5d95b..6b74158b22 100644 --- a/src/emailer.js +++ b/src/emailer.js @@ -289,22 +289,26 @@ Emailer.sendViaFallback = function (data, callback) { function buildCustomTemplates(config) { async.waterfall([ function (next) { - Emailer.getTemplates(config, next); + async.parallel({ + templates: function (cb) { + Emailer.getTemplates(config, cb); + }, + paths: function (cb) { + file.walk(viewsDir, cb); + }, + }, next); }, - function (templates, next) { - templates = templates.filter(function (template) { + function (result, next) { + var templates = result.templates.filter(function (template) { return template.isCustom && template.text !== prevConfig['email:custom:' + path]; }); + var paths = _.fromPairs(result.paths.map(function (p) { + var relative = path.relative(viewsDir, p).replace(/\\/g, '/'); + return [relative, p]; + })); async.each(templates, function (template, next) { async.waterfall([ function (next) { - file.walk(viewsDir, next); - }, - function (paths, next) { - paths = _.fromPairs(paths.map(function (p) { - var relative = path.relative(viewsDir, p).replace(/\\/g, '/'); - return [relative, p]; - })); meta.templates.processImports(paths, template.path, template.text, next); }, function (source, next) { diff --git a/src/meta/templates.js b/src/meta/templates.js index 1ccf14c8e3..11635dcb50 100644 --- a/src/meta/templates.js +++ b/src/meta/templates.js @@ -8,6 +8,7 @@ var path = require('path'); var fs = require('fs'); var nconf = require('nconf'); var _ = require('lodash'); +var Benchpress = require('benchpressjs'); var plugins = require('../plugins'); var file = require('../file'); @@ -113,6 +114,34 @@ function getTemplateFiles(dirs, callback) { ], callback); } +function compileTemplate(filename, source, callback) { + async.waterfall([ + function (next) { + file.walk(viewsPath, next); + }, + function (paths, next) { + paths = _.fromPairs(paths.map(function (p) { + var relative = path.relative(viewsPath, p).replace(/\\/g, '/'); + return [relative, p]; + })); + async.waterfall([ + function (next) { + processImports(paths, filename, source, next); + }, + function (source, next) { + Benchpress.precompile(source, { + minify: global.env !== 'development', + }, next); + }, + function (compiled, next) { + fs.writeFile(path.join(viewsPath, filename.replace(/\.tpl$/, '.js')), compiled, next); + }, + ], next); + }, + ], callback); +} +Templates.compileTemplate = compileTemplate; + function compile(callback) { callback = callback || function () {}; @@ -144,8 +173,22 @@ function compile(callback) { next(err, source); }); }, - function (compiled, next) { - fs.writeFile(path.join(viewsPath, name), compiled, next); + function (imported, next) { + async.parallel([ + function (cb) { + fs.writeFile(path.join(viewsPath, name), imported, cb); + }, + function (cb) { + Benchpress.precompile(imported, { minify: global.env !== 'development' }, function (err, compiled) { + if (err) { + cb(err); + return; + } + + fs.writeFile(path.join(viewsPath, name.replace(/\.tpl$/, '.js')), compiled, cb); + }); + }, + ], next); }, ], next); }, next); diff --git a/src/middleware/index.js b/src/middleware/index.js index 5c58771282..5eba1ed3dc 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -2,13 +2,11 @@ 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 LRU = require('lru-cache'); var plugins = require('../plugins'); @@ -207,58 +205,3 @@ middleware.delayLoading = function (req, res, next) { setTimeout(next, 1000); }; - -var viewsDir = nconf.get('views_dir'); -var workingCache = {}; - -middleware.templatesOnDemand = function (req, res, next) { - var filePath = req.filePath || path.join(viewsDir, req.path); - if (!filePath.endsWith('.js')) { - return next(); - } - var tplPath = filePath.replace(/\.js$/, '.tpl'); - if (workingCache[filePath]) { - workingCache[filePath].push(next); - return; - } - - async.waterfall([ - function (cb) { - file.exists(filePath, cb); - }, - function (exists, cb) { - if (exists) { - return next(); - } - - // need to check here again - // because compilation could have started since last check - if (workingCache[filePath]) { - workingCache[filePath].push(next); - return; - } - - workingCache[filePath] = [next]; - fs.readFile(tplPath, 'utf8', cb); - }, - function (source, cb) { - Benchpress.precompile({ - source: source, - minify: global.env !== 'development', - }, cb); - }, - function (compiled, cb) { - if (!compiled) { - return cb(new Error('[[error:templatesOnDemand.compiled-template-empty, ' + tplPath + ']]')); - } - fs.writeFile(filePath, compiled, cb); - }, - ], function (err) { - var arr = workingCache[filePath]; - workingCache[filePath] = null; - - arr.forEach(function (callback) { - callback(err); - }); - }); -}; diff --git a/src/routes/index.js b/src/routes/index.js index 0b5b37c7b2..1d5d4fe1ee 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -152,7 +152,6 @@ 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/webserver.js b/src/webserver.js index be4c02f443..86a84a1459 100644 --- a/src/webserver.js +++ b/src/webserver.js @@ -147,15 +147,7 @@ function setupExpressApp(app, callback) { app.engine('tpl', function (filepath, data, next) { filepath = filepath.replace(/\.tpl$/, '.js'); - middleware.templatesOnDemand({ - filePath: filepath, - }, null, function (err) { - if (err) { - return next(err); - } - - Benchpress.__express(filepath, data, next); - }); + Benchpress.__express(filepath, data, next); }); app.set('view engine', 'tpl'); app.set('views', viewsDir); diff --git a/test/controllers.js b/test/controllers.js index 2104fc3230..65ec3c6b91 100644 --- a/test/controllers.js +++ b/test/controllers.js @@ -72,15 +72,17 @@ describe('Controllers', function () { }); } var message = utils.generateUUID(); - var tplPath = path.join(nconf.get('views_dir'), 'custom.tpl'); + var name = 'custom.tpl'; + var tplPath = path.join(nconf.get('views_dir'), name); - before(function () { + before(function (done) { plugins.registerHook('myTestPlugin', { hook: 'action:homepage.get:custom', method: hookMethod, }); fs.writeFileSync(tplPath, message); + meta.templates.compileTemplate(name, message, done); }); it('should load default', function (done) {