Giter VIP home page Giter VIP logo

remix-electron's Introduction

remix-electron

Electron integration for Remix

demo screenshot

Setup

Use degit to create a new project from the template.

npx degit itsMapleLeaf/remix-electron/workspaces/template my-desktop-app

Adding to an existing Remix project

Install remix-electron and electron:

npm i remix-electron electron

Add a file at desktop/index.js to run the electron app. The initRemix function returns a url to load in the browser window.

// desktop/index.js
const { initRemix } = require("remix-electron")
const { app, BrowserWindow } = require("electron")
const { join } = require("node:path")

/** @type {BrowserWindow | undefined} */
let win

app.on("ready", async () => {
	try {
		const url = await initRemix({
			serverBuild: join(__dirname, "../build/index.js"),
		})

		win = new BrowserWindow({ show: false })
		await win.loadURL(url)
		win.show()
	} catch (error) {
		console.error(error)
	}
})

Build the app with npm run build, then run npx electron desktop/index.js to start the app! ๐Ÿš€

Using Electron APIs

Importing "electron" directly in route files results in Electron trying to get bundled and called in the browser / renderer process.

To circumvent this, create a electron.server.ts file, which re-exports from electron. The .server suffix tells Remix to only load it in the main process. You should use .server for any code that runs in the main process and uses node/electron APIs.

// app/electron.server.ts
import electron from "electron"
export default electron
// app/routes/_index.tsx
import electron from "~/electron.server"

export function loader() {
	return {
		userDataPath: electron.app.getPath("userData"),
	}
}

Likewise, for any code running in the renderer process, e.g. using the clipboard module, you can use the .client suffix. Renderer process modules require nodeIntegration.

// desktop/index.js
function createWindow() {
	// ...
	win = new BrowserWindow({
		// ...
		webPreferences: {
			nodeIntegration: true,
		},
	})
}

API

async initRemix({ serverBuild[, publicFolder, mode, getLoadContext] })

Initializes remix-electron. Returns a promise with a url to load in the browser window.

Options:

  • serverBuild: The path to your server build (e.g. path.join(__dirname, 'build')), or the server build itself (e.g. required from @remix-run/dev/server-build). Updates on refresh are only supported when passing a path string.

  • mode: The mode the app is running in. Can be "development" or "production". Defaults to "production" when packaged, otherwise uses process.env.NODE_ENV.

  • publicFolder: The folder where static assets are served from, including your browser build. Defaults to "public". Non-relative paths are resolved relative to app.getAppPath().

  • getLoadContext: Use this to inject some value into all of your remix loaders, e.g. an API client. The loaders receive it as context

Load context TS example

app/context.ts

import type * as remix from "@remix-run/node"

// your context type
export type LoadContext = {
	secret: string
}

// a custom data function args type to use for loaders/actions
export type DataFunctionArgs = Omit<remix.DataFunctionArgs, "context"> & {
	context: LoadContext
}

desktop/main.js

const url = await initRemix({
	// ...

	/** @type {import("~/context").LoadContext} */
	getLoadContext: () => ({
		secret: "123",
	}),
})

In a route file:

import type { DataFunctionArgs, LoadContext } from "~/context"

export async function loader({ context }: DataFunctionArgs) {
	// do something with context
}

Motivation

Electron has a comprehensive list of security recommendations to follow when building an app, especially if that app interacts with the web. Which includes, but is not limited to:

  • Using preload.js files to expose specific electron functionality to your app, via globals
  • Using IPC communication
  • Avoiding remote.require (which has since been removed)

These practices can lead to a lot of awkward boilerplate and splitting up related code across multiple files and domains.

With remix-electron, you can freely use Electron APIs in Remix loader functions. It's a Node process with full Node capabilities, with access to the full Electron API, none of which runs in the browser.

The browser only receives data and renders a view. Additionally, you can neatly colocate your main process code right beside the related renderer code in a route file.

Thinking about it another way: it's like a normal Remix web app, except Electron is your backend.

remix-electron's People

Contributors

aexylus avatar horusgoul avatar itsmapleleaf avatar jakoblorz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

remix-electron's Issues

Wrong asset files paths on Windows in 0.3.0

Describe the bug

  // asset-files.ts / collectAssetFiles()
  return files.map((file) => ({
    path: "/" + relative(folder, file),
    content: () => readFile(file),
  }))

This produces wrong file paths on Windows because fast-glob will return paths such as

C:/my-desktop-app/public/build/entry.client-WZGREWZ5.js

and path.relative mangles it into

/build\\entry.client-WZGREWZ5.js

Quick solution for me was to replace the slashes relative(folder, path).replace(/\\/g, '/') but normalize-path or unixify mentioned in fast-glob readme might be a more robust solution.

Minimal Reproduction

~

Steps to Reproduce the Bug or Issue

~

Expected behavior

~

Screenshots or Videos

No response

Platform

  • OS: Windows 10

Additional context

No response

filenames with spaces aren't fetchable from public or assets

Describe the bug

Using remix-electron 2.0.0, referencing images located in the public folder, or imported via the assets folder, only works when the image filename does not contain spaces. Referencing an image with spaces in the filename results in:

No routes matched location "/image%20copy.ico"
ErrorResponseImpl {
  status: 404,
  statusText: 'Not Found',
  internal: true,
  data: 'Error: No route matches URL "/image%20copy.ico"',
  error: Error: No route matches URL "/image%20copy.ico"

Minimal Reproduction

https://github.com/tonywoode/demonstateRemixElectonSpaceIssue

Steps to Reproduce the Bug or Issue

See https://github.com/tonywoode/demonstateRemixElectonSpaceIssue/blob/main/app/routes/_index.tsx - Start server note referenced icons with space-in-filename version vs no-space. Note space-in-filename version doesn't show

Expected behavior

I've done the same minimal test with Remix v1.9.x, Remix v2.2.0, and Electron 26.2.1 (via electron-react-boilerplate) and images in public/assets may contain spaces

Screenshots or Videos

No response

Platform

  • OS: macOS 10.15.7

Additional context

No response

Move to Remix Vite

Using Vite unlocks a lot of potential customizability that the classic compiler didn't have.

Typescript: Module remix-electron has no exported member initRemix

Describe the bug

Hello itsMapleLeaf,

since version 2.0.0 i get the following TypeScript error with remix-electron:
TS2305: Module "remix-electron" has no exported member initRemix

With version 1.3.0 it worked like i would expect it.

I hope you can help me.
Thanks in regards

Minimal Reproduction

https://github.com/bmsuseluda/emuze/blob/chore/upgradeRemix/desktop/main.ts

Steps to Reproduce the Bug or Issue

  1. yarn install
  2. open ./desktop/main.ts
  3. Remove // @ts-ignore in first line

Expected behavior

As with version 1.3.0 the types are exported that typescript can handle them.

Screenshots or Videos

No response

Platform

  • OS: [Linux]
  • typescript 5.2.2

Additional context

No response

๐Ÿ’ก Feature Request: add React devtools

Scenario

As a remix-electron user, it would be nice to be able to see inspect the browser devtools and see the react devtools tab there.
This would allow users inspect to state of their React application once its rendered in the browser window

Motivation for change

  • save devs time from setting this ip
  • help developers by enabling them to debug/inspect their React app's state etc..

CleanShot 2022-03-04 at 02 50 12@2x

References

Using express is a (potential and unnecessary) security issue

Using express opens up a port on the end users machine that any application can access, even a regular web browser. If there's any private info being served up a random website could potentially steal it or if the version of electron/node you're shipping with has another security issue in the http.Server implementation all of your users could get pwned.

To do this in a safe way we should be using Electron Custom Protocols via the protocol module.

Ref: https://electronjs.org/docs/latest/api/protocol

Not sure on the specifics of remix but you should be able to wire it up into a custom protocol that is substantially safer to use than a localhost server.

A question about cookies

Describe the bug

Not sure if this is a bug or just something I donโ€™t understand yet with Electron.

I have a remix app running almost perfectly within electron (thankyou).

however, session storage doesnโ€™t appear to be functional.

Have you tried using a cookie session storage in a remix server file and setting some cookie value within a remix action?

Minimal Reproduction

Beep

Steps to Reproduce the Bug or Issue

try login

Expected behavior

Should login

Screenshots or Videos

No response

Platform

  • OS: [e.g. macOS, Windows, Linux]
  • Browser: [e.g. Chrome, Safari, Firefox]
  • Version: [e.g. 91.1]

Additional context

No response

Not working with recent Remix starters (create-remix@latest / ESM)

Describe the bug

When you create-remix a fresh app, it now comes with "type": "module". (templates: default vite, legacy remix)

When you attempt to integrate remix-electron (readme "Adding to an existing Remix project"), it results in an error like this:

pnpm electron desktop/index.js
App threw an error during load
ReferenceError: require is not defined in ES module scope, you can use import instead
This file is being treated as an ES module because it has a '.js' file extension and '/path/to/remix-electron-issue/package.json' contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension.
    at file:///Users/jrr/work/remix-electron-issue/remix-electron-issue/desktop/index.js:2:23
    at ModuleJob.run (node:internal/modules/esm/module_job:217:25)

Minimal Reproduction

https://github.com/jrr/remix-electron-issue

Steps to Reproduce the Bug or Issue

  1. Clone the repo
  2. pnpm i
  3. pnpm build
  4. pnpm electron desktop/index.js

Or, alternatively, from scratch:

  1. pnpx create-remix@latest
  2. pnpm add remix-electron electron
  3. add desktop/index.js from the readme
  4. pnpm build
  5. pnpm electron desktop/index.js

Expected behavior

I expected the electron app to pop up.

Screenshots or Videos

No response

Platform

  • OS: macOS

Additional context

Here are some things I tried:

1. Convert index.js to ESM

Change require -> import, and workaround __dirname like this. (this is on branch esm-electron-entrypoint of the repro repo)

New error:

pnpm electron desktop/index.js
TypeError: __require.resolve is not a function
    at initRemix (file:///Users/jrr/work/remix-electron-issue/node_modules/.pnpm/[email protected]_@[email protected][email protected]/node_modules/remix-electron/dist/index.mjs:57:71)
    at App.<anonymous> (file:///Users/jrr/work/remix-electron-issue/desktop/index.js:14:23)
    at App.emit (node:events:514:28)

Skimming the source, I think this is the line it's choking on:

? require.resolve(serverBuildOption)

2. Rename index.js -> index.cjs

New error:

pnpm electron desktop/index.cjs
Error [ERR_REQUIRE_ESM]: require() of ES Module /Users/jrr/work/remix-electron-issue/build/index.js from /Users/jrr/work/remix-electron-issue/node_modules/.pnpm/[email protected]_@[email protected][email protected]/node_modules/remix-electron/dist/index.js not supported.
Instead change the require of /Users/jrr/work/remix-electron-issue/build/index.js in /Users/jrr/work/remix-electron-issue/node_modules/.pnpm/[email protected]_@[email protected][email protected]/node_modules/remix-electron/dist/index.js to a dynamic import() which is available in all CommonJS modules.
    at c._load (node:electron/js2c/node_init:2:13672)
    at initRemix (/Users/jrr/work/remix-electron-issue/node_modules/.pnpm/[email protected]_@[email protected][email protected]/node_modules/remix-electron/dist/index.js:86:5)
    at App.<anonymous> (/Users/jrr/work/remix-electron-issue/desktop/index.cjs:11:21)
    at App.emit (node:events:514:28) {
  code: 'ERR_REQUIRE_ESM'
}

So I expect remix-electron will need to make some changes to properly support ESM, but in the meantime it may also be helpful to offer some guidance about workarounds. Can a new remix project be configured to produce CJS output compatible with remix-electron?

Update: for anyone else in this situation, if you want to try reverting from ESM back down to CJS, here's an example diff applied to the default Remix starter: jrr/remix-electron-issue#1

hot restart during development

Describe the bug

Hello, I won't take up your time, I'll just say that you did an amazing job, I'm thrilled with it)
The only problem I have is that I want a hot restart to work) The electron app is constantly restarting completely, I don't think this was done specifically for the convenience of development. Often the application opens a second electron window. Can you tell me if it is possible to solve this problem, because I haven't found anything in the documentation that can help me. (electron.server.ts is there).
If you need information about anything, tell me, I will answer as soon as possible!

Minimal Reproduction

https://github.com/nope

Steps to Reproduce the Bug or Issue

Nothing works(

Expected behavior

Hot reboot works great

Screenshots or Videos

No response

Platform

  • OS: [Linux]
  • Browser: [-]
  • Version: [Ubuntu 20.~]

Additional context

No response

๐Ÿ’ก Feature Request Idea: help beginner electron users get started on good path with this Main script

Describe the bug

Scenario

As a user who is new to electron, I want to make sure I am getting started on the right path to success when coding my electron app. The desktop/main.js the entry point of the electron app in my experience can get messy and can easily become a place where lots of logic is placed there, its easy to create lots of top level variable there and create a mess, if one is not careful.

Proposed Solution

Create a file called electron-main which encapsulates the bootstrapping of the electron application into a Javascript class. The advantage of this is that we could now test the bootstrapping portions of our application like.

// FILENAME: electron-main.ts
// NOTE: this the code needs some tweaking, this is a high level idea

import { BrowserWindow } from 'electron';
export default class Main {
    static mainWindow: Electron.BrowserWindow;
    static application: Electron.App;
    static BrowserWindow;
    private static onWindowAllClosed() {
        if (process.platform !== 'darwin') {
            Main.application.quit();
        }
    }

    private static onClose() {
        // Dereference the window object. 
        Main.mainWindow = null;
    }

    private static onReady() {
        Main.mainWindow = new Main.BrowserWindow({ width: 800, height: 600 });
        Main.mainWindow
            .loadURL('file://' + __dirname + '/index.html');
        Main.mainWindow.on('closed', Main.onClose);
    }
    
     // NOTE: we could use a constructor() instead 
     // Also code can be updated to follow the single pattern beter
    static main(app: Electron.App, browserWindow: typeof BrowserWindow) {
        // we pass the Electron.App object and the  
        // Electron.BrowserWindow into this function 
        // so this class has no dependencies. This 
        // makes the code easier to write tests for 
        Main.BrowserWindow = browserWindow;
        Main.application = app;
        Main.application.on('window-all-closed', Main.onWindowAllClosed);
        Main.application.on('ready', Main.onReady);
    }
}
// FILENAME: electron-app.ts
import { app, BrowserWindow } from 'electron';
import Main from './Main';

Main.main(app, BrowserWindow);

Credits

Turn this into an NPM package

It has enough complexity, and has grown beyond a simple template/example. Will probably monorepo this into a library/example structure

๐Ÿ’ก Feature Request: add .`vscode` folder with debug options in `remix-electron` template

Scenario

As a user who has just downloaded the remix-electron template, I would like to be able to debug my remix electron app using VS Code's built in debugger.

Motivation for the Feature/Change?

Having a .vscode folder with the settings.json file already preconfigured for a user would allow user:

  • do step through debugging in their VS Code editor.
  • user will not need to figure out how to do this later (enhanced developer experience DX)

Demo

See the YouTube video below created by @kiliman (another awesome remix advocate ๐Ÿ’ฟ )

There likely needs to be some tweaking to ensure it opens debugs the right port / process. I took a peek at another electron app's .vscode folder and they had this (likely need to tweak):

Electron docs: https://www.electronjs.org/docs/latest/tutorial/debugging-vscode

// Sample file (not tested)
{
  "version": "0.2.0",
  "configurations": [
  {
    "version": "0.2.0",
    "configurations": [
      {
        "name": "Debug Main Process",
        "type": "node",
        "request": "launch",
        "cwd": "${workspaceFolder}",
        "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
        "windows": {
          "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
        },
        "args" : ["."],
        "outputCapture": "std"
      }
    ]
  },
  // TODO: change to vitest
  {
      "type": "node",
      "request": "launch",
      "name": "Jest Tests",
      "program": "${workspaceRoot}/node_modules/.bin/jest",
      "args": ["--config", "jest.json", "--runInBand"],
      "console": "internalConsole",
      "internalConsoleOptions": "neverOpen"
  },
  {
      "type": "chrome",
      "request": "attach",
      "name": "Electron Renderer",
      "port": 9223,
      "webRoot": "${workspaceFolder}",
      "timeout": 10000
  }
  ]
}

Platform

  • Operating System: All

Set cache control header and gzip compression for the remix chunk files

Describe the bug

Hello MapleLeaf,

thanks for your great package to use remix with electron.
I'm trying to set cache control header and gzip compression for the remix chunk files but dont know where to do it.
If i understand it correctly, your package serves the chunks or is it electron that is doing it?
i hope you can help me. I tried to google it for some days but did not find anything

Thanks

Minimal Reproduction

https://github.com/bmsuseluda/remix-electron-cache-header

Steps to Reproduce the Bug or Issue

npm install
npm run build
npm run start

Open developer tools (Menu -> View -> Toggle Developer Tools)
Start lighthouse performance check for desktop

Lighthouse will recommend to set cache control header and gzipรผ compression for all the remix chunks.

Expected behavior

I would like to know how to achieve this.

Screenshots or Videos

No response

Platform

  • OS: Linux
  • Browser: Electron
  • Version: 24.1.3

Additional context

No response

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.