Skip to content
This repository was archived by the owner on Jan 17, 2025. It is now read-only.

Commit 1ba118a

Browse files
authored
Merge pull request #33 from giuseppeg/esm
Add ESM support
2 parents 6c4a6e6 + d524f0c commit 1ba118a

22 files changed

+393
-107
lines changed

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ let win
3434
app.on("ready", async () => {
3535
try {
3636
const url = await initRemix({
37-
serverBuild: join(__dirname, "../build/index.js"),
37+
serverBuild: join(process.cwd(), "build/index.js"),
3838
})
3939

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

9595
Options:
9696

97-
- `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.
97+
- `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.
9898

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

101-
- `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()`.
101+
- `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()`.
102102

103-
- `getLoadContext`: Use this to inject some value into all of your remix loaders, e.g. an API client. The loaders receive it as `context`
103+
- `getLoadContext`: Use this to inject some value into all of your remix loaders, e.g. an API client. The loaders receive it as `context`.
104+
105+
- `esm`: Set this to `true` to use remix-electron in an ESM application.
104106

105107
<details>
106108
<summary>Load context TS example</summary>

knip.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
"workspaces/tests": {},
1818
"workspaces/test-app": {
1919
"ignoreDependencies": ["isbot", "nodemon"]
20+
},
21+
"workspaces/test-app-esm": {
22+
"ignoreDependencies": ["isbot", "nodemon"]
2023
}
2124
}
2225
}

pnpm-lock.yaml

Lines changed: 50 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

workspaces/remix-electron/src/index.mts

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as webFetch from "@remix-run/web-fetch"
44
// if we override everything else, we get errors caused by the mismatch of built-in types and remix types
55
global.File = webFetch.File
66

7-
import { watch } from "node:fs/promises"
7+
import { constants, access, watch } from "node:fs/promises"
88
import type { AppLoadContext, ServerBuild } from "@remix-run/node"
99
import { broadcastDevReady, createRequestHandler } from "@remix-run/node"
1010
import { app, protocol } from "electron"
@@ -25,6 +25,7 @@ interface InitRemixOptions {
2525
mode?: string
2626
publicFolder?: string
2727
getLoadContext?: GetLoadContextFunction
28+
esm?: boolean
2829
}
2930

3031
/**
@@ -38,18 +39,29 @@ export async function initRemix({
3839
mode,
3940
publicFolder: publicFolderOption = "public",
4041
getLoadContext,
42+
esm = typeof require === "undefined",
4143
}: InitRemixOptions): Promise<string> {
42-
const appRoot = app.getAppPath()
43-
const publicFolder = asAbsolutePath(publicFolderOption, appRoot)
44+
const publicFolder = asAbsolutePath(publicFolderOption, process.cwd())
45+
46+
if (
47+
!(await access(publicFolder, constants.R_OK).then(
48+
() => true,
49+
() => false,
50+
))
51+
) {
52+
throw new Error(
53+
`Public folder ${publicFolder} does not exist. Make sure that the initRemix \`publicFolder\` option is configured correctly.`,
54+
)
55+
}
4456

4557
const buildPath =
46-
typeof serverBuildOption === "string"
47-
? require.resolve(serverBuildOption)
48-
: undefined
58+
typeof serverBuildOption === "string" ? serverBuildOption : undefined
4959

5060
let serverBuild =
51-
typeof serverBuildOption === "string"
52-
? /** @type {ServerBuild} */ require(serverBuildOption)
61+
typeof buildPath === "string"
62+
? /** @type {ServerBuild} */ await import(
63+
esm ? `${buildPath}?${Date.now()}` : buildPath
64+
)
5365
: serverBuildOption
5466

5567
await app.whenReady()
@@ -95,9 +107,13 @@ export async function initRemix({
95107
) {
96108
void (async () => {
97109
for await (const _event of watch(buildPath)) {
98-
purgeRequireCache(buildPath)
99-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
100-
serverBuild = require(buildPath)
110+
if (esm) {
111+
serverBuild = await import(`${buildPath}?${Date.now()}`)
112+
} else {
113+
purgeRequireCache(buildPath)
114+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
115+
serverBuild = require(buildPath)
116+
}
101117
await broadcastDevReady(serverBuild)
102118
}
103119
})()

workspaces/test-app-esm/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/build
2+
/public/build
3+
.cache
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import electron from "electron"
2+
export default electron
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import type { MetaFunction } from "@remix-run/node"
2+
import {
3+
Links,
4+
LiveReload,
5+
Meta,
6+
Outlet,
7+
Scripts,
8+
ScrollRestoration,
9+
} from "@remix-run/react"
10+
11+
export const meta: MetaFunction = () => {
12+
return [{ title: "New Remix App" }]
13+
}
14+
15+
export default function App() {
16+
return (
17+
<html lang="en">
18+
<head>
19+
<meta charSet="utf8" />
20+
<meta name="viewport" content="width=device-width,initial-scale=1" />
21+
<Meta />
22+
<Links />
23+
</head>
24+
<body>
25+
<Outlet />
26+
<ScrollRestoration />
27+
<Scripts />
28+
{process.env.NODE_ENV === "development" && <LiveReload />}
29+
</body>
30+
</html>
31+
)
32+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { useLoaderData } from "@remix-run/react"
2+
import { useState } from "react"
3+
import electron from "~/electron.server"
4+
5+
export function loader() {
6+
return {
7+
userDataPath: electron.app.getPath("userData"),
8+
}
9+
}
10+
11+
export default function Index() {
12+
const data = useLoaderData<typeof loader>()
13+
const [count, setCount] = useState(0)
14+
return (
15+
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.4" }}>
16+
<h1>Welcome to Remix</h1>
17+
<p data-testid="user-data-path">{data.userDataPath}</p>
18+
<button
19+
type="button"
20+
data-testid="counter"
21+
onClick={() => setCount(count + 1)}
22+
>
23+
{count}
24+
</button>
25+
</div>
26+
)
27+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import {
2+
type ActionFunctionArgs,
3+
NodeOnDiskFile,
4+
json,
5+
unstable_createFileUploadHandler,
6+
unstable_parseMultipartFormData,
7+
} from "@remix-run/node"
8+
import { Form, useActionData } from "@remix-run/react"
9+
10+
export async function action({ request }: ActionFunctionArgs) {
11+
const formData = await unstable_parseMultipartFormData(
12+
request,
13+
unstable_createFileUploadHandler(),
14+
)
15+
16+
const file = formData.get("file")
17+
if (!(file instanceof NodeOnDiskFile)) {
18+
throw new Error("No file uploaded")
19+
}
20+
21+
const text = await file.text()
22+
return json({ text })
23+
}
24+
25+
export default function MultipartUploadsTest() {
26+
const data = useActionData<typeof action>()
27+
return (
28+
<>
29+
<Form method="post" encType="multipart/form-data">
30+
<input type="file" name="file" />
31+
<button type="submit">Submit</button>
32+
</Form>
33+
<p data-testid="result">{data?.text}</p>
34+
</>
35+
)
36+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type { ActionFunction } from "@remix-run/node"
2+
import { redirect } from "@remix-run/node"
3+
4+
export const action: ActionFunction = async ({ request }) => {
5+
const { redirects } = Object.fromEntries(await request.formData())
6+
const referrer = request.headers.get("referer")
7+
if (!referrer) {
8+
throw new Error("No referrer header")
9+
}
10+
const url = new URL(referrer)
11+
url.searchParams.set("redirects", String(Number(redirects) + 1))
12+
return redirect(url.toString())
13+
}

0 commit comments

Comments
 (0)