diff --git a/packages/heml-elements/src/Base.js b/packages/heml-elements/src/Base.js index 31fa3cb..6d89122 100644 --- a/packages/heml-elements/src/Base.js +++ b/packages/heml-elements/src/Base.js @@ -1,10 +1,10 @@ -import HEML, { createElement } from '@heml/utils' // eslint-disable-line no-unused-vars +import HEML, { createMetaElement } from '@heml/utils' // eslint-disable-line no-unused-vars import Meta from './Meta' import isAbsoluteUrl from 'is-absolute-url' import { resolve } from 'url' import { has, first } from 'lodash' -export default createElement('base', { +export default createMetaElement('base', { parent: [ 'head' ], children: false, unique: true, diff --git a/packages/heml-elements/src/Meta.js b/packages/heml-elements/src/Meta.js index 7c6db5b..634f475 100644 --- a/packages/heml-elements/src/Meta.js +++ b/packages/heml-elements/src/Meta.js @@ -1,8 +1,8 @@ -import HEML, { createElement } from '@heml/utils' // eslint-disable-line no-unused-vars +import HEML, { createMetaElement } from '@heml/utils' // eslint-disable-line no-unused-vars let metaMap -export default createElement('meta', { +export default createMetaElement('meta', { attrs: true, parent: [ 'head' ], diff --git a/packages/heml-elements/src/Preview.js b/packages/heml-elements/src/Preview.js index 8a49b99..3824084 100644 --- a/packages/heml-elements/src/Preview.js +++ b/packages/heml-elements/src/Preview.js @@ -1,7 +1,7 @@ -import HEML, { createElement } from '@heml/utils' // eslint-disable-line no-unused-vars +import HEML, { createMetaElement } from '@heml/utils' // eslint-disable-line no-unused-vars import Meta from './Meta' -export default createElement('preview', { +export default createMetaElement('preview', { parent: [ 'head' ], unique: true, diff --git a/packages/heml-elements/src/Style.js b/packages/heml-elements/src/Style.js index 37ae8aa..439b474 100644 --- a/packages/heml-elements/src/Style.js +++ b/packages/heml-elements/src/Style.js @@ -1,4 +1,4 @@ -import HEML, { createElement } from '@heml/utils' // eslint-disable-line no-unused-vars +import HEML, { createMetaElement } from '@heml/utils' // eslint-disable-line no-unused-vars import hemlstyles from '@heml/styles' import { castArray, isEqual, uniqWith, sortBy } from 'lodash' @@ -8,7 +8,7 @@ const START_INLINE_CSS = `/*!***START:INLINE_CSS*****/` let styleMap let options -export default createElement('style', { +export default createMetaElement('style', { parent: [ 'head' ], attrs: [ 'for', 'heml-embed' ], defaultAttrs: { diff --git a/packages/heml-elements/src/Subject.js b/packages/heml-elements/src/Subject.js index 081eb9e..3b7e1ee 100644 --- a/packages/heml-elements/src/Subject.js +++ b/packages/heml-elements/src/Subject.js @@ -1,7 +1,7 @@ -import HEML, { createElement } from '@heml/utils' // eslint-disable-line no-unused-vars +import HEML, { createMetaElement } from '@heml/utils' // eslint-disable-line no-unused-vars import Meta from './Meta' -export default createElement('subject', { +export default createMetaElement('subject', { parent: [ 'head' ], unique: true, diff --git a/packages/heml-render/src/index.js b/packages/heml-render/src/index.js index a188862..a202315 100644 --- a/packages/heml-render/src/index.js +++ b/packages/heml-render/src/index.js @@ -1,4 +1,4 @@ -import { filter, difference, keyBy, first } from 'lodash' +import { filter, keyBy, first } from 'lodash' import renderElement from './renderElement' export { renderElement } @@ -18,6 +18,7 @@ export default async function render ($, options = {}) { const Meta = first(elements.filter(({ tagName }) => tagName === 'meta')) await preRenderElements(elements, globals) + await renderMetaElements(elements, globals) await renderElements(elements, globals) await postRenderElements(elements, globals) @@ -49,29 +50,65 @@ async function postRenderElements (elements, globals) { } /** - * Renders all HEML elements + * Renders meta HEML elements + * @param {Array} elements List of element definitons + * @param {Object} globals + * @return {Promise} + */ +async function renderMetaElements (elements, globals) { + const { $ } = globals + const metaTagNames = filter(elements, ({ meta }) => !!meta).map(({ tagName }) => tagName) + + /** Render the meta elements first to last */ + const $nodes = $.findNodes(metaTagNames) + await renderNodes($nodes, globals) +} + +/** + * Renders HEML elements * @param {Array} elements List of element definitons * @param {Object} globals * @return {Promise} */ async function renderElements (elements, globals) { const { $ } = globals - const elementMap = keyBy(elements, 'tagName') - const metaTagNames = filter(elements, { parent: [ 'head' ] }).map(({ tagName }) => tagName) - const nonMetaTagNames = difference(elements.map(({ tagName }) => tagName), metaTagNames) + const nonMetaTagNames = filter(elements, ({ meta }) => !meta).map(({ tagName }) => tagName) + + /** Render the elements last to first/outside to inside */ + const $nodes = $.findNodes(nonMetaTagNames).reverse() + await renderNodes($nodes, globals) +} - const $nodes = [ - ...$.findNodes(metaTagNames), /** Render the meta elements first to last */ - ...$.findNodes(nonMetaTagNames).reverse() /** Render the elements last to first/outside to inside */ - ] +/** + * renders the given array of $nodes + * @param {Array[Cheerio]} $nodes + * @param {Object} globals { $, elements } + */ +async function renderNodes ($nodes, globals) { + const { elements } = globals + const elementMap = keyBy(elements, 'tagName') for (let $node of $nodes) { - const element = elementMap[$node.prop('tagName').toLowerCase()] - const contents = $node.html() - const attrs = $node[0].attribs + const tagName = $node.prop('tagName').toLowerCase() + + if (!elementMap[tagName]) { continue } - const renderedValue = await Promise.resolve(renderElement(element, attrs, contents)) + const element = elementMap[tagName] - $node.replaceWith(renderedValue.trim()) + await renderNode($node, element) } } + +/** + * renders a single $node of the given element + * @param {Cheerio} $node + * @param {Object} element + */ +async function renderNode ($node, element) { + const contents = $node.html() + const attrs = $node[0].attribs + + const renderedValue = await Promise.resolve(renderElement(element, attrs, contents)) + + $node.replaceWith(renderedValue.trim()) +} diff --git a/packages/heml-utils/src/createMetaElement.js b/packages/heml-utils/src/createMetaElement.js new file mode 100644 index 0000000..aea1b9f --- /dev/null +++ b/packages/heml-utils/src/createMetaElement.js @@ -0,0 +1,16 @@ +import { isFunction } from 'lodash' +import createElement from './createElement' + +export default function (name, element) { + if (!name || name.trim().length === 0) { + throw new Error(`When creating an element, you must set the name. ${name.trim().length === 0 ? 'An empty string' : `"${name}"`} was given.`) + } + + if (isFunction(element)) { + element = { render: element } + } + + element.meta = true + + return createElement(name, element) +} diff --git a/packages/heml-utils/src/index.js b/packages/heml-utils/src/index.js index 77ebeed..ed45de2 100644 --- a/packages/heml-utils/src/index.js +++ b/packages/heml-utils/src/index.js @@ -1,8 +1,9 @@ import { renderElement } from '@heml/render' import cssGroups from 'css-groups' import createElement from './createElement' +import createMetaElement from './createMetaElement' import HEMLError from './HEMLError' import transforms from './transforms' import condition from './condition' -module.exports = { createElement, renderElement, HEMLError, cssGroups, transforms, condition } +module.exports = { createElement, createMetaElement, renderElement, HEMLError, cssGroups, transforms, condition } diff --git a/packages/heml/src/bin/heml.js b/packages/heml/src/bin/heml.js index 62769f2..8c36fb3 100755 --- a/packages/heml/src/bin/heml.js +++ b/packages/heml/src/bin/heml.js @@ -27,7 +27,7 @@ cli .option('-v, --validate [level]', 'Sets the validation level', /^(none|soft|strict)$/i, 'soft') .action(build) -if (args.length === 0 || !commands.includes(first(args)) && !first(args).startsWith('-')) { +if (args.length === 0 || (!commands.includes(first(args)) && !first(args).startsWith('-'))) { cli.outputHelp() }