backoffice/node_modules/clean-css/lib/imports/inliner.js

333 lines
9.7 KiB
JavaScript
Raw Normal View History

2020-02-06 10:09:39 +00:00
var fs = require('fs');
var path = require('path');
var http = require('http');
var https = require('https');
var url = require('url');
var UrlRewriter = require('../images/url-rewriter');
var Splitter = require('../text/splitter.js');
var merge = function(source1, source2) {
var target = {};
for (var key1 in source1)
target[key1] = source1[key1];
for (var key2 in source2)
target[key2] = source2[key2];
return target;
};
module.exports = function Inliner(context, options) {
var defaultOptions = {
timeout: 5000,
request: {}
};
var inlinerOptions = merge(defaultOptions, options || {});
var process = function(data, options) {
if (options.shallow) {
options.shallow = false;
options._shared.done.push(data);
return processNext(options);
}
options._shared = options._shared || {
done: [],
left: []
};
var shared = options._shared;
var nextStart = 0;
var nextEnd = 0;
var cursor = 0;
var isComment = commentScanner(data);
var afterContent = contentScanner(data);
options.relativeTo = options.relativeTo || options.root;
options._baseRelativeTo = options._baseRelativeTo || options.relativeTo;
options.visited = options.visited || [];
for (; nextEnd < data.length;) {
nextStart = nextImportAt(data, cursor);
if (nextStart == -1)
break;
if (isComment(nextStart)) {
cursor = nextStart + 1;
continue;
}
nextEnd = data.indexOf(';', nextStart);
if (nextEnd == -1) {
cursor = data.length;
data = '';
break;
}
shared.done.push(data.substring(0, nextStart));
shared.left.unshift([data.substring(nextEnd + 1), options]);
return afterContent(nextStart) ?
processNext(options) :
inline(data, nextStart, nextEnd, options);
}
// no @import matched in current data
shared.done.push(data);
return processNext(options);
};
var nextImportAt = function (data, cursor) {
var nextLowerCase = data.indexOf('@import', cursor);
var nextUpperCase = data.indexOf('@IMPORT', cursor);
if (nextLowerCase > -1 && nextUpperCase == -1)
return nextLowerCase;
else if (nextLowerCase == -1 && nextUpperCase > -1)
return nextUpperCase;
else
return Math.min(nextLowerCase, nextUpperCase);
};
var processNext = function(options) {
if (options._shared.left.length > 0)
return process.apply(null, options._shared.left.shift());
else
return options.whenDone(options._shared.done.join(''));
};
var commentScanner = function(data) {
var commentRegex = /(\/\*(?!\*\/)[\s\S]*?\*\/)/;
var lastStartIndex = 0;
var lastEndIndex = 0;
var noComments = false;
// test whether an index is located within a comment
var scanner = function(idx) {
var comment;
var localStartIndex = 0;
var localEndIndex = 0;
var globalStartIndex = 0;
var globalEndIndex = 0;
// return if we know there are no more comments
if (noComments)
return false;
// idx can be still within last matched comment (many @import statements inside one comment)
if (idx > lastStartIndex && idx < lastEndIndex)
return true;
comment = data.match(commentRegex);
if (!comment) {
noComments = true;
return false;
}
// get the indexes relative to the current data chunk
lastStartIndex = localStartIndex = comment.index;
localEndIndex = localStartIndex + comment[0].length;
// calculate the indexes relative to the full original data
globalEndIndex = localEndIndex + lastEndIndex;
globalStartIndex = globalEndIndex - comment[0].length;
// chop off data up to and including current comment block
data = data.substring(localEndIndex);
lastEndIndex = globalEndIndex;
// re-run scan if comment ended before the idx
if (globalEndIndex < idx)
return scanner(idx);
return globalEndIndex > idx && idx > globalStartIndex;
};
return scanner;
};
var contentScanner = function(data) {
var isComment = commentScanner(data);
var firstContentIdx = -1;
while (true) {
firstContentIdx = data.indexOf('{', firstContentIdx + 1);
if (firstContentIdx == -1 || !isComment(firstContentIdx))
break;
}
return function(idx) {
return firstContentIdx > -1 ?
idx > firstContentIdx :
false;
};
};
var inline = function(data, nextStart, nextEnd, options) {
options.shallow = data.indexOf('@shallow') > 0;
var importDeclaration = data
.substring(nextImportAt(data, nextStart) + '@import'.length + 1, nextEnd)
.replace(/@shallow\)$/, ')')
.trim();
var viaUrl = importDeclaration.indexOf('url(') === 0;
var urlStartsAt = viaUrl ? 4 : 0;
var isQuoted = /^['"]/.exec(importDeclaration.substring(urlStartsAt, urlStartsAt + 2));
var urlEndsAt = isQuoted ?
importDeclaration.indexOf(isQuoted[0], urlStartsAt + 1) :
new Splitter(' ').split(importDeclaration)[0].length - (viaUrl ? 1 : 0);
var importedFile = importDeclaration
.substring(urlStartsAt, urlEndsAt)
.replace(/['"]/g, '')
.replace(/\)$/, '')
.trim();
var mediaQuery = importDeclaration
.substring(urlEndsAt + 1)
.replace(/^\)/, '')
.trim();
var isRemote = options.isRemote ||
/^(http|https):\/\//.test(importedFile) ||
/^\/\//.test(importedFile);
if (options.localOnly && isRemote) {
context.warnings.push('Ignoring remote @import declaration of "' + importedFile + '" as no callback given.');
restoreImport(importedFile, mediaQuery, options);
return processNext(options);
}
var method = isRemote ? inlineRemoteResource : inlineLocalResource;
return method(importedFile, mediaQuery, options);
};
var inlineRemoteResource = function(importedFile, mediaQuery, options) {
var importedUrl = /^https?:\/\//.test(importedFile) ?
importedFile :
url.resolve(options.relativeTo, importedFile);
if (importedUrl.indexOf('//') === 0)
importedUrl = 'http:' + importedUrl;
if (options.visited.indexOf(importedUrl) > -1)
return processNext(options);
if (context.debug)
console.error('Inlining remote stylesheet: ' + importedUrl);
options.visited.push(importedUrl);
var get = importedUrl.indexOf('http://') === 0 ?
http.get :
https.get;
var timedOut = false;
var handleError = function(message) {
context.errors.push('Broken @import declaration of "' + importedUrl + '" - ' + message);
restoreImport(importedUrl, mediaQuery, options);
processNext(options);
};
var requestOptions = merge(url.parse(importedUrl), inlinerOptions.request);
get(requestOptions, function(res) {
if (res.statusCode < 200 || res.statusCode > 399) {
return handleError('error ' + res.statusCode);
} else if (res.statusCode > 299) {
var movedUrl = url.resolve(importedUrl, res.headers.location);
return inlineRemoteResource(movedUrl, mediaQuery, options);
}
var chunks = [];
var parsedUrl = url.parse(importedUrl);
res.on('data', function(chunk) {
chunks.push(chunk.toString());
});
res.on('end', function() {
var importedData = chunks.join('');
importedData = UrlRewriter.process(importedData, { toBase: importedUrl });
if (mediaQuery.length > 0)
importedData = '@media ' + mediaQuery + '{' + importedData + '}';
process(importedData, {
isRemote: true,
relativeTo: parsedUrl.protocol + '//' + parsedUrl.host,
_shared: options._shared,
whenDone: options.whenDone,
visited: options.visited,
shallow: options.shallow
});
});
})
.on('error', function(res) {
handleError(res.message);
})
.on('timeout', function() {
// FIX: node 0.8 fires this event twice
if (timedOut)
return;
handleError('timeout');
timedOut = true;
})
.setTimeout(inlinerOptions.timeout);
};
var inlineLocalResource = function(importedFile, mediaQuery, options) {
var relativeTo = importedFile[0] == '/' ?
options.root :
options.relativeTo;
var fullPath = path.resolve(path.join(relativeTo, importedFile));
if (!fs.existsSync(fullPath) || !fs.statSync(fullPath).isFile()) {
context.errors.push('Broken @import declaration of "' + importedFile + '"');
return processNext(options);
}
if (options.visited.indexOf(fullPath) > -1)
return processNext(options);
if (context.debug)
console.error('Inlining local stylesheet: ' + fullPath);
options.visited.push(fullPath);
var importedData = fs.readFileSync(fullPath, 'utf8');
var importRelativeTo = path.dirname(fullPath);
importedData = UrlRewriter.process(importedData, {
relative: true,
fromBase: importRelativeTo,
toBase: options._baseRelativeTo
});
if (mediaQuery.length > 0)
importedData = '@media ' + mediaQuery + '{' + importedData + '}';
return process(importedData, {
root: options.root,
relativeTo: importRelativeTo,
_baseRelativeTo: options._baseRelativeTo,
_shared: options._shared,
visited: options.visited,
whenDone: options.whenDone,
localOnly: options.localOnly,
shallow: options.shallow
});
};
var restoreImport = function(importedUrl, mediaQuery, options) {
var restoredImport = '@import url(' + importedUrl + ')' + (mediaQuery.length > 0 ? ' ' + mediaQuery : '') + ';';
options._shared.done.push(restoredImport);
};
// Inlines all imports taking care of repetitions, unknown files, and circular dependencies
return { process: process };
};