├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── bug_report.yml │ └── feature_request.yml ├── workflows │ └── release-tag.yml ├── PULL_REQUEST_TEMPLATE.md └── commit-convention.md ├── .prettierignore ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── .prettierrc.yaml ├── .editorconfig ├── src ├── index.ts ├── preview.ts ├── plugins │ ├── importMeta.ts │ ├── buildReporter.ts │ ├── externalizeDeps.ts │ ├── worker.ts │ ├── esmShim.ts │ ├── swc.ts │ ├── modulePath.ts │ ├── asset.ts │ ├── isolateEntries.ts │ ├── electron.ts │ └── bytecode.ts ├── build.ts ├── utils.ts ├── server.ts ├── electron.ts ├── cli.ts └── config.ts ├── tsconfig.json ├── bin ├── electron-vite.js └── electron-bytecode.cjs ├── scripts └── verifyCommit.js ├── LICENSE ├── rollup.config.ts ├── CONTRIBUTING.md ├── eslint.config.js ├── package.json ├── node.d.ts ├── CODE_OF_CONDUCT.md ├── README.md └── CHANGELOG.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: alex8088 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | pnpm-lock.yaml 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .DS_Store 4 | .eslintcache 5 | *.log* 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint"] 3 | } 4 | -------------------------------------------------------------------------------- /.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | singleQuote: true 2 | semi: false 3 | printWidth: 120 4 | trailingComma: none 5 | arrowParens: avoid 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Questions & Discussions 4 | url: https://github.com/alex8088/electron-vite/discussions 5 | about: Use GitHub discussions for message-board style questions and discussions. 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[typescript]": { 3 | "editor.defaultFormatter": "esbenp.prettier-vscode" 4 | }, 5 | "[javascript]": { 6 | "editor.defaultFormatter": "esbenp.prettier-vscode" 7 | }, 8 | "[json]": { 9 | "editor.defaultFormatter": "esbenp.prettier-vscode" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { type LogLevel, createLogger, mergeConfig } from 'vite' 2 | export * from './config' 3 | export { createServer } from './server' 4 | export { build } from './build' 5 | export { preview } from './preview' 6 | export { loadEnv } from './utils' 7 | export * from './plugins/bytecode' 8 | export * from './plugins/externalizeDeps' 9 | export * from './plugins/swc' 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2023", 4 | "module": "ESNext", 5 | "lib": ["ESNext"], 6 | "sourceMap": false, 7 | "strict": true, 8 | "allowJs": true, 9 | "esModuleInterop": true, 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "skipLibCheck": true, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "noImplicitAny": false, 17 | "noImplicitReturns": true 18 | }, 19 | "include": ["src", "rollup.config.ts"] 20 | } 21 | -------------------------------------------------------------------------------- /src/preview.ts: -------------------------------------------------------------------------------- 1 | import colors from 'picocolors' 2 | import { createLogger } from 'vite' 3 | import type { InlineConfig } from './config' 4 | import { startElectron } from './electron' 5 | import { build } from './build' 6 | 7 | export async function preview(inlineConfig: InlineConfig = {}, options: { skipBuild?: boolean }): Promise { 8 | if (!options.skipBuild) { 9 | await build(inlineConfig) 10 | } 11 | 12 | const logger = createLogger(inlineConfig.logLevel) 13 | 14 | startElectron(inlineConfig.root) 15 | 16 | logger.info(colors.green(`\nstarting electron app...\n`)) 17 | } 18 | -------------------------------------------------------------------------------- /src/plugins/importMeta.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from 'vite' 2 | 3 | export default function importMetaPlugin(): Plugin { 4 | return { 5 | name: 'vite:import-meta', 6 | apply: 'build', 7 | enforce: 'pre', 8 | resolveImportMeta(property, { format }): string | null { 9 | if (property === 'url' && format === 'cjs') { 10 | return `require("url").pathToFileURL(__filename).href` 11 | } 12 | if (property === 'filename' && format === 'cjs') { 13 | return `__filename` 14 | } 15 | if (property === 'dirname' && format === 'cjs') { 16 | return `__dirname` 17 | } 18 | return null 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/release-tag.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 5 | 6 | name: Create Release 7 | 8 | jobs: 9 | build: 10 | name: Create Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@master 15 | - name: Create Release for Tag 16 | id: release_tag 17 | uses: actions/create-release@v1 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | with: 21 | tag_name: ${{ github.ref }} 22 | release_name: ${{ github.ref }} 23 | body: | 24 | Please refer to [CHANGELOG.md](https://github.com/alex8088/electron-vite/blob/${{ github.ref_name }}/CHANGELOG.md) for details. 25 | -------------------------------------------------------------------------------- /src/plugins/buildReporter.ts: -------------------------------------------------------------------------------- 1 | import { type Plugin } from 'vite' 2 | 3 | type BuildReporterApi = { 4 | getWatchFiles: () => string[] 5 | } 6 | 7 | export default function buildReporterPlugin(): Plugin { 8 | const moduleIds: string[] = [] 9 | return { 10 | name: 'vite:build-reporter', 11 | 12 | buildEnd() { 13 | const allModuleIds = Array.from(this.getModuleIds()) 14 | const sourceFiles = allModuleIds.filter(id => { 15 | if (id.includes('node_modules')) { 16 | return false 17 | } 18 | const info = this.getModuleInfo(id) 19 | return info && !info.isExternal 20 | }) 21 | moduleIds.push(...sourceFiles) 22 | }, 23 | 24 | api: { 25 | getWatchFiles() { 26 | return moduleIds 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /bin/electron-vite.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const debugIndex = process.argv.findIndex(arg => /^(?:-d|--debug)$/.test(arg)) 4 | const filterIndex = process.argv.findIndex(arg => /^(?:-f|--filter)$/.test(arg)) 5 | 6 | if (debugIndex > 0) { 7 | let value = process.argv[debugIndex + 1] 8 | if (!value || value.startsWith('-')) { 9 | value = 'vite:*' 10 | } else { 11 | value = value 12 | .split(',') 13 | .map(v => `vite:${v}`) 14 | .join(',') 15 | } 16 | process.env.DEBUG = `${process.env.DEBUG ? process.env.DEBUG + ',' : ''}${value}` 17 | 18 | if (filterIndex > 0) { 19 | const filter = process.argv[filterIndex + 1] 20 | if (filter && !filter.startsWith('-')) { 21 | process.env.VITE_DEBUG_FILTER = filter 22 | } 23 | } 24 | } 25 | 26 | function run() { 27 | import('../dist/cli.js') 28 | } 29 | 30 | run() 31 | -------------------------------------------------------------------------------- /bin/electron-bytecode.cjs: -------------------------------------------------------------------------------- 1 | const vm = require('vm') 2 | const v8 = require('v8') 3 | const wrap = require('module').wrap 4 | 5 | v8.setFlagsFromString('--no-lazy') 6 | v8.setFlagsFromString('--no-flush-bytecode') 7 | 8 | let code = '' 9 | 10 | process.stdin.setEncoding('utf-8') 11 | 12 | process.stdin.on('readable', () => { 13 | const data = process.stdin.read() 14 | if (data !== null) { 15 | code += data 16 | } 17 | }) 18 | 19 | process.stdin.on('end', () => { 20 | try { 21 | if (typeof code !== 'string') { 22 | throw new Error(`javascript code must be string. ${typeof code} was given.`) 23 | } 24 | 25 | const script = new vm.Script(wrap(code), { produceCachedData: true }) 26 | const bytecodeBuffer = script.createCachedData() 27 | 28 | process.stdout.write(bytecodeBuffer) 29 | } catch (error) { 30 | console.error(error) 31 | } 32 | }) 33 | -------------------------------------------------------------------------------- /src/build.ts: -------------------------------------------------------------------------------- 1 | import { build as viteBuild } from 'vite' 2 | import { type InlineConfig, resolveConfig } from './config' 3 | 4 | /** 5 | * Bundles the electron app for production. 6 | */ 7 | export async function build(inlineConfig: InlineConfig = {}): Promise { 8 | process.env.NODE_ENV_ELECTRON_VITE = 'production' 9 | const config = await resolveConfig(inlineConfig, 'build', 'production') 10 | 11 | if (!config.config) { 12 | return 13 | } 14 | 15 | // Build targets in order: main -> preload -> renderer 16 | const buildTargets = ['main', 'preload', 'renderer'] as const 17 | 18 | for (const target of buildTargets) { 19 | const viteConfig = config.config[target] 20 | if (viteConfig) { 21 | // Disable watch mode in production builds 22 | if (viteConfig.build?.watch) { 23 | viteConfig.build.watch = null 24 | } 25 | await viteBuild(viteConfig) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /scripts/verifyCommit.js: -------------------------------------------------------------------------------- 1 | // Invoked on the commit-msg git hook by simple-git-hooks. 2 | 3 | import colors from 'picocolors' 4 | import fs from 'node:fs' 5 | 6 | const msgPath = process.argv[2] 7 | const msg = fs.readFileSync(msgPath, 'utf-8').trim() 8 | 9 | const commitRE = 10 | /^(revert: )?(feat|fix|docs|dx|style|refactor|perf|test|workflow|build|ci|chore|types|wip|release)(\(.+\))?!?: .{1,50}/ 11 | 12 | if (!commitRE.test(msg)) { 13 | console.log() 14 | console.error( 15 | ` ${colors.bgRed(colors.white(' ERROR '))} ${colors.red(`invalid commit message format.`)}\n\n` + 16 | colors.red(` Proper commit message format is required for automated changelog generation. Examples:\n\n`) + 17 | ` ${colors.green(`feat: add 'comments' option`)}\n` + 18 | ` ${colors.green(`fix: handle events on blur (close #28)`)}\n\n` + 19 | colors.red(` See .github/commit-convention.md for more details.\n`) 20 | ) 21 | process.exit(1) 22 | } 23 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Description 4 | 5 | 6 | 7 | ### Additional context 8 | 9 | 10 | 11 | --- 12 | 13 | ### What is the purpose of this pull request? 14 | 15 | - [ ] Bug fix 16 | - [ ] New Feature 17 | - [ ] Documentation update 18 | - [ ] Other 19 | 20 | ### Before submitting the PR, please make sure you do the following 21 | 22 | - [ ] Read the [Contributing Guidelines](https://github.com/alex8088/electron-vite/blob/master/CONTRIBUTING.md). 23 | - [ ] Read the [Pull Request Guidelines](https://github.com/alex8088/electron-vite/blob/master/CONTRIBUTING.md#pull-request) and follow the [Commit Convention](https://github.com/alex8088/electron-vite/blob/master/.github/commit-convention.md). 24 | - [ ] Provide a description in this PR that addresses **what** the PR is solving, or reference the issue that it solves (e.g. `fixes #123`). 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022, Alex Wei 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /rollup.config.ts: -------------------------------------------------------------------------------- 1 | import { createRequire } from 'node:module' 2 | import { defineConfig } from 'rollup' 3 | import ts from '@rollup/plugin-typescript' 4 | import resolve from '@rollup/plugin-node-resolve' 5 | import json from '@rollup/plugin-json' 6 | import dts from 'rollup-plugin-dts' 7 | import rm from 'rollup-plugin-rm' 8 | 9 | const require = createRequire(import.meta.url) 10 | const pkg = require('./package.json') 11 | 12 | const external = [...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.peerDependencies || {})] 13 | 14 | export default defineConfig([ 15 | { 16 | input: ['src/index.ts', 'src/cli.ts'], 17 | output: [ 18 | { 19 | dir: 'dist', 20 | entryFileNames: '[name].js', 21 | chunkFileNames: 'chunks/lib-[hash].js', 22 | format: 'es' 23 | } 24 | ], 25 | external, 26 | plugins: [ 27 | rm('dist', 'buildStart'), 28 | json(), 29 | ts({ compilerOptions: { rootDir: 'src', declaration: true, declarationDir: 'dist/types' } }), 30 | resolve() 31 | ], 32 | treeshake: { 33 | moduleSideEffects: false 34 | } 35 | }, 36 | { 37 | input: 'dist/types/index.d.ts', 38 | output: [{ file: pkg.types, format: 'es' }], 39 | plugins: [dts(), rm('dist/types', 'buildEnd')] 40 | } 41 | ]) 42 | -------------------------------------------------------------------------------- /src/plugins/externalizeDeps.ts: -------------------------------------------------------------------------------- 1 | import { type Plugin, mergeConfig } from 'vite' 2 | import { loadPackageData } from '../utils' 3 | 4 | export interface ExternalOptions { 5 | exclude?: string[] 6 | include?: string[] 7 | } 8 | 9 | /** 10 | * Automatically externalize dependencies. 11 | * 12 | * @deprecated use `build.externalizeDeps` config option instead 13 | */ 14 | export function externalizeDepsPlugin(options: ExternalOptions = {}): Plugin | null { 15 | const { exclude = [], include = [] } = options 16 | 17 | const pkg = loadPackageData() || {} 18 | let deps = Object.keys(pkg.dependencies || {}) 19 | 20 | if (include.length) { 21 | deps = deps.concat(include.filter(dep => dep.trim() !== '')) 22 | } 23 | 24 | if (exclude.length) { 25 | deps = deps.filter(dep => !exclude.includes(dep)) 26 | } 27 | 28 | deps = [...new Set(deps)] 29 | 30 | return { 31 | name: 'vite:externalize-deps', 32 | enforce: 'pre', 33 | config(config): void { 34 | const defaultConfig = { 35 | build: { 36 | rollupOptions: { 37 | external: deps.length > 0 ? [...deps, new RegExp(`^(${deps.join('|')})/.+`)] : [] 38 | } 39 | } 40 | } 41 | const buildConfig = mergeConfig(defaultConfig.build, config.build || {}) 42 | config.build = buildConfig 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for being interested in contributing to this project! 4 | 5 | ## Repo Setup 6 | 7 | Clone this repo to your local machine and install the dependencies. 8 | 9 | ```bash 10 | pnpm install 11 | ``` 12 | 13 | **NOTE**: The package manager used to install and link dependencies must be pnpm. 14 | 15 | ## Pull Request 16 | 17 | - Checkout a topic branch from a base branch, e.g. fix-bug, and merge back against that branch. 18 | - It's OK to have multiple small commits as you work on the PR - GitHub can automatically squash them before merging. 19 | - To check that your contributions match the project coding style make sure `pnpm lint` && `pnpm typecheck` passes. To build project run: `pnpm build`. 20 | - Commit messages must follow the [commit message convention](./.github/commit-convention.md). Commit messages are automatically validated before commit (by invoking [Git Hooks](https://git-scm.com/docs/githooks) via [simple-git-hooks](https://github.com/toplenboren/simple-git-hooks)). 21 | - Commit messages preferably in English. 22 | - No need to worry about code style as long as you have installed the dev dependencies - modified files are automatically formatted with Prettier on commit (by invoking [Git Hooks](https://git-scm.com/docs/githooks) via [simple-git-hooks](https://github.com/toplenboren/simple-git-hooks)). 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: "\U0001F41E Bug Report" 2 | description: Report an issue with electron-vite 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | Thanks for taking the time to fill out this bug report! 8 | - type: textarea 9 | id: bug-description 10 | attributes: 11 | label: Describe the bug 12 | description: A clear and concise description of what the bug is. If you intend to submit a PR for this issue, tell us in the description. Thanks! 13 | placeholder: Bug description 14 | validations: 15 | required: true 16 | - type: input 17 | id: electron-vite-version 18 | attributes: 19 | label: Electron-Vite Version 20 | description: What version of Electron-Vite are you using? 21 | validations: 22 | required: true 23 | - type: input 24 | id: electron-version 25 | attributes: 26 | label: Electron Version 27 | description: What version of Electron are you using? 28 | validations: 29 | required: true 30 | - type: input 31 | id: vite-version 32 | attributes: 33 | label: Vite Version 34 | description: What version of Vite are you using? 35 | validations: 36 | required: true 37 | - type: checkboxes 38 | id: checkboxes 39 | attributes: 40 | label: Validations 41 | description: Before submitting the issue, please make sure you do the following 42 | options: 43 | - label: Follow the [Code of Conduct](https://github.com/alex8088/electron-vite/blob/master/CODE_OF_CONDUCT.md). 44 | required: true 45 | - label: Read the [Contributing Guidelines](https://github.com/alex8088/electron-vite/blob/master/CONTRIBUTING.md). 46 | required: true 47 | - label: Read the [docs](https://electron-vite.org). 48 | required: true 49 | - label: Check that there isn't [already an issue](https://github.com/alex8088/electron-vite/issues) that reports the same bug to avoid creating a duplicate. 50 | required: true 51 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: "\U0001F680 New Feature Proposal" 2 | description: Propose a new feature to be added to electron-vite 3 | labels: ['enhancement'] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for your interest in the project and taking the time to fill out this feature report! 9 | - type: textarea 10 | id: feature-description 11 | attributes: 12 | label: Clear and concise description of the problem 13 | description: 'As a developer using electron-vite I want [goal / wish] so that [benefit]. If you intend to submit a PR for this issue, tell us in the description. Thanks!' 14 | validations: 15 | required: true 16 | - type: textarea 17 | id: suggested-solution 18 | attributes: 19 | label: Suggested solution 20 | description: 'In module [xy] we could provide following implementation...' 21 | validations: 22 | required: true 23 | - type: textarea 24 | id: alternative 25 | attributes: 26 | label: Alternative 27 | description: Clear and concise description of any alternative solutions or features you've considered. 28 | - type: textarea 29 | id: additional-context 30 | attributes: 31 | label: Additional context 32 | description: Any other context or screenshots about the feature request here. 33 | - type: checkboxes 34 | id: checkboxes 35 | attributes: 36 | label: Validations 37 | description: Before submitting the issue, please make sure you do the following 38 | options: 39 | - label: Follow the [Code of Conduct](https://github.com/alex8088/electron-vite/blob/master/CODE_OF_CONDUCT.md). 40 | required: true 41 | - label: Read the [Contributing Guidelines](https://github.com/alex8088/electron-vite/blob/master/CONTRIBUTING.md). 42 | required: true 43 | - label: Read the [docs](https://electron-vite.org). 44 | required: true 45 | - label: Check that there isn't [already an issue](https://github.com/alex8088/electron-vite/issues) that requests the same feature to avoid creating a duplicate. 46 | required: true 47 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | // ts-check 2 | import { defineConfig } from 'eslint/config' 3 | import eslint from '@eslint/js' 4 | import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended' 5 | import globals from 'globals' 6 | import tseslint from 'typescript-eslint' 7 | 8 | export default defineConfig( 9 | { ignores: ['**/node_modules', '**/dist', '**/bin'] }, 10 | eslint.configs.recommended, 11 | tseslint.configs.recommended, 12 | eslintPluginPrettierRecommended, 13 | { 14 | languageOptions: { 15 | parser: tseslint.parser, 16 | parserOptions: { 17 | sourceType: 'module', 18 | ecmaVersion: 2022 19 | }, 20 | globals: { 21 | ...globals.es2021, 22 | ...globals.node 23 | } 24 | }, 25 | settings: { 26 | node: { 27 | version: '^20.19.0 || >=22.12.0' 28 | } 29 | }, 30 | rules: { 31 | 'prettier/prettier': 'warn', 32 | 'no-empty': ['warn', { allowEmptyCatch: true }], 33 | '@typescript-eslint/ban-ts-comment': ['error', { 'ts-ignore': 'allow-with-description' }], 34 | '@typescript-eslint/explicit-function-return-type': [ 35 | 'error', 36 | { 37 | allowExpressions: true, 38 | allowTypedFunctionExpressions: true, 39 | allowHigherOrderFunctions: true, 40 | allowIIFEs: true 41 | } 42 | ], 43 | '@typescript-eslint/explicit-module-boundary-types': 'off', 44 | '@typescript-eslint/no-empty-function': ['error', { allow: ['arrowFunctions'] }], 45 | '@typescript-eslint/no-empty-object-type': ['error', { allowInterfaces: 'always' }], 46 | '@typescript-eslint/no-explicit-any': 'error', 47 | '@typescript-eslint/no-non-null-assertion': 'off', 48 | '@typescript-eslint/no-require-imports': 'error', 49 | '@typescript-eslint/no-unused-expressions': [ 50 | 'error', 51 | { 52 | allowShortCircuit: true, 53 | allowTaggedTemplates: true, 54 | allowTernary: true 55 | } 56 | ], 57 | '@typescript-eslint/consistent-type-imports': [ 58 | 'error', 59 | { prefer: 'type-imports', disallowTypeAnnotations: false } 60 | ] 61 | } 62 | }, 63 | { 64 | files: ['*.js', '*.mjs'], 65 | rules: { 66 | '@typescript-eslint/explicit-function-return-type': 'off' 67 | } 68 | } 69 | ) 70 | -------------------------------------------------------------------------------- /src/plugins/worker.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from 'vite' 2 | import type { SourceMapInput } from 'rollup' 3 | import MagicString from 'magic-string' 4 | import { cleanUrl, toRelativePath } from '../utils' 5 | 6 | const nodeWorkerAssetUrlRE = /__VITE_NODE_WORKER_ASSET__([\w$]+)__/g 7 | const nodeWorkerRE = /\?nodeWorker(?:&|$)/ 8 | const nodeWorkerImporterRE = /(?:\?)nodeWorker&importer=([^&]+)(?:&|$)/ 9 | 10 | /** 11 | * Resolve `?nodeWorker` import and automatically generate `Worker` wrapper. 12 | */ 13 | export default function workerPlugin(): Plugin { 14 | return { 15 | name: 'vite:node-worker', 16 | apply: 'build', 17 | enforce: 'pre', 18 | resolveId(id, importer): string | void { 19 | if (id.endsWith('?nodeWorker')) { 20 | return id + `&importer=${importer}` 21 | } 22 | }, 23 | load(id): string | void { 24 | if (nodeWorkerRE.test(id)) { 25 | const match = nodeWorkerImporterRE.exec(id) 26 | if (match) { 27 | const hash = this.emitFile({ 28 | type: 'chunk', 29 | id: cleanUrl(id), 30 | importer: match[1] 31 | }) 32 | const assetRefId = `__VITE_NODE_WORKER_ASSET__${hash}__` 33 | return ` 34 | import { Worker } from 'node:worker_threads'; 35 | export default function (options) { return new Worker(new URL(${assetRefId}, import.meta.url), options); }` 36 | } 37 | } 38 | }, 39 | renderChunk(code, chunk, { sourcemap }): { code: string; map: SourceMapInput } | null { 40 | let match: RegExpExecArray | null 41 | let s: MagicString | undefined 42 | 43 | nodeWorkerAssetUrlRE.lastIndex = 0 44 | while ((match = nodeWorkerAssetUrlRE.exec(code))) { 45 | s ||= new MagicString(code) 46 | const [full, hash] = match 47 | const filename = this.getFileName(hash) 48 | const outputFilepath = toRelativePath(filename, chunk.fileName) 49 | const replacement = JSON.stringify(outputFilepath) 50 | s.overwrite(match.index, match.index + full.length, replacement, { 51 | contentOnly: true 52 | }) 53 | } 54 | 55 | if (s) { 56 | return { 57 | code: s.toString(), 58 | map: sourcemap ? s.generateMap({ hires: 'boundary' }) : null 59 | } 60 | } 61 | 62 | return null 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/plugins/esmShim.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * The core of this plugin was conceived by pi0 and is taken from the following repository: 3 | * https://github.com/unjs/unbuild/blob/main/src/builder/plugins/cjs.ts 4 | * license: https://github.com/unjs/unbuild/blob/main/LICENSE 5 | */ 6 | 7 | import MagicString from 'magic-string' 8 | import type { SourceMapInput } from 'rollup' 9 | import type { Plugin } from 'vite' 10 | 11 | import { supportImportMetaPaths } from '../electron' 12 | 13 | const CJSyntaxRe = /__filename|__dirname|require\(|require\.resolve\(/ 14 | 15 | const CJSShim_normal = ` 16 | // -- CommonJS Shims -- 17 | import __cjs_url__ from 'node:url'; 18 | import __cjs_path__ from 'node:path'; 19 | import __cjs_mod__ from 'node:module'; 20 | const __filename = __cjs_url__.fileURLToPath(import.meta.url); 21 | const __dirname = __cjs_path__.dirname(__filename); 22 | const require = __cjs_mod__.createRequire(import.meta.url); 23 | ` 24 | 25 | const CJSShim_node_20_11 = ` 26 | // -- CommonJS Shims -- 27 | import __cjs_mod__ from 'node:module'; 28 | const __filename = import.meta.filename; 29 | const __dirname = import.meta.dirname; 30 | const require = __cjs_mod__.createRequire(import.meta.url); 31 | ` 32 | 33 | const ESMStaticImportRe = 34 | /(?<=\s|^|;)import\s*([\s"']*(?[\p{L}\p{M}\w\t\n\r $*,/{}@.]+)from\s*)?["']\s*(?(?<="\s*)[^"]*[^\s"](?=\s*")|(?<='\s*)[^']*[^\s'](?=\s*'))\s*["'][\s;]*/gmu 35 | 36 | interface StaticImport { 37 | end: number 38 | } 39 | 40 | function findStaticImports(code: string): StaticImport[] { 41 | const matches: StaticImport[] = [] 42 | for (const match of code.matchAll(ESMStaticImportRe)) { 43 | matches.push({ end: (match.index || 0) + match[0].length }) 44 | } 45 | return matches 46 | } 47 | 48 | export default function esmShimPlugin(): Plugin { 49 | const CJSShim = supportImportMetaPaths() ? CJSShim_node_20_11 : CJSShim_normal 50 | 51 | return { 52 | name: 'vite:esm-shim', 53 | apply: 'build', 54 | enforce: 'post', 55 | renderChunk(code, _chunk, { format, sourcemap }): { code: string; map?: SourceMapInput } | null { 56 | if (format === 'es') { 57 | if (code.includes(CJSShim) || !CJSyntaxRe.test(code)) { 58 | return null 59 | } 60 | 61 | const lastESMImport = findStaticImports(code).pop() 62 | const indexToAppend = lastESMImport ? lastESMImport.end : 0 63 | const s = new MagicString(code) 64 | s.appendRight(indexToAppend, CJSShim) 65 | return { 66 | code: s.toString(), 67 | map: sourcemap ? s.generateMap({ hires: 'boundary' }) : null 68 | } 69 | } 70 | 71 | return null 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-vite", 3 | "version": "5.0.0", 4 | "description": "Electron build tooling based on Vite", 5 | "type": "module", 6 | "main": "./dist/index.js", 7 | "types": "./dist/index.d.ts", 8 | "exports": { 9 | ".": "./dist/index.js", 10 | "./node": { 11 | "types": "./node.d.ts" 12 | }, 13 | "./package.json": "./package.json" 14 | }, 15 | "bin": { 16 | "electron-vite": "bin/electron-vite.js" 17 | }, 18 | "files": [ 19 | "bin", 20 | "dist", 21 | "node.d.ts" 22 | ], 23 | "engines": { 24 | "node": "^20.19.0 || >=22.12.0" 25 | }, 26 | "packageManager": "pnpm@10.12.4", 27 | "author": "Alex Wei", 28 | "license": "MIT", 29 | "repository": { 30 | "type": "git", 31 | "url": "git+https://github.com/alex8088/electron-vite.git" 32 | }, 33 | "bugs": { 34 | "url": "https://github.com/alex8088/electron-vite/issues" 35 | }, 36 | "homepage": "https://electron-vite.org", 37 | "keywords": [ 38 | "electron", 39 | "vite", 40 | "cli", 41 | "plugin" 42 | ], 43 | "scripts": { 44 | "format": "prettier --write .", 45 | "lint": "eslint --cache .", 46 | "typecheck": "tsc --noEmit", 47 | "build": "pnpm run lint && rollup -c rollup.config.ts --configPlugin typescript" 48 | }, 49 | "simple-git-hooks": { 50 | "pre-commit": "npx lint-staged", 51 | "commit-msg": "node scripts/verifyCommit.js $1" 52 | }, 53 | "lint-staged": { 54 | "*.js": [ 55 | "prettier --write" 56 | ], 57 | "*.ts?(x)": [ 58 | "eslint", 59 | "prettier --parser=typescript --write" 60 | ] 61 | }, 62 | "peerDependencies": { 63 | "@swc/core": "^1.0.0", 64 | "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" 65 | }, 66 | "peerDependenciesMeta": { 67 | "@swc/core": { 68 | "optional": true 69 | } 70 | }, 71 | "devDependencies": { 72 | "@eslint/js": "^9.37.0", 73 | "@rollup/plugin-json": "^6.1.0", 74 | "@rollup/plugin-node-resolve": "^16.0.3", 75 | "@rollup/plugin-typescript": "^12.1.4", 76 | "@swc/core": "^1.13.5", 77 | "@types/babel__core": "^7.20.5", 78 | "@types/node": "^22.18.11", 79 | "eslint": "^9.37.0", 80 | "eslint-config-prettier": "^10.1.8", 81 | "eslint-plugin-prettier": "^5.5.4", 82 | "globals": "^16.4.0", 83 | "lint-staged": "^16.2.4", 84 | "prettier": "^3.6.2", 85 | "rollup": "^4.52.4", 86 | "rollup-plugin-dts": "^6.2.3", 87 | "rollup-plugin-rm": "^1.0.2", 88 | "simple-git-hooks": "^2.13.1", 89 | "tslib": "^2.8.1", 90 | "typescript": "^5.9.3", 91 | "typescript-eslint": "^8.46.1", 92 | "vite": "^7.1.10" 93 | }, 94 | "dependencies": { 95 | "@babel/core": "^7.28.4", 96 | "@babel/plugin-transform-arrow-functions": "^7.27.1", 97 | "cac": "^6.7.14", 98 | "esbuild": "^0.25.11", 99 | "magic-string": "^0.30.19", 100 | "picocolors": "^1.1.1" 101 | }, 102 | "pnpm": { 103 | "onlyBuiltDependencies": [ 104 | "@swc/core", 105 | "esbuild", 106 | "simple-git-hooks" 107 | ] 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /node.d.ts: -------------------------------------------------------------------------------- 1 | // node worker 2 | declare module '*?nodeWorker' { 3 | import type { Worker, WorkerOptions } from 'node:worker_threads' 4 | export default function (options: WorkerOptions): Worker 5 | } 6 | 7 | // module path 8 | declare module '*?modulePath' { 9 | const src: string 10 | export default src 11 | } 12 | 13 | // node asset 14 | declare module '*?asset' { 15 | const src: string 16 | export default src 17 | } 18 | 19 | declare module '*?asset&asarUnpack' { 20 | const src: string 21 | export default src 22 | } 23 | 24 | declare module '*.json?commonjs-external&asset' { 25 | const src: string 26 | export default src 27 | } 28 | 29 | // native node module 30 | declare module '*.node' { 31 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 32 | const node: any 33 | export default node 34 | } 35 | 36 | // node wasm 37 | declare module '*.wasm?loader' { 38 | const loadWasm: (options?: WebAssembly.Imports) => Promise 39 | export default loadWasm 40 | } 41 | 42 | // build-in process env 43 | declare namespace NodeJS { 44 | interface ProcessEnv { 45 | /** 46 | * Vite's dev server address for Electron renderers. 47 | */ 48 | readonly ELECTRON_RENDERER_URL?: string 49 | } 50 | } 51 | 52 | // Refer to Vite's ImportMeta type declarations 53 | // 54 | 55 | interface ImportMetaEnv { 56 | MODE: string 57 | DEV: boolean 58 | PROD: boolean 59 | } 60 | 61 | interface ImportGlobOptions { 62 | /** 63 | * Import type for the import url. 64 | */ 65 | as?: AsType 66 | /** 67 | * Import as static or dynamic 68 | * 69 | * @default false 70 | */ 71 | eager?: Eager 72 | /** 73 | * Import only the specific named export. Set to `default` to import the default export. 74 | */ 75 | import?: string 76 | /** 77 | * Custom queries 78 | */ 79 | query?: string | Record 80 | /** 81 | * Search files also inside `node_modules/` and hidden directories (e.g. `.git/`). This might have impact on performance. 82 | * 83 | * @default false 84 | */ 85 | exhaustive?: boolean 86 | } 87 | 88 | interface KnownAsTypeMap { 89 | raw: string 90 | url: string 91 | worker: Worker 92 | } 93 | 94 | interface ImportGlobFunction { 95 | /** 96 | * Import a list of files with a glob pattern. 97 | * 98 | * https://vitejs.dev/guide/features.html#glob-import 99 | */ 100 | ( 101 | glob: string | string[], 102 | options?: ImportGlobOptions 103 | ): (Eager extends true ? true : false) extends true ? Record : Record Promise> 104 | (glob: string | string[], options?: ImportGlobOptions): Record Promise> 105 | (glob: string | string[], options: ImportGlobOptions): Record 106 | } 107 | 108 | interface ImportMeta { 109 | url: string 110 | readonly env: ImportMetaEnv 111 | glob: ImportGlobFunction 112 | } 113 | -------------------------------------------------------------------------------- /.github/commit-convention.md: -------------------------------------------------------------------------------- 1 | ## Git Commit Message Convention 2 | 3 | > This is adapted from [Angular's commit convention](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular). 4 | 5 | #### TL;DR: 6 | 7 | Messages must be matched by the following regex: 8 | 9 | 10 | ```js 11 | /^(revert: )?(feat|fix|docs|dx|refactor|perf|test|workflow|build|ci|chore|types|wip|release|deps)(\(.+\))?: .{1,50}/ 12 | ``` 13 | 14 | #### Examples 15 | 16 | Appears under "Features" header, `dev` subheader: 17 | 18 | ``` 19 | feat(dev): add 'comments' option 20 | ``` 21 | 22 | Appears under "Bug Fixes" header, `dev` subheader, with a link to issue #28: 23 | 24 | ``` 25 | fix(dev): fix dev error 26 | 27 | close #28 28 | ``` 29 | 30 | Appears under "Performance Improvements" header, and under "Breaking Changes" with the breaking change explanation: 31 | 32 | ``` 33 | perf(build): remove 'foo' option 34 | 35 | BREAKING CHANGE: The 'foo' option has been removed. 36 | ``` 37 | 38 | The following commit and commit `667ecc1` do not appear in the changelog if they are under the same release. If not, the revert commit appears under the "Reverts" header. 39 | 40 | ``` 41 | revert: feat(compiler): add 'comments' option 42 | 43 | This reverts commit 667ecc1654a317a13331b17617d973392f415f02. 44 | ``` 45 | 46 | ### Full Message Format 47 | 48 | A commit message consists of a **header**, **body** and **footer**. The header has a **type**, **scope** and **subject**: 49 | 50 | ``` 51 | (): 52 | 53 | 54 | 55 |