source: trip-planner-front/node_modules/svgo/plugins/inlineStyles.js@ 6c1585f

Last change on this file since 6c1585f was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 8.4 KB
Line 
1'use strict';
2
3const csstree = require('css-tree');
4const { querySelectorAll, closestByName } = require('../lib/xast.js');
5const cssTools = require('../lib/css-tools');
6
7exports.name = 'inlineStyles';
8
9exports.type = 'full';
10
11exports.active = true;
12
13exports.params = {
14 onlyMatchedOnce: true,
15 removeMatchedSelectors: true,
16 useMqs: ['', 'screen'],
17 usePseudos: [''],
18};
19
20exports.description = 'inline styles (additional options)';
21
22/**
23 * Moves + merges styles from style elements to element styles
24 *
25 * Options
26 * onlyMatchedOnce (default: true)
27 * inline only selectors that match once
28 *
29 * removeMatchedSelectors (default: true)
30 * clean up matched selectors,
31 * leave selectors that hadn't matched
32 *
33 * useMqs (default: ['', 'screen'])
34 * what media queries to be used
35 * empty string element for styles outside media queries
36 *
37 * usePseudos (default: [''])
38 * what pseudo-classes/-elements to be used
39 * empty string element for all non-pseudo-classes and/or -elements
40 *
41 * @param {Object} root document element
42 * @param {Object} opts plugin params
43 *
44 * @author strarsis <strarsis@gmail.com>
45 */
46exports.fn = function (root, opts) {
47 // collect <style/>s
48 var styleEls = querySelectorAll(root, 'style');
49
50 //no <styles/>s, nothing to do
51 if (styleEls.length === 0) {
52 return root;
53 }
54
55 var styles = [],
56 selectors = [];
57
58 for (var styleEl of styleEls) {
59 // values other than the empty string or text/css are not used
60 if (
61 styleEl.attributes.type != null &&
62 styleEl.attributes.type !== '' &&
63 styleEl.attributes.type !== 'text/css'
64 ) {
65 continue;
66 }
67 // skip empty <style/>s or <foreignObject> content.
68 if (
69 styleEl.children.length === 0 ||
70 closestByName(styleEl, 'foreignObject')
71 ) {
72 continue;
73 }
74
75 var cssStr = cssTools.getCssStr(styleEl);
76
77 // collect <style/>s and their css ast
78 var cssAst = {};
79 try {
80 cssAst = csstree.parse(cssStr, {
81 parseValue: false,
82 parseCustomProperty: false,
83 });
84 } catch (parseError) {
85 // console.warn('Warning: Parse error of styles of <style/> element, skipped. Error details: ' + parseError);
86 continue;
87 }
88
89 styles.push({
90 styleEl: styleEl,
91 cssAst: cssAst,
92 });
93
94 selectors = selectors.concat(cssTools.flattenToSelectors(cssAst));
95 }
96
97 // filter for mediaqueries to be used or without any mediaquery
98 var selectorsMq = cssTools.filterByMqs(selectors, opts.useMqs);
99
100 // filter for pseudo elements to be used
101 var selectorsPseudo = cssTools.filterByPseudos(selectorsMq, opts.usePseudos);
102
103 // remove PseudoClass from its SimpleSelector for proper matching
104 cssTools.cleanPseudos(selectorsPseudo);
105
106 // stable sort selectors
107 var sortedSelectors = cssTools.sortSelectors(selectorsPseudo).reverse();
108
109 var selector, selectedEl;
110
111 // match selectors
112 for (selector of sortedSelectors) {
113 var selectorStr = csstree.generate(selector.item.data),
114 selectedEls = null;
115
116 try {
117 selectedEls = querySelectorAll(root, selectorStr);
118 } catch (selectError) {
119 // console.warn('Warning: Syntax error when trying to select \n\n' + selectorStr + '\n\n, skipped. Error details: ' + selectError);
120 continue;
121 }
122
123 if (selectedEls.length === 0) {
124 // nothing selected
125 continue;
126 }
127
128 selector.selectedEls = selectedEls;
129 }
130
131 // apply <style/> styles to matched elements
132 for (selector of sortedSelectors) {
133 if (!selector.selectedEls) {
134 continue;
135 }
136
137 if (
138 opts.onlyMatchedOnce &&
139 selector.selectedEls !== null &&
140 selector.selectedEls.length > 1
141 ) {
142 // skip selectors that match more than once if option onlyMatchedOnce is enabled
143 continue;
144 }
145
146 // apply <style/> to matched elements
147 for (selectedEl of selector.selectedEls) {
148 if (selector.rule === null) {
149 continue;
150 }
151 const styleDeclarationList = csstree.parse(
152 selectedEl.attributes.style == null ? '' : selectedEl.attributes.style,
153 {
154 context: 'declarationList',
155 parseValue: false,
156 }
157 );
158 const styleDeclarationItems = new Map();
159 csstree.walk(styleDeclarationList, {
160 visit: 'Declaration',
161 enter(node, item) {
162 styleDeclarationItems.set(node.property, item);
163 },
164 });
165 // merge declarations
166 csstree.walk(selector.rule, {
167 visit: 'Declaration',
168 enter(ruleDeclaration) {
169 // existing inline styles have higher priority
170 // no inline styles, external styles, external styles used
171 // inline styles, external styles same priority as inline styles, inline styles used
172 // inline styles, external styles higher priority than inline styles, external styles used
173 const matchedItem = styleDeclarationItems.get(
174 ruleDeclaration.property
175 );
176 const ruleDeclarationItem =
177 styleDeclarationList.children.createItem(ruleDeclaration);
178 if (matchedItem == null) {
179 styleDeclarationList.children.append(ruleDeclarationItem);
180 } else if (
181 matchedItem.data.important !== true &&
182 ruleDeclaration.important === true
183 ) {
184 styleDeclarationList.children.replace(
185 matchedItem,
186 ruleDeclarationItem
187 );
188 styleDeclarationItems.set(
189 ruleDeclaration.property,
190 ruleDeclarationItem
191 );
192 }
193 },
194 });
195 selectedEl.attributes.style = csstree.generate(styleDeclarationList);
196 }
197
198 if (
199 opts.removeMatchedSelectors &&
200 selector.selectedEls !== null &&
201 selector.selectedEls.length > 0
202 ) {
203 // clean up matching simple selectors if option removeMatchedSelectors is enabled
204 selector.rule.prelude.children.remove(selector.item);
205 }
206 }
207
208 if (!opts.removeMatchedSelectors) {
209 return root; // no further processing required
210 }
211
212 // clean up matched class + ID attribute values
213 for (selector of sortedSelectors) {
214 if (!selector.selectedEls) {
215 continue;
216 }
217
218 if (
219 opts.onlyMatchedOnce &&
220 selector.selectedEls !== null &&
221 selector.selectedEls.length > 1
222 ) {
223 // skip selectors that match more than once if option onlyMatchedOnce is enabled
224 continue;
225 }
226
227 for (selectedEl of selector.selectedEls) {
228 // class
229 const classList = new Set(
230 selectedEl.attributes.class == null
231 ? null
232 : selectedEl.attributes.class.split(' ')
233 );
234 const firstSubSelector = selector.item.data.children.first();
235 if (firstSubSelector.type === 'ClassSelector') {
236 classList.delete(firstSubSelector.name);
237 }
238 if (classList.size === 0) {
239 delete selectedEl.attributes.class;
240 } else {
241 selectedEl.attributes.class = Array.from(classList).join(' ');
242 }
243
244 // ID
245 if (firstSubSelector.type === 'IdSelector') {
246 if (selectedEl.attributes.id === firstSubSelector.name) {
247 delete selectedEl.attributes.id;
248 }
249 }
250 }
251 }
252
253 // clean up now empty elements
254 for (var style of styles) {
255 csstree.walk(style.cssAst, {
256 visit: 'Rule',
257 enter: function (node, item, list) {
258 // clean up <style/> atrules without any rulesets left
259 if (
260 node.type === 'Atrule' &&
261 // only Atrules containing rulesets
262 node.block !== null &&
263 node.block.children.isEmpty()
264 ) {
265 list.remove(item);
266 return;
267 }
268
269 // clean up <style/> rulesets without any css selectors left
270 if (node.type === 'Rule' && node.prelude.children.isEmpty()) {
271 list.remove(item);
272 }
273 },
274 });
275
276 if (style.cssAst.children.isEmpty()) {
277 // clean up now emtpy <style/>s
278 var styleParentEl = style.styleEl.parentNode;
279 styleParentEl.spliceContent(
280 styleParentEl.children.indexOf(style.styleEl),
281 1
282 );
283
284 if (
285 styleParentEl.name === 'defs' &&
286 styleParentEl.children.length === 0
287 ) {
288 // also clean up now empty <def/>s
289 var defsParentEl = styleParentEl.parentNode;
290 defsParentEl.spliceContent(
291 defsParentEl.children.indexOf(styleParentEl),
292 1
293 );
294 }
295
296 continue;
297 }
298
299 // update existing, left over <style>s
300 cssTools.setCssStr(style.styleEl, csstree.generate(style.cssAst));
301 }
302
303 return root;
304};
Note: See TracBrowser for help on using the repository browser.