From e37a029ce406e06834102a12ef8cc789663186eb Mon Sep 17 00:00:00 2001 From: KatoakDR <68095633+KatoakDR@users.noreply.github.com> Date: Sun, 22 Oct 2023 22:12:06 -0500 Subject: [PATCH] feat: use electron-next to run app in dev mode --- .env.example | 7 ++++++ .github/workflows/build.yml | 2 ++ electron/main/app.ts | 43 +++++++++++++++++++++++++++++--- electron/main/electron.next.d.ts | 23 +++++++++++++++++ package.json | 2 ++ 5 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 electron/main/electron.next.d.ts diff --git a/.env.example b/.env.example index 000de49f..d0c569fc 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,10 @@ +# App-specific environment variable. +# Similar purpose as NODE_ENV but for our own use, and at times +# may not be in sync with the NODE_ENV the app is running under. +# When running the app via `yarn start:dev` then this is set to 'development'. +# For all other cases, this should be set to 'production'. +APP_ENV="production" + # To allow Sentry to upload events from renderer process. # Our Content-Security Policy allow lists this domain. # Infer this from the SENTRY_DSN. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8f049b69..64a823d1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -54,6 +54,8 @@ jobs: SENTRY_PROJECT: ${{ github.event.repository.name }} run: | echo "" > .env + echo "APP_ENV=production" >> .env + echo "SENTRY_INGEST_DOMAIN=${SENTRY_INGEST_DOMAIN}" >> .env echo "SENTRY_AUTH_TOKEN=${SENTRY_AUTH_TOKEN}" >> .env echo "SENTRY_DSN=${SENTRY_DSN}" >> .env diff --git a/electron/main/app.ts b/electron/main/app.ts index ad0f60da..7bb88b7f 100644 --- a/electron/main/app.ts +++ b/electron/main/app.ts @@ -16,22 +16,52 @@ app.setAppUserModelId('com.github.dragonrealms-phoenix.phoenix'); const logger = createLogger('main'); +const appEnv = process.env.APP_ENV ?? 'production'; +const appEnvIsProd = appEnv === 'production'; +const appEnvIsDev = appEnv === 'development'; + const appPath = app.getAppPath(); -const appBuildPath = path.join(appPath, 'electron', 'build'); +const appElectronPath = path.join(appPath, 'electron'); +const appBuildPath = path.join(appElectronPath, 'build'); const appPreloadPath = path.join(appBuildPath, 'preload'); -const appRendererPath = path.join(appBuildPath, 'renderer'); + +// When running in production, serve the app from these paths. +const prodRendererPath = path.join(appBuildPath, 'renderer'); +const prodAppScheme = 'app'; +const prodAppUrl = `${prodAppScheme}://-`; + +// When running in development, serve the app from these paths. +const devRendererPath = path.join(appElectronPath, 'renderer'); +const devPort = 3000; +const devAppUrl = `http://localhost:${devPort}`; + +const appUrl = appEnvIsProd ? prodAppUrl : devAppUrl; // Register custom protocol 'app://' to serve our app. // Registering the protocol must be done before the app is ready. // This is necessary for both security and for single-page apps. // https://bishopfox.com/blog/reasonably-secure-electron // https://github.com/sindresorhus/electron-serve -serve({ directory: appRendererPath }); +if (appEnvIsProd) { + serve({ + scheme: prodAppScheme, + directory: prodRendererPath, + }); +} const createWindow = async (): Promise => { + if (appEnvIsDev) { + // If running in development, serve the renderer from localhost. + // This must be done once the app is ready. + // This enables hot reloading of the renderer. + const { default: serveDev } = await import('electron-next'); + await serveDev(devRendererPath, devPort); + } + const mainWindow = new BrowserWindow({ width: 1200, height: 800, + show: false, // hidden until window loads contents to avoid a blank screen webPreferences: { preload: path.join(appPreloadPath, 'index.js'), devTools: !app.isPackaged, @@ -54,7 +84,12 @@ const createWindow = async (): Promise => { }, }); - await mainWindow.loadURL('app://-'); + // Once the window has finished loading, show it. + mainWindow.webContents.once('did-finish-load', () => { + mainWindow.show(); + }); + + await mainWindow.loadURL(appUrl); initializeMenu(mainWindow); }; diff --git a/electron/main/electron.next.d.ts b/electron/main/electron.next.d.ts new file mode 100644 index 00000000..80d5970f --- /dev/null +++ b/electron/main/electron.next.d.ts @@ -0,0 +1,23 @@ +// Source: https://github.com/leo/electron-next +// Typings: https://github.com/vercel/next.js/blob/canary/examples/with-electron-typescript/electron-src/electron-next.d.ts + +declare module 'electron-next' { + interface Directories { + production: string; + development: string; + } + + export default function ( + /** + * Path to your nextjs app directory. + * Can provide a string or an object with + * separate paths for production and development. + */ + directories: Directories | string, + /** + * Port to serve the nextjs app from. + * Default 8000. + */ + port?: number + ): Promise; +} diff --git a/package.json b/package.json index 75f7f94e..f8e5676c 100644 --- a/package.json +++ b/package.json @@ -36,10 +36,12 @@ "lint:staged": "lint-staged --concurrent 1", "format": "yarn prettier:fix && yarn lint:fix", "start": "yarn build && yarn sentry:sourcemaps && electron .", + "start:dev": "yarn build:dev && APP_ENV=development electron .", "build:main": "tsc -p electron/main", "build:preload": "tsc -p electron/preload && yarn prettier:fix electron/preload/**/*.d.ts", "build:renderer": "next build electron/renderer", "build": "yarn clean && concurrently \"yarn build:main\" \"yarn build:preload\" \"yarn build:renderer\"", + "build:dev": "yarn clean && concurrently \"yarn build:main\" \"yarn build:preload\"", "build:all": "yarn build && electron-builder --win --mac --linux --config", "build:win": "yarn build && electron-builder --win --config", "build:mac": "yarn build && electron-builder --mac --config",