I'm trying to get an electron app running with typescript, where i'm able to structure my code. The most useful starting point in the was the first answer in this question: https://stackoverflow.com/a/71078436/3735510
However, i'm unable to extend my app, there's no error reported from typescript but during runtime i receive an Uncaught ReferenceError and the call doesn't work.
This is my folder & file structure:
├── forge.config.ts
├── node_modules (...)
├── package-lock.json
├── package.json
├── src
│ ├── auth
│ │ ├── index.ts
│ │ └── loginhandling.ts
│ ├── index.css
│ ├── index.d.ts
│ ├── index.html
│ ├── index.ts
│ ├── preload.ts
│ └── renderer.ts
├── tsconfig.json
├── webpack.main.config.ts
├── webpack.plugins.ts
├── webpack.renderer.config.ts
├── webpack.rules.ts
└── yarn.lock
This is the forge.config.ts:
import { MakerSquirrel } from '@electron-forge/maker-squirrel';
import { MakerZIP } from '@electron-forge/maker-zip';
import { MakerDeb } from '@electron-forge/maker-deb';
import { MakerRpm } from '@electron-forge/maker-rpm';
import { WebpackPlugin } from '@electron-forge/plugin-webpack';
import { mainConfig } from './webpack.main.config';
import { rendererConfig } from './webpack.renderer.config';
const config: ForgeConfig = {
packagerConfig: {},
rebuildConfig: {},
makers: [new MakerSquirrel({}), new MakerZIP({}, ['darwin']), new MakerRpm({}), new MakerDeb({})],
plugins: [
new WebpackPlugin({
mainConfig,
renderer: {
config: rendererConfig,
entryPoints: [
{
html: './src/index.html',
js: './src/renderer.ts',
name: 'main_window',
preload: {
js: './src/preload.ts',
},
},
],
},
}),
],
};
export default config;
my package.json:
"name": "someName",
"productName": "someName",
"version": "1.0.0",
"description": "My Electron application description",
"main": ".webpack/main",
"scripts": {
"start": "electron-forge start",
"package": "electron-forge package",
"make": "electron-forge make",
"publish": "electron-forge publish",
"lint": "eslint --ext .ts,.tsx ."
},
"keywords": [],
"author": {
"name": "A Name",
"email": "someone@this.org"
},
"license": "MIT",
"devDependencies": {
"@electron-forge/cli": "^6.0.4",
"@electron-forge/maker-deb": "^6.0.4",
"@electron-forge/maker-rpm": "^6.0.4",
"@electron-forge/maker-squirrel": "^6.0.4",
"@electron-forge/maker-zip": "^6.0.4",
"@electron-forge/plugin-webpack": "^6.0.4",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"@vercel/webpack-asset-relocator-loader": "1.7.3",
"css-loader": "^6.0.0",
"electron": "22.1.0",
"eslint": "^8.0.1",
"eslint-plugin-import": "^2.25.0",
"fork-ts-checker-webpack-plugin": "^7.2.13",
"node-loader": "^2.0.0",
"style-loader": "^3.0.0",
"ts-loader": "^9.2.2",
"ts-node": "^10.0.0",
"typescript": "~4.5.4"
},
"dependencies": {
"electron-squirrel-startup": "^1.0.0"
},
"entryPoints": {
"preload": {
"js": "./src/preload.ts"
}
}
}
My tsconfig.json:
"compilerOptions": {
"target": "ES6",
"allowJs": true,
"module": "commonjs",
"skipLibCheck": true,
"esModuleInterop": true,
"noImplicitAny": true,
"sourceMap": true,
"baseUrl": ".",
"outDir": "dist",
"moduleResolution": "node",
"resolveJsonModule": true,
"paths": {
"*": ["node_modules/*"]
}
},
"include": ["src/**/*"],
"files": [
"src/index.d.ts"
]
}
auth/index.ts:
// api exports functions that make up the frontend api, ie that in
// turn either do IPC calls to main for db communication or use
// allowed nodejs features like file i/o.
// Example `my-feature.ts`:
// export const fetchX = async (): Promise<X> => { ... }
export default {
...loginhandling
}
and auth/loginhandling.ts:
export const login = async (): Promise<boolean> => {
console.log("Triggered login");
return true;
}
export const refresh = async (): Promise<boolean> => {
console.log("Triggered refresh");
return true;
}
export const buttonCLicked = async (): Promise<void> => {
console.log(`button clicked, let's do something!`);
}
Here is my index.html:
<html>
<head>
<meta charset="UTF-8" />
<title>Hello World!</title>
</head>
<body>
<h1> Hello World!</h1>
<p>Welcome to your Electron application.</p>
<p>
<button class="button" id="loginButton">Login</button>
<label class="buttonDescription" id="loginButtonDescription">Login details...</label>
</p>
</body>
</html>
Here is my index.ts:
// This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Webpack
// plugin that tells the Electron app where to look for the Webpack-bundled app code (depending on
// whether you're running in development or production).
declare const MAIN_WINDOW_WEBPACK_ENTRY: string;
declare const MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY: string;
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
if (require('electron-squirrel-startup')) {
app.quit();
}
const createWindow = (): void => {
// Create the browser window.
const mainWindow = new BrowserWindow({
height: 720,
width: 1280,
webPreferences: {
contextIsolation: false,
nodeIntegration: true,
preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY
},
});
// and load the index.html of the app.
mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);
// Open the DevTools.
// mainWindow.webContents.openDevTools();
};
// 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.on('ready', 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', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
// On OS X 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();
}
});
// 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 import them here.
the index.d.ts:
declare const auth: typeof import("./auth").default;
my preload.ts:
// https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts
import { contextBridge } from "electron";
import auth from './auth'
contextBridge.exposeInMainWorld("auth", auth);
and the (for me problematic) renderer.ts, currently with two listeners for the same event:
* This file will automatically be loaded by webpack and run in the "renderer" context.
* To learn more about the differences between the "main" and the "renderer" context in
* Electron, visit:
*
* https://electronjs.org/docs/latest/tutorial/process-model
*
* By default, Node.js integration in this file is disabled. When enabling Node.js integration
* in a renderer process, please be aware of potential security implications. You can read
* more about security risks here:
*
* https://electronjs.org/docs/tutorial/security
*
* To enable Node.js integration in this file, open up `main.js` and enable the `nodeIntegration`
* flag:
*
* ```
* // Create the browser window.
* mainWindow = new BrowserWindow({
* width: 800,
* height: 600,
* webPreferences: {
* nodeIntegration: true
* }
* });
* ```
*/
import './index.css';
console.log(' This message is being logged by "renderer.ts", included via webpack');
// auth.login();
// get UI elements and connect
// get button
const loginBttn = <HTMLButtonElement>document.getElementById('loginButton');
// get label
const loginLbl = <HTMLLabelElement>document.getElementById('loginButtonDescription');
loginBttn.addEventListener('click', () => auth.buttonCLicked());
let counter = 0;
loginBttn.addEventListener('click', () => {loginLbl.innerText = `Loging clicked ${++counter} times`});
So the event listener changing the label directly does work, the other one just shows as an error:
The question: How can i get the button click to call the code from auth/loginhandling? To my understanding this is the first step towards other parts of code / long running background tasks that i later want to trigger from buttons in the GUI.