From e45343d5e95bf4a9328f9b56d975b5e500a0241b Mon Sep 17 00:00:00 2001 From: G Date: Wed, 28 Aug 2024 11:20:43 +0200 Subject: [PATCH] Add ESM support --- README.md | 54 +++++------ pnpm-lock.yaml | 40 ++++++++ workspaces/remix-electron/src/index.mts | 38 +++++--- workspaces/test-app-esm/.gitignore | 3 + .../test-app-esm/app/electron.server.ts | 2 + workspaces/test-app-esm/app/root.tsx | 32 +++++++ workspaces/test-app-esm/app/routes/_index.tsx | 27 ++++++ .../app/routes/multipart-uploads.tsx | 36 ++++++++ .../app/routes/referrer-redirect.action.tsx | 13 +++ .../app/routes/referrer-redirect.form.tsx | 21 +++++ workspaces/test-app-esm/desktop/index.js | 29 ++++++ workspaces/test-app-esm/nodemon.json | 6 ++ workspaces/test-app-esm/package.json | 27 ++++++ workspaces/test-app-esm/public/favicon.ico | Bin 0 -> 16958 bytes .../test-app-esm/public/with spaces.txt | 1 + workspaces/test-app-esm/remix.config.mjs | 4 + workspaces/test-app-esm/remix.env.d.ts | 2 + workspaces/test-app-esm/tsconfig.json | 11 +++ .../tests/tests/integration-esm.test.ts | 5 + workspaces/tests/tests/integration.test.ts | 81 +---------------- workspaces/tests/tests/integration.ts | 86 ++++++++++++++++++ 21 files changed, 402 insertions(+), 116 deletions(-) create mode 100644 workspaces/test-app-esm/.gitignore create mode 100644 workspaces/test-app-esm/app/electron.server.ts create mode 100644 workspaces/test-app-esm/app/root.tsx create mode 100644 workspaces/test-app-esm/app/routes/_index.tsx create mode 100644 workspaces/test-app-esm/app/routes/multipart-uploads.tsx create mode 100644 workspaces/test-app-esm/app/routes/referrer-redirect.action.tsx create mode 100644 workspaces/test-app-esm/app/routes/referrer-redirect.form.tsx create mode 100644 workspaces/test-app-esm/desktop/index.js create mode 100644 workspaces/test-app-esm/nodemon.json create mode 100644 workspaces/test-app-esm/package.json create mode 100644 workspaces/test-app-esm/public/favicon.ico create mode 100644 workspaces/test-app-esm/public/with spaces.txt create mode 100644 workspaces/test-app-esm/remix.config.mjs create mode 100644 workspaces/test-app-esm/remix.env.d.ts create mode 100644 workspaces/test-app-esm/tsconfig.json create mode 100644 workspaces/tests/tests/integration-esm.test.ts create mode 100644 workspaces/tests/tests/integration.ts diff --git a/README.md b/README.md index e893565..dc0236b 100644 --- a/README.md +++ b/README.md @@ -24,26 +24,26 @@ Add a file at `desktop/index.js` to run the electron app. The `initRemix` functi ```ts // desktop/index.js -const { initRemix } = require("remix-electron") -const { app, BrowserWindow } = require("electron") -const { join } = require("node:path") +const { initRemix } = require("remix-electron"); +const { app, BrowserWindow } = require("electron"); +const { join } = require("node:path"); /** @type {BrowserWindow | undefined} */ -let win +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 }) - await win.loadURL(url) - win.show() + win = new BrowserWindow({ show: false }); + await win.loadURL(url); + win.show(); } catch (error) { - console.error(error) + console.error(error); } -}) +}); ``` Build the app with `npm run build`, then run `npx electron desktop/index.js` to start the app! 🚀 @@ -56,18 +56,18 @@ To circumvent this, create a `electron.server.ts` file, which re-exports from el ```ts // app/electron.server.ts -import electron from "electron" -export default electron +import electron from "electron"; +export default electron; ``` ```ts // app/routes/_index.tsx -import electron from "~/electron.server" +import electron from "~/electron.server"; export function loader() { return { userDataPath: electron.app.getPath("userData"), - } + }; } ``` @@ -82,7 +82,7 @@ function createWindow() { webPreferences: { nodeIntegration: true, }, - }) + }); } ``` @@ -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.
Load context TS example @@ -108,17 +110,17 @@ Options: **app/context.ts** ```ts -import type * as remix from "@remix-run/node" +import type * as remix from "@remix-run/node"; // your context type export type LoadContext = { - secret: string -} + secret: string; +}; // a custom data function args type to use for loaders/actions export type DataFunctionArgs = Omit & { - context: LoadContext -} + context: LoadContext; +}; ``` **desktop/main.js** @@ -131,13 +133,13 @@ const url = await initRemix({ getLoadContext: () => ({ secret: "123", }), -}) +}); ``` In a route file: ```ts -import type { DataFunctionArgs, LoadContext } from "~/context" +import type { DataFunctionArgs, LoadContext } from "~/context"; export async function loader({ context }: DataFunctionArgs) { // do something with context diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 08b8aed..92ba5fa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -141,6 +141,46 @@ importers: specifier: ^5.3.3 version: 5.3.3 + workspaces/test-app-esm: + dependencies: + '@remix-run/node': + specifier: ^2.5.1 + version: 2.5.1(typescript@5.3.3) + '@remix-run/react': + specifier: ^2.5.1 + version: 2.5.1(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3) + isbot: + specifier: ^4.4.0 + version: 4.4.0 + react: + specifier: ^18.2.0 + version: 18.2.0 + react-dom: + specifier: ^18.2.0 + version: 18.2.0(react@18.2.0) + remix-electron: + specifier: ^2.0.1 + version: link:../remix-electron + devDependencies: + '@remix-run/dev': + specifier: ^2.5.1 + version: 2.5.1(@remix-run/serve@2.5.1)(@types/node@20.11.14)(typescript@5.3.3) + '@types/react': + specifier: ^18.2.48 + version: 18.2.48 + '@types/react-dom': + specifier: ^18.2.18 + version: 18.2.18 + electron: + specifier: ^28.2.1 + version: 28.2.1 + nodemon: + specifier: ^3.0.3 + version: 3.0.3 + typescript: + specifier: ^5.3.3 + version: 5.3.3 + workspaces/tests: devDependencies: '@playwright/test': diff --git a/workspaces/remix-electron/src/index.mts b/workspaces/remix-electron/src/index.mts index 56469f2..82a7a91 100644 --- a/workspaces/remix-electron/src/index.mts +++ b/workspaces/remix-electron/src/index.mts @@ -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" @@ -25,6 +25,7 @@ interface InitRemixOptions { mode?: string publicFolder?: string getLoadContext?: GetLoadContextFunction + esm?: boolean } /** @@ -38,18 +39,29 @@ export async function initRemix({ mode, publicFolder: publicFolderOption = "public", getLoadContext, + esm = typeof require === "undefined", }: InitRemixOptions): Promise { - const appRoot = app.getAppPath() - 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() @@ -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) } })() diff --git a/workspaces/test-app-esm/.gitignore b/workspaces/test-app-esm/.gitignore new file mode 100644 index 0000000..26b49f6 --- /dev/null +++ b/workspaces/test-app-esm/.gitignore @@ -0,0 +1,3 @@ +/build +/public/build +.cache diff --git a/workspaces/test-app-esm/app/electron.server.ts b/workspaces/test-app-esm/app/electron.server.ts new file mode 100644 index 0000000..9370d39 --- /dev/null +++ b/workspaces/test-app-esm/app/electron.server.ts @@ -0,0 +1,2 @@ +import electron from "electron" +export default electron diff --git a/workspaces/test-app-esm/app/root.tsx b/workspaces/test-app-esm/app/root.tsx new file mode 100644 index 0000000..ec18c94 --- /dev/null +++ b/workspaces/test-app-esm/app/root.tsx @@ -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 ( + + + + + + + + + + + + {process.env.NODE_ENV === "development" && } + + + ) +} diff --git a/workspaces/test-app-esm/app/routes/_index.tsx b/workspaces/test-app-esm/app/routes/_index.tsx new file mode 100644 index 0000000..7c7fd67 --- /dev/null +++ b/workspaces/test-app-esm/app/routes/_index.tsx @@ -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() + const [count, setCount] = useState(0) + return ( +
+

Welcome to Remix

+

{data.userDataPath}

+ +
+ ) +} diff --git a/workspaces/test-app-esm/app/routes/multipart-uploads.tsx b/workspaces/test-app-esm/app/routes/multipart-uploads.tsx new file mode 100644 index 0000000..609e0f5 --- /dev/null +++ b/workspaces/test-app-esm/app/routes/multipart-uploads.tsx @@ -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() + return ( + <> +
+ + +
+

{data?.text}

+ + ) +} diff --git a/workspaces/test-app-esm/app/routes/referrer-redirect.action.tsx b/workspaces/test-app-esm/app/routes/referrer-redirect.action.tsx new file mode 100644 index 0000000..e8539dc --- /dev/null +++ b/workspaces/test-app-esm/app/routes/referrer-redirect.action.tsx @@ -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()) +} diff --git a/workspaces/test-app-esm/app/routes/referrer-redirect.form.tsx b/workspaces/test-app-esm/app/routes/referrer-redirect.form.tsx new file mode 100644 index 0000000..3cf63f8 --- /dev/null +++ b/workspaces/test-app-esm/app/routes/referrer-redirect.form.tsx @@ -0,0 +1,21 @@ +import { useFetcher, useSearchParams } from "@remix-run/react" + +export default function RedirectForm() { + const fetcher = useFetcher() + const [params] = useSearchParams() + const redirects = params.get("redirects") + return ( + <> +

{redirects ?? 0}

+ + + + + ) +} diff --git a/workspaces/test-app-esm/desktop/index.js b/workspaces/test-app-esm/desktop/index.js new file mode 100644 index 0000000..7c5af34 --- /dev/null +++ b/workspaces/test-app-esm/desktop/index.js @@ -0,0 +1,29 @@ +import path from "node:path" +import { BrowserWindow, app } from "electron" +import { initRemix } from "remix-electron" + +/** @param {string} url */ +async function createWindow(url) { + const win = new BrowserWindow() + + // load the devtools first before loading the app URL so we can see initial network requests + // electron needs some page content to show the dev tools, so we'll load a dummy page first + await win.loadURL( + `data:text/html;charset=utf-8,${encodeURI("

Loading...

")}`, + ) + win.webContents.openDevTools() + win.webContents.on("devtools-opened", () => { + // devtools takes a bit to load, so we'll wait a bit before loading the app URL + setTimeout(() => { + win.loadURL(url).catch(console.error) + }, 500) + }) +} + +app.on("ready", async () => { + const url = await initRemix({ + serverBuild: path.join(process.cwd(), "./build/index.js"), + esm: true, + }) + await createWindow(url) +}) diff --git a/workspaces/test-app-esm/nodemon.json b/workspaces/test-app-esm/nodemon.json new file mode 100644 index 0000000..5d827d9 --- /dev/null +++ b/workspaces/test-app-esm/nodemon.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json.schemastore.org/nodemon.json", + "exec": "electron --trace-warnings ./desktop/index.mjs", + "watch": ["desktop"], + "ignore": ["build", "public/build"] +} diff --git a/workspaces/test-app-esm/package.json b/workspaces/test-app-esm/package.json new file mode 100644 index 0000000..b8440cc --- /dev/null +++ b/workspaces/test-app-esm/package.json @@ -0,0 +1,27 @@ +{ + "name": "remix-electron-test-app", + "version": "0.0.0", + "private": true, + "type": "module", + "main": "desktop/index.js", + "scripts": { + "dev": "remix dev --manual --command \"nodemon .\"", + "build": "remix build" + }, + "dependencies": { + "@remix-run/node": "^2.5.1", + "@remix-run/react": "^2.5.1", + "isbot": "^4.4.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "remix-electron": "^2.0.1" + }, + "devDependencies": { + "@remix-run/dev": "^2.5.1", + "@types/react": "^18.2.48", + "@types/react-dom": "^18.2.18", + "electron": "^28.2.1", + "nodemon": "^3.0.3", + "typescript": "^5.3.3" + } +} diff --git a/workspaces/test-app-esm/public/favicon.ico b/workspaces/test-app-esm/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..8830cf6821b354114848e6354889b8ecf6d2bc61 GIT binary patch literal 16958 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 EKbET!0{{R3 literal 0 HcmV?d00001 diff --git a/workspaces/test-app-esm/public/with spaces.txt b/workspaces/test-app-esm/public/with spaces.txt new file mode 100644 index 0000000..5da65bb --- /dev/null +++ b/workspaces/test-app-esm/public/with spaces.txt @@ -0,0 +1 @@ +This is a file with spaces in the path \ No newline at end of file diff --git a/workspaces/test-app-esm/remix.config.mjs b/workspaces/test-app-esm/remix.config.mjs new file mode 100644 index 0000000..e86b2af --- /dev/null +++ b/workspaces/test-app-esm/remix.config.mjs @@ -0,0 +1,4 @@ +/** @type {import("@remix-run/dev").AppConfig} */ +export default { + serverModuleFormat: "esm", +} diff --git a/workspaces/test-app-esm/remix.env.d.ts b/workspaces/test-app-esm/remix.env.d.ts new file mode 100644 index 0000000..72e2aff --- /dev/null +++ b/workspaces/test-app-esm/remix.env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/workspaces/test-app-esm/tsconfig.json b/workspaces/test-app-esm/tsconfig.json new file mode 100644 index 0000000..acd4c12 --- /dev/null +++ b/workspaces/test-app-esm/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "verbatimModuleSyntax": false, + "module": "ESNext", + "moduleResolution": "Bundler", + "paths": { + "~/*": ["./app/*"] + } + }, + "extends": "../../tsconfig.base.json" +} diff --git a/workspaces/tests/tests/integration-esm.test.ts b/workspaces/tests/tests/integration-esm.test.ts new file mode 100644 index 0000000..31875b5 --- /dev/null +++ b/workspaces/tests/tests/integration-esm.test.ts @@ -0,0 +1,5 @@ +import { run } from "./integration.js" + +export const appFolder = new URL("../../test-app-esm", import.meta.url) + +run("esm", appFolder) diff --git a/workspaces/tests/tests/integration.test.ts b/workspaces/tests/tests/integration.test.ts index 1b8d18c..cfda970 100644 --- a/workspaces/tests/tests/integration.test.ts +++ b/workspaces/tests/tests/integration.test.ts @@ -1,82 +1,5 @@ -import { readFile } from "node:fs/promises" -import { fileURLToPath } from "node:url" -import { - type ElectronApplication, - type Page, - expect, - test, -} from "@playwright/test" -import { execa } from "execa" -import { launchElectron } from "./launchElectron.js" +import { run } from "./integration.js" export const appFolder = new URL("../../test-app", import.meta.url) -let app!: ElectronApplication -let window!: Page - -test.beforeAll("build", async () => { - await execa("pnpm", ["build"], { - cwd: appFolder, - stderr: "inherit", - }) -}) - -test.beforeEach(async () => { - ;({ app, window } = await launchElectron({ - cwd: fileURLToPath(appFolder), - args: ["."], - })) -}) - -test.afterEach(async () => { - await app.close() -}) - -test("electron apis", async () => { - const userDataPath = await app.evaluate(({ app }) => app.getPath("userData")) - - await expect(window.locator('[data-testid="user-data-path"]')).toHaveText( - userDataPath, - ) -}) - -test("scripts", async () => { - const counter = window.locator("[data-testid='counter']") - await expect(counter).toHaveText("0") - await counter.click({ clickCount: 2 }) - await expect(counter).toHaveText("2") -}) - -test("action referrer redirect", async () => { - await window.goto("http://localhost/referrer-redirect/form") - - const redirectCount = window.locator("[data-testid=redirects]") - await expect(redirectCount).toHaveText("0") - await window.click("text=submit") - await expect(redirectCount).toHaveText("1") -}) - -test.skip("multipart uploads", async () => { - await window.goto("http://localhost/multipart-uploads") - - const assetUrl = new URL( - "./fixtures/asset-files/file-upload.txt", - import.meta.url, - ) - - const assetContent = await readFile(assetUrl, "utf-8") - - await window - .locator("input[type=file]") - .setInputFiles(fileURLToPath(assetUrl)) - await window.locator("button").click() - await expect(window.locator("[data-testid=result]")).toHaveText(assetContent) -}) - -test("can load public assets that contain whitespace in their path", async () => { - await window.goto("http://localhost/with spaces.txt") - - await expect(window.locator("body")).toHaveText( - "This is a file with spaces in the path", - ) -}) +run("cjs", appFolder) diff --git a/workspaces/tests/tests/integration.ts b/workspaces/tests/tests/integration.ts new file mode 100644 index 0000000..b744930 --- /dev/null +++ b/workspaces/tests/tests/integration.ts @@ -0,0 +1,86 @@ +import { readFile } from "node:fs/promises" +import { fileURLToPath } from "node:url" +import { + type ElectronApplication, + type Page, + expect, + test, +} from "@playwright/test" +import { execa } from "execa" +import { launchElectron } from "./launchElectron.js" + +export function run(type: "esm" | "cjs", appFolder: URL) { + let app!: ElectronApplication + let window!: Page + + test.beforeAll(`${type} - build`, async () => { + await execa("pnpm", ["build"], { + cwd: appFolder, + stderr: "inherit", + }) + }) + + test.beforeEach(async () => { + ;({ app, window } = await launchElectron({ + cwd: fileURLToPath(appFolder), + args: ["."], + })) + }) + + test.afterEach(async () => { + await app.close() + }) + + test(`${type} - electron apis`, async () => { + const userDataPath = await app.evaluate(({ app }) => + app.getPath("userData"), + ) + + await expect(window.locator('[data-testid="user-data-path"]')).toHaveText( + userDataPath, + ) + }) + + test(`${type} - scripts`, async () => { + const counter = window.locator("[data-testid='counter']") + await expect(counter).toHaveText("0") + await counter.click({ clickCount: 2 }) + await expect(counter).toHaveText("2") + }) + + test(`${type} - action referrer redirect`, async () => { + await window.goto("http://localhost/referrer-redirect/form") + + const redirectCount = window.locator("[data-testid=redirects]") + await expect(redirectCount).toHaveText("0") + await window.click("text=submit") + await expect(redirectCount).toHaveText("1") + }) + + test.skip(`${type} - multipart uploads`, async () => { + await window.goto("http://localhost/multipart-uploads") + + const assetUrl = new URL( + "./fixtures/asset-files/file-upload.txt", + import.meta.url, + ) + + const assetContent = await readFile(assetUrl, "utf-8") + + await window + .locator("input[type=file]") + .setInputFiles(fileURLToPath(assetUrl)) + await window.locator("button").click() + await expect(window.locator("[data-testid=result]")).toHaveText( + assetContent, + ) + }) + + test(`${type} - can load public assets that contain whitespace in their path`, async () => { + await window.goto("http://localhost/with spaces.txt") + + await expect(window.locator("body")).toHaveText( + "This is a file with spaces in the path", + ) + }) +}