3 var promise = require('bluebird'),
\r
4 async = require('async'),
\r
5 assign = require('object-assign'),
\r
7 glob = require('glob'),
\r
8 isHTML = require('is-html'),
\r
9 isURL = require('is-absolute-url'),
\r
10 phantom = require('./phantom.js'),
\r
11 postcss = require('postcss'),
\r
12 uncss = require('./lib.js'),
\r
13 utility = require('./utility.js'),
\r
14 _ = require('lodash');
\r
17 * Get the contents of HTML pages through PhantomJS.
\r
18 * @param {Array} files List of HTML files
\r
19 * @param {Object} options UnCSS options
\r
22 function getHTML(files, options) {
\r
23 if (_.isString(files)) {
\r
24 return phantom.fromRaw(files, options).then(function (pages) {
\r
25 return [files, options, [pages]];
\r
29 files = _.flatten(files.map(function (file) {
\r
30 if (!isURL(file) && !isHTML(file)) {
\r
31 return glob.sync(file);
\r
36 if (!files.length) {
\r
37 throw new Error('UnCSS: no HTML files found');
\r
40 return promise.map(files, function (filename) {
\r
41 if (isURL(filename)) {
\r
42 return phantom.fromRemote(filename, options);
\r
44 if (fs.existsSync(filename)) {
\r
45 return phantom.fromLocal(filename, options);
\r
48 return phantom.fromRaw(filename, options);
\r
49 }).then(function (pages) {
\r
50 return [files, options, pages];
\r
55 * Get the contents of CSS files.
\r
56 * @param {Array} files List of HTML files
\r
57 * @param {Object} options UnCSS options
\r
58 * @param {Array} pages Pages opened by phridge
\r
61 function getStylesheets(files, options, pages) {
\r
62 if (options.stylesheets && options.stylesheets.length) {
\r
63 /* Simulate the behavior below */
\r
64 return [files, options, pages, [options.stylesheets]];
\r
66 /* Extract the stylesheets from the HTML */
\r
67 return promise.map(pages, function (page) {
\r
68 return phantom.getStylesheets(page, options);
\r
69 }).then(function (stylesheets) {
\r
70 return [files, options, pages, stylesheets];
\r
75 * Get the contents of CSS files.
\r
76 * @param {Array} files List of HTML files
\r
77 * @param {Object} options UnCSS options
\r
78 * @param {Array} pages Pages opened by phridge
\r
79 * @param {Array} stylesheets List of CSS files
\r
82 function getCSS(files, options, pages, stylesheets) {
\r
83 /* Ignore specified stylesheets */
\r
84 if (options.ignoreSheets.length) {
\r
85 stylesheets = stylesheets.map(function (arr) {
\r
86 return arr.filter(function (sheet) {
\r
87 return _.every(options.ignoreSheets, function (ignore) {
\r
88 if (_.isRegExp(ignore)) {
\r
89 return !ignore.test(sheet);
\r
91 return sheet !== ignore;
\r
97 if (_.flatten(stylesheets).length) {
\r
98 /* Only run this if we found links to stylesheets (there may be none...)
\r
99 * files = ['some_file.html', 'some_other_file.html']
\r
100 * stylesheets = [['relative_css_path.css', ...],
\r
101 * ['maybe_a_duplicate.css', ...]]
\r
102 * We need to - make the stylesheets' paths relative to the HTML files,
\r
103 * - flatten the array,
\r
104 * - remove duplicates
\r
107 _.chain(stylesheets)
\r
108 .map(function (sheets, i) {
\r
109 return utility.parsePaths(files[i], sheets, options);
\r
115 /* Reset the array if we didn't find any link tags */
\r
118 return [files, options, pages, utility.readStylesheets(stylesheets)];
\r
122 * Do the actual work
\r
123 * @param {Array} files List of HTML files
\r
124 * @param {Object} options UnCSS options
\r
125 * @param {Array} pages Pages opened by phridge
\r
126 * @param {Array} stylesheets List of CSS files
\r
127 * @return {promise}
\r
129 function processWithTextApi(files, options, pages, stylesheets) {
\r
130 /* If we specified a raw string of CSS, add it to the stylesheets array */
\r
132 if (_.isString(options.raw)) {
\r
133 stylesheets.push(options.raw);
\r
135 throw new Error('UnCSS: options.raw - expected a string');
\r
139 /* At this point, there isn't any point in running the rest of the task if:
\r
140 * - We didn't specify any stylesheet links in the options object
\r
141 * - We couldn't find any stylesheet links in the HTML itself
\r
142 * - We weren't passed a string of raw CSS in addition to, or to replace
\r
143 * either of the above
\r
145 if (!_.flatten(stylesheets).length) {
\r
146 throw new Error('UnCSS: no stylesheets found');
\r
149 /* OK, so we have some CSS to work with!
\r
152 * - Remove the unused rules
\r
153 * - Return the optimized CSS as a string
\r
155 var cssStr = stylesheets.join(' \n'),
\r
158 pcss = postcss.parse(cssStr);
\r
160 /* Try and construct a helpful error message */
\r
161 throw utility.parseErrorMessage(err, cssStr);
\r
163 return uncss(pages, pcss, options.ignore).spread(function (css, rep) {
\r
164 var newCssStr = '';
\r
165 postcss.stringify(css, function(result) {
\r
166 newCssStr += result;
\r
169 if (options.report) {
\r
175 return new promise(function (resolve) {
\r
176 resolve([newCssStr, report]);
\r
182 * Main exposed function.
\r
183 * Here we check the options and callback, then run the files through PhantomJS.
\r
184 * @param {Array} files Array of filenames
\r
185 * @param {Object} [options] options
\r
186 * @param {Function} callback(Error, String, Object)
\r
188 function init(files, options, callback) {
\r
190 if (_.isFunction(options)) {
\r
191 /* There were no options, this argument is actually the callback */
\r
192 callback = options;
\r
194 } else if (!_.isFunction(callback)) {
\r
195 throw new TypeError('UnCSS: expected a callback');
\r
198 /* Try and read options from the specified uncssrc file */
\r
199 if (options.uncssrc) {
\r
201 /* Manually-specified options take precedence over uncssrc options */
\r
202 options = _.merge(utility.parseUncssrc(options.uncssrc), options);
\r
204 if (err instanceof SyntaxError) {
\r
205 callback(new SyntaxError('UnCSS: uncssrc file is invalid JSON.'));
\r
213 /* Assign default values to options, unless specified */
\r
214 options = _.defaults(options, {
\r
222 // gulp-uncss parameters:
\r
226 serializedQueue.push(options, callback);
\r
229 function processAsPostCss(files, options, pages) {
\r
230 return uncss(pages, options.rawPostCss, options.ignore);
\r
233 // There always seem to be problems trying to run more than one phantom at a time,
\r
234 // so let's serialize all their accesses here
\r
235 var serializedQueue = async.queue(function (opts, callback) {
\r
236 if (opts.usePostCssInternal) {
\r
238 .using(phantom.init(phantom.phantom), function () {
\r
239 return getHTML(opts.html, opts)
\r
240 .spread(processAsPostCss);
\r
242 .asCallback(callback);
\r
245 .using(phantom.init(phantom.phantom), function () {
\r
246 return getHTML(opts.html, opts)
\r
247 .spread(getStylesheets)
\r
249 .spread(processWithTextApi);
\r
251 .asCallback(callback, { spread: true });
\r
254 serializedQueue.drain = function() {
\r
255 phantom.cleanupAll();
\r
258 var postcssPlugin = postcss.plugin('uncss', function (opts) {
\r
259 opts = _.defaults(opts, {
\r
260 usePostCssInternal: true,
\r
261 // Ignore stylesheets in the HTML files; only use those from the stream
\r
262 ignoreSheets: [/\s*/],
\r
267 return function (css, result) { // eslint-disable-line no-unused-vars
\r
268 opts = assign(opts, {
\r
269 // This is used to pass the css object in to processAsPostCSS
\r
273 return new promise(function (resolve, reject) {
\r
274 serializedQueue.push(opts, function (err) {
\r
285 module.exports = init;
\r
286 module.exports.postcssPlugin = postcssPlugin;
\r