papakukito
papakukito

Reputation: 1

How to fix incorrect import paths in Electron app compiled with Vite externals?

I have an Electron application using TypeScript, Vite, and React.

Inside the app the code is divided into two folders.

  1. In the src/electron folder, I have all the Electron-related code, and it is transpiled directly by TypeScript to the dist-electronfolder. ( some code like classes, interfaces,etc are shared with the src/ui folder).

  2. In the src/ui folder i have the renderer part (React) and Vite transpile it to the dist-react folder.

Vite is configured to use the Electron code as external, but when i transpile my project, the imports path in the dist-react folder that points to the dist-electron folder are like that:

import { I18nService, Lang } from "@electron/services";

When I launch my Electron app, I get an error because it expects an import like that:

import { I18nService, Lang } from "../../dist-electron/services";

This is the error:

index.html:1 Uncaught TypeError: Failed to resolve module specifier "@electron/services". Relative references must start with either "/", "./", or "../".

How can I configure Vite to fix those imports to point to the correct external folder?

Structure of my project:

C:.
│   index.html
│   package.json
│   tsconfig.app.json
│   tsconfig.json
│   tsconfig.node.json
│   vite.config.ts
│
├───dist-electron     <==== Output folder for the src/electron folder
│   │   tsconfig.electron.tsbuildinfo
│   ├───apis
│   ├───models
│   └───services
│
├───dist-react        <==== Output folder for the src/ui folder
│   │   index.html
│   └───assets
│           index-Da2_jhOH.js
│
└───src
    ├───electron
    │   │   main.ts
    │   │   tsconfig.electron.json
    │   ├───apis
    │   ├───models
    │   └───services
    │
    └───ui
        │   main.tsx
        │   vite-env.d.ts
        ├───components
        └───views

This is my Vite config:

import react from "@vitejs/plugin-react";
import path from "path";
import { defineConfig } from "vite";

// https://vite.dev/config/
export default defineConfig({
    base: "./",
    plugins: [react()],
    build: {
        outDir: "dist-react",
        sourcemap: true,
        rollupOptions: {
            // treeshake: "smallest",
            external: (id) => id.includes("electron/"),
            output: {
                manualChunks(id) {
                    if (id.includes("electron")) {
                        return "electron";
                    }
                    if (id.includes("antd")) {
                        return "antd";
                    }
                    if (id.includes("react")) {
                        return "react";
                    }
                },
                paths: {
                    "@electron/": path.resolve(__dirname, "dist-electron")
                }
            }
        },
        // ---- turn off minify | Remove this for production------
        minify: false,
        terserOptions: {
            compress: false,
            mangle: false
        }
        //--------------------------------------
    },
    resolve: {
        alias: {
            "@electron": path.resolve(__dirname, "src", "electron")
        }
    },
    server: {
        port: 5173,
        strictPort: true
    }
});

tsconfig.app.json (React):

{
    "compilerOptions": {
        "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
        "target": "ES2020",
        "useDefineForClassFields": true,
        "lib": ["ES2020", "DOM", "DOM.Iterable"],
        "module": "ESNext",
        "skipLibCheck": true,
        "sourceMap": true,

        /* Bundler mode */
        "moduleResolution": "bundler",
        "allowImportingTsExtensions": true,
        "isolatedModules": true,
        "moduleDetection": "force",
        "noEmit": true,
        "jsx": "react-jsx",

        /* Linting */
        "strict": true,
        "forceConsistentCasingInFileNames": true,
        "strictPropertyInitialization": false,
        "composite": true,
        "declaration": true,
        "allowSyntheticDefaultImports": true,
        "paths": {
            "@electron/*": ["./src/electron/*"]
        }
    },
    "include": ["src/ui"],
    "references": [{ "path": "./src/electron/tsconfig.electron.json" }]
}

src/electron/tsconfig.electron.json:

{
    "compilerOptions": {
        "composite": true,
        "declaration": true,
        "allowSyntheticDefaultImports": true, // if esModuleInterop is set to true, this property is automatically true
        "esModuleInterop": true,
        "module": "NodeNext", // tell TypeScript to require ESM Syntax as input ( includin .js file imports) when we write our code
        "moduleResolution": "nodenext",
        "outDir": "../../dist-electron",
        "resolveJsonModule": true,
        "skipLibCheck": true, // ignore errors from dependencies that are not fully ready for typescript
        "sourceMap": true,
        "strict": true, // require strict types (null-save)
        "strictPropertyInitialization": false,
        "target": "ESNext", // tell TypeScript to generate ESM Syntax when build
        "types": ["./window.d.ts"],
        "forceConsistentCasingInFileNames": true
    },
    "include": ["."]
}

tsconfig.json:

{
    "files": [],
    "references": [
        { "path": "./tsconfig.app.json" },
        { "path": "./tsconfig.node.json" },
        { "path": "./src/electron/tsconfig.electron.json" }
    ]
}

package.json:

{
    "name": "name",
    "private": true,
    "version": "0.0.0",
    "main": "dist-electron/main.js",
    "type": "module",
    "author": {},
    "scripts": {
        "dev:electron": "nodemon --exec \"npm run build && electron .\"",
        "dev:ui-only": "vite",
        "watch": "nodemon --exec \"npm run build\"",
        "build": "npm run clean && tsc --project src/electron/tsconfig.electron.json && tsc -b && vite build",
        "build:react": "npm run clean && tsc -b && vite build",
        "build:electron": "npm run clean && tsc --project src/electron/tsconfig.electron.json",
        "lint": "eslint .",
        "preview": "vite preview",
        "dist:win": "npm run build && electron-builder --win --x64",
        "dist:mac": "npm run build && electron-builder --mac --arm64",
        "dist:linux": "npm run build && electron-builder --linux --x64",
        "clean": "node clean.cjs"
    },
    "dependencies": {
        "antd": "^5.22.3",
        "axios": "^1.7.9",
        "meowvies": "file:",
        "react": "^18.3.1",
        "react-dom": "^18.3.1",
        "rxjs": "^7.8.1"
    },
    "devDependencies": {
        "@eslint/js": "^9.15.0",
        "@types/node": "^22.10.1",
        "@types/react": "^18.3.12",
        "@types/react-dom": "^18.3.1",
        "@vitejs/plugin-react": "^4.3.4",
        "electron": "^33.2.1",
        "electron-builder": "^25.1.8",
        "eslint": "^9.15.0",
        "eslint-plugin-react-hooks": "^5.0.0",
        "eslint-plugin-react-refresh": "^0.4.14",
        "globals": "^15.12.0",
        "nodemon": "^3.1.7",
        "typescript": "~5.6.2",
        "typescript-eslint": "^8.15.0",
        "vite": "^6.0.1"
    }
}

Edit 1

After reviewing the documentation i had to remove the build.rollupOptions.output.path to avoid duplicated settings because i already had build.outDir set.

But i still dont understand why if i have this alias:

resolve: {
        alias: {
            "@electron/": path.resolve(__dirname, "dist-electron") 
            // I also tried this:          
            // "@electron/": path.resolve(__dirname, "../../dist-electron")           
            // "@electron": "dist-electron"
        }
    },

the output files have imports like this:

import { I18nService, Lang } from "@electron/services";

instead of

import { I18nService, Lang } from "../../dist-electron/services";

Aren't aliases supposed to be replaced by their values ​​during transpilation?

Upvotes: 0

Views: 76

Answers (0)

Related Questions