256 lines
5.3 KiB
JavaScript
256 lines
5.3 KiB
JavaScript
var pkg = require('../package.json')
|
||
var api = require('./api.js')
|
||
|
||
var parser = require('posthtml-parser')
|
||
var render = require('posthtml-render')
|
||
|
||
/**
|
||
* @author Ivan Voischev (@voischev),
|
||
* Anton Winogradov (@awinogradov),
|
||
* Alexej Yaroshevich (@zxqfox),
|
||
* Vasiliy (@Yeti-or)
|
||
*
|
||
* @requires api
|
||
* @requires posthtml-parser
|
||
* @requires posthtml-render
|
||
*
|
||
* @constructor PostHTML
|
||
* @param {Array} plugins - An array of PostHTML plugins
|
||
*/
|
||
function PostHTML (plugins) {
|
||
/**
|
||
* PostHTML Instance
|
||
*
|
||
* @prop plugins
|
||
* @prop options
|
||
*/
|
||
this.version = pkg.version
|
||
this.name = pkg.name
|
||
this.plugins = typeof plugins === 'function' ? [plugins] : plugins || []
|
||
}
|
||
|
||
/**
|
||
* @requires posthtml-parser
|
||
*
|
||
* @param {String} html - Input (HTML)
|
||
* @returns {Array} tree - PostHTMLTree (JSON)
|
||
*/
|
||
PostHTML.parser = parser
|
||
/**
|
||
* @requires posthtml-render
|
||
*
|
||
* @param {Array} tree - PostHTMLTree (JSON)
|
||
* @returns {String} html - HTML
|
||
*/
|
||
PostHTML.render = render
|
||
|
||
/**
|
||
* @this posthtml
|
||
* @param {Function} plugin - A PostHTML plugin
|
||
* @returns {Constructor} - this(PostHTML)
|
||
*
|
||
* **Usage**
|
||
* ```js
|
||
* ph.use((tree) => { tag: 'div', content: tree })
|
||
* .process('<html>..</html>', {})
|
||
* .then((result) => result))
|
||
* ```
|
||
*/
|
||
PostHTML.prototype.use = function () {
|
||
[].push.apply(this.plugins, arguments)
|
||
return this
|
||
}
|
||
|
||
/**
|
||
* @param {String} html - Input (HTML)
|
||
* @param {?Object} options - PostHTML Options
|
||
* @returns {Object<{html: String, tree: PostHTMLTree}>} - Sync Mode
|
||
* @returns {Promise<{html: String, tree: PostHTMLTree}>} - Async Mode (default)
|
||
*
|
||
* **Usage**
|
||
*
|
||
* **Sync**
|
||
* ```js
|
||
* ph.process('<html>..</html>', { sync: true }).html
|
||
* ```
|
||
*
|
||
* **Async**
|
||
* ```js
|
||
* ph.process('<html>..</html>', {}).then((result) => result))
|
||
* ```
|
||
*/
|
||
PostHTML.prototype.process = function (tree, options) {
|
||
/**
|
||
* ## PostHTML Options
|
||
*
|
||
* @type {Object}
|
||
* @prop {?Boolean} options.sync - enables sync mode, plugins will run synchronously, throws an error when used with async plugins
|
||
* @prop {?Function} options.parser - use custom parser, replaces default (posthtml-parser)
|
||
* @prop {?Function} options.render - use custom render, replaces default (posthtml-render)
|
||
* @prop {?Boolean} options.skipParse - disable parsing
|
||
*/
|
||
options = options || {}
|
||
|
||
if (options.parser) parser = options.parser
|
||
if (options.render) render = options.render
|
||
|
||
tree = options.skipParse ? tree : parser(tree)
|
||
|
||
tree.options = options
|
||
tree.processor = this
|
||
|
||
// sync mode
|
||
if (options.sync === true) {
|
||
this.plugins.forEach(function (plugin) {
|
||
apiExtend(tree)
|
||
|
||
var result
|
||
|
||
if (plugin.length === 2 || isPromise(result = plugin(tree))) {
|
||
throw new Error(
|
||
'Can’t process contents in sync mode because of async plugin: ' + plugin.name
|
||
)
|
||
}
|
||
// return the previous tree unless result is fulfilled
|
||
tree = result || tree
|
||
})
|
||
|
||
return lazyResult(render, tree)
|
||
}
|
||
|
||
// async mode
|
||
var i = 0
|
||
|
||
var next = function (result, cb) {
|
||
// all plugins called
|
||
if (this.plugins.length <= i) {
|
||
cb(null, result)
|
||
return
|
||
}
|
||
|
||
// little helper to go to the next iteration
|
||
function _next (res) {
|
||
return next(res || result, cb)
|
||
}
|
||
|
||
// (re)extend the object
|
||
apiExtend(result)
|
||
|
||
// call next
|
||
var plugin = this.plugins[i++]
|
||
|
||
if (plugin.length === 2) {
|
||
plugin(result, function (err, res) {
|
||
if (err) return cb(err)
|
||
_next(res)
|
||
})
|
||
return
|
||
}
|
||
|
||
// sync and promised plugins
|
||
var err = null
|
||
|
||
var res = tryCatch(function () {
|
||
return plugin(result)
|
||
}, function (err) {
|
||
return err
|
||
})
|
||
|
||
if (err) {
|
||
cb(err)
|
||
return
|
||
}
|
||
|
||
if (isPromise(res)) {
|
||
res.then(_next).catch(cb)
|
||
return
|
||
}
|
||
|
||
_next(res)
|
||
}.bind(this)
|
||
|
||
return new Promise(function (resolve, reject) {
|
||
next(tree, function (err, tree) {
|
||
if (err) reject(err)
|
||
else resolve(lazyResult(render, tree))
|
||
})
|
||
})
|
||
}
|
||
|
||
/**
|
||
* @exports posthtml
|
||
*
|
||
* @param {Array} plugins
|
||
* @return {Function} posthtml
|
||
*
|
||
* **Usage**
|
||
* ```js
|
||
* import posthtml from 'posthtml'
|
||
* import plugin from 'posthtml-plugin'
|
||
*
|
||
* const ph = posthtml([ plugin() ])
|
||
* ```
|
||
*/
|
||
module.exports = function (plugins) {
|
||
return new PostHTML(plugins)
|
||
}
|
||
|
||
/**
|
||
* Checks if parameter is a Promise (or thenable) object.
|
||
*
|
||
* @private
|
||
*
|
||
* @param {*} promise - Target `{}` to test
|
||
* @returns {Boolean}
|
||
*/
|
||
function isPromise (promise) {
|
||
return !!promise && typeof promise.then === 'function'
|
||
}
|
||
|
||
/**
|
||
* Simple try/catch helper, if exists, returns result
|
||
*
|
||
* @private
|
||
*
|
||
* @param {Function} tryFn - try block
|
||
* @param {Function} catchFn - catch block
|
||
* @returns {?*}
|
||
*/
|
||
function tryCatch (tryFn, catchFn) {
|
||
try {
|
||
return tryFn()
|
||
} catch (err) {
|
||
catchFn(err)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Extends the PostHTMLTree with the Tree API
|
||
*
|
||
* @private
|
||
*
|
||
* @param {Array} tree - PostHTMLTree
|
||
* @returns {Array} tree - PostHTMLTree with API
|
||
*/
|
||
function apiExtend (tree) {
|
||
tree.walk = api.walk
|
||
tree.match = api.match
|
||
}
|
||
|
||
/**
|
||
* Wraps the PostHTMLTree within an object using a getter to render HTML on demand.
|
||
*
|
||
* @private
|
||
*
|
||
* @param {Function} render
|
||
* @param {Array} tree
|
||
* @returns {Object<{html: String, tree: Array}>}
|
||
*/
|
||
function lazyResult (render, tree) {
|
||
return {
|
||
get html () {
|
||
return render(tree, tree.options)
|
||
},
|
||
tree: tree
|
||
}
|
||
}
|