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('..', {}) * .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('..', { sync: true }).html * ``` * * **Async** * ```js * ph.process('..', {}).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 } }