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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
use cssparser::CowRcStr;
use lightningcss::{
error::{Error, MinifyErrorKind, ParserError, PrinterErrorKind},
printer::Printer,
properties::custom::TokenOrValue,
rules::CssRule,
selector::{Component, PseudoClass, Selector},
stylesheet::{MinifyOptions, ParserOptions, PrinterOptions, StyleSheet, ToCssResult},
traits::ParseWithOptions,
};
use parcel_selectors::parser::Combinator;
#[derive(Default)]
pub struct TransformOptions<'a> {
pub parse: ParserOptions<'a, 'a>,
pub minify: Option<MinifyOptions>,
pub to_css: PrinterOptions<'a>,
}
#[derive(Debug)]
pub enum TransformError<'i> {
ParserError(Error<ParserError<'i>>),
MinifyError(Error<MinifyErrorKind>),
PrinterError(Error<PrinterErrorKind>),
}
pub struct Transformer<'i> {
input: &'i str,
scope: &'i str,
cache: Vec<String>,
}
impl<'i> Transformer<'i> {
/// Creates a new <u>single-use</u> transformer tied to `input`.
///
/// `scope` is the prefix which should be applied, e.g. `data-v-abcd1234`
pub fn new(input: &'i str, scope: &'i str) -> Self {
Self {
input,
scope,
cache: vec![],
}
}
/// Transforms stylesheets using `lightningcss`.
/// After calling the method, transformer instance becomes unusable and any subsequent calls
/// will trigger a panic.
///
/// ### Why is transformer not consumed?
/// Because `lightningcss` aims to be a borrow parser,
/// dynamically rewriting `:deep()` rules is a tricky problem due to Rust's lifetimes.
/// That is why we need to persuade the compiler that all the strings we create will be valid
/// for at least the same lifetime as the input stylesheet, and this is what `cache` is for.
///
/// An additional benefit of using `cache` is that we can reliably return warnings and errors
/// produced by `lightningcss`, because they must have the same lifetime
/// as input (and as transformer).
pub fn transform_style_scoped(
&'i mut self,
options: TransformOptions<'i>,
) -> Result<ToCssResult, TransformError<'i>> {
// Check if Transformer was consumed already
// Because we always need to tie output and `self.cache` to `self.input`,
// sadly we cannot just consume Transformer and have to use a reference
if self.cache.len() > 0 {
panic!("Transformer was already consumed")
}
let TransformOptions {
parse,
minify,
to_css,
} = options;
let mut stylesheet = StyleSheet::parse(self.input, parse)?;
let suffix = self.scope.into();
transform_cached_strategy(&mut stylesheet, &suffix, &mut self.cache);
if let Some(minify_options) = minify {
stylesheet.minify(minify_options)?;
}
Ok(stylesheet.to_css(to_css)?)
}
}
/// We cannot use `Visitor` from `lightningcss`, because it does not ensure
/// that `Visitor` has at least the same lifetime as its input.
/// And we need this guarantee to satisfy Rust's lifetime checks,
/// simply because we are creating new `String`s to parse and attach
/// the contents of `:deep()`.
///
/// Another method could have been using a static set,
/// but this sounds like even more effort, and potentially dangerous in WASM.
fn transform_cached_strategy<'i>(
stylesheet: &mut StyleSheet<'i, '_>,
suffix: &CowRcStr<'i>,
cache: &'i mut Vec<String>,
) {
// Collect phase, because we cannot write to cache and reference it at the same time
// Both phases must be identical in items they visit, otherwise `cache` will get out-of-bounds
for rule in stylesheet.rules.0.iter_mut() {
let CssRule::Style(style) = rule else {
continue;
};
for selector in style.selectors.0.iter_mut() {
let mut iter = selector.iter_raw_match_order();
while let Some(part) = iter.next() {
let Component::NonTSPseudoClass(PseudoClass::CustomFunction { name, arguments }) = part else {
continue;
};
if *name != "deep" {
continue;
}
let mut deep_css = String::new();
let mut printer = Printer::new(&mut deep_css, Default::default());
for arg in arguments.0.iter() {
let TokenOrValue::Token(token) = arg else { continue; };
lightningcss::traits::ToCss::to_css(token, &mut printer).unwrap();
}
// Cache MUST be pushed to inside this loop,
// otherwise referencing it later will panic
cache.push(deep_css);
break;
}
}
}
let mut ptr: usize = 0;
macro_rules! to_append {
() => {
Component::AttributeInNoNamespaceExists {
local_name: suffix.clone().into(),
local_name_lower: suffix.clone().into(),
}
};
}
macro_rules! is_combinator {
($what: ident) => {
matches!(
$what,
Component::Combinator(
Combinator::Child
| Combinator::Descendant
| Combinator::LaterSibling
| Combinator::NextSibling
)
)
};
}
// Write phase, cached contents of `:deep`s will be parsed as selectors
for rule in stylesheet.rules.0.iter_mut() {
let CssRule::Style(style) = rule else {
continue;
};
for selector in style.selectors.0.iter_mut() {
let selector_len = selector.len();
let iter = &mut selector.iter_mut_raw_match_order().enumerate();
let mut components: Vec<Component> = Vec::new();
let mut needs_descendant: Option<usize> = None;
let mut deep_without_selector = false;
let mut sequence_start = 0;
let mut previous_combinator = None;
while let Some((idx, part)) = iter.next() {
// Find the start of the sequence
if is_combinator!(part) {
previous_combinator = Some(idx);
} else if let Some(_) = previous_combinator {
sequence_start = idx;
previous_combinator = None;
}
let Component::NonTSPseudoClass(PseudoClass::CustomFunction { name, .. }) = part else {
continue;
};
if *name != "deep" {
continue;
}
// The algorithm:
// 1. Replace `:deep` with `[data-v-smth]`.
// 2. Check if `:deep` was inside a bigger sequence or on its own:
// a) Bigger sequence (`.foo:deep`) - add Combinator::Descendant
// at the sequence start. The way lightningcss works
// is by using sequences like that (numbers are indices in the vec):
// 3 4 5 2 0 1
// `.foo.bar.baz .qux:deep(#bar baz)`
//
// The sequence start is at index `0`, so after adding a Descendant,
// we will end up with
// 4 5 6 3 1 2 0
// `.foo.bar.baz .qux:deep(#bar baz) `
//
// Now we parse the inner contents of `:deep` like that
// 2 1 0
// `#bar baz`
//
// And at step 3, we will append just like that
// 7 8 9 6 4 5 3 2 1 0
// `.foo.bar.baz .qux:deep(#bar baz) #bar baz`
//
// And, remember, we replaced `:deep` with `[data-v-smth]` at step 1
// 7 8 9 6 4 5 3 2 1 0
// `.foo.bar.baz .qux[data-v-smth] #bar baz`
//
// b) On its own (`.foo :deep`). Now this is a tricky part,
// because we need to move `:deep` to the end of the next sequence.
// For doing so, we will be swapping it with the next element till we find
// a combinator. Then, we will swap till the end of the sequence.
//
// Take this example. I intentionally put `.qux` at the end
// to make sure we accept all kind of weird input (and because it
// allows `::v-deep` support):
// 4 5 6 3 2 1 0
// `.foo.bar.baz :deep(#bar baz) .qux`
//
// When we first swap `:deep` with Combinator::Descendant,
// interpretation of the Vec becomes questionable:
// 3 4 5 6 2 1 0
// `:deep(#bar baz).foo.bar.baz .qux`
//
// We continue swapping till we find either end of vec or end of sequence:
// 3 4 5 6 2 1 0
// `.foo.bar.baz:deep(#bar baz) .qux`
//
// Now, at step 3, we will be inserting contents of `:deep`
// at initial `sequence_start`,
// i.e. index of `:deep` in the beginning (in this case 2).
// The Combinator which now occupies index `sequence_start` will
// get moved because of `insert`, and remember about step 1:
//
// 6 7 8 9 5 4 3 2 1 0
// `.foo.bar.baz[data-v-smth] #bar baz .qux`
//
// c) Special case (just `:deep(...)`).
// Technically, this is similar to case b),
// but since we cannot move it anywhere, we just will insert a Descendant
// 0
// `:deep(#bar baz)`
//
// 3. Insert the parsed contents of `:deep` into the vec at `sequence_start`.
// Point 2 explains why it works.
// Get the contents of `:deep` from cache
let deep_contents = &cache[ptr];
ptr += 1;
// Step 1. Replace the contents
*part = to_append!();
// Check for special case - `:deep()` without selectors inside
if deep_contents.len() == 0 {
deep_without_selector = true;
} else {
// Parse contents of `:deep` as a Selector
let Ok(parsed) = Selector::parse_string_with_options(
deep_contents,
Default::default()
) else {
// return Err(VisitorError::ParsingDeepFailed);
continue;
};
// Just because `Selector.1` is a private field,
// we have to clone and use its public API to insert components
for part in parsed.iter_raw_match_order().cloned() {
components.push(part);
}
}
// Step 2. Determine the position of `:deep` inside the sequence
// Is `:deep` its own sequence (`.foo :deep`) or not (`.foo.bar:deep`)?
let is_deep_its_own_sequence = sequence_start == idx;
// b) Part of other sequence is the simplest, just signify that Descendant is needed
if !is_deep_its_own_sequence || selector_len == 1 {
// c) This is also true for cases where `:deep(#bar)` is the only component
if !is_deep_its_own_sequence || components.len() != 0 {
needs_descendant = Some(sequence_start)
}
break;
}
// a) `:deep` is its own sequence, we need to swap-move it
// `.foo.bar :deep` -> `.foo.bar[data-v-smth] `
// `.foo.bar > :deep` -> `.foo.bar[data-v-smth] >`
// `.foo.bar + :deep` -> `.foo.bar[data-v-smth] +`
// `.foo.bar ~ :deep` -> `.foo.bar[data-v-smth] ~`
let mut encountered_combinator = false;
let mut deep = &mut *part;
while let Some((_, next_part)) = iter.next() {
if is_combinator!(next_part) {
if !encountered_combinator {
encountered_combinator = true
} else {
break;
}
}
std::mem::swap(deep, next_part);
deep = next_part;
}
break;
}
// Deep without selector already did its job
if deep_without_selector {
continue;
}
// Because current SFC compiler treats `.foo:deep` the same as `.foo :deep`
if let Some(idx) = needs_descendant {
selector.insert_raw(idx, Component::Combinator(Combinator::Descendant));
}
// When we have components, we have parsed `:deep`,
// otherwise we have not and will just append to the right-most selector
if components.len() > 0 {
selector.insert_raw_multiple(sequence_start, components);
} else {
// this needs testing
selector.append(to_append!())
}
}
}
}
impl<'i> From<Error<ParserError<'i>>> for TransformError<'i> {
fn from(value: Error<ParserError<'i>>) -> Self {
TransformError::ParserError(value)
}
}
impl From<Error<MinifyErrorKind>> for TransformError<'_> {
fn from(value: Error<MinifyErrorKind>) -> Self {
TransformError::MinifyError(value)
}
}
impl From<Error<PrinterErrorKind>> for TransformError<'_> {
fn from(value: Error<PrinterErrorKind>) -> Self {
TransformError::PrinterError(value)
}
}