Cover Photo by Michael Fenton on Unsplash
Well, not really. While we can’t write Rust cartridges yet (😭), we can write a Rust-based WebAssembly component and load it in our SFCC instance (💡). You might be thinking: johnny, by definition a WebAssembly cannot directly access the DOM (https://developer.mozilla.org/en-US/docs/WebAssembly/Concept). How are we going to render the component to the page? Is there some kind of magic involved?? well…
If I had to summarize Yew in one sentence I would say it's a framework that allows you to write back-to-front web applications in a component mindset, completely in Rust. Now that's some cool sh*t right there! 🤘
Let’s begin our journey with a simple Yew application, just to, you know, get a feeling of how things work. Later on, we will add OCAPI into the mix and use it to search for a product from our SFCC instance.
Our simple application will bind an HTML input element to a read-only textarea element, copying everything the user writes in the input element to the textarea. I know it's not much (and in fact sounds pretty damn stupid) but think of it as the base for our product searcher, which will also use an input element to get the search term and display the results in a nicely styled div below it.
Create a new Rust library project by running cargo new --lib yew-product-searcher, cd to the new yew-product-searcher folder, and open it in your favorite code editor.
Our project is going to be a dynamic library, so let’s update our Cargo.toml to reflect that. Add the following snippet before the dependencies section:
Next, let’s add the dependencies for our project. Update your Cargo.toml dependencies section as follows:
📦 So why do we need all of these dependencies?
- yew/yewtil — The Yew library and its utility library.
- serde/serde_json — A serialization library we will use to serialize and deserialize JSON payloads.
- web-sys — Yew uses web-sys under the hood to communicate between Rust and Web APIs. By default, none of the features are added. We specify the features we are going to use with it in our dependencies declaration.
Like most things in life, our Yew application needs an entry point. The grand entrance. In our case, that point is the src/lib.rs file.
We begin by adding the required use statements:
🙋♂ Before you ask — yes I know we don't have an app.rs yet - we will add it shortly.️
Now, let’s add the actual entry point function:
🔬 So what do we have here?
- Line 1 — Using the wasm_bindgen(start) attribute, we annotate the app’s entry point. It is required by wasm-pack.
- Line 2 — Our main function. Returns an Option of either an empty tuple or a JsValue object.
- Lines 3–6 — Using web_sys we stroll through the DOM tree, from the top window object (line 3) all the way to the body (line 5). Once we have the body element we can get the collection of its children elements by calling the much appropriately named method - children(line 6).
- Lines 7–9 — Using the named_item method, we can get a reference to the div element which will host our app.
- Line 10 — Creates a new yew app and mounts it to an element (in our case, the div with the id of yew-app ).
- Line 11 — Returns a unit type.
We now have an entry point pointing to nothing (remember app::App from line 10 above?). Now is the perfect time to add the App component - a.k.a our application’s main component - to the mix.
Create a new app.rs file under the src folder with the following content:
Our App is basically a struct containing a link property with ComponentLink as its type and a text property of type String.
😐 Johnny — I know what a String type is, but wtf is aComponentLink?!
Glad you asked! A ComponentLink is basically a link (a.k.a a reference) to the component that enables us, at a later stage, to send messages (read: actions) to the component through callbacks. Imagine ComponentLink as your way of interacting with the components at runtime.
To send a message (a.k.a action) to the component we need to define an enum containing all the possible messages the calling party (the component host, a service, an agent, etc.) can send. Add the Msg enum to app.rs :
We will call our component on every change in the input element (i.e. the oninput event), passing the message of. DuplicateText with an InputData object. The InputData object contains, among other properties, the new value of the element, which is something we will shortly use.
Now it's time to implement Yew’s Component trait for our lovely app. As you’ll soon see — the Component trait basically enables us to control the component’s life cycle and how it will act at each event. We begin with the super basic stuff and set the Message and Properties types:
🔬 So what do we have here?
- Line 1 — We tell Rust we want to implement the Component trait for our App.
- Line 2 — We set the Message type to the Msg enum we created previously to let the component know of the different actions it should expect.
- Line 3 — If you have some React background, you are probably pretty familiar with the concept of properties. In a nutshell, properties are immutable inputs we can add on an element to pass to the component. To visualize the properties concept, check out the following example, in which title is a property we pass to the component:
<Page title="Hi properties!"/>
Next, we implement the very first life-cycle method: create. The create method fires when the component is created and it is here that we initialize the component properties as well as the ComponentLink:
When the component is created, it receives the properties (if any) from its host (a.k.a parent) as well as establish the link between itself and the host. The create method accepts these two arguments (line 4) that we then use to initialize our component (lines 5–8). Notice that we used _ as the properties variable name since in our simple component we don't them.
As mentioned earlier, we communicate with the component through messages. The life-cycle method that handles these messages is the update method. This method contains the logic to handle the different messages, and ultimately decide if the component should re-render itself:
When the update method is called it will perform a match on the msg object (line 4). We only have one possible message on our Msg enum for this component, so it will match DuplicateText (line 5). If the associated element's updated value matches DuplicateText we update the value of the text property with the value of the message. We end the method by returning true (line 7), indicating that the component needs to re-render.
📣 If we had more than one message, we had to handle cases where nothing matches, which usually results in returning false as the component doesn't need to re-render.
Next up: the
changemethod. Just like the
changeis also able to re-render the component, but unlike
changedeals with a change in the component properties.
changeshould return true (meaning re-render) only when the properties changed from the last call. In any other case (the component doesn't have properties at all, the properties didn't change etc.) we should return false:
And finally, the crown jewel —
viewmethod is where we define how to render our component to the DOM. To prevent things from getting messy here, Yew provides us with a super useful macro —
html!— for declaring HTML (and SVG) elements, their attributes, and their events. You can think of the
html!macro as the equivalent of JSX in React:
🔬 So what do we have here?
Line 4 — Using the
- Line 6 / Line 9 — When we want to write some text directly to the DOM we wrap it in curly brackets.
Line 10— We set the
oninputcallback using the link property, passing it the
InputDataof the element.
Line 14 — We bind the
textarea’s value attribute to the component text property, which will get updated whenever the data changes on the input element.
🗣 Keep the class names used here in the back of your mind. We will meet up with them again soon.
Our component is ready for prime time, but before we are able to load it, we have to pack it. We are going to use wasm-pack as our weapon of choice, mainly for its simplicity, but it's important to know that there are other ways to pack a WebAssembly as well.
Open your shell and cd to the project folder. Inside of it run the following:
wasm-pack build --target web
Before we proceed to add OCAPI and SFCC to the mix, let’s quickly test that it's working as expected.
To quickly test our WASM module, let’s write a short HTML file that will load the module and initiate it using the run_app command we defined way back in the lib.rs file.
In the root folder of the project create an index.html file with the following content:
😱 What do we have here??
- Line 6 — Remember all those class names we added to the view method? So they are all Tailwind CSS classes and in order to use them, we need to include the Tailwind CSS file
👀 It is not recommended to include the unoptimized CSS file from a CDN like we are doing here, but to keep things simple and avoid installing the Tailwind tooling, etc, we will just use that file.
- Line 10 — The div in which our app will render itself.
- Lines 13–16 — As we cannot have root-level async/await, we create an async function called run (line 13), await on the init function (line 14) and finally run the run_app function which starts our Yew app (line 15).
- Line 17 — Calling the async run method.
Use your local webserver of choice (I personally use Caddy) and embrace the beauty that is Yewplicator:
So far we:
- Built a Yew project from scratch.
- Packed the WASM module with wasm-pack and
In our next post, we will take our simple Yewplicator and throw SFCC into the mix by transforming it to an OCAPI based product searcher component.