Electron × TypeScript × React × TypeORM
Be my guest at the Electron table, take a seat and order your dishes, I'll be pleased to serve you 🍽️
Published Mar 11, 2019
Tags: Electron Webpack React TypeORM TypeScript
At first I had an idea, since I love papertable RPG I would like to build a desktop app built with electron which players can join a game room and follow game activity (such as life points, currency, possessions…) So,let’s get using electron.
Then I thought about the language I wanted to use and my preference for front-end project is Typescript since it’s built with hard types.
At this point I was thinking about how the data of the different clients can besaved and syncronized easily. I recently started to use TypeORM and I found it powerful, easily configurable and it reminds me about Doctrine and Hibernate.
Finally, for the front-end capabilities I choose React (since it’s the trendy one I know) with webpack and HMR.
Starter
The first thing we need to do is configure every component to work isolately then with all others.
Webpack
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
// a simple entry point
entry: ["./src/index.tsx"],
// configuration for HMR
devServer: {
contentBase: path.join(__dirname, "dist"),
compress: true,
port: 9000,
},
// needed to run correctly all JavaScript especially TypeORM
target: "electron-renderer",
module: {
rules: [
{
test: /\.tsx?$/,
use: "ts-loader",
exclude: /node_modules/,
},
],
},
resolve: {
extensions: [".tsx", ".ts", ".js"],
},
output: {
filename: "[name].js",
path: path.resolve(__dirname, "dist"),
},
plugins: [
new webpack.NormalModuleReplacementPlugin(/typeorm$/, function (result) {
result.request = result.request.replace(/typeorm/, "typeorm/browser");
}),
new webpack.ProvidePlugin({
"window.SQL": "sql.js/js/sql.js",
}),
new HtmlWebpackPlugin({
// this allows us to add a div#app inside the body
template: "index.html",
}),
],
};
TypeScript
{
"compilerOptions": {
"outDir": "./dist/",
"noImplicitAny": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"module": "commonjs",
"target": "es5",
"jsx": "react",
"allowJs": true,
"moduleResolution": "node",
"typeRoots": [
"node_modules/@types"
]
},
"exclude": [
"node_modules"
]
}
TypeORM
{
"type": "sqljs",
"synchronize": true,
"autoSave": true,
"entities": [
Hello
],
"logging": true,
"logger": "advanced-console",
"location": "core-db"
}
package.json
(used by electron)
{
"main": "main.js",
"scripts": {
"prestart": "NODE_ENV=production webpack",
"start": "electron .",
"dev": "NODE_ENV=development concurrently 'sleep 1 && yarn run dev:electron' 'yarn run dev:webpack'",
"dev:electron": "electron .",
"dev:webpack": "webpack-dev-server"
}
}
Main course
The goal here is to allow a component do display specific content using data fetched from the database.
We need to prepare the database connection that will be used inside a component. That’s why, in the entrypoint defined in webpack we need to create it and then use it inside our components using the getRepository
or getConnection
methods provided by TypeORM.
// src/index.tsx
import {createConnection} from "typeorm";
import * as ReactDOM from "react-dom";
import * as React from "react";
import "reflect-metadata";
import Hello from "./entities/Hello";
import HelloComponent from "./component/HelloComponent";
createConnection({
type: "sqljs",
synchronize: true,
autoSave: true,
entities: [Hello],
logging: true,
logger: "advanced-console",
location: "core-db",
}).then(() => {
ReactDOM.render(<HelloComponent id = {1}
/>, document.getElementById("app"));
});
The first component
// src/component/HelloComponent.ts
import * as React from "react";
import * as PropTypes from "prop-types";
interface HelloProps {
id: number;
}
export default class HelloComponent extends React.Component<HelloProps> {
static propTypes = {
id: PropTypes.number.isRequired,
};
state: any = {
firstname: "",
lastname: "",
};
render() {
return (
<div>
Hello
{
this.state.firstname
}
{
this.state.lastname
}
</div>
)
;
}
}
Using the local database
// src/component/HelloComponent.ts
import * as React from "react";
import * as PropTypes from "prop-types";
import {getRepository} from "typeorm";
import Hello from "../entities/Hello";
interface HelloProps {
id: number;
}
export default class HelloComponent extends React.Component<HelloProps> {
static propTypes = {
id: PropTypes.number.isRequired,
};
state: any = {
firstname: "",
lastname: "",
};
async componentDidMount() {
const entity = await getRepository(Hello).findOne({id: this.props.id});
this.setState({
firstname: entity.firstname,
lastname: entity.lastname,
});
}
render() {
return (
<div>
Hello
{
this.state.firstname
}
{
this.state.lastname
}
</div>
)
;
}
}
The electron entrypoint
At last if we want to have hot reload enabled we need to specify at the entrypoint used by electron to use a url or a file.
// main.js
const path = require("path");
const {app, BrowserWindow} = require("electron");
const environment = process.env.NODE_ENV || "production";
let win;
function createWindow() {
win = new BrowserWindow({});
if (environment === "development") {
win.webContents.openDevTools();
win.loadURL("http://0.0.0.0:9000/");
} else {
win.loadFile(path.resolve(__dirname, "dist/index.html"));
}
win.on("closed", () => {
win = null;
});
}
app.on("ready", createWindow);
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
});
app.on("activate", () => {
if (win === null) {
createWindow();
}
});
Dessert
You can clone or fork this repository if you want to start your own project.
Et voilà! 🍒