├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc.yml
├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── .husky
├── commit-msg
└── pre-commit
├── .npmignore
├── .nvmrc
├── .prettierrc.js
├── CHANGELOG.md
├── LICENSE
├── README.md
├── commitlint.config.js
├── cucumber.js
├── features
├── clean_exit.feature
├── compilation_error.feature
├── nodemon_options.feature
├── output_substitutions.feature
├── step_definitions
│ ├── assertions.js
│ ├── fileModifications.js
│ ├── typescript.js
│ └── webpack.js
├── support
│ ├── outputMonitoring.js
│ ├── templates.js
│ ├── templates
│ │ ├── entry.js
│ │ ├── unrelated.js
│ │ └── webpack.config.js
│ ├── tmpDir.js
│ ├── typescript.js
│ ├── uuid.js
│ └── webpackLauncher.js
├── typescript.feature
└── zero_configuration.feature
├── package.json
├── pnpm-lock.yaml
├── release.config.js
├── src
├── index.js
└── webpack-utils.js
└── typings.d.ts
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [ "@babel/preset-env", {
4 | "targets": {
5 | "node": 4
6 | }
7 | }]
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://EditorConfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | charset = utf-8
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 | indent_style = space
11 | indent_size = 2
12 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist/
2 | node_modules/
3 |
--------------------------------------------------------------------------------
/.eslintrc.yml:
--------------------------------------------------------------------------------
1 | extends:
2 | - airbnb-base
3 | - prettier
4 |
5 | rules:
6 | guard-for-in: 'off'
7 | func-names: 'off'
8 | import/prefer-default-export: 'off'
9 | import/no-extraneous-dependencies:
10 | - error
11 | - devDependencies:
12 | - './features/**/*.js'
13 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 |
9 | steps:
10 | - name: Checkout
11 | uses: actions/checkout@v4
12 |
13 | - uses: pnpm/action-setup@v2
14 | with:
15 | version: 8
16 |
17 | - name: Setup Node.js
18 | uses: actions/setup-node@v4
19 | with:
20 | node-version: 16.14
21 | cache: 'pnpm'
22 |
23 | - name: Install dependencies
24 | run: pnpm install --frozen-lockfile
25 |
26 | - name: Build
27 | run: pnpm build
28 |
29 | - name: Test
30 | run: pnpm test
31 |
32 | - name: Publish
33 | if: github.ref == 'refs/heads/master'
34 | env:
35 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
36 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
37 | run: npx semantic-release
38 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
3 | .DS_Store
4 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx --no -- commitlint --edit $1
5 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx lint-staged
5 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .git
2 | .gitignore
3 | npm-debug.log
4 | .DS_Store
5 | .eslintignore
6 | .eslintrc.yml
7 | .editorconfig
8 | .babelrc
9 | .travis.yml
10 | .vscode
11 | features/
12 | cucumber.js
13 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 14
2 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | bracketSpacing: true,
3 | singleQuote: true,
4 | trailingComma: 'es5',
5 | };
6 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [4.8.2](https://github.com/Izhaki/nodemon-webpack-plugin/compare/v4.8.1...v4.8.2) (2023-10-10)
2 |
3 |
4 | ### Bug Fixes
5 |
6 | * bump nodemon from 2.0.19 to 3.0.1 ([33f60d5](https://github.com/Izhaki/nodemon-webpack-plugin/commit/33f60d5f92b5a8c4efa431b29613a751815476b9))
7 |
8 | ## [4.8.1](https://github.com/Izhaki/nodemon-webpack-plugin/compare/v4.8.0...v4.8.1) (2022-07-11)
9 |
10 |
11 | ### Bug Fixes
12 |
13 | * bump nodemon from 2.0.16 to 2.0.19 ([922cd89](https://github.com/Izhaki/nodemon-webpack-plugin/commit/922cd89481559c98af2cf52d4b6bc2c3ffe55b92))
14 |
15 | # [4.8.0](https://github.com/Izhaki/nodemon-webpack-plugin/compare/v4.7.1...v4.8.0) (2022-06-17)
16 |
17 |
18 | ### Features
19 |
20 | * bump nodemon to 2.0.16 ([ae4d162](https://github.com/Izhaki/nodemon-webpack-plugin/commit/ae4d162e5284835c98c8ecb217ef2a3b978c8a18))
21 |
22 | ## [4.7.1](https://github.com/Izhaki/nodemon-webpack-plugin/compare/v4.7.0...v4.7.1) (2022-02-03)
23 |
24 |
25 | ### Bug Fixes
26 |
27 | * Typescript error with "noImplicitAny" ([071385c](https://github.com/Izhaki/nodemon-webpack-plugin/commit/071385c549e1ec4400d00320bf33034b46546b2b)), closes [#141](https://github.com/Izhaki/nodemon-webpack-plugin/issues/141)
28 |
29 | # [4.7.0](https://github.com/Izhaki/nodemon-webpack-plugin/compare/v4.6.0...v4.7.0) (2021-11-29)
30 |
31 |
32 | ### Features
33 |
34 | * add semantic-release ([04a8217](https://github.com/Izhaki/nodemon-webpack-plugin/commit/04a821701a2bf2987a3b351f0e55c9c11c47289b))
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Nate Johnson
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Nodemon Webpack Plugin
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | Uses [Nodemon](https://nodemon.io/) to watch and restart your module's output file (presumably a server), but only when webpack is in watch mode (ie, `--watch`).
20 |
21 | Saves the need for installing, configuring and running Nodemon as a separate process.
22 |
23 | ## Installation
24 |
25 | ```bash
26 | npm install nodemon-webpack-plugin --save-dev
27 | ```
28 |
29 | ## Usage
30 |
31 | ```javascript
32 | const NodemonPlugin = require('nodemon-webpack-plugin'); // Ding
33 |
34 | module.exports = {
35 | entry: './src/server.js',
36 | output: {
37 | path: path.resolve('./dist'),
38 | filename: 'server.js',
39 | },
40 | plugins: [
41 | new NodemonPlugin(), // Dong
42 | ],
43 | };
44 | ```
45 |
46 | Then:
47 |
48 | ```shell
49 | $ webpack --watch
50 | ```
51 |
52 | ## Modes
53 |
54 | ### Zero-config mode
55 |
56 | ```javascript
57 | new NodemonPlugin();
58 | ```
59 |
60 | Will watch and restart the output file.
61 |
62 | ### With config
63 |
64 | Provide a [Nodemon config object](https://github.com/remy/nodemon#config-files), like so:
65 |
66 | ```javascript
67 | new NodemonPlugin({
68 | // If using more than one entry, you can specify
69 | // which output file will be restarted.
70 | script: './dist/server.js',
71 |
72 | // What to watch.
73 | watch: path.resolve('./dist'),
74 |
75 | // Arguments to pass to the script being watched.
76 | args: ['demo'],
77 |
78 | // Node arguments.
79 | nodeArgs: ['--debug=9222'],
80 |
81 | // Files to ignore.
82 | ignore: ['*.js.map'],
83 |
84 | // Extensions to watch.
85 | ext: 'js,njk,json',
86 |
87 | // Unlike the cli option, delay here is in milliseconds (also note that it's a string).
88 | // Here's 1 second delay:
89 | delay: '1000',
90 |
91 | // Detailed log.
92 | verbose: true,
93 |
94 | // Environment variables to pass to the script to be restarted
95 | env: {
96 | NODE_ENV: 'development',
97 | },
98 | });
99 | ```
100 |
101 | For a full list of options, see Nodemon's [type definitions](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/nodemon/index.d.ts) (`Settings` interface).
102 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = { extends: ['@commitlint/config-conventional'] };
2 |
--------------------------------------------------------------------------------
/cucumber.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | default: ['--require-module @babel/register'],
3 | };
4 |
--------------------------------------------------------------------------------
/features/clean_exit.feature:
--------------------------------------------------------------------------------
1 | Feature: Clean exit
2 | Scenario: Ctrl-C is pressed
3 | Given I run webpack in watch mode
4 | And the server has started
5 | And Ctrl-c has been pressed
6 | When the entry file is modified
7 | Then the server shouldn't restart
8 |
--------------------------------------------------------------------------------
/features/compilation_error.feature:
--------------------------------------------------------------------------------
1 | Feature: Compilation error
2 | Scenario:
3 | Given a webpack configuration that yields an error
4 | And I run webpack in watch mode
5 | Then the output should include "[nodemon-webpack-plugin]: Compilation error."
6 |
--------------------------------------------------------------------------------
/features/nodemon_options.feature:
--------------------------------------------------------------------------------
1 | Feature: Support nodemon options
2 |
3 | Scenario: nodemon is asked to watch the whole output directory
4 | Given the following nodemon config:
5 | """
6 | {
7 | watch: outputDir,
8 | }
9 | """
10 | And I run webpack in watch mode
11 | And the server has started
12 | When a file unrelated to the entry file is modified
13 | Then the output should include "[nodemon] restarting due to changes..."
14 | And the server should restart
15 |
16 | Scenario: pass arguments to the output script
17 | Given the following nodemon config:
18 | """
19 | {
20 | args: [ '--port', '4096' ],
21 | }
22 | """
23 | And I run webpack in watch mode
24 | Then the server should start with the arguments "[ '--port', '4096' ]"
25 |
--------------------------------------------------------------------------------
/features/output_substitutions.feature:
--------------------------------------------------------------------------------
1 | Feature: Support substitutions in output filename
2 |
3 | Scenario: output filename includes substitutions
4 | Given the output filename is "[name]-[hash].js"
5 | And I run webpack in watch mode
6 | Then the server should start
7 |
--------------------------------------------------------------------------------
/features/step_definitions/assertions.js:
--------------------------------------------------------------------------------
1 | import { Given, Then } from 'cucumber';
2 |
3 | Given('the server has started', function () {
4 | return this.waitForOutputToContain('Server started');
5 | });
6 |
7 | Then('the output should include {string}', function (expectedOutput) {
8 | return this.waitForOutputToContain(expectedOutput);
9 | });
10 |
11 | Then('the server should start with the arguments {string}', function (argv) {
12 | return this.waitForOutputToContain(`Server started with argv: ${argv}`);
13 | });
14 |
15 | Then(/^the server should (?:start|restart)$/, function () {
16 | return this.waitForOutputToContain('Server started');
17 | });
18 |
19 | Then("the server shouldn't restart", function () {
20 | const reject = () => {
21 | throw new Error("The server restarted when it shouldn't have");
22 | };
23 | const fulfill = () => true;
24 | return this.waitForOutputToContain('Server started').then(reject, fulfill); // We invert the fulfill/reject logic here.
25 | });
26 |
--------------------------------------------------------------------------------
/features/step_definitions/fileModifications.js:
--------------------------------------------------------------------------------
1 | import { When } from 'cucumber';
2 |
3 | When('the entry file is modified', function () {
4 | this.renderEntryFile();
5 | });
6 |
7 | When('a file unrelated to the entry file is modified', function () {
8 | this.renderUnrelatedFile();
9 | });
10 |
--------------------------------------------------------------------------------
/features/step_definitions/typescript.js:
--------------------------------------------------------------------------------
1 | import { When, Then } from 'cucumber';
2 |
3 | When('I compile the file using typescript', function () {
4 | this.context.webpackConfigFileName = 'webpack.config.ts';
5 | this.context.isTypescript = true;
6 | this.tsIssues = this.runTypescript();
7 | });
8 |
9 | Then('there should be {int} typescript issue', function (issueCount) {
10 | if (this.tsIssues.length !== issueCount) {
11 | throw new Error(
12 | `Expected ${issueCount} issues, but got ${this.tsIssues.length}`
13 | );
14 | }
15 | });
16 |
17 | Then('the first issue should be {string}', function (issueMessage) {
18 | if (this.tsIssues[0] !== issueMessage) {
19 | throw new Error(
20 | `Expected ${issueMessage} issues, but got ${this.tsIssues[0]}`
21 | );
22 | }
23 | });
24 |
--------------------------------------------------------------------------------
/features/step_definitions/webpack.js:
--------------------------------------------------------------------------------
1 | import { Given } from 'cucumber';
2 |
3 | Given('the following nodemon config:', function (nodemonConfig) {
4 | this.context.nodemonConfig = nodemonConfig;
5 | });
6 |
7 | Given('the output filename is {string}', function (outputFileName) {
8 | this.context.outputFileName = outputFileName;
9 | });
10 |
11 | Given('a webpack configuration that yields an error', function () {
12 | this.context.loader = 'missing-loader';
13 | });
14 |
15 | Given('I run webpack in watch mode', function () {
16 | this.launchWebpack();
17 | });
18 |
19 | Given('Ctrl-c has been pressed', function () {
20 | this.simulateCtrlC();
21 | });
22 |
--------------------------------------------------------------------------------
/features/support/outputMonitoring.js:
--------------------------------------------------------------------------------
1 | import { Before, setDefaultTimeout } from 'cucumber';
2 |
3 | const ONE_SECOND = 1000;
4 | const STEP_TIME_OUT = 10 * ONE_SECOND;
5 | const WAIT_TIMEOUT = STEP_TIME_OUT - ONE_SECOND;
6 |
7 | Before(() => {
8 | setDefaultTimeout(STEP_TIME_OUT);
9 | });
10 |
11 | Before(function () {
12 | this.waitForOutputToContain = (text) => {
13 | const { output } = this;
14 | return new Promise((resolve, reject) => {
15 | /* eslint-disable no-use-before-define */
16 | const intervalID = setInterval(checkOutput, ONE_SECOND);
17 | setTimeout(onTimeout, WAIT_TIMEOUT);
18 | /* eslint-enable no-use-before-define */
19 |
20 | function checkOutput() {
21 | // Note that we shift the output array here.
22 | const getNextOutputBlock = () => output.shift() || '';
23 |
24 | let isFound = false;
25 |
26 | do {
27 | isFound = getNextOutputBlock().includes(text);
28 | } while (!isFound && output.length > 0);
29 |
30 | if (isFound) {
31 | clearInterval(intervalID);
32 | resolve();
33 | }
34 | }
35 |
36 | function onTimeout() {
37 | clearInterval(intervalID);
38 | reject(new Error('Timeout!'));
39 | }
40 | });
41 | };
42 | });
43 |
--------------------------------------------------------------------------------
/features/support/templates.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import { Before } from 'cucumber';
3 | import fs from 'fs-extra';
4 | import Mustache from 'mustache';
5 | import uuid from './uuid';
6 | import webpackConfigTpl from './templates/webpack.config';
7 | import entryTpl from './templates/entry';
8 | import unrelatedTpl from './templates/unrelated';
9 |
10 | const unrelatedFileName = 'dist/config.json';
11 | let port = 3421;
12 |
13 | Before(function () {
14 | // Default context
15 | this.context = {
16 | nodemonPluginPath: path.resolve('dist'),
17 | outputFileName: 'server.js',
18 | nodemonConfig: '',
19 | loader: 'babel-loader',
20 | webpackConfigFileName: 'webpack.config.js',
21 | isTypescript: false,
22 | };
23 | });
24 |
25 | Before(function () {
26 | const renderTemplate = (fileName, template) => {
27 | this.context.uuid = uuid(); // uuid is used to invalidate each file checksum
28 |
29 | // Sometimes builds fail due EADDRINUSE so increase the port for each test.
30 | this.context.port = port++; // eslint-disable-line no-plusplus
31 | const outputFile = path.join(this.tmpDir, fileName);
32 | const render = Mustache.render(template, this.context);
33 | fs.outputFileSync(outputFile, render);
34 | };
35 |
36 | this.renderWebpackConfig = () =>
37 | renderTemplate(this.context.webpackConfigFileName, webpackConfigTpl);
38 | this.renderEntryFile = () => renderTemplate('server.js', entryTpl);
39 | this.renderUnrelatedFile = () =>
40 | renderTemplate(unrelatedFileName, unrelatedTpl);
41 |
42 | this.renderTemplates = () => {
43 | this.renderWebpackConfig();
44 | this.renderEntryFile();
45 | this.renderUnrelatedFile();
46 | };
47 | });
48 |
--------------------------------------------------------------------------------
/features/support/templates/entry.js:
--------------------------------------------------------------------------------
1 | export default `
2 | const path = require( 'path' )
3 | const express = require( 'express' )
4 |
5 | const app = express()
6 |
7 | const argv = process.argv.slice( 2 )
8 |
9 | console.log( 'Server started with argv:', argv )
10 |
11 | app.get( '/', ( req, res ) => {
12 | res.send( 'hello' + '{{ uuid }}' )
13 | })
14 |
15 | app.listen( {{ port }} )
16 | `;
17 |
--------------------------------------------------------------------------------
/features/support/templates/unrelated.js:
--------------------------------------------------------------------------------
1 | export default `
2 | {
3 | "domain": "www.example.com",
4 | "mongodb": {
5 | "host": "localhost",
6 | "port": 27017
7 | },
8 | "sessionId": "{{ uuid }}"
9 | }
10 | `;
11 |
--------------------------------------------------------------------------------
/features/support/templates/webpack.config.js:
--------------------------------------------------------------------------------
1 | export default `
2 | const path = require( 'path' )
3 | const nodeExternals = require( 'webpack-node-externals' )
4 |
5 | {{#isTypescript}}
6 | import NodemonPlugin from 'nodemon-webpack-plugin';
7 | {{/isTypescript}}
8 | {{^isTypescript}}
9 | const NodemonPlugin = require( '{{{ nodemonPluginPath }}}' )
10 | {{/isTypescript}}
11 |
12 | const baseDir = __dirname
13 | const outputDir = path.resolve( baseDir, 'dist' )
14 |
15 | const config = {
16 | target: 'node',
17 | mode: 'development',
18 | node: {
19 | __dirname: false,
20 | __filename: false,
21 | },
22 |
23 | entry: path.resolve( baseDir, 'server.js' ),
24 |
25 | externals: [ nodeExternals() ],
26 |
27 | output: {
28 | path: outputDir,
29 | filename: '{{{ outputFileName }}}',
30 | },
31 |
32 | module: {
33 | rules: [{
34 | test: /.js$/,
35 | exclude: /node_modules/,
36 | use: {
37 | loader: '{{{loader}}}',
38 | options: {
39 | presets: [ '@babel/preset-env' ],
40 | },
41 | },
42 | }],
43 | },
44 | plugins: [
45 | new NodemonPlugin({{{ nodemonConfig }}})
46 | ],
47 | }
48 |
49 | module.exports = config
50 | `;
51 |
--------------------------------------------------------------------------------
/features/support/tmpDir.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import { Before } from 'cucumber';
3 | import fs from 'fs-extra';
4 | import tmp from 'tmp';
5 |
6 | const projectPath = path.join(__dirname, '..', '..');
7 |
8 | const symLinkNodeModules = (fromBaseDir, toBaseDir) => {
9 | const fromNodeModulesDir = path.join(fromBaseDir, 'node_modules');
10 | const toNodeModulesDir = path.join(toBaseDir, 'node_modules');
11 |
12 | const moduleNames = fs.readdirSync(fromNodeModulesDir);
13 |
14 | moduleNames.forEach((moduleName) => {
15 | const fromNodeModuleDir = path.join(fromNodeModulesDir, moduleName);
16 | const toNodeModuleDir = path.join(toNodeModulesDir, moduleName);
17 |
18 | fs.createSymlinkSync(fromNodeModuleDir, toNodeModuleDir);
19 | });
20 | };
21 |
22 | Before(function () {
23 | const tmpObject = tmp.dirSync({ unsafeCleanup: true });
24 | this.tmpDir = fs.realpathSync(tmpObject.name);
25 | // this.tmpDir = path.resolve( 'tmp' )
26 |
27 | symLinkNodeModules(projectPath, this.tmpDir);
28 | });
29 |
--------------------------------------------------------------------------------
/features/support/typescript.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | import path from 'path';
3 | import { Before } from 'cucumber';
4 | import * as ts from 'typescript';
5 |
6 | // https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API
7 | function compile(fileNames, options) {
8 | const program = ts.createProgram(fileNames, options);
9 | const allDiagnostics = ts.getPreEmitDiagnostics(program);
10 | return allDiagnostics.map((diagnostic) => diagnostic.messageText);
11 | }
12 |
13 | Before(function () {
14 | this.runTypescript = () => {
15 | this.renderWebpackConfig();
16 |
17 | const configFilePath = path.join(
18 | this.tmpDir,
19 | this.context.webpackConfigFileName
20 | );
21 |
22 | const baseUrl = path.join(__dirname, '../..');
23 |
24 | return compile([configFilePath], {
25 | noEmit: true,
26 | target: ts.ScriptTarget.ES5,
27 | module: ts.ModuleKind.CommonJS,
28 | esModuleInterop: true,
29 | // We use aliases to point to the package root - that way the types
30 | // are picked
31 | baseUrl,
32 | paths: { 'nodemon-webpack-plugin': ['.'] },
33 | });
34 | };
35 | });
36 |
--------------------------------------------------------------------------------
/features/support/uuid.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-bitwise */
2 | import crypto from 'crypto';
3 |
4 | // https://stackoverflow.com/a/40191779/1179377
5 | export default () => crypto.randomBytes(16).toString('hex');
6 |
--------------------------------------------------------------------------------
/features/support/webpackLauncher.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | import path from 'path';
3 | import { spawn } from 'child-process-promise';
4 | import kill from 'tree-kill';
5 | import { Before, After } from 'cucumber';
6 |
7 | Before(function () {
8 | this.launchWebpack = () => {
9 | this.renderTemplates();
10 |
11 | const webpackBin = path.relative(
12 | process.cwd(),
13 | path.join('node_modules', '.bin', 'webpack')
14 | );
15 | const configFilePath = path.join(
16 | this.tmpDir,
17 | this.context.webpackConfigFileName
18 | );
19 |
20 | const promise = spawn(webpackBin, ['--config', configFilePath, '--watch'], {
21 | capture: ['stderr'],
22 | });
23 |
24 | promise
25 | .then(() => {
26 | console.log('\nWebpack stopped');
27 | })
28 | .catch((err) => {
29 | console.error('Error starting webpack: ', err.stderr);
30 | });
31 |
32 | this.childProcess = promise.childProcess;
33 |
34 | this.output = [];
35 | this.childProcess.stdout.on('data', (data) => {
36 | console.log(data.toString());
37 | this.output.push(data.toString());
38 | });
39 | // Note: As of https://github.com/webpack/webpack-cli/commit/6ded275ac50f80f4ea6b29bfcc676238b59322e2
40 | // All webpack output is done using the error stream.
41 | this.childProcess.stderr.on('data', (data) => {
42 | console.log(data.toString());
43 | this.output.push(data.toString());
44 | });
45 | };
46 | });
47 |
48 | Before(function () {
49 | this.simulateCtrlC = () => {
50 | kill(this.childProcess.pid, 'SIGINT');
51 | };
52 | });
53 |
54 | After(function () {
55 | if (this.childProcess) {
56 | kill(this.childProcess.pid, 'SIGINT');
57 | }
58 | });
59 |
--------------------------------------------------------------------------------
/features/typescript.feature:
--------------------------------------------------------------------------------
1 | Feature: Typescript support
2 |
3 | Scenario: A nodemon config that includes wrong setting type
4 | Given the following nodemon config:
5 | """
6 | {
7 | noUpdateNotifier: 2,
8 | }
9 | """
10 | When I compile the file using typescript
11 | Then there should be 1 typescript issue
12 | And the first issue should be "Type 'number' is not assignable to type 'boolean'."
13 |
14 |
--------------------------------------------------------------------------------
/features/zero_configuration.feature:
--------------------------------------------------------------------------------
1 | Feature: Zero configuration
2 | Background:
3 | Given I run webpack in watch mode
4 | And the server has started
5 |
6 | Scenario: The entry file is modified
7 | When the entry file is modified
8 | Then the output should include "[nodemon] restarting due to changes..."
9 | And the server should restart
10 |
11 | Scenario: A file not related to the entry file is modified
12 | When a file unrelated to the entry file is modified
13 | Then the server shouldn't restart
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nodemon-webpack-plugin",
3 | "version": "4.8.2",
4 | "description": "A webpack plugin that starts and reloads a server using Nodemon.",
5 | "main": "dist/index.js",
6 | "types": "typings.d.ts",
7 | "files": [
8 | "dist/",
9 | "typings.d.ts"
10 | ],
11 | "scripts": {
12 | "lint": "eslint .",
13 | "test": "pnpm run test:webpack4 && npm run test:webpack5",
14 | "test:webpack4": "pnpm add webpack@^4.46.0 --save-dev && cucumber-js",
15 | "test:webpack5": "pnpm add webpack@^5.73.0 --save-dev && cucumber-js",
16 | "test:only": "cucumber-js --tags @only",
17 | "build": "pnpm run lint && rimraf dist/ && babel src/ --out-dir dist/",
18 | "watch": "rimraf dist/ && babel src/ --watch --out-dir dist/",
19 | "prettier:fix": "prettier '{**/,}*.{js,jsx,ts,tsx,json,scss,css,md,html,yaml,yml}' --ignore-path=.gitignore --write",
20 | "semantic-release": "semantic-release"
21 | },
22 | "engines": {
23 | "node": ">=8.10.0"
24 | },
25 | "repository": {
26 | "type": "git",
27 | "url": "https://github.com/Izhaki/nodemon-webpack-plugin.git"
28 | },
29 | "keywords": [
30 | "webpack",
31 | "nodemon",
32 | "plugin",
33 | "server",
34 | "start",
35 | "watch",
36 | "restart"
37 | ],
38 | "author": "Roey Izhaki",
39 | "license": "MIT",
40 | "bugs": {
41 | "url": "https://github.com/Izhaki/nodemon-webpack-plugin.git/issues"
42 | },
43 | "homepage": "https://github.com/Izhaki/nodemon-webpack-plugin.git",
44 | "dependencies": {
45 | "@types/nodemon": "latest",
46 | "nodemon": "3.0.1"
47 | },
48 | "peerDependencies": {
49 | "webpack": "4 || 5"
50 | },
51 | "devDependencies": {
52 | "@babel/cli": "^7.17.10",
53 | "@babel/core": "^7.18.5",
54 | "@babel/preset-env": "^7.18",
55 | "@babel/register": "^7.17.7",
56 | "@commitlint/cli": "^15.0.0",
57 | "@commitlint/config-conventional": "^15.0.0",
58 | "@semantic-release/changelog": "^6.0.1",
59 | "@semantic-release/exec": "^6.0.2",
60 | "@semantic-release/git": "^10.0.1",
61 | "babel-loader": "8.2.2",
62 | "child-process-promise": "2.2.1",
63 | "cucumber": "5.1.0",
64 | "eslint": "8.18.0",
65 | "eslint-config-airbnb-base": "15",
66 | "eslint-config-prettier": "8",
67 | "eslint-plugin-import": "2.26.0",
68 | "express": "4.18.1",
69 | "fs-extra": "9.0.1",
70 | "husky": "^7.0.4",
71 | "lint-staged": "^10.5.3",
72 | "mustache": "4.2.0",
73 | "prettier": "^2.7.1",
74 | "rimraf": "3.0.2",
75 | "semantic-release": "^19.0.3",
76 | "tmp": "^0.2.1",
77 | "tree-kill": "^1.2.2",
78 | "typescript": "^4.7.4",
79 | "webpack": "^5.89.0",
80 | "webpack-cli": "^4.3.1",
81 | "webpack-node-externals": "2.5.2"
82 | },
83 | "lint-staged": {
84 | "*.{js,jsx,ts,tsx,json,scss,css,md,html,yaml,yml}": [
85 | "prettier --write",
86 | "git add"
87 | ]
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/release.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | pkgRoot: '.',
3 | branches: ['master'],
4 | plugins: [
5 | '@semantic-release/commit-analyzer',
6 | '@semantic-release/release-notes-generator',
7 | '@semantic-release/npm',
8 | [
9 | '@semantic-release/exec',
10 | {
11 | prepareCmd:
12 | 'npx replace-json-property package.json version ${nextRelease.version}', // eslint-disable-line no-template-curly-in-string
13 | },
14 | ],
15 | '@semantic-release/changelog',
16 | [
17 | '@semantic-release/git',
18 | {
19 | assets: ['package.json', 'CHANGELOG.md'],
20 | message:
21 | 'chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}', // eslint-disable-line no-template-curly-in-string
22 | },
23 | ],
24 | '@semantic-release/github',
25 | ],
26 | };
27 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 |
3 | const nodemon = require('nodemon');
4 | const { getOutputFileMeta } = require('./webpack-utils');
5 |
6 | module.exports = class {
7 | constructor(nodemonOptions) {
8 | this.nodemonOptions = nodemonOptions;
9 | this.isWebpackWatching = false;
10 | this.isNodemonRunning = false;
11 | }
12 |
13 | apply(compiler) {
14 | const OnAfterEmit = (compilation, callback) => {
15 | if (this.isWebpackWatching) {
16 | if (compilation.errors.length > 0) {
17 | console.log('[nodemon-webpack-plugin]: Compilation error.');
18 | } else if (!this.isNodemonRunning) {
19 | const outputFile = getOutputFileMeta(
20 | compilation,
21 | compiler.outputPath
22 | );
23 | this.startMonitoring(outputFile);
24 | }
25 | }
26 | callback();
27 | };
28 |
29 | const onWatchRun = (comp, callback) => {
30 | this.isWebpackWatching = true;
31 | callback();
32 | };
33 |
34 | const plugin = { name: 'nodemon-webpack-plugin"' };
35 |
36 | if (compiler.hooks) {
37 | compiler.hooks.afterEmit.tapAsync(plugin, OnAfterEmit);
38 | compiler.hooks.watchRun.tapAsync(plugin, onWatchRun);
39 | } else {
40 | compiler.plugin('after-emit', OnAfterEmit);
41 | compiler.plugin('watch-run', onWatchRun);
42 | }
43 | }
44 |
45 | startMonitoring(relativeFileName) {
46 | const nodemonOptionsDefaults = {
47 | script: relativeFileName,
48 | watch: relativeFileName,
49 | };
50 |
51 | const nodemonOptions = {
52 | ...nodemonOptionsDefaults,
53 | ...this.nodemonOptions,
54 | };
55 |
56 | const monitor = nodemon(nodemonOptions);
57 |
58 | monitor.on('log', ({ colour: colouredMessage }) =>
59 | console.log(colouredMessage)
60 | );
61 |
62 | this.isNodemonRunning = true;
63 |
64 | // Ensure we exit nodemon when webpack exists.
65 | process.once('exit', () => {
66 | monitor.emit('exit');
67 | });
68 | // Ensure Ctrl-C triggers exit.
69 | process.once('SIGINT', () => {
70 | process.exit(0);
71 | });
72 | }
73 | };
74 |
--------------------------------------------------------------------------------
/src/webpack-utils.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | const notMapFile = (file) => !file.endsWith('.map');
4 |
5 | // Returns the first assets that is not a map file
6 | const getOutputFileName = (compilation) =>
7 | Object.keys(compilation.assets).filter(notMapFile)[0];
8 |
9 | const getOutputFileMeta = (compilation, outputPath) => {
10 | const outputFileName = getOutputFileName(compilation);
11 | const absoluteFileName = path.join(outputPath, outputFileName);
12 | return path.relative('', absoluteFileName);
13 | };
14 |
15 | module.exports = {
16 | getOutputFileMeta,
17 | };
18 |
--------------------------------------------------------------------------------
/typings.d.ts:
--------------------------------------------------------------------------------
1 | import { Settings as NodemonOptions } from 'nodemon';
2 |
3 | // We prioritise supporting CommonJS than ES6 syntax here.
4 | // Typescript users will have to add `esModuleInterop` in `compilerOptions`
5 | // or set `"allowSyntheticDefaultImports": true`.
6 | // https://www.typescriptlang.org/docs/handbook/modules.html#export--and-import--require
7 | export = NodemonPlugin;
8 |
9 | declare class NodemonPlugin {
10 | constructor(options?: NodemonOptions);
11 |
12 | // yields error TS2321: Excessive stack depth comparing types 'NodemonPlugin' and '((this: Compiler, compiler: Compiler) => void) | WebpackPluginInstance'.
13 | //apply(compiler: webpack.Compiler): void;
14 | apply(compiler: any): void;
15 | }
16 |
--------------------------------------------------------------------------------