Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto generate exposeInMainWorld typings for preload scripts exports #141

Open
4 tasks done
syabro opened this issue Apr 12, 2023 · 8 comments
Open
4 tasks done

Auto generate exposeInMainWorld typings for preload scripts exports #141

syabro opened this issue Apr 12, 2023 · 8 comments
Labels
enhancement New feature or request

Comments

@syabro
Copy link

syabro commented Apr 12, 2023

Clear and concise description of the problem

Currently api type is unknown

declare global {
  interface Window {
    electron: ElectronAPI
    api: unknown
  }
}

And I have to replace it with real api and keep them synced

Suggested solution

  1. extract api to a separate file api.ts
// api.ts
import { ipcRenderer } from "electron";

export const preloadApi = {
  ping: () => ipcRenderer.invoke('ping'),
}

declare global {
  type Api = typeof preloadApi
}
  1. update preload src to use imported api instead of writing it in the same file
// index.ts
import { contextBridge } from "electron";
import { electronAPI } from "@electron-toolkit/preload";

import { api } from "./api"; // 👈🏻 new import


if (process.contextIsolated) {
  try {
    contextBridge.exposeInMainWorld('electron', electronAPI)
    contextBridge.exposeInMainWorld('api', api)
  } catch (error) {
    console.error(error)
  }
} else {
  // @ts-ignore (define in dts)
  window.electron = electronAPI
  // @ts-ignore (define in dts)
  window.api = api
}
  1. Update index.d.ts to use global Api
// index.d.ts
import { ElectronAPI } from '@electron-toolkit/preload'

declare global {
  interface Window {
    electron: ElectronAPI
    api: Api
  }
}
  1. Generate api types
tsc --declaration --emitDeclarationOnly --outDir ./src/preload/ ./src/preload/api.ts -w
  1. Have fun

image

Alternative

No response

Additional context

The problem here I don't know how to call api type compilation somewhere from vite without running tsc separately :(

Validations

@syabro syabro added the enhancement New feature or request label Apr 12, 2023
@syabro
Copy link
Author

syabro commented Apr 12, 2023

Upd:
found https://vitejs.dev/guide/api-plugin.html#universal-hooks + closeBuild in vitejs/vite#9217

// vite.config.ts
export default defineConfig({
  build: {
    watch: {
      include: 'src/**'
    },
} ...
plugins: [
    {
      name: 'postbuild-commands', // the name of your custom plugin. Could be anything.
      closeBundle: async () => {
        await postBuildCommands() // run during closeBundle hook. https://rollupjs.org/guide/en/#closebundle
      }
    },
]
})

I guess we can write a tiny plugin that would open a process with tsc on on build start and close on build end...

@syabro
Copy link
Author

syabro commented Apr 12, 2023

UPD2: Used writeBundle hook, works :)

// electron.vite.config.ts

import { exec } from 'child_process'

// ...

export default defineConfig({
   // ... 
  preload: {
    plugins: [
       // ...
      {
        name: 'rebuild-api-types',
        writeBundle: (options: any, bundle: { [fileName: string]: any }) => {
          exec(
            'tsc --declaration --emitDeclarationOnly --outDir ./src/preload/ ./src/preload/api.ts',
            (error, stdout, stderr) => {
              if (error) return console.error(`Error: ${error.message}`)
              if (stderr) return console.error(`Stderr: ${stderr}`)
              console.log('  regenerated api.d.ts')
            }
          )
        }
      }
    ],
    // ...
  },
  // ...
})

@subframe7536
Copy link

maybe help https://github.com/cawa-93/unplugin-auto-expose

@alex8088
Copy link
Owner

// preload/index.d.ts
import { ElectronAPI } from '@electron-toolkit/preload'

declare global {
  interface Window {
    electron: ElectronAPI
    api: typeof preloadApi
  }
}

@alex8088
Copy link
Owner

duplicate #121

@syabro
Copy link
Author

syabro commented Apr 18, 2023

@alex8088 I've upgraded my approach - it generates api from handlers automatically so less boilerplate

// src/preload/api.ts
import { ipcRenderer } from 'electron'

import { ipcHandlers } from '../src-main/handlers'

type ApiFromHandlers<T extends Record<string, (event: unknown, ...args: any[]) => any>> = {
  [K in keyof T]: (
    ...args: Parameters<T[K]> extends [unknown, ...infer R] ? R : never
  ) => Promise<ReturnType<T[K]>>
}

function createApi<T extends Record<string, (event: unknown, ...args: any[]) => any>>(
  handlers: T
): ApiFromHandlers<T> {
  const api: Partial<ApiFromHandlers<T>> = {}

  for (const key in handlers) {
    api[key as keyof T] = (...args: any[]) => ipcRenderer.invoke(key as string, ...args)
  }

  return api as ApiFromHandlers<T>
}

export const api = createApi(ipcHandlers)

declare global {
  type Api = typeof api
}
// src/main/ipcHandlers


export const ipcHandlers = {
  downloadPreset: async (event: any, params: { packName: string, url: string, name: string, vst: string }) => {
  },
  
  checkPresetInstalled: (event, params: { packName: string, name: string, vst: string }): boolean | Error => {
  },
}

export type IpcHandlers = typeof ipcHandlers

@alex8088 alex8088 added the duplicate This issue or pull request already exists label Apr 19, 2023
@alex8088 alex8088 changed the title [Feature Request]: Automatically generate api type Auto generate exposeInMainWorld typings for preload scripts exports Apr 19, 2023
@alex8088 alex8088 removed the duplicate This issue or pull request already exists label Apr 19, 2023
@douglascayers
Copy link

douglascayers commented Sep 24, 2023

@syabro Thank you! I now have types for my preload api ⚡

@alex8088 Thank you for this great electron framework 😄

For automation, I added a typecheck:preload script to my package.json and called it from the existing typecheck script:

{
  "scripts": {
    "typecheck:preload": "tsc --declaration --emitDeclarationOnly --outDir ./src/preload/ ./src/preload/api.ts",
    "typecheck": "yarn typecheck:preload && yarn typecheck:node && yarn typecheck:web",
    ...
  }
}

Now if I make changes, I can just build the app and the typings will be regenerated for me.

@starknt
Copy link

starknt commented Oct 13, 2023

I think we may have a better solution to this problem. For now, we are considering unplugin-auto-expose or unplugin-elexpse as a possible solution. But they have some limitations and don't work out of the box.

Considerations for out-of-the-box:

  1. Automation, find entry scripts. And implement this goal we may need some conventions(like nuxt 3).
  2. Generate d.ts file

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

5 participants