diff --git a/wasm/.gitignore b/wasm/.gitignore new file mode 100644 index 00000000..2ee0272e --- /dev/null +++ b/wasm/.gitignore @@ -0,0 +1,2 @@ +# Rust +/target diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml new file mode 100644 index 00000000..6a39fc6c --- /dev/null +++ b/wasm/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "css-inline-wasm" +version = "0.3.0" +authors = ["Dmitry Dygalo "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +name = "css_inline" +crate-type = ["cdylib"] + +[dependencies.css-inline] +path = ".." +version = "= 0.3.3" +default-features = false + +[dependencies] +url = "2" +wasm-bindgen = { version = "0.2", features = ["serde-serialize"] } +serde = { version = "1.0", features = ["derive"] } +serde_derive = "1" +serde_json = "1" + +[profile.release] +codegen-units = 1 +lto = "fat" diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs new file mode 100644 index 00000000..fb42e7f3 --- /dev/null +++ b/wasm/src/lib.rs @@ -0,0 +1,104 @@ +use css_inline as rust_inline; +use wasm_bindgen::prelude::*; +use std::{ + borrow::Cow, + convert::{TryFrom, TryInto}, +}; + +struct InlineErrorWrapper(rust_inline::InlineError); + +impl From for JsValue { + fn from(error: InlineErrorWrapper) -> Self { + JsValue::from_str(error.0.to_string().as_str()) + } +} + +struct UrlError(url::ParseError); + +impl From for JsValue { + fn from(error: UrlError) -> Self { + JsValue::from_str(&error.0.to_string().as_str()) + } +} + +fn parse_url(url: Option) -> Result, JsValue> { + Ok(if let Some(url) = url { + Some(url::Url::parse(url.as_str()).map_err(UrlError)?) + } else { + None + }) +} + +#[macro_use] +extern crate serde_derive; + +#[derive(Deserialize)] +#[serde(default)] +struct Options { + inline_style_tags: bool, + remove_style_tags: bool, + base_url: Option, + load_remote_stylesheets: bool, + extra_css: Option, +} + +impl Default for Options { + fn default() -> Self { + Options { + inline_style_tags: true, + remove_style_tags: false, + base_url: None, + load_remote_stylesheets: true, + extra_css: None, + } + } +} + +struct SerdeError(serde_json::Error); + +impl From for JsValue { + fn from(error: SerdeError) -> Self { + JsValue::from_str(error.0.to_string().as_str()) + } +} + +impl TryFrom for rust_inline::InlineOptions<'_> { + type Error = JsValue; + + fn try_from(value: Options) -> Result { + Ok(rust_inline::InlineOptions { + inline_style_tags: value.inline_style_tags, + remove_style_tags: value.remove_style_tags, + base_url: parse_url(value.base_url)?, + load_remote_stylesheets: value.load_remote_stylesheets, + extra_css: value.extra_css.map(Cow::Owned), + }) + } +} + +#[wasm_bindgen(skip_typescript)] +pub fn inline( + html: &str, + options: &JsValue, +) -> Result { + let options: Options = if !options.is_undefined() { + options.into_serde().map_err(SerdeError)? + } else { + Options::default() + }; + let inliner = rust_inline::CSSInliner::new(options.try_into()?); + Ok(inliner.inline(html).map_err(InlineErrorWrapper)?) +} + +#[wasm_bindgen(typescript_custom_section)] +const INLINE: &'static str = r#" +interface InlineOptions { + inline_style_tags?: boolean, + remove_style_tags?: boolean, + base_url?: string, + load_remote_stylesheets?: boolean, + extra_css?: string, +} + +export function inline(html: string, options?: InlineOptions): string; +"#;