Building small desktop apps with Ember.js and Tauri
Tags: ember, rust, javascript
Published: 2021-05-24 13:04:23

I recently played around with Tauri, a toolkit for development desktop apps with web technologies. Here's how I got it working with an Ember.js application.

## What is Ember?
Ember.js is a frontend framework similar to React and Vue JS. I used it to build my app [Snipline](https://snipline.io), and it's also used for websites like Intercom and LinkedIn. It has a 'convention over configuration' approach similar to Ruby on Rails.

## What is Tauri?
Tauri is a library for making desktop applications with web technologies. Similar to Electron with a few key differences: 

1) It's built in Rust rather than Javascript. 

2) It uses your operating system's native web browser rather than bundling Chrome which resulting in quite tiny applications—at least compared to Electron!

## Installation and development

Here are the commands I ran for a simple Ember app to test routing with Ember and Tauri. For reference, I'm using Node. 14.17.0.

### Setting up Ember
```bash
npm install -g ember-cli
```

```bash
ember new tauri-test --lang en
```

```bash
ember g route index
ember g route from-ember
```

```
ember serve
```

I edited the two generated templates, `app/templates/index.hbs` and `app/templates/from-ember.hbs`.

```js
{{page-title "Index"}}
<h1>Hello, Tauri 😄</h1>
<LinkTo @route="from-ember">Click here</LinkTo>
```

```js
{{page-title "FromEmber"}}
<h1>From Ember 🧡</h1>
<LinkTo @route="index">Back</LinkTo>
```

That's enough to get started and test that routing works within the app. Now let's get to the good stuff.

### Setting up Tauri
First, [follow the set up guide for your OS in the Tauri documentation](https://tauri.studio/en/docs/getting-started/intro).

After this, it's a matter of adding it to your ember project - [See the integration documentation](https://tauri.studio/en/docs/usage/development/integration).

This is what I did to get it working.

```bash
npm install @tauri-apps/cli
```

```
// Add the `tauri` command to your `package.json`
{
  // This content is just a sample
  "scripts": {
	"tauri": "tauri"
  }
}
```

Run through the initialisation process.

```bash
npm run tauri init
```

When prompted, make sure that you set the development server to `http://localhost:4200` and the location of the files (relative to `src-tauri`) to `../dist`.

Then it's just a matter of running the development subcommand (Make sure your Ember server is still up, too).

```
npm run tauri dev
```

And that's it! It works even with hot reloading!

## Packaging

With development out of the way, here's how to package the app for distribution. I won't be looking at auto-updates in this guide, but [Tauri does have support for this](https://tauri.studio/en/docs/usage/guides/updater).

```bash
ember build --environment=production
npm run tauri build
```

On MacOS installer `.dmg` file came out at 5.4MB and the `.app` file  12.4MB.

![Tauri running the Ember.js app in MacOS](https://cdn.hashnode.com/res/hashnode/image/upload/v1621767450348/hYnW6LQ04.gif)

For Windows, the generated MSI installer came to 4.9MB and the executable 8.9MB.

![Tauri running the Ember.js app in Windows](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kubj34m4i8jagvfz38id.png)

## Communicating between Rust and Ember
Taking this one step further, I thought I'd test a simple ping/pong example of communicating between Ember and Rust. For more information [check the Tauri docs](https://tauri.studio/en/docs/usage/guides/command).

The following code allows for Ember to pass a string to Rust, Rust checks the value and toggles between the text 'Ping' and 'Pong'. In Ember, I've added a button that displays the response text and updates it on click.

```rust
// src-tauri/src/main.rs
#![cfg_attr(
  all(not(debug_assertions), target_os = "windows"),
  windows_subsystem = "windows"
)]

// Add a new function that takes a string and returns a string
#[tauri::command]
fn my_custom_command(current_text: String) -> String {
	// Depending on what we receive from Ember we toggle the response
	if current_text == "Ping" {
		"Pong!".into()
	} else {
		"Ping".into()
	}
}

fn main() {
  // Add the custom command so that the frontend can invoke it
  tauri::Builder::default()
	.invoke_handler(tauri::generate_handler![my_custom_command])
	.run(tauri::generate_context!())
	.expect("error while running tauri application");
}
```

```js
// app/controllers/index.js
import Controller from '@ember/controller'
import { action } from '@ember/object'
import { tracked } from '@glimmer/tracking'
import { invoke } from '@tauri-apps/api/tauri'

export default class IndexController extends Controller {
	// Set the default button text
	@tracked buttonText = 'Ping'
	// Create an action that will be attached to a button in the template
	@action
	checkRust() {
		// Invoke the Rust command and update the button text to the response
		invoke('my_custom_command', { currentText: this.buttonText }).then(resp => {
			console.log(resp)
			this.buttonText = resp
		})
	}
}
```

Here's the updated `app/templates/index.hbs` template file.

```js
{{page-title "Index"}}
<h1>Hello, Tauri 😄</h1>
<LinkTo @route="from-ember">Click here</LinkTo>
<button {{ on 'click' this.checkRust }}>{{this.buttonText}}</button>
```

![Tauri and Ember.js communicating with each other](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/suc4ziujfzlb3nzmrh3f.gif)

Pretty cool! I'm excited to see where Tauri goes, and to see its plugin ecosystem grow. Should I try building a full project in it or write some more tutorials using both technologies? Let me know in the comments!