3.0.3 • Published 3 years ago

bunbun v3.0.3

Weekly downloads
5
License
Apache-2.0
Repository
github
Last release
3 years ago

bunbun

Node.JS based simple, lightweight task bunner... I mean runner

Why?

Lightweight and very fast - very simple in design, without any library compilers that you will never use, just plain task runner

📐 Ready to use - just add to your project, create file and prepare your first task

🔌 Extendable - "extends-by-code" philosophy - you are owner of your build flow, without any magic and invisible parts/configurations etc., neither no compilers included

Universal - this library contains only language/tool/framework agnostic tools, all compilations/copying things are in your hands

🎈 Without magic - all of your tasks are just JS code, without any configs, 3rd-party plugins etc. just plain source code, therefore the learning curve is very low

🐞 Debuggable - debugging is very simple because none of all tasks are done invisibly underhood, it's easy to find erroring part if you see all steps of build process, also you can enable debugging mode in Bunbun to log all things

🧰 Safe versioning - version of Bunbun will never break any of building processes because Bunbun don't conains any API for them, updating Bunbun will need from you only update your building script - it's all, you will never have to wait for a some plugin to be updated

Requirements

  • Node.JS >= 7.6v
    • ...because of default support of async/await syntax
  • or Node.JS >= 10v
    • ...for execution and handling processes
  • NPM

How to install?

Simplest way is just using npm:

npm install --save-exact --save-dev bunbun

Real example

Example code:

const { build } = require('esbuild');
const nodeSass = require('node-sass');
const Bunbun = require('./../dist/main.js');

const sass = args => new Promise((res, rej) => {
    nodeSass.render(args, (err, data) => {
        err ? rej(err) : res(data);
    });
});

const hash = async file => {
    const res = await $.fs.hash(file, 'md5', 'base64');
    return res.replace(/[^a-z0-9]/gi, '');
};

const SOURCE_EXTS = '(scss|ts|tsx|js|jsx|html)';

const $ = new Bunbun;

$.task('build:typescript', async () => {
    await $.until('hash');
    await build({
        sourcemap: true,
        format: 'iife',
        minify: true,
        bundle: true,
        outfile: './build/index.js',
        entryPoints: ['./src/index.tsx'],
        platform: 'browser',
        tsconfig: './tsconfig.json',
        define: {
            'process.env.NODE_ENV': '"develop"',
        },
    });
    $.run('hash');
});

$.task('build:scss', async () => {
    await $.until('hash');
    const result = await sass({
        file: './src/index.scss',
        sourceMap: './index.css.map',
        outFile: './index.css',
        outputStyle: 'compressed',
        sourceMapContents: true,
    });
    await $.fs.write('./build/index.css', result.css || '');
    await $.fs.write('./build/index.css.map', result.map || '');
    $.run('hash');
});

$.task('hash', async () => {
    await $.fs.edit('./src/index.html', async html => {
        const jsHash = await hash('./build/index.js');
        const cssHash = await hash('./build/index.css');
        return html
            .replace('__JS_HASH__', jsHash)
            .replace('__CSS_HASH__', cssHash);
    });
});

$.alias('build', ['build:typescript', 'build:scss']);

$.task('assets', async () => {
    let list = await $.fs.list(['./src/**/*.*', `!**/*.${SOURCE_EXTS}`]);
    for (const x of list) {
        await $.fs.copy(x, x.replace(/^\.\/src/, './build'));
    }
});

$.task('watch', async () => {
    const server = $.serve('./build', 8080);

    $.fs.watch(`./src/**/*.${SOURCE_EXTS}`, async () => {
        await $.run('build');
        server.reload();
    });

    $.fs.watch(`./src/**/*.!${SOURCE_EXTS}`, async () => {
        await $.run('assets');
        server.reload();
    });
});

$.start('build');

Table of Contents

Main class of whole script, it's also default exported thing from module

type Options = {
    // Determines if logger should be louder by default
    debugging: boolean; // default: false

    // Determines if logger should be silent by default
    // this option has higher priority than `debugging`
    silent: boolean; // default: false

    // Determines if tasks should wait for previous promise by default
    debouncePromise: boolean; // default: true

    // Determines if tasks should debounce and with what time by default
    // 0 means turn off
    debounceTime: number; // default: 200

    // Current working directory for fs/exec etc.
    cwd: string; // default: process.cwd()
};

type Bunbun = {
    new(options?: Partial<Options>);
    // ...
};
const Bunbun = require('bunbun');

// Debugging instance
const $1 = new Bunbun({
    debugging: true,
});

// Default instance
const $2 = new Bunbun();

Fs (filesystem) instance available for external usage

fs: Fs;
const $ = new Bunbun;

$.fs.read('./file.txt').then(data => {
    // ...
});

Logger instance used by Bunbun and available for external usage as well

logger: Logger;
const $ = new Bunbun;

const myVar = { test: 1 };

$.logger.log('my var = $$', myVar);

Registers alias of tasks, those tasks will be executed asynchronously. Name of such alias will be in same pool where tasks are so you can't register task of same name which already exists

alias(
    name: string,
    tasks: string[],
): void;
const $ = new Bunbun;

$.task('foo', () => {
    console.log('foo');
});
$.task('bar', async () => {
    await someLongAction();
    console.log('bar');
});

$.alias('baz', ['bar', 'foo']);
// is equal to
$.task('qux', async () => {
    return Promise.all([
        $.run('bar'),
        $.run('foo'),
    ]);
});

$.run('baz'); // or $.run('qux');
// output:
// >> foo
// >> bar (because it's waited for long running function)

Creates debounce function of given function, this kind of function allows you avoid multiple calls of time-critical functions, e.g:

  • To avoid overloop building process for same code
  • To avoid overwriting things at overwriting they in previous call

💡 Tip - time debouncing don't call function until given timeout, promise debouncing call function and wait for fulfilling last promise

debounce<T>(
    fn: () => Promise<T> | T,
    time: number = 0,
): Promise<T>;
const $ = new Bunbun;

// Not working debounce (without promise nor time)
let i1 = 0;
const fnNothing = $.debounce(() => {
    console.log('1', i1 += 1);
});
// Promise based debounce
let i2 = 0;
const fnPromise = $.debounce(() => {
    new Promise(res => setTimeout(() => {
        console.log('2', i2 += 1);
        res();
    }, 10 * 1000))
});
// Time based debounce
let i3 = 0;
const fnTime = $.debounce(() => {
    console.log('3', i3 += 1);
}, 10 * 1000);
// Promise and time based debounce
let i4 = 0;
const fnPromiseAndTime = $.debounce(() =>
    new Promise(res => setTimeout(() => {
        console.log('4', i4 += 1);
        res();
    }, 9 * 1000))
, 9 * 1000);

// --- results ---

fnNothing(); // > 1
fnNothing(); // > 2
fnNothing(); // > 3
fnNothing(); // > 4

fnPromise(); // > wait for promise > 1
// after 5s
fnPromise(); // > wait for previous result > 1
// after 5s
fnPromise(); // > wait for new promise > 2
// after 10s
fnPromise(); // > wait for new promise > 3

fnTime(); // > wait for timeout > 1
// after 5s
fnTime(); // > wait for previous timeout > 1
// after 5s
fnTime(); // > wait for new timeout > 2
// after 10s
fnTime(); // > wait for new timeout > 3

fnPromiseAndTime(); // > wait for timeout > wait for promise > 1
// after 5s
fnPromiseAndTime(); // > wait for previous timeout > wait for previous promise > 1
// after 5s
fnPromiseAndTime(); // > wait for previous promise > 1
// after 10s
fnPromiseAndTime(); // > wait for new timeout > wait for new promise > 2

Allows you to execute command in shell and wait for result

💡 Tip - if timeout is equal or less than 0 then timeout will don't be turned on

exec(
    command: string,
    timeout: number = 0,
): Promise<{
    stdout: string;
    stderr: string;
}>;
const $ = new Bunbun;

$.exec('node -v').then(({ stdout }) => {
    console.log(stdout);
});

// or

$.task('node-v', async () => {
    const { stdout } = await $.exec('node -v');
    console.log(stdout);
});

Hash given string

hash(
    text: string,
    algorithm: 'md5' | 'sha1' | 'sha256' | 'sha512' = 'md5',
    encoding: 'hex' | 'base64' | 'buffer' | 'latin1' = 'base64'
): Promise<string>;
const $ = new Bunbun;

$.hash('test').then(hash => console.log(hash));

Allows catch promise and keep async/await syntax

💡 Tip - be careful and never call (await)rescue(await someFunc()) because this someFunc() will throw in higher, current context instead of rescue's context, instead of that call await rescue(someFunc())

🎈 Fun fact - in Ruby language rescue keyword is used instead of typical catch keyword - that's why Bunbun use this name in this context

rescue<T1, T2>(
    promise: Promise<T1> | T1,
    alter?: T2,
): Promise<T1 | T2>;
const $ = new Bunbun;

const justReject = async () => {
    throw 'wowie, such error';
};

const dontReject = async () => 'ok!';

$.task('test-1', async () => {
    const error = await $.rescue(justReject());
    console.log(error); // > 'wowie, such error'
});

$.task('test-2', async () => {
    const error = await $.rescue(await justReject());
    // unreachable code because `justReject` is executed
    // in current context!
});

$.task('test-3', async () => {
    const ok = await $.rescue(dontReject());
    console.log(ok); // > 'ok!'
});

$.task('test-4', async () => {
    const ok = await $.rescue('ok!');
    console.log(ok); // > 'ok!'
});

$.task('test-5', async () => {
    const alternativeValue = await $.rescue(justReject(), 'ok!');
    console.log(alternativeValue); // > 'ok!'
});

Run single task by name and returns promise of call of this task

💡 Tip - if task with such name will be unable to found then this function will throw false

run(name: string): Promise<boolean>;
const $ = new Bunbun;

$.task('reject', async () => {
    throw 'oh no!';
});

$.task('resolve', async () => {
    return 'ok!';
});

$.task('test', async () => {
    // try run task
    await $.rescue($.run('reject'));
    console.log(
        await $.rescue($.run('reject')
    ); // > 'oh no!'
    console.log(
        await $.rescue($.run('?')
    ); // > false
    console.log(
        await $.rescue($.run('resolve')
    ); // > true
});

Similar to bunbun.run() but instead of running single task allows run multiple tasks, with single fallback task. This function will don't throw at error (only log it)

💡 Tip - this function is created mainly for using as main method

start(
    defaultTask: string = 'default',
    tasks: string[] = process.argv.slice(2),
): Promise<void>;
const $ = new Bunbun;

$.task('reject', async () => {
    throw 'oh no!';
});

$.task('resolve', async () => {
    return 'ok!';
});

$.start(); // run 'default' task or given names from terminal
// or
$.start('resolve'); // run 'resolve' task or given names from terminal
// or
$.start(
    'resolve',
    ['resolve', 'reject'],
); // run always 'resolve' and 'reject'

Creates new instance of class HttpServer

type Options = {
    fallback: string = './index.html',,
    reload: boolean = true,
    reloadPort: number = 8181,
};

serve(
    directory: string,
    port: number,
    options?: Partial<Options>,
): HttpServer;
const $ = new Bunbun;

const server = $.serve('build', 8080);

// after some action you can force reload of pages
server.reload();

Register new task under given name. Task can be simple function or async function if needed

💡 Tip - if you cannot use async/await syntax then returning Promise instance will give same effect

task(
    name: string,
    fn: () => unknown,
): void;
const $ = new Bunbun;

$.task('foo', () => {
    console.log('foo');
});
$.task('bar', async () => {
    await someLongAction();
    console.log('bar');
});
$.task('baz', async () => {
    await $.run('foo');
    await $.run('bar');
});
$.task('qux', async () => {
    await $.run('bar');
    await $.run('foo');,
});

$.run('baz');
// output:
// >> foo
// >> bar (because it's waited for long running function)

$.run('qux');
// output:
// >> bar
// >> foo (because it's waited until previous task ended)

Waits until given task has been completed, it doesn't matter if it ended successfully, throws if task has been not found

until(name: string): Promise<boolean>;
const $ = new Bunbun;

$.task('long', async () => {
    await $.wait(1500);
});
$.task('run-and-wait', async () => {
    $.run('long');
    await $.until('long');
    // waits until 'long' ended
    await $.until('long');
    // pass immediately because 'long' is not running
});

setTimeout() alternative for async/await syntax

wait(time: number): Promise<void>;
const $ = new Bunbun;

$.task('long', async () => {
    console.log('wait moment');
    await $.wait(1500);
    console.log('hello again');
});

Class prepared for logging purposes. Logger is already constructed with class Bunbun with default settings

type Logger = {
    new();
    // ...
};
const $ = new Bunbun();
$.logger; // here you are

Determines if logger should log more useful data for debugging purposes

debugging: boolean = false;
const $ = new Bunbun();
$.logger.debugging = true;
// Now bunbun will throw more logs
$.logger.debugging = false;
// Now bunbun will don't throw so much data

Determines if logger should be silent

silent: boolean = false;
const $ = new Bunbun();
$.logger.silent = true;
// Now bunbun will be silent
$.logger.silent = false;
// Now bunbun will be loud

Using logger.format() function to colorize data, adds prefix and log given thing. Nothing will be logged if logger.silent is true. logger.debug() will log things only if logger.debugging is true

debug(template: string, ...args: any[]): void;
// or
error(template: string, ...args: any[]): void;
// or
log(template: string, ...args: any[]): void;
// or
success(template: string, ...args: any[]): void;
const $ = new Bunbun();

$.logger.debug('my var = $$', 10);
// logs only if 'logger.debugging' is equal to 'true'
// > '? ~ my var = 10'

$.logger.error('my var = $$', 10);
// > '✗ ~ my var = 10'

$.logger.log('my var = $$', 10);
// > '  ~ my var = 10'

$.logger.success('my var = $$', 10);
// > '✔ ~ my var = 10'

Format given template using built-in Node's functions, but uses $$ as placeholder for any type of variable

format(template: string, ...args: any[]): string;
const $ = new Bunbun();

console.log(
    $.logger.format(
        '1.$$\n2.$$\n3.$$',
        10,
        'test',
        { test: 10 },
    )
);
// > output:
// 1.10
// 2.'test'
// 3.{ test: 10 }

Class prepared for filesystem manipulations or fetching data purposes. Fs is already constructed with class Bunbun with default settings

type Fs = {
    new();
    // ...
};
const $ = new Bunbun();
$.fs; // here you are

Current working directory for all methods of class Fs

cwd: string = process.cwd();
const $ = new Bunbun();
$.fs.cwd === process.cwd(); // > true
$.fs.cwd = 'C:/src'; // now all methods starts from "C:/src"

Copy single file or whole dir (recursively), returns true if done without any errors, errors will be throwed as exception

copy(
    source: string,
    target: string
): Promise<true>;
const $ = new Bunbun();

$.task('maybe-copy-file', async () => {
    const res = await $.rescue(
        $.fs.copy('file.txt', 'second-file.txt'),
        false
    );

    if (res) {
        // file has been copied
    } else {
        // file has NOT been copied
    }
});

$.task('swallow-copy-file', async () => {
    await $.rescue($.fs.copy('file.txt', 'second-file.txt'));
    // nobody cares if file has been copied
});

$.task('force-copy-file', async () => {
    await $.fs.copy('file.txt', 'second-file.txt');
    // unreachable without successful copying file
});

If any directory in tree does not exists then they will be created - otherwise nothing will be created

createDir(
    path: string,
    mode?: number // e.g 0o776
): Promise<true>;
const $ = new Bunbun();

$.task('maybe-create-dir', async () => {
    const res = await $.rescue(
        $.fs.createDir('test/test/test'),
        false
    );

    if (res) {
        // dir is prepared
    } else {
        // dir isn't prepared for some reason
    }
});

$.task('swallow-create-dir', async () => {
    await $.rescue($.fs.createDir('test/test/test'));
    // nobody cares if dir exists
});

$.task('force-create-dir', async () => {
    await $.fs.createDir('file.txt', 'second-file.txt');
    // unreachable without successful dir creating or check if exists
});

Creates dir in temporary directory of your operating system, those directories are often cleared

createTempDir(): Promise<string>;
const $ = new Bunbun();

$.task('temp-dir', async () => {
    const dir = await $.fs.createTempDir();
    // "dir" variable contains temporary directory path
});

Allows seamlessly edit content of file

edit(
    path: string,
    fn: (data: string) => (Promise<string> | string)
): Promise<void>;
const $ = new Bunbun();

$.task('edit-file', async () => {
    await $.fs.edit(
        'test.txt',
        data => data.replace('$$', 'USD')
    );
    // file has been edited
});

Check if given path exists and if so, returns type of given path

exists(path: string): Promise<false | 'file' | 'dir'>;
const $ = new Bunbun();

$.task('check-path', async () => {
    const type = await $.fs.exists('test');

    switch (type) {
        case false:
            // does not exists
            break;

        case 'file':
            // path is file
            break;

        case 'dir':
            // path is directory
            break;
    }
});

Hash given file

hash(
    file: string,
    algorithm: 'md5' | 'sha1' | 'sha256' | 'sha512' = 'md5',
    encoding: 'hex' | 'base64' | 'buffer' | 'latin1' = 'base64'
): Promise<string>;
const $ = new Bunbun;

$.fs.hash('test.txt').then(hash => console.log(hash));

Returns list of files matching given pattern

type ListOptions = {
    absolute: boolean = false,
    cwd: string = fs.cwd,
    onlyDirectories: boolean = false,
    onlyFiles: boolean = false,
};

exists(
    pattern: string | string[],
    options: Partial<ListOptions> = {}
): Promise<string[]>;
const $ = new Bunbun();

$.task('assets-files', async () => {
    const images = await $.fs.list([
        './src/**/*.(png|jpg)',
        '!./src/**/*.raw.png'
    ]);

    // images is array of matching paths
});

Reads content of file as string

read(path: string): Promise<string>;
const $ = new Bunbun();

$.task('read-files', async () => {
    const text = await $.fs.read('package.json');
    // text is package.json content
});

Removes given file or dir

remove(path: string): Promise<true>;
const $ = new Bunbun();

$.task('remove-file', async () => {
    const text = await $.fs.remove('package.json');
    // package.json is removed here
});

Renames given file or dir

rename(source: string, target: string): Promise<true>;
const $ = new Bunbun();

$.task('rename-file', async () => {
    await $.fs.rename('package.json', 'package.json.bak');
    // package.json is renamed here
});

Watches given files which matches given paths and returns disposer, every time any matching file has been changed, removed or added then given function will be executed, also after initial scanning function is executed once

watch(
    pattern: string | string[],
    fn: () => any
): () => void;
const $ = new Bunbun();

$.fs.watch('./src/**/*.js', () => {
    console.log('some file has been changed!');
});

Writes given content into file

watch(
    path: string,
    data: string | Buffer
): void;
const $ = new Bunbun();

$.fs.write('test.txt').then(() => {
    console.log('DONE!');
});

Can be created via bunbun.serve()

type HttpServer = {
    new();
    // ...
};
const $ = new Bunbun;

const server = $.serve('build', 8080); // here you are

Reloads all pages with injected reloading script if turned on at creating

reload(): void;
const $ = new Bunbun;

const server = $.serve('build', 8080);

// after some change you can ask for reload pages:
server.reload();
3.0.3

3 years ago

3.0.2

3 years ago

3.0.1

3 years ago

3.0.0

3 years ago

2.0.0

4 years ago

1.2.0

4 years ago

1.1.1

4 years ago

1.1.0

4 years ago

1.0.0

4 years ago