Skip to content

Latest commit

 

History

History

las2021

marp style
true
/*@import url('https://fonts.googleapis.com/css2?family=Cantarell:ital,wght@0,400;0,700;1,400;1,700&display=swap'); @import url('https://cdn.jsdelivr.net/npm/hack-font@3/build/web/hack-subset.css');*/ @import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.14.0/css/all.min.css'); section { font-family: Cantarell; } code { background-color: #d8d8d8); border-radius: 5px; color: black; font-family: Hack } .hljs-params { color: #8f5902; } .hljs-built_in { color: #d73a49; }

bg opacity:.6

Writing native Linux desktop apps with JavaScript

Philip Chimento ptomato • @therealptomato Linux Application Summit, May 13, 2021


Introduction

  • I maintain GJS (GNOME JavaScript)
  • This talk is a bit of an experiment for me
  • Can web JS programmers ramp up quickly on writing a desktop app?

What this talk is

  • For JavaScript developers and enthusiasts
    • who are curious about writing a desktop app
  • A walk through creating and publishing a desktop app in JS
    • Technologies: GJS, GTK, Flatpak, Flathub
  • A slide deck that you can read later

What this talk is not

  • A step-by-step tutorial on how to write an app
  • Presented by an experienced web developer

bg right

Let's get started!


bg sepia opacity:.8

App: "Bloatpad"

the unnecessary note-taking app


bg left fit

Have something to start with


bg left:33% fit

  • a Meson build system
  • a placeholder icon
  • resource bundles
  • a .desktop file
  • a settings schema
  • an AppStream meta info file
  • infrastructure for i18n
  • skeleton code
  • a Flatpak manifest

Build systems

  • Meson is probably a good one to stick with
  • You will need it if your app ever includes any C code
  • Coming from JS development you still might want something more familiar
$ yarn init
"scripts": {
  "prebuild": "test -d _build || meson _build",
  "build": "ninja -C _build",
  "start": "meson compile -C _build devel",
  "test": "meson test -C _build"
}

Yarn

$ yarn build
$ yarn start


Linter

  • May as well install prettier and never again worry about code style
  • eslint for usage
$ yarn add --dev prettier eslint eslint-config-prettier
"lint": "eslint . --fix && prettier --write ."

TypeScript

  • You can write in TypeScript, it mostly works
  • Or write JS with type annotations in comments and use TypeScript to typecheck
  • Thanks to the hard work of Evan Welsh

Other build tools

  • Bundlers are probably not needed
  • Minifiers are probably not needed
  • Babel probably works

bg left

Assembling the UI


XML UI files or no?

  • XML-CSS-JS is like the trinity of HTML-CSS-JS
  • Alternative is to build your UI in code

XML UI files or no?

<object class="GtkListView" id="notesList">
  <property name="show-separators">True</property>
  <signal name="activate" handler="_onNotesListActivate"/>
</object>

vs.

this._notesList = new Gtk.ListView({ showSeparators: true });
this._notesList.connect("activate", this._onNotesListActivate.bind(this));

XML UI files

bg right fit


Result


CSS

drop-shadow

.large-icon {
  color: #888a85;
  -gtk-icon-shadow: #d3d7cf 1px 1px;
  padding-right: 8px;
}

CSS


bg

Time to write code


API Documentation

drop-shadow


About the API

  • Every UI element is based on Gtk.Widget
  • Roughly equivalent to a HTML DOM element
    • Methods
    • Properties
    • Signals (events)
    • CSS element name and classes
  • Things that are not UI elements are based on GObject.Object

ES modules

import Gdk from "gi:https://Gtk";
import Gio from "gi:https://Gio";
import GObject from "gi:https://GObject";
import Gtk from "gi:https://Gtk";

import { NotesListItem } from "./item.js";

Async operations

  • GNOME platform has asynchronous, cancellable I/O
  • Experimental opt-in support for JS await
Gio._promisify(Gio.OutputStream.prototype, 'write_bytes_async', 'write_bytes_finish');

// ...

let bytesWritten = 0;
while (bytesWritten < bytes.length) {
  bytesWritten = await stream.write_bytes_async(bytes, priority, cancellable);
  bytes = bytes.slice(bytesWritten);
}

Popular runtime libraries

  • These may or may not work
  • Check if you actually need the dependency
  • Use ES module directly if it doesn't have other deps
  • Some modules ship a browser bundle, this might work
  • Else, build a UMD bundle with Browserify and vendor it

Build a UMD bundle with browserify

yarn add my-library
mkdir -p src/vendor
npx browserify -r my-library -s myLibrary -o src/vendor/my-library.js
import './vendor/my-library.js';
// myLibrary is now a global object

Top 5 most used NPM libraries

  1. lodash
  2. chalk
  3. request
  4. commander
  5. react

Lodash

  • In some cases not necessary
  • Use lodash-es if you need lodash
import _ from './vendor/lodash-es/lodash.js';
_.defaults({ 'a': 1 }, { 'a': 3, 'b': 2 });

Chalk

  • No bundle, so make a Browserified one
  • Color support detection code is Node-only
    • Edit bundle, change stdout: false and stderr: false to true
import './vendor/chalk.js';
print(chalk.blue('Hello') + ' World' + chalk.red('!'));

Request

  • Deprecated
  • Use Soup instead
const request = require('request');
request('https://ptomato.name', function (error, response, body) {
  console.error('error:', error);
  console.log('statusCode:', response && response.statusCode);
  console.log('body:', body);
});
import Soup from 'gi:https://Soup';
const session = new Soup.Session();
const msg = new Soup.Message({ method: 'GET', uri: new Soup.URI('https://ptomato.name') });
session.queue_message(msg, (_, {statusCode, responseBody}) => {
  log(`statusCode: ${statusCode}`);
  log(`body: ${responseBody.data}`);
});

Commander

  • No bundle, so make a Browserified one
import System from 'system';
import './vendor/commander.js';
const { Command } = commander;

const options = new Command()
  .option('-p, --pizza-type <type>', 'flavour of pizza')
  .parse(System.programArgs, { from: 'user' })
  .opts();                  // ^^^^^^^^^^^^

if (options.pizzaType) print(`pizza flavour: ${options.pizzaType}`);

React

  • Not applicable

P.S. Although it would be cool if React Native worked with GTK


bg opacity:.6

Fast-forward to the written code

(Live demo, but in case that doesn't work out, screenshots follow)


bg fit


bg fit


bg fit


bg right

Distributing your app to users


How?

  • Flathub
  • Requirements
    • Luckily, the generated project skeleton meets all of these
    • Only need to fill in a few things

AppStream meta info

  • This file is used to provide the description that users see on Flathub
  • And in their software updater appplication
  • Description of file format

bg right


AppStream meta info


Desktop file

[Desktop Entry]
Name=Bloatpad
Comment=Unnecessary note-taking application
Exec=name.ptomato.Bloatpad
Icon=name.ptomato.Bloatpad
Terminal=false
Type=Application
Categories=Utility;GTK;
StartupNotify=true

Application icon

width:300 width:275


Submit it to Flathub


Translate your UI

  • Gettext is built-in to the platform
    • Venerable framework for UI translations
  • Use a website like Transifex
  • Recruit volunteer translators
  • Or translate the UI yourself in whatever languages you speak

Conclusion

  • Some things might seem familiar to JS developers, others might not
  • We should reduce the friction for these developers
  • But not everything from the web or Node.js applies well to the desktop

bg right

Questions


Thanks

  • Andy Holmes, Evan Welsh, Sri Ramkrishna for discussions and their work on improving the GJS developer experience

License

  • Presentation licensed under Creative Commons BY-NC-ND 4.0
  • Bloatpad code, permissive MIT license