Search code examples
typescriptelectrondeclaration

How to import other types from `.ts` file in `.d.ts` file


I have a electron project which has strucutre:

src
 - main
 - preload
   - preload.ts
   - preload.d.ts
 - render
 - shared

I want to inject some native code into my renderer code, so I use proload like this:

// preload.ts
import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron'
import { IpcChannels } from '@shared/channelNames'

contextBridge.exposeInMainWorld('api', {
    ipcSend(channel: IpcChannels, ...data: any[]) {
        ipcRenderer.send(channel, ...data)
    },
    ipcSendSync(channel: IpcChannels, ...data: any[]) {
        return ipcRenderer.sendSync(channel, ...data)
    },
    ipcOn(channel: IpcChannels, listener: (event: IpcRendererEvent, ...args: any[]) => void) {
        ipcRenderer.on(channel, listener)
    },
    ipcOnce(channel: IpcChannels, listener: (event: IpcRendererEvent, ...args: any[]) => void) {
        ipcRenderer.once(channel, listener)
    },
    ipcOff(channel: IpcChannels) {
        ipcRenderer.removeAllListeners(channel)
    }
})
// channelNames.ts
export const WINDOW_MINIMIZE = 'WINDOW_MINIMIZE'
export const WINDOW_MAXIMIZE = 'WINDOW_MAXIMIZE'
export const WINDOW_UNMAXIMIZE = 'WINDOW_UNMAXIMIZE'
export const WINDOW_CLOSE = 'WINDOW_CLOSE'
export const IS_WINDOW_MAXIMIZE = 'IS_WINDOW_MAXIMIZE'

export type IpcChannels =
    | 'WINDOW_MINIMIZE'
    | 'WINDOW_MAXIMIZE'
    | 'WINDOW_UNMAXIMIZE'
    | 'WINDOW_CLOSE'
    | 'IS_WINDOW_MAXIMIZE'

I add preload.d.ts into include array in my renderer's tsconfig.json:

{
    "compilerOptions": {
        "target": "esnext",
        "lib": ["dom", "dom.iterable", "esnext"],
        "allowJs": true,
        "skipLibCheck": true,
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true,
        "strict": true,
        "forceConsistentCasingInFileNames": true,
        "noFallthroughCasesInSwitch": true,
        "module": "esnext",
        "moduleResolution": "node",
        "resolveJsonModule": true,
        "isolatedModules": true,
        "noEmit": true,
        "jsx": "react-jsx",
        "baseUrl": ".",
        "paths": {
            "@@/*": ["../../*"],
            "@/*": ["*"],
            "@shared/*": ["../shared/*"]
        }
    },
    "include": [
        "./**/*.ts",
        "./**/*.tsx",
        "./**/*.less",
        "../shared/**/*.ts",
        "../../types/**/*.d.ts",
        "../preload/preload.d.ts"
    ]
}

Then I write preload.d.ts like this:

import { IpcChannels } from '@shared/channelNames'

type IpcListener = (event: Electron.IpcRendererEvent, ...args: any[]) => void

interface Api {
    readonly version: string
    readonly ipcSend(channel: IpcChannels, ...data: any[]): void
    readonly ipcSendSync(channel: IpcChannels, ...data: any[]): any
    readonly ipcOn(channel: IpcChannels, listener: IpcListener): void
    readonly ipcOnce(channel: IpcChannels, listener: IpcListener): void
    readonly ipcOff(channel: IpcChannels): void
}

interface Window {
    readonly api: Api 
}

But I can't get correct type hints, window.api is any type.

If I remove first line import and copy IpcChannels, it works:

type IpcChannels =
    | 'WINDOW_MINIMIZE'
    | 'WINDOW_MAXIMIZE'
    | 'WINDOW_UNMAXIMIZE'
    | 'WINDOW_CLOSE'
    | 'IS_WINDOW_MAXIMIZE'

type IpcListener = (event: Electron.IpcRendererEvent, ...args: any[]) => void

interface Api {
    readonly version: string
    readonly ipcSend(channel: IpcChannels, ...data: any[]): void
    readonly ipcSendSync(channel: IpcChannels, ...data: any[]): any
    readonly ipcOn(channel: IpcChannels, listener: IpcListener): void
    readonly ipcOnce(channel: IpcChannels, listener: IpcListener): void
    readonly ipcOff(channel: IpcChannels): void
}

Is there any way I can import IpcChannels rather than copy it? Otherwise I have to write twise if I want to add a channel name.


Solution

  • https://stackoverflow.com/a/51114250/12568197

    .d.ts files are treated as an ambient module declarations only if they don't have any imports. If you provide an import line, it's now treated as a normal module file, not the global one, so augmenting modules definitions doesn't work.

    For my case it should be:

    type IpcChannels = import('@shared/channelNames').IpcChannels 
    
    type IpcListener = (event: Electron.IpcRendererEvent, ...args: any[]) => void
    
    interface Api {
        readonly version: string
        readonly ipcSend(channel: IpcChannels, ...data: any[]): void
        readonly ipcSendSync(channel: IpcChannels, ...data: any[]): any
        readonly ipcOn(channel: IpcChannels, listener: IpcListener): void
        readonly ipcOnce(channel: IpcChannels, listener: IpcListener): void
        readonly ipcOff(channel: IpcChannels): void
    }
    
    interface Window {
        readonly api: Api 
    }