start
- highly composable and modular
- shareable tasks presets
- Higher-Order Functions and Promises
- really dead simple
TOC
Install
npm i -S start
yarn add start
Tasks file
import Start from 'start';
import reporter from 'start-pretty-reporter';
import env from 'start-env';
import files from 'start-files';
import watch from 'start-watch';
import clean from 'start-clean';
import read from 'start-read';
import babel from 'start-babel';
import write from 'start-write';
import eslint from 'start-eslint';
import mocha from 'start-mocha';
import * as istanbul from 'start-istanbul';
import codecov from 'start-codecov';
const start = Start(reporter());
export const build = () => start(
env('NODE_ENV', 'production'),
files('build/'),
clean(),
files('lib/**/*.js'),
read(),
babel(),
write('build/')
);
export const dev = () => start(
env('NODE_ENV', 'development'),
files('build/'),
clean(),
files('lib/**/*.js'),
watch((file) => start(
files(file),
read(),
babel(),
write('build/')
))
);
export const lint = () => start(
files([ 'lib/**/*.js', 'test/**/*.js' ]),
eslint()
);
export const test = () => start(
env('NODE_ENV', 'test'),
files('test/**/*.js'),
mocha()
);
export tdd = () => start(
files([ 'lib/**/*.js', 'test/**/*.js' ]),
watch(test)
);
export coverage = () => start(
env('NODE_ENV', 'test'),
files('coverage/'),
clean(),
files('lib/**/*.js'),
istanbul.instrument(),
test,
istanbul.report()
);
export ci = () => start(
lint,
coverage,
files('coverage/lcov.info'),
read(),
codecov()
);
Each named export return a Promise – "tasks runner" – sequence of tasks managed by start
, which will run them one by one passing data through until an error occurs. As you can see tasks runners can be nested in each other to achieve great reusability.
You can run it manually:
build()
.then((data) => {
console.log('ok:', data);
})
.catch((error) => {
console.error('not ok:', error);
});
Or you can use an external CLI:
CLI
npm i -D start-simple-cli
yarn add --dev start-simple-cli
Usage: index [options] <tasks runner> [arguments]
Options:
-h, --help output usage information
-f, --file, <file> tasks file path, tasks.js by default
-p, --preset, <preset> tasks preset
Browse available CLIs.
NPM scripts
For example for tasks.js
listed above, transpiling with Babel:
npm i -D start-babel-cli
yarn add --dev start-babel-cli
"scripts": {
"start": "start-runner -f tasks.js"
}
And your available commands are:
npm start build
npm start dev
npm start lint
npm start test
npm start tdd
npm start coverage
npm start ci
yarn start build
yarn start dev
yarn start lint
yarn start test
yarn start tdd
yarn start coverage
yarn start ci
See NPM documentation for details.
Presets
You can make your tasks file (and its dependencies!) completely external and shareable. Like a start-my-es6-preset
package for a bunch of your similar projects. See start-start-preset as an example and browse available presets.
"scripts": {
"start": "start-runner -p start-my-es6-preset"
}
API
start(reporter())(
task1(),
task2(),
...
);
Reporter
Reporter is an external function which prints the results of running tasks.
The simplest dummy reporter can be represented as following:
export default (params) => (name, type, message) => {
console.log(name, type, message);
};
(params)
First function call made by user. params
can be options object, multiple arguments or whatever your reporter needs to be configured and initialized.
(name, type, message)
Second function call made by start
:
name
– task nametype
– log type:
start
info
– must come with message
resolve
error
– may come with message
message
– may be undefined, string, array of strings or instance of Error
See start-simple-reporter as an example and browse available reporters.
Task
The simplest dummy task can be represented as following:
export default (params) => (input) => {
return function taskName(log, reporter) {
const cats = require('cats-names');
log(cats.random());
return Promise.resolve(input);
};
};
(params)
First function call made by user. params
can be options object, multiple arguments or whatever your task needs to be configured and initialized.
(input)
Second function call made by start
with the result of previous task in chain. It's a good idea to pass the input
data through if your task doesn't modify it.
Tasks like start-tape relies on array of files paths. This can be provided by start-files:
start(
files('tests/**/*.js'),
tape()
)
input
:
[
'/absolute/path/file1.js',
'/absolute/path/file2.js'
]
Tasks like start-babel relies on files data and optional source maps. This can be provided by start-read or other tasks which works with data:
start(
files('lib/**/*.js'),
read(),
babel()
)
input
:
[
{
path: '/absolute/path/file1.js',
data: '…',
map: '…'
},
{
path: '/absolute/path/file2.js',
data: '…',
map: null
}
]
And finally start-write may output files data along with source maps:
start(
files('lib/**/*.js'),
read(),
babel({ sourceMaps: true }),
write('build/')
)
taskName(log, reporter)
Third function call made by start
.
taskName
– will be used as task name for logginglog
– function which is bound to reporter(name, 'info')
, so if your task has something to say expect errors then you have to call log
with message (or array of messages)reporter
– original reporter, enables creating advanced tasks runners, see start-concurrent as an example
require
It's a good idea to "lazyload" your dependencies inside a task scope instead of requiring them at the very top. Execution time can be a problem, and there is no need to require all the heavy dependencies while cleaning a single directory (for example).
return
And finally, your task must return an ES6 Promise. It can be resolved with data which will be passed to the next Promise in chain, or rejected with some message (or array of messages).
Browse available tasks.
Advanced usage
Pass arguments to tasks through CLI
export const build = (arg1, arg2) => start(
task1(arg1),
task2(arg2)
);
npm start build 'lib/**/*.js' 'hi'
yarn start build 'lib/**/*.js' 'hi'
Pass arguments to nested tasks runners
export const test = (arg) => start(
task1(arg)
);
export const coverage = () => start(
tas2k(),
() => test('hi'),
task3()
);
Pass output data to nested tasks runners
import Start from 'start';
import reporter from 'start-pretty-reporter';
import files from 'start-files';
import inputConnector from 'start-input-connector';
import eslint from 'start-eslint';
const start = Start(reporter());
const lint = (input) => start(
inputConnector(input),
eslint()
);
export const lintLib = () => start(
files([ 'lib/**/*.js' ]),
lint
);
export const lintTest = () => start(
files([ 'test/**/*.js' ]),
lint
);
FAQ
Why do I need yet another tasks runner in 2k17 if I already have…
…Webpack?
Webpack is a "module bundler", not a tasks runner. Despite the fact that you may have some tricky tasks-plugins and can even lint files or clean folders with Webpack, in my opinion it's not a good idea. A great tool becomes a hulking "swiss-knife", in a bad way.
Make each program do one thing well. To do a new job, build afresh rather than complicate old programs by adding new "features".
Unix philosophy. So better let Webpack just to bundle modules, he knows how to do it well.
Also, you still need to somehow run Webpack itself. start-webpack :) Start can't and shouldn't replace any kind of smart module bundlers. It's more a low-level abstraction, something like Makefile.
…NPM scripts?
I know, I know. You don't need any runners because NPM scripts can solve all the common tasks. I thought that too. But then I began to write more and more complex NPM scripts. And pre-scripts. And post-scripts. And scripts like:that
. And still had a lot of &&
. After that I began to worry about Windows, because I should be a good person, so I had to install cross-env
. And rimraf
. And cli -i -s --tot ally -i n,c,o, -n=s -- istent
. Try to use --long-but-understandable-after-2-months
options and your NPM script will be 2 screens width.
(╯°□°)╯︵ ┻━┻
.
You already know JavaScript, so use it. API over CLI just because it's cool.
…Grunt/Gulp?
It's more a matter of taste. And "spirit of the age". If you are totally fine with Grunt/Gulp then most likely there is no any need to change your workflow.
Whoa! :scream_cat: What a great idea. I want more.
Sure :sunglasses:
Copyrights
This software is released under the terms of the MIT License.
The font used in logo is supernova fat.