The functionality of this package has been moved to the tapsig package. This package has in turn been deprecated.
This tiny library (0.6kb minified & gzipped) tacks custom extensions onto existing JavaScript functions and objects. That makes it incredibly easy to supplement existing JavaScript libraries with custom methods without changing the original vendor code.
It works by wrapping the target in a Proxy. The Proxy sticks to the tapped library by attaching itself to properties or method calls you access on it.
This package works in Node.js and in the browser. Note however that the browser must support ES2015 Proxies (which are not polyfillable) which leaves out IE11 in particular.
Install it from npm:
npm install --save apitap
You can use this package in your browser with one of the following snippets:
-
The most common version. Introduces a global
apitap
variable, runs in all modern browsers:<script src="node_modules/apitap/dist/browser.min.js"></script> <!-- or from CDN: --> <script src="https://unpkg.com/apitap"></script>
-
If you're really living on the bleeding edge and use ES modules directly in the browser, you can
import
the package as well:import * as apitap from "./node_modules/apitap/dist/browser.esm.min.js" // or from CDN: import * as apitap from "https://unpkg.com/apitap/dist/browser.esm.min.js"
As opposed to the first snippet, this will not create a global
apitap
function.
Include this package in Node.js like you usually do:
const apitap = require('apitap')
If you use --experimental-modules
, there's a .mjs
version, too:
import * as apitap from 'apitap/dist/node.esm'
Now that we have grabbed the apitap
object, we can start injecting custom properties and methods into a library.
Since most of us probably know jQuery, let's take that as an example.
Remember older jQuery versions? They had a size()
method that was removed in favor of the length
property.
Now let's re-implement that method:
const $ = apitap.wrap(jQuery, {
size () {
return this.length
}
})
$('div').size() // Returns some number
There are some things to notice here:
- The
this
context points to the object the method it is called on – in our case that's the$('div')
collection which holds the DOM elements. - The
size()
method is available although we're not calling it on the$
object itself – the proxy reproduces itself and sticks to each property you access or method you call. - We return a number from the
size()
method. However, if we returned an object or a function, the return value would be wrapped.
The second point is a feature, but in our example it can be quite unhandy: In most cases, we want to inject our custom properties only under certain circumstances.
In the example above, the size()
method is not only available on the $('div')
, but also on the $
itself. However, $
is not a jQuery collection and thus $.size()
would return undefined
.
That's why we only want to provide the size()
method on a jQuery collection. For that purpose, we can inject a function instead of an object. The function decides on a case-by-case basis which properties to provide:
const $ = apitap.wrap(jQuery, target => {
// Only add the `size()` method on a jQuery collection
if (target instanceof jQuery) {
return {
size () {
return this.length
}
}
}
// If we don't return anything, no custom properties are added
})
$('div').size() // Still returns some number
$.size() // TypeError: $.size is not a function
Injected custom properties will shadow existing ones. In other words, custom properties will always take precedence over builtin properties.
You may provide getters in an injection object:
const $ = apitap.wrap(jQuery, {
get version () {
return jQuery.fn.jquery
}
})
The ApiTap library exposes a CATCH_ALL
symbol. You can use it in the injection object to answer every property access that was not matched otherwise:
const $ = apitap.wrap(jQuery, {
foo: 'bar',
[apitap.CATCH_ALL] (name) {
return `no such property '${name}'`
}
})
$.foo // "bar"
$.baz // "no such property 'baz'"
If the CATCH_ALL
method returns a function, its context will be bound to the jQuery
object. Just like in a regular injected method, the result will be wrapped if it's an object or a function.
You can unwrap a tapped object with the unwrap()
method:
apitap.unwrap($) // Returns the vanilla jQuery object
Note that both the wrap()
and the unwrap()
methods are idempotent. That means you can't wrap/unwrap an object more than once. Wrapping an already wrapped object will do nothing, just like unwrapping a non-wrapped object won't do anything.
You can check if an object is wrapped via this library by running apitap.isWrapped(object)
.
The Node.js version of ApiTap uses the debug utility to print logs.
The browser build uses just a simple console.log()
. It also has to be enabled manually by setting the verbose
parameter (3rd parameter of apitap.wrap()
) to true
.