Electron app with React and Typescript set up using Electron Forge. Features include,
- Typescript
- React, including React Router
- SASS stylesheet
- Static assets
For first time usage, run:
npm install
Execute the following for development and production respectively,
npm start
npm run make
Create a new Electron app with TypeScript and Webpack using the built-in template:
npx create-electron-app my-new-app --template=typescript-webpack
Now, navigate to the project root directory, and executing:
npm start
... you will be prompted with a window of your Electron app, including a Dev Tool, which is super useful for development!
We however do not want the Dev Tool to be visible for production release. Therefore, we adjust the setting in src/index.ts
to have the openDevTools()
executed only in development mode,
// src/index.ts
const isProduction = process.env.NODE_ENV === 'production';
const createWindow = (): void => {
...
// Open the DevTools.
isProduction
? null
: mainWindow.webContents.openDevTools();
We also need to set up the environmental variable using cross-env
:
npm install --save-dev cross-env
// package.json
{
...
"scripts": {
"start": "electron-forge start",
"package": "cross-env NODE_ENV=production electron-forge package",
"make": "cross-env NODE_ENV=production electron-forge make",
"publish": "cross-env NODE_ENV=production electron-forge publish",
"lint": "eslint --ext .ts,.tsx ."
},
...
}
Follow the official Electron Forge guide on integrating React with TypeScript, add the React packages by running,
npm install --save react react-dom
npm install --save-dev @types/react @types/react-dom
Adjust Typescript configuration accordingly,
// tsconfig.json
{
"compilerOptions": {
"jsx": "react",
...
}
}
Create a minimum React component in src/app.tsx
, and import it to the end of src/render.ts
:
// src/render.ts
// Add this to the end of the existing file
import './app';
Instead of injecting React components directly into document.body
(which is a bad practice), we have them render()
into a container element:
<!-- index.html -->
<body>
-- <h1>💖 Hello World!</h1>
-- <p>Welcome to your Electron application.</p>
++ <div id="app"></div>
</body>
// app.tsx
function render() {
ReactDOM.render(<App />, document.getElementById('app'));
}
Install React Router by running:
npm install react-router-dom
Follow the instructions in React Router documentation, replace App()
with the router setup using <HashRouter>
,
// app.tsx
function App() {
return (
<HashRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route path="page1" element={<Page1 />} />
<Route path="page2" element={<Page2 />} />
</Route>
</Routes>
</HashRouter>
);
}
// ... and set up other components accordingly ...
Follow the instructions below to use a scss/sass stylesheet for app development:
npm install --save-dev sass sass-loader
// webpack.main.config.js
module.exports = {
...
resolve: {
extensions: ['.js', '.ts', '.jsx', '.tsx', '.css', '.json', '.scss', '.sass']
},
};
// webpack.renderer.config.jx
rules.push({
test: /\.(sass|scss|css)$/,
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader' },
{ loader: 'sass-loader' },
],
});
Now, rename index.css
as index.sass
and adjust the syntax in the file. Don't forget to import the .sass
stylesheet instead in renderer.ts
too:
// renderer.ts
-- import './index.css';
++ import './index.sass';
Say we'd like to add a static image in our app, serving from the directory src/assets/images
with something like:
// app.tsx
<img
src='./assets/images/inky-wow-1.png'
width={200}
height={'auto'}
/>
... you'd notice that, after running npm start
, the image is not properly rendered. That is because the assets are not correctly set to be bundled with Webpack.
We fix this by using copy-webpack-plugin
:
npm install --save-dev copy-webpack-plugin
... and set up the plugin configuration in webpack.plugins.js
. Note here that we again apply different settings for development and production mode using environmental variable that we set up earlier using cross-env
.
// webpack.plugins.js
const path = require('path');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const isProduction = process.env.NODE_ENV === 'production';
module.exports = [
new ForkTsCheckerWebpackPlugin(),
new CopyWebpackPlugin({
patterns: [
{
from: path.resolve(__dirname, 'src/assets/'),
to: isProduction
? path.resolve(__dirname, '.webpack/renderer/main_window/assets/')
: path.resolve(__dirname, '.webpack/renderer/assets/')
},
]
})
];
Finally, why not have an icon for our application?
We can set this up in package.json
with the configuration for Electron Packager:
// package.json
{
...
"config": {
"forge": {
"packagerConfig": {
"icon": "src/assets/favicon.ico"
},
...
}
Now, run npm run make
to generate your desktop application. You can find the outcome in the directory ./out
. And the app now comes with the icon specified!