Version 1
[yaffs-website] / node_modules / uncss / src / uncss.js
diff --git a/node_modules/uncss/src/uncss.js b/node_modules/uncss/src/uncss.js
new file mode 100644 (file)
index 0000000..ac27a89
--- /dev/null
@@ -0,0 +1,286 @@
+'use strict';\r
+\r
+var promise = require('bluebird'),\r
+    async = require('async'),\r
+    assign = require('object-assign'),\r
+    fs = require('fs'),\r
+    glob = require('glob'),\r
+    isHTML = require('is-html'),\r
+    isURL = require('is-absolute-url'),\r
+    phantom = require('./phantom.js'),\r
+    postcss = require('postcss'),\r
+    uncss = require('./lib.js'),\r
+    utility = require('./utility.js'),\r
+    _ = require('lodash');\r
+\r
+/**\r
+ * Get the contents of HTML pages through PhantomJS.\r
+ * @param  {Array}   files   List of HTML files\r
+ * @param  {Object}  options UnCSS options\r
+ * @return {promise}\r
+ */\r
+function getHTML(files, options) {\r
+    if (_.isString(files)) {\r
+        return phantom.fromRaw(files, options).then(function (pages) {\r
+            return [files, options, [pages]];\r
+        });\r
+    }\r
+\r
+    files = _.flatten(files.map(function (file) {\r
+        if (!isURL(file) && !isHTML(file)) {\r
+            return glob.sync(file);\r
+        }\r
+        return file;\r
+    }));\r
+\r
+    if (!files.length) {\r
+        throw new Error('UnCSS: no HTML files found');\r
+    }\r
+\r
+    return promise.map(files, function (filename) {\r
+        if (isURL(filename)) {\r
+            return phantom.fromRemote(filename, options);\r
+        }\r
+        if (fs.existsSync(filename)) {\r
+            return phantom.fromLocal(filename, options);\r
+        }\r
+        // raw html\r
+        return phantom.fromRaw(filename, options);\r
+    }).then(function (pages) {\r
+        return [files, options, pages];\r
+    });\r
+}\r
+\r
+/**\r
+ * Get the contents of CSS files.\r
+ * @param  {Array}   files   List of HTML files\r
+ * @param  {Object}  options UnCSS options\r
+ * @param  {Array}   pages   Pages opened by phridge\r
+ * @return {promise}\r
+ */\r
+function getStylesheets(files, options, pages) {\r
+    if (options.stylesheets && options.stylesheets.length) {\r
+        /* Simulate the behavior below */\r
+        return [files, options, pages, [options.stylesheets]];\r
+    }\r
+    /* Extract the stylesheets from the HTML */\r
+    return promise.map(pages, function (page) {\r
+        return phantom.getStylesheets(page, options);\r
+    }).then(function (stylesheets) {\r
+        return [files, options, pages, stylesheets];\r
+    });\r
+}\r
+\r
+/**\r
+ * Get the contents of CSS files.\r
+ * @param  {Array}   files       List of HTML files\r
+ * @param  {Object}  options     UnCSS options\r
+ * @param  {Array}   pages       Pages opened by phridge\r
+ * @param  {Array}   stylesheets List of CSS files\r
+ * @return {promise}\r
+ */\r
+function getCSS(files, options, pages, stylesheets) {\r
+    /* Ignore specified stylesheets */\r
+    if (options.ignoreSheets.length) {\r
+        stylesheets = stylesheets.map(function (arr) {\r
+            return arr.filter(function (sheet) {\r
+                return _.every(options.ignoreSheets, function (ignore) {\r
+                    if (_.isRegExp(ignore)) {\r
+                        return !ignore.test(sheet);\r
+                    }\r
+                    return sheet !== ignore;\r
+                });\r
+            });\r
+        });\r
+    }\r
+\r
+    if (_.flatten(stylesheets).length) {\r
+        /* Only run this if we found links to stylesheets (there may be none...)\r
+         *  files       = ['some_file.html', 'some_other_file.html']\r
+         *  stylesheets = [['relative_css_path.css', ...],\r
+         *                 ['maybe_a_duplicate.css', ...]]\r
+         * We need to - make the stylesheets' paths relative to the HTML files,\r
+         *            - flatten the array,\r
+         *            - remove duplicates\r
+         */\r
+        stylesheets =\r
+            _.chain(stylesheets)\r
+            .map(function (sheets, i) {\r
+                return utility.parsePaths(files[i], sheets, options);\r
+            })\r
+            .flatten()\r
+            .uniq()\r
+            .value();\r
+    } else {\r
+        /* Reset the array if we didn't find any link tags */\r
+        stylesheets = [];\r
+    }\r
+    return [files, options, pages, utility.readStylesheets(stylesheets)];\r
+}\r
+\r
+/**\r
+ * Do the actual work\r
+ * @param  {Array}   files       List of HTML files\r
+ * @param  {Object}  options     UnCSS options\r
+ * @param  {Array}   pages       Pages opened by phridge\r
+ * @param  {Array}   stylesheets List of CSS files\r
+ * @return {promise}\r
+ */\r
+function processWithTextApi(files, options, pages, stylesheets) {\r
+    /* If we specified a raw string of CSS, add it to the stylesheets array */\r
+    if (options.raw) {\r
+        if (_.isString(options.raw)) {\r
+            stylesheets.push(options.raw);\r
+        } else {\r
+            throw new Error('UnCSS: options.raw - expected a string');\r
+        }\r
+    }\r
+\r
+    /* At this point, there isn't any point in running the rest of the task if:\r
+     * - We didn't specify any stylesheet links in the options object\r
+     * - We couldn't find any stylesheet links in the HTML itself\r
+     * - We weren't passed a string of raw CSS in addition to, or to replace\r
+     *     either of the above\r
+     */\r
+    if (!_.flatten(stylesheets).length) {\r
+        throw new Error('UnCSS: no stylesheets found');\r
+    }\r
+\r
+    /* OK, so we have some CSS to work with!\r
+     * Three steps:\r
+     * - Parse the CSS\r
+     * - Remove the unused rules\r
+     * - Return the optimized CSS as a string\r
+     */\r
+    var cssStr = stylesheets.join(' \n'),\r
+        pcss, report;\r
+    try {\r
+        pcss = postcss.parse(cssStr);\r
+    } catch (err) {\r
+        /* Try and construct a helpful error message */\r
+        throw utility.parseErrorMessage(err, cssStr);\r
+    }\r
+    return uncss(pages, pcss, options.ignore).spread(function (css, rep) {\r
+        var newCssStr = '';\r
+        postcss.stringify(css, function(result) {\r
+            newCssStr += result;\r
+        });\r
+\r
+        if (options.report) {\r
+            report = {\r
+                original: cssStr,\r
+                selectors: rep\r
+            };\r
+        }\r
+        return new promise(function (resolve) {\r
+            resolve([newCssStr, report]);\r
+        });\r
+    });\r
+}\r
+\r
+/**\r
+ * Main exposed function.\r
+ * Here we check the options and callback, then run the files through PhantomJS.\r
+ * @param  {Array}    files     Array of filenames\r
+ * @param  {Object}   [options] options\r
+ * @param  {Function} callback(Error, String, Object)\r
+ */\r
+function init(files, options, callback) {\r
+\r
+    if (_.isFunction(options)) {\r
+        /* There were no options, this argument is actually the callback */\r
+        callback = options;\r
+        options = {};\r
+    } else if (!_.isFunction(callback)) {\r
+        throw new TypeError('UnCSS: expected a callback');\r
+    }\r
+\r
+    /* Try and read options from the specified uncssrc file */\r
+    if (options.uncssrc) {\r
+        try {\r
+            /* Manually-specified options take precedence over uncssrc options */\r
+            options = _.merge(utility.parseUncssrc(options.uncssrc), options);\r
+        } catch (err) {\r
+            if (err instanceof SyntaxError) {\r
+                callback(new SyntaxError('UnCSS: uncssrc file is invalid JSON.'));\r
+                return;\r
+            }\r
+            callback(err);\r
+            return;\r
+        }\r
+    }\r
+\r
+    /* Assign default values to options, unless specified */\r
+    options = _.defaults(options, {\r
+        csspath: '',\r
+        ignore: [],\r
+        media: [],\r
+        timeout: 0,\r
+        report: false,\r
+        ignoreSheets: [],\r
+        html: files,\r
+        // gulp-uncss parameters:\r
+        raw: null\r
+    });\r
+\r
+    serializedQueue.push(options, callback);\r
+}\r
+\r
+function processAsPostCss(files, options, pages) {\r
+    return uncss(pages, options.rawPostCss, options.ignore);\r
+}\r
+\r
+// There always seem to be problems trying to run more than one phantom at a time,\r
+// so let's serialize all their accesses here\r
+var serializedQueue = async.queue(function (opts, callback) {\r
+    if (opts.usePostCssInternal) {\r
+        return promise\r
+            .using(phantom.init(phantom.phantom), function () {\r
+                return getHTML(opts.html, opts)\r
+                    .spread(processAsPostCss);\r
+            })\r
+            .asCallback(callback);\r
+    }\r
+    return promise\r
+        .using(phantom.init(phantom.phantom), function () {\r
+            return getHTML(opts.html, opts)\r
+                .spread(getStylesheets)\r
+                .spread(getCSS)\r
+                .spread(processWithTextApi);\r
+        })\r
+        .asCallback(callback, { spread: true });\r
+}, 1);\r
+\r
+serializedQueue.drain = function() {\r
+    phantom.cleanupAll();\r
+};\r
+\r
+var postcssPlugin = postcss.plugin('uncss', function (opts) {\r
+    opts = _.defaults(opts, {\r
+        usePostCssInternal: true,\r
+        // Ignore stylesheets in the HTML files; only use those from the stream\r
+        ignoreSheets: [/\s*/],\r
+        html: [],\r
+        ignore: []\r
+    });\r
+\r
+    return function (css, result) { // eslint-disable-line no-unused-vars\r
+        opts = assign(opts, {\r
+            // This is used to pass the css object in to processAsPostCSS\r
+            rawPostCss: css\r
+        });\r
+\r
+        return new promise(function (resolve, reject) {\r
+            serializedQueue.push(opts, function (err) {\r
+                if (err) {\r
+                    reject(err);\r
+                } else {\r
+                    resolve();\r
+                }\r
+            });\r
+        });\r
+    };\r
+});\r
+\r
+module.exports = init;\r
+module.exports.postcssPlugin = postcssPlugin;\r