Nevertheless! Party Metaphorically
    Share your code. npm Orgs help your team discover, share, and reuse code. Create a free org »

    flypublic

    This package has been deprecated

    Author message:

    Hello there! Fly has been renamed to Taskr, so please install `taskr` instead =) Currently the two packages are (nearly) identical; however, `fly` will no longer receive updates. There is a very quick migration guide available here: https://github.com/lukeed/taskr#history
    fly logo

    Table of Contents

    A generator & coroutine-based task runner.
    Fasten your seatbelt. :rocket:

    Fly is a highly performant task automation tool, much like Gulp or Grunt, but written with concurrency in mind. With Fly, everything is a coroutine, which allows for cascading and composable tasks; but unlike Gulp, it's not limited to the stream metaphor.

    Fly is extremely extensible, so anything can be a task. Our core system will accept whatever you throw at it, resulting in a modular system of reusable plugins and tasks, connected by a declarative flyfile.js that's easy to read.

    Table of Contents

    Table of Contents

    Features

    • lightweight: with 5 dependencies, installation takes seconds
    • minimal API: Fly only exposes a couple methods, but they're everything you'll ever need
    • performant: because of Bluebird, creating and running Tasks are quick and inexpensive
    • cascading: sequential Task chains can cascade their return values, becoming the next Task's argument
    • asynchronous: concurrent Task chains run without side effects & can be yielded consistently
    • composable: chain APIs and Tasks directly; say goodbye to pipe() x 100!
    • modular: easily share or export individual Tasks or Plugins for later use
    • stable: requires Node 6.x to run (LTS is 6.9)

    Example

    Here's a simple flyfile (with shorthand generator methods) depicting a parallel chain.

    const sass = "src/{admin,client}/*.sass"
    const js = "src/{admin,client}/*.js"
    const dist = "build"
     
    module.exports = {
      *lint(fly) {
        yield fly.source(js).xo({ esnext: true })
      },
      *scripts(fly) {
        yield fly.source(js).babel({ presets: ["es2015"] }).target(`${dist}/js`)
      },
      *styles(fly) {
        yield fly.source(sass).sass({ outputStyle: "compressed" }).autoprefixer().target(`${dist}/css`)
      },
      *build(fly) {
        yield fly.parallel(["lint", "scripts", "styles"])
      }
    }

    Concepts

    Core

    Fly is a task runner. It's designed to get you from A to B -- that's it.

    If it helps, imagine you're dining in a restaurant and Fly is the food runner. Fly's role is solely to collect meals from the kitchen (fly.source) and deliver them to the correct table (fly.target). As a food runner, Fly may do this one plate at a time (fly.serial) or deliver multiple plates at once (fly.parallel). Either way, Fly only cares about going from A to B. It may not be the most glamorous job, but as far as you (the patron) are concerned, it's incredibly important because it brings you food.

    Plugins

    Because Fly is single-minded and cares only about executing tasks, everything else is a plugin. This keeps development with Fly easy, approachable, and lightweight.

    You see, installing Fly gives access to a reliable task runner. You decide what it can do, provide it functionality, and dictate when to do it. You're in full control.

    Through plugins, you are able to capture useful behavior and share them across tasks or projects for repeated use. Plugins come in three flavors:

    • external - installed via NPM; called "external" because they live outside your codebase
    • inline - generally simple, one-time functions; not sensible for reuse since declared within a task (hence "inline")
    • local - private, reusable plugins; appear exactly like external plugins but not public on NPM.

    Tasks

    Tasks are used to tell Fly what to do. They are written as generator functions & converted to coroutines internally. They're also fully self-contained and, like plugins, can be shared across projects if desired.

    Upon runtime, tasks are cheap to create, so are also destroyed once completed. This also helps Fly remain efficient; history won't weigh it down.

    Lastly, tasks have the power to start other Tasks, including serial and parallel chains!

    Flyfiles

    Much like Gulp, Fly uses a flyfile.js (case sensitive) to read and run your Tasks. However, because it's a regular JavaScript file, you may also require() additional modules and incorporate them directly into your Tasks, without the need for a custom Plugin!

    const browserSync = require("browser-sync")
     
    exports.serve = function * (fly) {
      browserSync({
        port: 3000,
        server: "dist",
        middleware: [
          require("connect-history-api-fallback")()
        ]
      })
      yield fly.$.log("> Listening on localhost:3000")
    }

    Flyfiles should generally be placed in the root of your project, alongside your package.json. Although this is not required, Fly (strongly) prefers this location.

    Note: You may set an alternate directory path through the CLI's cwd option.

    Through Node, Fly only supports ES5 syntax; however, if you prefer ES6 or ES7, just install fly-esnext!

    CLI

    Fly's CLI tool is very simple and straightforward.

    fly [options] <tasks>
    fly --mode=parallel task1 task2 ...
    

    Please run fly --help or fly -h for usage information.

    Most commonly, the CLI is used for NPM script definitions.

    // package.json
    {
      "scripts": {
        "build": "fly task1 task2"
      }
    }

    API

    Fly

    Fly itself acts as a "parent" class to its Task children. Because of this, Fly's methods are purely executive; aka, they manage Tasks and tell them how & when to run.

    fly.start(task, [options])

    Yield: Any
    Start a Task by its name; may also pass initial values. Can return anything the Task is designed to.

    task

    Type: String
    Default: 'default'
    The Task's name to run. Task must exist/be defined or an Error is thrown.

    Important! Fly expects a default task if no task is specified. This also applies to CLI usage.

    options

    Type: Object
    Default: {src: null, val: null}
    Initial/Custom values to start with. You may customize the object shape, but only val will be cascaded from Task to Task.

    fly.parallel(tasks, [options])

    Yield: Any
    Run a group of tasks simultaneously. Cascading is disabled.

    tasks

    Type: Array
    The names of Tasks to run. Task names must be strings and must be defined.

    options

    Type: Object
    Initial values to start with; passed to each task in the group. Does not cascade.

    fly.serial(tasks, [options])

    Yield: Any
    Run a group of tasks sequentially. Cascading is enabled.

    tasks

    Type: Array
    The names of Tasks to run. Task names must be strings and must be defined.

    options

    Type: Object
    Initial values to start with; passed to each task in the group. Does cascade.

    module.exports = {
      *default(fly) {
        yield fly.serial(["first", "second"], {val: 10})
      },
      *first(fly, opts) {
        yield fly.$.log(`first: ${opts.val}`)
        return opts.val * 4
      },
      *second(fly, opts) {
        yield fly.$.log(`second: ${opts.val}`)
        return opts.val + 2
      }
    }
     
    const output = yield fly.start()
    //=> first: 10
    //=> second: 40
    console.log(output)
    //=> 42

    Plugin

    Plugins can be external, internal, or local. However, all plugins share the same options:

    options.every

    Type: Boolean
    Default: true
    If the plugin function should iterate through every file|glob.

    options.files

    Type: Boolean
    Default: true
    If the plugin should receive the Task's glob patterns or its expanded file objects. Uses globs if false.

    Every plugin must also pass a generator function, which will be wrapped into a coroutine. This function's arguments will be the file|glob(s), depending on the options.every and options.files combination. The function's second argument is the user-provided config object.

    The plugin's generator function is always bound to the current Task, which means this refers to the Task instance.

    Internal Plugins

    Internal plugins are for single-use only. If you're defining the same behavior repeatedly, it should be extracted to a local or external plugin instead.

    Note: Inline plugins have no need for a second argument in their generator function; you are the "user" here.

    See task.run for a simple example. The same inline example may be written purely as an object:

    exports.foo = function * (fly) {
      yield fly.source("src/*.js").run({
        every: false,
        *func(files) {
          Array.isArray(files) //=> true
          yield Promise.resolve("this will run once.")
        }
      }).target("dist")
    }

    External Plugins

    Unlike "inline" plugins, external and local plugins are defined before a Task is performed. Because of this, they must define a name for their method to use within a Task.

    Similar to inline plugins, there are two ways of defining an exported module -- via functional or object definitions.

    When using a functional definition, the definition receives the Fly instance and the utilities object.

    module.exports = function (fly, utils) {
      // promisify before running else repeats per execution
      const render = utils.promisify(function () {})
      // verbose API
      fly.plugin("myName", {every: false}, function * (files, opts) {
        console.log("all my files: ", files) //=> Array
        console.log(this._.files === files) //=> true
        console.log(this instanceof Task) //=> true
        console.log("user options: ", opts)
        yield render(opts)
      })
      // or w/condensed API
      fly.plugin({
        name: "myName",
        every: false,
        *func(files, opts) {
          // ...same
        }
      })
    }

    When using an object definition, you are not provided the fly or utils objects. This assumes that you do not need any prep work for your plugin!

    module.exports = {
      name: "myName",
      every: false,
      *func(files, opts) {
        // do stuff
      }
    }

    Then, within your Task, you may use it like so:

    exports.default = function * (fly) {
      yield fly.source("src/*.js").myName({ foo: "bar" }).target("dist")
    }

    Local Plugins

    Local plugins are defined exactly like external plugins. The only difference is that they're not installable via NPM.

    In order to use a local plugin, add a fly key to your package.json file. Then define a requires array with paths to your plugins.

    {
      "fly": {
        "requires": [
          "./build/custom-plugin-one.js",
          "./build/custom-plugin-two.js"
        ]
      }
    }

    For programmatic usage, simply pass an array of definitions to the plugins key:

    const Fly = require('fly')
    const fly = new Fly({
      plugins: [
        require("./build/custom-plugin-one.js"),
        require("./build/custom-plugin-two.js"),
        require("fly-clear")
        {
          name: "plugThree",
          every: false,
          files: false,
          *func(globs, opts) {
            // nifty, eh?
          }
        }
      ]
    })

    Task

    A Task receives itself as its first argument. We choose to name the parameter fly simply as a convention; of course, you may call it whatever you'd like.

    Tasks are exported from a flyfile.js, which means you can use either syntax:

    exports.foo = function * (fly) {
      yield fly.source("src/*.js").target("dist/js")
    }
    exports.bar = function * (fly) {
      yield fly.source("src/*.css").target("dist/css")
    }
    // or
    module.exports = {
      *foo(fly) {
        yield fly.source("src/*.js").target("dist/js")
      },
      *bar(fly) {
        yield fly.source("src/*.css").target("dist/css")
      }
    }

    Each Task also receives an opts object, consisting of src and val keys. Although src is primarily used for fly-watch, the val key can be used or set at any time see fly.serial.

    All methods and values below are exposed within a Task's function.

    task.root

    Type: String
    The directory wherein flyfile.js resides, now considered the root. Also accessible within plugins.

    task.$

    Type: Object
    The Task's utility helpers. Also accessible within plugins. See Utilities.

    task._

    Type: Object
    The Task's internal state, populated by task.source(). Also accessible within plugins.

    task._.files

    Type: Array
    The Task's active files. Each object contains a dir and base key from its pathObject and maintains the file's Buffer contents as a data key.

    task._.globs

    Type: Array
    The Task's glob patterns, from task.source(). Used to populate task._.files.

    task._.prevs

    Type: Array
    The Task's last-known (aka, outdated) set of glob patterns. USed only for fly-watch.

    task.source(globs, [options])

    globs

    Type: Array|String
    Any valid glob pattern or array of patterns.

    options

    Type: Object
    Default: {}
    Additional options, passed directly to node-glob.

    task.target(dirs, [options])

    dirs

    Type: Array|String
    The destination folder(s).

    options

    Type: Object
    Default: {}
    Additional options, passed directly to fs.writeFile.

    Please note that task.source() glob ambiguity affects the destination structure.

    yield fly.source("src/*.js").target("dist")
    //=> dist/foo.js, dist/bar.js
    yield fly.source("src/**/*.js").target("dist")
    //=> dist/foo.js, dist/bar.js, dist/sub/baz.js, dist/sub/deep/bat.js

    task.run(options, generator)

    Perform an inline plugin.

    options

    Type: Object
    The See plugin options.

    generator

    Type: Function
    The action to perform; must be a Generator function.

    exports.foo = function * (fly) {
      yield fly.source("src/*.js").run({every: false}, function * (files) {
        Array.isArray(files) //=> true
        yield Promise.resolve("this will run once.")
      }).target("dist")
    }

    task.start(task, [options])

    See fly.start.

    task.parallel(tasks, [options])

    See fly.parallel.

    task.serial(tasks, [options])

    See fly.serial.

    Utilities

    A collection of utility helpers to make life easy.

    alert()

    Print to console with timestamp and alert coloring.

    coroutine(generator)

    See Bluebird.coroutine.

    error()

    Print to console with timestamp and error coloring.

    expand(globs, options)

    Yield: Array
    Get all filepaths that match the glob pattern constraints.

    globs

    Type: Array|String

    options

    Type: Object
    Default: {}
    Additional options, passed directly to node-glob.

    find(filename, dir)

    Yield: String|null
    Find a complete filepath from a given path, or optional directory.

    filename

    Type: String
    The file to file; may also be a complete filepath.

    dir

    Type: String
    Default: '.'
    The directory to look within. Will be prepended to the filename value.

    log()

    Print to console with timestamp and normal coloring.

    promisify(function, callback)

    See Bluebird.promisify.

    read(filepath, options)

    Yield: Buffer|String|null
    Get a file's contents. Ignores directory paths.

    filepath

    Type: String
    The full filepath to read.

    options

    Type: Object
    Additional options, passed to fs.readFile.

    trace(stack)

    Parse and prettify an Error's stack.

    write(filepath, data, options)

    Yield: null
    Write given data to a filepath. Will create directories as needed.

    filepath

    Type: String
    The full filepath to write into.

    data

    Type: String|Buffer
    The data to be written; see fs.writeFile.

    options

    Type: Object
    Additional options, passed to fs.writeFile.

    Installation

    $ npm install --save-dev fly

    Usage

    Getting Started

    1. Install Fly & any desired plugins. (see installation and ecosystem)
    2. Create a flyfile.js next to your package.json.
    3. Define default and additional tasks within your flyfile.js.
    export.default = function * (fly) {
      yield fly.parallel(["styles", "scripts"])
    }
     
    export.styles = function * (fly) {
      yield fly.source("src/**/*.css").autoprefixer().target("dist/css")
    }
     
    export.scripts = function * (fly) {
      yield fly.source("src/**/*.js").babel({
        presets: [
          ["es2015", {loose: true, modules: false}]
        ]
      })
    }
    1. Add a "scripts" key to your package.json:
    {
      "name": "my-project",
      "scripts": {
        "build": "fly"
      }
    }

    Note: The default task is run if no other tasks are specified.

    1. Run your build command:
    $ npm run build

    You may be interested in checking out a Web Starter Kit for a head start.

    Programmatic

    Fly is extremely flexible should you choose to use Fly outside of its standard configuration.

    The quickest path to a valid Fly instance is to send a tasks object:

    const Fly = require("Fly")
    const fly = new Fly({
      tasks: {
        *foo(f) {},
        *bar(f) {}
      }
    })
    fly.start("foo")

    By default, your new Fly instance will not include any plugins. You have the power to pick and choose what your instance needs!

    To do this, you may pass an array of external and local plugins:

    const fly = new Fly({
      plugins: [
        require("fly-clear"),
        require("fly-concat"),
        require("./my-plugin")
      ]
    })

    Important: This assumes you have provided a valid file or tasks object. Without either of these, your Fly instance will be incomplete and therefore invalid. This will cause the instance to exit early, which means that your plugins will not be mounted to the instance.

    You may also define your tasks by supplying a flyfile.js path to file. Whenever you do this, you should also update the cwd key because your root has changed!

    const join = require("path").join
     
    const cwd = join(__dirname, "..", "build")
    const file = join(cwd, "flyfile.js")
     
    const fly = new Fly({ file, cwd })

    Ecosystem

    Below is a list of official plugins. You may also browse all fly-related plugins on NPM.

    If you'd like to create and share a plugin for Fly, we have a Yeoman generator to speed up the process.

    install

    npm i fly

    Downloadsweekly downloads

    217

    version

    2.0.6

    license

    MIT

    repository

    githubgithub

    last publish

    collaborators

    • avatar