├── .config
├── .eslintrc.js
└── entitlements.mac.plist
├── .gitignore
├── .meta
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── package.json
├── public
├── dmgBackground-source.svg
├── dmgBackground.png
├── icon.icns
├── icon.ico
├── icon.png
└── screenshot.png
├── scripts
├── common.ts
├── dev.ts
├── notarize.js
├── prod-esbuild.ts
├── prod.ts
├── run-electron.ts
├── run-vite.ts
└── tsconfig.json
├── src
├── common
│ └── .gitkeep
├── index.twstyled.css
├── main
│ ├── index.ts
│ └── tsconfig.json
└── renderer
│ ├── App.tsx
│ ├── components
│ └── top-bar.tsx
│ ├── favicon.svg
│ ├── index.css
│ ├── index.html
│ ├── logo.png
│ ├── logo.svg
│ ├── main.tsx
│ └── tsconfig.json
├── tailwind.config.js
├── tsconfig.json
├── vite.config.ts
└── yarn.lock
/.config/.eslintrc.js:
--------------------------------------------------------------------------------
1 | // Sync Labs defaults
2 | // See https://github.com/synclabs-dev/eslint-config
3 | // Includes Typescript and prettier
4 |
5 | // FIX-DEPENDENCIES
6 | require.resolve('eslint')
7 | require.resolve('prettier')
8 |
9 | // This is a workaround for https://github.com/eslint/eslint/issues/3458
10 | require('@sync-labs/eslint-config/patch/modern-module-resolution')
11 |
12 | // The ESLint browser environment defines all browser globals as valid,
13 | // even though most people don't know some of them exist (e.g. `name` or `status`).
14 | // This is dangerous as it hides accidentally undefined variables.
15 | // We blacklist the globals that we deem potentially confusing.
16 | // To use them, explicitly reference them, e.g. `window.name` or `window.status`.
17 |
18 | module.exports = {
19 | root: true,
20 | extends: ["@sync-labs/eslint-config/profile/node"], // <---- put your profile string here
21 | parserOptions: {
22 | tsconfigRootDir: require('path').resolve(__dirname, '..'),
23 | "ecmaVersion": 2017,
24 | "env": {
25 | "es6": true
26 | },
27 | "sourceType": "module",
28 | "allowImportExportEverywhere": true
29 | },
30 | rules: {
31 | '@typescript-eslint/no-parameter-properties': 'off',
32 | '@typescript-eslint/no-use-before-define': 'off',
33 | '@typescript-eslint/no-explicit-any': 'off',
34 | '@typescript-eslint/explicit-function-return-type': 'off',
35 | '@typescript-eslint/ban-types': 'off',
36 | 'no-void': 'off',
37 | '@rushstack/no-new-null': 'off',
38 | 'react/jsx-uses-react': "off",
39 | 'react/react-in-jsx-scope': "off"
40 | }
41 | }
--------------------------------------------------------------------------------
/.config/entitlements.mac.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.cs.allow-jit
6 |
7 | com.apple.security.cs.allow-unsigned-executable-memory
8 |
9 | com.apple.security.cs.allow-dyld-environment-variables
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | dist
4 | dist-ssr
5 | *.local
6 | .twstyled-cache/
7 |
8 |
9 | # Secrets
10 | .env-secrets.json
11 |
12 | # Workspaces
13 |
--------------------------------------------------------------------------------
/.meta:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {
3 | "lib-twstyled": "git@github.com:twstyled/twstyled.git"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "search.useIgnoreFiles": false,
3 | "search.exclude": {
4 | "**/.next": true,
5 | "**/build": true,
6 | "**/node_modules": true,
7 | "**/out": true,
8 | "**/script.js": true,
9 | "**/*-cache": true,
10 | "**/dist": true,
11 | "yarn.lock": true,
12 | "**/*.mdx": true
13 | },
14 | "editor.codeActionsOnSave": {
15 | "source.fixAll.eslint": true
16 | },
17 | "editor.formatOnSave": false,
18 | "editor.codeActionsOnSaveTimeout": 6000,
19 | "css.lint.unknownAtRules": "ignore",
20 | "eslint.options": {
21 | "configFile": "./.config/.eslintrc.js"
22 | },
23 | "eslint.lintTask.options": "-c ${workspaceRoot}/.config/.eslintrc.js --ext .ts,.js,.tsx .",
24 | "eslint.lintTask.enable": true,
25 | "[javascript]": {
26 | "editor.formatOnSave": true
27 | },
28 | "[javascriptreact]": {
29 | "editor.formatOnSave": false
30 | },
31 | "[typescript]": {
32 | "editor.formatOnSave": false
33 | },
34 | "[typescriptreact]": {
35 | "editor.formatOnSave": false
36 | },
37 | "[json]": {
38 | "editor.formatOnSave": true
39 | },
40 | "eslint.validate": [
41 | "javascript",
42 | "javascriptreact",
43 | "typescript",
44 | "typescriptreact"
45 | ],
46 | "eslint.alwaysShowStatus": true,
47 | "files.eol": "\n",
48 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts
49 | "typescript.tsc.autoDetect": "off",
50 | "tailwindCSS.includeLanguages": {
51 | "plaintext": "html",
52 | "javascript": "javascriptreact",
53 | "typescript": "javascript",
54 | "typescriptreact": "javascriptreact"
55 | }
56 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 twstyled contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Electron 11 + TypeScript 4.0 + Vite 2 + React 17 + Tailwind 2 + twstyled starter
2 |
3 | 
4 |
5 | Blazing fast Electron starter including
6 | - [x] `Vite` for next generation frontend tooling
7 | - [x] `Typescript`
8 | - [x] `ESBuild` for building all assets including the main process
9 | - [x] `React` as the front end framework
10 | - [x] `Tailwind CSS` for styling without templates
11 | - [x] `twstyled` tailwind compiler for no PostCSS processing and full-featured CSS in JS
12 | - [x] `Framer Motion` for animation transitions
13 | - [x] `React fast refresh` for hot module reloading
14 | - [x] `electron-builder` and `electron-notarize` for distribution release
15 | - [x] Zero dependency on Vue, tsc or styled-components, emotion or other runtime CSS in JS (but supports all the same API)
16 |
17 | Configured with best practices.
18 |
19 | ## Installation
20 |
21 | `yarn`
22 |
23 | ## Development
24 |
25 | `yarn dev`
26 |
27 | ## Build
28 |
29 | `yarn build`
30 |
31 | ## Release
32 |
33 | Add any configuration to the `build` section of `package.json`, add an `.env-secrets.json` file in the `.config` folder with any environment secrets that you need for your publisher, and then run
34 |
35 | `yarn publish`
36 |
37 | # Prior art
38 |
39 | Inspired by https://github.com/appinteractive/electron-vite-tailwind-starter and https://github.com/maxstue/vite-reactts-electron-starter
40 |
41 | # License
42 |
43 | MIT
44 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "electron-vite-react",
3 | "description": "This Starter utilizes Electron, TypeScript, Vite, React, Tailwind CSS with twstyled CSS in JS. It tries to adhere to best practices.",
4 | "version": "1.0.1",
5 | "license": "MIT",
6 | "private": true,
7 | "scripts": {
8 | "clean": "rimraf yarn.lock && rimraf ./**/.next && rimraf dist && rimraf ./**/out && rimraf {**/,}node_modules",
9 | "dev": "cross-env NODE_ENV=development node -r esbuild-register ./scripts/dev.ts",
10 | "preview": "cross-env NODE_ENV=production electron ./dist/main",
11 | "dev:renderer": "vite",
12 | "dev:main": "cross-env NODE_ENV=development node -r esbuild-register ./scripts/dev.ts",
13 | "build": "rimraf dist && vite build && npm run prod:esbuild",
14 | "prod:esbuild": "cross-env NODE_ENV=production node -r esbuild-register ./scripts/prod.ts",
15 | "prod:tsc": "tsc --project ./src/main/tsconfig.json",
16 | "publish": "concurrently \"npm:publish:mac\" \"npm:publish:win\"",
17 | "publish:mac": "env-cmd -r ./.config/.env-secrets.json -e production electron-builder --macos",
18 | "publish:win": "env-cmd -r ./.config/.env-secrets.json -e production electron-builder --win"
19 | },
20 | "dependencies": {
21 | "@twstyled/core": "^3.2.4",
22 | "@twstyled/theme": "^3.2.4",
23 | "@twstyled/util": "^3.2.4",
24 | "electron-context-menu": "^2.5.0",
25 | "electron-window-state": "^5.0.3",
26 | "framer-motion": "3.7.0",
27 | "react": "^17.0.0",
28 | "react-dom": "^17.0.0",
29 | "tailwindcss": "^2.0.3"
30 | },
31 | "devDependencies": {
32 | "@sync-labs/eslint-config": "^2.2.6",
33 | "@types/node": "^14.14.31",
34 | "@types/react": "^17.0.0",
35 | "@types/react-dom": "^17.0.0",
36 | "@vitejs/plugin-react-refresh": "^1.1.0",
37 | "chalk": "^4.1.0",
38 | "concurrently": "^6.0.0",
39 | "cross-env": "^7.0.3",
40 | "env-cmd": "^10.1.0",
41 | "electron": "^11.3.0",
42 | "electron-builder": "^22.9.1",
43 | "electron-notarize": "^1.0.0",
44 | "esbuild": "^0.8.53",
45 | "esbuild-register": "^2.0.0",
46 | "eslint": "^7.20.0",
47 | "prettier": "^2.2.1",
48 | "rimraf": "^3.0.2",
49 | "typescript": "^4.1.2",
50 | "vite": "^2.0.4",
51 | "vite-plugin-twstyled": "^3.2.4"
52 | },
53 | "resolutions": {
54 | "@linaria/babel-preset": "npm:@twstyled/linaria-babel-preset@3.0.0-beta.1",
55 | "@linaria/preeval": "npm:@twstyled/linaria-preeval@3.0.0-beta.1"
56 | },
57 | "author": "twstyled contributors",
58 | "browserslist": [
59 | "last 1 electron version"
60 | ],
61 | "build": {
62 | "directories": {
63 | "output": "dist",
64 | "buildResources": "public"
65 | },
66 | "appId": "dev.twstyled.app",
67 | "productName": "twstyled-app",
68 | "afterSign": "scripts/notarize.js",
69 | "mac": {
70 | "category": "public.app-category.developer-tools",
71 | "extendInfo": {
72 | "NSUserNotificationAlertStyle": "alert"
73 | },
74 | "hardenedRuntime": true,
75 | "gatekeeperAssess": false,
76 | "entitlements": ".config/entitlements.mac.plist",
77 | "entitlementsInherit": ".config/entitlements.mac.plist",
78 | "publish": [
79 | {
80 | "provider": "generic",
81 | "url": "https://files.twstyled.com/releases/${name}/${os}/"
82 | }
83 | ]
84 | },
85 | "dmg": {
86 | "background": "public/dmgBackground.png",
87 | "icon": "icon.icns",
88 | "iconSize": 155,
89 | "window": {
90 | "width": 660,
91 | "height": 400
92 | },
93 | "contents": [
94 | {
95 | "x": 123,
96 | "y": 172
97 | },
98 | {
99 | "x": 539,
100 | "y": 168,
101 | "type": "link",
102 | "path": "/Applications"
103 | }
104 | ]
105 | },
106 | "win": {
107 | "publisherName": "twstyled contributors",
108 | "sign": null,
109 | "publish": [
110 | {
111 | "provider": "generic",
112 | "url": "https://files.twstyled.com/releases/${name}/${os}/"
113 | }
114 | ]
115 | },
116 | "asar": false,
117 | "extraMetadata": {
118 | "main": "main/index.js"
119 | },
120 | "files": [
121 | {
122 | "filter": [
123 | "package.json",
124 | "tailwind.config.js"
125 | ]
126 | },
127 | {
128 | "from": "./dist",
129 | "to": "./",
130 | "filter": [
131 | "index.html"
132 | ]
133 | },
134 | {
135 | "from": "./dist/main",
136 | "to": "./main"
137 | },
138 | {
139 | "from": "./dist/assets",
140 | "to": "./assets"
141 | }
142 | ]
143 | },
144 | "workspaces": [
145 | "lib-twstyled/packages/*"
146 | ]
147 | }
--------------------------------------------------------------------------------
/public/dmgBackground-source.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
27 |
--------------------------------------------------------------------------------
/public/dmgBackground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twstyled/electron-vite-react/045bf9642e8e2777c32c62bf937078c7eb061963/public/dmgBackground.png
--------------------------------------------------------------------------------
/public/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twstyled/electron-vite-react/045bf9642e8e2777c32c62bf937078c7eb061963/public/icon.icns
--------------------------------------------------------------------------------
/public/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twstyled/electron-vite-react/045bf9642e8e2777c32c62bf937078c7eb061963/public/icon.ico
--------------------------------------------------------------------------------
/public/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twstyled/electron-vite-react/045bf9642e8e2777c32c62bf937078c7eb061963/public/icon.png
--------------------------------------------------------------------------------
/public/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twstyled/electron-vite-react/045bf9642e8e2777c32c62bf937078c7eb061963/public/screenshot.png
--------------------------------------------------------------------------------
/scripts/common.ts:
--------------------------------------------------------------------------------
1 | import * as os from 'os'
2 | import * as path from 'path'
3 | import * as chalk from 'chalk'
4 |
5 | export type WatchMain = (
6 | reportError: (errs: CompileError[]) => void,
7 | buildStart: () => void,
8 | buildComplete: (dir: string) => void,
9 | notFoundTSConfig: () => void
10 | ) => void
11 |
12 | export const srcPath = path.join(process.cwd(), './src')
13 | export const mainPath = path.join(process.cwd(), './src/main')
14 | export const outDir = path.join(process.cwd(), './dist')
15 | export const outDirMain = path.join(process.cwd(), './dist/main')
16 | export const entryPath = path.join(mainPath, 'index.ts')
17 |
18 | export const consoleMessagePrefix = '[script]'
19 | export const consoleViteMessagePrefix = '[vite]'
20 |
21 | export const cannotFoundTSConfigMessage =
22 | "Could not find a valid 'tsconfig.json'."
23 | export const startMessage = chalk.cyan(
24 | `${consoleMessagePrefix} Start compile main process...`
25 | )
26 | export const finishMessageDev = chalk.green(
27 | `${consoleMessagePrefix} Finished compiling. Rerun electron main process...`
28 | )
29 | export const finishMessageProd = chalk.green(
30 | `${consoleMessagePrefix} Finished production build.`
31 | )
32 |
33 | export interface CompileError {
34 | location: {
35 | column: number
36 | file: string
37 | length: number
38 | line: number
39 | lineText: string
40 | }
41 | message: string
42 | }
43 |
44 | function repeatString(char: string, len: number): string {
45 | return Array(len).fill(char).join('')
46 | }
47 |
48 | function formatCompileError(error: CompileError): string {
49 | const pathMessage =
50 | chalk.cyan(error.location.file) +
51 | ':' +
52 | chalk.yellow(error.location.line) +
53 | ':' +
54 | chalk.yellow(error.location.column)
55 | const categoryMessage = chalk.red('error:')
56 |
57 | const code =
58 | chalk.gray(error.location.line) +
59 | ' ' +
60 | error.location.lineText +
61 | os.EOL +
62 | repeatString(
63 | ' ',
64 | error.location.column + `${error.location.line}`.length + 1 + 1
65 | ) +
66 | chalk.red(repeatString('~', error.location.length)) +
67 | repeatString(
68 | ' ',
69 | error.location.lineText.length -
70 | error.location.column -
71 | error.location.length
72 | )
73 |
74 | return `${pathMessage} - ${categoryMessage} ${error.message} ${os.EOL} ${code}`
75 | }
76 |
77 | export function formatDiagnosticsMessage(errors: CompileError[]): string {
78 | const messages = errors.map((e) => formatCompileError(e))
79 | const errorMessage = `Found ${errors.length} errors. Watching for file changes.`
80 |
81 | let diagnosticDetail = ''
82 | messages.forEach((item, index, { length }) => {
83 | diagnosticDetail += item
84 | .split(os.EOL)
85 | .map((i) => ' ' + i)
86 | .join(os.EOL)
87 | if (index + 1 !== length) {
88 | diagnosticDetail += os.EOL
89 | }
90 | })
91 |
92 | const res =
93 | chalk.rgb(
94 | 255,
95 | 161,
96 | 237
97 | )(`${consoleMessagePrefix} Some typescript compilation errors occurred:`) +
98 | '\n' +
99 | diagnosticDetail +
100 | '\n' +
101 | chalk.rgb(255, 161, 237)(errorMessage)
102 |
103 | return res
104 | }
105 |
--------------------------------------------------------------------------------
/scripts/dev.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path'
2 | import * as fs from 'fs'
3 | import * as esbuild from 'esbuild'
4 | import startViteServer from './run-vite'
5 | import startElectron from './run-electron'
6 |
7 | import {
8 | cannotFoundTSConfigMessage,
9 | CompileError,
10 | finishMessageDev,
11 | formatDiagnosticsMessage,
12 | startMessage,
13 | mainPath,
14 | outDir,
15 | entryPath
16 | } from './common'
17 |
18 | const chalk = require('chalk')
19 |
20 | function reportError(errors: CompileError[]) {
21 | const reportingMessage = formatDiagnosticsMessage(errors)
22 | console.error(reportingMessage)
23 | }
24 |
25 | function buildStart() {
26 | console.log(startMessage)
27 | }
28 |
29 | async function electronClosed() {
30 | if (viteClose) {
31 | await viteClose()
32 | }
33 | }
34 |
35 | function buildComplete(dir: string) {
36 | console.log(finishMessageDev)
37 | void startElectron(dir, electronClosed)
38 | }
39 |
40 | function notFoundTSConfig() {
41 | console.error(chalk.red(cannotFoundTSConfigMessage))
42 | process.exit()
43 | }
44 |
45 | let viteClose: () => Promise
46 |
47 | async function main() {
48 | // Start vite server
49 | viteClose = await startViteServer()
50 | // Start dev for main process
51 | void esDev(reportError, buildStart, buildComplete, notFoundTSConfig)
52 | }
53 |
54 | void main()
55 |
56 | //
57 | // SUPPORTING BUILD SCRIPT
58 | //
59 |
60 | function transformErrors(error: esbuild.BuildFailure): CompileError[] {
61 | const errors = error.errors.map(
62 | (e): CompileError => {
63 | return {
64 | location: e.location,
65 | message: e.text
66 | }
67 | }
68 | )
69 | return errors
70 | }
71 |
72 | async function esDev(
73 | reportError: { (errors: CompileError[]): void; (arg0: CompileError[]): void },
74 | buildStart: () => void,
75 | buildComplete: { (dir: string): void; (arg0: string): void },
76 | notFoundTSConfig: { (): void; (): void }
77 | ) {
78 | const tsconfigPath = path.join(mainPath, 'tsconfig.json')
79 | if (!fs.existsSync(tsconfigPath)) {
80 | notFoundTSConfig()
81 | }
82 |
83 | try {
84 | await esbuild.build({
85 | outdir: outDir,
86 | entryPoints: [entryPath],
87 | tsconfig: tsconfigPath,
88 | format: 'cjs',
89 | logLevel: 'silent',
90 | errorLimit: 0,
91 | incremental: true,
92 | platform: 'node',
93 | sourcemap: true,
94 | watch: {
95 | onRebuild: (error) => {
96 | if (error) {
97 | reportError(transformErrors(error))
98 | } else {
99 | buildComplete(outDir)
100 | }
101 | }
102 | }
103 | })
104 | buildComplete(outDir)
105 | } catch (e) {
106 | if (!!e.errors && !!e.errors.length && e.errors.length > 0) {
107 | const error = e as esbuild.BuildFailure
108 | reportError(transformErrors(error))
109 | }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/scripts/notarize.js:
--------------------------------------------------------------------------------
1 | const { notarize } = require('electron-notarize')
2 |
3 | exports.default = async function notarizing(context) {
4 |
5 | return
6 | const { electronPlatformName, appOutDir } = context
7 | if (electronPlatformName !== 'darwin') {
8 | return
9 | }
10 |
11 | // // .replace(new RegExp('\\n', 'g'), '\n'),
12 |
13 | const appName = context.packager.appInfo.productFilename
14 |
15 | await notarize({
16 | appBundleId: 'com.twstyled.starter',
17 | appPath: `${appOutDir}/${appName}.app`,
18 | appleApiKey: process.env.AC_API_KEY,
19 | appleApiIssuer: process.env.AC_API_ISSUER
20 | })
21 | }
22 |
--------------------------------------------------------------------------------
/scripts/prod-esbuild.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Build main process using ESBuild
3 | */
4 | import * as path from 'path'
5 | import * as fs from 'fs'
6 | import * as esbuild from 'esbuild'
7 | import { CompileError, mainPath, outDirMain, entryPath } from './common'
8 |
9 | function transformErrors(error: esbuild.BuildFailure): CompileError[] {
10 | const errors = error.errors.map(
11 | (e): CompileError => {
12 | return {
13 | location: e.location,
14 | message: e.text
15 | }
16 | }
17 | )
18 | return errors
19 | }
20 |
21 | export default async (
22 | reportError,
23 | buildStart,
24 | buildComplete,
25 | notFoundTSConfig
26 | ) => {
27 | const tsconfigPath = path.join(mainPath, 'tsconfig.json')
28 | if (!fs.existsSync(tsconfigPath)) {
29 | notFoundTSConfig()
30 | }
31 |
32 | try {
33 | await esbuild.build({
34 | outdir: outDirMain,
35 | entryPoints: [entryPath],
36 | tsconfig: tsconfigPath,
37 | format: 'cjs',
38 | logLevel: 'info',
39 | errorLimit: 0,
40 | incremental: true,
41 | platform: 'node',
42 | sourcemap: false,
43 | watch: false
44 | })
45 | buildComplete(outDirMain)
46 | } catch (e) {
47 | if (!!e.errors && !!e.errors.length && e.errors.length > 0) {
48 | const error = e as esbuild.BuildFailure
49 | reportError(transformErrors(error))
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/scripts/prod.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Build main process using ESBuild
3 | */
4 | import * as path from 'path'
5 | import * as fs from 'fs'
6 | import * as esbuild from 'esbuild'
7 | import {
8 | cannotFoundTSConfigMessage,
9 | finishMessageProd,
10 | formatDiagnosticsMessage,
11 | startMessage,
12 | CompileError,
13 | mainPath,
14 | outDirMain,
15 | entryPath
16 | } from './common'
17 |
18 | function reportError(errors: CompileError[]) {
19 | const reportingMessage = formatDiagnosticsMessage(errors)
20 | console.error(reportingMessage)
21 | }
22 |
23 | function buildStart() {
24 | console.log(startMessage)
25 | }
26 |
27 | function buildComplete(dir: string) {
28 | console.log(finishMessageProd)
29 | process.exit()
30 | }
31 |
32 | function notFoundTSConfig() {
33 | console.error(chalk.red(cannotFoundTSConfigMessage))
34 | process.exit()
35 | }
36 |
37 | async function main() {
38 | // Start dev for main process
39 | void esProd(reportError, buildStart, buildComplete, notFoundTSConfig)
40 | }
41 |
42 | void main()
43 |
44 | const chalk = require('chalk')
45 |
46 | function transformErrors(error: esbuild.BuildFailure): CompileError[] {
47 | const errors = error.errors.map(
48 | (e): CompileError => {
49 | return {
50 | location: e.location,
51 | message: e.text
52 | }
53 | }
54 | )
55 | return errors
56 | }
57 |
58 | //
59 | // SUPPORTING BUILD SCRIPT
60 | //
61 | async function esProd(
62 | reportError: { (errors: CompileError[]): void; (arg0: CompileError[]): void },
63 | buildStart: () => void,
64 | buildComplete: { (dir: string): void; (arg0: string): void },
65 | notFoundTSConfig: { (): void; (): void }
66 | ) {
67 | const tsconfigPath = path.join(mainPath, 'tsconfig.json')
68 | if (!fs.existsSync(tsconfigPath)) {
69 | notFoundTSConfig()
70 | }
71 |
72 | try {
73 | await esbuild.build({
74 | outdir: outDirMain,
75 | entryPoints: [entryPath],
76 | tsconfig: tsconfigPath,
77 | format: 'cjs',
78 | logLevel: 'info',
79 | errorLimit: 0,
80 | incremental: true,
81 | platform: 'node',
82 | sourcemap: false,
83 | watch: false
84 | })
85 | buildComplete(outDirMain)
86 | } catch (e) {
87 | if (!!e.errors && !!e.errors.length && e.errors.length > 0) {
88 | const error = e as esbuild.BuildFailure
89 | reportError(transformErrors(error))
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/scripts/run-electron.ts:
--------------------------------------------------------------------------------
1 | import * as childProcess from 'child_process'
2 | import * as stream from 'stream'
3 | import chalk = require('chalk')
4 |
5 | const electron = require('electron')
6 |
7 | let electronProcess: childProcess.ChildProcess | null = null
8 |
9 | const removeJunkTransformOptions: stream.TransformOptions = {
10 | decodeStrings: false,
11 | transform: (chunk, encoding, done) => {
12 | const source = chunk.toString()
13 | // Example: 2018-08-10 22:48:42.866 Electron[90311:4883863] *** WARNING: Textured window
14 | if (
15 | /\d+-\d+-\d+ \d+:\d+:\d+\.\d+ Electron(?: Helper)?\[\d+:\d+] /.test(
16 | source
17 | )
18 | ) {
19 | return false
20 | }
21 | // Example: [90789:0810/225804.894349:ERROR:CONSOLE(105)] "Uncaught (in promise) Error: Could not instantiate: ProductRegistryImpl.Registry", source: chrome-devtools://devtools/bundled/inspector.js (105)
22 | if (/\[\d+:\d+\/|\d+\.\d+:ERROR:CONSOLE\(\d+\)\]/.test(source)) {
23 | return false
24 | }
25 | // Example: ALSA lib confmisc.c:767:(parse_card) cannot find card '0'
26 | if (/ALSA lib [a-z]+\.c:\d+:\([a-z_]+\)/.test(source)) {
27 | return false
28 | }
29 | done(null, chunk)
30 | }
31 | }
32 |
33 | function delay(duration: number): Promise {
34 | return new Promise((resolve) => {
35 | setTimeout(() => {
36 | resolve()
37 | }, duration)
38 | })
39 | }
40 |
41 | let exitByScripts = false
42 | export default async function startElectron(
43 | path: string,
44 | onClose: () => Promise
45 | ) {
46 | if (electronProcess) {
47 | process.kill(electronProcess.pid)
48 | exitByScripts = true
49 | electronProcess = null
50 | await delay(500)
51 | }
52 |
53 | // eslint-disable-next-line require-atomic-updates
54 | electronProcess = childProcess.spawn(electron, [path])
55 | electronProcess.on('exit', async (code) => {
56 | if (!exitByScripts) {
57 | console.log(chalk.gray(`Electron exited with code ${code}`))
58 | await onClose()
59 | process.exit()
60 | }
61 | // eslint-disable-next-line require-atomic-updates
62 | exitByScripts = true
63 | })
64 |
65 | const removeElectronLoggerJunkOut = new stream.Transform(
66 | removeJunkTransformOptions
67 | )
68 | const removeElectronLoggerJunkErr = new stream.Transform(
69 | removeJunkTransformOptions
70 | )
71 | electronProcess.stdout.pipe(removeElectronLoggerJunkOut).pipe(process.stdout)
72 | electronProcess.stderr.pipe(removeElectronLoggerJunkErr).pipe(process.stderr)
73 | }
74 |
--------------------------------------------------------------------------------
/scripts/run-vite.ts:
--------------------------------------------------------------------------------
1 | import { createServer, InlineConfig, Plugin } from 'vite'
2 | import * as chalk from 'chalk'
3 | import config from '../vite.config'
4 | import { consoleViteMessagePrefix, srcPath } from './common'
5 |
6 | function LoggerPlugin(): Plugin {
7 | return {
8 | name: 'electron-scripts-logger',
9 | handleHotUpdate: (ctx) => {
10 | for (const file of ctx.modules) {
11 | const path = file.file.replace(srcPath, '')
12 | console.log(
13 | chalk.yellow(consoleViteMessagePrefix),
14 | chalk.yellow('hmr update'),
15 | chalk.grey(path)
16 | )
17 | }
18 | return ctx.modules
19 | }
20 | }
21 | }
22 |
23 | export default async function startViteServer(): Promise<() => Promise> {
24 | const cfg = config as InlineConfig
25 | const server = await createServer({
26 | ...cfg,
27 | configFile: false,
28 | logLevel: 'silent',
29 | plugins: [...(cfg.plugins ?? []), LoggerPlugin()]
30 | })
31 | await server.listen()
32 | const address = server.httpServer.address()
33 | if (typeof address === 'object') {
34 | const port = address.port
35 | console.log(
36 | chalk.green(consoleViteMessagePrefix),
37 | chalk.green(`Dev server running at: localhost:${port}`)
38 | )
39 | }
40 | return async () => {
41 | await server.close()
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/scripts/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "CommonJS",
4 | "target": "ES2017"
5 | },
6 | "include": ["./**/*"]
7 | }
--------------------------------------------------------------------------------
/src/common/.gitkeep:
--------------------------------------------------------------------------------
1 | replace this folder with any files you want to include from both main and renderer
2 |
3 | ``` js
4 | import ____ from '@/common/util'
5 | ```
--------------------------------------------------------------------------------
/src/index.twstyled.css:
--------------------------------------------------------------------------------
1 | /*! Generated with twstyled | https://github.com/twstyled/twstyled */
2 | /*! tailwindcss v2.0.3 | MIT License | https://tailwindcss.com *//*! modern-normalize v1.0.0 | MIT License | https://github.com/sindresorhus/modern-normalize *//*
3 | Document
4 | ========
5 | *//**
6 | Use a better box model (opinionated).
7 | */*,
8 | *::before,
9 | *::after {
10 | box-sizing: border-box;
11 | }/**
12 | Use a more readable tab size (opinionated).
13 | */:root {
14 | -moz-tab-size: 4;
15 | tab-size: 4;
16 | }/**
17 | 1. Correct the line height in all browsers.
18 | 2. Prevent adjustments of font size after orientation changes in iOS.
19 | */html {
20 | line-height: 1.15; /* 1 */
21 | -webkit-text-size-adjust: 100%; /* 2 */
22 | }/*
23 | Sections
24 | ========
25 | *//**
26 | Remove the margin in all browsers.
27 | */body {
28 | margin: 0;
29 | }/**
30 | Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3)
31 | */body {
32 | font-family:
33 | system-ui,
34 | -apple-system, /* Firefox supports this but not yet `system-ui` */
35 | 'Segoe UI',
36 | Roboto,
37 | Helvetica,
38 | Arial,
39 | sans-serif,
40 | 'Apple Color Emoji',
41 | 'Segoe UI Emoji';
42 | }/*
43 | Grouping content
44 | ================
45 | *//**
46 | 1. Add the correct height in Firefox.
47 | 2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
48 | */hr {
49 | height: 0; /* 1 */
50 | color: inherit; /* 2 */
51 | }/*
52 | Text-level semantics
53 | ====================
54 | *//**
55 | Add the correct text decoration in Chrome, Edge, and Safari.
56 | */abbr[title] {
57 | text-decoration: underline dotted;
58 | }/**
59 | Add the correct font weight in Edge and Safari.
60 | */b,
61 | strong {
62 | font-weight: bolder;
63 | }/**
64 | 1. Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3)
65 | 2. Correct the odd 'em' font sizing in all browsers.
66 | */code,
67 | kbd,
68 | samp,
69 | pre {
70 | font-family:
71 | ui-monospace,
72 | SFMono-Regular,
73 | Consolas,
74 | 'Liberation Mono',
75 | Menlo,
76 | monospace; /* 1 */
77 | font-size: 1em; /* 2 */
78 | }/**
79 | Add the correct font size in all browsers.
80 | */small {
81 | font-size: 80%;
82 | }/**
83 | Prevent 'sub' and 'sup' elements from affecting the line height in all browsers.
84 | */sub,
85 | sup {
86 | font-size: 75%;
87 | line-height: 0;
88 | position: relative;
89 | vertical-align: baseline;
90 | }sub {
91 | bottom: -0.25em;
92 | }sup {
93 | top: -0.5em;
94 | }/*
95 | Tabular data
96 | ============
97 | *//**
98 | 1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
99 | 2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
100 | */table {
101 | text-indent: 0; /* 1 */
102 | border-color: inherit; /* 2 */
103 | }/*
104 | Forms
105 | =====
106 | *//**
107 | 1. Change the font styles in all browsers.
108 | 2. Remove the margin in Firefox and Safari.
109 | */button,
110 | input,
111 | optgroup,
112 | select,
113 | textarea {
114 | font-family: inherit; /* 1 */
115 | font-size: 100%; /* 1 */
116 | line-height: 1.15; /* 1 */
117 | margin: 0; /* 2 */
118 | }/**
119 | Remove the inheritance of text transform in Edge and Firefox.
120 | 1. Remove the inheritance of text transform in Firefox.
121 | */button,
122 | select { /* 1 */
123 | text-transform: none;
124 | }/**
125 | Correct the inability to style clickable types in iOS and Safari.
126 | */button,
127 | [type='button'],
128 | [type='reset'],
129 | [type='submit'] {
130 | -webkit-appearance: button;
131 | }/**
132 | Remove the inner border and padding in Firefox.
133 | */::-moz-focus-inner {
134 | border-style: none;
135 | padding: 0;
136 | }/**
137 | Restore the focus styles unset by the previous rule.
138 | */:-moz-focusring {
139 | outline: 1px dotted ButtonText;
140 | }/**
141 | Remove the additional ':invalid' styles in Firefox.
142 | See: https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737
143 | */:-moz-ui-invalid {
144 | box-shadow: none;
145 | }/**
146 | Remove the padding so developers are not caught out when they zero out 'fieldset' elements in all browsers.
147 | */legend {
148 | padding: 0;
149 | }/**
150 | Add the correct vertical alignment in Chrome and Firefox.
151 | */progress {
152 | vertical-align: baseline;
153 | }/**
154 | Correct the cursor style of increment and decrement buttons in Safari.
155 | */::-webkit-inner-spin-button,
156 | ::-webkit-outer-spin-button {
157 | height: auto;
158 | }/**
159 | 1. Correct the odd appearance in Chrome and Safari.
160 | 2. Correct the outline style in Safari.
161 | */[type='search'] {
162 | -webkit-appearance: textfield; /* 1 */
163 | outline-offset: -2px; /* 2 */
164 | }/**
165 | Remove the inner padding in Chrome and Safari on macOS.
166 | */::-webkit-search-decoration {
167 | -webkit-appearance: none;
168 | }/**
169 | 1. Correct the inability to style clickable types in iOS and Safari.
170 | 2. Change font properties to 'inherit' in Safari.
171 | */::-webkit-file-upload-button {
172 | -webkit-appearance: button; /* 1 */
173 | font: inherit; /* 2 */
174 | }/*
175 | Interactive
176 | ===========
177 | *//*
178 | Add the correct display in Chrome and Safari.
179 | */summary {
180 | display: list-item;
181 | }/**
182 | * Manually forked from SUIT CSS Base: https://github.com/suitcss/base
183 | * A thin layer on top of normalize.css that provides a starting point more
184 | * suitable for web applications.
185 | *//**
186 | * Removes the default spacing and border for appropriate elements.
187 | */blockquote,
188 | dl,
189 | dd,
190 | h1,
191 | h2,
192 | h3,
193 | h4,
194 | h5,
195 | h6,
196 | hr,
197 | figure,
198 | p,
199 | pre {
200 | margin: 0;
201 | }button {
202 | background-color: transparent;
203 | background-image: none;
204 | }/**
205 | * Work around a Firefox/IE bug where the transparent `button` background
206 | * results in a loss of the default `button` focus styles.
207 | */button:focus {
208 | outline: 1px dotted;
209 | outline: 5px auto -webkit-focus-ring-color;
210 | }fieldset {
211 | margin: 0;
212 | padding: 0;
213 | }ol,
214 | ul {
215 | list-style: none;
216 | margin: 0;
217 | padding: 0;
218 | }/**
219 | * Tailwind custom reset styles
220 | *//**
221 | * 1. Use the user's configured `sans` font-family (with Tailwind's default
222 | * sans-serif font stack as a fallback) as a sane default.
223 | * 2. Use Tailwind's default "normal" line-height so the user isn't forced
224 | * to override it to ensure consistency even when using the default theme.
225 | */html {
226 | font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; /* 1 */
227 | line-height: 1.5; /* 2 */
228 | }/**
229 | * Inherit font-family and line-height from `html` so users can set them as
230 | * a class directly on the `html` element.
231 | */body {
232 | font-family: inherit;
233 | line-height: inherit;
234 | }/**
235 | * 1. Prevent padding and border from affecting element width.
236 | *
237 | * We used to set this in the html element and inherit from
238 | * the parent element for everything else. This caused issues
239 | * in shadow-dom-enhanced elements like where the content
240 | * is wrapped by a div with box-sizing set to `content-box`.
241 | *
242 | * https://github.com/mozdevs/cssremedy/issues/4
243 | *
244 | *
245 | * 2. Allow adding a border to an element by just adding a border-width.
246 | *
247 | * By default, the way the browser specifies that an element should have no
248 | * border is by setting it's border-style to `none` in the user-agent
249 | * stylesheet.
250 | *
251 | * In order to easily add borders to elements by just setting the `border-width`
252 | * property, we change the default border-style for all elements to `solid`, and
253 | * use border-width to hide them instead. This way our `border` utilities only
254 | * need to set the `border-width` property instead of the entire `border`
255 | * shorthand, making our border utilities much more straightforward to compose.
256 | *
257 | * https://github.com/tailwindcss/tailwindcss/pull/116
258 | */*,
259 | ::before,
260 | ::after {
261 | box-sizing: border-box; /* 1 */
262 | border-width: 0; /* 2 */
263 | border-style: solid; /* 2 */
264 | border-color: #e5e7eb; /* 2 */
265 | }/*
266 | * Ensure horizontal rules are visible by default
267 | */hr {
268 | border-top-width: 1px;
269 | }/**
270 | * Undo the `border-style: none` reset that Normalize applies to images so that
271 | * our `border-{width}` utilities have the expected effect.
272 | *
273 | * The Normalize reset is unnecessary for us since we default the border-width
274 | * to 0 on all elements.
275 | *
276 | * https://github.com/tailwindcss/tailwindcss/issues/362
277 | */img {
278 | border-style: solid;
279 | }textarea {
280 | resize: vertical;
281 | }input::placeholder,
282 | textarea::placeholder {
283 | opacity: 1;
284 | color: #9ca3af;
285 | }button,
286 | [role="button"] {
287 | cursor: pointer;
288 | }table {
289 | border-collapse: collapse;
290 | }h1,
291 | h2,
292 | h3,
293 | h4,
294 | h5,
295 | h6 {
296 | font-size: inherit;
297 | font-weight: inherit;
298 | }/**
299 | * Reset links to optimize for opt-in styling instead of
300 | * opt-out.
301 | */a {
302 | color: inherit;
303 | text-decoration: inherit;
304 | }/**
305 | * Reset form element properties that are easy to forget to
306 | * style explicitly so you don't inadvertently introduce
307 | * styles that deviate from your design system. These styles
308 | * supplement a partial reset that is already applied by
309 | * normalize.css.
310 | */button,
311 | input,
312 | optgroup,
313 | select,
314 | textarea {
315 | padding: 0;
316 | line-height: inherit;
317 | color: inherit;
318 | }/**
319 | * Use the configured 'mono' font family for elements that
320 | * are expected to be rendered with a monospace font, falling
321 | * back to the system monospace stack if there is no configured
322 | * 'mono' font family.
323 | */pre,
324 | code,
325 | kbd,
326 | samp {
327 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
328 | }/**
329 | * Make replaced elements `display: block` by default as that's
330 | * the behavior you want almost all of the time. Inspired by
331 | * CSS Remedy, with `svg` added as well.
332 | *
333 | * https://github.com/mozdevs/cssremedy/issues/14
334 | */img,
335 | svg,
336 | video,
337 | canvas,
338 | audio,
339 | iframe,
340 | embed,
341 | object {
342 | display: block;
343 | vertical-align: middle;
344 | }/**
345 | * Constrain images and videos to the parent width and preserve
346 | * their instrinsic aspect ratio.
347 | *
348 | * https://github.com/mozdevs/cssremedy/issues/14
349 | */img,
350 | video {
351 | max-width: 100%;
352 | height: auto;
353 | }
354 | * {
355 | --tw-shadow: 0 0 #0000
356 | }
357 | * {
358 | --tw-ring-inset: var(--tw-empty,/*!*/ /*!*/);
359 | --tw-ring-offset-width: 0px;
360 | --tw-ring-offset-color: #fff;
361 | --tw-ring-color: rgba(59, 130, 246, 0.5);
362 | --tw-ring-offset-shadow: 0 0 #0000;
363 | --tw-ring-shadow: 0 0 #0000
364 | }
365 | @keyframes spin {
366 | to {
367 | transform: rotate(360deg)
368 | }
369 | }
370 | @keyframes ping {
371 | 75%, 100% {
372 | transform: scale(2);
373 | opacity: 0
374 | }
375 | }
376 | @keyframes pulse {
377 | 50% {
378 | opacity: .5
379 | }
380 | }
381 | @keyframes bounce {
382 | 0%, 100% {
383 | transform: translateY(-25%);
384 | animation-timing-function: cubic-bezier(0.8,0,1,1)
385 | }
386 | 50% {
387 | transform: none;
388 | animation-timing-function: cubic-bezier(0,0,0.2,1)
389 | }
390 | }
391 | .border-primary-800 {
392 | --tw-border-opacity: 1;
393 | border-color: rgba(32, 73, 89, var(--tw-border-opacity))
394 | }
395 | .border-b {
396 | border-bottom-width: 1px
397 | }
398 | .flex {
399 | display: flex
400 | }
401 | .flex-col {
402 | flex-direction: column
403 | }
404 | .items-center {
405 | align-items: center
406 | }
407 | .justify-center {
408 | justify-content: center
409 | }
410 | .h-12 {
411 | height: 3rem
412 | }
413 | .h-48 {
414 | height: 12rem
415 | }
416 | .h-full {
417 | height: 100%
418 | }
419 | .h-screen {
420 | height: 100vh
421 | }
422 | .ml-5 {
423 | margin-left: 1.25rem
424 | }
425 | .pb-0 {
426 | padding-bottom: 0px
427 | }
428 | .pt-12 {
429 | padding-top: 3rem
430 | }
431 | .fixed {
432 | position: fixed
433 | }
434 | .top-0 {
435 | top: 0px
436 | }
437 | .shadow-xl {
438 | --tw-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
439 | box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)
440 | }
441 | .w-screen {
442 | width: 100vw
443 | }
444 | .z-0 {
445 | z-index: 0
446 | }
--------------------------------------------------------------------------------
/src/main/index.ts:
--------------------------------------------------------------------------------
1 | import { app, BrowserWindow } from 'electron'
2 | import type { BrowserWindowConstructorOptions } from 'electron'
3 | import contextMenu from 'electron-context-menu'
4 | import windowStateKeeper from 'electron-window-state'
5 | import { getTwConfig, getTwConfigPath } from '@twstyled/util'
6 |
7 | const resolvedTailwindConfig = getTwConfig(getTwConfigPath())
8 |
9 | const isDevelopment = !app.isPackaged
10 |
11 | function createWindow() {
12 | const windowOptions: BrowserWindowConstructorOptions = {
13 | minWidth: 800,
14 | minHeight: 600,
15 | backgroundColor: resolvedTailwindConfig.theme.colors.primary[800],
16 | titleBarStyle: 'hidden',
17 | autoHideMenuBar: true,
18 | trafficLightPosition: {
19 | x: 20,
20 | y: 32
21 | },
22 | webPreferences: {
23 | contextIsolation: true,
24 | devTools: isDevelopment,
25 | spellcheck: false,
26 | nodeIntegration: true
27 | },
28 | show: false
29 | }
30 |
31 | contextMenu({
32 | showSearchWithGoogle: false,
33 | showCopyImage: false,
34 | prepend: (defaultActions, params, browserWindow) => [
35 | {
36 | label: 'its like magic 💥'
37 | }
38 | ]
39 | })
40 |
41 | const windowState = windowStateKeeper({
42 | defaultWidth: windowOptions.minWidth,
43 | defaultHeight: windowOptions.minHeight
44 | })
45 |
46 | const browserWindow = new BrowserWindow({
47 | ...windowOptions,
48 | x: windowState.x,
49 | y: windowState.y,
50 | width: windowState.width,
51 | height: windowState.height
52 | })
53 |
54 | windowState.manage(browserWindow)
55 |
56 | browserWindow.once('ready-to-show', () => {
57 | browserWindow.show()
58 | browserWindow.focus()
59 | })
60 |
61 | const port = process.env.PORT || 3000
62 |
63 | if (isDevelopment) {
64 | void browserWindow.loadURL(`http://localhost:${port}`)
65 | } else {
66 | void browserWindow.loadFile('./index.html')
67 | }
68 | }
69 |
70 | void app.whenReady().then(createWindow)
71 |
72 | app.on('window-all-closed', () => {
73 | app.quit()
74 | })
75 |
76 | app.on('activate', () => {
77 | if (BrowserWindow.getAllWindows().length === 0) {
78 | createWindow()
79 | }
80 | })
81 |
--------------------------------------------------------------------------------
/src/main/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "target": "ES2018",
5 | "module": "CommonJS",
6 | "sourceMap": false,
7 | "outDir": "../../dist",
8 | "baseUrl": "../../src",
9 | "paths": {
10 | "@/main/*": ["main/*"],
11 | "@/common/*": ["common/*"]
12 | },
13 | },
14 | "include": ["../main/**/*", "../common/**/*"],
15 | "exclude": ["../renderer/**/*"]
16 | }
17 |
--------------------------------------------------------------------------------
/src/renderer/App.tsx:
--------------------------------------------------------------------------------
1 | import type * as React from 'react'
2 | import { motion } from 'framer-motion'
3 | import TopBar from './components/top-bar'
4 | import logo from './logo.png'
5 |
6 | const containerMotion = {
7 | initial: 'hidden',
8 | animate: 'visible',
9 | variants: {
10 | hidden: { opacity: 1, scale: 0 },
11 | visible: {
12 | opacity: 1,
13 | scale: 1,
14 | transition: {
15 | delayChildren: 0.3,
16 | staggerChildren: 0.2
17 | }
18 | }
19 | }
20 | }
21 |
22 | function App() {
23 | return (
24 |
25 |
26 |
27 |
28 |

29 |
30 |
31 |
32 | )
33 | }
34 |
35 | export default App
36 |
--------------------------------------------------------------------------------
/src/renderer/components/top-bar.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from '@twstyled/core'
2 |
3 | const TopBar = styled.nav`
4 | -webkit-app-region: drag;
5 | @tailwind w-screen h-12 border-primary-800 border-b items-center flex shadow-xl z-0 fixed top-0;
6 | `
7 |
8 | export default TopBar
9 |
--------------------------------------------------------------------------------
/src/renderer/favicon.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/src/renderer/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
3 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
4 | sans-serif;
5 | -webkit-font-smoothing: antialiased;
6 | -moz-osx-font-smoothing: grayscale;
7 |
8 | /* used for behaving more like an app, selection inside inputs and textareas still work in chromium */
9 | user-select: none;
10 | }
11 |
12 | code {
13 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
14 | monospace;
15 | }
16 |
--------------------------------------------------------------------------------
/src/renderer/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite App
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/renderer/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twstyled/electron-vite-react/045bf9642e8e2777c32c62bf937078c7eb061963/src/renderer/logo.png
--------------------------------------------------------------------------------
/src/renderer/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/renderer/main.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from 'react'
2 | import ReactDOM from 'react-dom'
3 | import '../index.twstyled.css'
4 | import './index.css'
5 | import App from './App'
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | )
13 |
--------------------------------------------------------------------------------
/src/renderer/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
5 | "types": ["vite/client", "@twstyled/core"],
6 | "target": "esnext",
7 | "module": "esnext",
8 | "moduleResolution": "node",
9 | "importHelpers": true,
10 | "jsx": "preserve",
11 | "esModuleInterop": true,
12 | "sourceMap": true,
13 | "baseUrl": "../../src",
14 | "paths": {
15 | "@/renderer/*": ["renderer/*"],
16 | "@/common/*": ["common/*"]
17 | },
18 | "allowSyntheticDefaultImports": true
19 | },
20 | "include": ["../renderer/**/*", "../common/**/*"],
21 | "exclude": ["../main/**/*"]
22 | }
23 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | purge: false,
3 | darkMode: false, // or 'media' or 'class'
4 | theme: {
5 | fontFamily: {
6 | // caption uses the systemfont so it looks more native
7 | display: ['caption'],
8 | body: ['caption'],
9 | },
10 | extend: {
11 | colors: {
12 | primary: {
13 | 100: '#91C4D7',
14 | 200: '#65ACC8',
15 | 300: '#4FA0C0',
16 | 400: '#4091B1',
17 | 500: '#387F9B',
18 | 600: '#306D85',
19 | 700: '#285B6F',
20 | 800: '#204959',
21 | 900: '#183642',
22 | },
23 | },
24 | },
25 | },
26 | variants: {
27 | outline: ['focus', 'hover'],
28 | border: ['focus', 'hover'],
29 | },
30 | plugins: [],
31 | }
32 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": false,
4 | "allowSyntheticDefaultImports": true,
5 | "esModuleInterop": true,
6 | "isolatedModules": true,
7 | "jsx": "preserve",
8 | "lib": ["es6"],
9 | "moduleResolution": "node",
10 | "module": "commonjs",
11 | "forceConsistentCasingInFileNames": true,
12 | "noImplicitReturns": true,
13 | "noImplicitThis": true,
14 | "noImplicitAny": true,
15 | "strict": true,
16 | "noUnusedLocals": false,
17 | "pretty": true,
18 | "skipLibCheck": true,
19 | "target": "ES6",
20 | "resolveJsonModule": true,
21 | "declaration": true,
22 | "sourceMap": true,
23 | "baseUrl": ".",
24 | "paths": {
25 | "@/renderer/*": ["src/renderer/*"],
26 | "@/common/*": ["src/common/*"]
27 | }
28 | },
29 | "include": [
30 | "**/*.ts",
31 | "**/*.tsx"
32 | ],
33 | "exclude": ["node_modules"]
34 | }
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { resolve } from 'path'
2 | import { defineConfig } from 'vite'
3 | import reactRefresh from '@vitejs/plugin-react-refresh'
4 | import twstyled from 'vite-plugin-twstyled'
5 |
6 | export default defineConfig({
7 | plugins: [twstyled(), reactRefresh()],
8 | base: './',
9 | root: resolve('./src/renderer'),
10 | build: {
11 | outDir: resolve('./dist'),
12 | emptyOutDir: true
13 | },
14 | resolve: {
15 | alias: [
16 | {
17 | find: '@/renderer',
18 | replacement: resolve(__dirname, 'src/renderer')
19 | },
20 | {
21 | find: '@/common',
22 | replacement: resolve(__dirname, 'src/common')
23 | }
24 | ]
25 | }
26 | })
27 |
--------------------------------------------------------------------------------