Making Desktop GUI Applications using Deno Webview

Recently, Deno community member Elias Sjögreen created a library called Deno Webview, which allows you to make desktop application using web technologies and Deno. This tutorial goes through the process of making and bundling a basic desktop application using these technologies.


How Does it Work?

Internally, the Deno Webview library wraps around Rust bindings for Webview, which itself is a small library for creating cross-platform GUI applications using web technologies. Webview is written in a combination of C, C++, and Golang, and uses a combination of web rendering technologies to achieve cross platform support.


Basic Webview Application

Lets see a basic example of how to use the library. Create a file called test.js with the following contents:

test.js
	// Importing the webview library
	import { WebView } from "https://deno.land/x/webview/mod.ts";
	
	// Creating an HTML page
	let html = `
		<html>
			<body>
				<h1>Hello World!</h1>
			</body>
		</html>
	`;
	
	// Creating and configuring the webview
	const webview = new WebView({
		title: "Deno Webview Example",
		url: "data:text/html," + html,
		width: 800,
		height: 600,
		resizable: true,
		debug: true,
		frameless: false
	});
	
	// Running the webview
	webview.run();

In this example, we import the Deno Webview library, create a basic HTML webpage, and then create a new Webview object with some configurations for our webview. Finally, at the bottom we run the webview to create the new window with the webview running in it.

To run this example program, we need to run the deno command with a few flags:

Command Line Shell
	deno run -A --unstable test.js

When running the command the first time, you'll see this text in the console like this:

Command Line Shell
	INFO load deno plugin "deno_webview" from local "/home/user/webview/.deno_plugins/deno_webview_1294439695e9a9d19c0fc6d2a913e9f3.so"

The first time Deno Webview is run, it will create a folder called .deno_plugins, and will download the plugins necessary to run the webview.

A window should pop up with our webview:

And that's it! Deno Webview is very simple to use!


Advanced Webview Application

Now lets make a more advanced Deno Webview application. This application will be a simple cryptocurrency application that will create a chart of the change in CoinDesk's Bitcoin Price Index over the prior month. Additionally, since the API used to pull this data is updated daily, every day a new chart will be created, making it a neat tool to keep track of trends in the Bitcoin Price Index.

Let's create a folder for our application:

webview folder
	|--webview
	  |--app.js
	  |--index.html

First lets create the app.js file, which will house all the logic of our application:

app.js
	import { WebView } from "https://deno.land/x/webview/mod.ts";
	import denjucks from "https://deno.land/x/denjucks/mod.js";
	
	// Fetching Bitcoin Price Index data from CoinDesk
	fetch("https://api.coindesk.com/v1/bpi/historical/close.json")
	.then(resp => resp.json())
	.then(data => {
		
		let bpiData = data;
	
		// Rendering the Denjucks template from the index.html file, including in
		// data for the Bitcoin Price Index when rendering the template
		let renderedTemplate = denjucks.render("index.html", {
			bpiData: JSON.stringify(bpiData.bpi),
		});
		
		let html = "data:text/html," + renderedTemplate;
		
		// Creating the webview with the rendered template
		let webview = new WebView({
			title: "Deno Cryptocurrency Webview",
			url: html,
			width: 800,
			height: 600,
			resizable: true,
			debug: true,
			frameless: false
		});
		
		// Running the webview
		webview.run();
		
	}).catch(err => {
		
		// Creating some HTML markup to display the error message
		let html = `
			data:text/html,
		    <html>
				<head></head>
			    <body>
					<h1>Error</h1>
					<p>${err}</p>
			    </body>
		    </html>
		    `
		;
		
		// Creating a webview with the error if something failed while running the
		// application
		let webview = new WebView({
			title: "Deno Cryptocurrency Webview",
			url: html,
			width: 500,
			height: 400,
			resizable: true,
			debug: true,
			frameless: false
		});
		
		// Running the webview with the error message
		webview.run();
	});

The file starts by trying to fetch data that the webview will use, renders the template in index.html, and then starts the webview with some configurations.

Now let's take a look at the template in the index.html file:

index.html
	<html>
		<head>
			<style>
				html, body {
					background-color: #111111;
					color: #eeeeee;
				}
				
				h1 {
					text-align: center;
				}
			</style>
			
			<!-- 
				Including the Plotly.js library. In production applications this
				library will be linked to a file on the disk instead of pulled from
				the web. 
			-->
			<script src="https://cdn.plot.ly/plotly-1.53.0.min.js"></script>
		</head>
		<body>
			<h1>Bitcoin Price Index</h1>
			<div id="BitcoinChart"></div>
			
			<!-- 
				Code to create the chart. First the Bitcoin Price Index data is added
				to the JavaScript code when the template gets rendered. Then Plotly is
				used to generate a chart.
			 -->
			<script>
				let bpiData = {{ bpiData|safe }};
				
				let chart = Plotly.newPlot(
					document.querySelector("#BitcoinChart"),
					[
						{
							x: Object.keys(bpiData),
							y: Object.values(bpiData),
							type: "scatter",
							mode: "lines",
							line: {
								color: "#FF0000",
								width: 2.5,
							},
						},
					],
				);
			</script>
		</body>
	</html>

The comments in the code describe most of the template. Essentially, I am fetching the plotly.js library, setting some styles, and then creating a chart from the Bitcoing Price Index data. Note that the only denjucks tag im using here is a context variable, {{ bpiData|safe }}, that will be replaced with the context variable value provided when rendering the template in the app.js file.

Finally, let's check out our application by running the following command:

Command Line Shell
	deno run -A --unstable app.js

After running this command, you should see a window like this:

And there it is, our application rendered a chart from the Bitcoin Price Index data! If we run this program tomorrow, new data will be pulled and thus the chart will be continuously updated.


Packaging the Webview Application

Now that we've created our desktop GUI application, lets package it into a zip folder, and then send it to another machine to see if it works there as well. Fortunately, Deno is a single file executable, making it extremely easy to package our application. Additionally, Deno comes built in with the bundle command that we can use to package our JavaScript code into a single file. Let's run the bundle command on our app.js file:

Command Line Shell
	deno bundle app.js bundle.js

This will create a bundled file called bundle.js in the current directory, which will contain all the libraries we used in the app.js file, as well as the code in the app.js file, into a single bundled file. For good measure, lets run the deno command again to ensure our application still works:

Command Line Shell
	deno -A --unstable bundle.js

And if you see the same screen as we did when running the app.js file, then it worked!

Now that we've bundled all the dependencies, lets also bundle in the deno executable into this folder as well. Deno ships as a single file executable, so we simply need to place this executable for our platform in our folder. A list of executables for various releases and platforms can be found here:


And if you scroll down the page, you'll see the releases for various platforms:


You can see the list of compressed files for various platforms containing the deno executable. Since I am building this on Windows, I'll download the deno-x86_64-pc-windows-msvc.zip, and within this zip file is the deno.exe executable that we can add to our folder. The contents of our folder should now look like this:

webview folder
	|--webview
	  |--.deno_plugins
	  |  |--deno_webview_xyz1234.dll
	  |	
	  |--app.js
	  |--bundle.js
	  |--index.html
	  |--deno.exe

Finally, now that we have our bundled code and deno runtime packaged, we can make a simple script to start our program. In my case, since this the program will run on Windows, I am using a basic VBS script to start the program:

run.vbs
	Set WshShell = CreateObject("WScript.Shell")
	WshShell.Run "deno.exe run -A --unstable bundle.js", 0
	Set WshShell = Nothing

And that's it! Our application, the webview plugin, our bundled dependencies, and the deno runtime executable are all we need to test out our application on another machine. For good measure, lets zip our folder into a zip file, and then transfer to another machine (in this case I am using 2 separate Windows 10 machines, one with Deno already installed, and the other one a clean machine). Finally, unzip on the other machine, and the double click the run.vbs file, and:

It worked, We are now able to ship the application to other machines!


Summary

While for this tutorial I created my Desktop GUI application using Windows, both Deno and Deno Webview are cross platform, meaning you can use the same process of creating, bundling, and running the GUI application on other platforms as well, like Linux and MacOS, with very minimal changes to the source code.

If you liked this tutorial or have any questions or comments, please feel free to email me at denotutorials@protonmail.com