Skip to content
This repository was archived by the owner on Jan 17, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ let win
app.on("ready", async () => {
try {
const url = await initRemix({
serverBuild: join(__dirname, "../build/index.js"),
serverBuild: join(process.cwd(), "build/index.js"),
})

win = new BrowserWindow({ show: false })
Expand Down Expand Up @@ -94,13 +94,15 @@ Initializes remix-electron. Returns a promise with a url to load in the browser

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.
- `serverBuild`: The path to your server build (e.g. `path.join(process.cwd(), '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()`.
- `publicFolder`: The folder where static assets are served from, including your browser build. Defaults to `"public"`. Non-absolute paths are resolved relative to `process.cwd()`.

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

- `esm`: Set this to `true` to use remix-electron in an ESM application.

<details>
<summary>Load context TS example</summary>
Expand Down
3 changes: 3 additions & 0 deletions knip.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
"workspaces/tests": {},
"workspaces/test-app": {
"ignoreDependencies": ["isbot", "nodemon"]
},
"workspaces/test-app-esm": {
"ignoreDependencies": ["isbot", "nodemon"]
}
}
}
63 changes: 50 additions & 13 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 27 additions & 11 deletions workspaces/remix-electron/src/index.mts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as webFetch from "@remix-run/web-fetch"
// if we override everything else, we get errors caused by the mismatch of built-in types and remix types
global.File = webFetch.File

import { watch } from "node:fs/promises"
import { constants, access, watch } from "node:fs/promises"
import type { AppLoadContext, ServerBuild } from "@remix-run/node"
import { broadcastDevReady, createRequestHandler } from "@remix-run/node"
import { app, protocol } from "electron"
Expand All @@ -25,6 +25,7 @@ interface InitRemixOptions {
mode?: string
publicFolder?: string
getLoadContext?: GetLoadContextFunction
esm?: boolean
}

/**
Expand All @@ -38,18 +39,29 @@ export async function initRemix({
mode,
publicFolder: publicFolderOption = "public",
getLoadContext,
esm = typeof require === "undefined",
}: InitRemixOptions): Promise<string> {
const appRoot = app.getAppPath()
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the electron app root not the remix project's and therefore publicFolder will be wrong when resolved below, so I replaced it with process.cwd()

const publicFolder = asAbsolutePath(publicFolderOption, appRoot)
const publicFolder = asAbsolutePath(publicFolderOption, process.cwd())

if (
!(await access(publicFolder, constants.R_OK).then(
() => true,
() => false,
))
) {
throw new Error(
`Public folder ${publicFolder} does not exist. Make sure that the initRemix \`publicFolder\` option is configured correctly.`,
)
}

const buildPath =
typeof serverBuildOption === "string"
? require.resolve(serverBuildOption)
: undefined
typeof serverBuildOption === "string" ? serverBuildOption : undefined

let serverBuild =
typeof serverBuildOption === "string"
? /** @type {ServerBuild} */ require(serverBuildOption)
typeof buildPath === "string"
? /** @type {ServerBuild} */ await import(
esm ? `${buildPath}?${Date.now()}` : buildPath
)
: serverBuildOption

await app.whenReady()
Expand Down Expand Up @@ -95,9 +107,13 @@ export async function initRemix({
) {
void (async () => {
for await (const _event of watch(buildPath)) {
purgeRequireCache(buildPath)
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
serverBuild = require(buildPath)
if (esm) {
serverBuild = await import(`${buildPath}?${Date.now()}`)
} else {
purgeRequireCache(buildPath)
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
serverBuild = require(buildPath)
}
await broadcastDevReady(serverBuild)
}
})()
Expand Down
3 changes: 3 additions & 0 deletions workspaces/test-app-esm/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/build
/public/build
.cache
2 changes: 2 additions & 0 deletions workspaces/test-app-esm/app/electron.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import electron from "electron"
export default electron
32 changes: 32 additions & 0 deletions workspaces/test-app-esm/app/root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { MetaFunction } from "@remix-run/node"
import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "@remix-run/react"

export const meta: MetaFunction = () => {
return [{ title: "New Remix App" }]
}

export default function App() {
return (
<html lang="en">
<head>
<meta charSet="utf8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<Meta />
<Links />
</head>
<body>
<Outlet />
<ScrollRestoration />
<Scripts />
{process.env.NODE_ENV === "development" && <LiveReload />}
</body>
</html>
)
}
27 changes: 27 additions & 0 deletions workspaces/test-app-esm/app/routes/_index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { useLoaderData } from "@remix-run/react"
import { useState } from "react"
import electron from "~/electron.server"

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

export default function Index() {
const data = useLoaderData<typeof loader>()
const [count, setCount] = useState(0)
return (
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.4" }}>
<h1>Welcome to Remix</h1>
<p data-testid="user-data-path">{data.userDataPath}</p>
<button
type="button"
data-testid="counter"
onClick={() => setCount(count + 1)}
>
{count}
</button>
</div>
)
}
36 changes: 36 additions & 0 deletions workspaces/test-app-esm/app/routes/multipart-uploads.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {
type ActionFunctionArgs,
NodeOnDiskFile,
json,
unstable_createFileUploadHandler,
unstable_parseMultipartFormData,
} from "@remix-run/node"
import { Form, useActionData } from "@remix-run/react"

export async function action({ request }: ActionFunctionArgs) {
const formData = await unstable_parseMultipartFormData(
request,
unstable_createFileUploadHandler(),
)

const file = formData.get("file")
if (!(file instanceof NodeOnDiskFile)) {
throw new Error("No file uploaded")
}

const text = await file.text()
return json({ text })
}

export default function MultipartUploadsTest() {
const data = useActionData<typeof action>()
return (
<>
<Form method="post" encType="multipart/form-data">
<input type="file" name="file" />
<button type="submit">Submit</button>
</Form>
<p data-testid="result">{data?.text}</p>
</>
)
}
13 changes: 13 additions & 0 deletions workspaces/test-app-esm/app/routes/referrer-redirect.action.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { ActionFunction } from "@remix-run/node"
import { redirect } from "@remix-run/node"

export const action: ActionFunction = async ({ request }) => {
const { redirects } = Object.fromEntries(await request.formData())
const referrer = request.headers.get("referer")
if (!referrer) {
throw new Error("No referrer header")
}
const url = new URL(referrer)
url.searchParams.set("redirects", String(Number(redirects) + 1))
return redirect(url.toString())
}
Loading