Automate tasks on file change, run them in parallel, and forget about tool-specific plugins.
Think NPM scripts + (Precise) File watcher + Parallel scripting - Task Runner/Build Tool - Tool-specific plugins.
Precise: definitely or strictly stated, defined, or fixed
.
Run npm i precise-watcher
or yarn add precise-watcher
.
Using precise-watcher
is as simple as adding the following to your package.json
and running npm run watch
:
{
"precise-watcher": {
"src": [{
"pattern": ["**/*"],
"on": "change",
"run": [{
"cmd": "echo",
"args": ["<file> changed."]
}]
}]
},
"scripts": {
"watch": "precise-watcher"
},
"devDependency": {
"precise-watcher": "^1.0.0"
}
}
To run it, just modify any file not included in your .gitignore file...
This tool allows you to:
- Run commands on 1 single file when it changes (by using "<file>" in src.run.args).
- Call as many commands in parallel or serial as you want.
- Watch not only for changes but errors, removals, addings, ...
- Watch multiple sources and run multiple scoped commands.
- Write complex solutions easily.
- [From v2.0]: Run all your commands and exit. Useful for production!
Once you see what it does and why you need it, you can use it in any of the following ways:
Add precise-watcher
to your package.json
file in the following way:
{
"precise-watcher": {
// Your options go here.
},
"scripts": {
// Replace "watch" with whatever you want.
"watch": "precise-watcher"
}
}
Then, run the NPM script (npm run watch
in this case) and press ctrl + c
, as normal, to stop watching files.
Addittionaly, you can remove the options from your package.json
file and use a config. file to set your options:
Create a config. file called precise-watcher.config.js
(for example), and reference it using the --config
option in the command you added before.
// precise-watcher.config.js
module.exports = {
// Your options go here.
}
{
"scripts": {
"watch": "precise-watcher --config precise-watcher.config.js"
}
}
As you can see, you no longer need the precise-watcher
property anymore.
To start watching file changes:
- Create a config file called
precise-watcher.config.js
, for example. - Run
./node_modules/.bin/precise-watcher --config precise-watcher.config.js
.
To stop watching file changes:
- Press
ctrl + c
.
Below are a list of options that you can use with precise-watcher
:
Option | Defaults | Description |
---|---|---|
--cwd | process.cwd() | Directory for chokidar, config file, and child_process.spawn(). |
--config | package.json | Path to your config. file, relative to cwd. |
Should be as easy as:
const { start, stop, shutdown } = require('precise-watcher')
// Resolves to an array of chokidar.watch() instances:
start().then(async (watchers) => {
// To remove some watchers:
// Resolves to an array of each watcher.close() result.
const closedWatchers = await stop(watchers)
// To remove all watchers:
const allClosedWatchers = await stop()
// To exit:
shutdown() // Calls stop() internally.
}) // ...
With async/await:
const { start, stop, shutdown } = require('precise-watcher')
(async () => {
try {
const watchers = await start()
const closedWatchers = await stop(watchers)
const allClosedWatchers = await stop()
shutdown()
} catch (error) {
// Log error...
}
})()
If you need more inspiration, you can check out these examples:
- Run
npm install eslint --save-dev
. - Add the following to your
package.json
:
{
"precise-watcher": {
"src": [{
"pattern": ["**/*.js"],
"on": "change",
"run": [{
"cmd": "echo",
"args": [
"Running eslint <file> --fix"
],
"callNext": "parallel"
}, {
"cmd": "eslint",
"args": [
"<file>",
"--fix"
],
"callNext": "serial"
}, {
"cmd": "echo",
"args": ["Done"]
}]
}]
},
"scripts": {
"watch": "precise-watcher"
}
}
- Run
npm run watch
. - Modify any .js file.
- Install requirements: Run
npm install live-server precise-watcher@^2.0 cpy-cli --save-dev
. - Create some files:
/static/index.html
and/static/img/favicon.svg
, for example. - Add the following to
precise-watcher.config.js
:
const {NODE_ENV} = process.env;
const isProduction = NODE_ENV === 'production';
const isDevelopment = !isProduction;
module.exports = {
"src": [].concat(isDevelopment ? {
"pattern": "public",
"on": "ready",
"run": [{
// Start development server.
"cmd": "live-server",
"args": ["public"]
}]
} : {
"pattern": "dist",
// Empty the /dist dir.
"run": [{
"cmd": "rm",
"args": ["dist -R"]
}, {
"cmd": "mkdir",
"args": ["dist"]
}]
}).concat({
"pattern": ["static/**/*.{jpg,jpeg,png,ico,svg,html}"],
"baseDir": "static",
"on": (isProduction
? null // Run as soon as possible.
: ["ready", "change"] // Run when chokidar is ready and when it detects a change.
)
"run": {
// Copy your static content (images and HTMLs), one by one.
"cmd": "cpy",
"args": `<file> ../${isDevelopment ? "public" : "dist"} --cwd=static --parents`.split(" ")
}
})
};
- Update your npm scripts (update your
package.json
):
"scripts": {
"start": "precise-watcher --config precise-watcher.config.js",
"dev": "NODE_ENV=development npm run start",
"prod": "NODE_ENV=production npm run start"
}
-
Run
npm run dev
and wait for your browser to start. Then, modify any file (e.g,static/index.html
) and your changes should be visible right away. -
When you're done, hit
ctrl + c
and runnpm run prod
. Your/dist
folder will now contain all your final files.
{
/** @type {?object} (Optional) chokidar options that will apply to all sources. Defaults to the following, as of chokidar@3.5: */
"chokidar": {
"persistent": true,
// Concatenated with src.ignoreFrom sources.
"ignored": [],
"ignoreInitial": false,
"followSymlinks": false,
// Defaults to value passed via --cwd, "cwd" param in src/start.js' main function, or process.cwd()
"cwd": "",
"disableGlobbing": false,
"usePolling": false,
"useFsEvents": true,
"alwaysStat": false,
"depth": undefined,
"awaitWriteFinish": false,
"ignorePermissionErrors": false,
"atomic": true
},
/** @type {object|object[]} Source(s) to watch. */
"src": [{
/** @type {string|RegExp|string[]|RegExp[]} Pattern(s) to watch. */
"pattern": [],
/** @type {?string} (Optional) Set "<file>" replacement relative to this value. Basically: path.relative(baseDir, watchedFile). Useful to convert /some/path/file to /file, for example */
"baseDir": "",
/** @type {?string} A path to a .gitignore-like file to ignore sources matched by src.pattern. Relative to cwd. */
"ignoreFrom": ".gitignore",
/** @type {object|object[]} An array of commands. */
"run": [{
/** @type {?string} The command to run. */
"cmd": "",
/**
* @type {?string[]} List of arguments for cmd.
*
* Use "<file>" to replace it with the changed filepath if the event
* supports it, or set each src.pattern value as default (and call
* this command in parallel multiple times with each given pattern).
*/
"args": [],
/** @type {?string} Any of "serial" or "parallel". Defaults to "serial". */
"callNext": "serial",
/**
* @type {?function} A function triggered before running the command.
* @return {?boolean} If false is returned, the command won't run.
*/
beforeRun (cmdInfo, eventInfo) {
const {
callNext,
patterns,
baseDir,
commands
} = this;
const {
cmd,
args: cmdArgs,
options: cmdOptions
} = cmdInfo;
const {
name: eventName,
args: eventArgs
} = eventInfo;
let keepRunning = true;
return keepRunning;
}
}],
/** @see https://github.com/paulmillr/chokidar */
/** @type {?string[]|?string} One or many chokidar events: "add", "unlink", "addDir", "unlinkDir", "error", "ready", "raw". */
"on": [],
/** @see https://github.com/paulmillr/chokidar */
/** @type {?object} Chokidar options that will apply to these sources only. Merged with global "chokidar" options. */
"chokidar": {}
}]
}
Thanks to Chokidar's author and contributors and this project's contributors.
This project is MIT Licensed.