Build Proton apps
Mats Tage Axelsson takes you through creating a desktop application using Proton Native, the React way to the desktop.
Mats Tage Axelsson takes you through creating a desktop application using Proton, the React way to the desktop.
You may not be surprised that some developers have criticised Electron (see tutorials LXF256), mostly regarding the memory usage of its final binaries. The initial binary is over 100MB, because a major chunk of code from Chrome is embedded. When you create a small piece of code, this is a horrible waste. With Proton Native the binary is compiled with the bare essentials, making the binary a few kilobytes in size. That’s a very big win for small utilities.
Until now Proton Native hasn’t been in much use with bigger projects. However, it may become popular thanks to the potential for more efficient and smaller binaries compared to Electron. Since it supports Node.js, developers can create pretty much everything that they can with Electron.
Environment and compilers
Under the bonnet, Proton Native uses libgtk3-dev, build-essential, python2 and pkg-config for its compiling, so make sure they are installed.
$ sudo apt install libgtk3-dev build-essential python2 pkg-config
Now you can compile the package you are about to make. This is a Node.js package that you can install using npm or npx. At the time of writing, this package needs an old Node.js version – 10.15.3 works. To ensure that your applications work, make sure each project uses the correct version and follow up as you go.
Proton Native uses auto-gyp to compile to the local binaries. Auto-gyp works with gyp and nbind to choose the compiler and its options, making it possible to compile your code in C++. For your case, you need to keep track of developments so you can upgrade to a newer Node.js and move to Python 3, since Python 2’s End Of Life status is 2020.
Start by creating a directory for all your projects. If you use the automatic install method, the script will create a directory for your project and install all Node.js software you need. However, it will fail. This is where you need to choose the old Node.js. Install nvm globally. $ sudo npm install -g nvm
Now you can create a file for setting another Node.js for your application. Put a .nvmrc file in your project directory and fill in the version value v10.15.3 . Until the project is upgraded you will still get a lot of warnings about deprecated functions in the V8 engine of Chrome.
If you want to be more bleeding-edge, you can use the Github repository for your projects. If you can solve it yourself, make a branch and refer to that one. Branches are reached with a hashtag ‘#’. Move into your project directory, remove node_modules and reinstall.
$ cd
$ rm -rf node_nodules/
$ npm install
You will still get a lot of warnings but it will go through and you can try out your template project.
If this seems complicated, you can also create the library and add the files manually. Use the Proton Native install script while in your project directory. Do this after you have installed nvm and set the .nvmrc file as described earlier.
$ npm i -S Proton Native
After the script runs, you will have the node_ modules directory and package-lock.json as well as the .nvmrc file. You need to add two more files: .babelrc and package.json. Add this to .babelrc:
{
“presets”: [
“env”,
“stage-0”,
“react”
} This specifies the Babel functions and you can see that it runs in react mode. Babel is a Javascript compiler that will make sure your final result is backwards-compatible and correctly formatted. This includes handling the JSX syntax; without it you would have to write much more complex code. You can now install Babel itself.
$ npm install --save-dev babel-cli babel-preset-env babel-preset-stage-0 babel-preset-react
Next, in package.json you need to define that babel runs your main Javascript file, in this code: index.js. scripts: {
“start”: “babel-node index.js” }
With this method, you are also missing the template index.js file. However, it is very small, so create the file and add: import React, { Component } from ‘react’; // import from react import fs from’fs’; import { render, Window, App, } from ‘Proton Native’; // import the Proton Native components
class example extends Component { render() { // all Components must have a render method return (
{/* all your other components go here*/}
);
}
} render(
With those files in place, you can run your test project like this:
$ npm run start
The result is a little window with the heading “Proton Native Rocks!’ You are now ready to take on the challenge of creating your own application, so let’s have a look at doing that.
Your code is going to be in Javascript and React, so using your favourite IDE or editor is your best bet. Obviously Emacs is best. Proton Native also requires g++, but you have installed all these at the beginning of the tutorial.
When you have written a full application, you can have Proton Native create binaries that you host somewhere on the web. This function actually uses electron-package but makes sure not to include the entire binary from Chrome, as Electron does.
In this project, you will upgrade an existing example from the proton repository. The project you start with is Notepad, so download all of Proton Native from https://github.com/kusti8/proton Native. In the examples, you can find a box statement; copy it into your code, add Box and Textinput to your import
statement and let’s get started.
This code creates a box that you cannot see, but it is inside the application. As you can guess from the code, you can input text into the box. The first statement is
onchange ; as you might guess, this statement runs the function when the text changes.
Your text is in the state of the app function. This means it updates the state at every letter you write, which could be a problem – but React has solutions for this. You may also want to save the notes as a file, so let’s add that too. To do this, add fs to your import and
Dialog to the Proton Native import. The same code also includes shouldcomponentupdate() – this function updates the component when the state or props change. In the Notepad application, this happens every time you enter text.
Now, let’s try something else! Make an input box that takes radio buttons and saves the value to state and a file. You can copy the code this time from the examples in the documents for Proton Native on Github. This example has two options and there’s no way to know which is selected. Change the code like below in index.js for Lovemytasks.
class Lovemytasks extends Component {
state = { text: ‘’, project: ‘’ }; ...
Radiobuttons.item>
Radiobuttons.item>
This code adds how the application reacts when an item has been selected. For it to work, you must add project to the state at the top of the class. To style the buttons, you need some CSS. In Proton Native, this is achieved with the built-in functions.
Make a grid
When you just add buttons in your code, you can change very few aspects of where the parts go. Your components will appear somewhere on the screen without any special order. In this case, you can use Grid. Grid encapsulates your graphical elements, giving you the opportunity to set them on the screen where you want them. When you have done this, every component has new values they can use to place them in relation to each other. The Radiobuttons code changes like the code below.
The values are row , column and expand . The first two you use as expected to make a grid. The expand value denotes if the component is allowed to expand in the direction that you mention. Make sure you use an object – the double curly braces are easy to forget. There are also other values available, experiment with them to get to know them.
To be able to save your data to a file, you need to add some code to the file save and open options.
index.js (File menu)
… save() { const filename = Dialog(‘save’); if (filename) { let data = (Json.stringify(this.state)); console.log(data); fs.writefilesync(filename, data ); } } open() { const filename = Dialog(‘open’); if (filename) { let data = fs.readfilesync(filename); // Data is a string, parsing with JSON); this.setstate(json.parse(data)); // Make interface update this.forceupdate(); console.log(this.state); } }
Now you have the data saved as a string which is saved to whatever file you choose to use. When you open the file, you only get the first part of the string back into the application. The chosen project is not transferred since setstate does not read it out.
The functions we want to use are Json.stringify and Json.parse . They are each other’s counterparts – stringify makes a string of a Javascript object. In our code, we do that when we want to save our file. Next, when we want to load the file back in, we parse the JSON file. In production code, you will need to handle files that do not contain JSON, but that is a bigger issue.
Now we have to make the interface update after we load it in. We make sure of this with the forceupdate
function of our main application. When you start getting serious, your code will start looking messy. It will become hard to read and each time you have a new idea, it is hard to find your way around the code. To solve this, we can use components.
Make a component
When you want to make a component, you will use the object-orientated power of Javascript. In the file, you define a class and export it at the end of the file. You can export by name if you want several modules, or use the default keyword to add it to your import. Combining this with React, you get a method to add JSX to your
code. The best way to understand this is to move out code you have already written. So start with moving out the menu to a separate file. This makes your main file cleaner. Create Mainmenu.js and enter this code: import React, { Component } from ‘react’; import {
Menu,
} from ‘Proton Native’;
export default class Mainmenu extends Component { render() { return(
);} }
The observant reader will notice that the onclick statement has changed to contain only the name of the code, not the entire code. In the index.js file, you now need to change the top of the class. Add a constructor so you can refer to the imported class:
... import Mainmenu from ‘./Components/mainmenu.js’ ... class Lovemytasks extends Component { constructor(props) { super(props); this.state ={ text: ‘’, project: ‘’, }; this.save = this.save.bind(this); this.open = this.open.bind(this); }
The key difference here is that the ‘state’ needs to be part of the constructor and that you bind the code that you will use from the imported code. You also need to throw out the menu code inside your
/> As you can see, the menu is now much smaller, making it easier to read the code. If you need to change the menu, start in the Mainmenu.js file and tweak the index.js file. The Proton Native project tries to make it easy to create desktop applications using Javascript and React. In contrast to Electron, it does not include the Chromium browser to achieve this. Instead, it uses other projects to compile to native components using Qt. Qt is not delivered on all platforms but it uses the underlying system, avoiding large binaries for small tasks. In short, Proton Native is an exciting project with a bright future but it is currently lacking some stability.