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

delta.js

v1.4.1-alpha

Published

achieve modularity and separation of concerns through feature-oriented development

Downloads

4

Readme

delta.js

Build Status Coverage Status

achieve modularity and separation of concerns through feature-oriented programming

Description

Programming is an activity very prone to human error, especially if you have multiple developers changing the same code-base at the same time. As more and more features are implemented by different programmers, progress will often slow to a crawl. Programmers can easily lose overview and step on each others toes when their code is spread across the code base surrounded by the code of others.

delta.js helps you organize your JavaScript code in terms of features. As it turns out, files, modules, objects, functions (and so on), are not the right abstractions for describing a feature. But they are very good at other stuff! This library introduces the notion of a delta, which complements those other constructs.

A delta is the place to gather all the code belonging to a specific feature. Basically, for each piece of code, you instruct the delta to put that code in the file, module, object or function where it belongs. That way, the code can physically be gathered in one place, and therefore be easy to maintain.

This has other benefits as well. By having some deltas be explicitly applied after others, you give them permission to overwrite things. If two deltas that are unordered try to overwrite each others code, you will get a friendly error message. Moreover, by separating your features so explicitly, you will be able to turn them on or off with a switch, for either debugging or production.

This might all seem overly complicated at first. But once you get started, you'll soon fall in love with this approach.

At this time, delta.js is a runtime library. But in concept, deltas could be applied by a preprocessor. This will be supported by a future version.

Installation

This library depends only on the Babel ES6 polyfill. For your convenience, a delta.js version is provided with this polyfill already baked in, but you also have the option of providing it yourself.

Install using Bower

delta.js is available as a Bower package:

bower install delta.js

Unfortunately, the Babel polyfill is not distributed through Bower. So you'll have to either use the version of delta.js with the polyfill baked in, or you'll have to get the polyfill from someplace else, like NPM.

Install using NPM

delta.js is available as an NPM package:

npm install delta.js

Importing DeltaJs

The delta.js package offers a UMD API, so it supports AMD (RequireJS), CommonJS and script-tags:

requirejs(['lib/delta.js/dist/delta.js'], function (DeltaJs) {
    // use DeltaJs
});
var DeltaJs = require('lib/delta.js/dist/delta.js')
// use DeltaJs
<script src="lib/delta.js/dist/delta.js"></script>
<!-- use DeltaJs -->

###Files

The dist directory offers different files for use in different circumstances. Use the following table to determine which file to use in your situation.

| File | Description | | ----------------------------- | -------------------------------------------------------------------------------- | | delta.js,delta.min.js | requires you to load the Babel polyfill yourself | | delta.full.js,delta.full.min.js | already includes the Babel polyfill |

If you don't know which you need, you probably want delta.full.min.js, because it will work out-of-the-box. But it is generally more elegant to load the polyfill yourself, especially if you use other libraries that depend on it.

Using DeltaJs

DeltaJs is a class that can be instantiated:

var deltaJs = new DeltaJs();

deltaJs is a context within which all new types of delta operation and all project-specific features are localized. It is also home to a core delta model, which is the structure that organizes and orders a set of deltas. Using multiple DeltaJs instances allows you to use different sets of deltas and rules in the same project that work independently from each other. But you'll usually need only one.

The DeltaJs API

Given a DeltaJs instance, the following methods are available:

.vp(name, initialValue)

This method creates a variation point. It should be used in your code-base wherever deltas may need to make changes. Each variation point needs a unique name for deltas to refer to. The method modifies a value 'in place', and does not make a copy. For example:

function MyClass() {
    this.someMethod = function () {};
    this.someField  = 'some value';
};
deltaJs.vp('MyClass', MyClass);

This allows the selected deltas to modify the MyClass class, as well as all of its properties. The .vp method also returns the new value. So the following is equivalent to the example above:

var MyClass = deltaJs.vp('MyClass', function MyClass() {
    this.someMethod = function () {};
    this.someField  = 'some value';
});

.do(name, [options])

This function returns a Proxy interface that may be used to easily create new deltas / operations in a core delta model that can then target specific variation points:

var proxy = deltaJs.do('cool-feature', {
    // options
});

This proxy variable can then be used to specify the modifications its corresponding delta should perform, and where to perform them. For example:

proxy.add('MyClass.prototype.coolMethod', function () {
	// do something cool
}).insert('MyClass.prototype.construct', function () {
	this.coolMethod();
});

So when the 'cool-feature' is selected, this delta adds the new coolMethod method to MyClass, and ensures that it is called by the existing construct method. A comprehensive list of these operations and how to use them follows later in this document.

Like variation points, deltas in a delta model need a unique name. But deltas and variation points have separate namespaces. In other words: you may give a delta and a variation point the same name. (Thought if you find yourself doing this often, you may not have fully understood the point of delta.js.)

Predicates

Before listing the supported options, let's briefly describe the notion of 'predicate', a type of data accepted by several of those options. A predicate is a condition on the set of selected features, and can be given as one of the following:

  • the value true
  • the value false
  • an array of feature-names, interpreted as a conjunction. The condition is true exactly if all features in the list are selected.

Supported Options

The following options may be passed:

| options | default | meaning | -------------------- | --------- | ------- | feature | true | a Boolean, specifying whether a feature with the same name should be linked to this delta | if | false | a predicate, specifying when (if ever) this delta should be automatically selected | onlyIf | true | a predicate that is required to hold if this delta is ever selected. If this delta is ever selected without this predicate being met, an error will be thrown whenever this delta is applied. | after | [] | a list of delta names. This delta is guaranteed to be applied after the deltas in the list. If the registration of this delta creates an application order cycle, an error will be thrown. | selects | [] | a list of delta names. If this delta is selected, all deltas in this list will also be selected.

For convenience, there are some options that combine multiple of the above:

| options | combines | ----------- | -------- | iff | if and onlyIf | requires | after and selects | resolves | if, onlyIf and after; and sets feature to false

Deltas representing specific changes to the variation points can be created through various operations through the Proxy API described below.

.select(...deltaNames)

This method can be used to manually select specific features by name.

deltaJs.select('cool-feature', 'silly-feature', 'debugging-patch-359');

Other features and deltas, if they are linked with the features given, can be automatically pulled in through any if or selects options.

Note that a feature needs to be registered before it can be selected. And a delta needs to be selected before the appearance of any variation point to which it applies. It is probably something you want to do very early in your application.

The Proxy API

Deltas can modify values in any number of ways, from completely replacing them with other values, to making fine-grained modifications in a deeply nested object. It does so through well-defined operations, which you can specify through the various methods of a Proxy instance.

Every delta targets a specific JavaScript object, which we'll denote as target in the descriptions below. All operations of that delta act on one of the properties of target. The deltas constructed directly from the delta model target an imaginary root object that contains all variation points as properties. We'll denote this object as root.

.modify(key)

This method returns another Proxy object that targets target[key], allowing us to step into a sub-object to make fine-grained modifications inside:

var d1 = deltaJs.do('my-delta');         // d1 targets 'root'
var d2 = d1.modify('vp1');               // d2 targets 'root.vp1'
var d3 = d2.modify('foo').modify('bar'); // d3 targets 'root.vp1.foo.bar'

For convenience, the key can also be given as a dot-separated list:

var d4 = d1.modify('vp1.foo.bar');       // d4 also targets 'root.vp1.foo.bar'

The key is then usually referred to as a path. This also works for the all the other operations below. It implicitly constructs a .modify chain to reach the right-most key.

When you've targeted the proper object, you can use the operations described below to make specific changes to its properties.

.add(key, newValue)

This makes the delta add a new property key to target, such that target[key] === newValue. Before the operation, it is expected that typeof target[key] === 'undefined'. In other words, it cannot already have a property named key.

delta.modify('my-vp.foo').add('bar', 'bas');
var value = vp('my-vp', { foo: {}, bar: [] });
expect(value).toEqual({ foo: { bar: 'bas' }, bar: [] });

.remove(key)

This makes the delta remove the property named key from target, such that typeof target[key] === 'undefined'. Before the operation, it is expected that typeof target[key] !== 'undefined'. In other words, it must first have a property named key.

delta.modify('my-vp').remove('foo');
var value = vp('my-vp', { foo: { bar: 'bas' }, bar: [] });
expect(value).toEqual({ bar: [] });

.replace(key, newValue)

This makes the delta replace the property named key in target, such that target[key] === value. Before the operation, it is expected that typeof target[key] !== 'undefined'. In other words, it must already have a property named key.

delta.modify('my-vp').replace('foo', 'bar');
var value = vp('my-vp', { foo: { bar: 'bas' }, bar: [] });
expect(value).toEqual({ foo: 'bar', bar: [] });

.prepend(key, newValue)

.append(key, newValue)

.insert(key, newValue)

These methods work if target[key] is an array or a function, and respectively prepend, append or insert newValue into it.

If target[key] is a function, newValue also needs to be a function, which will then be set up to run in the position where it is placed, and provided with the same arguments and this context of the original function-call.

The .prepend operation places the value in the beginning, and .append places it at the end of the existing array / function.

The .insert operation is special, because it gives the delta the freedom to put the value in an arbitrary position; you don't have any control over the placement. The advantage of .insert is that the order between multiple insertions does not matter. As far as delta.js is concerned, d.insert('f', fn1).insert('f', fn2) and d.insert('f', fn2).insert('f', fn1) are equivalent. Moreover, two unordered deltas can insert things into the same array or function without this being regarded as a conflict.

For example, for an array:

delta.modify('my-vp').append('a', 999).prepend('a', -999);
var value = vp('my-vp', {
    a: [1, 2, 3]
});
expect(value).toEqual({
	a: [-999, 1, 2, 3, 999]
});

And for a function:

delta.modify('my-vp').append('f', function (a, b) {
    doOtherThings(a, b);
});
var value = vp('my-vp', {
    f: function (a, b, c) {
        doThings(b, c);
    }
});

The variable value will be equivalent to:

{
    f: function () {
        (function (a, b, c) {
            doThings(a, b);
        }).apply(this, arguments);
        (function (a, b) {
            doOtherThings(a, b);
        }).apply(this, arguments);
    }
}

delta.js cannot actually add statements to existing functions. Instead, it defines a new function, which calls the original and the added function in the proper order, properly passing the function arguments and this context to both. This has some consequences:

  • Local variables are not shared across function 'parts'. 'Function-part local' is also 'delta local'.
  • All non-standard properties of a function are lost after a .prepend, .append or .insert. If this is undesirable, it is recommended that the original function is written to call an auxiliary function, so the delta operations can apply to the auxiliary. For class constructors, it is recommended that you use a function Class.prototype.construct for this purpose:
function MyClass() { this.construct.apply(this, arguments) }
MyClass.prototype.construct = function () {};
deltaJs.vp('MyClass', MyClass)
deltaJs.do('my-delta').append('MyClass.prototype.construct', function (a, b, c) {
    this.doExtraThings(a, b, c);
});

Future Plans

Here is an incomplete list of future plans for this library:

  • support for changing HTML and CSS with deltas
  • transpiler to resolve deltas at build-time

License

delta.js is released under the terms of the MIT license. It permits reuse within both open and proprietary software, provided all copies of the licensed software include a copy of the MIT License terms and the copyright notice.