/html-dist

A tool for editing HTML files to add new scripts

Primary LanguageJavaScript

Note: html-dist is being completely rewritten. If you want the old version, install html-dist@0.1.0 and read this README.

html-dist

Easily manipulate HTML files by removing and inserting elements.

VERY WIP! Until V1.0.0 you should expect breaking changes in minor versions. EG: 0.2 -> 0.3 may include breaking changes

npm install --save-dev html-dist@0.3.1

Why?

The problem that lead to me creating html-dist was deploying front-end only projects. In development mode I tend to have an HTML file set up for development. For example, an HTML file for a jspm project might look like so:

<!DOCTYPE html>
<html>
  <head>
    <title>My Project</title>
    <link rel="stylesheet" type="text/css" href="style.css">

    <script src="jspm_packages/system.js"></script>
    <script src="config.js"></script>
    <script>
      System.import('app/main');
    </script>
  </head>
  <body>
  </body>
</html>

JSPM provides a tool to bundle all my JavaScript into one file, but I'm left to deal with my HTML file. I could manually edit it each time, but that gets boring quickly. This is why I built html-dist. I can create a configuration file that tells html-dist how to manipulate the HTML and it will produce the new HTML file. In this instance I want to remove all script tags and insert one new one to bundle.js. Additionally I'd like to add the Google Analytics script.

Here's how I would do that with html-dist:

  1. Install html-dist: npm install --save-dev html-dist
  2. Create a html-dist.config.js file (with full ES2015 support):
var { script, googleAnalytics } = require('html-dist');

module.exports = {
  // where to write to
  outputFile: 'dist/index.html',
  // minify the HTML
  minify: true,
  head: {
    // in the <head>, remove any elements matching the 'script' CSS selector
    remove: 'script'
  },
  body: {
    // append the following things to the body
    appends: [
      script({
        src: 'bundle.js'
      }),
      googleAnalytics('UA-1234')
    ]
  }
}

Note that the configuration file is not parsed with Babel. You may use whatever set of ES features your Node version supports.

  1. Run html-dist:
html-dist --config html-dist.config-js --input index.html

Which will produce the following:

<html><head><title>My Project</title><link rel="stylesheet" type="text/css" href="style.css"></head><body><script src="bundle.js"></script><script>(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)})(window,document,'script','//www.google-analytics.com/analytics.js','ga'); ga('create', 'UA-1234', 'auto'); ga('send', 'pageview');</script></body></html>
  1. Deploy dist/index.html to production.

Fundamentals of html-dist

Under the hood html-dist uses the virtual-dom library to manipulate all the HTML. It parses your HTML into a Virtual DOM tree which it then allows you to manipulate, before generating HTML from that virtual dom tree at the end of the process.

This means if you want you can harness the full power of the virtual DOM to manipulate your HTML. However, for most use cases html-dist will provide helpers to make it easy for you. It's possible to use html-dist without ever dealing with the virtual DOM.

The configuration object

As of V0.3.0, the configuration file is NOT transpiled with Babel.

The configuration object must be the default export:

module.exports = {
  // configuration here
}

Using a .js file rather than JSON also enables you to leave comments as you wish.

The top level keys you can specify are:

  • outputFile: the path to write the generated HTML to. If not given the output will be printed to STDOUT.
  • minfiy: if set to true, the generated HTML will be minified. Else it will be formatted nicely.
  • head: an object of manipulations to make to the <head> element.
  • body: an object of manipulations to make to the <body> element.

Manipulation objects

The object given as head or body also must specify certain keys.

Appends and Prepends

The recommended way to use html-dist that should satisfy most use cases is through the keys appends, prepends and remove:

  • remove: 'cssSelector' removes any elements that match the given selector.
  • prepends: [...] prepend the given elements.
  • appends: [...] append the given elements.

For example, given:

head: {
  remove: 'script'
  prepends: [
    script({ src: 'bundle.js' })
  ],
  appends: [
    script({ src: 'other-bundle.js' })
  ]
}

The <head> element will first have all script tags removed (remove is always called first), it will then have <script src='bundle.js'></script> prepended and <script src='other-bundle.js'></script> appended.

### Full control with tree

If you'd like full control over the manipulations, you should define the tree function:

head: {
  tree: function(head) {
    // manipulate head here
  }
}

When the tree function is given it is the only function called. It will be passed an object that you can use to manipulate the element. See "Manipulating Trees" below for the full documentation.

Manipulating Trees

When you define a tree function in a manipulation object that function will be given an instance of TreeManipulator, which allows you to manipulate the tree as you like:

head: {
  tree: function(head) {
    // head is an instance of `TreeManipulator`
  }
}

The following methods are available:

  • remove(cssSelector) removes all elements that match the given CSS selector. For example: head.remove('script').
  • append(node) appends the new node as a child element. For example: head.append(script({ src: 'test.js' })).
  • prepend(node) same as append, but prepends rather than appends.
  • replaceWith(childrenArray) replace the entire contents of the node with the new ones. For example:
head: {
  tree: function(head) {
    return head.replaceWith([
      script({ src: 'test.js' }),
      googleAnalytics('UA-1234-1')
    ]);
  }
}

That would replace the contents of your <head> with a script tag and the Google Analytics snippet.

Additionally, remove, append and prepend are chainable:

head.remove('script').append(...).prepend(...);

Helpers for creating Elements

The most common elements that need to be inserted are made easier by html-dist's helpers. You can import them into your configuration file:

var { link, script, googleAnalytics } = require('html-dist');

link

Creates a new link element:

var link = require('html-dist').link;

link({ rel: 'stylesheet', type: 'text/css', href: 'style.css' })

script

Creates a new script element:

var script = require('html-dist').script;

script({ type: 'text/javascript', src: 'bundle.js' });

Additionally it can take a special contents property to be used to create a script tag with JS inside it:

var script = require('html-dist').script;

script({ contents: 'console.log("hello world")' });
//=> <script>console.log("hello world")</script>

googleAnalytics

Takes your GA user agent and produces the Google Analytics snippet:

var googleAnalytics = require('html-dist').googleAnayltics;

googleAnalytics('UA-1234-X');

Creating custom elements

If html-dist doesn't provide a suitable helper, and you think it should, please raise an issue!

There are two ways to create custom HTML:

fromHtml

You can use the fromHtml helper from html-dist/lib/html:

var fromHtml = require('html-dist/html');

fromHtml('<div>HELLO</div>');

This will cause a new div to be inserted into the final HTML.

h

You can use h to create custom DOM elements:

var h = require('html-dist').h;

h('div#foo', 'hello');

Will create a div with an id of foo and the contents hello inside. This function is provided by virtual-DOM, so refer to its documentation to see how to use it.

Passing extra information to html-dist

Sometimes you'll need to pass html-dist some extra information, such as the location of your JavaScript file, so html-dist can insert a script tag with the right src attribute.

The CLI lets you pass in any arbitrary arguments:

./html-dist --config my.config.js --input index.html --output dist/index.html --jsFile "bundle-1234.js"

In my.config.js, I can import args and have access to them:

var { script, args } = require('html-dist');

module.exports = {
  outputFile: 'dist/index.html',
  minify: true,
  head: {
    remove: 'script'
  },
  body: {
    appends: [
      script({
        src: args.jsFile
      })
    ]
  }
}

The resulting dist/index.html will contain <script src='bundle-1234.js'></script> within it.