Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Electron 28 breaks build: ERR_UNKNOWN_FILE_EXTENSION – Unknown file extension ".ts" for … #3568

Open
5 tasks done
slhck opened this issue Dec 19, 2023 · 33 comments
Open
5 tasks done
Labels

Comments

@slhck
Copy link

slhck commented Dec 19, 2023

Prerequisites

  • Using npm
  • Using an up-to-date main branch
  • Using latest version of devtools. Check the docs for how to update
  • Tried solutions mentioned in #400
  • For issue in production release, add devtools output of DEBUG_PROD=true npm run build && npm start

Expected Behavior

Updating electron should make the app start.

Current Behavior

I updated Electron to 28.0.0, then ran npm install. Now:

➜ npm start

> start
> ts-node ./.erb/scripts/check-port-in-use.js && npm run start:renderer


> start:renderer
> cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack serve --config ./.erb/configs/webpack.config.renderer.dev.ts

Starting preload.js builder...
Starting Main Process...
<i> [webpack-dev-server] Project is running at:
<i> [webpack-dev-server] Loopback: http://localhost:1212/
<i> [webpack-dev-server] On Your Network (IPv4): http://192.168.0.119:1212/
<i> [webpack-dev-server] On Your Network (IPv6): http://[fe80::1]:1212/
<i> [webpack-dev-server] Content not from webpack is served from '/Users/werner/Documents/Software/electron-react-boilerplate/public' directory
<i> [webpack-dev-server] 404s will fallback to '/index.html'

> start:main
> cross-env NODE_ENV=development electronmon -r ts-node/register/transpile-only .


> start:preload
> cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.preload.dev.ts

[electronmon] waiting for a change to restart it
App threw an error during load
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for /Users/werner/Documents/Software/electron-react-boilerplate/src/main/main.ts
    at new NodeError (node:internal/errors:405:5)
    at Object.getFileProtocolModuleFormat [as file:] (node:internal/modules/esm/get_format:80:11)
    at defaultGetFormat (node:internal/modules/esm/get_format:125:36)
    at defaultLoad (node:internal/modules/esm/load:89:20)
    at nextLoad (node:internal/modules/esm/loader:163:28)
    at ESMLoader.load (node:internal/modules/esm/loader:603:26)
    at ESMLoader.moduleProvider (node:internal/modules/esm/loader:457:22)
    at new ModuleJob (node:internal/modules/esm/module_job:64:26)
    at ESMLoader.#createModuleJob (node:internal/modules/esm/loader:480:17)
    at ESMLoader.getModuleJob (node:internal/modules/esm/loader:434:34)
[electronmon] uncaught exception occured
[electronmon] waiting for any change to restart the app

Steps to Reproduce

See above.

Possible Solution (Not obligatory)

Not sure what the issue is. I know that Node v18.19.0 broke ts-node with a similar error message, see TypeStrong/ts-node#1997

Latest known good version for Electron is 27.1.3.

Context

N/A

Your Environment

  • Node version : v18.19.0
  • electron-react-boilerplate version or branch : main
  • Operating System and version : macOS
  • Link to your project : N/A
@slhck slhck added the bug label Dec 19, 2023
@ransome1
Copy link

@slhck same here. Did you find a solution meanwhile?

@slhck
Copy link
Author

slhck commented Dec 24, 2023

No, I don't know enough about the internals... I will post a solution should I find one, but in the meantime I'll stay on the last 27 release.

@slhck
Copy link
Author

slhck commented Jan 8, 2024

So the only thing I know is that the error comes from Electron itself, but I can't find any issue with the Electron package referencing that error. It's as if it wasn't passed a compiled JS file, instead trying to load a TS file, and, obviously, failing. So perhaps it's the webpack step that doesn't finish properly? I would think that this is due to the underlying ts-node bug.

@amilajack Any ideas?

@evgenys91
Copy link

Hope this will help somebody until the fix is released, because I've spent more time on this than I expected... This is how I managed to configure the boilerplate with Electron 28

Prerequisites:
Node.js: v18.16.0

Dependencies:
"electron": "^28.1.1",
"electron-builder": "^24.6.4",
"@electron/rebuild": "^3.3.0"

Steps:

  1. Remove ts-node dependency - npm uninstall ts-node

  2. Install tsx package - npm i --save tsx

    Package for TS and ESM compilation, supports both:
    https://www.npmjs.com/package/tsx

  3. In every script defined in "scripts" section of package.json make replace ts-node with node --loader tsx
    For example, my "start" script

    before:
    "start": "ts-node ./.erb/scripts/check-port-in-use.js && npm run start:renderer",
    after:
    "start": "node --loader tsx ./.erb/scripts/check-port-in-use.js && npm run start:renderer",

    For the parts where cross-env is used, you may use NODE_OPTIONS like this:
    "start:main": "cross-env NODE_ENV=development NODE_OPTIONS=\"--loader tsx\" electronmon .",

  4. Repeat step 3 for release/app/package.json

  5. Not sure about this step, but I think this issue appeared after version upgrade. If you see Could not detect abi for version... error, install node-abi version (I have v3.54.0) and add this to package.json resolutions:

    "resolutions": {
      "node-abi": "^3.54.0"
    }
    

@slhck
Copy link
Author

slhck commented Jan 9, 2024

Thanks for providing these steps. When I apply them to this repo, I get:

➜ npm start       

> start
> node --loader tsx ./.erb/scripts/check-port-in-use.js && npm run start:renderer


node:internal/process/esm_loader:40
      internalBinding('errors').triggerUncaughtException(
                                ^
Error: tsx must be loaded with --import instead of --loader
The --loader flag was deprecated in Node v20.6.0 and v18.19.0

Maybe you are running on an older version of Node?

@slhck
Copy link
Author

slhck commented Jan 9, 2024

That said, applying your suggested changes I still get:

➜ npm start

> start
> node --import tsx ./.erb/scripts/check-port-in-use.js && npm run start:renderer


> start:renderer
> cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack serve --config ./.erb/configs/webpack.config.renderer.dev.ts

Starting preload.js builder...
Starting Main Process...
<i> [webpack-dev-server] Project is running at:
<i> [webpack-dev-server] Loopback: http://localhost:1212/
<i> [webpack-dev-server] On Your Network (IPv4): http://192.168.0.119:1212/
<i> [webpack-dev-server] On Your Network (IPv6): http://[fe80::1]:1212/
<i> [webpack-dev-server] Content not from webpack is served from '/Users/werner/Documents/Software/electron-react-boilerplate/public' directory
<i> [webpack-dev-server] 404s will fallback to '/index.html'

> start:main
> cross-env NODE_ENV=development NODE_OPTIONS="--import tsx" electronmon .


> start:preload
> cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.preload.dev.ts

[electronmon] waiting for a change to restart it
App threw an error during load
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for /Users/werner/Documents/Software/electron-react-boilerplate/src/main/main.ts
    at __node_internal_captureLargerStackTrace (node:internal/errors:496:5)
    at new NodeError (node:internal/errors:405:5)
    at Object.getFileProtocolModuleFormat [as file:] (node:internal/modules/esm/get_format:80:11)
    at defaultGetFormat (node:internal/modules/esm/get_format:125:36)
    at defaultLoad (node:internal/modules/esm/load:89:20)
    at nextLoad (node:internal/modules/esm/loader:163:28)
    at ESMLoader.load (node:internal/modules/esm/loader:603:26)
    at ESMLoader.moduleProvider (node:internal/modules/esm/loader:457:22)
    at new ModuleJob (node:internal/modules/esm/module_job:64:26)
    at #createModuleJob (node:internal/modules/esm/loader:480:17)
    at ESMLoader.getModuleJob (node:internal/modules/esm/loader:434:34)
[electronmon] uncaught exception occured
[electronmon] waiting for any change to restart the app

@evgenys91
Copy link

evgenys91 commented Jan 9, 2024

Yes, as I specified before, I'm using older version of Node.js - v18.16.0
I guess you may try to make it work with newer version, but steps will be different.

Also I noticed that your start:renderer script is not using NODE_OPTIONS I mentioned in the step 3 (same for start:preload)

@slhck
Copy link
Author

slhck commented Jan 9, 2024

I can confirm that your suggestions work with older versions of Node, where one has to use --loader. It breaks with v18.18 onwards, which also makes tsx ask for the --import option instead of --loader.

And once I use --import, it no longer works. Specifically, the error only affects npm run start:main. I feel like the loader part does not get forwarded properly to electron itself when using these newer Node versions.

Curiously, when I switch to Node v18.19.0, and use the old Electron version (v26.2.1) as shipped with this repo, and I try to use the --import option with tsx, I get:

➜ npm run start:main

> start:main
> cross-env NODE_ENV=development NODE_OPTIONS="--import tsx" electronmon .

[electronmon] waiting for a change to restart it
electron: --import is not allowed in NODE_OPTIONS
[electronmon] app exited with code 9, waiting for change to restart it

I am a bit lost here; I don't know enough about Node's internals, e.g. how hooks work.

@davidliudev
Copy link

Same issue here. Have to stay at v27 for now

@dsanders11
Copy link

@slhck, it looks like esbuild-register works as an alternative to ts-node in my quick testing, here's a branch that makes the necessary changes: https://github.com/dsanders11/electron-react-boilerplate/tree/esbuild

If you want to test it and let me know if that resolves your issues, I could open a PR on this repo with the changes.

@slhck
Copy link
Author

slhck commented Jan 20, 2024

@dsanders11 Awesome, that works great! I didn't know about esbuild-register. Tested and working with Node v18.19.0.

I should add that it prints a warning:

(node:86715) ExperimentalWarning: --experimental-loader may be removed in the future; instead use register():
--import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register("esbuild-register/loader", pathToFileURL("./"));'

Would be great if you could open a PR!

@Kocicak
Copy link

Kocicak commented Feb 2, 2024

The fix with esbuild-register is not working for me. I am using typeorm and there is an error with decorators not being supported yet. I am stuck with electron 27 for now.

@CristianDeluxe
Copy link

Following the great contribution of dsanders11 I had to change more things in the scripts section of the "package.json" file to get everything to work correctly:

"scripts": {
    "build": "concurrently \"npm run build:main\" \"npm run build:renderer\"",
    "build:dll": "cross-env NODE_ENV=development NODE_OPTIONS=\"--loader esbuild-register/loader -r esbuild-register\" webpack --config ./.erb/configs/webpack.config.renderer.dev.dll.ts",
    "build:main": "cross-env NODE_ENV=production NODE_OPTIONS=\"--loader esbuild-register/loader -r esbuild-register\" webpack --config ./.erb/configs/webpack.config.main.prod.ts",
    "build:renderer": "cross-env NODE_ENV=production NODE_OPTIONS=\"--loader esbuild-register/loader -r esbuild-register\" webpack --config ./.erb/configs/webpack.config.renderer.prod.ts",
    "postinstall": "node -r esbuild-register .erb/scripts/check-native-dep.js && electron-builder install-app-deps && npm run build:dll",
    "lint": "cross-env NODE_ENV=development eslint . --ext .js,.jsx,.ts,.tsx",
    "package": "node -r esbuild-register ./.erb/scripts/clean.js dist && npm run build && electron-builder build --publish never && npm run build:dll",
    "rebuild": "electron-rebuild --parallel --types prod,dev,optional --module-dir release/app",
    "start": "node -r esbuild-register ./.erb/scripts/check-port-in-use.js && npm run start:renderer",
    "start:main": "cross-env NODE_ENV=development NODE_OPTIONS=\"--loader esbuild-register/loader -r esbuild-register\" electronmon .",
    "start:preload": "cross-env NODE_ENV=development NODE_OPTIONS=\"--loader esbuild-register/loader -r esbuild-register\" webpack --config ./.erb/configs/webpack.config.preload.dev.ts",
    "start:renderer": "cross-env NODE_ENV=development NODE_OPTIONS=\"--loader esbuild-register/loader -r esbuild-register\" webpack serve --config ./.erb/configs/webpack.config.renderer.dev.ts",
    "test": "jest"
  },

@slhck
Copy link
Author

slhck commented Feb 4, 2024

Be aware that I found some inconsistencies with how esbuild-register treats TypeScript code that is working/transpiled without issues using tools like ts-node, tsx, and tsc. This is a minimal reproducible example: https://github.com/slhck/esbuild-register-test

So, at the moment I cannot recommend using esbuild-register because it does not error while transpiling, instead producing code that silently breaks during runtime, outputting undefined in places where your local tsc check would not flag any errors.

Note that this only applies to library versions before ES2022. So the change made here a few months ago should make tsc throw errors for this in your project. My fork of electron-react-boilerplate is older so it wasn't catching that.

@dsanders11
Copy link

at the moment I cannot recommend using esbuild-register because it does not error while transpiling

That was an oversight by me - esbuild does not do typechecking, so it needs to be used in combination with tsc -noEmit if you want that.

Be aware that I found some inconsistencies with how esbuild-register treats TypeScript code that is working/transpiled without issues using tools like ts-node, tsx, and tsc.

That sounds like a potential issue to open upstream on esbuild, since you have a minimal repro case for ES2021., although I don't know how likely that particular case is to be fixed there. The esbuild site lists a handful of caveats around its TypeScript support, so there are various other differences from ts-node/tsc which might bite folks.

Given that, not sure switching to esbuild is a good idea in practice. Since this repo only really needs the ts-node/register functionality for the main process, and Webpack is already used (and for the prod output for the main process), it should probably be refactored to use Webpack for the main process during dev as well and tweak electronmon to trigger on the Webpack output for the main process (and use Webpack in watch mode to rebuild on changes).

I don't have time to look into the above, but that might be a better long-term solution for the ESM issue starting with E28 since it would sidestep the upstream ts-node issue.

@slhck
Copy link
Author

slhck commented Feb 5, 2024

Thanks for the reference. Indeed the breakage I am observing has to do with the class field semantics. So it's not a bug.

I agree that simply using webpack to trigger transpilation would be the best way going forward.

@elvince
Copy link

elvince commented Feb 7, 2024

Hi,

First, thanks all for sharing your experience for setuping this repository with the new electron version.
I'm not an expert in the build chain and I'm a bit lost on whether or not you finally found a way to migrate safely to v28+?

Is there any plan to merge/PR something on the main branch on this project so everyone can have a stable, battle tested config up to date?
Thanks,

@slhck
Copy link
Author

slhck commented Feb 7, 2024

No, there is no reliable solution. esbuild-register works, but requires a few adaptations in package.json. Even then it prints warnings. I wouldn't recommend it for now.

The best path forward, it seems, would be this:

  • ts-node has to be removed entirely from this repo.
  • webpack is triggered for compiling the main part during development upon save, which would make Electron load a .js file rather than relying upon a Node loader.

I am not sure when I will have time to look into this. Unfortunately no word from the maintainer yet, either. (I am a bit tired of providing PRs without an indication of them being merged … the last commit to this repo was 5 months ago.)

@elvince
Copy link

elvince commented Feb 7, 2024

Thanks for the insight.

I hope you will be able to find some time to share a solution. Are there any other repo that provide end to end integration like this one?

I hope the maintainer will react on this topic quickly as the 26 version will be unsupported soon and 27 in few months.
We need to find solutions. If I can help in any way, I will. but clearly all those stuffs are out of my current knowledge, I able to tweak a ready config but not to create a new one :(

@elvince
Copy link

elvince commented Feb 8, 2024

@slhck
I did dig into Electron-Forge and the setup for a blanck project is straight forward and works with the latest Electron version out of the box.
The setup is not as complete as this one, but it might a good start to fix this repo?
Or didn't they use the same approach?
Thanks

@singhparminder02
Copy link

singhparminder02 commented Feb 18, 2024

I ran into this issue yesterday and I was able to get it working with the following changes:

  1. In the start script, replace ts-node with tsx.
  2. In the start:main script, add NODE_OPTION flag and minor changes to the electronmon arguments.

Here is what the updated version of these scripts look like:

"start": "tsx ./.erb/scripts/check-port-in-use.js && npm run start:renderer"
"start:main": "cross-env NODE_ENV=development NODE_OPTIONS=\"--loader tsx\" electronmon ."

I haven't exhaustively checked for the side-effects but the development environment is working fine i.e. the files are getting transpiled and on code change, the latest changes are loaded automatically.

A few points to note:

  1. My postinstall script still uses ts-node. Will get around to this soon.
  2. My package script still uses ts-node. Will get around to this soon.
  3. I had to install tsx as a dev dependency.

My Environment

Node version : v20.5.0
Electron: 28.2.3
electron-react-boilerplate version or branch : main
Operating System and version : macOS

I hope this helps developers who stumble onto this thread in future.

@Zagrios
Copy link

Zagrios commented Feb 26, 2024

Hey, I managed to get it working with Electron 28 using esbuild-register. However, after updating to Electron 29.0.1, I encountered an Cannot find module error. 🫠
Does anyone else have encountered this issue and managed to resolve it?

"scripts": {
        "build": "concurrently \"npm run build:main\" \"npm run build:renderer\"",
        "build:dll": "cross-env NODE_ENV=development webpack --config ./.erb/configs/webpack.config.renderer.dev.dll.ts",
        "build:main": "cross-env NODE_ENV=production webpack --config ./.erb/configs/webpack.config.main.prod.ts",
        "build:renderer": "cross-env NODE_ENV=production webpack --config ./.erb/configs/webpack.config.renderer.prod.ts",
        "postinstall": "node -r esbuild-register .erb/scripts/check-native-dep.js && electron-builder install-app-deps && npm run build:dll",
        "rebuild": "electron-rebuild --parallel --types prod,dev,optional --module-dir release/app",
        "lint": "cross-env NODE_ENV=development eslint . --ext .js,.jsx,.ts,.tsx",
        "package": "node -r esbuild-register ./.erb/scripts/clean.js dist && npm run build && electron-builder build --publish never && npm run build:dll",
        "start": "node -r esbuild-register ./.erb/scripts/check-port-in-use.js && npm run start:renderer",
        "start:main": "cross-env NODE_ENV=development NODE_OPTIONS=\"--loader esbuild-register/loader -r esbuild-register\" electronmon .",
        "start:preload": "cross-env NODE_ENV=development webpack --config ./.erb/configs/webpack.config.preload.dev.ts",
        "start:renderer": "cross-env NODE_ENV=development webpack serve --config ./.erb/configs/webpack.config.renderer.dev.ts",
        "test": "jest",
        "publish": "npm run build && electron-builder -c.win.certificateSha1=842a817a51e2a1d360fcd62f54bf5f9193e919e1 --publish always --win --x64"
    },

image

@1111mp
Copy link

1111mp commented Mar 5, 2024

I ran into this issue yesterday and I was able to get it working with the following changes:

  1. In the start script, replace ts-node with tsx.
  2. In the start:main script, add NODE_OPTION flag and minor changes to the electronmon arguments.

Here is what the updated version of these scripts look like:

"start": "tsx ./.erb/scripts/check-port-in-use.js && npm run start:renderer" "start:main": "cross-env NODE_ENV=development NODE_OPTIONS=\"--loader tsx\" electronmon ."

I haven't exhaustively checked for the side-effects but the development environment is working fine i.e. the files are getting transpiled and on code change, the latest changes are loaded automatically.

A few points to note:

  1. My postinstall script still uses ts-node. Will get around to this soon.
  2. My package script still uses ts-node. Will get around to this soon.
  3. I had to install tsx as a dev dependency.

My Environment

Node version : v20.5.0 Electron: 28.2.3 electron-react-boilerplate version or branch : main Operating System and version : macOS

I hope this helps developers who stumble onto this thread in future.

Because webpack needs to use ts-node when using typescript as the configuration language: webpack-configuration-languages. And currently it does not support ESModule, this will be a hindrance when we move fully to ESModule.

One more thing, it seems that node will no longer support the use of custom ESM Loader(you will get this warning):

ExperimentalWarning: Custom ESM Loaders is an experimental feature and might change at any time

Well it doesn't "load" typescript sources at runtime, it resolves them in typescript, stores them somewhere and then redirects require() calls to them, most likely achieved by monkey-patching require(). Which is of course not possible in ESM since import is a keyword and not a symbol.
The solution is to explicitly have a ts -> js transformation step in your build pipeline and drop ts-node from runtime dependencies.

As mentioned here, after electron v28.0.0 switches to esmodule we will no longer be able to use ts-node to compile ts -> js at runtime. Instead, ts should be compiled into js before running. The simplest way is that we can use webpack or tsc to compile ts into js in advance and write it to a disk file, and then execute electron. to start the service (as far as I know electron-forge uses this method).

  • Write a webpack.config.main.dev.ts for the main process code (the output path must be consistent with the main attribute of package.json
  • Change start:main command
"start:main": "cross-env NODE_ENV=development  TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.main.dev.ts && electronmon .",

(If you care about the time-consuming compilation, you can use swc-loader to speed up the compilation process.)

I've tried this solution myself and everything works fine. But when I fully migrated to esmodule, webpack started to complain: The webpack.config.ts file could not be recognized. Because webpack does not yet support esmodule when using typescript as the configuration language.

So eventually I started to give up ts-node & webpack and turned to vite and esbuild for a good development experience. At the same time, I also retained the advantages of electron-react-boilerplate. If you are interested you can view the current complete project code: https://github.com/1111mp/nvm-desktop. Currently everything is working fine.

(This comment does not in any way imply that electron-react-boilerplate is obsolete. In fact, all this has nothing to do with electron-react-boilerplate itself, because the problem that causes incompatibility with electron v28.0.0 and above is caused by the tools that its upstream depends on. The design of electron-react-boilerplate is still very good.)

@slhck
Copy link
Author

slhck commented Mar 6, 2024

Thanks for your comment!

Write a webpack.config.main.dev.ts for the main process code

That sounds reasonable, although it was far from obvious to me how that would look like. I played around a little, and what I did was the following. Create a file ./erb/configs/webpack.config.main.dev.ts:

/**
 * Webpack config for production electron main process
 */

import path from 'path';
import webpack from 'webpack';
import { merge } from 'webpack-merge';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import baseConfig from './webpack.config.base';
import webpackPaths from './webpack.paths';
import checkNodeEnv from '../scripts/check-node-env';
import { getContentScriptEntries } from './getContentScriptEntries';

// When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's
// at the dev webpack config is not accidentally run in a production environment
if (process.env.NODE_ENV === 'production') {
  checkNodeEnv('development');
}

const configuration: webpack.Configuration = {
  devtool: 'inline-source-map',

  mode: 'development',

  target: 'electron-main',

  entry: {
    ...getContentScriptEntries(),
    main: path.join(webpackPaths.srcMainPath, 'main.ts'),
    // preload: path.join(webpackPaths.srcMainPath, 'preload.ts'),
    // TODO merge this with the preload-file ...
  },

  output: {
    path: webpackPaths.dllPath,
    filename: '[name].bundle.dev.js',
    library: {
      type: 'umd',
    },
  },

  plugins: [
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore
    new BundleAnalyzerPlugin({
      analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled',
      analyzerPort: 8888,
    }),

    new webpack.DefinePlugin({
      'process.type': '"browser"',
    }),
  ],

  /**
   * Disables webpack processing of __dirname and __filename.
   * If you run the bundle in node.js it falls back to these values of node.js.
   * https://github.com/webpack/webpack/issues/2010
   */
  node: {
    __dirname: false,
    __filename: false,
  },
};

export default merge(baseConfig, configuration);

Now change package.json to point to the newly generated bundle JS file:

diff --git a/package.json b/package.json
index 893ca68..d3dbe0f 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,7 @@
   "version": "0.9.0",
-  "main": "./src/main/main.ts",
+  "main": "./.erb/dll/main.bundle.dev.js",
   "private": true,
   "scripts": {
     "build": "concurrently \"yarn run build:main\" \"yarn run build:renderer\"",
@@ -24,7 +24,7 @@
     "package:all": "yarn run prepackage && yarn run package:linux && yarn run package:mac && yarn run package:win",
     "rebuild": "electron-rebuild --parallel --types prod,dev,optional --module-dir release/app",
     "start": "ts-node ./.erb/scripts/check-port-in-use.js && yarn run start:renderer",
-    "start:main": "cross-env NODE_ENV=development electronmon --inspect=5858 -r ts-node/register/transpile-only .",
+    "start:main": "cross-env NODE_ENV=development  TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.main.dev.ts && electronmon .",
     "start:preload": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.preload.dev.ts",
     "start:renderer": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack serve --config ./.erb/configs/webpack.config.renderer.dev.ts",
     "test": "yarn run test:unit && yarn run test:e2e",

I also had to change a few places in my application that looked like this — due to outputting the bundle in .erb/dll now:

diff --git a/src/main/helpers/paths.ts b/src/main/helpers/paths.ts
index 80b9b34..db33e96 100644
--- a/src/main/helpers/paths.ts
+++ b/src/main/helpers/paths.ts
@@ -3,7 +3,7 @@ import path from 'path';
 
 export const RESOURCES_PATH = app.isPackaged
   ? path.join(process.resourcesPath, 'assets')
-  : path.join(__dirname, '../../../assets');
+  : path.join(__dirname, '../../assets');
 
 export function getAssetPath(...paths: string[]) {
   return path.join(RESOURCES_PATH, ...paths);

This works, even with hot-reloading, and I have had no issues launching the app with Electron 29 and Node v18.19.0.

This comment does not in any way imply that electron-react-boilerplate is obsolete

The only thing that irks me is that there is no official comment from the maintainers yet. The package is lagging behind and v27 will be unsupported as of April 16, 2024. Of course, I understand it's free, open-source software and there's no guarantee to receive any support whatsoever. But given the various solutions proposed here, an official comment regarding what would be the recommended solution would be good.

@remigarcia
Copy link

@slhck first, thanks for working on this.

I tried your solution in your last post, but it's not working for me because of a native dependency (keytar) of my Electron app.

At start I get :

App threw an error during load
Error: Cannot find module 'keytar'
Require stack:
- C:\Users\user\Projets\myproject\app\.erb\dll\main.bundle.dev.js
    at Module._resolveFilename (node:internal/modules/cjs/loader:1084:15)
    at s._resolveFilename (node:electron/js2c/browser_init:2:114421)
    at Module._load (C:\Users\user\Projets\myproject\app\node_modules\runtime-required\runtime-required.js:28:44)
    at Module.require (node:internal/modules/cjs/loader:1150:19)
    at require (node:internal/modules/cjs/helpers:119:18)
    at webpackUniversalModuleDefinition (C:\Users\user\Projets\myproject\app\.erb\dll\main.bundle.dev.js:3:28)
    at Object.<anonymous> (C:\Users\user\Projets\myproject\app\.erb\dll\main.bundle.dev.js:10:3)
    at Module._compile (node:internal/modules/cjs/loader:1271:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1326:10)
    at Module.load (node:internal/modules/cjs/loader:1126:32)

And it cannot be installed in main package, the dependency is only installed in the release/app/package.json

@jgresham
Copy link
Contributor

jgresham commented Apr 4, 2024

I am thinking about making the jump to electron's new official tool for a lot of this electron-forge. There is a webpack + typescript template which works with a react + typescript front-end that should be easiest to convert to at first. Although, they have a vite + typescript plugin and it might be worth it long-term to also switch to vite.

Does anyone see potential issues with converting to a electron-forge + webpackge + typescript setup? If not, I will probably give it a try soon.

@albireox
Copy link

albireox commented Apr 4, 2024

For what is worth, I did that migration recently, to the electron-forge boilerplate with the vite-typescript template, and the transition was surprisingly smooth. I hardly had to change anything serious in my code, other than to specify in the configuration that I wanted different directories for main and renderer. I'm not an expert in webpack, but I found the vite configuration much easier to understand and modify, which I was never able to do reliably with webpack.

@jgresham
Copy link
Contributor

jgresham commented Apr 4, 2024

For what is worth, I did that migration recently, to the electron-forge boilerplate with the vite-typescript template, and the transition was surprisingly smooth. I hardly had to change anything serious in my code, other than to specify in the configuration that I wanted different directories for main and renderer. I'm not an expert in webpack, but I found the vite configuration much easier to understand and modify, which I was never able to do reliably with webpack.

thanks for the push! I used your repo to help get things working... thanks. Also my dev environment feels much quickier and more snappy. For everyone else, I'll tag my PR here so y'all can see the file changes

@ifree92
Copy link

ifree92 commented Apr 13, 2024

That sounds reasonable, although it was far from obvious to me how that would look like. I played around a little, and what I did was the following. Create a file ./erb/configs/webpack.config.main.dev.ts:

Your solution worked for me too. The only thing I want to adopt is to make a hot reload of electronmon work. Currently, if any changes happen in src/main/main.ts (and all imports tree), it requires a manual restart of the npm start command.

I tried your solution in your last post, but it's not working for me because of a native dependency (keytar) of my Electron app.

I also have a native dependency (better-sqlite3).
To make it work, you need to create a symlink of release/app/node_modules into .erb/dll/node_modules or at least to .erb/node_modules.

As you see, the boilerplate already has a script: https://github.com/electron-react-boilerplate/electron-react-boilerplate/blob/main/.erb/scripts/link-modules.ts that links release/app/node_modules into src/node_modules to make native dependencies reachable. Since within the proposed changes the entry point moved from the src/main/main.ts to .erb/dll/main.bundle.dev.js, you might need to put your native dependencies somewhere in the hierarchy before reaching the root path of the project.

So, the following changes worked for me:

.erb/scripts/link-modules.ts:

import fs from 'fs';
import webpackPaths from '../configs/webpack.paths';

const { srcNodeModulesPath, appNodeModulesPath, erbNodeModulesPath } = webpackPaths;

if (!fs.existsSync(srcNodeModulesPath) && fs.existsSync(appNodeModulesPath)) {
  fs.symlinkSync(appNodeModulesPath, srcNodeModulesPath, 'junction');
}

if (!fs.existsSync(erbNodeModulesPath) && fs.existsSync(appNodeModulesPath)) {
  fs.symlinkSync(appNodeModulesPath, erbNodeModulesPath, 'junction');
}

.erb/configs/webpack.paths.ts:

const path = require('path');

const rootPath = path.join(__dirname, '../..');

const erbPath = path.join(__dirname, '..');
const erbNodeModulesPath = path.join(erbPath, 'node_modules');

const dllPath = path.join(__dirname, '../dll');

const srcPath = path.join(rootPath, 'src');
const srcMainPath = path.join(srcPath, 'main');
const srcRendererPath = path.join(srcPath, 'renderer');

const releasePath = path.join(rootPath, 'release');
const appPath = path.join(releasePath, 'app');
const appPackagePath = path.join(appPath, 'package.json');
const appNodeModulesPath = path.join(appPath, 'node_modules');
const srcNodeModulesPath = path.join(srcPath, 'node_modules');

const distPath = path.join(appPath, 'dist');
const distMainPath = path.join(distPath, 'main');
const distRendererPath = path.join(distPath, 'renderer');

const buildPath = path.join(releasePath, 'build');

export default {
  rootPath,
  erbNodeModulesPath,
  dllPath,
  srcPath,
  srcMainPath,
  srcRendererPath,
  releasePath,
  appPath,
  appPackagePath,
  appNodeModulesPath,
  srcNodeModulesPath,
  distPath,
  distMainPath,
  distRendererPath,
  buildPath,
};

I tried to play around with electron-forge, but for me, it requires too much work to move the codebase to match their configuration. I'm deeply using electron-builder, its publishers, configured signing & notarizing of macOS builds, assembling via NSIS the app for Windows, configured unit tests, etc. I might need weeks to migrate it 😆
Will try to stay with the existing boilerplate.

Also tried to play around with electron-vite, but the main pitfall is that it does not support typescript decorators, but I'm using typeorm together with better-sqlite3.

The bad thing is that there's no good solution at the moment, it is time to invent your own wheels! 🚴

@jgresham
Copy link
Contributor

jgresham commented Apr 13, 2024

fyi, if it helps, here is my PR NiceNode/nice-node#536
Changes:
electron-buildersuite -> electron-forge
webpack -> vite
eslint, babel & prettier -> biome,
jest -> vitest

I agree, it does take a week+ to fully migrate. I suggest wait as long as you can until you have to switch.

@1111mp
Copy link

1111mp commented Apr 13, 2024

Also tried to play around with electron-vite, but the main pitfall is that it does not support typescript decorators, but I'm using typeorm together with better-sqlite3.

@ifree92 You can check out the documentation here for support for the typescript decorator: electron-vite typescript-decorator

Or you can use tsup as the build tool to compile your code (tsup uses esbuild, but falls back to swc when encountering typescript decorators). Currently I am using tsup in my project (with some custom development work based on electron-vite). You can check the code of this repository if necessary: https://github.com/1111mp/electron-vite-tsup

Hope it helps. 😄

@n-gist
Copy link

n-gist commented May 8, 2024

Your solution worked for me too. The only thing I want to adopt is to make a hot reload of electronmon work. Currently, if any changes happen in src/main/main.ts (and all imports tree), it requires a manual restart of the npm start command.

@ifree92 Did you found how to fix this? It reloads renderer process only, no matter what main/ or renderer/ file was changed

@491239
Copy link

491239 commented May 14, 2024

Thanks for your comment!

Write a webpack.config.main.dev.ts for the main process code

That sounds reasonable, although it was far from obvious to me how that would look like. I played around a little, and what I did was the following. Create a file ./erb/configs/webpack.config.main.dev.ts:

/**
 * Webpack config for production electron main process
 */

import path from 'path';
import webpack from 'webpack';
import { merge } from 'webpack-merge';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import baseConfig from './webpack.config.base';
import webpackPaths from './webpack.paths';
import checkNodeEnv from '../scripts/check-node-env';
import { getContentScriptEntries } from './getContentScriptEntries';

// When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's
// at the dev webpack config is not accidentally run in a production environment
if (process.env.NODE_ENV === 'production') {
  checkNodeEnv('development');
}

const configuration: webpack.Configuration = {
  devtool: 'inline-source-map',

  mode: 'development',

  target: 'electron-main',

  entry: {
    ...getContentScriptEntries(),
    main: path.join(webpackPaths.srcMainPath, 'main.ts'),
    // preload: path.join(webpackPaths.srcMainPath, 'preload.ts'),
    // TODO merge this with the preload-file ...
  },

  output: {
    path: webpackPaths.dllPath,
    filename: '[name].bundle.dev.js',
    library: {
      type: 'umd',
    },
  },

  plugins: [
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore
    new BundleAnalyzerPlugin({
      analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled',
      analyzerPort: 8888,
    }),

    new webpack.DefinePlugin({
      'process.type': '"browser"',
    }),
  ],

  /**
   * Disables webpack processing of __dirname and __filename.
   * If you run the bundle in node.js it falls back to these values of node.js.
   * https://github.com/webpack/webpack/issues/2010
   */
  node: {
    __dirname: false,
    __filename: false,
  },
};

export default merge(baseConfig, configuration);

Now change package.json to point to the newly generated bundle JS file:

diff --git a/package.json b/package.json
index 893ca68..d3dbe0f 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,7 @@
   "version": "0.9.0",
-  "main": "./src/main/main.ts",
+  "main": "./.erb/dll/main.bundle.dev.js",
   "private": true,
   "scripts": {
     "build": "concurrently \"yarn run build:main\" \"yarn run build:renderer\"",
@@ -24,7 +24,7 @@
     "package:all": "yarn run prepackage && yarn run package:linux && yarn run package:mac && yarn run package:win",
     "rebuild": "electron-rebuild --parallel --types prod,dev,optional --module-dir release/app",
     "start": "ts-node ./.erb/scripts/check-port-in-use.js && yarn run start:renderer",
-    "start:main": "cross-env NODE_ENV=development electronmon --inspect=5858 -r ts-node/register/transpile-only .",
+    "start:main": "cross-env NODE_ENV=development  TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.main.dev.ts && electronmon .",
     "start:preload": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.preload.dev.ts",
     "start:renderer": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack serve --config ./.erb/configs/webpack.config.renderer.dev.ts",
     "test": "yarn run test:unit && yarn run test:e2e",

I also had to change a few places in my application that looked like this — due to outputting the bundle in .erb/dll now:

diff --git a/src/main/helpers/paths.ts b/src/main/helpers/paths.ts
index 80b9b34..db33e96 100644
--- a/src/main/helpers/paths.ts
+++ b/src/main/helpers/paths.ts
@@ -3,7 +3,7 @@ import path from 'path';
 
 export const RESOURCES_PATH = app.isPackaged
   ? path.join(process.resourcesPath, 'assets')
-  : path.join(__dirname, '../../../assets');
+  : path.join(__dirname, '../../assets');
 
 export function getAssetPath(...paths: string[]) {
   return path.join(RESOURCES_PATH, ...paths);

This works, even with hot-reloading, and I have had no issues launching the app with Electron 29 and Node v18.19.0.

This comment does not in any way imply that electron-react-boilerplate is obsolete

The only thing that irks me is that there is no official comment from the maintainers yet. The package is lagging behind and v27 will be unsupported as of April 16, 2024. Of course, I understand it's free, open-source software and there's no guarantee to receive any support whatsoever. But given the various solutions proposed here, an official comment regarding what would be the recommended solution would be good.

This works well for me.Thanks,

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.