UNPKG

12.8 kBJavaScriptView Raw
1'use strict';
2
3function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
4
5var postcss = _interopDefault(require('postcss'));
6var valueParser = require('postcss-value-parser');
7var valueParser__default = _interopDefault(valueParser);
8var path = require('path');
9var fs = require('fs');
10var htmlparser2 = require('htmlparser2');
11var serialize = _interopDefault(require('dom-serializer'));
12var cssSelect = require('css-select');
13
14function parseRuleDefinition(params) {
15 var _valueParser = valueParser__default(params),
16 nodes = _valueParser.nodes;
17
18 if (nodes.length !== 3 || nodes[0].type !== 'word' || nodes[1].type !== 'space' || nodes[2].type !== 'function' || nodes[2].value !== 'url' || nodes[2].nodes.length === 0) {
19 throw Error('Invalid "@svg-load" definition');
20 }
21 return {
22 name: nodes[0].value,
23 url: nodes[2].nodes[0].value
24 };
25}
26
27function getRuleParams(rule) {
28 var params = {};
29 var selectors = {};
30
31 rule.each(function (node) {
32 if (node.type === 'decl') {
33 params[node.prop] = node.value;
34 } else if (node.type === 'rule') {
35 var selector = selectors[node.selectors] || {};
36 node.each(function (child) {
37 if (child.type === 'decl') {
38 selector[child.prop] = child.value;
39 }
40 });
41 selectors[node.selectors] = selector;
42 }
43 });
44
45 return {
46 params,
47 selectors
48 };
49}
50
51function getUrl(nodes) {
52 var url = '';
53 var urlEnd = 0;
54
55 for (var i = 0; i < nodes.length; i += 1) {
56 var node = nodes[i];
57 if (node.type === 'string') {
58 if (i !== 0) {
59 throw Error(`Invalid "svg-load(${valueParser.stringify(nodes)})" definition`);
60 }
61 url = node.value;
62 urlEnd = i + 1;
63 break;
64 }
65 if (node.type === 'div' && node.value === ',') {
66 if (i === 0) {
67 throw Error(`Invalid "svg-load(${valueParser.stringify(nodes)})" definition`);
68 }
69 urlEnd = i;
70 break;
71 }
72 url += valueParser.stringify(node);
73 urlEnd += 1;
74 }
75
76 return {
77 url,
78 urlEnd
79 };
80}
81
82function getParamChunks(nodes) {
83 var list = [];
84 var lastArg = nodes.reduce(function (arg, node) {
85 if (node.type === 'word' || node.type === 'string') {
86 return arg + node.value;
87 }
88 if (node.type === 'space') {
89 return arg + ' ';
90 }
91 if (node.type === 'div' && node.value === ',') {
92 list.push(arg);
93 return '';
94 }
95 return arg + valueParser.stringify(node);
96 }, '');
97
98 return list.concat(lastArg);
99}
100
101function splitParams(list) {
102 var params = {};
103
104 list.reduce(function (sep, arg) {
105 if (!arg) {
106 throw Error(`Expected parameter`);
107 }
108
109 if (!sep) {
110 if (arg.indexOf(':') !== -1) {
111 sep = ':';
112 } else if (arg.indexOf('=') !== -1) {
113 sep = '=';
114 } else {
115 throw Error(`Expected ":" or "=" separator in "${arg}"`);
116 }
117 }
118
119 var pair = arg.split(sep);
120 if (pair.length !== 2) {
121 throw Error(`Expected "${sep}" separator in "${arg}"`);
122 }
123 params[pair[0].trim()] = pair[1].trim();
124
125 return sep;
126 }, null);
127
128 return params;
129}
130
131function getLoader(parsedValue, valueNode) {
132 if (!valueNode.nodes.length) {
133 throw Error(`Invalid "svg-load()" statement`);
134 }
135
136 // parse url
137
138 var _getUrl = getUrl(valueNode.nodes),
139 url = _getUrl.url,
140 urlEnd = _getUrl.urlEnd;
141
142 // parse params
143
144
145 var paramsNodes = valueNode.nodes.slice(urlEnd + 1);
146 var params = urlEnd !== valueNode.nodes.length ? splitParams(getParamChunks(paramsNodes)) : {};
147
148 return {
149 url,
150 params,
151 valueNode,
152 parsedValue
153 };
154}
155
156function getInliner(parsedValue, valueNode) {
157 if (!valueNode.nodes.length) {
158 throw Error(`Invalid "svg-inline()" statement`);
159 }
160 var name = valueNode.nodes[0].value;
161
162 return {
163 name,
164 valueNode,
165 parsedValue
166 };
167}
168
169function parseDeclValue(value) {
170 var loaders = [];
171 var inliners = [];
172 var parsedValue = valueParser__default(value);
173
174 parsedValue.walk(function (valueNode) {
175 if (valueNode.type === 'function') {
176 if (valueNode.value === 'svg-load') {
177 loaders.push(getLoader(parsedValue, valueNode));
178 } else if (valueNode.value === 'svg-inline') {
179 inliners.push(getInliner(parsedValue, valueNode));
180 }
181 }
182 });
183
184 return {
185 loaders,
186 inliners
187 };
188}
189
190function resolveId(file, url, opts) {
191 if (opts.path) {
192 return path.resolve(opts.path, url);
193 }
194 if (file) {
195 return path.resolve(path.dirname(file), url);
196 }
197 return path.resolve(url);
198}
199
200function render(code) {
201 var dom = htmlparser2.parseDOM(code, { xmlMode: true });
202
203 for (var _len = arguments.length, processors = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
204 processors[_key - 1] = arguments[_key];
205 }
206
207 processors.forEach(function (processor) {
208 return processor(dom);
209 });
210
211 return serialize(dom);
212}
213
214function encode(code) {
215 return code.replace(/%/g, '%25').replace(/</g, '%3C').replace(/>/g, '%3E').replace(/&/g, '%26').replace(/#/g, '%23');
216}
217
218function addXmlns(code) {
219 if (code.indexOf('xmlns') === -1) {
220 return code.replace(/<svg/g, '<svg xmlns="http://www.w3.org/2000/svg"');
221 } else {
222 return code;
223 }
224}
225
226function normalize(code) {
227 return code.replace(/'/g, '%22').replace(/"/g, '\'').replace(/\s+/g, ' ').trim();
228}
229
230function transform(code) {
231 return `"data:image/svg+xml;charset=utf-8,${normalize(code)}"`;
232}
233
234function matchId(exp, id) {
235 return exp instanceof RegExp ? exp.test(id) : Boolean(exp);
236}
237
238function removeFillAttrib(element) {
239 delete element.attribs.fill;
240}
241
242function removeFill(id, opts) {
243 return function (dom) {
244 if (matchId(opts.removeFill, id)) {
245 cssSelect.selectAll('[fill]', dom).forEach(removeFillAttrib);
246 }
247 };
248}
249
250function applyParams(params) {
251 return function (_ref) {
252 var attribs = _ref.attribs;
253
254 Object.keys(params).forEach(function (name) {
255 attribs[name] = params[name];
256 });
257 };
258}
259
260function applyRootParams(params) {
261 return function (dom) {
262 applyParams(params)(cssSelect.selectOne('svg', dom));
263 };
264}
265
266function applySelectedParams(selectors) {
267 return function (dom) {
268 var svg = cssSelect.selectOne('svg', dom);
269
270 Object.keys(selectors).forEach(function (selector) {
271 cssSelect.selectAll(selector, svg).forEach(applyParams(selectors[selector]));
272 });
273 };
274}
275
276function read(id) {
277 return new Promise(function (resolve, reject) {
278 fs.readFile(id, 'utf-8', function (err, data) {
279 if (err) {
280 reject(Error(`Can't load '${id}'`));
281 } else {
282 resolve(data);
283 }
284 });
285 });
286}
287
288function load(id, params, selectors, opts) {
289 var processors = [removeFill(id, opts), applyRootParams(params), applySelectedParams(selectors)];
290 return read(id).then(function (data) {
291 var code = render.apply(undefined, [data].concat(processors));
292
293 if (opts.xmlns !== false) {
294 code = addXmlns(code);
295 }
296
297 if (opts.encode !== false) {
298 code = (opts.encode || encode)(code);
299 }
300
301 if (opts.transform !== false) {
302 code = (opts.transform || transform)(code, id);
303 }
304
305 return code;
306 });
307}
308
309function removeLoader(loader) {
310 if (!loader.error && loader.node.type === 'atrule') {
311 loader.node.remove();
312 }
313}
314
315function applyInliner(inliner) {
316 if (!inliner.loader.error) {
317 inliner.valueNode.value = 'url';
318 inliner.valueNode.nodes = [{
319 type: 'word',
320 value: inliner.loader.svg
321 }];
322 }
323}
324
325function stringifyInliner(inliner) {
326 if (!inliner.loader.error) {
327 inliner.node.value = String(inliner.parsedValue);
328 }
329}
330
331var index = postcss.plugin('postcss-inline-svg', function () {
332 var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
333 return function (css, result) {
334 var loadersMap = {};
335 var loaders = [];
336 var inliners = [];
337
338 css.walk(function (node) {
339 if (node.type === 'atrule') {
340 if (node.name === 'svg-load') {
341 try {
342 var file = node.source && node.source.input && node.source.input.file;
343
344 var _parseRuleDefinition = parseRuleDefinition(node.params),
345 name = _parseRuleDefinition.name,
346 url = _parseRuleDefinition.url;
347
348 var _getRuleParams = getRuleParams(node),
349 params = _getRuleParams.params,
350 selectors = _getRuleParams.selectors;
351
352 var loader = {
353 id: resolveId(file, url, opts),
354 parent: file,
355 params,
356 selectors,
357 node
358 };
359 loaders.push(loader);
360 loadersMap[name] = loader;
361 } catch (e) {
362 node.warn(result, e.message);
363 }
364 }
365 } else if (node.type === 'decl') {
366 if (node.value.indexOf('svg-load(') !== -1 || node.value.indexOf('svg-inline(') !== -1) {
367 try {
368 var _file = node.source && node.source.input && node.source.input.file;
369 var statements = parseDeclValue(node.value);
370 statements.loaders.forEach(function (_ref) {
371 var url = _ref.url,
372 params = _ref.params,
373 valueNode = _ref.valueNode,
374 parsedValue = _ref.parsedValue;
375
376 var loader = {
377 id: resolveId(_file, url, opts),
378 parent: _file,
379 params,
380 selectors: {},
381 node
382 };
383 loaders.push(loader);
384 inliners.push({
385 loader,
386 node,
387 valueNode,
388 parsedValue
389 });
390 });
391 statements.inliners.forEach(function (_ref2) {
392 var name = _ref2.name,
393 valueNode = _ref2.valueNode,
394 parsedValue = _ref2.parsedValue;
395
396 var loader = loadersMap[name];
397 if (loader) {
398 inliners.push({
399 loader,
400 node,
401 valueNode,
402 parsedValue
403 });
404 } else {
405 node.warn(result, `"${name}" svg is not defined`);
406 }
407 });
408 } catch (e) {
409 node.warn(result, e.message);
410 }
411 }
412 }
413 });
414
415 var promises = loaders.map(function (loader) {
416 return load(loader.id, loader.params, loader.selectors, opts).then(function (code) {
417 loader.svg = code;
418 result.messages.push({
419 type: 'dependency',
420 file: loader.id,
421 parent: loader.parent
422 });
423 }).catch(function (err) {
424 loader.error = true;
425 loader.node.warn(result, err.message);
426 });
427 });
428
429 return Promise.all(promises).then(function () {
430 loaders.forEach(removeLoader);
431 inliners.forEach(applyInliner);
432 inliners.forEach(stringifyInliner);
433 });
434 };
435});
436
437module.exports = index;
\No newline at end of file