├── .editorconfig ├── .env ├── .eslintrc.js ├── .gitignore ├── .npmrc ├── .stylelintignore ├── .stylelintrc ├── .yarnrc ├── LICENSE.md ├── README.md ├── app ├── icons │ ├── icon.icns │ ├── icon.ico │ └── icon.png ├── main.js ├── menu.js ├── package.json ├── preload.js └── renderer.js ├── config-overrides.js ├── package.json ├── tsconfig.json ├── webview ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt └── src │ ├── App.css │ ├── App.test.tsx │ ├── App.tsx │ ├── index.css │ ├── index.tsx │ ├── logo.svg │ ├── react-app-env.d.ts │ ├── serviceWorker.ts │ └── views │ └── Index │ ├── index.css │ └── index.tsx └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | BROWSER=none 2 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | ecmaFeatures: { 5 | jsx: true 6 | }, 7 | project: './tsconfig.json' 8 | }, 9 | plugins: ['@typescript-eslint'], 10 | extends: [ 11 | 'react-app', 12 | 'plugin:@typescript-eslint/eslint-recommended', 13 | 'plugin:@typescript-eslint/recommended' 14 | ], 15 | rules: { 16 | '@typescript-eslint/triple-slash-reference': 0, 17 | '@typescript-eslint/no-var-requires': 0, 18 | '@typescript-eslint/no-use-before-define': 0, 19 | '@typescript-eslint/member-delimiter-style': 0, 20 | '@typescript-eslint/explicit-function-return-type': 0, 21 | 'arrow-body-style': 0, 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | /dist 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | sass_binary_site = "https://npm.taobao.org/mirrors/node-sass/" 2 | phantomjs_cdnurl = "https://npm.taobao.org/mirrors/phantomjs/" 3 | electron_mirror = "https://npm.taobao.org/mirrors/electron/" 4 | registry = "https://registry.npm.taobao.org" 5 | -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | *.jsx 2 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard", 3 | "plugins": ["stylelint-scss"], 4 | "rules": { 5 | "at-rule-no-unknown": null, 6 | "color-hex-case": null, 7 | "block-closing-brace-newline-after": null, 8 | "at-rule-empty-line-before":null, 9 | "number-no-trailing-zeros": null, 10 | "no-empty-source": null, 11 | "unit-case": null, 12 | "scss/at-rule-no-unknown": true, 13 | "no-descending-specificity": null, 14 | "selector-list-comma-newline-after": null, 15 | "value-list-comma-newline-after": null, 16 | "font-family-no-missing-generic-family-keyword": null, 17 | "declaration-block-trailing-semicolon": null 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | sass_binary_site "https://npm.taobao.org/mirrors/node-sass/" 2 | phantomjs_cdnurl "https://npm.taobao.org/mirrors/phantomjs/" 3 | electron_mirror "https://npm.taobao.org/mirrors/electron/" 4 | registry "https://registry.npm.taobao.org" 5 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | ================== 3 | 4 | Statement of Purpose 5 | --------------------- 6 | 7 | The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). 8 | 9 | Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. 10 | 11 | For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 12 | 13 | 1. Copyright and Related Rights. 14 | -------------------------------- 15 | A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: 16 | 17 | i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; 18 | ii. moral rights retained by the original author(s) and/or performer(s); 19 | iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; 20 | iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; 21 | v. rights protecting the extraction, dissemination, use and reuse of data in a Work; 22 | vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and 23 | vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 24 | 25 | 2. Waiver. 26 | ----------- 27 | To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 28 | 29 | 3. Public License Fallback. 30 | ---------------------------- 31 | Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 32 | 33 | 4. Limitations and Disclaimers. 34 | -------------------------------- 35 | 36 | a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. 37 | b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. 38 | c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. 39 | d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # electron-react-boilerplate 2 | 3 | `electron-react-boilerplate` -- Electron App 开发的 react 模板。 4 | 5 | [Electron Official Documentation](https://electronjs.org/) 6 | 7 | [electron-builder Official Documentation](https://www.electron.build/) 8 | 9 | # Usage 10 | 11 | ### clone 仓库 12 | 13 | ``` 14 | git@github.com:pfan123/electron-react-boilerplate.git 15 | ``` 16 | 17 | ### 安装 18 | 19 | ``` 20 | yarn start 21 | ``` 22 | 23 | ### 开发 24 | 25 | ``` 26 | yarn dev // 开发调试 27 | 28 | yarn build // 打包 electron、webview 文件 29 | 30 | yarn postinstall // 正式环境同步依赖 31 | 32 | yarn start // 预览 33 | 34 | yarn dist // 打包安装包 35 | ``` 36 | 37 | ### 构建打包 38 | 39 | `electron-builder` A complete solution to package and build a ready for distribution Electron app for macOS, Windows and Linux with “auto update” support out of the box. 40 | 41 | - [Icons](https://www.electron.build/icons) 42 | - [Auto Update](https://www.electron.build/auto-update) 43 | - [Code Signing](https://www.electron.build/code-signing) 44 | - [Multi Platform Build](https://www.electron.build/code-signing) 45 | 46 | 47 | > 48 | 49 | 50 | ## Electron开发桌面应用 51 | 52 | Electron 是一款可以利用 Web技术 开发跨平台桌面应用的框架,最初是 Github 发布的 Atom 编辑器衍生出的 Atom Shell,后更名为 Electron。Electron 提供了一个能通过 JavaScript 和 HTML 创建桌面应用的平台,同时集成 Node 来授予网页访问底层系统的权限。目前常见的有[NW](http://nwjs.io/)、[heX](http://hex.youdao.com/zh-cn/index.html)、[Electron](http://electron.atom.io/)、[MacGap](http://macgapproject.github.io/)、[MacGap案例switchhost](https://github.com/oldj/SwitchHosts),可以打造桌面应用。 53 | 54 | 55 | 56 | ![electron结构](http://img.pfan123.com/electron-construct.jpg) 57 | 58 | ## electron项目 59 | 60 | [electronic-wechat](https://github.com/geeeeeeeeek/electronic-wechat/blob/master/README_zh.md?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io) 61 | 62 | [WeFlow](https://github.com/weixin/WeFlow) 63 | 64 | [Hozz](https://github.com/ppoffice/Hozz) 65 | 66 | [hummingbird](https://github.com/stormtea123/hummingbird) 67 | 68 | [ElectronOCR](https://github.com/wxyyxc1992/ElectronOCR) 69 | 70 | [N1](https://github.com/nylas/N1) 71 | 72 | 73 | 74 | ## Electron 运行流程 75 | 76 | ![Electron 运行流程](http://img.pfan123.com/electron_process.jpg) 77 | 78 | ## Electron 主进程和渲染进程 79 | 80 | - `Electron` 运行 `package.json` 的 `main` 脚本的进程被称为主进程。 81 | 82 | - 在主进程中运行的脚本通过创建 `web` 页面来展示用户界面。 一个 `Electron` 应用总是有且只有一个主进程。 83 | 84 | - 由于 `Electron` 使用了 `Chromium`来展示 `web` 页面,所以 `Chromium` 的多进程架构也被使用到。 每个 `Electron` 中的 `web` 页面运行在它自己的渲染进程中。 85 | 86 | - 主进程使用 `BrowserWindow` 实例创建页面。每个 `BrowserWindow` 实例都在自己的渲染进程里运行页面。 当一个 `BrowserWindow`实例被销毁后,相应的渲染进程也会被终止 87 | 88 | ![electron 主进程](http://img.pfan123.com/electron_main.jpg) 89 | 90 | 91 | 92 | ## Electron 主进程和渲染进程中的模块 93 | 94 | ![electron 模块](http://img.pfan123.com/electron_api.jpg) 95 | 96 | ## Electron remote 模块 97 | 98 | `remote` 模块提供了一种在渲染进程(网页)和主进程之间进行进程间通讯 (`IPC`) 的简便途径 99 | 100 | `Electron` 中, 与 `GUI` 相关的模块(如 `dialog`, `menu` 等)只存在于主进程,而不在渲染进程中 。为了能从渲染进程中使用它们,需要用`ipc`模块来给主进程发送进程间消息。使用 `remote` 模块,可以调用主进程对象的方法,而无需显式地发送进程间消息,这类似于 `Java` 的 `RMI` 101 | 102 | ## Electron快速上手demo 103 | 104 | [electron-boilerplate](https://github.com/chuyik/electron-boilerplate) 105 | 106 | [electron-connect](https://github.com/Quramy/electron-connect) 107 | 108 | 109 | ## 参考资料: 110 | 111 | - [用 Electron 开发桌面应用](http://gold.xitu.io/entry/5603549660b20d2d666cf0d2) 112 | 113 | - [Electron API文档](https://github.com/electron/electron/tree/master/docs/api) 114 | 115 | - [Awesome Electron 大全](https://github.com/sindresorhus/awesome-electron) 116 | 117 | -------------------------------------------------------------------------------- /app/icons/icon.icns: -------------------------------------------------------------------------------- 1 | icns -------------------------------------------------------------------------------- /app/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfan123/electron-react-boilerplate/8ac7de24671dadcae862859ff983271cbea5d0e7/app/icons/icon.ico -------------------------------------------------------------------------------- /app/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfan123/electron-react-boilerplate/8ac7de24671dadcae862859ff983271cbea5d0e7/app/icons/icon.png -------------------------------------------------------------------------------- /app/main.js: -------------------------------------------------------------------------------- 1 | // https://github.com/electron/electron/blob/master/docs/api/browser-window.md 2 | // https://github.com/electron/electron/tree/master/lib/browser 3 | 4 | // Modules to control application life and create native browser window 5 | const {app, BrowserWindow, Menu} = require('electron') 6 | const path = require('path') 7 | const menuTemplate = require('./menu') 8 | 9 | // Keep a global reference of the window object, if you don't, the window will 10 | // be closed automatically when the JavaScript object is garbage collected. 11 | let mainWindow 12 | 13 | function createWindow () { 14 | // Create the browser window. 15 | mainWindow = new BrowserWindow({ 16 | backgroundColor: '#fff', 17 | width: 800, 18 | height: 800, 19 | webPreferences: { 20 | preload: path.join(__dirname, 'preload.js'), 21 | devTools: true, 22 | } 23 | }) 24 | 25 | mainWindow.webContents.openDevTools () 26 | 27 | const menu = Menu.buildFromTemplate(menuTemplate) 28 | Menu.setApplicationMenu(menu) 29 | 30 | // and load the index.html of the app. 31 | if(process.env.NODE_ENV === 'dev'){ 32 | mainWindow.loadURL('http://localhost:3000/') 33 | }else { 34 | mainWindow.loadFile('./index.html') 35 | } 36 | 37 | // Open the DevTools. 38 | // mainWindow.webContents.openDevTools() 39 | 40 | // Emitted when the window is closed. 41 | mainWindow.on('closed', function () { 42 | // Dereference the window object, usually you would store windows 43 | // in an array if your app supports multi windows, this is the time 44 | // when you should delete the corresponding element. 45 | mainWindow = null 46 | }) 47 | } 48 | 49 | // This method will be called when Electron has finished 50 | // initialization and is ready to create browser windows. 51 | // Some APIs can only be used after this event occurs. 52 | app.on('ready', createWindow) 53 | 54 | // Quit when all windows are closed. 55 | app.on('window-all-closed', function () { 56 | // On macOS it is common for applications and their menu bar 57 | // to stay active until the user quits explicitly with Cmd + Q 58 | if (process.platform !== 'darwin') app.quit() 59 | }) 60 | 61 | app.on('activate', function () { 62 | // On macOS it's common to re-create a window in the app when the 63 | // dock icon is clicked and there are no other windows open. 64 | if (mainWindow === null) createWindow() 65 | }) 66 | 67 | // In this file you can include the rest of your app's specific main process 68 | // code. You can also put them in separate files and require them here. 69 | -------------------------------------------------------------------------------- /app/menu.js: -------------------------------------------------------------------------------- 1 | const { app } = require('electron') 2 | 3 | const isMac = process.platform === 'darwin' 4 | 5 | const menuTemplate = [ 6 | // { role: 'appMenu' } 7 | ...(isMac ? [{ 8 | label: app.name, 9 | submenu: [ 10 | { role: 'about' }, 11 | { type: 'separator' }, 12 | { role: 'services' }, 13 | { type: 'separator' }, 14 | { role: 'hide' }, 15 | { role: 'hideothers' }, 16 | { role: 'unhide' }, 17 | { type: 'separator' }, 18 | { role: 'quit' } 19 | ] 20 | }] : []), 21 | // { role: 'fileMenu' } 22 | { 23 | label: '打开File', 24 | submenu: [ 25 | isMac ? { role: 'close' } : { role: 'quit' } 26 | ] 27 | }, 28 | // { role: 'editMenu' } 29 | { 30 | label: 'Edit', 31 | submenu: [ 32 | { role: 'undo' }, 33 | { role: 'redo' }, 34 | { type: 'separator' }, 35 | { role: 'cut' }, 36 | { role: 'copy' }, 37 | { role: 'paste' }, 38 | ...(isMac ? [ 39 | { role: 'pasteAndMatchStyle' }, 40 | { role: 'delete' }, 41 | { role: 'selectAll' }, 42 | { type: 'separator' }, 43 | { 44 | label: 'Speech', 45 | submenu: [ 46 | { role: 'startspeaking' }, 47 | { role: 'stopspeaking' } 48 | ] 49 | } 50 | ] : [ 51 | { role: 'delete' }, 52 | { type: 'separator' }, 53 | { role: 'selectAll' } 54 | ]) 55 | ] 56 | }, 57 | // { role: 'viewMenu' } 58 | { 59 | label: 'View', 60 | submenu: [ 61 | { role: 'reload' }, 62 | { role: 'forcereload' }, 63 | { role: 'toggledevtools' }, 64 | { type: 'separator' }, 65 | { role: 'resetzoom' }, 66 | { role: 'zoomin' }, 67 | { role: 'zoomout' }, 68 | { type: 'separator' }, 69 | { role: 'togglefullscreen' } 70 | ] 71 | }, 72 | // { role: 'windowMenu' } 73 | { 74 | label: 'Window', 75 | submenu: [ 76 | { role: 'minimize' }, 77 | { role: 'zoom' }, 78 | ...(isMac ? [ 79 | { type: 'separator' }, 80 | { role: 'front' }, 81 | { type: 'separator' }, 82 | { role: 'window' } 83 | ] : [ 84 | { role: 'close' } 85 | ]) 86 | ] 87 | }, 88 | { 89 | role: 'help', 90 | submenu: [ 91 | { 92 | label: 'Learn More', 93 | click: async () => { 94 | const { shell } = require('electron') 95 | await shell.openExternal('https://electronjs.org') 96 | } 97 | } 98 | ] 99 | } 100 | ] 101 | 102 | 103 | module.exports = menuTemplate 104 | -------------------------------------------------------------------------------- /app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "app", 3 | "version" : "0.1.0", 4 | "main" : "main.js", 5 | "description": "A minimal Electron application", 6 | "author": "pfan123", 7 | "devDependencies": { 8 | "electron": "9.2.0" 9 | }, 10 | "build": { 11 | "compression": "maximum", 12 | "files": [ 13 | "**/*", 14 | "!**/node_modules/*/{CHANGELOG.md,README.md,README,readme.md,readme}", 15 | "!**/node_modules/*/{test,__tests__,tests,powered-test,example,examples}", 16 | "!**/node_modules/*.d.ts", 17 | "!**/node_modules/.bin", 18 | "!**/*.{iml,o,hprof,orig,pyc,pyo,rbc,swp,csproj,sln,xproj}", 19 | "!.editorconfig", 20 | "!**/._*", 21 | "!**/{.DS_Store,.git,.hg,.svn,CVS,RCS,SCCS,.gitignore,.gitattributes}", 22 | "!**/{__pycache__,thumbs.db,.flowconfig,.idea,.vs,.nyc_output}", 23 | "!**/{appveyor.yml,.travis.yml,circle.yml}", 24 | "!**/{npm-debug.log,yarn.lock,.yarn-integrity,.yarn-metadata.json}" 25 | ], 26 | "appId": "com.electron.app", 27 | "productName": "app", 28 | "copyright": " Copyright © 2019 @pfan123", 29 | "mac": { 30 | "category": "public.app-category.developer-tools", 31 | "target": "dmg", 32 | "icon": "./icons/icon.icns" 33 | }, 34 | "win": { 35 | "icon": "./icons/icon.ico", 36 | "target": [ 37 | { 38 | "target": "nsis", 39 | "arch": [ 40 | "x64", 41 | "ia32" 42 | ] 43 | } 44 | ] 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/preload.js: -------------------------------------------------------------------------------- 1 | // All of the Node.js APIs are available in the preload process. 2 | // It has the same sandbox as a Chrome extension. 3 | window.addEventListener('DOMContentLoaded', () => { 4 | const replaceText = (selector, text) => { 5 | const element = document.getElementById(selector) 6 | if (element) element.innerText = text 7 | } 8 | 9 | for (const type of ['chrome', 'node', 'electron']) { 10 | replaceText(`${type}-version`, process.versions[type]) 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /app/renderer.js: -------------------------------------------------------------------------------- 1 | // This file is required by the index.html file and will 2 | // be executed in the renderer process for that window. 3 | // No Node.js APIs are available in this process because 4 | // `nodeIntegration` is turned off. Use `preload.js` to 5 | // selectively enable features needed in the rendering 6 | // process. 7 | console.error(221121122222); 8 | -------------------------------------------------------------------------------- /config-overrides.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | 4 | module.exports = { 5 | paths: (paths, env) => { 6 | paths.appBuild = path.resolve(__dirname, './dist') 7 | paths.appPublic = path.resolve(__dirname, './webview/public') 8 | paths.appHtml = path.resolve(__dirname, './webview/public/index.html') 9 | paths.appIndexJs = path.resolve(__dirname, './webview/src/index.tsx') 10 | paths.appSrc = path.resolve(__dirname, './webview/src') 11 | paths.testsSetup = path.resolve(__dirname, './webview/src/setupTests.js') 12 | paths.proxySetup = path.resolve(__dirname, './webview/src/setupProxy.js') 13 | paths.appTypeDeclarations = path.resolve(__dirname, './webview/src/react-app-env.d.ts') 14 | // paths.appBuild = path.resolve(__dirname, '../dist') 15 | return paths 16 | }, 17 | devServer: (configFunction) => { 18 | return function(proxy, allowedHost) { 19 | // Create the default config by calling configFunction with the proxy/allowedHost parameters 20 | const config = configFunction(proxy, allowedHost) 21 | 22 | config.setup = (app, server) => { 23 | app.get('/renderer.js', (req, res) => { 24 | res.setHeader('Content-Type', 'text/javascript') 25 | const content = fs.readFileSync('./app/renderer.js', 'utf8') 26 | res.send(content) 27 | }) 28 | } 29 | 30 | // Return your customised Webpack Development Server config. 31 | return config 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-quick-start", 3 | "version": "1.0.0", 4 | "description": "A minimal Electron application", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "NODE_ENV=dev node ./build/dev.js", 8 | "build": "node ./build/build.js", 9 | "start": "electron ./dist/", 10 | "electron": "electron ./app", 11 | "postinstall": "cd dist && electron-builder install-app-deps", 12 | "pack": "electron-builder --dir", 13 | "dist": "cd dist && electron-builder -mw", 14 | "start:webview": "react-app-rewired start", 15 | "build:webview": "react-app-rewired build", 16 | "test": "react-app-rewired test", 17 | "eject": "react-scripts eject", 18 | "deploy": "standard-version --release-as patch && git push --follow-tags origin HEAD" 19 | }, 20 | "husky": { 21 | "hooks": { 22 | "pre-commit": "lint-staged", 23 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 24 | } 25 | }, 26 | "lint-staged": { 27 | "webview/src/**/*.{js,jsx,ts,tsx}": [ 28 | "eslint --fix", 29 | "git add" 30 | ], 31 | "webview/src/**/*.scss": [ 32 | "stylelint --syntax scss --fix", 33 | "git add" 34 | ] 35 | }, 36 | "commitlint": { 37 | "extends": [ 38 | "@commitlint/config-conventional" 39 | ] 40 | }, 41 | "eslintConfig": { 42 | "extends": "react-app" 43 | }, 44 | "browserslist": { 45 | "production": [ 46 | ">0.2%", 47 | "not dead", 48 | "not op_mini all" 49 | ], 50 | "development": [ 51 | "last 1 chrome version", 52 | "last 1 firefox version", 53 | "last 1 safari version" 54 | ] 55 | }, 56 | "keywords": [ 57 | "Electron", 58 | "quick", 59 | "start", 60 | "tutorial", 61 | "demo" 62 | ], 63 | "dependencies": { 64 | "@commitlint/config-conventional": "^8.2.0", 65 | "@types/jest": "24.0.23", 66 | "@types/node": "12.12.14", 67 | "@types/react": "16.9.13", 68 | "@types/react-dom": "16.9.4", 69 | "@types/react-router-dom": "^5.1.3", 70 | "babel-eslint": "10.x", 71 | "chokidar": "^3.3.0", 72 | "commitlint": "^8.2.0", 73 | "eslint-config-react-app": "^5.0.2", 74 | "eslint-plugin-flowtype": "3.x", 75 | "eslint-plugin-import": "2.x", 76 | "eslint-plugin-jsx-a11y": "6.x", 77 | "eslint-plugin-react": "7.x", 78 | "eslint-plugin-react-hooks": "1.x", 79 | "fs-extra": "^8.1.0", 80 | "husky": "^3.1.0", 81 | "lint-staged": "^9.5.0", 82 | "lowdb": "^1.0.0", 83 | "react": "^16.12.0", 84 | "react-app-rewired": "^2.1.5", 85 | "react-dom": "^16.12.0", 86 | "react-router-dom": "^5.1.2", 87 | "react-scripts": "3.2.0", 88 | "rimraf": "^3.0.0", 89 | "standard-version": "^8.0.1", 90 | "typescript": "3.7.2" 91 | }, 92 | "devDependencies": { 93 | "@typescript-eslint/eslint-plugin": "2.x", 94 | "@typescript-eslint/parser": "2.x", 95 | "electron": "^7.1.2", 96 | "electron-builder": "^21.2.0", 97 | "eslint": "6.x", 98 | "stylelint": "11.0.0", 99 | "stylelint-config-standard": "^19.0.0", 100 | "stylelint-scss": "3.12.1" 101 | }, 102 | "homepage": "./", 103 | "repository": "https://github.com/electron/electron-quick-start", 104 | "author": "pfan123", 105 | "license": "CC0-1.0" 106 | } 107 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "noEmit": true, 20 | "jsx": "react" 21 | }, 22 | "include": [ 23 | "webview/src", 24 | "app/", 25 | "build/", 26 | "config-overrides.js/" 27 | ], 28 | "exclude": [ 29 | "node_modules" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /webview/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfan123/electron-react-boilerplate/8ac7de24671dadcae862859ff983271cbea5d0e7/webview/public/favicon.ico -------------------------------------------------------------------------------- /webview/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |

Hello World!

32 | We are using Node.js , 33 | Chromium , 34 | and Electron . 35 |
36 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /webview/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfan123/electron-react-boilerplate/8ac7de24671dadcae862859ff983271cbea5d0e7/webview/public/logo192.png -------------------------------------------------------------------------------- /webview/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfan123/electron-react-boilerplate/8ac7de24671dadcae862859ff983271cbea5d0e7/webview/public/logo512.png -------------------------------------------------------------------------------- /webview/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /webview/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /webview/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | } 8 | 9 | .App-header { 10 | background-color: #282c34; 11 | min-height: 100vh; 12 | display: flex; 13 | flex-direction: column; 14 | align-items: center; 15 | justify-content: center; 16 | font-size: calc(10px + 2vmin); 17 | color: white; 18 | } 19 | 20 | .App-link { 21 | color: #09d3ac; 22 | } 23 | -------------------------------------------------------------------------------- /webview/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /webview/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | BrowserRouter as Router, 4 | Switch, 5 | Route, 6 | } from "react-router-dom"; 7 | 8 | import Index from './views/Index' 9 | 10 | const App: React.FC = () => { 11 | return ( 12 | 13 | 14 | } /> 15 | 16 | 17 | ) 18 | } 19 | 20 | export default App; 21 | -------------------------------------------------------------------------------- /webview/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /webview/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | -------------------------------------------------------------------------------- /webview/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /webview/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /webview/src/serviceWorker.ts: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | type Config = { 24 | onSuccess?: (registration: ServiceWorkerRegistration) => void; 25 | onUpdate?: (registration: ServiceWorkerRegistration) => void; 26 | }; 27 | 28 | export function register(config?: Config) { 29 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 30 | // The URL constructor is available in all browsers that support SW. 31 | const publicUrl = new URL( 32 | (process as { env: { [key: string]: string } }).env.PUBLIC_URL, 33 | window.location.href 34 | ); 35 | if (publicUrl.origin !== window.location.origin) { 36 | // Our service worker won't work if PUBLIC_URL is on a different origin 37 | // from what our page is served on. This might happen if a CDN is used to 38 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 39 | return; 40 | } 41 | 42 | window.addEventListener('load', () => { 43 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 44 | 45 | if (isLocalhost) { 46 | // This is running on localhost. Let's check if a service worker still exists or not. 47 | checkValidServiceWorker(swUrl, config); 48 | 49 | // Add some additional logging to localhost, pointing developers to the 50 | // service worker/PWA documentation. 51 | navigator.serviceWorker.ready.then(() => { 52 | console.log( 53 | 'This web app is being served cache-first by a service ' + 54 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 55 | ); 56 | }); 57 | } else { 58 | // Is not localhost. Just register service worker 59 | registerValidSW(swUrl, config); 60 | } 61 | }); 62 | } 63 | } 64 | 65 | function registerValidSW(swUrl: string, config?: Config) { 66 | navigator.serviceWorker 67 | .register(swUrl) 68 | .then(registration => { 69 | registration.onupdatefound = () => { 70 | const installingWorker = registration.installing; 71 | if (installingWorker == null) { 72 | return; 73 | } 74 | installingWorker.onstatechange = () => { 75 | if (installingWorker.state === 'installed') { 76 | if (navigator.serviceWorker.controller) { 77 | // At this point, the updated precached content has been fetched, 78 | // but the previous service worker will still serve the older 79 | // content until all client tabs are closed. 80 | console.log( 81 | 'New content is available and will be used when all ' + 82 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 83 | ); 84 | 85 | // Execute callback 86 | if (config && config.onUpdate) { 87 | config.onUpdate(registration); 88 | } 89 | } else { 90 | // At this point, everything has been precached. 91 | // It's the perfect time to display a 92 | // "Content is cached for offline use." message. 93 | console.log('Content is cached for offline use.'); 94 | 95 | // Execute callback 96 | if (config && config.onSuccess) { 97 | config.onSuccess(registration); 98 | } 99 | } 100 | } 101 | }; 102 | }; 103 | }) 104 | .catch(error => { 105 | console.error('Error during service worker registration:', error); 106 | }); 107 | } 108 | 109 | function checkValidServiceWorker(swUrl: string, config?: Config) { 110 | // Check if the service worker can be found. If it can't reload the page. 111 | fetch(swUrl) 112 | .then(response => { 113 | // Ensure service worker exists, and that we really are getting a JS file. 114 | const contentType = response.headers.get('content-type'); 115 | if ( 116 | response.status === 404 || 117 | (contentType != null && contentType.indexOf('javascript') === -1) 118 | ) { 119 | // No service worker found. Probably a different app. Reload the page. 120 | navigator.serviceWorker.ready.then(registration => { 121 | registration.unregister().then(() => { 122 | window.location.reload(); 123 | }); 124 | }); 125 | } else { 126 | // Service worker found. Proceed as normal. 127 | registerValidSW(swUrl, config); 128 | } 129 | }) 130 | .catch(() => { 131 | console.log( 132 | 'No internet connection found. App is running in offline mode.' 133 | ); 134 | }); 135 | } 136 | 137 | export function unregister() { 138 | if ('serviceWorker' in navigator) { 139 | navigator.serviceWorker.ready.then(registration => { 140 | registration.unregister(); 141 | }); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /webview/src/views/Index/index.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | } 8 | 9 | .App-header { 10 | background-color: #282c34; 11 | min-height: 100vh; 12 | display: flex; 13 | flex-direction: column; 14 | align-items: center; 15 | justify-content: center; 16 | font-size: calc(10px + 2vmin); 17 | color: white; 18 | } 19 | 20 | .App-link { 21 | color: #09d3ac; 22 | } 23 | -------------------------------------------------------------------------------- /webview/src/views/Index/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import logo from '../../logo.svg'; 3 | import './index.css'; 4 | 5 | const Index: React.FC = () => { 6 | return ( 7 |
8 |
9 | logo 10 |

11 | Edit src/App.tsx and save to reload. 12 |

13 | 19 | Learn electron-react-boilerplate 20 | 21 |
22 |
23 | ) 24 | } 25 | 26 | export default Index 27 | --------------------------------------------------------------------------------