devsite/node_modules/posthtml/lib/index.js
2024-07-07 18:49:38 -07:00

324 lines
7.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const pkg = require('../package.json')
const Api = require('./api.js')
let { parser } = require('posthtml-parser')
let { render } = require('posthtml-render')
/**
* @author Ivan Voischev (@voischev),
* Ivan Demidov (@scrum)
*
* @requires api
* @requires posthtml-parser
* @requires posthtml-render
*
* @constructor PostHTML
* @param {Array} plugins - An array of PostHTML plugins
*/
class PostHTML {
constructor (plugins) {
/**
* PostHTML Instance
*
* @prop plugins
* @prop options
*/
this.version = pkg.version
this.name = pkg.name
this.plugins = typeof plugins === 'function' ? [plugins] : plugins || []
this.source = ''
/**
* Tree messages to store and pass metadata between plugins
*
* @memberof tree
* @type {Array} messages
*
* @example
* ```js
* export default function plugin (options = {}) {
* return function (tree) {
* tree.messages.push({
* type: 'dependency',
* file: 'path/to/dependency.html',
* from: tree.options.from
* })
*
* return tree
* }
* }
* ```
*/
this.messages = []
/**
* Tree method parsing string inside plugins.
*
* @memberof tree
* @type {Function} parser
*
* @example
* ```js
* export default function plugin (options = {}) {
* return function (tree) {
* tree.match({ tag: 'include' }, function(node) {
* node.tag = false;
* node.content = tree.parser(fs.readFileSync(node.attr.src))
* return node
* })
*
* return tree
* }
* }
* ```
*/
this.parser = parser
/**
* Tree method rendering tree to string inside plugins.
*
* @memberof tree
* @type {Function} render
*
* @example
* ```js
* export default function plugin (options = {}) {
* return function (tree) {
* var outherTree = ['\n', {tag: 'div', content: ['1']}, '\n\t', {tag: 'div', content: ['2']}, '\n'];
* var htmlWitchoutSpaceless = tree.render(outherTree).replace(/[\n|\t]/g, '');
* return tree.parser(htmlWitchoutSpaceless)
* }
* }
* ```
*/
this.render = render
// extend api methods
Api.call(this)
}
/**
* @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))
* ```
*/
use (...args) {
this.plugins.push(...args)
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))
* ```
*/
process (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
* @prop {?Array} options.directives - Adds processing of custom [directives](https://github.com/posthtml/posthtml-parser#directives).
*/
this.options = options
this.source = tree
if (options.parser) parser = this.parser = options.parser
if (options.render) render = this.render = options.render
tree = options.skipParse
? tree || []
: parser(tree, options)
tree = [].concat(tree)
// sync mode
if (options.sync === true) {
this.plugins.forEach((plugin, index) => {
_treeExtendApi(tree, this)
let result
if (plugin.length === 2 || isPromise(result = plugin(tree))) {
throw new Error(
`Cant process contents in sync mode because of async plugin: ${plugin.name}`
)
}
// clearing the tree of options
if (index !== this.plugins.length - 1 && !options.skipParse) {
tree = [].concat(tree)
}
// return the previous tree unless result is fulfilled
tree = result || tree
})
return lazyResult(render, tree)
}
// async mode
let i = 0
const next = (result, cb) => {
_treeExtendApi(result, this)
// all plugins called
if (this.plugins.length <= i) {
cb(null, result)
return
}
// little helper to go to the next iteration
function _next (res) {
if (res && !options.skipParse) {
res = [].concat(res)
}
return next(res || result, cb)
}
// call next
const plugin = this.plugins[i++]
if (plugin.length === 2) {
plugin(result, (err, res) => {
if (err) return cb(err)
_next(res)
})
return
}
// sync and promised plugins
let err = null
const res = tryCatch(() => plugin(result), e => {
err = e
return e
})
if (err) {
cb(err)
return
}
if (isPromise(res)) {
res.then(_next).catch(cb)
return
}
_next(res)
}
return new Promise((resolve, reject) => {
next(tree, (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 = plugins => new PostHTML(plugins)
/**
* Extension of options tree
*
* @private
*
* @param {Array} tree
* @param {Object} PostHTML
* @returns {?*}
*/
function _treeExtendApi (t, _t) {
if (typeof t === 'object') {
t = Object.assign(t, _t)
}
}
/**
* 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)
}
}
/**
* 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,
messages: tree.messages
}
}