When it comes to creating user interfaces there is no better platform today than the web. The open nature of the web means there is a plethora of libraries and components available which somehow can be combined together. You can argue whether PWA can start to compete with native apps on mobile. But on desktop, I cannot think of any competitor that can match the web. Naturally, when I was looking to create a desktop app, I gravitated quickly to Electron®.
So without further ado, let’s write an electron application. But while electron magically combines node.js and chrome environments I need a few more things. I need webpack to handle module importing on the renderer side. And I like TypeScript for type checking and code completion in IDE. Configuring these two can be a headache, but luckily there is a tool called electron-builder.
# clone template repository
git clone https://github.com/electron-userland/electron-webpack-quick-start
cd electron-webpack-quick-start
yarn # install dependencies
yarn dev # run app in development mode
In addition to electron-builder there is also electron-forge, which has similar set of features, though a quick glance shows that electron-builder is more mature.
Setting up renderer
Electron application is composed of the main and renderer processes which can communicate with each other. The main process is the entry point of the app is being run by node.js and can create and manage browser windows. Renderer processes are scripts run in chrome browser, but can access the node environment thru inter process communication.
To change the renderer process we need to edit src/renderer/index.js
.
Since I am going to use TypeScript and React JSX I am going to replace index.js
with index.tsx
:
import * as React from 'react';
import { render } from 'react-dom';
function App() {
return <p>Aloha!</p>;
}
render(<App/>, document.getElementById('app'));
But to get this simple boilerplate working we still need to install few dependencies.
# install TypeScript
yarn add -D typescript ts-loader
# install React
yarn add react react-dom
# plus type definitions
yarn add -D @types/react @types/react-dom
And configure TypeScript to compile JSX into React calls in tsconfig.json
:
{
"extends": "./node_modules/electron-webpack/tsconfig-base.json",
"compilerOptions": {
"jsx": "react"
}
}
Material Components
Our app looks pretty plain so far. So let’s add some Material Components.
yarn add @material-ui/core @material-ui/icons
Remember to white list any component libraries you use from begin treated as externals. Otherwise, you may into a common issue with React being loaded twice as I did.
To do so add electron-webpack configuration to package.json
.
"electronWebpack": {
"whiteListedModules": [
"@material-ui/core",
"@material-ui/icons"
]
}
Now to create our first real-life component src/renderer/coffee.tsx
:
import * as React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import Card from '@material-ui/core/Card';
import CardActions from '@material-ui/core/CardActions';
import CardContent from '@material-ui/core/CardContent';
import CardMedia from '@material-ui/core/CardMedia';
import IconButton from '@material-ui/core/IconButton';
import Badge from '@material-ui/core/Badge';
import Typography from '@material-ui/core/Typography';
import AddShoppingCartIcon from '@material-ui/icons/AddShoppingCart';
import RemoveShoppingCartIcon from '@material-ui/icons/RemoveShoppingCart';
const useStyles = makeStyles({
root: {
minWidth: 150,
},
picture: {
height: '50vh',
},
title: {
fontSize: 20,
},
});
export default function Coffee() {
const classes = useStyles();
const [inCart, setInCart] = React.useState(0);
const addToCart = (n=1) => setInCart(inCart+n);
return (
<Card className={classes.root}>
<CardMedia
className={classes.picture}
image="https://upload.wikimedia.org/wikipedia/commons/thumb/4/45/A_small_cup_of_coffee.JPG/1280px-A_small_cup_of_coffee.JPG"
title="Coffee"
/>
<CardContent>
<Typography className={classes.title} color="textSecondary" gutterBottom>
Black coffee
</Typography>
<Typography variant="body2" component="p">
Great to wake up
</Typography>
</CardContent>
<CardActions>
<IconButton
aria-label="Add to cart"
color="primary"
onClick={() => addToCart()}>
<Badge badgeContent={inCart} color="secondary">
<AddShoppingCartIcon/>
</Badge>
</IconButton>
<IconButton
aria-label="Remove from cart"
color="secondary"
disabled={inCart===0}
onClick={() =>setInCart(0)}>
<RemoveShoppingCartIcon/>
</IconButton>
</CardActions>
</Card>
);
}
and use it in the app:
import Coffee from './coffee';
function App() {
return <Coffee/>;
}
You can check out all of the code at https://github.com/lispmachine/electron-webpack-quick-start/tree/coffee-shop.
Stay tuned for more.
Electron and Electron logo are registered trademarks of The OpenJS Foundation™