Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

artist

Package Overview
Dependencies
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

artist - npm Package Compare versions

Comparing version 0.1.3 to 0.1.4

test/http.js

2

CHANGES.md
# Artist changes
* 0.1.4
1. Added proxy() middleware
* 0.1.3

@@ -4,0 +6,0 @@ 1. Option `refresh` is replaced by `cache`

@@ -109,2 +109,130 @@ var defaults = {

}
}
var formats = {
'amd': 'define(function(){return #FN#});'
};
/**
* Returns a Connect's middleware.
*
* Allows to compile the templates into JavaScript files on the fly.
* Supports JSONP, AMD or custom output formatting.
*
* Options:
*
* - `route` mounting point for the middleware
* - `templates` path to the templates directory
* - `format` output formating string or 'amd' (default) or 'jsonp'
* - `maxAge` http cache max-age in milliseconds (defaults to 0)
* - `cache` cache compiled functions
* - `beautify` beautify compiled functions
* - `debug` include debugging information
*
* @param {Options} options
* @return {Function}
*/
exports.proxy = function (options) {
var fs = require('fs'),
url = require('url'),
fest = require('fest'),
cache = {};
options = extend({
maxAge: 0,
format: 'amd'
}, defaults, options);
if (!options.route) throw new Error('route is required');
if (!options.templates) throw new Error('tempates is required');
if (options.route[0] !== '/') options.route = '/' + options.route;
if (options.route.slice(-1) !== '/') options.route += '/';
if (options.format in formats) {
options.format = formats[options.format];
}
return function (req, res, next) {
if (req.method !== 'HEAD' && req.method !== 'GET') return next();
try {
var uo = url.parse(req.url, true),
path = decodeURIComponent(uo.pathname),
jsonp = decodeURIComponent(uo.query.jsonp);
} catch (e) {
return next(e);
}
if (0 !== path.indexOf(options.route) || path.slice(-3) !== '.js') {
return next();
}
var id = path.slice(options.route.length).slice(0, -3);
path = options.templates + '/' + id + '.xml';
function send(file) {
if (options.format === 'jsonp' && jsonp) {
file.content = jsonp + '(' + file.content + ');';
file.ts = new Date;
}
res.setHeader('Date', (new Date).toUTCString());
res.setHeader('Cache-Control', 'public, max-age=' + options.maxAge);
res.setHeader('Last-Modified', file.ts.toUTCString());
var ms = req.headers['if-modified-since'];
if (ms) {
ms = new Date(ms);
if (!isNaN(ms.valueOf()) && ms >= file.ts) {
res.statusCode = 304;
return res.end();
}
}
res.setHeader('Content-Type', 'application/javascript');
res.setHeader('Content-Length', Buffer.byteLength(file.content, 'utf8'));
if (req.method == 'HEAD') {
res.end();
} else {
res.end(file.content);
}
}
if (options.cache && path in cache) {
send(cache[path]);
} else {
fs.stat(path, function (err, stats) {
if (err) {
return next();
} else try {
if (!stats.isFile()) {
return next();
}
var content = fest.compile(path, options);
if (options.format !== 'jsonp') {
content = options.format
.replace(/#ID#/g, id.replace(/\'/g, "\\'").replace(/\"/g, '\\"').replace(/\\/g, '\\'))
.replace(/#FN#/g, content);
}
var file = {
content: content,
ts: new Date
};
if (options.cache) {
cache[path] = file;
}
send(file);
} catch (err) {
next(err);
}
});
}
};
}

2

package.json
{
"name": "artist",
"version": "0.1.3",
"version": "0.1.4",
"description": "Template engine for node.js built on fest",

@@ -5,0 +5,0 @@ "main": "index.js",

@@ -13,2 +13,3 @@ # Artist

* Handles fest's runtime errors
* Connect's middleware proxy

@@ -23,3 +24,3 @@ ## Installation

Provides `render()` and `renderSync()` functions. View source for documentation.
View source for documentation.

@@ -55,2 +56,40 @@ ## Usage

</fest:template>
```
```
## Proxy
Proxy is a connect middleware. It handles requests to the templates, compiles them and wraps results
into AMD module, JSONP or any other format.
AMD:
```javascript
app.use(require('artist').proxy({
format: 'amd', // 'amd', 'jsonp' or any string with #ID# and #FN# placeholders
route: '/scripts/templates', // middleware mounting point
templates: app.get('views') // path to the view directory
}));
```
GET /scripts/templates/layout.js
```javascript
define(function(){return function(__fest_context){
...
}});
```
JSONP:
```javascript
app.use(require('artist').proxy({
format: 'jsonp',
route: '/scripts/templates',
templates: app.get('views')
}));
```
GET /scripts/templates/layout.js?jsonp=handleTemplate
```javascript
handleTemplate(function(__fest_context){
...
});
```
var assert = require('assert'),
artist = require('..');
artist = require('..'),
http = require('./http');
describe("api", function() {
describe(".render(opts)", function () {
it("should invoke the function with null and a string", function () {

@@ -29,2 +31,3 @@ var errors = [],

});
it("should invoke the function with an error when the file doesn't exist", function () {

@@ -45,2 +48,3 @@ var errors = [],

});
it("should invoke the function with an error when the file contains syntax errors", function () {

@@ -61,2 +65,3 @@ var errors = [],

});
it("should log an message when the file contains runtime error", function () {

@@ -80,2 +85,3 @@ var errors = [],

describe(".renderSync(opts)", function () {
it("should return a string", function () {

@@ -96,2 +102,3 @@ var errors = [],

});
it("should throw an error when the file doesn't exist", function () {

@@ -112,2 +119,3 @@ var errors = [],

});
it("should throw an error when the file contains syntax errors", function () {

@@ -122,6 +130,7 @@ var errors = [],

render(__dirname + '/error-syntax.xml', {});
}, 'Unexpected close tag');
}, /Unexpected close tag/);
// no runtime errors
assert.deepEqual(errors, []);
});
it("should log an message when the file contains runtime error", function () {

@@ -140,2 +149,159 @@ var errors = [],

});
describe(".proxy(opts)", function () {
function performRequest(proxy, request, callback) {
var req = new http.Request(request),
res = new http.Response();
res.on('finish', function () {
callback(null, res);
});
proxy(req, res, function (err) {
callback(err, res);
});
}
var defaultProxy = artist.proxy({
// format: 'amd',
route: '/scripts',
templates: __dirname
});
var cacheProxy = artist.proxy({
cache: true,
route: '/scripts',
templates: __dirname
});
var jsonpProxy = artist.proxy({
format: 'jsonp',
route: '/scripts',
templates: __dirname,
maxAge: 86400000
});
var festProxy = artist.proxy({
format: ';(function(x){if(!x.fest)x.fest={};x.fest["#ID#"]=#FN#})(this);',
route: '/scripts',
templates: __dirname
});
it("should throw an exception if required parameters are missing", function () {
assert.throws(function () {
artist.proxy()
}, /route is required/);
assert.throws(function () {
artist.proxy({route: '/'})
}, /tempates is required/);
});
it("should format the function into AMD module by default and prevent browser cache", function (done) {
performRequest(defaultProxy, {
method: 'GET',
url: '/scripts/test.js'
}, function (err, res) {
assert(res.finished);
assert.equal(res.statusCode, 200);
assert.equal(res._headers['cache-control'], 'public, max-age=0');
assert(res.match(/^define\(function\(\)\{return [\s\S]*\}\);$/));
done(err);
});
});
it("should format the function into JSONP reponse and send custom Cache-Control header", function (done) {
performRequest(jsonpProxy, {
method: 'GET',
url: '/scripts/test.js?jsonp=cb1'
}, function (err, res) {
assert(res.finished);
assert.equal(res.statusCode, 200);
assert(res.match(/^cb1\([\s\S]*\);$/));
assert.equal(res._headers['cache-control'], 'public, max-age=86400000');
done(err);
});
});
it("should format the function into JSONP response with different jsonp values", function (done) {
performRequest(jsonpProxy, {
method: 'GET',
url: '/scripts/test.js?jsonp=cb2'
}, function (err, res) {
assert(res.finished);
assert.equal(res.statusCode, 200);
assert(res.match(/^cb2\([\s\S]*\);$/));
done(err);
});
});
it("should format the function into custom layout", function (done) {
performRequest(festProxy, {
method: 'GET',
url: '/scripts/test.js'
}, function (err, res) {
assert(res.finished);
assert.equal(res.statusCode, 200);
assert(res.match(/^;\(function\(x\)\{if\(!x\.fest\)x\.fest=\{\};x\.fest\["test"\]=[\s\S]*\}\)\(this\);$/));
done(err);
});
});
it("should just call next() when the route mismatch", function (done) {
performRequest(defaultProxy, {
method: 'GET',
url: '/templates/test.js'
}, function (err, res) {
assert(!res.finished);
done(err);
});
});
it("should deal with HEAD requests", function (done) {
performRequest(defaultProxy, {
method: 'HEAD',
url: '/scripts/test.js'
}, function (err, res) {
assert(res.finished);
assert.equal(res.statusCode, 200);
assert(res.match(''));
done(err);
});
});
it("should deal with HEAD and GET requests only", function (done) {
performRequest(defaultProxy, {
method: 'POST',
url: '/scripts/test.js'
}, function (err, res) {
assert(!res.finished);
done(err);
});
});
it("should deal with If-Modified-Since header", function (done) {
performRequest(cacheProxy, {
headers: {'if-modified-since': (new Date).toUTCString()},
method: 'GET',
url: '/scripts/test.js'
}, function (err, res) {
assert(res.finished);
assert.equal(res.statusCode, 200);
assert(!res.match(''));
if (err) {
done(err);
} else {
performRequest(cacheProxy, {
headers: {'if-modified-since': (new Date(Date.now() + 60 * 1000)).toUTCString()},
method: 'GET',
url: '/scripts/test.js'
}, function (err, res) {
assert(res.finished);
assert.equal(res.statusCode, 304);
assert(res.match(''));
done(err);
});
}
});
});
});
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc