Minimalistic building tool
For 3.x to 4.x migration instructions look here
Get started
Install runjs in your project
npm install runjs --save-dev
Create runfile.js
in your root project directory:
const run = { console} { } moduleexports = hello makedir
Call in your terminal:
$ npx run hello TommyHello Tommy!$ npx run makedirmkdir somedir
For node < 8.2, npx is not available, so doing
npm install -g runjs-cli
is neccessary which installs globalrun
script. After that above task would be called like:run hello Tommy
Mechanism of RunJS is very simple. Tasks are run by just importing runfile.js
as a
normal node.js module. Then based on command line arguments proper exported function
from runfile.js
is called.
RunJS in a nutshell
const runfile = const taskName = processargv2const options params = runfiletaskName
Why runjs ?
We have Grunt, Gulp, npm scripts, Makefile. Why another building tool ?
Gulp or Grunt files seem overly complex for what they do and the plugin ecosystem adds a layer of complexity towards the simple command line tools underneath. The documentation is not always up to date and the plugin does not always use the latest version of the tool. After a while customizing the process even with simple things, reconfiguring it becomes time consuming.
Npm scripts are simple but they get out of hand pretty quickly if we need more complex process which make them quite hard to read and manage.
Makefiles are simple, better for more complex processes
but they depend on bash scripting. Within runfile
you can use
command line calls as well as JavaScript code and npm
libraries which makes that approach much more flexible.
Features
Executing shell commands
RunJS gives an easy way to execute shell commands in your tasks by run
function
in synchronous and asynchronous way:
const run = { } moduleexports = all
$ run commands
Because ./node_modules/.bin
is included in PATH
when calling shell commands
by run
function, you can call "bins" from your local project in the same way as
in npm scripts.
Handling arguments
Provided arguments in the command line are passed to the function:
{ console} moduleexports = sayHello
$ run sayHello worldHello world!
You can also provide dash arguments like -a
or --test
. Order of them doesn't
matter after task name. They will be always available by options
helper
from inside a function.
const options = { console console} moduleexports = sayHello
$ run sayHello -a --test=something worldHello world!Given options:
Documenting tasks
To display all available tasks for your runfile.js
type run
in your command line
without any arguments:
$ run
Processing runfile.js...
Available tasks:
echo - echo task description
buildjs - Compile JS files
Use help
utility function for your task to get additional description:
const run help = { } moduleexports = buildjs
$ run buildjs --help
Processing runfile.js...
Usage: buildjs
Compile JS files
You can provide detailed annotation to give even more info about the task:
const dedent = const run help = { } moduleexports = test
$ run test --help
Processing runfile.js...
Usage: test [options] [file]
Run unit tests
Options:
--watch run tests in a watch mode
Examples:
run test dummyComponent.js
run test dummyComponent.js --watch
Namespacing
To better organise tasks, it is possible to call them from namespaces:
const test = { console } moduleexports = test
$ run test:unitDoing unit testing!
This is especially useful if runfile.js
gets too large. We can move some tasks
to external modules and import them back to a namespace:
./tasks/test.js
:
{ console} { console} moduleexports = unit integration
runfile.js
const test = moduleexports = test
$ run test:unitDoing unit testing!
If we don't want to put imported tasks into a namespace, we can always use spread operator:
moduleexports = ...test
$ run unitDoing unit testing!
With ES6 modules import/export syntax this becomes even simpler:
// export with no namespace // export with namespace
$ run unit$ run test:unit
Sharing tasks
Because runfile.js
is just a node.js module and runjs
just calls exported
functions from that module based on cli arguments, nothing stops you to move
some repetitive tasks across your projects to external npm package and
just reuse it.
shared-runfile
module:
{ console} { console} moduleexports = shared1 shared2
Local runfile.js
const shared = { console} moduleexports = ...shared local
$ run shared1$ run shared2$ run local
Autocompletion
After setting up autocompletion, suggestions about available
tasks from your runfile.js
will be given when calling run <tab>
.
This is an experimental feature. It will work slowly if you use transpiler with your
runfile.js
. It won't work also withnpx run <task>
calls,npm -g install runjs-cli
is necessary, so you could do calls likerun <task>
.
Setup process:
run --completion >> ~/runjs.completion.sh
echo 'source ~/runjs.completion.sh' >> .bash_profile
- Restart your shell (reopen terminal)
Depending on your shell, use proper bootstrap files accordingly.
If you get errors like
_get_comp_words_by_ref command not found
you need to install bash completion package. For MacOS users doingbrew install bash-completion
should do the job and then adding[ -f /usr/local/etc/bash_completion ] && ./usr/local/etc/bash_completion
. to your~/.bash_profile
.
Transpilers
Transpilers gives you an advantage of using ES6/ES7 features which may not be available in your node version.
So for example writing runfile.js
with es6 imports/exports is possible:
{ console}
$ run makeThatDir somedirmkdir somedirDone!
Babel
If you want to use Babel transpiler for your runfile.js
install it:
npm install babel-core babel-preset-es2015 babel-register --save-dev
and in your package.json
write:
RunJS will require defined transpiler before requiring runfile.js
so you can
use all ES6/ES7 features which are not supported by your node version.
TypeScript
If you want to use TypeScript transpiler for your runfile, install TypeScript tooling:
npm install typescript ts-node --save-dev
and then in your package.json
define a path to ts-node/register
and
runfile.ts
.
You need to also define custom path to your runfile as TypeScript files have
*.ts
extension. RunJS will require defined transpiler before requiring
./runfile.ts
.
API
For inside runfile.js
usage.
run(cmd, options)
run given command as a child process and log the call in the output.
./node_modules/.bin/
is included into PATH so you can call installed scripts directly.
const run =
Options:
cwd: ... // current working directory (String) async: ... // run command asynchronously (true/false), false by default stdio: ... // 'inherit' (default), 'pipe' or 'ignore' env: ... // environment key-value pairs (Object) timeout: ...
Examples:
To get an output from run
function we need to set stdio
option to pipe
otherwise
output
will be null
:
const output =
For stdio: 'pipe'
outputs are returned but not forwarded to the parent process thus
not printed out to the terminal.
For stdio: 'inherit'
(default) outputs are passed
to the terminal, but run
function will resolve (async) / return (sync)
null
.
For stdio: 'ignore'
nothing will be returned or printed
options(this)
A helper which returns an object with options which were given through dash params of command line script.
const options =
Example:
$ run lint --fix
{ fix ? : }
To execute a task in JS with options:
lint
help(func, annotation)
Define help annotation for task function, so it will be printed out when calling task with --help
option and when calling run
without any arguments.
const help =
$ run build --help
$ run test --help
Using Async/Await
For node >= 7.10 it is possible to use async functions out of the box since node will support them natively.
Expected usage in your runfile:
const run = { await console} moduleexports = testasyncawait
and then just
$ run testasyncawait
If your node version is older you need to depend on transpilers,
either Babel
or TypeScript
. For TypeScript
you do no more than transpiler
setup which was described above and async/await should just
work.
For Babel
you additionally need babel-preset-es2017
and babel-polyfill
:
npm install babel-preset-es2017 babel-polyfill --save-dev
and proper config in your package.json
:
"babel": {
"presets": ["es2017"]
},
"runjs": {
"requires": [
"./node_modules/babel-polyfill",
"./node_modules/babel-register"
]
}