-
Notifications
You must be signed in to change notification settings - Fork 23
/
transpile-decl.js
147 lines (117 loc) · 4.07 KB
/
transpile-decl.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/* Tooling
/* ========================================================================== */
// native tooling
import path from 'path';
// external tooling
import parser from 'postcss-values-parser';
// internal tooling
import elementClone from './element-clone';
import elementById from './element-by-id';
import elementAsDURISVG from './element-as-data-uri-svg';
import readClosestSVG from './read-closest-svg';
import transpileStyles from './transpile-styles';
/* Transpile declarations
/* ========================================================================== */
export default function transpileDecl(result, promises, decl, opts, cache) { // eslint-disable-line max-params
// path to the current working file and directory by declaration
const declWF = path.resolve(decl.source && decl.source.input && decl.source.input.file ? decl.source.input.file : result.root.source && result.root.source.input && result.root.source.input.file ? result.root.source.input.file : path.join(process.cwd(), 'index.css'));
const declWD = path.dirname(declWF);
// list of files to watch
const files = {};
// walk each node of the declaration
const declAST = parser(decl.value).parse();
declAST.walk(node => {
// if the node is a url containing an svg fragment
if (isExternalURLFunction(node)) {
// <url> of url(<url>)
const urlNode = node.nodes[1];
// <url> split by fragment identifier symbol (#)
const urlParts = urlNode.value.split('#');
// <url> src
const src = urlParts[0];
// <url> fragment identifier
const id = urlParts.slice(1).join('#');
// whether the <url> has a fragment identifier
const hasID = urlParts.length > 1;
// <url> param()s
const params = paramsFromNodes(
node.nodes.slice(2, -1)
);
node.nodes.slice(2, -1).forEach(childNode => {
childNode.remove();
});
promises.push(
readClosestSVG(src, [declWD].concat(opts.dirs), cache).then(svgResult => {
const file = svgResult.file;
const document = svgResult.document;
// conditionally watch svgs for changes
if (!files[file]) {
files[file] = result.messages.push({
type: 'dependency',
file,
parent: declWF
});
}
// document cache
const ids = document.ids = document.ids || {};
// conditionally update the document cache
if (hasID && !ids[id]) {
ids[id] = elementById(document, id);
}
// element fragment or document
const element = hasID ? ids[id] : document;
// if the element exists
if (element) {
// clone of the element
const clone = elementClone(element);
// update the clone styles using the params
transpileStyles(clone, params);
// promise updated <url> and declaration
return elementAsDURISVG(clone, document, opts).then(xml => {
// update <url>
urlNode.value = xml;
// conditionally quote <url>
if (opts.utf8) {
urlNode.replaceWith(
parser.string({
value: urlNode.value,
quoted: true,
raws: Object.assign(urlNode.raws, { quote: '"' })
})
);
}
// update declaration
decl.value = String(declAST);
});
}
}).catch(error => {
result.warn(error, node);
})
);
}
});
}
/* Inline Tooling
/* ========================================================================== */
// whether the node if a function()
function isExternalURLFunction(node) {
return node.type === 'func' && node.value === 'url' && Object(node.nodes).length && (/^(?!data:)/).test(node.nodes[1].value);
}
// params from nodes
function paramsFromNodes(nodes) {
// valid params as an object
const params = {};
// for each node
nodes.forEach(node => {
// conditionally add the valid param
if (isFilledParam(node)) {
params[node.nodes[1].value] = String(node.nodes[2]).trim();
}
});
// return valid params as an object
return params;
}
// whether the node is a filled param()
function isFilledParam(node) {
return node.type === 'func' && node.value === 'param' && node.nodes.length === 4 && node.nodes[1].type === 'word';
}