pulsar icon indicating copy to clipboard operation
pulsar copied to clipboard

Make it easier (or harder) to require `@electron/remote`

Open savetheclocktower opened this issue 2 months ago • 1 comments

It used to be that a package could do

const { remote } = require('electron');

and it would Just Work, even if the package did not declare electron as a dependency. The Electron codebase makes this work, I think, by short-circuiting the module resolution algorithm so that it just returns "electron" instead of a full path to a specific place on disk; in this way I think it ends up acting similar to a Node built-in.

But starting with Electron 13, require('electron').remote is deprecated; the equivalent functionality now lives in its own package called @electron/remote. There is, as far as I can tell, no “magic” associated with this package; if a third-party Pulsar package wants to depend on @electron/remote, it's got to install it as a dependency. (Built-in packages are saved from this because Pulsar's own node_modules folder is part of the chain of folders consulted when trying to resolve a package's dependencies. I think.)

Or, well, not necessarily; since Pulsar itself depends on @electron/remote, it's able to shim old references to require('electron').remote to work properly, and to add a deprecation message so package authors know to update their imports. Nice work, @mauricioszabo! But this creates a perverse incentive for a package author to purposefully use a deprecated tactic, since it ends up being less work than the “correct” approach.

So we should decide: do we want @electron/remote to be treated like a regular package?

Argument for

@electron/remote is a hack and is discouraged by the Electron team, which is probably why it's no longer in the core electron module. So we should discourage people from using it, including with speed bumps like these.

If we agree with this, that's fine — but it means we should envision all the things people might need @electron/remote for, and then build preferred APIs for those things. Right now, a handful of our own built-in packages use @electron/remote, so we should clean up our own house.

Argument against

If package authors are going to use @electron/remote, they might as well use the version that we've already required; that'll ensure they're using a version that's compatible with the version of Electron we're using. So we should discourage them from installing their own version and try to treat imports of @electron/remote magically, as already happens with electron.

Amazingly, we could do this. We inject some logic into the module resolution algorithm, so we could detect requests to import @electron/remote and always direct them to the absolute path of our own module. Feels a bit gross, though? (Maybe we could offer up our copy only if resolution will otherwise fail; that way we don't get in the way of packages that have explicitly installed @electron/remote as a dependency.)

“Chaotic neutral” argument

We could just allow people to keep doing require('electron').remote? It's accidentally doing the thing we want here. Maybe we just remove the deprecation notice in the short term while we pursue one of the other two options. (This isn't my favorite idea, but it's one possible solution, so it's worth bringing up.)

savetheclocktower avatar Nov 09 '25 18:11 savetheclocktower

Ways to implement the “Argument for” — i.e., APIs that do the things people need @electron/remote for:

I had gone down this road (by accident) when I implemented atom.trashItem and atom.showItemInFolder, each of which took something that worked only on the main process (for at least one platform, even if it worked fine on other platforms) and made it available to the renderer via message-passing.

Hanging each of those directly off of the atom global was evidence of the one-off nature of the effort; if I did it again, I'd probably want to namespace it and turn it into something like atom.shell.trashItem and atom.shell.showItemInFolder.

In the beginning, it could even just be a thin veneer over @electron/remote directly — and then could be reimplemented more elegantly as IPC calls between renderer and main.

savetheclocktower avatar Nov 10 '25 07:11 savetheclocktower

Hanging each of those directly off of the atom global was evidence of the one-off nature of the effort; if I did it again, I'd probably want to namespace it and turn it into something like atom.shell.trashItem and atom.shell.showItemInFolder.

I take this back, actually. After perusing src/application-delegate.js, I realize that so many of the methods that hang off the atom global are just proxies for main-process methods.

At the very least we'd want to add:

  • showItemInFolder
  • trashItem
  • openExternal
  • openPath

But here are the atom methods that are already implemented as messages sent to the main process so they can invoke main-process methods:

  • beep
  • confirm
  • focus
  • show
  • hide
  • center
  • reload
  • restartApplication
  • open
  • pickFolder
  • close
  • getSize
  • setSize
  • getPosition
  • setPosition
  • getCurrentWindow
  • maximize
  • isMaximized
  • isFullScreen
  • setFullScreen
  • addRecentDocument
  • closeWindowDevTools
  • openWindowDevTools

If we were doing it from scratch, maybe these could all be namespaced into atom.remote — but we're not, so we should follow existing convention.

All the new methods are related to the shell object, so they could arguably still live in an atom.shell namespace — but then we’d want the pre-existing pickFolder and perhaps addRecentDocunent to live in that namespace as well. Better to just define

  • atom.showItemInFolder
  • atom.trashItem
  • atom.openExternal
  • atom.openPath

in my opinion.

savetheclocktower avatar Nov 20 '25 18:11 savetheclocktower

I think Pulsar itself depends heavily on @electron/remote - I remember trying to avoid it completely, and I never could.

In fact, if my memory is correct, there are some stuff that depends on the synchronous API of @electron/remote, which is... well, not good, to say the least, because if I'm not mistaken, IPC itself is async...

mauricioszabo avatar Nov 20 '25 18:11 mauricioszabo