-
-
Notifications
You must be signed in to change notification settings - Fork 29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
initial attempt poc at wasm #47
initial attempt poc at wasm #47
Conversation
Thank you very much for your contribution!
Yes, it is possible to control features of a dependency: Cargo.toml
I am actually OK with switching from |
#50 is merged, so some parts could be rolled back :) |
wasm/src/lib.rs
Outdated
load_remote_stylesheets: false, | ||
}; | ||
let inliner = rust_inline::CSSInliner::new(options); | ||
Ok(inliner.inline(html).unwrap()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To have a more explicit behavior on errors we can add a newtype wrapper for rust_inline::InlineError
and implement From
for JsValue
:
struct InlineErrorWrapper(rust_inline::InlineError);
impl From<InlineErrorWrapper> for JsValue {
fn from(error: InlineErrorWrapper) -> Self {
JsValue::from_str(error.0.to_string().as_str())
}
}
Then we can use ?
syntax instead of unwrap
:
Ok(inliner.inline(html).map_err(InlineErrorWrapper)?)
Lets say we have an invalid @
rule specified in a "style" tag (css-inline
produces an error in this case):
<html><head><style>h1 {background-color: blue;}</style></head><body><h1 style="@wrong { color: ---}">Hello world!</h1></body></html>
Then the error handling change will lead to the actual error message, that happened inside:
Uncaught (in promise) Invalid @ rule: wrong
Instead of the one below, which happens with unwrap
:
Uncaught (in promise) RuntimeError: unreachable
wasm/Cargo.toml
Outdated
name = "css_inline" | ||
crate-type = ["cdylib"] | ||
|
||
[build-dependencies] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the moment there is no usage of built
, so I propose to remove it. In the Python bindings, it is used to provide some meta-information about the Rust side (all dependencies, environment, etc), but here I am not sure if there are any standard ways to provide such info (I'll be happy to apply them if there are)
wasm/src/lib.rs
Outdated
) -> Result<String, JsValue> { | ||
let options = rust_inline::InlineOptions { | ||
remove_style_tags: remove_style_tags.unwrap_or(false), | ||
base_url: None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to provide the ability to pass all config values to InlineOptions
, not sure what is the canonical way to do so in JS - is it an extra "config" object like it is done in juice
? Or is it better to have multiple arguments?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will do :)
a config object would be the preferred way to go as that's the only way to provide named arguments in js
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
although how would something like that be written? :p
I tried
#[wasm_bindgen]
pub fn inline(
html: &str,
options: rust_inline::InlineOptions,
) -> Result<String, JsValue> {
but understandably got
`the trait `wasm_bindgen::convert::traits::FromWasmAbi` is not implemented for `css_inline::InlineOptions<'_>`
I guess it doesn't know how to convert a struct
to a javascript object?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool! I'll post an update regarding the struct conversion soon :) Need to do some more experiments, but the general idea could be taken from here + another newtype wrapper to avoid unwrap
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To use an object for "options" argument we need to:
- Use
JsValue
as the argument type
#[wasm_bindgen]
pub fn inline(html: &str, options: JsValue) -> Result<String, JsValue> {
- Convert this
JsValue
torust_inline::InlineOptions<'_>
I think that the easiest way is to add an intermediateOptions
struct, and parseJsValue
into it withserde
:
#[macro_use]
extern crate serde_derive;
#[derive(Deserialize)]
#[serde(default)]
pub struct Options {
inline_style_tags: bool,
remove_style_tags: bool,
base_url: Option<String>,
load_remote_stylesheets: bool,
extra_css: Option<String>,
}
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,
}
}
}
Then to avoid unwrap
add another newtype wrapper and From
implementation for JsValue
:
struct SerdeError(serde_json::Error);
impl From<SerdeError> for JsValue {
fn from(error: SerdeError) -> Self {
JsValue::from_str(error.0.to_string().as_str())
}
}
So we can parse simple JSON values like this:
#[wasm_bindgen]
pub fn inline(html: &str, options: JsValue) -> Result<String, JsValue> {
let options: Options = options.into_serde().map_err(SerdeError)?;
// ...
}
- Convert this intermediate struct to
InlineOptions
The conversion is fallible, so we need to implementTryFrom
:
impl TryFrom<Options> for rust_inline::InlineOptions<'_> {
type Error = JsValue;
fn try_from(value: Options) -> Result<Self, Self::Error> {
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),
})
}
}
(I used parse_url
and UrlError
+ From
implementation you added)
And the final version might look like this:
#[wasm_bindgen]
pub fn inline(html: &str, options: JsValue) -> Result<String, JsValue> {
let options: Options = options.into_serde().map_err(SerdeError)?;
let inliner = rust_inline::CSSInliner::new(options.try_into()?);
Ok(inliner.inline(html).map_err(InlineErrorWrapper)?)
}
The downside is having some code duplication between the original crate options and Options
, but I think it is acceptable for now since there is an extra logical step involved (those values are 1:1 mapped from JS and then URL parsing / Cow
wrapping is applied). So, I think we can start like this and then maybe add some refactorings :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that works! tested locally as well :)
there seems to be a bit of tradeoff here that the typescript bindings that end up being generated this way end up with options
as any
;
/ **
* @param {string} html
* @param {any} options
* @returns {string}
*/
export function inline(html: string, options: any): string;
it looks like a limitation that's being discussed/explored? rustwasm/wasm-bindgen#1591
Might need to go into the definition file to manually fiddle with the generated typescript definitions before publishing until wasm-bindgen figures this out
#[wasm_bindgen]
#[derive(Deserialize)]
#[serde(default)]
pub struct Options {
pub inline_style_tags: bool,
pub remove_style_tags: bool,
pub base_url: Option<String>,
pub load_remote_stylesheets: bool,
pub extra_css: Option<String>,
}
but that fails with "the trait std::marker::Copy
is not implemented for std::string::String
"
Removing pub
on the strings makes it sort of work
#[wasm_bindgen]
#[derive(Deserialize)]
#[serde(default)]
pub struct Options {
pub inline_style_tags: bool,
pub remove_style_tags: bool,
base_url: Option<String>,
pub load_remote_stylesheets: bool,
extra_css: Option<String>,
}
this generates
export class Options {
free(): void;
/**
* @returns {boolean}
*/
inline_style_tags: boolean;
/**
* @returns {boolean}
*/
load_remote_stylesheets: boolean;
/**
* @returns {boolean}
*/
remove_style_tags: boolean;
}
it is still weird that a class is generated rather than an interface/dictionary; and weird that there's a free
method
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, I need to do some research on this matter :) Would it work if we'll write proper TS types manually?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should, although I'm not sure how to tell rust where to look for the manually written TS types?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably we can use some sections from here. For example skip TS generation for inline
:
#[wasm_bindgen(skip_typescript)]
pub fn inline(html: &str, options: &JsValue) -> Result<String, JsValue> {
// ...
}
And define it manually:
#[wasm_bindgen(typescript_custom_section)]
const INLINE: &'static str = r#"
... some TS definitions
"#;
although I'm not sure how to tell rust where to look for the manually written TS types?
Hope that it sheds some light on this if not - please, let me know if I missed something :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cool!
One more thing - the "Verify commit message" check verifies the commit message conformance with the conventional commits specification. In this case, the commit message should be prefixed with |
Let me know if I can provide some more information or support here :) |
ece36c3
to
d826571
Compare
Signed-off-by: Chang Wang <garnwraly@gmail.com>
e69cf77
to
24de062
Compare
Signed-off-by: Chang Wang <garnwraly@gmail.com>
24de062
to
6b99172
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great for me! I added a couple of small suggestions
Co-authored-by: Dmitry Dygalo <Stranger6667@users.noreply.github.com>
0b1cfc6
to
7badd7c
Compare
Signed-off-by: Chang Wang <garnwraly@gmail.com>
Signed-off-by: Chang Wang <garnwraly@gmail.com>
Signed-off-by: Chang Wang <garnwraly@gmail.com>
7badd7c
to
f19f92f
Compare
Signed-off-by: Chang Wang <garnwraly@gmail.com>
c2adb54
to
8ed2cfb
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great! Thanks again for bringing this. I will create some separate issues as a follow-up (tests, CI, release, etc)
Thanks so much for the patient instructions! |
apologies, first time reading/writing rust! XD
I referenced mdn's article to get this sort of working
probably wouldn't want to merge this though since I ripped out the feature to load external stylesheets
wasm-pack build
would fail when trying to build openssl-sys thatattohttpc
brings inIs there a way in rust to conditionally exclude a dependency from the main package?