Let’s Build a Salesforce Commerce Cloud Product Search Component with OCAPI, Rust, and Yew — Part 1: Yew Basics
Salesforce Commerce Cloud + Rust = ❤️
Anyone familiar with Salesforce Commerce Cloud (SFCC for short) knows that you usually develop a cartridge to extend and add new features to your SFCC instance. Since a cartridge is basically a module written in JavaScript, how do we go about adding Rust to the cartridge world? Are we really going to write a Rust-based cartridge in this post?
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…
Yew 101
Meet Yew. By its own definition, Yew is “a modern Rust framework for creating multi-threaded front-end web apps using WebAssembly.” (https://yew.rs/#what-is-yew). It features component-based development (React devs should feel right at home here), access to services (fetch
for example) and great JavaScript interoperability.
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.
Creating the Project
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.
🏗 Before we begin, make sure you have the following installed:
* Rust 1.50+- https://www.rust-lang.org/learn/get-started
* WASM-Pack — https://github.com/rustwasm/wasm-pack
- Create a new Rust library project by running
cargo new --lib yew-product-searcher
, cd to the newyew-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:
3. 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?
- wasm-bindgen — A Rust library we will use to communicate between Rust and JavaScript. Also contains a set of macros that help when working with WASM and Rust.
- 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.
⛳ Round 1: The Entry Point
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.️
2. 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 aJsValue
object. - Lines 3–6 — Using
web_sys
we stroll through the DOM tree, from the topwindow
object (line 3) all the way to thebody
(line 5). Once we have thebody
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 thediv
element which will host our app. - Line 10 — Creates a new
yew
app and mounts it to an element (in our case, thediv
with theid
of yew-app ). - Line 11 — Returns a unit type.
⛳ Round 2: The App Component
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<Self>
as its type and a text
property of type String
.
😐 Johnny — I know what a
String
type is, but wtf is aComponentLink<Self>
?!
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.
2. 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.
3. 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 ourApp
. - 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!"/>
4. 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.
5. 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.
6. Next up: thechange
method. Just like the update
method, change
is also able to re-render the component, but unlike update
— change
deals with a change in the component properties. change
should 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
:
7. And finally, the crown jewel — view
. The view
method 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
html!
macro. - 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
oninput
callback using thelink
property, passing it theInputData
of the element. - Line 14 — We bind the
textarea
’svalue
attribute to the componenttext
property, which will get updated whenever the data changes on theinput
element.
🗣 Keep the class names used here in the back of your mind. We will meet up with them again soon.
⛳ Round 3: Packing Time
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
If you check the project folder now you will notice a new folder — pkg. This new folder contains the compiled WebAssembly (yay 🎉) along with its supporting JavaScript wrappers.
Before we proceed to add OCAPI and SFCC to the mix, let’s quickly test that it's working as expected.
⛳ Final Round: Testing
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. - Line 11 — We declare a JavaScript module
script
section. - Line 12 — We import the
init
andrun_app
methods from yew_product_searcher.js, the JavaScript wasm wrapperwasm-pack
created for us. It is with these functions that we are going to load our module in the browser. - Lines 13–16 — As we cannot have root-level async/await, we create an async function called
run
(line 13),await
on theinit
function (line 14) and finally run therun_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:
🥂 Summary
So far we:
- Built a Yew project from scratch.
- Added a root component with a number of HTML elements that interact with one another without any JavaScript.
- Packed the WASM module with
wasm-pack
and - Created a test HTML file that uses a JavaScript module script to load and initialize the WASM.
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.