@witchcraft/nuxt-electron 0.0.1

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.
Files changed (82) hide show
  1. package/README.md +299 -0
  2. package/dist/module.d.mts +117 -0
  3. package/dist/module.json +9 -0
  4. package/dist/module.mjs +362 -0
  5. package/dist/runtime/components/ElectronWindowControls.d.vue.ts +31 -0
  6. package/dist/runtime/components/ElectronWindowControls.vue +67 -0
  7. package/dist/runtime/components/ElectronWindowControls.vue.d.ts +31 -0
  8. package/dist/runtime/components/WindowControls/CloseButton.d.vue.ts +16 -0
  9. package/dist/runtime/components/WindowControls/CloseButton.vue +54 -0
  10. package/dist/runtime/components/WindowControls/CloseButton.vue.d.ts +16 -0
  11. package/dist/runtime/components/WindowControls/MaximizeButton.d.vue.ts +16 -0
  12. package/dist/runtime/components/WindowControls/MaximizeButton.vue +33 -0
  13. package/dist/runtime/components/WindowControls/MaximizeButton.vue.d.ts +16 -0
  14. package/dist/runtime/components/WindowControls/MinimizeButton.d.vue.ts +16 -0
  15. package/dist/runtime/components/WindowControls/MinimizeButton.vue +40 -0
  16. package/dist/runtime/components/WindowControls/MinimizeButton.vue.d.ts +16 -0
  17. package/dist/runtime/components/WindowControls/PinButton.d.vue.ts +27 -0
  18. package/dist/runtime/components/WindowControls/PinButton.vue +51 -0
  19. package/dist/runtime/components/WindowControls/PinButton.vue.d.ts +27 -0
  20. package/dist/runtime/electron/apiBuilder.d.ts +8 -0
  21. package/dist/runtime/electron/apiBuilder.js +9 -0
  22. package/dist/runtime/electron/createBroadcastHandlers.d.ts +4 -0
  23. package/dist/runtime/electron/createBroadcastHandlers.js +30 -0
  24. package/dist/runtime/electron/createBroadcaster.d.ts +2 -0
  25. package/dist/runtime/electron/createBroadcaster.js +7 -0
  26. package/dist/runtime/electron/createNuxtFileProtocolHandler.d.ts +14 -0
  27. package/dist/runtime/electron/createNuxtFileProtocolHandler.js +20 -0
  28. package/dist/runtime/electron/createWindowControlsApi.d.ts +19 -0
  29. package/dist/runtime/electron/createWindowControlsApi.js +6 -0
  30. package/dist/runtime/electron/createWindowControlsApiHandler.d.ts +4 -0
  31. package/dist/runtime/electron/createWindowControlsApiHandler.js +24 -0
  32. package/dist/runtime/electron/getEventWindow.d.ts +11 -0
  33. package/dist/runtime/electron/getEventWindow.js +8 -0
  34. package/dist/runtime/electron/getPaths.d.ts +27 -0
  35. package/dist/runtime/electron/getPaths.js +33 -0
  36. package/dist/runtime/electron/getPreloadMeta.d.ts +25 -0
  37. package/dist/runtime/electron/getPreloadMeta.js +7 -0
  38. package/dist/runtime/electron/index.d.ts +16 -0
  39. package/dist/runtime/electron/index.js +16 -0
  40. package/dist/runtime/electron/promisifyApi.d.ts +40 -0
  41. package/dist/runtime/electron/promisifyApi.js +41 -0
  42. package/dist/runtime/electron/promisifyReply.d.ts +8 -0
  43. package/dist/runtime/electron/promisifyReply.js +27 -0
  44. package/dist/runtime/electron/registerDevtoolsShortcuts.d.ts +2 -0
  45. package/dist/runtime/electron/registerDevtoolsShortcuts.js +10 -0
  46. package/dist/runtime/electron/static.d.ts +12 -0
  47. package/dist/runtime/electron/static.js +8 -0
  48. package/dist/runtime/electron/types.d.ts +1 -0
  49. package/dist/runtime/electron/types.js +0 -0
  50. package/dist/runtime/electron/useDevDataDir.d.ts +1 -0
  51. package/dist/runtime/electron/useDevDataDir.js +8 -0
  52. package/dist/runtime/electron/useNuxtRuntimeConfig.d.ts +2 -0
  53. package/dist/runtime/electron/useNuxtRuntimeConfig.js +4 -0
  54. package/dist/runtime/utils/isElectron.d.ts +9 -0
  55. package/dist/runtime/utils/isElectron.js +3 -0
  56. package/dist/types.d.mts +3 -0
  57. package/genDevDesktop.js +47 -0
  58. package/package.json +93 -0
  59. package/src/module.ts +549 -0
  60. package/src/runtime/components/ElectronWindowControls.vue +94 -0
  61. package/src/runtime/components/WindowControls/CloseButton.vue +56 -0
  62. package/src/runtime/components/WindowControls/MaximizeButton.vue +35 -0
  63. package/src/runtime/components/WindowControls/MinimizeButton.vue +42 -0
  64. package/src/runtime/components/WindowControls/PinButton.vue +56 -0
  65. package/src/runtime/electron/apiBuilder.ts +27 -0
  66. package/src/runtime/electron/createBroadcastHandlers.ts +36 -0
  67. package/src/runtime/electron/createBroadcaster.ts +11 -0
  68. package/src/runtime/electron/createNuxtFileProtocolHandler.ts +42 -0
  69. package/src/runtime/electron/createWindowControlsApi.ts +27 -0
  70. package/src/runtime/electron/createWindowControlsApiHandler.ts +34 -0
  71. package/src/runtime/electron/getEventWindow.ts +19 -0
  72. package/src/runtime/electron/getPaths.ts +68 -0
  73. package/src/runtime/electron/getPreloadMeta.ts +36 -0
  74. package/src/runtime/electron/index.ts +17 -0
  75. package/src/runtime/electron/promisifyApi.ts +102 -0
  76. package/src/runtime/electron/promisifyReply.ts +49 -0
  77. package/src/runtime/electron/registerDevtoolsShortcuts.ts +12 -0
  78. package/src/runtime/electron/static.ts +14 -0
  79. package/src/runtime/electron/types.ts +1 -0
  80. package/src/runtime/electron/useDevDataDir.ts +8 -0
  81. package/src/runtime/electron/useNuxtRuntimeConfig.ts +7 -0
  82. package/src/runtime/utils/isElectron.ts +11 -0
package/src/module.ts ADDED
@@ -0,0 +1,549 @@
1
+ import { crop } from "@alanscodelog/utils/crop"
2
+ import { run } from "@alanscodelog/utils/run"
3
+ import {
4
+ addComponentsDir,
5
+ addImportsDir,
6
+ addTemplate,
7
+ createResolver,
8
+ defineNuxtModule,
9
+ extendRouteRules,
10
+ installModule,
11
+ useLogger
12
+ } from "@nuxt/kit"
13
+ import { createConstantCaseVariables, nuxtFileBasedRouting, nuxtRemoveUneededPages, nuxtRerouteOutputTo } from "@witchcraft/nuxt-utils/utils"
14
+ import { defu } from "defu"
15
+ import fs from "node:fs/promises"
16
+ import path from "node:path"
17
+ import type { ViteConfig } from "nuxt/schema"
18
+ import type { RollupOutput, RollupWatcher } from "rollup"
19
+ import type { ViteDevServer } from "vite"
20
+ import { build, type ElectronOptions, startup } from "vite-plugin-electron"
21
+ import { notBundle } from "vite-plugin-electron/plugin"
22
+ import { externalizeDeps } from "vite-plugin-externalize-deps"
23
+
24
+ // https://github.com/electron-vite/vite-plugin-electron/issues/251#issuecomment-2360153184
25
+ startup.exit = async () => {
26
+ if ("electronApp" in process) {
27
+ try {
28
+ const app = process.electronApp as any
29
+ app.removeAllListeners()
30
+ process.kill(app.pid)
31
+ } catch (e) {
32
+ // eslint-disable-next-line no-console
33
+ console.error("Could not kill electron instance/s.")
34
+ // eslint-disable-next-line no-console
35
+ console.error(e)
36
+ }
37
+ }
38
+ }
39
+
40
+ export interface ModuleOptions {
41
+ srcDir: string
42
+ /**
43
+ * The dir for building for electron. `.output` and `build` will be put in here, and also release (but that's left up to your electron packing configuration).
44
+ *
45
+ * @default ".dist/electron/"
46
+ */
47
+ electronBuildDir: string
48
+ /**
49
+ * The nuxt output dir when not building electron.
50
+ *
51
+ * @default ".dist/web/.output"
52
+ */
53
+ nonElectronNuxtBuildDir: string
54
+ /**
55
+ * The main route of the electron app. Electron will be pointed to this route.
56
+ *
57
+ * @default "/app"
58
+ */
59
+ electronRoute: string
60
+ /**
61
+ * Additional routes to include in the electron build. Note that "/" is always included as not including it was causing issues.
62
+ */
63
+ additionalRoutes: string[]
64
+ /** Extra cli arguments to launch electron with in dev mode */
65
+ extraCliArgs: string[]
66
+ /**
67
+ * If set, adds `--user-data-dir ${devUserDataDir}` to the cli arguments in development mode.
68
+ *
69
+ * You will then need to parse this in main.ts, a `useDevDataDir` function is provided for this. This does not do any advanced parsing, just takes the next argument after `--user-data-dir` so the path must not contain spaces.
70
+ * ```ts
71
+ * const userDataDir = useDevDataDir() ?? app.getPath("userData")
72
+ *
73
+ * @default "~~/.user-data-dir"
74
+ */
75
+ devUserDataDir: string
76
+ /**
77
+ * The script to run to build/pack the electron app.
78
+ *
79
+ * @default "npm run build:electron:pack"
80
+ */
81
+ electronBuildPackScript: string
82
+ /** Whether to enable the module. */
83
+ enable: boolean
84
+ /**
85
+ * Whether to auto-open electron. If undefined, is controlled by the AUTO_OPEN env variable instead (it should include the word `electron` to enable autoOpen).
86
+ *
87
+ * This only works in dev mode.
88
+ *
89
+ * @default undefined
90
+ */
91
+ autoOpen: boolean
92
+ /**
93
+ * Pass public runtime config options only for electron.
94
+ */
95
+ electronOnlyRuntimeConfig: Record<string, any>
96
+ /**
97
+ * Whether you're using a preload script.
98
+ *
99
+ * @default true
100
+ */
101
+ usePreloadScript: boolean
102
+ /**
103
+ * Pass custom vite options to the electron vite builder (e.g. wasm()) as they are not copied over from nuxt's vite config.
104
+ *
105
+ * Note that `build.emptyOutDir` cannot be changed, it must be false for the reloading to work.
106
+ */
107
+ // importing the type from vite is causing build issues :/
108
+ electronViteOptions: ElectronOptions["vite"]
109
+ notBundleOptions: Parameters<typeof notBundle>[0]
110
+ /**
111
+ * Additional variables to "bake" into the electron build. Note these must be quoted if they are strings.
112
+ *
113
+ * You can set these as properties of STATIC, to make it clearer what they are.
114
+ *
115
+ * ```ts
116
+ * import { STATIC } from "@witchcraft/nuxt-electron/electron"
117
+ *
118
+ * // assuming electron.additionalElectronVariables.someVariable = `"some-var"`
119
+ *
120
+ * STATIC.SOME_VARIABLE = process.env.SOME_VARIABLE
121
+ *
122
+ * // becomes
123
+ * STATIC.SOME_VARIABLE = "some-var"
124
+ * ```
125
+ */
126
+ additionalElectronVariables: Record<string, string>
127
+ /**
128
+ * Additional vite defines to copy from the resolved vite config.
129
+ *
130
+ * The module copies the following defines from nuxt's vite config for the electron vite config:
131
+ *
132
+ * ```
133
+ * __NUXT_VERSION__
134
+ * process.dev
135
+ * import.meta.dev
136
+ * process.test
137
+ * import.meta.test
138
+ *```
139
+ * And the following are also added:
140
+ * ```
141
+ * import.meta.electron (true from main, false elsewhere)
142
+ * process.electron (true from main, false elsewhere)
143
+ * ```
144
+ *
145
+ * They will only be true for files that might be imported by main only. For renderer/client side use `isElectron()` instead. We cannot define them on the client side because during dev they would be wrong (electorn is always pointing to the same page as the server).
146
+ *
147
+ */
148
+ additionalViteDefinesToCopy: string[]
149
+ }
150
+
151
+ export default defineNuxtModule<ModuleOptions>({
152
+ meta: {
153
+ name: "electron",
154
+ configKey: "electron"
155
+ },
156
+ // Default configuration options of the Nuxt module
157
+ defaults: {
158
+ srcDir: "~~/app-electron",
159
+ electronBuildDir: "~~/.dist/electron",
160
+ nonElectronNuxtBuildDir: "~~/.dist/web/.output",
161
+ devUserDataDir: "~~/.user-data-dir",
162
+ electronRoute: "/app",
163
+ autoOpen: process.env.AUTO_OPEN?.includes("electron"),
164
+ electronBuildPackScript: "npm run build:electron:pack",
165
+ additionalRoutes: [],
166
+ extraCliArgs: [],
167
+ enable: true,
168
+ electronOnlyRuntimeConfig: {},
169
+ usePreloadScript: true,
170
+ electronViteOptions: {},
171
+ additionalElectronVariables: {},
172
+ additionalViteDefinesToCopy: [],
173
+ notBundleOptions: {}
174
+ },
175
+ async setup(options, nuxt) {
176
+ if (!options.enable) { return }
177
+ const moduleName = "@witchcraft/nuxt-electron"
178
+ const logger = useLogger(moduleName)
179
+ const { resolvePath, resolve } = createResolver(import.meta.url)
180
+
181
+ addComponentsDir({
182
+ global: true,
183
+ path: resolve("runtime/components")
184
+ })
185
+ await installModule("@witchcraft/ui/nuxt", (nuxt.options as any).witchcraftUi)
186
+
187
+ addTemplate({
188
+ filename: "witchcraft-electron.css",
189
+ write: true,
190
+ getContents: () => crop`
191
+ @source "${resolve("runtime/components")}";
192
+ `
193
+ })
194
+
195
+ const isDev = nuxt.options.dev
196
+ const srcDir = await resolvePath(options.srcDir, nuxt.options.alias)
197
+ const nonElectronNuxtBuildDir = await resolvePath(options.nonElectronNuxtBuildDir, nuxt.options.alias)
198
+
199
+ const electronRootBuildDir = await resolvePath(options.electronBuildDir, nuxt.options.alias)
200
+ const relativeElectronDir = path.relative(nuxt.options.rootDir, electronRootBuildDir)
201
+ // must be relative
202
+ const electronNuxtDir = path.join(relativeElectronDir, ".output")
203
+ // must be relative
204
+ const electronBuildDir = path.join(relativeElectronDir, "build")
205
+ // relative to nuxt dir
206
+ const electronProdUrl = `${options.electronRoute}/index.html`
207
+ const electronRoute = options.electronRoute
208
+
209
+ const electronNuxtPublicDir = path.join(electronNuxtDir, "public")
210
+ const mainScriptPath = path.join(srcDir, "main.ts")
211
+ const preloadScriptPath = path.join(srcDir, "preload.ts")
212
+
213
+ const hasMainScript = await fs.stat(mainScriptPath).then(() => true).catch(() => false)
214
+ const hasPreloadScript = !options.usePreloadScript || await fs.stat(preloadScriptPath).then(() => true).catch(() => false)
215
+
216
+ const hasScripts = hasMainScript && hasPreloadScript
217
+ // also needed because during prepare for the module, they won't exist in the module dir
218
+ if (!hasScripts) {
219
+ logger.warn(`Missing electron scripts: ${[hasMainScript ? "" : "main.ts", hasPreloadScript ? "" : "preload.ts"].join(", ")}. Skipping electron build.`)
220
+ }
221
+
222
+ const isElectronBuild = process.env.BUILD_ELECTRON === "true" && hasScripts
223
+ const skipElectronPack = process.env.SKIP_ELECTRON_PACK === "true"
224
+
225
+ const autoOpen = !!(options.autoOpen && hasScripts && isDev)
226
+ const useWatch = nuxt.options.dev
227
+
228
+ const devUserDataDir = options.devUserDataDir && await resolvePath(options.devUserDataDir, nuxt.options.alias)
229
+ if (devUserDataDir) {
230
+ if (!await fs.stat(devUserDataDir).then(() => true).catch(() => false)) {
231
+ await fs.mkdir(devUserDataDir, {
232
+ recursive: true
233
+ })
234
+ }
235
+ }
236
+
237
+ logger.debug({
238
+ isDev,
239
+ useWatch,
240
+ srcDir,
241
+ prodNonElectronNuxtDir: nonElectronNuxtBuildDir,
242
+ electronRootBuildDir,
243
+ relativeElectronDir,
244
+ electronNuxtDir,
245
+ electronBuildDir,
246
+ electronProdUrl,
247
+ electronRoute,
248
+ electronNuxtPublicDir,
249
+ mainScriptPath,
250
+ preloadScriptPath,
251
+ isElectronBuild,
252
+ skipElectronPack,
253
+ autoOpen,
254
+ devUserDataDir
255
+ })
256
+
257
+ let resolveGetViteServer: (res: ViteDevServer) => void
258
+
259
+ const viteServerPromise = new Promise<ViteDevServer>(resolve => {
260
+ resolveGetViteServer = resolve
261
+ })
262
+ nuxt.hook("vite:serverCreated", server => {
263
+ logger.info(`Resolved vite server.`)
264
+ resolveGetViteServer(server as any)
265
+ })
266
+
267
+ const viteServerUrl = new Promise<string | undefined>(resolve => {
268
+ nuxt.hook("build:before", () => {
269
+ resolve(undefined)
270
+ })
271
+ nuxt.hook("listen", (_server, listener) => {
272
+ logger.info(`Resolved server url.`, listener.url)
273
+ // listener does not have a type! :/
274
+ resolve(listener.url as string)
275
+ })
276
+ })
277
+
278
+ const viteConfigPromise = new Promise<ViteConfig>(resolve => {
279
+ nuxt.hook("vite:configResolved", config => {
280
+ resolve(config)
281
+ })
282
+ })
283
+
284
+ let maybeWatchers: (RollupWatcher | RollupOutput | RollupOutput[])[]
285
+ let started = false
286
+
287
+ nuxt.hook("vite:extendConfig", config => {
288
+ config.define ??= {}
289
+ config.define["import.meta.electron"] = "false"
290
+ config.define["process.electron"] = "false"
291
+ })
292
+
293
+ const buildElectron = async () => {
294
+ if (maybeWatchers || started) return
295
+
296
+ started = true
297
+
298
+ const viteConfig = await viteConfigPromise
299
+
300
+ const electronRuntimeConfig = defu(
301
+ options.electronOnlyRuntimeConfig,
302
+ nuxt.options.runtimeConfig.public
303
+ )
304
+ const additionalElectronVariables = defu(
305
+ options.additionalElectronVariables,
306
+ nuxt.options.electron.additionalElectronVariables
307
+ )
308
+ const additionalViteDefinesToCopy = [
309
+ ...options.additionalViteDefinesToCopy,
310
+ ...(nuxt.options.electron.additionalViteDefinesToCopy ?? [])
311
+ ]
312
+
313
+ const copyFromVite = [
314
+ "__NUXT_VERSION__",
315
+ "process.dev",
316
+ "import.meta.dev",
317
+ "process.test",
318
+ "import.meta.test",
319
+ ...additionalViteDefinesToCopy
320
+ ]
321
+
322
+ // this is typed in app-electron/static.ts
323
+ const electronVariables = {
324
+ "process.electron": true,
325
+ "import.meta.electron": true,
326
+ ...Object.fromEntries(
327
+ copyFromVite.map(v => [v, viteConfig.define![v]])
328
+ ),
329
+ ...createConstantCaseVariables({
330
+ electronRoute,
331
+ electronProdUrl,
332
+ electronNuxtDir,
333
+ electronNuxtPublicDir,
334
+ electronBuildDir,
335
+ // ...options.additionalElectronVariables,
336
+ // nuxt's runtimeConfig cannot be used in electron's main since it's built seperately
337
+ // also we must stringify ourselves since escaped double quotes are not preserved in the final output :/
338
+ electronRuntimeConfig: JSON.stringify(electronRuntimeConfig).replaceAll("\\", "\\\\")
339
+ }, "process.env."), // wut am having issue with just using STATIC. directly ???
340
+ ...createConstantCaseVariables(
341
+ additionalElectronVariables,
342
+ "process.env.",
343
+ { autoquote: false }
344
+ )
345
+ }
346
+
347
+ const electronViteOptions: ElectronOptions["vite"] = defu(
348
+ {
349
+ build: {
350
+ // must be false or preload can get deleted when vite rebuilds
351
+ emptyOutDir: false
352
+ }
353
+ },
354
+ options.electronViteOptions,
355
+ {
356
+ build: {
357
+ outDir: electronBuildDir,
358
+ minify: false
359
+ },
360
+ define: electronVariables,
361
+ resolve: {
362
+ alias: nuxt.options.alias,
363
+ extensions: [".mjs", ".js", ".ts", ".jsx", ".tsx", ".json", ".vue"]
364
+ },
365
+ plugins: [externalizeDeps() as any]
366
+ })
367
+
368
+ const electronCliArgs = [
369
+ ".",
370
+ ...(process.env.NODE_ENV !== "production"
371
+ && devUserDataDir
372
+ ? [
373
+ "--user-data-dir",
374
+ devUserDataDir
375
+ ]
376
+ : []),
377
+ ...(options.extraCliArgs ?? [])
378
+ ]
379
+
380
+ const builds: ElectronOptions[] = [
381
+ {
382
+ entry: mainScriptPath,
383
+ onStart: autoOpen
384
+ ? async () => {
385
+ // vite-plugin-electron adds --no-electron to deal with dev server issues on some linux distros, but it's working for me and we don't want it
386
+ // https://github.com/electron-vite/vite-plugin-electron/pull/57
387
+ await startup([...electronCliArgs])
388
+ }
389
+ : undefined
390
+ },
391
+ ...(
392
+ options.usePreloadScript
393
+ ? [{
394
+ entry: preloadScriptPath,
395
+ onStart: async () => {
396
+ (await viteServerPromise).hot.send({ type: "full-reload" })
397
+ },
398
+ build: {
399
+ rollupOptions: {
400
+ output: {
401
+ inlineDynamicImports: true
402
+ }
403
+ }
404
+ }
405
+ }]
406
+ : []
407
+ )
408
+ ].map(entry => ({
409
+ vite: {
410
+ mode: process.env.NODE_ENV,
411
+ ...electronViteOptions,
412
+ build: {
413
+ watch: useWatch
414
+ ? {
415
+ include: [`${srcDir}/**/*`]
416
+ }
417
+ : null,
418
+ lib: {
419
+ entry: entry.entry,
420
+ formats: entry.entry.includes("preload") ? ["cjs"] : ["es"],
421
+ fileName: () => entry.entry.includes("preload") ? "[name].cjs" : "[name].mjs"
422
+ },
423
+ ...electronViteOptions.build
424
+ },
425
+ plugins: [
426
+ autoOpen
427
+ ? {
428
+ name: "plugin-start-electron",
429
+ async closeBundle() {
430
+ void entry.onStart?.()
431
+ }
432
+ }
433
+ : undefined,
434
+ // not bundle breaks preload because it tries to required from node_modules
435
+ // and sandboxed windows can't do that
436
+ ...(!isElectronBuild && !entry.entry.includes("preload") ? [notBundle(options.notBundleOptions)] : []),
437
+ ...(electronViteOptions.plugins ?? [])
438
+ ]
439
+ }
440
+ }))
441
+
442
+ logger.debug(builds)
443
+
444
+ const devUrl = await viteServerUrl
445
+ if (devUrl) {
446
+ Object.assign(process.env, {
447
+ VITE_DEV_SERVER_URL: devUrl.slice(0, -1)
448
+ })
449
+ }
450
+
451
+ logger.debug({
452
+ electronViteOptions,
453
+ prodNonElectronNuxtDir: nonElectronNuxtBuildDir,
454
+ electronVariables,
455
+ electronRuntimeConfig,
456
+ electronCliArgs,
457
+ devUrl
458
+ })
459
+
460
+ maybeWatchers = (await Promise.all(builds.map(async config =>
461
+ build(config)
462
+ .then(res => {
463
+ logger.info(`Build done.`)
464
+ return res
465
+ })
466
+ .catch(err => {
467
+ logger.error(`Build failed.`, err)
468
+ process.exit(1)
469
+ })
470
+ )))
471
+
472
+ if (useWatch) {
473
+ for (const maybeWatcher of maybeWatchers) {
474
+ if (maybeWatcher && "on" in maybeWatcher) {
475
+ maybeWatcher.on("change", e => {
476
+ logger.info(`Detected change in: ${e}`)
477
+ })
478
+ }
479
+ }
480
+ }
481
+ }
482
+
483
+ if (autoOpen) {
484
+ nuxt.hook("close", async () => {
485
+ logger.info(`Killing`)
486
+ await startup?.exit()
487
+ })
488
+
489
+ nuxt.hook("restart", async () => {
490
+ logger.info(`Killing and Restarting`)
491
+ await startup?.exit()
492
+ })
493
+ }
494
+
495
+ logger.info(`Building Electron: ${isElectronBuild}`)
496
+ if (isElectronBuild) {
497
+ // completely disable prefetching
498
+ // https://github.com/nuxt/nuxt/issues/18376#issuecomment-1431318970
499
+ nuxt.hook("build:manifest", manifest => {
500
+ for (const key of Object.keys(manifest)) {
501
+ manifest[key]!.dynamicImports = []
502
+ }
503
+ })
504
+
505
+ // the "/" is not technically needed but only if we properly split the chunks by pages and that was causing issues
506
+ nuxtRemoveUneededPages(nuxt, ["/", electronRoute, ...options.additionalRoutes!])
507
+ extendRouteRules(electronRoute, { ssr: false, prerender: true }, { override: true })
508
+
509
+ nuxt.options.router = defu(
510
+ nuxtFileBasedRouting().router,
511
+ (nuxt.options.router as any) ?? {}
512
+ )
513
+
514
+ nuxtRerouteOutputTo(nuxt, electronNuxtDir)
515
+
516
+ // the build:done hook doesn't work as nitro seems to build after
517
+ nuxt.hook("close", async () => {
518
+ logger.info(`Building Electron`)
519
+ await buildElectron()
520
+ if (!skipElectronPack) {
521
+ logger.info(`Packing Electron`)
522
+ const buildCommand = run(options.electronBuildPackScript, {
523
+ stdio: "inherit"
524
+ })
525
+
526
+ await buildCommand.promise
527
+ .catch(err => { logger.error("Error building electron.", err); process.exit(1) })
528
+ } else {
529
+ logger.info(`Skipping Electron Pack`)
530
+ }
531
+ logger.info(`Done Building Electron`)
532
+ })
533
+ } else {
534
+ if (isDev) {
535
+ nuxt.hook("ready", async () => {
536
+ logger.info("electron - ready")
537
+ void buildElectron()
538
+ })
539
+ logger.info(`Watching Electron`)
540
+ } else {
541
+ logger.info(`Skipping Electron Build`)
542
+ }
543
+
544
+ nuxtRerouteOutputTo(nuxt, nonElectronNuxtBuildDir)
545
+ }
546
+
547
+ addImportsDir(resolve("runtime/utils"))
548
+ }
549
+ })
@@ -0,0 +1,94 @@
1
+ <template>
2
+ <ClientOnly>
3
+ <div
4
+ v-if="isElectron()"
5
+ :class="twMerge(`
6
+ flex
7
+ items-center
8
+ gap-2
9
+ `, ($attrs as any).class)"
10
+ v-bind="{ ...$attrs, class: undefined }"
11
+ :style="`
12
+ --electron-wc-size:${props.buttonSize};
13
+ --electron-wc-border:${props.borderWidth};
14
+ --electron-wc-rounded:${props.borderRadius};
15
+ --electron-wc-diagonal:calc((var(--electron-wc-size) - var(--electron-wc-border)/2)*sqrt(2));
16
+ `"
17
+ >
18
+ <template
19
+ v-for="button in buttonsOrder"
20
+ :key="button"
21
+ >
22
+ <component
23
+ :is="componentsMap[button]"
24
+ @action="actionHandler($event)"
25
+ />
26
+ </template>
27
+ </div>
28
+ </ClientOnly>
29
+ </template>
30
+
31
+ <script lang="ts" setup>
32
+ // eslint-disable-next-line import/no-extraneous-dependencies
33
+ import { type Component, computed, useAttrs } from "vue"
34
+
35
+ import CloseButton from "./WindowControls/CloseButton.vue"
36
+ import MaximizeButton from "./WindowControls/MaximizeButton.vue"
37
+ import MinimizeButton from "./WindowControls/MinimizeButton.vue"
38
+ import PinButton from "./WindowControls/PinButton.vue"
39
+
40
+ import { twMerge } from "#imports"
41
+
42
+ import type { WindowControlsApi } from "../electron/types.js"
43
+ import { isElectron } from "../utils/isElectron.js"
44
+
45
+ const $attrs = useAttrs()
46
+
47
+ const props = withDefaults(defineProps<{
48
+ borderWidth?: string
49
+ buttonSize?: string
50
+ borderRadius?: string
51
+ /**
52
+ *
53
+ * Replace any of the default buttons with your own components.
54
+ *
55
+ * Note that you can get the existing button components (import from `/components/WIndowControls/[name]`) and pass a slot to change the icon then pass your new component here.
56
+ *
57
+ * If using a completely custom component, it must emit an `action` event with the action name as the value.
58
+ *
59
+ * This wrapper component sets the following css variables if you need them:
60
+ * `--electron-wc-size`
61
+ * `--electron-wc-border`
62
+ * `--electron-wc-rounded`
63
+ * `--electron-wc-diagonal` (useful for creating the close cross)
64
+ */
65
+ components?: Partial<Record<"CloseButton" | "MinimizeButton" | "MaximizeButton" | "PinButton", Component>>
66
+ buttonsOrder?: ("CloseButton" | "MinimizeButton" | "MaximizeButton" | "PinButton")[]
67
+ handler?: WindowControlsApi
68
+ }>(), {
69
+ borderWidth: "2px",
70
+ buttonSize: "15px",
71
+ borderRadius: "1.5px",
72
+ buttonsOrder: () => (["PinButton", "MinimizeButton", "MaximizeButton", "CloseButton"])
73
+ })
74
+ const componentsMap = computed(() => ({
75
+ CloseButton,
76
+ MinimizeButton,
77
+ MaximizeButton,
78
+ PinButton,
79
+ ...props.components
80
+ }))
81
+ const actionHandler = computed(() => {
82
+ if (!isElectron()) return undefined
83
+ if (!props.handler) {
84
+ const defaultHandlerPath = (window as any).electron?.api?.ui?.windowAction
85
+ if (defaultHandlerPath) {
86
+ return defaultHandlerPath
87
+ } else {
88
+ // eslint-disable-next-line no-console
89
+ console.warn("No ElectronWindowControls handler specified and could not find default handler at `window.electron.api.ui.windowAction`")
90
+ }
91
+ }
92
+ return props.handler
93
+ })
94
+ </script>
@@ -0,0 +1,56 @@
1
+ <template>
2
+ <WButton
3
+ :border="false"
4
+ aria-label="Close"
5
+ class="
6
+ p-0
7
+ [&:hover_.default-icon:after]:bg-accent-500
8
+ [&:hover_.default-icon:before]:bg-accent-500
9
+ [&:hover_.default-icon:after]:shadow-xs
10
+ [&:hover_.default-icon:after]:shadow-fg/50
11
+ [&:hover_.default-icon:before]:shadow-xs
12
+ [&:hover_.default-icon:before]:shadow-fg/50
13
+
14
+ "
15
+ @click="emit('action', 'close')"
16
+ >
17
+ <slot>
18
+ <div
19
+ class="
20
+ default-icon
21
+ relative
22
+ w-[calc(var(--electron-wc-size)-var(--electron-wc-border)/2)]
23
+ h-[calc(var(--electron-wc-size)-var(--electron-wc-border)/2)]
24
+ before:absolute
25
+ before:content-['']
26
+ before:left-0
27
+ before:rotate-45
28
+ before:origin-left
29
+ before:top-[calc(var(--electron-wc-border)/-2)]
30
+ before:w-[var(--electron-wc-diagonal)]
31
+ before:h-[var(--electron-wc-border)]
32
+ before:rounded-(--electron-wc-rounded)
33
+ before:bg-fg
34
+ dark:before:bg-bg
35
+ after:absolute
36
+ after:content-['']
37
+ after:origin-top
38
+ after:right-[calc(var(--electron-wc-border)/-2)]
39
+ after:top-0
40
+ after:rotate-[45deg]
41
+ after:h-[var(--electron-wc-diagonal)]
42
+ after:w-[var(--electron-wc-border)]
43
+ after:bg-fg
44
+ after:rounded-(--electron-wc-rounded)
45
+ dark:after:bg-bg
46
+ "
47
+ />
48
+ </slot>
49
+ </WButton>
50
+ </template>
51
+
52
+ <script lang="ts" setup>
53
+ const emit = defineEmits<{
54
+ (e: "action", action: "close"): void
55
+ }>()
56
+ </script>