From 85a701623bfe538c3bab90fb7efb85d2f3cfdbad Mon Sep 17 00:00:00 2001 From: Simone Todaro Date: Wed, 11 Dec 2024 15:24:10 +0000 Subject: [PATCH] Fix x-ignore being added after using Alpine.morph (#47) * Add failing test * Do not rerun x-load when morphing elements --- dist/async-alpine.cjs.js | 36 +++++++++++++++++-------------- dist/async-alpine.esm.js | 36 +++++++++++++++++-------------- dist/async-alpine.script.js | 2 +- src/async-alpine.js | 36 +++++++++++++++++-------------- tests/alpine/morph/index.html | 37 ++++++++++++++++++++++++++++++++ tests/alpine/morph/morph.test.js | 12 +++++++++++ tests/index.html | 1 + 7 files changed, 111 insertions(+), 49 deletions(-) create mode 100644 tests/alpine/morph/index.html create mode 100644 tests/alpine/morph/morph.test.js diff --git a/dist/async-alpine.cjs.js b/dist/async-alpine.cjs.js index 84f61bc..ec6c013 100644 --- a/dist/async-alpine.cjs.js +++ b/dist/async-alpine.cjs.js @@ -241,24 +241,28 @@ function async_alpine_default(Alpine) { alias = path; }; const syncHandler = (el) => { - if (el._x_async) return; - el._x_async = "init"; - el._x_ignore = true; - el.setAttribute(ignoreAttr, ""); + Alpine.skipDuringClone(() => { + if (el._x_async) return; + el._x_async = "init"; + el._x_ignore = true; + el.setAttribute(ignoreAttr, ""); + })(); }; const handler = async (el) => { - if (el._x_async !== "init") return; - el._x_async = "await"; - const { name, strategy } = elementPrep(el); - await awaitRequirements({ - name, - strategy, - el, - id: el.id || index() - }); - await download(name); - activate(el); - el._x_async = "loaded"; + Alpine.skipDuringClone(async () => { + if (el._x_async !== "init") return; + el._x_async = "await"; + const { name, strategy } = elementPrep(el); + await awaitRequirements({ + name, + strategy, + el, + id: el.id || index() + }); + await download(name); + activate(el); + el._x_async = "loaded"; + })(); }; handler.inline = syncHandler; Alpine.directive(directive, handler).before("ignore"); diff --git a/dist/async-alpine.esm.js b/dist/async-alpine.esm.js index 35ff501..eea9906 100644 --- a/dist/async-alpine.esm.js +++ b/dist/async-alpine.esm.js @@ -206,24 +206,28 @@ function async_alpine_default(Alpine) { alias = path; }; const syncHandler = (el) => { - if (el._x_async) return; - el._x_async = "init"; - el._x_ignore = true; - el.setAttribute(ignoreAttr, ""); + Alpine.skipDuringClone(() => { + if (el._x_async) return; + el._x_async = "init"; + el._x_ignore = true; + el.setAttribute(ignoreAttr, ""); + })(); }; const handler = async (el) => { - if (el._x_async !== "init") return; - el._x_async = "await"; - const { name, strategy } = elementPrep(el); - await awaitRequirements({ - name, - strategy, - el, - id: el.id || index() - }); - await download(name); - activate(el); - el._x_async = "loaded"; + Alpine.skipDuringClone(async () => { + if (el._x_async !== "init") return; + el._x_async = "await"; + const { name, strategy } = elementPrep(el); + await awaitRequirements({ + name, + strategy, + el, + id: el.id || index() + }); + await download(name); + activate(el); + el._x_async = "loaded"; + })(); }; handler.inline = syncHandler; Alpine.directive(directive, handler).before("ignore"); diff --git a/dist/async-alpine.script.js b/dist/async-alpine.script.js index ca349c1..e888577 100644 --- a/dist/async-alpine.script.js +++ b/dist/async-alpine.script.js @@ -1 +1 @@ -(()=>{var C=Object.defineProperty;var w=Object.getOwnPropertySymbols;var M=Object.prototype.hasOwnProperty,$=Object.prototype.propertyIsEnumerable;var v=(e,r,n)=>r in e?C(e,r,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[r]=n,y=(e,r)=>{for(var n in r||(r={}))M.call(r,n)&&v(e,n,r[n]);if(w)for(var n of w(r))$.call(r,n)&&v(e,n,r[n]);return e};var f=(e,r,n)=>new Promise((i,c)=>{var d=u=>{try{o(n.next(u))}catch(l){c(l)}},s=u=>{try{o(n.throw(u))}catch(l){c(l)}},o=u=>u.done?i(u.value):Promise.resolve(u.value).then(d,s);o((n=n.apply(e,r)).next())});function j(){return!0}function q({component:e,argument:r}){return new Promise(n=>{if(r)window.addEventListener(r,()=>n(),{once:!0});else{let i=c=>{c.detail.id===e.id&&(window.removeEventListener("async-alpine:load",i),n())};window.addEventListener("async-alpine:load",i)}})}function z(){return new Promise(e=>{"requestIdleCallback"in window?window.requestIdleCallback(e):setTimeout(e,200)})}function A({argument:e}){return new Promise(r=>{if(!e)return console.log("Async Alpine: media strategy requires a media query. Treating as 'eager'"),r();let n=window.matchMedia(`(${e})`);n.matches?r():n.addEventListener("change",r,{once:!0})})}function D({component:e,argument:r}){return new Promise(n=>{let i=r||"0px 0px 0px 0px",c=new IntersectionObserver(d=>{d[0].isIntersecting&&(c.disconnect(),n())},{rootMargin:i});c.observe(e.el)})}var m={eager:j,event:q,idle:z,media:A,visible:D};function _(e){return f(this,null,function*(){let r=S(e.strategy);yield x(e,r)})}function x(e,r){return f(this,null,function*(){if(r.type==="expression"){if(r.operator==="&&")return Promise.all(r.parameters.map(n=>x(e,n)));if(r.operator==="||")return Promise.any(r.parameters.map(n=>x(e,n)))}return m[r.method]?m[r.method]({component:e,argument:r.argument}):!1})}function S(e){let r=H(e),n=R(r);return n.type==="method"?{type:"expression",operator:"&&",parameters:[n]}:n}function H(e){let r=/\s*([()])\s*|\s*(\|\||&&|\|)\s*|\s*((?:[^()&|]+\([^()]+\))|[^()&|]+)\s*/g,n=[],i;for(;(i=r.exec(e))!==null;){let[c,d,s,o]=i;if(d!==void 0)n.push({type:"parenthesis",value:d});else if(s!==void 0)n.push({type:"operator",value:s==="|"?"&&":s});else{let u={type:"method",method:o.trim()};o.includes("(")&&(u.method=o.substring(0,o.indexOf("(")).trim(),u.argument=o.substring(o.indexOf("(")+1,o.indexOf(")"))),o.method==="immediate"&&(o.method="eager"),n.push(u)}}return n}function R(e){let r=b(e);for(;e.length>0&&(e[0].value==="&&"||e[0].value==="|"||e[0].value==="||");){let n=e.shift().value,i=b(e);r.type==="expression"&&r.operator===n?r.parameters.push(i):r={type:"expression",operator:n,parameters:[r,i]}}return r}function b(e){if(e[0].value==="("){e.shift();let r=R(e);return e[0].value===")"&&e.shift(),r}else return e.shift()}function E(e){let r="load",n=e.prefixed("load-src"),i=e.prefixed("ignore"),c={defaultStrategy:"eager",keepRelativeURLs:!1},d=!1,s={},o=0;function u(){return o++}e.asyncOptions=t=>{c=y(y({},c),t)},e.asyncData=(t,a=!1)=>{s[t]={loaded:!1,download:a}},e.asyncUrl=(t,a)=>{!t||!a||s[t]||(s[t]={loaded:!1,download:()=>import(T(a))})},e.asyncAlias=t=>{d=t};let l=t=>{t._x_async||(t._x_async="init",t._x_ignore=!0,t.setAttribute(i,""))},g=t=>f(this,null,function*(){if(t._x_async!=="init")return;t._x_async="await";let{name:a,strategy:p}=L(t);yield _({name:a,strategy:p,el:t,id:t.id||u()}),yield U(a),P(t),t._x_async="loaded"});g.inline=l,e.directive(r,g).before("ignore");function L(t){let a=N(t.getAttribute(e.prefixed("data"))),p=t.getAttribute(e.prefixed(r))||c.defaultStrategy,h=t.getAttribute(n);return h&&e.asyncUrl(a,h),{name:a,strategy:p}}function U(t){return f(this,null,function*(){if(t.startsWith("_x_async_")||(I(t),!s[t]||s[t].loaded))return;let a=yield O(t);e.data(t,a),s[t].loaded=!0})}function O(t){return f(this,null,function*(){if(!s[t])return;let a=yield s[t].download(t);return typeof a=="function"?a:a[t]||a.default||Object.values(a)[0]||!1})}function P(t){e.destroyTree(t),t._x_ignore=!1,t.removeAttribute(i),!t.closest(`[${i}]`)&&e.initTree(t)}function I(t){if(!(!d||s[t])){if(typeof d=="function"){e.asyncData(t,d);return}e.asyncUrl(t,d.replaceAll("[name]",t))}}function N(t){return(t||"").split(/[({]/g)[0]||`_x_async_${u()}`}function T(t){return c.keepRelativeURLs||new RegExp("^(?:[a-z+]+:)?//","i").test(t)?t:new URL(t,document.baseURI).href}}document.addEventListener("alpine:init",()=>{Alpine.plugin(E),document.dispatchEvent(new CustomEvent("async-alpine:init"))});})(); +(()=>{var T=Object.defineProperty;var w=Object.getOwnPropertySymbols;var D=Object.prototype.hasOwnProperty,M=Object.prototype.propertyIsEnumerable;var v=(e,r,n)=>r in e?T(e,r,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[r]=n,y=(e,r)=>{for(var n in r||(r={}))D.call(r,n)&&v(e,n,r[n]);if(w)for(var n of w(r))M.call(r,n)&&v(e,n,r[n]);return e};var f=(e,r,n)=>new Promise((a,c)=>{var d=u=>{try{o(n.next(u))}catch(l){c(l)}},s=u=>{try{o(n.throw(u))}catch(l){c(l)}},o=u=>u.done?a(u.value):Promise.resolve(u.value).then(d,s);o((n=n.apply(e,r)).next())});function $(){return!0}function j({component:e,argument:r}){return new Promise(n=>{if(r)window.addEventListener(r,()=>n(),{once:!0});else{let a=c=>{c.detail.id===e.id&&(window.removeEventListener("async-alpine:load",a),n())};window.addEventListener("async-alpine:load",a)}})}function q(){return new Promise(e=>{"requestIdleCallback"in window?window.requestIdleCallback(e):setTimeout(e,200)})}function z({argument:e}){return new Promise(r=>{if(!e)return console.log("Async Alpine: media strategy requires a media query. Treating as 'eager'"),r();let n=window.matchMedia(`(${e})`);n.matches?r():n.addEventListener("change",r,{once:!0})})}function S({component:e,argument:r}){return new Promise(n=>{let a=r||"0px 0px 0px 0px",c=new IntersectionObserver(d=>{d[0].isIntersecting&&(c.disconnect(),n())},{rootMargin:a});c.observe(e.el)})}var g={eager:$,event:j,idle:q,media:z,visible:S};function _(e){return f(this,null,function*(){let r=H(e.strategy);yield m(e,r)})}function m(e,r){return f(this,null,function*(){if(r.type==="expression"){if(r.operator==="&&")return Promise.all(r.parameters.map(n=>m(e,n)));if(r.operator==="||")return Promise.any(r.parameters.map(n=>m(e,n)))}return g[r.method]?g[r.method]({component:e,argument:r.argument}):!1})}function H(e){let r=Q(e),n=R(r);return n.type==="method"?{type:"expression",operator:"&&",parameters:[n]}:n}function Q(e){let r=/\s*([()])\s*|\s*(\|\||&&|\|)\s*|\s*((?:[^()&|]+\([^()]+\))|[^()&|]+)\s*/g,n=[],a;for(;(a=r.exec(e))!==null;){let[c,d,s,o]=a;if(d!==void 0)n.push({type:"parenthesis",value:d});else if(s!==void 0)n.push({type:"operator",value:s==="|"?"&&":s});else{let u={type:"method",method:o.trim()};o.includes("(")&&(u.method=o.substring(0,o.indexOf("(")).trim(),u.argument=o.substring(o.indexOf("(")+1,o.indexOf(")"))),o.method==="immediate"&&(o.method="eager"),n.push(u)}}return n}function R(e){let r=b(e);for(;e.length>0&&(e[0].value==="&&"||e[0].value==="|"||e[0].value==="||");){let n=e.shift().value,a=b(e);r.type==="expression"&&r.operator===n?r.parameters.push(a):r={type:"expression",operator:n,parameters:[r,a]}}return r}function b(e){if(e[0].value==="("){e.shift();let r=R(e);return e[0].value===")"&&e.shift(),r}else return e.shift()}function E(e){let r="load",n=e.prefixed("load-src"),a=e.prefixed("ignore"),c={defaultStrategy:"eager",keepRelativeURLs:!1},d=!1,s={},o=0;function u(){return o++}e.asyncOptions=t=>{c=y(y({},c),t)},e.asyncData=(t,i=!1)=>{s[t]={loaded:!1,download:i}},e.asyncUrl=(t,i)=>{!t||!i||s[t]||(s[t]={loaded:!1,download:()=>import(N(i))})},e.asyncAlias=t=>{d=t};let l=t=>{e.skipDuringClone(()=>{t._x_async||(t._x_async="init",t._x_ignore=!0,t.setAttribute(a,""))})()},x=t=>f(this,null,function*(){e.skipDuringClone(()=>f(this,null,function*(){if(t._x_async!=="init")return;t._x_async="await";let{name:i,strategy:p}=L(t);yield _({name:i,strategy:p,el:t,id:t.id||u()}),yield U(i),P(t),t._x_async="loaded"}))()});x.inline=l,e.directive(r,x).before("ignore");function L(t){let i=C(t.getAttribute(e.prefixed("data"))),p=t.getAttribute(e.prefixed(r))||c.defaultStrategy,h=t.getAttribute(n);return h&&e.asyncUrl(i,h),{name:i,strategy:p}}function U(t){return f(this,null,function*(){if(t.startsWith("_x_async_")||(I(t),!s[t]||s[t].loaded))return;let i=yield O(t);e.data(t,i),s[t].loaded=!0})}function O(t){return f(this,null,function*(){if(!s[t])return;let i=yield s[t].download(t);return typeof i=="function"?i:i[t]||i.default||Object.values(i)[0]||!1})}function P(t){e.destroyTree(t),t._x_ignore=!1,t.removeAttribute(a),!t.closest(`[${a}]`)&&e.initTree(t)}function I(t){if(!(!d||s[t])){if(typeof d=="function"){e.asyncData(t,d);return}e.asyncUrl(t,d.replaceAll("[name]",t))}}function C(t){return(t||"").split(/[({]/g)[0]||`_x_async_${u()}`}function N(t){return c.keepRelativeURLs||new RegExp("^(?:[a-z+]+:)?//","i").test(t)?t:new URL(t,document.baseURI).href}}document.addEventListener("alpine:init",()=>{Alpine.plugin(E),document.dispatchEvent(new CustomEvent("async-alpine:init"))});})(); diff --git a/src/async-alpine.js b/src/async-alpine.js index ea084bf..3349ffa 100644 --- a/src/async-alpine.js +++ b/src/async-alpine.js @@ -77,26 +77,30 @@ export default function (Alpine) { */ // inline handler adds x-ignore immediately and synchronously const syncHandler = (el) => { - if (el._x_async) return - el._x_async = 'init' - el._x_ignore = true - el.setAttribute(ignoreAttr, '') + Alpine.skipDuringClone(() => { + if (el._x_async) return + el._x_async = 'init' + el._x_ignore = true + el.setAttribute(ignoreAttr, '') + })() } // the bulk of processing happens within Alpine's deferred handler const handler = async (el) => { - if (el._x_async !== 'init') return - el._x_async = 'await' - const { name, strategy } = elementPrep(el) - await awaitRequirements({ - name, - strategy, - el, - id: el.id || index(), - }) - await download(name) - activate(el) - el._x_async = 'loaded' + Alpine.skipDuringClone(async () => { + if (el._x_async !== 'init') return + el._x_async = 'await' + const { name, strategy } = elementPrep(el) + await awaitRequirements({ + name, + strategy, + el, + id: el.id || index(), + }) + await download(name) + activate(el) + el._x_async = 'loaded' + })() } // register handler functions and directive diff --git a/tests/alpine/morph/index.html b/tests/alpine/morph/index.html new file mode 100644 index 0000000..61c14d4 --- /dev/null +++ b/tests/alpine/morph/index.html @@ -0,0 +1,37 @@ + + + + + + + morph - Async Alpine tests + + +

Tests Alpine 'morph'. Passes if x-ignore is not re-added after morphing (pressing 'Run Morph' button).

+ +
+
+ + + + + + diff --git a/tests/alpine/morph/morph.test.js b/tests/alpine/morph/morph.test.js new file mode 100644 index 0000000..132bcba --- /dev/null +++ b/tests/alpine/morph/morph.test.js @@ -0,0 +1,12 @@ +import { test, expect } from '@playwright/test' + +test('alpine/morph', async ({ page }) => { + await page.goto('/tests/alpine/morph/') + await expect(page.getByTestId('test')) + .toHaveText('testing123') + await page.getByTestId('button').click() + await expect(page.getByTestId('test')) + .not.toHaveAttribute('x-ignore') + await expect(page.getByTestId('test')) + .toHaveText('testing123') +}); diff --git a/tests/index.html b/tests/index.html index ff161ba..623a127 100644 --- a/tests/index.html +++ b/tests/index.html @@ -50,6 +50,7 @@

Alpine Features

  • x-teleport
  • x-if
  • x-id
  • +
  • morph
  • Loading Method