@witchcraft/nuxt-electron 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,158 @@
1
+ import { keys } from "@alanscodelog/utils/keys";
2
+ import { pick } from "@alanscodelog/utils/pick";
3
+ import { net } from "electron";
4
+ import fs from "node:fs/promises";
5
+ import path from "node:path";
6
+ import { pathToFileURL } from "node:url";
7
+ const cache = /* @__PURE__ */ new Map();
8
+ async function getPathToServe(originalPath, pathWithIndexHtml, key, logger) {
9
+ if (cache.has(key)) {
10
+ const result = cache.get(key);
11
+ if (logger) {
12
+ logger.trace({
13
+ ns: "main:createProxiedProtocolHandler:cacheHit",
14
+ requestUrl: key,
15
+ result: cache.get(key)
16
+ });
17
+ }
18
+ return result;
19
+ }
20
+ const stats = await fs.stat(originalPath).catch(() => void 0);
21
+ if (logger) {
22
+ logger.trace({
23
+ ns: "main:createProxiedProtocolHandler:stats",
24
+ key,
25
+ stats,
26
+ isFile: stats?.isFile(),
27
+ isDirectory: stats?.isDirectory()
28
+ });
29
+ }
30
+ const pathToServe = stats !== void 0 ? stats.isFile() ? originalPath : stats.isDirectory() ? (await fs.stat(pathWithIndexHtml).catch(() => void 0))?.isFile() ? pathWithIndexHtml : void 0 : void 0 : void 0;
31
+ cache.set(key, pathToServe);
32
+ return pathToServe;
33
+ }
34
+ export function createProxiedProtocolHandler(protocol, protocolName = "app", basePath, routeProxies, {
35
+ logger,
36
+ errorPage = "404.html"
37
+ } = {}) {
38
+ const routeProxyKeys = keys(routeProxies);
39
+ if (protocol.isProtocolHandled(protocolName)) {
40
+ throw new Error(`Protocol ${protocolName} is already handled.`);
41
+ }
42
+ if (routeProxyKeys.length > 0) {
43
+ if (!process.env.PUBLIC_SERVER_URL && !process.env.VITE_DEV_URL && !process.env.PUBLIC_SERVER_URL) {
44
+ throw new Error("You defined proxy routes but didn't set PUBLIC_SERVER_URL or VITE_DEV_URL set. This is required for the /api routes to work.");
45
+ }
46
+ }
47
+ let errorPage404;
48
+ protocol.handle(protocolName, async (request) => {
49
+ errorPage404 ??= await getPathToServe(path.join(basePath, errorPage), "404.html", "404.html", logger);
50
+ if (!errorPage404) {
51
+ throw new Error(`Error page ${path.join(basePath, errorPage)} does not exist. Did you override the routeRules for it?`);
52
+ }
53
+ const parsedUrl = new URL(request.url);
54
+ const requestPath = decodeURIComponent(parsedUrl.pathname);
55
+ const proxyKey = routeProxyKeys.find((key) => requestPath.startsWith(key));
56
+ if (proxyKey) {
57
+ const proxyUrl = routeProxies[proxyKey];
58
+ const finalPath2 = proxyUrl + requestPath;
59
+ if (logger) {
60
+ logger.trace({
61
+ ns: "main:createProxiedProtocolHandler:fetchingViaProxy",
62
+ requestUrl: request.url,
63
+ proxyKey,
64
+ route: proxyUrl,
65
+ requestPath,
66
+ finalPath: finalPath2
67
+ });
68
+ }
69
+ return net.fetch(finalPath2, {
70
+ ...pick(request, ["headers", "destination", "referrer", "referrerPolicy", "mode", "credentials", "cache", "redirect", "integrity", "keepalive"])
71
+ });
72
+ }
73
+ const originalPath = path.join(basePath, requestPath);
74
+ const pathWithIndexHtml = path.join(originalPath, "index.html");
75
+ const relativePath = path.relative(basePath, originalPath);
76
+ const isSafe = !relativePath.startsWith("..") && !path.isAbsolute(relativePath);
77
+ if (!isSafe) {
78
+ if (logger) {
79
+ logger.error({
80
+ ns: "main:createProxiedProtocolHandler:badRequest",
81
+ request: pick(request, [
82
+ "url",
83
+ "headers",
84
+ "destination",
85
+ "referrer",
86
+ "referrerPolicy",
87
+ "mode",
88
+ "credentials",
89
+ "cache",
90
+ "redirect",
91
+ "integrity",
92
+ "keepalive",
93
+ // these also seem to exist
94
+ ...["isReloadNavigation", "isHistoryNavigation"]
95
+ ]),
96
+ requestPath,
97
+ pathWithIndexHtml,
98
+ relativePath,
99
+ originalPath,
100
+ isSafe
101
+ });
102
+ }
103
+ return new Response(JSON.stringify({
104
+ error: "Bad Request - Unsafe Path"
105
+ }), {
106
+ headers: { "content-type": "application/json" },
107
+ status: 400
108
+ });
109
+ }
110
+ const pathToServe = await getPathToServe(originalPath, pathWithIndexHtml, request.url, logger);
111
+ if (!pathToServe) {
112
+ if (logger) {
113
+ logger.error({
114
+ ns: "main:createProxiedProtocolHandler:noFileFound",
115
+ requestUrl: request.url,
116
+ originalPath,
117
+ requestPath,
118
+ pathWithIndexHtml,
119
+ pathToServe,
120
+ errorPath: pathToFileURL(errorPage404).toString()
121
+ });
122
+ }
123
+ const res = await net.fetch(pathToFileURL(errorPage404).toString(), {
124
+ // see below
125
+ bypassCustomProtocolHandlers: true
126
+ }).catch((err) => err);
127
+ return res;
128
+ }
129
+ const finalPath = pathToFileURL(pathToServe).toString();
130
+ if (logger) {
131
+ logger.trace({
132
+ ns: "main:createProxiedProtocolHandler:fetchingFile",
133
+ requestUrl: request.url,
134
+ finalPath,
135
+ requestPath,
136
+ originalPath
137
+ });
138
+ }
139
+ const response = await net.fetch(finalPath, {
140
+ // avoid infinite loop if protocolName protocol is "file"
141
+ // or file is otherwise handled
142
+ bypassCustomProtocolHandlers: true
143
+ }).catch((err) => {
144
+ if (logger) {
145
+ logger.error({
146
+ ns: "main:createProxiedProtocolHandler:fetchError",
147
+ requestUrl: request.url,
148
+ finalPath,
149
+ requestPath,
150
+ originalPath,
151
+ err
152
+ });
153
+ }
154
+ return err;
155
+ });
156
+ return response;
157
+ });
158
+ }
@@ -1,25 +1,17 @@
1
- export declare function forceRelativePath(filepath: string): string;
2
1
  /**
3
2
  * Calculates the correct paths for the app to run in electron.
4
3
  *
5
- * Note: For serverUrl to not be overridable in production, you should set `electron.additionalElectronVariables.publicServerUrl` to a quoted string.
6
4
  *
7
- * ```ts[nuxt.config.ts]
8
- * export default defineNuxtConfig({
9
- * modules: [
10
- * "@witchcraft/nuxt-electron",
11
- * ],
12
- * electron: {
13
- * additionalElectronVariables: {
14
- * publicServerUrl: process.env.NODE_ENV === "production"
15
- * ? `"mysite.com"`
16
- * : `undefined`
17
- * }
18
- * }
5
+ * Paths are not overridable in production unless you pass the variables that can override them.
6
+ *
7
+ * ```ts
8
+ * const paths = getPaths("app", {
9
+ * windowUrl: process.env.OVERRIDE_WINDOW_URL,
10
+ * publicServerUrl: process.env.OVERRIDE_PUBLIC_SERVER_URL
19
11
  * })
20
12
  * ```
21
13
  */
22
- export declare function getPaths(): {
14
+ export declare function getPaths(protocolName?: string, overridingEnvs?: Record<string, string | undefined>): {
23
15
  windowUrl: string;
24
16
  publicServerUrl: string;
25
17
  nuxtPublicDir: string;
@@ -1,32 +1,38 @@
1
1
  import { unreachable } from "@alanscodelog/utils/unreachable";
2
- import { app } from "electron";
3
2
  import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
4
  import { STATIC } from "./static.js";
5
- export function forceRelativePath(filepath) {
6
- return path.join(`.${path.sep}`, filepath);
7
- }
8
- export function getPaths() {
9
- const rootDir = app.getAppPath();
10
- const nuxtPublicDir = path.resolve(rootDir, forceRelativePath(STATIC.ELECTRON_NUXT_PUBLIC_DIR));
11
- const preloadPath = path.resolve(rootDir, forceRelativePath(STATIC.ELECTRON_BUILD_DIR), "./preload.cjs");
5
+ export function getPaths(protocolName = "app", overridingEnvs = {
6
+ windowUrl: void 0,
7
+ publicServerUrl: void 0
8
+ }) {
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
+ const nuxtPublicDir = path.join(__dirname, STATIC.ELECTRON_NUXT_PUBLIC_DIR);
11
+ const preloadPath = path.join(__dirname, STATIC.ELECTRON_BUILD_DIR, "./preload.cjs");
12
12
  const base = {
13
13
  nuxtPublicDir,
14
14
  preloadPath,
15
- publicServerUrl: process.env.PUBLIC_SERVER_URL ?? process.env.PUBLIC_SERVER_URL ?? process.env.VITE_DEV_SERVER_URL
15
+ publicServerUrl: overridingEnvs.publicServerUrl ?? process.env.PUBLIC_SERVER_URL ?? process.env.VITE_DEV_SERVER_URL
16
16
  };
17
17
  if (!base.publicServerUrl) {
18
18
  throw new Error("publicServerUrl could not be determined.");
19
19
  }
20
- if (process.env.VITE_DEV_SERVER_URL) {
20
+ if (overridingEnvs.windowUrl) {
21
+ return {
22
+ ...base,
23
+ windowUrl: `${overridingEnvs.windowUrl}${STATIC.ELECTRON_ROUTE}`
24
+ };
25
+ }
26
+ if (process.env.NODE_ENV === "production" && process.env.VITE_DEV_SERVER_URL) {
21
27
  return {
22
28
  ...base,
23
29
  windowUrl: `${process.env.VITE_DEV_SERVER_URL}${STATIC.ELECTRON_ROUTE}`
24
30
  };
25
- } else if (STATIC.ELECTRON_PROD_URL && STATIC.ELECTRON_BUILD_DIR) {
31
+ } else if (STATIC.ELECTRON_PROD_URL !== void 0 && STATIC.ELECTRON_BUILD_DIR !== void 0) {
26
32
  return {
27
33
  ...base,
28
34
  // careful, do not use path.join, it will remove extra slashes
29
- windowUrl: `file://${STATIC.ELECTRON_PROD_URL}`
35
+ windowUrl: `${protocolName}://bundle/${STATIC.ELECTRON_PROD_URL}`
30
36
  };
31
37
  }
32
38
  unreachable();
@@ -1,6 +1,7 @@
1
1
  export * from "./types";
2
2
  export { getPaths } from "./getPaths";
3
- export { createNuxtFileProtocolHandler } from "./createNuxtFileProtocolHandler";
3
+ export { createProxiedProtocolHandler } from "./createProxiedProtocolHandler";
4
+ export { createPrivilegedProtocolScheme } from "./createPrivilegedProtocolScheme";
4
5
  export { createWindowControlsApi } from "./createWindowControlsApi";
5
6
  export { createWindowControlsApiHandler } from "./createWindowControlsApiHandler";
6
7
  export { useNuxtRuntimeConfig } from "./useNuxtRuntimeConfig";
@@ -1,6 +1,7 @@
1
1
  export * from "./types.js";
2
2
  export { getPaths } from "./getPaths.js";
3
- export { createNuxtFileProtocolHandler } from "./createNuxtFileProtocolHandler.js";
3
+ export { createProxiedProtocolHandler } from "./createProxiedProtocolHandler.js";
4
+ export { createPrivilegedProtocolScheme } from "./createPrivilegedProtocolScheme.js";
4
5
  export { createWindowControlsApi } from "./createWindowControlsApi.js";
5
6
  export { createWindowControlsApiHandler } from "./createWindowControlsApiHandler.js";
6
7
  export { useNuxtRuntimeConfig } from "./useNuxtRuntimeConfig.js";
package/genDevDesktop.js CHANGED
@@ -1,9 +1,12 @@
1
+ import { crop } from "@alanscodelog/utils/crop"
1
2
  import { run } from "@alanscodelog/utils/run"
2
3
  import JSON5 from "json5"
4
+ import fsSync from "node:fs"
3
5
  import fs from "node:fs/promises"
4
6
  import path from "node:path"
5
7
 
6
8
  if (process.env.CI) {
9
+ // eslint-disable-next-line no-console
7
10
  console.log("Skipping desktop file generation in CI.")
8
11
  process.exit(0)
9
12
  }
@@ -15,10 +18,54 @@ if (["darwin", "linux"].includes(process.platform)) {
15
18
  process.exit(0)
16
19
  }
17
20
 
18
- const electronBuilderConfigPath = path.join(process.cwd(), "electron-builder.json5")
19
- const electronBuilderConfig = JSON5.parse(await fs.readFile(electronBuilderConfigPath, "utf8"))
21
+ function getConfigPath() {
22
+ const electronBuilderConfigJsPath = path.join(process.cwd(), "electron-builder-config.js")
23
+ const electronBuilderConfigJson5Path = path.join(process.cwd(), "electron-builder.json5")
24
+ const electronBuilderConfigJsonPath = path.join(process.cwd(), "electron-builder.json")
25
+ const existingPath = fsSync.existsSync(electronBuilderConfigJsPath)
26
+ ? electronBuilderConfigJsPath
27
+ : fsSync.existsSync(electronBuilderConfigJsonPath)
28
+ ? electronBuilderConfigJsonPath
29
+ : fsSync.existsSync(electronBuilderConfigJson5Path)
30
+ ? electronBuilderConfigJson5Path
31
+ : undefined
32
+ if (!existingPath) {
33
+ throw new Error(crop`Electron builder config file could not be found at the default locations. If you are using a different file path, please pass it as the second argument.
34
+
35
+ Default search locations:
36
+ - electron-builder-config.js (this cannot be named electron-builder.js, see, https://github.com/electron-userland/electron-builder/issues/6227)
37
+ - electron-builder.json5
38
+ - electron-builder.json
39
+ `
40
+ )
41
+ } else {
42
+ // eslint-disable-next-line no-console
43
+ console.log(`Using config path ${existingPath}.`)
44
+ }
45
+ return existingPath
46
+ }
47
+
48
+ async function getConfig(pathOverride) {
49
+ const configPath = pathOverride ?? getConfigPath()
50
+ /** @type {import("electron-builder").Configuration} */
51
+ let config
52
+ if (configPath.endsWith(".js")) {
53
+ config = (await import(configPath)).default
54
+ } else {
55
+ config = JSON5.parse(await fs.readFile(configPath, "utf-8"))
56
+ }
57
+ if (process.env.DEBUG) {
58
+ // eslint-disable-next-line no-console
59
+ console.log(config)
60
+ }
61
+ return config
62
+ }
63
+
64
+ const configPathOverride = process.argv[4]
65
+
66
+ const config = await getConfig(configPathOverride)
20
67
 
21
- const desktopFile = electronBuilderConfig.linux?.desktop
68
+ const desktopFile = config.linux?.desktop
22
69
 
23
70
  desktopFile.Path = process.cwd()
24
71
  desktopFile.Exec = process.argv[3] ?? `npm run launch:electron "%u"`
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@witchcraft/nuxt-electron",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Nuxt module for working with electron.",
5
- "repository": "witchcraftjs/nuxt-electron",
5
+ "repository": "https://github.com/witchcraftjs/nuxt-electron",
6
6
  "license": "MIT",
7
7
  "type": "module",
8
8
  "sideEffects": false,
@@ -26,17 +26,18 @@
26
26
  ],
27
27
  "dependencies": {
28
28
  "@alanscodelog/utils": "^6.0.2",
29
- "@nuxt/kit": "^4.0.3",
29
+ "@nuxt/kit": "^4.3.1",
30
30
  "@witchcraft/nuxt-utils": "^0.3.6",
31
- "@witchcraft/ui": "^0.3.7",
31
+ "@witchcraft/ui": "^0.3.24",
32
32
  "defu": "^6.1.4",
33
- "es-toolkit": "^1.39.10",
33
+ "electron": "^37.10.3",
34
+ "es-toolkit": "^1.44.0",
34
35
  "vite-plugin-electron": "^0.29.0",
35
36
  "vite-plugin-externalize-deps": "^0.9.0"
36
37
  },
37
38
  "peerDependencies": {
38
- "@witchcraft/ui": "^0.3.7",
39
- "unplugin-icons": "^22.3.0"
39
+ "@witchcraft/ui": "^0.3.24",
40
+ "unplugin-icons": "^22.5.0"
40
41
  },
41
42
  "peerDependenciesMeta": {
42
43
  "unplugin-icons": {
@@ -48,23 +49,22 @@
48
49
  },
49
50
  "devDependencies": {
50
51
  "@alanscodelog/eslint-config": "^6.3.1",
51
- "@alanscodelog/semantic-release-config": "^6.0.0",
52
+ "@alanscodelog/semantic-release-config": "^6.0.2",
52
53
  "@alanscodelog/tsconfigs": "^6.2.0",
53
- "@nuxt/eslint-config": "^1.9.0",
54
+ "@nuxt/eslint-config": "^1.15.1",
54
55
  "@nuxt/module-builder": "^1.0.2",
55
- "@nuxt/schema": "^4.0.3",
56
- "@nuxt/test-utils": "^3.19.2",
56
+ "@nuxt/schema": "^4.3.1",
57
+ "@nuxt/test-utils": "^3.23.0",
57
58
  "@types/node": "latest",
58
59
  "changelogen": "^0.6.2",
59
- "electron": "^38.0.0",
60
- "eslint": "^9.34.0",
60
+ "eslint": "^9.39.2",
61
61
  "husky": "^9.1.7",
62
62
  "json5": "^2.2.3",
63
- "nuxt": "^4.0.3",
64
- "typescript": "^5.8.3",
65
- "unplugin-icons": "^22.3.0",
63
+ "nuxt": "^4.3.1",
64
+ "typescript": "^5.9.3",
65
+ "unplugin-icons": "^22.5.0",
66
66
  "vitest": "^3.2.4",
67
- "vue-tsc": "^3.0.6"
67
+ "vue-tsc": "^3.2.4"
68
68
  },
69
69
  "release": {
70
70
  "extends": [
@@ -80,11 +80,13 @@
80
80
  "access": "public"
81
81
  },
82
82
  "scripts": {
83
- "build": "nuxt-module-build prepare && nuxt-module-build build && nuxi build playground",
83
+ "build": "nuxt-module-build prepare && nuxt-module-build build && pnpm build:playground",
84
+ "build:playground": "cd playground && pnpm nuxt prepare && pnpm build:electron",
84
85
  "build:only": "nuxt-module-build build",
85
- "dev": "nuxi dev playground",
86
+ "dev": "cd playground && pnpm dev:electron",
86
87
  "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
87
- "lint": "eslint \"{src,test,playground/app}/**/*.{ts,vue}\" \"*.{js,cjs,mjs,ts}\"",
88
+ "lint": "pnpm lint:eslint && pnpm lint:types",
89
+ "lint:eslint": "eslint \"{src,test,playground/app}/**/*.{ts,vue}\" \"*.{js,cjs,mjs,ts}\"",
88
90
  "lint:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit",
89
91
  "test": "vitest run",
90
92
  "test:watch": "vitest watch"
package/src/module.ts CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  extendRouteRules,
10
10
  useLogger
11
11
  } from "@nuxt/kit"
12
- import { createConstantCaseVariables, nuxtFileBasedRouting, nuxtRemoveUneededPages, nuxtRerouteOutputTo } from "@witchcraft/nuxt-utils/utils"
12
+ import { createConstantCaseVariables, nuxtRemoveUneededPages, nuxtRerouteOutputTo } from "@witchcraft/nuxt-utils/utils"
13
13
  import { defu } from "defu"
14
14
  import fs from "node:fs/promises"
15
15
  import path from "node:path"
@@ -20,7 +20,6 @@ import { build, type ElectronOptions, startup } from "vite-plugin-electron"
20
20
  import { notBundle } from "vite-plugin-electron/plugin"
21
21
  import { externalizeDeps } from "vite-plugin-externalize-deps"
22
22
 
23
- import pkg from "../package.json" with { type: "json" }
24
23
 
25
24
  // https://github.com/electron-vite/vite-plugin-electron/issues/251#issuecomment-2360153184
26
25
  startup.exit = async () => {
@@ -206,13 +205,14 @@ export default defineNuxtModule<ModuleOptions>({
206
205
  const srcDir = await resolvePath(options.srcDir, nuxt.options.alias)
207
206
  const nonElectronNuxtBuildDir = await resolvePath(options.nonElectronNuxtBuildDir, nuxt.options.alias)
208
207
 
208
+ // at this point they are made relative to the nuxt dir
209
+ // for passing to electron we make them relative to main
209
210
  const electronRootBuildDir = await resolvePath(options.electronBuildDir, nuxt.options.alias)
210
211
  const relativeElectronDir = path.relative(nuxt.options.rootDir, electronRootBuildDir)
211
212
  // must be relative
212
213
  const electronNuxtDir = path.join(relativeElectronDir, ".output")
213
214
  // must be relative
214
215
  const electronBuildDir = path.join(relativeElectronDir, "build")
215
- // relative to nuxt dir
216
216
  const electronProdUrl = `${options.electronRoute}/index.html`
217
217
  const electronRoute = options.electronRoute
218
218
 
@@ -230,7 +230,7 @@ export default defineNuxtModule<ModuleOptions>({
230
230
  }
231
231
 
232
232
  const isElectronBuild = process.env.BUILD_ELECTRON === "true" && hasScripts
233
- const skipElectronPack = process.env.SKIP_ELECTRON_PACK === "true"
233
+ const skipElectronPack = !isElectronBuild || process.env.SKIP_ELECTRON_PACK === "true"
234
234
 
235
235
  const autoOpen = !!(options.autoOpen && hasScripts && isDev)
236
236
  const useWatch = nuxt.options.dev
@@ -295,6 +295,7 @@ export default defineNuxtModule<ModuleOptions>({
295
295
  let started = false
296
296
 
297
297
  nuxt.hook("vite:extendConfig", config => {
298
+ // @ts-expect-error it's both readonly and possible undefined???
298
299
  config.define ??= {}
299
300
  config.define["import.meta.electron"] = "false"
300
301
  config.define["process.electron"] = "false"
@@ -313,11 +314,11 @@ export default defineNuxtModule<ModuleOptions>({
313
314
  )
314
315
  const additionalElectronVariables = defu(
315
316
  options.additionalElectronVariables,
316
- nuxt.options.electron.additionalElectronVariables
317
+ (nuxt.options.electron === false ? {} : nuxt.options.electron.additionalElectronVariables)
317
318
  )
318
319
  const additionalViteDefinesToCopy = [
319
320
  ...options.additionalViteDefinesToCopy,
320
- ...(nuxt.options.electron.additionalViteDefinesToCopy ?? [])
321
+ ...(nuxt.options.electron === false ? [] : (nuxt.options.electron.additionalViteDefinesToCopy ?? []))
321
322
  ]
322
323
 
323
324
  const copyFromVite = [
@@ -337,11 +338,13 @@ export default defineNuxtModule<ModuleOptions>({
337
338
  copyFromVite.map(v => [v, viteConfig.define![v]])
338
339
  ),
339
340
  ...createConstantCaseVariables({
340
- electronRoute,
341
- electronProdUrl,
342
- electronNuxtDir,
343
- electronNuxtPublicDir,
344
- electronBuildDir,
341
+ // on windows the backslashes in the paths must be double escaped
342
+ electronRoute: electronRoute.replaceAll("\\", "\\\\"),
343
+ electronProdUrl: electronProdUrl.replaceAll("\\", "\\\\"),
344
+ // all paths muxt be made relative to the build dir where electron will launch from in production
345
+ electronNuxtDir: path.relative(electronBuildDir, relativeElectronDir).replaceAll("\\", "\\\\"),
346
+ electronNuxtPublicDir: path.relative(electronBuildDir, electronNuxtPublicDir).replaceAll("\\", "\\\\"),
347
+ electronBuildDir: path.relative(electronBuildDir, electronBuildDir).replaceAll("\\", "\\\\"),
345
348
  // ...options.additionalElectronVariables,
346
349
  // nuxt's runtimeConfig cannot be used in electron's main since it's built seperately
347
350
  // also we must stringify ourselves since escaped double quotes are not preserved in the final output :/
@@ -514,12 +517,28 @@ export default defineNuxtModule<ModuleOptions>({
514
517
 
515
518
  // the "/" is not technically needed but only if we properly split the chunks by pages and that was causing issues
516
519
  nuxtRemoveUneededPages(nuxt, ["/", electronRoute, ...options.additionalRoutes!])
517
- extendRouteRules(electronRoute, { ssr: false, prerender: true }, { override: true })
518
520
 
519
- nuxt.options.router = defu(
520
- nuxtFileBasedRouting().router,
521
- (nuxt.options.router as any) ?? {}
522
- )
521
+ nuxt.options.router.options ??= {}
522
+ nuxt.options.router.options.hashMode = false
523
+
524
+ extendRouteRules(electronRoute + "/**", {
525
+ prerender: true
526
+ }, { override: true })
527
+
528
+ for (const route of options.additionalRoutes) {
529
+ extendRouteRules(route, {
530
+ prerender: true
531
+ }, { override: true })
532
+ }
533
+
534
+
535
+ /* Nuxt in build mode won't generate this. And we need build mode so api calls aren't baked in. */
536
+ nuxt.hook("prerender:routes", ({ routes }) => {
537
+ for (const route of ["/404.html"]) {
538
+ routes.add(route)
539
+ }
540
+ })
541
+
523
542
 
524
543
  nuxtRerouteOutputTo(nuxt, electronNuxtDir)
525
544
 
@@ -550,8 +569,16 @@ export default defineNuxtModule<ModuleOptions>({
550
569
  } else {
551
570
  logger.info(`Skipping Electron Build`)
552
571
  }
553
-
554
- nuxtRerouteOutputTo(nuxt, nonElectronNuxtBuildDir)
572
+ const nuxtOutputDir = nuxt.options.nitro?.output?.dir
573
+ if (nuxtOutputDir === undefined || nuxtOutputDir === ".output") {
574
+ logger.warn(crop`Nitro output dir is not set or set to the default, it's suggested you set it to the following when using nuxt-electron:
575
+ nitro: {
576
+ output: ".dist/web/.output",
577
+ serverDir: ".dist/web/.output/server"
578
+ publicDir: ".dist/web/.output/public"
579
+ }
580
+ .`)
581
+ }
555
582
  }
556
583
 
557
584
  addImportsDir(resolve("runtime/utils"))
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Creates a protocol scheme to register as privileged with the following permissions (you can override them with the second parameter).
3
+ *
4
+ * Note this must be called before the app's ready event:
5
+ *
6
+ * ```ts
7
+ * protocol.registerSchemesAsPrivileged([
8
+ * createPrivilegedProtocolScheme("app")
9
+ * })
10
+ * ```
11
+ *
12
+ * ## Privileges
13
+ *
14
+ * Basically all except `bypassCSP` and `enableCORS`.
15
+ *
16
+ * - `standard`
17
+ * - `allowServiceWorkers`
18
+ * - `bypassCSP`
19
+ * - `corsEnabled`
20
+ * - `secure`
21
+ * - `supportFetchAPI`
22
+ * - `codeCache`
23
+ * - `stream`
24
+ */
25
+ export function createPrivilegedProtocolScheme(protocolName: string, privileges: Partial<Electron.CustomScheme["privileges"]> = {}): Electron.CustomScheme {
26
+ return {
27
+ scheme: protocolName,
28
+ privileges: {
29
+ bypassCSP: false,
30
+ corsEnabled: false,
31
+ stream: false,
32
+ standard: true,
33
+ secure: true,
34
+ supportFetchAPI: true,
35
+ allowServiceWorkers: true,
36
+ codeCache: true,
37
+ ...privileges
38
+ }
39
+ }
40
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Creates a protocol scheme to register as privileged with the following permissions (you can override them with the second parameter).
3
+ *
4
+ * Note this must be called before the app's ready event:
5
+ *
6
+ * ```ts
7
+ * protocol.registerSchemesAsPrivileged([
8
+ * createPrivilegedProtocolScheme("app")
9
+ * })
10
+ * ```
11
+ *
12
+ * ## Privileges
13
+ *
14
+ * Basically all except `bypassCSP` and `enableCORS`.
15
+ *
16
+ * - `standard`
17
+ * - `allowServiceWorkers`
18
+ * - `bypassCSP`
19
+ * - `corsEnabled`
20
+ * - `secure`
21
+ * - `supportFetchAPI`
22
+ * - `codeCache`
23
+ * - `stream`
24
+ */
25
+ export function createPrivilegedProtocolScheme(protocolName: string, privileges: Partial<Electron.CustomScheme["privileges"]> = {}): Electron.CustomScheme {
26
+ return {
27
+ scheme: protocolName,
28
+ privileges: {
29
+ bypassCSP: false,
30
+ corsEnabled: false,
31
+ stream: false,
32
+ standard: true,
33
+ secure: true,
34
+ supportFetchAPI: true,
35
+ allowServiceWorkers: true,
36
+ codeCache: true,
37
+ ...privileges
38
+ }
39
+ }
40
+ }