chore: init
This commit is contained in:
commit
ef99b02b18
9
.editorconfig
Normal file
9
.editorconfig
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
2
.eslintignore
Normal file
2
.eslintignore
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
node_modules
|
||||||
|
dist
|
38
.eslintrc.js
Normal file
38
.eslintrc.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
commonjs: true,
|
||||||
|
es6: true,
|
||||||
|
node: true
|
||||||
|
},
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
parserOptions: {
|
||||||
|
sourceType: 'module',
|
||||||
|
ecmaVersion: 2021
|
||||||
|
},
|
||||||
|
plugins: ['@typescript-eslint'],
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:@typescript-eslint/eslint-recommended',
|
||||||
|
'plugin:prettier/recommended'
|
||||||
|
],
|
||||||
|
rules: {
|
||||||
|
'no-empty': ['warn', { allowEmptyCatch: true }],
|
||||||
|
'@typescript-eslint/ban-ts-comment': ['error', { 'ts-ignore': 'allow-with-description' }],
|
||||||
|
'@typescript-eslint/explicit-function-return-type': 'error',
|
||||||
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
|
'@typescript-eslint/no-empty-function': ['error', { allow: ['arrowFunctions'] }],
|
||||||
|
'@typescript-eslint/no-explicit-any': 'error',
|
||||||
|
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||||
|
'@typescript-eslint/no-var-requires': 'off'
|
||||||
|
},
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ['*.js'],
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/explicit-function-return-type': 'off'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
51
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
51
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
name: "\U0001F41E Bug Report"
|
||||||
|
description: Report an issue with electron-vite
|
||||||
|
labels: ['bug', 'triage']
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for taking the time to fill out this bug report!
|
||||||
|
- type: textarea
|
||||||
|
id: bug-description
|
||||||
|
attributes:
|
||||||
|
label: Describe the bug
|
||||||
|
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!
|
||||||
|
placeholder: Bug description
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: electron-vite-version
|
||||||
|
attributes:
|
||||||
|
label: Electron-Vite Version
|
||||||
|
description: What version of Electron-Vite are you using?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: electron-version
|
||||||
|
attributes:
|
||||||
|
label: Electron Version
|
||||||
|
description: What version of Electron are you using?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: vite-version
|
||||||
|
attributes:
|
||||||
|
label: Vite Version
|
||||||
|
description: What version of Vite are you using?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
id: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Validations
|
||||||
|
description: Before submitting the issue, please make sure you do the following
|
||||||
|
options:
|
||||||
|
- label: Follow the [Code of Conduct](https://github.com/alex8088/electron-vite/blob/master/CODE_OF_CONDUCT.md).
|
||||||
|
required: true
|
||||||
|
- label: Read the [Contributing Guidelines](https://github.com/alex8088/electron-vite/blob/master/CONTRIBUTING.md).
|
||||||
|
required: true
|
||||||
|
- label: Read the [docs](https://github.com/alex8088/electron-vite#readme).
|
||||||
|
required: true
|
||||||
|
- 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.
|
||||||
|
required: true
|
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
blank_issues_enabled: false
|
46
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
46
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
name: "\U0001F680 New Feature Proposal"
|
||||||
|
description: Propose a new feature to be added to electron-vite
|
||||||
|
labels: ['enhancement']
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for your interest in the project and taking the time to fill out this feature report!
|
||||||
|
- type: textarea
|
||||||
|
id: feature-description
|
||||||
|
attributes:
|
||||||
|
label: Clear and concise description of the problem
|
||||||
|
description: 'As a developer using Vite I want [goal / wish] so that [benefit]. If you intend to submit a PR for this issue, tell us in the description. Thanks!'
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: suggested-solution
|
||||||
|
attributes:
|
||||||
|
label: Suggested solution
|
||||||
|
description: 'In module [xy] we could provide following implementation...'
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: alternative
|
||||||
|
attributes:
|
||||||
|
label: Alternative
|
||||||
|
description: Clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
- type: textarea
|
||||||
|
id: additional-context
|
||||||
|
attributes:
|
||||||
|
label: Additional context
|
||||||
|
description: Any other context or screenshots about the feature request here.
|
||||||
|
- type: checkboxes
|
||||||
|
id: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Validations
|
||||||
|
description: Before submitting the issue, please make sure you do the following
|
||||||
|
options:
|
||||||
|
- label: Follow the [Code of Conduct](https://github.com/alex8088/electron-vite/blob/master/CODE_OF_CONDUCT.md).
|
||||||
|
required: true
|
||||||
|
- label: Read the [Contributing Guidelines](https://github.com/alex8088/electron-vite/blob/master/CONTRIBUTING.md).
|
||||||
|
required: true
|
||||||
|
- label: Read the [docs](https://github.com/alex8088/electron-vite#readme).
|
||||||
|
required: true
|
||||||
|
- 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.
|
||||||
|
required: true
|
24
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
24
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<!-- Thank you for contributing! -->
|
||||||
|
|
||||||
|
### Description
|
||||||
|
|
||||||
|
<!-- Please insert your description here and provide especially info about the "what" this PR is solving -->
|
||||||
|
|
||||||
|
### Additional context
|
||||||
|
|
||||||
|
<!-- e.g. is there anything you'd like reviewers to focus on? -->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### What is the purpose of this pull request? <!-- (put an "X" next to an item) -->
|
||||||
|
|
||||||
|
- [ ] Bug fix
|
||||||
|
- [ ] New Feature
|
||||||
|
- [ ] Documentation update
|
||||||
|
- [ ] Other
|
||||||
|
|
||||||
|
### Before submitting the PR, please make sure you do the following
|
||||||
|
|
||||||
|
- [ ] Read the [Contributing Guidelines](https://github.com/alex8088/electron-vite/blob/master/CONTRIBUTING.md).
|
||||||
|
- [ ] 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).
|
||||||
|
- [ ] Provide a description in this PR that addresses **what** the PR is solving, or reference the issue that it solves (e.g. `fixes #123`).
|
92
.github/commit-convention.md
vendored
Normal file
92
.github/commit-convention.md
vendored
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
## Git Commit Message Convention
|
||||||
|
|
||||||
|
> This is adapted from [Angular's commit convention](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular).
|
||||||
|
|
||||||
|
#### TL;DR:
|
||||||
|
|
||||||
|
Messages must be matched by the following regex:
|
||||||
|
|
||||||
|
<!-- prettier-ignore -->
|
||||||
|
```js
|
||||||
|
/^(revert: )?(feat|fix|docs|dx|refactor|perf|test|workflow|build|ci|chore|types|wip|release|deps)(\(.+\))?: .{1,50}/
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
Appears under "Features" header, `dev` subheader:
|
||||||
|
|
||||||
|
```
|
||||||
|
feat(dev): add 'comments' option
|
||||||
|
```
|
||||||
|
|
||||||
|
Appears under "Bug Fixes" header, `dev` subheader, with a link to issue #28:
|
||||||
|
|
||||||
|
```
|
||||||
|
fix(dev): fix dev error
|
||||||
|
|
||||||
|
close #28
|
||||||
|
```
|
||||||
|
|
||||||
|
Appears under "Performance Improvements" header, and under "Breaking Changes" with the breaking change explanation:
|
||||||
|
|
||||||
|
```
|
||||||
|
perf(build): remove 'foo' option
|
||||||
|
|
||||||
|
BREAKING CHANGE: The 'foo' option has been removed.
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
```
|
||||||
|
revert: feat(compiler): add 'comments' option
|
||||||
|
|
||||||
|
This reverts commit 667ecc1654a317a13331b17617d973392f415f02.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Full Message Format
|
||||||
|
|
||||||
|
A commit message consists of a **header**, **body** and **footer**. The header has a **type**, **scope** and **subject**:
|
||||||
|
|
||||||
|
```
|
||||||
|
<type>(<scope>): <subject>
|
||||||
|
<BLANK LINE>
|
||||||
|
<body>
|
||||||
|
<BLANK LINE>
|
||||||
|
<footer>
|
||||||
|
```
|
||||||
|
|
||||||
|
The **header** is mandatory and the **scope** of the header is optional.
|
||||||
|
|
||||||
|
### Revert
|
||||||
|
|
||||||
|
If the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit. In the body, it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit being reverted.
|
||||||
|
|
||||||
|
### Type
|
||||||
|
|
||||||
|
If the prefix is `feat`, `fix` or `perf`, it will appear in the changelog. However, if there is any [BREAKING CHANGE](#footer), the commit will always appear in the changelog.
|
||||||
|
|
||||||
|
Other prefixes are up to your discretion. Suggested prefixes are `docs`, `chore`, `style`, `refactor`, and `test` for non-changelog related tasks.
|
||||||
|
|
||||||
|
### Scope
|
||||||
|
|
||||||
|
The scope could be anything specifying the place of the commit change. For example `dev`, `build`, `workflow`, `cli` etc...
|
||||||
|
|
||||||
|
### Subject
|
||||||
|
|
||||||
|
The subject contains a succinct description of the change:
|
||||||
|
|
||||||
|
- use the imperative, present tense: "change" not "changed" nor "changes"
|
||||||
|
- don't capitalize the first letter
|
||||||
|
- no dot (.) at the end
|
||||||
|
|
||||||
|
### Body
|
||||||
|
|
||||||
|
Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes".
|
||||||
|
The body should include the motivation for the change and contrast this with previous behavior.
|
||||||
|
|
||||||
|
### Footer
|
||||||
|
|
||||||
|
The footer should contain any information about **Breaking Changes** and is also the place to
|
||||||
|
reference GitHub issues that this commit **Closes**.
|
||||||
|
|
||||||
|
**Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this.
|
24
.github/workflows/release-tag.yml
vendored
Normal file
24
.github/workflows/release-tag.yml
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
|
||||||
|
|
||||||
|
name: Create Release
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Create Release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@master
|
||||||
|
- name: Create Release for Tag
|
||||||
|
id: release_tag
|
||||||
|
uses: actions/create-release@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
tag_name: ${{ github.ref }}
|
||||||
|
release_name: ${{ github.ref }}
|
||||||
|
body: |
|
||||||
|
Please refer to [CHANGELOG.md](https://github.com/alex8088/electron-vite/blob/${{ github.ref_name }}/CHANGELOG.md) for details.
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
|
||||||
|
*.log*
|
2
.prettierignore
Normal file
2
.prettierignore
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
dist
|
||||||
|
pnpm-lock.yaml
|
5
.prettierrc.yaml
Normal file
5
.prettierrc.yaml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
singleQuote: true
|
||||||
|
semi: false
|
||||||
|
printWidth: 120
|
||||||
|
trailingComma: none
|
||||||
|
arrowParens: avoid
|
3
.vscode/extensions.json
vendored
Normal file
3
.vscode/extensions.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"recommendations": ["dbaeumer.vscode-eslint"]
|
||||||
|
}
|
5
CHANGELOG.md
Normal file
5
CHANGELOG.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
### v1.0.0 (_2022-03-17_)
|
||||||
|
|
||||||
|
#### Features
|
||||||
|
|
||||||
|
- electron-vite
|
45
CODE_OF_CONDUCT.md
Normal file
45
CODE_OF_CONDUCT.md
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
# Code Of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, political party, or sexual identity and orientation. Note, however, that religion, political party, or other ideological affiliation provide no exemptions for the behavior we outline as unacceptable in this Code of Conduct.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to creating a positive environment include:
|
||||||
|
|
||||||
|
- Using welcoming and inclusive language
|
||||||
|
- Being respectful of differing viewpoints and experiences
|
||||||
|
- Gracefully accepting constructive criticism
|
||||||
|
- Focusing on what is best for the community
|
||||||
|
- Showing empathy towards other community members
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
- The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||||
|
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
|
- Public or private harassment
|
||||||
|
- Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||||
|
- Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||||
|
|
||||||
|
## Our Responsibilities
|
||||||
|
|
||||||
|
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||||
|
|
||||||
|
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at alexwei114@163.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||||
|
|
||||||
|
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
21
CONTRIBUTING.md
Normal file
21
CONTRIBUTING.md
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# Contributing
|
||||||
|
|
||||||
|
Thanks for being interested in contributing to this project!
|
||||||
|
|
||||||
|
## Repo Setup
|
||||||
|
|
||||||
|
Clone this repo to your local machine and install the dependencies.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
**NOTE**: The package manager used to install and link dependencies must be pnpm.
|
||||||
|
|
||||||
|
## Pull Request
|
||||||
|
|
||||||
|
- Checkout a topic branch from a base branch, e.g. master, and merge back against that branch.
|
||||||
|
- It's OK to have multiple small commits as you work on the PR - GitHub can automatically squash them before merging.
|
||||||
|
- To check that your contributions match the project coding style make sure `pnpm lint` && `pnpm typecheck` passes. To build project run: `pnpm build`.
|
||||||
|
- 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)).
|
||||||
|
- 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)).
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022, Alex Wei
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
328
README.md
Normal file
328
README.md
Normal file
|
@ -0,0 +1,328 @@
|
||||||
|
# electron-vite
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<img src="https://img.shields.io/badge/node->14.0.0-blue.svg" alt="node" />
|
||||||
|
<img src="https://img.shields.io/badge/vite->2.6.0-747bff.svg" alt="vite" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
> An Electron CLI integrated with Vite
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- ⚡️Use the same way as [Vite](https://vitejs.dev)
|
||||||
|
- 🔨Both main process and renderer process source code are built using Vite
|
||||||
|
- 📃The main process and the renderer process Vite configuration are combined into one file
|
||||||
|
- 📦Preset optimal build configuration
|
||||||
|
- 🚀HMR for renderer processes
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Install
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm i electron-vite -D
|
||||||
|
```
|
||||||
|
|
||||||
|
### Development & Build
|
||||||
|
|
||||||
|
In a project where `electron-vite` is installed, you can use `electron-vite` binary directly with `npx electron-vite` or add the npm scripts to your `package.json` file like this:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"start": "electron-vite preview", // start electron app to preview production build
|
||||||
|
"dev": "electron-vite dev", // start dev server and electron app
|
||||||
|
"prebuild": "electron-vite build" // build for production
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In order to use the renderer process HMR, you need to use the `environment variables` to determine whether the window browser loads a local html file or a remote URL.
|
||||||
|
|
||||||
|
```js
|
||||||
|
function createWindow() {
|
||||||
|
// Create the browser window
|
||||||
|
const mainWindow = new BrowserWindow({
|
||||||
|
width: 800,
|
||||||
|
height: 600,
|
||||||
|
webPreferences: {
|
||||||
|
preload: path.join(__dirname, '../preload/index.js')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Load the remote URL for development or the local html file for production
|
||||||
|
if (!app.isPackaged && process.env['ELECTRON_RENDERER_URL']) {
|
||||||
|
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
|
||||||
|
} else {
|
||||||
|
mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: For development, the renderer process `index.html` file needs to reference your script code via `<script type="module">`.
|
||||||
|
|
||||||
|
### Recommended project directory
|
||||||
|
|
||||||
|
```shell
|
||||||
|
├──src
|
||||||
|
| ├──main
|
||||||
|
| | ├──index.js
|
||||||
|
| | └──...
|
||||||
|
| ├──preload
|
||||||
|
| | ├──index.js
|
||||||
|
| | └──...
|
||||||
|
| ├──renderer
|
||||||
|
| | ├──src
|
||||||
|
| | ├──index.html
|
||||||
|
| | └──...
|
||||||
|
├──electron.vite.config.js
|
||||||
|
└──package.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get started
|
||||||
|
|
||||||
|
Clone the [electron-vite-boilerplate](https://github.com/alex8088/electron-vite-boilerplate) or use the [create-electron](https://github.com/alex8088/quick-start/tree/master/packages/create-electron) tool to scaffold your project.
|
||||||
|
|
||||||
|
## Configure
|
||||||
|
|
||||||
|
### Config file
|
||||||
|
|
||||||
|
When running `electron-vite` from the command line, electron-vite will automatically try to resolve a config file named `electron.vite.config.js` inside project root. The most basic config file looks like this:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// electron.vite.config.js
|
||||||
|
export default {
|
||||||
|
main: {
|
||||||
|
// vite config options
|
||||||
|
},
|
||||||
|
preload: {
|
||||||
|
// vite config options
|
||||||
|
},
|
||||||
|
renderer: {
|
||||||
|
// vite config options
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also explicitly specify a config file to use with the `--config` CLI option (resolved relative to `cwd`):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
electron-vite --config my-config.js
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tips**: `electron-vite` also supports `ts` or `mjs` config file.
|
||||||
|
|
||||||
|
### Config intellisense
|
||||||
|
|
||||||
|
Since `electron-vite` ships with TypeScript typings, you can leverage your IDE's intellisense with jsdoc type hints:
|
||||||
|
|
||||||
|
```js
|
||||||
|
/**
|
||||||
|
* @type {import('electron-vite').UserConfig}
|
||||||
|
*/
|
||||||
|
const config = {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
export default config
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, you can use the `defineConfig` and `defineViteConfig` helper which should provide intellisense without the need for jsdoc annotations:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { defineConfig, defineViteConfig } from 'electron-vite'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
main: {
|
||||||
|
// ...
|
||||||
|
},
|
||||||
|
preload: {
|
||||||
|
// ...
|
||||||
|
},
|
||||||
|
renderer: defineViteConfig(({ command, mode }) => {
|
||||||
|
// conditional config use defineViteConfig
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tips**: The `defineViteConfig` exports from `Vite`.
|
||||||
|
|
||||||
|
### Config reference
|
||||||
|
|
||||||
|
See [vitejs.dev](https://vitejs.dev/config)
|
||||||
|
|
||||||
|
### Config presets
|
||||||
|
|
||||||
|
#### Build options for `main`:
|
||||||
|
|
||||||
|
- **outDir**: `out\main`(relative to project root)
|
||||||
|
- **target**: `node*`, automatically match node target of `Electron`. For example, the node target of Electron 17 is `node16.13`
|
||||||
|
- **lib.entry**: `src\main\{index|main}.{js|ts|mjs|cjs}`(relative to project root), empty string if not found
|
||||||
|
- **lib.formats**: `cjs`
|
||||||
|
- **rollupOptions.external**: `electron` and all builtin modules
|
||||||
|
|
||||||
|
#### Build options for `preload`:
|
||||||
|
|
||||||
|
- **outDir**: `out\preload`(relative to project root)
|
||||||
|
- **target**: the same as `main`
|
||||||
|
- **lib.entry**: `src\preload\{index|preload}.{js|ts|mjs|cjs}`(relative to project root), empty string if not found
|
||||||
|
- **lib.formats**: `cjs`
|
||||||
|
- **rollupOptions.external**: the same as `main`
|
||||||
|
|
||||||
|
#### Build options for `renderer`:
|
||||||
|
|
||||||
|
- **root**: `src\renderer`(relative to project root)
|
||||||
|
- **outDir**: `out\renderer`(relative to project root)
|
||||||
|
- **target**: `chrome*`, automatically match chrome target of `Electron`. For example, the chrome target of Electron 17 is `chrome98`
|
||||||
|
- **lib.entry**: `src\renderer\index.html`(relative to project root), empty string if not found
|
||||||
|
- **rollupOptions.external**: the same as `main`
|
||||||
|
|
||||||
|
#### Define option for `main` and `preload`
|
||||||
|
|
||||||
|
In web development, Vite will transform `'process.env.'` to `'({}).'`. This is reasonable and correct. But in nodejs development, we sometimes need to use `process.env`, so `electron-vite` will automatically add config define field to redefine global variable replacements like this:
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default {
|
||||||
|
main: {
|
||||||
|
define: {
|
||||||
|
'process.env': 'process.env'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: If you want to use these configurations in an existing project, please see the Vite plugin [vite-plugin-electron-config](https://github.com/alex8088/vite-plugin-electron-config)
|
||||||
|
|
||||||
|
### Config FAQs
|
||||||
|
|
||||||
|
#### How do I configure when the Electron app has multiple windows?
|
||||||
|
|
||||||
|
When your electron app has multiple windows, it means there are multiple html files or preload files. You can modify your config file like this:
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default {
|
||||||
|
main: {},
|
||||||
|
preload: {
|
||||||
|
build: {
|
||||||
|
rollupOptions: {
|
||||||
|
input: {
|
||||||
|
browser: resolve(__dirname, 'src/preload/browser.ts'),
|
||||||
|
webview: resolve(__dirname, 'src/preload/webview.ts')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
renderer: {
|
||||||
|
build: {
|
||||||
|
rollupOptions: {
|
||||||
|
input: {
|
||||||
|
browser: resolve(__dirname, 'src/renderer/browser.html'),
|
||||||
|
webview: resolve(__dirname, 'src/renderer/webview.html')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## CLI options
|
||||||
|
|
||||||
|
For the full list of CLI options, you can run `npx electron-vite -h` in your project. The flags listed below are only available via the command line interface:
|
||||||
|
|
||||||
|
- `--ignoreConfigWarning`: boolean, allow you ignore warning when config missing
|
||||||
|
- `--outDir`: string, output directory (default: out)
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### build
|
||||||
|
|
||||||
|
Type Signature:
|
||||||
|
|
||||||
|
```js
|
||||||
|
async function build(inlineConfig: InlineConfig = {}): Promise<void>
|
||||||
|
```
|
||||||
|
|
||||||
|
Example Usage:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const path = require('path')
|
||||||
|
const { build } = require('electron-vite')
|
||||||
|
|
||||||
|
;(async () => {
|
||||||
|
await build({
|
||||||
|
build: {
|
||||||
|
outDir: 'out'
|
||||||
|
rollupOptions: {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})()
|
||||||
|
```
|
||||||
|
|
||||||
|
### createServer
|
||||||
|
|
||||||
|
Type Signature:
|
||||||
|
|
||||||
|
```js
|
||||||
|
async function createServer(inlineConfig: InlineConfig = {}): Promise<void>
|
||||||
|
```
|
||||||
|
|
||||||
|
Example Usage:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { createServer } = require('electron-vite')
|
||||||
|
|
||||||
|
;(async () => {
|
||||||
|
await createServer({
|
||||||
|
server: {
|
||||||
|
port: 1337
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})()
|
||||||
|
```
|
||||||
|
|
||||||
|
### preview
|
||||||
|
|
||||||
|
Type Signature:
|
||||||
|
|
||||||
|
```js
|
||||||
|
async function preview(inlineConfig: InlineConfig = {}): Promise<void>
|
||||||
|
```
|
||||||
|
|
||||||
|
Example Usage:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { preview } = require('electron-vite')
|
||||||
|
|
||||||
|
;(async () => {
|
||||||
|
await preview({})
|
||||||
|
})()
|
||||||
|
```
|
||||||
|
|
||||||
|
### InlineConfig
|
||||||
|
|
||||||
|
The InlineConfig interface extends Vite [UserConfig](https://vitejs.dev/guide/api-javascript.html#inlineconfig) with additional properties:
|
||||||
|
|
||||||
|
- `ignoreConfigWarning`: set to `false` to ignore warning when config missing
|
||||||
|
|
||||||
|
And omit `base` property because it is not necessary to set the base public path in Electron.
|
||||||
|
|
||||||
|
### resolveConfig
|
||||||
|
|
||||||
|
Type Signature:
|
||||||
|
|
||||||
|
```js
|
||||||
|
async function resolveConfig(
|
||||||
|
inlineConfig: InlineConfig,
|
||||||
|
command: 'build' | 'serve',
|
||||||
|
defaultMode = 'development'
|
||||||
|
): Promise<ResolvedConfig>
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[MIT](./LICENSE)
|
52
api-extractor.json
Normal file
52
api-extractor.json
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
|
||||||
|
|
||||||
|
"mainEntryPointFilePath": "./dist/types/index.d.ts",
|
||||||
|
|
||||||
|
"dtsRollup": {
|
||||||
|
"enabled": true,
|
||||||
|
"untrimmedFilePath": "",
|
||||||
|
"publicTrimmedFilePath": "./dist/index.d.ts"
|
||||||
|
},
|
||||||
|
|
||||||
|
"apiReport": {
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
|
||||||
|
"docModel": {
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
|
||||||
|
"tsdocMetadata": {
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
|
||||||
|
"messages": {
|
||||||
|
"compilerMessageReporting": {
|
||||||
|
"default": {
|
||||||
|
"logLevel": "warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"extractorMessageReporting": {
|
||||||
|
"default": {
|
||||||
|
"logLevel": "warning",
|
||||||
|
"addToApiReportFile": true
|
||||||
|
},
|
||||||
|
|
||||||
|
"ae-missing-release-tag": {
|
||||||
|
"logLevel": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"tsdocMessageReporting": {
|
||||||
|
"default": {
|
||||||
|
"logLevel": "warning"
|
||||||
|
},
|
||||||
|
|
||||||
|
"tsdoc-undefined-tag": {
|
||||||
|
"logLevel": "none"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
bin/electron-vite.js
Normal file
30
bin/electron-vite.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const debugIndex = process.argv.findIndex(arg => /^(?:-d|--debug)$/.test(arg))
|
||||||
|
const filterIndex = process.argv.findIndex(arg => /^(?:-f|--filter)$/.test(arg))
|
||||||
|
|
||||||
|
if (debugIndex > 0) {
|
||||||
|
let value = process.argv[debugIndex + 1]
|
||||||
|
if (!value || value.startsWith('-')) {
|
||||||
|
value = 'vite:*'
|
||||||
|
} else {
|
||||||
|
value = value
|
||||||
|
.split(',')
|
||||||
|
.map(v => `vite:${v}`)
|
||||||
|
.join(',')
|
||||||
|
}
|
||||||
|
process.env.DEBUG = `${process.env.DEBUG ? process.env.DEBUG + ',' : ''}${value}`
|
||||||
|
|
||||||
|
if (filterIndex > 0) {
|
||||||
|
const filter = process.argv[filterIndex + 1]
|
||||||
|
if (filter && !filter.startsWith('-')) {
|
||||||
|
process.env.VITE_DEBUG_FILTER = filter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function run() {
|
||||||
|
require('../dist/cli')
|
||||||
|
}
|
||||||
|
|
||||||
|
run()
|
79
package.json
Normal file
79
package.json
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
{
|
||||||
|
"name": "electron-vite",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Use vite for your electron app.",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
|
"bin": {
|
||||||
|
"electron-vite": "bin/electron-vite.js"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"bin",
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.2.0"
|
||||||
|
},
|
||||||
|
"author": "Alex Wei<https://github.com/alex8088>",
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/alex8088/electron-vite.git"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/alex8088/electron-vite/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/alex8088/electron-vite#readme",
|
||||||
|
"keywords": [
|
||||||
|
"electron",
|
||||||
|
"vite",
|
||||||
|
"cli",
|
||||||
|
"plugin"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"format": "prettier --write .",
|
||||||
|
"lint": "eslint --ext .ts src/**",
|
||||||
|
"typecheck": "tsc --noEmit",
|
||||||
|
"build": "npm run lint && node scripts/build.js"
|
||||||
|
},
|
||||||
|
"simple-git-hooks": {
|
||||||
|
"pre-commit": "npx lint-staged",
|
||||||
|
"commit-msg": "node scripts/verifyCommit.js $1"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"*.js": [
|
||||||
|
"prettier --write"
|
||||||
|
],
|
||||||
|
"*.ts?(x)": [
|
||||||
|
"eslint",
|
||||||
|
"prettier --parser=typescript --write"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vite": "^2.6.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@microsoft/api-extractor": "^7.19.4",
|
||||||
|
"@rollup/plugin-node-resolve": "^13.1.3",
|
||||||
|
"@rollup/plugin-typescript": "^8.3.0",
|
||||||
|
"@types/node": "16.11.22",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.11.0",
|
||||||
|
"@typescript-eslint/parser": "^5.11.0",
|
||||||
|
"eslint": "^8.7.0",
|
||||||
|
"eslint-config-prettier": "^8.3.0",
|
||||||
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
|
"fs-extra": "^10.0.0",
|
||||||
|
"lint-staged": "^12.3.6",
|
||||||
|
"prettier": "^2.5.1",
|
||||||
|
"rollup": "^2.64.0",
|
||||||
|
"simple-git-hooks": "^2.7.0",
|
||||||
|
"tslib": "^2.3.1",
|
||||||
|
"typescript": "^4.5.5",
|
||||||
|
"vite": "^2.8.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"cac": "^6.7.12",
|
||||||
|
"esbuild": "^0.14.21",
|
||||||
|
"picocolors": "^1.0.0"
|
||||||
|
}
|
||||||
|
}
|
1888
pnpm-lock.yaml
Normal file
1888
pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load diff
67
scripts/build.js
Normal file
67
scripts/build.js
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
const path = require('path')
|
||||||
|
const colors = require('picocolors')
|
||||||
|
const fs = require('fs-extra')
|
||||||
|
const rollup = require('rollup')
|
||||||
|
const typescript = require('@rollup/plugin-typescript')
|
||||||
|
const { nodeResolve } = require('@rollup/plugin-node-resolve')
|
||||||
|
const { Extractor, ExtractorConfig } = require('@microsoft/api-extractor')
|
||||||
|
|
||||||
|
;(async () => {
|
||||||
|
const dist = path.resolve(__dirname, '../dist')
|
||||||
|
|
||||||
|
await fs.remove(dist)
|
||||||
|
|
||||||
|
console.log()
|
||||||
|
console.log(colors.bold(colors.yellow(`Rolling up ts code...`)))
|
||||||
|
|
||||||
|
const pkg = require('../package.json')
|
||||||
|
|
||||||
|
const external = ['esbuild', ...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.peerDependencies || {})]
|
||||||
|
|
||||||
|
const bundle = await rollup.rollup({
|
||||||
|
input: {
|
||||||
|
index: path.resolve(__dirname, '../src/index.ts'),
|
||||||
|
cli: path.resolve(__dirname, '../src/cli.ts')
|
||||||
|
},
|
||||||
|
external,
|
||||||
|
plugins: [
|
||||||
|
typescript({
|
||||||
|
tsconfig: path.resolve(__dirname, '../tsconfig.json')
|
||||||
|
}),
|
||||||
|
nodeResolve()
|
||||||
|
],
|
||||||
|
treeshake: {
|
||||||
|
moduleSideEffects: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await bundle.write({
|
||||||
|
dir: dist,
|
||||||
|
entryFileNames: '[name].js',
|
||||||
|
chunkFileNames: 'chunks/lib-[hash].js',
|
||||||
|
format: 'cjs'
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(colors.bold(colors.yellow(`Rolling up type definitions...`)))
|
||||||
|
|
||||||
|
if (pkg.types) {
|
||||||
|
const extractorConfig = ExtractorConfig.loadFileAndPrepare(path.resolve(__dirname, '../api-extractor.json'))
|
||||||
|
const extractorResult = Extractor.invoke(extractorConfig, {
|
||||||
|
localBuild: true,
|
||||||
|
showVerboseMessages: true
|
||||||
|
})
|
||||||
|
|
||||||
|
if (extractorResult.succeeded) {
|
||||||
|
console.log(colors.green('API Extractor completed successfully'))
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
`API Extractor completed with ${extractorResult.errorCount} errors` +
|
||||||
|
` and ${extractorResult.warningCount} warnings`
|
||||||
|
)
|
||||||
|
process.exitCode = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await fs.remove(path.resolve(dist, 'types'))
|
||||||
|
|
||||||
|
console.log(colors.green(`Build ${pkg.name}@${pkg.version} successfully`))
|
||||||
|
})()
|
22
scripts/verifyCommit.js
Normal file
22
scripts/verifyCommit.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
// Invoked on the commit-msg git hook by simple-git-hooks.
|
||||||
|
|
||||||
|
const colors = require('picocolors')
|
||||||
|
const fs = require('fs')
|
||||||
|
|
||||||
|
const msgPath = process.argv[2]
|
||||||
|
const msg = fs.readFileSync(msgPath, 'utf-8').trim()
|
||||||
|
|
||||||
|
const commitRE =
|
||||||
|
/^(revert: )?(feat|fix|docs|dx|style|refactor|perf|test|workflow|build|ci|chore|types|wip|release)(\(.+\))?: .{1,50}/
|
||||||
|
|
||||||
|
if (!commitRE.test(msg)) {
|
||||||
|
console.log()
|
||||||
|
console.error(
|
||||||
|
` ${colors.bgRed.white(' ERROR ')} ${colors.red(`invalid commit message format.`)}\n\n` +
|
||||||
|
colors.red(` Proper commit message format is required for automated changelog generation. Examples:\n\n`) +
|
||||||
|
` ${colors.green(`feat: add 'comments' option`)}\n` +
|
||||||
|
` ${colors.green(`fix: handle events on blur (close #28)`)}\n\n` +
|
||||||
|
colors.red(` See .github/commit-convention.md for more details.\n`)
|
||||||
|
)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
23
src/build.ts
Normal file
23
src/build.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { build as viteBuild } from 'vite'
|
||||||
|
import { InlineConfig, resolveConfig } from './config'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bundles the electron app for production.
|
||||||
|
*/
|
||||||
|
export async function build(inlineConfig: InlineConfig = {}): Promise<void> {
|
||||||
|
const config = await resolveConfig(inlineConfig, 'build', 'production')
|
||||||
|
if (config.config) {
|
||||||
|
const mainViteConfig = config.config?.main
|
||||||
|
if (mainViteConfig) {
|
||||||
|
await viteBuild(mainViteConfig)
|
||||||
|
}
|
||||||
|
const preloadViteConfig = config.config?.preload
|
||||||
|
if (preloadViteConfig) {
|
||||||
|
await viteBuild(preloadViteConfig)
|
||||||
|
}
|
||||||
|
const rendererViteConfig = config.config?.renderer
|
||||||
|
if (rendererViteConfig) {
|
||||||
|
await viteBuild(rendererViteConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
104
src/cli.ts
Normal file
104
src/cli.ts
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
import { cac } from 'cac'
|
||||||
|
import colors from 'picocolors'
|
||||||
|
import { LogLevel, createLogger } from 'vite'
|
||||||
|
import { InlineConfig } from './config'
|
||||||
|
|
||||||
|
const cli = cac('electron-vite')
|
||||||
|
|
||||||
|
// global options
|
||||||
|
interface GlobalCLIOptions {
|
||||||
|
'--'?: string[]
|
||||||
|
c?: boolean | string
|
||||||
|
config?: string
|
||||||
|
l?: LogLevel
|
||||||
|
logLevel?: LogLevel
|
||||||
|
clearScreen?: boolean
|
||||||
|
d?: boolean | string
|
||||||
|
debug?: boolean | string
|
||||||
|
f?: string
|
||||||
|
filter?: string
|
||||||
|
m?: string
|
||||||
|
mode?: string
|
||||||
|
ignoreConfigWarning?: boolean
|
||||||
|
outDir?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function createInlineConfig(root: string, options: GlobalCLIOptions): InlineConfig {
|
||||||
|
return {
|
||||||
|
root,
|
||||||
|
mode: options.mode,
|
||||||
|
configFile: options.config,
|
||||||
|
logLevel: options.logLevel,
|
||||||
|
clearScreen: options.clearScreen,
|
||||||
|
ignoreConfigWarning: options.ignoreConfigWarning,
|
||||||
|
build: {
|
||||||
|
outDir: options.outDir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cli
|
||||||
|
.option('-c, --config <file>', `[string] use specified config file`)
|
||||||
|
.option('-l, --logLevel <level>', `[string] info | warn | error | silent`)
|
||||||
|
.option('--clearScreen', `[boolean] allow/disable clear screen when logging`)
|
||||||
|
.option('-d, --debug [feat]', `[string | boolean] show debug logs`)
|
||||||
|
.option('-f, --filter <filter>', `[string] filter debug logs`)
|
||||||
|
.option('-m, --mode <mode>', `[string] set env mode`)
|
||||||
|
.option('--ignoreConfigWarning', `[boolean] ignore config warning`)
|
||||||
|
.option('--outDir <dir>', `[string] output directory (default: out)`)
|
||||||
|
|
||||||
|
// dev
|
||||||
|
cli
|
||||||
|
.command('[root]', 'start dev server and electron app')
|
||||||
|
.alias('serve')
|
||||||
|
.alias('dev')
|
||||||
|
.action(async (root: string, options: GlobalCLIOptions) => {
|
||||||
|
const { createServer } = await import('./server')
|
||||||
|
const inlineConfig = createInlineConfig(root, options)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await createServer(inlineConfig)
|
||||||
|
} catch (e) {
|
||||||
|
const error = e as Error
|
||||||
|
createLogger(options.logLevel).error(
|
||||||
|
colors.red(`error during start dev server and electron app:\n${error.stack}`),
|
||||||
|
{ error }
|
||||||
|
)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// build
|
||||||
|
cli.command('build [root]', 'build for production').action(async (root: string, options: GlobalCLIOptions) => {
|
||||||
|
const { build } = await import('./build')
|
||||||
|
const inlineConfig = createInlineConfig(root, options)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await build(inlineConfig)
|
||||||
|
} catch (e) {
|
||||||
|
const error = e as Error
|
||||||
|
createLogger(options.logLevel).error(colors.red(`error during build:\n${error.stack}`), { error })
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// preview
|
||||||
|
cli
|
||||||
|
.command('preview [root]', 'start electron app to preview production build')
|
||||||
|
.action(async (root: string, options: GlobalCLIOptions) => {
|
||||||
|
const { preview } = await import('./preview')
|
||||||
|
const inlineConfig = createInlineConfig(root, options)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await preview(inlineConfig)
|
||||||
|
} catch (e) {
|
||||||
|
const error = e as Error
|
||||||
|
createLogger(options.logLevel).error(colors.red(`error during preview electron app:\n${error.stack}`), { error })
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
cli.help()
|
||||||
|
cli.version(require('../package.json').version)
|
||||||
|
|
||||||
|
cli.parse()
|
340
src/config.ts
Normal file
340
src/config.ts
Normal file
|
@ -0,0 +1,340 @@
|
||||||
|
import * as path from 'path'
|
||||||
|
import * as fs from 'fs'
|
||||||
|
import colors from 'picocolors'
|
||||||
|
import { UserConfig as ViteConfig, ConfigEnv, Plugin, LogLevel, createLogger, mergeConfig, normalizePath } from 'vite'
|
||||||
|
import { build } from 'esbuild'
|
||||||
|
|
||||||
|
import { electronMainVitePlugin, electronPreloadVitePlugin, electronRendererVitePlugin } from './plugin'
|
||||||
|
import { isObject, dynamicImport } from './utils'
|
||||||
|
|
||||||
|
export { defineConfig as defineViteConfig } from 'vite'
|
||||||
|
|
||||||
|
export interface UserConfig {
|
||||||
|
/**
|
||||||
|
* Vite config options for electron main process
|
||||||
|
*
|
||||||
|
* https://cn.vitejs.dev/config/
|
||||||
|
*/
|
||||||
|
main?: ViteConfig
|
||||||
|
/**
|
||||||
|
* Vite config options for electron renderer process
|
||||||
|
*
|
||||||
|
* https://cn.vitejs.dev/config/
|
||||||
|
*/
|
||||||
|
renderer?: ViteConfig
|
||||||
|
/**
|
||||||
|
* Vite config options for electron preload files
|
||||||
|
*
|
||||||
|
* https://cn.vitejs.dev/config/
|
||||||
|
*/
|
||||||
|
preload?: ViteConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
export type InlineConfig = Omit<ViteConfig, 'base'> & {
|
||||||
|
configFile?: string | false
|
||||||
|
envFile?: false
|
||||||
|
ignoreConfigWarning?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UserConfigExport = UserConfig | Promise<UserConfig>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type helper to make it easier to use `electron.vite.config.ts`
|
||||||
|
* accepts a direct {@link UserConfig} object, or a function that returns it.
|
||||||
|
*/
|
||||||
|
export function defineConfig(config: UserConfigExport): UserConfigExport {
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResolvedConfig {
|
||||||
|
config?: UserConfig
|
||||||
|
configFile?: string
|
||||||
|
configFileDependencies: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function resolveConfig(
|
||||||
|
inlineConfig: InlineConfig,
|
||||||
|
command: 'build' | 'serve',
|
||||||
|
defaultMode = 'development'
|
||||||
|
): Promise<ResolvedConfig> {
|
||||||
|
const config = inlineConfig
|
||||||
|
const mode = inlineConfig.mode || defaultMode
|
||||||
|
|
||||||
|
config.mode = mode
|
||||||
|
|
||||||
|
if (mode === 'production') {
|
||||||
|
process.env.NODE_ENV = 'production'
|
||||||
|
}
|
||||||
|
|
||||||
|
let userConfig: UserConfig | undefined
|
||||||
|
let configFileDependencies: string[] = []
|
||||||
|
|
||||||
|
let { configFile } = config
|
||||||
|
if (configFile !== false) {
|
||||||
|
const configEnv = {
|
||||||
|
mode,
|
||||||
|
command
|
||||||
|
}
|
||||||
|
const loadResult = await loadConfigFromFile(
|
||||||
|
configEnv,
|
||||||
|
configFile,
|
||||||
|
config.root,
|
||||||
|
config.logLevel,
|
||||||
|
config.ignoreConfigWarning
|
||||||
|
)
|
||||||
|
if (loadResult) {
|
||||||
|
const root = config.root
|
||||||
|
delete config.root
|
||||||
|
delete config.configFile
|
||||||
|
|
||||||
|
const outDir = config.build?.outDir
|
||||||
|
|
||||||
|
if (loadResult.config.main) {
|
||||||
|
const mainViteConfig: ViteConfig = mergeConfig(loadResult.config.main, deepClone(config))
|
||||||
|
|
||||||
|
if (outDir) {
|
||||||
|
resetOutDir(mainViteConfig, outDir, 'main')
|
||||||
|
}
|
||||||
|
|
||||||
|
mergePlugins(mainViteConfig, electronMainVitePlugin({ root }))
|
||||||
|
|
||||||
|
loadResult.config.main = mainViteConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loadResult.config.preload) {
|
||||||
|
const preloadViteConfig: ViteConfig = mergeConfig(loadResult.config.preload, deepClone(config))
|
||||||
|
|
||||||
|
if (outDir) {
|
||||||
|
resetOutDir(preloadViteConfig, outDir, 'preload')
|
||||||
|
}
|
||||||
|
mergePlugins(preloadViteConfig, electronPreloadVitePlugin({ root }))
|
||||||
|
|
||||||
|
loadResult.config.preload = preloadViteConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loadResult.config.renderer) {
|
||||||
|
const rendererViteConfig: ViteConfig = mergeConfig(loadResult.config.renderer, deepClone(config))
|
||||||
|
|
||||||
|
if (outDir) {
|
||||||
|
resetOutDir(rendererViteConfig, outDir, 'renderer')
|
||||||
|
}
|
||||||
|
|
||||||
|
mergePlugins(rendererViteConfig, electronRendererVitePlugin({ root }))
|
||||||
|
|
||||||
|
loadResult.config.renderer = rendererViteConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
userConfig = loadResult.config
|
||||||
|
configFile = loadResult.path
|
||||||
|
configFileDependencies = loadResult.dependencies
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolved: ResolvedConfig = {
|
||||||
|
config: userConfig,
|
||||||
|
configFile: configFile ? normalizePath(configFile) : undefined,
|
||||||
|
configFileDependencies
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolved
|
||||||
|
}
|
||||||
|
|
||||||
|
function deepClone<T>(data: T): T {
|
||||||
|
return JSON.parse(JSON.stringify(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetOutDir(config: ViteConfig, outDir: string, subOutDir: string): void {
|
||||||
|
let userOutDir = config.build?.outDir
|
||||||
|
if (outDir === userOutDir) {
|
||||||
|
userOutDir = path.resolve(config.root || process.cwd(), outDir, subOutDir)
|
||||||
|
if (config.build) {
|
||||||
|
config.build.outDir = userOutDir
|
||||||
|
} else {
|
||||||
|
config.build = { outDir: userOutDir }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergePlugins(config: ViteConfig, plugins: Plugin[]): void {
|
||||||
|
const userPlugins = config.plugins || []
|
||||||
|
config.plugins = userPlugins.concat(plugins)
|
||||||
|
}
|
||||||
|
|
||||||
|
const CONFIG_FILE_NAME = 'electron.vite.config'
|
||||||
|
|
||||||
|
export async function loadConfigFromFile(
|
||||||
|
configEnv: ConfigEnv,
|
||||||
|
configFile?: string,
|
||||||
|
configRoot: string = process.cwd(),
|
||||||
|
logLevel?: LogLevel,
|
||||||
|
ignoreConfigWarning = false
|
||||||
|
): Promise<{
|
||||||
|
path: string
|
||||||
|
config: UserConfig
|
||||||
|
dependencies: string[]
|
||||||
|
}> {
|
||||||
|
let resolvedPath: string
|
||||||
|
let isESM = false
|
||||||
|
|
||||||
|
if (configFile && /^vite.config.(js)|(ts)|(mjs)|(cjs)$/.test(configFile)) {
|
||||||
|
throw new Error(`config file cannot be named ${configFile}.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
resolvedPath = configFile ? path.resolve(configFile) : findConfigFile(configRoot, ['js', 'ts', 'mjs', 'cjs'])
|
||||||
|
|
||||||
|
if (!resolvedPath) {
|
||||||
|
return {
|
||||||
|
path: '',
|
||||||
|
config: { main: {}, preload: {}, renderer: {} },
|
||||||
|
dependencies: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resolvedPath.endsWith('.mjs')) {
|
||||||
|
isESM = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resolvedPath.endsWith('.js')) {
|
||||||
|
const pkg = path.join(configRoot, 'package.json')
|
||||||
|
if (fs.existsSync(pkg)) {
|
||||||
|
isESM = require(pkg).type === 'module'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const configFilePath = resolvedPath
|
||||||
|
|
||||||
|
try {
|
||||||
|
const bundled = await bundleConfigFile(resolvedPath)
|
||||||
|
|
||||||
|
if (!isESM) {
|
||||||
|
resolvedPath = path.resolve(configRoot, `${CONFIG_FILE_NAME}.mjs`)
|
||||||
|
fs.writeFileSync(resolvedPath, bundled.code)
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileUrl = require('url').pathToFileURL(resolvedPath)
|
||||||
|
|
||||||
|
const userConfig = (await dynamicImport(fileUrl)).default
|
||||||
|
|
||||||
|
if (!isESM) {
|
||||||
|
fs.unlinkSync(resolvedPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = await (typeof userConfig === 'function' ? userConfig() : userConfig)
|
||||||
|
if (!isObject(config)) {
|
||||||
|
throw new Error(`config must export or return an object`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const configRequired: string[] = []
|
||||||
|
|
||||||
|
let mainConfig
|
||||||
|
if (config.main) {
|
||||||
|
const mainViteConfig = config.main
|
||||||
|
mainConfig = await (typeof mainViteConfig === 'function' ? mainViteConfig(configEnv) : mainViteConfig)
|
||||||
|
if (!isObject(mainConfig)) {
|
||||||
|
throw new Error(`main config must export or return an object`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
configRequired.push('main')
|
||||||
|
}
|
||||||
|
|
||||||
|
let rendererConfig
|
||||||
|
if (config.renderer) {
|
||||||
|
const rendererViteConfig = config.renderer
|
||||||
|
rendererConfig = await (typeof rendererViteConfig === 'function'
|
||||||
|
? rendererViteConfig(configEnv)
|
||||||
|
: rendererViteConfig)
|
||||||
|
if (!isObject(rendererConfig)) {
|
||||||
|
throw new Error(`renderer config must export or return an object`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
configRequired.push('renderer')
|
||||||
|
}
|
||||||
|
|
||||||
|
let preloadConfig
|
||||||
|
if (config.preload) {
|
||||||
|
const preloadViteConfig = config.preload
|
||||||
|
preloadConfig = await (typeof preloadViteConfig === 'function' ? preloadViteConfig(configEnv) : preloadViteConfig)
|
||||||
|
if (!isObject(preloadViteConfig)) {
|
||||||
|
throw new Error(`preload config must export or return an object`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
configRequired.push('preload')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ignoreConfigWarning && configRequired.length > 0) {
|
||||||
|
createLogger(logLevel).warn(colors.yellow(`${configRequired.join(' and ')} config is missing`))
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
path: normalizePath(configFilePath),
|
||||||
|
config: {
|
||||||
|
main: mainConfig,
|
||||||
|
renderer: rendererConfig,
|
||||||
|
preload: preloadConfig
|
||||||
|
},
|
||||||
|
dependencies: bundled.dependencies
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
createLogger(logLevel).error(colors.red(`failed to load config from ${configFilePath}`), { error: e as Error })
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function findConfigFile(configRoot: string, extensions: string[]): string {
|
||||||
|
for (const ext of extensions) {
|
||||||
|
const configFile = path.resolve(configRoot, `${CONFIG_FILE_NAME}.${ext}`)
|
||||||
|
if (fs.existsSync(configFile)) {
|
||||||
|
return configFile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
async function bundleConfigFile(fileName: string): Promise<{ code: string; dependencies: string[] }> {
|
||||||
|
const result = await build({
|
||||||
|
absWorkingDir: process.cwd(),
|
||||||
|
entryPoints: [fileName],
|
||||||
|
write: false,
|
||||||
|
platform: 'node',
|
||||||
|
bundle: true,
|
||||||
|
format: 'esm',
|
||||||
|
sourcemap: false,
|
||||||
|
metafile: true,
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
name: 'externalize-deps',
|
||||||
|
setup(build): void {
|
||||||
|
build.onResolve({ filter: /.*/ }, args => {
|
||||||
|
const id = args.path
|
||||||
|
if (id[0] !== '.' && !path.isAbsolute(id)) {
|
||||||
|
return {
|
||||||
|
external: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'replace-import-meta',
|
||||||
|
setup(build): void {
|
||||||
|
build.onLoad({ filter: /\.[jt]s$/ }, async args => {
|
||||||
|
const contents = await fs.promises.readFile(args.path, 'utf8')
|
||||||
|
return {
|
||||||
|
loader: args.path.endsWith('.ts') ? 'ts' : 'js',
|
||||||
|
contents: contents
|
||||||
|
.replace(/\bimport\.meta\.url\b/g, JSON.stringify(`file://${args.path}`))
|
||||||
|
.replace(/\b__dirname\b/g, JSON.stringify(path.dirname(args.path)))
|
||||||
|
.replace(/\b__filename\b/g, JSON.stringify(args.path))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
const { text } = result.outputFiles[0]
|
||||||
|
return {
|
||||||
|
code: text,
|
||||||
|
dependencies: result.metafile ? Object.keys(result.metafile.inputs) : []
|
||||||
|
}
|
||||||
|
}
|
6
src/index.ts
Normal file
6
src/index.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export { LogLevel, createLogger } from 'vite'
|
||||||
|
export * from './config'
|
||||||
|
export { createServer } from './server'
|
||||||
|
export { build } from './build'
|
||||||
|
export { preview } from './preview'
|
||||||
|
export * from './plugin'
|
350
src/plugin.ts
Normal file
350
src/plugin.ts
Normal file
|
@ -0,0 +1,350 @@
|
||||||
|
import path from 'path'
|
||||||
|
import * as fs from 'fs'
|
||||||
|
import colors from 'picocolors'
|
||||||
|
import { builtinModules, createRequire } from 'module'
|
||||||
|
import { Plugin, mergeConfig, normalizePath } from 'vite'
|
||||||
|
|
||||||
|
export interface ElectronPluginOptions {
|
||||||
|
root?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function findLibEntry(root: string, scope: string): string {
|
||||||
|
for (const name of ['index', scope]) {
|
||||||
|
for (const ext of ['js', 'ts', 'mjs', 'cjs']) {
|
||||||
|
const entryFile = path.resolve(root, 'src', scope, `${name}.${ext}`)
|
||||||
|
if (fs.existsSync(entryFile)) {
|
||||||
|
return entryFile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function findInput(root: string, scope = 'renderer'): string {
|
||||||
|
const rendererDir = path.resolve(root, 'src', scope, 'index.html')
|
||||||
|
if (fs.existsSync(rendererDir)) {
|
||||||
|
return rendererDir
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function processEnvDefine(): Record<string, string> {
|
||||||
|
return {
|
||||||
|
'process.env': `process.env`,
|
||||||
|
'global.process.env': `global.process.env`,
|
||||||
|
'globalThis.process.env': `globalThis.process.env`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function electronMainVitePlugin(options?: ElectronPluginOptions): Plugin[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'vite:electron-main-preset-config',
|
||||||
|
apply: 'build',
|
||||||
|
enforce: 'pre',
|
||||||
|
config(config): void {
|
||||||
|
const root = options?.root || process.cwd()
|
||||||
|
|
||||||
|
const electornVer = getElectronMainVer(root)
|
||||||
|
const nodeTarget = getElectronNodeTarget(electornVer)
|
||||||
|
|
||||||
|
const defaultConfig = {
|
||||||
|
build: {
|
||||||
|
outDir: path.resolve(root, 'out', 'main'),
|
||||||
|
target: nodeTarget,
|
||||||
|
lib: {
|
||||||
|
entry: findLibEntry(root, 'main'),
|
||||||
|
formats: ['cjs']
|
||||||
|
},
|
||||||
|
rollupOptions: {
|
||||||
|
external: ['electron', ...builtinModules.flatMap(m => [m, `node:${m}`])],
|
||||||
|
output: {
|
||||||
|
entryFileNames: '[name].js'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
minify: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildConfig = mergeConfig(defaultConfig.build, config.build || {})
|
||||||
|
config.build = buildConfig
|
||||||
|
|
||||||
|
config.define = config.define || {}
|
||||||
|
config.define = { ...processEnvDefine(), ...config.define }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'vite:electron-main-resolved-config',
|
||||||
|
apply: 'build',
|
||||||
|
enforce: 'post',
|
||||||
|
configResolved(config): void {
|
||||||
|
const build = config.build
|
||||||
|
if (!build.target) {
|
||||||
|
throw new Error('build target required for the electron vite main config')
|
||||||
|
} else {
|
||||||
|
const targets = Array.isArray(build.target) ? build.target : [build.target]
|
||||||
|
if (targets.some(t => !t.startsWith('node'))) {
|
||||||
|
throw new Error('the electron vite main config build target must be node')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const lib = build.lib
|
||||||
|
if (!lib) {
|
||||||
|
throw new Error('build lib field required for the electron vite main config')
|
||||||
|
} else {
|
||||||
|
if (!lib.entry) {
|
||||||
|
throw new Error('build entry field required for the electron vite main config')
|
||||||
|
}
|
||||||
|
if (!lib.formats) {
|
||||||
|
throw new Error('build format field required for the electron vite main config')
|
||||||
|
} else if (!lib.formats.includes('cjs')) {
|
||||||
|
throw new Error('the electron vite main config build lib format must be cjs')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function electronPreloadVitePlugin(options?: ElectronPluginOptions): Plugin[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'vite:electron-preload-preset-config',
|
||||||
|
apply: 'build',
|
||||||
|
enforce: 'pre',
|
||||||
|
config(config): void {
|
||||||
|
const root = options?.root || process.cwd()
|
||||||
|
|
||||||
|
const electornVer = getElectronMainVer(root)
|
||||||
|
const nodeTarget = getElectronNodeTarget(electornVer)
|
||||||
|
|
||||||
|
const defaultConfig = {
|
||||||
|
build: {
|
||||||
|
outDir: path.resolve(root, 'out', 'preload'),
|
||||||
|
target: nodeTarget,
|
||||||
|
rollupOptions: {
|
||||||
|
external: ['electron', ...builtinModules.flatMap(m => [m, `node:${m}`])],
|
||||||
|
output: {
|
||||||
|
entryFileNames: '[name].js'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
minify: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const build = config.build || {}
|
||||||
|
const rollupOptions = build.rollupOptions || {}
|
||||||
|
if (!rollupOptions.input) {
|
||||||
|
defaultConfig.build['lib'] = {
|
||||||
|
entry: findLibEntry(root, 'preload'),
|
||||||
|
formats: ['cjs']
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!rollupOptions.output) {
|
||||||
|
defaultConfig.build.rollupOptions.output['format'] = 'cjs'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildConfig = mergeConfig(defaultConfig.build, config.build || {})
|
||||||
|
config.build = buildConfig
|
||||||
|
|
||||||
|
config.define = config.define || {}
|
||||||
|
config.define = { ...processEnvDefine(), ...config.define }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'vite:electron-preload-resolved-config',
|
||||||
|
apply: 'build',
|
||||||
|
enforce: 'post',
|
||||||
|
configResolved(config): void {
|
||||||
|
const build = config.build
|
||||||
|
if (!build.target) {
|
||||||
|
throw new Error('build target required for the electron vite preload config')
|
||||||
|
} else {
|
||||||
|
const targets = Array.isArray(build.target) ? build.target : [build.target]
|
||||||
|
if (targets.some(t => !t.startsWith('node'))) {
|
||||||
|
throw new Error('the electron vite preload config build target must be node')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const lib = build.lib
|
||||||
|
if (!lib) {
|
||||||
|
const rollupOptions = build.rollupOptions
|
||||||
|
if (!rollupOptions?.input) {
|
||||||
|
throw new Error('build lib field required for the electron vite preload config')
|
||||||
|
} else {
|
||||||
|
const output = rollupOptions?.output
|
||||||
|
if (output) {
|
||||||
|
const formats = Array.isArray(output) ? output : [output]
|
||||||
|
if (!formats.some(f => f !== 'cjs')) {
|
||||||
|
throw new Error('the electron vite preload config output format must be cjs')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!lib.entry) {
|
||||||
|
throw new Error('build entry field required for the electron vite preload config')
|
||||||
|
}
|
||||||
|
if (!lib.formats) {
|
||||||
|
throw new Error('build format field required for the electron vite preload config')
|
||||||
|
} else if (!lib.formats.includes('cjs')) {
|
||||||
|
throw new Error('the electron vite preload config lib format must be cjs')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function electronRendererVitePlugin(options?: ElectronPluginOptions): Plugin[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'vite:electron-renderer-preset-config',
|
||||||
|
enforce: 'pre',
|
||||||
|
config(config): void {
|
||||||
|
const root = options?.root || process.cwd()
|
||||||
|
|
||||||
|
config.base = config.mode === 'production' ? './' : config.base
|
||||||
|
config.root = config.root || './src/renderer'
|
||||||
|
|
||||||
|
const electornVer = getElectronMainVer(root)
|
||||||
|
const chromeTarget = getElectronChromeTarget(electornVer)
|
||||||
|
|
||||||
|
const emptyOutDir = (): boolean => {
|
||||||
|
let outDir = config.build?.outDir
|
||||||
|
if (outDir) {
|
||||||
|
if (!path.isAbsolute(outDir)) {
|
||||||
|
outDir = path.resolve(root, outDir)
|
||||||
|
}
|
||||||
|
const resolvedRoot = normalizePath(path.resolve(root))
|
||||||
|
return normalizePath(outDir).startsWith(resolvedRoot + '/')
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultConfig = {
|
||||||
|
build: {
|
||||||
|
outDir: path.resolve(root, 'out', 'renderer'),
|
||||||
|
target: chromeTarget,
|
||||||
|
rollupOptions: {
|
||||||
|
input: findInput(root),
|
||||||
|
external: [...builtinModules.flatMap(m => [m, `node:${m}`])]
|
||||||
|
},
|
||||||
|
minify: false,
|
||||||
|
emptyOutDir: emptyOutDir()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildConfig = mergeConfig(defaultConfig.build, config.build || {})
|
||||||
|
config.build = buildConfig
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'vite:electron-renderer-resolved-config',
|
||||||
|
enforce: 'post',
|
||||||
|
configResolved(config): void {
|
||||||
|
if (config.base !== './' && config.base !== '/') {
|
||||||
|
config.logger.warn(colors.yellow('should not set base field for the electron vite renderer config'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const build = config.build
|
||||||
|
if (!build.target) {
|
||||||
|
throw new Error('build target required for the electron vite renderer config')
|
||||||
|
} else {
|
||||||
|
const targets = Array.isArray(build.target) ? build.target : [build.target]
|
||||||
|
if (targets.some(t => !t.startsWith('chrome'))) {
|
||||||
|
throw new Error('the electron vite renderer config build target must be chrome')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rollupOptions = build.rollupOptions
|
||||||
|
if (!rollupOptions.input) {
|
||||||
|
config.logger.warn(colors.yellow(`index.html file is not found in ${colors.dim('/src/renderer')} directory`))
|
||||||
|
throw new Error('build rollupOptions input field required for the electron vite renderer config')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function electronConfigServeVitePlugin(options: {
|
||||||
|
configFile: string
|
||||||
|
configFileDependencies: string[]
|
||||||
|
}): Plugin {
|
||||||
|
const getShortName = (file: string, root: string): string => {
|
||||||
|
return file.startsWith(root + '/') ? path.posix.relative(root, file) : file
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'vite:electron-config-serve',
|
||||||
|
apply: 'serve',
|
||||||
|
handleHotUpdate({ file, server }): void {
|
||||||
|
const { config } = server
|
||||||
|
const logger = config.logger
|
||||||
|
const shortFile = getShortName(file, config.root)
|
||||||
|
const isConfig = file === options.configFile
|
||||||
|
const isConfigDependency = options.configFileDependencies.some(name => file === path.resolve(name))
|
||||||
|
if (isConfig || isConfigDependency) {
|
||||||
|
logger.info(`[config change] ${colors.dim(shortFile)}`)
|
||||||
|
logger.info(colors.green(`${path.relative(process.cwd(), file)} changed, restarting server...`), {
|
||||||
|
clear: true,
|
||||||
|
timestamp: true
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
server.restart()
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(colors.red('failed to restart server'), { error: e as Error })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getElectronMainVer(root: string): string {
|
||||||
|
let mainVer = process.env.ELECTRON_MAIN_VER || ''
|
||||||
|
if (!mainVer) {
|
||||||
|
const electronModulePath = path.resolve(root, 'node_modules', 'electron')
|
||||||
|
const pkg = path.join(electronModulePath, 'package.json')
|
||||||
|
if (fs.existsSync(pkg)) {
|
||||||
|
const require = createRequire(import.meta.url)
|
||||||
|
const version = require(pkg).version
|
||||||
|
mainVer = version.split('.')[0]
|
||||||
|
process.env.ELECTRON_MAIN_VER = mainVer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mainVer
|
||||||
|
}
|
||||||
|
|
||||||
|
function getElectronNodeTarget(electronVer: string): string {
|
||||||
|
const nodeVer = {
|
||||||
|
'18': '16.13',
|
||||||
|
'17': '16.13',
|
||||||
|
'16': '16.9',
|
||||||
|
'15': '16.5',
|
||||||
|
'14': '14.17',
|
||||||
|
'13': '14.17',
|
||||||
|
'12': '14.16',
|
||||||
|
'11': '12.18'
|
||||||
|
}
|
||||||
|
if (electronVer && parseInt(electronVer) > 10) {
|
||||||
|
return 'node' + nodeVer[electronVer]
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function getElectronChromeTarget(electronVer: string): string {
|
||||||
|
const chromeVer = {
|
||||||
|
'18': '99',
|
||||||
|
'17': '98',
|
||||||
|
'16': '96',
|
||||||
|
'15': '94',
|
||||||
|
'14': '93',
|
||||||
|
'13': '91',
|
||||||
|
'12': '89',
|
||||||
|
'11': '87'
|
||||||
|
}
|
||||||
|
if (electronVer && parseInt(electronVer) > 10) {
|
||||||
|
return 'chrome' + chromeVer[electronVer]
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
27
src/preview.ts
Normal file
27
src/preview.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { spawn } from 'child_process'
|
||||||
|
import colors from 'picocolors'
|
||||||
|
import { createLogger } from 'vite'
|
||||||
|
import { InlineConfig } from './config'
|
||||||
|
import { ensureElectronEntryFile, getElectronPath } from './utils'
|
||||||
|
import { build } from './build'
|
||||||
|
|
||||||
|
export async function preview(inlineConfig: InlineConfig = {}): Promise<void> {
|
||||||
|
await build(inlineConfig)
|
||||||
|
|
||||||
|
const logger = createLogger(inlineConfig.logLevel)
|
||||||
|
|
||||||
|
ensureElectronEntryFile(inlineConfig.root)
|
||||||
|
|
||||||
|
const electronPath = getElectronPath()
|
||||||
|
|
||||||
|
const ps = spawn(electronPath, ['.'])
|
||||||
|
ps.stdout.on('data', chunk => {
|
||||||
|
chunk.toString().trim() && logger.info(chunk.toString())
|
||||||
|
})
|
||||||
|
ps.stderr.on('data', chunk => {
|
||||||
|
chunk.toString().trim() && logger.error(chunk.toString())
|
||||||
|
})
|
||||||
|
ps.on('close', process.exit)
|
||||||
|
|
||||||
|
logger.info(colors.green(`\nstart electron app...`))
|
||||||
|
}
|
70
src/server.ts
Normal file
70
src/server.ts
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
import { spawn } from 'child_process'
|
||||||
|
import { createServer as ViteCreateServer, build as viteBuild, createLogger } from 'vite'
|
||||||
|
import colors from 'picocolors'
|
||||||
|
import { InlineConfig, resolveConfig } from './config'
|
||||||
|
import { ensureElectronEntryFile, getElectronPath } from './utils'
|
||||||
|
|
||||||
|
export async function createServer(inlineConfig: InlineConfig = {}): Promise<void> {
|
||||||
|
const config = await resolveConfig(inlineConfig, 'serve', 'development')
|
||||||
|
if (config.config) {
|
||||||
|
const rendererViteConfig = config.config?.renderer
|
||||||
|
if (rendererViteConfig) {
|
||||||
|
const server = await ViteCreateServer(rendererViteConfig)
|
||||||
|
|
||||||
|
if (!server.httpServer) {
|
||||||
|
throw new Error('HTTP server not available')
|
||||||
|
}
|
||||||
|
|
||||||
|
await server.listen()
|
||||||
|
|
||||||
|
const conf = server.config.server
|
||||||
|
|
||||||
|
const protocol = conf.https ? 'https:' : 'http:'
|
||||||
|
const host = conf.host || 'localhost'
|
||||||
|
const port = conf.port
|
||||||
|
process.env.ELECTRON_RENDERER_URL = `${protocol}//${host}:${port}`
|
||||||
|
|
||||||
|
const logger = server.config.logger
|
||||||
|
|
||||||
|
logger.info(colors.green(`dev server running for the electron renderer process at:\n`), {
|
||||||
|
clear: !logger.hasWarned
|
||||||
|
})
|
||||||
|
|
||||||
|
server.printUrls()
|
||||||
|
}
|
||||||
|
|
||||||
|
const logger = createLogger(inlineConfig.logLevel)
|
||||||
|
|
||||||
|
const mainViteConfig = config.config?.main
|
||||||
|
if (mainViteConfig) {
|
||||||
|
logger.info(colors.gray(`\n-----\n`))
|
||||||
|
|
||||||
|
await viteBuild(mainViteConfig)
|
||||||
|
|
||||||
|
logger.info(colors.green(`\nbuild the electron main process successfully`))
|
||||||
|
}
|
||||||
|
|
||||||
|
const preloadViteConfig = config.config?.preload
|
||||||
|
if (preloadViteConfig) {
|
||||||
|
logger.info(colors.gray(`\n-----\n`))
|
||||||
|
await viteBuild(preloadViteConfig)
|
||||||
|
|
||||||
|
logger.info(colors.green(`\nbuild the electron preload files successfully`))
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureElectronEntryFile(inlineConfig.root)
|
||||||
|
|
||||||
|
const electronPath = getElectronPath()
|
||||||
|
|
||||||
|
const ps = spawn(electronPath, ['.'])
|
||||||
|
ps.stdout.on('data', chunk => {
|
||||||
|
chunk.toString().trim() && logger.info(chunk.toString())
|
||||||
|
})
|
||||||
|
ps.stderr.on('data', chunk => {
|
||||||
|
chunk.toString().trim() && logger.error(chunk.toString())
|
||||||
|
})
|
||||||
|
ps.on('close', process.exit)
|
||||||
|
|
||||||
|
logger.info(colors.green(`\nstart electron app...`))
|
||||||
|
}
|
||||||
|
}
|
39
src/utils.ts
Normal file
39
src/utils.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import * as path from 'path'
|
||||||
|
import * as fs from 'fs'
|
||||||
|
|
||||||
|
export function isObject(value: unknown): value is Record<string, unknown> {
|
||||||
|
return Object.prototype.toString.call(value) === '[object Object]'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dynamicImport = new Function('file', 'return import(file)')
|
||||||
|
|
||||||
|
export function ensureElectronEntryFile(root = process.cwd()): void {
|
||||||
|
const pkg = path.join(root, 'package.json')
|
||||||
|
if (fs.existsSync(pkg)) {
|
||||||
|
const main = require(pkg).main
|
||||||
|
if (!main) {
|
||||||
|
throw new Error('not found an entry point to electorn app, please add main field for your package.json')
|
||||||
|
} else {
|
||||||
|
const entryPath = path.resolve(root, main)
|
||||||
|
if (!fs.existsSync(entryPath)) {
|
||||||
|
throw new Error(`not found the electorn app entry file: ${entryPath}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('no package.json')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getElectronPath(): string {
|
||||||
|
const electronModulePath = path.resolve(process.cwd(), 'node_modules', 'electron')
|
||||||
|
const pathFile = path.join(electronModulePath, 'path.txt')
|
||||||
|
let executablePath
|
||||||
|
if (fs.existsSync(pathFile)) {
|
||||||
|
executablePath = fs.readFileSync(pathFile, 'utf-8')
|
||||||
|
}
|
||||||
|
if (executablePath) {
|
||||||
|
return path.join(electronModulePath, 'dist', executablePath)
|
||||||
|
} else {
|
||||||
|
throw new Error('Electron uninstall')
|
||||||
|
}
|
||||||
|
}
|
23
tsconfig.json
Normal file
23
tsconfig.json
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es2019",
|
||||||
|
"module": "esnext",
|
||||||
|
"lib": ["esnext"],
|
||||||
|
"sourceMap": false,
|
||||||
|
"strict": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noImplicitAny": false,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationDir": "dist/types",
|
||||||
|
"outDir": "dist"
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
Loading…
Reference in a new issue