1 | /*
|
2 | * MIT License http://opensource.org/licenses/MIT
|
3 | * Author: Ben Holloway @bholloway
|
4 | */
|
5 | ;
|
6 |
|
7 | var path = require('path'),
|
8 | loaderUtils = require('loader-utils');
|
9 |
|
10 | /**
|
11 | * Create a value processing function for a given file path.
|
12 | *
|
13 | * @param {string} filename The current file being processed
|
14 | * @param {{absolute:string, keepQuery:boolean, join:function, root:string}} options Options hash
|
15 | * @return {function} value processing function
|
16 | */
|
17 | function valueProcessor(filename, options) {
|
18 | var URL_STATEMENT_REGEX = /(url\s*\()\s*(?:(['"])((?:(?!\2).)*)(\2)|([^'"](?:(?!\)).)*[^'"]))\s*(\))/g;
|
19 | var directory = path.dirname(filename);
|
20 | var join = options.join(filename, options);
|
21 |
|
22 | /**
|
23 | * Process the given CSS declaration value.
|
24 | *
|
25 | * @param {string} value A declaration value that may or may not contain a url() statement
|
26 | * @param {string|Iterator.<string>} candidate An absolute path that may be the correct base or an Iterator thereof
|
27 | */
|
28 | return function transformValue(value, candidate) {
|
29 |
|
30 | // allow multiple url() values in the declaration
|
31 | // split by url statements and process the content
|
32 | // additional capture groups are needed to match quotations correctly
|
33 | // escaped quotations are not considered
|
34 | return value
|
35 | .split(URL_STATEMENT_REGEX)
|
36 | .map(eachSplitOrGroup)
|
37 | .join('');
|
38 |
|
39 | /**
|
40 | * Encode the content portion of <code>url()</code> statements.
|
41 | * There are 4 capture groups in the split making every 5th unmatched.
|
42 | *
|
43 | * @param {string} token A single split item
|
44 | * @param {number} i The index of the item in the split
|
45 | * @param {Array} arr The array of split values
|
46 | * @returns {string} Every 3 or 5 items is an encoded url everything else is as is
|
47 | */
|
48 | function eachSplitOrGroup(token, i, arr) {
|
49 |
|
50 | // we can get groups as undefined under certain match circumstances
|
51 | var initialised = token || '';
|
52 |
|
53 | // the content of the url() statement is either in group 3 or group 5
|
54 | var mod = i % 7;
|
55 | if ((mod === 3) || (mod === 5)) {
|
56 |
|
57 | // detect quoted url and unescape backslashes
|
58 | var before = arr[i - 1],
|
59 | after = arr[i + 1],
|
60 | isQuoted = (before === after) && ((before === '\'') || (before === '"')),
|
61 | unescaped = isQuoted ? initialised.replace(/\\{2}/g, '\\') : initialised;
|
62 |
|
63 | // split into uri and query/hash and then find the absolute path to the uri
|
64 | var split = unescaped.split(/([?#])/g),
|
65 | uri = split[0],
|
66 | absolute = testIsRelative(uri) && join(uri, candidate) || testIsAbsolute(uri) && join(uri),
|
67 | query = options.keepQuery ? split.slice(1).join('') : '';
|
68 |
|
69 | // use the absolute path in absolute mode or else relative path (or default to initialised)
|
70 | // #6 - backslashes are not legal in URI
|
71 | if (!absolute) {
|
72 | return initialised;
|
73 | } else if (options.absolute) {
|
74 | return absolute.replace(/\\/g, '/') + query;
|
75 | } else {
|
76 | return loaderUtils.urlToRequest(
|
77 | path.relative(directory, absolute).replace(/\\/g, '/') + query
|
78 | );
|
79 | }
|
80 | }
|
81 | // everything else, including parentheses and quotation (where present) and media statements
|
82 | else {
|
83 | return initialised;
|
84 | }
|
85 | }
|
86 | };
|
87 |
|
88 | /**
|
89 | * The loaderUtils.isUrlRequest() doesn't support windows absolute paths on principle. We do not subscribe to that
|
90 | * dogma so we add path.isAbsolute() check to allow them.
|
91 | *
|
92 | * We also eliminate module relative (~) paths.
|
93 | *
|
94 | * @param {string|undefined} uri A uri string possibly empty or undefined
|
95 | * @return {boolean} True for relative uri
|
96 | */
|
97 | function testIsRelative(uri) {
|
98 | return !!uri && loaderUtils.isUrlRequest(uri, false) && !path.isAbsolute(uri) && (uri.indexOf('~') !== 0);
|
99 | }
|
100 |
|
101 | /**
|
102 | * The loaderUtils.isUrlRequest() doesn't support windows absolute paths on principle. We do not subscribe to that
|
103 | * dogma so we add path.isAbsolute() check to allow them.
|
104 | *
|
105 | * @param {string|undefined} uri A uri string possibly empty or undefined
|
106 | * @return {boolean} True for absolute uri
|
107 | */
|
108 | function testIsAbsolute(uri) {
|
109 | return !!uri && (typeof options.root === 'string') && loaderUtils.isUrlRequest(uri, options.root) &&
|
110 | (/^\//.test(uri) || path.isAbsolute(uri));
|
111 | }
|
112 | }
|
113 |
|
114 | module.exports = valueProcessor;
|