From 46dea7d2644ef00d009932aad4280a9a51bd22df Mon Sep 17 00:00:00 2001 From: Miro Yovchev <2827783+myovchev@users.noreply.github.com> Date: Thu, 7 Nov 2024 10:15:51 +0200 Subject: [PATCH] HMR condition argument and widget player fix (#4794) --- CHANGELOG.md | 5 ++++ modules/@apostrophecms/template/index.js | 24 ++++++++++++------ modules/@apostrophecms/util/ui/src/util.js | 29 ++++++++++++++++------ 3 files changed, 42 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28f30207df..b08558b834 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,13 @@ ### Fixes * Extra bundle detection when using external build module works properly now. +* Widget players are now properly invoked when they arrive later in the page load process. * Fix permission grid tooltip display. +### Adds + +* It's possible now to target the HMR build when registering via `template.append` and `template.prepend`. Use `when: 'hmr:public'` or `when: 'hmr:apos'` that will be evaluated against the current asset `options.hmr` configuration. + ## 4.9.0 (2024-10-31) ### Adds diff --git a/modules/@apostrophecms/template/index.js b/modules/@apostrophecms/template/index.js index d7dc024437..2c749a1cb9 100644 --- a/modules/@apostrophecms/template/index.js +++ b/modules/@apostrophecms/template/index.js @@ -864,7 +864,7 @@ module.exports = { // apos.template.prepend({ // component: 'module-name:async-component-name', // where: 'head', - // when: 'hmr, // or e.g. ['hmr', 'dev'], logical AND + // when: 'hmr', // or e.g. ['hmr', 'dev'], logical AND // bundler: 'vite', // }); // OR @@ -909,7 +909,7 @@ module.exports = { // apos.template.append({ // component: 'module-name:async-component-name', // where: 'head', - // when: 'hmr, // or e.g. ['hmr', 'dev'], logical AND + // when: 'hmr', // or e.g. ['hmr', 'dev'], logical AND // bundler: 'vite', // }); // OR @@ -938,6 +938,8 @@ module.exports = { // - `when`: (optional) string or array of strings, the conditions to be met to insert the component. // When an array, a logical AND is applied. One match against the injected `when` data is required. // Currently supported values are `hmr`, `dev`, `prod`. See `getInjectConditionHandlers()` for more info. + // The `when` value can include an argument separated by `:`. E.g. `hmr:apos`, `hmr:public`. If the condition + // handler does not support arguments, it's ignored. // - `bundler`: (optional) string, the alias of the currently registered asset external build module. // The bundler condition is not parth of the actual inject data. It's evaluated just on the registration // data. @@ -975,7 +977,7 @@ module.exports = { conditions.when = conditions.when ? [ conditions.when ] : []; } // Both sides `when` should match - if (data.when && !conditions.when.includes(data.when)) { + if (data.when && !conditions.when.map(s => s.split(':')[0]).includes(data.when)) { return; } if (!data.when && conditions.when.length) { @@ -990,11 +992,12 @@ module.exports = { // `when` being an object same as the schema `if`, supporting // the same logical operators. But it's too much for now. const conditionMet = when.every(val => { - if (!handlers[val]) { + const [ fn, arg ] = val.split(':'); + if (!handlers[fn]) { self.apos.util.error(`Invalid inject condition: ${when}`); return false; } - return handlers[when](data); + return handlers[fn](arg, data); }); if (bundler) { @@ -1020,11 +1023,16 @@ module.exports = { // Simple conditions handling for `when` injects. It can be extended to support // custom conditions in the future - registered by modules similar to // `helpers`. - // Every condition function receives the nunjucks `data` (`when`, `where`, etc) - // object as an argument. + // Every condition function receives an argument if available and the nunjucks + // data object. For example `when: hmr:apos` will call `hmr('apos', data)`. + // The function should return a boolean. getInjectConditionHandlers() { return { - hmr() { + hmr(kind) { + if (kind) { + return self.apos.asset.hasHMR() && + self.apos.asset.getBuildOptions().devServer === kind; + } return self.apos.asset.hasHMR(); }, dev() { diff --git a/modules/@apostrophecms/util/ui/src/util.js b/modules/@apostrophecms/util/ui/src/util.js index 7dc298e765..68f7074500 100644 --- a/modules/@apostrophecms/util/ui/src/util.js +++ b/modules/@apostrophecms/util/ui/src/util.js @@ -128,7 +128,20 @@ export default () => { // THAT ONE WIDGET and NO OTHER. Don't worry about finding the // others, we will do that for you and we guarantee only one call per widget. - apos.util.widgetPlayers = {}; + const widgetPlayersConfig = { + list: {}, + initialized: false + }; + apos.util.widgetPlayers = new Proxy(widgetPlayersConfig.list, { + set(target, prop, value) { + target[prop] = value; + // run the player if we missed the initial run + if (widgetPlayersConfig.initialized) { + apos.util.runPlayers(); + } + return true; + } + }); // Run the given function whenever the DOM has new changes that // may require attention. The passed function will be @@ -183,8 +196,7 @@ export default () => { // Your player is guaranteed to run only once per widget. Hint: // DON'T try to find all the widgets. DO just enhance `el`. // This is a computer science principle known as "separation of concerns." - - apos.util.runPlayers = function(el) { + apos.util.runPlayers = function (el) { const players = apos.util.widgetPlayers; const playerList = Object.keys(players); @@ -192,16 +204,16 @@ export default () => { const playerOpts = players[playerList[i]]; const playerEls = (el || document).querySelectorAll(playerOpts.selector); - playerEls.forEach(function (el) { - if (el.aposWidgetPlayed) { + playerEls.forEach(function (playerEl) { + if (playerEl.aposWidgetPlayed) { return; } // Use an actual property, not a DOM attribute or // "data" prefix property, to avoid the problem of // elements cloned from innerHTML appearing to have // been played too - el.aposWidgetPlayed = true; - playerOpts.player(el); + playerEl.aposWidgetPlayed = true; + playerOpts.player(playerEl); }); } }; @@ -210,7 +222,8 @@ export default () => { // when the page is partially refreshed by the editor. if (!apos.bus) { - apos.util.onReadyAndRefresh(function() { + apos.util.onReady(function () { + widgetPlayersConfig.initialized = true; apos.util.runPlayers(); }); }