git clone https://github.com/natancabral/react-js-electron-database.git react-js-electron-database
cd react-js-electron-database
npm install
npm audit fix
npm run start
- Create project
- Run React
- Working with: Babel | read: css-error
- Working with: Concurrently + Wait-on (need nodejs installed to run)
- Database
- MySql
- Sqlite3 (todo)
- AppTray Window
- Tray + NativeImage (todo)
- Packaging App
- Update (npm audit fix, npm update)
- Kill Process
- Test
- Error
First option begin with React
# you need node.js
$ npx create-react-app react-js-electron-sqlite
$ cd react-js-electron-sqlite
# need sudo
$ npm install electron --save-dev
- en: Open package.json file add insert above "scripts"
"main": "main.js",
- en: Insert inside "scripts"
"electron": "electron .",
- en: Copy this script:
const { app, BrowserWindow } = require('electron')
const path = require('path')
//const url = require('url')
function createWindow () {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
//webSecurity: false,
//allowRunningInsecureContent: true,
//preload: path.join(__dirname, 'preload.js')
nodeIntegration: true
}
})
win.loadFile('src/index.html')
//win.loadURL('http://127.0.0.1:3000')
//win.webContents.openDevTools()
}
app.whenReady().then(createWindow)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
I will create a src/ folder and index.html file:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" href="./index.css">
<title>React-js + Electron + Sqlite3</title>
</head>
<body>
<h1>React-js + Electron + Sqlite3</h1>
<!-- you need only one or app, or root -->
<div id="app"></div>
<div id="root"></div>
<script src="./index.js"></script>
</body>
</html>
# need sudo
$ npm install react react-dom
$ npm run electron
Second option begin with Electron
# you need node.js
$ git clone https://github.com/electron/electron-quick-start react-js-electron-sqlite
$ cd react-js-electron-sqlite
$ npm install
$ npm start
$ npm install react react-dom
- I will create a src/ folder
- and move to inside index.html and renderer.js,
- renamed as renderer.js to index.js.
- I will create an index.css and link it to the html. Finally, add an app container div.
- Replace index.html file:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" href="./index.css">
<title>React-js + Electron + Sqlite3</title>
</head>
<body>
<h1>React-js + Electron + Sqlite3</h1>
<!-- you need only one or app, or root -->
<div id="app"></div>
<div id="root"></div>
<script src="./index.js"></script>
</body>
</html>
With a small change to main.js so that it points to the correct file:
.loadFile(path.join(__dirname, 'src', 'index.html'))
and preload.js to index.js
webPreferences: {
preload: path.join(__dirname, 'src', 'index.js')
}
import React from 'react';
import ReactDOM from 'react-dom';
//import App from './App.js'; // <-- App / begin React
import App from './app.js'; // <-- app / begin Electron
window.onload = () => {
ReactDOM.render(<App />, document.getElementById('app'));
};
Transpiling ES6 with Babel 7.
$ npm i @babel/core --save-dev # this will install babel 7.0
$ npm i @babel/preset-env @babel/preset-react --save-dev
$ npm i @babel/plugin-proposal-class-properties
When it runs, it looks for its configuration in a file named .babelrc, so create in on the root and add:
{
"presets": ["@babel/preset-env", "@babel/preset-react"],
"plugins": ["@babel/plugin-proposal-class-properties"]
}
Babel needs to run before any code executes and the best way schedule that is through a build tool
$ npm i gulp gulp-babel --save-dev # basic gulp
$ npm i gulp-concat gulp-clean-css --save-dev # plugins
$ npm i gulp-livereload
# need root/admin
$ npm i -g gulp-cli # best sudo
Create a gulpfile.js at the root of your project and add the tasks
const exec = require('child_process').exec;
const gulp = require('gulp');
const babel = require('gulp-babel');
const css = require('gulp-clean-css');
const livereload = require('gulp-livereload');
//1. Compile HTML file and move them to the app folder
gulp.task('html', () => {
return gulp.src('src/index.html')
.pipe(gulp.dest('app/'))
.pipe(livereload());
});
//2. Compile CSS file and move them to the app folder
gulp.task('css', () => {
return gulp.src('src/**/*.css')
.pipe(css())
.pipe(gulp.dest('app/'))
.pipe(livereload());
});
//3. Compile JS and JSX files and move them to the app folder
gulp.task('js*', () => {
return gulp.src(['main*.js', 'src/**/*.js*'])
.pipe(babel())
.pipe(gulp.dest('app/'))
.pipe(livereload());
});
//4. Compile IMAGES file and move them to the app folder
// ------------------------------------------------------------------------------------ All images inside ./assets/
gulp.task('images', () => {
return gulp.src('src/assets/**/*')
.pipe(gulp.dest('app/assets'))
.pipe(livereload());
})
//5. Watch files
gulp.task('watch', async function() {
livereload.listen();
gulp.watch('src/**/*.html', gulp.series('html'));
gulp.watch('src/**/*.css', gulp.series('css'));
gulp.watch('src/**/*.js*', gulp.series('js*'));
gulp.watch('src/assets/**/*', gulp.series('images'));
});
//6. Send to app folder
gulp.task('build', gulp.series('html', 'css', 'js*', 'images'));
//7. Start the electron process.
gulp.task('start', gulp.series('build', () => {
return exec(
__dirname+'/node_modules/.bin/electron .'
).on('close', () => process.exit());
}));
//0. Default process.
gulp.task('default', gulp.parallel('start', 'watch'));
Insert/change this:
"main": "app/main.js",
And this:
"scripts": {
"electron": "electron .",
"dev": "react-scripts start",
"start": "gulp",
"delete:all": "rm -r ./app",
"postinstall": "install-app-deps",
"build": "gulp build",
"test": "gulp test",
"release": "gulp release"
}
Insert/change this:
// find .loadFile(
// change to
.loadFile(path.join(__dirname, 'app', 'index.html'))
// or
.loadFile('app/index.html')
Babel cant import css files in js or jsx.
import './style.css' show error
You need load css files inside react-context or index.html file.
$ npm run-script start
# or
$ npm run start
- en: Wait-on is a cross-platform command line utility which will wait for files, ports, sockets, and http(s) resources to become available (or not available using reverse mode). Functionality is also available via a Node.js API. Cross-platform - runs everywhere Node.js runs (linux, unix, mac OS X, windows)
$ npm install wait-on
- en: Run multiple commands concurrently
$ npm install concurrently
- en: Insert inside "scripts"
"electron-react": "concurrently \"BROWSER=none npm start\" \"wait-on http://localhost:3000 && electron .\"",
- en: Remember change this: (file)
.loadFile('index.html')
- en: To this: (url)
.loadURL('http://127.0.0.1:3000')
$ npm run-script electron-react
Welcome React-Electron project!
Install package mysql
$ npm install mysql
Import package mysql on component file:
import mysql from 'mysql';
inside same component file:
// set connection variable
const [conn,setConn] = React.useState(undefined);
// function connection mysql remote
const connection = () => {
let c = mysql.createConnection({
//host : '888.88.88.88', //:3306
host : 'localhost',
user : 'root',
password : '',
database : 'databasename'
});
c.connect((err) => {
// in case of error
if(err){
alert( err.code );
return console.log(err.code, err.fatal, err.stack);
}
return console.log('Connection successfully established');
});
setConn(c);
}
// function query/search
const query = () => {
let sql = 'SELECT `name`,`id` FROM `tablename` where id > 0 limit 0,50 ';
conn.query(sql, function (err, results, fields) {
if (err) {
alert(err.code);
console.log(err.code);
}
else {
alert(results);
console.log(results);
if(results.length)
alert(results[0].id + results[0].name);
}
});
// Close the connection
conn.end(function(){
// The connection has been closed
console.log('Connection successfully closed');
});
}
Insert call buttons HTML/React/JSX:
<button onClick={()=>connection()}>Connection</button>
<button onClick={()=>query()}>Query</button>
- en: I will create a main-tray.js file:
const {app, BrowserWindow, Tray, nativeImage, screen } = require('electron')
const path = require('path')
let mainWindow = undefined;;
let tray = undefined;
//app.dock.hide();
function createWindow () {
// Create the browser window.
mainWindow = new BrowserWindow({
width: 300,
height: 600,
//-----tray----- start
show: true, //set this false
frame: false,
fullscreen: false,
resizable: false,
transparent: false,
//-----tray----- end
webPreferences: {
backgroundThrottling: false
}
})
// mainWindow.loadFile('index.html')
mainWindow.loadFile(path.join(__dirname, 'index.html'))
// Open the DevTools.
// mainWindow.webContents.openDevTools()
//-----tray----- start
mainWindow.on('closed',()=>mainWindow = null)
// Hide the window when it loses focus
mainWindow.on('blur', () => {
// if (!mainWindow.webContents.isDevToolsOpened()) {
// mainWindow.hide();
// }
});
//-----tray----- end
}
//-----tray----- start
const createTray = () => {
const icon = path.join(__dirname, 'app', 'assets', 'tray.png')
const nimage = nativeImage.createFromPath(icon)
//tray is a app variable
tray = new Tray(nimage)
tray.on('click', (event)=>toggleWindow())
}
const toggleWindow = () => {
mainWindow.isVisible() ? mainWindow.hide() : showWindow();
}
const showWindow = () => {
const position = getWindowPosition();
mainWindow.setPosition(position.x,position.y, false);
mainWindow.show();
}
const getWindowPosition = () => {
//console.log(process.platform);
//'aix'
//'darwin'
//'freebsd'
//'linux'
//'openbsd'
//'sunos'
//'win32'
const { screenWidth, screenHeight } = screen.getPrimaryDisplay().workAreaSize;
const windowBounds = mainWindow.getBounds();
const trayBounds = tray.getBounds();
// Center window horizontally below the tray icon
const x = Math.round(trayBounds.x + (trayBounds.width / 2) - (windowBounds.width / 2));
let y = undefined;
// Position window 4 pixels vertically below the tray icon
if(process.platform == 'win32')
y = Math.round(trayBounds.y - windowBounds.height);
else
y = Math.round(trayBounds.y + trayBounds.height + 4);
return {x,y}
}
//-----tray----- end
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
//-----tray----- start
createTray()
//-----tray----- end
createWindow()
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
Insert/change this:
"main": "app/main-tray.js",
- en: There are mainly two options for packaging an Electron app and we will go with the second, Electron Builder (the other being Electron Packager).
$ npm i electron-builder --save-dev
We need to point the tool to the folder with the code to be compiled through the package.json by adding:
"build": {
"asar": true,
"extends": null,
"publish": null,
"appId": "com.natancabral.react-js-electron-sqlite3",
"compression": "maximum",
"productName": "File Name App",
"nsis": {
"shortcutName": "App Name Shortcut",
"installerIcon": "./app/assets/ico/icon48.ico",
"oneClick": true,
"perMachine": true,
"allowElevation": true,
"runAfterFinish": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"allowToChangeInstallationDirectory": false
},
"win": {
"target": "nsis",
"icon": "./app/assets/ico/icon48.ico"
},
"directories": {
"buildResources": "./app",
"output": "dist"
},
"files": [
"package.json",
"app/**/*"
]
}
And inside scritps.
"scripts": {
...
"build:dist:1": "electron-builder",
"build:dist:2": "electron-builder --dir",
"build:postinstall": "electron-builder install-app-deps",
}
Plus info.
"version": "1.0.0"
"author": "",
"description": "",
"license": "",
"private": true,
- en: Install electron-packager
$ npm install electron-packager --save-dev
- en: Open package.json and insert inside on scripts:
"scripts": {
...
"packager:win:1": "electron-packager . --overwrite --platform=win32 --arch=ia32 --out=release-builds",
"packager:win:2": "electron-packager . --overwrite --platform=win32 --arch=ia32 --out=release-builds --icon=./app/assets/icons/win/app.ico",
"packager:win:3": "electron-packager . --overwrite --platform=win32 --arch=ia32 --out=release-builds --icon=./app/assets/icons/win/icon.ico --prune=true --version-string.CompanyName=CE --version-string.FileDescription=CE --version-string.ProductName=\"React Electron Sqlite\"",
"packager:mac:1": "electron-packager . --overwrite --platform=darwin --arch=x64 --out=release-builds",
"packager:mac:2": "electron-packager . --overwrite --platform=darwin --arch=x64 --out=release-builds --icon=./app/assets/icons/mac/icon.icns --prune=true",
"packager:mac:3": "electron-packager . --overwrite --platform=darwin --arch=x64 --out=release-builds --icon=./app/assets/icons/mac/app.icns --osx-sign.identity='React Electron Sqlite' --extend-info=assets/mac/info.plist",
"packager:linux:1": "electron-packager . --overwrite --platform=linux --arch=x64 --out=release-builds",
"packager:linux:2": "electron-packager . --overwrite --platform=linux --arch=x64 --out=release-builds --icon=./app/assets/icons/png/1024x1024.png --prune=true",
"packager:sign-exe": "signcode './release-builds/Electron API Demos-win32-ia32/Electron API Demos.exe' --cert ~/electron-api-demos.p12 --prompt --name 'React Electron Sqlite' --url 'http://electron.atom.io'",
"packager:installer": "node ./script/installer.js",
"packager:sign-installer": "signcode './release-builds/windows-installer/ElectronAPIDemosSetup.exe' --cert ~/electron-api-demos.p12 --prompt --name 'React Electron Sqlite' --url 'http://electron.atom.io'",
...
}
$ npm run packager:win:1
$ npm run packager:mac:1
$ npm run packager:linux:1
- en: Install find-process
$ npm install find-process
- en: main.js
const find = require('find-process');
app.on('before-quit', (e) => {
find('port', 3000)
.then(function (list) {
if (list[0] != null) {
console.log('---kill---:', list[0].pid);
process.kill(list[0].pid, 'SIGHUP');
}
})
.catch((e) => {
console.log('---error---',e.stack || e);
});
});
$ npm i jest
$ npm i babel-jest
$ npm i react-test-renderer
- Error: EACCES: permission denied, mkdir '/usr/local/lib/node_modules/electron/.electron' issues
$ sudo npm install -g electron --unsafe-perm=true --allow-root
- Cant remove or update global create-react-app version.
$ sudo rm -rf /usr/local/bin/create-react-app
- Update babel
$ sudo npm install --save-dev @babel/core @babel/cli
$ sudo npx babel-upgrade --write --install # --install optional
- Danger: Permission denied
$ sudo npm install ... --unsafe-perm=true --allow-root # danger
- Gtk-Message: hh:mm:ss.mls: Failed to load module "canberra-gtk-module"
$ sudo apt-get install libcanberra-gtk-module