PostHTML
PostHTML is a tool for transforming HTML/XML with JS plugins. PostHTML itself is very small. It includes only a HTML parser, a HTML node tree API and a node tree stringifier.
All HTML transformations are made by plugins. And these plugins are just small plain JS functions, which receive a HTML node tree, transform it, and return a modified tree.
Usage
Install PostHTML
npm install --save-dev posthtml
Simple example
var posthtml = require('posthtml');
var html = '<myComponent><myTitle>Super Title</myTitle><myText>Awesome Text</myText></myComponent>';
posthtml()
.use(require('posthtml-custom-elements')())
.process(html)
.then(function(result) {
console.log(result.html);
});
Сomplex example
var posthtml = require('posthtml');
var html = '<html><body><p class="wow">OMG</p></body></html>';
posthtml([
require('posthtml-doctype')('<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'),
require('posthtml-to-svg-tags')(),
require('posthtml-extend-attrs')({
attrsTree: {
'.wow' : {
id: 'wow_id',
fill: '#4A83B4',
'fill-rule': 'evenodd',
'font-family': 'Verdana'
}
}
})
])
.process(html)
.then(function(result) {
console.log(result.html);
});
Gulp plugin for PostHTML
npm install --save-dev gulp-posthtml
gulp.task('html', function() {
var posthtml = require('gulp-posthtml');
return gulp.src('src/**/*.html')
.pipe(posthtml([ require('posthtml-custom-elements')() ]))
.pipe(gulp.dest('build/'));
});
PostHTML JSON tree example
input HTML
<a class="animals" href="#">
<span class="animals__cat" style="background: url(cat.png)">Cat</span>
</a>
Tree in PostHTML (PostHTMLTree)
[{
tag: 'a',
attrs: {
class: 'animals',
href: '#'
},
content: [{
tag: 'span',
attrs: {
class: 'animals__cat',
style: 'background: url(cat.png)'
},
content: ['Cat']
}]
}]
Create PostHTML plugin
This is a simple function with a single argument
Synchronous plugin example
module.exports = function pluginName(tree) {
tree.match({ tag: 'img' }, function(node) {
node = Object.assign(node, { attrs: { class: 'img-wrapped' } }});
return {
tag: 'span',
attrs: { class: 'img-wrapper' },
content: node
}
});
};
Classic asynchronous plugin example
var request = request('request');
module.exports = function pluginName(tree, cb) {
var tasks = 0;
tree.match({ tag: 'a' }, function(node) {
if (!/^(https?:)?\/\//.test(node.attrs.href)) {
return node;
}
request.head(node.attrs.href, function (err, resp) {
if (err) return done();
if (resp.statusCode >= 400) {
node.attrs.class += ' ' + 'Erroric';
}
if (resp.headers.contentType) {
node.attrs.class += ' content-type_' + resp.headers.contentType;
}
done();
});
tasks += 1;
return node;
});
function done() {
tasks -= 1;
if (!tasks) cb(null, tree);
}
};
Promised asynchronous plugin example
import { PostHTML } from 'posthtml';
import request from 'request';
export default tree => {
return new Promise(resolve => {
tree.match({ tag: 'user-info' }, (node) => {
request(`/api/user-info?${node.attrs.dataUserId}`, (err, resp, body) {
if (!err && body) node.content = PostHTML.parse(body);
resolve(tree);
});
});
});
};
class PostHTML
#parse ({String} html): {PostHTMLTree}
Parses HTML string into a PostHTMLTree object.
Example
import { PostHTML } from 'posthtml';
PostHTML.parse('<div></div>');
.use ({Function} plugin): {PostHTML}
Adds a plugin into the flow.
Example
var posthtml = require('posthtml');
var ph = posthtml()
.use(function(tree) {
return { tag: 'div', content: tree };
});
.process ({String|PostHTMLTree} html, {Object} options): {{tree: PostHTMLTree, html: String}}
Applies all plugins to the incoming html
object.
Returns (eventually) an Object with modified html and/or tree.
Example
var ph = posthtml()
.process('<div></div>');
Options
singleTags
Array tags for extend default list single tags
Default: []
Options { singleTags: ['rect', 'custom'] }
...
<div>
...
<rect>
<custom>
</div>
closingSingleTag
Option to specify version closing single tags.
Accepts values: default
, slash
, tag
.
Default: default
Options { closingSingleTag: 'default' }
<singletag>
Options { closingSingleTag: 'slash' }
<singletag />
Options { closingSingleTag: 'tag' }
<singletag></singletag>
skipParse
Skips input html parsing process.
Default: null
posthtml()
.use(function(tree) { tree.tag = 'section'; })
.process({ tag: 'div' }, { skipParse: true })
.then(function (result) {
result.tree;
result.html;
});
sync
Try to run plugins synchronously. Throws if some plugins are async.
Default: null
posthtml()
.use(function(tree) { tree.tag = 'section'; })
.process('<div>foo</div>', { sync: true })
.html;
class API
.walk ({function(PostHTMLNode): PostHTMLNode})
Walk for all nodes in tree, run callback.
Example
tree.walk(function(node) {
let classes = node.attrs && node.attrs.class.split(' ') || [];
if(classes.includes(className)) {
return node;
}
return node;
});
.match ({Object|String}, {function(PostHTMLNode): PostHTMLNode|String})
Find subtree in tree, run callback.
Example
tree.match({ tag: 'custom-tag' }, function(node) {
return Object.assign(node, {
tag: 'div',
attrs: { class: node.tag }
});
});
Support Array matchers
Example
tree.match([{ tag: 'b' }, { tag: 'strong' }], function(node) {
var style = 'font-weight: bold;';
node.tag = 'span';
node.attrs ? (
node.attrs.style ? (
node.attrs.style += style
) : node.attrs.style = style;
) : node.attrs = { style: style };
return node
});
.matchClass ({String}, {function(PostHTMLNode): PostHTMLNode})
For each found of class run callback
Example
tree.matchClass('class-for-delete', function(node) {
return '';
});
Plugins
Ideas for plugins
Something more? ;)