react-saasify-chrisvxd
Version:
React components for Saasify web clients.
193 lines (174 loc) • 6.72 kB
JavaScript
const isHTML = require('is-html'),
isURL = require('is-absolute-url'),
request = require('request'),
fs = require('fs'),
os = require('os'),
path = require('path'),
url = require('url');
function isWindows() {
return os.platform() === 'win32';
}
/**
* Check if the supplied string might be a RegExp and, if so, return the corresponding RegExp.
* @param {String} str The regex to transform.
* @return {RegExp|String} The final RegExp
*/
function strToRegExp(str) {
if (str[0] === '/') {
return new RegExp(str.replace(/^\/|\/$/g, ''));
}
return str;
}
/**
* Parse a given uncssrc file.
* @param {String} filename The location of the uncssrc file
* @return {Object} The options object
*/
function parseUncssrc(filename) {
let options = JSON.parse(fs.readFileSync(filename, 'utf-8'));
/* RegExps can't be stored as JSON, therefore we need to parse them manually.
* A string is a RegExp if it starts with '/', since that wouldn't be a valid CSS selector.
*/
options.ignore = options.ignore ? options.ignore.map(strToRegExp) : undefined;
options.ignoreSheets = options.ignoreSheets ? options.ignoreSheets.map(strToRegExp) : [];
return options;
}
/**
* Parse paths relatives to a source.
* @param {String} source Where the paths originate from
* @param {Array} stylesheets List of paths
* @param {Object} options Options, as passed to UnCSS
* @return {Array} List of paths
*/
function parsePaths(source, stylesheets, options) {
return stylesheets.map((sheet) => {
let sourceProtocol;
const isLocalFile = sheet.substr(0, 5) === 'file:';
if (sheet.substr(0, 4) === 'http') {
/* No need to parse, it's already a valid path */
return sheet;
}
/* Check if we are fetching over http(s) */
if (isURL(source) && !isLocalFile) {
sourceProtocol = url.parse(source).protocol;
if (sheet.substr(0, 2) === '//') {
/* Use the same protocol we used for fetching this page.
* Default to http.
*/
return sourceProtocol ? sourceProtocol + sheet : 'http:' + sheet;
}
return url.resolve(source, sheet);
}
/* We are fetching local files
* Should probably report an error if we find an absolute path and
* have no htmlroot specified.
*/
/* Fix the case when there is a query string or hash */
sheet = sheet.split('?')[0].split('#')[0];
/* Path already parsed by jsdom or user supplied local file */
if (isLocalFile) {
sheet = url.parse(sheet).path.replace('%20', ' ');
/* If on windows, remove first '/' */
sheet = isWindows() ? sheet.substring(1) : sheet;
if (options.htmlroot) {
return path.join(options.htmlroot, sheet);
}
sheet = path.relative(path.join(path.dirname(source)), sheet);
}
if (sheet[0] === '/' && options.htmlroot) {
return path.join(options.htmlroot, sheet);
} else if (isHTML(source)) {
return path.join(options.csspath, sheet);
}
return path.join(path.dirname(source), options.csspath, sheet);
});
}
/**
* Given an array of filenames, return an array of the files' contents,
* only if the filename matches a regex
* @param {Array} files An array of the filenames to read
* @return {Promise}
*/
function readStylesheets(files, outputBanner) {
return Promise.all(files.map((filename) => {
if (isURL(filename)) {
return new Promise((resolve, reject) => {
request({
url: filename,
headers: { 'User-Agent': 'UnCSS' }
}, (err, response, body) => {
if (err) {
return reject(err);
}
return resolve(body);
});
});
} else if (fs.existsSync(filename)) {
return new Promise((resolve, reject) => {
fs.readFile(filename, 'utf-8', (err, contents) => {
if (err) {
return reject(err);
}
return resolve(contents);
});
});
}
throw new Error(`UnCSS: could not open ${path.join(process.cwd(), filename)}`);
})).then((res) => {
// res is an array of the content of each file in files (in the same order)
if (outputBanner) {
for (let i = 0, len = files.length; i < len; i++) {
// We append a small banner to keep track of which file we are currently processing
// super helpful for debugging
const banner = `/*** uncss> filename: ${files[i].replace(/\\/g, '/')} ***/\n`;
res[i] = banner + res[i];
}
}
return res;
});
}
function parseErrorMessage(error, cssStr) {
/* TODO: FIXME */
/* Base line for conveying the line number in the error message */
let zeroLine = 0;
if (error.line) {
const lines = cssStr.split('\n');
if (lines.length) {
/* We get the filename of the css file that contains the error */
let i = error.line - 1;
while (i >= 0 && !error.filename) {
if (lines[i].substr(0, 21) === '/*** uncss> filename:') {
error.filename = lines[i].substring(22, lines[i].length - 4);
zeroLine = i;
}
i--;
}
for (let j = error.line - 6; j < error.line + 5; j++) {
if (j - zeroLine < 0 || j >= lines.length) {
continue;
}
let line = lines[j];
/* It could be minified CSS */
if (line.length > 120 && error.column) {
line = line.substring(error.column - 40, error.column);
}
error.message += '\n\t' + (j + 1 - zeroLine) + ': ';
error.message += j === error.line - 1 ? ' -> ' : ' ';
error.message += line;
}
}
}
if (zeroLine > 0) {
error.message = error.message.replace(/[0-9]+:/, error.line - zeroLine + ':');
}
error.message = `uncss/node_modules/css: unable to parse ${error.filename}:\n${error.message}\n`;
return error;
}
module.exports = {
isWindows,
parseUncssrc,
parseErrorMessage,
parsePaths,
readStylesheets
};
;