├── .npmrc ├── .gitattributes ├── .gitignore ├── .github ├── funding.yml └── workflows │ └── main.yml ├── test ├── index.html ├── readme.md └── main │ └── index.js ├── .editorconfig ├── index.test-d.ts ├── index.d.ts ├── license ├── package.json ├── readme.md └── index.js /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn.lock 3 | -------------------------------------------------------------------------------- /.github/funding.yml: -------------------------------------------------------------------------------- 1 | github: sindresorhus 2 | open_collective: sindresorhus 3 | custom: https://sindresorhus.com/donate 4 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Test

8 | 9 | 10 | -------------------------------------------------------------------------------- /test/readme.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | The tests are manual as I have no idea how to automate this. 4 | 5 | From the root directory, run `npm start`, and then edit `index.html` to see the window refresh and `index.js` to see the app restart. 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.yml] 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /test/main/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const path = require('path'); 3 | const {app, BrowserWindow} = require('electron'); 4 | 5 | require('../../index.js')(module, { 6 | debug: true 7 | }); 8 | 9 | let mainWindow; 10 | 11 | (async () => { 12 | await app.whenReady(); 13 | 14 | mainWindow = new BrowserWindow(); 15 | await mainWindow.loadFile(path.join(__dirname, '../index.html')); 16 | })(); 17 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | - push 4 | - pull_request 5 | jobs: 6 | test: 7 | name: Node.js ${{ matrix.node-version }} 8 | runs-on: macos-latest 9 | strategy: 10 | fail-fast: false 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-node@v1 14 | with: 15 | node-version: 14 16 | - run: npm install 17 | - run: npm test 18 | -------------------------------------------------------------------------------- /index.test-d.ts: -------------------------------------------------------------------------------- 1 | import {expectType} from 'tsd'; 2 | import electronReloader = require('.'); 3 | 4 | expectType(electronReloader(module)); 5 | expectType(electronReloader(module, {watchRenderer: true})); 6 | expectType(electronReloader(module, {debug: true, ignore: ['tmp', /dist/]})); 7 | expectType(electronReloader(module, {watchRenderer: true, ignore: ['tmp', /dist/]})); 8 | expectType(electronReloader(module, {watchRenderer: true, debug: true, ignore: ['tmp', /dist/]})); 9 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace electronReloader { 2 | interface Options { 3 | /** 4 | Ignore patterns passed to [`chokidar`](https://github.com/paulmillr/chokidar#path-filtering). 5 | 6 | By default, files/directories starting with a `.`, `.map` files, and `node_modules` directories are ignored. This option is additive to those. 7 | */ 8 | readonly ignore?: ReadonlyArray; 9 | 10 | /** 11 | Watch files used in the renderer process and reload the window when they change. 12 | 13 | @default true 14 | */ 15 | readonly watchRenderer?: boolean; 16 | 17 | /** 18 | Prints watched paths and when files change. Can be useful to make sure you set it up correctly. 19 | 20 | @default false 21 | */ 22 | readonly debug?: boolean; 23 | } 24 | } 25 | 26 | /** 27 | @param moduleObject - Global `module` object. 28 | 29 | @example 30 | ``` 31 | try { 32 | require('electron-reloader')(module); 33 | } catch {} 34 | ``` 35 | */ 36 | declare function electronReloader( 37 | moduleObject: NodeModule, 38 | options?: electronReloader.Options 39 | ): void; 40 | 41 | export = electronReloader; 42 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Sindre Sorhus (https://sindresorhus.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-reloader", 3 | "version": "1.2.3", 4 | "description": "Simple auto-reloading for Electron apps during development", 5 | "license": "MIT", 6 | "repository": "sindresorhus/electron-reloader", 7 | "funding": "https://github.com/sponsors/sindresorhus", 8 | "author": { 9 | "name": "Sindre Sorhus", 10 | "email": "sindresorhus@gmail.com", 11 | "url": "https://sindresorhus.com" 12 | }, 13 | "scripts": { 14 | "test": "xo && tsd", 15 | "start": "electron test/main" 16 | }, 17 | "files": [ 18 | "index.js", 19 | "index.d.ts" 20 | ], 21 | "keywords": [ 22 | "electron", 23 | "reload", 24 | "reloader", 25 | "livereload", 26 | "auto-reload", 27 | "live-reload", 28 | "refresh", 29 | "restart", 30 | "watch", 31 | "watcher", 32 | "watching", 33 | "monitor", 34 | "hot", 35 | "files", 36 | "fs", 37 | "dev", 38 | "development" 39 | ], 40 | "dependencies": { 41 | "chalk": "^4.1.0", 42 | "chokidar": "^3.5.0", 43 | "date-time": "^3.1.0", 44 | "electron-is-dev": "^1.2.0", 45 | "find-up": "^5.0.0" 46 | }, 47 | "devDependencies": { 48 | "@types/node": "^14.14.20", 49 | "electron": "^11.2.0", 50 | "tsd": "^0.14.0", 51 | "xo": "^0.37.1" 52 | }, 53 | "xo": { 54 | "envs": [ 55 | "node", 56 | "browser" 57 | ] 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # electron-reloader 2 | 3 | > Simple auto-reloading for Electron apps during development 4 | 5 | It *just works*. When files used in the main process are changed, the app is restarted, and when files used in the browser window are changed, the page is reloaded. 6 | 7 | Note that it will not work correctly if you transpile the main process JS files of your app, but it doesn't make sense to do that anyway. 8 | 9 | ## Install 10 | 11 | ```sh 12 | npm install --save-dev electron-reloader 13 | ``` 14 | 15 | *Requires Electron 5 or later.* 16 | 17 | ## Usage 18 | 19 | The following must be included in the app entry file, usually named `index.js`: 20 | 21 | ```js 22 | try { 23 | require('electron-reloader')(module); 24 | } catch {} 25 | ``` 26 | 27 | You have to pass the `module` object so we can read the module graph and figure out which files belong to the main process. 28 | 29 | The `try/catch` is needed so it doesn't throw `Cannot find module 'electron-reloader'` in production. 30 | 31 | ## API 32 | 33 | ### reloader(module, options?) 34 | 35 | #### module 36 | 37 | Type: `object` 38 | 39 | The global `module` object. 40 | 41 | #### options 42 | 43 | Type: `object` 44 | 45 | ##### ignore 46 | 47 | Type: `Array` 48 | 49 | Ignore patterns passed to [`chokidar`](https://github.com/paulmillr/chokidar#path-filtering). 50 | 51 | By default, files/directories starting with a `.`, `.map` files, and `node_modules` directories are ignored. This option is additive to those. 52 | 53 | ##### watchRenderer 54 | 55 | Type: `boolean`\ 56 | Default: `true` 57 | 58 | Watch files used in the renderer process and reload the window when they change. 59 | 60 | Setting this to `false` can be useful if you use a different reload strategy in the rendererer process, like [`HMR`](https://webpack.js.org/concepts/hot-module-replacement/). 61 | 62 | ##### debug 63 | 64 | Type: `boolean`\ 65 | Default: `false` 66 | 67 | Prints watched paths and when files change. 68 | 69 | Can be useful to make sure you set it up correctly. 70 | 71 | ## Tip 72 | 73 | ### Using it with Webpack watch mode 74 | 75 | Just add the source directory to the `ignore` option. The dist directory is already watched, so when a source file changes, webpack will build it and output it to the dist directory, which this module will detect. 76 | 77 | ## Related 78 | 79 | - [electron-util](https://github.com/sindresorhus/electron-util) - Useful utilities for developing Electron apps and modules 80 | - [electron-debug](https://github.com/sindresorhus/electron-debug) - Adds useful debug features to your Electron app 81 | - [electron-context-menu](https://github.com/sindresorhus/electron-context-menu) - Context menu for your Electron app 82 | - [electron-dl](https://github.com/sindresorhus/electron-dl) - Simplified file downloads for your Electron app 83 | - [electron-unhandled](https://github.com/sindresorhus/electron-unhandled) - Catch unhandled errors and promise rejections in your Electron app 84 | - [electron-serve](https://github.com/sindresorhus/electron-serve) - Static file serving for Electron apps 85 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {inspect} = require('util'); 3 | const path = require('path'); 4 | const electron = require('electron'); 5 | const chokidar = require('chokidar'); 6 | const isDev = require('electron-is-dev'); 7 | const dateTime = require('date-time'); 8 | const chalk = require('chalk'); 9 | const findUp = require('find-up'); 10 | 11 | function getMainProcessPaths(topModuleObject, cwd) { 12 | const paths = new Set([topModuleObject.filename]); 13 | 14 | const getPaths = moduleObject => { 15 | for (const child of moduleObject.children) { 16 | if (paths.has(child.filename)) { 17 | continue; 18 | } 19 | 20 | if (path.relative(cwd, child.filename).includes('node_modules')) { 21 | continue; 22 | } 23 | 24 | paths.add(child.filename); 25 | getPaths(child); 26 | } 27 | }; 28 | 29 | getPaths(topModuleObject); 30 | 31 | return paths; 32 | } 33 | 34 | module.exports = (moduleObject, options = {}) => { 35 | // This module should be a dev dependency, but guard 36 | // this in case the user included it as a dependency. 37 | if (!isDev) { 38 | return; 39 | } 40 | 41 | if (!moduleObject) { 42 | throw new Error('You have to pass the `module` object'); 43 | } 44 | 45 | if (options.ignored) { 46 | throw new Error('The option is named `ignore` not `ignored`'); 47 | } 48 | 49 | options = { 50 | watchRenderer: true, 51 | ...options 52 | }; 53 | 54 | const mainProcessDirectory = path.dirname(moduleObject.filename); 55 | const packageDirectory = findUp.sync('package.json', {cwd: mainProcessDirectory}); 56 | const cwd = packageDirectory ? path.dirname(packageDirectory) : mainProcessDirectory; 57 | const mainProcessPaths = getMainProcessPaths(moduleObject, cwd); 58 | const watchPaths = options.watchRenderer ? cwd : [...mainProcessPaths]; 59 | let isRelaunching = false; 60 | 61 | const watcher = chokidar.watch(watchPaths, { 62 | cwd, 63 | disableGlobbing: true, 64 | ignored: [ 65 | /(^|[/\\])\../, // Dotfiles 66 | 'node_modules', 67 | '**/*.map' 68 | ].concat(options.ignore) 69 | }); 70 | 71 | electron.app.on('quit', () => { 72 | watcher.close(); 73 | }); 74 | 75 | if (options.debug) { 76 | watcher.on('ready', () => { 77 | console.log('Watched paths:', inspect(watcher.getWatched(), {compact: false, colors: true})); 78 | }); 79 | } 80 | 81 | watcher.on('change', filePath => { 82 | if (options.debug) { 83 | console.log('File changed:', chalk.bold(filePath), chalk.dim(`(${dateTime().split(' ')[1]})`)); 84 | } 85 | 86 | if (mainProcessPaths.has(path.join(cwd, filePath))) { 87 | // Prevent multiple instances of Electron from being started due to the change 88 | // handler being called multiple times before the original instance exits. 89 | if (!isRelaunching) { 90 | electron.app.relaunch(); 91 | electron.app.exit(0); 92 | } 93 | 94 | isRelaunching = true; 95 | } else { 96 | for (const window_ of electron.BrowserWindow.getAllWindows()) { 97 | window_.webContents.reloadIgnoringCache(); 98 | 99 | for (const view_ of window_.getBrowserViews()) { 100 | view_.webContents.reloadIgnoringCache(); 101 | } 102 | } 103 | } 104 | }); 105 | }; 106 | --------------------------------------------------------------------------------