Skip to content

Commit 726f42c

Browse files
committed
Add an API to transform an inline style attribute
1 parent 6126068 commit 726f42c

File tree

7 files changed

+196
-99
lines changed

7 files changed

+196
-99
lines changed

node/index.d.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,41 @@
11
import type {Targets} from './targets';
22

33
export interface TransformOptions {
4+
/** The filename being transformed. Used for error messages and source maps. */
45
filename: string,
6+
/** The source code to transform. */
57
code: Buffer,
6-
minify: boolean,
7-
source_map: boolean,
8-
targets: Targets
8+
/** Whether to enable minification. */
9+
minify?: boolean,
10+
/** Whether to output a source map. */
11+
source_map?: boolean,
12+
/** The browser targets for the generated code. */
13+
targets?: Targets
914
}
1015

1116
export interface TransformResult {
17+
/** The transformed code. */
1218
code: Buffer,
13-
map: Buffer
19+
/** The generated source map, if enabled. */
20+
map: Buffer | void
1421
}
1522

23+
/**
24+
* Compiles a CSS file, including optionally minifying and lowering syntax to the given
25+
* targets. A source map may also be generated, but this is not enabled by default.
26+
*/
1627
export declare function transform(options: TransformOptions): TransformResult;
28+
29+
export interface TransformAttributeOptions {
30+
/** The source code to transform. */
31+
code: Buffer,
32+
/** Whether to enable minification. */
33+
minify?: boolean,
34+
/** The browser targets for the generated code. */
35+
targets?: Targets
36+
}
37+
38+
/**
39+
* Compiles a single CSS declaration list, such as an inline style attribute in HTML.
40+
*/
41+
export declare function transformStyleAttribute(options: TransformAttributeOptions): Buffer;

node/src/lib.rs

Lines changed: 69 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
static GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc;
44

55
use serde::{Serialize, Deserialize};
6-
use parcel_css::stylesheet::StyleSheet;
6+
use parcel_css::stylesheet::{StyleSheet, StyleAttribute};
77
use parcel_css::targets::Browsers;
88

99
// ---------------------------------------------
@@ -23,6 +23,15 @@ pub fn transform(config_val: JsValue) -> Result<JsValue, JsValue> {
2323
res.serialize(&serializer).map_err(JsValue::from)
2424
}
2525

26+
#[cfg(target_arch = "wasm32")]
27+
#[wasm_bindgen(js_name = "transformStyleAttribute")]
28+
pub fn transform_style_attribute(config_val: JsValue) -> Result<Vec<u8>, JsValue> {
29+
let config: AttrConfig = from_value(config_val).map_err(JsValue::from)?;
30+
let code = unsafe { std::str::from_utf8_unchecked(&config.code) };
31+
let res = compile_attr(code, &config)?;
32+
Ok(res)
33+
}
34+
2635
// ---------------------------------------------
2736

2837
#[cfg(not(target_arch = "wasm32"))]
@@ -58,37 +67,29 @@ fn transform(ctx: CallContext) -> napi::Result<JsUnknown> {
5867

5968
match res {
6069
Ok(res) => ctx.env.to_js_value(&res),
61-
Err(err) => {
62-
match &err {
63-
CompileError::ParseError(e) => {
64-
// Generate an error with location information.
65-
let syntax_error = ctx.env.get_global()?
66-
.get_named_property::<napi::JsFunction>("SyntaxError")?;
67-
let reason = ctx.env.create_string_from_std(err.reason())?;
68-
let line = ctx.env.create_int32((e.location.line + 1) as i32)?;
69-
let col = ctx.env.create_int32(e.location.column as i32)?;
70-
let mut obj = syntax_error.new(&[reason])?;
71-
let filename = ctx.env.create_string_from_std(config.filename)?;
72-
obj.set_named_property("fileName", filename)?;
73-
let source = ctx.env.create_string(code)?;
74-
obj.set_named_property("source", source)?;
75-
let mut loc = ctx.env.create_object()?;
76-
loc.set_named_property("line", line)?;
77-
loc.set_named_property("column", col)?;
78-
obj.set_named_property("loc", loc)?;
79-
ctx.env.throw(obj)?;
80-
Ok(ctx.env.get_undefined()?.into_unknown())
81-
}
82-
_ => Err(err.into())
83-
}
84-
}
70+
Err(err) => err.throw(ctx, Some(config.filename), code)
71+
}
72+
}
73+
74+
#[cfg(not(target_arch = "wasm32"))]
75+
#[js_function(1)]
76+
fn transform_style_attribute(ctx: CallContext) -> napi::Result<JsUnknown> {
77+
let opts = ctx.get::<JsObject>(0)?;
78+
let config: AttrConfig = ctx.env.from_js_value(opts)?;
79+
let code = unsafe { std::str::from_utf8_unchecked(&config.code) };
80+
let res = compile_attr(code, &config);
81+
82+
match res {
83+
Ok(res) => Ok(ctx.env.create_buffer_with_data(res)?.into_unknown()),
84+
Err(err) => err.throw(ctx, None, code)
8585
}
8686
}
8787

8888
#[cfg(not(target_arch = "wasm32"))]
8989
#[module_exports]
9090
fn init(mut exports: JsObject) -> napi::Result<()> {
9191
exports.create_named_method("transform", transform)?;
92+
exports.create_named_method("transformStyleAttribute", transform_style_attribute)?;
9293

9394
Ok(())
9495
}
@@ -138,6 +139,21 @@ fn compile<'i>(code: &'i str, config: &Config) -> Result<TransformResult, Compil
138139
})
139140
}
140141

142+
#[derive(Serialize, Debug, Deserialize)]
143+
struct AttrConfig {
144+
#[serde(with = "serde_bytes")]
145+
pub code: Vec<u8>,
146+
pub targets: Option<Browsers>,
147+
pub minify: Option<bool>
148+
}
149+
150+
fn compile_attr<'i>(code: &'i str, config: &AttrConfig) -> Result<Vec<u8>, CompileError<'i>> {
151+
let mut attr = StyleAttribute::parse(&code)?;
152+
attr.minify(config.targets); // TODO: should this be conditional?
153+
let res = attr.to_css(config.minify.unwrap_or(false), config.targets)?;
154+
Ok(res.into_bytes())
155+
}
156+
141157
enum CompileError<'i> {
142158
ParseError(cssparser::ParseError<'i, ()>),
143159
PrinterError,
@@ -166,6 +182,34 @@ impl<'i> CompileError<'i> {
166182
_ => "Unknown error".into()
167183
}
168184
}
185+
186+
#[cfg(not(target_arch = "wasm32"))]
187+
fn throw(self, ctx: CallContext, filename: Option<String>, code: &str) -> napi::Result<JsUnknown> {
188+
match &self {
189+
CompileError::ParseError(e) => {
190+
// Generate an error with location information.
191+
let syntax_error = ctx.env.get_global()?
192+
.get_named_property::<napi::JsFunction>("SyntaxError")?;
193+
let reason = ctx.env.create_string_from_std(self.reason())?;
194+
let line = ctx.env.create_int32((e.location.line + 1) as i32)?;
195+
let col = ctx.env.create_int32(e.location.column as i32)?;
196+
let mut obj = syntax_error.new(&[reason])?;
197+
if let Some(filename) = filename {
198+
let filename = ctx.env.create_string_from_std(filename)?;
199+
obj.set_named_property("fileName", filename)?;
200+
}
201+
let source = ctx.env.create_string(code)?;
202+
obj.set_named_property("source", source)?;
203+
let mut loc = ctx.env.create_object()?;
204+
loc.set_named_property("line", line)?;
205+
loc.set_named_property("column", col)?;
206+
obj.set_named_property("loc", loc)?;
207+
ctx.env.throw(obj)?;
208+
Ok(ctx.env.get_undefined()?.into_unknown())
209+
}
210+
_ => Err(self.into())
211+
}
212+
}
169213
}
170214

171215
impl<'i> From<cssparser::ParseError<'i, ()>> for CompileError<'i> {

src/declaration.rs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use cssparser::*;
22
use crate::properties::Property;
3-
use crate::traits::{PropertyHandler, ToCss};
3+
use crate::traits::{PropertyHandler, Parse, ToCss};
44
use crate::printer::Printer;
55
use crate::properties::{
66
align::AlignHandler,
@@ -27,6 +27,22 @@ pub struct DeclarationBlock {
2727
pub declarations: Vec<Declaration>
2828
}
2929

30+
impl Parse for DeclarationBlock {
31+
fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ()>> {
32+
let mut parser = DeclarationListParser::new(input, PropertyDeclarationParser);
33+
let mut declarations = vec![];
34+
while let Some(decl) = parser.next() {
35+
if let Ok(decl) = decl {
36+
declarations.push(decl);
37+
}
38+
}
39+
40+
Ok(DeclarationBlock {
41+
declarations
42+
})
43+
}
44+
}
45+
3046
impl ToCss for DeclarationBlock {
3147
fn to_css<W>(&self, dest: &mut Printer<W>) -> std::fmt::Result where W: std::fmt::Write {
3248
dest.whitespace()?;
@@ -65,6 +81,30 @@ impl DeclarationBlock {
6581
}
6682
}
6783

84+
struct PropertyDeclarationParser;
85+
86+
/// Parse a declaration within {} block: `color: blue`
87+
impl<'i> cssparser::DeclarationParser<'i> for PropertyDeclarationParser {
88+
type Declaration = Declaration;
89+
type Error = ();
90+
91+
fn parse_value<'t>(
92+
&mut self,
93+
name: CowRcStr<'i>,
94+
input: &mut cssparser::Parser<'i, 't>,
95+
) -> Result<Self::Declaration, cssparser::ParseError<'i, Self::Error>> {
96+
Declaration::parse(name, input)
97+
}
98+
}
99+
100+
/// Default methods reject all at rules.
101+
impl<'i> AtRuleParser<'i> for PropertyDeclarationParser {
102+
type PreludeNoBlock = ();
103+
type PreludeBlock = ();
104+
type AtRule = Declaration;
105+
type Error = ();
106+
}
107+
68108
#[derive(Debug, Clone, PartialEq)]
69109
pub struct Declaration {
70110
pub property: Property,

src/lib.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ mod tests {
4141
assert_eq!(res, expected);
4242
}
4343

44+
fn attr_test(source: &str, expected: &str, minify: bool) {
45+
let mut attr = StyleAttribute::parse(source).unwrap();
46+
attr.minify(None);
47+
let res = attr.to_css(minify, None).unwrap();
48+
assert_eq!(res, expected);
49+
}
50+
4451
#[test]
4552
pub fn test_border() {
4653
test(r#"
@@ -6101,4 +6108,10 @@ mod tests {
61016108
}
61026109
"#})
61036110
}
6111+
6112+
#[test]
6113+
fn test_style_attr() {
6114+
attr_test("color: yellow; flex: 1 1 auto", "color: #ff0; flex: auto", false);
6115+
attr_test("color: yellow; flex: 1 1 auto", "color:#ff0;flex:auto", true);
6116+
}
61046117
}

src/parser.rs

Lines changed: 4 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use crate::rules::{
1818
document::MozDocumentRule
1919
};
2020
use crate::values::ident::CustomIdent;
21-
use crate::declaration::{Declaration, DeclarationBlock};
21+
use crate::declaration::DeclarationBlock;
2222
use crate::vendor_prefix::VendorPrefix;
2323

2424
/// The parser for the top-level rules in a stylesheet.
@@ -311,19 +311,9 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser {
311311
// ))))
312312
// },
313313
AtRulePrelude::CounterStyle(name) => {
314-
let mut parser = DeclarationListParser::new(input, PropertyDeclarationParser);
315-
let mut declarations = vec![];
316-
while let Some(decl) = parser.next() {
317-
if let Ok(decl) = decl {
318-
declarations.push(decl);
319-
}
320-
}
321-
322314
Ok(CssRule::CounterStyle(CounterStyleRule {
323315
name,
324-
declarations: DeclarationBlock {
325-
declarations
326-
},
316+
declarations: DeclarationBlock::parse(input)?,
327317
loc
328318
}))
329319
},
@@ -362,18 +352,9 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser {
362352
}))
363353
},
364354
AtRulePrelude::Page(selectors) => {
365-
let mut parser = DeclarationListParser::new(input, PropertyDeclarationParser);
366-
let mut declarations = vec![];
367-
while let Some(decl) = parser.next() {
368-
if let Ok(decl) = decl {
369-
declarations.push(decl);
370-
}
371-
}
372355
Ok(CssRule::Page(PageRule {
373356
selectors,
374-
declarations: DeclarationBlock {
375-
declarations
376-
},
357+
declarations: DeclarationBlock::parse(input)?,
377358
loc
378359
}))
379360
},
@@ -415,49 +396,15 @@ impl<'a, 'b, 'i> QualifiedRuleParser<'i> for NestedRuleParser {
415396
input: &mut Parser<'i, 't>,
416397
) -> Result<CssRule, ParseError<'i, Self::Error>> {
417398
let loc = start.source_location();
418-
let mut parser = DeclarationListParser::new(input, PropertyDeclarationParser);
419-
let mut declarations = vec![];
420-
while let Some(decl) = parser.next() {
421-
if let Ok(decl) = decl {
422-
declarations.push(decl);
423-
}
424-
}
425-
426399
Ok(CssRule::Style(StyleRule {
427400
selectors,
428401
vendor_prefix: VendorPrefix::empty(),
429-
declarations: DeclarationBlock {
430-
declarations
431-
},
402+
declarations: DeclarationBlock::parse(input)?,
432403
loc
433404
}))
434405
}
435406
}
436407

437-
pub struct PropertyDeclarationParser;
438-
439-
/// Parse a declaration within {} block: `color: blue`
440-
impl<'i> cssparser::DeclarationParser<'i> for PropertyDeclarationParser {
441-
type Declaration = Declaration;
442-
type Error = ();
443-
444-
fn parse_value<'t>(
445-
&mut self,
446-
name: CowRcStr<'i>,
447-
input: &mut cssparser::Parser<'i, 't>,
448-
) -> Result<Self::Declaration, cssparser::ParseError<'i, Self::Error>> {
449-
Declaration::parse(name, input)
450-
}
451-
}
452-
453-
/// Default methods reject all at rules.
454-
impl<'i> AtRuleParser<'i> for PropertyDeclarationParser {
455-
type PreludeNoBlock = ();
456-
type PreludeBlock = ();
457-
type AtRule = Declaration;
458-
type Error = ();
459-
}
460-
461408
fn starts_with_ignore_ascii_case(string: &str, prefix: &str) -> bool {
462409
string.len() >= prefix.len() && string.as_bytes()[0..prefix.len()].eq_ignore_ascii_case(prefix.as_bytes())
463410
}

src/rules/keyframes.rs

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use cssparser::*;
22
use crate::values::percentage::Percentage;
33
use crate::traits::{Parse, ToCss};
4-
use crate::parser::{PropertyDeclarationParser};
54
use crate::declaration::{DeclarationBlock, DeclarationHandler};
65
use crate::vendor_prefix::VendorPrefix;
76
use crate::printer::Printer;
@@ -166,18 +165,9 @@ impl<'a, 'i> QualifiedRuleParser<'i> for KeyframeListParser {
166165
_: &ParserState,
167166
input: &mut Parser<'i, 't>,
168167
) -> Result<Self::QualifiedRule, ParseError<'i, ()>> {
169-
let mut parser = DeclarationListParser::new(input, PropertyDeclarationParser);
170-
let mut declarations = vec![];
171-
while let Some(decl) = parser.next() {
172-
if let Ok(decl) = decl {
173-
declarations.push(decl);
174-
}
175-
}
176168
Ok(Keyframe {
177169
selectors,
178-
declarations: DeclarationBlock {
179-
declarations
180-
}
170+
declarations: DeclarationBlock::parse(input)?
181171
})
182172
}
183173
}

0 commit comments

Comments
 (0)