From 91a4477ad0d293949e19668f028bcbacc7bba174 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Wed, 30 Oct 2019 02:21:53 -0400 Subject: [PATCH] replace handlebars with squirrelly (#5521) * WIP: rip out handlebars and implement with squirrelly - handle caching ourselves - TODO: add tests, make sure escaping and all that jazz works * fixes squirrelly template handling * only fire mocha events when in run mode * add unit tests for template engine rendering + caching --- .../cypress/integration/e2e/rerun_spec.coffee | 6 ++- packages/runner/static/index.html | 2 +- packages/server/lib/html/iframe.html | 8 +-- packages/server/lib/server.coffee | 8 +-- packages/server/lib/template_engine.js | 29 ++++++++++ packages/server/package.json | 2 +- .../server/test/unit/template_engine_spec.js | 53 +++++++++++++++++++ 7 files changed, 94 insertions(+), 14 deletions(-) create mode 100644 packages/server/lib/template_engine.js create mode 100644 packages/server/test/unit/template_engine_spec.js diff --git a/packages/driver/test/cypress/integration/e2e/rerun_spec.coffee b/packages/driver/test/cypress/integration/e2e/rerun_spec.coffee index c0926be2faa9..abc29c91fb96 100644 --- a/packages/driver/test/cypress/integration/e2e/rerun_spec.coffee +++ b/packages/driver/test/cypress/integration/e2e/rerun_spec.coffee @@ -7,6 +7,8 @@ window.top.hasRunOnce ?= false window.top.previousHash ?= window.top.location.hash +isTextTerminal = Cypress.config("isTextTerminal") + describe "rerun state bugs", -> it "stores viewport globally and does not hang on re-runs", -> ## NOTE: there's probably other ways to cause a re-run @@ -28,8 +30,8 @@ describe "rerun state bugs", -> else if window.top.location.hash is window.top.previousHash ## 3rd time around - ## let the mocha end events fire - Cypress.config("isTextTerminal", true) + ## let the mocha end events fire if they're supposed to + Cypress.config("isTextTerminal", isTextTerminal) else ## our test has already run so remove ## the query param diff --git a/packages/runner/static/index.html b/packages/runner/static/index.html index c6948debb63a..8243cf6e261a 100644 --- a/packages/runner/static/index.html +++ b/packages/runner/static/index.html @@ -17,7 +17,7 @@ window.__Cypress__ = true setTimeout(function(){ - Runner.start(document.getElementById('app'), "{{{base64Config}}}") + Runner.start(document.getElementById('app'), "{{base64Config | safe}}") }, 0) diff --git a/packages/server/lib/html/iframe.html b/packages/server/lib/html/iframe.html index e8164328fb13..35fcfe497642 100644 --- a/packages/server/lib/html/iframe.html +++ b/packages/server/lib/html/iframe.html @@ -17,12 +17,12 @@ })(window.opener || window.parent); - {{#each javascripts}} - + {{each(options.javascripts)}} + {{/each}} - {{#each specs}} - + {{each(options.specs)}} + {{/each}} diff --git a/packages/server/lib/server.coffee b/packages/server/lib/server.coffee index fe6e7bb25d18..61bf235916c9 100644 --- a/packages/server/lib/server.coffee +++ b/packages/server/lib/server.coffee @@ -1,5 +1,4 @@ _ = require("lodash") -exphbs = require("express-handlebars") url = require("url") http = require("http") concatStream = require("concat-stream") @@ -31,6 +30,7 @@ logger = require("./logger") Socket = require("./socket") Request = require("./request") fileServer = require("./file_server") +templateEngine = require("./template_engine") DEFAULT_DOMAIN_NAME = "localhost" fullyQualifiedRe = /^https?:\/\// @@ -85,11 +85,7 @@ class Server ## since we use absolute paths, configure express-handlebars to not automatically find layouts ## https://github.com/cypress-io/cypress/issues/2891 - app.engine("html", exphbs({ - defaultLayout: false - layoutsDir: [] - partialsDir: [] - })) + app.engine("html", templateEngine.render) ## handle the proxied url in case ## we have not yet started our websocket server diff --git a/packages/server/lib/template_engine.js b/packages/server/lib/template_engine.js new file mode 100644 index 000000000000..44ca41e0bcc9 --- /dev/null +++ b/packages/server/lib/template_engine.js @@ -0,0 +1,29 @@ +const Sqrl = require('squirrelly') +const fs = require('./util/fs') + +const cache = {} + +module.exports = { + cache, + + render (filePath, options, cb) { + const cachedFn = cache[filePath] + + // if we already have a cachedFn function + if (cachedFn) { + // just return it and move in + return cb(null, cachedFn(options, Sqrl)) + } + + // else go read it off the filesystem + return fs + .readFileAsync(filePath, 'utf8') + .then((str) => { + // and cache the Sqrl compiled template fn + const compiledFn = cache[filePath] = Sqrl.Compile(str) + + return compiledFn(options, Sqrl) + }) + .asCallback(cb) + }, +} diff --git a/packages/server/package.json b/packages/server/package.json index 872e63b4e1a8..af3b800caa18 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -72,7 +72,6 @@ "evil-dns": "0.2.0", "execa": "1.0.0", "express": "4.16.4", - "express-handlebars": "3.0.2", "find-process": "1.4.1", "fix-path": "2.1.0", "fluent-ffmpeg": "2.1.2", @@ -125,6 +124,7 @@ "shell-env": "3.0.0", "signal-exit": "3.0.2", "sinon": "5.1.1", + "squirrelly": "7.7.0", "strip-ansi": "3.0.1", "syntax-error": "1.4.0", "term-size": "2.1.0", diff --git a/packages/server/test/unit/template_engine_spec.js b/packages/server/test/unit/template_engine_spec.js new file mode 100644 index 000000000000..a136cc407004 --- /dev/null +++ b/packages/server/test/unit/template_engine_spec.js @@ -0,0 +1,53 @@ +require('../spec_helper') + +const os = require('os') +const path = require('path') +const Bluebird = require('bluebird') +const { cache, render } = require('../../lib/template_engine') +const fs = require('../../lib/util/fs') + +describe('lib/template_engine', () => { + it('renders and caches a template function', () => { + sinon.spy(fs, 'readFile') + + expect(cache).to.deep.eq({}) + + const tmpPath = path.join(os.tmpdir(), 'index.html') + + return fs + .writeFileAsync(tmpPath, 'My favorite template engine is {{favorite}}.') + .then(() => { + return Bluebird.fromCallback((cb) => { + const opts = { + favorite: 'Squirrelly', + } + + return render(tmpPath, opts, cb) + }) + }) + .then((str) => { + expect(str).to.eq('My favorite template engine is Squirrelly.') + + expect(fs.readFile).to.be.calledOnce + + const compiledFn = cache[tmpPath] + + expect(compiledFn).to.be.a('function') + + return Bluebird.fromCallback((cb) => { + const opts = { + favorite: 'Squirrelly2', + } + + return render(tmpPath, opts, cb) + }) + .then((str) => { + expect(str).to.eq('My favorite template engine is Squirrelly2.') + + expect(cache[tmpPath]).to.eq(compiledFn) + + expect(fs.readFile).to.be.calledOnce + }) + }) + }) +})