infuse.host
Version:
Infuse your HTML with dynamic content.
116 lines (100 loc) • 5.14 kB
JavaScript
import parseDocument from './parseDocument.js';
import parseTemplate from './parseTemplate.js';
import configs, { contextFunctions, parsedTemplates } from './configs.js';
/**
* Path to the configs module to use in generated ES modules.
*/
const DEFAULT_CONFIGS_PATH = 'infuse.host/src/configs.js';
/**
* Generates a function that can be used to create unique IDs. The returned function is meant to be
* used by the infuse.host parser to ensure that all parsed elements use unique IDs within a given
* HTML document. The `hash` argument is used to make sure IDs generated for elements in one HTML
* document don't conflict with those of another document.
*
* @function uniqueIdFn
* @param {string} fullHash A hash to append to all IDs.
* @param {number} [hashLength=7] Limits the length of the hash to the number of characters
* specified by this attribute. Uses 7 by default, which means that the returned function will
* only append the first 7 characters of the hash to the generated unique IDs.
* @returns {Function} The return function takes a `tagName` (string) as the only argument and
* returns a unique ID string.
*/
export const uniqueIdFn = function createUniqueIdFunction(fullHash, hashLength = 7) {
let idCounter = 1;
const hash = hashLength > fullHash.length ? fullHash : fullHash.substr(0, hashLength);
return tagName => `${ tagName }${ idCounter++ }_${ hash }`;
};
/**
* Generates an ES module from an HTML document. If the provided `htmlDocument` is a string, it is
* parsed into an actual `Document`. It is then ran through the infuse.host parser and the source
* code for an ES module is generated. The returned ES module exports:
*
* * The resulting `document`, after all the parsing is performed by infuse.host.
* * Default export: The first template found in the exported `document`.
* * All other templates found in `document`, using their predefined or generated
* "template ids" as export names.
*
* The generated ES module also loads all the `contextFunctions`, generated by the infuse.host
* parser, into memory.
*
* @function createESModule
* @param {(string|Document)} htmlDocument The HTML document as a string (HTML source code) or as an
* instance of `Document`.
* @param {Object} [options={}] Options object.
* @param {number} [options.hashLength=7] The length of the hash to use when generating unique
* element IDs. The hash is used to avoid collisions with IDs of elements in other documents.
* @param {Function} [options.uniqueId] A function to generate unique ID values during the
* parsing process.
* @param {string} [options.configsPath='infuse.host/src/configs'] Path to the infuse.host
* configs module to use in the generated ES module.
* @returns {string} The source code of the generated ES module.
*/
export default function createESModule(htmlDocument, options = {}) {
const lines = [''];
const templateId = configs.get('templateId');
const configsPath = options.configsPath || DEFAULT_CONFIGS_PATH;
// Parse the document.
const { doctype, document, hash, window } = parseDocument(htmlDocument);
// If a unique ID function was not provided, create one using the document's `hash`.
const uniqueId = options.uniqueId || uniqueIdFn(hash, options.hashLength);
// Find and parse all templates in the `document`. This changes the document's DOM.
let templates = Array.from(document.querySelectorAll('template'));
for (let i = 0; i < templates.length; i++) {
parseTemplate(templates[i], { uniqueId, window });
}
// Since the document's DOM changed, we need to find all templates again.
templates = Array.from(document.querySelectorAll('template'));
/**
* Iterate over each template and add lines of code to `lines` to perform the following actions
* when the generated ES module is loaded by the browser:
*
* * Export the template using its template ID.
* * Add the template to the `parsedTemplates` map using its template ID.
*/
for (let i = 0; i < templates.length; i++) {
const tid = templates[i].getAttribute(templateId);
lines.splice(i, 0, `export const ${ tid } = templates[${ i }];`);
lines.push(`parsedTemplates.set('${ tid }', ${ tid });`);
}
lines.push('');
/**
* Iterate over the generated parsed elements `Map` and add code to `lines` so that its
* contents are added to `contextFunctions` when the generated ES module is loaded by
* the browser.
*/
for (const [tid, fn] of contextFunctions) {
const fnSrc = fn.toString().replace('function anonymous(\n', 'function(');
lines.push(`contextFunctions.set('${ tid }', ${ fnSrc });`);
}
// Clear context functions and templates from memory.
contextFunctions.clear();
parsedTemplates.clear();
return `import { contextFunctions, parsedTemplates } from '${ configsPath }';
const html = \`${ doctype }\n${ document.body.outerHTML.replace(/\\?`/g, '\\`') }\`;
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const templates = doc.getElementsByTagName('template');
export { doc as document };
export default templates[0];
${ lines.join('\n') }`;
}