Add: Vite Electron Builder template

From https://github.com/cawa-93/vite-electron-builder
This commit is contained in:
LmeSzinc 2021-10-08 17:59:53 +08:00
parent f66803b1ad
commit a4e4dda9f0
56 changed files with 18835 additions and 0 deletions

18
webapp/.editorconfig Normal file
View File

@ -0,0 +1,18 @@
# EditorConfig is awesome: http://EditorConfig.org
# https://github.com/jokeyrhyme/standard-editorconfig
# top-most EditorConfig file
root = true
# defaults
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_size = 2
indent_style = space
[*.md]
trim_trailing_whitespace = false

0
webapp/.env.development Normal file
View File

54
webapp/.eslintrc.json Normal file
View File

@ -0,0 +1,54 @@
{
"root": true,
"env": {
"es2021": true,
"node": true,
"browser": false
},
"extends": [
"eslint:recommended",
/** @see https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#recommended-configs */
"plugin:@typescript-eslint/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": [
"@typescript-eslint"
],
"ignorePatterns": [
"types/env.d.ts",
"node_modules/**",
"**/dist/**"
],
"rules": {
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/consistent-type-imports": "error",
/**
* Having a semicolon helps the optimizer interpret your code correctly.
* This avoids rare errors in optimized code.
* @see https://twitter.com/alex_kozack/status/1364210394328408066
*/
"semi": [
"error",
"always"
],
/**
* This will make the history of changes in the hit a little cleaner
*/
"comma-dangle": [
"warn",
"always-multiline"
],
/**
* Just for beauty
*/
"quotes": [
"warn", "single"
]
}
}

3
webapp/.gitattributes vendored Normal file
View File

@ -0,0 +1,3 @@
.github/actions/**/*.js linguist-detectable=false
scripts/*.js linguist-detectable=false
*.config.js linguist-detectable=false

4
webapp/.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,4 @@
# These are supported funding model platforms
patreon: Kozack
open_collective: vite-electron-builder

View File

@ -0,0 +1,27 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: cawa-93
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.

View File

@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Questions & Discussions
url: https://github.com/cawa-93/vite-electron-builder/discussions/categories/q-a
about: Use GitHub discussions for message-board style questions and discussions.

View File

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: cawa-93
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@ -0,0 +1,23 @@
name: 'Release Notes'
description: 'Return release notes based on Git Commits'
inputs:
from:
description: 'Commit from which start log'
required: true
to:
description: 'Commit to which end log'
required: true
include-commit-body:
description: 'Should the commit body be in notes'
required: false
default: 'false'
include-abbreviated-commit:
description: 'Should the commit sha be in notes'
required: false
default: 'true'
outputs:
release-note: # id of output
description: 'Release notes'
runs:
using: 'node12'
main: 'main.js'

View File

@ -0,0 +1,346 @@
// TODO: Refactor this action
const {execSync} = require('child_process');
/**
* Gets the value of an input. The value is also trimmed.
*
* @param name name of the input to get
* @param options optional. See InputOptions.
* @returns string
*/
function getInput(name, options) {
const val = process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] || '';
if (options && options.required && !val) {
throw new Error(`Input required and not supplied: ${name}`);
}
return val.trim();
}
const START_FROM = getInput('from');
const END_TO = getInput('to');
const INCLUDE_COMMIT_BODY = getInput('include-commit-body') === 'true';
const INCLUDE_ABBREVIATED_COMMIT = getInput('include-abbreviated-commit') === 'true';
/**
* @typedef {Object} ICommit
* @property {string | undefined} abbreviated_commit
* @property {string | undefined} subject
* @property {string | undefined} body
*/
/**
* @typedef {ICommit & {type: string | undefined, scope: string | undefined}} ICommitExtended
*/
/**
* Any unique string that is guaranteed not to be used in committee text.
* Used to split data in the commit line
* @type {string}
*/
const commitInnerSeparator = '~~~~';
/**
* Any unique string that is guaranteed not to be used in committee text.
* Used to split each commit line
* @type {string}
*/
const commitOuterSeparator = '₴₴₴₴';
/**
* Commit data to be obtained.
* @type {Map<string, string>}
*
* @see https://git-scm.com/docs/git-log#Documentation/git-log.txt-emnem
*/
const commitDataMap = new Map([
['subject', '%s'], // Required
]);
if (INCLUDE_COMMIT_BODY) {
commitDataMap.set('body', '%b');
}
if (INCLUDE_ABBREVIATED_COMMIT) {
commitDataMap.set('abbreviated_commit', '%h');
}
/**
* The type used to group commits that do not comply with the convention
* @type {string}
*/
const fallbackType = 'other';
/**
* List of all desired commit groups and in what order to display them.
* @type {string[]}
*/
const supportedTypes = [
'feat',
'fix',
'perf',
'refactor',
'style',
'docs',
'test',
'build',
'ci',
'chore',
'revert',
'deps',
fallbackType,
];
/**
* @param {string} commitString
* @returns {ICommit}
*/
function parseCommit(commitString) {
/** @type {ICommit} */
const commitDataObj = {};
const commitDataArray =
commitString
.split(commitInnerSeparator)
.map(s => s.trim());
for (const [key] of commitDataMap) {
commitDataObj[key] = commitDataArray.shift();
}
return commitDataObj;
}
/**
* Returns an array of commits since the last git tag
* @return {ICommit[]}
*/
function getCommits() {
const format = Array.from(commitDataMap.values()).join(commitInnerSeparator) + commitOuterSeparator;
const logs = String(execSync(`git --no-pager log ${START_FROM}..${END_TO} --pretty=format:"${format}" --reverse`));
return logs
.trim()
.split(commitOuterSeparator)
.filter(r => !!r.trim()) // Skip empty lines
.map(parseCommit);
}
/**
*
* @param {ICommit} commit
* @return {ICommitExtended}
*/
function setCommitTypeAndScope(commit) {
const matchRE = new RegExp(`^(?:(${supportedTypes.join('|')})(?:\\((\\S+)\\))?:)?(.*)`, 'i');
let [, type, scope, clearSubject] = commit.subject.match(matchRE);
/**
* Additional rules for checking committees that do not comply with the convention, but for which it is possible to determine the type.
*/
// Commits like `revert something`
if (type === undefined && commit.subject.startsWith('revert')) {
type = 'revert';
}
return {
...commit,
type: (type || fallbackType).toLowerCase().trim(),
scope: (scope || '').toLowerCase().trim(),
subject: (clearSubject || commit.subject).trim(),
};
}
class CommitGroup {
constructor() {
this.scopes = new Map;
this.commits = [];
}
/**
*
* @param {ICommitExtended[]} array
* @param {ICommitExtended} commit
*/
static _pushOrMerge(array, commit) {
const similarCommit = array.find(c => c.subject === commit.subject);
if (similarCommit) {
if (commit.abbreviated_commit !== undefined) {
similarCommit.abbreviated_commit += `, ${commit.abbreviated_commit}`;
}
} else {
array.push(commit);
}
}
/**
* @param {ICommitExtended} commit
*/
push(commit) {
if (!commit.scope) {
CommitGroup._pushOrMerge(this.commits, commit);
return;
}
const scope = this.scopes.get(commit.scope) || {commits: []};
CommitGroup._pushOrMerge(scope.commits, commit);
this.scopes.set(commit.scope, scope);
}
get isEmpty() {
return this.commits.length === 0 && this.scopes.size === 0;
}
}
/**
* Groups all commits by type and scopes
* @param {ICommit[]} commits
* @returns {Map<string, CommitGroup>}
*/
function getGroupedCommits(commits) {
const parsedCommits = commits.map(setCommitTypeAndScope);
const types = new Map(
supportedTypes.map(id => ([id, new CommitGroup()])),
);
for (const parsedCommit of parsedCommits) {
const typeId = parsedCommit.type;
const type = types.get(typeId);
type.push(parsedCommit);
}
return types;
}
/**
* Return markdown list with commits
* @param {ICommitExtended[]} commits
* @param {string} pad
* @returns {string}
*/
function getCommitsList(commits, pad = '') {
let changelog = '';
for (const commit of commits) {
changelog += `${pad}- ${commit.subject}.`;
if (commit.abbreviated_commit !== undefined) {
changelog += ` (${commit.abbreviated_commit})`;
}
changelog += '\r\n';
if (commit.body === undefined) {
continue;
}
const body = commit.body.replace('[skip ci]', '').trim();
if (body !== '') {
changelog += `${
body
.split(/\r*\n+/)
.filter(s => !!s.trim())
.map(s => `${pad} ${s}`)
.join('\r\n')
}${'\r\n'}`;
}
}
return changelog;
}
function replaceHeader(str) {
switch (str) {
case 'feat':
return 'New Features';
case 'fix':
return 'Bug Fixes';
case 'docs':
return 'Documentation Changes';
case 'build':
return 'Build System';
case 'chore':
return 'Chores';
case 'ci':
return 'Continuous Integration';
case 'refactor':
return 'Refactors';
case 'style':
return 'Code Style Changes';
case 'test':
return 'Tests';
case 'perf':
return 'Performance improvements';
case 'revert':
return 'Reverts';
case 'deps':
return 'Dependency updates';
case 'other':
return 'Other Changes';
default:
return str;
}
}
/**
* Return markdown string with changelog
* @param {Map<string, CommitGroup>} groups
*/
function getChangeLog(groups) {
let changelog = '';
for (const [typeId, group] of groups) {
if (group.isEmpty) {
continue;
}
changelog += `### ${replaceHeader(typeId)}${'\r\n'}`;
for (const [scopeId, scope] of group.scopes) {
if (scope.commits.length) {
changelog += `- #### ${replaceHeader(scopeId)}${'\r\n'}`;
changelog += getCommitsList(scope.commits, ' ');
}
}
if (group.commits.length) {
changelog += getCommitsList(group.commits);
}
changelog += ('\r\n' + '\r\n');
}
return changelog.trim();
}
function escapeData(s) {
return String(s)
.replace(/%/g, '%25')
.replace(/\r/g, '%0D')
.replace(/\n/g, '%0A');
}
try {
const commits = getCommits();
const grouped = getGroupedCommits(commits);
const changelog = getChangeLog(grouped);
process.stdout.write('::set-output name=release-note::' + escapeData(changelog) + '\r\n');
// require('fs').writeFileSync('../CHANGELOG.md', changelog, {encoding: 'utf-8'})
} catch (e) {
console.error(e);
process.exit(1);
}

58
webapp/.github/renovate.json vendored Normal file
View File

@ -0,0 +1,58 @@
{
"extends": [
"config:base",
":semanticCommits",
":automergeTypes",
":disableDependencyDashboard"
],
"labels": [
"dependencies"
],
"baseBranches": [
"main"
],
"bumpVersion": "patch",
"patch": {
"automerge": true
},
"minor": {
"automerge": true
},
"packageRules": [
{
"packageNames": [
"node",
"npm"
],
"enabled": false
},
{
"depTypeList": [
"devDependencies"
],
"semanticCommitType": "build"
},
{
"matchSourceUrlPrefixes": [
"https://github.com/vitejs/vite/"
],
"groupName": "Vite monorepo packages",
"automerge": false
},
{
"matchPackagePatterns": [
"^@typescript-eslint",
"^eslint"
],
"automerge": true,
"groupName": "eslint"
},
{
"matchPackageNames": [
"electron"
],
"separateMajorMinor": false
}
],
"rangeStrategy": "pin"
}

40
webapp/.github/workflows/lint.yml vendored Normal file
View File

@ -0,0 +1,40 @@
name: Linters
on:
push:
branches:
- main
paths:
- '**.js'
- '**.ts'
- '**.vue'
- 'package-lock.json'
- '.github/workflows/lint.yml'
pull_request:
paths:
- '**.js'
- '**.ts'
- '**.vue'
- 'package-lock.json'
- '.github/workflows/lint.yml'
defaults:
run:
shell: 'bash'
jobs:
eslint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 16 # Need for npm >=7.7
cache: 'npm'
# TODO: Install not all dependencies, but only those required for this workflow
- name: Install dependencies
run: npm ci
- run: npm run lint

145
webapp/.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,145 @@
name: Release
on:
push:
branches:
- main
paths-ignore:
- '**.md'
- '**.spec.js'
- '.idea'
- '.gitignore'
- '.github/**'
- '!.github/workflows/release.yml'
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: true
defaults:
run:
shell: 'bash'
jobs:
draft:
runs-on: ubuntu-latest
outputs:
release-note: ${{ steps.release-note.outputs.release-note }}
version: ${{ steps.version.outputs.build-version }}
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-node@v2
with:
node-version: 14
- name: Get last git tag
id: tag
run: echo "::set-output name=last-tag::$(git describe --tags --abbrev=0 || git rev-list --max-parents=0 ${{github.ref}})"
- name: Generate release notes
uses: ./.github/actions/release-notes
id: release-note
with:
from: ${{ steps.tag.outputs.last-tag }}
to: ${{ github.ref }}
include-commit-body: true
include-abbreviated-commit: true
- name: Get version from current date
id: version
run: echo "::set-output name=build-version::$(node -e "try{console.log(require('./electron-builder.config.js').extraMetadata.version)}catch(e){console.error(e);process.exit(1)}")"
- name: Waiting on All checks
uses: lewagon/wait-on-check-action@v0.2
with:
ref: ${{ github.ref }}
repo-token: ${{ secrets.GITHUB_TOKEN }}
running-workflow-name: 'draft'
- name: Delete outdated drafts
uses: hugo19941994/delete-draft-releases@v1.0.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create Release Draft
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.github_token }}
with:
prerelease: true
draft: true
tag_name: v${{ steps.version.outputs.build-version }}
name: v${{ steps.version.outputs.build-version }}
body: ${{ steps.release-note.outputs.release-note }}
upload_artifacts:
needs: [ draft ]
strategy:
matrix:
os: [ windows-latest ]
# To compile the application for different platforms, use:
# os: [ macos-latest, ubuntu-latest, windows-latest ]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 16 # Need for npm >=7.7
cache: 'npm'
- name: Install dependencies
run: npm ci
# The easiest way to transfer release notes to a compiled application is create `release-notes.md` in the build resources.
# See https://github.com/electron-userland/electron-builder/issues/1511#issuecomment-310160119
- name: Prepare release notes
env:
RELEASE_NOTE: ${{ needs.draft.outputs.release-note }}
run: echo "$RELEASE_NOTE" >> ./buildResources/release-notes.md
# Compile app and upload artifacts
- name: Compile & release Electron app
uses: samuelmeuli/action-electron-builder@v1
env:
VITE_APP_VERSION: ${{ needs.draft.outputs.version }}
with:
build_script_name: build
args: --config electron-builder.config.js
# GitHub token, automatically provided to the action
# (No need to define this secret in the repo settings)
github_token: ${{ secrets.github_token }}
# If the commit is tagged with a version (e.g. "v1.0.0"),
# release the app after building
release: true
# Sometimes the build may fail due to a connection problem with Apple, GitHub, etc. servers.
# This option will restart the build as many attempts as possible
max_attempts: 3
# Code Signing params
# Base64-encoded code signing certificate for Windows
# windows_certs: ''
# Password for decrypting `windows_certs`
# windows_certs_password: ''
# Base64-encoded code signing certificate for macOS
# mac_certs: ''
# Password for decrypting `mac_certs`
# mac_certs_password: ''

41
webapp/.github/workflows/tests.yml vendored Normal file
View File

@ -0,0 +1,41 @@
name: Tests
on:
push:
branches:
- main
paths:
- 'packages/**'
- 'tests/**'
- 'package-lock.json'
- '.github/workflows/tests.yml'
pull_request:
paths:
- 'packages/**'
- 'tests/**'
- 'package-lock.json'
- '.github/workflows/tests.yml'
defaults:
run:
shell: 'bash'
jobs:
e2e:
strategy:
matrix:
os: [ windows-latest ]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 16 # Need for npm >=7.7
cache: 'npm'
# TODO: Install not all dependencies, but only those required for this workflow
- name: Install dependencies
run: npm ci
- run: npm test

View File

@ -0,0 +1,42 @@
name: Typechecking
on:
push:
branches:
- main
paths:
- '**.ts'
- '**.vue'
- '**/tsconfig.json'
- 'package-lock.json'
- '.github/workflows/typechecking.yml'
pull_request:
paths:
- '**.ts'
- '**.vue'
- '**/tsconfig.json'
- 'package-lock.json'
- '.github/workflows/typechecking.yml'
defaults:
run:
shell: 'bash'
jobs:
typescript:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 16 # Need for npm >=7.7
cache: 'npm'
# TODO: Install not all dependencies, but only those required for this workflow
- name: Install dependencies
run: npm ci
# Type checking is divided into three separate commands for more convenient logs
- run: npm run typecheck-main
- run: npm run typecheck-preload
- run: npm run typecheck-renderer

View File

@ -0,0 +1,44 @@
name: Update Electon vendors versions
on:
push:
branches:
- main
paths:
- 'package-lock.json'
concurrency:
group: update-electron-vendors-${{ github.ref }}
cancel-in-progress: true
defaults:
run:
shell: 'bash'
jobs:
node-chrome:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 16 # Need for npm >=7.7
cache: 'npm'
# TODO: Install not all dependencies, but only those required for this workflow
- name: Install dependencies
run: npm ci
- run: node ./scripts/update-electron-vendors.js
- name: Create Pull Request
uses: peter-evans/create-pull-request@v3
with:
delete-branch: true
commit-message: Update electron vendors
branch: autoupdates/electron-vendors
title: Update electron vendors
body: Updated versions of electron vendors in `electron-vendors.config.json` and `.browserslistrc` files

56
webapp/.gitignore vendored Normal file
View File

@ -0,0 +1,56 @@
node_modules
.DS_Store
dist
*.local
thumbs.db
.eslintcache
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
.idea/artifacts
.idea/compiler.xml
.idea/jarRepositories.xml
.idea/modules.xml
.idea/*.iml
.idea/modules
*.iml
*.ipr
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# Editor-based Rest Client
.idea/httpRequests
/.idea/csv-plugin.xml

21
webapp/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Alex Kozack
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.

200
webapp/README.md Normal file
View File

@ -0,0 +1,200 @@
# Vite Electron Builder Boilerplate v2
[![GitHub issues by-label](https://img.shields.io/github/issues/cawa-93/vite-electron-builder/help%20wanted?label=issues%20need%20help&logo=github)](https://github.com/cawa-93/vite-electron-builder/issues?q=label%3A%22help+wanted%22+is%3Aopen+is%3Aissue)
[![Minimal node version](https://img.shields.io/static/v1?label=node&message=%3E=14.16&logo=node.js&color)](https://nodejs.org/about/releases/)
[![Minimal npm version](https://img.shields.io/static/v1?label=npm&message=%3E=7.7&logo=npm&color)](https://github.com/npm/cli/releases)
> Vite+Electron = 🔥
This is a secure template for electron applications. Written following the latest safety requirements, recommendations and best practices.
Under the hood is used [Vite] — super fast, nextgen bundler, and [electron-builder] for compilation.
___
### Support
- This template maintained by [Alex Kozack][cawa-93-github]. You can [💖 sponsor him][cawa-93-sponsor] for continued development of this template.
- Found a problem? Pull requests are welcome.
- If you have ideas, questions or suggestions - **Welcome to [discussions](https://github.com/cawa-93/vite-electron-builder/discussions)**. 😊
___
## Get started
Follow these steps to get started with this template:
1. Click the **[Use this template](https://github.com/cawa-93/vite-electron-builder/generate)** button (you must be logged in) or just clone this repo.
2. If you want use another package manager don't forget edit [`.github/workflows`](/.github/workflows) -- it uses `npm` by default.
That's all you need. 😉
**Note**: This template uses npm v7 feature — [**Installing Peer Dependencies Automatically**](https://github.com/npm/rfcs/blob/latest/implemented/0025-install-peer-deps.md). If you are using a different package manager, you may need to install some peerDependencies manually.
## Features
### Electron [![Electron version](https://img.shields.io/github/package-json/dependency-version/cawa-93/vite-electron-builder/dev/electron?label=%20)][electron]
- Template use the latest electron version with all the latest security patches.
- The architecture of the application is built according to the security [guids](https://www.electronjs.org/docs/tutorial/security) and best practices.
- The latest version of the [electron-builder] is used to compile the application.
### Vite [![Vite version](https://img.shields.io/github/package-json/dependency-version/cawa-93/vite-electron-builder/dev/vite?label=%20)][vite]
- [Vite] is used to bundle all source codes. This is an extremely fast packer that has a bunch of great features. You can learn more about how it is arranged in [this](https://youtu.be/xXrhg26VCSc) video.
- Vite [supports](https://vitejs.dev/guide/env-and-mode.html) reading `.env` files. You can also specify types of your environment variables in [`types/vite-env.d.ts`](types/vite-env.d.ts).
Vite provides you with many useful features, such as: `TypeScript`, `TSX/JSX`, `CSS/JSON Importing`, `CSS Modules`, `Web Assembly` and much more.
[See all Vite features](https://vitejs.dev/guide/features.html).
### TypeScript [![TypeScript version](https://img.shields.io/github/package-json/dependency-version/cawa-93/vite-electron-builder/dev/typescript?label=%20)][typescript] (optional)
- The Latest TypeScript is used for all source code.
- **Vite** supports TypeScript out of the box. However, it does not support type checking.
- Code formatting rules follow the latest TypeScript recommendations and best practices thanks to [@typescript-eslint/eslint-plugin](https://www.npmjs.com/package/@typescript-eslint/eslint-plugin).
**[See this discussion](https://github.com/cawa-93/vite-electron-builder/discussions/339)** if you want completly remove TypeScript.
### Vue [![Vue version](https://img.shields.io/github/package-json/dependency-version/cawa-93/vite-electron-builder/vue?label=%20)][vue] (optional)
- By default, web pages are built using [Vue]. However, you can easily change it. Or do not use additional frameworks at all. (See [React fork](https://github.com/soulsam480/vite-electron-react-starter))
- Also, by default, the [vue-router] version [![Vue-router version](https://img.shields.io/github/package-json/dependency-version/cawa-93/vite-electron-builder/vue-router?label=%20)][vue-router] is used.
- Code formatting rules follow the latest Vue recommendations and best practices thanks to [eslint-plugin-vue].
- Installed [Vue.js devtools beta](https://chrome.google.com/webstore/detail/vuejs-devtools/ljjemllljcmogpfapbkkighbhhppjdbg) with Vue 3 support.
See [examples of web pages for different frameworks](https://github.com/vitejs/vite/tree/main/packages/create-vite).
### Continuous Integration
- The configured workflow for check the types for each push and PR.
- The configured workflow for check the code style for each push and PR.
- **Automatic tests** used [playwright]. Simple, automated test check:
- Does the main window created and visible?
- Is the main window not empty?
- Is dev tools closed?
- Is preload script loaded?
### Continuous delivery
- Each time you push changes to the `main` branch, [`release`](.github/workflows/release.yml) workflow starts, which creates release draft.
- The version is automatically set based on the current date in the format `yy.mm.dd-minutes`.
- Notes are automatically generated and added to the release draft.
- Code signing supported. See [`compile` job in `release` workflow](.github/workflows/release.yml).
- **Auto-update is supported**. After the release will be published, all client applications will download the new version and install updates silently.
## Status
This template was created to make my work easier. It may not be universal, but I try to keep it that way.
I am actively involved in its development. But I do not guarantee that this template will be maintained in the future.
**At the moment, there are the following problems:**
- ⚠ Playwright has **experimental** support for Electron.
- ⚠ Release notes are created automatically based on commit history. [`.github/actions/release-notes`](.github/actions/release-notes) is used for generation. It may not provide some scenarios. If you encounter a problem - write about it.
- ⏳ I want to migrate all code base to ESM. But because Nodejs ecosystem is unprepared I have not known whether this will give more benefits or more inconvenience.
Some improvement or problems can be listed in [issues](https://github.com/cawa-93/vite-electron-builder/issues).
**Pull requests are welcome**.
## How it works
The template required a minimum [dependencies](package.json). Only **Vite** is used for building, nothing more.
### Project Structure
The structure of this template is very similar to the structure of a monorepo.
The entire source code of the program is divided into three modules (packages) that are bundled each independently:
- [`packages/main`](packages/main)
Electron [**main script**](https://www.electronjs.org/docs/tutorial/quick-start#create-the-main-script-file).
- [`packages/preload`](packages/preload)
Used in `BrowserWindow.webPreferences.preload`. See [Checklist: Security Recommendations](https://www.electronjs.org/docs/tutorial/security#2-do-not-enable-nodejs-integration-for-remote-content).
- [`packages/renderer`](packages/renderer)
Electron [**web page**](https://www.electronjs.org/docs/tutorial/quick-start#create-a-web-page).
### Build web resources
Packages `main` and `preload` are built in [library mode](https://vitejs.dev/guide/build.html#library-mode) as it is a simple javascript.
`renderer` package build as regular web app.
The build of web resources is performed in the [`scripts/build.js`](scripts/build.js). Its analogue is a sequential call to `vite build` for each package.
### Compile App
Next step is run packaging and compilation a ready for distribution Electron app for macOS, Windows and Linux with "auto update" support out of the box.
To do this, using the [electron-builder]:
- In npm script `compile`: This script is configured to compile the application as quickly as possible. It is not ready for distribution, is compiled only for the current platform and is used for debugging.
- In GitHub Action: The application is compiled for any platform and ready-to-distribute files are automatically added to the draft GitHub release.
### Using Node.js API in renderer
According to [Electron's security guidelines](https://www.electronjs.org/docs/tutorial/security#2-do-not-enable-nodejs-integration-for-remote-content), Node.js integration is disabled for remote content. This means that **you cannot call any Node.js api in the `packages/renderer` directly**. To do this, you **must** describe the interface in the `packages/preload` where Node.js api is allowed:
```ts
// packages/preload/src/index.ts
import {readFile} from 'fs/promises'
const api = {
readConfig: () => readFile('/path/to/config.json', {encoding: 'utf-8'}),
}
contextBridge.exposeInMainWorld('electron', api)
```
```ts
// packages/renderer/src/App.vue
import {useElectron} from '/@/use/electron'
const {readConfig} = useElectron()
```
[Read more about Security Considerations](https://www.electronjs.org/docs/tutorial/context-isolation#security-considerations).
### Modes and Environment Variables
All environment variables set as part of the `import.meta`, so you can access them as follows: `import.meta.env`.
You can also specify types of your environment variables in [`types/vite-env.d.ts`](types/vite-env.d.ts).
The mode option is used to specify the value of `import.meta.env.MODE` and the corresponding environment variables files that needs to be loaded.
By default, there are two modes:
- `production` is used by default
- `development` is used by `npm run watch` script
When running building, environment variables are loaded from the following files in your project root:
```
.env # loaded in all cases
.env.local # loaded in all cases, ignored by git
.env.[mode] # only loaded in specified env mode
.env.[mode].local # only loaded in specified env mode, ignored by git
```
**Note:** only variables prefixed with `VITE_` are exposed to your code (e.g. `VITE_SOME_KEY=123`) and `SOME_KEY=123` will not. You can access `VITE_SOME_KEY` using `import.meta.env.VITE_SOME_KEY`. This is because the `.env` files may be used by some users for server-side or build scripts and may contain sensitive information that should not be exposed in code shipped to browsers.
## Contribution
See [Contributing Guide](contributing.md).
[vite]: https://github.com/vitejs/vite/
[electron]: https://github.com/electron/electron
[electron-builder]: https://github.com/electron-userland/electron-builder
[vue]: https://github.com/vuejs/vue-next
[vue-router]: https://github.com/vuejs/vue-router-next/
[typescript]: https://github.com/microsoft/TypeScript/
[playwright]: https://playwright.dev
[vue-tsc]: https://github.com/johnsoncodehk/vue-tsc
[eslint-plugin-vue]: https://github.com/vuejs/eslint-plugin-vue
[cawa-93-github]: https://github.com/cawa-93/
[cawa-93-sponsor]: https://www.patreon.com/Kozack/

View File

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

27
webapp/contributing.md Normal file
View File

@ -0,0 +1,27 @@
# Contributing
First and foremost, thank you! We appreciate that you want to contribute to vite-electron-builder, your time is valuable, and your contributions mean a lot to us.
## Issues
Do not create issues about bumping dependencies unless a bug has been identified, and you can demonstrate that it effects this library.
**Help us to help you**
Remember that were here to help, but not to make guesses about what you need help with:
- Whatever bug or issue you're experiencing, assume that it will not be as obvious to the maintainers as it is to you.
- Spell it out completely. Keep in mind that maintainers need to think about _all potential use cases_ of a library. It's important that you explain how you're using a library so that maintainers can make that connection and solve the issue.
_It can't be understated how frustrating and draining it can be to maintainers to have to ask clarifying questions on the most basic things, before it's even possible to start debugging. Please try to make the best use of everyone's time involved, including yourself, by providing this information up front._
## Repo Setup
The package manager used to install and link dependencies must be npm v7 or later.
1. Clone repo
1. `npm run watch` start electron app in watch mode.
1. `npm run compile` build app but for local debugging only.
1. `npm run lint` lint your code.
1. `npm run typecheck` Run typescript check.
1. `npm run test` Run app test.

View File

@ -0,0 +1,23 @@
if (process.env.VITE_APP_VERSION === undefined) {
const now = new Date;
process.env.VITE_APP_VERSION = `${now.getUTCFullYear() - 2000}.${now.getUTCMonth() + 1}.${now.getUTCDate()}-${now.getUTCHours() * 60 + now.getUTCMinutes()}`;
}
/**
* @type {import('electron-builder').Configuration}
* @see https://www.electron.build/configuration/configuration
*/
const config = {
directories: {
output: 'dist',
buildResources: 'buildResources',
},
files: [
'packages/**/dist/**',
],
extraMetadata: {
version: process.env.VITE_APP_VERSION,
},
};
module.exports = config;

View File

@ -0,0 +1,4 @@
{
"chrome": "94",
"node": "16"
}

12797
webapp/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

54
webapp/package.json Normal file
View File

@ -0,0 +1,54 @@
{
"name": "vite-electron-builder",
"private": true,
"engines": {
"node": ">=v14.14",
"npm": ">=7.7"
},
"main": "packages/main/dist/index.cjs",
"scripts": {
"build": "node scripts/build.js",
"precompile": "cross-env MODE=production npm run build",
"compile": "electron-builder build --config electron-builder.config.js --dir --config.asar=false",
"pretest": "npm run build",
"test": "node tests/app.spec.js",
"watch": "node scripts/watch.js",
"lint": "eslint . --ext js,ts,vue",
"typecheck-main": "tsc --noEmit -p packages/main/tsconfig.json",
"typecheck-preload": "tsc --noEmit -p packages/preload/tsconfig.json",
"typecheck-renderer": "vue-tsc --noEmit -p packages/renderer/tsconfig.json",
"typecheck": "npm run typecheck-main && npm run typecheck-preload && npm run typecheck-renderer"
},
"browserslist": [
"Chrome 94"
],
"simple-git-hooks": {
"pre-commit": "npx lint-staged",
"pre-push": "npm run typecheck"
},
"lint-staged": {
"*.{js,ts,vue}": "eslint --cache --fix"
},
"devDependencies": {
"@types/electron-devtools-installer": "2.2.0",
"@typescript-eslint/eslint-plugin": "4.32.0",
"@vitejs/plugin-vue": "1.9.2",
"cross-env": "7.0.3",
"electron": "15.1.0",
"electron-builder": "22.11.7",
"electron-devtools-installer": "3.2.0",
"eslint": "7.32.0",
"eslint-plugin-vue": "7.18.0",
"lint-staged": "11.1.2",
"playwright": "1.15.1",
"simple-git-hooks": "2.6.1",
"typescript": "4.4.2",
"vite": "2.6.2",
"vue-tsc": "0.3.0"
},
"dependencies": {
"electron-updater": "4.6.0",
"vue": "3.2.19",
"vue-router": "4.0.11"
}
}

View File

@ -0,0 +1,94 @@
import {app, BrowserWindow} from 'electron';
import {join} from 'path';
import {URL} from 'url';
const isSingleInstance = app.requestSingleInstanceLock();
if (!isSingleInstance) {
app.quit();
process.exit(0);
}
app.disableHardwareAcceleration();
// Install "Vue.js devtools"
if (import.meta.env.MODE === 'development') {
app.whenReady()
.then(() => import('electron-devtools-installer'))
.then(({default: installExtension, VUEJS3_DEVTOOLS}) => installExtension(VUEJS3_DEVTOOLS, {
loadExtensionOptions: {
allowFileAccess: true,
},
}))
.catch(e => console.error('Failed install extension:', e));
}
let mainWindow: BrowserWindow | null = null;
const createWindow = async () => {
mainWindow = new BrowserWindow({
show: false, // Use 'ready-to-show' event to show window
webPreferences: {
nativeWindowOpen: true,
preload: join(__dirname, '../../preload/dist/index.cjs'),
},
});
/**
* If you install `show: true` then it can cause issues when trying to close the window.
* Use `show: false` and listener events `ready-to-show` to fix these issues.
*
* @see https://github.com/electron/electron/issues/25012
*/
mainWindow.on('ready-to-show', () => {
mainWindow?.show();
if (import.meta.env.MODE === 'development') {
mainWindow?.webContents.openDevTools();
}
});
/**
* URL for main window.
* Vite dev server for development.
* `file://../renderer/index.html` for production and test
*/
const pageUrl = import.meta.env.MODE === 'development' && import.meta.env.VITE_DEV_SERVER_URL !== undefined
? import.meta.env.VITE_DEV_SERVER_URL
: new URL('../renderer/dist/index.html', 'file://' + __dirname).toString();
await mainWindow.loadURL(pageUrl);
};
app.on('second-instance', () => {
// Someone tried to run a second instance, we should focus our window.
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.focus();
}
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.whenReady()
.then(createWindow)
.catch((e) => console.error('Failed create window:', e));
// Auto-updates
if (import.meta.env.PROD) {
app.whenReady()
.then(() => import('electron-updater'))
.then(({autoUpdater}) => autoUpdater.checkForUpdatesAndNotify())
.catch((e) => console.error('Failed check updates:', e));
}

View File

@ -0,0 +1,17 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"/@/*": [
"./src/*"
]
},
},
"files": [
"src/index.ts"
],
"include": [
"../../types/**/*.d.ts"
]
}

View File

@ -0,0 +1,53 @@
import {node} from '../../electron-vendors.config.json';
import {join} from 'path';
import {builtinModules} from 'module';
const PACKAGE_ROOT = __dirname;
/**
* @type {import('vite').UserConfig}
* @see https://vitejs.dev/config/
*/
const config = {
mode: process.env.MODE,
root: PACKAGE_ROOT,
envDir: process.cwd(),
resolve: {
alias: {
'/@/': join(PACKAGE_ROOT, 'src') + '/',
},
},
build: {
sourcemap: 'inline',
target: `node${node}`,
outDir: 'dist',
assetsDir: '.',
minify: process.env.MODE === 'development' ? false : 'terser',
terserOptions: {
ecma: 2020,
compress: {
passes: 2,
},
safari10: false,
},
lib: {
entry: 'src/index.ts',
formats: ['cjs'],
},
rollupOptions: {
external: [
'electron',
'electron-devtools-installer',
...builtinModules,
],
output: {
entryFileNames: '[name].cjs',
},
},
emptyOutDir: true,
brotliSize: false,
},
};
export default config;

View File

@ -0,0 +1,17 @@
import {contextBridge} from 'electron';
const apiKey = 'electron';
/**
* @see https://github.com/electron/electron/issues/21437#issuecomment-573522360
*/
const api: ElectronApi = {
versions: process.versions,
};
/**
* The "Main World" is the JavaScript context that your main renderer code runs in.
* By default, the page you load in your renderer executes code in this world.
*
* @see https://www.electronjs.org/docs/api/context-bridge
*/
contextBridge.exposeInMainWorld(apiKey, api);

View File

@ -0,0 +1,18 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"/@/*": [
"./src/*"
]
}
},
"files": [
"types/electron-api.d.ts",
"src/index.ts"
],
"include": [
"../../types/**/*.d.ts"
]
}

View File

@ -0,0 +1,9 @@
interface ElectronApi {
readonly versions: Readonly<NodeJS.ProcessVersions>
}
declare interface Window {
electron: Readonly<ElectronApi>
electronRequire?: NodeRequire
}

View File

@ -0,0 +1,51 @@
import {chrome} from '../../electron-vendors.config.json';
import {join} from 'path';
import {builtinModules} from 'module';
const PACKAGE_ROOT = __dirname;
/**
* @type {import('vite').UserConfig}
* @see https://vitejs.dev/config/
*/
const config = {
mode: process.env.MODE,
root: PACKAGE_ROOT,
envDir: process.cwd(),
resolve: {
alias: {
'/@/': join(PACKAGE_ROOT, 'src') + '/',
},
},
build: {
sourcemap: 'inline',
target: `chrome${chrome}`,
outDir: 'dist',
assetsDir: '.',
minify: process.env.MODE === 'development' ? false : 'terser',
terserOptions: {
ecma: 2020,
compress: {
passes: 2,
},
safari10: false,
},
lib: {
entry: 'src/index.ts',
formats: ['cjs'],
},
rollupOptions: {
external: [
'electron',
...builtinModules,
],
output: {
entryFileNames: '[name].cjs',
},
},
emptyOutDir: true,
brotliSize: false,
},
};
export default config;

View File

@ -0,0 +1,15 @@
{
"env": {
"browser": true,
"node": false
},
"extends": [
/** @see https://eslint.vuejs.org/rules/ */
"plugin:vue/vue3-recommended"
],
"parserOptions": {
"parser": "@typescript-eslint/parser",
"ecmaVersion": 12,
"sourceType": "module"
}
}

View File

@ -0,0 +1,15 @@
<svg width="410" height="404" viewBox="0 0 410 404" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M399.641 59.5246L215.643 388.545C211.844 395.338 202.084 395.378 198.228 388.618L10.5817 59.5563C6.38087 52.1896 12.6802 43.2665 21.0281 44.7586L205.223 77.6824C206.398 77.8924 207.601 77.8904 208.776 77.6763L389.119 44.8058C397.439 43.2894 403.768 52.1434 399.641 59.5246Z" fill="url(#paint0_linear)"/>
<path d="M292.965 1.5744L156.801 28.2552C154.563 28.6937 152.906 30.5903 152.771 32.8664L144.395 174.33C144.198 177.662 147.258 180.248 150.51 179.498L188.42 170.749C191.967 169.931 195.172 173.055 194.443 176.622L183.18 231.775C182.422 235.487 185.907 238.661 189.532 237.56L212.947 230.446C216.577 229.344 220.065 232.527 219.297 236.242L201.398 322.875C200.278 328.294 207.486 331.249 210.492 326.603L212.5 323.5L323.454 102.072C325.312 98.3645 322.108 94.137 318.036 94.9228L279.014 102.454C275.347 103.161 272.227 99.746 273.262 96.1583L298.731 7.86689C299.767 4.27314 296.636 0.855181 292.965 1.5744Z" fill="url(#paint1_linear)"/>
<defs>
<linearGradient id="paint0_linear" x1="6.00017" y1="32.9999" x2="235" y2="344" gradientUnits="userSpaceOnUse">
<stop stop-color="#41D1FF"/>
<stop offset="1" stop-color="#BD34FE"/>
</linearGradient>
<linearGradient id="paint1_linear" x1="194.651" y1="8.81818" x2="236.076" y2="292.989" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFEA83"/>
<stop offset="0.0833333" stop-color="#FFDD35"/>
<stop offset="1" stop-color="#FFA800"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="script-src 'self' blob:">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script src="./src/index.ts" type="module"></script>
</body>
</html>

View File

@ -0,0 +1,31 @@
<template>
<img
alt="Vue logo"
src="../assets/logo.svg"
width="300"
>
<app-navigation />
<router-view />
</template>
<script lang="ts">
import {defineComponent} from 'vue';
import AppNavigation from '/@/components/AppNavigation.vue';
export default defineComponent({
name: 'App',
components: {
AppNavigation,
},
});
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>

View File

@ -0,0 +1,36 @@
<template>
<h2 id="versions">
Lib versions
</h2>
<div>
<ul aria-labelledby="versions">
<li
v-for="(version, lib) in versions"
:key="lib"
>
<strong>{{ lib }}</strong>: v{{ version }}
</li>
</ul>
</div>
</template>
<script lang="ts">
import {defineComponent} from 'vue';
import {useElectron} from '/@/use/electron';
export default defineComponent({
name: 'App',
setup() {
const {versions} = useElectron();
// It makes no sense to make "versions" reactive
return {versions};
},
});
</script>
<style scoped>
div {
text-align: left;
display: grid;
justify-content: center;
}
</style>

View File

@ -0,0 +1,27 @@
<template>
<nav>
<router-link to="/">
Home
</router-link>
<span> | </span>
<router-link to="/about">
About
</router-link>
</nav>
</template>
<script lang="ts">
import {defineComponent} from 'vue';
export default defineComponent({
name: 'AppNavigation',
});
</script>
<style scoped>
nav {
display: flex;
gap: 1em;
justify-content: center;
}
</style>

View File

@ -0,0 +1,51 @@
<template>
<p>
For a guide and recipes on how to configure / customize this project,<br>
check out the
<a
href="https://github.com/cawa-93/vite-electron-builder"
rel="noopener"
target="_blank"
>vite-electron-builder documentation</a>.
</p>
<p>
<a
href="https://vitejs.dev/guide/features.html"
target="_blank"
>Vite Documentation</a> |
<a
href="https://v3.vuejs.org/"
target="_blank"
>Vue 3 Documentation</a>
</p>
<hr>
<button @click="count++">
count is: {{ count }}
</button>
<p>
Edit
<code>renderer/components/Home.vue</code> to test hot module replacement.
</p>
</template>
<script lang="ts">
import {defineComponent, ref} from 'vue';
export default defineComponent({
name: 'HelloWorld',
setup() {
const count = ref(0);
return {count};
},
});
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
a {
color: #42b983;
}
</style>

View File

@ -0,0 +1,7 @@
import {createApp} from 'vue';
import App from '/@/App.vue';
import router from '/@/router';
createApp(App)
.use(router)
.mount('#app');

View File

@ -0,0 +1,12 @@
import {createRouter, createWebHashHistory} from 'vue-router';
import Home from '/@/components/Home.vue';
const routes = [
{path: '/', name: 'Home', component: Home},
{path: '/about', name: 'About', component: () => import('/@/components/About.vue')}, // Lazy load route component
];
export default createRouter({
routes,
history: createWebHashHistory(),
});

View File

@ -0,0 +1,3 @@
export function useElectron(): Readonly<ElectronApi> {
return window.electron;
}

View File

@ -0,0 +1,21 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"/@/*": [
"./src/*"
]
},
"lib": ["ESNext", "dom", "dom.iterable"]
},
"include": [
"src/**/*.vue",
"src/**/*.ts",
"src/**/*.tsx",
"types/**/*.d.ts",
"../../types/**/*.d.ts",
"../preload/types/electron-api.d.ts"
]
}

View File

@ -0,0 +1,6 @@
declare module '*.vue' {
import type { DefineComponent } from 'vue';
// eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/no-explicit-any
const component: DefineComponent<{}, {}, any>;
export default component;
}

View File

@ -0,0 +1,51 @@
/* eslint-env node */
import {chrome} from '../../electron-vendors.config.json';
import {join} from 'path';
import {builtinModules} from 'module';
import vue from '@vitejs/plugin-vue';
const PACKAGE_ROOT = __dirname;
/**
* @type {import('vite').UserConfig}
* @see https://vitejs.dev/config/
*/
const config = {
mode: process.env.MODE,
root: PACKAGE_ROOT,
resolve: {
alias: {
'/@/': join(PACKAGE_ROOT, 'src') + '/',
},
},
plugins: [vue()],
base: '',
server: {
fs: {
strict: true,
},
},
build: {
sourcemap: true,
target: `chrome${chrome}`,
outDir: 'dist',
assetsDir: '.',
terserOptions: {
ecma: 2020,
compress: {
passes: 2,
},
safari10: false,
},
rollupOptions: {
external: [
...builtinModules,
],
},
emptyOutDir: true,
brotliSize: false,
},
};
export default config;

43
webapp/scripts/build.js Normal file
View File

@ -0,0 +1,43 @@
#!/usr/bin/node
const {build} = require('vite');
const {dirname} = require('path');
/** @type 'production' | 'development' | 'test' */
const mode = process.env.MODE = process.env.MODE || 'production';
const packagesConfigs = [
'packages/main/vite.config.js',
'packages/preload/vite.config.js',
'packages/renderer/vite.config.js',
];
/**
* Run `vite build` for config file
*/
const buildByConfig = (configFile) => build({configFile, mode});
(async () => {
try {
const totalTimeLabel = 'Total bundling time';
console.time(totalTimeLabel);
for (const packageConfigPath of packagesConfigs) {
const consoleGroupName = `${dirname(packageConfigPath)}/`;
console.group(consoleGroupName);
const timeLabel = 'Bundling time';
console.time(timeLabel);
await buildByConfig(packageConfigPath);
console.timeEnd(timeLabel);
console.groupEnd();
console.log('\n'); // Just for pretty print
}
console.timeEnd(totalTimeLabel);
} catch (e) {
console.error(e);
process.exit(1);
}
})();

View File

@ -0,0 +1,58 @@
const {writeFile, readFile} = require('fs/promises');
const {execSync} = require('child_process');
const electron = require('electron');
const path = require('path');
/**
* Returns versions of electron vendors
* The performance of this feature is very poor and can be improved
* @see https://github.com/electron/electron/issues/28006
*
* @returns {NodeJS.ProcessVersions}
*/
function getVendors() {
const output = execSync(`${electron} -p "JSON.stringify(process.versions)"`, {
env: {'ELECTRON_RUN_AS_NODE': '1'},
encoding: 'utf-8',
});
return JSON.parse(output);
}
function formattedJSON(obj) {
return JSON.stringify(obj, null, 2) + '\n';
}
function updateVendors() {
const electronRelease = getVendors();
const nodeMajorVersion = electronRelease.node.split('.')[0];
const chromeMajorVersion = electronRelease.v8.split('.')[0] + electronRelease.v8.split('.')[1];
const packageJSONPath = path.resolve(process.cwd(), 'package.json');
return Promise.all([
writeFile('./electron-vendors.config.json',
formattedJSON({
chrome: chromeMajorVersion,
node: nodeMajorVersion,
}),
),
readFile(packageJSONPath).then(JSON.parse).then((packageJSON) => {
if (!packageJSON || !Array.isArray(packageJSON.browserslist)) {
throw new Error(`Can't find browserslist in ${packageJSONPath}`);
}
packageJSON.browserslist = [`Chrome ${chromeMajorVersion}`];
return writeFile(packageJSONPath, formattedJSON(packageJSON));
}),
]);
}
updateVendors().catch(err => {
console.error(err);
process.exit(1);
});

113
webapp/scripts/watch.js Normal file
View File

@ -0,0 +1,113 @@
#!/usr/bin/node
const {createServer, build, createLogger} = require('vite');
const electronPath = require('electron');
const {spawn} = require('child_process');
/** @type 'production' | 'development' | 'test' */
const mode = process.env.MODE = process.env.MODE || 'development';
/** @type {import('vite').LogLevel} */
const LOG_LEVEL = 'warn';
/** @type {import('vite').InlineConfig} */
const sharedConfig = {
mode,
build: {
watch: {},
},
logLevel: LOG_LEVEL,
};
/**
* @param configFile
* @param writeBundle
* @param name
* @returns {Promise<import('vite').RollupOutput | Array<import('vite').RollupOutput> | import('vite').RollupWatcher>}
*/
const getWatcher = ({name, configFile, writeBundle}) => {
return build({
...sharedConfig,
configFile,
plugins: [{name, writeBundle}],
});
};
/**
* Start or restart App when source files are changed
* @param {import('vite').ViteDevServer} viteDevServer
* @returns {Promise<import('vite').RollupOutput | Array<import('vite').RollupOutput> | import('vite').RollupWatcher>}
*/
const setupMainPackageWatcher = (viteDevServer) => {
// Write a value to an environment variable to pass it to the main process.
{
const protocol = `http${viteDevServer.config.server.https ? 's' : ''}:`;
const host = viteDevServer.config.server.host || 'localhost';
const port = viteDevServer.config.server.port; // Vite searches for and occupies the first free port: 3000, 3001, 3002 and so on
const path = '/';
process.env.VITE_DEV_SERVER_URL = `${protocol}//${host}:${port}${path}`;
}
const logger = createLogger(LOG_LEVEL, {
prefix: '[main]',
});
/** @type {ChildProcessWithoutNullStreams | null} */
let spawnProcess = null;
return getWatcher({
name: 'reload-app-on-main-package-change',
configFile: 'packages/main/vite.config.js',
writeBundle() {
if (spawnProcess !== null) {
spawnProcess.kill('SIGINT');
spawnProcess = null;
}
spawnProcess = spawn(String(electronPath), ['.']);
spawnProcess.stdout.on('data', d => d.toString().trim() && logger.warn(d.toString(), {timestamp: true}));
spawnProcess.stderr.on('data', d => d.toString().trim() && logger.error(d.toString(), {timestamp: true}));
},
});
};
/**
* Start or restart App when source files are changed
* @param {import('vite').ViteDevServer} viteDevServer
* @returns {Promise<import('vite').RollupOutput | Array<import('vite').RollupOutput> | import('vite').RollupWatcher>}
*/
const setupPreloadPackageWatcher = (viteDevServer) => {
return getWatcher({
name: 'reload-page-on-preload-package-change',
configFile: 'packages/preload/vite.config.js',
writeBundle() {
viteDevServer.ws.send({
type: 'full-reload',
});
},
});
};
(async () => {
try {
const viteDevServer = await createServer({
...sharedConfig,
configFile: 'packages/renderer/vite.config.js',
});
await viteDevServer.listen();
await setupPreloadPackageWatcher(viteDevServer);
await setupMainPackageWatcher(viteDevServer);
} catch (e) {
console.error(e);
process.exit(1);
}
})();

66
webapp/tests/app.spec.js Normal file
View File

@ -0,0 +1,66 @@
const {_electron: electron} = require('playwright');
const {strict: assert} = require('assert');
// Playwright has EXPERIMENTAL electron support.
(async () => {
const electronApp = await electron.launch({args: ['.']});
/**
* App main window state
* @type {{isVisible: boolean; isDevToolsOpened: boolean; isCrashed: boolean}}
*/
const windowState = await electronApp.evaluate(({BrowserWindow}) => {
const mainWindow = BrowserWindow.getAllWindows()[0];
const getState = () => ({
isVisible: mainWindow.isVisible(),
isDevToolsOpened: mainWindow.webContents.isDevToolsOpened(),
isCrashed: mainWindow.webContents.isCrashed(),
});
return new Promise((resolve) => {
if (mainWindow.isVisible()) {
resolve(getState());
} else
mainWindow.once('ready-to-show', () => setTimeout(() => resolve(getState()), 0));
});
});
// Check main window state
assert.ok(windowState.isVisible, 'Main window not visible');
assert.ok(!windowState.isDevToolsOpened, 'DevTools opened');
assert.ok(!windowState.isCrashed, 'Window crashed');
/**
* Rendered Main window web-page
* @type {Page}
*/
const page = await electronApp.firstWindow();
// Check web-page content
const element = await page.$('#app', {strict: true});
assert.notStrictEqual(element, null, 'Can\'t find root element');
assert.notStrictEqual((await element.innerHTML()).trim(), '', 'Window content is empty');
// Checking the framework.
// It is assumed that on the main screen there is a `<button>` that changes its contents after clicking.
const button = await page.$('button');
const originalBtnText = await button.textContent();
await button.click();
const newBtnText = await button.textContent();
assert.ok(originalBtnText !== newBtnText, 'The button did not change the contents after clicking');
// Check Preload script
const renderedExposedApi = await page.evaluate(() => globalThis.electron);
const realVersions = await electronApp.evaluate(() => process.versions);
assert.notStrictEqual(renderedExposedApi, undefined, 'In renderer `globalThis.electron` is undefined');
assert.strictEqual(renderedExposedApi?.versions?.electron, realVersions.electron);
// Close app
await electronApp.close();
})();

18
webapp/tsconfig.json Normal file
View File

@ -0,0 +1,18 @@
{
"compilerOptions": {
"module": "esnext",
"target": "esnext",
"sourceMap": true,
"moduleResolution": "Node",
"skipLibCheck": true,
"strict": true,
"isolatedModules": true,
"types": [
"node"
],
"typeRoots": [
"node_modules/@types",
],
"lib": ["ESNext"]
},
}

0
webapp/types/.gitkeep Normal file
View File

15
webapp/types/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1,15 @@
/// <reference types="vite/client" />
/**
* Describes all existing environment variables and their types.
* Assists in autocomplete and typechecking
*
* @see https://github.com/vitejs/vite/blob/eef51cb37db98a1ad9a541bdd3cd74736ff8488d/packages/vite/types/importMeta.d.ts#L62-L69 Base Interface
*/
interface ImportMetaEnv {
/**
* The value of the variable is set in scripts/watch.js and depend on packages/main/vite.config.js
*/
VITE_DEV_SERVER_URL: undefined | string;
}

25
webapp/vetur.config.js Normal file
View File

@ -0,0 +1,25 @@
/** @type {import('vls').VeturConfig} */
module.exports = {
settings: {
'vetur.useWorkspaceDependencies': true,
'vetur.experimental.templateInterpolationService': true,
},
projects: [
{
root: './packages/renderer',
tsconfig: './tsconfig.json',
snippetFolder: './.vscode/vetur/snippets',
globalComponents: [
'./src/components/**/*.vue',
],
},
{
root: './packages/main',
tsconfig: './tsconfig.json',
},
{
root: './packages/preload',
tsconfig: './tsconfig.json',
},
],
};

3898
webapp/yarn.lock Normal file

File diff suppressed because it is too large Load Diff