1
0
mirror of https://github.com/upscayl/upscayl.git synced 2024-11-27 17:00:52 +01:00

Updated project

This commit is contained in:
Nayam Amarshe 2022-08-05 02:32:12 +05:30
commit 6c18b7eeb3
23 changed files with 6388 additions and 0 deletions

12
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,12 @@
# These are supported funding model platforms
github: [iamWing]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: ['https://paypal.me/iamWing0w0']

31
.gitignore vendored Normal file
View File

@ -0,0 +1,31 @@
.vscode/
coverage/
dist/
node_modules/
out/
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Yarn Integrity file
.yarn-integrity
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Windows thumbnail cache files
Thumbs.db
# OS X files
.DS_Store
._*

165
CHANGELOG_PRE_V4.md Normal file
View File

@ -0,0 +1,165 @@
# Changelog (pre `v4`)
All notable changes up to `v3.0.0` are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [3.0.0] - 2020-12-26
### Added
- NPM packages `react-router` & `react-router-dom` to package dependencies.
- NPM package `eslint-plugin-react-hooks` to `devDependencies` as required by
the updated version of `eslint-config-airbnb`.
- NPM package `copy-webpack-plugin` to `devDependencies` as replacement of
`copy-pkg-json-webpack-plugin`.
- NPM packages `@typescript-eslint/eslint-plugin` & `@typescript-eslint/parser`
to `devDependencies` for using ESLint to lint TypeScript.
### Changed
- __Project is now being developed based on Node.js `v14 (LTS)`.__
- Minor version upgrades on package dependencies:
- `eslint-import-resolver-webpack` - `0.11.1` -> `0.13.0`
- `eslint-plugin-import` - `2.18.2` -> `2.22.1`
- `eslint-plugin-jsx-a11y` - `6.2.3` -> `6.4.1`
- `eslint-plugin-react` - `7.17.0` -> ``7.21.5`
- `lodash` - `4.17.15` -> `4.17.20`
- Major version upgrades on package dependencies:
- `react` & `react-dom` - `16.12.0` -> `17.0.1`
- `cross-env` - `5.2.1` -> `7.0.3`
- `css-loader` - `1.0.1` -> `5.0.1`
- `electron` - `3.1.13` -> `11.1.1`
- `electron-builder` - `20.44.4` -> `22.9.1`
- `eslint` - `5.16.0` -> `7.16.0`
- `eslint-config-airbnb` - `17.1.1` -> `18.2.1`
- `file-loader` - `2.0.0` -> `6.2.0`
- `html-webpack-plugin` - `3.2.0` -> `4.5.0`
- `mocha` - `5.2.0` -> `8.2.1`
- `rimraf` - `2.7.1` -> `3.0.2`
- `source-map-loader` - `0.2.4` -> `2.0.0`
- `spectron` - `5.0.0` -> `13.0.0`
- `style-loader` - `0.23.1` -> `2.0.0`
- `ts-loader` - `5.4.5` -> `8.0.12`
- `ts-node` - `7.0.1` -> `9.1.1`
- `typescript` - `3.7.2` - > `4.1.3`
- `webpack` - `4.41.2` -> `5.11.0`
- `webpack-cli` - `3.3.10` -> `4.3.0`
- Moved `@types` packages from `dependencies` to `devDependencies` as those
have no need to be included in production builds.
- Commands of NPM scripts `dev` & `prod` to make them work with Webpack 5.
- Migrated to ESLint from TSLint.
- Updated prefix of internal paths from `@` to `_` to avoid confusions with
scoped NPM packages.
- Indentation for `.ts` files is now set as `2` spaces instead of `4`.
- `electron-builder` settings:
- `win`:
- Enabled `asar`. _(turn it off if the executable built doesn't work)_
- Disabled one click installer.
- Allowed custom installation directory in the installer built.
- `mac`:
- Changed build target back to `dmg`.
- `buildVersion` is now being used as build number instead of just another
parameter for semantic version number.
- Updated section `Known issues` in `README`.
### Fixed
- ESLint errors/warnings on `main.ts` & `renderer.tsx`.
- `electron-builder` fails to build `dmg` on `macOS` (issue
[electron-builder #3990])by upgrading the package version to `> 21.2.0`.
### Removed
- NPM package `copy-pkg-json-webpack-plugin` as it doesn't work with Webpack 5
and seems not very well maintained.
- NPM packages `acorn` & `ajv` from `devDependencies` as they're not being used
in this boilerplate.
- NPM packages `tslint` & `tslint-microsoft-contrib` as TSLint is now
deprecated.
## [2.0.2] - 2019-12-02
`v2.0.2` is a minor hotfix release fixed the documentation error and build
error on `macOS Catalina(10.15+)`.
### Added
- Extended README section `Getting started` with `npm start` command
description.
- README section `Known issue`.
### Changed
- Minor version upgrades on package dependencies.
- `macOS` build target to `pkg` from default `dmg` due to no 32-bit apps
support from `macOS Catalina` that caused `electron-builder` fails to build
`dmg` image on `macOS` prior to `electron-builder@21.2.0`.
_`pkg` build is unaffected and is used as a workaround for the current version
prior to the major version upgrades on dependencies in next release._
_Related issue: [electron-builder #3990](https://github.com/electron-userland/electron-builder/issues/3990)_
### Fixed
- [Issue #2] - incorrect command `npm run install` to `npm install` in `README`.
## [2.0.1] - 2018-02-05
`v2.0.1` is a minor hotfix release patched the `NODE_ENV` not set on Windows
issue.
### Added
- Package `cross-env` as `devDependencies`.
- README section "Author".
### Fixed
- NPM scripts won't set environment variables on Windows issue.
## 2.0.0 - 2018-02-04
`v2.0.0` is a major release that most part of the boilerplate has been rewritten.
### Added
- ESLint for code checking on Javascript files.
- Airbnb's extensible `.eslintrc` package & its' peer dependencies.
- `.eslintrc` that extends Airbnb's config and has customised rules configured.
- _Rule `no-default-export` is set for JavaScript files to align with
TypeScript._
- ESLint plugin `eslint-import-resolver-webpack` for ESLint to resolve path
aliases set in Webpack config.
- Webpack plugin `copy-pkg-json-webpack-plugin` to generate a `package.json`
file and pack into Webpack's bundled package for production.
- Build commands `build:mac` & `build:win` to package & build the installer of
your Electron app for macOS & Windows using `electron-builder`.
- README section "Building the installer for your Electron app" & sub-section
"Extra options".
### Changed
- Refactored Webpack config file to have `mainConfig` & `rendererConfig`
cleaned up, and set mode by environment variable.
- `.gitignore` to ignore folder `out/` which will be auto-generated during the
build process.
- README section "How does it work?" is now renamed to "Getting started" &
completed the documentation of this section.
- README section "Folder structure" to reflect the changes in `v2.0.0`.
### Fixed
- CSS files fail to inject into views issue by setting Webpack to use
`style-loader` alongside with `css-loader` to bundle the files in Webpack
config file.
- `baseUrl` in `tsconfig.json` points to root directory incorrectly issue.
Corrected to current directory so VSCode can resolves the path aliases
correctly.
### Removed
- Redux & React-Redux related settings, including packages listed on
`devDependencies`, path aliases & folders listed in folder structure.
- Since Electron's built-in IPC & basic React states should be enough to get
the works done, and most Electron apps which have their application logic
runs on Electron's `main` process rather then `renderer` process actually
don't need React-Redux, `redux` & `react-redux` are no longer included in
this boilerplate.
- Redux & React-Redux can still be used on this boilerplate by installing the
package yourself. For details, please refer to the corresponding library's
documents, there's no different than working on any other project which
isn't based on this boilerplate.
- Separated Webpack config files for `development` & `production` mode.
[Unreleased]: https://github.com/Devtography/electron-react-typescript-webpack-boilerplate/compare/v3.0.0...HEAD
[2.0.1]: https://github.com/Devtography/electron-react-typescript-webpack-boilerplate/compare/v2.0.0...v2.0.1
[2.0.2]: https://github.com/Devtography/electron-react-typescript-webpack-boilerplate/compare/v2.0.1...v2.0.2
[3.0.0]: https://github.com/Devtography/electron-react-typescript-webpack-boilerplate/compare/v2.0.2...v3.0.0
[Issue #2]: https://github.com/Devtography/electron-react-typescript-webpack-boilerplate/issues/2
[electron-builder #3990]: https://github.com/electron-userland/electron-builder/issues/3990

91
CHANGELOG_V4+.md Normal file
View File

@ -0,0 +1,91 @@
# Changelog `v4+`
All notable changes to this project on `v4+` will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [v4.0.0] - 2022-07-22
### Added
- Jest as default unit testing framework, with sample test suite for `main`.
- Integrated Electron preload pattern.
- NPM scripts:
- `watch-test` to run Jest in watch mode.
- `next-rc`, `next-patch`, `next-minor` & `next-major` for quick
package version number advance.
### Changed
- Webpack will now take the module path alias from `tsconfig.json` and set it
for you thanks to `tsconfig-paths-webpack-plugin`. Manually set up in Webpack
config is no longer needed.
- `tsconfig` now configured to use `ES2020` features, with module resolution
set to `Node16` to match the NodeJS version used by Electron.
- Migrated to the new `createRoot` API introduced in React `v18`.
- Some APIs changed in Electron `main` entry script:
- `mainWindow` now use `loadFile` API instead of `loadURL`.
- Replaced `app.on('ready')` with `app.whenReady()` to align with syntax from
[Electron official quick start guide](https://www.electronjs.org/docs/latest/tutorial/quick-start).
- `electron-builder` now configured to build `universal` `dmg` for mac, 32 &
64 bit `exe` for Windows.
- Moved `electron-builder`, Webpack & Webpack-related packages to
`optionalDependencies`.
- Revamped `README`.
- __*Starting from this version, the maintenance schedule will be on a monthly
update basis to keep the package dependencies up to date and keep the
development going.*__
### Fixed
- Allow to use `import default` statement on non ES modules (e.g. React,
lodash) by enabling `esModuleInterop` in `tsconfig`.
[#14](https://github.com/Devtography/electron-react-typescript-webpack-boilerplate/issues/14)
### Removed
- Mocha in favour of Jest.
- Spectron as it ahs been deprecated.
*See - [Spectron Deprecation Notice](https://www.electronjs.org/blog/spectron-deprecation-notice)*
### Updates on package dependencies
### Added
- `@types/jest`, `jest`, `ts-jest`, `eslint-plugin-jest` *- Jest support*
- `eslint-config-airbnb-typescript` *- Enhance Airbnb's rules in TypeScript*
- `ts-config-paths-webpack-plugin` *- Load `tsconfig` path alias into Webpack
config*
### Updated
- Major version updates:
- `react` & `react-dom` - `17.0.1` -> `18.2.0`
- `@types/react` - `17.0.0` -> `18.0.15`
- `@types/react-dom` - `17.0.0` -> `18.0.6`
- `@typescript-eslint/eslint-plugin` & `@typescript-eslint/parser` -
`4.11.0` -> `5.30.7`
- `electron` - `11.1.1` -> `19.0.9`
- `eslint-config-airbnb` - `18.2.1` -> `19.0.4`
- `copy-webpack-plugin` - `7.0.0` -> `11.0.0`
- `css-loader` - `5.0.1` -> `6.7.1`
- `electron-builder` - `22.9.1` -> `23.1.0`
- `html-webpack-plugin` - `4.5.0` -> `5.5.0`
- `style-loader` - `2.0.0` -> `3.3.1`
- `ts-loader` - `8.0.12` -> `9.3.1`
- `ts-config-paths` - `3.9.0` -> `4.0.0`
- Minor & patch version updates:
- `eslint-import-resolver-webpack` - `0.13.0` -> `0.13.2`
- `eslint-plugin-import` - `2.22.1` -> `2.26.0`
- `eslint-plugin-jsx-a11y` - `6.4.1` -> `6.6.1`
- `eslint-plugin-react` - `7.21.5` -> `7.30.1`
- `eslint-plugin-react-hoots` - `4.2.0` -> `4.6.0`
- `lodash` - `4.17.20` -> `4.17.21`
- `webpack` - `5.11.0` -> `5.73.0`
- `webpack-cli` - `4.3.0` -> `4.10.0`
### Removed
- `react-router`, `react-router-dom`, `@types/react-router`,
`@types/react-router-dom`
*\- Not being used in any part of the boilerplate*
- `@types/mocha`, `mocha`, `ts-node` *- Replaced by `@types/jest`, `jest` &
`ts-jest`*
- `spectron` *- Deprecated package; No replacement*
[Unreleased]: https://github.com/Devtography/electron-react-typescript-webpack-boilerplate/compare/v4.0.0...HEAD
[v4.0.0]: https://github.com/Devtography/electron-react-typescript-webpack-boilerplate/compare/v3.0.0...v4.0.0

21
LICENSE Normal file
View File

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

275
README.md Normal file
View File

@ -0,0 +1,275 @@
# Electron-React-TypeScript-Webpack-Boilerplate
A boilerplate that let you instantly start working on your next [Electron] app
in [TypeScript] with no time wasted messing with the config files.
- Ready to use [Electron] project template with [React], [Webpack] and
[TypeScript] seamlessly integrated.
- [ESLint] set up with TypeScript, Airbnb's rules, and [Jest] support.
- [Jest] integrated and configured.
- [`electron-builder`] for app packaging, with basic build config for Windows
macOS included.
- Clean, easy to read and alter config files. No config file is hidden behind
yet another script!
- Monthly maintenance to keep things up to date!
*This boilerplate is tested on the latest macOS and Windows. If anything
doesn't work, please [file an issue].*
### Maintenance schedule
Starting from `v4.0.0`, the project maintenance will become much more regular.
A new release will be published on a monthly basis to keep the package
dependencies, package configurations and APIs / syntax up to date.
Maintenance work will begin on 1st of each month, and expect the new version to
be released within the first week of the month. New features from different
tools integrated in this boilerplate might not always be implemented at once,
especially on experimental features. If you want any particular feature to be
implemented, please [file an issue], or consider make a [new pull request].
### Development plan
- [ ] Create a `create-react-app`-like package initialiser __!!!__
- [ ] Integrate another end-to-end testing framework to replace [Spectron]
- [ ] Migrate to Webpack 5 `Asset Modules`
---
## 🚨 🚧 CAUTION 🚧 🚨
- [Spectron] has officially been deprecated by the [Electron] team on
February 1, 2022, thus, its' integration has also been dropped from this
boilerplate on `v4+`.
A replacement will be integrated in future version (pending for `v5`).
Currently evaluating different options including [Playwright] and
[WebdriverIO].
*See - [Spectron Deprecation Notice]*
- `mocha` has been dropped and replaced by [Jest] on `v4+`. If you're using
`mocha` as your unit testing framework, please reference to `package.json`
from [`v3.0.0`].
---
## Getting started
1. Clone this repository, or if you're hosting your Electron project on GitHub,
click [`Use this template`] to create a new project.
2. Edit the following fields in `package.json` for your own project:
```json
{
"name": "your-project-name",
"version": "whatever-you-like",
"description": "your-own-description",
"build": {
"appId": "your-app-id",
"productName": "your-product-name",
"buildVersion": "your-build-number"
},
"author": "who's-the-author?",
"license": "if-you-don't-want-to-use-MIT",
"repository": "type-and-link-of-your-repo",
"bugs": "issue-page-of-your-repo",
"homepage": "homepage-of-your-repo"
}
```
3. `npm install` to install the dependencies.
*Please note that `optionalDependencies` should only be omitted on your
CI/CD pipeline for unit testing. It's meant to save some bandwidth. You'll
need all the packages listed for development.*
Done! Now run `npm run dev` to start the Webpack in development and watch mode.
It's time to start working on your project.
Be aware that starting Webpack will only compile your files to `dist` folder
but won't start the Electron app. Use `npm start` command to start your
Electron app once the files are compiled.
__*Starting from `v4.0.0`, you no longer need to manually config your module
path alias in `webpack.config.js`. All module path alias set in `tsconfig.json`
will be configured in Webpack automatically thanks to [`tsconfig-paths`] and
[`tsconfig-paths-webpack-plugin`].*__
## Build your Electron app package
Different from the official [Electron quick start guide], this boilerplate uses
[`electron-builder`] instead of [Electron Forge] to package your Electron app.
By default, the build configuration in `package.json` is configured to build the
mac universal package (for Apple Silicon & Intel based machines) and Windows
`exe` installer (both 32 & 64 bit). You should not need to change anything in
the build script other than supplying the app icon unless you need to sign your
code/package or build for Linux.
For code signing and notarization, or to build for Linux, please read
[`electron-builder`'s document] for configuring the build script.
To package your Electron app, run `npm run prod` to get your code compiled in
`production` mode, then use `npm run build:(win|mac)` to build the package.
## Known issues
- [`electron-builder`] packages the file into Electron's `asar` archive format
by default. Based on past experiences with old Electron & `electron-builder`
versions, this might lead to runtime error on Windows while launching the
installed Electron app.
One way to verify this issue is to build the mac package and see if your app
runs fine on mac. If it's the case, you can override the `asar` archive
option in the build configuration in `package.json` by adding `asar: false`
in `win` section.
This solution isn't ideal but since `asar` archiving is
meant to improve performance of reading files if bundler like Webpack is not
being used. The app packaging workflow defined in this boilerplate already
uses Webpack to minify your code in `production` builds, so there shouldn't
be any significant performance different with `asar` archiving disabled.
## Project folders & files
- `dist/` - [Webpack] output location
__Contents will be flushed automatically on execution of `npm run <dev|prod>`
script.__
- `out/` - [`electron-builder`] output location
- `public/` - Global static assets.
- `index.html` - Template for `HTML Webpack Plugin`
Update the value of `<title>` tag to change the default window title.
- `style.css` - `CSS` file location sample
Not much defined in this file. You can either put your `CSS` settings here
or use any other tools you prefer.
- `src/` - Folder for all your source code
- `main/` - For modules which run on the `main` process.
- `main.ts` - [Electron] `main` process entry point
- `preload` - Preload scripts go here
- `ipc-api.ts` - APIs for IPC between `main` & `renderer`
Consider convert this module into a collection of submodules if you have
many APIs for IPC. See example as below:
```ts
// ipc-api/index.ts
import submoduleA from './submodule-a';
import submoduleB from './submodule-b';
export default { ...submoduleA, ...submoduleB };
// ipc-api/submodule-a.ts
import { ipcRenderer } from 'electron';
function a { ipcRenderer.send('a'); }
export default { a };
// ipc-api/submodule-b.ts
import { ipcRenderer } from 'electron';
function b { ipcRenderer.send('b'); }
export default { b };
```
- `preload.ts` - [Electron] preload script entry point
There should be no need to modify this file unless you want to use other
key(s) for your IPC APIs. By default, all APIs defined in `ipc-api`
module are exposed under key `ipcApi` in `contextBridge`.
- `renderer/` - Where the frontend scripts stay
- `App.tsx` - Root [React] component
- `renderer.tsx` - [Electron] `renderer` process entry point
*`public/style.css` imported here. Change it if you want.*
- `types/` - Home for self-defined `.d.ts` files
- `global.d.ts` - Extends global scope interfaces
This file includes ambient declaration for calling the IPC APIs defined in
`preload/ipc-api` from the `renderer`. Remember __NOT__ to remove this
part, otherwise TypeScript will tell you `type not exist`. However, if
you've opted to use a different key other than `ipcAPI` in the preload
script, __DO__ remember to update this file to match your own settings.
- `utils/` - Place to store the helper scripts
- `node-env.ts` - Shortcut to determine `NODE` environment
- `tests/` - Unit testing files location
To avoid test files mixing up with the source code, [Jest] is configured to
look for test file(s) within this folder only.
File name of the test files can either be `[filename].test.tsx` or
`[filename].spec.ts(x)`. `js(x)` can also be used for test files, but I assume
you'd use TypeScript if you're using this boilerplate.
- `main/main.spec.ts` - Sample test file for `src/main/main`
- `tsconfig.json` - TypeScript config file for `tests` module
- `.eslintignore` - [ESLint] ignore file
- `.eslintrc` - [ESLint] config file
Configured to use Airbnb's rules with [TypeScript] supported, and rules for
[Jest] applied.
- `.gitignore` - Git ignore file
- `CHANGELOG_PRE_V4.md` - Changelog of this boilerplate prior to `v4.0.0`
- `CHANGELOG_V4+.md` - Changelog of this boilerplate from `v4.0.0` onwards
- `jest.config.js` - [Jest] config file
- `LICENSE` - MIT license
- `package-lock.json`
- `package.json`
Includes basic build config for `electron-builder`. It's likely that you'll
have to personalise the build config when it comes to the time you're about
to release your app. Please read [`electron-builder`'s document] for the
build config setup guides.
- `README.md`
- `tsconfig.json` - [TypeScript] config file
Module path aliases are configured here. [Jest] & [Webpack] will pick up the
alias settings here to config their own. No need to manually config in Jest
& Webpack again.
- `webpack.config.json` - [Webpack] config file
Includes configurations targetting `electron-main`, `electron-preload`, and
`electron-renderer` respectively.
## Author
[Wing Chau](https://github.com/iamWing) [@Devtography](https://github.com/Devtography)
## Donation
Maintaining this project takes time, lots of cups of coffee, and I do it for
free. Consider buy me coffee via [donations]. 100% of donation will fund my
coffee buying budget for quality coffee beans from great roasters I know 😉 ☕️️
## License
Electron React TypeScript Webpack Boilerplate is open source software
[licensed as MIT](LICENSE).
[Electron]: https://www.electronjs.org
[React]: https://reactjs.org
[Webpack]: https://webpack.js.org
[TypeScript]: https://www.typescriptlang.org
[ESLint]: http://eslint.org
[Jest]: https://jestjs.io
[`electron-builder`]: https://github.com/electron-userland/electron-builder
[file an issue]: https://www.electronjs.org
[new pull request]: https://github.com/Devtography/electron-react-typescript-webpack-boilerplate/compare
[Spectron]: https://github.com/electron-userland/spectron
[Playwright]: https://playwright.dev
[WebdriverIO]: https://webdriver.io
[Spectron Deprecation Notice]: https://www.electronjs.org/blog/spectron-deprecation-notice
[`v3.0.0`]: https://github.com/Devtography/electron-react-typescript-webpack-boilerplate/releases/tag/v3.0.0
[`Use this template`]: https://github.com/Devtography/electron-react-typescript-webpack-boilerplate/generate
[`tsconfig-paths`]: https://github.com/dividab/tsconfig-paths
[`tsconfig-paths-webpack-plugin`]: https://github.com/dividab/tsconfig-paths-webpack-plugin
[Electron quick start guide]: https://www.electronjs.org/docs/latest/tutorial/quick-start
[Electron Forge]: https://github.com/electron-userland/electron-forge
[`electron-builder`'s document]: https://www.electron.build
[donations]: https://github.com/sponsors/iamWing

30
jest.config.js Normal file
View File

@ -0,0 +1,30 @@
const { pathsToModuleNameMapper } = require('ts-jest')
const { compilerOptions } = require('./tsconfig.json')
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = config = {
testEnvironment: 'node',
globals: {
'ts-jest': {
tsconfig: 'tsconfig.json',
},
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
moduleNameMapper: pathsToModuleNameMapper(
compilerOptions.paths,
{ prefix: '<rootDir>/' },
),
modulePathIgnorePatterns: [
'<rootDir>/dist',
'<rootDir>/node_modules',
'<rootDir>/out',
],
transform: {
'^.+\\.(ts|tsx)$': 'ts-jest',
},
testMatch: [
'**/tests/**/*.(spec|test).(ts?(x)|js?(x))',
],
collectCoverage: true,
verbose: true,
};

89
package.json Normal file
View File

@ -0,0 +1,89 @@
{
"name": "electron-react-typescript-webpack-boilerplate",
"version": "4.0.0",
"description": "Pre-configured boilerplate for Electron + React + TypeScript",
"main": "./dist/main.bundle.js",
"scripts": {
"start": "electron ./dist/main.bundle.js",
"webpack:dev": "rimraf dist && cross-env NODE_ENV=development webpack --watch --progress --color",
"prod": "rimraf dist && cross-env NODE_ENV=production webpack --progress --color",
"test": "cross-env NODE_ENV=test jest",
"watch-test": "npm run test -- --watchAll",
"next-rc": "npm --no-git-tag-version version prerelease --preid=rc",
"next-patch": "npm --no-git-tag-version version patch",
"next-minor": "npm --no-git-tag-version version minor",
"next-major": "npm --no-git-tag-version version major",
"build:win": "electron-builder build --win",
"build:mac": "electron-builder build --mac"
},
"build": {
"appId": "com.devtography.electron_boilerplate",
"productName": "Electron+React+TypeScript Boilerplate",
"directories": {
"app": "./dist/",
"output": "./out/"
},
"mac": {
"target": {
"target": "dmg",
"arch": "universal"
}
},
"win": {
"target": {
"target": "nsis",
"arch": [
"x64",
"ia32"
]
}
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true
},
"buildVersion": "1"
},
"author": "Wing Chau @Devtography",
"license": "MIT",
"dependencies": {
"@tensorflow/tfjs": "^3.19.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.2",
"upscaler": "^1.0.0-beta.5"
},
"devDependencies": {
"@types/jest": "^28.1.6",
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"cross-env": "^7.0.3",
"electron": "^19.0.9",
"jest": "^28.1.3",
"ts-jest": "^28.0.7",
"typescript": "^4.7.4"
},
"optionalDependencies": {
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.7.1",
"electron-builder": "^23.1.0",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.5.0",
"lodash": "^4.17.21",
"rimraf": "^3.0.2",
"style-loader": "^3.3.1",
"ts-loader": "^9.3.1",
"tsconfig-paths": "^4.0.0",
"tsconfig-paths-webpack-plugin": "^3.5.2",
"webpack": "^5.73.0",
"webpack-cli": "^4.10.0"
},
"repository": {
"type": "git",
"url": "git+https://github.com/Devtography/electron-react-typescript-webpack-boilerplate"
},
"bugs": {
"url": "https://github.com/Devtography/electron-react-typescript-webpack-boilerplate/issues"
},
"homepage": "https://github.com/Devtography/electron-react-typescript-webpack-boilerplate#readme"
}

13
public/index.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello World!</title>
</head>
<body>
<div id="app" class="app"></div>
</body>
</html>

3
public/style.css Normal file
View File

@ -0,0 +1,3 @@
.app {
color: grey;
}

68
src/main/main.ts Normal file
View File

@ -0,0 +1,68 @@
/**
* Entry point of the Election app.
*/
import * as path from "path";
import * as nodeEnv from "_utils/node-env";
// eslint-disable-next-line import/no-extraneous-dependencies
import { BrowserWindow, app, ipcMain } from "electron";
let mainWindow: Electron.BrowserWindow | undefined;
function createWindow() {
// Create the browser window.
mainWindow = new BrowserWindow({
height: 600,
width: 800,
webPreferences: {
devTools: nodeEnv.dev,
preload: path.join(__dirname, "./preload.bundle.js"),
webSecurity: nodeEnv.prod,
nodeIntegration: true,
},
});
// and load the index.html of the app.
mainWindow.loadFile("index.html").finally(() => {
/* no action */
});
// Emitted when the window is closed.
mainWindow.on("closed", () => {
mainWindow = undefined;
});
}
app.commandLine.appendSwitch("ignore-gpu-blacklist");
app
.whenReady()
.then(() => {
if (nodeEnv.dev || nodeEnv.prod) createWindow();
app.on("activate", () => {
if (BrowserWindow.getAllWindows.length === 0) createWindow();
});
})
.finally(() => {
/* no action */
});
app.on("window-all-closed", () => {
if (process.platform !== "darwin") app.quit();
});
// const getUpscaledImage = async () => {
// const file = fs.readFileSync(path.resolve(__dirname, "./image.png"));
// const image = tf.node.decodeImage(file, 3);
// const tensor = await upscaler.upscale(image, {
// output: "tensor",
// patchSize: 64,
// padding: 6,
// });
// image.dispose();
// const upscaledTensor = await tf.node.encodePng(tensor);
// tensor.dispose();
// return upscaledTensor;
// };
ipcMain.on("hello", async () => {});
export const exportedForTests = nodeEnv.test ? { createWindow } : undefined;

4
src/preload/preload.ts Normal file
View File

@ -0,0 +1,4 @@
import { contextBridge } from "electron";
import { ipcRenderer } from "electron";
contextBridge.exposeInMainWorld("ipcRenderer", ipcRenderer);

121
src/renderer/App.css Normal file
View File

@ -0,0 +1,121 @@
body {
padding: 40px;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
display: flex;
}
* {
box-sizing: border-box;
}
#root {
display: flex;
}
html,
body,
#root {
width: 100%;
height: 100%;
}
.dropzone {
cursor: move; /* fallback if grab cursor is unsupported */
cursor: grab;
cursor: -moz-grab;
cursor: -webkit-grab;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
flex: 1;
padding: 40px;
border: 5px dotted #ddd;
margin: 0 auto;
border-radius: 20px;
}
.app {
width: 100%;
text-align: center;
display: flex;
flex: 1;
flex-direction: column;
}
.original-image {
display: flex;
justify-content: center;
align-items: center;
flex: 1;
flex-direction: column;
margin: 0 auto;
}
.original img {
transition-duration: 1.2s;
position: relative;
}
button {
background: #ddd;
padding: 5px 10px;
border: 1px solid #aaa;
}
button:first-child {
border-radius: 5px 0 0 5px;
border-right: none;
}
button:last-child {
border-radius: 0 5px 5px 0;
}
button.active {
background: #c4c4ff;
}
.display {
position: relative;
margin-top: 5px;
}
.display .image-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}
.display .scaled-up img {
display: block;
margin: 0;
position: absolute;
left: 0%;
}
.dragOverlay {
position: absolute;
width: 100%;
height: 100%;
z-index: 1;
}
.dragger {
cursor: pointer;
height: 100%;
width: 4px;
background: rgba(255, 255, 255, 0.8);
position: absolute;
transform: translate(50px 0);
}
.header {
display: flex;
width: 100%;
min-height: 40px;
}
.interpolation {
flex: 1;
display: flex;
}

182
src/renderer/App.tsx Normal file
View File

@ -0,0 +1,182 @@
import Upscaler from "upscaler";
import React, { useCallback, useState, useEffect, useRef } from "react";
import { useDropzone } from "react-dropzone";
import "./App.css";
const upscaler = new Upscaler();
function App() {
const [src, setSrc] = useState<string>();
const [originalSize, setOriginalSize] = useState({ width: 0, height: 0 });
const [scale, setScale] = useState(1);
const [interpolation, setInterpolation] = useState("bicubic");
const [upscaledImageSrc, setUpscaledImageSrc] = useState<string>();
const [displayUpscaledImageSrc, setDisplayUpscaledImageSrc] = useState(false);
const [dragX, setDragX] = useState(0.5);
const [dragging, setDragging] = useState(false);
const container = useRef<HTMLDivElement>();
const onDrop = useCallback((acceptedFiles) => {
const file = acceptedFiles[0];
const fr = new FileReader();
fr.onload = async () => {
setSrc(fr.result as string);
};
fr.readAsDataURL(file);
}, []);
const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop });
useEffect(() => {
if (src) {
const img = new Image();
img.crossOrigin = "anonymous";
img.src = src;
img.onload = () => {
upscaler.upscale(img).then(setUpscaledImageSrc);
const width = img.width;
const height = img.height;
setOriginalSize({
width,
height,
});
};
}
}, [src]);
useEffect(() => {
if (originalSize) {
let upscaledImageSrcTimer;
const timer = setTimeout(() => {
setScale(3);
upscaledImageSrcTimer = setTimeout(() => {
setDisplayUpscaledImageSrc(true);
}, 1200);
}, 300);
return () => {
clearTimeout(timer);
clearTimeout(upscaledImageSrcTimer);
};
}
}, [originalSize]);
const startDragging = () => {
setDragging(true);
};
const drag = (e) => {
if (dragging) {
const offsetWidth = container.current.offsetWidth;
const x = e.clientX - (window.innerWidth - offsetWidth) / 2 - 10;
setDragX(x / offsetWidth);
}
};
const stopDragging = () => {
console.log("stop");
setDragging(false);
};
if (src) {
const left = dragX * 100;
return (
<div
className="original-image"
style={{
width: originalSize ? originalSize.width * scale : null,
}}
>
<div className="header">
{displayUpscaledImageSrc && (
<>
<div className="interpolation">
<button
className={interpolation === "none" ? "active" : null}
onClick={() => setInterpolation("none")}
>
None
</button>
<button
className={interpolation === "bicubic" ? "active" : null}
onClick={() => setInterpolation("bicubic")}
>
Bicubic interpolation
</button>
</div>
<div>Upscaled image</div>
</>
)}
</div>
<div
className="display"
style={{
width: originalSize ? originalSize.width * scale : null,
height: originalSize ? originalSize.height * scale : null,
}}
>
{displayUpscaledImageSrc && (
<div
className="dragOverlay"
ref={container}
onMouseMove={drag}
onMouseUp={stopDragging}
>
<div
className="dragger"
onMouseDown={startDragging}
style={{
left: `calc(${left}%)`,
}}
/>
</div>
)}
<div className="image-container original">
<img
src={src}
alt="Original"
width={originalSize ? originalSize.width * scale : null}
style={{
imageRendering: interpolation === "none" ? "pixelated" : null,
}}
/>
</div>
{displayUpscaledImageSrc && (
<div
className="image-container scaled-up"
style={{
width: `${100 - left}%`,
left: `${left}%`,
}}
>
<img
style={{
left: ((originalSize.width * scale * left) / 100) * -1,
}}
alt="Upscaled"
src={upscaledImageSrc}
width={originalSize ? originalSize.width * scale : null}
/>
</div>
)}
</div>
{displayUpscaledImageSrc && <p>Resized to 3x</p>}
</div>
);
}
return (
<div className="dropzone" {...getRootProps()}>
<input {...getInputProps()} />
{isDragActive ? (
<p>Drop the files here ...</p>
) : (
<p>Drag 'n' drop some files here, or click to select files</p>
)}
</div>
);
}
export default () => {
return (
<div className="app">
<h1>React Demo Integration</h1>
<App />
</div>
);
};

BIN
src/renderer/image.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

13
src/renderer/renderer.tsx Normal file
View File

@ -0,0 +1,13 @@
/**
* React renderer.
*/
// Import the styles here to process them with webpack
import "_public/style.css";
import App from "_renderer/App";
import * as React from "react";
import { createRoot } from "react-dom/client";
const container = document.getElementById("app");
const root = createRoot(container!);
root.render(<App />);

10
src/types/global.d.ts vendored Normal file
View File

@ -0,0 +1,10 @@
import { ipcRenderer } from "electron";
declare global {
interface Window {
/** APIs for Electron IPC */
ipcRenderer?: typeof ipcRenderer;
}
}
// Makes TS sees this as an external modules so we can extend the global scope.
export {};

12
src/utils/node-env.ts Normal file
View File

@ -0,0 +1,12 @@
/**
* Predefined NODE_ENV values
*/
/** `NODE_ENV=production` */
export const prod = process.env.NODE_ENV === 'production';
/** `NODE_ENV=development` */
export const dev = process.env.NODE_ENV === 'development';
/** `NODE_ENV=test` */
export const test = process.env.NODE_ENV === 'test';

25
tests/main/main.spec.ts Normal file
View File

@ -0,0 +1,25 @@
import { exportedForTests } from '_main/main';
import { BrowserWindow } from 'electron';
jest.mock('electron', () => ({
app: {
on: jest.fn(),
whenReady: jest.fn(() => Promise.resolve()),
},
ipcMain: { on: jest.fn() },
BrowserWindow: jest.fn().mockImplementation(() => ({
loadFile: jest.fn(() => Promise.resolve()),
on: jest.fn(),
})),
}));
test('Private props exported for unit tests', () => {
expect(exportedForTests).toBeDefined();
});
test('func createWindow()', () => {
const { createWindow } = exportedForTests!;
createWindow();
expect(BrowserWindow).toHaveBeenCalledTimes(1);
});

7
tests/tsconfig.json Normal file
View File

@ -0,0 +1,7 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"noEmit": true
},
"include": ["./**/*"]
}

28
tsconfig.json Normal file
View File

@ -0,0 +1,28 @@
{
"compilerOptions": {
"strict": false,
"allowJs": true,
"baseUrl": ".",
"module": "ES2020",
"moduleResolution": "Node16",
"noImplicitAny": false,
"paths": {
"_/*": ["src/*"],
"_public/*": ["public/*"],
"_main/*": ["src/main/*"],
"_preload/*": ["src/preload/*"],
"_renderer/*": ["src/renderer/*"],
"_types/*": ["src/types/*"],
"_utils/*": ["src/utils/*"],
"_tests/*": ["tests/*"]
},
"typeRoots": ["./node_modules/@types", "./src/types"],
"sourceMap": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"jsx": "react",
"target": "ES2020"
},
"include": ["src/**/*", "tests/**/*"]
}

95
webpack.config.js Normal file
View File

@ -0,0 +1,95 @@
const lodash = require('lodash');
const CopyPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
const path = require('path');
function srcPaths(src) {
return path.join(__dirname, src);
}
const isEnvProduction = process.env.NODE_ENV === 'production';
const isEnvDevelopment = process.env.NODE_ENV === 'development';
// #region Common settings
const commonConfig = {
devtool: isEnvDevelopment ? 'source-map' : false,
mode: isEnvProduction ? 'production' : 'development',
output: { path: srcPaths('dist') },
node: { __dirname: false, __filename: false },
resolve: {
extensions: ['.js', '.json', '.ts', '.tsx'],
plugins: [new TsconfigPathsPlugin({
configFile: './tsconfig.json',
extensions: ['.js', '.json', '.ts', '.tsx'],
})],
},
module: {
rules: [
{
test: /\.(ts|tsx)$/,
exclude: /node_modules/,
loader: 'ts-loader',
},
{
test: /\.(scss|css)$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(jpg|png|svg|ico|icns)$/,
loader: 'file-loader',
options: {
name: '[path][name].[ext]',
},
},
],
},
};
// #endregion
const mainConfig = lodash.cloneDeep(commonConfig);
mainConfig.entry = './src/main/main.ts';
mainConfig.target = 'electron-main';
mainConfig.output.filename = 'main.bundle.js';
mainConfig.plugins = [
new CopyPlugin({
patterns: [
{
from: 'package.json',
to: 'package.json',
transform: (content, _path) => {
const jsonContent = JSON.parse(content);
const electronVersion = jsonContent.devDependencies.electron;
delete jsonContent.devDependencies;
delete jsonContent.optionalDependencies;
delete jsonContent.scripts;
delete jsonContent.build;
jsonContent.main = './main.bundle.js';
jsonContent.scripts = { start: 'electron ./main.bundle.js' };
jsonContent.devDependencies = { electron: electronVersion }
return JSON.stringify(jsonContent, undefined, 2);
},
},
],
}),
];
const preloadConfig = lodash.cloneDeep(commonConfig);
preloadConfig.entry = './src/preload/preload.ts';
preloadConfig.target = 'electron-preload';
preloadConfig.output.filename = 'preload.bundle.js';
const rendererConfig = lodash.cloneDeep(commonConfig);
rendererConfig.entry = './src/renderer/renderer.tsx';
rendererConfig.target = 'electron-renderer';
rendererConfig.output.filename = 'renderer.bundle.js';
rendererConfig.plugins = [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, './public/index.html'),
}),
];
module.exports = [mainConfig, preloadConfig, rendererConfig];

5093
yarn.lock Normal file

File diff suppressed because it is too large Load Diff