diff --git a/.eslintignore b/.eslintignore index e69de29..a7e5fab 100644 --- a/.eslintignore +++ b/.eslintignore @@ -0,0 +1,3 @@ +dist/ +coverage/ + diff --git a/README.md b/README.md index 9fc5b34..393e55a 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ I've experimented with other existing libraries (ejs templates, etc.) but wanted ## Getting Started -1. copy the faintly.js file to the scripts directory of your project +1. copy the /dist/faintly.js file to the scripts directory of your project 2. in the folder for your block, add a `blockName.html` file for the block template 3. in your block javascript, call the `renderBlock` function: @@ -96,4 +96,4 @@ Faintly supports a simple expression syntax for resolving data from the renderin For `data-fly-include`, HTML text, and normal attributes, wrap your expression in `${}`. -In all other `data-fly-*` attributes, just set the expression directly as the attribute value, no wrapping needed. +In all other `data-fly-*` attributes, just set the expression directly as the attribute value, no wrapping needed. \ No newline at end of file diff --git a/src/faintly.js b/dist/faintly.js similarity index 51% rename from src/faintly.js rename to dist/faintly.js index 39e575b..761451f 100644 --- a/src/faintly.js +++ b/dist/faintly.js @@ -1,122 +1,77 @@ -/** - * resolve the template to render - * - * @param {object} context the rendering context - * @returns {Promise} the template element - */ +// src/templates.js async function resolveTemplate(context) { context.template = context.template || {}; context.template.path = context.template.path || `${context.codeBasePath}/blocks/${context.blockName}/${context.blockName}.html`; - - const templateId = `faintly-template-${context.template.path}#${context.template.name || ''}`.toLowerCase().replace(/[^0-9a-z]/gi, '-'); + const templateId = `faintly-template-${context.template.path}#${context.template.name || ""}`.toLowerCase().replace(/[^0-9a-z]/gi, "-"); let template = document.getElementById(templateId); if (!template) { const resp = await fetch(context.template.path); if (!resp.ok) throw new Error(`Failed to fetch template from ${context.template.path} for block ${context.blockName}.`); - const markup = await resp.text(); - const dp = new DOMParser(); - const templateDom = dp.parseFromString(markup, 'text/html'); - - templateDom.querySelectorAll('template').forEach((t) => { - const name = t.getAttribute('data-fly-name') || ''; - t.id = `faintly-template-${context.template.path}#${name}`.toLowerCase().replace(/[^0-9a-z]/gi, '-'); - + const templateDom = dp.parseFromString(markup, "text/html"); + templateDom.querySelectorAll("template").forEach((t) => { + const name = t.getAttribute("data-fly-name") || ""; + t.id = `faintly-template-${context.template.path}#${name}`.toLowerCase().replace(/[^0-9a-z]/gi, "-"); document.body.append(t); }); } - template = document.getElementById(templateId); if (!template) throw new Error(`Failed to find template with id ${templateId}.`); - return template; } -/** - * resolves and returns data from the rendering context - * - * @param {string} expression the name of the data - * @param {Object} context the rendering context - * @returns {Promise} the data that was resolved - */ +// src/expressions.js async function resolveExpression(expression, context) { let resolved = context; - let prevResolved; - - const parts = expression.split('.'); + let previousResolvedValue; + const parts = expression.split("."); for (let i = 0; i < parts.length; i += 1) { - if (typeof resolved === 'undefined') break; - + if (typeof resolved === "undefined") break; const part = parts[i]; - prevResolved = resolved; + previousResolvedValue = resolved; resolved = resolved[part]; - - if (typeof resolved === 'function') { + if (typeof resolved === "function") { const functionParams = [{ ...context }]; - // eslint-disable-next-line no-await-in-loop - resolved = await resolved.apply(prevResolved, functionParams); + resolved = await resolved.apply(previousResolvedValue, functionParams); } } - return resolved; } - -/** - * resolves expressions in a string - * - * @param {string} str the string that may contain expressions - * @param {Object} context the rendering context - */ async function resolveExpressions(str, context) { const regexp = /(\\)?\${([a-z0-9\\.\s]+)}/dgi; - const promises = []; str.replaceAll(regexp, (match, escapeChar, expression) => { if (escapeChar) { promises.push(Promise.resolve(match.slice(1))); } - promises.push(resolveExpression(expression.trim(), context)); - return match; }); - if (promises.length > 0) { const promiseResults = await Promise.all(promises); const updatedText = str.replaceAll(regexp, () => { const result = promiseResults.shift(); return result; }); - return { updated: true, updatedText }; } - return { updated: false, updatedText: str }; } - -/** - * process text expressions within a text node, updating the node's textContent - * - * @param {Node} node the text node - * @param {Object} context the rendering context - */ async function processTextExpressions(node, context) { const { updated, updatedText } = await resolveExpressions(node.textContent, context); - if (updated) node.textContent = updatedText; } +// src/directives.js async function processAttributesDirective(el, context) { - if (!el.hasAttribute('data-fly-attributes')) return; - - const attrsExpression = el.getAttribute('data-fly-attributes'); + if (!el.hasAttribute("data-fly-attributes")) return; + const attrsExpression = el.getAttribute("data-fly-attributes"); const attrsData = await resolveExpression(attrsExpression, context); - - el.removeAttribute('data-fly-attributes'); + el.removeAttribute("data-fly-attributes"); if (attrsData) { Object.entries(attrsData).forEach(([k, v]) => { - if (v === undefined) { + if (v === void 0) { el.removeAttribute(k); } else { el.setAttribute(k, v); @@ -124,296 +79,159 @@ async function processAttributesDirective(el, context) { }); } } - -/** - * process the attributes directive, as well as any expressions in non `data-fly-*` attributes - * - * @param {Element} el the element to process - * @param {Object} context the rendering context - */ async function processAttributes(el, context) { processAttributesDirective(el, context); - - const attrPromises = el.getAttributeNames() - .filter((attrName) => !attrName.startsWith('data-fly-')) - .map(async (attrName) => { - const { updated, updatedText } = await resolveExpressions(el.getAttribute(attrName), context); - if (updated) el.setAttribute(attrName, updatedText); - }); + const attrPromises = el.getAttributeNames().filter((attrName) => !attrName.startsWith("data-fly-")).map(async (attrName) => { + const { updated, updatedText } = await resolveExpressions(el.getAttribute(attrName), context); + if (updated) el.setAttribute(attrName, updatedText); + }); await Promise.all(attrPromises); } - -/** - * processes the test directive - * - * @param {Element} el the element to process - * @param {Object} context the rendering context - * @returns {Promise} indicator if node should be rendered - */ async function processTest(el, context) { - const testAttrName = el.getAttributeNames().find((attrName) => attrName.startsWith('data-fly-test') || attrName.startsWith('data-fly-not')); + const testAttrName = el.getAttributeNames().find((attrName) => attrName.startsWith("data-fly-test") || attrName.startsWith("data-fly-not")); if (!testAttrName) return true; - - const nameParts = testAttrName.split('.'); - const contextName = nameParts[1] || ''; - + const nameParts = testAttrName.split("."); + const contextName = nameParts[1] || ""; const testExpression = el.getAttribute(testAttrName); const testData = await resolveExpression(testExpression, context); - el.removeAttribute(testAttrName); - - const testResult = testAttrName.startsWith('data-fly-not') ? !testData : !!testData; - + const testResult = testAttrName.startsWith("data-fly-not") ? !testData : !!testData; if (contextName) context[contextName.toLowerCase()] = testResult; - if (!testResult) { el.remove(); } - return testResult; } - -/** - * process the unwrap directive, leavving the attribute only if it resolves to true - * - * @param {Element} el the element to process - * @param {Object} context the rendering context - * @returns {Promise} - */ -async function resolveUnwrap(el, context) { - if (!el.hasAttribute('data-fly-unwrap')) return; - - const unwrapExpression = el.getAttribute('data-fly-unwrap'); - if (unwrapExpression) { - const unwrapVal = !!(await resolveExpression(unwrapExpression, context)); - - if (!unwrapVal) { - el.removeAttribute('data-fly-unwrap'); - } - } -} - -function processUnwraps(el) { - el.querySelectorAll('[data-fly-unwrap]').forEach((unwrapEl) => { - unwrapEl.before(...unwrapEl.childNodes); - unwrapEl.remove(); - }); -} - -/** - * process the content directive - * - * @param {Element} el the element to process - * @param {Object} context the rendering context - * @returns {Promise} if there was a content directive - */ async function processContent(el, context) { - if (!el.hasAttribute('data-fly-content')) return false; - - const contentExpression = el.getAttribute('data-fly-content'); + if (!el.hasAttribute("data-fly-content")) return false; + const contentExpression = el.getAttribute("data-fly-content"); const content = await resolveExpression(contentExpression, context); - - el.removeAttribute('data-fly-content'); - - if (content !== undefined) { + el.removeAttribute("data-fly-content"); + if (content !== void 0) { if (content instanceof Node) { el.replaceChildren(content); - } else if (Array.isArray(content) - || content instanceof NodeList || content instanceof HTMLCollection) { + } else if (Array.isArray(content) || content instanceof NodeList || content instanceof HTMLCollection) { el.replaceChildren(...content); } else { const textNode = document.createTextNode(content); el.replaceChildren(textNode); } } else { - el.textContent = ''; + el.textContent = ""; } - return true; } - -/** - * processes the repeat directive - * - * @param {Element} el the element to potentially be repeated - * @param {Object} context the rendering context - * @returns {Promise} if the node was repeated - * the net number of nodes added/removed as a result of the repeat directive - */ async function processRepeat(el, context) { - const repeatAttrName = el.getAttributeNames().find((attrName) => attrName.startsWith('data-fly-repeat')); + const repeatAttrName = el.getAttributeNames().find((attrName) => attrName.startsWith("data-fly-repeat")); if (!repeatAttrName) return false; - - const nameParts = repeatAttrName.split('.'); - const contextName = nameParts[1] || 'item'; - + const nameParts = repeatAttrName.split("."); + const contextName = nameParts[1] || "item"; const repeatExpression = el.getAttribute(repeatAttrName); const arr = await resolveExpression(repeatExpression, context); if (!arr || Object.keys(arr).length === 0) { el.remove(); return true; } - el.removeAttribute(repeatAttrName); const repeatedNodes = await Promise.all(Object.entries(arr).map(async ([key, item], i) => { const cloned = el.cloneNode(true); - const repeatContext = { ...context }; repeatContext[contextName.toLowerCase()] = item; repeatContext[`${contextName.toLowerCase()}Index`] = i; repeatContext[`${contextName.toLowerCase()}Number`] = i + 1; repeatContext[`${contextName.toLowerCase()}Key`] = key; - - // eslint-disable-next-line no-use-before-define await processNode(cloned, repeatContext); - return cloned; })); - let afterEL = el; repeatedNodes.forEach((node) => { afterEL.after(node); afterEL = node; }); - el.remove(); - return true; } - -/** - * process the include directive - * - * @param {Element} el the element to process - * @param {Object} context the rendering context - * @returns {Promise} if there was a include directive - */ async function processInclude(el, context) { - if (!el.hasAttribute('data-fly-include')) return false; - - const includeValue = el.getAttribute('data-fly-include'); - el.removeAttribute('data-fly-include'); + if (!el.hasAttribute("data-fly-include")) return false; + const includeValue = el.getAttribute("data-fly-include"); + el.removeAttribute("data-fly-include"); const { updatedText } = await resolveExpressions(includeValue, context); - - let templatePath = context.template ? context.template.path : ''; + let templatePath = context.template ? context.template.path : ""; let templateName = updatedText; - if (templateName.startsWith('/')) { - const [path, name] = templateName.split('#'); + if (templateName.startsWith("/")) { + const [path, name] = templateName.split("#"); templatePath = path; templateName = name; } - const includeContext = { ...context, template: { name: templateName, - path: templatePath, - }, + path: templatePath + } }; - - // eslint-disable-next-line no-use-before-define await renderElement(el, includeContext); - return true; } +async function resolveUnwrap(el, context) { + if (!el.hasAttribute("data-fly-unwrap")) return; + const unwrapExpression = el.getAttribute("data-fly-unwrap"); + if (unwrapExpression) { + const unwrapVal = !!await resolveExpression(unwrapExpression, context); + if (!unwrapVal) { + el.removeAttribute("data-fly-unwrap"); + } + } +} +function processUnwraps(el) { + el.querySelectorAll("[data-fly-unwrap]").forEach((unwrapEl) => { + unwrapEl.before(...unwrapEl.childNodes); + unwrapEl.remove(); + }); +} -/** - * recursively renders a dom node, processing all directives - * - * @param {Node} node the node to render - * @param {Object} context the rendering context - * @returns {Promise} a promise that resolves when the node has been rendered - */ +// src/render.js async function processNode(node, context) { context.currentNode = node; let processChildren = [Node.ELEMENT_NODE, Node.DOCUMENT_FRAGMENT_NODE].includes(node.nodeType); if (node.nodeType === Node.ELEMENT_NODE) { const shouldRender = await processTest(node, context); if (!shouldRender) return; - const repeated = await processRepeat(node, context); if (repeated) return; - await processAttributes(node, context); - - processChildren = (await processContent(node, context)) - || (await processInclude(node, context)) || true; - + processChildren = await processContent(node, context) || await processInclude(node, context) || true; await resolveUnwrap(node, context); } else if (node.nodeType === Node.TEXT_NODE) { await processTextExpressions(node, context); } - const children = !processChildren ? [] : [...node.childNodes]; - for (let i = 0; i < children.length; i += 1) { const child = children[i]; - // eslint-disable-next-line no-await-in-loop await processNode(child, context); } } - -/** - * Render a template - * @param {Element} template the template to render - * @param {Object} context the rendering context - */ async function renderTemplate(template, context) { const templateClone = template.cloneNode(true); await processNode(templateClone.content, context); - processUnwraps(templateClone.content); - return templateClone; } - -/** - * transform the element, replacing it's children with the content from the template - * @param {Element} el the element - * @param {Element} template the template element - * @param {Object} context the rendering context - */ async function renderElementWithTemplate(el, template, context) { const rendered = await renderTemplate(template, context); el.replaceChildren(rendered.content); } - -/** - * Transform an element using an HTML template - * - * @param {Element} block the block element - * @param {Object} context the rendering context - */ -export async function renderElement(el, context) { +async function renderElement(el, context) { const template = await resolveTemplate(context); - await renderElementWithTemplate(el, template, context); } - -/** - * Transform a block using an HTML template - * - * @param {Element} block the block element - * @param {Object} context the rendering context - */ -export async function renderBlock(block, context = {}) { +async function renderBlock(block, context = {}) { context.block = block; context.blockName = block.dataset.blockName; - context.codeBasePath = context.codeBasePath || (window.hlx ? window.hlx.codeBasePath : ''); - + context.codeBasePath = context.codeBasePath || (window.hlx ? window.hlx.codeBasePath : ""); await renderElement(block, context); } - -export const exportForTesting = { - resolveTemplate, - resolveExpression, - resolveExpressions, - processTextExpressions, - processAttributes, - processTest, - processContent, - processInclude, - processRepeat, - resolveUnwrap, - processUnwraps, +export { + renderBlock, + renderElement }; diff --git a/package-lock.json b/package-lock.json index 99d07a8..992f4d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@web/test-runner": "0.19.0", "@web/test-runner-commands": "0.9.0", "@web/test-runner-mocha": "0.9.0", + "esbuild": "0.23.0", "eslint": "8.57.1", "eslint-config-airbnb-base": "15.0.0", "eslint-plugin-import": "2.31.0" @@ -296,6 +297,414 @@ "node": ">=6.9.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.0.tgz", + "integrity": "sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.0.tgz", + "integrity": "sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.0.tgz", + "integrity": "sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.0.tgz", + "integrity": "sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.0.tgz", + "integrity": "sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.0.tgz", + "integrity": "sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.0.tgz", + "integrity": "sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.0.tgz", + "integrity": "sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.0.tgz", + "integrity": "sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.0.tgz", + "integrity": "sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.0.tgz", + "integrity": "sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.0.tgz", + "integrity": "sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.0.tgz", + "integrity": "sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.0.tgz", + "integrity": "sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.0.tgz", + "integrity": "sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.0.tgz", + "integrity": "sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.0.tgz", + "integrity": "sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.0.tgz", + "integrity": "sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.0.tgz", + "integrity": "sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.0.tgz", + "integrity": "sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.0.tgz", + "integrity": "sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.0.tgz", + "integrity": "sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.0.tgz", + "integrity": "sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.0.tgz", + "integrity": "sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", @@ -2918,6 +3327,46 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/esbuild": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.0.tgz", + "integrity": "sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.23.0", + "@esbuild/android-arm": "0.23.0", + "@esbuild/android-arm64": "0.23.0", + "@esbuild/android-x64": "0.23.0", + "@esbuild/darwin-arm64": "0.23.0", + "@esbuild/darwin-x64": "0.23.0", + "@esbuild/freebsd-arm64": "0.23.0", + "@esbuild/freebsd-x64": "0.23.0", + "@esbuild/linux-arm": "0.23.0", + "@esbuild/linux-arm64": "0.23.0", + "@esbuild/linux-ia32": "0.23.0", + "@esbuild/linux-loong64": "0.23.0", + "@esbuild/linux-mips64el": "0.23.0", + "@esbuild/linux-ppc64": "0.23.0", + "@esbuild/linux-riscv64": "0.23.0", + "@esbuild/linux-s390x": "0.23.0", + "@esbuild/linux-x64": "0.23.0", + "@esbuild/netbsd-x64": "0.23.0", + "@esbuild/openbsd-arm64": "0.23.0", + "@esbuild/openbsd-x64": "0.23.0", + "@esbuild/sunos-x64": "0.23.0", + "@esbuild/win32-arm64": "0.23.0", + "@esbuild/win32-ia32": "0.23.0", + "@esbuild/win32-x64": "0.23.0" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", diff --git a/package.json b/package.json index 169f766..46058c2 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,9 @@ "test:perf": "web-test-runner --node-resolve --group perf", "test:perf:watch": "web-test-runner --node-resolve --watch --group perf", "lint": "eslint .", - "lint:fix": "eslint . --fix" + "lint:fix": "eslint . --fix", + "build": "esbuild src/index.js --bundle --format=esm --platform=browser --outfile=dist/faintly.js", + "clean": "rm -rf dist" }, "author": "Sean Steimer", "license": "Apache-2.0", @@ -21,6 +23,7 @@ "@web/test-runner-mocha": "0.9.0", "eslint": "8.57.1", "eslint-config-airbnb-base": "15.0.0", - "eslint-plugin-import": "2.31.0" + "eslint-plugin-import": "2.31.0", + "esbuild": "0.23.0" } } \ No newline at end of file diff --git a/src/directives.js b/src/directives.js new file mode 100644 index 0000000..468c7e0 --- /dev/null +++ b/src/directives.js @@ -0,0 +1,212 @@ +import { resolveExpression, resolveExpressions } from './expressions.js'; +// eslint-disable-next-line import/no-cycle +import { processNode, renderElement } from './render.js'; + +async function processAttributesDirective(el, context) { + if (!el.hasAttribute('data-fly-attributes')) return; + + const attrsExpression = el.getAttribute('data-fly-attributes'); + const attrsData = await resolveExpression(attrsExpression, context); + + el.removeAttribute('data-fly-attributes'); + if (attrsData) { + Object.entries(attrsData).forEach(([k, v]) => { + if (v === undefined) { + el.removeAttribute(k); + } else { + el.setAttribute(k, v); + } + }); + } +} + +/** + * process the attributes directive, as well as any expressions in non `data-fly-*` attributes + * + * @param {Element} el the element to process + * @param {Object} context the rendering context + */ +export async function processAttributes(el, context) { + processAttributesDirective(el, context); + + const attrPromises = el.getAttributeNames() + .filter((attrName) => !attrName.startsWith('data-fly-')) + .map(async (attrName) => { + const { updated, updatedText } = await resolveExpressions(el.getAttribute(attrName), context); + if (updated) el.setAttribute(attrName, updatedText); + }); + await Promise.all(attrPromises); +} + +/** + * processes the test directive + * + * @param {Element} el the element to process + * @param {Object} context the rendering context + * @returns {Promise} indicator if node should be rendered + */ +export async function processTest(el, context) { + const testAttrName = el.getAttributeNames().find((attrName) => attrName.startsWith('data-fly-test') || attrName.startsWith('data-fly-not')); + if (!testAttrName) return true; + + const nameParts = testAttrName.split('.'); + const contextName = nameParts[1] || ''; + + const testExpression = el.getAttribute(testAttrName); + const testData = await resolveExpression(testExpression, context); + + el.removeAttribute(testAttrName); + + const testResult = testAttrName.startsWith('data-fly-not') ? !testData : !!testData; + + if (contextName) context[contextName.toLowerCase()] = testResult; + + if (!testResult) { + el.remove(); + } + + return testResult; +} + +/** + * process the content directive + * + * @param {Element} el the element to process + * @param {Object} context the rendering context + * @returns {Promise} if there was a content directive + */ +export async function processContent(el, context) { + if (!el.hasAttribute('data-fly-content')) return false; + + const contentExpression = el.getAttribute('data-fly-content'); + const content = await resolveExpression(contentExpression, context); + + el.removeAttribute('data-fly-content'); + + if (content !== undefined) { + if (content instanceof Node) { + el.replaceChildren(content); + } else if (Array.isArray(content) + || content instanceof NodeList || content instanceof HTMLCollection) { + el.replaceChildren(...content); + } else { + const textNode = document.createTextNode(content); + el.replaceChildren(textNode); + } + } else { + el.textContent = ''; + } + + return true; +} + +/** + * processes the repeat directive + * + * @param {Element} el the element to potentially be repeated + * @param {Object} context the rendering context + * @returns {Promise} if the node was repeated + * the net number of nodes added/removed as a result of the repeat directive + */ +export async function processRepeat(el, context) { + const repeatAttrName = el.getAttributeNames().find((attrName) => attrName.startsWith('data-fly-repeat')); + if (!repeatAttrName) return false; + + const nameParts = repeatAttrName.split('.'); + const contextName = nameParts[1] || 'item'; + + const repeatExpression = el.getAttribute(repeatAttrName); + const arr = await resolveExpression(repeatExpression, context); + if (!arr || Object.keys(arr).length === 0) { + el.remove(); + return true; + } + + el.removeAttribute(repeatAttrName); + const repeatedNodes = await Promise.all(Object.entries(arr).map(async ([key, item], i) => { + const cloned = el.cloneNode(true); + + const repeatContext = { ...context }; + repeatContext[contextName.toLowerCase()] = item; + repeatContext[`${contextName.toLowerCase()}Index`] = i; + repeatContext[`${contextName.toLowerCase()}Number`] = i + 1; + repeatContext[`${contextName.toLowerCase()}Key`] = key; + + // eslint-disable-next-line no-use-before-define + await processNode(cloned, repeatContext); + + return cloned; + })); + + let afterEL = el; + repeatedNodes.forEach((node) => { + afterEL.after(node); + afterEL = node; + }); + + el.remove(); + + return true; +} + +/** + * process the include directive + * + * @param {Element} el the element to process + * @param {Object} context the rendering context + * @returns {Promise} if there was a include directive + */ +export async function processInclude(el, context) { + if (!el.hasAttribute('data-fly-include')) return false; + + const includeValue = el.getAttribute('data-fly-include'); + el.removeAttribute('data-fly-include'); + const { updatedText } = await resolveExpressions(includeValue, context); + + let templatePath = context.template ? context.template.path : ''; + let templateName = updatedText; + if (templateName.startsWith('/')) { + const [path, name] = templateName.split('#'); + templatePath = path; + templateName = name; + } + + const includeContext = { + ...context, + template: { + name: templateName, + path: templatePath, + }, + }; + + await renderElement(el, includeContext); + + return true; +} + +/** + * process the unwrap directive, leavving the attribute only if it resolves to true + * + * @param {Element} el the element to process + * @param {Object} context the rendering context + * @returns {Promise} + */ +export async function resolveUnwrap(el, context) { + if (!el.hasAttribute('data-fly-unwrap')) return; + + const unwrapExpression = el.getAttribute('data-fly-unwrap'); + if (unwrapExpression) { + const unwrapVal = !!(await resolveExpression(unwrapExpression, context)); + + if (!unwrapVal) { + el.removeAttribute('data-fly-unwrap'); + } + } +} + +export function processUnwraps(el) { + el.querySelectorAll('[data-fly-unwrap]').forEach((unwrapEl) => { + unwrapEl.before(...unwrapEl.childNodes); + unwrapEl.remove(); + }); +} diff --git a/src/expressions.js b/src/expressions.js new file mode 100644 index 0000000..109edeb --- /dev/null +++ b/src/expressions.js @@ -0,0 +1,73 @@ +/** + * resolves and returns data from the rendering context + * + * @param {string} expression the name of the data + * @param {Object} context the rendering context + * @returns {Promise} the data that was resolved + */ +export async function resolveExpression(expression, context) { + let resolved = context; + let previousResolvedValue; + + const parts = expression.split('.'); + for (let i = 0; i < parts.length; i += 1) { + if (typeof resolved === 'undefined') break; + + const part = parts[i]; + previousResolvedValue = resolved; + resolved = resolved[part]; + + if (typeof resolved === 'function') { + const functionParams = [{ ...context }]; + // eslint-disable-next-line no-await-in-loop + resolved = await resolved.apply(previousResolvedValue, functionParams); + } + } + + return resolved; +} + +/** + * resolves expressions in a string + * + * @param {string} str the string that may contain expressions + * @param {Object} context the rendering context + */ +export async function resolveExpressions(str, context) { + const regexp = /(\\)?\${([a-z0-9\\.\s]+)}/dgi; + + const promises = []; + str.replaceAll(regexp, (match, escapeChar, expression) => { + if (escapeChar) { + promises.push(Promise.resolve(match.slice(1))); + } + + promises.push(resolveExpression(expression.trim(), context)); + + return match; + }); + + if (promises.length > 0) { + const promiseResults = await Promise.all(promises); + const updatedText = str.replaceAll(regexp, () => { + const result = promiseResults.shift(); + return result; + }); + + return { updated: true, updatedText }; + } + + return { updated: false, updatedText: str }; +} + +/** + * process text expressions within a text node, updating the node's textContent + * + * @param {Node} node the text node + * @param {Object} context the rendering context + */ +export async function processTextExpressions(node, context) { + const { updated, updatedText } = await resolveExpressions(node.textContent, context); + + if (updated) node.textContent = updatedText; +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..1afd0b5 --- /dev/null +++ b/src/index.js @@ -0,0 +1 @@ +export { renderElement, renderBlock } from './render.js'; diff --git a/src/render.js b/src/render.js new file mode 100644 index 0000000..10328a4 --- /dev/null +++ b/src/render.js @@ -0,0 +1,99 @@ +import resolveTemplate from './templates.js'; +import { processTextExpressions } from './expressions.js'; +// eslint-disable-next-line import/no-cycle +import { + processAttributes, + processContent, + processInclude, + processRepeat, + processTest, + processUnwraps, + resolveUnwrap, +} from './directives.js'; + +/** + * recursively renders a dom node, processing all directives + * + * @param {Node} node the node to render + * @param {Object} context the rendering context + * @returns {Promise} a promise that resolves when the node has been rendered + */ +export async function processNode(node, context) { + context.currentNode = node; + let processChildren = [Node.ELEMENT_NODE, Node.DOCUMENT_FRAGMENT_NODE].includes(node.nodeType); + if (node.nodeType === Node.ELEMENT_NODE) { + const shouldRender = await processTest(node, context); + if (!shouldRender) return; + + const repeated = await processRepeat(node, context); + if (repeated) return; + + await processAttributes(node, context); + + processChildren = (await processContent(node, context)) + || (await processInclude(node, context)) || true; + + await resolveUnwrap(node, context); + } else if (node.nodeType === Node.TEXT_NODE) { + await processTextExpressions(node, context); + } + + const children = !processChildren ? [] : [...node.childNodes]; + + for (let i = 0; i < children.length; i += 1) { + const child = children[i]; + // eslint-disable-next-line no-await-in-loop + await processNode(child, context); + } +} + +/** + * Render a template + * @param {Element} template the template to render + * @param {Object} context the rendering context + */ +export async function renderTemplate(template, context) { + const templateClone = template.cloneNode(true); + await processNode(templateClone.content, context); + + processUnwraps(templateClone.content); + + return templateClone; +} + +/** + * transform the element, replacing it's children with the content from the template + * @param {Element} el the element + * @param {Element} template the template element + * @param {Object} context the rendering context + */ +export async function renderElementWithTemplate(el, template, context) { + const rendered = await renderTemplate(template, context); + el.replaceChildren(rendered.content); +} + +/** + * Transform an element using an HTML template + * + * @param {Element} block the block element + * @param {Object} context the rendering context + */ +export async function renderElement(el, context) { + const template = await resolveTemplate(context); + + await renderElementWithTemplate(el, template, context); +} + +/** + * Transform a block using an HTML template + * + * @param {Element} block the block element + * @param {Object} context the rendering context + */ +export async function renderBlock(block, context = {}) { + context.block = block; + context.blockName = block.dataset.blockName; + context.codeBasePath = context.codeBasePath || (window.hlx ? window.hlx.codeBasePath : ''); + + await renderElement(block, context); +} diff --git a/src/templates.js b/src/templates.js new file mode 100644 index 0000000..b852135 --- /dev/null +++ b/src/templates.js @@ -0,0 +1,34 @@ +/** + * resolve the template to render + * + * @param {object} context the rendering context + * @returns {Promise} the template element + */ +export default async function resolveTemplate(context) { + context.template = context.template || {}; + context.template.path = context.template.path || `${context.codeBasePath}/blocks/${context.blockName}/${context.blockName}.html`; + + const templateId = `faintly-template-${context.template.path}#${context.template.name || ''}`.toLowerCase().replace(/[^0-9a-z]/gi, '-'); + let template = document.getElementById(templateId); + if (!template) { + const resp = await fetch(context.template.path); + if (!resp.ok) throw new Error(`Failed to fetch template from ${context.template.path} for block ${context.blockName}.`); + + const markup = await resp.text(); + + const dp = new DOMParser(); + const templateDom = dp.parseFromString(markup, 'text/html'); + + templateDom.querySelectorAll('template').forEach((t) => { + const name = t.getAttribute('data-fly-name') || ''; + t.id = `faintly-template-${context.template.path}#${name}`.toLowerCase().replace(/[^0-9a-z]/gi, '-'); + + document.body.append(t); + }); + } + + template = document.getElementById(templateId); + if (!template) throw new Error(`Failed to find template with id ${templateId}.`); + + return template; +} diff --git a/test/directives/attributes/processAttributes.test.js b/test/directives/attributes/processAttributes.test.js index 56a5130..68cab34 100644 --- a/test/directives/attributes/processAttributes.test.js +++ b/test/directives/attributes/processAttributes.test.js @@ -2,9 +2,7 @@ /* eslint-disable no-unused-expressions */ import { expect } from '@esm-bundle/chai'; -import { exportForTesting } from '../../../src/faintly.js'; - -const { processAttributes } = exportForTesting; +import { processAttributes } from '../../../src/directives.js'; describe('processAttributes', () => { it('resolves expressions in non data-fly-* attributes', async () => { diff --git a/test/directives/content/processContent.test.js b/test/directives/content/processContent.test.js index 8b49ee3..558cea6 100644 --- a/test/directives/content/processContent.test.js +++ b/test/directives/content/processContent.test.js @@ -2,11 +2,9 @@ /* eslint-disable no-unused-expressions */ import { expect } from '@esm-bundle/chai'; -import { exportForTesting } from '../../../src/faintly.js'; +import { processContent } from '../../../src/directives.js'; import { compareDom, compareDomInline } from '../../test-utils.js'; -const { processContent } = exportForTesting; - describe('processContent', () => { it('returns false when children the directive is absent', async () => { const el = document.createElement('div'); diff --git a/test/directives/include/processInclude.test.js b/test/directives/include/processInclude.test.js index 1a8931e..98fea1c 100644 --- a/test/directives/include/processInclude.test.js +++ b/test/directives/include/processInclude.test.js @@ -1,11 +1,9 @@ /* eslint-env mocha */ /* eslint-disable no-unused-expressions */ import { expect } from '@esm-bundle/chai'; -import { exportForTesting } from '../../../src/faintly.js'; +import { processInclude } from '../../../src/directives.js'; import { compareDom } from '../../test-utils.js'; -const { processInclude } = exportForTesting; - describe('processInclude', () => { it('returns false when the directive is absent', async () => { const el = document.createElement('div'); diff --git a/test/directives/repeat/processRepeat.test.js b/test/directives/repeat/processRepeat.test.js index 52c777b..cd74eab 100644 --- a/test/directives/repeat/processRepeat.test.js +++ b/test/directives/repeat/processRepeat.test.js @@ -2,9 +2,7 @@ /* eslint-env mocha */ /* eslint-disable no-unused-expressions */ import { expect } from '@esm-bundle/chai'; -import { exportForTesting } from '../../../src/faintly.js'; - -const { processRepeat } = exportForTesting; +import { processRepeat } from '../../../src/directives.js'; describe('processRepeat', () => { it('returns false when repeat the directive is absent', async () => { diff --git a/test/directives/test/processTest.test.js b/test/directives/test/processTest.test.js index 8a79417..73b726a 100644 --- a/test/directives/test/processTest.test.js +++ b/test/directives/test/processTest.test.js @@ -2,9 +2,7 @@ /* eslint-disable no-unused-expressions */ import { expect } from '@esm-bundle/chai'; -import { exportForTesting } from '../../../src/faintly.js'; - -const { processTest } = exportForTesting; +import { processTest } from '../../../src/directives.js'; describe('processTest', () => { describe('data-fly-test', () => { diff --git a/test/directives/unwrap/processUnwrap.test.js b/test/directives/unwrap/processUnwrap.test.js index c151cc8..98a87ec 100644 --- a/test/directives/unwrap/processUnwrap.test.js +++ b/test/directives/unwrap/processUnwrap.test.js @@ -2,11 +2,9 @@ /* eslint-disable no-unused-expressions */ import { expect } from '@esm-bundle/chai'; -import { exportForTesting } from '../../../src/faintly.js'; +import { processUnwraps, resolveUnwrap } from '../../../src/directives.js'; import { compareDomInline } from '../../test-utils.js'; -const { processUnwraps, resolveUnwrap } = exportForTesting; - describe('resolveUnwrap', () => { it('unwraps all the unwrap elements', async () => { const div = document.createElement('div'); diff --git a/test/expressions/processTextExpressions.test.js b/test/expressions/processTextExpressions.test.js index b16c683..ce80019 100644 --- a/test/expressions/processTextExpressions.test.js +++ b/test/expressions/processTextExpressions.test.js @@ -2,9 +2,7 @@ /* eslint-disable no-unused-expressions */ import { expect } from '@esm-bundle/chai'; -import { exportForTesting } from '../../src/faintly.js'; - -const { processTextExpressions } = exportForTesting; +import { processTextExpressions } from '../../src/expressions.js'; describe('processTextExpressions', () => { it('updates text node content', async () => { diff --git a/test/expressions/resolveExpression.test.js b/test/expressions/resolveExpression.test.js index f78c490..133a374 100644 --- a/test/expressions/resolveExpression.test.js +++ b/test/expressions/resolveExpression.test.js @@ -2,9 +2,7 @@ /* eslint-disable no-unused-expressions */ import { expect } from '@esm-bundle/chai'; -import { exportForTesting } from '../../src/faintly.js'; - -const { resolveExpression } = exportForTesting; +import { resolveExpression } from '../../src/expressions.js'; describe('resolveExpression', () => { it('resolves object expressions from the rendering context', async () => { diff --git a/test/expressions/resolveExpressions.test.js b/test/expressions/resolveExpressions.test.js index 6aefcd5..2ff0816 100644 --- a/test/expressions/resolveExpressions.test.js +++ b/test/expressions/resolveExpressions.test.js @@ -3,9 +3,7 @@ /* eslint-disable no-unused-expressions */ import { expect } from '@esm-bundle/chai'; -import { exportForTesting } from '../../src/faintly.js'; - -const { resolveExpressions } = exportForTesting; +import { resolveExpressions } from '../../src/expressions.js'; describe('resolveExpressions', () => { it('resolves multiple expressions in a string', async () => { diff --git a/test/fixtures/blocks/accordion/accordion.js b/test/fixtures/blocks/accordion/accordion.js index fde7b0e..5af433c 100644 --- a/test/fixtures/blocks/accordion/accordion.js +++ b/test/fixtures/blocks/accordion/accordion.js @@ -1,4 +1,4 @@ -import { renderBlock } from '../../../../src/faintly.js'; +import { renderBlock } from '../../../../src/render.js'; export default async function decorate(block) { await renderBlock(block); diff --git a/test/fixtures/blocks/article-feed/article-feed.js b/test/fixtures/blocks/article-feed/article-feed.js index ba47b70..fc7abb9 100644 --- a/test/fixtures/blocks/article-feed/article-feed.js +++ b/test/fixtures/blocks/article-feed/article-feed.js @@ -1,4 +1,4 @@ -import { renderBlock } from '../../../../src/faintly.js'; +import { renderBlock } from '../../../../src/render.js'; async function fetchArticles() { const response = await fetch(`${window.hlx.codeBasePath}/blocks/article-feed/articles.json`); diff --git a/test/fixtures/blocks/cards/cards.js b/test/fixtures/blocks/cards/cards.js index 1f92ce2..180cb90 100644 --- a/test/fixtures/blocks/cards/cards.js +++ b/test/fixtures/blocks/cards/cards.js @@ -1,5 +1,5 @@ import { createOptimizedPicture } from '../../scripts/aem.js'; -import { renderBlock } from '../../../../src/faintly.js'; +import { renderBlock } from '../../../../src/render.js'; function transformCardColumn(context) { const col = context.card; diff --git a/test/templates/resolveTemplate.test.js b/test/templates/resolveTemplate.test.js index 8155e7a..941c36f 100644 --- a/test/templates/resolveTemplate.test.js +++ b/test/templates/resolveTemplate.test.js @@ -2,11 +2,9 @@ /* eslint-disable no-unused-expressions */ import { expect } from '@esm-bundle/chai'; -import { exportForTesting } from '../../src/faintly.js'; +import resolveTemplate from '../../src/templates.js'; import { compareDom } from '../test-utils.js'; -const { resolveTemplate } = exportForTesting; - describe('resolveTemplates', () => { it('loads the default template for a block', async () => { const template = await resolveTemplate({