NOTE: You are viewing documentation for the MoovJS/Adapt version of the Moovweb SDK
View documentation for next-gen Moovweb XDN & PWA framework
Moovweb | moov_perf.js
Menu Developer Moovweb University

moov_perf.js

/**
 * A namespace containing methods for performance optimizations.
 * @namespace perf
 */

/**
 * An extended Cheerio engine containing specific $ namespace methods for
 * performance optimizations.
 * @namespace $
 */

'use strict';
const url = require('url');
const moov_opt_host = 'opt2.moovweb.net/img';

function Perf(selector) {
    this.selector = selector;
    this.blacklist = {};
    let perf = this;

    /**
     * @method quality
     * @summary moov_perf
     * @memberof $
     * @description Sets the quality for an optimized image as an alternative
     * shorthand to the quality setting as a second object parameter to
     * `.optimizeImg()`. This sets the quality as an HTML attribute, which can
     * then be read by the opt.moovweb.net optimizer application as a way to
     * dramatically reduce image quality without reducing fidelity. This should
     * come before any call to `.optimizeImg()`.
     * @param {String|Number} quality A number or string containing the number
     * for the desired quality from 1 (worst) to 100 (best).
     * @return {Object} The updated form of the original Cheerio matched-set
     * object.
     * @example
     * const Perf = require("moov_perf");
     * let perf = new Perf($);
     * $body.find("img").quality("88");
     * // => HTML input:
     * //    <body>
     * //      <img src="http://www.bing.com/img.jpeg" alt="selfie">
     * //    </body>
     * // => Returns: original object associated with $body.find("img")
     * // => HTML output:
     * //    <body>
     * //      <img src="http://www.bing.com/img.jpeg" alt="selfie" quality="88">
     * //    </body>
     * @example
     * const Perf = require("moov_perf");
     * let perf = new Perf($);
     * $body.find("img").quality("88").optimizeImg();
     * // => HTML input:
     * //    <body>
     * //      <img src="http://www.bing.com/img.jpeg" alt="selfie">
     * //    </body>
     * // => Returns: original object associated with $body.find("img")
     * // => HTML output:
     * //    <body>
     * //      <img src="//opt.moovweb.net/img?img=http%3A%2F%2Fwww.bing.com%2Fimg.jpeg&linkEncoded=0&preserve_aspect=1&quality=88" alt="selfie" quality="88">
     * //    </body>
     */
    selector.prototype.quality = function(quality) {
        if (quality !== undefined || quality !== '') {
            if (typeof(this.attributes) === 'function') {
                this.attributes({
                    quality: quality
                });
            } else {
                this.attribs.quality = quality;
            }
        }
        return this;
    };

    /**
     * @method optimizeImg
     * @summary moov_perf
     * @memberof $
     * @description Modifies the `src` attribute of selected `img` tags to run
     * through Moovweb's image-optimization service at opt.moovweb.net, which
     * provides configurable settings for optimizing images. Utilizes Perf's
     * `.optimizeImageAttributes()` method for each element in the matched set
     * in order to rewrite all `src` attribute URLs to the opt.moovweb.net
     * optimizer, with the given settings.
     * @param {Object} [opts] An object containing optimization options. The
     * default options object contains the settings that come from Perf's
     * `.optimizeImageAttribs()` method (the list of attributes attached to the
     * input node, along with the settings
     * `{ link_encoded: false, preserve_aspect: true }`, unless the host is
     * blacklisted, in which case, the object only contains
     * `bypass_attributes: true`, a reserved property-value pair for knowing not
     * to set height or width HTML attributes). The usable attributes (that get
     * passed in to other Perf methods) are as follows:
     * @param {Number|String} [opts.width] A number or a string containing the
     * number for the desired pixel width.
     * @param {Number|String} [opts.height] A number or string containing the
     * number for the desired pixel height.
     * @param {Boolean} [opts.link_encoded] A boolean for whether the link
     * should be base64-encoded.
     * @param {Boolean} [opts.preserve_aspect] A boolean for whether the aspect
     * ratio should be preserved, provded that only one dimension is given. If
     * both dimensions are given, width is given priority. If both dimensions
     * are set, this setting is ignored.
     * @param {Number|String} [opts.quality] A number or string containing the
     * number for the desired quality, on a scale from 1 (worst) to 100 (best).
     * @param {String} [opts.format] A string containing the desired file format.
     * Options include jpg, png, and webp.
     * @return {Object} The updated form of the original Cheerio matched-set.
     * object.
     * @example
     * const Perf = require("moov_perf");
     * let perf = new Perf($);
     * $body.find("img").optimizeImg();
     * // => HTML input:
     * //    <body>
     * //      <img src="http://www.bing.com/img.jpeg" alt="selfie">
     * //    </body>
     * // => Returns: original object associated with $body.find("img")
     * // => HTML output:
     * //    <body>
     * //      <img src="//opt.moovweb.net/img?img=http%3A%2F%2Fwww.bing.com%2Fimg.jpeg&linkEncoded=0&preserve_aspect=1" alt="selfie">
     * //    </body>
     * @example
     * const Perf = require("moov_perf");
     * let perf = new Perf($);
     * perf.ignore("http://www.google.com/");
     * $body.find("img").optimizeImg({
     *   height: 666,
     *   width: 777,
     *   quality: 88,
     *   format: 'webp'
     * });
     * // => HTML input:
     * //    <body>
     * //      <img src="http://www.bing.com/img.jpeg" alt="selfie">
     * //      <img src="http://www.google.com/img.jpeg" alt="selfie">
     * //    </body>
     * // => Returns: original object associated with $body.find("img")
     * // => HTML output:
     * //    <body>
     * //      <img src="//opt.moovweb.net/img?img=http%3A%2F%2Fwww.bing.com%2Fimg.jpeg&linkEncoded=0&width=777&height=666&preserve_aspect=1&quality=88&format=webp" alt="selfie" height="666" width="777" quality="88" format="webp">
     * //      <img src="http://www.google.com/img.jpeg" alt="selfie" height="666" width="777" quality="88" format="webp">
     * //    </body>
     * @example
     * const Perf = require("moov_perf");
     * let perf = new Perf($);
     * $body.find("img").optimizeImg({
     *   width: 777,
     *   quality: 88,
     *   preserve_aspect: false,
     *   link_encoded: true
     * });
     * // => HTML input:
     * //    <body>
     * //      <img src="http://www.bing.com/img.jpeg" alt="selfie">
     * //    </body>
     * // => Returns: original object associated with $body.find("img")
     * // => HTML output:
     * //    <body>
     * //      <img src="//opt.moovweb.net/img?img=aHR0cDovL3d3dy5iaW5nLmNvbS9pbWcuanBlZw==&linkEncoded=1&width=777&quality=80" alt="selfie" width="777" quality="80" preserve_aspect="false" link_encoded="true">
     * //    </body>
     */
    selector.prototype.optimizeImg = function(opts) {
        for (let i = 0; i < this.length; i++) {
            let opt_attrs = perf.optimizeImageAttribs(this[i], opts);
            if (opt_attrs) {
                this[i].attribs = opt_attrs;
            }
        }
        return this;
    };

    selector.prototype.perf = this;
}

function parseUrl(src) {
    // Return a parsed version of an img src (or any url, really)
    return url.parse(src, false, true);
}

/**
 * @method blacklistContains
 * @summary moov_perf
 * @memberof perf
 * @description Checks whether the domain is blacklisted. Note that domains to
 * check should not include any protocols or paths.
 * @param {String} domain A string for the domain to check.
 * @return {boolean} A boolean for whether the domain is in the blacklist.
 * @example
 * const Perf = require("moov_perf");
 * let perf = new Perf($);
 * perf.ignore("http://www.google.com/");
 * perf.blacklistContains("www.google.com");
 * // => Returns: true
 * perf.blacklistContains("http://www.google.com/");
 * // => Returns: false
 */
Perf.prototype.blacklistContains = function(domain) {
    return this.ignored()[domain] === true ? true : false;
};

function extend(defaults, opts) {
    // helper used to return a new object with cloned properties of two other objects
    // note: properties in opts will override those in defaults
    let ext = {};
    var prop;
    for (prop in defaults) {
        if (Object.prototype.hasOwnProperty.call(defaults, prop)) {
            ext[prop] = defaults[prop];
        }
    }
    for (prop in opts) {
        if (Object.prototype.hasOwnProperty.call(opts, prop)) {
            ext[prop] = opts[prop];
        }
    }
    return ext;
}

/**
 * @method optimizedAttrs
 * @summary moov_perf
 * @memberof perf
 * @description Provides a first pass to convert an object containing options
 * meant to be applied to an optimized image element (as passed into other
 * public helper methods), including a newly-generated `src` attribute.
 * @param {String} domain A string for the image source.
 * @param {Object} [opts] An object containing optimization options. The
 * default options object is `{ link_encoded: false, preserve_aspect: true }`,
 * unless the host is blacklisted (in which case, the object only contains
 * `bypass_attributes: true`, a reserved property-value pair for knowing not to
 * set height or width HTML attributes). The usable attributes are as follows:
 * @param {Boolean} [opts.bypass_attributes] A boolean for whether
 * attributes should be bypassed from the optimized image elemetnt.
 * @param {Number|String} [opts.width] A number or a string containing the
 * number for the desired pixel width.
 * @param {Number|String} [opts.height] A number or string containing the number
 * for the desired pixel height.
 * @param {Boolean} [opts.link_encoded] A boolean for whether the link
 * should be base64-encoded.
 * @param {Boolean} [opts.preserve_aspect] A boolean for whether the aspect
 * ratio should be preserved, provded that only one dimension is given. If both
 * dimensions are given, width is given priority. If both dimensions are set,
 * this setting is ignored.
 * @param {Number|String} [opts.quality] A number or string containing the
 * number for the desired quality, on a scale from 1 (worst) to 100 (best).
 * @param {String} [opts.format] A string containing the desired file format.
 * Options include jpg, png, and webp.
 * @return {Object} A first-pass object containing attributes to apply to an
 * image node.
 * @example
 * const Perf = require("moov_perf");
 * let perf = new Perf($);
 * perf.ignore("www.moovweb.com");
 * perf.optimizedAttrs("http://www.moovweb.com/img.jpg", {height: 300});
 * Returns: { height: 300, bypass_attributes: true, src: 'http://www.moovweb.com/selfie.jpg' }
 * @example
 * let perf = new Perf($);
 * perf.ignore("www.moovweb.com");
 * perf.optimizedAttrs("http://www.google.com/img.jpg", {height: 300});
 * Returns: { height: 300, bypass_attributes: true, src: 'http://www.moovweb.com/selfie.jpg' }
 */
Perf.prototype.optimizedAttrs = function (src, opts) {
    let parsed_src = parseUrl(src, false, true);

    // create a new object with defaults based on options we've been given
    let attrs = extend(opts);
    if (this.blacklistContains(parsed_src.host)) {
        attrs = extend(attrs, {
            bypass_attributes: true,
            src: src
        });
    } else {
        attrs = extend({
            width: undefined,
            height: undefined,
            link_encoded: false,
            preserve_aspect: true
        }, attrs);
        // Begin constructing the opt_url
        // This will create a link to our opt server, which will serve an optimized image
        let img_path = parsed_src.href;
        let encoding_flag = "0";

        if (attrs.link_encoded === true) {
            // encode the url to base64
            encoding_flag = "1";
            img_path = new Buffer(img_path).toString('base64');
        } else {
            // if it's not meant to be base64-encoded, percent-encode instead
            // (don't do both)
            img_path = encodeURIComponent(img_path);
        }
        // Start by setting the first param, img, to the upstream location
        // of the image to be optimized
        let opt_url = url.format({
            host: moov_opt_host,
            search: '?img=' + img_path,
            slashes: true
        });

        // We concatenate these in order instead of using param so the same image
        // doesn't get duplicate cached versions with parameters in arbitrary order
        opt_url += '&linkEncoded=' + encoding_flag;
        if (attrs.width !== undefined) {
            opt_url += '&width=' + attrs.width;
            if (attrs.height === undefined) {
                // Only width is set
                // Pass the aspect ratio flag
                if (attrs.preserve_aspect !== false) {
                    opt_url += '&preserve_aspect=1';
                }
            }
        }

        if (attrs.height !== undefined) {
            opt_url += '&height=' + attrs.height;
            if (attrs.width === undefined) {
                // Only height is set
                if (attrs.preserve_aspect !== false) {
                    opt_url += '&preserve_aspect=1';
                }
            }

        }

        if (attrs.preserve_aspect !== false) {
            opt_url += '&preserve_aspect=1';
        }

        if (attrs.quality !== undefined) {
            opt_url += '&quality=' + attrs.quality;
        }

        if (attrs.format !== undefined) {
            opt_url += '&format=' + attrs.format;
        }

        attrs.src = opt_url;
    }
    return attrs;
};

/**
 * @method optimizeImageAttribs
 * @summary moov_perf
 * @memberof perf
 * @description Provides a second pass to convert an object containing options
 * meant to be applied to an optimized image element. Note that this calls the
 * "first-pass" `.optimizedAttrs()` method with the given settings object. This
 * second pass most notably does the following two things: it allows for the
 * use of `data-src` (in the case of a lazyloading usage), and it retains any
 * width or height dimensions for the purpose HTML attribute usage.
 * @param {Object} node An image node element.
 * @param {Object} [opts] An object containing optimization options. The
 * default is the list of attributes attached to the input node, along with the
 * settings that come from the `.optimizedAttrs()` method
 * (`{ link_encoded: false, preserve_aspect: true }`, unless the host is
 * blacklisted, in which case, the object only contains
 * `bypass_attributes: true`, a reserved property-value pair for knowing not to
 * set height or width HTML attributes). The usable attributes (that get passed
 * in to other Perf methods) are as follows:
 * @param {Boolean} [opts.bypass_attributes] A boolean for whether
 * attributes should be bypassed from the optimized image elemetnt.
 * @param {Number|String} [opts.width] A number or string containing the number
 * for the desired pixel width.
 * @param {Number|String} [opts.height] A number or string containing the number
 * for the desired pixel height.
 * @param {Boolean} [opts.link_encoded] A boolean for whether the link
 * should be base64-encoded.
 * @param {Boolean} [opts.preserve_aspect] A boolean for whether the aspect
 * ratio should be preserved, provded that only one dimension is given. If both
 * dimensions are given, width is given priority. If both dimensions are set,
 * this setting is ignored.
 * @param {Number|String} [opts.quality] A number or string containing the
 * number for the desired quality, on a scale from 1 (worst) to 100 (best).
 * @param {String} [opts.format] A string containing the desired file format.
 * Options include jpg, png, and webp.
 * @return {Object} A second-pass object containing attributes to apply to an
 * image node.
 * @returns {Object} An object containing the second-pass optimized form of
 * attributes, including the original node attributes, with some or all that may
 * be overridden by the input options, as well as support for `data-src` and
 * dimension* attributes.
 * @example
 * const Perf = require("moov_perf");
 * let perf = new Perf($);
 * let bingTag = tag("img", {src: "http://www.bing.com/selfie.jpg"});
 * console.log(perf.optimizeImageAttribs(bingTag, {height: 300}));
 * // => Returns: { src: '//opt.moovweb.net/img?img=http%3A%2F%2Fwww.bing.com%2Fselfie.jpg&linkEncoded=0&height=300&preserve_aspect=1&preserve_aspect=1', height: 300}
 * @example
 * let perf = new Perf($);
 * let moovTag = tag("img", {src: "http://www.moovweb.com/selfie.jpg"});
 * perf.ignore("http://www.moovweb.com/");
 * console.log(perf.optimizeImageAttribs(moovTag, {height: 300}));
 * // => Returns: { src: 'http://www.moovweb.com/selfie.jpg', height: 300 }
 */
Perf.prototype.optimizeImageAttribs = function (node, opts) {
    if (node === undefined || typeof(node.attribs) === undefined) {
        return undefined;
    }

    let src = node.attribs.src || node.attribs['data-src'];
    if (src === undefined) {
        // selected node had an unset 'src'
        return undefined;
    }

    let clone_attribs = extend(node.attribs, opts);
    let optimized_attribs = this.optimizedAttrs(src, clone_attribs);

    // Update the src
    clone_attribs.src = optimized_attribs.src;
    if (node.attribs['data-src']) {
        clone_attribs['data-src'] = optimized_attribs.src;
    }
    if (optimized_attribs.bypass_attributes !== true) {
        // If the user specified the width/height of the image with attributes
        // not bypassed, set them directly on the node for faster browser
        // rendering
        if (optimized_attribs.width !== undefined) {
            clone_attribs.width = optimized_attribs.width;
        }
        if (optimized_attribs.height !== undefined) {
            clone_attribs.height = optimized_attribs.height;
        }
    }
    return clone_attribs;
};

/**
 * @method ignored
 * @summary moov_perf
 * @memberof perf
 * @description View the Perf instance's blacklisted domains, which are the
 * domains that do not get rewritten to reference Moovweb's image optimization
 * domain. Domains added to this list via the `.ignore()` method will strip off
 * the protocol and any trailing path.
 * @returns {Object} An object containing all domains currently being ignored.
 * @example
 * const Perf = require("moov_perf");
 * let perf = new Perf($);
 * perf.ignore("http://www.google.com/");
 * perf.ignore("http://www.bing.com");
 * perf.ignore("http://www.yahoo.com/image.jpg");
 * perf.ignored();
 * // => Returns: {
 * //      "www.google.com": true,
 * //      "www.bing.com": true,
 * //      "www.yahoo.com": true
 * //    }
 */
Perf.prototype.ignored = function() {
    return this.blacklist;
};

/**
 * @method ignore
 * @summary moov_perf
 * @memberof perf
 * @description Add domain to the Perf instance's blacklist. Thus, when using
 * any of the image optimization methods for a given Cheerio object selector, it
 * will specifically avoid rewriting the image source for any domains included
 * in the ignored list. This method will strip off the protocol, as well as any
 * trailing path.
 * @param {String} domain A string for the domain to ignore.
 * @returns {Object} The original Perf instance.
 * @example
 * const Perf = require("moov_perf");
 * let perf = new Perf($);
 * perf.ignore("http://www.google.com/");
 * // => Returns: The Perf object instance assigned to `perf`
 *
 * perf.ignore("http://www.bing.com");
 * perf.ignore("http://www.yahoo.com/image.jpg");
 * perf.ignored();
 * // => Returns: {
 * //      "www.google.com": true,
 * //      "www.bing.com": true,
 * //      "www.yahoo.com": true
 * //    }
 */
Perf.prototype.ignore = function(domain) {
    let valid = parseUrl(domain, false, true);
    if (valid.host !== undefined) {
        this.ignored()[valid.host] = true;
    }

    return this;
};
/** Export the Perf object*/
module.exports = Perf;
Last updated Mon Feb 26 2018 22:51:26 GMT+0000 (UTC)