--- /dev/null
+'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