Security update for permissions_by_term
[yaffs-website] / node_modules / uncss / src / uncss.js
1 'use strict';\r
2 \r
3 var promise = require('bluebird'),\r
4     async = require('async'),\r
5     assign = require('object-assign'),\r
6     fs = require('fs'),\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
15 \r
16 /**\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
20  * @return {promise}\r
21  */\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
26         });\r
27     }\r
28 \r
29     files = _.flatten(files.map(function (file) {\r
30         if (!isURL(file) && !isHTML(file)) {\r
31             return glob.sync(file);\r
32         }\r
33         return file;\r
34     }));\r
35 \r
36     if (!files.length) {\r
37         throw new Error('UnCSS: no HTML files found');\r
38     }\r
39 \r
40     return promise.map(files, function (filename) {\r
41         if (isURL(filename)) {\r
42             return phantom.fromRemote(filename, options);\r
43         }\r
44         if (fs.existsSync(filename)) {\r
45             return phantom.fromLocal(filename, options);\r
46         }\r
47         // raw html\r
48         return phantom.fromRaw(filename, options);\r
49     }).then(function (pages) {\r
50         return [files, options, pages];\r
51     });\r
52 }\r
53 \r
54 /**\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
59  * @return {promise}\r
60  */\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
65     }\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
71     });\r
72 }\r
73 \r
74 /**\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
80  * @return {promise}\r
81  */\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
90                     }\r
91                     return sheet !== ignore;\r
92                 });\r
93             });\r
94         });\r
95     }\r
96 \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
105          */\r
106         stylesheets =\r
107             _.chain(stylesheets)\r
108             .map(function (sheets, i) {\r
109                 return utility.parsePaths(files[i], sheets, options);\r
110             })\r
111             .flatten()\r
112             .uniq()\r
113             .value();\r
114     } else {\r
115         /* Reset the array if we didn't find any link tags */\r
116         stylesheets = [];\r
117     }\r
118     return [files, options, pages, utility.readStylesheets(stylesheets)];\r
119 }\r
120 \r
121 /**\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
128  */\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
131     if (options.raw) {\r
132         if (_.isString(options.raw)) {\r
133             stylesheets.push(options.raw);\r
134         } else {\r
135             throw new Error('UnCSS: options.raw - expected a string');\r
136         }\r
137     }\r
138 \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
144      */\r
145     if (!_.flatten(stylesheets).length) {\r
146         throw new Error('UnCSS: no stylesheets found');\r
147     }\r
148 \r
149     /* OK, so we have some CSS to work with!\r
150      * Three steps:\r
151      * - Parse the CSS\r
152      * - Remove the unused rules\r
153      * - Return the optimized CSS as a string\r
154      */\r
155     var cssStr = stylesheets.join(' \n'),\r
156         pcss, report;\r
157     try {\r
158         pcss = postcss.parse(cssStr);\r
159     } catch (err) {\r
160         /* Try and construct a helpful error message */\r
161         throw utility.parseErrorMessage(err, cssStr);\r
162     }\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
167         });\r
168 \r
169         if (options.report) {\r
170             report = {\r
171                 original: cssStr,\r
172                 selectors: rep\r
173             };\r
174         }\r
175         return new promise(function (resolve) {\r
176             resolve([newCssStr, report]);\r
177         });\r
178     });\r
179 }\r
180 \r
181 /**\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
187  */\r
188 function init(files, options, callback) {\r
189 \r
190     if (_.isFunction(options)) {\r
191         /* There were no options, this argument is actually the callback */\r
192         callback = options;\r
193         options = {};\r
194     } else if (!_.isFunction(callback)) {\r
195         throw new TypeError('UnCSS: expected a callback');\r
196     }\r
197 \r
198     /* Try and read options from the specified uncssrc file */\r
199     if (options.uncssrc) {\r
200         try {\r
201             /* Manually-specified options take precedence over uncssrc options */\r
202             options = _.merge(utility.parseUncssrc(options.uncssrc), options);\r
203         } catch (err) {\r
204             if (err instanceof SyntaxError) {\r
205                 callback(new SyntaxError('UnCSS: uncssrc file is invalid JSON.'));\r
206                 return;\r
207             }\r
208             callback(err);\r
209             return;\r
210         }\r
211     }\r
212 \r
213     /* Assign default values to options, unless specified */\r
214     options = _.defaults(options, {\r
215         csspath: '',\r
216         ignore: [],\r
217         media: [],\r
218         timeout: 0,\r
219         report: false,\r
220         ignoreSheets: [],\r
221         html: files,\r
222         // gulp-uncss parameters:\r
223         raw: null\r
224     });\r
225 \r
226     serializedQueue.push(options, callback);\r
227 }\r
228 \r
229 function processAsPostCss(files, options, pages) {\r
230     return uncss(pages, options.rawPostCss, options.ignore);\r
231 }\r
232 \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
237         return promise\r
238             .using(phantom.init(phantom.phantom), function () {\r
239                 return getHTML(opts.html, opts)\r
240                     .spread(processAsPostCss);\r
241             })\r
242             .asCallback(callback);\r
243     }\r
244     return promise\r
245         .using(phantom.init(phantom.phantom), function () {\r
246             return getHTML(opts.html, opts)\r
247                 .spread(getStylesheets)\r
248                 .spread(getCSS)\r
249                 .spread(processWithTextApi);\r
250         })\r
251         .asCallback(callback, { spread: true });\r
252 }, 1);\r
253 \r
254 serializedQueue.drain = function() {\r
255     phantom.cleanupAll();\r
256 };\r
257 \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
263         html: [],\r
264         ignore: []\r
265     });\r
266 \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
270             rawPostCss: css\r
271         });\r
272 \r
273         return new promise(function (resolve, reject) {\r
274             serializedQueue.push(opts, function (err) {\r
275                 if (err) {\r
276                     reject(err);\r
277                 } else {\r
278                     resolve();\r
279                 }\r
280             });\r
281         });\r
282     };\r
283 });\r
284 \r
285 module.exports = init;\r
286 module.exports.postcssPlugin = postcssPlugin;\r