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

express-compiless

Package Overview
Dependencies
Maintainers
3
Versions
48
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

express-compiless - npm Package Compare versions

Comparing version 4.0.0 to 4.1.0

.eslintignore

319

lib/compiless.js

@@ -1,153 +0,218 @@

var Path = require('path'),
crypto = require('crypto'),
fs = require('fs'),
async = require('async'),
csserror = require('csserror'),
passError = require('passerror'),
hijackResponse = require('hijackresponse');
const Path = require('path');
const crypto = require('crypto');
const fs = require('fs');
const async = require('async');
const csserror = require('csserror');
const passError = require('passerror');
const hijackResponse = require('hijackresponse');
module.exports = function compiless(options) {
if (!options || !options.root) {
throw new Error('options.root is mandatory');
}
if (!options || !options.root) {
throw new Error('options.root is mandatory');
}
var less = options.less || require('less');
var isOldLessApi = !less.version || less.version[0] < 2;
var plugins = options.plugins || [];
if (plugins.length > 0 && isOldLessApi) {
throw new Error('Less version < 2.0.0 does not support plugins');
}
var toCSSOptions = options.toCSSOptions || {};
const less = options.less || require('less');
const isOldLessApi = !less.version || less.version[0] < 2;
const plugins = options.plugins || [];
if (plugins.length > 0 && isOldLessApi) {
throw new Error('Less version < 2.0.0 does not support plugins');
}
const toCSSOptions = options.toCSSOptions || {};
function formatError(err) { // err can either be a string or an error object
if (typeof err === 'string') {
return err;
} else {
// Assume error object
return err.message + ('line' in err ? ' at line ' + err.line + ('column' in err ? ', column ' + err.column : '') : '') +
('extract' in err ? ':\n' + err.extract.join('\n') : '');
}
function formatError(err) {
// err can either be a string or an error object
if (typeof err === 'string') {
return err;
} else {
// Assume error object
return (
err.message +
('line' in err
? ' at line ' +
err.line +
('column' in err ? ', column ' + err.column : '')
: '') +
('extract' in err ? ':\n' + err.extract.join('\n') : '')
);
}
}
return function (req, res, next) {
// Prevent If-None-Match revalidation with the downstream middleware with ETags that aren't suffixed with "-compiless":
var ifNoneMatch = req.headers['if-none-match'];
return (req, res, next) => {
// Prevent If-None-Match revalidation with the downstream middleware with ETags that aren't suffixed with "-compiless":
const ifNoneMatch = req.headers['if-none-match'];
// only hijack requests for .less files
if (/\.less$/.test(req.originalUrl)) {
if (ifNoneMatch) {
var validIfNoneMatchTokens = ifNoneMatch.split(" ").filter(function (etag) {
return /-compiless\"$/.test(etag);
});
// only hijack requests for .less files
if (/\.less$/.test(req.originalUrl)) {
if (ifNoneMatch) {
const validIfNoneMatchTokens = ifNoneMatch
.split(' ')
.filter(etag => /-compiless"$/.test(etag));
if (validIfNoneMatchTokens.length > 0) {
req.headers['if-none-match'] = validIfNoneMatchTokens.join(" ");
} else {
delete req.headers['if-none-match'];
}
}
delete req.headers['if-modified-since']; // Prevent false positive conditional GETs after enabling compiless
if (validIfNoneMatchTokens.length > 0) {
req.headers['if-none-match'] = validIfNoneMatchTokens.join(' ');
} else {
delete req.headers['if-none-match'];
}
}
delete req.headers['if-modified-since']; // Prevent false positive conditional GETs after enabling compiless
hijackResponse(res, function (err, res) {
if (err) {
res.unhijack();
return next(err);
}
hijackResponse(
res,
(err, res) => {
if (err) {
res.unhijack();
return next(err);
}
var contentType = res.getHeader('Content-Type'),
matchContentType = contentType && contentType.match(/^text\/less(?:;\s*charset=([a-z0-9\-]+))?$/i);
const contentType = res.getHeader('Content-Type');
const matchContentType =
contentType &&
contentType.match(/^text\/less(?:;\s*charset=([a-z0-9-]+))?$/i);
// The mime module doesn't support less yet, so we fall back:
if (!(matchContentType || contentType === 'application/octet-stream')) {
return res.unhijack();
}
// The mime module doesn't support less yet, so we fall back:
if (
!(matchContentType || contentType === 'application/octet-stream')
) {
return res.unhijack();
}
var baseDir = Path.resolve(options.root, req.url.replace(/\/[^\/]*(?:\?.*)?$/, '/').substr(1)),
filename = Path.resolve(options.root, req.url.substr(1)),
lessOptions = {
paths: [baseDir],
filename: filename,
plugins: plugins,
relativeUrls: true
};
const baseDir = Path.resolve(
options.root,
req.url.replace(/\/[^/]*(?:\?.*)?$/, '/').substr(1)
);
const filename = Path.resolve(options.root, req.url.substr(1));
const lessOptions = {
paths: [baseDir],
filename,
plugins,
relativeUrls: true
};
function sendErrorResponse(err, cssText) {
var errorMessage = "express-compiless: Error compiling " + req.originalUrl + ":\n" + formatError(err);
res.removeHeader('Content-Length');
res.removeHeader('ETag');
res.setHeader('Content-Type', 'text/css; charset=utf-8');
res.end(csserror(errorMessage) + '\n' + (cssText || ''));
}
function sendErrorResponse(err, cssText) {
const errorMessage =
'express-compiless: Error compiling ' +
req.originalUrl +
':\n' +
formatError(err);
res.removeHeader('Content-Length');
res.removeHeader('ETag');
res.setHeader('Content-Type', 'text/css; charset=utf-8');
res.end(csserror(errorMessage) + '\n' + (cssText || ''));
}
function respondWithCss(css, importedFileNames) {
importedFileNames = importedFileNames || [];
var statsByFileName = {};
async.eachLimit(importedFileNames, 10, function (importedFileName, cb) {
fs.stat(importedFileName, passError(cb, function (stats) {
statsByFileName[importedFileName] = stats;
cb();
}));
}, passError(sendErrorResponse, function () {
var oldETag = res.getHeader('ETag');
function respondWithCss(css, importedFileNames) {
importedFileNames = importedFileNames || [];
const statsByFileName = {};
async.eachLimit(
importedFileNames,
10,
(importedFileName, cb) => {
fs.stat(
importedFileName,
passError(cb, stats => {
statsByFileName[importedFileName] = stats;
cb();
})
);
},
passError(sendErrorResponse, function() {
const oldETag = res.getHeader('ETag');
if (oldETag) {
var oldETagIsWeak = oldETag && /^W\//.test(oldETag),
etagFragments = [oldETag.replace(/^(?:W\/)?"|"$/g, '')];
if (oldETag) {
const oldETagIsWeak = oldETag && /^W\//.test(oldETag);
const etagFragments = [oldETag.replace(/^(?:W\/)?"|"$/g, '')];
if (importedFileNames.length) {
var importedFileStats = [];
if (importedFileNames.length) {
const importedFileStats = [];
importedFileNames.sort().forEach(function (importedFileName) {
var stats = statsByFileName[importedFileName];
importedFileStats.push(importedFileName, String(stats.mtime.getTime()), String(stats.size));
}, this);
importedFileNames.sort().forEach(importedFileName => {
const stats = statsByFileName[importedFileName];
importedFileStats.push(
importedFileName,
String(stats.mtime.getTime()),
String(stats.size)
);
}, this);
etagFragments.push(crypto.createHash('md5').update(importedFileStats.join('-')).digest('hex').substr(0, 16));
}
etagFragments.push(
crypto
.createHash('md5')
.update(importedFileStats.join('-'))
.digest('hex')
.substr(0, 16)
);
}
var newETag = (oldETagIsWeak ? 'W/' : '') + '"' + etagFragments.join('-') + '-compiless"';
res.setHeader('ETag', newETag);
const newETag =
(oldETagIsWeak ? 'W/' : '') +
'"' +
etagFragments.join('-') +
'-compiless"';
res.setHeader('ETag', newETag);
if (ifNoneMatch && ifNoneMatch.indexOf(newETag) !== -1) {
return res.status(304).send();
}
}
if (ifNoneMatch && ifNoneMatch.indexOf(newETag) !== -1) {
return res.status(304).send();
}
}
var cssText = importedFileNames.map(function (importedFileName) {
return ".compilessinclude {background-image: url(" + Path.relative(baseDir, importedFileName) + "); display: none;}\n";
}).join("") + css;
const cssText =
importedFileNames
.map(
importedFileName =>
'.compilessinclude {background-image: url(' +
Path.relative(baseDir, importedFileName) +
'); display: none;}\n'
)
.join('') + css;
res.setHeader('Content-Type', 'text/css; charset=utf-8');
res.setHeader('Content-Length', Buffer.byteLength(cssText));
res.end(cssText);
}));
}
res.setHeader('Content-Type', 'text/css; charset=utf-8');
res.setHeader('Content-Length', Buffer.byteLength(cssText));
res.end(cssText);
})
);
}
var chunks = [];
res.on('data', function (chunk) {
chunks.push(chunk);
}).on('end', function () {
var lessText = Buffer.concat(chunks).toString();
const chunks = [];
res
.on('data', chunk => {
chunks.push(chunk);
})
.on('end', () => {
const lessText = Buffer.concat(chunks).toString();
if (isOldLessApi) {
var parser = new less.Parser(lessOptions);
parser.parse(lessText, passError(sendErrorResponse, function (root) {
var css;
try {
css = root.toCSS(toCSSOptions);
} catch (e) {
return sendErrorResponse(e);
}
respondWithCss(css, parser.imports && parser.imports.files && Object.keys(parser.imports.files));
}));
} else {
less.render(lessText, lessOptions, passError(sendErrorResponse, function (output) {
respondWithCss(output.css, output.imports);
}));
if (isOldLessApi) {
const parser = new less.Parser(lessOptions);
parser.parse(
lessText,
passError(sendErrorResponse, root => {
let css;
try {
css = root.toCSS(toCSSOptions);
} catch (e) {
return sendErrorResponse(e);
}
});
}, {disableBackpressure: true});
}
next();
};
respondWithCss(
css,
parser.imports &&
parser.imports.files &&
Object.keys(parser.imports.files)
);
})
);
} else {
less.render(
lessText,
lessOptions,
passError(sendErrorResponse, output => {
respondWithCss(output.css, output.imports);
})
);
}
});
},
{ disableBackpressure: true }
);
}
next();
};
};
{
"name": "express-compiless",
"version": "4.0.0",
"version": "4.1.0",
"description": "Express middleware that compiles less files to css on the way out.",
"main": "lib/compiless.js",
"scripts": {
"lint": "jshint .",
"test": "mocha && npm run lint",
"travis": "npm test && npm run coverage && <coverage/lcov.info coveralls",
"coverage": "NODE_ENV=development istanbul cover _mocha -- --reporter dot && echo google-chrome coverage/lcov-report/index.html"
"lint": "eslint . && prettier --check '**/*.js'",
"test": "mocha",
"ci": "npm test && npm run coverage && npm run lint && <coverage/lcov.info coveralls",
"coverage": "NODE_ENV=development nyc --reporter=lcov --reporter=text --all -- npm test && echo google-chrome coverage/lcov-report/index.html"
},

@@ -26,17 +26,30 @@ "repository": {

"dependencies": {
"async": "=2.6.1",
"async": "^2.6.2",
"csserror": "^2.0.2",
"hijackresponse": "3.0.0",
"less": "3.9.0",
"passerror": "1.1.1"
"hijackresponse": "^4.0.0",
"less": "^3.9.0",
"passerror": "^1.1.1"
},
"devDependencies": {
"coveralls": "^3.0.0",
"express": "^4.16.3",
"istanbul": "^0.4.5",
"jshint": "^2.9.5",
"mocha": "^5.0.5",
"unexpected": "^10.37.2",
"coveralls": "^3.0.3",
"eslint": "^5.1.0",
"eslint-config-prettier": "^4.0.0",
"eslint-config-standard": "^12.0.0",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-mocha": "^5.2.1",
"eslint-plugin-node": "^8.0.1",
"eslint-plugin-promise": "^4.0.1",
"eslint-plugin-standard": "^4.0.0",
"express": "^4.16.4",
"mocha": "^6.0.0",
"nyc": "^13.3.0",
"prettier": "^1.16.4",
"unexpected": "^11.0.0",
"unexpected-express": "^11.0.0"
},
"nyc": {
"include": [
"lib/**"
]
}
}

@@ -1,167 +0,170 @@

/*global describe, it, __dirname*/
var express = require('express'),
Path = require('path'),
unexpected = require('unexpected'),
compiless = require('../lib/compiless');
/* global describe, it, __dirname */
const express = require('express');
const Path = require('path');
const unexpected = require('unexpected');
const compiless = require('../lib/compiless');
describe('compiless', function () {
var root = Path.resolve(__dirname, 'root'),
expect = unexpected.clone()
.installPlugin(require('unexpected-express'))
.addAssertion('to yield response', function (expect, subject, value) {
return expect(
express()
.use(compiless({ root: root }))
.use('/hello', function (req, res, next) {
res.setHeader('Content-Type', 'text/plain');
res.setHeader('ETag', 'W/"fake-etag"');
res.status(200);
res.write('world');
res.end();
})
.use(express['static'](root)),
'to yield exchange', {
request: subject,
response: value
}
);
});
describe('compiless', () => {
const root = Path.resolve(__dirname, 'root');
const expect = unexpected
.clone()
.installPlugin(require('unexpected-express'))
.addAssertion('to yield response', (expect, subject, value) =>
expect(
express()
.use(compiless({ root }))
.use('/hello', (req, res, next) => {
res.setHeader('Content-Type', 'text/plain');
res.setHeader('ETag', 'W/"fake-etag"');
res.status(200);
res.write('world');
res.end();
})
.use(express['static'](root)),
'to yield exchange',
{
request: subject,
response: value
}
)
);
it('should not mess with request for non-less file', function () {
return expect('GET /something.txt', 'to yield response', {
headers: {
'Content-Type': 'text/plain; charset=UTF-8',
'ETag': expect.it('not to match', /-compiless/),
'Content-Length': '4'
},
body: 'foo\n'
});
});
it('should not mess with request for non-less file', () =>
expect('GET /something.txt', 'to yield response', {
headers: {
'Content-Type': 'text/plain; charset=UTF-8',
ETag: expect.it('not to match', /-compiless/),
'Content-Length': '4'
},
body: 'foo\n'
}));
it('should not mess with request for non-less related route', function () {
return expect('GET /hello', 'to yield response', {
headers: {
'Content-Type': 'text/plain',
'ETag': expect.it('not to match', /-compiless/)
},
body: 'world'
});
});
it('should not mess with request for non-less related route', () =>
expect('GET /hello', 'to yield response', {
headers: {
'Content-Type': 'text/plain',
ETag: expect.it('not to match', /-compiless/)
},
body: 'world'
}));
it('should respond with an ETag header and support conditional GET', function () {
return expect('GET /simple.less', 'to yield response', {
statusCode: 200,
headers: {
'Content-Type': 'text/css; charset=utf-8',
ETag: /^W\/".*-compiless"$/
},
body: '#foo #bar {\n color: blue;\n}\n'
}).then(function (context) {
var etag = context.httpResponse.headers.get('ETag');
return expect({
url: 'GET /simple.less',
headers: {
'If-None-Match': etag
}
}, 'to yield response', {
statusCode: 304,
headers: {
ETag: etag
}
});
});
});
it('should respond with an ETag header and support conditional GET', () =>
expect('GET /simple.less', 'to yield response', {
statusCode: 200,
headers: {
'Content-Type': 'text/css; charset=utf-8',
ETag: /^W\/".*-compiless"$/
},
body: '#foo #bar {\n color: blue;\n}\n'
}).then(context => {
const etag = context.httpResponse.headers.get('ETag');
return expect(
{
url: 'GET /simple.less',
headers: {
'If-None-Match': etag
}
},
'to yield response',
{
statusCode: 304,
headers: {
ETag: etag
}
}
);
}));
it('should not destroy if-none-match for non .less files', () =>
expect('GET /something.txt', 'to yield response', {
statusCode: 200,
headers: {
ETag: /^W\/"[a-z0-9-]+"$/
},
body: 'foo\n'
}).then(context => {
const etag = context.httpResponse.headers.get('ETag');
return expect(
{
url: 'GET /something.txt',
headers: {
'If-None-Match': etag
}
},
'to yield response',
{
statusCode: 304,
headers: {
ETag: etag
}
}
);
}));
it('should not destroy if-none-match for non .less files', function () {
return expect('GET /something.txt', 'to yield response', {
statusCode: 200,
headers: {
ETag: /^W\/"[a-z0-9-]+"$/
},
body: 'foo\n'
}).then(function (context) {
var etag = context.httpResponse.headers.get('ETag');
return expect({
url: 'GET /something.txt',
headers: {
'If-None-Match': etag
}
}, 'to yield response', {
statusCode: 304,
headers: {
ETag: etag
}
});
});
});
it('should compile less file with @import to css with .compilessinclude rules first', () =>
expect('GET /stylesheet.less', 'to yield response', {
headers: {
'Content-Type': 'text/css; charset=utf-8'
},
body:
'.compilessinclude {background-image: url(imports/a.less); display: none;}\nbody {\n width: 100%;\n}\n#foo #bar {\n color: red;\n}\n/* multi-line\n comment\n*/\n'
}));
it('should compile less file with @import to css with .compilessinclude rules first', function () {
return expect('GET /stylesheet.less', 'to yield response', {
headers: {
'Content-Type': 'text/css; charset=utf-8'
},
body: '.compilessinclude {background-image: url(imports/a.less); display: none;}\nbody {\n width: 100%;\n}\n#foo #bar {\n color: red;\n}\n/* multi-line\n comment\n*/\n'
});
});
it('should render less file that has a syntax error with the error message as the first thing in the output, wrapped in a body:before rule', () =>
expect('GET /syntaxerror.less', 'to yield response', {
statusCode: 200,
headers: {
'Content-Type': 'text/css; charset=utf-8'
},
body: /Error compiling/
}));
it('should render less file that has a syntax error with the error message as the first thing in the output, wrapped in a body:before rule', function () {
return expect('GET /syntaxerror.less', 'to yield response', {
statusCode: 200,
headers: {
'Content-Type': 'text/css; charset=utf-8'
},
body: /Error compiling/
});
});
it('should render less file that has an @import error with the error message as the first thing in the output, wrapped in a body:before rule', () =>
expect('GET /importerror.less', 'to yield response', {
statusCode: 200,
headers: {
'Content-Type': 'text/css; charset=utf-8'
},
body: /Error compiling/
}));
it('should render less file that has an @import error with the error message as the first thing in the output, wrapped in a body:before rule', function () {
return expect('GET /importerror.less', 'to yield response', {
statusCode: 200,
headers: {
'Content-Type': 'text/css; charset=utf-8'
},
body: /Error compiling/
});
});
it('should render less file that has an @import that points at a file with a syntax error', () =>
expect('GET /importedsyntaxerror.less', 'to yield response', {
statusCode: 200,
headers: {
'Content-Type': 'text/css; charset=utf-8'
},
body: /Error compiling/
}));
it('should render less file that has an @import that points at a file with a syntax error', function () {
return expect('GET /importedsyntaxerror.less', 'to yield response', {
statusCode: 200,
headers: {
'Content-Type': 'text/css; charset=utf-8'
},
body: /Error compiling/
});
});
it('should render less file that has an second level @import error with the error message as the first thing in the output, wrapped in a body:before rule', () =>
expect('GET /secondlevelimporterror.less', 'to yield response', {
statusCode: 200,
headers: {
'Content-Type': 'text/css; charset=utf-8'
},
body: /Error compiling/
}));
it('should render less file that has an second level @import error with the error message as the first thing in the output, wrapped in a body:before rule', function () {
return expect('GET /secondlevelimporterror.less', 'to yield response', {
statusCode: 200,
headers: {
'Content-Type': 'text/css; charset=utf-8'
},
body: /Error compiling/
});
});
it('should rewrite urls correctly', () =>
expect(
'GET /importLessWithRelativeImageReferenceInDifferentDir.less',
'to yield response',
{
statusCode: 200,
headers: {
'Content-Type': 'text/css; charset=utf-8'
},
body: /url\(imports\/images\/foo.png\)/
}
));
it('should rewrite urls correctly', function () {
return expect('GET /importLessWithRelativeImageReferenceInDifferentDir.less', 'to yield response', {
statusCode: 200,
headers: {
'Content-Type': 'text/css; charset=utf-8'
},
body: /url\(imports\/images\/foo.png\)/
});
});
it('should deliver a response even though the less file has @imports and references an undefined variable', function () {
return expect('GET /undefinedVariable.less', 'to yield response', {
statusCode: 200,
headers: {
'Content-Type': 'text/css; charset=utf-8'
},
body: /Error compiling.*variable.*undefined/
});
});
it('should deliver a response even though the less file has @imports and references an undefined variable', () =>
expect('GET /undefinedVariable.less', 'to yield response', {
statusCode: 200,
headers: {
'Content-Type': 'text/css; charset=utf-8'
},
body: /Error compiling.*variable.*undefined/
}));
});

Sorry, the diff of this file is not supported yet

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