From dbcb25cc3a7c1e7643a636e21e9a4aeb95f59ba4 Mon Sep 17 00:00:00 2001 From: Julien Ramboz Date: Thu, 26 Oct 2023 10:11:12 +0200 Subject: [PATCH 01/12] feat: add minimal plugin system --- src/block-loader.js | 41 +++++++----- src/dom-utils.js | 47 +++++++++++++ src/plugins-registry.js | 87 +++++++++++++++++++++++++ src/setup.js | 5 ++ src/templates-registry.js | 37 +++++++++++ test/dom-utils/getAllMetadata.test.html | 51 +++++++++++++++ 6 files changed, 250 insertions(+), 18 deletions(-) create mode 100644 src/plugins-registry.js create mode 100644 src/templates-registry.js create mode 100644 test/dom-utils/getAllMetadata.test.html diff --git a/src/block-loader.js b/src/block-loader.js index 2c23788..88d779b 100644 --- a/src/block-loader.js +++ b/src/block-loader.js @@ -10,7 +10,7 @@ * governing permissions and limitations under the License. */ -import { loadCSS } from './dom-utils.js'; +import { loadModule } from './dom-utils.js'; /** * Updates all section status in a container element. @@ -65,6 +65,26 @@ export function buildBlock(blockName, content) { return (blockEl); } +/** + * Gets the configuration for the given block, and also passes + * the config through all custom patching helpers added to the project. + * + * @param {Element} block The block element + * @returns {Object} The block config (blockName, cssPath and jsPath) + */ +function getBlockConfig(block) { + const { blockName } = block.dataset; + const cssPath = `${window.hlx.codeBasePath}/blocks/${blockName}/${blockName}.css`; + const jsPath = `${window.hlx.codeBasePath}/blocks/${blockName}/${blockName}.js`; + const original = { blockName, cssPath, jsPath }; + return (window.hlx.patchBlockConfig || []) + .filter((fn) => typeof fn === 'function') + .reduce( + (config, fn) => fn(config, original), + { blockName, cssPath, jsPath }, + ); +} + /** * Loads JS and CSS for a block. * @param {Element} block The block element @@ -73,24 +93,9 @@ export async function loadBlock(block) { const status = block.dataset.blockStatus; if (status !== 'loading' && status !== 'loaded') { block.dataset.blockStatus = 'loading'; - const { blockName } = block.dataset; + const { blockName, cssPath, jsPath } = getBlockConfig(block); try { - const cssLoaded = loadCSS(`${window.hlx.codeBasePath}/blocks/${blockName}/${blockName}.css`); - const decorationComplete = new Promise((resolve) => { - (async () => { - try { - const mod = await import(`${window.hlx.codeBasePath}/blocks/${blockName}/${blockName}.js`); - if (mod.default) { - await mod.default(block); - } - } catch (error) { - // eslint-disable-next-line no-console - console.log(`failed to load module for ${blockName}`, error); - } - resolve(); - })(); - }); - await Promise.all([cssLoaded, decorationComplete]); + await loadModule(blockName, jsPath, cssPath, block); } catch (error) { // eslint-disable-next-line no-console console.log(`failed to load block ${blockName}`, error); diff --git a/src/dom-utils.js b/src/dom-utils.js index 6b5e8d6..fc345c6 100644 --- a/src/dom-utils.js +++ b/src/dom-utils.js @@ -56,6 +56,36 @@ export async function loadScript(src, attrs) { }); } +/** + * Loads JS and CSS for a module and executes it's default export. + * @param {string} name The module name + * @param {string} jsPath The JS file to load + * @param {string} [cssPath] An optional CSS file to load + * @param {object[]} [args] Parameters to be passed to the default export when it is called + */ +export async function loadModule(name, jsPath, cssPath, ...args) { + const cssLoaded = cssPath ? loadCSS(cssPath) : Promise.resolve(); + const decorationComplete = jsPath + ? new Promise((resolve) => { + (async () => { + let mod; + try { + mod = await import(jsPath); + if (mod.default) { + await mod.default.apply(null, args); + } + } catch (error) { + // eslint-disable-next-line no-console + console.log(`failed to load module for ${name}`, error); + } + resolve(mod); + })(); + }) + : Promise.resolve(); + return Promise.all([cssLoaded, decorationComplete]) + .then(([, api]) => api); +} + /** * Retrieves the content of metadata tags. * @param {string} name The metadata name (or property) @@ -68,6 +98,23 @@ export function getMetadata(name, doc = document) { return meta || ''; } +/** + * Gets all the metadata elements that are in the given scope. + * @param {String} scope The scope/prefix for the metadata + * @param {Document} doc Document object to query for metadata. Defaults to the window's document + * @returns an array of HTMLElement nodes that match the given scope + */ +export function getAllMetadata(scope, doc = document) { + return [...doc.head.querySelectorAll(`meta[property^="${scope}:"],meta[name^="${scope}-"]`)] + .reduce((res, meta) => { + const id = toClassName(meta.name + ? meta.name.substring(scope.length + 1) + : meta.getAttribute('property').split(':')[1]); + res[id] = meta.getAttribute('content'); + return res; + }, {}); +} + /** * Returns a picture element with webp and fallbacks * @param {string} src The image URL diff --git a/src/plugins-registry.js b/src/plugins-registry.js new file mode 100644 index 0000000..9030d9b --- /dev/null +++ b/src/plugins-registry.js @@ -0,0 +1,87 @@ +/* + * Copyright 2023 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import { loadModule } from './dom-utils.js'; + +export default class PluginsRegistry { + #plugins; + + static parsePluginParams(id, config) { + const pluginId = !config + ? id.split('/').splice(id.endsWith('/') ? -2 : -1, 1)[0].replace(/\.js/, '') + : id; + const pluginConfig = typeof config === 'string' || !config + ? { url: (config || id).replace(/\/$/, '') } + : { load: 'eager', ...config }; + pluginConfig.options ||= {}; + return { id: pluginId, config: pluginConfig }; + } + + constructor() { + this.#plugins = new Map(); + } + + // Register a new plugin + add(id, config) { + const { id: pluginId, config: pluginConfig } = PluginsRegistry.parsePluginParams(id, config); + this.#plugins.set(pluginId, pluginConfig); + } + + // Get the plugin + get(id) { return this.#plugins.get(id); } + + // Check if the plugin exists + includes(id) { return !!this.#plugins.has(id); } + + // Load all plugins that are referenced by URL, and updated their configuration with the + // actual API they expose + async load(phase, context) { + [...this.#plugins.entries()] + .filter(([, plugin]) => plugin.condition + && !plugin.condition(document, plugin.options, context)) + .map(([id]) => this.#plugins.delete(id)); + return Promise.all([...this.#plugins.entries()] + // Filter plugins that don't match the execution conditions + .filter(([, plugin]) => ( + (!plugin.condition || plugin.condition(document, plugin.options, context)) + && phase === plugin.load && plugin.url + )) + .map(async ([key, plugin]) => { + try { + // If the plugin has a default export, it will be executed immediately + const pluginApi = (await loadModule( + key, + !plugin.url.endsWith('.js') ? `${plugin.url}/${key}.js` : plugin.url, + !plugin.url.endsWith('.js') ? `${plugin.url}/${key}.css` : null, + document, + plugin.options, + context, + )) || {}; + this.#plugins.set(key, { ...plugin, ...pluginApi }); + } catch (err) { + // eslint-disable-next-line no-console + console.error('Could not load specified plugin', key); + } + })); + } + + // Run a specific phase in the plugin + async run(phase, context) { + return [...this.#plugins.values()] + .reduce((promise, plugin) => ( // Using reduce to execute plugins sequencially + plugin[phase] && (!plugin.condition + || plugin.condition(document, plugin.options, context)) + ? promise.then(() => plugin[phase](document, plugin.options, context)) + : promise + ), Promise.resolve()); + } +} diff --git a/src/setup.js b/src/setup.js index 4698152..75ece81 100644 --- a/src/setup.js +++ b/src/setup.js @@ -11,6 +11,8 @@ */ import { sampleRUM } from '@adobe/helix-rum-js'; +import PluginsRegistry from './plugins-registry.js'; +import TemplatesRegistry from './templates-registry.js'; /** * Setup block utils. @@ -20,6 +22,9 @@ export function setup() { window.hlx.RUM_MASK_URL = 'full'; window.hlx.codeBasePath = ''; window.hlx.lighthouse = new URLSearchParams(window.location.search).get('lighthouse') === 'on'; + window.hlx.patchBlockConfig = []; + window.hlx.plugins = new PluginsRegistry(); + window.hlx.templates = new TemplatesRegistry(); const scriptEl = document.querySelector('script[src$="/scripts/scripts.js"]'); if (scriptEl) { diff --git a/src/templates-registry.js b/src/templates-registry.js new file mode 100644 index 0000000..b19b856 --- /dev/null +++ b/src/templates-registry.js @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import { getMetadata } from './dom-utils.js'; +import PluginsRegistry from './plugins-registry.js'; +import { toClassName } from './utils.js'; + +export default class TemplatesRegistry { + // Register a new template + // eslint-disable-next-line class-methods-use-this + add(id, url) { + if (Array.isArray(id)) { + id.forEach((i) => this.add(i)); + return; + } + const { id: templateId, config: templateConfig } = PluginsRegistry.parsePluginParams(id, url); + templateConfig.condition = () => toClassName(getMetadata('template')) === templateId; + window.hlx.plugins.add(templateId, templateConfig); + } + + // Get the template + // eslint-disable-next-line class-methods-use-this + get(id) { return window.hlx.plugins.get(id); } + + // Check if the template exists + // eslint-disable-next-line class-methods-use-this + includes(id) { return window.hlx.plugins.includes(id); } +} diff --git a/test/dom-utils/getAllMetadata.test.html b/test/dom-utils/getAllMetadata.test.html new file mode 100644 index 0000000..033b2d2 --- /dev/null +++ b/test/dom-utils/getAllMetadata.test.html @@ -0,0 +1,51 @@ + + + + + + + + + + From f5742ee9024efdde8baa8222b174396987493306 Mon Sep 17 00:00:00 2001 From: Julien Ramboz Date: Fri, 27 Oct 2023 12:05:07 +0200 Subject: [PATCH 02/12] fix: default loading phase for plugins --- src/plugins-registry.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/plugins-registry.js b/src/plugins-registry.js index 9030d9b..58a1e0a 100644 --- a/src/plugins-registry.js +++ b/src/plugins-registry.js @@ -19,9 +19,12 @@ export default class PluginsRegistry { const pluginId = !config ? id.split('/').splice(id.endsWith('/') ? -2 : -1, 1)[0].replace(/\.js/, '') : id; - const pluginConfig = typeof config === 'string' || !config - ? { url: (config || id).replace(/\/$/, '') } - : { load: 'eager', ...config }; + const pluginConfig = { + load: 'eager', + ...(typeof config === 'string' || !config + ? { url: (config || id).replace(/\/$/, '') } + : config), + }; pluginConfig.options ||= {}; return { id: pluginId, config: pluginConfig }; } From d2ee4c35ed0784231c3af71971f89779245ad78f Mon Sep 17 00:00:00 2001 From: Julien Ramboz Date: Tue, 31 Oct 2023 14:37:31 +0100 Subject: [PATCH 03/12] test: add tests --- src/block-loader.js | 2 +- src/dom-utils.js | 8 +- src/plugins-registry.js | 6 +- .../loadBlock.customconfig.test.html | 50 +++ test/dom-utils/loadModule.test.js | 37 +++ test/fixtures/plugins/complex/complex.css | 3 + test/fixtures/plugins/complex/complex.js | 15 + test/fixtures/plugins/full-api.js | 15 + test/fixtures/plugins/partial-api.js | 7 + .../plugins-registry.test.html | 284 ++++++++++++++++++ test/setup/setup.test.html | 3 + .../templates-registry.test.html | 55 ++++ 12 files changed, 476 insertions(+), 9 deletions(-) create mode 100644 test/block-loader/loadBlock.customconfig.test.html create mode 100644 test/dom-utils/loadModule.test.js create mode 100644 test/fixtures/plugins/complex/complex.css create mode 100644 test/fixtures/plugins/complex/complex.js create mode 100644 test/fixtures/plugins/full-api.js create mode 100644 test/fixtures/plugins/partial-api.js create mode 100644 test/plugins-registry/plugins-registry.test.html create mode 100644 test/templates-registry/templates-registry.test.html diff --git a/src/block-loader.js b/src/block-loader.js index 88d779b..539244f 100644 --- a/src/block-loader.js +++ b/src/block-loader.js @@ -95,7 +95,7 @@ export async function loadBlock(block) { block.dataset.blockStatus = 'loading'; const { blockName, cssPath, jsPath } = getBlockConfig(block); try { - await loadModule(blockName, jsPath, cssPath, block); + await loadModule(jsPath, cssPath, block); } catch (error) { // eslint-disable-next-line no-console console.log(`failed to load block ${blockName}`, error); diff --git a/src/dom-utils.js b/src/dom-utils.js index fc345c6..2000831 100644 --- a/src/dom-utils.js +++ b/src/dom-utils.js @@ -58,15 +58,14 @@ export async function loadScript(src, attrs) { /** * Loads JS and CSS for a module and executes it's default export. - * @param {string} name The module name * @param {string} jsPath The JS file to load * @param {string} [cssPath] An optional CSS file to load * @param {object[]} [args] Parameters to be passed to the default export when it is called */ -export async function loadModule(name, jsPath, cssPath, ...args) { +export async function loadModule(jsPath, cssPath, ...args) { const cssLoaded = cssPath ? loadCSS(cssPath) : Promise.resolve(); const decorationComplete = jsPath - ? new Promise((resolve) => { + ? new Promise((resolve, reject) => { (async () => { let mod; try { @@ -75,8 +74,7 @@ export async function loadModule(name, jsPath, cssPath, ...args) { await mod.default.apply(null, args); } } catch (error) { - // eslint-disable-next-line no-console - console.log(`failed to load module for ${name}`, error); + reject(error); } resolve(mod); })(); diff --git a/src/plugins-registry.js b/src/plugins-registry.js index 58a1e0a..c04dbf6 100644 --- a/src/plugins-registry.js +++ b/src/plugins-registry.js @@ -62,9 +62,8 @@ export default class PluginsRegistry { try { // If the plugin has a default export, it will be executed immediately const pluginApi = (await loadModule( - key, - !plugin.url.endsWith('.js') ? `${plugin.url}/${key}.js` : plugin.url, - !plugin.url.endsWith('.js') ? `${plugin.url}/${key}.css` : null, + !plugin.url.endsWith('.js') ? `${plugin.url}/${plugin.url.split('/').pop()}.js` : plugin.url, + !plugin.url.endsWith('.js') ? `${plugin.url}/${plugin.url.split('/').pop()}.css` : null, document, plugin.options, context, @@ -73,6 +72,7 @@ export default class PluginsRegistry { } catch (err) { // eslint-disable-next-line no-console console.error('Could not load specified plugin', key); + this.#plugins.delete(key); } })); } diff --git a/test/block-loader/loadBlock.customconfig.test.html b/test/block-loader/loadBlock.customconfig.test.html new file mode 100644 index 0000000..b82e885 --- /dev/null +++ b/test/block-loader/loadBlock.customconfig.test.html @@ -0,0 +1,50 @@ + + + +
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/test/dom-utils/loadModule.test.js b/test/dom-utils/loadModule.test.js new file mode 100644 index 0000000..ab00106 --- /dev/null +++ b/test/dom-utils/loadModule.test.js @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +/* eslint-env mocha */ +/* eslint-disable no-unused-expressions */ + +import { expect } from '@esm-bundle/chai'; +import { loadModule } from '../../src/dom-utils.js'; + +describe('loadModule', () => { + it('loads the JS, returns the api and executes the default export', async () => { + const options = {}; + const api = await loadModule('/test/fixtures/plugins/full-api.js', null, document, options); + expect(options.init).to.be.true; + expect(api.loadEager).to.not.be.undefined; + expect(api.loadLazy).to.not.be.undefined; + expect(api.loadDelayed).to.not.be.undefined; + }); + + it('loads and applies the CSS file', async () => { + await loadModule('/test/fixtures/plugins/complex/complex.js', '/test/fixtures/plugins/complex/complex.css', document, {}); + expect(getComputedStyle(document.body).fontFamily).to.equal('system-ui'); + }); + + it('throws if the file cannot be loaded', async () => { + expect(async () => { await loadModule('/invalid.js'); }).to.throw; + }); +}); diff --git a/test/fixtures/plugins/complex/complex.css b/test/fixtures/plugins/complex/complex.css new file mode 100644 index 0000000..2b6a92a --- /dev/null +++ b/test/fixtures/plugins/complex/complex.css @@ -0,0 +1,3 @@ +body { + font-family: system-ui; +} \ No newline at end of file diff --git a/test/fixtures/plugins/complex/complex.js b/test/fixtures/plugins/complex/complex.js new file mode 100644 index 0000000..f2e2b0f --- /dev/null +++ b/test/fixtures/plugins/complex/complex.js @@ -0,0 +1,15 @@ +export default (_, options) => { + options.init = true; +}; + +export const loadEager = (_, options) => { + options.eager = true; +}; + +export const loadLazy = (_, options) => { + options.lazy = true; +}; + +export const loadDelayed = (_, options) => { + options.delayed = true; +}; diff --git a/test/fixtures/plugins/full-api.js b/test/fixtures/plugins/full-api.js new file mode 100644 index 0000000..4ee41ed --- /dev/null +++ b/test/fixtures/plugins/full-api.js @@ -0,0 +1,15 @@ +export default function init(_, options) { + options.init = true; +} + +export function loadEager(_, options) { + options.eager = true; +} + +export function loadLazy(_, options) { + options.lazy = true; +} + +export function loadDelayed(_, options) { + options.delayed = true; +} diff --git a/test/fixtures/plugins/partial-api.js b/test/fixtures/plugins/partial-api.js new file mode 100644 index 0000000..746ac9b --- /dev/null +++ b/test/fixtures/plugins/partial-api.js @@ -0,0 +1,7 @@ +export default (_, options) => { + options.init = true; +}; + +export const loadEager = (_, options) => { + options.eager = true; +}; diff --git a/test/plugins-registry/plugins-registry.test.html b/test/plugins-registry/plugins-registry.test.html new file mode 100644 index 0000000..9235441 --- /dev/null +++ b/test/plugins-registry/plugins-registry.test.html @@ -0,0 +1,284 @@ + + + + + + + + \ No newline at end of file diff --git a/test/setup/setup.test.html b/test/setup/setup.test.html index 586a720..c010fb9 100644 --- a/test/setup/setup.test.html +++ b/test/setup/setup.test.html @@ -32,6 +32,9 @@ expect(window.hlx).to.not.be.null; expect(window.hlx.codeBasePath).to.be.equal('/some/path'); expect(window.hlx.lighthouse).to.be.false; + expect(window.hlx.patchBlockConfig).to.be.eql([]); + expect(window.hlx.plugins).to.exist; + expect(window.hlx.templates).to.exist; }); }); diff --git a/test/templates-registry/templates-registry.test.html b/test/templates-registry/templates-registry.test.html new file mode 100644 index 0000000..65350e2 --- /dev/null +++ b/test/templates-registry/templates-registry.test.html @@ -0,0 +1,55 @@ + + + + + + + + + \ No newline at end of file From e330d1059472913fd83c2b113ed245809cc1bf0a Mon Sep 17 00:00:00 2001 From: Julien Ramboz Date: Tue, 31 Oct 2023 14:38:58 +0100 Subject: [PATCH 04/12] test: add tests --- test/dom-utils/loadModule.test.js | 6 +- .../plugins-registry.test.html | 100 +++--------------- 2 files changed, 15 insertions(+), 91 deletions(-) diff --git a/test/dom-utils/loadModule.test.js b/test/dom-utils/loadModule.test.js index ab00106..aa01b08 100644 --- a/test/dom-utils/loadModule.test.js +++ b/test/dom-utils/loadModule.test.js @@ -21,9 +21,9 @@ describe('loadModule', () => { const options = {}; const api = await loadModule('/test/fixtures/plugins/full-api.js', null, document, options); expect(options.init).to.be.true; - expect(api.loadEager).to.not.be.undefined; - expect(api.loadLazy).to.not.be.undefined; - expect(api.loadDelayed).to.not.be.undefined; + expect(api.loadEager).to.exist; + expect(api.loadLazy).to.exist; + expect(api.loadDelayed).to.exist; }); it('loads and applies the CSS file', async () => { diff --git a/test/plugins-registry/plugins-registry.test.html b/test/plugins-registry/plugins-registry.test.html index 9235441..c6b2792 100644 --- a/test/plugins-registry/plugins-registry.test.html +++ b/test/plugins-registry/plugins-registry.test.html @@ -24,7 +24,7 @@ options: { foo: 'bar' }, }); const plugin = window.hlx.plugins.get('inline'); - expect(plugin).to.not.be.undefined; + expect(plugin).to.exist; expect(plugin.options).to.eql({ foo: 'bar' }); expect(window.hlx.plugins.includes('inline')).to.true; }); @@ -72,14 +72,14 @@ it('registration and retrieval', async () => { window.hlx.plugins.add('/test/fixtures/plugins/partial-api.js'); const plugin = window.hlx.plugins.get('partial-api'); - expect(plugin).to.not.be.undefined; + expect(plugin).to.exist; expect(window.hlx.plugins.includes('partial-api')).to.true; }); it('run the eager phase', async () => { await window.hlx.plugins.load('eager'); const plugin = window.hlx.plugins.get('partial-api'); - expect(plugin.loadEager).to.not.be.undefined; + expect(plugin.loadEager).to.exist; expect(plugin.loadLazy).to.be.undefined; expect(plugin.loadDelayed).to.be.undefined; sinon.spy(plugin, 'loadEager'); @@ -107,7 +107,7 @@ url: '/test/fixtures/plugins/full-api.js', }); let plugin = window.hlx.plugins.get('fullapi'); - expect(plugin).to.not.be.undefined; + expect(plugin).to.exist; expect(window.hlx.plugins.includes('fullapi')).to.true; await window.hlx.plugins.load('eager'); plugin = window.hlx.plugins.get('fullapi'); @@ -121,9 +121,9 @@ it('run the eager phase', async () => { await window.hlx.plugins.load('eager'); const plugin = window.hlx.plugins.get('fullapi'); - expect(plugin.loadEager).to.not.be.undefined; - expect(plugin.loadLazy).to.not.be.undefined; - expect(plugin.loadDelayed).to.not.be.undefined; + expect(plugin.loadEager).to.exist; + expect(plugin.loadLazy).to.exist; + expect(plugin.loadDelayed).to.exist; sinon.spy(plugin, 'loadEager'); await window.hlx.plugins.run('loadEager'); sinon.assert.calledOnce(plugin.loadEager); @@ -155,7 +155,7 @@ url: '/test/fixtures/plugins/complex', }); const plugin = window.hlx.plugins.get('complex'); - expect(plugin).to.not.be.undefined; + expect(plugin).to.exist; expect(window.hlx.plugins.includes('complex')).to.true; }); @@ -170,9 +170,9 @@ it('run the lazy phase', async () => { await window.hlx.plugins.load('lazy'); const plugin = window.hlx.plugins.get('complex'); - expect(plugin.loadEager).to.not.be.undefined; - expect(plugin.loadLazy).to.not.be.undefined; - expect(plugin.loadDelayed).to.not.be.undefined; + expect(plugin.loadEager).to.exist; + expect(plugin.loadLazy).to.exist; + expect(plugin.loadDelayed).to.exist; sinon.spy(plugin, 'loadEager'); sinon.spy(plugin, 'loadLazy'); await window.hlx.plugins.run('loadLazy'); @@ -196,88 +196,12 @@ it('removes invalid plugins if loading fails', async () => { window.hlx.plugins.add('/invalid.js'); let plugin = window.hlx.plugins.get('invalid'); - expect(plugin).to.not.be.undefined; + expect(plugin).to.exist; await window.hlx.plugins.load('eager'); plugin = window.hlx.plugins.get('invalid'); expect(plugin).to.be.undefined; }); }); - // it('add a simple plugin', () => { - // window.hlx.plugins.add('/test/fixtures/plugins/plugin1.js'); - // expect(window.hlx.plugins.get('plugin1')).to.not.be.null; - // expect(window.hlx.plugins.get('plugin1')).to.have.property('url', '/test/fixtures/plugins/plugin1.js'); - // expect(window.hlx.plugins.get('plugin1')).to.have.property('load', 'eager'); - // }); - - // it('add a named plugin', async () => { - // window.hlx.plugins.add('plugin-2', { - // url: '/test/fixtures/plugins/plugin2', - // load: 'lazy', - // }); - // expect(window.hlx.plugins.get('plugin-2')).to.not.be.null; - // expect(window.hlx.plugins.get('plugin-2')).to.have.property('url', '/test/fixtures/plugins/plugin2'); - // expect(window.hlx.plugins.get('plugin-2')).to.have.property('load', 'lazy'); - // }); - - // it('add a complex plugin', async () => { - // window.hlx.plugins.add('plugin-3', { - // url: '/test/fixtures/plugins/plugin3.js', - // condition: () => window.loadPlugin3, - // load: 'delayed', - // }); - // expect(window.hlx.plugins.get('plugin-3')).to.not.be.null; - // expect(window.hlx.plugins.get('plugin-3')).to.have.property('url', '/test/fixtures/plugins/plugin3.js'); - // expect(window.hlx.plugins.get('plugin-3')).to.have.property('load', 'delayed'); - // }); - - // it('loads plugins according to their load property', async () => { - // window.loadPlugin3 = true; - // await window.hlx.plugins.load('eager'); - // expect(window.hlx.plugins.get('plugin1').options.init).to.be.true; - // expect(window.hlx.plugins.get('plugin-2').options.init).to.be.undefined; - // expect(window.hlx.plugins.get('plugin-3').options.init).to.be.undefined; - // await window.hlx.plugins.load('lazy'); - // expect(window.hlx.plugins.get('plugin1').options.init).to.be.true; - // expect(window.hlx.plugins.get('plugin-2').options.init).to.be.true; - // expect(window.hlx.plugins.get('plugin-3').options.init).to.be.undefined; - // await window.hlx.plugins.load('delayed'); - // expect(window.hlx.plugins.get('plugin1').options.init).to.be.true; - // expect(window.hlx.plugins.get('plugin-2').options.init).to.be.true; - // expect(window.hlx.plugins.get('plugin-3').options.init).to.be.true; - // }); - - // it('removes plugin if it fails loading', async () => { - // window.hlx.plugins.add('/test/fixtures/plugins/plugin0.js'); - // expect(window.hlx.plugins.get('plugin0')).to.not.be.undefined; - // await window.hlx.plugins.load('eager'); - // expect(window.hlx.plugins.get('plugin0')).to.be.undefined; - // }); - - // it('run the matching phase for a plugin', async () => { - // window.loadPlugin3 = true; - // window.hlx.plugins.add('plugin4', '/test/fixtures/plugins/plugin1.js'); - // window.hlx.plugins.add('plugin5', { url: '/test/fixtures/plugins/plugin2/plugin2.js', load: 'lazy' }); - // window.hlx.plugins.add('plugin6', { url: '/test/fixtures/plugins/plugin3.js', load: 'delayed', condition: () => window.loadPlugin3 }); - // await window.hlx.plugins.load('eager'); - // await window.hlx.plugins.run('loadEager'); - // expect(window.hlx.plugins.get('plugin4').options.eager).to.be.true; - // expect(window.hlx.plugins.get('plugin5').options.eager).to.be.undefined; - // expect(window.hlx.plugins.get('plugin6').options.eager).to.be.undefined; - // await window.hlx.plugins.load('lazy'); - // await window.hlx.plugins.run('loadLazy'); - // expect(window.hlx.plugins.get('plugin4').options.lazy).to.be.undefined; // plugin1.js does not have a loadLazy export - // expect(window.hlx.plugins.get('plugin5').options.lazy).to.be.true; - // expect(window.hlx.plugins.get('plugin6').options.lazy).to.be.undefined; // plugin3.js is not loaded yet - // await window.hlx.plugins.load('delayed'); - // window.hlx.plugins.get('plugin6').condition = () => false; - // await window.hlx.plugins.run('loadDelayed'); - // expect(window.hlx.plugins.get('plugin4').options.delayed).to.be.undefined; // plugin1.js does not have a loadDelayed export - // expect(window.hlx.plugins.get('plugin5').options.delayed).to.be.true; - // expect(window.hlx.plugins.get('plugin6').options.delayed).to.be.undefined; // plugin3.js condition is not met - // window.hlx.plugins.get('plugin6').condition = () => true; - // await window.hlx.plugins.run('loadDelayed'); - // expect(window.hlx.plugins.get('plugin6').options.delayed).to.be.true; - // }); }); From 90e630be347227aa01a67c1b08ac237081100c3d Mon Sep 17 00:00:00 2001 From: Julien Ramboz Date: Tue, 31 Oct 2023 14:41:44 +0100 Subject: [PATCH 05/12] test: add tests --- test/plugins-registry/plugins-registry.test.html | 1 + 1 file changed, 1 insertion(+) diff --git a/test/plugins-registry/plugins-registry.test.html b/test/plugins-registry/plugins-registry.test.html index c6b2792..80a6237 100644 --- a/test/plugins-registry/plugins-registry.test.html +++ b/test/plugins-registry/plugins-registry.test.html @@ -178,6 +178,7 @@ await window.hlx.plugins.run('loadLazy'); sinon.assert.notCalled(plugin.loadEager); sinon.assert.calledOnce(plugin.loadLazy); + expect(getComputedStyle(document.body).fontFamily).to.equal('system-ui'); }); it('run the delayed phase', async () => { From 5e7192a3ec884301f0ef3bcd93eabe13282a6ba0 Mon Sep 17 00:00:00 2001 From: Julien Ramboz Date: Tue, 31 Oct 2023 14:57:57 +0100 Subject: [PATCH 06/12] test: add tests --- test/setup/setup.test.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/setup/setup.test.html b/test/setup/setup.test.html index c010fb9..9c869fa 100644 --- a/test/setup/setup.test.html +++ b/test/setup/setup.test.html @@ -1,7 +1,6 @@ - From deee3f453a92f034b8f43c6047367d35bc83639f Mon Sep 17 00:00:00 2001 From: Julien Ramboz Date: Tue, 31 Oct 2023 15:21:07 +0100 Subject: [PATCH 08/12] test: add tests --- test/setup/setup.test.html | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/test/setup/setup.test.html b/test/setup/setup.test.html index a8e20a6..27ed41f 100644 --- a/test/setup/setup.test.html +++ b/test/setup/setup.test.html @@ -1,6 +1,7 @@ + From 80da353ad6384f109c9d5022cdc5f55d2d2e0ae5 Mon Sep 17 00:00:00 2001 From: Julien Ramboz Date: Tue, 31 Oct 2023 15:28:39 +0100 Subject: [PATCH 09/12] test: add tests --- test/plugins-registry/plugins-registry.test.html | 1 + test/templates-registry/templates-registry.test.html | 1 + 2 files changed, 2 insertions(+) diff --git a/test/plugins-registry/plugins-registry.test.html b/test/plugins-registry/plugins-registry.test.html index 80a6237..56dc233 100644 --- a/test/plugins-registry/plugins-registry.test.html +++ b/test/plugins-registry/plugins-registry.test.html @@ -1,6 +1,7 @@ + From 3511e6bc9d473c4b36b984abf9fd9518690ffe22 Mon Sep 17 00:00:00 2001 From: Julien Ramboz Date: Tue, 31 Oct 2023 15:35:21 +0100 Subject: [PATCH 10/12] test: add tests --- src/setup.js | 3 ++- test/plugins-registry/plugins-registry.test.html | 1 - test/setup/setup.test.html | 16 ---------------- .../templates-registry.test.html | 1 - 4 files changed, 2 insertions(+), 19 deletions(-) diff --git a/src/setup.js b/src/setup.js index 75ece81..7f3b1eb 100644 --- a/src/setup.js +++ b/src/setup.js @@ -27,11 +27,12 @@ export function setup() { window.hlx.templates = new TemplatesRegistry(); const scriptEl = document.querySelector('script[src$="/scripts/scripts.js"]'); + /* c8 ignore next 1 */ if (scriptEl) { try { [window.hlx.codeBasePath] = new URL(scriptEl.src).pathname.split('/scripts/scripts.js'); } catch (error) { - /* c8 ignore next 3 */ + /* c8 ignore next 2 */ // eslint-disable-next-line no-console console.log(error); } diff --git a/test/plugins-registry/plugins-registry.test.html b/test/plugins-registry/plugins-registry.test.html index 56dc233..80a6237 100644 --- a/test/plugins-registry/plugins-registry.test.html +++ b/test/plugins-registry/plugins-registry.test.html @@ -1,7 +1,6 @@ - diff --git a/test/templates-registry/templates-registry.test.html b/test/templates-registry/templates-registry.test.html index a3213b1..65350e2 100644 --- a/test/templates-registry/templates-registry.test.html +++ b/test/templates-registry/templates-registry.test.html @@ -1,7 +1,6 @@ - From ea5bf29534814419bd9c4faa959293fcc9228bcc Mon Sep 17 00:00:00 2001 From: Julien Ramboz Date: Tue, 31 Oct 2023 15:38:23 +0100 Subject: [PATCH 11/12] chore: add linting exclusion --- rollup.config.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rollup.config.js b/rollup.config.js index 124bc4b..17a2683 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -24,7 +24,8 @@ const banner = `/* * OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ -`; + +/* eslint-disable max-classes-per-file */`; const bundles = [ { From 73e7eb85fce782fa13361a6916d7307b36d7e057 Mon Sep 17 00:00:00 2001 From: Julien Ramboz Date: Tue, 31 Oct 2023 15:40:34 +0100 Subject: [PATCH 12/12] chore: add linting exclusion --- src/plugins-registry.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plugins-registry.js b/src/plugins-registry.js index c04dbf6..c886fac 100644 --- a/src/plugins-registry.js +++ b/src/plugins-registry.js @@ -80,10 +80,10 @@ export default class PluginsRegistry { // Run a specific phase in the plugin async run(phase, context) { return [...this.#plugins.values()] - .reduce((promise, plugin) => ( // Using reduce to execute plugins sequencially - plugin[phase] && (!plugin.condition - || plugin.condition(document, plugin.options, context)) - ? promise.then(() => plugin[phase](document, plugin.options, context)) + .reduce((promise, p) => ( // Using reduce to execute plugins sequencially + p[phase] && (!p.condition + || p.condition(document, p.options, context)) + ? promise.then(() => p[phase](document, p.options, context)) : promise ), Promise.resolve()); }