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/*, options */)
.then(function(result) {
console.log(result.html);
// <div class="myComponent"><div class="myTitle">Super Title</div><div class="myText">Awesome Text</div></div>
});
Сomplex example
var posthtml = require('posthtml');
var html = '<html><body><p class="wow">OMG</p></body></html>';
posthtml([
require('posthtml-to-svg-tags')(),
require('posthtml-extend-attrs')({
attrsTree: {
'.wow' : {
id: 'wow_id',
fill: '#4A83B4',
'fill-rule': 'evenodd',
'font-family': 'Verdana'
}
}
})
])
.process(html/*, options */)
.then(function(result) {
console.log(result.html);
// <svg xmlns="http://www.w3.org/2000/svg"><text class="wow" id="wow_id" fill="#4A83B4" fill-rule="evenodd" font-family="Verdana">OMG</text></svg>
});
Gulp plugin for PostHTML
gulp-posthtml
Installnpm 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')() ]/*, options */))
.pipe(gulp.dest('build/'));
});
Check project-stub example with Gulp
PostHTML with Jade engine in Expressjs
Also it's work with other view engine. Callback in app.engine
is called by res.render()
to render the template code.
app.engine('jade', function (path, options, callback) {
// PostHTML plugins
var plugins = [
require('posthtml-bem')(),
require('posthtml-textr')({ locale: 'ru'}, [
require('typographic-ellipses'),
require('typographic-single-spaces'),
require('typographic-quotes')
])
];
var html = require('jade').renderFile(path, options);
posthtml(plugins)
.process(html)
.then(function (result) {
if (typeof callback === 'function') {
var res;
try {
res = result.html;
} catch (ex) {
return callback(ex);
}
return callback(null, res);
}
});
})
app.set('view engine', 'jade');
Plugins
- posthtml-bem — Support BEM naming in html structure
- posthtml-retext — Extensible system for analysing and manipulating natural language
- posthtml-textr — Modular typographic framework
- posthtml-custom-elements — Use custom elements now
- posthtml-style-to-file — Save HTML style nodes and attributes to CSS file
- posthtml-classes — Get a list of classes from HTML
- posthtml-doctype — Extend html tags doctype
- posthtml-include — Include html file
- posthtml-to-svg-tags — Convert html tags to svg equals
- posthtml-extend-attrs — Extend html tags attributes with custom data and attributes
- posthtml-modular-css — Makes css modular
- posthtml-static-react — Render custom elements as static React components
- posthtml-head-elements — Store head elements (title, script, link, base and meta) in a JSON file and render them into the document during the build process
- posthtml-web-component — Web Component ServerSide Rending, Component as Service in Server
- posthtml-inline-css — CSS Inliner
Ideas for plugins
- posthtml-imports — Support W3C HTML imports
- posthtml-style — Include css file in HTML. Save <style>, style attrs to CSS file
- beml — HTML preprocessor for BEM
- mimic
Something more? ;)
Helpers
- posthml-match-helper - Expand CSS selectors into PostHTML matcher objects.
Dependency
- posthtml-parser — Parser HTML/XML to PostHTMLTree
- posthtml-render — Render PostHTMLTree to HTML/XML
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: [
'\n ',
{
tag: 'span',
attrs: {
class: 'animals__cat',
style: 'background: url(cat.png)'
},
content: ['Cat']
},
'\n'
]
}]
Create PostHTML plugin
This is a simple function with a single argument
Synchronous plugin example
module.exports = function pluginName(tree) {
// do something for 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) {
// skip local anchors
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 { toTree } from 'posthtml/lib/api';
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 = toTree(body);
resolve(tree);
});
});
});
};
class PostHTML
.use()
Arguments: {Function} plugin
Adds a plugin into the flow.
Example
var posthtml = require('posthtml');
var ph = posthtml()
.use(function(tree) {
return { tag: 'div', content: tree };
});
.process()
Arguments: {String|PostHTMLTree} html[, {Object} options]
Applies all plugins to the incoming html
object.
Returns: {{tree: PostHTMLTree, html: String}}
(eventually) an Object with modified html and/or tree.
Example
var ph = posthtml()
.process('<div></div>'/*, { options }*/);
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; // { tag: 'section' }
result.html; // <section></section>
});
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; // <section>foo</section>
class API
.walk()
Arguments: {function(PostHTMLNode|String): PostHTMLNode|String}
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)) {
// do something for node
return node;
}
return node;
});
.match()
Arguments: {Object|String|RegExp}, {function(PostHTMLNode|String): PostHTMLNode|String}
Find subtree in tree, run callback.
Example
tree.match({ tag: 'custom-tag' }, function(node) {
// do something for 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
});
License
MIT