diff --git a/docs/express-marko.md b/docs/express-marko.md index c55e5f9c9..14a91c96a 100644 --- a/docs/express-marko.md +++ b/docs/express-marko.md @@ -10,6 +10,10 @@ npm install marko --save # Usage +Marko provides a submodule (`marko/express`) to add a `res.marko` method to the express response object. This function works much like `res.render`, but doesn't impose the restrictions of the express view engine and allows you to take full advantage of Marko's streaming and modular approach to templates. + +By using `res.marko` you'll automatically have access to `req`, `res`, `app`, `app.locals`, and `res.locals` from within your Marko template and custom tags. These values are added to `out.global`. + ```javascript require('marko/node-require').install(); @@ -18,13 +22,16 @@ var template = require('./template.marko'); var app = express(); +//enable res.marko +require('marko/express').injectInto(express); + app.get('/', function(req, res) { - template.render({ - name: 'Frank', - count: 30, - colors: ['red', 'green', 'blue'] - }, res); + res.marko(template, { + name: 'Frank', + count: 30, + colors: ['red', 'green', 'blue'] + }); }); app.listen(8080); -``` \ No newline at end of file +``` diff --git a/express.js b/express.js new file mode 100644 index 000000000..9c73e1cf0 --- /dev/null +++ b/express.js @@ -0,0 +1,32 @@ +var assign = require('object-assign'); + +exports.injectInto = function injectInto(express) { + if(express.response.marko) return; + + express.response.marko = function(template, data) { + if(typeof template === 'string') { + throw new Error( + 'res.marko does not take a template name or path like res.render. ' + + 'Instead you should use `require(\'./path/to/template.marko\')` ' + + 'and pass the loaded template to this function.' + ) + } + + var res = this; + var req = res.req; + var app = res.app; + var $global = assign({ app, req, res }, app.locals, res.locals); + + if (data) { + data = assign(data, { + $global: assign($global, data.$global) + }); + } else { + data = { $global }; + } + + res.set({ 'content-type': 'text/html; charset=utf-8' }); + + template.render(data, res); + }; +}; diff --git a/package.json b/package.json index c7488f6aa..fb3a8b9e9 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "test-fast": "node_modules/.bin/mocha --ui bdd --reporter spec ./test/render-test", "test-async": "node_modules/.bin/mocha --ui bdd --reporter spec ./test/render-async-test", "test-taglib-loader": "node_modules/.bin/mocha --ui bdd --reporter spec ./test/taglib-loader-test", + "test-express": "node_modules/.bin/mocha --ui bdd --reporter spec ./test/express-test", "jshint": "node_modules/.bin/jshint compiler/ runtime/ taglibs/" }, "author": "Patrick Steele-Idem ", @@ -33,6 +34,7 @@ "htmljs-parser": "^1.5.3", "lasso-package-root": "^1.0.0", "minimatch": "^0.2.14", + "object-assign": "^4.1.0", "property-handlers": "^1.0.0", "raptor-args": "^1.0.0", "raptor-async": "^1.1.2", @@ -50,8 +52,10 @@ "devDependencies": { "bluebird": "^2.9.30", "chai": "^3.3.0", + "express": "^4.13.4", "jshint": "^2.5.0", "mocha": "^2.3.3", + "request": "^2.72.0", "through": "^2.3.4" }, "license": "Apache-2.0", diff --git a/test/autotests/express/headers/template.marko b/test/autotests/express/headers/template.marko new file mode 100644 index 000000000..7c89b545c --- /dev/null +++ b/test/autotests/express/headers/template.marko @@ -0,0 +1 @@ +
diff --git a/test/autotests/express/headers/test.js b/test/autotests/express/headers/test.js new file mode 100644 index 000000000..19b8552e7 --- /dev/null +++ b/test/autotests/express/headers/test.js @@ -0,0 +1,25 @@ +exports.createApp = function(express, markoExpress) { + var app = express(); + + markoExpress.injectInto(express); + + app.locals.foo = 'FOO'; + + app.use(function(req, res, next) { + res.locals.bar = 'BAR'; + next(); + }); + + return app; +}; + +exports.createController = function(template) { + return function(req, res) { + res.marko(template); + }; +} + +exports.checkResponse = function(response, expect, helpers) { + expect(response.headers['content-type']).to.equal('text/html; charset=utf-8'); + expect(response.body).to.equal('
'); +} diff --git a/test/autotests/express/locals/expected.html b/test/autotests/express/locals/expected.html new file mode 100644 index 000000000..229ac7d45 --- /dev/null +++ b/test/autotests/express/locals/expected.html @@ -0,0 +1 @@ +
FOOBAR
\ No newline at end of file diff --git a/test/autotests/express/locals/template.marko b/test/autotests/express/locals/template.marko new file mode 100644 index 000000000..f2c155df9 --- /dev/null +++ b/test/autotests/express/locals/template.marko @@ -0,0 +1 @@ +
${out.global.foo}${out.global.bar}
diff --git a/test/autotests/express/locals/test.js b/test/autotests/express/locals/test.js new file mode 100644 index 000000000..a841bd39c --- /dev/null +++ b/test/autotests/express/locals/test.js @@ -0,0 +1,20 @@ +exports.createApp = function(express, markoExpress) { + markoExpress.injectInto(express); + + var app = express(); + + app.locals.foo = 'FOO'; + + app.use(function(req, res, next) { + res.locals.bar = 'BAR'; + next(); + }); + + return app; +}; + +exports.createController = function(template) { + return function(req, res) { + res.marko(template); + }; +} diff --git a/test/autotests/express/priority/expected.html b/test/autotests/express/priority/expected.html new file mode 100644 index 000000000..8e6407eee --- /dev/null +++ b/test/autotests/express/priority/expected.html @@ -0,0 +1 @@ +
DATARESAPPHELLO
\ No newline at end of file diff --git a/test/autotests/express/priority/template.marko b/test/autotests/express/priority/template.marko new file mode 100644 index 000000000..e7b92e89b --- /dev/null +++ b/test/autotests/express/priority/template.marko @@ -0,0 +1 @@ +
${out.global.foo}${out.global.bar}${out.global.baz}${data.test}
diff --git a/test/autotests/express/priority/test.js b/test/autotests/express/priority/test.js new file mode 100644 index 000000000..ee92b2415 --- /dev/null +++ b/test/autotests/express/priority/test.js @@ -0,0 +1,23 @@ +exports.createApp = function(express, markoExpress) { + markoExpress.injectInto(express); + + var app = express(); + + app.locals.foo = 'APP'; + app.locals.bar = 'APP'; + app.locals.baz = 'APP'; + + app.use(function(req, res, next) { + res.locals.foo = 'RES'; + res.locals.bar = 'RES'; + next(); + }); + + return app; +}; + +exports.createController = function(template) { + return function(req, res) { + res.marko(template, { $global:{ foo:'DATA' }, test:'HELLO' }); + }; +} diff --git a/test/express-test.js b/test/express-test.js new file mode 100644 index 000000000..76259c325 --- /dev/null +++ b/test/express-test.js @@ -0,0 +1,81 @@ +'use strict'; +require('./patch-module'); + +var chai = require('chai'); +chai.config.includeStack = true; +var path = require('path'); +var marko = require('../'); +var autotest = require('./autotest'); +var express = require('express'); +var markoExpress = require('../express'); +var request = require('request'); +var fs = require('fs'); + +require('../node-require').install(); + +describe('render', function() { + var autoTestDir = path.join(__dirname, 'autotests/express'); + + autotest.scanDir( + autoTestDir, + function run(dir, helpers, done) { + var mainPath = path.join(dir, 'test.js'); + var templatePath = path.join(dir, 'template.marko'); + + var main = fs.existsSync(mainPath) ? require(mainPath) : {}; + var loadOptions = main && main.loadOptions; + + if (main.checkError) { + var e; + + try { + main.createApp(express, markoExpress); + } catch(_e) { + e = _e; + } + + if (!e) { + throw new Error('Error expected'); + } + + main.checkError(e); + return done(); + } else { + var app = main.createApp(express, markoExpress); + var template = marko.load(templatePath, loadOptions); + + app.get('/test', main.createController(template)); + + var server = app.listen(0, function(err) { + if(err) { + return done(err); + } + + var port = server.address().port; + var address = `http://localhost:${port}/test`; + + request(address, function(error, response, body) { + try { + if(main.checkResponse) { + response.body = body; + response.error = error; + main.checkResponse(response, chai.expect, helpers); + } else { + if(error) { + return done(error); + } + chai.expect(response.statusCode).to.equal(200); + helpers.compare(body, '.html'); + } + } catch(error) { + server.close(); + throw error; + } + + server.close(); + done(); + }); + }); + } + }); +});