Sign Up for Free

RunKit +

Try any Node.js package right in your browser

This is a playground to test code. It runs a full Node.js environment and already has all of npm’s 400,000 packages pre-installed, including html-cdnify with all npm packages installed. Try it out:

var htmlCdnify = require("html-cdnify")

This service is provided by RunKit and is not affiliated with npm, Inc or the package authors.

html-cdnify v0.1.5

Transform the relative URLs in your HTML markup (e.g. scripts, stylesheets, images) to use your CDN URL.

logo

Codeship Status for altano/html-cdnify

Transform the relative URLs in your HTML markup (e.g. scripts, stylesheets, images) to use your CDN URL.

  • Uses a real HTML parser, trumpet, not regular expressions.
  • Doesn't require changes to your HTML, is a purely post-process transformation.
  • Supports relative URLs in your HTML.
  • Will perform minimal modifications to your HTML, only modifying the element that is having an attribute cdnified.
  • Like grunt-cdnify but usable with or without grunt.
  • Like cdnify but you don't have to mark up your HTML with magic attributes.
  • This library's scope is limited to HTML and is not meant to process CSS.

Simplest usage

var cdnify = require("html-cdnify").cdnify;

cdnify({
    cdnUrl: "//cdn.com",
    buffer: `<img src="/face.png">`,
})
.then(buffer => buffer.toString())
.then(output => console.log(output));

// Output:
// <img src="//cdn.com/face.png">

What gets cdnified

Any URLs where only the path (and after) is specified which are found in the following locations will be cdnified:

   <img data-src="____">
   <img src="____">
   <img srcset="____">
 <video poster="____">
<script src="____">
<source src="____">
  <link rel="apple-touch-icon" href="____">
  <link rel="icon" href="____">
  <link rel="shortcut icon" href="____">
  <link rel="stylesheet" href="____">

Will be cdnified

  • <img src="/logo.png">
  • <script src="jquery.js"></script>
  • <link rel="stylesheet" href="main.css"></script>

Won't be cdnified

HTMLElementReason not cdnified
<img src="http://foo.com/logo.png">Absolute URL specified
<img src="//foo.com/logo.png">Scheme-relative/agnostic URL specified
<img custom="/logo.png">"custom" attribute not among those cdnified

Adding the data-cdn-ignore attribute to any element will cause the element to be skipped during cdnification, and the data-cdn-ignore attribute will be removed to clean up your HTML.

More Examples

Using bufferPath to deal with subdirectories

You can specify bufferPath, the relative path to the buffer being processed. If this isn't specified, all resources with relative paths will be assumed to be at the root of the domain before being cdnified.

var cdnify = require("html-cdnify").cdnify;

var input = `
<img src="figure1.png">
<img src="articleSubDirectory/figure2.png">
<img src="/images/logo.png">
`;

var outputPromise = cdnify({
    cdnUrl: "//cdn.com",
    bufferPath: "articles/article1/index.html",
    buffer: input,
}).then(buffer => buffer.toString());

outputPromise.then(output => console.log(output));

// Output:
// <img src="//cdn.com/articles/article1/figure1.png">
// <img src="//cdn.com/articles/article1/articleSubDirectory/figure2.png">
// <img src="//cdn.com/images/logo.png">

Without bufferPath, the output would be:

// Output:
// <img src="//cdn.com/figure1.png">
// <img src="//cdn.com/articleSubDirectory/figure2.png">
// <img src="//cdn.com/images/logo.png">

If your CDN has a path specified, it is assumed that both relative URLs AND root-relative URLs in your HTML are relative to the CDN directory. In other words, the CDN subdirectory will always be in the final URL. For example:

var cdnify = require("html-cdnify").cdnify;

var input = `
<img src="figure1.png">
<img src="articleSubDirectory/figure2.png">
<img src="/images/logo.png">
`;

var outputPromise = cdnify({
    cdnUrl: "//cdn.com/sub/directory/in/cdn/is/always/present",
    bufferPath: "article/index.html",
    buffer: input,
}).then(buffer => buffer.toString());

outputPromise.then(output => console.log(output));

// Output:
// <img src="//cdn.com/sub/directory/in/cdn/is/always/present/article/figure1.png">
// <img src="//cdn.com/sub/directory/in/cdn/is/always/present/article/articleSubDirectory/figure2.png">
// <img src="//cdn.com/sub/directory/in/cdn/is/always/present/images/logo.png">

It should usually be the expectation that a CDN subdir is always present in the cdnified result. If you need to specify a CDN subdirectory AND you need to sometimes escape URLs in your HTML to the root of the CDN domain, you'll have to write a custom transformFunction.

Using the underlying stream instead of a Promise

If you are dealing with large files or network IO and would like to use the underlying NodeJS Transform Stream rather than buffering all of the input and output, feel free:

var streamifier = require("streamifier");
var CDNTransformer = require("html-cdnify").CDNTransformer;

var transformer = new CDNTransformer({
    cdnUrl: "http://cdn.com"
});

var outputStream = streamifier
    .createReadStream(`<img src="face1.png">
<img src="face2.png">
<img src="face3.png">`)
    .pipe(transformer.stream);

outputStream.pipe(process.stdout);

// Output:
// <img src="http://cdn.com/face1.png">
// <img src="http://cdn.com/face2.png">
// <img src="http://cdn.com/face3.png">

Specifying a transform defintion to add <custom-element src="____"> to the list of attributes to be cdnified

var cdnify = require("html-cdnify").cdnify;

var outputPromise = cdnify({
  cdnUrl: "//cdn.com/cdn/",
  transformDefinitions: [
    {
      selector: "custom-element[src]",
      attribute: "src",
    }
  ],
  buffer: `<custom-element src="/face7.png">`
}).then(buffer => buffer.toString());

outputPromise.then(output => console.log(output));

// Output:
// <custom-element src="//cdn.com/cdn/face7.png">

Specifying a custom transform function

var cdnify = require("html-cdnify").cdnify;

var outputPromise = cdnify({
  cdnUrl: "//cdn.com",
  transformFunction: (cdnUrl, oldUrl, bufferPath) => {
    return cdnUrl + "/subdir" + oldUrl;
  },
  buffer: `<img src="/logo.png">`
}).then(buffer => buffer.toString());

outputPromise.then(output => console.log(output));

// Output:
// <img src="//cdn.com/subdir/logo.png">

Specifying a custom transform function that delegates to the default

If your assets are on different CDNs, you might need to write something like:

var cdnify = require("html-cdnify").cdnify;
var CDNTransformer = require("html-cdnify").CDNTransformer;

var outputPromise = cdnify({
  cdnUrl: "//cdn.com",
  transformFunction: (cdnUrl, oldUrl, bufferPath) => {
    if (oldUrl.endsWith(".png")) {
      var customCdnBaseUrl = "//imagecdn.com";
    }
    else {
      var customCdnBaseUrl = "//assetcdn.com"
    }
    return CDNTransformer.defaultTransformFunction(customCdnBaseUrl, oldUrl, bufferPath);
  },
  buffer: `<img src="logo.png"><script src="main.js"></script>`
}).then(buffer => buffer.toString());

outputPromise.then(output => console.log(output));

// Output:
// <img src="//imagecdn.com/logo.png"><script src="//assetcdn.com/main.js"></script>

Specifying a custom attributeParser

A custom attributeParser is like a custom transformFunction, but is scoped to just one selector rather than being applied to every attribute transformation. For example, to uppercase only PNG image names before cdnifying:

var cdnify = require("html-cdnify").cdnify;

var outputPromise = cdnify({
  cdnUrl: "//cdn.com",
  transformDefinitions: [
    {
      selector: `img[src$="png"]`,
      attribute: "src",
      attributeParser: (oldAttribute, transformFunction) => transformFunction(oldAttribute.toUpperCase())
    }
  ],
  buffer: `<img src="/logo.gif"><img src="/logo.png">`
}).then(buffer => buffer.toString());

outputPromise.then(output => console.log(output));

// Output:
// <img src="//cdn.com/logo.gif"><img src="//cdn.com/LOGO.PNG">

Overriding an existing transform

The default transformDefinitions array is:

[
  {
    selector: `video[poster]:not([data-cdn-ignore])`,
    attribute: "poster",
  },
  {
    selector: `img[data-src]:not([data-cdn-ignore])`,
    attribute: "data-src",
  },
  {
    selector: `script[src]:not([data-cdn-ignore])`,
    attribute: "src",
  },
  {
    selector: `source[src]:not([data-cdn-ignore])`,
    attribute: "src",
  },
  {
    selector: `link[rel="apple-touch-icon"]:not([data-cdn-ignore])`,
    attribute: "href",
  },
  {
    selector: `link[rel="icon"]:not([data-cdn-ignore])`,
    attribute: "href",
  },
  {
    selector: `link[rel="shortcut icon"]:not([data-cdn-ignore])`,
    attribute: "href",
  },
  {
    selector: `link[rel="stylesheet"]:not([data-cdn-ignore])`,
    attribute: "href",
  },
  {
    selector: `img[src]:not([data-cdn-ignore])`,
    attribute: "src",
  },
  {
    selector: `img[srcset]:not([data-cdn-ignore])`,
    attribute: "srcset",
    attributeParser: (attr, transformFunction) => {
      return attr.split(",")
        .map(imgInfo => imgInfo.replace(/([^ ]+)/, transformFunction))
        .join(",");
    }
  }
]

If you'd like to ovverride any of these, specify the same selector and attribute but a different attributeParser. For example:

var cdnify = require("html-cdnify").cdnify;

var noopFn = oldAttribute => oldAttribute;

var outputPromise = cdnify({
  cdnUrl: "//cdn.com",
  transformDefinitions: [
    {
      selector: "img[src]:not([data-cdn-ignore])",
      attribute: "src",
      attributeParser: noopFn // pass-through attribute without modification
    }
  ],
  buffer: `<img src="logo.gif">`
}).then(buffer => buffer.toString());

outputPromise.then(output => console.log(output));

// Output:
// <img src="logo.gif">

API

Promise-based API (recommended for simplicity)

var cdnify = require("html-cdnify").cdnify;

cdnify({ ... Options ...}) => Promise<Buffer>

Options

Same options as stream-based API (see below), plus:

PropertyTypeOptionalDescription
bufferBuffer or stringThe content that is going to be cdnified

Stream-based API

use the stream-based API when you are dealing with large files or network IO and would like to use the underlying NodeJS Transform Stream rather than buffering all of the input and output. The Promisebased API is simpler but the input/output must be completely buffered, which is obviously fine most of the time.

var CDNTransformer = require("html-cdnify").CDNTransformer;

var transformer = new CDNTransformer({ ... Options ... }) => CDNTransformer
transformer.stream => NodeJS TransformStream

Options

PropertyTypeOptionalDescription
cdnUrlstringThe absolute (or scheme-relative/agnostic) URL to your CDN.
bufferPathstringYThe relative path to the buffer being processed. If this isn't specified, all resources with relative paths will be assumed to be at the root of the domain before being cdnified.
transformDefinitionsTransformDefinitionYSee below...
transformFunctionCDNTransformFunctionYSee below...

TransformDefinition

interface TransformDefinition {
  selector: string;
  attribute: string;
  attributeParser?: (oldAttribute: string, transformFunction: TransformFunction) => string;
}

The default attributeParser when one isn't specified is:

HtmlAttributeStreamTransformerOptions.attributeParsers = {
  default: (attr, transformFunction) => transformFunction(attr)
};

CDNTransformFunction

(cdnUrl: string, oldUrl: string, bufferPath: string) => string;

TransformFunction

(oldAttribute: string) => string;

Shield (in logo) by Flaticon from Freepik

Metadata

RunKit is a free, in-browser JavaScript dev environment for prototyping Node.js code, with every npm package installed. Sign up to share your code.
Sign Up for Free