Web-site | https://electronjs.org |
Platforms | Linux, macOS, Windows |
Renderer | Skia + WebGL |
Download Electron Chat | Coming soon |
Tech stack used for building a showcase app:
Build system | Webpack |
Languages | TypeScript |
Libraries | React, styled components |
Editor | Visual Studio Code |
The browser is an incredibly complicated piece of software comparable with the Operating System itself. There is a lot happening to render these simple HTML + CSS into pixels that we see on the screen. I should put here a link to Chrome internals. Chromium uses cross-platform library Skia (skia.org) to paint these pixels.
Let’s look at a simple button. In order to place it on the screen you need HTML:
<button type="button">Save</button>
some optional CSS to style this button:
button {
margin: 2em;
}
and optionally JavaScript to handle changes:
document.querySelector("input").onchange = e => console.log(e);
This is the result you get on macOS in Chrome and Electron:
At first glance, it looks like a native button on macOS, but when we press it, we can spot a little difference comparing to the same button in Safari:
Why is it happening? If we dump our rendered chat client from HTML+CSS to Skia’s internal representation format and use a debugger, we can watch a step-by-step demonstration of how the whole document was painted by using lower-level graphical instructions:
And the button here is just an image of button’s background + set of glyphs drawn over it + some transformation logic applied to get the correct size.
So the button in Chrome doesn’t match the native look&feel for every operating system out there. But who cares? In terms of styling, usually for a web site it does not mean a lot, controls are rarely used as it is, their design is customized and it probably won’t look anything like a default native button anyway:
This is not always true for the desktop. For a certain group of desktop apps, though, you may want to “mimic” the look to make it closer to system controls. That is still possible by carefully crafting the right CSS, but it’ll be harder to maintain over time. As an example, look at the library that provided a set of macOS UI controls called Maverix, it became outdated the next second Apple released the next macOS version.
Good news, we don’t need anything like for the most parts of our chat app, we can use custom styles.
Since Electron is based on a web browser, Chromium, in this case, the developer experience for anyone who is already familiar with the web is just amazing. For everything that is related to the UI inside a window, you feel like you just write a web application. You have all sort of hot reloadings, DevTools that allows you to change styles on the fly and profile on every machine your app installed, and an infinite amount of integrations with design tools.
The level of googlability of any problem is really high, either that is an npm package, a stackoverflow question or a GitHub issue similar to yours.
What about writing network code, HTTP and WebSocket in particular? We can guess just by looking at the names of these protocols, we have Hypertext and WebSocket here, both invented for the web browser, which exactly Electron is. Surprisingly, though, HTTP was much more suited initially for interlinked documents, not apps, so all we have is XmlHttpRequest and Fetch and abstractions over them. Now, things are changing and browsers are getting better. You have WebRTC now and can try to build Skype on top of it.
And again we’re writing a web application, so to start using Slack’s API we’ll go to npm:
yarn add @slack/client
and use it like this for HTTP API:
const web = new WebClient(token);
return web.users.list({}).then(results => {
return results.members as Array<SlackUser>;
});
and we can also use WebSocket-based, real-time client, without even thinking about details:
const rtm = new RTMClient(token, params);
rtm.on("message", message => {
// Log the message
console.log(
`(channel:${message.channel}) ${message.user} says: ${message.text}`
);
});
Here comes a small rant about how hard to write a desktop client for Slack, even though it’s understandable from the business’ standpoint and I should have probably picked something open sourced. But thanks to the support, turns out it’s possible to use undocumented way to use their API. And again, which is one might love web platform, it’s so easy to do live debugging
To be able to write an app
OAuth leveraging existing Slack session in the browser.
Let’s enable acessibility on macOS and check simple things:
At first I was getting “link, #webrender, we” on each menu:
Why is that?
Because React components inside ChannelList
produced HTML that looked like this:
<div>
<!-- ... -->
<a href="">
<span>#</span>
<span>webrender</span>
<span>we</span>
</a>
<!-- ... -->
</div>
After reading https://inclusive-components.design/menus-menu-buttons/, I added some semantics and aria attributes:
<nav>
<ul aria-labelledby="sections-heading">
<h3 id="sections-heading">Channel List</h3>
<!-- ... -->
<li>
<a href="" aria-current="page">
<span aria-hidden="true">#</span>
<span aria-label="channel webrender">webrender</span>
<span aria-hidden="true">we</span>
</a>
</li>
<!-- ... -->
</ul>
</nav>
It’s all done in styled-components and React fully supports aria-
attributes.
So we hide #
and we
(needed for the compact mode), and now it looks like a menu, and users know what to expect:
And what’s nice, we can use LightHouse to inspect accessibility since our UI is basically an HTML opened in the web browser and we can use Devtron — a tool to debug Electron apps.
To be continued…