npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@gebruederheitz/debuggable

v3.0.4

Published

Flexible debug output for browser applications.

Downloads

468

Readme

Debuggable

Flexible debug output control for browser oder NodeJS applications.


Installation

> npm i @gebruederheitz/debuggable

Usage

The library exports a singleton object, which can be used directly, or to spawn further child debuggers. While it's enabled (~~which is the default state~~) it will simply proxy its arguments through to console. Currently implemented are the methods log(), warn() and error().

import { debug } from '@gebruederheitz/debuggable';

const object = 'world';
debug.log('Hello %s.', object); // Will print "Hello world." to the browser console.

debug.disable(); // You could alternatively use `debug.toggle(false);`
debug.log('Goodbye!') // Will now not output anything.

Spawing children and using namespaces

Namespaces make it easier to identify where a particular log message came from. They also allow filtering the console for messages from a particular component quite simply.

const childLogger = debug.spawn('childID', 'ChildNamespace');
childLogger.log('happy');

/*
-->> [ChildNamespace] happy

 */

Child debuggers are disabled and enabled with their parent, while maintaining their internal state. This sounds more complicated than it is:

const child = debug.spawn('child');
child.log('something') // logs something

child.disable();
child.log('something') // doesn't log anything, because "locally disabled"

debug.disable();
child.enable();
child.log('something') // doesn't log anything, because globally disabled

debug.enable();
child.log('something') // logs something

Instances can recursively spawn children of their own:

const child = debug.spawn('child', 'Child');
const grandChild = child.spawn('grandchild', 'Grandchild');
const greatGrandChild = grandChild.spawn('greatgrandchild', 'Great-Grandchild');

greatGrandChild.log('Hi Grandpa!');

/*
-->> [Child] [Grandchild] [Great-Grandchild] Hi Grandpa!
 */

grancChild.disable();
greatGrandChild.log('**crickets**'); // no output, because disabled state is inherited

Here's some example usage with ES classes, each using their own child instance:

import { debug } from '@gebruederheitz/debuggable';

class MyModule {
    constructor() {
        this.debug = debug.spawn('MyModule');
    }
    
    someMethod(arg) {
        this.debug.log('User has called someMethod with', arg);
    }
    
    quiet() {
        this.debug.disable();
    }
}

class OtherModule {
    constructor() {
        this.debug = debug.spawn('OtherModule');
    }
    
    otherMethod() {
        this.debug.log('otherMethod has been called!');
    }
}

const x = new MyModule();
const y = new OtherModule();
x.someMethod('someArg');
y.otherMethod();

x.quiet();
x.someMethod('otherArg'); // no output
y.otherMethod();

debug.disable();
x.someMethod(42); // no output
y.otherMethod(); // no output

Resulting output:

>> [MyModule] User has called someMethod with someArg
>> [OtherModule] otherMethod has been called!
>> [OtherModule] otherMethod has been called!

IDs, Namespaces & Tagging

interface DebugHelper {
    spawn(
        id?: string,  // A unique identifier. Pass null to have 
                      // a UUIDv4 generated for you.
        namespace?: string, // The prefix for this instance, which will
                            // be prepended [in square brackets] along
                            // the prefixes of all ancestors.
        startsEnabled?: boolean, // Default true, pass false to initially
                                 // silence this instance.
        ...tags: string[]  // A list of tags to be associated with this
                           // instance. Can be used with configure()
                           // as shown below.
    ): DebugHelper;
    // ...
}

Another way to add tags is using the dedicated, chainable method:

const child = debug.spawn('some-id').addTags('firstTag', 'secondTag');

Runtime Configuration

You can toggle instances by their ID or assigned tags using the configure() function on the global instance.

debug.configure({
  tag: false,       // Will be disabled
  othertag: true,   // Will be enabled
  childId: true,
});

Typescript Class Decorator

If you're using Typescript, you can use the decorator to automagically inject a child instance as a class property. It will automatically inherit from the debug instance on the parent class if it exists.

import { debug, DecoratedWithDebug } from '@gebruederheitz/debuggable';

// A little hack to communicate the additional property to Typescript
export interface MyDecoratedClass extends DecoratedWithDebug {}

@debug.decorate('id', 'tag', 'more-tags', '...')
class MyDecoratedClass {
    squawk() {
        this.debug.log('Quack!');
    }
}

new MyDecoratedClass().squawk();

// Automatic inheritance
@debug.decorate('child')
class MyChildClass extends MyDecoratedClass {}

const child = new MyChildClass();
child.squawk();

// -->> [id] [child] Quack!

MyDecoratedClass.prototype.debug.disable();
child.squawk();
// > silence

Event Interface

The event interface is an instance of mitt that is exposed as a public property on the global instance.

import type { Events } from 'gebruederheitz/debuggable';
import type { Emitter } from 'mitt';

import { debug } from 'gebruederheitz/debuggable';

const eventInterface: Emitter<Events> = debug.events;

// Listen to events
eventInterface.on('register', ({instance, parent}: Events['register']) => {
    console.log('New instance has been spawned.', {
        newInstance: instance,
        parentInstance: parent,
    });
});

// Trigger events manually
eventInterface.emit('message', {
    type: 'log',
    instance: someDebugHelperInstance,
    message: ['Hello'],
});
eventInterface.emit('toggle_some-id', { enabled: false });

On-Page Console (browser only)

This feature is particularly useful for debugging web applications on mobile devices, particularly ones you might not have direct access to – i.e. whenever using remote or USB debugging is impractical or impossible. Somewhere on the page running your scripts you'll insert an empty div element with the ID attribute debug-visualize, and enable "visualisation" on the debug object.

Now the debugger will replicate any debug output into this "fake console" element, allowing you to read your app's debug messages without access to the browser console. Obviously this is not suitable for production use.

<div id="debug-visualize"></div>
debug.toggleVisualization(true);
// Use toggleVisualization(false) to turn it back off again
debug.spawn('Test').log('Hello!');
<div id="debug-visualize" style="...">
  <div class="console" style="...">
    <code class="debug-visualize__entry debug-visualize__entry--log" style="...">
      [Test] Hello!
    </code>
  </div>
</div>

Helpers & Utilities

timeout()

I sometimes use this to simulate any asynchronous action, like a network request that might take a while. It simply wraps the native setTimeout() in a Promise and returns that, so it's a convenient one-liner in an async flow:

async function getDataFromApi() {
    // @TODO: write actual API request once backend team is ready
    await debug.timeout(1200) // Wait for 1.2 seconds to simulate a request
  
    return {
        success: true,
        data: {
            items: [],
        },
    };
}

devnull()

Slightly silly, but I found it useful during development to just shut the linters up (and especially TS) while I'm working with method stubs. Maybe there's more or even better uses for this:

class MyClass {
    methodStub(knownArg) {
        // It simply returns the args passed to it. Nothing else.
        this.debug.devnull(knownArg);
    }
}

Development

Dependencies

  • nodeJS LTS (18.x)
  • nice to have:
    • GNU make or drop-in alternative
    • NVM or asdf

Quickstart

You can use the watch task:

$> nvm use
$> npm run watch
# or
$> make
# or, more explicitly
$> make dev

After making your changes, run

$> npm run build
# or
$> make build

to create the ES-module build at dist/index.mjs and make certain everything runs smoothly. You should also run make test at least once to avoid simple linting issues and run the test suite.

When you're finished, you can use make release on the main branch to publish your changes.