├── .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 |
--------------------------------------------------------------------------------