From 6c9f92fbfd034e09327f5c918f0c816720c15aa5 Mon Sep 17 00:00:00 2001 From: ThornWalli Date: Tue, 21 Nov 2023 16:29:37 +0000 Subject: [PATCH] deploy: 77dccff5fd8c5adcdf8c1c30dc1428c29b8f316f --- docs/404.html | 6 +- docs/assets/app.2TeU76ik.js | 7 + docs/assets/chunks/framework._u06EGUx.js | 2 + docs/assets/chunks/theme.pN5TPI87.js | 1 + .../components_speedkit-iframe.md.c5N44rt-.js | 39 +++ ...onents_speedkit-iframe.md.c5N44rt-.lean.js | 1 + .../components_speedkit-image.md.ch9gdyuF.js | 101 ++++++++ ...ponents_speedkit-image.md.ch9gdyuF.lean.js | 1 + .../components_speedkit-layer.md.IBUESyjR.js | 105 ++++++++ ...ponents_speedkit-layer.md.IBUESyjR.lean.js | 1 + ...components_speedkit-picture.md.9XE6ev5S.js | 97 ++++++++ ...nents_speedkit-picture.md.9XE6ev5S.lean.js | 1 + .../components_speedkit-vimeo.md.VOcroeY4.js | 119 +++++++++ ...ponents_speedkit-vimeo.md.VOcroeY4.lean.js | 1 + ...components_speedkit-youtube.md.MYGlq3t8.js | 117 +++++++++ ...nents_speedkit-youtube.md.MYGlq3t8.lean.js | 1 + ...nents_weak-hardware-overlay.md.sQu1lie2.js | 99 ++++++++ ..._weak-hardware-overlay.md.sQu1lie2.lean.js | 1 + ...sables_useComponentObserver.md.86iHNGdS.js | 29 +++ ...s_useComponentObserver.md.86iHNGdS.lean.js | 1 + .../composables_useConfig.md.HxJG3vdy.js | 3 + .../composables_useConfig.md.HxJG3vdy.lean.js | 1 + .../composables_useCritical.md.nddRf2fx.js | 15 ++ ...omposables_useCritical.md.nddRf2fx.lean.js | 1 + .../assets/composables_useFont.md.zDVqwOCI.js | 15 ++ .../composables_useFont.md.zDVqwOCI.lean.js | 1 + docs/assets/concept.md.s_9WUY8f.js | 1 + docs/assets/concept.md.s_9WUY8f.lean.js | 1 + docs/assets/directives_v-font.md.vdRrO8ZL.js | 51 ++++ .../directives_v-font.md.vdRrO8ZL.lean.js | 1 + .../examples_api-examples.md.mC_SbDeW.js | 31 +++ .../examples_api-examples.md.mC_SbDeW.lean.js | 1 + docs/assets/examples_index.md.UAHQB5IW.js | 1 + .../assets/examples_index.md.UAHQB5IW.lean.js | 1 + .../examples_markdown-examples.md.M6_8vTVM.js | 65 +++++ ...ples_markdown-examples.md.M6_8vTVM.lean.js | 1 + docs/assets/guide_caveats.md.rxJAUaDq.js | 45 ++++ docs/assets/guide_caveats.md.rxJAUaDq.lean.js | 1 + docs/assets/guide_options.md.G-dxl61U.js | 107 ++++++++ docs/assets/guide_options.md.G-dxl61U.lean.js | 1 + docs/assets/guide_setup.md.5ri77JFP.js | 189 +++++++++++++++ docs/assets/guide_setup.md.5ri77JFP.lean.js | 1 + docs/assets/guide_usage.md.wgLYVO7n.js | 35 +++ docs/assets/guide_usage.md.wgLYVO7n.lean.js | 1 + docs/assets/index.md.itw9ycj3.js | 1 + docs/assets/index.md.itw9ycj3.lean.js | 1 + docs/assets/migration_v2-0-13.md.kI3DdF5o.js | 17 ++ .../migration_v2-0-13.md.kI3DdF5o.lean.js | 1 + docs/assets/migration_v2-2-0.md.NbSpjQqB.js | 1 + .../migration_v2-2-0.md.NbSpjQqB.lean.js | 1 + docs/assets/migration_v2.md.Jnf8W_4F.js | 35 +++ docs/assets/migration_v2.md.Jnf8W_4F.lean.js | 1 + docs/assets/migration_v3.md.pgAE9dPh.js | 15 ++ docs/assets/migration_v3.md.pgAE9dPh.lean.js | 1 + ...perimental_speedkit-picture.md.8TZco5vW.js | 71 ++++++ ...ental_speedkit-picture.md.8TZco5vW.lean.js | 1 + ...perimental_speedkit-youtube.md.m7szeORz.js | 85 +++++++ ...ental_speedkit-youtube.md.m7szeORz.lean.js | 1 + ..._components_speedkit-iframe.md.FFeQ4ub1.js | 43 ++++ ...onents_speedkit-iframe.md.FFeQ4ub1.lean.js | 1 + ...1_components_speedkit-layer.md.lVxe18Ze.js | 125 ++++++++++ ...ponents_speedkit-layer.md.lVxe18Ze.lean.js | 1 + ...components_speedkit-picture.md.8iUNQNQI.js | 229 ++++++++++++++++++ ...nents_speedkit-picture.md.8iUNQNQI.lean.js | 1 + ...components_speedkit-youtube.md.42HHjCjS.js | 125 ++++++++++ ...nents_speedkit-youtube.md.42HHjCjS.lean.js | 1 + docs/assets/v1_concept.md.Z6zMo_qc.js | 1 + docs/assets/v1_concept.md.Z6zMo_qc.lean.js | 1 + .../v1_directives_v-font.md.kNiUmR7R.js | 79 ++++++ .../v1_directives_v-font.md.kNiUmR7R.lean.js | 1 + docs/assets/v1_guide_options.md.gNDuU5V1.js | 105 ++++++++ .../v1_guide_options.md.gNDuU5V1.lean.js | 1 + docs/assets/v1_guide_setup.md.8rJExCV3.js | 131 ++++++++++ .../assets/v1_guide_setup.md.8rJExCV3.lean.js | 1 + docs/assets/v1_guide_usage.md.tutYb0dJ.js | 97 ++++++++ .../assets/v1_guide_usage.md.tutYb0dJ.lean.js | 1 + docs/assets/v1_index.md.sX8weVju.js | 1 + docs/assets/v1_index.md.sX8weVju.lean.js | 1 + .../v2_classes_loading-spinner.md.Kt9B_64I.js | 1 + ...lasses_loading-spinner.md.Kt9B_64I.lean.js | 1 + ..._components_speedkit-iframe.md.mfHg_6sq.js | 43 ++++ ...onents_speedkit-iframe.md.mfHg_6sq.lean.js | 1 + ...2_components_speedkit-image.md.DHxRtycv.js | 83 +++++++ ...ponents_speedkit-image.md.DHxRtycv.lean.js | 1 + ...2_components_speedkit-layer.md.WkUt2OmF.js | 105 ++++++++ ...ponents_speedkit-layer.md.WkUt2OmF.lean.js | 1 + ...components_speedkit-picture.md.O5xJ3ujA.js | 79 ++++++ ...nents_speedkit-picture.md.O5xJ3ujA.lean.js | 1 + ...2_components_speedkit-vimeo.md.6hA686kW.js | 105 ++++++++ ...ponents_speedkit-vimeo.md.6hA686kW.lean.js | 1 + ...components_speedkit-youtube.md.3WlGGiXM.js | 99 ++++++++ ...nents_speedkit-youtube.md.3WlGGiXM.lean.js | 1 + docs/assets/v2_concept.md.p1bQg3c8.js | 1 + docs/assets/v2_concept.md.p1bQg3c8.lean.js | 1 + .../v2_directives_v-font.md.JKoSYXEX.js | 79 ++++++ .../v2_directives_v-font.md.JKoSYXEX.lean.js | 1 + docs/assets/v2_guide_caveats.md.QQFtLQVa.js | 159 ++++++++++++ .../v2_guide_caveats.md.QQFtLQVa.lean.js | 1 + docs/assets/v2_guide_options.md.FbwA7bCa.js | 115 +++++++++ .../v2_guide_options.md.FbwA7bCa.lean.js | 1 + docs/assets/v2_guide_setup.md.Q4GqF1oR.js | 201 +++++++++++++++ .../assets/v2_guide_setup.md.Q4GqF1oR.lean.js | 1 + docs/assets/v2_guide_usage.md.DrnFp7qu.js | 35 +++ .../assets/v2_guide_usage.md.DrnFp7qu.lean.js | 1 + docs/assets/v2_index.md.GB204pUc.js | 1 + docs/assets/v2_index.md.GB204pUc.lean.js | 1 + docs/components/speedkit-iframe.html | 12 +- docs/components/speedkit-image.html | 12 +- docs/components/speedkit-layer.html | 12 +- docs/components/speedkit-picture.html | 12 +- docs/components/speedkit-vimeo.html | 12 +- docs/components/speedkit-youtube.html | 12 +- docs/components/weak-hardware-overlay.html | 16 +- docs/composables/useComponentObserver.html | 12 +- docs/composables/useConfig.html | 12 +- docs/composables/useCritical.html | 12 +- docs/composables/useFont.html | 12 +- docs/concept.html | 12 +- docs/directives/v-font.html | 12 +- docs/examples/api-examples.html | 12 +- docs/examples/index.html | 12 +- docs/examples/markdown-examples.html | 12 +- docs/guide/caveats.html | 12 +- docs/guide/options.html | 12 +- docs/guide/setup.html | 16 +- docs/guide/usage.html | 12 +- docs/hashmap.json | 2 +- docs/index.html | 12 +- docs/migration/v2-0-13.html | 12 +- docs/migration/v2-2-0.html | 12 +- docs/migration/v2.html | 12 +- docs/migration/v3.html | 12 +- .../experimental/speedkit-picture.html | 12 +- .../experimental/speedkit-youtube.html | 12 +- docs/v1/components/speedkit-iframe.html | 12 +- docs/v1/components/speedkit-layer.html | 12 +- docs/v1/components/speedkit-picture.html | 12 +- docs/v1/components/speedkit-youtube.html | 12 +- docs/v1/concept.html | 12 +- docs/v1/directives/v-font.html | 16 +- docs/v1/guide/options.html | 12 +- docs/v1/guide/setup.html | 14 +- docs/v1/guide/usage.html | 14 +- docs/v1/index.html | 12 +- docs/v2/classes/loading-spinner.html | 12 +- docs/v2/components/speedkit-iframe.html | 12 +- docs/v2/components/speedkit-image.html | 12 +- docs/v2/components/speedkit-layer.html | 12 +- docs/v2/components/speedkit-picture.html | 12 +- docs/v2/components/speedkit-vimeo.html | 12 +- docs/v2/components/speedkit-youtube.html | 12 +- docs/v2/concept.html | 12 +- docs/v2/directives/v-font.html | 12 +- docs/v2/guide/caveats.html | 16 +- docs/v2/guide/options.html | 12 +- docs/v2/guide/setup.html | 16 +- docs/v2/guide/usage.html | 12 +- docs/v2/index.html | 12 +- 158 files changed, 3914 insertions(+), 322 deletions(-) create mode 100644 docs/assets/app.2TeU76ik.js create mode 100644 docs/assets/chunks/framework._u06EGUx.js create mode 100644 docs/assets/chunks/theme.pN5TPI87.js create mode 100644 docs/assets/components_speedkit-iframe.md.c5N44rt-.js create mode 100644 docs/assets/components_speedkit-iframe.md.c5N44rt-.lean.js create mode 100644 docs/assets/components_speedkit-image.md.ch9gdyuF.js create mode 100644 docs/assets/components_speedkit-image.md.ch9gdyuF.lean.js create mode 100644 docs/assets/components_speedkit-layer.md.IBUESyjR.js create mode 100644 docs/assets/components_speedkit-layer.md.IBUESyjR.lean.js create mode 100644 docs/assets/components_speedkit-picture.md.9XE6ev5S.js create mode 100644 docs/assets/components_speedkit-picture.md.9XE6ev5S.lean.js create mode 100644 docs/assets/components_speedkit-vimeo.md.VOcroeY4.js create mode 100644 docs/assets/components_speedkit-vimeo.md.VOcroeY4.lean.js create mode 100644 docs/assets/components_speedkit-youtube.md.MYGlq3t8.js create mode 100644 docs/assets/components_speedkit-youtube.md.MYGlq3t8.lean.js create mode 100644 docs/assets/components_weak-hardware-overlay.md.sQu1lie2.js create mode 100644 docs/assets/components_weak-hardware-overlay.md.sQu1lie2.lean.js create mode 100644 docs/assets/composables_useComponentObserver.md.86iHNGdS.js create mode 100644 docs/assets/composables_useComponentObserver.md.86iHNGdS.lean.js create mode 100644 docs/assets/composables_useConfig.md.HxJG3vdy.js create mode 100644 docs/assets/composables_useConfig.md.HxJG3vdy.lean.js create mode 100644 docs/assets/composables_useCritical.md.nddRf2fx.js create mode 100644 docs/assets/composables_useCritical.md.nddRf2fx.lean.js create mode 100644 docs/assets/composables_useFont.md.zDVqwOCI.js create mode 100644 docs/assets/composables_useFont.md.zDVqwOCI.lean.js create mode 100644 docs/assets/concept.md.s_9WUY8f.js create mode 100644 docs/assets/concept.md.s_9WUY8f.lean.js create mode 100644 docs/assets/directives_v-font.md.vdRrO8ZL.js create mode 100644 docs/assets/directives_v-font.md.vdRrO8ZL.lean.js create mode 100644 docs/assets/examples_api-examples.md.mC_SbDeW.js create mode 100644 docs/assets/examples_api-examples.md.mC_SbDeW.lean.js create mode 100644 docs/assets/examples_index.md.UAHQB5IW.js create mode 100644 docs/assets/examples_index.md.UAHQB5IW.lean.js create mode 100644 docs/assets/examples_markdown-examples.md.M6_8vTVM.js create mode 100644 docs/assets/examples_markdown-examples.md.M6_8vTVM.lean.js create mode 100644 docs/assets/guide_caveats.md.rxJAUaDq.js create mode 100644 docs/assets/guide_caveats.md.rxJAUaDq.lean.js create mode 100644 docs/assets/guide_options.md.G-dxl61U.js create mode 100644 docs/assets/guide_options.md.G-dxl61U.lean.js create mode 100644 docs/assets/guide_setup.md.5ri77JFP.js create mode 100644 docs/assets/guide_setup.md.5ri77JFP.lean.js create mode 100644 docs/assets/guide_usage.md.wgLYVO7n.js create mode 100644 docs/assets/guide_usage.md.wgLYVO7n.lean.js create mode 100644 docs/assets/index.md.itw9ycj3.js create mode 100644 docs/assets/index.md.itw9ycj3.lean.js create mode 100644 docs/assets/migration_v2-0-13.md.kI3DdF5o.js create mode 100644 docs/assets/migration_v2-0-13.md.kI3DdF5o.lean.js create mode 100644 docs/assets/migration_v2-2-0.md.NbSpjQqB.js create mode 100644 docs/assets/migration_v2-2-0.md.NbSpjQqB.lean.js create mode 100644 docs/assets/migration_v2.md.Jnf8W_4F.js create mode 100644 docs/assets/migration_v2.md.Jnf8W_4F.lean.js create mode 100644 docs/assets/migration_v3.md.pgAE9dPh.js create mode 100644 docs/assets/migration_v3.md.pgAE9dPh.lean.js create mode 100644 docs/assets/v1_components_experimental_speedkit-picture.md.8TZco5vW.js create mode 100644 docs/assets/v1_components_experimental_speedkit-picture.md.8TZco5vW.lean.js create mode 100644 docs/assets/v1_components_experimental_speedkit-youtube.md.m7szeORz.js create mode 100644 docs/assets/v1_components_experimental_speedkit-youtube.md.m7szeORz.lean.js create mode 100644 docs/assets/v1_components_speedkit-iframe.md.FFeQ4ub1.js create mode 100644 docs/assets/v1_components_speedkit-iframe.md.FFeQ4ub1.lean.js create mode 100644 docs/assets/v1_components_speedkit-layer.md.lVxe18Ze.js create mode 100644 docs/assets/v1_components_speedkit-layer.md.lVxe18Ze.lean.js create mode 100644 docs/assets/v1_components_speedkit-picture.md.8iUNQNQI.js create mode 100644 docs/assets/v1_components_speedkit-picture.md.8iUNQNQI.lean.js create mode 100644 docs/assets/v1_components_speedkit-youtube.md.42HHjCjS.js create mode 100644 docs/assets/v1_components_speedkit-youtube.md.42HHjCjS.lean.js create mode 100644 docs/assets/v1_concept.md.Z6zMo_qc.js create mode 100644 docs/assets/v1_concept.md.Z6zMo_qc.lean.js create mode 100644 docs/assets/v1_directives_v-font.md.kNiUmR7R.js create mode 100644 docs/assets/v1_directives_v-font.md.kNiUmR7R.lean.js create mode 100644 docs/assets/v1_guide_options.md.gNDuU5V1.js create mode 100644 docs/assets/v1_guide_options.md.gNDuU5V1.lean.js create mode 100644 docs/assets/v1_guide_setup.md.8rJExCV3.js create mode 100644 docs/assets/v1_guide_setup.md.8rJExCV3.lean.js create mode 100644 docs/assets/v1_guide_usage.md.tutYb0dJ.js create mode 100644 docs/assets/v1_guide_usage.md.tutYb0dJ.lean.js create mode 100644 docs/assets/v1_index.md.sX8weVju.js create mode 100644 docs/assets/v1_index.md.sX8weVju.lean.js create mode 100644 docs/assets/v2_classes_loading-spinner.md.Kt9B_64I.js create mode 100644 docs/assets/v2_classes_loading-spinner.md.Kt9B_64I.lean.js create mode 100644 docs/assets/v2_components_speedkit-iframe.md.mfHg_6sq.js create mode 100644 docs/assets/v2_components_speedkit-iframe.md.mfHg_6sq.lean.js create mode 100644 docs/assets/v2_components_speedkit-image.md.DHxRtycv.js create mode 100644 docs/assets/v2_components_speedkit-image.md.DHxRtycv.lean.js create mode 100644 docs/assets/v2_components_speedkit-layer.md.WkUt2OmF.js create mode 100644 docs/assets/v2_components_speedkit-layer.md.WkUt2OmF.lean.js create mode 100644 docs/assets/v2_components_speedkit-picture.md.O5xJ3ujA.js create mode 100644 docs/assets/v2_components_speedkit-picture.md.O5xJ3ujA.lean.js create mode 100644 docs/assets/v2_components_speedkit-vimeo.md.6hA686kW.js create mode 100644 docs/assets/v2_components_speedkit-vimeo.md.6hA686kW.lean.js create mode 100644 docs/assets/v2_components_speedkit-youtube.md.3WlGGiXM.js create mode 100644 docs/assets/v2_components_speedkit-youtube.md.3WlGGiXM.lean.js create mode 100644 docs/assets/v2_concept.md.p1bQg3c8.js create mode 100644 docs/assets/v2_concept.md.p1bQg3c8.lean.js create mode 100644 docs/assets/v2_directives_v-font.md.JKoSYXEX.js create mode 100644 docs/assets/v2_directives_v-font.md.JKoSYXEX.lean.js create mode 100644 docs/assets/v2_guide_caveats.md.QQFtLQVa.js create mode 100644 docs/assets/v2_guide_caveats.md.QQFtLQVa.lean.js create mode 100644 docs/assets/v2_guide_options.md.FbwA7bCa.js create mode 100644 docs/assets/v2_guide_options.md.FbwA7bCa.lean.js create mode 100644 docs/assets/v2_guide_setup.md.Q4GqF1oR.js create mode 100644 docs/assets/v2_guide_setup.md.Q4GqF1oR.lean.js create mode 100644 docs/assets/v2_guide_usage.md.DrnFp7qu.js create mode 100644 docs/assets/v2_guide_usage.md.DrnFp7qu.lean.js create mode 100644 docs/assets/v2_index.md.GB204pUc.js create mode 100644 docs/assets/v2_index.md.GB204pUc.lean.js diff --git a/docs/404.html b/docs/404.html index 059c894f22..1f3b4722fd 100644 --- a/docs/404.html +++ b/docs/404.html @@ -5,17 +5,17 @@ 404 | Nuxt Speedkit - + - +
Skip to content

404

PAGE NOT FOUND

But if you don't change your direction, and if you keep looking, you may end up where you are heading.
- + \ No newline at end of file diff --git a/docs/assets/app.2TeU76ik.js b/docs/assets/app.2TeU76ik.js new file mode 100644 index 0000000000..c9782e7aa3 --- /dev/null +++ b/docs/assets/app.2TeU76ik.js @@ -0,0 +1,7 @@ +import{a2 as i,v as s,a3 as c,a4 as l,a5 as f,a6 as d,a7 as m,a8 as h,a9 as A,aa as g,ab as v,ac as y,ad as P,d as w,u as C,j as R,z as _,ae as b,af as D,ag as E}from"./chunks/framework._u06EGUx.js";import{t as p}from"./chunks/theme.pN5TPI87.js";const L={extends:p,Layout:()=>i(p.Layout,null,{}),enhanceApp({app:e,router:a,siteData:t}){}};function u(e){if(e.extends){const a=u(e.extends);return{...a,...e,async enhanceApp(t){a.enhanceApp&&await a.enhanceApp(t),e.enhanceApp&&await e.enhanceApp(t)}}}return e}const o=u(L),T=w({name:"VitePressApp",setup(){const{site:e}=C();return R(()=>{_(()=>{document.documentElement.lang=e.value.lang,document.documentElement.dir=e.value.dir})}),e.value.router.prefetchLinks&&b(),D(),E(),o.setup&&o.setup(),()=>i(o.Layout)}});async function j(){const e=O(),a=x();a.provide(l,e);const t=f(e.route);return a.provide(d,t),a.component("Content",m),a.component("ClientOnly",h),Object.defineProperties(a.config.globalProperties,{$frontmatter:{get(){return t.frontmatter.value}},$params:{get(){return t.page.value.params}}}),o.enhanceApp&&await o.enhanceApp({app:a,router:e,siteData:A}),{app:a,router:e,data:t}}function x(){return g(T)}function O(){let e=s,a;return v(t=>{let n=y(t),r=null;return n&&(e&&(a=n),(e||a===n)&&(n=n.replace(/\.js$/,".lean.js")),r=P(()=>import(n),__vite__mapDeps([]))),s&&(e=!1),r},o.NotFound)}s&&j().then(({app:e,router:a,data:t})=>{a.go().then(()=>{c(a.route,t.site),e.mount("#app")})});export{j as createApp}; +function __vite__mapDeps(indexes) { + if (!__vite__mapDeps.viteFileDeps) { + __vite__mapDeps.viteFileDeps = [] + } + return indexes.map((i) => __vite__mapDeps.viteFileDeps[i]) +} \ No newline at end of file diff --git a/docs/assets/chunks/framework._u06EGUx.js b/docs/assets/chunks/framework._u06EGUx.js new file mode 100644 index 0000000000..476742717e --- /dev/null +++ b/docs/assets/chunks/framework._u06EGUx.js @@ -0,0 +1,2 @@ +function li(e,t){const n=Object.create(null),i=e.split(",");for(let o=0;o!!n[o.toLowerCase()]:o=>!!n[o]}const te={},mt=[],Fe=()=>{},Zo=()=>!1,Go=/^on[^a-z]/,Ut=e=>Go.test(e),ai=e=>e.startsWith("onUpdate:"),re=Object.assign,ci=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},er=Object.prototype.hasOwnProperty,X=(e,t)=>er.call(e,t),$=Array.isArray,ht=e=>yn(e)==="[object Map]",Rs=e=>yn(e)==="[object Set]",K=e=>typeof e=="function",ne=e=>typeof e=="string",vn=e=>typeof e=="symbol",ee=e=>e!==null&&typeof e=="object",Ps=e=>(ee(e)||K(e))&&K(e.then)&&K(e.catch),Is=Object.prototype.toString,yn=e=>Is.call(e),tr=e=>yn(e).slice(8,-1),Fs=e=>yn(e)==="[object Object]",fi=e=>ne(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,Pt=li(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),bn=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},nr=/-(\w)/g,Me=bn(e=>e.replace(nr,(t,n)=>n?n.toUpperCase():"")),ir=/\B([A-Z])/g,ct=bn(e=>e.replace(ir,"-$1").toLowerCase()),_n=bn(e=>e.charAt(0).toUpperCase()+e.slice(1)),on=bn(e=>e?`on${_n(e)}`:""),at=(e,t)=>!Object.is(e,t),Nn=(e,t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,value:n})},sr=e=>{const t=parseFloat(e);return isNaN(t)?e:t},or=e=>{const t=ne(e)?Number(e):NaN;return isNaN(t)?e:t};let ki;const zn=()=>ki||(ki=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function ui(e){if($(e)){const t={};for(let n=0;n{if(n){const i=n.split(lr);i.length>1&&(t[i[0].trim()]=i[1].trim())}}),t}function pi(e){let t="";if(ne(e))t=e;else if($(e))for(let n=0;nne(e)?e:e==null?"":$(e)||ee(e)&&(e.toString===Is||!K(e.toString))?JSON.stringify(e,Ls,2):String(e),Ls=(e,t)=>t&&t.__v_isRef?Ls(e,t.value):ht(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((n,[i,o])=>(n[`${i} =>`]=o,n),{})}:Rs(t)?{[`Set(${t.size})`]:[...t.values()]}:ee(t)&&!$(t)&&!Fs(t)?String(t):t;let ye;class pr{constructor(t=!1){this.detached=t,this._active=!0,this.effects=[],this.cleanups=[],this.parent=ye,!t&&ye&&(this.index=(ye.scopes||(ye.scopes=[])).push(this)-1)}get active(){return this._active}run(t){if(this._active){const n=ye;try{return ye=this,t()}finally{ye=n}}}on(){ye=this}off(){ye=this.parent}stop(t){if(this._active){let n,i;for(n=0,i=this.effects.length;n{const t=new Set(e);return t.w=0,t.n=0,t},Ns=e=>(e.w&Xe)>0,Hs=e=>(e.n&Xe)>0,hr=({deps:e})=>{if(e.length)for(let t=0;t{const{deps:t}=e;if(t.length){let n=0;for(let i=0;i{(p==="length"||!vn(p)&&p>=a)&&l.push(f)})}else switch(n!==void 0&&l.push(r.get(n)),t){case"add":$(e)?fi(n)&&l.push(r.get("length")):(l.push(r.get(ot)),ht(e)&&l.push(r.get(Jn)));break;case"delete":$(e)||(l.push(r.get(ot)),ht(e)&&l.push(r.get(Jn)));break;case"set":ht(e)&&l.push(r.get(ot));break}if(l.length===1)l[0]&&Xn(l[0]);else{const a=[];for(const f of l)f&&a.push(...f);Xn(di(a))}}function Xn(e,t){const n=$(e)?e:[...e];for(const i of n)i.computed&&Hi(i);for(const i of n)i.computed||Hi(i)}function Hi(e,t){(e!==je||e.allowRecurse)&&(e.scheduler?e.scheduler():e.run())}function xr(e,t){var n;return(n=cn.get(e))==null?void 0:n.get(t)}const vr=li("__proto__,__v_isRef,__isVue"),Bs=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!=="arguments"&&e!=="caller").map(e=>Symbol[e]).filter(vn)),$i=yr();function yr(){const e={};return["includes","indexOf","lastIndexOf"].forEach(t=>{e[t]=function(...n){const i=Q(this);for(let s=0,r=this.length;s{e[t]=function(...n){Et();const i=Q(this)[t].apply(this,n);return Tt(),i}}),e}function br(e){const t=Q(this);return xe(t,"has",e),t.hasOwnProperty(e)}class Us{constructor(t=!1,n=!1){this._isReadonly=t,this._shallow=n}get(t,n,i){const o=this._isReadonly,s=this._shallow;if(n==="__v_isReactive")return!o;if(n==="__v_isReadonly")return o;if(n==="__v_isShallow")return s;if(n==="__v_raw"&&i===(o?s?Fr:Vs:s?qs:Ws).get(t))return t;const r=$(t);if(!o){if(r&&X($i,n))return Reflect.get($i,n,i);if(n==="hasOwnProperty")return br}const l=Reflect.get(t,n,i);return(vn(n)?Bs.has(n):vr(n))||(o||xe(t,"get",n),s)?l:ae(l)?r&&fi(n)?l:l.value:ee(l)?o?En(l):Cn(l):l}}class Ks extends Us{constructor(t=!1){super(!1,t)}set(t,n,i,o){let s=t[n];if(bt(s)&&ae(s)&&!ae(i))return!1;if(!this._shallow&&(!fn(i)&&!bt(i)&&(s=Q(s),i=Q(i)),!$(t)&&ae(s)&&!ae(i)))return s.value=i,!0;const r=$(t)&&fi(n)?Number(n)e,wn=e=>Reflect.getPrototypeOf(e);function qt(e,t,n=!1,i=!1){e=e.__v_raw;const o=Q(e),s=Q(t);n||(at(t,s)&&xe(o,"get",t),xe(o,"get",s));const{has:r}=wn(o),l=i?hi:n?vi:kt;if(r.call(o,t))return l(e.get(t));if(r.call(o,s))return l(e.get(s));e!==o&&e.get(t)}function Vt(e,t=!1){const n=this.__v_raw,i=Q(n),o=Q(e);return t||(at(e,o)&&xe(i,"has",e),xe(i,"has",o)),e===o?n.has(e):n.has(e)||n.has(o)}function zt(e,t=!1){return e=e.__v_raw,!t&&xe(Q(e),"iterate",ot),Reflect.get(e,"size",e)}function Di(e){e=Q(e);const t=Q(this);return wn(t).has.call(t,e)||(t.add(e),He(t,"add",e,e)),this}function Bi(e,t){t=Q(t);const n=Q(this),{has:i,get:o}=wn(n);let s=i.call(n,e);s||(e=Q(e),s=i.call(n,e));const r=o.call(n,e);return n.set(e,t),s?at(t,r)&&He(n,"set",e,t):He(n,"add",e,t),this}function Ui(e){const t=Q(this),{has:n,get:i}=wn(t);let o=n.call(t,e);o||(e=Q(e),o=n.call(t,e)),i&&i.call(t,e);const s=t.delete(e);return o&&He(t,"delete",e,void 0),s}function Ki(){const e=Q(this),t=e.size!==0,n=e.clear();return t&&He(e,"clear",void 0,void 0),n}function Yt(e,t){return function(i,o){const s=this,r=s.__v_raw,l=Q(r),a=t?hi:e?vi:kt;return!e&&xe(l,"iterate",ot),r.forEach((f,p)=>i.call(o,a(f),a(p),s))}}function Jt(e,t,n){return function(...i){const o=this.__v_raw,s=Q(o),r=ht(s),l=e==="entries"||e===Symbol.iterator&&r,a=e==="keys"&&r,f=o[e](...i),p=n?hi:t?vi:kt;return!t&&xe(s,"iterate",a?Jn:ot),{next(){const{value:d,done:y}=f.next();return y?{value:d,done:y}:{value:l?[p(d[0]),p(d[1])]:p(d),done:y}},[Symbol.iterator](){return this}}}}function De(e){return function(...t){return e==="delete"?!1:this}}function Tr(){const e={get(s){return qt(this,s)},get size(){return zt(this)},has:Vt,add:Di,set:Bi,delete:Ui,clear:Ki,forEach:Yt(!1,!1)},t={get(s){return qt(this,s,!1,!0)},get size(){return zt(this)},has:Vt,add:Di,set:Bi,delete:Ui,clear:Ki,forEach:Yt(!1,!0)},n={get(s){return qt(this,s,!0)},get size(){return zt(this,!0)},has(s){return Vt.call(this,s,!0)},add:De("add"),set:De("set"),delete:De("delete"),clear:De("clear"),forEach:Yt(!0,!1)},i={get(s){return qt(this,s,!0,!0)},get size(){return zt(this,!0)},has(s){return Vt.call(this,s,!0)},add:De("add"),set:De("set"),delete:De("delete"),clear:De("clear"),forEach:Yt(!0,!0)};return["keys","values","entries",Symbol.iterator].forEach(s=>{e[s]=Jt(s,!1,!1),n[s]=Jt(s,!0,!1),t[s]=Jt(s,!1,!0),i[s]=Jt(s,!0,!0)}),[e,n,t,i]}const[Ar,jr,Sr,Or]=Tr();function gi(e,t){const n=t?e?Or:Sr:e?jr:Ar;return(i,o,s)=>o==="__v_isReactive"?!e:o==="__v_isReadonly"?e:o==="__v_raw"?i:Reflect.get(X(n,o)&&o in i?n:i,o,s)}const Rr={get:gi(!1,!1)},Pr={get:gi(!1,!0)},Ir={get:gi(!0,!1)},Ws=new WeakMap,qs=new WeakMap,Vs=new WeakMap,Fr=new WeakMap;function Mr(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function Lr(e){return e.__v_skip||!Object.isExtensible(e)?0:Mr(tr(e))}function Cn(e){return bt(e)?e:xi(e,!1,wr,Rr,Ws)}function kr(e){return xi(e,!1,Er,Pr,qs)}function En(e){return xi(e,!0,Cr,Ir,Vs)}function xi(e,t,n,i,o){if(!ee(e)||e.__v_raw&&!(t&&e.__v_isReactive))return e;const s=o.get(e);if(s)return s;const r=Lr(e);if(r===0)return e;const l=new Proxy(e,r===2?i:n);return o.set(e,l),l}function gt(e){return bt(e)?gt(e.__v_raw):!!(e&&e.__v_isReactive)}function bt(e){return!!(e&&e.__v_isReadonly)}function fn(e){return!!(e&&e.__v_isShallow)}function zs(e){return gt(e)||bt(e)}function Q(e){const t=e&&e.__v_raw;return t?Q(t):e}function It(e){return an(e,"__v_skip",!0),e}const kt=e=>ee(e)?Cn(e):e,vi=e=>ee(e)?En(e):e;function yi(e){Ve&&je&&(e=Q(e),Ds(e.dep||(e.dep=di())))}function bi(e,t){e=Q(e);const n=e.dep;n&&Xn(n)}function ae(e){return!!(e&&e.__v_isRef===!0)}function ge(e){return Js(e,!1)}function Ys(e){return Js(e,!0)}function Js(e,t){return ae(e)?e:new Nr(e,t)}class Nr{constructor(t,n){this.__v_isShallow=n,this.dep=void 0,this.__v_isRef=!0,this._rawValue=n?t:Q(t),this._value=n?t:kt(t)}get value(){return yi(this),this._value}set value(t){const n=this.__v_isShallow||fn(t)||bt(t);t=n?t:Q(t),at(t,this._rawValue)&&(this._rawValue=t,this._value=n?t:kt(t),bi(this))}}function _i(e){return ae(e)?e.value:e}const Hr={get:(e,t,n)=>_i(Reflect.get(e,t,n)),set:(e,t,n,i)=>{const o=e[t];return ae(o)&&!ae(n)?(o.value=n,!0):Reflect.set(e,t,n,i)}};function Xs(e){return gt(e)?e:new Proxy(e,Hr)}class $r{constructor(t){this.dep=void 0,this.__v_isRef=!0;const{get:n,set:i}=t(()=>yi(this),()=>bi(this));this._get=n,this._set=i}get value(){return this._get()}set value(t){this._set(t)}}function Dr(e){return new $r(e)}class Br{constructor(t,n,i){this._object=t,this._key=n,this._defaultValue=i,this.__v_isRef=!0}get value(){const t=this._object[this._key];return t===void 0?this._defaultValue:t}set value(t){this._object[this._key]=t}get dep(){return xr(Q(this._object),this._key)}}class Ur{constructor(t){this._getter=t,this.__v_isRef=!0,this.__v_isReadonly=!0}get value(){return this._getter()}}function Kr(e,t,n){return ae(e)?e:K(e)?new Ur(e):ee(e)&&arguments.length>1?Wr(e,t,n):ge(e)}function Wr(e,t,n){const i=e[t];return ae(i)?i:new Br(e,t,n)}class qr{constructor(t,n,i,o){this._setter=n,this.dep=void 0,this.__v_isRef=!0,this.__v_isReadonly=!1,this._dirty=!0,this.effect=new mi(t,()=>{this._dirty||(this._dirty=!0,bi(this))}),this.effect.computed=this,this.effect.active=this._cacheable=!o,this.__v_isReadonly=i}get value(){const t=Q(this);return yi(t),(t._dirty||!t._cacheable)&&(t._dirty=!1,t._value=t.effect.run()),t._value}set value(t){this._setter(t)}}function Vr(e,t,n=!1){let i,o;const s=K(e);return s?(i=e,o=Fe):(i=e.get,o=e.set),new qr(i,o,s||!o,n)}function ze(e,t,n,i){let o;try{o=i?e(...i):e()}catch(s){Tn(s,t,n)}return o}function Ee(e,t,n,i){if(K(e)){const s=ze(e,t,n,i);return s&&Ps(s)&&s.catch(r=>{Tn(r,t,n)}),s}const o=[];for(let s=0;s>>1,o=ue[i],s=Ht(o);sIe&&ue.splice(t,1)}function Xr(e){$(e)?xt.push(...e):(!Ne||!Ne.includes(e,e.allowRecurse?nt+1:nt))&&xt.push(e),Zs()}function Wi(e,t=Nt?Ie+1:0){for(;tHt(n)-Ht(i)),nt=0;nte.id==null?1/0:e.id,Qr=(e,t)=>{const n=Ht(e)-Ht(t);if(n===0){if(e.pre&&!t.pre)return-1;if(t.pre&&!e.pre)return 1}return n};function Gs(e){Qn=!1,Nt=!0,ue.sort(Qr);try{for(Ie=0;Iene(E)?E.trim():E)),d&&(o=n.map(sr))}let l,a=i[l=on(t)]||i[l=on(Me(t))];!a&&s&&(a=i[l=on(ct(t))]),a&&Ee(a,e,6,o);const f=i[l+"Once"];if(f){if(!e.emitted)e.emitted={};else if(e.emitted[l])return;e.emitted[l]=!0,Ee(f,e,6,o)}}function eo(e,t,n=!1){const i=t.emitsCache,o=i.get(e);if(o!==void 0)return o;const s=e.emits;let r={},l=!1;if(!K(e)){const a=f=>{const p=eo(f,t,!0);p&&(l=!0,re(r,p))};!n&&t.mixins.length&&t.mixins.forEach(a),e.extends&&a(e.extends),e.mixins&&e.mixins.forEach(a)}return!s&&!l?(ee(e)&&i.set(e,null),null):($(s)?s.forEach(a=>r[a]=null):re(r,s),ee(e)&&i.set(e,r),r)}function jn(e,t){return!e||!Ut(t)?!1:(t=t.slice(2).replace(/Once$/,""),X(e,t[0].toLowerCase()+t.slice(1))||X(e,ct(t))||X(e,t))}let pe=null,Sn=null;function pn(e){const t=pe;return pe=e,Sn=e&&e.type.__scopeId||null,t}function wc(e){Sn=e}function Cc(){Sn=null}function Gr(e,t=pe,n){if(!t||e._n)return e;const i=(...o)=>{i._d&&ns(-1);const s=pn(t);let r;try{r=e(...o)}finally{pn(s),i._d&&ns(1)}return r};return i._n=!0,i._c=!0,i._d=!0,i}function Hn(e){const{type:t,vnode:n,proxy:i,withProxy:o,props:s,propsOptions:[r],slots:l,attrs:a,emit:f,render:p,renderCache:d,data:y,setupState:E,ctx:F,inheritAttrs:O}=e;let H,q;const z=pn(e);try{if(n.shapeFlag&4){const m=o||i;H=Ae(p.call(m,m,d,s,E,y,F)),q=a}else{const m=t;H=Ae(m.length>1?m(s,{attrs:a,slots:l,emit:f}):m(s,null)),q=t.props?a:el(a)}}catch(m){Lt.length=0,Tn(m,e,1),H=fe(be)}let g=H;if(q&&O!==!1){const m=Object.keys(q),{shapeFlag:I}=g;m.length&&I&7&&(r&&m.some(ai)&&(q=tl(q,r)),g=Qe(g,q))}return n.dirs&&(g=Qe(g),g.dirs=g.dirs?g.dirs.concat(n.dirs):n.dirs),n.transition&&(g.transition=n.transition),H=g,pn(z),H}const el=e=>{let t;for(const n in e)(n==="class"||n==="style"||Ut(n))&&((t||(t={}))[n]=e[n]);return t},tl=(e,t)=>{const n={};for(const i in e)(!ai(i)||!(i.slice(9)in t))&&(n[i]=e[i]);return n};function nl(e,t,n){const{props:i,children:o,component:s}=e,{props:r,children:l,patchFlag:a}=t,f=s.emitsOptions;if(t.dirs||t.transition)return!0;if(n&&a>=0){if(a&1024)return!0;if(a&16)return i?qi(i,r,f):!!r;if(a&8){const p=t.dynamicProps;for(let d=0;de.__isSuspense;function io(e,t){t&&t.pendingBranch?$(e)?t.effects.push(...e):t.effects.push(e):Xr(e)}function so(e,t){return On(e,null,t)}function Ac(e,t){return On(e,null,{flush:"post"})}const Xt={};function Ye(e,t,n){return On(e,t,n)}function On(e,t,{immediate:n,deep:i,flush:o,onTrack:s,onTrigger:r}=te){var l;const a=ks()===((l=le)==null?void 0:l.scope)?le:null;let f,p=!1,d=!1;if(ae(e)?(f=()=>e.value,p=fn(e)):gt(e)?(f=()=>e,i=!0):$(e)?(d=!0,p=e.some(m=>gt(m)||fn(m)),f=()=>e.map(m=>{if(ae(m))return m.value;if(gt(m))return dt(m);if(K(m))return ze(m,a,2)})):K(e)?t?f=()=>ze(e,a,2):f=()=>{if(!(a&&a.isUnmounted))return y&&y(),Ee(e,a,3,[E])}:f=Fe,t&&i){const m=f;f=()=>dt(m())}let y,E=m=>{y=z.onStop=()=>{ze(m,a,4)}},F;if(Dt)if(E=Fe,t?n&&Ee(t,a,3,[f(),d?[]:void 0,E]):f(),o==="sync"){const m=Zl();F=m.__watcherHandles||(m.__watcherHandles=[])}else return Fe;let O=d?new Array(e.length).fill(Xt):Xt;const H=()=>{if(z.active)if(t){const m=z.run();(i||p||(d?m.some((I,U)=>at(I,O[U])):at(m,O)))&&(y&&y(),Ee(t,a,3,[m,O===Xt?void 0:d&&O[0]===Xt?[]:O,E]),O=m)}else z.run()};H.allowRecurse=!!t;let q;o==="sync"?q=H:o==="post"?q=()=>me(H,a&&a.suspense):(H.pre=!0,a&&(H.id=a.uid),q=()=>Ci(H));const z=new mi(f,q);t?n?H():O=z.run():o==="post"?me(z.run.bind(z),a&&a.suspense):z.run();const g=()=>{z.stop(),a&&a.scope&&ci(a.scope.effects,z)};return F&&F.push(g),g}function ol(e,t,n){const i=this.proxy,o=ne(e)?e.includes(".")?oo(i,e):()=>i[e]:e.bind(i,i);let s;K(t)?s=t:(s=t.handler,n=t);const r=le;wt(this);const l=On(o,s.bind(i),n);return r?wt(r):rt(),l}function oo(e,t){const n=t.split(".");return()=>{let i=e;for(let o=0;o{dt(n,t)});else if(Fs(e))for(const n in e)dt(e[n],t);return e}function Pe(e,t,n,i){const o=e.dirs,s=t&&t.dirs;for(let r=0;r{e.isMounted=!0}),uo(()=>{e.isUnmounting=!0}),e}const _e=[Function,Array],ro={mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:_e,onEnter:_e,onAfterEnter:_e,onEnterCancelled:_e,onBeforeLeave:_e,onLeave:_e,onAfterLeave:_e,onLeaveCancelled:_e,onBeforeAppear:_e,onAppear:_e,onAfterAppear:_e,onAppearCancelled:_e},ll={name:"BaseTransition",props:ro,setup(e,{slots:t}){const n=Mn(),i=rl();let o;return()=>{const s=t.default&&ao(t.default(),!0);if(!s||!s.length)return;let r=s[0];if(s.length>1){for(const O of s)if(O.type!==be){r=O;break}}const l=Q(e),{mode:a}=l;if(i.isLeaving)return $n(r);const f=zi(r);if(!f)return $n(r);const p=Zn(f,l,i,n);Gn(f,p);const d=n.subTree,y=d&&zi(d);let E=!1;const{getTransitionKey:F}=f.type;if(F){const O=F();o===void 0?o=O:O!==o&&(o=O,E=!0)}if(y&&y.type!==be&&(!it(f,y)||E)){const O=Zn(y,l,i,n);if(Gn(y,O),a==="out-in")return i.isLeaving=!0,O.afterLeave=()=>{i.isLeaving=!1,n.update.active!==!1&&n.update()},$n(r);a==="in-out"&&f.type!==be&&(O.delayLeave=(H,q,z)=>{const g=lo(i,y);g[String(y.key)]=y,H[We]=()=>{q(),H[We]=void 0,delete p.delayedLeave},p.delayedLeave=z})}return r}}},al=ll;function lo(e,t){const{leavingVNodes:n}=e;let i=n.get(t.type);return i||(i=Object.create(null),n.set(t.type,i)),i}function Zn(e,t,n,i){const{appear:o,mode:s,persisted:r=!1,onBeforeEnter:l,onEnter:a,onAfterEnter:f,onEnterCancelled:p,onBeforeLeave:d,onLeave:y,onAfterLeave:E,onLeaveCancelled:F,onBeforeAppear:O,onAppear:H,onAfterAppear:q,onAppearCancelled:z}=t,g=String(e.key),m=lo(n,e),I=(R,T)=>{R&&Ee(R,i,9,T)},U=(R,T)=>{const A=T[1];I(R,T),$(R)?R.every(W=>W.length<=1)&&A():R.length<=1&&A()},D={mode:s,persisted:r,beforeEnter(R){let T=l;if(!n.isMounted)if(o)T=O||l;else return;R[We]&&R[We](!0);const A=m[g];A&&it(e,A)&&A.el[We]&&A.el[We](),I(T,[R])},enter(R){let T=a,A=f,W=p;if(!n.isMounted)if(o)T=H||a,A=q||f,W=z||p;else return;let j=!1;const V=R[Qt]=oe=>{j||(j=!0,oe?I(W,[R]):I(A,[R]),D.delayedLeave&&D.delayedLeave(),R[Qt]=void 0)};T?U(T,[R,V]):V()},leave(R,T){const A=String(e.key);if(R[Qt]&&R[Qt](!0),n.isUnmounting)return T();I(d,[R]);let W=!1;const j=R[We]=V=>{W||(W=!0,T(),V?I(F,[R]):I(E,[R]),R[We]=void 0,m[A]===e&&delete m[A])};m[A]=e,y?U(y,[R,j]):j()},clone(R){return Zn(R,t,n,i)}};return D}function $n(e){if(Rn(e))return e=Qe(e),e.children=null,e}function zi(e){return Rn(e)?e.children?e.children[0]:void 0:e}function Gn(e,t){e.shapeFlag&6&&e.component?Gn(e.component.subTree,t):e.shapeFlag&128?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function ao(e,t=!1,n){let i=[],o=0;for(let s=0;s1)for(let s=0;s!!e.type.__asyncLoader,Rn=e=>e.type.__isKeepAlive;function cl(e,t){fo(e,"a",t)}function fl(e,t){fo(e,"da",t)}function fo(e,t,n=le){const i=e.__wdc||(e.__wdc=()=>{let o=n;for(;o;){if(o.isDeactivated)return;o=o.parent}return e()});if(Pn(t,i,n),n){let o=n.parent;for(;o&&o.parent;)Rn(o.parent.vnode)&&ul(i,t,n,o),o=o.parent}}function ul(e,t,n,i){const o=Pn(t,e,i,!0);In(()=>{ci(i[t],o)},n)}function Pn(e,t,n=le,i=!1){if(n){const o=n[e]||(n[e]=[]),s=t.__weh||(t.__weh=(...r)=>{if(n.isUnmounted)return;Et(),wt(n);const l=Ee(t,n,e,r);return rt(),Tt(),l});return i?o.unshift(s):o.push(s),s}}const $e=e=>(t,n=le)=>(!Dt||e==="sp")&&Pn(e,(...i)=>t(...i),n),pl=$e("bm"),At=$e("m"),dl=$e("bu"),ml=$e("u"),uo=$e("bum"),In=$e("um"),hl=$e("sp"),gl=$e("rtg"),xl=$e("rtc");function vl(e,t=le){Pn("ec",e,t)}function jc(e,t,n,i){let o;const s=n&&n[i];if($(e)||ne(e)){o=new Array(e.length);for(let r=0,l=e.length;rt(r,l,void 0,s&&s[l]));else{const r=Object.keys(e);o=new Array(r.length);for(let l=0,a=r.length;lgn(t)?!(t.type===be||t.type===he&&!po(t.children)):!0)?e:null}function Oc(e,t){const n={};for(const i in e)n[t&&/[A-Z]/.test(i)?`on:${i}`:on(i)]=e[i];return n}const ei=e=>e?Oo(e)?Oi(e)||e.proxy:ei(e.parent):null,Ft=re(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>ei(e.parent),$root:e=>ei(e.root),$emit:e=>e.emit,$options:e=>Ti(e),$forceUpdate:e=>e.f||(e.f=()=>Ci(e.update)),$nextTick:e=>e.n||(e.n=An.bind(e.proxy)),$watch:e=>ol.bind(e)}),Dn=(e,t)=>e!==te&&!e.__isScriptSetup&&X(e,t),yl={get({_:e},t){const{ctx:n,setupState:i,data:o,props:s,accessCache:r,type:l,appContext:a}=e;let f;if(t[0]!=="$"){const E=r[t];if(E!==void 0)switch(E){case 1:return i[t];case 2:return o[t];case 4:return n[t];case 3:return s[t]}else{if(Dn(i,t))return r[t]=1,i[t];if(o!==te&&X(o,t))return r[t]=2,o[t];if((f=e.propsOptions[0])&&X(f,t))return r[t]=3,s[t];if(n!==te&&X(n,t))return r[t]=4,n[t];ti&&(r[t]=0)}}const p=Ft[t];let d,y;if(p)return t==="$attrs"&&xe(e,"get",t),p(e);if((d=l.__cssModules)&&(d=d[t]))return d;if(n!==te&&X(n,t))return r[t]=4,n[t];if(y=a.config.globalProperties,X(y,t))return y[t]},set({_:e},t,n){const{data:i,setupState:o,ctx:s}=e;return Dn(o,t)?(o[t]=n,!0):i!==te&&X(i,t)?(i[t]=n,!0):X(e.props,t)||t[0]==="$"&&t.slice(1)in e?!1:(s[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:i,appContext:o,propsOptions:s}},r){let l;return!!n[r]||e!==te&&X(e,r)||Dn(t,r)||(l=s[0])&&X(l,r)||X(i,r)||X(Ft,r)||X(o.config.globalProperties,r)},defineProperty(e,t,n){return n.get!=null?e._.accessCache[t]=0:X(n,"value")&&this.set(e,t,n.value,null),Reflect.defineProperty(e,t,n)}};function Rc(){return bl().slots}function bl(){const e=Mn();return e.setupContext||(e.setupContext=Po(e))}function Yi(e){return $(e)?e.reduce((t,n)=>(t[n]=null,t),{}):e}let ti=!0;function _l(e){const t=Ti(e),n=e.proxy,i=e.ctx;ti=!1,t.beforeCreate&&Ji(t.beforeCreate,e,"bc");const{data:o,computed:s,methods:r,watch:l,provide:a,inject:f,created:p,beforeMount:d,mounted:y,beforeUpdate:E,updated:F,activated:O,deactivated:H,beforeDestroy:q,beforeUnmount:z,destroyed:g,unmounted:m,render:I,renderTracked:U,renderTriggered:D,errorCaptured:R,serverPrefetch:T,expose:A,inheritAttrs:W,components:j,directives:V,filters:oe}=t;if(f&&wl(f,i,null),r)for(const J in r){const N=r[J];K(N)&&(i[J]=N.bind(n))}if(o){const J=o.call(n,n);ee(J)&&(e.data=Cn(J))}if(ti=!0,s)for(const J in s){const N=s[J],Le=K(N)?N.bind(n,n):K(N.get)?N.get.bind(n,n):Fe,Kt=!K(N)&&K(N.set)?N.set.bind(n):Fe,Ze=se({get:Le,set:Kt});Object.defineProperty(i,J,{enumerable:!0,configurable:!0,get:()=>Ze.value,set:Oe=>Ze.value=Oe})}if(l)for(const J in l)mo(l[J],i,n,J);if(a){const J=K(a)?a.call(n):a;Reflect.ownKeys(J).forEach(N=>{Sl(N,J[N])})}p&&Ji(p,e,"c");function M(J,N){$(N)?N.forEach(Le=>J(Le.bind(n))):N&&J(N.bind(n))}if(M(pl,d),M(At,y),M(dl,E),M(ml,F),M(cl,O),M(fl,H),M(vl,R),M(xl,U),M(gl,D),M(uo,z),M(In,m),M(hl,T),$(A))if(A.length){const J=e.exposed||(e.exposed={});A.forEach(N=>{Object.defineProperty(J,N,{get:()=>n[N],set:Le=>n[N]=Le})})}else e.exposed||(e.exposed={});I&&e.render===Fe&&(e.render=I),W!=null&&(e.inheritAttrs=W),j&&(e.components=j),V&&(e.directives=V)}function wl(e,t,n=Fe){$(e)&&(e=ni(e));for(const i in e){const o=e[i];let s;ee(o)?"default"in o?s=yt(o.from||i,o.default,!0):s=yt(o.from||i):s=yt(o),ae(s)?Object.defineProperty(t,i,{enumerable:!0,configurable:!0,get:()=>s.value,set:r=>s.value=r}):t[i]=s}}function Ji(e,t,n){Ee($(e)?e.map(i=>i.bind(t.proxy)):e.bind(t.proxy),t,n)}function mo(e,t,n,i){const o=i.includes(".")?oo(n,i):()=>n[i];if(ne(e)){const s=t[e];K(s)&&Ye(o,s)}else if(K(e))Ye(o,e.bind(n));else if(ee(e))if($(e))e.forEach(s=>mo(s,t,n,i));else{const s=K(e.handler)?e.handler.bind(n):t[e.handler];K(s)&&Ye(o,s,e)}}function Ti(e){const t=e.type,{mixins:n,extends:i}=t,{mixins:o,optionsCache:s,config:{optionMergeStrategies:r}}=e.appContext,l=s.get(t);let a;return l?a=l:!o.length&&!n&&!i?a=t:(a={},o.length&&o.forEach(f=>dn(a,f,r,!0)),dn(a,t,r)),ee(t)&&s.set(t,a),a}function dn(e,t,n,i=!1){const{mixins:o,extends:s}=t;s&&dn(e,s,n,!0),o&&o.forEach(r=>dn(e,r,n,!0));for(const r in t)if(!(i&&r==="expose")){const l=Cl[r]||n&&n[r];e[r]=l?l(e[r],t[r]):t[r]}return e}const Cl={data:Xi,props:Qi,emits:Qi,methods:Rt,computed:Rt,beforeCreate:de,created:de,beforeMount:de,mounted:de,beforeUpdate:de,updated:de,beforeDestroy:de,beforeUnmount:de,destroyed:de,unmounted:de,activated:de,deactivated:de,errorCaptured:de,serverPrefetch:de,components:Rt,directives:Rt,watch:Tl,provide:Xi,inject:El};function Xi(e,t){return t?e?function(){return re(K(e)?e.call(this,this):e,K(t)?t.call(this,this):t)}:t:e}function El(e,t){return Rt(ni(e),ni(t))}function ni(e){if($(e)){const t={};for(let n=0;n1)return n&&K(t)?t.call(i&&i.proxy):t}}function Ol(e,t,n,i=!1){const o={},s={};an(s,Fn,1),e.propsDefaults=Object.create(null),go(e,t,o,s);for(const r in e.propsOptions[0])r in o||(o[r]=void 0);n?e.props=i?o:kr(o):e.type.props?e.props=o:e.props=s,e.attrs=s}function Rl(e,t,n,i){const{props:o,attrs:s,vnode:{patchFlag:r}}=e,l=Q(o),[a]=e.propsOptions;let f=!1;if((i||r>0)&&!(r&16)){if(r&8){const p=e.vnode.dynamicProps;for(let d=0;d{a=!0;const[y,E]=xo(d,t,!0);re(r,y),E&&l.push(...E)};!n&&t.mixins.length&&t.mixins.forEach(p),e.extends&&p(e.extends),e.mixins&&e.mixins.forEach(p)}if(!s&&!a)return ee(e)&&i.set(e,mt),mt;if($(s))for(let p=0;p-1,E[1]=O<0||F-1||X(E,"default"))&&l.push(d)}}}const f=[r,l];return ee(e)&&i.set(e,f),f}function Zi(e){return e[0]!=="$"}function Gi(e){const t=e&&e.toString().match(/^\s*(function|class) (\w+)/);return t?t[2]:e===null?"null":""}function es(e,t){return Gi(e)===Gi(t)}function ts(e,t){return $(t)?t.findIndex(n=>es(n,e)):K(t)&&es(t,e)?0:-1}const vo=e=>e[0]==="_"||e==="$stable",Ai=e=>$(e)?e.map(Ae):[Ae(e)],Pl=(e,t,n)=>{if(t._n)return t;const i=Gr((...o)=>Ai(t(...o)),n);return i._c=!1,i},yo=(e,t,n)=>{const i=e._ctx;for(const o in e){if(vo(o))continue;const s=e[o];if(K(s))t[o]=Pl(o,s,i);else if(s!=null){const r=Ai(s);t[o]=()=>r}}},bo=(e,t)=>{const n=Ai(t);e.slots.default=()=>n},Il=(e,t)=>{if(e.vnode.shapeFlag&32){const n=t._;n?(e.slots=Q(t),an(t,"_",n)):yo(t,e.slots={})}else e.slots={},t&&bo(e,t);an(e.slots,Fn,1)},Fl=(e,t,n)=>{const{vnode:i,slots:o}=e;let s=!0,r=te;if(i.shapeFlag&32){const l=t._;l?n&&l===1?s=!1:(re(o,t),!n&&l===1&&delete o._):(s=!t.$stable,yo(t,o)),r=t}else t&&(bo(e,t),r={default:1});if(s)for(const l in o)!vo(l)&&r[l]==null&&delete o[l]};function hn(e,t,n,i,o=!1){if($(e)){e.forEach((y,E)=>hn(y,t&&($(t)?t[E]:t),n,i,o));return}if(vt(i)&&!o)return;const s=i.shapeFlag&4?Oi(i.component)||i.component.proxy:i.el,r=o?null:s,{i:l,r:a}=e,f=t&&t.r,p=l.refs===te?l.refs={}:l.refs,d=l.setupState;if(f!=null&&f!==a&&(ne(f)?(p[f]=null,X(d,f)&&(d[f]=null)):ae(f)&&(f.value=null)),K(a))ze(a,l,12,[r,p]);else{const y=ne(a),E=ae(a);if(y||E){const F=()=>{if(e.f){const O=y?X(d,a)?d[a]:p[a]:a.value;o?$(O)&&ci(O,s):$(O)?O.includes(s)||O.push(s):y?(p[a]=[s],X(d,a)&&(d[a]=p[a])):(a.value=[s],e.k&&(p[e.k]=a.value))}else y?(p[a]=r,X(d,a)&&(d[a]=r)):E&&(a.value=r,e.k&&(p[e.k]=r))};r?(F.id=-1,me(F,n)):F()}}}let Be=!1;const Zt=e=>/svg/.test(e.namespaceURI)&&e.tagName!=="foreignObject",Gt=e=>e.nodeType===8;function Ml(e){const{mt:t,p:n,o:{patchProp:i,createText:o,nextSibling:s,parentNode:r,remove:l,insert:a,createComment:f}}=e,p=(g,m)=>{if(!m.hasChildNodes()){n(null,g,m),un(),m._vnode=g;return}Be=!1,d(m.firstChild,g,null,null,null),un(),m._vnode=g,Be&&console.error("Hydration completed but contains mismatches.")},d=(g,m,I,U,D,R=!1)=>{const T=Gt(g)&&g.data==="[",A=()=>O(g,m,I,U,D,T),{type:W,ref:j,shapeFlag:V,patchFlag:oe}=m;let ce=g.nodeType;m.el=g,oe===-2&&(R=!1,m.dynamicChildren=null);let M=null;switch(W){case _t:ce!==3?m.children===""?(a(m.el=o(""),r(g),g),M=g):M=A():(g.data!==m.children&&(Be=!0,g.data=m.children),M=s(g));break;case be:z(g)?(M=s(g),q(m.el=g.content.firstChild,g,I)):ce!==8||T?M=A():M=s(g);break;case Mt:if(T&&(g=s(g),ce=g.nodeType),ce===1||ce===3){M=g;const J=!m.children.length;for(let N=0;N{R=R||!!m.dynamicChildren;const{type:T,props:A,patchFlag:W,shapeFlag:j,dirs:V,transition:oe}=m,ce=T==="input"&&V||T==="option";if(ce||W!==-1){if(V&&Pe(m,null,I,"created"),A)if(ce||!R||W&48)for(const N in A)(ce&&N.endsWith("value")||Ut(N)&&!Pt(N))&&i(g,N,null,A[N],!1,void 0,I);else A.onClick&&i(g,"onClick",null,A.onClick,!1,void 0,I);let M;(M=A&&A.onVnodeBeforeMount)&&we(M,I,m);let J=!1;if(z(g)){J=_o(U,oe)&&I&&I.vnode.props&&I.vnode.props.appear;const N=g.content.firstChild;J&&oe.beforeEnter(N),q(N,g,I),m.el=g=N}if(V&&Pe(m,null,I,"beforeMount"),((M=A&&A.onVnodeMounted)||V||J)&&io(()=>{M&&we(M,I,m),J&&oe.enter(g),V&&Pe(m,null,I,"mounted")},U),j&16&&!(A&&(A.innerHTML||A.textContent))){let N=E(g.firstChild,m,g,I,U,D,R);for(;N;){Be=!0;const Le=N;N=N.nextSibling,l(Le)}}else j&8&&g.textContent!==m.children&&(Be=!0,g.textContent=m.children)}return g.nextSibling},E=(g,m,I,U,D,R,T)=>{T=T||!!m.dynamicChildren;const A=m.children,W=A.length;for(let j=0;j{const{slotScopeIds:T}=m;T&&(D=D?D.concat(T):T);const A=r(g),W=E(s(g),m,A,I,U,D,R);return W&&Gt(W)&&W.data==="]"?s(m.anchor=W):(Be=!0,a(m.anchor=f("]"),A,W),W)},O=(g,m,I,U,D,R)=>{if(Be=!0,m.el=null,R){const W=H(g);for(;;){const j=s(g);if(j&&j!==W)l(j);else break}}const T=s(g),A=r(g);return l(g),n(null,m,A,T,I,U,Zt(A),D),T},H=(g,m="[",I="]")=>{let U=0;for(;g;)if(g=s(g),g&&Gt(g)&&(g.data===m&&U++,g.data===I)){if(U===0)return s(g);U--}return g},q=(g,m,I)=>{const U=m.parentNode;U&&U.replaceChild(g,m);let D=I;for(;D;)D.vnode.el===m&&(D.vnode.el=D.subTree.el=g),D=D.parent},z=g=>g.nodeType===1&&g.tagName.toLowerCase()==="template";return[p,d]}const me=io;function Ll(e){return kl(e,Ml)}function kl(e,t){const n=zn();n.__VUE__=!0;const{insert:i,remove:o,patchProp:s,createElement:r,createText:l,createComment:a,setText:f,setElementText:p,parentNode:d,nextSibling:y,setScopeId:E=Fe,insertStaticContent:F}=e,O=(c,u,h,x=null,v=null,w=null,S=!1,_=null,C=!!u.dynamicChildren)=>{if(c===u)return;c&&!it(c,u)&&(x=Wt(c),Oe(c,v,w,!0),c=null),u.patchFlag===-2&&(C=!1,u.dynamicChildren=null);const{type:b,ref:L,shapeFlag:P}=u;switch(b){case _t:H(c,u,h,x);break;case be:q(c,u,h,x);break;case Mt:c==null&&z(u,h,x,S);break;case he:j(c,u,h,x,v,w,S,_,C);break;default:P&1?I(c,u,h,x,v,w,S,_,C):P&6?V(c,u,h,x,v,w,S,_,C):(P&64||P&128)&&b.process(c,u,h,x,v,w,S,_,C,ft)}L!=null&&v&&hn(L,c&&c.ref,w,u||c,!u)},H=(c,u,h,x)=>{if(c==null)i(u.el=l(u.children),h,x);else{const v=u.el=c.el;u.children!==c.children&&f(v,u.children)}},q=(c,u,h,x)=>{c==null?i(u.el=a(u.children||""),h,x):u.el=c.el},z=(c,u,h,x)=>{[c.el,c.anchor]=F(c.children,u,h,x,c.el,c.anchor)},g=({el:c,anchor:u},h,x)=>{let v;for(;c&&c!==u;)v=y(c),i(c,h,x),c=v;i(u,h,x)},m=({el:c,anchor:u})=>{let h;for(;c&&c!==u;)h=y(c),o(c),c=h;o(u)},I=(c,u,h,x,v,w,S,_,C)=>{S=S||u.type==="svg",c==null?U(u,h,x,v,w,S,_,C):T(c,u,v,w,S,_,C)},U=(c,u,h,x,v,w,S,_)=>{let C,b;const{type:L,props:P,shapeFlag:k,transition:B,dirs:Y}=c;if(C=c.el=r(c.type,w,P&&P.is,P),k&8?p(C,c.children):k&16&&R(c.children,C,null,x,v,w&&L!=="foreignObject",S,_),Y&&Pe(c,null,x,"created"),D(C,c,c.scopeId,S,x),P){for(const Z in P)Z!=="value"&&!Pt(Z)&&s(C,Z,null,P[Z],w,c.children,x,v,ke);"value"in P&&s(C,"value",null,P.value),(b=P.onVnodeBeforeMount)&&we(b,x,c)}Y&&Pe(c,null,x,"beforeMount");const G=_o(v,B);G&&B.beforeEnter(C),i(C,u,h),((b=P&&P.onVnodeMounted)||G||Y)&&me(()=>{b&&we(b,x,c),G&&B.enter(C),Y&&Pe(c,null,x,"mounted")},v)},D=(c,u,h,x,v)=>{if(h&&E(c,h),x)for(let w=0;w{for(let b=C;b{const _=u.el=c.el;let{patchFlag:C,dynamicChildren:b,dirs:L}=u;C|=c.patchFlag&16;const P=c.props||te,k=u.props||te;let B;h&&Ge(h,!1),(B=k.onVnodeBeforeUpdate)&&we(B,h,u,c),L&&Pe(u,c,h,"beforeUpdate"),h&&Ge(h,!0);const Y=v&&u.type!=="foreignObject";if(b?A(c.dynamicChildren,b,_,h,x,Y,w):S||N(c,u,_,null,h,x,Y,w,!1),C>0){if(C&16)W(_,u,P,k,h,x,v);else if(C&2&&P.class!==k.class&&s(_,"class",null,k.class,v),C&4&&s(_,"style",P.style,k.style,v),C&8){const G=u.dynamicProps;for(let Z=0;Z{B&&we(B,h,u,c),L&&Pe(u,c,h,"updated")},x)},A=(c,u,h,x,v,w,S)=>{for(let _=0;_{if(h!==x){if(h!==te)for(const _ in h)!Pt(_)&&!(_ in x)&&s(c,_,h[_],null,S,u.children,v,w,ke);for(const _ in x){if(Pt(_))continue;const C=x[_],b=h[_];C!==b&&_!=="value"&&s(c,_,b,C,S,u.children,v,w,ke)}"value"in x&&s(c,"value",h.value,x.value)}},j=(c,u,h,x,v,w,S,_,C)=>{const b=u.el=c?c.el:l(""),L=u.anchor=c?c.anchor:l("");let{patchFlag:P,dynamicChildren:k,slotScopeIds:B}=u;B&&(_=_?_.concat(B):B),c==null?(i(b,h,x),i(L,h,x),R(u.children,h,L,v,w,S,_,C)):P>0&&P&64&&k&&c.dynamicChildren?(A(c.dynamicChildren,k,h,v,w,S,_),(u.key!=null||v&&u===v.subTree)&&wo(c,u,!0)):N(c,u,h,L,v,w,S,_,C)},V=(c,u,h,x,v,w,S,_,C)=>{u.slotScopeIds=_,c==null?u.shapeFlag&512?v.ctx.activate(u,h,x,S,C):oe(u,h,x,v,w,S,C):ce(c,u,C)},oe=(c,u,h,x,v,w,S)=>{const _=c.component=ql(c,x,v);if(Rn(c)&&(_.ctx.renderer=ft),Vl(_),_.asyncDep){if(v&&v.registerDep(_,M),!c.el){const C=_.subTree=fe(be);q(null,C,u,h)}return}M(_,c,u,h,v,w,S)},ce=(c,u,h)=>{const x=u.component=c.component;if(nl(c,u,h))if(x.asyncDep&&!x.asyncResolved){J(x,u,h);return}else x.next=u,Jr(x.update),x.update();else u.el=c.el,x.vnode=u},M=(c,u,h,x,v,w,S)=>{const _=()=>{if(c.isMounted){let{next:L,bu:P,u:k,parent:B,vnode:Y}=c,G=L,Z;Ge(c,!1),L?(L.el=Y.el,J(c,L,S)):L=Y,P&&Nn(P),(Z=L.props&&L.props.onVnodeBeforeUpdate)&&we(Z,B,L,Y),Ge(c,!0);const ie=Hn(c),Te=c.subTree;c.subTree=ie,O(Te,ie,d(Te.el),Wt(Te),c,v,w),L.el=ie.el,G===null&&il(c,ie.el),k&&me(k,v),(Z=L.props&&L.props.onVnodeUpdated)&&me(()=>we(Z,B,L,Y),v)}else{let L;const{el:P,props:k}=u,{bm:B,m:Y,parent:G}=c,Z=vt(u);if(Ge(c,!1),B&&Nn(B),!Z&&(L=k&&k.onVnodeBeforeMount)&&we(L,G,u),Ge(c,!0),P&&kn){const ie=()=>{c.subTree=Hn(c),kn(P,c.subTree,c,v,null)};Z?u.type.__asyncLoader().then(()=>!c.isUnmounted&&ie()):ie()}else{const ie=c.subTree=Hn(c);O(null,ie,h,x,c,v,w),u.el=ie.el}if(Y&&me(Y,v),!Z&&(L=k&&k.onVnodeMounted)){const ie=u;me(()=>we(L,G,ie),v)}(u.shapeFlag&256||G&&vt(G.vnode)&&G.vnode.shapeFlag&256)&&c.a&&me(c.a,v),c.isMounted=!0,u=h=x=null}},C=c.effect=new mi(_,()=>Ci(b),c.scope),b=c.update=()=>C.run();b.id=c.uid,Ge(c,!0),b()},J=(c,u,h)=>{u.component=c;const x=c.vnode.props;c.vnode=u,c.next=null,Rl(c,u.props,x,h),Fl(c,u.children,h),Et(),Wi(),Tt()},N=(c,u,h,x,v,w,S,_,C=!1)=>{const b=c&&c.children,L=c?c.shapeFlag:0,P=u.children,{patchFlag:k,shapeFlag:B}=u;if(k>0){if(k&128){Kt(b,P,h,x,v,w,S,_,C);return}else if(k&256){Le(b,P,h,x,v,w,S,_,C);return}}B&8?(L&16&&ke(b,v,w),P!==b&&p(h,P)):L&16?B&16?Kt(b,P,h,x,v,w,S,_,C):ke(b,v,w,!0):(L&8&&p(h,""),B&16&&R(P,h,x,v,w,S,_,C))},Le=(c,u,h,x,v,w,S,_,C)=>{c=c||mt,u=u||mt;const b=c.length,L=u.length,P=Math.min(b,L);let k;for(k=0;kL?ke(c,v,w,!0,!1,P):R(u,h,x,v,w,S,_,C,P)},Kt=(c,u,h,x,v,w,S,_,C)=>{let b=0;const L=u.length;let P=c.length-1,k=L-1;for(;b<=P&&b<=k;){const B=c[b],Y=u[b]=C?qe(u[b]):Ae(u[b]);if(it(B,Y))O(B,Y,h,null,v,w,S,_,C);else break;b++}for(;b<=P&&b<=k;){const B=c[P],Y=u[k]=C?qe(u[k]):Ae(u[k]);if(it(B,Y))O(B,Y,h,null,v,w,S,_,C);else break;P--,k--}if(b>P){if(b<=k){const B=k+1,Y=Bk)for(;b<=P;)Oe(c[b],v,w,!0),b++;else{const B=b,Y=b,G=new Map;for(b=Y;b<=k;b++){const ve=u[b]=C?qe(u[b]):Ae(u[b]);ve.key!=null&&G.set(ve.key,b)}let Z,ie=0;const Te=k-Y+1;let ut=!1,Fi=0;const jt=new Array(Te);for(b=0;b=Te){Oe(ve,v,w,!0);continue}let Re;if(ve.key!=null)Re=G.get(ve.key);else for(Z=Y;Z<=k;Z++)if(jt[Z-Y]===0&&it(ve,u[Z])){Re=Z;break}Re===void 0?Oe(ve,v,w,!0):(jt[Re-Y]=b+1,Re>=Fi?Fi=Re:ut=!0,O(ve,u[Re],h,null,v,w,S,_,C),ie++)}const Mi=ut?Nl(jt):mt;for(Z=Mi.length-1,b=Te-1;b>=0;b--){const ve=Y+b,Re=u[ve],Li=ve+1{const{el:w,type:S,transition:_,children:C,shapeFlag:b}=c;if(b&6){Ze(c.component.subTree,u,h,x);return}if(b&128){c.suspense.move(u,h,x);return}if(b&64){S.move(c,u,h,ft);return}if(S===he){i(w,u,h);for(let P=0;P_.enter(w),v);else{const{leave:P,delayLeave:k,afterLeave:B}=_,Y=()=>i(w,u,h),G=()=>{P(w,()=>{Y(),B&&B()})};k?k(w,Y,G):G()}else i(w,u,h)},Oe=(c,u,h,x=!1,v=!1)=>{const{type:w,props:S,ref:_,children:C,dynamicChildren:b,shapeFlag:L,patchFlag:P,dirs:k}=c;if(_!=null&&hn(_,null,h,c,!0),L&256){u.ctx.deactivate(c);return}const B=L&1&&k,Y=!vt(c);let G;if(Y&&(G=S&&S.onVnodeBeforeUnmount)&&we(G,u,c),L&6)Qo(c.component,h,x);else{if(L&128){c.suspense.unmount(h,x);return}B&&Pe(c,null,u,"beforeUnmount"),L&64?c.type.remove(c,u,h,v,ft,x):b&&(w!==he||P>0&&P&64)?ke(b,u,h,!1,!0):(w===he&&P&384||!v&&L&16)&&ke(C,u,h),x&&Pi(c)}(Y&&(G=S&&S.onVnodeUnmounted)||B)&&me(()=>{G&&we(G,u,c),B&&Pe(c,null,u,"unmounted")},h)},Pi=c=>{const{type:u,el:h,anchor:x,transition:v}=c;if(u===he){Xo(h,x);return}if(u===Mt){m(c);return}const w=()=>{o(h),v&&!v.persisted&&v.afterLeave&&v.afterLeave()};if(c.shapeFlag&1&&v&&!v.persisted){const{leave:S,delayLeave:_}=v,C=()=>S(h,w);_?_(c.el,w,C):C()}else w()},Xo=(c,u)=>{let h;for(;c!==u;)h=y(c),o(c),c=h;o(u)},Qo=(c,u,h)=>{const{bum:x,scope:v,update:w,subTree:S,um:_}=c;x&&Nn(x),v.stop(),w&&(w.active=!1,Oe(S,c,u,h)),_&&me(_,u),me(()=>{c.isUnmounted=!0},u),u&&u.pendingBranch&&!u.isUnmounted&&c.asyncDep&&!c.asyncResolved&&c.suspenseId===u.pendingId&&(u.deps--,u.deps===0&&u.resolve())},ke=(c,u,h,x=!1,v=!1,w=0)=>{for(let S=w;Sc.shapeFlag&6?Wt(c.component.subTree):c.shapeFlag&128?c.suspense.next():y(c.anchor||c.el),Ii=(c,u,h)=>{c==null?u._vnode&&Oe(u._vnode,null,null,!0):O(u._vnode||null,c,u,null,null,null,h),Wi(),un(),u._vnode=c},ft={p:O,um:Oe,m:Ze,r:Pi,mt:oe,mc:R,pc:N,pbc:A,n:Wt,o:e};let Ln,kn;return t&&([Ln,kn]=t(ft)),{render:Ii,hydrate:Ln,createApp:jl(Ii,Ln)}}function Ge({effect:e,update:t},n){e.allowRecurse=t.allowRecurse=n}function _o(e,t){return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}function wo(e,t,n=!1){const i=e.children,o=t.children;if($(i)&&$(o))for(let s=0;s>1,e[n[l]]0&&(t[i]=n[s-1]),n[s]=i)}}for(s=n.length,r=n[s-1];s-- >0;)n[s]=r,r=t[r];return n}const Hl=e=>e.__isTeleport,he=Symbol.for("v-fgt"),_t=Symbol.for("v-txt"),be=Symbol.for("v-cmt"),Mt=Symbol.for("v-stc"),Lt=[];let Se=null;function Co(e=!1){Lt.push(Se=e?null:[])}function $l(){Lt.pop(),Se=Lt[Lt.length-1]||null}let $t=1;function ns(e){$t+=e}function Eo(e){return e.dynamicChildren=$t>0?Se||mt:null,$l(),$t>0&&Se&&Se.push(e),e}function Pc(e,t,n,i,o,s){return Eo(jo(e,t,n,i,o,s,!0))}function To(e,t,n,i,o){return Eo(fe(e,t,n,i,o,!0))}function gn(e){return e?e.__v_isVNode===!0:!1}function it(e,t){return e.type===t.type&&e.key===t.key}const Fn="__vInternal",Ao=({key:e})=>e??null,rn=({ref:e,ref_key:t,ref_for:n})=>(typeof e=="number"&&(e=""+e),e!=null?ne(e)||ae(e)||K(e)?{i:pe,r:e,k:t,f:!!n}:e:null);function jo(e,t=null,n=null,i=0,o=null,s=e===he?0:1,r=!1,l=!1){const a={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&Ao(t),ref:t&&rn(t),scopeId:Sn,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetAnchor:null,staticCount:0,shapeFlag:s,patchFlag:i,dynamicProps:o,dynamicChildren:null,appContext:null,ctx:pe};return l?(ji(a,n),s&128&&e.normalize(a)):n&&(a.shapeFlag|=ne(n)?8:16),$t>0&&!r&&Se&&(a.patchFlag>0||s&6)&&a.patchFlag!==32&&Se.push(a),a}const fe=Dl;function Dl(e,t=null,n=null,i=0,o=null,s=!1){if((!e||e===to)&&(e=be),gn(e)){const l=Qe(e,t,!0);return n&&ji(l,n),$t>0&&!s&&Se&&(l.shapeFlag&6?Se[Se.indexOf(e)]=l:Se.push(l)),l.patchFlag|=-2,l}if(Xl(e)&&(e=e.__vccOpts),t){t=Bl(t);let{class:l,style:a}=t;l&&!ne(l)&&(t.class=pi(l)),ee(a)&&(zs(a)&&!$(a)&&(a=re({},a)),t.style=ui(a))}const r=ne(e)?1:sl(e)?128:Hl(e)?64:ee(e)?4:K(e)?2:0;return jo(e,t,n,i,o,r,s,!0)}function Bl(e){return e?zs(e)||Fn in e?re({},e):e:null}function Qe(e,t,n=!1){const{props:i,ref:o,patchFlag:s,children:r}=e,l=t?Ul(i||{},t):i;return{__v_isVNode:!0,__v_skip:!0,type:e.type,props:l,key:l&&Ao(l),ref:t&&t.ref?n&&o?$(o)?o.concat(rn(t)):[o,rn(t)]:rn(t):o,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:r,target:e.target,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==he?s===-1?16:s|16:s,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:e.transition,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&Qe(e.ssContent),ssFallback:e.ssFallback&&Qe(e.ssFallback),el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce}}function So(e=" ",t=0){return fe(_t,null,e,t)}function Ic(e,t){const n=fe(Mt,null,e);return n.staticCount=t,n}function Fc(e="",t=!1){return t?(Co(),To(be,null,e)):fe(be,null,e)}function Ae(e){return e==null||typeof e=="boolean"?fe(be):$(e)?fe(he,null,e.slice()):typeof e=="object"?qe(e):fe(_t,null,String(e))}function qe(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:Qe(e)}function ji(e,t){let n=0;const{shapeFlag:i}=e;if(t==null)t=null;else if($(t))n=16;else if(typeof t=="object")if(i&65){const o=t.default;o&&(o._c&&(o._d=!1),ji(e,o()),o._c&&(o._d=!0));return}else{n=32;const o=t._;!o&&!(Fn in t)?t._ctx=pe:o===3&&pe&&(pe.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else K(t)?(t={default:t,_ctx:pe},n=32):(t=String(t),i&64?(n=16,t=[So(t)]):n=8);e.children=t,e.shapeFlag|=n}function Ul(...e){const t={};for(let n=0;nle||pe;let Si,pt,is="__VUE_INSTANCE_SETTERS__";(pt=zn()[is])||(pt=zn()[is]=[]),pt.push(e=>le=e),Si=e=>{pt.length>1?pt.forEach(t=>t(e)):pt[0](e)};const wt=e=>{Si(e),e.scope.on()},rt=()=>{le&&le.scope.off(),Si(null)};function Oo(e){return e.vnode.shapeFlag&4}let Dt=!1;function Vl(e,t=!1){Dt=t;const{props:n,children:i}=e.vnode,o=Oo(e);Ol(e,n,o,t),Il(e,i);const s=o?zl(e,t):void 0;return Dt=!1,s}function zl(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=It(new Proxy(e.ctx,yl));const{setup:i}=n;if(i){const o=e.setupContext=i.length>1?Po(e):null;wt(e),Et();const s=ze(i,e,0,[e.props,o]);if(Tt(),rt(),Ps(s)){if(s.then(rt,rt),t)return s.then(r=>{ss(e,r,t)}).catch(r=>{Tn(r,e,0)});e.asyncDep=s}else ss(e,s,t)}else Ro(e,t)}function ss(e,t,n){K(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:ee(t)&&(e.setupState=Xs(t)),Ro(e,n)}let os;function Ro(e,t,n){const i=e.type;if(!e.render){if(!t&&os&&!i.render){const o=i.template||Ti(e).template;if(o){const{isCustomElement:s,compilerOptions:r}=e.appContext.config,{delimiters:l,compilerOptions:a}=i,f=re(re({isCustomElement:s,delimiters:l},r),a);i.render=os(o,f)}}e.render=i.render||Fe}{wt(e),Et();try{_l(e)}finally{Tt(),rt()}}}function Yl(e){return e.attrsProxy||(e.attrsProxy=new Proxy(e.attrs,{get(t,n){return xe(e,"get","$attrs"),t[n]}}))}function Po(e){const t=n=>{e.exposed=n||{}};return{get attrs(){return Yl(e)},slots:e.slots,emit:e.emit,expose:t}}function Oi(e){if(e.exposed)return e.exposeProxy||(e.exposeProxy=new Proxy(Xs(It(e.exposed)),{get(t,n){if(n in t)return t[n];if(n in Ft)return Ft[n](e)},has(t,n){return n in t||n in Ft}}))}function Jl(e,t=!0){return K(e)?e.displayName||e.name:e.name||t&&e.__name}function Xl(e){return K(e)&&"__vccOpts"in e}const se=(e,t)=>Vr(e,t,Dt);function si(e,t,n){const i=arguments.length;return i===2?ee(t)&&!$(t)?gn(t)?fe(e,null,[t]):fe(e,t):fe(e,null,t):(i>3?n=Array.prototype.slice.call(arguments,2):i===3&&gn(n)&&(n=[n]),fe(e,t,n))}const Ql=Symbol.for("v-scx"),Zl=()=>yt(Ql),Gl="3.3.8",ea="http://www.w3.org/2000/svg",st=typeof document<"u"?document:null,rs=st&&st.createElement("template"),ta={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,i)=>{const o=t?st.createElementNS(ea,e):st.createElement(e,n?{is:n}:void 0);return e==="select"&&i&&i.multiple!=null&&o.setAttribute("multiple",i.multiple),o},createText:e=>st.createTextNode(e),createComment:e=>st.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>st.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,i,o,s){const r=n?n.previousSibling:t.lastChild;if(o&&(o===s||o.nextSibling))for(;t.insertBefore(o.cloneNode(!0),n),!(o===s||!(o=o.nextSibling)););else{rs.innerHTML=i?`${e}`:e;const l=rs.content;if(i){const a=l.firstChild;for(;a.firstChild;)l.appendChild(a.firstChild);l.removeChild(a)}t.insertBefore(l,n)}return[r?r.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}},Ue="transition",St="animation",Bt=Symbol("_vtc"),Io=(e,{slots:t})=>si(al,na(e),t);Io.displayName="Transition";const Fo={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String};Io.props=re({},ro,Fo);const et=(e,t=[])=>{$(e)?e.forEach(n=>n(...t)):e&&e(...t)},ls=e=>e?$(e)?e.some(t=>t.length>1):e.length>1:!1;function na(e){const t={};for(const j in e)j in Fo||(t[j]=e[j]);if(e.css===!1)return t;const{name:n="v",type:i,duration:o,enterFromClass:s=`${n}-enter-from`,enterActiveClass:r=`${n}-enter-active`,enterToClass:l=`${n}-enter-to`,appearFromClass:a=s,appearActiveClass:f=r,appearToClass:p=l,leaveFromClass:d=`${n}-leave-from`,leaveActiveClass:y=`${n}-leave-active`,leaveToClass:E=`${n}-leave-to`}=e,F=ia(o),O=F&&F[0],H=F&&F[1],{onBeforeEnter:q,onEnter:z,onEnterCancelled:g,onLeave:m,onLeaveCancelled:I,onBeforeAppear:U=q,onAppear:D=z,onAppearCancelled:R=g}=t,T=(j,V,oe)=>{tt(j,V?p:l),tt(j,V?f:r),oe&&oe()},A=(j,V)=>{j._isLeaving=!1,tt(j,d),tt(j,E),tt(j,y),V&&V()},W=j=>(V,oe)=>{const ce=j?D:z,M=()=>T(V,j,oe);et(ce,[V,M]),as(()=>{tt(V,j?a:s),Ke(V,j?p:l),ls(ce)||cs(V,i,O,M)})};return re(t,{onBeforeEnter(j){et(q,[j]),Ke(j,s),Ke(j,r)},onBeforeAppear(j){et(U,[j]),Ke(j,a),Ke(j,f)},onEnter:W(!1),onAppear:W(!0),onLeave(j,V){j._isLeaving=!0;const oe=()=>A(j,V);Ke(j,d),ra(),Ke(j,y),as(()=>{j._isLeaving&&(tt(j,d),Ke(j,E),ls(m)||cs(j,i,H,oe))}),et(m,[j,oe])},onEnterCancelled(j){T(j,!1),et(g,[j])},onAppearCancelled(j){T(j,!0),et(R,[j])},onLeaveCancelled(j){A(j),et(I,[j])}})}function ia(e){if(e==null)return null;if(ee(e))return[Bn(e.enter),Bn(e.leave)];{const t=Bn(e);return[t,t]}}function Bn(e){return or(e)}function Ke(e,t){t.split(/\s+/).forEach(n=>n&&e.classList.add(n)),(e[Bt]||(e[Bt]=new Set)).add(t)}function tt(e,t){t.split(/\s+/).forEach(i=>i&&e.classList.remove(i));const n=e[Bt];n&&(n.delete(t),n.size||(e[Bt]=void 0))}function as(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}let sa=0;function cs(e,t,n,i){const o=e._endId=++sa,s=()=>{o===e._endId&&i()};if(n)return setTimeout(s,n);const{type:r,timeout:l,propCount:a}=oa(e,t);if(!r)return i();const f=r+"end";let p=0;const d=()=>{e.removeEventListener(f,y),s()},y=E=>{E.target===e&&++p>=a&&d()};setTimeout(()=>{p(n[F]||"").split(", "),o=i(`${Ue}Delay`),s=i(`${Ue}Duration`),r=fs(o,s),l=i(`${St}Delay`),a=i(`${St}Duration`),f=fs(l,a);let p=null,d=0,y=0;t===Ue?r>0&&(p=Ue,d=r,y=s.length):t===St?f>0&&(p=St,d=f,y=a.length):(d=Math.max(r,f),p=d>0?r>f?Ue:St:null,y=p?p===Ue?s.length:a.length:0);const E=p===Ue&&/\b(transform|all)(,|$)/.test(i(`${Ue}Property`).toString());return{type:p,timeout:d,propCount:y,hasTransform:E}}function fs(e,t){for(;e.lengthus(n)+us(e[i])))}function us(e){return e==="auto"?0:Number(e.slice(0,-1).replace(",","."))*1e3}function ra(){return document.body.offsetHeight}function la(e,t,n){const i=e[Bt];i&&(t=(t?[t,...i]:[...i]).join(" ")),t==null?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}const aa=Symbol("_vod");function ca(e,t,n){const i=e.style,o=ne(n);if(n&&!o){if(t&&!ne(t))for(const s in t)n[s]==null&&oi(i,s,"");for(const s in n)oi(i,s,n[s])}else{const s=i.display;o?t!==n&&(i.cssText=n):t&&e.removeAttribute("style"),aa in e&&(i.display=s)}}const ps=/\s*!important$/;function oi(e,t,n){if($(n))n.forEach(i=>oi(e,t,i));else if(n==null&&(n=""),t.startsWith("--"))e.setProperty(t,n);else{const i=fa(e,t);ps.test(n)?e.setProperty(ct(i),n.replace(ps,""),"important"):e[i]=n}}const ds=["Webkit","Moz","ms"],Un={};function fa(e,t){const n=Un[t];if(n)return n;let i=Me(t);if(i!=="filter"&&i in e)return Un[t]=i;i=_n(i);for(let o=0;oKn||(xa.then(()=>Kn=0),Kn=Date.now());function ya(e,t){const n=i=>{if(!i._vts)i._vts=Date.now();else if(i._vts<=n.attached)return;Ee(ba(i,n.value),t,5,[i])};return n.value=e,n.attached=va(),n}function ba(e,t){if($(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map(i=>o=>!o._stopped&&i&&i(o))}else return t}const xs=/^on[a-z]/,_a=(e,t,n,i,o=!1,s,r,l,a)=>{t==="class"?la(e,i,o):t==="style"?ca(e,n,i):Ut(t)?ai(t)||ha(e,t,n,i,r):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):wa(e,t,i,o))?pa(e,t,i,s,r,l,a):(t==="true-value"?e._trueValue=i:t==="false-value"&&(e._falseValue=i),ua(e,t,i,o))};function wa(e,t,n,i){return i?!!(t==="innerHTML"||t==="textContent"||t in e&&xs.test(t)&&K(n)):t==="spellcheck"||t==="draggable"||t==="translate"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA"||xs.test(t)&&ne(n)?!1:t in e}const Ca=["ctrl","shift","alt","meta"],Ea={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&e.button!==0,middle:e=>"button"in e&&e.button!==1,right:e=>"button"in e&&e.button!==2,exact:(e,t)=>Ca.some(n=>e[`${n}Key`]&&!t.includes(n))},Mc=(e,t)=>(n,...i)=>{for(let o=0;on=>{if(!("key"in n))return;const i=ct(n.key);if(t.some(o=>o===i||Ta[o]===i))return e(n)},Aa=re({patchProp:_a},ta);let Wn,vs=!1;function ja(){return Wn=vs?Wn:Ll(Aa),vs=!0,Wn}const kc=(...e)=>{const t=ja().createApp(...e),{mount:n}=t;return t.mount=i=>{const o=Sa(i);if(o)return n(o,!0,o instanceof SVGElement)},t};function Sa(e){return ne(e)?document.querySelector(e):e}const Nc=(e,t)=>{const n=e.__vccOpts||e;for(const[i,o]of t)n[i]=o;return n},Oa=window.__VP_SITE_DATA__;function Ri(e){return ks()?(mr(e),!0):!1}function Je(e){return typeof e=="function"?e():_i(e)}function Hc(e,t){const n=(t==null?void 0:t.computedGetter)===!1?_i:Je;return function(...i){return se(()=>e.apply(this,i.map(o=>n(o))))}}const Mo=typeof window<"u"&&typeof document<"u";typeof WorkerGlobalScope<"u"&&globalThis instanceof WorkerGlobalScope;const Ra=Object.prototype.toString,Pa=e=>Ra.call(e)==="[object Object]",Lo=()=>{},ys=Ia();function Ia(){var e;return Mo&&((e=window==null?void 0:window.navigator)==null?void 0:e.userAgent)&&/iP(ad|hone|od)/.test(window.navigator.userAgent)}function Fa(e,t){function n(...i){return new Promise((o,s)=>{Promise.resolve(e(()=>t.apply(this,i),{fn:t,thisArg:this,args:i})).then(o).catch(s)})}return n}const ko=e=>e();function Ma(e=ko){const t=ge(!0);function n(){t.value=!1}function i(){t.value=!0}const o=(...s)=>{t.value&&e(...s)};return{isActive:En(t),pause:n,resume:i,eventFilter:o}}function No(...e){if(e.length!==1)return Kr(...e);const t=e[0];return typeof t=="function"?En(Dr(()=>({get:t,set:Lo}))):ge(t)}function La(e,t,n={}){const{eventFilter:i=ko,...o}=n;return Ye(e,Fa(i,t),o)}function ka(e,t,n={}){const{eventFilter:i,...o}=n,{eventFilter:s,pause:r,resume:l,isActive:a}=Ma(i);return{stop:La(e,t,{...o,eventFilter:s}),pause:r,resume:l,isActive:a}}function Ho(e,t=!0){Mn()?At(e):t?e():An(e)}function $o(e){var t;const n=Je(e);return(t=n==null?void 0:n.$el)!=null?t:n}const Ct=Mo?window:void 0;function xn(...e){let t,n,i,o;if(typeof e[0]=="string"||Array.isArray(e[0])?([n,i,o]=e,t=Ct):[t,n,i,o]=e,!t)return Lo;Array.isArray(n)||(n=[n]),Array.isArray(i)||(i=[i]);const s=[],r=()=>{s.forEach(p=>p()),s.length=0},l=(p,d,y,E)=>(p.addEventListener(d,y,E),()=>p.removeEventListener(d,y,E)),a=Ye(()=>[$o(t),Je(o)],([p,d])=>{if(r(),!p)return;const y=Pa(d)?{...d}:d;s.push(...n.flatMap(E=>i.map(F=>l(p,E,F,y))))},{immediate:!0,flush:"post"}),f=()=>{a(),r()};return Ri(f),f}function Na(){const e=ge(!1);return Mn()&&At(()=>{e.value=!0}),e}function Ha(e){const t=Na();return se(()=>(t.value,!!e()))}function $a(e,t={}){const{window:n=Ct}=t,i=Ha(()=>n&&"matchMedia"in n&&typeof n.matchMedia=="function");let o;const s=ge(!1),r=f=>{s.value=f.matches},l=()=>{o&&("removeEventListener"in o?o.removeEventListener("change",r):o.removeListener(r))},a=so(()=>{i.value&&(l(),o=n.matchMedia(Je(e)),"addEventListener"in o?o.addEventListener("change",r):o.addListener(r),s.value=o.matches)});return Ri(()=>{a(),l(),o=void 0}),s}const en=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{},tn="__vueuse_ssr_handlers__",Da=Ba();function Ba(){return tn in en||(en[tn]=en[tn]||{}),en[tn]}function Do(e,t){return Da[e]||t}function Ua(e){return e==null?"any":e instanceof Set?"set":e instanceof Map?"map":e instanceof Date?"date":typeof e=="boolean"?"boolean":typeof e=="string"?"string":typeof e=="object"?"object":Number.isNaN(e)?"any":"number"}const Ka={boolean:{read:e=>e==="true",write:e=>String(e)},object:{read:e=>JSON.parse(e),write:e=>JSON.stringify(e)},number:{read:e=>Number.parseFloat(e),write:e=>String(e)},any:{read:e=>e,write:e=>String(e)},string:{read:e=>e,write:e=>String(e)},map:{read:e=>new Map(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e.entries()))},set:{read:e=>new Set(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e))},date:{read:e=>new Date(e),write:e=>e.toISOString()}},bs="vueuse-storage";function Wa(e,t,n,i={}){var o;const{flush:s="pre",deep:r=!0,listenToStorageChanges:l=!0,writeDefaults:a=!0,mergeDefaults:f=!1,shallow:p,window:d=Ct,eventFilter:y,onError:E=T=>{console.error(T)},initOnMounted:F}=i,O=(p?Ys:ge)(typeof t=="function"?t():t);if(!n)try{n=Do("getDefaultStorage",()=>{var T;return(T=Ct)==null?void 0:T.localStorage})()}catch(T){E(T)}if(!n)return O;const H=Je(t),q=Ua(H),z=(o=i.serializer)!=null?o:Ka[q],{pause:g,resume:m}=ka(O,()=>I(O.value),{flush:s,deep:r,eventFilter:y});return d&&l&&Ho(()=>{xn(d,"storage",R),xn(d,bs,D),F&&R()}),F||R(),O;function I(T){try{if(T==null)n.removeItem(e);else{const A=z.write(T),W=n.getItem(e);W!==A&&(n.setItem(e,A),d&&d.dispatchEvent(new CustomEvent(bs,{detail:{key:e,oldValue:W,newValue:A,storageArea:n}})))}}catch(A){E(A)}}function U(T){const A=T?T.newValue:n.getItem(e);if(A==null)return a&&H!==null&&n.setItem(e,z.write(H)),H;if(!T&&f){const W=z.read(A);return typeof f=="function"?f(W,H):q==="object"&&!Array.isArray(W)?{...H,...W}:W}else return typeof A!="string"?A:z.read(A)}function D(T){R(T.detail)}function R(T){if(!(T&&T.storageArea!==n)){if(T&&T.key==null){O.value=H;return}if(!(T&&T.key!==e)){g();try{(T==null?void 0:T.newValue)!==z.write(O.value)&&(O.value=U(T))}catch(A){E(A)}finally{T?An(m):m()}}}}}function qa(e){return $a("(prefers-color-scheme: dark)",e)}function Va(e={}){const{selector:t="html",attribute:n="class",initialValue:i="auto",window:o=Ct,storage:s,storageKey:r="vueuse-color-scheme",listenToStorageChanges:l=!0,storageRef:a,emitAuto:f,disableTransition:p=!0}=e,d={auto:"",light:"light",dark:"dark",...e.modes||{}},y=qa({window:o}),E=se(()=>y.value?"dark":"light"),F=a||(r==null?No(i):Wa(r,i,s,{window:o,listenToStorageChanges:l})),O=se(()=>F.value==="auto"?E.value:F.value),H=Do("updateHTMLAttrs",(m,I,U)=>{const D=typeof m=="string"?o==null?void 0:o.document.querySelector(m):$o(m);if(!D)return;let R;if(p){R=o.document.createElement("style");const T="*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}";R.appendChild(document.createTextNode(T)),o.document.head.appendChild(R)}if(I==="class"){const T=U.split(/\s/g);Object.values(d).flatMap(A=>(A||"").split(/\s/g)).filter(Boolean).forEach(A=>{T.includes(A)?D.classList.add(A):D.classList.remove(A)})}else D.setAttribute(I,U);p&&(o.getComputedStyle(R).opacity,document.head.removeChild(R))});function q(m){var I;H(t,n,(I=d[m])!=null?I:m)}function z(m){e.onChanged?e.onChanged(m,q):q(m)}Ye(O,z,{flush:"post",immediate:!0}),Ho(()=>z(O.value));const g=se({get(){return f?F.value:O.value},set(m){F.value=m}});try{return Object.assign(g,{store:F,system:E,state:O})}catch{return g}}function za(e={}){const{valueDark:t="dark",valueLight:n=""}=e,i=Va({...e,onChanged:(s,r)=>{var l;e.onChanged?(l=e.onChanged)==null||l.call(e,s==="dark",r,s):r(s)},modes:{dark:t,light:n}});return se({get(){return i.value==="dark"},set(s){const r=s?"dark":"light";i.system.value===r?i.value="auto":i.value=r}})}function qn(e){return typeof Window<"u"&&e instanceof Window?e.document.documentElement:typeof Document<"u"&&e instanceof Document?e.documentElement:e}function Bo(e){const t=window.getComputedStyle(e);if(t.overflowX==="scroll"||t.overflowY==="scroll"||t.overflowX==="auto"&&e.clientWidth1?!0:(t.preventDefault&&t.preventDefault(),!1)}const nn=new WeakMap;function $c(e,t=!1){const n=ge(t);let i=null,o;Ye(No(e),l=>{const a=qn(Je(l));if(a){const f=a;nn.get(f)||nn.set(f,o),n.value&&(f.style.overflow="hidden")}},{immediate:!0});const s=()=>{const l=qn(Je(e));!l||n.value||(ys&&(i=xn(l,"touchmove",a=>{Ya(a)},{passive:!1})),l.style.overflow="hidden",n.value=!0)},r=()=>{var l;const a=qn(Je(e));!a||!n.value||(ys&&(i==null||i()),a.style.overflow=(l=nn.get(a))!=null?l:"",nn.delete(a),n.value=!1)};return Ri(r),se({get(){return n.value},set(l){l?s():r()}})}function Dc(e={}){const{window:t=Ct,behavior:n="auto"}=e;if(!t)return{x:ge(0),y:ge(0)};const i=ge(t.scrollX),o=ge(t.scrollY),s=se({get(){return i.value},set(l){scrollTo({left:l,behavior:n})}}),r=se({get(){return o.value},set(l){scrollTo({top:l,behavior:n})}});return xn(t,"scroll",()=>{i.value=t.scrollX,o.value=t.scrollY},{capture:!1,passive:!0}),{x:s,y:r}}const Uo=/^(?:[a-z]+:|\/\/)/i,Ja="vitepress-theme-appearance",Ko=/#.*$/,Xa=/(index)?\.(md|html)$/,Ce=typeof document<"u",Wo={relativePath:"",filePath:"",title:"404",description:"Not Found",headers:[],frontmatter:{sidebar:!1,layout:"page"},lastUpdated:0,isNotFound:!0};function Qa(e,t,n=!1){if(t===void 0)return!1;if(e=_s(`/${e}`),n)return new RegExp(t).test(e);if(_s(t)!==e)return!1;const i=t.match(Ko);return i?(Ce?location.hash:"")===i[0]:!0}function _s(e){return decodeURI(e).replace(Ko,"").replace(Xa,"")}function Za(e){return Uo.test(e)}function Ga(e,t){var i,o,s,r,l,a,f;const n=Object.keys(e.locales).find(p=>p!=="root"&&!Za(p)&&Qa(t,`/${p}/`,!0))||"root";return Object.assign({},e,{localeIndex:n,lang:((i=e.locales[n])==null?void 0:i.lang)??e.lang,dir:((o=e.locales[n])==null?void 0:o.dir)??e.dir,title:((s=e.locales[n])==null?void 0:s.title)??e.title,titleTemplate:((r=e.locales[n])==null?void 0:r.titleTemplate)??e.titleTemplate,description:((l=e.locales[n])==null?void 0:l.description)??e.description,head:Vo(e.head,((a=e.locales[n])==null?void 0:a.head)??[]),themeConfig:{...e.themeConfig,...(f=e.locales[n])==null?void 0:f.themeConfig}})}function qo(e,t){const n=t.title||e.title,i=t.titleTemplate??e.titleTemplate;if(typeof i=="string"&&i.includes(":title"))return i.replace(/:title/g,n);const o=ec(e.title,i);return`${n}${o}`}function ec(e,t){return t===!1?"":t===!0||t===void 0?` | ${e}`:e===t?"":` | ${t}`}function tc(e,t){const[n,i]=t;if(n!=="meta")return!1;const o=Object.entries(i)[0];return o==null?!1:e.some(([s,r])=>s===n&&r[o[0]]===o[1])}function Vo(e,t){return[...e.filter(n=>!tc(t,n)),...t]}const nc=/[\u0000-\u001F"#$&*+,:;<=>?[\]^`{|}\u007F]/g,ic=/^[a-z]:/i;function ws(e){const t=ic.exec(e),n=t?t[0]:"";return n+e.slice(n.length).replace(nc,"_").replace(/(^|\/)_+(?=[^/]*$)/,"$1")}const sc=Symbol(),lt=Ys(Oa);function Bc(e){const t=se(()=>Ga(lt.value,e.data.relativePath)),n=t.value.appearance,i=n==="force-dark"?ge(!0):n?za({storageKey:Ja,initialValue:()=>typeof n=="string"?n:"auto",...typeof n=="object"?n:{}}):ge(!1);return{site:t,theme:se(()=>t.value.themeConfig),page:se(()=>e.data),frontmatter:se(()=>e.data.frontmatter),params:se(()=>e.data.params),lang:se(()=>t.value.lang),dir:se(()=>t.value.dir),localeIndex:se(()=>t.value.localeIndex||"root"),title:se(()=>qo(t.value,e.data)),description:se(()=>e.data.description||t.value.description),isDark:i}}function oc(){const e=yt(sc);if(!e)throw new Error("vitepress data not properly injected in app");return e}const rc={ez:"application/andrew-inset",aw:"application/applixware",atom:"application/atom+xml",atomcat:"application/atomcat+xml",atomdeleted:"application/atomdeleted+xml",atomsvc:"application/atomsvc+xml",dwd:"application/atsc-dwd+xml",held:"application/atsc-held+xml",rsat:"application/atsc-rsat+xml",bdoc:"application/bdoc",xcs:"application/calendar+xml",ccxml:"application/ccxml+xml",cdfx:"application/cdfx+xml",cdmia:"application/cdmi-capability",cdmic:"application/cdmi-container",cdmid:"application/cdmi-domain",cdmio:"application/cdmi-object",cdmiq:"application/cdmi-queue",cu:"application/cu-seeme",mpd:"application/dash+xml",davmount:"application/davmount+xml",dbk:"application/docbook+xml",dssc:"application/dssc+der",xdssc:"application/dssc+xml",es:"application/ecmascript",ecma:"application/ecmascript",emma:"application/emma+xml",emotionml:"application/emotionml+xml",epub:"application/epub+zip",exi:"application/exi",fdt:"application/fdt+xml",pfr:"application/font-tdpfr",geojson:"application/geo+json",gml:"application/gml+xml",gpx:"application/gpx+xml",gxf:"application/gxf",gz:"application/gzip",hjson:"application/hjson",stk:"application/hyperstudio",ink:"application/inkml+xml",inkml:"application/inkml+xml",ipfix:"application/ipfix",its:"application/its+xml",jar:"application/java-archive",war:"application/java-archive",ear:"application/java-archive",ser:"application/java-serialized-object",class:"application/java-vm",js:"application/javascript",mjs:"application/javascript",json:"application/json",map:"application/json",json5:"application/json5",jsonml:"application/jsonml+json",jsonld:"application/ld+json",lgr:"application/lgr+xml",lostxml:"application/lost+xml",hqx:"application/mac-binhex40",cpt:"application/mac-compactpro",mads:"application/mads+xml",webmanifest:"application/manifest+json",mrc:"application/marc",mrcx:"application/marcxml+xml",ma:"application/mathematica",nb:"application/mathematica",mb:"application/mathematica",mathml:"application/mathml+xml",mbox:"application/mbox",mscml:"application/mediaservercontrol+xml",metalink:"application/metalink+xml",meta4:"application/metalink4+xml",mets:"application/mets+xml",maei:"application/mmt-aei+xml",musd:"application/mmt-usd+xml",mods:"application/mods+xml",m21:"application/mp21",mp21:"application/mp21",mp4s:"application/mp4",m4p:"application/mp4",doc:"application/msword",dot:"application/msword",mxf:"application/mxf",nq:"application/n-quads",nt:"application/n-triples",cjs:"application/node",bin:"application/octet-stream",dms:"application/octet-stream",lrf:"application/octet-stream",mar:"application/octet-stream",so:"application/octet-stream",dist:"application/octet-stream",distz:"application/octet-stream",pkg:"application/octet-stream",bpk:"application/octet-stream",dump:"application/octet-stream",elc:"application/octet-stream",deploy:"application/octet-stream",exe:"application/octet-stream",dll:"application/octet-stream",deb:"application/octet-stream",dmg:"application/octet-stream",iso:"application/octet-stream",img:"application/octet-stream",msi:"application/octet-stream",msp:"application/octet-stream",msm:"application/octet-stream",buffer:"application/octet-stream",oda:"application/oda",opf:"application/oebps-package+xml",ogx:"application/ogg",omdoc:"application/omdoc+xml",onetoc:"application/onenote",onetoc2:"application/onenote",onetmp:"application/onenote",onepkg:"application/onenote",oxps:"application/oxps",relo:"application/p2p-overlay+xml",xer:"application/patch-ops-error+xml",pdf:"application/pdf",pgp:"application/pgp-encrypted",asc:"application/pgp-signature",sig:"application/pgp-signature",prf:"application/pics-rules",p10:"application/pkcs10",p7m:"application/pkcs7-mime",p7c:"application/pkcs7-mime",p7s:"application/pkcs7-signature",p8:"application/pkcs8",ac:"application/pkix-attr-cert",cer:"application/pkix-cert",crl:"application/pkix-crl",pkipath:"application/pkix-pkipath",pki:"application/pkixcmp",pls:"application/pls+xml",ai:"application/postscript",eps:"application/postscript",ps:"application/postscript",provx:"application/provenance+xml",cww:"application/prs.cww",pskcxml:"application/pskc+xml",raml:"application/raml+yaml",rdf:"application/rdf+xml",owl:"application/rdf+xml",rif:"application/reginfo+xml",rnc:"application/relax-ng-compact-syntax",rl:"application/resource-lists+xml",rld:"application/resource-lists-diff+xml",rs:"application/rls-services+xml",rapd:"application/route-apd+xml",sls:"application/route-s-tsid+xml",rusd:"application/route-usd+xml",gbr:"application/rpki-ghostbusters",mft:"application/rpki-manifest",roa:"application/rpki-roa",rsd:"application/rsd+xml",rss:"application/rss+xml",rtf:"application/rtf",sbml:"application/sbml+xml",scq:"application/scvp-cv-request",scs:"application/scvp-cv-response",spq:"application/scvp-vp-request",spp:"application/scvp-vp-response",sdp:"application/sdp",senmlx:"application/senml+xml",sensmlx:"application/sensml+xml",setpay:"application/set-payment-initiation",setreg:"application/set-registration-initiation",shf:"application/shf+xml",siv:"application/sieve",sieve:"application/sieve",smi:"application/smil+xml",smil:"application/smil+xml",rq:"application/sparql-query",srx:"application/sparql-results+xml",gram:"application/srgs",grxml:"application/srgs+xml",sru:"application/sru+xml",ssdl:"application/ssdl+xml",ssml:"application/ssml+xml",swidtag:"application/swid+xml",tei:"application/tei+xml",teicorpus:"application/tei+xml",tfi:"application/thraud+xml",tsd:"application/timestamped-data",toml:"application/toml",trig:"application/trig",ttml:"application/ttml+xml",ubj:"application/ubjson",rsheet:"application/urc-ressheet+xml",td:"application/urc-targetdesc+xml",vxml:"application/voicexml+xml",wasm:"application/wasm",wgt:"application/widget",hlp:"application/winhlp",wsdl:"application/wsdl+xml",wspolicy:"application/wspolicy+xml",xaml:"application/xaml+xml",xav:"application/xcap-att+xml",xca:"application/xcap-caps+xml",xdf:"application/xcap-diff+xml",xel:"application/xcap-el+xml",xns:"application/xcap-ns+xml",xenc:"application/xenc+xml",xhtml:"application/xhtml+xml",xht:"application/xhtml+xml",xlf:"application/xliff+xml",xml:"application/xml",xsl:"application/xml",xsd:"application/xml",rng:"application/xml",dtd:"application/xml-dtd",xop:"application/xop+xml",xpl:"application/xproc+xml",xslt:"application/xml",xspf:"application/xspf+xml",mxml:"application/xv+xml",xhvml:"application/xv+xml",xvml:"application/xv+xml",xvm:"application/xv+xml",yang:"application/yang",yin:"application/yin+xml",zip:"application/zip","3gpp":"video/3gpp",adp:"audio/adpcm",amr:"audio/amr",au:"audio/basic",snd:"audio/basic",mid:"audio/midi",midi:"audio/midi",kar:"audio/midi",rmi:"audio/midi",mxmf:"audio/mobile-xmf",mp3:"audio/mpeg",m4a:"audio/mp4",mp4a:"audio/mp4",mpga:"audio/mpeg",mp2:"audio/mpeg",mp2a:"audio/mpeg",m2a:"audio/mpeg",m3a:"audio/mpeg",oga:"audio/ogg",ogg:"audio/ogg",spx:"audio/ogg",opus:"audio/ogg",s3m:"audio/s3m",sil:"audio/silk",wav:"audio/wav",weba:"audio/webm",xm:"audio/xm",ttc:"font/collection",otf:"font/otf",ttf:"font/ttf",woff:"font/woff",woff2:"font/woff2",exr:"image/aces",apng:"image/apng",avif:"image/avif",bmp:"image/bmp",cgm:"image/cgm",drle:"image/dicom-rle",emf:"image/emf",fits:"image/fits",g3:"image/g3fax",gif:"image/gif",heic:"image/heic",heics:"image/heic-sequence",heif:"image/heif",heifs:"image/heif-sequence",hej2:"image/hej2k",hsj2:"image/hsj2",ief:"image/ief",jls:"image/jls",jp2:"image/jp2",jpg2:"image/jp2",jpeg:"image/jpeg",jpg:"image/jpeg",jpe:"image/jpeg",jph:"image/jph",jhc:"image/jphc",jpm:"image/jpm",jpx:"image/jpx",jpf:"image/jpx",jxr:"image/jxr",jxra:"image/jxra",jxrs:"image/jxrs",jxs:"image/jxs",jxsc:"image/jxsc",jxsi:"image/jxsi",jxss:"image/jxss",ktx:"image/ktx",ktx2:"image/ktx2",png:"image/png",btif:"image/prs.btif",pti:"image/prs.pti",sgi:"image/sgi",svg:"image/svg+xml",svgz:"image/svg+xml",t38:"image/t38",tif:"image/tiff",tiff:"image/tiff",tfx:"image/tiff-fx",webp:"image/webp",wmf:"image/wmf","disposition-notification":"message/disposition-notification",u8msg:"message/global",u8dsn:"message/global-delivery-status",u8mdn:"message/global-disposition-notification",u8hdr:"message/global-headers",eml:"message/rfc822",mime:"message/rfc822","3mf":"model/3mf",gltf:"model/gltf+json",glb:"model/gltf-binary",igs:"model/iges",iges:"model/iges",msh:"model/mesh",mesh:"model/mesh",silo:"model/mesh",mtl:"model/mtl",obj:"model/obj",stpz:"model/step+zip",stpxz:"model/step-xml+zip",stl:"model/stl",wrl:"model/vrml",vrml:"model/vrml",x3db:"model/x3d+fastinfoset",x3dbz:"model/x3d+binary",x3dv:"model/x3d-vrml",x3dvz:"model/x3d+vrml",x3d:"model/x3d+xml",x3dz:"model/x3d+xml",appcache:"text/cache-manifest",manifest:"text/cache-manifest",ics:"text/calendar",ifb:"text/calendar",coffee:"text/coffeescript",litcoffee:"text/coffeescript",css:"text/css",csv:"text/csv",html:"text/html",htm:"text/html",shtml:"text/html",jade:"text/jade",jsx:"text/jsx",less:"text/less",markdown:"text/markdown",md:"text/markdown",mml:"text/mathml",mdx:"text/mdx",n3:"text/n3",txt:"text/plain",text:"text/plain",conf:"text/plain",def:"text/plain",list:"text/plain",log:"text/plain",in:"text/plain",ini:"text/plain",dsc:"text/prs.lines.tag",rtx:"text/richtext",sgml:"text/sgml",sgm:"text/sgml",shex:"text/shex",slim:"text/slim",slm:"text/slim",spdx:"text/spdx",stylus:"text/stylus",styl:"text/stylus",tsv:"text/tab-separated-values",t:"text/troff",tr:"text/troff",roff:"text/troff",man:"text/troff",me:"text/troff",ms:"text/troff",ttl:"text/turtle",uri:"text/uri-list",uris:"text/uri-list",urls:"text/uri-list",vcard:"text/vcard",vtt:"text/vtt",yaml:"text/yaml",yml:"text/yaml","3gp":"video/3gpp","3g2":"video/3gpp2",h261:"video/h261",h263:"video/h263",h264:"video/h264",m4s:"video/iso.segment",jpgv:"video/jpeg",jpgm:"image/jpm",mj2:"video/mj2",mjp2:"video/mj2",ts:"video/mp2t",mp4:"video/mp4",mp4v:"video/mp4",mpg4:"video/mp4",mpeg:"video/mpeg",mpg:"video/mpeg",mpe:"video/mpeg",m1v:"video/mpeg",m2v:"video/mpeg",ogv:"video/ogg",qt:"video/quicktime",mov:"video/quicktime",webm:"video/webm"};function lc(e){let t=(""+e).trim().toLowerCase(),n=t.lastIndexOf(".");return rc[~n?t.substring(++n):t]}function ac(e,t){return`${e}${t}`.replace(/\/+/g,"/")}function Cs(e){return Uo.test(e)||!e.startsWith("/")?e:ac(lt.value.base,e)}function cc(e){let t=e.replace(/\.html$/,"");if(t=decodeURIComponent(t),t=t.replace(/\/$/,"/index"),Ce){const n="/";t=ws(t.slice(n.length).replace(/\//g,"_")||"index")+".md";let i=__VP_HASH_MAP__[t.toLowerCase()];if(i||(t=t.endsWith("_index.md")?t.slice(0,-9)+".md":t.slice(0,-3)+"_index.md",i=__VP_HASH_MAP__[t.toLowerCase()]),!i)return null;t=`${n}assets/${t}.${i}.js`}else t=`./${ws(t.slice(1).replace(/\//g,"_"))}.md.js`;return t}let ln=[];function Uc(e){ln.push(e),In(()=>{ln=ln.filter(t=>t!==e)})}const fc=Symbol(),zo="http://a.com",uc=()=>({path:"/",component:null,data:Wo});function Kc(e,t){const n=Cn(uc()),i={route:n,go:o};async function o(l=Ce?location.href:"/"){var a,f;l=ri(l),await((a=i.onBeforeRouteChange)==null?void 0:a.call(i,l))!==!1&&(As(l),await r(l),await((f=i.onAfterRouteChanged)==null?void 0:f.call(i,l)))}let s=null;async function r(l,a=0,f=!1){var y;if(await((y=i.onBeforePageLoad)==null?void 0:y.call(i,l))===!1)return;const p=new URL(l,zo),d=s=p.pathname;try{let E=await e(d);if(!E)throw new Error(`Page not found: ${d}`);if(s===d){s=null;const{default:F,__pageData:O}=E;if(!F)throw new Error(`Invalid route component: ${F}`);n.path=Ce?d:Cs(d),n.component=It(F),n.data=It(O),Ce&&An(()=>{let H=lt.value.base+O.relativePath.replace(/(?:(^|\/)index)?\.md$/,"$1");if(!lt.value.cleanUrls&&!H.endsWith("/")&&(H+=".html"),H!==p.pathname&&(p.pathname=H,l=H+p.search+p.hash,history.replaceState(null,"",l)),p.hash&&!a){let q=null;try{q=document.getElementById(decodeURIComponent(p.hash).slice(1))}catch(z){console.warn(z)}if(q){Es(q,p.hash);return}}window.scrollTo(0,a)})}}catch(E){if(!/fetch|Page not found/.test(E.message)&&!/^\/404(\.html|\/)?$/.test(l)&&console.error(E),!f)try{const F=await fetch(lt.value.base+"hashmap.json");window.__VP_HASH_MAP__=await F.json(),await r(l,a,!0);return}catch{}s===d&&(s=null,n.path=Ce?d:Cs(d),n.component=t?It(t):null,n.data=Wo)}}return Ce&&(window.addEventListener("click",l=>{if(l.target.closest("button"))return;const f=l.target.closest("a");if(f&&!f.closest(".vp-raw")&&(f instanceof SVGElement||!f.download)){const{target:p}=f,{href:d,origin:y,pathname:E,hash:F,search:O}=new URL(f.href instanceof SVGAnimatedString?f.href.animVal:f.href,f.baseURI),H=window.location,q=lc(E);!l.ctrlKey&&!l.shiftKey&&!l.altKey&&!l.metaKey&&!p&&y===H.origin&&(!q||q==="text/html")&&(l.preventDefault(),E===H.pathname&&O===H.search?(F!==H.hash&&(history.pushState(null,"",F),window.dispatchEvent(new Event("hashchange"))),F?Es(f,F,f.classList.contains("header-anchor")):(As(d),window.scrollTo(0,0))):o(d))}},{capture:!0}),window.addEventListener("popstate",l=>{r(ri(location.href),l.state&&l.state.scrollPosition||0)}),window.addEventListener("hashchange",l=>{l.preventDefault()})),i}function pc(){const e=yt(fc);if(!e)throw new Error("useRouter() is called without provider.");return e}function Yo(){return pc().route}function Es(e,t,n=!1){let i=null;try{i=e.classList.contains("header-anchor")?e:document.getElementById(decodeURIComponent(t).slice(1))}catch(o){console.warn(o)}if(i){let o=function(){!n||Math.abs(f-window.scrollY)>window.innerHeight?window.scrollTo(0,f):window.scrollTo({left:0,top:f,behavior:"smooth"})},s=lt.value.scrollOffset,r=0,l=24;if(typeof s=="object"&&"padding"in s&&(l=s.padding,s=s.selector),typeof s=="number")r=s;else if(typeof s=="string")r=Ts(s,l);else if(Array.isArray(s))for(const p of s){const d=Ts(p,l);if(d){r=d;break}}const a=parseInt(window.getComputedStyle(i).paddingTop,10),f=window.scrollY+i.getBoundingClientRect().top-r+a;requestAnimationFrame(o)}}function Ts(e,t){const n=document.querySelector(e);if(!n)return 0;const i=n.getBoundingClientRect().bottom;return i<0?0:i+t}function As(e){Ce&&e!==ri(location.href)&&(history.replaceState({scrollPosition:window.scrollY},document.title),history.pushState(null,"",e))}function ri(e){const t=new URL(e,zo);return t.pathname=t.pathname.replace(/(^|\/)index(\.html)?$/,"$1"),lt.value.cleanUrls?t.pathname=t.pathname.replace(/\.html$/,""):!t.pathname.endsWith("/")&&!t.pathname.endsWith(".html")&&(t.pathname+=".html"),t.pathname+t.search+t.hash}const js=()=>ln.forEach(e=>e()),Wc=co({name:"VitePressContent",props:{as:{type:[Object,String],default:"div"}},setup(e){const t=Yo(),{site:n}=oc();return()=>si(e.as,n.value.contentProps??{style:{position:"relative"}},[t.component?si(t.component,{onVnodeMounted:js,onVnodeUpdated:js}):"404 Page Not Found"])}}),qc="/intro-light.png",dc="modulepreload",mc=function(e){return"/"+e},Ss={},Vc=function(t,n,i){if(!n||n.length===0)return t();const o=document.getElementsByTagName("link");return Promise.all(n.map(s=>{if(s=mc(s),s in Ss)return;Ss[s]=!0;const r=s.endsWith(".css"),l=r?'[rel="stylesheet"]':"";if(!!i)for(let p=o.length-1;p>=0;p--){const d=o[p];if(d.href===s&&(!r||d.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${s}"]${l}`))return;const f=document.createElement("link");if(f.rel=r?"stylesheet":dc,r||(f.as="script",f.crossOrigin=""),f.href=s,document.head.appendChild(f),r)return new Promise((p,d)=>{f.addEventListener("load",p),f.addEventListener("error",()=>d(new Error(`Unable to preload CSS for ${s}`)))})})).then(()=>t()).catch(s=>{const r=new Event("vite:preloadError",{cancelable:!0});if(r.payload=s,window.dispatchEvent(r),!r.defaultPrevented)throw s})},zc=co({setup(e,{slots:t}){const n=ge(!1);return At(()=>{n.value=!0}),()=>n.value&&t.default?t.default():null}});function Yc(){Ce&&window.addEventListener("click",e=>{var n;const t=e.target;if(t.matches(".vp-code-group input")){const i=(n=t.parentElement)==null?void 0:n.parentElement;if(!i)return;const o=Array.from(i.querySelectorAll("input")).indexOf(t);if(o<0)return;const s=i.querySelector(".blocks");if(!s)return;const r=Array.from(s.children).find(f=>f.classList.contains("active"));if(!r)return;const l=s.children[o];if(!l||r===l)return;r.classList.remove("active"),l.classList.add("active");const a=i==null?void 0:i.querySelector(`label[for="${t.id}"]`);a==null||a.scrollIntoView({block:"nearest"})}})}function Jc(){if(Ce){const e=new WeakMap;window.addEventListener("click",t=>{var i;const n=t.target;if(n.matches('div[class*="language-"] > button.copy')){const o=n.parentElement,s=(i=n.nextElementSibling)==null?void 0:i.nextElementSibling;if(!o||!s)return;const r=/language-(shellscript|shell|bash|sh|zsh)/.test(o.className);let l="";s.querySelectorAll("span.line:not(.diff.remove)").forEach(a=>l+=(a.textContent||"")+` +`),l=l.slice(0,-1),r&&(l=l.replace(/^ *(\$|>) /gm,"").trim()),hc(l).then(()=>{n.classList.add("copied"),clearTimeout(e.get(n));const a=setTimeout(()=>{n.classList.remove("copied"),n.blur(),e.delete(n)},2e3);e.set(n,a)})}})}}async function hc(e){try{return navigator.clipboard.writeText(e)}catch{const t=document.createElement("textarea"),n=document.activeElement;t.value=e,t.setAttribute("readonly",""),t.style.contain="strict",t.style.position="absolute",t.style.left="-9999px",t.style.fontSize="12pt";const i=document.getSelection(),o=i?i.rangeCount>0&&i.getRangeAt(0):null;document.body.appendChild(t),t.select(),t.selectionStart=0,t.selectionEnd=e.length,document.execCommand("copy"),document.body.removeChild(t),o&&(i.removeAllRanges(),i.addRange(o)),n&&n.focus()}}function Xc(e,t){let n=[],i=!0;const o=s=>{if(i){i=!1;return}const r=s.map(Os);n.forEach((l,a)=>{const f=r.findIndex(p=>p==null?void 0:p.isEqualNode(l??null));f!==-1?delete r[f]:(l==null||l.remove(),delete n[a])}),r.forEach(l=>l&&document.head.appendChild(l)),n=[...n,...r].filter(Boolean)};so(()=>{const s=e.data,r=t.value,l=s&&s.description,a=s&&s.frontmatter.head||[],f=qo(r,s);f!==document.title&&(document.title=f);const p=l||r.description;let d=document.querySelector("meta[name=description]");d?d.getAttribute("content")!==p&&d.setAttribute("content",p):Os(["meta",{name:"description",content:p}]),o(Vo(r.head,xc(a)))})}function Os([e,t,n]){const i=document.createElement(e);for(const o in t)i.setAttribute(o,t[o]);return n&&(i.innerHTML=n),e==="script"&&!t.async&&(i.async=!1),i}function gc(e){return e[0]==="meta"&&e[1]&&e[1].name==="description"}function xc(e){return e.filter(t=>!gc(t))}const Vn=new Set,Jo=()=>document.createElement("link"),vc=e=>{const t=Jo();t.rel="prefetch",t.href=e,document.head.appendChild(t)},yc=e=>{const t=new XMLHttpRequest;t.open("GET",e,t.withCredentials=!0),t.send()};let sn;const bc=Ce&&(sn=Jo())&&sn.relList&&sn.relList.supports&&sn.relList.supports("prefetch")?vc:yc;function Qc(){if(!Ce||!window.IntersectionObserver)return;let e;if((e=navigator.connection)&&(e.saveData||/2g/.test(e.effectiveType)))return;const t=window.requestIdleCallback||setTimeout;let n=null;const i=()=>{n&&n.disconnect(),n=new IntersectionObserver(s=>{s.forEach(r=>{if(r.isIntersecting){const l=r.target;n.unobserve(l);const{pathname:a}=l;if(!Vn.has(a)){Vn.add(a);const f=cc(a);f&&bc(f)}}})}),t(()=>{document.querySelectorAll("#app a").forEach(s=>{const{hostname:r,pathname:l}=new URL(s.href instanceof SVGAnimatedString?s.href.animVal:s.href,s.baseURI),a=l.match(/\.\w+$/);a&&a[0]!==".html"||s.target!=="_blank"&&r===location.hostname&&(l!==location.pathname?n.observe(s):Vn.add(l))})})};At(i);const o=Yo();Ye(()=>o.path,i),In(()=>{n&&n.disconnect()})}export{Mc as $,In as A,Ac as B,ml as C,Ec as D,jc as E,he as F,Ys as G,Uc as H,fe as I,Tc as J,Uo as K,Yo as L,Ul as M,yt as N,ui as O,An as P,Dc as Q,Ic as R,En as S,Io as T,Hc as U,Kr as V,$c as W,Sl as X,Oc as Y,Lc as Z,Nc as _,So as a,Rc as a0,qc as a1,si as a2,Xc as a3,fc as a4,Bc as a5,sc as a6,Wc as a7,zc as a8,lt as a9,kc as aa,Kc as ab,cc as ac,Vc as ad,Qc as ae,Jc as af,Yc as ag,To as b,Pc as c,co as d,Fc as e,Cs as f,se as g,ge as h,Za as i,At as j,jo as k,lc as l,_i as m,pi as n,Co as o,wc as p,Cc as q,Sc as r,Qa as s,_c as t,oc as u,Ce as v,Gr as w,$a as x,Ye as y,so as z}; diff --git a/docs/assets/chunks/theme.pN5TPI87.js b/docs/assets/chunks/theme.pN5TPI87.js new file mode 100644 index 0000000000..ea8a32dfa1 --- /dev/null +++ b/docs/assets/chunks/theme.pN5TPI87.js @@ -0,0 +1 @@ +import{d as g,o as a,c as i,r as u,n as C,a as z,t as L,_ as m,b as k,w as h,e as f,T as ce,u as Ee,i as Fe,l as De,f as ue,g as b,h as S,j as G,k as c,m as l,p as H,q as E,s as O,v as K,x as re,y as U,z as te,A as de,B as Ve,C as xe,D as j,F as M,E as N,G as ve,H as Y,I as _,J as x,K as Pe,L as se,M as Q,N as ne,O as Oe,P as Ge,Q as we,R as Le,S as Ue,U as je,V as qe,W as Se,X as Me,Y as Re,Z as Ke,$ as We,a0 as Ye}from"./framework._u06EGUx.js";const Je=g({__name:"VPBadge",props:{text:{},type:{default:"tip"}},setup(s){return(e,t)=>(a(),i("span",{class:C(["VPBadge",e.type])},[u(e.$slots,"default",{},()=>[z(L(e.text),1)],!0)],2))}}),Ze=m(Je,[["__scopeId","data-v-852b59b9"]]),Qe={key:0,class:"VPBackdrop"},Xe=g({__name:"VPBackdrop",props:{show:{type:Boolean}},setup(s){return(e,t)=>(a(),k(ce,{name:"fade"},{default:h(()=>[e.show?(a(),i("div",Qe)):f("",!0)]),_:1}))}}),et=m(Xe,[["__scopeId","data-v-b06cdb19"]]),V=Ee;function tt(s,e){let t,n=!1;return()=>{t&&clearTimeout(t),n?t=setTimeout(s,e):(s(),(n=!0)&&setTimeout(()=>n=!1,e))}}function le(s){return/^\//.test(s)?s:`/${s}`}function J(s){const{pathname:e,search:t,hash:n,protocol:o}=new URL(s,"http://a.com");if(Fe(s)||s.startsWith("#")||!o.startsWith("http")||/\.(?!html|md)\w+($|\?)/i.test(s)&&De(s))return s;const{site:r}=V(),d=e.endsWith("/")||e.endsWith(".html")?s:s.replace(/(?:(^\.+)\/)?.*$/,`$1${e.replace(/(\.md)?$/,r.value.cleanUrls?"":".html")}${t}${n}`);return ue(d)}function Z({removeCurrent:s=!0,correspondingLink:e=!1}={}){const{site:t,localeIndex:n,page:o,theme:r}=V(),d=b(()=>{var v,$;return{label:(v=t.value.locales[n.value])==null?void 0:v.label,link:(($=t.value.locales[n.value])==null?void 0:$.link)||(n.value==="root"?"/":`/${n.value}/`)}});return{localeLinks:b(()=>Object.entries(t.value.locales).flatMap(([v,$])=>s&&d.value.label===$.label?[]:{text:$.label,link:st($.link||(v==="root"?"/":`/${v}/`),r.value.i18nRouting!==!1&&e,o.value.relativePath.slice(d.value.link.length-1),!t.value.cleanUrls)})),currentLang:d}}function st(s,e,t,n){return e?s.replace(/\/$/,"")+le(t.replace(/(^|\/)index\.md$/,"$1").replace(/\.md$/,n?".html":"")):s}const nt=s=>(H("data-v-792811ca"),s=s(),E(),s),ot={class:"NotFound"},at={class:"code"},rt={class:"title"},lt=nt(()=>c("div",{class:"divider"},null,-1)),it={class:"quote"},ct={class:"action"},ut=["href","aria-label"],dt=g({__name:"NotFound",setup(s){const{site:e,theme:t}=V(),{localeLinks:n}=Z({removeCurrent:!1}),o=S("/");return G(()=>{var d;const r=window.location.pathname.replace(e.value.base,"").replace(/(^.*?\/).*$/,"/$1");n.value.length&&(o.value=((d=n.value.find(({link:p})=>p.startsWith(r)))==null?void 0:d.link)||n.value[0].link)}),(r,d)=>{var p,v,$,y,w;return a(),i("div",ot,[c("p",at,L(((p=l(t).notFound)==null?void 0:p.code)??"404"),1),c("h1",rt,L(((v=l(t).notFound)==null?void 0:v.title)??"PAGE NOT FOUND"),1),lt,c("blockquote",it,L((($=l(t).notFound)==null?void 0:$.quote)??"But if you don't change your direction, and if you keep looking, you may end up where you are heading."),1),c("div",ct,[c("a",{class:"link",href:l(ue)(o.value),"aria-label":((y=l(t).notFound)==null?void 0:y.linkLabel)??"go to home"},L(((w=l(t).notFound)==null?void 0:w.linkText)??"Take me home"),9,ut)])])}}}),vt=m(dt,[["__scopeId","data-v-792811ca"]]);function Ce(s,e){if(Array.isArray(s))return X(s);if(s==null)return[];e=le(e);const t=Object.keys(s).sort((o,r)=>r.split("/").length-o.split("/").length).find(o=>e.startsWith(le(o))),n=t?s[t]:[];return Array.isArray(n)?X(n):X(n.items,n.base)}function ht(s){const e=[];let t=0;for(const n in s){const o=s[n];if(o.items){t=e.push(o);continue}e[t]||e.push({items:[]}),e[t].items.push(o)}return e}function pt(s){const e=[];function t(n){for(const o of n)o.text&&o.link&&e.push({text:o.text,link:o.link,docFooterText:o.docFooterText}),o.items&&t(o.items)}return t(s),e}function ie(s,e){return Array.isArray(e)?e.some(t=>ie(s,t)):O(s,e.link)?!0:e.items?ie(s,e.items):!1}function X(s,e){return[...s].map(t=>{const n={...t},o=n.base||e;return o&&n.link&&(n.link=o+n.link),n.items&&(n.items=X(n.items,o)),n})}function F(){const{frontmatter:s,page:e,theme:t}=V(),n=re("(min-width: 960px)"),o=S(!1),r=b(()=>{const A=t.value.sidebar,P=e.value.relativePath;return A?Ce(A,P):[]}),d=S(r.value);U(r,(A,P)=>{JSON.stringify(A)!==JSON.stringify(P)&&(d.value=r.value)});const p=b(()=>s.value.sidebar!==!1&&d.value.length>0&&s.value.layout!=="home"),v=b(()=>$?s.value.aside==null?t.value.aside==="left":s.value.aside==="left":!1),$=b(()=>s.value.layout==="home"?!1:s.value.aside!=null?!!s.value.aside:t.value.aside!==!1),y=b(()=>p.value&&n.value),w=b(()=>p.value?ht(d.value):[]);function T(){o.value=!0}function I(){o.value=!1}function B(){o.value?I():T()}return{isOpen:o,sidebar:d,sidebarGroups:w,hasSidebar:p,hasAside:$,leftAside:v,isSidebarEnabled:y,open:T,close:I,toggle:B}}function _t(s,e){let t;te(()=>{t=s.value?document.activeElement:void 0}),G(()=>{window.addEventListener("keyup",n)}),de(()=>{window.removeEventListener("keyup",n)});function n(o){o.key==="Escape"&&s.value&&(e(),t==null||t.focus())}}const Ie=S(K?location.hash:"");K&&window.addEventListener("hashchange",()=>{Ie.value=location.hash});function ft(s){const{page:e}=V(),t=S(!1),n=b(()=>s.value.collapsed!=null),o=b(()=>!!s.value.link),r=S(!1),d=()=>{r.value=O(e.value.relativePath,s.value.link)};U([e,s,Ie],d),G(d);const p=b(()=>r.value?!0:s.value.items?ie(e.value.relativePath,s.value.items):!1),v=b(()=>!!(s.value.items&&s.value.items.length));te(()=>{t.value=!!(n.value&&s.value.collapsed)}),Ve(()=>{(r.value||p.value)&&(t.value=!1)});function $(){n.value&&(t.value=!t.value)}return{collapsed:t,collapsible:n,isLink:o,isActiveLink:r,hasActiveLink:p,hasChildren:v,toggle:$}}function mt(){const{hasSidebar:s}=F(),e=re("(min-width: 960px)"),t=re("(min-width: 1280px)");return{isAsideEnabled:b(()=>!t.value&&!e.value?!1:s.value?t.value:e.value)}}const gt=71;function he(s){return typeof s.outline=="object"&&!Array.isArray(s.outline)&&s.outline.label||s.outlineTitle||"On this page"}function pe(s){const e=[...document.querySelectorAll(".VPDoc :where(h1,h2,h3,h4,h5,h6)")].filter(t=>t.id&&t.hasChildNodes()).map(t=>{const n=Number(t.tagName[1]);return{title:$t(t),link:"#"+t.id,level:n}});return kt(e,s)}function $t(s){let e="";for(const t of s.childNodes)if(t.nodeType===1){if(t.classList.contains("VPBadge")||t.classList.contains("header-anchor"))continue;e+=t.textContent}else t.nodeType===3&&(e+=t.textContent);return e.trim()}function kt(s,e){if(e===!1)return[];const t=(typeof e=="object"&&!Array.isArray(e)?e.level:e)||2,[n,o]=typeof t=="number"?[t,t]:t==="deep"?[2,6]:t;s=s.filter(d=>d.level>=n&&d.level<=o);const r=[];e:for(let d=0;d=0;v--){const $=s[v];if($.level{requestAnimationFrame(r),window.addEventListener("scroll",n)}),xe(()=>{d(location.hash)}),de(()=>{window.removeEventListener("scroll",n)});function r(){if(!t.value)return;const p=[].slice.call(s.value.querySelectorAll(".outline-link")),v=[].slice.call(document.querySelectorAll(".content .header-anchor")).filter(I=>p.some(B=>B.hash===I.hash&&I.offsetParent!==null)),$=window.scrollY,y=window.innerHeight,w=document.body.offsetHeight,T=Math.abs($+y-w)<1;if(v.length&&T){d(v[v.length-1].hash);return}for(let I=0;I{const o=j("VPDocOutlineItem",!0);return a(),i("ul",{class:C(t.root?"root":"nested")},[(a(!0),i(M,null,N(t.headers,({children:r,link:d,title:p})=>(a(),i("li",null,[c("a",{class:"outline-link",href:d,onClick:e,title:p},L(p),9,Vt),r!=null&&r.length?(a(),k(o,{key:0,headers:r},null,8,["headers"])):f("",!0)]))),256))],2)}}}),_e=m(Pt,[["__scopeId","data-v-bee2b9d1"]]),wt=s=>(H("data-v-29d194e1"),s=s(),E(),s),Lt={class:"content"},St={class:"outline-title",role:"heading","aria-level":"2"},Mt={"aria-labelledby":"doc-outline-aria-label"},Ct=wt(()=>c("span",{class:"visually-hidden",id:"doc-outline-aria-label"}," Table of Contents for current page ",-1)),It=g({__name:"VPDocAsideOutline",setup(s){const{frontmatter:e,theme:t}=V(),n=ve([]);Y(()=>{n.value=pe(e.value.outline??t.value.outline)});const o=S(),r=S();return bt(o,r),(d,p)=>(a(),i("div",{class:C(["VPDocAsideOutline",{"has-outline":n.value.length>0}]),ref_key:"container",ref:o,role:"navigation"},[c("div",Lt,[c("div",{class:"outline-marker",ref_key:"marker",ref:r},null,512),c("div",St,L(l(he)(l(t))),1),c("nav",Mt,[Ct,_(_e,{headers:n.value,root:!0},null,8,["headers"])])])],2))}}),Tt=m(It,[["__scopeId","data-v-29d194e1"]]),At={class:"VPDocAsideCarbonAds"},Bt=g({__name:"VPDocAsideCarbonAds",props:{carbonAds:{}},setup(s){const e=()=>null;return(t,n)=>(a(),i("div",At,[_(l(e),{"carbon-ads":t.carbonAds},null,8,["carbon-ads"])]))}}),Nt=s=>(H("data-v-6d7b3c46"),s=s(),E(),s),zt={class:"VPDocAside"},Ht=Nt(()=>c("div",{class:"spacer"},null,-1)),Et=g({__name:"VPDocAside",setup(s){const{theme:e}=V();return(t,n)=>(a(),i("div",zt,[u(t.$slots,"aside-top",{},void 0,!0),u(t.$slots,"aside-outline-before",{},void 0,!0),_(Tt),u(t.$slots,"aside-outline-after",{},void 0,!0),Ht,u(t.$slots,"aside-ads-before",{},void 0,!0),l(e).carbonAds?(a(),k(Bt,{key:0,"carbon-ads":l(e).carbonAds},null,8,["carbon-ads"])):f("",!0),u(t.$slots,"aside-ads-after",{},void 0,!0),u(t.$slots,"aside-bottom",{},void 0,!0)]))}}),Ft=m(Et,[["__scopeId","data-v-6d7b3c46"]]);function Dt(){const{theme:s,page:e}=V();return b(()=>{const{text:t="Edit this page",pattern:n=""}=s.value.editLink||{};let o;return typeof n=="function"?o=n(e.value):o=n.replace(/:path/g,e.value.filePath),{url:o,text:t}})}function xt(){const{page:s,theme:e,frontmatter:t}=V();return b(()=>{var v,$,y,w,T,I,B,A;const n=Ce(e.value.sidebar,s.value.relativePath),o=pt(n),r=o.findIndex(P=>O(s.value.relativePath,P.link)),d=((v=e.value.docFooter)==null?void 0:v.prev)===!1&&!t.value.prev||t.value.prev===!1,p=(($=e.value.docFooter)==null?void 0:$.next)===!1&&!t.value.next||t.value.next===!1;return{prev:d?void 0:{text:(typeof t.value.prev=="string"?t.value.prev:typeof t.value.prev=="object"?t.value.prev.text:void 0)??((y=o[r-1])==null?void 0:y.docFooterText)??((w=o[r-1])==null?void 0:w.text),link:(typeof t.value.prev=="object"?t.value.prev.link:void 0)??((T=o[r-1])==null?void 0:T.link)},next:p?void 0:{text:(typeof t.value.next=="string"?t.value.next:typeof t.value.next=="object"?t.value.next.text:void 0)??((I=o[r+1])==null?void 0:I.docFooterText)??((B=o[r+1])==null?void 0:B.text),link:(typeof t.value.next=="object"?t.value.next.link:void 0)??((A=o[r+1])==null?void 0:A.link)}}})}const Ot={},Gt={xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},Ut=c("path",{d:"M18,23H4c-1.7,0-3-1.3-3-3V6c0-1.7,1.3-3,3-3h7c0.6,0,1,0.4,1,1s-0.4,1-1,1H4C3.4,5,3,5.4,3,6v14c0,0.6,0.4,1,1,1h14c0.6,0,1-0.4,1-1v-7c0-0.6,0.4-1,1-1s1,0.4,1,1v7C21,21.7,19.7,23,18,23z"},null,-1),jt=c("path",{d:"M8,17c-0.3,0-0.5-0.1-0.7-0.3C7,16.5,6.9,16.1,7,15.8l1-4c0-0.2,0.1-0.3,0.3-0.5l9.5-9.5c1.2-1.2,3.2-1.2,4.4,0c1.2,1.2,1.2,3.2,0,4.4l-9.5,9.5c-0.1,0.1-0.3,0.2-0.5,0.3l-4,1C8.2,17,8.1,17,8,17zM9.9,12.5l-0.5,2.1l2.1-0.5l9.3-9.3c0.4-0.4,0.4-1.1,0-1.6c-0.4-0.4-1.2-0.4-1.6,0l0,0L9.9,12.5z M18.5,2.5L18.5,2.5L18.5,2.5z"},null,-1),qt=[Ut,jt];function Rt(s,e){return a(),i("svg",Gt,qt)}const Kt=m(Ot,[["render",Rt]]),D=g({__name:"VPLink",props:{tag:{},href:{},noIcon:{type:Boolean},target:{},rel:{}},setup(s){const e=s,t=b(()=>e.tag??(e.href?"a":"span")),n=b(()=>e.href&&Pe.test(e.href));return(o,r)=>(a(),k(x(t.value),{class:C(["VPLink",{link:o.href,"vp-external-link-icon":n.value,"no-icon":o.noIcon}]),href:o.href?l(J)(o.href):void 0,target:o.target??(n.value?"_blank":void 0),rel:o.rel??(n.value?"noreferrer":void 0)},{default:h(()=>[u(o.$slots,"default")]),_:3},8,["class","href","target","rel"]))}}),Wt={class:"VPLastUpdated"},Yt=["datetime"],Jt=g({__name:"VPDocFooterLastUpdated",setup(s){const{theme:e,page:t,frontmatter:n,lang:o}=V(),r=b(()=>new Date(n.value.lastUpdated??t.value.lastUpdated)),d=b(()=>r.value.toISOString()),p=S("");return G(()=>{te(()=>{var v,$,y;p.value=new Intl.DateTimeFormat(($=(v=e.value.lastUpdated)==null?void 0:v.formatOptions)!=null&&$.forceLocale?o.value:void 0,((y=e.value.lastUpdated)==null?void 0:y.formatOptions)??{dateStyle:"short",timeStyle:"short"}).format(r.value)})}),(v,$)=>{var y;return a(),i("p",Wt,[z(L(((y=l(e).lastUpdated)==null?void 0:y.text)||l(e).lastUpdatedText||"Last updated")+": ",1),c("time",{datetime:d.value},L(p.value),9,Yt)])}}}),Zt=m(Jt,[["__scopeId","data-v-9da12f1d"]]),Qt={key:0,class:"VPDocFooter"},Xt={key:0,class:"edit-info"},es={key:0,class:"edit-link"},ts={key:1,class:"last-updated"},ss={key:1,class:"prev-next"},ns={class:"pager"},os=["href"],as=["innerHTML"],rs=["innerHTML"],ls={class:"pager"},is=["href"],cs=["innerHTML"],us=["innerHTML"],ds=g({__name:"VPDocFooter",setup(s){const{theme:e,page:t,frontmatter:n}=V(),o=Dt(),r=xt(),d=b(()=>e.value.editLink&&n.value.editLink!==!1),p=b(()=>t.value.lastUpdated&&n.value.lastUpdated!==!1),v=b(()=>d.value||p.value||r.value.prev||r.value.next);return($,y)=>{var w,T,I,B,A,P;return v.value?(a(),i("footer",Qt,[u($.$slots,"doc-footer-before",{},void 0,!0),d.value||p.value?(a(),i("div",Xt,[d.value?(a(),i("div",es,[_(D,{class:"edit-link-button",href:l(o).url,"no-icon":!0},{default:h(()=>[_(Kt,{class:"edit-link-icon","aria-label":"edit icon"}),z(" "+L(l(o).text),1)]),_:1},8,["href"])])):f("",!0),p.value?(a(),i("div",ts,[_(Zt)])):f("",!0)])):f("",!0),(w=l(r).prev)!=null&&w.link||(T=l(r).next)!=null&&T.link?(a(),i("nav",ss,[c("div",ns,[(I=l(r).prev)!=null&&I.link?(a(),i("a",{key:0,class:"pager-link prev",href:l(J)(l(r).prev.link)},[c("span",{class:"desc",innerHTML:((B=l(e).docFooter)==null?void 0:B.prev)||"Previous page"},null,8,as),c("span",{class:"title",innerHTML:l(r).prev.text},null,8,rs)],8,os)):f("",!0)]),c("div",ls,[(A=l(r).next)!=null&&A.link?(a(),i("a",{key:0,class:"pager-link next",href:l(J)(l(r).next.link)},[c("span",{class:"desc",innerHTML:((P=l(e).docFooter)==null?void 0:P.next)||"Next page"},null,8,cs),c("span",{class:"title",innerHTML:l(r).next.text},null,8,us)],8,is)):f("",!0)])])):f("",!0)])):f("",!0)}}}),vs=m(ds,[["__scopeId","data-v-9519b7ec"]]),hs={},ps={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},_s=c("path",{d:"M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"},null,-1),fs=[_s];function ms(s,e){return a(),i("svg",ps,fs)}const fe=m(hs,[["render",ms]]),gs={key:0,class:"VPDocOutlineDropdown"},$s={key:0,class:"items"},ks=g({__name:"VPDocOutlineDropdown",setup(s){const{frontmatter:e,theme:t}=V(),n=S(!1);Y(()=>{n.value=!1});const o=ve([]);return Y(()=>{o.value=pe(e.value.outline??t.value.outline)}),(r,d)=>o.value.length>0?(a(),i("div",gs,[c("button",{onClick:d[0]||(d[0]=p=>n.value=!n.value),class:C({open:n.value})},[z(L(l(he)(l(t)))+" ",1),_(fe,{class:"icon"})],2),n.value?(a(),i("div",$s,[_(_e,{headers:o.value},null,8,["headers"])])):f("",!0)])):f("",!0)}}),bs=m(ks,[["__scopeId","data-v-afa18848"]]),ys=s=>(H("data-v-e521b9a4"),s=s(),E(),s),Vs={class:"container"},Ps=ys(()=>c("div",{class:"aside-curtain"},null,-1)),ws={class:"aside-container"},Ls={class:"aside-content"},Ss={class:"content"},Ms={class:"content-container"},Cs={class:"main"},Is=g({__name:"VPDoc",setup(s){const{theme:e}=V(),t=se(),{hasSidebar:n,hasAside:o,leftAside:r}=F(),d=b(()=>t.path.replace(/[./]+/g,"_").replace(/_html$/,""));return(p,v)=>{const $=j("Content");return a(),i("div",{class:C(["VPDoc",{"has-sidebar":l(n),"has-aside":l(o)}])},[u(p.$slots,"doc-top",{},void 0,!0),c("div",Vs,[l(o)?(a(),i("div",{key:0,class:C(["aside",{"left-aside":l(r)}])},[Ps,c("div",ws,[c("div",Ls,[_(Ft,null,{"aside-top":h(()=>[u(p.$slots,"aside-top",{},void 0,!0)]),"aside-bottom":h(()=>[u(p.$slots,"aside-bottom",{},void 0,!0)]),"aside-outline-before":h(()=>[u(p.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":h(()=>[u(p.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":h(()=>[u(p.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":h(()=>[u(p.$slots,"aside-ads-after",{},void 0,!0)]),_:3})])])],2)):f("",!0),c("div",Ss,[c("div",Ms,[u(p.$slots,"doc-before",{},void 0,!0),_(bs),c("main",Cs,[_($,{class:C(["vp-doc",[d.value,l(e).externalLinkIcon&&"external-link-icon-enabled"]])},null,8,["class"])]),_(vs,null,{"doc-footer-before":h(()=>[u(p.$slots,"doc-footer-before",{},void 0,!0)]),_:3}),u(p.$slots,"doc-after",{},void 0,!0)])])]),u(p.$slots,"doc-bottom",{},void 0,!0)],2)}}}),Ts=m(Is,[["__scopeId","data-v-e521b9a4"]]),As=g({__name:"VPButton",props:{tag:{},size:{default:"medium"},theme:{default:"brand"},text:{},href:{}},setup(s){const e=s,t=b(()=>e.href&&Pe.test(e.href)),n=b(()=>e.tag||e.href?"a":"button");return(o,r)=>(a(),k(x(n.value),{class:C(["VPButton",[o.size,o.theme]]),href:o.href?l(J)(o.href):void 0,target:t.value?"_blank":void 0,rel:t.value?"noreferrer":void 0},{default:h(()=>[z(L(o.text),1)]),_:1},8,["class","href","target","rel"]))}}),Bs=m(As,[["__scopeId","data-v-fb532479"]]),Ns=["src","alt"],zs=g({inheritAttrs:!1,__name:"VPImage",props:{image:{},alt:{}},setup(s){return(e,t)=>{const n=j("VPImage",!0);return e.image?(a(),i(M,{key:0},[typeof e.image=="string"||"src"in e.image?(a(),i("img",Q({key:0,class:"VPImage"},typeof e.image=="string"?e.$attrs:{...e.image,...e.$attrs},{src:l(ue)(typeof e.image=="string"?e.image:e.image.src),alt:e.alt??(typeof e.image=="string"?"":e.image.alt||"")}),null,16,Ns)):(a(),i(M,{key:1},[_(n,Q({class:"dark",image:e.image.dark,alt:e.image.alt},e.$attrs),null,16,["image","alt"]),_(n,Q({class:"light",image:e.image.light,alt:e.image.alt},e.$attrs),null,16,["image","alt"])],64))],64)):f("",!0)}}}),ee=m(zs,[["__scopeId","data-v-35a7d0b8"]]),Hs=s=>(H("data-v-4feefb96"),s=s(),E(),s),Es={class:"container"},Fs={class:"main"},Ds={key:0,class:"name"},xs=["innerHTML"],Os=["innerHTML"],Gs=["innerHTML"],Us={key:0,class:"actions"},js={key:0,class:"image"},qs={class:"image-container"},Rs=Hs(()=>c("div",{class:"image-bg"},null,-1)),Ks=g({__name:"VPHero",props:{name:{},text:{},tagline:{},image:{},actions:{}},setup(s){const e=ne("hero-image-slot-exists");return(t,n)=>(a(),i("div",{class:C(["VPHero",{"has-image":t.image||l(e)}])},[c("div",Es,[c("div",Fs,[u(t.$slots,"home-hero-info",{},()=>[t.name?(a(),i("h1",Ds,[c("span",{innerHTML:t.name,class:"clip"},null,8,xs)])):f("",!0),t.text?(a(),i("p",{key:1,innerHTML:t.text,class:"text"},null,8,Os)):f("",!0),t.tagline?(a(),i("p",{key:2,innerHTML:t.tagline,class:"tagline"},null,8,Gs)):f("",!0)],!0),t.actions?(a(),i("div",Us,[(a(!0),i(M,null,N(t.actions,o=>(a(),i("div",{key:o.link,class:"action"},[_(Bs,{tag:"a",size:"medium",theme:o.theme,text:o.text,href:o.link},null,8,["theme","text","href"])]))),128))])):f("",!0)]),t.image||l(e)?(a(),i("div",js,[c("div",qs,[Rs,u(t.$slots,"home-hero-image",{},()=>[t.image?(a(),k(ee,{key:0,class:"image-src",image:t.image},null,8,["image"])):f("",!0)],!0)])])):f("",!0)])],2))}}),Ws=m(Ks,[["__scopeId","data-v-4feefb96"]]),Ys=g({__name:"VPHomeHero",setup(s){const{frontmatter:e}=V();return(t,n)=>l(e).hero?(a(),k(Ws,{key:0,class:"VPHomeHero",name:l(e).hero.name,text:l(e).hero.text,tagline:l(e).hero.tagline,image:l(e).hero.image,actions:l(e).hero.actions},{"home-hero-info":h(()=>[u(t.$slots,"home-hero-info")]),"home-hero-image":h(()=>[u(t.$slots,"home-hero-image")]),_:3},8,["name","text","tagline","image","actions"])):f("",!0)}}),Js={},Zs={xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},Qs=c("path",{d:"M19.9,12.4c0.1-0.2,0.1-0.5,0-0.8c-0.1-0.1-0.1-0.2-0.2-0.3l-7-7c-0.4-0.4-1-0.4-1.4,0s-0.4,1,0,1.4l5.3,5.3H5c-0.6,0-1,0.4-1,1s0.4,1,1,1h11.6l-5.3,5.3c-0.4,0.4-0.4,1,0,1.4c0.2,0.2,0.5,0.3,0.7,0.3s0.5-0.1,0.7-0.3l7-7C19.8,12.6,19.9,12.5,19.9,12.4z"},null,-1),Xs=[Qs];function en(s,e){return a(),i("svg",Zs,Xs)}const tn=m(Js,[["render",en]]),sn={class:"box"},nn={key:0,class:"icon"},on=["innerHTML"],an=["innerHTML"],rn=["innerHTML"],ln={key:4,class:"link-text"},cn={class:"link-text-value"},un=g({__name:"VPFeature",props:{icon:{},title:{},details:{},link:{},linkText:{},rel:{},target:{}},setup(s){return(e,t)=>(a(),k(D,{class:"VPFeature",href:e.link,rel:e.rel,target:e.target,"no-icon":!0,tag:e.link?"a":"div"},{default:h(()=>[c("article",sn,[typeof e.icon=="object"&&e.icon.wrap?(a(),i("div",nn,[_(ee,{image:e.icon,alt:e.icon.alt,height:e.icon.height||48,width:e.icon.width||48},null,8,["image","alt","height","width"])])):typeof e.icon=="object"?(a(),k(ee,{key:1,image:e.icon,alt:e.icon.alt,height:e.icon.height||48,width:e.icon.width||48},null,8,["image","alt","height","width"])):e.icon?(a(),i("div",{key:2,class:"icon",innerHTML:e.icon},null,8,on)):f("",!0),c("h2",{class:"title",innerHTML:e.title},null,8,an),e.details?(a(),i("p",{key:3,class:"details",innerHTML:e.details},null,8,rn)):f("",!0),e.linkText?(a(),i("div",ln,[c("p",cn,[z(L(e.linkText)+" ",1),_(tn,{class:"link-text-icon"})])])):f("",!0)])]),_:1},8,["href","rel","target","tag"]))}}),dn=m(un,[["__scopeId","data-v-362f5d26"]]),vn={key:0,class:"VPFeatures"},hn={class:"container"},pn={class:"items"},_n=g({__name:"VPFeatures",props:{features:{}},setup(s){const e=s,t=b(()=>{const n=e.features.length;if(n){if(n===2)return"grid-2";if(n===3)return"grid-3";if(n%3===0)return"grid-6";if(n>3)return"grid-4"}else return});return(n,o)=>n.features?(a(),i("div",vn,[c("div",hn,[c("div",pn,[(a(!0),i(M,null,N(n.features,r=>(a(),i("div",{key:r.title,class:C(["item",[t.value]])},[_(dn,{icon:r.icon,title:r.title,details:r.details,link:r.link,"link-text":r.linkText,rel:r.rel,target:r.target},null,8,["icon","title","details","link","link-text","rel","target"])],2))),128))])])])):f("",!0)}}),fn=m(_n,[["__scopeId","data-v-d0a190d7"]]),mn=g({__name:"VPHomeFeatures",setup(s){const{frontmatter:e}=V();return(t,n)=>l(e).features?(a(),k(fn,{key:0,class:"VPHomeFeatures",features:l(e).features},null,8,["features"])):f("",!0)}}),gn={class:"VPHome"},$n=g({__name:"VPHome",setup(s){return(e,t)=>{const n=j("Content");return a(),i("div",gn,[u(e.$slots,"home-hero-before",{},void 0,!0),_(Ys,null,{"home-hero-info":h(()=>[u(e.$slots,"home-hero-info",{},void 0,!0)]),"home-hero-image":h(()=>[u(e.$slots,"home-hero-image",{},void 0,!0)]),_:3}),u(e.$slots,"home-hero-after",{},void 0,!0),u(e.$slots,"home-features-before",{},void 0,!0),_(mn),u(e.$slots,"home-features-after",{},void 0,!0),_(n)])}}}),kn=m($n,[["__scopeId","data-v-c3f834ca"]]),bn={},yn={class:"VPPage"};function Vn(s,e){const t=j("Content");return a(),i("div",yn,[u(s.$slots,"page-top"),_(t),u(s.$slots,"page-bottom")])}const Pn=m(bn,[["render",Vn]]),wn=g({__name:"VPContent",setup(s){const{page:e,frontmatter:t}=V(),{hasSidebar:n}=F();return(o,r)=>(a(),i("div",{class:C(["VPContent",{"has-sidebar":l(n),"is-home":l(t).layout==="home"}]),id:"VPContent"},[l(e).isNotFound?u(o.$slots,"not-found",{key:0},()=>[_(vt)],!0):l(t).layout==="page"?(a(),k(Pn,{key:1},{"page-top":h(()=>[u(o.$slots,"page-top",{},void 0,!0)]),"page-bottom":h(()=>[u(o.$slots,"page-bottom",{},void 0,!0)]),_:3})):l(t).layout==="home"?(a(),k(kn,{key:2},{"home-hero-before":h(()=>[u(o.$slots,"home-hero-before",{},void 0,!0)]),"home-hero-info":h(()=>[u(o.$slots,"home-hero-info",{},void 0,!0)]),"home-hero-image":h(()=>[u(o.$slots,"home-hero-image",{},void 0,!0)]),"home-hero-after":h(()=>[u(o.$slots,"home-hero-after",{},void 0,!0)]),"home-features-before":h(()=>[u(o.$slots,"home-features-before",{},void 0,!0)]),"home-features-after":h(()=>[u(o.$slots,"home-features-after",{},void 0,!0)]),_:3})):l(t).layout&&l(t).layout!=="doc"?(a(),k(x(l(t).layout),{key:3})):(a(),k(Ts,{key:4},{"doc-top":h(()=>[u(o.$slots,"doc-top",{},void 0,!0)]),"doc-bottom":h(()=>[u(o.$slots,"doc-bottom",{},void 0,!0)]),"doc-footer-before":h(()=>[u(o.$slots,"doc-footer-before",{},void 0,!0)]),"doc-before":h(()=>[u(o.$slots,"doc-before",{},void 0,!0)]),"doc-after":h(()=>[u(o.$slots,"doc-after",{},void 0,!0)]),"aside-top":h(()=>[u(o.$slots,"aside-top",{},void 0,!0)]),"aside-outline-before":h(()=>[u(o.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":h(()=>[u(o.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":h(()=>[u(o.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":h(()=>[u(o.$slots,"aside-ads-after",{},void 0,!0)]),"aside-bottom":h(()=>[u(o.$slots,"aside-bottom",{},void 0,!0)]),_:3}))],2))}}),Ln=m(wn,[["__scopeId","data-v-3cfaefff"]]),Sn={class:"container"},Mn=["innerHTML"],Cn=["innerHTML"],In=g({__name:"VPFooter",setup(s){const{theme:e,frontmatter:t}=V(),{hasSidebar:n}=F();return(o,r)=>l(e).footer&&l(t).footer!==!1?(a(),i("footer",{key:0,class:C(["VPFooter",{"has-sidebar":l(n)}])},[c("div",Sn,[l(e).footer.message?(a(),i("p",{key:0,class:"message",innerHTML:l(e).footer.message},null,8,Mn)):f("",!0),l(e).footer.copyright?(a(),i("p",{key:1,class:"copyright",innerHTML:l(e).footer.copyright},null,8,Cn)):f("",!0)])],2)):f("",!0)}}),Tn=m(In,[["__scopeId","data-v-c970a860"]]),An={class:"header"},Bn={class:"outline"},Nn=g({__name:"VPLocalNavOutlineDropdown",props:{headers:{},navHeight:{}},setup(s){const e=s,{theme:t}=V(),n=S(!1),o=S(0),r=S();Y(()=>{n.value=!1});function d(){n.value=!n.value,o.value=window.innerHeight+Math.min(window.scrollY-e.navHeight,0)}function p($){$.target.classList.contains("outline-link")&&(r.value&&(r.value.style.transition="none"),Ge(()=>{n.value=!1}))}function v(){n.value=!1,window.scrollTo({top:0,left:0,behavior:"smooth"})}return($,y)=>(a(),i("div",{class:"VPLocalNavOutlineDropdown",style:Oe({"--vp-vh":o.value+"px"})},[$.headers.length>0?(a(),i("button",{key:0,onClick:d,class:C({open:n.value})},[z(L(l(he)(l(t)))+" ",1),_(fe,{class:"icon"})],2)):(a(),i("button",{key:1,onClick:v},L(l(t).returnToTopLabel||"Return to top"),1)),_(ce,{name:"flyout"},{default:h(()=>[n.value?(a(),i("div",{key:0,ref_key:"items",ref:r,class:"items",onClick:p},[c("div",An,[c("a",{class:"top-link",href:"#",onClick:v},L(l(t).returnToTopLabel||"Return to top"),1)]),c("div",Bn,[_(_e,{headers:$.headers},null,8,["headers"])])],512)):f("",!0)]),_:1})],4))}}),zn=m(Nn,[["__scopeId","data-v-1b525709"]]),Hn={},En={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},Fn=c("path",{d:"M17,11H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S17.6,11,17,11z"},null,-1),Dn=c("path",{d:"M21,7H3C2.4,7,2,6.6,2,6s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,7,21,7z"},null,-1),xn=c("path",{d:"M21,15H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,15,21,15z"},null,-1),On=c("path",{d:"M17,19H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S17.6,19,17,19z"},null,-1),Gn=[Fn,Dn,xn,On];function Un(s,e){return a(),i("svg",En,Gn)}const jn=m(Hn,[["render",Un]]),qn=["aria-expanded"],Rn={class:"menu-text"},Kn=g({__name:"VPLocalNav",props:{open:{type:Boolean}},emits:["open-menu"],setup(s){const{theme:e,frontmatter:t}=V(),{hasSidebar:n}=F(),{y:o}=we(),r=ve([]),d=S(0);G(()=>{d.value=parseInt(getComputedStyle(document.documentElement).getPropertyValue("--vp-nav-height"))}),Y(()=>{r.value=pe(t.value.outline??e.value.outline)});const p=b(()=>r.value.length===0&&!n.value),v=b(()=>({VPLocalNav:!0,fixed:p.value,"reached-top":o.value>=d.value}));return($,y)=>l(t).layout!=="home"&&(!p.value||l(o)>=d.value)?(a(),i("div",{key:0,class:C(v.value)},[l(n)?(a(),i("button",{key:0,class:"menu","aria-expanded":$.open,"aria-controls":"VPSidebarNav",onClick:y[0]||(y[0]=w=>$.$emit("open-menu"))},[_(jn,{class:"menu-icon"}),c("span",Rn,L(l(e).sidebarMenuLabel||"Menu"),1)],8,qn)):f("",!0),_(zn,{headers:r.value,navHeight:d.value},null,8,["headers","navHeight"])],2)):f("",!0)}}),Wn=m(Kn,[["__scopeId","data-v-2090af1f"]]);function Yn(){const s=S(!1);function e(){s.value=!0,window.addEventListener("resize",o)}function t(){s.value=!1,window.removeEventListener("resize",o)}function n(){s.value?t():e()}function o(){window.outerWidth>=768&&t()}const r=se();return U(()=>r.path,t),{isScreenOpen:s,openScreen:e,closeScreen:t,toggleScreen:n}}const Jn={},Zn={class:"VPSwitch",type:"button",role:"switch"},Qn={class:"check"},Xn={key:0,class:"icon"};function eo(s,e){return a(),i("button",Zn,[c("span",Qn,[s.$slots.default?(a(),i("span",Xn,[u(s.$slots,"default",{},void 0,!0)])):f("",!0)])])}const to=m(Jn,[["render",eo],["__scopeId","data-v-b60171c1"]]),so={},no={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},oo=c("path",{d:"M12.1,22c-0.3,0-0.6,0-0.9,0c-5.5-0.5-9.5-5.4-9-10.9c0.4-4.8,4.2-8.6,9-9c0.4,0,0.8,0.2,1,0.5c0.2,0.3,0.2,0.8-0.1,1.1c-2,2.7-1.4,6.4,1.3,8.4c2.1,1.6,5,1.6,7.1,0c0.3-0.2,0.7-0.3,1.1-0.1c0.3,0.2,0.5,0.6,0.5,1c-0.2,2.7-1.5,5.1-3.6,6.8C16.6,21.2,14.4,22,12.1,22zM9.3,4.4c-2.9,1-5,3.6-5.2,6.8c-0.4,4.4,2.8,8.3,7.2,8.7c2.1,0.2,4.2-0.4,5.8-1.8c1.1-0.9,1.9-2.1,2.4-3.4c-2.5,0.9-5.3,0.5-7.5-1.1C9.2,11.4,8.1,7.7,9.3,4.4z"},null,-1),ao=[oo];function ro(s,e){return a(),i("svg",no,ao)}const lo=m(so,[["render",ro]]),io={},co={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},uo=Le('',9),vo=[uo];function ho(s,e){return a(),i("svg",co,vo)}const po=m(io,[["render",ho]]),_o=g({__name:"VPSwitchAppearance",setup(s){const{isDark:e}=V(),t=ne("toggle-appearance",()=>{e.value=!e.value}),n=b(()=>e.value?"Switch to light theme":"Switch to dark theme");return(o,r)=>(a(),k(to,{title:n.value,class:"VPSwitchAppearance","aria-checked":l(e),onClick:l(t)},{default:h(()=>[_(po,{class:"sun"}),_(lo,{class:"moon"})]),_:1},8,["title","aria-checked","onClick"]))}}),me=m(_o,[["__scopeId","data-v-0ddf2836"]]),fo={key:0,class:"VPNavBarAppearance"},mo=g({__name:"VPNavBarAppearance",setup(s){const{site:e}=V();return(t,n)=>l(e).appearance&&l(e).appearance!=="force-dark"?(a(),i("div",fo,[_(me)])):f("",!0)}}),go=m(mo,[["__scopeId","data-v-ead91a81"]]),ge=S();let Te=!1,ae=0;function $o(s){const e=S(!1);if(K){!Te&&ko(),ae++;const t=U(ge,n=>{var o,r,d;n===s.el.value||(o=s.el.value)!=null&&o.contains(n)?(e.value=!0,(r=s.onFocus)==null||r.call(s)):(e.value=!1,(d=s.onBlur)==null||d.call(s))});de(()=>{t(),ae--,ae||bo()})}return Ue(e)}function ko(){document.addEventListener("focusin",Ae),Te=!0,ge.value=document.activeElement}function bo(){document.removeEventListener("focusin",Ae)}function Ae(){ge.value=document.activeElement}const yo={},Vo={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},Po=c("path",{d:"M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"},null,-1),wo=[Po];function Lo(s,e){return a(),i("svg",Vo,wo)}const Be=m(yo,[["render",Lo]]),So={},Mo={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},Co=c("circle",{cx:"12",cy:"12",r:"2"},null,-1),Io=c("circle",{cx:"19",cy:"12",r:"2"},null,-1),To=c("circle",{cx:"5",cy:"12",r:"2"},null,-1),Ao=[Co,Io,To];function Bo(s,e){return a(),i("svg",Mo,Ao)}const No=m(So,[["render",Bo]]),zo={class:"VPMenuLink"},Ho=g({__name:"VPMenuLink",props:{item:{}},setup(s){const{page:e}=V();return(t,n)=>(a(),i("div",zo,[_(D,{class:C({active:l(O)(l(e).relativePath,t.item.activeMatch||t.item.link,!!t.item.activeMatch)}),href:t.item.link,target:t.item.target,rel:t.item.rel},{default:h(()=>[z(L(t.item.text),1)]),_:1},8,["class","href","target","rel"])]))}}),oe=m(Ho,[["__scopeId","data-v-8b74d055"]]),Eo={class:"VPMenuGroup"},Fo={key:0,class:"title"},Do=g({__name:"VPMenuGroup",props:{text:{},items:{}},setup(s){return(e,t)=>(a(),i("div",Eo,[e.text?(a(),i("p",Fo,L(e.text),1)):f("",!0),(a(!0),i(M,null,N(e.items,n=>(a(),i(M,null,["link"in n?(a(),k(oe,{key:0,item:n},null,8,["item"])):f("",!0)],64))),256))]))}}),xo=m(Do,[["__scopeId","data-v-48c802d0"]]),Oo={class:"VPMenu"},Go={key:0,class:"items"},Uo=g({__name:"VPMenu",props:{items:{}},setup(s){return(e,t)=>(a(),i("div",Oo,[e.items?(a(),i("div",Go,[(a(!0),i(M,null,N(e.items,n=>(a(),i(M,{key:n.text},["link"in n?(a(),k(oe,{key:0,item:n},null,8,["item"])):(a(),k(xo,{key:1,text:n.text,items:n.items},null,8,["text","items"]))],64))),128))])):f("",!0),u(e.$slots,"default",{},void 0,!0)]))}}),jo=m(Uo,[["__scopeId","data-v-97491713"]]),qo=["aria-expanded","aria-label"],Ro={key:0,class:"text"},Ko=["innerHTML"],Wo={class:"menu"},Yo=g({__name:"VPFlyout",props:{icon:{},button:{},label:{},items:{}},setup(s){const e=S(!1),t=S();$o({el:t,onBlur:n});function n(){e.value=!1}return(o,r)=>(a(),i("div",{class:"VPFlyout",ref_key:"el",ref:t,onMouseenter:r[1]||(r[1]=d=>e.value=!0),onMouseleave:r[2]||(r[2]=d=>e.value=!1)},[c("button",{type:"button",class:"button","aria-haspopup":"true","aria-expanded":e.value,"aria-label":o.label,onClick:r[0]||(r[0]=d=>e.value=!e.value)},[o.button||o.icon?(a(),i("span",Ro,[o.icon?(a(),k(x(o.icon),{key:0,class:"option-icon"})):f("",!0),o.button?(a(),i("span",{key:1,innerHTML:o.button},null,8,Ko)):f("",!0),_(Be,{class:"text-icon"})])):(a(),k(No,{key:1,class:"icon"}))],8,qo),c("div",Wo,[_(jo,{items:o.items},{default:h(()=>[u(o.$slots,"default",{},void 0,!0)]),_:3},8,["items"])])],544))}}),$e=m(Yo,[["__scopeId","data-v-473f4f87"]]),Jo={discord:'Discord',facebook:'Facebook',github:'GitHub',instagram:'Instagram',linkedin:'LinkedIn',mastodon:'Mastodon',slack:'Slack',twitter:'Twitter',x:'X',youtube:'YouTube'},Zo=["href","aria-label","innerHTML"],Qo=g({__name:"VPSocialLink",props:{icon:{},link:{},ariaLabel:{}},setup(s){const e=s,t=b(()=>typeof e.icon=="object"?e.icon.svg:Jo[e.icon]);return(n,o)=>(a(),i("a",{class:"VPSocialLink no-icon",href:n.link,"aria-label":n.ariaLabel??(typeof n.icon=="string"?n.icon:""),target:"_blank",rel:"noopener",innerHTML:t.value},null,8,Zo))}}),Xo=m(Qo,[["__scopeId","data-v-a94e74d9"]]),ea={class:"VPSocialLinks"},ta=g({__name:"VPSocialLinks",props:{links:{}},setup(s){return(e,t)=>(a(),i("div",ea,[(a(!0),i(M,null,N(e.links,({link:n,icon:o,ariaLabel:r})=>(a(),k(Xo,{key:n,icon:o,link:n,ariaLabel:r},null,8,["icon","link","ariaLabel"]))),128))]))}}),ke=m(ta,[["__scopeId","data-v-ee7a9424"]]),sa={key:0,class:"group translations"},na={class:"trans-title"},oa={key:1,class:"group"},aa={class:"item appearance"},ra={class:"label"},la={class:"appearance-action"},ia={key:2,class:"group"},ca={class:"item social-links"},ua=g({__name:"VPNavBarExtra",setup(s){const{site:e,theme:t}=V(),{localeLinks:n,currentLang:o}=Z({correspondingLink:!0}),r=b(()=>n.value.length&&o.value.label||e.value.appearance||t.value.socialLinks);return(d,p)=>r.value?(a(),k($e,{key:0,class:"VPNavBarExtra",label:"extra navigation"},{default:h(()=>[l(n).length&&l(o).label?(a(),i("div",sa,[c("p",na,L(l(o).label),1),(a(!0),i(M,null,N(l(n),v=>(a(),k(oe,{key:v.link,item:v},null,8,["item"]))),128))])):f("",!0),l(e).appearance&&l(e).appearance!=="force-dark"?(a(),i("div",oa,[c("div",aa,[c("p",ra,L(l(t).darkModeSwitchLabel||"Appearance"),1),c("div",la,[_(me)])])])):f("",!0),l(t).socialLinks?(a(),i("div",ia,[c("div",ca,[_(ke,{class:"social-links-list",links:l(t).socialLinks},null,8,["links"])])])):f("",!0)]),_:1})):f("",!0)}}),da=m(ua,[["__scopeId","data-v-9b536d0b"]]),va=s=>(H("data-v-5dea55bf"),s=s(),E(),s),ha=["aria-expanded"],pa=va(()=>c("span",{class:"container"},[c("span",{class:"top"}),c("span",{class:"middle"}),c("span",{class:"bottom"})],-1)),_a=[pa],fa=g({__name:"VPNavBarHamburger",props:{active:{type:Boolean}},emits:["click"],setup(s){return(e,t)=>(a(),i("button",{type:"button",class:C(["VPNavBarHamburger",{active:e.active}]),"aria-label":"mobile navigation","aria-expanded":e.active,"aria-controls":"VPNavScreen",onClick:t[0]||(t[0]=n=>e.$emit("click"))},_a,10,ha))}}),ma=m(fa,[["__scopeId","data-v-5dea55bf"]]),ga=["innerHTML"],$a=g({__name:"VPNavBarMenuLink",props:{item:{}},setup(s){const{page:e}=V();return(t,n)=>(a(),k(D,{class:C({VPNavBarMenuLink:!0,active:l(O)(l(e).relativePath,t.item.activeMatch||t.item.link,!!t.item.activeMatch)}),href:t.item.link,target:t.item.target,rel:t.item.rel,tabindex:"0"},{default:h(()=>[c("span",{innerHTML:t.item.text},null,8,ga)]),_:1},8,["class","href","target","rel"]))}}),ka=m($a,[["__scopeId","data-v-2781b5e7"]]),ba=g({__name:"VPNavBarMenuGroup",props:{item:{}},setup(s){const e=s,{page:t}=V(),n=r=>"link"in r?O(t.value.relativePath,r.link,!!e.item.activeMatch):r.items.some(n),o=b(()=>n(e.item));return(r,d)=>(a(),k($e,{class:C({VPNavBarMenuGroup:!0,active:l(O)(l(t).relativePath,r.item.activeMatch,!!r.item.activeMatch)||o.value}),button:r.item.text,items:r.item.items},null,8,["class","button","items"]))}}),ya=s=>(H("data-v-492ea56d"),s=s(),E(),s),Va={key:0,"aria-labelledby":"main-nav-aria-label",class:"VPNavBarMenu"},Pa=ya(()=>c("span",{id:"main-nav-aria-label",class:"visually-hidden"},"Main Navigation",-1)),wa=g({__name:"VPNavBarMenu",setup(s){const{theme:e}=V();return(t,n)=>l(e).nav?(a(),i("nav",Va,[Pa,(a(!0),i(M,null,N(l(e).nav,o=>(a(),i(M,{key:o.text},["link"in o?(a(),k(ka,{key:0,item:o},null,8,["item"])):(a(),k(ba,{key:1,item:o},null,8,["item"]))],64))),128))])):f("",!0)}}),La=m(wa,[["__scopeId","data-v-492ea56d"]]);function Sa(s,e){const{localeIndex:t}=V();function n(o){var I,B;const r=o.split("."),d=s&&typeof s=="object",p=d&&((B=(I=s.locales)==null?void 0:I[t.value])==null?void 0:B.translations)||null,v=d&&s.translations||null;let $=p,y=v,w=e;const T=r.pop();for(const A of r){let P=null;const q=w==null?void 0:w[A];q&&(P=w=q);const W=y==null?void 0:y[A];W&&(P=y=W);const R=$==null?void 0:$[A];R&&(P=$=R),q||(w=P),W||(y=P),R||($=P)}return($==null?void 0:$[T])??(y==null?void 0:y[T])??(w==null?void 0:w[T])??""}return n}const Ma=["aria-label"],Ca={class:"DocSearch-Button-Container"},Ia=c("svg",{class:"DocSearch-Search-Icon",width:"20",height:"20",viewBox:"0 0 20 20","aria-label":"search icon"},[c("path",{d:"M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z",stroke:"currentColor",fill:"none","fill-rule":"evenodd","stroke-linecap":"round","stroke-linejoin":"round"})],-1),Ta={class:"DocSearch-Button-Placeholder"},Aa=c("span",{class:"DocSearch-Button-Keys"},[c("kbd",{class:"DocSearch-Button-Key"}),c("kbd",{class:"DocSearch-Button-Key"},"K")],-1),ye=g({__name:"VPNavBarSearchButton",setup(s){const{theme:e}=V(),t={button:{buttonText:"Search",buttonAriaLabel:"Search"}},n=je(Sa)(qe(()=>{var o;return(o=e.value.search)==null?void 0:o.options}),t);return(o,r)=>(a(),i("button",{type:"button",class:"DocSearch DocSearch-Button","aria-label":l(n)("button.buttonAriaLabel")},[c("span",Ca,[Ia,c("span",Ta,L(l(n)("button.buttonText")),1)]),Aa],8,Ma))}}),Ba={class:"VPNavBarSearch"},Na={id:"local-search"},za={key:1,id:"docsearch"},Ha=g({__name:"VPNavBarSearch",setup(s){const e=()=>null,t=()=>null,{theme:n}=V(),o=S(!1),r=S(!1);G(()=>{});function d(){o.value||(o.value=!0,setTimeout(p,16))}function p(){const y=new Event("keydown");y.key="k",y.metaKey=!0,window.dispatchEvent(y),setTimeout(()=>{document.querySelector(".DocSearch-Modal")||p()},16)}const v=S(!1),$="";return(y,w)=>{var T;return a(),i("div",Ba,[l($)==="local"?(a(),i(M,{key:0},[v.value?(a(),k(l(e),{key:0,onClose:w[0]||(w[0]=I=>v.value=!1)})):f("",!0),c("div",Na,[_(ye,{onClick:w[1]||(w[1]=I=>v.value=!0)})])],64)):l($)==="algolia"?(a(),i(M,{key:1},[o.value?(a(),k(l(t),{key:0,algolia:((T=l(n).search)==null?void 0:T.options)??l(n).algolia,onVnodeBeforeMount:w[2]||(w[2]=I=>r.value=!0)},null,8,["algolia"])):f("",!0),r.value?f("",!0):(a(),i("div",za,[_(ye,{onClick:d})]))],64)):f("",!0)])}}}),Ea=g({__name:"VPNavBarSocialLinks",setup(s){const{theme:e}=V();return(t,n)=>l(e).socialLinks?(a(),k(ke,{key:0,class:"VPNavBarSocialLinks",links:l(e).socialLinks},null,8,["links"])):f("",!0)}}),Fa=m(Ea,[["__scopeId","data-v-164c457f"]]),Da={},xa={viewBox:"0 0 949 114"},Oa=Le('',9),Ga=[Oa];function Ua(s,e,t,n,o,r){return a(),i("svg",xa,Ga)}const ja=m(Da,[["render",Ua],["__scopeId","data-v-023efc27"]]),qa=["href"],Ra={__name:"VPNavBarTitle",setup(s){const{site:e,theme:t}=V(),{hasSidebar:n}=F(),{currentLang:o}=Z();return(r,d)=>(a(),i("div",{class:C(["VPNavBarTitle",{"has-sidebar":l(n)}])},[c("a",{class:"title",href:l(t).logoLink??l(J)(l(o).link)},[u(r.$slots,"nav-bar-title-before",{},void 0,!0),l(t).logoComponent?(a(),k(ja,{key:0,class:"logo"})):l(t).logo?(a(),k(ee,{key:1,class:"logo",image:l(t).logo},null,8,["image"])):l(t).siteTitle?(a(),i(M,{key:2},[z(L(l(t).siteTitle),1)],64)):l(t).siteTitle===void 0?(a(),i(M,{key:3},[z(L(l(e).title),1)],64)):f("",!0),u(r.$slots,"nav-bar-title-after",{},void 0,!0)],8,qa)],2))}},Ka=m(Ra,[["__scopeId","data-v-02319261"]]),Wa={},Ya={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},Ja=c("path",{d:"M0 0h24v24H0z",fill:"none"},null,-1),Za=c("path",{d:" M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z ",class:"css-c4d79v"},null,-1),Qa=[Ja,Za];function Xa(s,e){return a(),i("svg",Ya,Qa)}const Ne=m(Wa,[["render",Xa]]),e1={class:"items"},t1={class:"title"},s1=g({__name:"VPNavBarTranslations",setup(s){const{theme:e}=V(),{localeLinks:t,currentLang:n}=Z({correspondingLink:!0});return(o,r)=>l(t).length&&l(n).label?(a(),k($e,{key:0,class:"VPNavBarTranslations",icon:Ne,label:l(e).langMenuLabel||"Change language"},{default:h(()=>[c("div",e1,[c("p",t1,L(l(n).label),1),(a(!0),i(M,null,N(l(t),d=>(a(),k(oe,{key:d.link,item:d},null,8,["item"]))),128))])]),_:1},8,["label"])):f("",!0)}}),n1=m(s1,[["__scopeId","data-v-bb3d9832"]]),o1=s=>(H("data-v-2fb52a83"),s=s(),E(),s),a1={class:"container"},r1={class:"title"},l1={class:"content"},i1=o1(()=>c("div",{class:"curtain"},null,-1)),c1={class:"content-body"},u1=g({__name:"VPNavBar",props:{isScreenOpen:{type:Boolean}},emits:["toggle-screen"],setup(s){const{y:e}=we(),{hasSidebar:t}=F(),{frontmatter:n}=V(),o=S({});return Ve(()=>{o.value={"has-sidebar":t.value,top:n.value.layout==="home"&&e.value===0}}),(r,d)=>(a(),i("div",{class:C(["VPNavBar",o.value])},[c("div",a1,[c("div",r1,[_(Ka,null,{"nav-bar-title-before":h(()=>[u(r.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":h(()=>[u(r.$slots,"nav-bar-title-after",{},void 0,!0)]),_:3})]),c("div",l1,[i1,c("div",c1,[u(r.$slots,"nav-bar-content-before",{},void 0,!0),_(Ha,{class:"search"}),_(La,{class:"menu"}),_(n1,{class:"translations"}),_(go,{class:"appearance"}),_(Fa,{class:"social-links"}),_(da,{class:"extra"}),u(r.$slots,"nav-bar-content-after",{},void 0,!0),_(ma,{class:"hamburger",active:r.isScreenOpen,onClick:d[0]||(d[0]=p=>r.$emit("toggle-screen"))},null,8,["active"])])])])],2))}}),d1=m(u1,[["__scopeId","data-v-2fb52a83"]]),v1={key:0,class:"VPNavScreenAppearance"},h1={class:"text"},p1=g({__name:"VPNavScreenAppearance",setup(s){const{site:e,theme:t}=V();return(n,o)=>l(e).appearance&&l(e).appearance!=="force-dark"?(a(),i("div",v1,[c("p",h1,L(l(t).darkModeSwitchLabel||"Appearance"),1),_(me)])):f("",!0)}}),_1=m(p1,[["__scopeId","data-v-2b89f08b"]]),f1=g({__name:"VPNavScreenMenuLink",props:{item:{}},setup(s){const e=ne("close-screen");return(t,n)=>(a(),k(D,{class:"VPNavScreenMenuLink",href:t.item.link,target:t.item.target,rel:t.item.rel,onClick:l(e)},{default:h(()=>[z(L(t.item.text),1)]),_:1},8,["href","target","rel","onClick"]))}}),m1=m(f1,[["__scopeId","data-v-d45ba3e8"]]),g1={},$1={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},k1=c("path",{d:"M18.9,10.9h-6v-6c0-0.6-0.4-1-1-1s-1,0.4-1,1v6h-6c-0.6,0-1,0.4-1,1s0.4,1,1,1h6v6c0,0.6,0.4,1,1,1s1-0.4,1-1v-6h6c0.6,0,1-0.4,1-1S19.5,10.9,18.9,10.9z"},null,-1),b1=[k1];function y1(s,e){return a(),i("svg",$1,b1)}const V1=m(g1,[["render",y1]]),P1=g({__name:"VPNavScreenMenuGroupLink",props:{item:{}},setup(s){const e=ne("close-screen");return(t,n)=>(a(),k(D,{class:"VPNavScreenMenuGroupLink",href:t.item.link,target:t.item.target,rel:t.item.rel,onClick:l(e)},{default:h(()=>[z(L(t.item.text),1)]),_:1},8,["href","target","rel","onClick"]))}}),ze=m(P1,[["__scopeId","data-v-7179dbb7"]]),w1={class:"VPNavScreenMenuGroupSection"},L1={key:0,class:"title"},S1=g({__name:"VPNavScreenMenuGroupSection",props:{text:{},items:{}},setup(s){return(e,t)=>(a(),i("div",w1,[e.text?(a(),i("p",L1,L(e.text),1)):f("",!0),(a(!0),i(M,null,N(e.items,n=>(a(),k(ze,{key:n.text,item:n},null,8,["item"]))),128))]))}}),M1=m(S1,[["__scopeId","data-v-4b8941ac"]]),C1=["aria-controls","aria-expanded"],I1=["innerHTML"],T1=["id"],A1={key:1,class:"group"},B1=g({__name:"VPNavScreenMenuGroup",props:{text:{},items:{}},setup(s){const e=s,t=S(!1),n=b(()=>`NavScreenGroup-${e.text.replace(" ","-").toLowerCase()}`);function o(){t.value=!t.value}return(r,d)=>(a(),i("div",{class:C(["VPNavScreenMenuGroup",{open:t.value}])},[c("button",{class:"button","aria-controls":n.value,"aria-expanded":t.value,onClick:o},[c("span",{class:"button-text",innerHTML:r.text},null,8,I1),_(V1,{class:"button-icon"})],8,C1),c("div",{id:n.value,class:"items"},[(a(!0),i(M,null,N(r.items,p=>(a(),i(M,{key:p.text},["link"in p?(a(),i("div",{key:p.text,class:"item"},[_(ze,{item:p},null,8,["item"])])):(a(),i("div",A1,[_(M1,{text:p.text,items:p.items},null,8,["text","items"])]))],64))),128))],8,T1)],2))}}),N1=m(B1,[["__scopeId","data-v-7c9d6a1d"]]),z1={key:0,class:"VPNavScreenMenu"},H1=g({__name:"VPNavScreenMenu",setup(s){const{theme:e}=V();return(t,n)=>l(e).nav?(a(),i("nav",z1,[(a(!0),i(M,null,N(l(e).nav,o=>(a(),i(M,{key:o.text},["link"in o?(a(),k(m1,{key:0,item:o},null,8,["item"])):(a(),k(N1,{key:1,text:o.text||"",items:o.items},null,8,["text","items"]))],64))),128))])):f("",!0)}}),E1=g({__name:"VPNavScreenSocialLinks",setup(s){const{theme:e}=V();return(t,n)=>l(e).socialLinks?(a(),k(ke,{key:0,class:"VPNavScreenSocialLinks",links:l(e).socialLinks},null,8,["links"])):f("",!0)}}),F1={class:"list"},D1=g({__name:"VPNavScreenTranslations",setup(s){const{localeLinks:e,currentLang:t}=Z({correspondingLink:!0}),n=S(!1);function o(){n.value=!n.value}return(r,d)=>l(e).length&&l(t).label?(a(),i("div",{key:0,class:C(["VPNavScreenTranslations",{open:n.value}])},[c("button",{class:"title",onClick:o},[_(Ne,{class:"icon lang"}),z(" "+L(l(t).label)+" ",1),_(Be,{class:"icon chevron"})]),c("ul",F1,[(a(!0),i(M,null,N(l(e),p=>(a(),i("li",{key:p.link,class:"item"},[_(D,{class:"link",href:p.link},{default:h(()=>[z(L(p.text),1)]),_:2},1032,["href"])]))),128))])],2)):f("",!0)}}),x1=m(D1,[["__scopeId","data-v-56996409"]]),O1={class:"container"},G1=g({__name:"VPNavScreen",props:{open:{type:Boolean}},setup(s){const e=S(null),t=Se(K?document.body:null);return(n,o)=>(a(),k(ce,{name:"fade",onEnter:o[0]||(o[0]=r=>t.value=!0),onAfterLeave:o[1]||(o[1]=r=>t.value=!1)},{default:h(()=>[n.open?(a(),i("div",{key:0,class:"VPNavScreen",ref_key:"screen",ref:e,id:"VPNavScreen"},[c("div",O1,[u(n.$slots,"nav-screen-content-before",{},void 0,!0),_(H1,{class:"menu"}),_(x1,{class:"translations"}),_(_1,{class:"appearance"}),_(E1,{class:"social-links"}),u(n.$slots,"nav-screen-content-after",{},void 0,!0)])],512)):f("",!0)]),_:3}))}}),U1=m(G1,[["__scopeId","data-v-382f42e9"]]),j1={key:0,class:"VPNav"},q1=g({__name:"VPNav",setup(s){const{isScreenOpen:e,closeScreen:t,toggleScreen:n}=Yn(),{frontmatter:o}=V(),r=b(()=>o.value.navbar!==!1);return Me("close-screen",t),te(()=>{K&&document.documentElement.classList.toggle("hide-nav",!r.value)}),(d,p)=>r.value?(a(),i("header",j1,[_(d1,{"is-screen-open":l(e),onToggleScreen:l(n)},{"nav-bar-title-before":h(()=>[u(d.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":h(()=>[u(d.$slots,"nav-bar-title-after",{},void 0,!0)]),"nav-bar-content-before":h(()=>[u(d.$slots,"nav-bar-content-before",{},void 0,!0)]),"nav-bar-content-after":h(()=>[u(d.$slots,"nav-bar-content-after",{},void 0,!0)]),_:3},8,["is-screen-open","onToggleScreen"]),_(U1,{open:l(e)},{"nav-screen-content-before":h(()=>[u(d.$slots,"nav-screen-content-before",{},void 0,!0)]),"nav-screen-content-after":h(()=>[u(d.$slots,"nav-screen-content-after",{},void 0,!0)]),_:3},8,["open"])])):f("",!0)}}),R1=m(q1,[["__scopeId","data-v-f1e365da"]]),K1=s=>(H("data-v-1cde40c8"),s=s(),E(),s),W1=["role","tabindex"],Y1=K1(()=>c("div",{class:"indicator"},null,-1)),J1=["onKeydown"],Z1={key:1,class:"items"},Q1=g({__name:"VPSidebarItem",props:{item:{},depth:{}},setup(s){const e=s,{collapsed:t,collapsible:n,isLink:o,isActiveLink:r,hasActiveLink:d,hasChildren:p,toggle:v}=ft(b(()=>e.item)),$=b(()=>p.value?"section":"div"),y=b(()=>o.value?"a":"div"),w=b(()=>p.value?e.depth+2===7?"p":`h${e.depth+2}`:"p"),T=b(()=>o.value?void 0:"button"),I=b(()=>[[`level-${e.depth}`],{collapsible:n.value},{collapsed:t.value},{"is-link":o.value},{"is-active":r.value},{"has-active":d.value}]);function B(P){"key"in P&&P.key!=="Enter"||!e.item.link&&v()}function A(){e.item.link&&v()}return(P,q)=>{const W=j("VPSidebarItem",!0);return a(),k(x($.value),{class:C(["VPSidebarItem",I.value])},{default:h(()=>[P.item.text?(a(),i("div",Q({key:0,class:"item",role:T.value},Re(P.item.items?{click:B,keydown:B}:{},!0),{tabindex:P.item.items&&0}),[Y1,P.item.link?(a(),k(D,{key:0,tag:y.value,class:"link",href:P.item.link,rel:P.item.rel,target:P.item.target},{default:h(()=>[(a(),k(x(w.value),{class:"text",innerHTML:P.item.text},null,8,["innerHTML"]))]),_:1},8,["tag","href","rel","target"])):(a(),k(x(w.value),{key:1,class:"text",innerHTML:P.item.text},null,8,["innerHTML"])),P.item.collapsed!=null?(a(),i("div",{key:2,class:"caret",role:"button","aria-label":"toggle section",onClick:A,onKeydown:Ke(A,["enter"]),tabindex:"0"},[_(fe,{class:"caret-icon"})],40,J1)):f("",!0)],16,W1)):f("",!0),P.item.items&&P.item.items.length?(a(),i("div",Z1,[P.depth<5?(a(!0),i(M,{key:0},N(P.item.items,R=>(a(),k(W,{key:R.text,item:R,depth:P.depth+1},null,8,["item","depth"]))),128)):f("",!0)])):f("",!0)]),_:1},8,["class"])}}}),X1=m(Q1,[["__scopeId","data-v-1cde40c8"]]),He=s=>(H("data-v-c3f8b67a"),s=s(),E(),s),er=He(()=>c("div",{class:"curtain"},null,-1)),tr={class:"nav",id:"VPSidebarNav","aria-labelledby":"sidebar-aria-label",tabindex:"-1"},sr=He(()=>c("span",{class:"visually-hidden",id:"sidebar-aria-label"}," Sidebar Navigation ",-1)),nr=g({__name:"VPSidebar",props:{open:{type:Boolean}},setup(s){const{sidebarGroups:e,hasSidebar:t}=F(),n=s,o=S(null),r=Se(K?document.body:null);return U([n,o],()=>{var d;n.open?(r.value=!0,(d=o.value)==null||d.focus()):r.value=!1},{immediate:!0,flush:"post"}),(d,p)=>l(t)?(a(),i("aside",{key:0,class:C(["VPSidebar",{open:d.open}]),ref_key:"navEl",ref:o,onClick:p[0]||(p[0]=We(()=>{},["stop"]))},[er,c("nav",tr,[sr,u(d.$slots,"sidebar-nav-before",{},void 0,!0),(a(!0),i(M,null,N(l(e),v=>(a(),i("div",{key:v.text,class:"group"},[_(X1,{item:v,depth:0},null,8,["item"])]))),128)),u(d.$slots,"sidebar-nav-after",{},void 0,!0)])],2)):f("",!0)}}),or=m(nr,[["__scopeId","data-v-c3f8b67a"]]),ar=g({__name:"VPSkipLink",setup(s){const e=se(),t=S();U(()=>e.path,()=>t.value.focus());function n({target:o}){const r=document.getElementById(decodeURIComponent(o.hash).slice(1));if(r){const d=()=>{r.removeAttribute("tabindex"),r.removeEventListener("blur",d)};r.setAttribute("tabindex","-1"),r.addEventListener("blur",d),r.focus(),window.scrollTo(0,0)}}return(o,r)=>(a(),i(M,null,[c("span",{ref_key:"backToTop",ref:t,tabindex:"-1"},null,512),c("a",{href:"#VPContent",class:"VPSkipLink visually-hidden",onClick:n}," Skip to content ")],64))}}),rr=m(ar,[["__scopeId","data-v-c3508ec8"]]),lr=g({__name:"Layout",setup(s){const{isOpen:e,open:t,close:n}=F(),o=se();U(()=>o.path,n),_t(e,n);const{frontmatter:r}=V(),d=Ye(),p=b(()=>!!d["home-hero-image"]);return Me("hero-image-slot-exists",p),(v,$)=>{const y=j("Content");return l(r).layout!==!1?(a(),i("div",{key:0,class:C(["Layout",l(r).pageClass])},[u(v.$slots,"layout-top",{},void 0,!0),_(rr),_(et,{class:"backdrop",show:l(e),onClick:l(n)},null,8,["show","onClick"]),_(R1,null,{"nav-bar-title-before":h(()=>[u(v.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":h(()=>[u(v.$slots,"nav-bar-title-after",{},void 0,!0)]),"nav-bar-content-before":h(()=>[u(v.$slots,"nav-bar-content-before",{},void 0,!0)]),"nav-bar-content-after":h(()=>[u(v.$slots,"nav-bar-content-after",{},void 0,!0)]),"nav-screen-content-before":h(()=>[u(v.$slots,"nav-screen-content-before",{},void 0,!0)]),"nav-screen-content-after":h(()=>[u(v.$slots,"nav-screen-content-after",{},void 0,!0)]),_:3}),_(Wn,{open:l(e),onOpenMenu:l(t)},null,8,["open","onOpenMenu"]),_(or,{open:l(e)},{"sidebar-nav-before":h(()=>[u(v.$slots,"sidebar-nav-before",{},void 0,!0)]),"sidebar-nav-after":h(()=>[u(v.$slots,"sidebar-nav-after",{},void 0,!0)]),_:3},8,["open"]),_(Ln,null,{"page-top":h(()=>[u(v.$slots,"page-top",{},void 0,!0)]),"page-bottom":h(()=>[u(v.$slots,"page-bottom",{},void 0,!0)]),"not-found":h(()=>[u(v.$slots,"not-found",{},void 0,!0)]),"home-hero-before":h(()=>[u(v.$slots,"home-hero-before",{},void 0,!0)]),"home-hero-info":h(()=>[u(v.$slots,"home-hero-info",{},void 0,!0)]),"home-hero-image":h(()=>[u(v.$slots,"home-hero-image",{},void 0,!0)]),"home-hero-after":h(()=>[u(v.$slots,"home-hero-after",{},void 0,!0)]),"home-features-before":h(()=>[u(v.$slots,"home-features-before",{},void 0,!0)]),"home-features-after":h(()=>[u(v.$slots,"home-features-after",{},void 0,!0)]),"doc-footer-before":h(()=>[u(v.$slots,"doc-footer-before",{},void 0,!0)]),"doc-before":h(()=>[u(v.$slots,"doc-before",{},void 0,!0)]),"doc-after":h(()=>[u(v.$slots,"doc-after",{},void 0,!0)]),"doc-top":h(()=>[u(v.$slots,"doc-top",{},void 0,!0)]),"doc-bottom":h(()=>[u(v.$slots,"doc-bottom",{},void 0,!0)]),"aside-top":h(()=>[u(v.$slots,"aside-top",{},void 0,!0)]),"aside-bottom":h(()=>[u(v.$slots,"aside-bottom",{},void 0,!0)]),"aside-outline-before":h(()=>[u(v.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":h(()=>[u(v.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":h(()=>[u(v.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":h(()=>[u(v.$slots,"aside-ads-after",{},void 0,!0)]),_:3}),_(Tn),u(v.$slots,"layout-bottom",{},void 0,!0)],2)):(a(),k(y,{key:1}))}}}),ir=m(lr,[["__scopeId","data-v-63b43b87"]]),ur={Layout:ir,enhanceApp:({app:s})=>{s.component("Badge",Ze)}};export{ur as t}; diff --git a/docs/assets/components_speedkit-iframe.md.c5N44rt-.js b/docs/assets/components_speedkit-iframe.md.c5N44rt-.js new file mode 100644 index 0000000000..05232c775d --- /dev/null +++ b/docs/assets/components_speedkit-iframe.md.c5N44rt-.js @@ -0,0 +1,39 @@ +import{_ as a,o as n,c as o,k as s,a as l,t,R as p}from"./chunks/framework._u06EGUx.js";const g=JSON.parse('{"title":"SpeedkitIframe","description":"","frontmatter":{"title":"SpeedkitIframe"},"headers":[],"relativePath":"components/speedkit-iframe.md","filePath":"components/speedkit-iframe.md"}'),r={name:"components/speedkit-iframe.md"},c={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),E=p(`

SpeedkitIframe, Iframe & IntersectionObserver in one.

Exkurs

Iframes have a tendency, in the special case of the initial page load, to disrupt the construction and initialisation of the actual page through the massive loading of resources from another source.

For the user, this is particularly visible by:

WARNING

  • Freeze (Short freeze of the page)
  • Delayed loading of resources (images, fonts)
  • Unnecessarily generated traffic

Solution

In order to solve these points, care should be taken to ensure that the initialisation of the iframe takes place downstream. This can be realised, for example, via an IntersectionObserver. This sets the source on the iframe only when the visible viewport has been reached.

The following conditions can thus be fulfilled:

TIP

  • Iframe load is reactive.
  • No resources are blocked during loading.
  • Traffic is only generated when the iframe is visible.

The strategy mentioned above is provided by the SpeedkitIframe, which can be used in the same way as a normal HTML Iframe. The included IntersectionObserver is configured via the intersectionObserver property.

Usage

The SpeedkitIframe is used like a normal HTML Iframe.

Example

vue
<template>
+  <speedkit-iframe v-bind="iframe" @load="onLoadIframe" />
+</template>
+
+<script setup>
+const src = defineProps({
+  src: String,
+  componentObserver: {
+    type: Object,
+    default() {
+      return { trackVisibility: true, delay: 350 };
+    }
+  }
+});
+
+const onLoadIframe = () => console.log('iframe loaded!');
+</script>
<template>
+  <speedkit-iframe v-bind="iframe" @load="onLoadIframe" />
+</template>
+
+<script setup>
+const src = defineProps({
+  src: String,
+  componentObserver: {
+    type: Object,
+    default() {
+      return { trackVisibility: true, delay: 350 };
+    }
+  }
+});
+
+const onLoadIframe = () => console.log('iframe loaded!');
+</script>

Properties

Use native attributes from HTML Iframe.

componentObserver

Sets the options from the integrated IntersectionObserver.

For advanced usage, learn more about option trackVisibility from IntersectionObserver.

Events

html
<speedkit-iframe 
+  @load="console.log('Iframe Loaded!')" 
+  @enter="console.log('Iframe enter viewport!')" 
+/>
<speedkit-iframe 
+  @load="console.log('Iframe Loaded!')" 
+  @enter="console.log('Iframe enter viewport!')" 
+/>
NameDescription
loadTriggered when Iframe has finished loading.
enterTriggered when component has reached the viewport.
`,23);function d(e,y,h,m,u,f){return n(),o("div",null,[s("h1",c,[l(t(e.$frontmatter.title)+" ",1),i]),E])}const v=a(r,[["render",d]]);export{g as __pageData,v as default}; diff --git a/docs/assets/components_speedkit-iframe.md.c5N44rt-.lean.js b/docs/assets/components_speedkit-iframe.md.c5N44rt-.lean.js new file mode 100644 index 0000000000..017900ebdd --- /dev/null +++ b/docs/assets/components_speedkit-iframe.md.c5N44rt-.lean.js @@ -0,0 +1 @@ +import{_ as a,o as n,c as o,k as s,a as l,t,R as p}from"./chunks/framework._u06EGUx.js";const g=JSON.parse('{"title":"SpeedkitIframe","description":"","frontmatter":{"title":"SpeedkitIframe"},"headers":[],"relativePath":"components/speedkit-iframe.md","filePath":"components/speedkit-iframe.md"}'),r={name:"components/speedkit-iframe.md"},c={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),E=p("",23);function d(e,y,h,m,u,f){return n(),o("div",null,[s("h1",c,[l(t(e.$frontmatter.title)+" ",1),i]),E])}const v=a(r,[["render",d]]);export{g as __pageData,v as default}; diff --git a/docs/assets/components_speedkit-image.md.ch9gdyuF.js b/docs/assets/components_speedkit-image.md.ch9gdyuF.js new file mode 100644 index 0000000000..e108d12283 --- /dev/null +++ b/docs/assets/components_speedkit-image.md.ch9gdyuF.js @@ -0,0 +1,101 @@ +import{_ as e,o as n,c as l,k as s,a as o,t as p,R as t}from"./chunks/framework._u06EGUx.js";const b=JSON.parse('{"title":"SpeedkitImage","description":"","frontmatter":{"title":"SpeedkitImage"},"headers":[],"relativePath":"components/speedkit-image.md","filePath":"components/speedkit-image.md"}'),r={name:"components/speedkit-image.md"},c={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),E=t(`

The SpeedkitImage is a img implementation based on the module @nuxt/image.
It uses the provided API $img.

Features

With the current implementation of SpeedkitImage we can cover the following functionality:

  • generation of multiple image resolutions (srcset)
  • breakpoint-based differentiation of multiple image resolutions (srcset)
  • optimized preloading of critical image resources
  • lazy load of non-critical image resources
  • base path support
  • lazy hydration support
  • load and optimize remote images from custom domains
  • full SEO support

Usage

The SpeedkitImage is used to automatically generate and display different image sizes for different viewports.

The specified resources can be given by absolute path (static folder) or complete URL. nuxt/image downloads the resources fully automatically and stores the generated and optimized renditions in the destination folder.

WARNING

Important: For using SpeedkitImage do not disable @nuxt/image via disableNuxtImage.

Example

vue
<template>
+  <div>
+    <speedkit-image v-bind="{ source, title, alt }" @load="onLoadImage"  />
+  </div>
+</template>
+
+<script setup>
+import SpeedkitImage from '#speedkit/components/SpeedkitImage';
+
+defineProps({
+  source: {
+    type: Object,
+    default() {
+      return {
+        format: 'jpg',
+        src: '/img/image.jpg',
+        sizes: {
+          default: '100vw',
+          xxs: '100vw',
+          xs: '100vw',
+          sm: '100vw',
+          md: '100vw',
+          lg: '100vw',
+          xl: '100vw',
+          xxl: '100vw'
+        }
+      };
+    }
+  },
+  title: String,
+  alt: String
+});
+
+const onLoadImage = () => console.log('Image loaded!');
+</script>
<template>
+  <div>
+    <speedkit-image v-bind="{ source, title, alt }" @load="onLoadImage"  />
+  </div>
+</template>
+
+<script setup>
+import SpeedkitImage from '#speedkit/components/SpeedkitImage';
+
+defineProps({
+  source: {
+    type: Object,
+    default() {
+      return {
+        format: 'jpg',
+        src: '/img/image.jpg',
+        sizes: {
+          default: '100vw',
+          xxs: '100vw',
+          xs: '100vw',
+          sm: '100vw',
+          md: '100vw',
+          lg: '100vw',
+          xl: '100vw',
+          xxl: '100vw'
+        }
+      };
+    }
+  },
+  title: String,
+  alt: String
+});
+
+const onLoadImage = () => console.log('Image loaded!');
+</script>

Properties

js
{
+  source: { … },
+  title: 'Image Title',
+  alt: 'Image Alt'
+}
{
+  source: { … },
+  title: 'Image Title',
+  alt: 'Image Alt'
+}

hydrate

  • Type: Boolean
    • Default: true

The initialization of the SpeedkitImage in the client can be controlled manually.
Here for the property hydrate must be set externally. If true the SpeedkitImage is initialized.

source

  • Type: Object
js
{
+  format: '<output-format>', 
+  src: '<file-path|url>', 
+  sizes: { … }, 
+  modifiers: { … }, 
+  preset: { … }, 
+  provider: '<provider>',
+  densities: '<densities>'
+}
{
+  format: '<output-format>', 
+  src: '<file-path|url>', 
+  sizes: { … }, 
+  modifiers: { … }, 
+  preset: { … }, 
+  provider: '<provider>',
+  densities: '<densities>'
+}

format

Sets the image output format.

Available formats:

  • avif
  • webp
  • png
  • jpg

WARNING

Important: Note that if you specify src without a file extension, the format must be included.

src

Information on property src can be found at here.

sizes

Describes the image sizes in which the resource should be displayed. The image sizes are passed as an object and describe with the key-value pairs the image width and the width of the viewport depending on it, e.g. ImageWidth:MinWidth.

The image width, is definied by screens option from @nuxt/image

Example

In the following example, one image with two different image sizes by breakpoints and output format is webp.

js
[
+  { format: 'webp', src: '/img/image.jpg', sizes: { default: '100vw', sm: '100vw' } }
+]
[
+  { format: 'webp', src: '/img/image.jpg', sizes: { default: '100vw', sm: '100vw' } }
+]

modifiers

  • Type: Object

You can give separate modifiers to each source. This overwrites the global ones of the preset if available.

Learn more about modifiers:

preset

  • Type: Object

If a preset is set on a source, the globally defined one is ignored.

This means that a separate preset can be specified for each source.

Learn more about preset:

provider

  • Type: String

If a provider is set on a source, the globally defined one is ignored.

This means that a separate provider can be specified for each source.

Learn more about provider:

densities

  • Type: String
    • Default: 'x1 x2'

If a densities is set on a source, the globally defined one is ignored.

Learn more about densities:

alt

  • Type: String

Image alternative Text.

MDN - HTMLImageElement.alt

title

  • Type: String

Image Title.

MDN - HTMLElement.title

crossorigin

  • Type: String, Boolean

If not set, the global crossorigin is used this.$speedkit.crossorigin.

Learn more about crossorigin option

MDN - HTML.Attributes.crossorigin

critical

  • Type: Boolean
    • Default: $parent.isCritical

Set component as critical component.

Learn more about critical components

Events

html
<speedkit-image 
+  @load="console.log('Image Loaded!')" 
+/>
<speedkit-image 
+  @load="console.log('Image Loaded!')" 
+/>
NameDescription
loadTriggered when the image resource has been completely loaded.
`,73);function d(a,y,u,g,h,m){return n(),l("div",null,[s("h1",c,[o(p(a.$frontmatter.title)+" ",1),i]),E])}const F=e(r,[["render",d]]);export{b as __pageData,F as default}; diff --git a/docs/assets/components_speedkit-image.md.ch9gdyuF.lean.js b/docs/assets/components_speedkit-image.md.ch9gdyuF.lean.js new file mode 100644 index 0000000000..a4f18cb45a --- /dev/null +++ b/docs/assets/components_speedkit-image.md.ch9gdyuF.lean.js @@ -0,0 +1 @@ +import{_ as e,o as n,c as l,k as s,a as o,t as p,R as t}from"./chunks/framework._u06EGUx.js";const b=JSON.parse('{"title":"SpeedkitImage","description":"","frontmatter":{"title":"SpeedkitImage"},"headers":[],"relativePath":"components/speedkit-image.md","filePath":"components/speedkit-image.md"}'),r={name:"components/speedkit-image.md"},c={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),E=t("",73);function d(a,y,u,g,h,m){return n(),l("div",null,[s("h1",c,[o(p(a.$frontmatter.title)+" ",1),i]),E])}const F=e(r,[["render",d]]);export{b as __pageData,F as default}; diff --git a/docs/assets/components_speedkit-layer.md.IBUESyjR.js b/docs/assets/components_speedkit-layer.md.IBUESyjR.js new file mode 100644 index 0000000000..d5610b655f --- /dev/null +++ b/docs/assets/components_speedkit-layer.md.IBUESyjR.js @@ -0,0 +1,105 @@ +import{_ as p,D as r,o as c,c as i,k as s,a as e,t as E,I as a,w as l,R as t}from"./chunks/framework._u06EGUx.js";const O=JSON.parse('{"title":"SpeedkitLayer","description":"","frontmatter":{"title":"SpeedkitLayer"},"headers":[],"relativePath":"components/speedkit-layer.md","filePath":"components/speedkit-layer.md"}'),y={name:"components/speedkit-layer.md"},d={id:"frontmatter-title",tabindex:"-1"},u=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),h=t('

If the SpeedkitLayer is implemented, the javascript initialisation is automatically monitored. If one of the events

  • ✅ reduced bandwidth
  • ✅ weak hardware
  • ✅ unsupported browser

occurs, the process is paused and only continued or cancelled after a user interaction in the layer.

The layer is placed once in the layout (e.g. layouts/default.vue). The included SpeedkitLayer serves as a wrapper and must be filled according to the template, see example component.

The content contains messages and buttons that are displayed in the respective event. Messages and buttons are defined with an id, these are set to display: none; by default via CSS.

  • e.g. nuxt-speedkit-message-unsupported-browser for message
  • e.g. nuxt-speedkit-button-init-app for button

INFO

For the closing mechanism of the layer, see Hide Layer.

Messages

The messages are elements that are displayed for the relevant events.

Initially, all IDs are set to display: none;, so no message is visible.
When an event is triggered, the relevant message is displayed via the ID using the style attribute display: block;.

',10),g=s("thead",null,[s("tr",null,[s("th",null,"ID"),s("th",null,"Description")])],-1),b=s("code",null,"nuxt-speedkit-message-nojs",-1),_=s("td",null,"Javascript is disabled.",-1),m=s("code",null,"nuxt-speedkit-message-reduced-bandwidth",-1),v=s("td",null,"Connection bandwidth is too low.",-1),k=s("code",null,"nuxt-speedkit-message-weak-hardware",-1),f=s("td",null,"User hardware are not sufficient.",-1),w=s("code",null,"nuxt-speedkit-message-unsupported-browser",-1),F=s("td",null,[e("User Browser is not supported by "),s("a",{href:"/guide/options.html#browsersupport"},[s("code",null,"Browserslist")]),e(".")],-1),D=t(`

Example

html
<!-- initial -->
+<div id="nuxt-speedkit-message-unsupported-browser">
+  Your browser is not supported!
+</div>
+
+<!-- active -->
+<div id="nuxt-speedkit-message-unsupported-browser" style="display: block;">
+  Your browser is not supported!
+</div>
<!-- initial -->
+<div id="nuxt-speedkit-message-unsupported-browser">
+  Your browser is not supported!
+</div>
+
+<!-- active -->
+<div id="nuxt-speedkit-message-unsupported-browser" style="display: block;">
+  Your browser is not supported!
+</div>

Buttons

The buttons are interaction elements for the user with which he can make his choice at the relevant event.

Initially, all IDs except for nuxt-speedkit-button-nojs are set to display: none;. When an event is triggered, the relevant button is displayed via the ID using the style attribute display: block;.

`,5),A=s("thead",null,[s("tr",null,[s("th",null,"ID"),s("th",null,"Description")])],-1),q=s("code",null,"nuxt-speedkit-button-init-nojs",-1),x=s("td",null,[e("Visible when javascript is disabled, needed so that the user can hide the layer. Requires the "),s("a",{href:"/components/speedkit-layer.html#hide-layer"},"Hide Layer"),e(" implementation.")],-1),C=s("code",null,"nuxt-speedkit-button-init-reduced-view",-1),B=s("td",null,"Is used to offer the user the possibility to visit the page only with activated fonts and images. Other initialisations of the Javascript are prevented.",-1),T=s("code",null,"nuxt-speedkit-button-init-app",-1),I=s("td",null,"Activates all features. The initialisation of the JavaScript is started, images are loaded.",-1),j=t(`

INFO

It is recommended to register an Inline Click-Event for the buttons #nuxt-speedkit-button-init-reduced-view and #nuxt-speedkit-button-init-app.

More information under Force App initialization

Hide Layer

html
<label for="nuxt-speedkit-layer-close">
+  Close Layer
+</label>
<label for="nuxt-speedkit-layer-close">
+  Close Layer
+</label>

The layer can be closed via a for attribute with the id nuxt-speedkit-layer-close.

  • ✅ Closing mechanics does not require javascript.

Template

html
<speedkit-layer>
+  <div>
+    <p>Sorry, but you will have a limited user experience due to a…</p>
+
+    <ul style="padding: 0; list-style: none;">
+      <!-- Displayed when javascript is disabled. -->
+      <li id="nuxt-speedkit-message-nojs">
+        disabled javascript
+      </li>
+      <!-- Displayed when browser does not support. -->
+      <li id="nuxt-speedkit-message-unsupported-browser">
+        outdated browser
+      </li>
+      <!-- Displayed when connection bandwidth is too low. -->
+      <li id="nuxt-speedkit-message-reduced-bandwidth">
+        reduced-bandwidth
+      </li>
+      <!-- Displayed when user hardware are not sufficient.  -->
+      <li id="nuxt-speedkit-message-weak-hardware">
+        weak hardware
+      </li>
+    </ul>
+
+    <!-- Button to hide the layer with no javascript -->
+    <button id="nuxt-speedkit-button-init-nojs">
+      <label for="nuxt-speedkit-layer-close">
+        Apply without js
+      </label>
+    </button>
+
+    <!-- Button for use without javascript and with fonts -->
+    <button id="nuxt-speedkit-button-init-reduced-view">
+      <label for="nuxt-speedkit-layer-close">
+        Apply without scripts
+      </label>
+    </button>
+
+    <!-- Button for activate javascript by bad connection or browser support -->
+    <button id="nuxt-speedkit-button-init-app">
+      Apply with all Features
+    </button>
+  </div>
+</speedkit-layer>
<speedkit-layer>
+  <div>
+    <p>Sorry, but you will have a limited user experience due to a…</p>
+
+    <ul style="padding: 0; list-style: none;">
+      <!-- Displayed when javascript is disabled. -->
+      <li id="nuxt-speedkit-message-nojs">
+        disabled javascript
+      </li>
+      <!-- Displayed when browser does not support. -->
+      <li id="nuxt-speedkit-message-unsupported-browser">
+        outdated browser
+      </li>
+      <!-- Displayed when connection bandwidth is too low. -->
+      <li id="nuxt-speedkit-message-reduced-bandwidth">
+        reduced-bandwidth
+      </li>
+      <!-- Displayed when user hardware are not sufficient.  -->
+      <li id="nuxt-speedkit-message-weak-hardware">
+        weak hardware
+      </li>
+    </ul>
+
+    <!-- Button to hide the layer with no javascript -->
+    <button id="nuxt-speedkit-button-init-nojs">
+      <label for="nuxt-speedkit-layer-close">
+        Apply without js
+      </label>
+    </button>
+
+    <!-- Button for use without javascript and with fonts -->
+    <button id="nuxt-speedkit-button-init-reduced-view">
+      <label for="nuxt-speedkit-layer-close">
+        Apply without scripts
+      </label>
+    </button>
+
+    <!-- Button for activate javascript by bad connection or browser support -->
+    <button id="nuxt-speedkit-button-init-app">
+      Apply with all Features
+    </button>
+  </div>
+</speedkit-layer>

Force App initialization

For Unsupported-Browser and Insufficient Hardware events, an onclick event must also be set with the id.

In the event, the global variable __NUXT_SPEEDKIT_AUTO_INIT__ must be set to true.

These are needed if the user has already reacted before the initial Javascript has been loaded. After the javascript has been successfully loaded, the app is automatically initialised.

VariableTypeDescriptionDefault
__NUXT_SPEEDKIT_AUTO_INIT__BooleanIf set, initialisation continues after the javascript has been fully loaded.false
`,12);function S(o,P,N,L,V,U){const n=r("nobr");return c(),i("div",null,[s("h1",d,[e(E(o.$frontmatter.title)+" ",1),u]),h,s("table",null,[g,s("tbody",null,[s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[b]),_:1})]),_]),s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[m]),_:1})]),v]),s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[k]),_:1})]),f]),s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[w]),_:1})]),F])])]),D,s("table",null,[A,s("tbody",null,[s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[q]),_:1})]),x]),s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[C]),_:1})]),B]),s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[T]),_:1})]),I])])]),j])}const $=p(y,[["render",S]]);export{O as __pageData,$ as default}; diff --git a/docs/assets/components_speedkit-layer.md.IBUESyjR.lean.js b/docs/assets/components_speedkit-layer.md.IBUESyjR.lean.js new file mode 100644 index 0000000000..a1b7548e24 --- /dev/null +++ b/docs/assets/components_speedkit-layer.md.IBUESyjR.lean.js @@ -0,0 +1 @@ +import{_ as p,D as r,o as c,c as i,k as s,a as e,t as E,I as a,w as l,R as t}from"./chunks/framework._u06EGUx.js";const O=JSON.parse('{"title":"SpeedkitLayer","description":"","frontmatter":{"title":"SpeedkitLayer"},"headers":[],"relativePath":"components/speedkit-layer.md","filePath":"components/speedkit-layer.md"}'),y={name:"components/speedkit-layer.md"},d={id:"frontmatter-title",tabindex:"-1"},u=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),h=t("",10),g=s("thead",null,[s("tr",null,[s("th",null,"ID"),s("th",null,"Description")])],-1),b=s("code",null,"nuxt-speedkit-message-nojs",-1),_=s("td",null,"Javascript is disabled.",-1),m=s("code",null,"nuxt-speedkit-message-reduced-bandwidth",-1),v=s("td",null,"Connection bandwidth is too low.",-1),k=s("code",null,"nuxt-speedkit-message-weak-hardware",-1),f=s("td",null,"User hardware are not sufficient.",-1),w=s("code",null,"nuxt-speedkit-message-unsupported-browser",-1),F=s("td",null,[e("User Browser is not supported by "),s("a",{href:"/guide/options.html#browsersupport"},[s("code",null,"Browserslist")]),e(".")],-1),D=t("",5),A=s("thead",null,[s("tr",null,[s("th",null,"ID"),s("th",null,"Description")])],-1),q=s("code",null,"nuxt-speedkit-button-init-nojs",-1),x=s("td",null,[e("Visible when javascript is disabled, needed so that the user can hide the layer. Requires the "),s("a",{href:"/components/speedkit-layer.html#hide-layer"},"Hide Layer"),e(" implementation.")],-1),C=s("code",null,"nuxt-speedkit-button-init-reduced-view",-1),B=s("td",null,"Is used to offer the user the possibility to visit the page only with activated fonts and images. Other initialisations of the Javascript are prevented.",-1),T=s("code",null,"nuxt-speedkit-button-init-app",-1),I=s("td",null,"Activates all features. The initialisation of the JavaScript is started, images are loaded.",-1),j=t("",12);function S(o,P,N,L,V,U){const n=r("nobr");return c(),i("div",null,[s("h1",d,[e(E(o.$frontmatter.title)+" ",1),u]),h,s("table",null,[g,s("tbody",null,[s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[b]),_:1})]),_]),s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[m]),_:1})]),v]),s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[k]),_:1})]),f]),s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[w]),_:1})]),F])])]),D,s("table",null,[A,s("tbody",null,[s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[q]),_:1})]),x]),s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[C]),_:1})]),B]),s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[T]),_:1})]),I])])]),j])}const $=p(y,[["render",S]]);export{O as __pageData,$ as default}; diff --git a/docs/assets/components_speedkit-picture.md.9XE6ev5S.js b/docs/assets/components_speedkit-picture.md.9XE6ev5S.js new file mode 100644 index 0000000000..0575bf7200 --- /dev/null +++ b/docs/assets/components_speedkit-picture.md.9XE6ev5S.js @@ -0,0 +1,97 @@ +import{_ as n,o as e,c as l,k as s,a as o,t as p,R as t}from"./chunks/framework._u06EGUx.js";const f=JSON.parse('{"title":"SpeedkitPicture","description":"","frontmatter":{"title":"SpeedkitPicture"},"headers":[],"relativePath":"components/speedkit-picture.md","filePath":"components/speedkit-picture.md"}'),c={name:"components/speedkit-picture.md"},r={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),E=t(`

The SpeedkitPicture is a picture implementation based on the module @nuxt/image.
It uses the provided API $img.

Features

With the current implementation of SpeedkitPicture we can cover the following functionality:

  • generation of multiple sources with multiple image resolutions (srcset)
  • breakpoint-based differentiation of multiple image resolutions and ratios (srcset + media-rule)
  • optimized preloading of critical image resources
  • lazy load of non-critical image resources
  • base path support
  • lazy hydration support
  • load and optimize remote images from custom domains
  • full SEO support

Usage

The SpeedkitPicture is used to automatically generate and display different image sizes and/or image ratios for different viewports.

The specified resources can be given by absolute path (static folder) or complete URL. nuxt/image downloads the resources fully automatically and stores the generated and optimized renditions in the destination folder.

WARNING

Important: For using SpeedkitPicture do not disable @nuxt/image via disableNuxtImage.

Example

vue
<template>
+  <div>
+    <speedkit-picture v-bind="{ sources, title, alt }" @load="onLoadPicture" />
+  </div>
+</template>
+
+<script setup>
+import SpeedkitPicture from '#speedkit/components/SpeedkitPicture';
+
+defineProps({
+  sources: {
+    type: Object,
+    default() {
+      return [
+        {
+          src: '/img/landscape.png',
+          sizes: {
+            sm: '100vw',
+            md: '100vw',
+            lg: '100vw',
+            xl: '100vw',
+            xxl: '100vw'
+          },
+          media: '(orientation: landscape)'
+        },
+        {
+          src: '/img/portrait.png',
+          sizes: { default: '100vw', xxs: '100vw', xs: '100vw' },
+          media: '(orientation: portrait)'
+        }
+      ];
+    }
+  },
+  title: String,
+  alt: String
+});
+
+const onLoadPicture = () => console.log('Picture loaded!');
+</script>
<template>
+  <div>
+    <speedkit-picture v-bind="{ sources, title, alt }" @load="onLoadPicture" />
+  </div>
+</template>
+
+<script setup>
+import SpeedkitPicture from '#speedkit/components/SpeedkitPicture';
+
+defineProps({
+  sources: {
+    type: Object,
+    default() {
+      return [
+        {
+          src: '/img/landscape.png',
+          sizes: {
+            sm: '100vw',
+            md: '100vw',
+            lg: '100vw',
+            xl: '100vw',
+            xxl: '100vw'
+          },
+          media: '(orientation: landscape)'
+        },
+        {
+          src: '/img/portrait.png',
+          sizes: { default: '100vw', xxs: '100vw', xs: '100vw' },
+          media: '(orientation: portrait)'
+        }
+      ];
+    }
+  },
+  title: String,
+  alt: String
+});
+
+const onLoadPicture = () => console.log('Picture loaded!');
+</script>

Properties

js
{
+  sources: [ … ],
+  formats: ['avif', 'webp', 'jpg|jpeg|png'],
+  alt: 'Image Alt',
+  title: 'Image Title',
+}
{
+  sources: [ … ],
+  formats: ['avif', 'webp', 'jpg|jpeg|png'],
+  alt: 'Image Alt',
+  title: 'Image Title',
+}

hydrate

  • Type: Boolean
    • Default: true

The initialization of the SpeedkitPicture in the client can be controlled manually.
Here for the property hydrate must be set externally. If true the SpeedkitPicture is initialized.

sources

  • Type: Array

List of resources used.

The definitions in the sources are equivalent to the SpeedkitImage (source).

The only differences are:

  • The media property can be used. This allows even more dependencies for the display, e.g. (orientation: portrait).
  • The format property is not used. Instead formats is used for setting the output formats.

Example

In the following example, two different image ratios are used.

  • landscape.jpg is applied at a viewport of 996px with an image size of 996px (100vw) by orientation landscape.
  • portrait.jpg is applied below 768px and has two viewport dependent image sizes, at (min-width: 768px) the width 768px and everything below that the width 320px by orientation portrait
js
[
+  { src: '/img/landscape.png', sizes: { md: '100vw' }, media: '(orientation: landscape)' },
+  { src: '/img/portrait.png', sizes: { default: '100vw', sm: '100vw' }, media: '(orientation: portrait)' }
+]
[
+  { src: '/img/landscape.png', sizes: { md: '100vw' }, media: '(orientation: landscape)' },
+  { src: '/img/portrait.png', sizes: { default: '100vw', sm: '100vw' }, media: '(orientation: portrait)' }
+]

formats

  • Type: Array
    • Default: ['webp', 'avif', 'jpg|jpeg|png|gif']

Overrides the pictureFormats property defined in the module options.

Defines the formats that are to be generated and provided as source in the Picture.
Is used to offer the correct image type for the browser.

WARNING

Formats can also be specified as OR condition (jpg|jpeg|png|gif). This is important when using JPGs and PNGs with the same format configuration.

alt

  • Type: String

Image alternative Text.

MDN - HTMLImageElement.alt

title

  • Type: String

Image Title.

MDN - HTMLElement.title

crossorigin

  • Type: String, Boolean

If not set, the global crossorigin is used this.$speedkit.crossorigin.

Learn more about crossorigin option

MDN - HTML.Attributes.crossorigin

sortSources

  • Type: Boolean
    • Default: true

If set, the sources are sorted by the media properties.

This is made possible by sort-css-media-queries.

critical

  • Type: Boolean
    • Default: $parent.isCritical

Set component as critical component.

Learn more about critical components

Events

html
<speedkit-picture 
+  @load="console.log('Loaded!')" 
+/>
<speedkit-picture 
+  @load="console.log('Loaded!')" 
+/>
NameDescription
loadTriggered when the image resource has been completely loaded.
`,54);function d(a,y,u,h,m,g){return e(),l("div",null,[s("h1",r,[o(p(a.$frontmatter.title)+" ",1),i]),E])}const b=n(c,[["render",d]]);export{f as __pageData,b as default}; diff --git a/docs/assets/components_speedkit-picture.md.9XE6ev5S.lean.js b/docs/assets/components_speedkit-picture.md.9XE6ev5S.lean.js new file mode 100644 index 0000000000..4cec696340 --- /dev/null +++ b/docs/assets/components_speedkit-picture.md.9XE6ev5S.lean.js @@ -0,0 +1 @@ +import{_ as n,o as e,c as l,k as s,a as o,t as p,R as t}from"./chunks/framework._u06EGUx.js";const f=JSON.parse('{"title":"SpeedkitPicture","description":"","frontmatter":{"title":"SpeedkitPicture"},"headers":[],"relativePath":"components/speedkit-picture.md","filePath":"components/speedkit-picture.md"}'),c={name:"components/speedkit-picture.md"},r={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),E=t("",54);function d(a,y,u,h,m,g){return e(),l("div",null,[s("h1",r,[o(p(a.$frontmatter.title)+" ",1),i]),E])}const b=n(c,[["render",d]]);export{f as __pageData,b as default}; diff --git a/docs/assets/components_speedkit-vimeo.md.VOcroeY4.js b/docs/assets/components_speedkit-vimeo.md.VOcroeY4.js new file mode 100644 index 0000000000..2389413ab0 --- /dev/null +++ b/docs/assets/components_speedkit-vimeo.md.VOcroeY4.js @@ -0,0 +1,119 @@ +import{_ as n,o as e,c as l,k as s,a as p,t as o,R as t}from"./chunks/framework._u06EGUx.js";const v=JSON.parse('{"title":"SpeedkitVimeo","description":"","frontmatter":{"title":"SpeedkitVimeo"},"headers":[],"relativePath":"components/speedkit-vimeo.md","filePath":"components/speedkit-vimeo.md"}'),c={name:"components/speedkit-vimeo.md"},r={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),E=t(`

We have integrated SpeedkitVimeo as an example to show how iFrame content must be integrated in a performance-optimized manner. For this purpose, a separate IntersectionObserver has been implemented, which detects a longer dwell time of the component in the viewport. The iFrame is initialized only after a positive detection. This prevents immense data from having to be loaded when simply scrolling through the page. So that no empty space is visible to the user, we use the functionality of the SpeedkitPicture and preload the corresponding Vimeo poster in different renditions, so the illusion is perfect for the user and he does not notice anything of the optimized lazy load procedure.

Usage

The SpeedkitVimeo is used to initialise Vimeo videos with Vimeo Player-SDK.

INFO

The SDK is not part of nuxt-speedkit and will be loaded by an external script.

The url of the Vimeo video must be specified.

The SpeedkitPicture is used for the poster, so the generation of the poster is automated, you can define the image sizes with sizes (What is sizes?).

Learn more about SpeedkitPicture

::alert{type="warning"} Important: For using SpeedkitVimeo do not disable @nuxt/image via disableNuxtImage ::

Example

vue
<template>
+  <div>
+    <speedkit-vimeo
+      v-bind="{
+        url,
+        title,
+        options,
+        posterSizes
+      }"
+      @playing="onPlaying" />
+  </div>
+</template>
+
+<script setup>
+import SpeedkitVimeo from '#speedkit/components/SpeedkitVimeo';
+
+defineProps({
+  url: { type: String, default: '<vimeo-url>' },
+  title: { type: String, default: 'Vimeo Title' },
+  options: {
+    type: String,
+    default() {
+      return {
+        controls: false
+      };
+    }
+  },
+  posterSizes: {
+    type: Object,
+    default() {
+      return {
+        default: '100vw',
+        md: '50vw'
+      };
+    }
+  }
+});
+
+const onPlaying = () => console.log('Vimeo Player playing!');
+</script>
<template>
+  <div>
+    <speedkit-vimeo
+      v-bind="{
+        url,
+        title,
+        options,
+        posterSizes
+      }"
+      @playing="onPlaying" />
+  </div>
+</template>
+
+<script setup>
+import SpeedkitVimeo from '#speedkit/components/SpeedkitVimeo';
+
+defineProps({
+  url: { type: String, default: '<vimeo-url>' },
+  title: { type: String, default: 'Vimeo Title' },
+  options: {
+    type: String,
+    default() {
+      return {
+        controls: false
+      };
+    }
+  },
+  posterSizes: {
+    type: Object,
+    default() {
+      return {
+        default: '100vw',
+        md: '50vw'
+      };
+    }
+  }
+});
+
+const onPlaying = () => console.log('Vimeo Player playing!');
+</script>

Properties

js
{
+  url: '<vimeo-url>',
+  title: 'Player Title',
+  autoplay: false,
+  mute: false,
+  posterSizes: { … },
+  options: { … }
+}
{
+  url: '<vimeo-url>',
+  title: 'Player Title',
+  autoplay: false,
+  mute: false,
+  posterSizes: { … },
+  options: { … }
+}

url

  • Type: String

Sets a video via the vimeo url.

title

  • Type: String

Sets the title for the player iframe and poster.

autoplay

  • Type: Boolean
    • Default: false

When set starts video in autoplay. It is required that the component is integrated via SpeedkitHydrate or is only activated when entering the visible area.

mute

  • Type: Boolean
    • Default: false

When set the player is muted.

posterSizes

  • Type: String
    • Default: { default: '100vw', xxs: '100vw', xs: '100vw', sm: '100vw', md: '100vw', lg: '100vw', xl: '100vw', xxl: '100vw' }

Sets the image sizes of the poster.

Learn more about sizes

options

  • Type: Object

Overrides the vimeo player options. These will be the same as the vimeo player embed options.

Learn more about Vimeo Player Parameters

WARNING

For autoplay and mute the component properties are used.

Option playsinline is always set, mute is set automatically for touch devices.
This is important for autoplay on mobile devices.

Slots

html
<template #default="{ videoData }">
+  {{videoData.title}}
+</template>
+
+<template #loading-spinner>
+  Loading…
+</template>
+
+<template #play>
+  <span>Click!</span>
+</template>
<template #default="{ videoData }">
+  {{videoData.title}}
+</template>
+
+<template #loading-spinner>
+  Loading…
+</template>
+
+<template #play>
+  <span>Click!</span>
+</template>
NameDescription
defaultUsed to display more information about the video below the player.
The slot has a scoped property videoData.
This contains the result from the Vimeo oembed api.

https://developer.vimeo.com/api/oembed/videos#table-2
loading-spinnerOverwrites the loading spinner.
playOverwrites the play button.

Events

html
<speedkit-vimeo 
+  @ready="console.log('Player Ready!')" 
+  @playing="console.log('Player Playing!')" 
+/>
<speedkit-vimeo 
+  @ready="console.log('Player Ready!')" 
+  @playing="console.log('Player Playing!')" 
+/>
NameDescription
readyTriggered when Vimeo Player-SDK is completely loaded.
playingTriggered when video is finished loading and playing.
beforePlayerUsed to place elements in the player container (before).
afterPlayerUsed to place elements in the player container (after).
`,39);function y(a,d,h,u,m,g){return e(),l("div",null,[s("h1",r,[p(o(a.$frontmatter.title)+" ",1),i]),E])}const f=n(c,[["render",y]]);export{v as __pageData,f as default}; diff --git a/docs/assets/components_speedkit-vimeo.md.VOcroeY4.lean.js b/docs/assets/components_speedkit-vimeo.md.VOcroeY4.lean.js new file mode 100644 index 0000000000..c51b68865a --- /dev/null +++ b/docs/assets/components_speedkit-vimeo.md.VOcroeY4.lean.js @@ -0,0 +1 @@ +import{_ as n,o as e,c as l,k as s,a as p,t as o,R as t}from"./chunks/framework._u06EGUx.js";const v=JSON.parse('{"title":"SpeedkitVimeo","description":"","frontmatter":{"title":"SpeedkitVimeo"},"headers":[],"relativePath":"components/speedkit-vimeo.md","filePath":"components/speedkit-vimeo.md"}'),c={name:"components/speedkit-vimeo.md"},r={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),E=t("",39);function y(a,d,h,u,m,g){return e(),l("div",null,[s("h1",r,[p(o(a.$frontmatter.title)+" ",1),i]),E])}const f=n(c,[["render",y]]);export{v as __pageData,f as default}; diff --git a/docs/assets/components_speedkit-youtube.md.MYGlq3t8.js b/docs/assets/components_speedkit-youtube.md.MYGlq3t8.js new file mode 100644 index 0000000000..de5209e3eb --- /dev/null +++ b/docs/assets/components_speedkit-youtube.md.MYGlq3t8.js @@ -0,0 +1,117 @@ +import{_ as n,o as e,c as l,k as s,a as p,t as o,R as t}from"./chunks/framework._u06EGUx.js";const f=JSON.parse('{"title":"SpeedkitYoutube","description":"","frontmatter":{"title":"SpeedkitYoutube"},"headers":[],"relativePath":"components/speedkit-youtube.md","filePath":"components/speedkit-youtube.md"}'),c={name:"components/speedkit-youtube.md"},r={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),E=t(`

We have integrated SpeedkitYoutube as an example to show how iFrame content must be integrated in a performance-optimized manner. For this purpose, a separate IntersectionObserver has been implemented, which detects a longer dwell time of the component in the viewport. The iFrame is initialized only after a positive detection. This prevents immense data from having to be loaded when simply scrolling through the page. So that no empty space is visible to the user, we use the functionality of the SpeedkitPicture and preload the corresponding Youtube poster in different renditions, so the illusion is perfect for the user and he does not notice anything of the optimized lazy load procedure.

Usage

The SpeedkitYoutubeis used to initialise Youtube videos with Youtube Iframe-API.

The url of the Youtube video must be specified.

The SpeedkitPicture is used for the poster, so the generation of the poster is automated, you can define the image sizes with sizes (What is sizes?).

Learn more about SpeedkitPicture

WARNING

Important: For using SpeedkitYoutube do not disable @nuxt/image via disableNuxtImage.

Example

vue
<template>
+  <div>
+    <speedkit-youtube v-bind="youtube" @playing="onPlaying" />
+  </div>
+</template>
+
+<script setup>
+import SpeedkitYoutube from '#speedkit/components/SpeedkitYoutube';
+
+defineProps({
+  url: {
+    type: String,
+    default: '<youtube-url>'
+  },
+  title: {
+    type: String,
+    default: 'Youtube Title'
+  },
+  host: {
+    type: String,
+    default: 'https://www.youtube.com'
+  },
+  options: {
+    type: Object,
+    default() {
+      return {
+        fs: false // use boolean instead of 0 or 1
+      };
+    }
+  },
+  posterSizes: {
+    type: Object,
+    default() {
+      return {
+        default: '100vw',
+        md: '50vw'
+      };
+    }
+  }
+});
+
+const onPlaying = () => console.log('Youtube Player playing!');
+</script>
<template>
+  <div>
+    <speedkit-youtube v-bind="youtube" @playing="onPlaying" />
+  </div>
+</template>
+
+<script setup>
+import SpeedkitYoutube from '#speedkit/components/SpeedkitYoutube';
+
+defineProps({
+  url: {
+    type: String,
+    default: '<youtube-url>'
+  },
+  title: {
+    type: String,
+    default: 'Youtube Title'
+  },
+  host: {
+    type: String,
+    default: 'https://www.youtube.com'
+  },
+  options: {
+    type: Object,
+    default() {
+      return {
+        fs: false // use boolean instead of 0 or 1
+      };
+    }
+  },
+  posterSizes: {
+    type: Object,
+    default() {
+      return {
+        default: '100vw',
+        md: '50vw'
+      };
+    }
+  }
+});
+
+const onPlaying = () => console.log('Youtube Player playing!');
+</script>

Properties

js
{
+  url: '<youtube-url>',
+  title: 'Player Title',
+  autoplay: false,
+  mute: false,
+  posterSizes: { … },
+  options: { … }
+}
{
+  url: '<youtube-url>',
+  title: 'Player Title',
+  autoplay: false,
+  mute: false,
+  posterSizes: { … },
+  options: { … }
+}

url

  • Type: String

Sets a video via the youtube url.

title

  • Type: String

Sets the title for the player iframe and poster.

autoplay

  • Type: Boolean
    • Default: false

When set starts video in autoplay. It is required that the component is integrated via SpeedkitHydrate or is only activated when entering the visible area.

mute

  • Type: Boolean
    • Default: false

When set the player is muted.

posterSizes

  • Type: String
    • Default: { default: '100vw', xxs: '100vw', xs: '100vw', sm: '100vw', md: '100vw', lg: '100vw', xl: '100vw', xxl: '100vw' }

Sets the image sizes of the poster.

Learn more about sizes

options

  • Type: Object

Overrides the youtube player options. These will be the same as the youtube player parameters.

Use boolean values instead of integers (e.g. 0, 1).

Learn more about Youtube Player Parameters

WARNING

For autoplay and mute the component properties are used.

Option playsinline is always set, mute is set automatically for touch devices.
This is important for autoplay on mobile devices.

host

  • Type: String
    • default: 'https://www.youtube-nocookie.com'

Sets the host for the player.

INFO

It is recommended to use the default (https://www.youtube-nocookie.com).

Slots

html
<template #loading-spinner>
+  Loading…
+</template>
+
+<template #play>
+  <span>Click!</span>
+</template>
<template #loading-spinner>
+  Loading…
+</template>
+
+<template #play>
+  <span>Click!</span>
+</template>
NameDescription
loading-spinnerOverwrites the loading spinner.
playOverwrites the play button.

Events

html
<speedkit-youtube 
+  @ready="console.log('Player Ready!')" 
+  @playing="console.log('Player Playing!')" 
+/>
<speedkit-youtube 
+  @ready="console.log('Player Ready!')" 
+  @playing="console.log('Player Playing!')" 
+/>
NameDescription
readyTriggered when Youtube-API is completely loaded.
playingTriggered when video is finished loading and playing.
beforePlayerUsed to place elements in the player container (before).
afterPlayerUsed to place elements in the player container (after).
`,43);function y(a,d,u,h,m,g){return e(),l("div",null,[s("h1",r,[p(o(a.$frontmatter.title)+" ",1),i]),E])}const F=n(c,[["render",y]]);export{f as __pageData,F as default}; diff --git a/docs/assets/components_speedkit-youtube.md.MYGlq3t8.lean.js b/docs/assets/components_speedkit-youtube.md.MYGlq3t8.lean.js new file mode 100644 index 0000000000..0267f35089 --- /dev/null +++ b/docs/assets/components_speedkit-youtube.md.MYGlq3t8.lean.js @@ -0,0 +1 @@ +import{_ as n,o as e,c as l,k as s,a as p,t as o,R as t}from"./chunks/framework._u06EGUx.js";const f=JSON.parse('{"title":"SpeedkitYoutube","description":"","frontmatter":{"title":"SpeedkitYoutube"},"headers":[],"relativePath":"components/speedkit-youtube.md","filePath":"components/speedkit-youtube.md"}'),c={name:"components/speedkit-youtube.md"},r={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),E=t("",43);function y(a,d,u,h,m,g){return e(),l("div",null,[s("h1",r,[p(o(a.$frontmatter.title)+" ",1),i]),E])}const F=n(c,[["render",y]]);export{f as __pageData,F as default}; diff --git a/docs/assets/components_weak-hardware-overlay.md.sQu1lie2.js b/docs/assets/components_weak-hardware-overlay.md.sQu1lie2.js new file mode 100644 index 0000000000..284defaa74 --- /dev/null +++ b/docs/assets/components_weak-hardware-overlay.md.sQu1lie2.js @@ -0,0 +1,99 @@ +import{_ as n,o as l,c as p,k as s,a as e,t as o,R as t}from"./chunks/framework._u06EGUx.js";const g=JSON.parse('{"title":"WeakHardwareOverlay","description":"","frontmatter":{"title":"WeakHardwareOverlay"},"headers":[],"relativePath":"components/weak-hardware-overlay.md","filePath":"components/weak-hardware-overlay.md"}'),c={name:"components/weak-hardware-overlay.md"},r={id:"frontmatter-title",tabindex:"-1"},E=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),y=t(`

The WeakHardwareOverlay is used in components that are affected by the SpeedkitLayer event Weak Hardware. (Example: Component requires the execution of mounted for functionality.)

INFO

The performance issue event occurs when initialization determines that the client is overloaded with execution and the user has confirmed the #nuxt-speedkit-button-init-reduced-view button in the SpeedkitLayer.

Basically, the overlay is used to make content visible when the Weak Hardware has occurred, if this does not occur, the overlay is not visible.

It is recommended to include an interaction element in the overlay that allows the user to switch to the normal state. For this the interaction element must get the ID nuxt-speedkit-button-init-app and reacts on click with the initialization of the app.

Example

Example of defining a custom WeakHardwareOverlay component and placing it in a target component that is affected by the Weak Hardware event.

Customize Overlay

vue
<template>
+  <speedkit-weak-hardware-overlay>
+    To improve your experience, extensive features have been disabled.<br>
+    <button id="nuxt-speedkit-button-init-app">
+     Click here to enable them.
+    </button>
+  </speedkit-weak-hardware-overlay>
+</template>
+<script>
+import SpeedkitWeakHardwareOverlay from 'nuxt-speedkit/components/WeakHardwareOverlay';
+export default {
+  components: { SpeedkitWeakHardwareOverlay }
+};
+</script>
+<style lang="postcss" scoped>
+.nuxt-speedkit-weak-hardware-overlay {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background: rgb(0 0 0 / 60%);
+  backdrop-filter: blur(em(2px));
+}
+</style>
<template>
+  <speedkit-weak-hardware-overlay>
+    To improve your experience, extensive features have been disabled.<br>
+    <button id="nuxt-speedkit-button-init-app">
+     Click here to enable them.
+    </button>
+  </speedkit-weak-hardware-overlay>
+</template>
+<script>
+import SpeedkitWeakHardwareOverlay from 'nuxt-speedkit/components/WeakHardwareOverlay';
+export default {
+  components: { SpeedkitWeakHardwareOverlay }
+};
+</script>
+<style lang="postcss" scoped>
+.nuxt-speedkit-weak-hardware-overlay {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background: rgb(0 0 0 / 60%);
+  backdrop-filter: blur(em(2px));
+}
+</style>

Usage Overlay

vue
<template>
+  <div>
+    <div ref="player" />
+    <weak-hardware-overlay />
+  </div>
+</template>
+<script>
+import WeakHardwareOverlay from '@/components/WeakHardwareOverlay';
+export default {
+  components: { WeakHardwareOverlay },
+  mounted () {
+    this.player = new Player(this.$refs.player)
+  }
+};
+</script>
+<style lang="postcss" scoped>
+.nuxt-speedkit-performance-issue-overlay {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background: rgb(0 0 0 / 60%);
+  backdrop-filter: blur(2px);
+}
+</style>
<template>
+  <div>
+    <div ref="player" />
+    <weak-hardware-overlay />
+  </div>
+</template>
+<script>
+import WeakHardwareOverlay from '@/components/WeakHardwareOverlay';
+export default {
+  components: { WeakHardwareOverlay },
+  mounted () {
+    this.player = new Player(this.$refs.player)
+  }
+};
+</script>
+<style lang="postcss" scoped>
+.nuxt-speedkit-performance-issue-overlay {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background: rgb(0 0 0 / 60%);
+  backdrop-filter: blur(2px);
+}
+</style>
`,10);function i(a,d,u,h,v,m){return l(),p("div",null,[s("h1",r,[e(o(a.$frontmatter.title)+" ",1),E]),y])}const b=n(c,[["render",i]]);export{g as __pageData,b as default}; diff --git a/docs/assets/components_weak-hardware-overlay.md.sQu1lie2.lean.js b/docs/assets/components_weak-hardware-overlay.md.sQu1lie2.lean.js new file mode 100644 index 0000000000..b8408b8624 --- /dev/null +++ b/docs/assets/components_weak-hardware-overlay.md.sQu1lie2.lean.js @@ -0,0 +1 @@ +import{_ as n,o as l,c as p,k as s,a as e,t as o,R as t}from"./chunks/framework._u06EGUx.js";const g=JSON.parse('{"title":"WeakHardwareOverlay","description":"","frontmatter":{"title":"WeakHardwareOverlay"},"headers":[],"relativePath":"components/weak-hardware-overlay.md","filePath":"components/weak-hardware-overlay.md"}'),c={name:"components/weak-hardware-overlay.md"},r={id:"frontmatter-title",tabindex:"-1"},E=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),y=t("",10);function i(a,d,u,h,v,m){return l(),p("div",null,[s("h1",r,[e(o(a.$frontmatter.title)+" ",1),E]),y])}const b=n(c,[["render",i]]);export{g as __pageData,b as default}; diff --git a/docs/assets/composables_useComponentObserver.md.86iHNGdS.js b/docs/assets/composables_useComponentObserver.md.86iHNGdS.js new file mode 100644 index 0000000000..db09f575ee --- /dev/null +++ b/docs/assets/composables_useComponentObserver.md.86iHNGdS.js @@ -0,0 +1,29 @@ +import{_ as a,o as n,c as t,k as s,a as o,t as l,R as p}from"./chunks/framework._u06EGUx.js";const g=JSON.parse('{"title":"useComponentObserver","description":"","frontmatter":{"title":"useComponentObserver"},"headers":[],"relativePath":"composables/useComponentObserver.md","filePath":"composables/useComponentObserver.md"}'),r={name:"composables/useComponentObserver.md"},c={id:"frontmatter-title",tabindex:"-1"},d=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),i=p(`

Options

PropertyTypeDescriptionDefault Value
rootHTMLElementMDN rootundefined
rootMarginStringMDN rootMargin'0px'
thresholdArrayMDN threshold[0]
trackVisibilityBooleanMDN trackVisibilityfalse
delayNumberMDN delay0

Return

PropertyTypeDescription
elObjectComponent ref for tag referencing.
inViewBooleanReference that indicates whether referenced element is visible.

Example

html
<template>
+  <div ref="target" :class="{visible: inView}">
+
+  </div>
+</template>
+
+<script setup>
+import useComponentObserver from '#speedkit/composables/componentObserver';
+
+const { el: target, inView } = useComponentObserver({
+  trackVisibility: true,
+  delay: 350
+});
+
+</script>
<template>
+  <div ref="target" :class="{visible: inView}">
+
+  </div>
+</template>
+
+<script setup>
+import useComponentObserver from '#speedkit/composables/componentObserver';
+
+const { el: target, inView } = useComponentObserver({
+  trackVisibility: true,
+  delay: 350
+});
+
+</script>
`,6);function E(e,y,h,b,m,u){return n(),t("div",null,[s("h1",c,[o(l(e.$frontmatter.title)+" ",1),d]),i])}const f=a(r,[["render",E]]);export{g as __pageData,f as default}; diff --git a/docs/assets/composables_useComponentObserver.md.86iHNGdS.lean.js b/docs/assets/composables_useComponentObserver.md.86iHNGdS.lean.js new file mode 100644 index 0000000000..ffe567e9a3 --- /dev/null +++ b/docs/assets/composables_useComponentObserver.md.86iHNGdS.lean.js @@ -0,0 +1 @@ +import{_ as a,o as n,c as t,k as s,a as o,t as l,R as p}from"./chunks/framework._u06EGUx.js";const g=JSON.parse('{"title":"useComponentObserver","description":"","frontmatter":{"title":"useComponentObserver"},"headers":[],"relativePath":"composables/useComponentObserver.md","filePath":"composables/useComponentObserver.md"}'),r={name:"composables/useComponentObserver.md"},c={id:"frontmatter-title",tabindex:"-1"},d=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),i=p("",6);function E(e,y,h,b,m,u){return n(),t("div",null,[s("h1",c,[o(l(e.$frontmatter.title)+" ",1),d]),i])}const f=a(r,[["render",E]]);export{g as __pageData,f as default}; diff --git a/docs/assets/composables_useConfig.md.HxJG3vdy.js b/docs/assets/composables_useConfig.md.HxJG3vdy.js new file mode 100644 index 0000000000..83b3af53c8 --- /dev/null +++ b/docs/assets/composables_useConfig.md.HxJG3vdy.js @@ -0,0 +1,3 @@ +import{_ as e,o,c as n,k as s,a as t,t as l,R as p}from"./chunks/framework._u06EGUx.js";const g=JSON.parse('{"title":"useConfig","description":"","frontmatter":{"title":"useConfig"},"headers":[],"relativePath":"composables/useConfig.md","filePath":"composables/useConfig.md"}'),r={name:"composables/useConfig.md"},c={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),d=p(`

Return

Returns nuxt-speedkit public runtime options.

Example

js
import useConfig from '#speedkit/composables/config';
+const $speedkitOptions = useConfig();
import useConfig from '#speedkit/composables/config';
+const $speedkitOptions = useConfig();
`,4);function E(a,u,m,y,f,h){return o(),n("div",null,[s("h1",c,[t(l(a.$frontmatter.title)+" ",1),i]),d])}const C=e(r,[["render",E]]);export{g as __pageData,C as default}; diff --git a/docs/assets/composables_useConfig.md.HxJG3vdy.lean.js b/docs/assets/composables_useConfig.md.HxJG3vdy.lean.js new file mode 100644 index 0000000000..1a6a56b0df --- /dev/null +++ b/docs/assets/composables_useConfig.md.HxJG3vdy.lean.js @@ -0,0 +1 @@ +import{_ as e,o,c as n,k as s,a as t,t as l,R as p}from"./chunks/framework._u06EGUx.js";const g=JSON.parse('{"title":"useConfig","description":"","frontmatter":{"title":"useConfig"},"headers":[],"relativePath":"composables/useConfig.md","filePath":"composables/useConfig.md"}'),r={name:"composables/useConfig.md"},c={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),d=p("",4);function E(a,u,m,y,f,h){return o(),n("div",null,[s("h1",c,[t(l(a.$frontmatter.title)+" ",1),i]),d])}const C=e(r,[["render",E]]);export{g as __pageData,C as default}; diff --git a/docs/assets/composables_useCritical.md.nddRf2fx.js b/docs/assets/composables_useCritical.md.nddRf2fx.js new file mode 100644 index 0000000000..c441da3309 --- /dev/null +++ b/docs/assets/composables_useCritical.md.nddRf2fx.js @@ -0,0 +1,15 @@ +import{_ as l,o as t,c as n,k as s,a as o,t as p,R as e}from"./chunks/framework._u06EGUx.js";const g=JSON.parse('{"title":"useCritical","description":"","frontmatter":{"title":"useCritical"},"headers":[],"relativePath":"composables/useCritical.md","filePath":"composables/useCritical.md"}'),c={name:"composables/useCritical.md"},r={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),E=e(`

Options

PropertyTypeDescriptionDefault Value
criticalBooleanOverride critical from component.inherit from component
js
const { isCritical } = useCritical({critical: true});
const { isCritical } = useCritical({critical: true});

Return

PropertyDescription
isCritical

Example

html
<template>
+  <span>Critical? {{isCritical}}</span>
+</template>
+
+<script setup>
+  import useCritical from '#speedkit/composables/critical';
+  const { isCritical } = useCritical();
+</script>
<template>
+  <span>Critical? {{isCritical}}</span>
+</template>
+
+<script setup>
+  import useCritical from '#speedkit/composables/critical';
+  const { isCritical } = useCritical();
+</script>
`,7);function y(a,d,h,u,m,C){return t(),n("div",null,[s("h1",r,[o(p(a.$frontmatter.title)+" ",1),i]),E])}const _=l(c,[["render",y]]);export{g as __pageData,_ as default}; diff --git a/docs/assets/composables_useCritical.md.nddRf2fx.lean.js b/docs/assets/composables_useCritical.md.nddRf2fx.lean.js new file mode 100644 index 0000000000..dd1adaf5bd --- /dev/null +++ b/docs/assets/composables_useCritical.md.nddRf2fx.lean.js @@ -0,0 +1 @@ +import{_ as l,o as t,c as n,k as s,a as o,t as p,R as e}from"./chunks/framework._u06EGUx.js";const g=JSON.parse('{"title":"useCritical","description":"","frontmatter":{"title":"useCritical"},"headers":[],"relativePath":"composables/useCritical.md","filePath":"composables/useCritical.md"}'),c={name:"composables/useCritical.md"},r={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),E=e("",7);function y(a,d,h,u,m,C){return t(),n("div",null,[s("h1",r,[o(p(a.$frontmatter.title)+" ",1),i]),E])}const _=l(c,[["render",y]]);export{g as __pageData,_ as default}; diff --git a/docs/assets/composables_useFont.md.zDVqwOCI.js b/docs/assets/composables_useFont.md.zDVqwOCI.js new file mode 100644 index 0000000000..3af33447f3 --- /dev/null +++ b/docs/assets/composables_useFont.md.zDVqwOCI.js @@ -0,0 +1,15 @@ +import{_ as n,o as t,c as o,k as s,a as l,t as p,R as e}from"./chunks/framework._u06EGUx.js";const b=JSON.parse('{"title":"useFont","description":"","frontmatter":{"title":"useFont"},"headers":[],"relativePath":"composables/useFont.md","filePath":"composables/useFont.md"}'),c={name:"composables/useFont.md"},r={id:"frontmatter-title",tabindex:"-1"},E=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),y=e(`

Options

PropertyTypeDescriptionDefault Value
criticalBooleanOverride critical from component.inherit from component
js
const { $getFont } = useFonts({critical: true});
const { $getFont } = useFonts({critical: true});

Return

PropertyDescription
isCritical
$getFont

Example

html
<template>
+  <span v-font="$getFont(…)"></span>
+</template>
+
+<script setup>
+  import useFonts from '#speedkit/composables/fonts';
+  const { $getFont } = useFonts();
+</script>
<template>
+  <span v-font="$getFont(…)"></span>
+</template>
+
+<script setup>
+  import useFonts from '#speedkit/composables/fonts';
+  const { $getFont } = useFonts();
+</script>
`,7);function i(a,d,h,u,F,m){return t(),o("div",null,[s("h1",r,[l(p(a.$frontmatter.title)+" ",1),E]),y])}const _=n(c,[["render",i]]);export{b as __pageData,_ as default}; diff --git a/docs/assets/composables_useFont.md.zDVqwOCI.lean.js b/docs/assets/composables_useFont.md.zDVqwOCI.lean.js new file mode 100644 index 0000000000..8609aa096f --- /dev/null +++ b/docs/assets/composables_useFont.md.zDVqwOCI.lean.js @@ -0,0 +1 @@ +import{_ as n,o as t,c as o,k as s,a as l,t as p,R as e}from"./chunks/framework._u06EGUx.js";const b=JSON.parse('{"title":"useFont","description":"","frontmatter":{"title":"useFont"},"headers":[],"relativePath":"composables/useFont.md","filePath":"composables/useFont.md"}'),c={name:"composables/useFont.md"},r={id:"frontmatter-title",tabindex:"-1"},E=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),y=e("",7);function i(a,d,h,u,F,m){return t(),o("div",null,[s("h1",r,[l(p(a.$frontmatter.title)+" ",1),E]),y])}const _=n(c,[["render",i]]);export{b as __pageData,_ as default}; diff --git a/docs/assets/concept.md.s_9WUY8f.js b/docs/assets/concept.md.s_9WUY8f.js new file mode 100644 index 0000000000..5f8c7c247b --- /dev/null +++ b/docs/assets/concept.md.s_9WUY8f.js @@ -0,0 +1 @@ +import{_ as t,o as i,c as o,k as e,a as n,t as r,R as s}from"./chunks/framework._u06EGUx.js";const v=JSON.parse('{"title":"Concept","description":"","frontmatter":{"title":"Concept"},"headers":[],"relativePath":"concept.md","filePath":"concept.md"}'),l={name:"concept.md"},h={id:"frontmatter-title",tabindex:"-1"},d=e("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),c=s('

Current Situation

The loading behavior of webpages based on NuxtJS is designed in such a way that all necessary Javascript resources are preloaded and directly initialized with the initial load of the page. However, this behavior creates a negative impact on the Lighthouse Performance Score (TTI) for larger pages that have an increased initial load of additional resources, such as fonts, images, plugins, modules (@nuxtjs/i18n, ...).

Excursus

The Lighthouse Test is not a tool to make a general statement about the quality of a website programming. Lighthouse rather tries to map a metric for the usability of a page from the user's point of view. This includes accessibility, best practices, SEO and of course performance.

This last point is often misinterpreted by developers. If you want to implement features that increase usability for the user (interactions/more complex animations, ...), this will always have an impact on performance in the Lighthouse Test for larger website projects, as the corresponding Javascript must be loaded for this. Finally, Lighthouse does also not rate the design, but the accessibility (size of click areas, etc.) of a website. You should therefore not ask yourself the following question: "How can I fully optimize my JavaScript to achieve a Lighthouse score of 100/100?". You have to ask yourself much more the question: "What is especially important to a user with low bandwidth or weak hardware on my site?".

The answer to this is relatively simple: the content must be accessible and you must be able to get to the information you need quickly.

No more and no less.

The user doesn't need any fancy slider animations and parallax effects that can only be implemented with certain libraries. Or a softload mechanism to get to more pages in a more elegant and animated way, but which initially needs an increased amount of javascript logic. All he wants is that information is retrievable reasonably fast and he can click through the presence.

Problem

The good news is that the NuxtJS SSR build provides the right foundation. The content is already in the form of HTML and CSS and can be used without Javascript. But what is missing

  • is a fully automated preload logic that allows component and viewport based handling and prioritization of the individual resources (FCP, LCP, CLS)
  • is a logic that enables a perfomance-oriented initialization of the javascript (TTI, TBT)

These two central points are handled by Nuxt Speedkit and enable a fast and resource-saving loading behavior of the website.

Approach

Over a longer period of time, we analyzed the Google Lighthouse test in more detail and approached the topic with the help of use cases. We did not start with the best case for page content (one image, one font, minimal javascript), but with the worst case (many images, many fonts, large Javascript files, ...). So we avoided to develop only a solution for simple SinglePages. Our claim was much more to create a generalistic, performant solution even with a CMS connection and dynamic component compositions per page. All our thoughts are based on HTTP/2 request prioritization and the lazy hydration approach. Initial resources are prioritized by preload and all further data is reloaded viewport-based.

Insights & Solutions

During the tests, we gained the following insights, which we would like to share with you, but which also allow us to draw conclusions regarding the performance optimization of the initial loading process and which have been incorporated into the Nuxt Speedkit solution.

Critical Render Path

The critical render path is the core of a high-performance and efficient loading and rendering behavior of a website. It is important that components and resources in the viewport are loaded and executed with priority so that the user can be provided with a functioning page as quickly as possible. A browser is not able to recognize this fully automatically to dynamically adapt the loading behavior. Some attempts have been made in the past to systematically identify the critical render path.However, this has the consequence that every generated page in a virtual browser has to be analyzed in given viewport sizes, which slows down the deployment process and makes it more error-prone. For this reason, we (the developers) will be forced to provide the build process with appropriate hints in the form of a Critical Attribute on the affected component, so that an automated optimization by preloads, lazy hydration, etc. can be performed in response.

Font Loading

Fonts are the great mystery on the Internet. For more complex designs it is not uncommon that more than 6 font files have to be loaded. It would be desirable if there were many more variable fonts, but the reality is usually different. Often, developers are forced to register tons of fonts with different font styles. So it can happen that the website needs a total count of 12 font files, which have to be loaded initially to achieve the right visual result on the whole page.

This is a real performance problem. If you look for solutions, you like to hear

  • don't use WebFonts that have to be loaded
  • use another optimized font
  • reduce the number of used fonts
  • embed the fonts via Base64

You will find some articles about font loading. But most of them are more than 3 years old. Summary: not much happened here. A nice and recommendable list of different strategies can be found at web-font-loading-recipes or comprehensive-webfonts. From this it can be deduced that there is still no universal solution to the problem. However, it is possible to approach the issue very efficiently by using a preload strategy and setting classes accordingly. However, this does not make the handling of the fonts any easier. On the one hand, the preloads have to be defined per page and on the other hand, the CSS in the respective component has to be activated with the corresponding font declaration per class on demand. This is manageable for smaller projects in a 1 person team. But if several people are working in parallel, it can quickly become a nightmare. This will inevitably lead to the fact that the approach will not be accepted by the team and the optimization will be optimized out of the project in the long run.

INFO

A few words about Google Fonts: If possible, the FontFaces should always be included directly as Woff/Woff2 files via inline style. The loading mechanism via external CSS file, as it is the case with Google Fonts, creates an additional network roundtrip, which delays the loading of the actual font files.

The strategy mentioned above makes sense, but is hardly implementable with the current tools. For this reason, we are introducing Directive v-font, which takes care of the outlined behavior in a fully automated way and thus represents a truly relevant solution even on larger projects. Combined with the lazy hydration approach, the relevant fonts can be declared and loaded per component. The preloads are controlled via the critical attribute. With the help of this loading strategy, a FOUT (flash of unstyled text) and CLS can be massively reduced or eliminated. If no javascript is activated on the end device, all fonts are automatically activated via CSS.

Image Loading

For image compression and different image formats, the module nuxt-optimized-images was popularly used in the nuxt world in the past. The downside, however, is that this approach is not particularly CMS and deployment friendly. With each image change, a full build process had to be initiated. For this reason, we use the nuxt-image module, as this takes advantage of a change in NuxtJS as of version 2.13.0. In this version update, the build was split into two separate processes (javascript compilation + page generation). With nuxt-optimized-images the full build process had to be run for every image change. This is no longer the case with nuxt-image. Here only the page generation process is necessary. As a result, deployment times for all content changes can be massively reduced.

We use the module in its complete form. However, we have redeveloped the nuxt-image and nuxt-picture components, as the current version does not fully meet our requirements. For example, we lacked an appropriate preloading and lazy hydration strategy. Although there is a native loading attribute on the image element that allows prioritization, the use for websites with a lot of images is still not optimal, because the distance-from-viewport threshold is still too generous and the loading performance can deteriorate unintentionally. For this we have implemented a corresponding SEO-compliant alternative, which loads the images only when the viewport is reached, but also provides the image sources for search engines via no-script tag. This way all relevant images can be displayed even if Javascript is disabled. Furthermore you can also define multiple image sources in the picture, so it is possible to display an image in portait mode with a 9/16 aspect-ratio (multiple renditions) and in landscape mode with a 16/9 aspect-ratio (multiple renditions).

Javascript Loading

NuxtJS follows the approach to load the core files (page, app, payload, vendor, state, etc.) as fast and efficient as possible via (module-)preload from the client. This also makes total sense if you want to deliver an SPA. For the SSR build, however, we modified the delivery a bit. The many parallel downloads (fonts, images, js, ...) have a negative impact from a performance perspective. This effect increases when the javascript files grow in size due to modules and plugins. It would make sense if the initial package is kept small and only the absolutely necessary resources that can trigger the further initialization process are transferred via dynamic import. This leaves enough bandwidth to load the remaining resources (fonts, images).

This loading behavior only makes sense with an SSR build, since the full page-related static content can already be delivered and rendered with the HTML and the included CSS. This means that the user does not notice any time lags and the page is still usable. Another advantage: If the bandwidth is low, a basic functionality of the page (links, ...) can be ensured thanks to the SSR build.

RequestIdleCallback

The TimeRemaining function of the IdleDeadline object continuously returns a value <= 10 in the Lighthouse Test (simulated Motorola G4). This can be seen as an indicator for weak hardware on the end device and allows the following conclusion. If there are not enough hardware resources available to execute the JavaScript quickly, this process is suppressed. Who needs optional functionality that takes a long time to initialize and possibly leads to a temporary freeze in the browser.

We use this effect by executing the intial javascript process and the component initialization in the RequestIdleCallback, if we get a time slot >10ms from the device. Hereby we achieve a massive reduction of the TTI/TBT in the Lighthouse Test and on weak hardware, because the javascript execution is simply paused in the worst case until sufficient resources are available. This also prevents blocking of the MainThread.

Side effect: The timeslots in the Google Lighthouse Test are always <= 10ms, so no javascript will be initialized.

SpeedkitLayer

With the solutions described above, the user gets a functioning webpage displayed very quickly. However, the following situation can also occur on the end device:

  • no Javascript enabled
  • reduced bandwidth
  • weak hardware
  • unsupported browser

The reduced bandwidth or weak hardware should get a focus especially when larger amounts of data have to be transferred and executed, e.g. a ThreeJS component with more complex 3D objects. In this case, we should inform the user that the experience will be negatively affected and that there may be waiting times.

For this purpose, we provide an InfoLayer that is displayed when a minimum FCP time is exceeded, when the number of available CPU cores falls below a minimum level, when javascript is disabled or the users opens the page by an unsupported browser. The user can decide in this dialog box whether he wants to load the remaining resources despite the restrictions. If the user declines this dialog, only the fonts and images for the page will be loaded and no further javascript will be loaded or executed.

Conclusion

The findings and solutions described above have been incorporated and systematized in the Nuxt Speedkit module. Only in combination can they unfold their full functionality and ensure an overall optimization of the loading behavior. Overall we have reduced the following timing metrics ...

  • FCP
  • LCP
  • TTI
  • TBT

With this module we enable every developer in the NuxtJS context to achieve a Lighthouse Performance Score 100/100 and drastically reduce the development time for website performance optimization.

',44);function p(a,u,m,f,g,b){return i(),o("div",null,[e("h1",h,[n(r(a.$frontmatter.title)+" ",1),d]),c])}const y=t(l,[["render",p]]);export{v as __pageData,y as default}; diff --git a/docs/assets/concept.md.s_9WUY8f.lean.js b/docs/assets/concept.md.s_9WUY8f.lean.js new file mode 100644 index 0000000000..1029085c48 --- /dev/null +++ b/docs/assets/concept.md.s_9WUY8f.lean.js @@ -0,0 +1 @@ +import{_ as t,o as i,c as o,k as e,a as n,t as r,R as s}from"./chunks/framework._u06EGUx.js";const v=JSON.parse('{"title":"Concept","description":"","frontmatter":{"title":"Concept"},"headers":[],"relativePath":"concept.md","filePath":"concept.md"}'),l={name:"concept.md"},h={id:"frontmatter-title",tabindex:"-1"},d=e("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),c=s("",44);function p(a,u,m,f,g,b){return i(),o("div",null,[e("h1",h,[n(r(a.$frontmatter.title)+" ",1),d]),c])}const y=t(l,[["render",p]]);export{v as __pageData,y as default}; diff --git a/docs/assets/directives_v-font.md.vdRrO8ZL.js b/docs/assets/directives_v-font.md.vdRrO8ZL.js new file mode 100644 index 0000000000..143caf69e6 --- /dev/null +++ b/docs/assets/directives_v-font.md.vdRrO8ZL.js @@ -0,0 +1,51 @@ +import{_ as a,o as e,c as t,k as s,a as o,t as l,R as p}from"./chunks/framework._u06EGUx.js";const u=JSON.parse('{"title":"v-font","description":"","frontmatter":{"title":"v-font"},"headers":[],"relativePath":"directives/v-font.md","filePath":"directives/v-font.md"}'),c={name:"directives/v-font.md"},r={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),d=p(`

The directive v-font is used to integrate the fonts defined in the module options into the website.

To do this, the respective font must be retrieved via the $getFont method contained in the component scope (e.g. this).

Fonts are specified by family, weight and style and can be limited to elements and viewports via the options (media, selector).

Normally the directive activates the fonts only when the viewport is reached. It is recommended to use the property critical for components that are already initially contained in the viewport.

With critical component the fonts are preloaded and are initially active.
More information on critical components can be found here.

For multiple fonts, a list (Array) can be passed.

html

+<!-- single definition -->
+<element v-font="$getFont(…)">
+
+<!-- multiple definitions -->
+<element v-font="[
+  $getFont(…),
+  $getFont(…)
+]">

+<!-- single definition -->
+<element v-font="$getFont(…)">
+
+<!-- multiple definitions -->
+<element v-font="[
+  $getFont(…),
+  $getFont(…)
+]">

$getFont(family, [weight, style, options])

$getFont is included as a plugin and can be accessed via any component scope.
Use $getFont in the v-font directive and create the relevant font definition.

KeyTypeRequriedDescriptionDefault
familyStringyesFont-Family e.g. Custom Font
weightString, NumberFont-Weight e.g. 400, normal400
styleStringFont-Style e.g. normal, italicnormal
optionsObjectMedia & Selector Options see more

options

Each definition can be modified in its behaviour via the options.
With the property media, the call of the font definition can be made dependent on the viewport. The property selector can be used to limit the font to elements (e.g. span, .class).

js
{
+  media: '(min-width: 768px)',
+  selector: 'element, .elm, .elm:before'
+}
{
+  media: '(min-width: 768px)',
+  selector: 'element, .elm, .elm:before'
+}
KeyTypeRequriedDescriptionDefault
mediaStringCSS Media Query e.g. (min-width: 768px)
selectorStringCSS Selector e.g. element, .elm, .elm:before

Examples

Basic Usage

html
<element v-font="$getFont('Font Family', 700)">Text…</element>
<element v-font="$getFont('Font Family', 700)">Text…</element>

Advanced Usage

js
[
+  
+  // Font wird auf alles angewendet
+  $getFont('Font Family A'),
+
+  // Font wird auf \`b\` und \`strong\` Tags angwendet
+  $getFont('Font Family B', 700, 'normal', { selector: 'b, strong' }),
+
+  // Font erscheint erst ab Viewport \`>768px\`
+  $getFont('Font Family B', 400, 'normal', { media: '(min-width: 768px)' }),
+
+  // Font wird auf \`b\` und \`strong\` Tags angwendet und erscheint erst ab Viewport \`>768px\`
+  $getFont('Font Family B', 700, 'normal', { selector: 'b, strong', media: '(min-width: 768px)' })
+
+]
[
+  
+  // Font wird auf alles angewendet
+  $getFont('Font Family A'),
+
+  // Font wird auf \`b\` und \`strong\` Tags angwendet
+  $getFont('Font Family B', 700, 'normal', { selector: 'b, strong' }),
+
+  // Font erscheint erst ab Viewport \`>768px\`
+  $getFont('Font Family B', 400, 'normal', { media: '(min-width: 768px)' }),
+
+  // Font wird auf \`b\` und \`strong\` Tags angwendet und erscheint erst ab Viewport \`>768px\`
+  $getFont('Font Family B', 700, 'normal', { selector: 'b, strong', media: '(min-width: 768px)' })
+
+]
`,19);function y(n,E,h,F,g,m){return e(),t("div",null,[s("h1",r,[o(l(n.$frontmatter.title)+" ",1),i]),d])}const b=a(c,[["render",y]]);export{u as __pageData,b as default}; diff --git a/docs/assets/directives_v-font.md.vdRrO8ZL.lean.js b/docs/assets/directives_v-font.md.vdRrO8ZL.lean.js new file mode 100644 index 0000000000..dbf84737d5 --- /dev/null +++ b/docs/assets/directives_v-font.md.vdRrO8ZL.lean.js @@ -0,0 +1 @@ +import{_ as a,o as e,c as t,k as s,a as o,t as l,R as p}from"./chunks/framework._u06EGUx.js";const u=JSON.parse('{"title":"v-font","description":"","frontmatter":{"title":"v-font"},"headers":[],"relativePath":"directives/v-font.md","filePath":"directives/v-font.md"}'),c={name:"directives/v-font.md"},r={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),d=p("",19);function y(n,E,h,F,g,m){return e(),t("div",null,[s("h1",r,[o(l(n.$frontmatter.title)+" ",1),i]),d])}const b=a(c,[["render",y]]);export{u as __pageData,b as default}; diff --git a/docs/assets/examples_api-examples.md.mC_SbDeW.js b/docs/assets/examples_api-examples.md.mC_SbDeW.js new file mode 100644 index 0000000000..dc2bd4cfb5 --- /dev/null +++ b/docs/assets/examples_api-examples.md.mC_SbDeW.js @@ -0,0 +1,31 @@ +import{u as o,o as r,c as i,k as a,t as s,m as n,R as c,a as e}from"./chunks/framework._u06EGUx.js";const d=c(`

Runtime API Examples

This page demonstrates usage of some of the runtime APIs provided by VitePress.

The main useData() API can be used to access site, theme, and page data for the current page. It works in both .md and .vue files:

md
<script setup>
+import { useData } from 'vitepress'
+
+const { theme, page, frontmatter } = useData()
+</script>
+
+## Results
+
+### Theme Data
+<pre>{{ theme }}</pre>
+
+### Page Data
+<pre>{{ page }}</pre>
+
+### Page Frontmatter
+<pre>{{ frontmatter }}</pre>
<script setup>
+import { useData } from 'vitepress'
+
+const { theme, page, frontmatter } = useData()
+</script>
+
+## Results
+
+### Theme Data
+<pre>{{ theme }}</pre>
+
+### Page Data
+<pre>{{ page }}</pre>
+
+### Page Frontmatter
+<pre>{{ frontmatter }}</pre>

Results

Theme Data

`,6),h=a("h3",{id:"page-data",tabindex:"-1"},[e("Page Data "),a("a",{class:"header-anchor",href:"#page-data","aria-label":'Permalink to "Page Data"'},"​")],-1),m=a("h3",{id:"page-frontmatter",tabindex:"-1"},[e("Page Frontmatter "),a("a",{class:"header-anchor",href:"#page-frontmatter","aria-label":'Permalink to "Page Frontmatter"'},"​")],-1),g=a("h2",{id:"more",tabindex:"-1"},[e("More "),a("a",{class:"header-anchor",href:"#more","aria-label":'Permalink to "More"'},"​")],-1),u=a("p",null,[e("Check out the documentation for the "),a("a",{href:"https://vitepress.dev/reference/runtime-api#usedata",target:"_blank",rel:"noreferrer"},"full list of runtime APIs"),e(".")],-1),x=JSON.parse('{"title":"Runtime API Examples","description":"","frontmatter":{"outline":"deep"},"headers":[],"relativePath":"examples/api-examples.md","filePath":"examples/api-examples.md"}'),f={name:"examples/api-examples.md"},D=Object.assign(f,{setup(_){const{site:E,theme:t,page:l,frontmatter:p}=o();return(b,y)=>(r(),i("div",null,[d,a("pre",null,s(n(t)),1),h,a("pre",null,s(n(l)),1),m,a("pre",null,s(n(p)),1),g,u]))}});export{x as __pageData,D as default}; diff --git a/docs/assets/examples_api-examples.md.mC_SbDeW.lean.js b/docs/assets/examples_api-examples.md.mC_SbDeW.lean.js new file mode 100644 index 0000000000..958133d258 --- /dev/null +++ b/docs/assets/examples_api-examples.md.mC_SbDeW.lean.js @@ -0,0 +1 @@ +import{u as o,o as r,c as i,k as a,t as s,m as n,R as c,a as e}from"./chunks/framework._u06EGUx.js";const d=c("",6),h=a("h3",{id:"page-data",tabindex:"-1"},[e("Page Data "),a("a",{class:"header-anchor",href:"#page-data","aria-label":'Permalink to "Page Data"'},"​")],-1),m=a("h3",{id:"page-frontmatter",tabindex:"-1"},[e("Page Frontmatter "),a("a",{class:"header-anchor",href:"#page-frontmatter","aria-label":'Permalink to "Page Frontmatter"'},"​")],-1),g=a("h2",{id:"more",tabindex:"-1"},[e("More "),a("a",{class:"header-anchor",href:"#more","aria-label":'Permalink to "More"'},"​")],-1),u=a("p",null,[e("Check out the documentation for the "),a("a",{href:"https://vitepress.dev/reference/runtime-api#usedata",target:"_blank",rel:"noreferrer"},"full list of runtime APIs"),e(".")],-1),x=JSON.parse('{"title":"Runtime API Examples","description":"","frontmatter":{"outline":"deep"},"headers":[],"relativePath":"examples/api-examples.md","filePath":"examples/api-examples.md"}'),f={name:"examples/api-examples.md"},D=Object.assign(f,{setup(_){const{site:E,theme:t,page:l,frontmatter:p}=o();return(b,y)=>(r(),i("div",null,[d,a("pre",null,s(n(t)),1),h,a("pre",null,s(n(l)),1),m,a("pre",null,s(n(p)),1),g,u]))}});export{x as __pageData,D as default}; diff --git a/docs/assets/examples_index.md.UAHQB5IW.js b/docs/assets/examples_index.md.UAHQB5IW.js new file mode 100644 index 0000000000..aba5f55c1b --- /dev/null +++ b/docs/assets/examples_index.md.UAHQB5IW.js @@ -0,0 +1 @@ +import{_ as e,o as t,c as a}from"./chunks/framework._u06EGUx.js";const c=JSON.parse('{"title":"","description":"","frontmatter":{"layout":"home","hero":{"name":"Nuxt Speedkit","text":"Nuxt Speedkit takes over the Lighthouse performance optimization of your generated website.","tagline":"My great project tagline","actions":[{"theme":"brand","text":"Markdown Examples","link":"/markdown-examples"},{"theme":"alt","text":"API Examples","link":"/api-examples"}]},"features":[{"title":"Feature A","details":"Lorem ipsum dolor sit amet, consectetur adipiscing elit"},{"title":"Feature B","details":"Lorem ipsum dolor sit amet, consectetur adipiscing elit"},{"title":"Feature C","details":"Lorem ipsum dolor sit amet, consectetur adipiscing elit"}]},"headers":[],"relativePath":"examples/index.md","filePath":"examples/index.md"}'),i={name:"examples/index.md"};function o(s,r,n,l,m,p){return t(),a("div")}const u=e(i,[["render",o]]);export{c as __pageData,u as default}; diff --git a/docs/assets/examples_index.md.UAHQB5IW.lean.js b/docs/assets/examples_index.md.UAHQB5IW.lean.js new file mode 100644 index 0000000000..aba5f55c1b --- /dev/null +++ b/docs/assets/examples_index.md.UAHQB5IW.lean.js @@ -0,0 +1 @@ +import{_ as e,o as t,c as a}from"./chunks/framework._u06EGUx.js";const c=JSON.parse('{"title":"","description":"","frontmatter":{"layout":"home","hero":{"name":"Nuxt Speedkit","text":"Nuxt Speedkit takes over the Lighthouse performance optimization of your generated website.","tagline":"My great project tagline","actions":[{"theme":"brand","text":"Markdown Examples","link":"/markdown-examples"},{"theme":"alt","text":"API Examples","link":"/api-examples"}]},"features":[{"title":"Feature A","details":"Lorem ipsum dolor sit amet, consectetur adipiscing elit"},{"title":"Feature B","details":"Lorem ipsum dolor sit amet, consectetur adipiscing elit"},{"title":"Feature C","details":"Lorem ipsum dolor sit amet, consectetur adipiscing elit"}]},"headers":[],"relativePath":"examples/index.md","filePath":"examples/index.md"}'),i={name:"examples/index.md"};function o(s,r,n,l,m,p){return t(),a("div")}const u=e(i,[["render",o]]);export{c as __pageData,u as default}; diff --git a/docs/assets/examples_markdown-examples.md.M6_8vTVM.js b/docs/assets/examples_markdown-examples.md.M6_8vTVM.js new file mode 100644 index 0000000000..626cb172e2 --- /dev/null +++ b/docs/assets/examples_markdown-examples.md.M6_8vTVM.js @@ -0,0 +1,65 @@ +import{_ as s,o as a,c as n,R as l}from"./chunks/framework._u06EGUx.js";const g=JSON.parse('{"title":"Markdown Extension Examples","description":"","frontmatter":{},"headers":[],"relativePath":"examples/markdown-examples.md","filePath":"examples/markdown-examples.md"}'),p={name:"examples/markdown-examples.md"},e=l(`

Markdown Extension Examples

This page demonstrates some of the built-in markdown extensions provided by VitePress.

Syntax Highlighting

VitePress provides Syntax Highlighting powered by Shiki, with additional features like line-highlighting:

Input

\`\`\`js{4}
+export default {
+  data () {
+    return {
+      msg: 'Highlighted!'
+    }
+  }
+}
+\`\`\`
\`\`\`js{4}
+export default {
+  data () {
+    return {
+      msg: 'Highlighted!'
+    }
+  }
+}
+\`\`\`

Output

js
export default {
+  data () {
+    return {
+      msg: 'Highlighted!'
+    }
+  }
+}
export default {
+  data () {
+    return {
+      msg: 'Highlighted!'
+    }
+  }
+}

Custom Containers

Input

md
::: info
+This is an info box.
+:::
+
+::: tip
+This is a tip.
+:::
+
+::: warning
+This is a warning.
+:::
+
+::: danger
+This is a dangerous warning.
+:::
+
+::: details
+This is a details block.
+:::
::: info
+This is an info box.
+:::
+
+::: tip
+This is a tip.
+:::
+
+::: warning
+This is a warning.
+:::
+
+::: danger
+This is a dangerous warning.
+:::
+
+::: details
+This is a details block.
+:::

Output

INFO

This is an info box.

TIP

This is a tip.

WARNING

This is a warning.

DANGER

This is a dangerous warning.

Details

This is a details block.

More

Check out the documentation for the full list of markdown extensions.

`,19),o=[e];function t(i,c,r,d,h,E){return a(),n("div",null,o)}const u=s(p,[["render",t]]);export{g as __pageData,u as default}; diff --git a/docs/assets/examples_markdown-examples.md.M6_8vTVM.lean.js b/docs/assets/examples_markdown-examples.md.M6_8vTVM.lean.js new file mode 100644 index 0000000000..80469e06ce --- /dev/null +++ b/docs/assets/examples_markdown-examples.md.M6_8vTVM.lean.js @@ -0,0 +1 @@ +import{_ as s,o as a,c as n,R as l}from"./chunks/framework._u06EGUx.js";const g=JSON.parse('{"title":"Markdown Extension Examples","description":"","frontmatter":{},"headers":[],"relativePath":"examples/markdown-examples.md","filePath":"examples/markdown-examples.md"}'),p={name:"examples/markdown-examples.md"},e=l("",19),o=[e];function t(i,c,r,d,h,E){return a(),n("div",null,o)}const u=s(p,[["render",t]]);export{g as __pageData,u as default}; diff --git a/docs/assets/guide_caveats.md.rxJAUaDq.js b/docs/assets/guide_caveats.md.rxJAUaDq.js new file mode 100644 index 0000000000..43b274dc85 --- /dev/null +++ b/docs/assets/guide_caveats.md.rxJAUaDq.js @@ -0,0 +1,45 @@ +import{_ as a,o as e,c as l,k as s,a as p,t,R as o}from"./chunks/framework._u06EGUx.js";const _=JSON.parse('{"title":"Caveats","description":"","frontmatter":{"title":"Caveats"},"headers":[],"relativePath":"guide/caveats.md","filePath":"guide/caveats.md"}'),c={name:"guide/caveats.md"},i={id:"frontmatter-title",tabindex:"-1"},r=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),d=o(`

Prevent SPEEDINDEX_OF_ZERO and NO_LCP

The window event nuxt-speedkit:run is provided and useable to run code outside the app during initialization.

If the performance is not sufficient on the client side, this can be retrieved with the help of the event object e.detail.sufficient.

Example

A case where the event may be needed would be when the initial viewport on a website is blank and it is not displayed until the initialization is complete.

In this case, measurements with Lighthouse can lead to these errors SPEEDINDEX_OF_ZERO and NO_LCP.

In order to solve this case, it can be provided that the content of the stage can already be displayed outside of the app initialization in the case of a slow initialization.

In this case the global event nuxt-speedkit:run can be used. It will return an event object with e.detail.sufficient as value. With the help of this status you can decide whether the stage should be displayed in advance.

Component Example

html
<template>
+  <div class="stage">…</div>
+</template>
+
+<script setup>
+
+  useHead({
+    script: [
+      {
+        key: 'prevent-script',
+        children: \`
+          window.addEventListener("nuxt-speedkit:run", function (e) {
+            if (!e.detail.sufficient) {
+              // added style class to display the content
+              document.querySelector('.stage').classList.add('visible')
+            }
+          });
+        \`
+      }
+    ]
+  })
+
+</script>
<template>
+  <div class="stage">…</div>
+</template>
+
+<script setup>
+
+  useHead({
+    script: [
+      {
+        key: 'prevent-script',
+        children: \`
+          window.addEventListener("nuxt-speedkit:run", function (e) {
+            if (!e.detail.sufficient) {
+              // added style class to display the content
+              document.querySelector('.stage').classList.add('visible')
+            }
+          });
+        \`
+      }
+    ]
+  })
+
+</script>
`,10);function E(n,y,h,u,v,F){return e(),l("div",null,[s("h1",i,[p(t(n.$frontmatter.title)+" ",1),r]),d])}const m=a(c,[["render",E]]);export{_ as __pageData,m as default}; diff --git a/docs/assets/guide_caveats.md.rxJAUaDq.lean.js b/docs/assets/guide_caveats.md.rxJAUaDq.lean.js new file mode 100644 index 0000000000..10c9260470 --- /dev/null +++ b/docs/assets/guide_caveats.md.rxJAUaDq.lean.js @@ -0,0 +1 @@ +import{_ as a,o as e,c as l,k as s,a as p,t,R as o}from"./chunks/framework._u06EGUx.js";const _=JSON.parse('{"title":"Caveats","description":"","frontmatter":{"title":"Caveats"},"headers":[],"relativePath":"guide/caveats.md","filePath":"guide/caveats.md"}'),c={name:"guide/caveats.md"},i={id:"frontmatter-title",tabindex:"-1"},r=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),d=o("",10);function E(n,y,h,u,v,F){return e(),l("div",null,[s("h1",i,[p(t(n.$frontmatter.title)+" ",1),r]),d])}const m=a(c,[["render",E]]);export{_ as __pageData,m as default}; diff --git a/docs/assets/guide_options.md.G-dxl61U.js b/docs/assets/guide_options.md.G-dxl61U.js new file mode 100644 index 0000000000..9fa51c588f --- /dev/null +++ b/docs/assets/guide_options.md.G-dxl61U.js @@ -0,0 +1,107 @@ +import{_ as e,o as n,c as o,k as s,a as l,t,R as p}from"./chunks/framework._u06EGUx.js";const m=JSON.parse('{"title":"Options","description":"","frontmatter":{"title":"Options"},"headers":[],"relativePath":"guide/options.md","filePath":"guide/options.md"}'),c={name:"guide/options.md"},r={id:"frontmatter-title",tabindex:"-1"},d=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),i=p(`

crossorigin

  • Type: String, Boolean
    • Default: 'anonymous'
    • Valid values: anonymous, use-credentials, '', true, false

Sets the global crossorigin value of the Nuxt Speedkit preloads.
The default value is the crossorigin value from the Render Configuration.

Set false to disable the crossorigin.

MDN - HTML.Attributes.crossorigin

optimizePreloads

  • Type: Boolean
    • Default: true

Activating this option optimizes the initial script preloads and removes unnecessary loads.

The following NuxtJS settings are made or overwritten in the nuxt.config:

PropertyValue
nuxt.options.vite.build.manifestfalse
nuxt.options.noScriptstrue

detection

  • Type: Object

These options can be used to define the initial checks to display the SpeedkitLayer. The prerequisite are that the SpeedkitLayer has been embedded into the layout.

js
{
+  performance: true,
+  browserSupport: true
+}
{
+  performance: true,
+  browserSupport: true
+}
KeyTypeRequiredDescriptionDefault
performanceBooleanyesChecking whether the minimum characteristic values have been reached. If the test is negative, the SpeedkitLayer will be displayed.true
browserSupportBooleanyesCheck if the current browser on client side is supported. If the test is negative, the SpeedkitLayer will be displayed.true

INFO

For the browser support detection, the default Browserslist of the NuxtJS configuration is used.

performanceMetrics

  • Type: Object

With the help of the metrics, the actual performance check on client side can be configured.

js
{
+  device: {
+    hardwareConcurrency: { min: 2, max: 48 },
+    deviceMemory: { min: 2 }
+  },
+  timing: {
+    fcp: 800,
+    dcl: 1200 // fallback if fcp is not available (safari)
+  }
+}
{
+  device: {
+    hardwareConcurrency: { min: 2, max: 48 },
+    deviceMemory: { min: 2 }
+  },
+  timing: {
+    fcp: 800,
+    dcl: 1200 // fallback if fcp is not available (safari)
+  }
+}

device

  • Type: Object

Definition of the minimum hardware requirements for visiting the website.

js
{
+  hardwareConcurrency: { min: 2, max: 48 },
+  deviceMemory: { min: 2 }
+}
{
+  hardwareConcurrency: { min: 2, max: 48 },
+  deviceMemory: { min: 2 }
+}
KeyTypeRequiredDescriptionDefault
hardwareConcurrencyObjectyesmin/max number of CPUs{ min: 2, max: 48 }
deviceMemoryObjectyesmin size of memory{ min: 2 }

timing

  • Type: Object

Definition of the max. FCP duration (ms). If the specified value is exceeded, the SpeedkitLayer will be displayed. If the browser does not grant access to the FCP, as fallback the DCL will be evaluated.

js
{
+  fcp: 800,
+  dcl: 1200 // fallback if fcp is not available (safari)
+}
{
+  fcp: 800,
+  dcl: 1200 // fallback if fcp is not available (safari)
+}
KeyTypeRequiredDescriptionDefault
fcpNumberyesMax. FCP duration in ms learn More800
dclNumberyesMax. DCL duration in ms1200

fonts

  • Type: Array

List of all font families used in the project. Only the fonts that are listed in the configuration can be retrieved and integrated via $fonts.getFont(...).

js
[
+  {
+    family: 'Font A',
+    locals: ['Font A'],
+    fallback: ['Arial', 'sans-serif'],
+    variances: […]
+  },
+  {
+    family: 'Font B',
+    locals: ['Font B'],
+    fallback: ['Arial', 'sans-serif'],
+    variances: […]
+  }
+]
[
+  {
+    family: 'Font A',
+    locals: ['Font A'],
+    fallback: ['Arial', 'sans-serif'],
+    variances: […]
+  },
+  {
+    family: 'Font B',
+    locals: ['Font B'],
+    fallback: ['Arial', 'sans-serif'],
+    variances: […]
+  }
+]

Font-Family

  • Type: Object

Describes a font family with all its variants.

js
{
+  family: 'Font A',
+  locals: ['Font A'],
+  fallback: ['Arial', 'sans-serif'],
+  variances: […]
+}
{
+  family: 'Font A',
+  locals: ['Font A'],
+  fallback: ['Arial', 'sans-serif'],
+  variances: […]
+}
KeyTypeRequiredDescription
familyStringyesname of the font family
localsArrayyessystem font name of the specified font family
fallbackArrayyesfallback fonts e.g. ['Arial', 'sans-serif']
variancesArrayyeslist of font family variants (e.g. Bold, Italic)

WARNING

Prevent sizing discrepancy between your custom and fallback font for perfect swap and reduction of layout shifts. Learn more

Font-Variance

  • Type: Object

A font variant describes an instance of a font family and is used to generate the FontFace declaration. Font variants differ in style and weight.

js
{
+  style: 'normal',
+  weight: 400,
+  sources: [
+    { src: '@/assets/fonts/font-a-regular.woff', type:'woff' },
+    { src: '@/assets/fonts/font-a-regular.woff2', type:'woff2' }
+  ]
+}
{
+  style: 'normal',
+  weight: 400,
+  sources: [
+    { src: '@/assets/fonts/font-a-regular.woff', type:'woff' },
+    { src: '@/assets/fonts/font-a-regular.woff2', type:'woff2' }
+  ]
+}
KeyTypeRequiredDescription
styleStringyesfont-style of FontFace, e.g. normal, italic
weightString or Numberyesfont-weight of FontFace, e.g. 400, normal
sourcesArrayyeslist of all font files assigned to the variant (sources)

sources

  • Type: Array

List of all available font files of a font family variation.

js
[
+  { src: '@/assets/fonts/font-a-regular.woff', type:'woff' },
+  { src: '@/assets/fonts/font-a-regular.woff2', type:'woff2' }
+]
[
+  { src: '@/assets/fonts/font-a-regular.woff', type:'woff' },
+  { src: '@/assets/fonts/font-a-regular.woff2', type:'woff2' }
+]
KeyTypeRequiredValue
srcStringyespath to a font file, the use of aliases is possible
typeStringyesfile format of the specified file, e.g. woff, woff2, …

targetFormats

  • Type: Array
    • Default: ['webp', 'avif', 'jpg|jpeg|png|gif']

Sets the default formats for the SpeedkitPicture.

Can be overridden in the SpeedkitPicture via the formats property.

For png, jpeg and gif formats we have added the | operator in the declaration.
This adjusts the destination format to the source format.

Example

Bad

The declaration below generates a png, jpeg and gif (destination format) for each jpeg (source format). The same applies to a png and a gif as source format. However, this is not practical for the source specifications in the Picture.

js
{
+  targetFormats: ['jpg', 'jpeg', 'png', 'gif']
+}
{
+  targetFormats: ['jpg', 'jpeg', 'png', 'gif']
+}

Good

Based on the source format, the appropriate target format is created using the declaration described below.

js
{
+  targetFormats: ['jpg|jpeg|png|gif']
+}
{
+  targetFormats: ['jpg|jpeg|png|gif']
+}

INFO

For the avif and webp formats the | operator is not needed, because these two image formats do not depend on the source format, as it is the case for png, jpeg and gif.

lazyOffset

  • Type: Object

Global option for the IntersectionObserver built into the Nuxt Speedkit.

js
{
+  component: '0%',
+  asset: '0%' 
+}
{
+  component: '0%',
+  asset: '0%' 
+}
KeyTypeRequiredDescriptionDefault
componentStringyesrootMargin value for SpeedkitHydrate.0%
assetStringyesrootMargin value for all static ressources (v-font, SpeedkitPicture & SpeedkitImage).0%

disableNuxtFontaine

  • Type: Boolean
    • Default: false

If set, @nuxtjs/fontaine will not be integrated.

disableNuxtImage

  • Type: Boolean
    • Default: false

If set, @nuxt/image will not be integrated.

DANGER

Note that the use of SpeedkitImage, SpeedkitPicture, SpeedkitVimeo and SpeedkitYoutube is not supported if @nuxt/image is not integrated.

`,76);function y(a,E,h,f,u,g){return n(),o("div",null,[s("h1",r,[l(t(a.$frontmatter.title)+" ",1),d]),i])}const F=e(c,[["render",y]]);export{m as __pageData,F as default}; diff --git a/docs/assets/guide_options.md.G-dxl61U.lean.js b/docs/assets/guide_options.md.G-dxl61U.lean.js new file mode 100644 index 0000000000..bd0188c204 --- /dev/null +++ b/docs/assets/guide_options.md.G-dxl61U.lean.js @@ -0,0 +1 @@ +import{_ as e,o as n,c as o,k as s,a as l,t,R as p}from"./chunks/framework._u06EGUx.js";const m=JSON.parse('{"title":"Options","description":"","frontmatter":{"title":"Options"},"headers":[],"relativePath":"guide/options.md","filePath":"guide/options.md"}'),c={name:"guide/options.md"},r={id:"frontmatter-title",tabindex:"-1"},d=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),i=p("",76);function y(a,E,h,f,u,g){return n(),o("div",null,[s("h1",r,[l(t(a.$frontmatter.title)+" ",1),d]),i])}const F=e(c,[["render",y]]);export{m as __pageData,F as default}; diff --git a/docs/assets/guide_setup.md.5ri77JFP.js b/docs/assets/guide_setup.md.5ri77JFP.js new file mode 100644 index 0000000000..4aefd79fca --- /dev/null +++ b/docs/assets/guide_setup.md.5ri77JFP.js @@ -0,0 +1,189 @@ +import{_ as a,o as l,c as p,k as s,a as o,t as e,R as c}from"./chunks/framework._u06EGUx.js";const g=JSON.parse('{"title":"Setup","description":"","frontmatter":{"title":"Setup"},"headers":[],"relativePath":"guide/setup.md","filePath":"guide/setup.md"}'),t={name:"guide/setup.md"},r={id:"frontmatter-title",tabindex:"-1"},E=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),y=c(`

Check the Nuxt.js documentation for more information about installing and using modules in Nuxt.js.

Installation

Install nuxt-speedkit as a dependency to your project:

bash
yarn add nuxt-speedkit@next
yarn add nuxt-speedkit@next
bash
npm install nuxt-speedkit@next
npm install nuxt-speedkit@next

Add nuxt-speedkit to the modules section of nuxt.config.js:

@nuxt/image

Nuxt Speedkit uses the module @nuxt/image, if this is not already present, it will be integrated automatically.

It is necessary for the use of the components SpeedkitYoutube and SpeedkitVimeo to add aliases and domains to the @nuxt/image options. These are needed to retrieve the images from Youtube and Vimeo.

js
{
+  domains: ['img.youtube.com', 'i.vimeocdn.com'],
+  alias: {
+    youtube: 'https://img.youtube.com',
+    vimeo: 'https://i.vimeocdn.com',
+  }
+}
{
+  domains: ['img.youtube.com', 'i.vimeocdn.com'],
+  alias: {
+    youtube: 'https://img.youtube.com',
+    vimeo: 'https://i.vimeocdn.com',
+  }
+}

More about @nuxt/image module options can be found here.

Example Configuration

js
{
+  modules: [
+    'nuxt-speedkit'
+  ],
+
+  speedkit: {
+
+    detection: {
+      performance: true,
+      browserSupport: true
+    },
+
+    performanceMetrics: {
+      device: {
+        hardwareConcurrency: { min: 2, max: 48 },
+        deviceMemory: { min: 2 }
+      },
+      timing: {
+        fcp: 800,
+        dcl: 1200
+      }
+    },
+
+    fonts: [{
+      family: 'Font A',
+      locals: ['Font A'],
+      fallback: ['Arial', 'sans-serif'],
+      variances: [
+        {
+          style: 'normal',
+          weight: 400,
+          sources: [
+            { src: '@/assets/fonts/font-a-regular.woff', type:'woff' },
+            { src: '@/assets/fonts/font-a-regular.woff2', type:'woff2' }
+          ]
+        }, {
+          style: 'italic',
+          weight: 400,
+          sources: [
+            { src: '@/assets/fonts/font-a-regularItalic.woff', type:'woff' },
+            { src: '@/assets/fonts/font-a-regularItalic.woff2', type:'woff2' }
+          ]
+        }, {
+          style: 'normal',
+          weight: 700,
+          sources: [
+            { src: '@/assets/fonts/font-a-700.woff', type:'woff' },
+            { src: '@/assets/fonts/font-a-700.woff2', type:'woff2' }
+          ]
+        }
+      ]
+    }],
+
+    targetFormats: ['webp', 'avif', 'jpg|jpeg|png|gif'],
+
+    componentAutoImport: false,
+    componentPrefix: undefined,
+
+    /**
+     * IntersectionObserver rootMargin for Compoennts and Assets
+     */
+    lazyOffset: {
+      component: '0%',
+      asset: '0%'
+    }
+    
+  },
+
+  image: {
+    screens: {
+      default: 320,
+      xxs: 480,
+      xs: 576,
+      sm: 768,
+      md: 996,
+      lg: 1200,
+      xl: 1367,
+      xxl: 1600,
+      '4k': 1921
+    },
+
+    domains: ['img.youtube.com', 'i.vimeocdn.com'],
+
+    alias: {
+      youtube: 'https://img.youtube.com',
+      vimeo: 'https://i.vimeocdn.com',
+    }
+  }
+}
{
+  modules: [
+    'nuxt-speedkit'
+  ],
+
+  speedkit: {
+
+    detection: {
+      performance: true,
+      browserSupport: true
+    },
+
+    performanceMetrics: {
+      device: {
+        hardwareConcurrency: { min: 2, max: 48 },
+        deviceMemory: { min: 2 }
+      },
+      timing: {
+        fcp: 800,
+        dcl: 1200
+      }
+    },
+
+    fonts: [{
+      family: 'Font A',
+      locals: ['Font A'],
+      fallback: ['Arial', 'sans-serif'],
+      variances: [
+        {
+          style: 'normal',
+          weight: 400,
+          sources: [
+            { src: '@/assets/fonts/font-a-regular.woff', type:'woff' },
+            { src: '@/assets/fonts/font-a-regular.woff2', type:'woff2' }
+          ]
+        }, {
+          style: 'italic',
+          weight: 400,
+          sources: [
+            { src: '@/assets/fonts/font-a-regularItalic.woff', type:'woff' },
+            { src: '@/assets/fonts/font-a-regularItalic.woff2', type:'woff2' }
+          ]
+        }, {
+          style: 'normal',
+          weight: 700,
+          sources: [
+            { src: '@/assets/fonts/font-a-700.woff', type:'woff' },
+            { src: '@/assets/fonts/font-a-700.woff2', type:'woff2' }
+          ]
+        }
+      ]
+    }],
+
+    targetFormats: ['webp', 'avif', 'jpg|jpeg|png|gif'],
+
+    componentAutoImport: false,
+    componentPrefix: undefined,
+
+    /**
+     * IntersectionObserver rootMargin for Compoennts and Assets
+     */
+    lazyOffset: {
+      component: '0%',
+      asset: '0%'
+    }
+    
+  },
+
+  image: {
+    screens: {
+      default: 320,
+      xxs: 480,
+      xs: 576,
+      sm: 768,
+      md: 996,
+      lg: 1200,
+      xl: 1367,
+      xxl: 1600,
+      '4k': 1921
+    },
+
+    domains: ['img.youtube.com', 'i.vimeocdn.com'],
+
+    alias: {
+      youtube: 'https://img.youtube.com',
+      vimeo: 'https://i.vimeocdn.com',
+    }
+  }
+}

See module options.

`,13);function i(n,F,d,u,f,m){return l(),p("div",null,[s("h1",r,[o(e(n.$frontmatter.title)+" ",1),E]),y])}const B=a(t,[["render",i]]);export{g as __pageData,B as default}; diff --git a/docs/assets/guide_setup.md.5ri77JFP.lean.js b/docs/assets/guide_setup.md.5ri77JFP.lean.js new file mode 100644 index 0000000000..c1526936ff --- /dev/null +++ b/docs/assets/guide_setup.md.5ri77JFP.lean.js @@ -0,0 +1 @@ +import{_ as a,o as l,c as p,k as s,a as o,t as e,R as c}from"./chunks/framework._u06EGUx.js";const g=JSON.parse('{"title":"Setup","description":"","frontmatter":{"title":"Setup"},"headers":[],"relativePath":"guide/setup.md","filePath":"guide/setup.md"}'),t={name:"guide/setup.md"},r={id:"frontmatter-title",tabindex:"-1"},E=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),y=c("",13);function i(n,F,d,u,f,m){return l(),p("div",null,[s("h1",r,[o(e(n.$frontmatter.title)+" ",1),E]),y])}const B=a(t,[["render",i]]);export{g as __pageData,B as default}; diff --git a/docs/assets/guide_usage.md.wgLYVO7n.js b/docs/assets/guide_usage.md.wgLYVO7n.js new file mode 100644 index 0000000000..41fc3f4618 --- /dev/null +++ b/docs/assets/guide_usage.md.wgLYVO7n.js @@ -0,0 +1,35 @@ +import{_ as a,o as n,c as t,k as e,a as o,t as l,R as p}from"./chunks/framework._u06EGUx.js";const b=JSON.parse('{"title":"Usage","description":"","frontmatter":{"title":"Usage"},"headers":[],"relativePath":"guide/usage.md","filePath":"guide/usage.md"}'),i={name:"guide/usage.md"},c={id:"frontmatter-title",tabindex:"-1"},r=e("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),d=p(`

The following tools are provided to optimize your webpage:

Critical prop for critical components

A critical component is visible in the viewport when the web page is initially loaded. This can be communicated to the automated background process via a critical prop. The flag is passed on to all child components. This means that only the main component (organism) must be provided with it. With the help of this flag, the corresponding static resources (images & fonts) are also declared as preload tags in the page head. All other components and their associated resources, that do not have a positive critical prop, are lazy loaded on demand.

html
<component critical />
<component critical />

INFO

In the current version, the critical flag must be set manually on the components. Automation would be conceivable in the future. However, according to current knowledge, this would have a massive impact on deployment times when using Puppeteer or similar tools. We are still collecting ideas here. If you know of a more efficient way, please send us a feature request.

Font declaration

The integration of fonts is component-based directly in the Vue template. All fonts, which have been declared in nuxt.config, can be assigned directly to the corresponding HTML element or component. In addition, subselectors and media queries can be defined, which enable viewport-based declarations or rich-text declarations. The cool thing about this is that it saves the additional declaration in the CSS. You no longer have to keep the template and the CSS with its corresponding selectors for fonts in sync. Yeah! This is extremely helpful, especially when it comes to theming.

html
<element v-font="$getFont(…)" />
<element v-font="$getFont(…)" />

Learn more about directive v-font.

WARNING

Fonts are no longer explicitly defined via CSS, otherwise the loading behavior of the fonts cannot be controlled and an optimized loading behavior of the page can no longer be guaranteed.

Import components

Until now, components were imported either statically (import component from '@/component';) or dynamically (import('@/component')). However, with these two variants, hydration cannot be controlled. As a result, all components are also initialized on initial load. nuxt-speedkit offers a corresponding loader for this feature request. Each async component import should be enclosed with this loader in a page or layout.

  • 'Ensures that components are initialized only when needed in the visible viewport.'
  • 'Optimizes initialization of critical components on initial page load (critical components are initially in the visible viewport).'

In the background, the module vue3-lazy-hydration inspired by vue-lazy-hydration from Markus Oberlehner is used in a standardised way.

js
import speedkitHydrate from '#speedkit/hydrate';
+
+export default {
+  components: {
+    Stage: speedkitHydrate(() => import('@/components/organisms/Stage')),
+  }
+};
import speedkitHydrate from '#speedkit/hydrate';
+
+export default {
+  components: {
+    Stage: speedkitHydrate(() => import('@/components/organisms/Stage')),
+  }
+};

Whether a component is in the viewport or not is determined in the background by the intersection observer. If the initialisation is to take place earlier, e.g. when scrolling, this can be adjusted accordingly via the rootMargin option in the nuxt.config.

WARNING

Although the #speedkit/hydrate function can be used in any component, we recommend its explicit use only in pages and layout. Its use within components can be useful only in explicit special cases. Here we recommend the general use of static imports.

INFO

With NODE-ENV (development), the components are included directly.
This is relevant for the hot reload of the imported vue files.

Speedkit Components

In order to be able to load further static resources such as pictures, iFrames or Vimeo/Youtube videos in the iFrame in a performance-optimised way, we provide the following components. The speedkit components can be imported via the namespace #speedkit/components.

html
<template>
+  <speedkit-picture>
+</template>
+
+<script>
+import SpeedkitPicture from '#speedkit/components/SpeedkitPicture'
+export default {
+  components: {
+    SpeedkitPicture
+  }
+}
+</script>
<template>
+  <speedkit-picture>
+</template>
+
+<script>
+import SpeedkitPicture from '#speedkit/components/SpeedkitPicture'
+export default {
+  components: {
+    SpeedkitPicture
+  }
+}
+</script>

INFO

The speedkit components will be expanded in the future. If you have explicit wishes, please send us a feature request or directly a pull request with the corresponding feature 😃

Example

You can check out a sample integration of nuxt-speedkit at Nuxt Speedkit Example.

`,26);function h(s,m,y,E,u,g){return n(),t("div",null,[e("h1",c,[o(l(s.$frontmatter.title)+" ",1),r]),d])}const k=a(i,[["render",h]]);export{b as __pageData,k as default}; diff --git a/docs/assets/guide_usage.md.wgLYVO7n.lean.js b/docs/assets/guide_usage.md.wgLYVO7n.lean.js new file mode 100644 index 0000000000..b2ecb96ee8 --- /dev/null +++ b/docs/assets/guide_usage.md.wgLYVO7n.lean.js @@ -0,0 +1 @@ +import{_ as a,o as n,c as t,k as e,a as o,t as l,R as p}from"./chunks/framework._u06EGUx.js";const b=JSON.parse('{"title":"Usage","description":"","frontmatter":{"title":"Usage"},"headers":[],"relativePath":"guide/usage.md","filePath":"guide/usage.md"}'),i={name:"guide/usage.md"},c={id:"frontmatter-title",tabindex:"-1"},r=e("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),d=p("",26);function h(s,m,y,E,u,g){return n(),t("div",null,[e("h1",c,[o(l(s.$frontmatter.title)+" ",1),r]),d])}const k=a(i,[["render",h]]);export{b as __pageData,k as default}; diff --git a/docs/assets/index.md.itw9ycj3.js b/docs/assets/index.md.itw9ycj3.js new file mode 100644 index 0000000000..fbb3595931 --- /dev/null +++ b/docs/assets/index.md.itw9ycj3.js @@ -0,0 +1 @@ +import{_ as r,o as a,c as o,k as e,a as i,t as n,R as s,a1 as l}from"./chunks/framework._u06EGUx.js";const v=JSON.parse('{"title":"Introduction","description":"","frontmatter":{"outline":"deep","title":"Introduction"},"headers":[],"relativePath":"index.md","filePath":"index.md"}'),d={name:"index.md"},p={id:"frontmatter-title",tabindex:"-1"},u=e("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),h=s('

Module for NuxtJS.

You are reading the documentation for Nuxt Speedkit (v3)!

Nuxt Speedkit takes over the lighthouse performance optimization of your generated website.

In order to achieve a performance score of 100/100, only the necessary resources located in the current viewport may be initialized when the page is loaded. This includes images, fonts and the js-modules. Until now, there has been no practical and usable concept to help developers maintain an overview and enable accurate targeting in NuxtJS projects.

This module addresses this problem and provides a holistic approach to intelligently load the necessary viewport related resources to reduce FCP, DCL, TTI, TBT and CLS.

We didn't reinvent the whole wheel. We adapt the lazy hydration concept of Markus Oberlehner to load js components in an efficient way, use the nuxt/image module as a base to retrieve optimized image resolutions for our picture and image components and add some new stuff to obtain a holistic solution.

Requirements

  • NodeJS >= 19
  • NuxtJS >= 3.5.0

Features

We provide the following CMS-friendly features:

  • dynamic loading of viewport based page resources like fonts, components, pictures, images and iframes
  • optional blocking of javascript execution by initial performance measuring
  • optimized initial load of javascript files by eliminating of unnecessary javascript files
  • prevents the loading of unnecessary resources (including components) that are outside the current viewport.
  • optional info layer concept to inform users about a reduced UX when bandwidth or hardware is compromised.
  • completely new approach of font declaration
  • optimized picture component (supports viewport based sources e.g. landscape/portrait)
  • optimized image component
  • supports SEO-friendly lazy hydration mode (picture + image)
  • optimized youtube/vimeo component (auto generated poster image in different resolutions)

Results

  • delivery of the minimum required resources based on the current viewport
  • if you use the tools as specified you will get a lighthouse performance score of 100/100

Demos

',16);function c(t,m,f,g,b,k){return a(),o("div",null,[e("h1",p,[i(n(t.$frontmatter.title)+" ",1),u]),h])}const x=r(d,[["render",c]]);export{v as __pageData,x as default}; diff --git a/docs/assets/index.md.itw9ycj3.lean.js b/docs/assets/index.md.itw9ycj3.lean.js new file mode 100644 index 0000000000..7461a86bc3 --- /dev/null +++ b/docs/assets/index.md.itw9ycj3.lean.js @@ -0,0 +1 @@ +import{_ as r,o as a,c as o,k as e,a as i,t as n,R as s,a1 as l}from"./chunks/framework._u06EGUx.js";const v=JSON.parse('{"title":"Introduction","description":"","frontmatter":{"outline":"deep","title":"Introduction"},"headers":[],"relativePath":"index.md","filePath":"index.md"}'),d={name:"index.md"},p={id:"frontmatter-title",tabindex:"-1"},u=e("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),h=s("",16);function c(t,m,f,g,b,k){return a(),o("div",null,[e("h1",p,[i(n(t.$frontmatter.title)+" ",1),u]),h])}const x=r(d,[["render",c]]);export{v as __pageData,x as default}; diff --git a/docs/assets/migration_v2-0-13.md.kI3DdF5o.js b/docs/assets/migration_v2-0-13.md.kI3DdF5o.js new file mode 100644 index 0000000000..6fb823e686 --- /dev/null +++ b/docs/assets/migration_v2-0-13.md.kI3DdF5o.js @@ -0,0 +1,17 @@ +import{_ as a,o as t,c as o,k as e,a as n,t as l,R as p}from"./chunks/framework._u06EGUx.js";const k=JSON.parse('{"title":"Migrate from v2.0.x to v2.0.13","description":"","frontmatter":{"title":"Migrate from v2.0.x to v2.0.13"},"headers":[],"relativePath":"migration/v2-0-13.md","filePath":"migration/v2-0-13.md"}'),c={name:"migration/v2-0-13.md"},i={id:"frontmatter-title",tabindex:"-1"},r=e("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),d=p(`

With the change to version 2.0.13 there are the following changes:

SpeedkitLayer

Hardware performance check removed

The hardware check for showing the SpeedkitLayer has been removed.

The option device from module and the use of the message (#nuxt-speedkit-message-outdated-device) in the SpeedkitLayer, are no longer valid and must be removed.

Alternatively, it now waits for a free idle slot when initializing the JS,

  • if a free slot is available, the JS is initialized
  • if no free slot is available, the message #nuxt-speedkit-message-weak-hardware is showed in the SpeedkitLayer.

The old message #nuxt-speedkit-message-outdated-device must be replaced by the new #nuxt-speedkit-message-weak-hardware. (See Messages)

Messages

  1. outdated-device to weak-hardware

    html
    <li id="nuxt-speedkit-message-outdated-device">
    +  outdated device
    +</li>
    <li id="nuxt-speedkit-message-outdated-device">
    +  outdated device
    +</li>

    replaced with

    html
    <li id="nuxt-speedkit-message-weak-hardware">
    +  weak hardware
    +</li>
    <li id="nuxt-speedkit-message-weak-hardware">
    +  weak hardware
    +</li>
  2. slow-connection to reduced-bandwidth

    html
    <li id="nuxt-speedkit-message-slow-connection">
    +  slow-connection
    +</li>
    <li id="nuxt-speedkit-message-slow-connection">
    +  slow-connection
    +</li>

    replaced with

    html
    <li id="nuxt-speedkit-message-reduced-bandwidth">
    +  reduced-bandwidth
    +</li>
    <li id="nuxt-speedkit-message-reduced-bandwidth">
    +  reduced-bandwidth
    +</li>

Button Interactions

Button #nuxt-speedkit-button-init-font has been replaced by #nuxt-speedkit-button-init-reduced-view.

#nuxt-speedkit-button-init-reduced-view does the following when clicked:

  1. Sets the CSS class nuxt-speedkit-reduced-view on the html tag.
  2. Activates all fonts by setting the class font-active on all elements with the attribute data-font.
  3. Converts all not activated pictures (:hydrate="false") from noscript to picture.

INFO

The CSS class nuxt-speedkit-reduced-view is removed again at app initialization.

`,15);function E(s,h,y,u,g,m){return t(),o("div",null,[e("h1",i,[n(l(s.$frontmatter.title)+" ",1),r]),d])}const b=a(c,[["render",E]]);export{k as __pageData,b as default}; diff --git a/docs/assets/migration_v2-0-13.md.kI3DdF5o.lean.js b/docs/assets/migration_v2-0-13.md.kI3DdF5o.lean.js new file mode 100644 index 0000000000..24775c0309 --- /dev/null +++ b/docs/assets/migration_v2-0-13.md.kI3DdF5o.lean.js @@ -0,0 +1 @@ +import{_ as a,o as t,c as o,k as e,a as n,t as l,R as p}from"./chunks/framework._u06EGUx.js";const k=JSON.parse('{"title":"Migrate from v2.0.x to v2.0.13","description":"","frontmatter":{"title":"Migrate from v2.0.x to v2.0.13"},"headers":[],"relativePath":"migration/v2-0-13.md","filePath":"migration/v2-0-13.md"}'),c={name:"migration/v2-0-13.md"},i={id:"frontmatter-title",tabindex:"-1"},r=e("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),d=p("",15);function E(s,h,y,u,g,m){return t(),o("div",null,[e("h1",i,[n(l(s.$frontmatter.title)+" ",1),r]),d])}const b=a(c,[["render",E]]);export{k as __pageData,b as default}; diff --git a/docs/assets/migration_v2-2-0.md.NbSpjQqB.js b/docs/assets/migration_v2-2-0.md.NbSpjQqB.js new file mode 100644 index 0000000000..3b88e195e2 --- /dev/null +++ b/docs/assets/migration_v2-2-0.md.NbSpjQqB.js @@ -0,0 +1 @@ +import{_ as d,o,c,k as t,a as s,t as i,R as r}from"./chunks/framework._u06EGUx.js";const S=JSON.parse('{"title":"Migrate from v2.0.13 to v2.2.0","description":"","frontmatter":{"title":"Migrate from v2.0.13 to v2.2.0"},"headers":[],"relativePath":"migration/v2-2-0.md","filePath":"migration/v2-2-0.md"}'),a={name:"migration/v2-2-0.md"},n={id:"frontmatter-title",tabindex:"-1"},p=t("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),u=r('

With the change to version 2.2.0 there are the following changes:

Package Structure

Package structure was updated.

Everything in the folder runtime is available with the alias #speedkit.

General

Old PathNew Path
nuxt-speedkit/hydrate#speedkit/hyrdate

Components

Old PathNew Path
nuxt-speedkit/components/abstracts/ComponentObserver#speedkit/components/abstracts/ComponentObserver
nuxt-speedkit/components/abstracts/OnlySsr#speedkit/components/abstracts/OnlySsr
nuxt-speedkit/components/GoogleLighthouse#speedkit/components/GoogleLighthouse
nuxt-speedkit/components/SpeedkitImage#speedkit/components/SpeedkitImage
nuxt-speedkit/components/SpeedkitPicture#speedkit/components/SpeedkitPicture
nuxt-speedkit/components/SpeedkitVimeo#speedkit/components/SpeedkitVimeo
nuxt-speedkit/components/SpeedkitYoutube#speedkit/components/SpeedkitYoutube
nuxt-speedkit/components/SpeedkitIframe#speedkit/components/SpeedkitIframe
nuxt-speedkit/components/SpeedkitImage#speedkit/components/SpeedkitImage
nuxt-speedkit/components/SpeedkitLayer#speedkit/components/SpeedkitLayer
nuxt-speedkit/components/SpeedkitPicture#speedkit/components/SpeedkitPicture
nuxt-speedkit/components/SpeedkitVimeo#speedkit/components/SpeedkitVimeo
nuxt-speedkit/components/SpeedkitYoutube#speedkit/components/SpeedkitYoutube

Utils

Old PathNew Path
nuxt-speedkit/utils#speedkit/utils
nuxt-speedkit/utils/base64#speedkit/utils/base64
nuxt-speedkit/utils/browser#speedkit/utils/browser
nuxt-speedkit/utils/description#speedkit/utils/description
nuxt-speedkit/utils/lighthouse#speedkit/utils/lighthouse
nuxt-speedkit/utils/mimeType#speedkit/utils/mimeType
nuxt-speedkit/utils/performance#speedkit/utils/performance
nuxt-speedkit/utils/placeholder#speedkit/utils/placeholder
nuxt-speedkit/utils/string#speedkit/utils/string
nuxt-speedkit/utils/support#speedkit/utils/support
',10);function l(e,k,h,m,b,x){return o(),c("div",null,[t("h1",n,[s(i(e.$frontmatter.title)+" ",1),p]),u])}const _=d(a,[["render",l]]);export{S as __pageData,_ as default}; diff --git a/docs/assets/migration_v2-2-0.md.NbSpjQqB.lean.js b/docs/assets/migration_v2-2-0.md.NbSpjQqB.lean.js new file mode 100644 index 0000000000..8bf588f065 --- /dev/null +++ b/docs/assets/migration_v2-2-0.md.NbSpjQqB.lean.js @@ -0,0 +1 @@ +import{_ as d,o,c,k as t,a as s,t as i,R as r}from"./chunks/framework._u06EGUx.js";const S=JSON.parse('{"title":"Migrate from v2.0.13 to v2.2.0","description":"","frontmatter":{"title":"Migrate from v2.0.13 to v2.2.0"},"headers":[],"relativePath":"migration/v2-2-0.md","filePath":"migration/v2-2-0.md"}'),a={name:"migration/v2-2-0.md"},n={id:"frontmatter-title",tabindex:"-1"},p=t("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),u=r("",10);function l(e,k,h,m,b,x){return o(),c("div",null,[t("h1",n,[s(i(e.$frontmatter.title)+" ",1),p]),u])}const _=d(a,[["render",l]]);export{S as __pageData,_ as default}; diff --git a/docs/assets/migration_v2.md.Jnf8W_4F.js b/docs/assets/migration_v2.md.Jnf8W_4F.js new file mode 100644 index 0000000000..dccb4047c8 --- /dev/null +++ b/docs/assets/migration_v2.md.Jnf8W_4F.js @@ -0,0 +1,35 @@ +import{_ as a,o as t,c as n,k as e,a as o,t as p,R as l}from"./chunks/framework._u06EGUx.js";const b=JSON.parse('{"title":"Migrate from v1 to v2","description":"","frontmatter":{"title":"Migrate from v1 to v2"},"headers":[],"relativePath":"migration/v2.md","filePath":"migration/v2.md"}'),c={name:"migration/v2.md"},r={id:"frontmatter-title",tabindex:"-1"},d=e("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),i=l(`

With v2 the documentation was changed. You can find the previous version here.

Component Import

The speedkitComponents property is removed from skeleton.

Instead, the SpeedkitHydrate is now used as a function wrapper.

old (v1)

js
export default {
+  speedkitComponents: {
+    Stage: () => import('@/components/organisms/Stage'),
+  }
+};
export default {
+  speedkitComponents: {
+    Stage: () => import('@/components/organisms/Stage'),
+  }
+};

now (v2)

js
import speedkitHydrate from '#speedkit/hydrate';
+
+export default {
+  components: {
+    Stage: speedkitHydrate(() => import('@/components/organisms/Stage')),
+  }
+};
import speedkitHydrate from '#speedkit/hydrate';
+
+export default {
+  components: {
+    Stage: speedkitHydrate(() => import('@/components/organisms/Stage')),
+  }
+};

Components

SpeedkitLayer

In the SpeedkitLayer the ids and style classes were updated, these are now kebab-case.

Typeoldnew
id#nuxt-speedkit__speedkit-layernuxt-speedkit-layer
id#nuxt-speedkit__speedkit-layer__content#nuxt-speedkit-layer-content
id#nuxt-speedkit__speedkit-layer__close#nuxt-speedkit-layer-close
class.nuxt-speedkit__speedkit-layer--visible.nuxt-speedkit-layer-visible
id#nuxt-speedkit__message__nojs#nuxt-speedkit-message-nojs
id#nuxt-speedkit__message__unsupported-browser#nuxt-speedkit-message-unsupported-browser
id#nuxt-speedkit__message__outdated-device#nuxt-speedkit-message-outdated-device
id#nuxt-speedkit__message__slow-connection#nuxt-speedkit-message-slow-connection
id#nuxt-speedkit__button__init-nojs#nuxt-speedkit-button-init-nojs
id#nuxt-speedkit__button__init-font#nuxt-speedkit-button-init-font
id#nuxt-speedkit__button__init-app#nuxt-speedkit-button-init-app

SpeedkitPicture / SpeedkitPictureExperimental

The SpeedkitPicture was replaced by a new version of the SpeedkitPictureExperimental, therefore the SpeedkitPictureExperimental was removed from the project.

When reusing a SpeedkitPictureExperimental as a SpeedkitPicture, the structure of the property sources must be adapted, this has been updated.

old (v1)

js
[ 
+  { src: 'landscape.jpg', sizes: '992:1024' },
+  { src: 'portrait.jpg', sizes: '414,768:768' }
+]
[ 
+  { src: 'landscape.jpg', sizes: '992:1024' },
+  { src: 'portrait.jpg', sizes: '414,768:768' }
+]

now (v2)

js

+[
+  { src: '/img/landscape.png', sizes: { md: '100vw' }, media: '(orientation: landscape)' },
+  { src: '/img/portrait.png', sizes: { default: '100vw', sm: '100vw' }, media: '(orientation: portrait)' }
+]

+[
+  { src: '/img/landscape.png', sizes: { md: '100vw' }, media: '(orientation: landscape)' },
+  { src: '/img/portrait.png', sizes: { default: '100vw', sm: '100vw' }, media: '(orientation: portrait)' }
+]

More information about the integration of SpeedkitPicture can be found here.

WARNING

Important: In the new version of SpeedkitPicture the placeholder property is no longer included.

SpeedkitYoutube / SpeedkitYoutubeExperimental

With the change of the SpeedkitPicture also SpeedkitYoutube and SpeedkitYoutubeExperimental were reduced to SpeedkitYoutube.

The events loading and enter have been removed.

More information about the integration of SpeedkitYoutube can be found here.

SpeedkitIframe

Property intersectionObserver was renamed to componentObserver.

More information about the integration of SpeedkitIframe can be found here.

`,28);function E(s,y,u,h,m,k){return t(),n("div",null,[e("h1",r,[o(p(s.$frontmatter.title)+" ",1),d]),i])}const v=a(c,[["render",E]]);export{b as __pageData,v as default}; diff --git a/docs/assets/migration_v2.md.Jnf8W_4F.lean.js b/docs/assets/migration_v2.md.Jnf8W_4F.lean.js new file mode 100644 index 0000000000..b16a00291e --- /dev/null +++ b/docs/assets/migration_v2.md.Jnf8W_4F.lean.js @@ -0,0 +1 @@ +import{_ as a,o as t,c as n,k as e,a as o,t as p,R as l}from"./chunks/framework._u06EGUx.js";const b=JSON.parse('{"title":"Migrate from v1 to v2","description":"","frontmatter":{"title":"Migrate from v1 to v2"},"headers":[],"relativePath":"migration/v2.md","filePath":"migration/v2.md"}'),c={name:"migration/v2.md"},r={id:"frontmatter-title",tabindex:"-1"},d=e("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),i=l("",28);function E(s,y,u,h,m,k){return t(),n("div",null,[e("h1",r,[o(p(s.$frontmatter.title)+" ",1),d]),i])}const v=a(c,[["render",E]]);export{b as __pageData,v as default}; diff --git a/docs/assets/migration_v3.md.pgAE9dPh.js b/docs/assets/migration_v3.md.pgAE9dPh.js new file mode 100644 index 0000000000..a03ca673b5 --- /dev/null +++ b/docs/assets/migration_v3.md.pgAE9dPh.js @@ -0,0 +1,15 @@ +import{_ as a,o as t,c as o,k as s,a as n,t as p,R as l}from"./chunks/framework._u06EGUx.js";const f=JSON.parse('{"title":"Migrate from v2 to v3","description":"","frontmatter":{"title":"Migrate from v2 to v3"},"headers":[],"relativePath":"migration/v3.md","filePath":"migration/v3.md"}'),r={name:"migration/v3.md"},c={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),d=l(`

With v3 the documentation was changed. You can find the previous version here.

Deprecations

Components

Component
abstracts/ComponentObserverreplaced with composable useComponentObserver
abstracts/AbstractOnlySsrremoved

options

Option
loaderremoved
componentAutoImportremoved
componentPrefixremoved

Components

SpeedkitPicture & SpeedkitImage

Die Eigenschaft loader wurde entfernt.

Directives

v-font

Wenn die Directive v-font verwendet wird, muss das Composable useFont verwendet werden, um die Funktion $getFont im template bereitzustellen.

html
<template>
+  <span v-font="$getFont(…)"></span>
+</template>
+
+<script setup>
+import useFonts from '#speedkit/composables/fonts';
+const { $getFont } = useFonts();
+</script>
<template>
+  <span v-font="$getFont(…)"></span>
+</template>
+
+<script setup>
+import useFonts from '#speedkit/composables/fonts';
+const { $getFont } = useFonts();
+</script>
`,13);function E(e,y,h,m,u,b){return t(),o("div",null,[s("h1",c,[n(p(e.$frontmatter.title)+" ",1),i]),d])}const g=a(r,[["render",E]]);export{f as __pageData,g as default}; diff --git a/docs/assets/migration_v3.md.pgAE9dPh.lean.js b/docs/assets/migration_v3.md.pgAE9dPh.lean.js new file mode 100644 index 0000000000..ee7786ecdf --- /dev/null +++ b/docs/assets/migration_v3.md.pgAE9dPh.lean.js @@ -0,0 +1 @@ +import{_ as a,o as t,c as o,k as s,a as n,t as p,R as l}from"./chunks/framework._u06EGUx.js";const f=JSON.parse('{"title":"Migrate from v2 to v3","description":"","frontmatter":{"title":"Migrate from v2 to v3"},"headers":[],"relativePath":"migration/v3.md","filePath":"migration/v3.md"}'),r={name:"migration/v3.md"},c={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),d=l("",13);function E(e,y,h,m,u,b){return t(),o("div",null,[s("h1",c,[n(p(e.$frontmatter.title)+" ",1),i]),d])}const g=a(r,[["render",E]]);export{f as __pageData,g as default}; diff --git a/docs/assets/v1_components_experimental_speedkit-picture.md.8TZco5vW.js b/docs/assets/v1_components_experimental_speedkit-picture.md.8TZco5vW.js new file mode 100644 index 0000000000..dc33ec0e8d --- /dev/null +++ b/docs/assets/v1_components_experimental_speedkit-picture.md.8TZco5vW.js @@ -0,0 +1,71 @@ +import{_ as e,o as n,c as l,k as s,a as o,t as p,R as t}from"./chunks/framework._u06EGUx.js";const b=JSON.parse('{"title":"SpeedkitPicture (Experimental)","description":"","frontmatter":{"title":"SpeedkitPicture (Experimental)"},"headers":[],"relativePath":"v1/components/experimental/speedkit-picture.md","filePath":"v1/components/experimental/speedkit-picture.md"}'),r={name:"v1/components/experimental/speedkit-picture.md"},c={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),d=t(`

SpeedkitPicture is based on the module @nuxt/image@0.2.0. However, we have created a separate Vue component for it, because at the time of the implementation of the module the component NuxtPicture and the API for generating the images were not yet completely finished, or this module did not yet completely cover our use-case. We hope that with the final completion of @nuxt/image we will be able to remove our component SpeedkitPicture and can use @nuxt/image & NuxtPicture with full functionality.

WARNING

This is an experimental component that we will offer until nuxt/image is fully feature-complete released. This also means that we will accept bug reports for the SpeedkitPicture component. However, we will not fix bugs that are present in the generator of @nuxt/image.

Features

With the current implementation of SpeedkitPicture we can cover the following functionality:

  • generation of multiple image resolutions (srcset)
  • breakpoint-based differentiation of multiple image resolutions and ratios (srcset + media-rule)
  • generation of breakpoint-based placeholders (different ratios e.g. for mobile portrait and landscape)
  • optimized preloading of critical image resources
  • lazy load of non-critical image resources
  • base path support
  • lazy hydration support
  • load and optimize remote images from custom domains
  • full SEO support

Usage

The SpeedkitPicture (Experimental) is used to automatically generate and display different image sizes and/or image ratios for different viewports.

The specified resources can be given by absolute path (static folder) or complete URL. nuxt/image downloads the resources fully automatically and stores the generated and optimized renditions in the destination folder.

Example

vue
<template>
+  <div>
+    <speedkit-picture v-bind="image" />
+  </div>
+</template>
+
+<script>
+import SpeedkitPicture from '#speedkit/components/experimental/SpeedkitPicture';
+export default {
+  components: { SpeedkitPicture },
+  data () {
+    return {
+      image: {
+        sources: [
+          { src: 'landscape.jpg', sizes: '576:576,1024:1024,1280:1280,1680:1680,1920:1920' },
+          { src: 'portrait.jpg', sizes: '414,768:768' }
+        ],
+        title: 'Image Title',
+        alt: 'Image Alt'
+      }
+    };
+  }
+};
+</script>
<template>
+  <div>
+    <speedkit-picture v-bind="image" />
+  </div>
+</template>
+
+<script>
+import SpeedkitPicture from '#speedkit/components/experimental/SpeedkitPicture';
+export default {
+  components: { SpeedkitPicture },
+  data () {
+    return {
+      image: {
+        sources: [
+          { src: 'landscape.jpg', sizes: '576:576,1024:1024,1280:1280,1680:1680,1920:1920' },
+          { src: 'portrait.jpg', sizes: '414,768:768' }
+        ],
+        title: 'Image Title',
+        alt: 'Image Alt'
+      }
+    };
+  }
+};
+</script>

Properties

js
{
+  sources: […],
+  alt: 'Image Alt',
+  title: 'Image Title',
+  crossorigin: 'anonymous'
+}
{
+  sources: […],
+  alt: 'Image Alt',
+  title: 'Image Title',
+  crossorigin: 'anonymous'
+}

sources

  • Type: Array

List of resources used.

INFO

Note: If more than one resource is included, the smallest width from the sizes property is taken as the condition for the source e.g. (min-width: 992px). This allows viewport dependent aspect ratios.

Information on property src can be found at here.

Property sizes describes the image sizes in which the resource is to be displayed. Image sizes are comma separated and describe the image width and its dependent viewport width e.g. ImageWidth:MinWidth.

In the following example, two different image ratios are used.

  • landscape.jpg is applied at a viewport of 992px with an image size of 1024px.
  • portrait.jpg is applied below 992px and has two viewport dependent image sizes, at (min-width: 768px) the width 768px and everything below that the width 414px.
js
[
+  
+  { src: 'landscape.jpg', sizes: '992:1024' },
+  { src: 'portrait.jpg', sizes: '414,768:768' }
+]
[
+  
+  { src: 'landscape.jpg', sizes: '992:1024' },
+  { src: 'portrait.jpg', sizes: '414,768:768' }
+]

critical

  • Type: Boolean
    • Default: $parent.isCritical

Set component as critical component.

Learn more about critical components

alt

  • Type: String

Image alternative Text.

MDN - HTMLImageElement.alt

title

  • Type: String

Image Title.

MDN - HTMLElement.title

crossorigin

  • Type: String
    • default: anonymus

Image CrossOrigin.

MDN - HTMLImageElement.crossOrigin

Events

html
<speedkit-picture 
+  @load="console.log('Loaded!')" 
+  @enter="console.log('Viewport!')" 
+/>
<speedkit-picture 
+  @load="console.log('Loaded!')" 
+  @enter="console.log('Viewport!')" 
+/>
NameDescription
loadTriggered when the image resource has been completely loaded.
enterTriggered when component has reached the viewport.
`,41);function E(a,y,u,h,m,g){return n(),l("div",null,[s("h1",c,[o(p(a.$frontmatter.title)+" ",1),i]),d])}const F=e(r,[["render",E]]);export{b as __pageData,F as default}; diff --git a/docs/assets/v1_components_experimental_speedkit-picture.md.8TZco5vW.lean.js b/docs/assets/v1_components_experimental_speedkit-picture.md.8TZco5vW.lean.js new file mode 100644 index 0000000000..94ab0da4a3 --- /dev/null +++ b/docs/assets/v1_components_experimental_speedkit-picture.md.8TZco5vW.lean.js @@ -0,0 +1 @@ +import{_ as e,o as n,c as l,k as s,a as o,t as p,R as t}from"./chunks/framework._u06EGUx.js";const b=JSON.parse('{"title":"SpeedkitPicture (Experimental)","description":"","frontmatter":{"title":"SpeedkitPicture (Experimental)"},"headers":[],"relativePath":"v1/components/experimental/speedkit-picture.md","filePath":"v1/components/experimental/speedkit-picture.md"}'),r={name:"v1/components/experimental/speedkit-picture.md"},c={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),d=t("",41);function E(a,y,u,h,m,g){return n(),l("div",null,[s("h1",c,[o(p(a.$frontmatter.title)+" ",1),i]),d])}const F=e(r,[["render",E]]);export{b as __pageData,F as default}; diff --git a/docs/assets/v1_components_experimental_speedkit-youtube.md.m7szeORz.js b/docs/assets/v1_components_experimental_speedkit-youtube.md.m7szeORz.js new file mode 100644 index 0000000000..810b3454a9 --- /dev/null +++ b/docs/assets/v1_components_experimental_speedkit-youtube.md.m7szeORz.js @@ -0,0 +1,85 @@ +import{_ as l,D as p,o as t,c,k as s,a,t as r,I as i,w as E,R as n}from"./chunks/framework._u06EGUx.js";const q=JSON.parse('{"title":"SpeedkitYoutube (Experimental)","description":"","frontmatter":{"title":"SpeedkitYoutube (Experimental)"},"headers":[],"relativePath":"v1/components/experimental/speedkit-youtube.md","filePath":"v1/components/experimental/speedkit-youtube.md"}'),y={name:"v1/components/experimental/speedkit-youtube.md"},d={id:"frontmatter-title",tabindex:"-1"},u=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),h=n(`

Please note the privacy policy when using. Google Youtube-API is integrated via dependency youtube-player.

We have integrated SpeedkitYoutube (Experimental) as an example to show how iFrame content must be integrated in a performance-optimized manner. For this purpose, a separate IntersectionObserver has been implemented, which detects a longer dwell time of the component in the viewport. The iFrame is initialized only after a positive detection. This prevents immense data from having to be loaded when simply scrolling through the page. So that no empty space is visible to the user, we use the functionality of the SpeedkitPicture (Experimental) and preload the corresponding Youtube poster in different renditions, so the illusion is perfect for the user and he does not notice anything of the optimized lazy load procedure.

WARNING

SpeedkitYoutube (Experimental) is based on the component SpeedkitPicture (Experimental). We hope that with the final completion of @nuxt/image we will be able to modify this component and can use @nuxt/image & NuxtPicture with full functionality.

Usage

The SpeedkitYoutube (Experimental) is used to initialise Youtube videos with Youtube-API only when they are in the visible viewport.

The id of the Youtube video and the appropriate viewport dependent widths must be specified in sizes (What is sizes?). The SpeedkitPicture (Experimental) is used for the poster, so the generation of the poster is automated.

Learn more about SpeedkitPicture (Experimental)

Example

vue
<template>
+  <div>
+    <speedkit-youtube v-bind="youtube" @playing="onPlaying"  />
+  </div>
+</template>
+
+<script>
+import SpeedkitPicture from '#speedkit/components/experimental/SpeedkitYoutube';
+export default {
+  components: { SpeedkitPicture },
+  data () {
+    return {
+      youtube: {
+        id: 'youtube-id',
+        host: 'https://www.youtube-nocookie.com',
+        config: { … },
+        poster: {
+          sources: [
+            { src: 'poster.jpg', sizes: '414,768:768,576:576,1024:1024,1280:1280,1680:1680,1920:1920' },
+          ],
+          alt: 'Youtube Alt',
+          title: 'Youtube Title'
+        }
+      }
+    };
+  },
+  methods: {
+    onPlaying () {
+      console.log('Youtube Player playing!');
+    }
+  }
+};
+</script>
<template>
+  <div>
+    <speedkit-youtube v-bind="youtube" @playing="onPlaying"  />
+  </div>
+</template>
+
+<script>
+import SpeedkitPicture from '#speedkit/components/experimental/SpeedkitYoutube';
+export default {
+  components: { SpeedkitPicture },
+  data () {
+    return {
+      youtube: {
+        id: 'youtube-id',
+        host: 'https://www.youtube-nocookie.com',
+        config: { … },
+        poster: {
+          sources: [
+            { src: 'poster.jpg', sizes: '414,768:768,576:576,1024:1024,1280:1280,1680:1680,1920:1920' },
+          ],
+          alt: 'Youtube Alt',
+          title: 'Youtube Title'
+        }
+      }
+    };
+  },
+  methods: {
+    onPlaying () {
+      console.log('Youtube Player playing!');
+    }
+  }
+};
+</script>

Properties

js
{
+  id: 'youtube-id',
+  autoplay: false,
+  host: 'https://www.youtube-nocookie.com',
+  config: { … }
+}
{
+  id: 'youtube-id',
+  autoplay: false,
+  host: 'https://www.youtube-nocookie.com',
+  config: { … }
+}

id

`,12),g=s("code",null,"String",-1),m=n(`

Sets the Youtube ID.

sizes

  • Type: String
    • Default: undefined

Defines the viewport dependent image sizes for the poster.

If undefined the image size of Youtube thumbnail is used (e.g. https://img.youtube.com/vi/\${id}/maxresdefault.jpg).

autoplay

  • Type: Boolean
    • Default: false

If set, the player is started automatically when the viewport centre is reached.

host

  • Type: String
    • Default: https://www.youtube-nocookie.com

Sets the host url for the Youtube player.

config

  • Type: Object
    • Default: { playsinline: 1, modestbranding: 1 }

Sets the Youtube player configuration.

Learn more about Youtube Player Parameters

Events

html
<speedkit-picture 
+  @ready="console.log('Ready!')" 
+  @loading="console.log('Video Loading!')" 
+  @playing="console.log('Video Playing!')" 
+  @enter="console.log('Viewport!')" 
+/>
<speedkit-picture 
+  @ready="console.log('Ready!')" 
+  @loading="console.log('Video Loading!')" 
+  @playing="console.log('Video Playing!')" 
+  @enter="console.log('Viewport!')" 
+/>
NameDescription
readyTriggered when Youtube-Api is completely loaded.
loadingTriggered when video starts loading.
playingTriggered when video is finished loading and playing.
enterTriggered when component has reached the viewport.
`,18);function b(e,f,F,k,v,_){const o=p("badge");return t(),c("div",null,[s("h1",d,[a(r(e.$frontmatter.title)+" ",1),u]),h,s("ul",null,[s("li",null,[a("Type: "),g,s("ul",null,[s("li",null,[i(o,null,{default:E(()=>[a("required")]),_:1})])])])]),m])}const x=l(y,[["render",b]]);export{q as __pageData,x as default}; diff --git a/docs/assets/v1_components_experimental_speedkit-youtube.md.m7szeORz.lean.js b/docs/assets/v1_components_experimental_speedkit-youtube.md.m7szeORz.lean.js new file mode 100644 index 0000000000..6a0483e4f6 --- /dev/null +++ b/docs/assets/v1_components_experimental_speedkit-youtube.md.m7szeORz.lean.js @@ -0,0 +1 @@ +import{_ as l,D as p,o as t,c,k as s,a,t as r,I as i,w as E,R as n}from"./chunks/framework._u06EGUx.js";const q=JSON.parse('{"title":"SpeedkitYoutube (Experimental)","description":"","frontmatter":{"title":"SpeedkitYoutube (Experimental)"},"headers":[],"relativePath":"v1/components/experimental/speedkit-youtube.md","filePath":"v1/components/experimental/speedkit-youtube.md"}'),y={name:"v1/components/experimental/speedkit-youtube.md"},d={id:"frontmatter-title",tabindex:"-1"},u=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),h=n("",12),g=s("code",null,"String",-1),m=n("",18);function b(e,f,F,k,v,_){const o=p("badge");return t(),c("div",null,[s("h1",d,[a(r(e.$frontmatter.title)+" ",1),u]),h,s("ul",null,[s("li",null,[a("Type: "),g,s("ul",null,[s("li",null,[i(o,null,{default:E(()=>[a("required")]),_:1})])])])]),m])}const x=l(y,[["render",b]]);export{q as __pageData,x as default}; diff --git a/docs/assets/v1_components_speedkit-iframe.md.FFeQ4ub1.js b/docs/assets/v1_components_speedkit-iframe.md.FFeQ4ub1.js new file mode 100644 index 0000000000..be82e99235 --- /dev/null +++ b/docs/assets/v1_components_speedkit-iframe.md.FFeQ4ub1.js @@ -0,0 +1,43 @@ +import{_ as a,o as n,c as o,k as s,a as l,t,R as p}from"./chunks/framework._u06EGUx.js";const g=JSON.parse('{"title":"SpeedkitIframe","description":"","frontmatter":{"title":"SpeedkitIframe"},"headers":[],"relativePath":"v1/components/speedkit-iframe.md","filePath":"v1/components/speedkit-iframe.md"}'),r={name:"v1/components/speedkit-iframe.md"},c={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),E=p(`

SpeedkitIframe, Iframe & IntersectionObserver in one.

Exkurs

Iframes have a tendency, in the special case of the initial page load, to disrupt the construction and initialisation of the actual page through the massive loading of resources from another source.

For the user, this is particularly visible by:

  • Freeze (Short freeze of the page)
  • Delayed loading of resources (Bilder, Fonts)
  • Unnecessarily generated traffic

Solution

In order to solve these points, care should be taken to ensure that the initialisation of the iframe takes place downstream. This can be realised, for example, via an IntersectionObserver. This sets the source on the iframe only when the visible viewport has been reached.

The following conditions can thus be fulfilled:

  • Iframe load is reactive.
  • No resources are blocked during loading.
  • Traffic is only generated when the iframe is visible.

The strategy mentioned above is provided by the SpeedkitIframe, which can be used in the same way as a normal HTML Iframe. The included IntersectionObserver is configured via the intersectionObserver property.

Usage

The SpeedkitIframe is used like a normal HTML Iframe.

Example

vue
<template>
+  <speedkit-iframe v-bind="iframe" @load="onIFrameLoaded" />
+</template>
+
+<script>
+  export default {
+    data: {
+      iframe: {
+       src: '…',
+       intersectionObserver: { trackVisibility: true, delay: 350 }
+      }
+    },
+    methods: {
+      onIFrameLoaded (){
+        console.log('iframe loaded!');
+      }
+    }
+  };
+</script>
<template>
+  <speedkit-iframe v-bind="iframe" @load="onIFrameLoaded" />
+</template>
+
+<script>
+  export default {
+    data: {
+      iframe: {
+       src: '…',
+       intersectionObserver: { trackVisibility: true, delay: 350 }
+      }
+    },
+    methods: {
+      onIFrameLoaded (){
+        console.log('iframe loaded!');
+      }
+    }
+  };
+</script>

Properties

Use native attributes from HTML Iframe.

intersectionObserver

Sets the options from the integrated IntersectionObserver.

For advanced usage, learn more about option trackVisibility from IntersectionObserver.

Events

html
<speedkit-iframe 
+  @load="console.log('Loaded!')" 
+  @enter="console.log('Enter Viewport!')" 
+/>
<speedkit-iframe 
+  @load="console.log('Loaded!')" 
+  @enter="console.log('Enter Viewport!')" 
+/>
NameDescription
loadTriggered when Iframe has finished loading.
enterTriggered when component has reached the viewport.
`,23);function d(e,y,h,m,u,f){return n(),o("div",null,[s("h1",c,[l(t(e.$frontmatter.title)+" ",1),i]),E])}const v=a(r,[["render",d]]);export{g as __pageData,v as default}; diff --git a/docs/assets/v1_components_speedkit-iframe.md.FFeQ4ub1.lean.js b/docs/assets/v1_components_speedkit-iframe.md.FFeQ4ub1.lean.js new file mode 100644 index 0000000000..e6f8e12d0a --- /dev/null +++ b/docs/assets/v1_components_speedkit-iframe.md.FFeQ4ub1.lean.js @@ -0,0 +1 @@ +import{_ as a,o as n,c as o,k as s,a as l,t,R as p}from"./chunks/framework._u06EGUx.js";const g=JSON.parse('{"title":"SpeedkitIframe","description":"","frontmatter":{"title":"SpeedkitIframe"},"headers":[],"relativePath":"v1/components/speedkit-iframe.md","filePath":"v1/components/speedkit-iframe.md"}'),r={name:"v1/components/speedkit-iframe.md"},c={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),E=p("",23);function d(e,y,h,m,u,f){return n(),o("div",null,[s("h1",c,[l(t(e.$frontmatter.title)+" ",1),i]),E])}const v=a(r,[["render",d]]);export{g as __pageData,v as default}; diff --git a/docs/assets/v1_components_speedkit-layer.md.lVxe18Ze.js b/docs/assets/v1_components_speedkit-layer.md.lVxe18Ze.js new file mode 100644 index 0000000000..16ba73e552 --- /dev/null +++ b/docs/assets/v1_components_speedkit-layer.md.lVxe18Ze.js @@ -0,0 +1,125 @@ +import{_ as p,D as r,o as c,c as i,k as s,a as e,t as E,I as a,w as l,R as t}from"./chunks/framework._u06EGUx.js";const O=JSON.parse('{"title":"SpeedkitLayer","description":"","frontmatter":{"title":"SpeedkitLayer"},"headers":[],"relativePath":"v1/components/speedkit-layer.md","filePath":"v1/components/speedkit-layer.md"}'),y={name:"v1/components/speedkit-layer.md"},d={id:"frontmatter-title",tabindex:"-1"},u=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),h=t('

The loading behavior of webpages based on nuxtjs is designed in such a way that all necessary Javascript resources are preloaded and directly initialized with the initial load of the page. However, this behavior creates a negative impact on the Lighthouse Performance Score (TTI) for larger pages that have an increased initial load of additional resources, such as fonts, images, plugins, modules (nuxt-i18n, ...).

Excursus

The Lighthouse Test is not a tool to make a general statement about the quality of a website programming. Lighthouse rather tries to map a metric for the usability of a page from the user's point of view. This includes accessibility, best practices, SEO and of course performance.

This last point is often misinterpreted by developers. If you want to implement features that increase usability for the user (interactions/more complex animations, ...), this will always have an impact on performance in the Lighthouse Test for larger website projects, as the corresponding Javascript must be loaded for this. Finally, Lighthouse does also not rate the design, but the accessibility (size of click areas, etc.) of a website. You should therefore not ask yourself the following question: "How can I fully optimize my JavaScript to achieve a Lighthouse score of 100/100?". You have to ask yourself much more the question: "What is especially important to a user with low bandwidth or weak hardware on my site?".

The answer to this is relatively simple: the site must first and foremost work and you must be able to get to the information you need quickly.

No more and no less.

The user doesn't need any fancy slider animations and parallax effects that can only be implemented with certain libraries. Or a softload mechanism to get to more pages in a more elegant and animated way, but which initially needs an increased amount of javascript logic. All he wants is that information is retrievable reasonably fast and he can click through the presence. Among other things, he doesn't need full retina images, which simply take a long time to load with 3G.

Solution

For this reason, we pause the initialization of the javascript in the following cases:

  • reduced bandwidth
  • weak hardware
  • unsupported browser

In these cases, a layer will be displayed that allows the user to decide whether he wants to initialize the full experience and download further resources despite the physical impairment or whether he wants to visit the website with a reduced UX (without Javascript). The layer is also displayed with a corresponding message when Javascript is deactivated.

Learn more in Concept.

Usage

If the SpeedkitLayer is implemented, the javascript initialisation is automatically monitored. If one of the events described above occurs, the process is paused and only continued or cancelled after a user interaction in the layer.

The layer is placed once in the layout (e.g. layouts/default.vue). The included SpeedkitLayer serves as a wrapper and must be filled according to the template, see example component.

The content contains messages and buttons that are displayed in the respective event. Messages and buttons are defined with an id, these are set to display: none; by default via CSS.

  • e.g. nuxt-speedkit__message__unsupported-browser for message
  • e.g. nuxt-speedkit__button__init-app for button

TIP

For the closing mechanism of the layer, see Hide Layer.

Messages

The messages are elements that are displayed for the relevant events.

Initially, all IDs are set to display: none;, so no message is visible.
When an event is triggered, the relevant message is displayed via the ID using the style attribute display: block;.

',21),_=s("thead",null,[s("tr",null,[s("th",null,"ID"),s("th",null,"Description")])],-1),g=s("code",null,"nuxt-speedkit__message__unsupported-browser",-1),b=s("td",null,[e("User Browser is not supported by "),s("a",{href:"/v1/guide/options.html#browsersupport"},[s("code",null,"Browserslist")]),e(".")],-1),F=s("code",null,"nuxt-speedkit__message__outdated-device",-1),m=s("td",null,"User hardware (number of processor & RAM) are not sufficient.",-1),f=s("code",null,"nuxt-speedkit__message__slow-connection",-1),v=s("td",null,"Connection speed is too low.",-1),w=t(`

Example

html
<!-- initial -->
+<div id="nuxt-speedkit__message__unsupported-browser">
+  Your browser is not supported!
+</div>
+
+<!-- active -->
+<div id="nuxt-speedkit__message__unsupported-browser" style="display: block;">
+  Your browser is not supported!
+</div>
<!-- initial -->
+<div id="nuxt-speedkit__message__unsupported-browser">
+  Your browser is not supported!
+</div>
+
+<!-- active -->
+<div id="nuxt-speedkit__message__unsupported-browser" style="display: block;">
+  Your browser is not supported!
+</div>

Buttons

The buttons are interaction elements for the user with which he can make his choice at the relevant event.

Initially, all IDs except for nuxt-speedkit__button__init-nojs are set to display: none;. When an event is triggered, the relevant button is displayed via the ID using the style attribute display: block;.

`,5),k=s("thead",null,[s("tr",null,[s("th",null,"ID"),s("th",null,"Description")])],-1),q=s("code",null,"nuxt-speedkit__button__init-nojs",-1),D=s("td",null,[e("Visible when javascript is disabled, needed so that the user can hide the layer. Requires the "),s("a",{href:"/v1/components/speedkit-layer.html#hide-layer"},"Hide Layer"),e(" implementation.")],-1),T=s("code",null,"nuxt-speedkit__button__init-font",-1),C=s("td",null,"Is used to offer the user the possibility to visit the page only with activated fonts. Other initialisations of the Javascript and loading of the pictures are prevented.",-1),A=s("code",null,"nuxt-speedkit__button__init-app",-1),B=s("td",null,"Activates all features. The initialisation of the JavaScript is started, images are loaded.",-1),x=t(`

INFO

It is recommended to register an Inline Click-Event for the buttons nuxt-speedkit__button__init-font and nuxt-speedkit__button__init-app.

More information under Force App initialization or Font load

Force initialization (App, Font)

For Unsupported-Browser and Insufficient Hardware events, an onclick event must also be set with the id.

In the event, the global variable __NUXT_SPEEDKIT_FONT_INIT__ or __NUXT_SPEEDKIT_FONT_INIT__ must be set to true.

These are needed if the user has already reacted before the initial Javascript has been loaded. After the javascript has been successfully loaded, the app is automatically initialised.

VariableTypeDescriptionDefault
__NUXT_SPEEDKIT_FONT_INIT__BooleanIf set, only the fonts are initialised.false
__NUXT_SPEEDKIT_AUTO_INIT__BooleanIf set, initialisation continues after the javascript has been fully loaded.false

Example

html
<button
+  id="nuxt-speedkit__button__init-font"
+  onclick="window.__NUXT_SPEEDKIT_FONT_INIT__ = true;"
+  >…</button>
+
+<button
+  id="nuxt-speedkit__button__init-app"
+  onclick="window.__NUXT_SPEEDKIT_AUTO_INIT__ = true;"
+  >…</button>
<button
+  id="nuxt-speedkit__button__init-font"
+  onclick="window.__NUXT_SPEEDKIT_FONT_INIT__ = true;"
+  >…</button>
+
+<button
+  id="nuxt-speedkit__button__init-app"
+  onclick="window.__NUXT_SPEEDKIT_AUTO_INIT__ = true;"
+  >…</button>

Hide Layer

html
<label for="nuxt-speedkit__speedkit-layer__close">
+  Close Layer
+</label>
<label for="nuxt-speedkit__speedkit-layer__close">
+  Close Layer
+</label>

The layer can be closed via a for attribute with the id nuxt-speedkit__speedkit-layer__close.

  • Closing mechanics does not require javascript.

Template

html
<speedkit-layer>
+  <div>
+
+    <p>Sorry, but you will have a limited user experience due to a…</p>
+
+    <ul style="padding: 0; list-style: none;">
+      <!-- Displayed when javascript is disabled. -->
+      <li id="nuxt-speedkit__message__nojs">
+        disabled javascript
+      </li>
+      <!-- Displayed when browser does not support. -->
+      <li id="nuxt-speedkit__message__unsupported-browser">
+        outdated browser
+      </li>
+      <!-- Displayed when user hardware is not sufficient. -->
+      <li id="nuxt-speedkit__message__outdated-device">
+        outdated device
+      </li>
+      <!-- Displayed when connection is too slow. -->
+      <li id="nuxt-speedkit__message__slow-connection">
+        slow connection
+      </li>
+    </ul>
+
+    <!-- Button to hide the layer with no javascript -->
+    <button id="nuxt-speedkit__button__init-nojs">
+      <label for="nuxt-speedkit__speedkit-layer__close">
+        Apply without js
+      </label>
+    </button>
+
+    <!-- Button for use without javascript and with fonts -->
+    <button id="nuxt-speedkit__button__init-font" onclick="window.__NUXT_SPEEDKIT_FONT_INIT__ = true;">
+      <label for="nuxt-speedkit__speedkit-layer__close">
+        Apply with Fonts
+      </label>
+    </button>
+
+    <!-- Button for activate javascript by bad connection or browser support -->
+    <button id="nuxt-speedkit__button__init-app" onclick="window.__NUXT_SPEEDKIT_AUTO_INIT__ = true;">
+      Apply with all Features
+    </button>
+
+  </div>
+</speedkit-layer>
<speedkit-layer>
+  <div>
+
+    <p>Sorry, but you will have a limited user experience due to a…</p>
+
+    <ul style="padding: 0; list-style: none;">
+      <!-- Displayed when javascript is disabled. -->
+      <li id="nuxt-speedkit__message__nojs">
+        disabled javascript
+      </li>
+      <!-- Displayed when browser does not support. -->
+      <li id="nuxt-speedkit__message__unsupported-browser">
+        outdated browser
+      </li>
+      <!-- Displayed when user hardware is not sufficient. -->
+      <li id="nuxt-speedkit__message__outdated-device">
+        outdated device
+      </li>
+      <!-- Displayed when connection is too slow. -->
+      <li id="nuxt-speedkit__message__slow-connection">
+        slow connection
+      </li>
+    </ul>
+
+    <!-- Button to hide the layer with no javascript -->
+    <button id="nuxt-speedkit__button__init-nojs">
+      <label for="nuxt-speedkit__speedkit-layer__close">
+        Apply without js
+      </label>
+    </button>
+
+    <!-- Button for use without javascript and with fonts -->
+    <button id="nuxt-speedkit__button__init-font" onclick="window.__NUXT_SPEEDKIT_FONT_INIT__ = true;">
+      <label for="nuxt-speedkit__speedkit-layer__close">
+        Apply with Fonts
+      </label>
+    </button>
+
+    <!-- Button for activate javascript by bad connection or browser support -->
+    <button id="nuxt-speedkit__button__init-app" onclick="window.__NUXT_SPEEDKIT_AUTO_INIT__ = true;">
+      Apply with all Features
+    </button>
+
+  </div>
+</speedkit-layer>
`,14);function I(o,N,S,P,j,U){const n=r("nobr");return c(),i("div",null,[s("h1",d,[e(E(o.$frontmatter.title)+" ",1),u]),h,s("table",null,[_,s("tbody",null,[s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[g]),_:1})]),b]),s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[F]),_:1})]),m]),s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[f]),_:1})]),v])])]),w,s("table",null,[k,s("tbody",null,[s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[q]),_:1})]),D]),s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[T]),_:1})]),C]),s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[A]),_:1})]),B])])]),x])}const X=p(y,[["render",I]]);export{O as __pageData,X as default}; diff --git a/docs/assets/v1_components_speedkit-layer.md.lVxe18Ze.lean.js b/docs/assets/v1_components_speedkit-layer.md.lVxe18Ze.lean.js new file mode 100644 index 0000000000..1448bcacd8 --- /dev/null +++ b/docs/assets/v1_components_speedkit-layer.md.lVxe18Ze.lean.js @@ -0,0 +1 @@ +import{_ as p,D as r,o as c,c as i,k as s,a as e,t as E,I as a,w as l,R as t}from"./chunks/framework._u06EGUx.js";const O=JSON.parse('{"title":"SpeedkitLayer","description":"","frontmatter":{"title":"SpeedkitLayer"},"headers":[],"relativePath":"v1/components/speedkit-layer.md","filePath":"v1/components/speedkit-layer.md"}'),y={name:"v1/components/speedkit-layer.md"},d={id:"frontmatter-title",tabindex:"-1"},u=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),h=t("",21),_=s("thead",null,[s("tr",null,[s("th",null,"ID"),s("th",null,"Description")])],-1),g=s("code",null,"nuxt-speedkit__message__unsupported-browser",-1),b=s("td",null,[e("User Browser is not supported by "),s("a",{href:"/v1/guide/options.html#browsersupport"},[s("code",null,"Browserslist")]),e(".")],-1),F=s("code",null,"nuxt-speedkit__message__outdated-device",-1),m=s("td",null,"User hardware (number of processor & RAM) are not sufficient.",-1),f=s("code",null,"nuxt-speedkit__message__slow-connection",-1),v=s("td",null,"Connection speed is too low.",-1),w=t("",5),k=s("thead",null,[s("tr",null,[s("th",null,"ID"),s("th",null,"Description")])],-1),q=s("code",null,"nuxt-speedkit__button__init-nojs",-1),D=s("td",null,[e("Visible when javascript is disabled, needed so that the user can hide the layer. Requires the "),s("a",{href:"/v1/components/speedkit-layer.html#hide-layer"},"Hide Layer"),e(" implementation.")],-1),T=s("code",null,"nuxt-speedkit__button__init-font",-1),C=s("td",null,"Is used to offer the user the possibility to visit the page only with activated fonts. Other initialisations of the Javascript and loading of the pictures are prevented.",-1),A=s("code",null,"nuxt-speedkit__button__init-app",-1),B=s("td",null,"Activates all features. The initialisation of the JavaScript is started, images are loaded.",-1),x=t("",14);function I(o,N,S,P,j,U){const n=r("nobr");return c(),i("div",null,[s("h1",d,[e(E(o.$frontmatter.title)+" ",1),u]),h,s("table",null,[_,s("tbody",null,[s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[g]),_:1})]),b]),s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[F]),_:1})]),m]),s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[f]),_:1})]),v])])]),w,s("table",null,[k,s("tbody",null,[s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[q]),_:1})]),D]),s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[T]),_:1})]),C]),s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[A]),_:1})]),B])])]),x])}const X=p(y,[["render",I]]);export{O as __pageData,X as default}; diff --git a/docs/assets/v1_components_speedkit-picture.md.8iUNQNQI.js b/docs/assets/v1_components_speedkit-picture.md.8iUNQNQI.js new file mode 100644 index 0000000000..2685b006cc --- /dev/null +++ b/docs/assets/v1_components_speedkit-picture.md.8iUNQNQI.js @@ -0,0 +1,229 @@ +import{_ as n,o as p,c as l,k as s,a as e,t as o,R as t}from"./chunks/framework._u06EGUx.js";const b=JSON.parse('{"title":"SpeedkitPicture","description":"","frontmatter":{"title":"SpeedkitPicture"},"headers":[],"relativePath":"v1/components/speedkit-picture.md","filePath":"v1/components/speedkit-picture.md"}'),c={name:"v1/components/speedkit-picture.md"},r={id:"frontmatter-title",tabindex:"-1"},E=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),i=t(`

Since the SpeedkitPicture (Experimental) is still marked as experimental, we still offer the simplified version called SpeedkitPicture. Here, all resources that are generated fully automatically in the experimental module must be defined manually.

Except for the manual resource definition, all other features of SpeedkitPicture (Experimental) are identical.

Usage

Without using @nuxt/image, all sources and placeholders must be specified.

Examples for defining the resources can be found in the example of the module.

Think about the optimisation of the images!

If possible, use the format webp, analog to the existing jpg files and make sure that the images are optimised.

Example

vue
<template>
+  <div>
+    <speedkit-picture v-bind="image" />
+  </div>
+</template>
+
+<script>
+import SpeedkitPicture from '#speedkit/components/SpeedkitPicture';
+export default {
+  components: { SpeedkitPicture },
+  data () {
+    return {
+      image: {
+        placeholders: [
+          {
+            media: '(min-width: 768px)',
+            format: 'jpg',
+            url: 'data:image/jpeg;base64,…' // landscape
+          },
+          {
+            format: 'jpg',
+            url: 'data:image/jpeg;base64,…' // portrait
+          }
+        ],
+        sources: [
+          {
+            media: '(min-width: 768px)',
+            format: 'webp',
+            sizes: [
+              { width: 768, media: '(min-width: 768px)', url: '768.webp' },
+              { width: 1024, media: '(min-width: 1024px)', url: '1024.webp' }
+            ]
+          },
+          {
+            media: '(min-width: 768px)',
+            format: 'jpg',
+            sizes: [
+              { width: 768, media: '(min-width: 768px)', url: '768.jpg' },
+              { width: 1024, media: '(min-width: 1024px)', url: '1024.jpg' }
+            ]
+          },
+          {
+            format: 'webp',
+            sizes: [
+              { width: 414, media: 'all', url: '414.webp' }
+            ]
+          },
+          {
+            format: 'jpg',
+            sizes: [
+              { width: 414, media: 'all', url: '414.jpg' }
+            ]
+          }
+        ],
+        alt: 'Image Alt',
+        title: 'Image Title',
+        crossorigin: 'anonymous'
+      }
+    };
+  }
+};
+</script>
<template>
+  <div>
+    <speedkit-picture v-bind="image" />
+  </div>
+</template>
+
+<script>
+import SpeedkitPicture from '#speedkit/components/SpeedkitPicture';
+export default {
+  components: { SpeedkitPicture },
+  data () {
+    return {
+      image: {
+        placeholders: [
+          {
+            media: '(min-width: 768px)',
+            format: 'jpg',
+            url: 'data:image/jpeg;base64,…' // landscape
+          },
+          {
+            format: 'jpg',
+            url: 'data:image/jpeg;base64,…' // portrait
+          }
+        ],
+        sources: [
+          {
+            media: '(min-width: 768px)',
+            format: 'webp',
+            sizes: [
+              { width: 768, media: '(min-width: 768px)', url: '768.webp' },
+              { width: 1024, media: '(min-width: 1024px)', url: '1024.webp' }
+            ]
+          },
+          {
+            media: '(min-width: 768px)',
+            format: 'jpg',
+            sizes: [
+              { width: 768, media: '(min-width: 768px)', url: '768.jpg' },
+              { width: 1024, media: '(min-width: 1024px)', url: '1024.jpg' }
+            ]
+          },
+          {
+            format: 'webp',
+            sizes: [
+              { width: 414, media: 'all', url: '414.webp' }
+            ]
+          },
+          {
+            format: 'jpg',
+            sizes: [
+              { width: 414, media: 'all', url: '414.jpg' }
+            ]
+          }
+        ],
+        alt: 'Image Alt',
+        title: 'Image Title',
+        crossorigin: 'anonymous'
+      }
+    };
+  }
+};
+</script>

Properties

All properties except sources and placeholders are identical to the SpeedkitPicture (Experimental).

Learn more about ExperimentalSpeedkitPicture - Properties.

sources

  • Type: Array

Contains resources that are to be displayed depending on the viewport.

js
[
+  {
+    media: '(min-width: 768px)',
+    format: 'webp',
+    sizes: [
+      { width: 768, media: '(min-width: 768px)', url: '768.webp' },
+      { width: 1024, media: '(min-width: 1024px)', url: '1024.webp' }
+    ]
+  },
+  {
+    media: '(min-width: 768px)',
+    format: 'jpg',
+    sizes: [
+      { width: 768, media: '(min-width: 768px)', url: '768.jpg' },
+      { width: 1024, media: '(min-width: 1024px)', url: '1024.jpg' }
+    ]
+  },
+  {
+    format: 'webp',
+    sizes: [
+      { width: 414, media: 'all', url: '414.webp' }
+    ]
+  },
+  {
+    format: 'jpg',
+    sizes: [
+      { width: 414, media: 'all', url: '414.jpg' }
+    ]
+  }
+]
[
+  {
+    media: '(min-width: 768px)',
+    format: 'webp',
+    sizes: [
+      { width: 768, media: '(min-width: 768px)', url: '768.webp' },
+      { width: 1024, media: '(min-width: 1024px)', url: '1024.webp' }
+    ]
+  },
+  {
+    media: '(min-width: 768px)',
+    format: 'jpg',
+    sizes: [
+      { width: 768, media: '(min-width: 768px)', url: '768.jpg' },
+      { width: 1024, media: '(min-width: 1024px)', url: '1024.jpg' }
+    ]
+  },
+  {
+    format: 'webp',
+    sizes: [
+      { width: 414, media: 'all', url: '414.webp' }
+    ]
+  },
+  {
+    format: 'jpg',
+    sizes: [
+      { width: 414, media: 'all', url: '414.jpg' }
+    ]
+  }
+]

Each source in the list describes a file format with its viewport dependent image sizes.

Property sizes is used to define the viewport dependent image sizes and media is used to display different aspect ratios depending on the viewport.

KeyTypeRequiredValueDefault
sizesArrayyesDescribes the different image sizes.[]
formatStringyesImage format of the specified resource, e.g. webp, jpg, …
mediaStringCSS Media Query e.g. (min-width: 768px)

TIP

media can be used for breakpoint specific aspect ratios.

js
{
+  media: '(min-width: 768px)',
+  format: 'webp',
+  sizes: [
+    { width: 768, media: '(min-width: 768px)', url: '768.webp' },
+    { width: 1024, media: '(min-width: 1024px)', url: '1024.webp' }
+  ]
+}
{
+  media: '(min-width: 768px)',
+  format: 'webp',
+  sizes: [
+    { width: 768, media: '(min-width: 768px)', url: '768.webp' },
+    { width: 1024, media: '(min-width: 1024px)', url: '1024.webp' }
+  ]
+}
js
{ 
+  width: 768, 
+  media: '(min-width: 768px)', 
+  url: '768.webp' 
+}
{ 
+  width: 768, 
+  media: '(min-width: 768px)', 
+  url: '768.webp' 
+}

The size object in sizes describes the different image sizes for the respective breakpoints.

From the list sizes, the srcset & sizes is generated.

More about HTMLImageElement.srcset & HTMLImageElement.sizes

url and width are applied in srcset (e.g. srcset="image.jpg 768w").
media is applied in sizes for media query to width (e.g. sizes="(min-width: 768px) 768px").

KeyRequiredValueDefault
urlyesAbsolute Path to the ressource.
widthViewport width as Number (e.g. 768)undefined
mediaCSS Media Query (e.g. (min-width: 768px))undefined

Important

The url specification is absolute. If an alias is used (e.g. @/assets/img/image.jpg), the path must be resolved by require (e.g. url: require('@/assets/img/image.jpg')).

Example

js
[
+  { width: 768, media: '(min-width: 768px)', url: '768.webp' },
+  { width: 1024, media: '(min-width: 1024px)', url: '1024.webp' }
+]
[
+  { width: 768, media: '(min-width: 768px)', url: '768.webp' },
+  { width: 1024, media: '(min-width: 1024px)', url: '1024.webp' }
+]

placeholders

  • Type: Array

Describes the placeholders that are displayed as long as no resources have been loaded.

It is possible to define different image ratios for the placeholders via the media property.

WARNING

Make sure that the placeholders have a width of 30px and are optimized.

KeyTypeRequiredValue
urlStringyesUrl or Base64 of an image.
formatStringyesImage format of the specified resource. e.g. webp, jpg, …
mediaStringCSS Media Query e.g. (min-width: 768px)
js
[
+  {
+    media: '(min-width: 768px)',
+    format: 'jpg',
+    url: 'landscape.jpg' // base64 or url
+  },
+  {
+    format: 'jpg',
+    url: 'portrait.jpg' // base64 or url
+  }
+]
[
+  {
+    media: '(min-width: 768px)',
+    format: 'jpg',
+    url: 'landscape.jpg' // base64 or url
+  },
+  {
+    format: 'jpg',
+    url: 'portrait.jpg' // base64 or url
+  }
+]

Events

More on events at SpeedkitPicture (Experimental) - Events.

`,38);function y(a,d,h,m,F,u){return p(),l("div",null,[s("h1",r,[e(o(a.$frontmatter.title)+" ",1),E]),i])}const w=n(c,[["render",y]]);export{b as __pageData,w as default}; diff --git a/docs/assets/v1_components_speedkit-picture.md.8iUNQNQI.lean.js b/docs/assets/v1_components_speedkit-picture.md.8iUNQNQI.lean.js new file mode 100644 index 0000000000..ee85e56bd2 --- /dev/null +++ b/docs/assets/v1_components_speedkit-picture.md.8iUNQNQI.lean.js @@ -0,0 +1 @@ +import{_ as n,o as p,c as l,k as s,a as e,t as o,R as t}from"./chunks/framework._u06EGUx.js";const b=JSON.parse('{"title":"SpeedkitPicture","description":"","frontmatter":{"title":"SpeedkitPicture"},"headers":[],"relativePath":"v1/components/speedkit-picture.md","filePath":"v1/components/speedkit-picture.md"}'),c={name:"v1/components/speedkit-picture.md"},r={id:"frontmatter-title",tabindex:"-1"},E=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),i=t("",38);function y(a,d,h,m,F,u){return p(),l("div",null,[s("h1",r,[e(o(a.$frontmatter.title)+" ",1),E]),i])}const w=n(c,[["render",y]]);export{b as __pageData,w as default}; diff --git a/docs/assets/v1_components_speedkit-youtube.md.42HHjCjS.js b/docs/assets/v1_components_speedkit-youtube.md.42HHjCjS.js new file mode 100644 index 0000000000..93f3cdbf20 --- /dev/null +++ b/docs/assets/v1_components_speedkit-youtube.md.42HHjCjS.js @@ -0,0 +1,125 @@ +import{_ as l,D as o,o as e,c as t,k as s,a as n,t as c,I as r,w as E,R as y}from"./chunks/framework._u06EGUx.js";const S=JSON.parse('{"title":"SpeedkitYoutube","description":"","frontmatter":{"title":"SpeedkitYoutube"},"headers":[],"relativePath":"v1/components/speedkit-youtube.md","filePath":"v1/components/speedkit-youtube.md"}'),i={name:"v1/components/speedkit-youtube.md"},d={id:"frontmatter-title",tabindex:"-1"},u=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),F=y(`

Please note the privacy policy when using. Google Youtube-API is integrated via dependency youtube-player.

Since the SpeedkitYoutube (Experimental) is still marked as experimental, we still offer the simplified version called SpeedkitYoutube. Here, all resources that are generated fully automatically in the experimental module must be defined manually.

Except for the manual resource definition for the poster, all other features of SpeedkitYoutube (Experimental) are identical.

Usage

The SpeedkitYoutube differs from the SpeedkitYoutube (Experimental) in the definition of the poster.
A SpeedkitPicture must be additionally defined in the poster property.

Learn more about SpeedkitPicture

Example

vue
<template>
+  <div>
+    <speedkit-youtube v-bind="youtube" @playing="onPlaying" />
+  </div>
+</template>
+
+<script>
+import SpeedkitYoutube from '#speedkit/components/SpeedkitYoutube';
+export default {
+  components: { SpeedkitYoutube },
+  data () {
+    return {
+      youtube: {
+        id: 'youtube-id',
+        host: 'https://www.youtube-nocookie.com',
+        config: { … },
+        poster: {
+          placeholders: [
+            {
+              format: 'jpg',
+              url: 'data:image/jpeg;base64,…'
+            }
+          ],
+          sources: [
+            {
+              format: 'jpg',
+              sizes: [
+                { width: 414, url: 'poster-414.jpg' },
+                { width: 576, media: '(min-width: 576px)', url: 'poster-576.jpg' },
+                { width: 768, media: '(min-width: 768px)', url: 'poster-768.jpg' },
+                { width: 1024, media: '(min-width: 1024px)', url: 'poster-1024.jpg' },
+                { width: 1280, media: '(min-width: 1200px)', url: 'poster-1280.jpg' }
+              ]
+            },
+            {
+              format: 'webp',
+              sizes: [
+                { width: 414, url: 'poster-414.webp') },
+                { width: 576, media: '(min-width: 576px)', url: 'poster-576.webp' },
+                { width: 768, media: '(min-width: 768px)', url: 'poster-768.webp' },
+                { width: 1024, media: '(min-width: 1024px)', url: 'poster-1024.webp' },
+                { width: 1280, media: '(min-width: 1200px)', url: 'poster-1280.webp' }
+              ]
+            }
+          ],
+          alt: 'Youtube Alt',
+          title: 'Youtube Title'
+        }
+      }
+    };
+  },
+  methods: {
+    onPlaying () {
+      console.log('Youtube Player playing!');
+    }
+  }
+};
+</script>
<template>
+  <div>
+    <speedkit-youtube v-bind="youtube" @playing="onPlaying" />
+  </div>
+</template>
+
+<script>
+import SpeedkitYoutube from '#speedkit/components/SpeedkitYoutube';
+export default {
+  components: { SpeedkitYoutube },
+  data () {
+    return {
+      youtube: {
+        id: 'youtube-id',
+        host: 'https://www.youtube-nocookie.com',
+        config: { … },
+        poster: {
+          placeholders: [
+            {
+              format: 'jpg',
+              url: 'data:image/jpeg;base64,…'
+            }
+          ],
+          sources: [
+            {
+              format: 'jpg',
+              sizes: [
+                { width: 414, url: 'poster-414.jpg' },
+                { width: 576, media: '(min-width: 576px)', url: 'poster-576.jpg' },
+                { width: 768, media: '(min-width: 768px)', url: 'poster-768.jpg' },
+                { width: 1024, media: '(min-width: 1024px)', url: 'poster-1024.jpg' },
+                { width: 1280, media: '(min-width: 1200px)', url: 'poster-1280.jpg' }
+              ]
+            },
+            {
+              format: 'webp',
+              sizes: [
+                { width: 414, url: 'poster-414.webp') },
+                { width: 576, media: '(min-width: 576px)', url: 'poster-576.webp' },
+                { width: 768, media: '(min-width: 768px)', url: 'poster-768.webp' },
+                { width: 1024, media: '(min-width: 1024px)', url: 'poster-1024.webp' },
+                { width: 1280, media: '(min-width: 1200px)', url: 'poster-1280.webp' }
+              ]
+            }
+          ],
+          alt: 'Youtube Alt',
+          title: 'Youtube Title'
+        }
+      }
+    };
+  },
+  methods: {
+    onPlaying () {
+      console.log('Youtube Player playing!');
+    }
+  }
+};
+</script>

Properties

js
{
+  id: 'youtube-id',
+  autoplay: false,
+  host: 'https://www.youtube-nocookie.com',
+  config: { … }
+}
{
+  id: 'youtube-id',
+  autoplay: false,
+  host: 'https://www.youtube-nocookie.com',
+  config: { … }
+}

All properties except poster are identical to SpeedkitYoutube.

Learn more about SpeedkitYoutube (Experimental) - Properties.

poster

`,13),h=s("code",null,"Object",-1),m=s("a",{href:"/v1/components/speedkit-picture.html"},"SpeedkitPicture",-1),b=s("p",null,"Poster is displayed as long as the player is not playing.",-1),g=s("h2",{id:"events",tabindex:"-1"},[n("Events "),s("a",{class:"header-anchor",href:"#events","aria-label":'Permalink to "Events"'},"​")],-1),C=s("p",null,[n("More on events at "),s("a",{href:"/v1/components/experimental/speedkit-youtube.html#events"},[s("code",null,"SpeedkitYoutube"),n(" (Experimental) - Events")]),n(".")],-1);function w(a,f,k,B,v,x){const p=o("badge");return e(),t("div",null,[s("h1",d,[n(c(a.$frontmatter.title)+" ",1),u]),F,s("ul",null,[s("li",null,[n("Type: "),h,n(" The "),m,n(" is used as the configuration. "),s("ul",null,[s("li",null,[r(p,null,{default:E(()=>[n("required")]),_:1})])])])]),b,g,C])}const P=l(i,[["render",w]]);export{S as __pageData,P as default}; diff --git a/docs/assets/v1_components_speedkit-youtube.md.42HHjCjS.lean.js b/docs/assets/v1_components_speedkit-youtube.md.42HHjCjS.lean.js new file mode 100644 index 0000000000..845f5a58a6 --- /dev/null +++ b/docs/assets/v1_components_speedkit-youtube.md.42HHjCjS.lean.js @@ -0,0 +1 @@ +import{_ as l,D as o,o as e,c as t,k as s,a as n,t as c,I as r,w as E,R as y}from"./chunks/framework._u06EGUx.js";const S=JSON.parse('{"title":"SpeedkitYoutube","description":"","frontmatter":{"title":"SpeedkitYoutube"},"headers":[],"relativePath":"v1/components/speedkit-youtube.md","filePath":"v1/components/speedkit-youtube.md"}'),i={name:"v1/components/speedkit-youtube.md"},d={id:"frontmatter-title",tabindex:"-1"},u=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),F=y("",13),h=s("code",null,"Object",-1),m=s("a",{href:"/v1/components/speedkit-picture.html"},"SpeedkitPicture",-1),b=s("p",null,"Poster is displayed as long as the player is not playing.",-1),g=s("h2",{id:"events",tabindex:"-1"},[n("Events "),s("a",{class:"header-anchor",href:"#events","aria-label":'Permalink to "Events"'},"​")],-1),C=s("p",null,[n("More on events at "),s("a",{href:"/v1/components/experimental/speedkit-youtube.html#events"},[s("code",null,"SpeedkitYoutube"),n(" (Experimental) - Events")]),n(".")],-1);function w(a,f,k,B,v,x){const p=o("badge");return e(),t("div",null,[s("h1",d,[n(c(a.$frontmatter.title)+" ",1),u]),F,s("ul",null,[s("li",null,[n("Type: "),h,n(" The "),m,n(" is used as the configuration. "),s("ul",null,[s("li",null,[r(p,null,{default:E(()=>[n("required")]),_:1})])])])]),b,g,C])}const P=l(i,[["render",w]]);export{S as __pageData,P as default}; diff --git a/docs/assets/v1_concept.md.Z6zMo_qc.js b/docs/assets/v1_concept.md.Z6zMo_qc.js new file mode 100644 index 0000000000..6a9d863613 --- /dev/null +++ b/docs/assets/v1_concept.md.Z6zMo_qc.js @@ -0,0 +1 @@ +import{_ as o,o as i,c as a,k as e,a as s,t as n,R as r}from"./chunks/framework._u06EGUx.js";const b=JSON.parse('{"title":"Concept","description":"","frontmatter":{"title":"Concept"},"headers":[],"relativePath":"v1/concept.md","filePath":"v1/concept.md"}'),c={name:"v1/concept.md"},d={id:"frontmatter-title",tabindex:"-1"},l=e("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),h=r('

nuxt-speedkit is used to increase the initial loading performance of the website.
For this purpose, various tools are provided that optimise the loading and initialisation of resources (images, fonts) and components automatically and on demand.

This has the following impact:

1. Reduced initial download of the web page ::list

  • only the critical viewport resources will be loaded
  • all other resources will be loaded on demand, e.g. when scrolling. ::

2. Reduction of timing metrics

  • FCP
  • TTI
  • TBT

The module recognises the critical resources (images, fonts, javascript) for the initial load and preloads them when the page is called up directly. However, if an impairment of the UX is detected during the initialisation phase due to the following factors:

  • no Javascript enabled
  • reduced bandwidth
  • weak hardware
  • unsupported browser

the further initialisation process is paused and the user is given the decision whether to load the website completely (incl. Javascript) or to have only the static content (HTML, CSS, images and fonts) displayed. Through this loading behaviour, a correspondingly high performance score can be achieved even with a low bandwidth, as specified by the lighthouse test, for example. For the user, on the other hand, it becomes transparent why there may be delays in the display of complex components or static resources in the further course of the website visit.

For this reason, this module can only be used with NuxtJS, as this requires static HTML in order to continue to display the full content to the user despite uninitialised Javascript.

',10);function p(t,u,m,f,_,g){return i(),a("div",null,[e("h1",d,[s(n(t.$frontmatter.title)+" ",1),l]),h])}const v=o(c,[["render",p]]);export{b as __pageData,v as default}; diff --git a/docs/assets/v1_concept.md.Z6zMo_qc.lean.js b/docs/assets/v1_concept.md.Z6zMo_qc.lean.js new file mode 100644 index 0000000000..c334203078 --- /dev/null +++ b/docs/assets/v1_concept.md.Z6zMo_qc.lean.js @@ -0,0 +1 @@ +import{_ as o,o as i,c as a,k as e,a as s,t as n,R as r}from"./chunks/framework._u06EGUx.js";const b=JSON.parse('{"title":"Concept","description":"","frontmatter":{"title":"Concept"},"headers":[],"relativePath":"v1/concept.md","filePath":"v1/concept.md"}'),c={name:"v1/concept.md"},d={id:"frontmatter-title",tabindex:"-1"},l=e("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),h=r("",10);function p(t,u,m,f,_,g){return i(),a("div",null,[e("h1",d,[s(n(t.$frontmatter.title)+" ",1),l]),h])}const v=o(c,[["render",p]]);export{b as __pageData,v as default}; diff --git a/docs/assets/v1_directives_v-font.md.kNiUmR7R.js b/docs/assets/v1_directives_v-font.md.kNiUmR7R.js new file mode 100644 index 0000000000..43e982d41f --- /dev/null +++ b/docs/assets/v1_directives_v-font.md.kNiUmR7R.js @@ -0,0 +1,79 @@ +import{_ as n,o as e,c as o,k as s,a as t,t as l,R as p}from"./chunks/framework._u06EGUx.js";const v=JSON.parse('{"title":"v-font","description":"","frontmatter":{"title":"v-font"},"headers":[],"relativePath":"v1/directives/v-font.md","filePath":"v1/directives/v-font.md"}'),c={name:"v1/directives/v-font.md"},r={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),d=p(`

Fonts are the great mystery on the Internet. For more complex designs it is not uncommon that more than 6 font files have to be loaded. It would be desirable if there were many more variable fonts, but the reality is usually different. Often, developers are forced to register tens of fonts with different font styles. So it can happen that the website needs a total of 12 font files, which have to be loaded initially to achieve the right visual result on the whole page.

This is really a performance problem. If you look for solutions, you like to hear

  • don't use WebFonts that have to be loaded
  • use another optimized font
  • reduce the number of used fonts
  • embed the fonts via Base64

In the internet you can find some articles about font loading. But most of them are more than 3 years old. So not much new has happened on that front. A nice and recommendable list of different strategies can be found at web-font-loading-recipes or comprehensive-webfonts. From this it can be deduced that there is still no universal solution to the problem. However, it is possible to approach the issue very efficiently by using a preload strategy and setting classes accordingly. However, this does not make the handling of the fonts any easier. On the one hand, the preloads have to be defined per page and on the other hand, the CSS in the respective component has to be activated with the corresponding font declaration per class on demand. This is manageable for smaller projects in a 1 person team. But if several people are working in parallel, it can quickly become a horror trip. This will inevitably lead to the fact that the approach will not be accepted by the team in the long run and the optimization will be optimized out of the project in the long run.

INFO

A few words about Google Fonts: If possible, the FontFaces should always be included directly as Woff/Woff2 files via inline style. The loading mechanism via external CSS file, as it is the case with Google Fonts, creates an additional network roundtrip, which delays the loading of the actual font files.

Solution

The strategy mentioned above makes sense, but is hardly implementable with the current tools. For this reason, we are introducing Directive v-font, which takes care of the outlined behavior in a fully automated way and thus represents a truly relevant solution even on larger projects.

Usage

The directive v-font is used to integrate the fonts defined in the module options into the website.

To do this, the respective font must be retrieved via the $getFont method contained in the component scope (e.g. this).

Fonts are specified by family, weight and style and can be limited to elements and viewports via the options (media, selector).

Normally the directive activates the fonts only when the viewport is reached. It is recommended to use the property critical for components that are already initially contained in the viewport.

With critical component the fonts are preloaded and are initially active. More information on critical components can be found here.

For multiple fonts, a list (Array) can be passed.

html

+<!-- single definition -->
+<node v-font="$getFont(…)">
+
+<!-- multiple definitions -->
+<node v-font="[
+  $getFont(…),
+  $getFont(…)
+]">

+<!-- single definition -->
+<node v-font="$getFont(…)">
+
+<!-- multiple definitions -->
+<node v-font="[
+  $getFont(…),
+  $getFont(…)
+]">

DANGER

Currently the use of v-font on components or in combination with v-html/v-text directives is not possible. Caused is a bug in the Vue SSR, directive is not applied.

Read more in the Issue: vue-server-renderer: directive not applied to imported component.

As long as this error exists, you can look here for workarounds.

$getFont

$getFont is included as a plugin and can be accessed via any component scope.

Is used in the v-font directive and creates the relevant font definition.

KeyTypeRequriedDescriptionDefault
familyStringyesFont-Family e.g. Custom Font
weightString, NumberFont-Style e.g. normal, italic400
styleStringFont-Weight e.g. 400, normalnormal
optionsObjectMedia & Selector Options see more

options

Each definition can be modified in its behaviour via the options. With the property media, the call of the font definition can be made dependent on the viewport.
The property selector can be used to limit the font to elements (e.g. span, .class).

KeyTypeRequriedDescriptionDefault
mediaStringCSS Media Query e.g. (min-width: 768px)
selectorStringCSS Selector e.g. element, .elm, .elm:before

DANGER

link Tag is not supported orientation media query. e.g. (orientation: portrait). This has an effect on prefetches and preloads.

js
{
+  media: '(min-width: 768px)',
+  selector: 'element, .elm, .elm:before'
+}
{
+  media: '(min-width: 768px)',
+  selector: 'element, .elm, .elm:before'
+}

Examples

Basic Usage

html
<h1 v-font="$getFont('Font Family', 700)">Headline</h1>
<h1 v-font="$getFont('Font Family', 700)">Headline</h1>

Advanced Usage

js
[
+  
+  // Font wird auf alles angewendet
+  $getFont('Font Family A'),
+
+  // Font wird auf \`b\` und \`strong\` Tags angwendet
+  $getFont('Font Family B', 700, 'normal', { selector: 'b, strong' }),
+
+  // Font erscheint erst ab Viewport \`>768px\`
+  $getFont('Font Family B', 400, 'normal', { media: '(min-width: 768px)' }),
+
+  // Font wird auf \`b\` und \`strong\` Tags angwendet und erscheint erst ab Viewport \`>768px\`
+  $getFont('Font Family B', 700, 'normal', { selector: 'b, strong', media: '(min-width: 768px)' })
+
+]
[
+  
+  // Font wird auf alles angewendet
+  $getFont('Font Family A'),
+
+  // Font wird auf \`b\` und \`strong\` Tags angwendet
+  $getFont('Font Family B', 700, 'normal', { selector: 'b, strong' }),
+
+  // Font erscheint erst ab Viewport \`>768px\`
+  $getFont('Font Family B', 400, 'normal', { media: '(min-width: 768px)' }),
+
+  // Font wird auf \`b\` und \`strong\` Tags angwendet und erscheint erst ab Viewport \`>768px\`
+  $getFont('Font Family B', 700, 'normal', { selector: 'b, strong', media: '(min-width: 768px)' })
+
+]

Workarounds

Workarounds are used to work around a bug in the Vue SSR, read more in Usage.

Use component

html
<template>
+  <nuxt-link to="/" v-font="$getFont(…)">Back</nuxt-link>
+</template>
<template>
+  <nuxt-link to="/" v-font="$getFont(…)">Back</nuxt-link>
+</template>
html
<template>
+  <nuxt-link to="/">
+    <span v-font="$getFont(…)">Back</span>
+  </nuxt-link>
+</template>
<template>
+  <nuxt-link to="/">
+    <span v-font="$getFont(…)">Back</span>
+  </nuxt-link>
+</template>

Use v-html/v-text

html
<template>
+  <div>
+    <div v-font="$getFont(…)" v-html="…" />
+  </div>
+</template>
<template>
+  <div>
+    <div v-font="$getFont(…)" v-html="…" />
+  </div>
+</template>
html
<template>
+  <div v-font="$getFont(…)">
+    <div v-html="…" />
+  </div>
+</template>
<template>
+  <div v-font="$getFont(…)">
+    <div v-html="…" />
+  </div>
+</template>
`,36);function E(a,y,h,u,g,m){return e(),o("div",null,[s("h1",r,[t(l(a.$frontmatter.title)+" ",1),i]),d])}const f=n(c,[["render",E]]);export{v as __pageData,f as default}; diff --git a/docs/assets/v1_directives_v-font.md.kNiUmR7R.lean.js b/docs/assets/v1_directives_v-font.md.kNiUmR7R.lean.js new file mode 100644 index 0000000000..4d8dcf532e --- /dev/null +++ b/docs/assets/v1_directives_v-font.md.kNiUmR7R.lean.js @@ -0,0 +1 @@ +import{_ as n,o as e,c as o,k as s,a as t,t as l,R as p}from"./chunks/framework._u06EGUx.js";const v=JSON.parse('{"title":"v-font","description":"","frontmatter":{"title":"v-font"},"headers":[],"relativePath":"v1/directives/v-font.md","filePath":"v1/directives/v-font.md"}'),c={name:"v1/directives/v-font.md"},r={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),d=p("",36);function E(a,y,h,u,g,m){return e(),o("div",null,[s("h1",r,[t(l(a.$frontmatter.title)+" ",1),i]),d])}const f=n(c,[["render",E]]);export{v as __pageData,f as default}; diff --git a/docs/assets/v1_guide_options.md.gNDuU5V1.js b/docs/assets/v1_guide_options.md.gNDuU5V1.js new file mode 100644 index 0000000000..099458fcb2 --- /dev/null +++ b/docs/assets/v1_guide_options.md.gNDuU5V1.js @@ -0,0 +1,105 @@ +import{_ as a,o as n,c as o,k as s,a as t,t as l,R as p}from"./chunks/framework._u06EGUx.js";const g=JSON.parse('{"title":"Options","description":"","frontmatter":{"title":"Options"},"headers":[],"relativePath":"v1/guide/options.md","filePath":"v1/guide/options.md"}'),r={name:"v1/guide/options.md"},c={id:"frontmatter-title",tabindex:"-1"},d=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),i=p(`

crossorigin

  • Type: String
    • Default: 'anonymous'
    • String values: 'anonymous', 'use-credentials' or undefined learn more

Sets the global crossorigin value of the nuxt-speedkit preloads.
The default value is the crossorigin from the Render Configuration.

detection

  • Type: Object

These options can be used to define which initial checks are to be carried out when using the SpeedkitLayer.

js
{
+  performance: true,
+  browserSupport: true
+}
{
+  performance: true,
+  browserSupport: true
+}
KeyTypeRequiredDescriptionDefault
performanceBooleanyesChecks whether the minimum performance requirement has been met. If this is not the case, the SpeedkitLayer is displayed.true
browserSupportBooleanyesÜberprüft, ob die Webseite über einen supported Browser besucht wird. Handelt es sich hierbei um einen unsupported Browser, wird der SpeedkitLayer eingeblendet.true

::alert{type="info"} For the browser support detection, the default Browserslist of the NuxtJS configuration is used. ::

performanceMetrics

  • Type: Object

With the help of the metrics, the actual performance check on client side can be configured. An explicit lighthouse check via user agent can be optionally added.

js
{
+  device: {
+    hardwareConcurrency: { min: 2, max: 48 },
+    deviceMemory: { min: 2 }
+  },
+  timing: {
+    fcp: 800,
+    dcl: 1200 // fallback if fcp is not available (safari)
+  },
+  lighthouseDetectionByUserAgent: false
+}
{
+  device: {
+    hardwareConcurrency: { min: 2, max: 48 },
+    deviceMemory: { min: 2 }
+  },
+  timing: {
+    fcp: 800,
+    dcl: 1200 // fallback if fcp is not available (safari)
+  },
+  lighthouseDetectionByUserAgent: false
+}

device

  • Type: Object

Describes the minimum hardware requirements that a device should meet to display the website.

js
{
+  hardwareConcurrency: { min: 2, max: 48 },
+  deviceMemory: { min: 2 }
+}
{
+  hardwareConcurrency: { min: 2, max: 48 },
+  deviceMemory: { min: 2 }
+}
KeyTypeRequiredDescriptionDefault
hardwareConcurrencyObjectyesmin/max number of CPUs{ min: 2, max: 48 }
deviceMemoryObjectyesmin size of memory{ min: 2 }

timing

  • Type: Object

Defines the max. time (ms) for the FCP. If the specified value is exceeded, the SpeedkitLayer is displayed. If the browser does not yet grant access to the FCP, the DCL is evaluated as an alternative.

js
{
+  fcp: 800,
+  dcl: 1200 // fallback if fcp is not available (safari)
+}
{
+  fcp: 800,
+  dcl: 1200 // fallback if fcp is not available (safari)
+}
KeyTypeRequiredDescriptionDefault
fcpNumberyesMax. First contentful paint duration in ms learn More800
dclNumberyesMax. Dom content load duration in ms1200

lighthouseDetectionByUserAgent

  • Type: Boolean
    • Default: false

Fallback to detect lighthouse user agent when the other ressources like the hardware detect don't work anymore.

::alert{type="warning"} We recommend that you disable the explicit lighthouse check . In the description of the SpeedkitLayer you will find a more detailed description of the trick that can be used to detect a lighthouse test. ::

fonts

  • Type: Array

List of all font families used in the project. Only the fonts that are listed in the configuration can be retrieved and integrated via $fonts.getFont(...).

js
[
+  {
+    family: 'Font A',
+    locals: ['Font A'],
+    fallback: ['Arial', 'sans-serif'],
+    variances: […]
+  },
+  {
+    family: 'Font B',
+    locals: ['Font B'],
+    fallback: ['Arial', 'sans-serif'],
+    variances: […]
+  }
+]
[
+  {
+    family: 'Font A',
+    locals: ['Font A'],
+    fallback: ['Arial', 'sans-serif'],
+    variances: […]
+  },
+  {
+    family: 'Font B',
+    locals: ['Font B'],
+    fallback: ['Arial', 'sans-serif'],
+    variances: […]
+  }
+]

Font-Family

  • Type: Object

Describes a font family with all its variants.

js
{
+  family: 'Font A',
+  locals: ['Font A'],
+  fallback: ['Arial', 'sans-serif'],
+  variances: […]
+}
{
+  family: 'Font A',
+  locals: ['Font A'],
+  fallback: ['Arial', 'sans-serif'],
+  variances: […]
+}
KeyTypeRequiredDescription
familyStringyesname of the font family
localsArrayyessystem font name of the specified font family
fallbackArrayyesfallback fonts e.g. ['Arial', 'sans-serif']
variancesArrayyeslist of font family variants (e.g. Bold, Italic)

::alert{type="warning"} Prevent sizing discrepancy between your custom and fallback font for perfect swap and reduction of layout shifts. more ::

Font-Variance

  • Type: Object

A font variant describes an instance of a font family and is used to generate the FontFace declaration. Font variants differ in style and weight.

js
{
+  style: 'normal',
+  weight: 400,
+  sources: [
+    { src: '@/assets/fonts/font-a-regular.woff', type:'woff' },
+    { src: '@/assets/fonts/font-a-regular.woff2', type:'woff2' }
+  ]
+}
{
+  style: 'normal',
+  weight: 400,
+  sources: [
+    { src: '@/assets/fonts/font-a-regular.woff', type:'woff' },
+    { src: '@/assets/fonts/font-a-regular.woff2', type:'woff2' }
+  ]
+}
KeyTypeRequiredDescription
styleStringyesfont-style of FontFace, e.g. normal, italic
weightString or Numberyesfont-weight of FontFace, e.g. 400, normal
sourcesArrayyeslist of all font files assigned to the variant (sources)

sources

  • Type: Array

List of all available font files of a font family variation.

js
[
+  { src: '@/assets/fonts/font-a-regular.woff', type:'woff' },
+  { src: '@/assets/fonts/font-a-regular.woff2', type:'woff2' }
+]
[
+  { src: '@/assets/fonts/font-a-regular.woff', type:'woff' },
+  { src: '@/assets/fonts/font-a-regular.woff2', type:'woff2' }
+]
KeyTypeRequiredValue
srcStringyespath to a font file, the use of aliases is possible
typeStringyesfile format of the specified file, e.g. woff, woff2, …

componentAutoImport

  • Type: Boolean
    • Default: false

With this attribute all components that can be found under #speedkit/components can be registered globally. Learn more @nuxt/components

Available components

Global NameImport Path
SpeedkitIframe#speedkit/components/SpeedkitIframeSource
SpeedkitLayer#speedkit/components/SpeedkitLayerSource
SpeedkitPicture#speedkit/components/SpeedkitPictureSource
SpeedkitYoutube#speedkit/components/SpeedkitYoutubeSource
AbstractIntersectionObserver#speedkit/components/abstracts/IntersectionObserverSource
AbstractOnlySsr#speedkit/components/abstracts/OnlySsrSource
ExperimentalSpeedkitPicture#speedkit/components/experimental/SpeedkitPictureSource
ExperimentalSpeedkitYoutube#speedkit/components/experimental/SpeedkitYoutubeSource

componentPrefix

  • Type: String
    • Default: undefined

Defines a prefix for the module components, important for auto import e.g. option componentAutoImport.

Example: SpeedkitPicture => PrefixSpeedkitPicture

lazyOffset

  • Type: Object

Global option for the IntersectionObserver built into the nuxt-speedkit.

js
{
+  // rootMargin for speedkitComponents components
+  component: '0%',
+  // rootMargin for SpeedkitPicture and SpeedkitImage
+  asset: '0%' 
+}
{
+  // rootMargin for speedkitComponents components
+  component: '0%',
+  // rootMargin for SpeedkitPicture and SpeedkitImage
+  asset: '0%' 
+}
KeyTypeRequiredDescriptionDefault
componentStringyesrootMargin value for speedkitComponents0%
assetStringyesrootMargin value for all static ressources (v-font und SpeedkitPicture)0%

disableNuxtImage

  • Type: Boolean
    • Default: false

If set, @nuxt/image will not be integrated.

⚠️  The following components can no longer be used:

`,66);function y(e,E,h,u,f,b){return n(),o("div",null,[s("h1",c,[t(l(e.$frontmatter.title)+" ",1),d]),i])}const F=a(r,[["render",y]]);export{g as __pageData,F as default}; diff --git a/docs/assets/v1_guide_options.md.gNDuU5V1.lean.js b/docs/assets/v1_guide_options.md.gNDuU5V1.lean.js new file mode 100644 index 0000000000..ae768e6b3b --- /dev/null +++ b/docs/assets/v1_guide_options.md.gNDuU5V1.lean.js @@ -0,0 +1 @@ +import{_ as a,o as n,c as o,k as s,a as t,t as l,R as p}from"./chunks/framework._u06EGUx.js";const g=JSON.parse('{"title":"Options","description":"","frontmatter":{"title":"Options"},"headers":[],"relativePath":"v1/guide/options.md","filePath":"v1/guide/options.md"}'),r={name:"v1/guide/options.md"},c={id:"frontmatter-title",tabindex:"-1"},d=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),i=p("",66);function y(e,E,h,u,f,b){return n(),o("div",null,[s("h1",c,[t(l(e.$frontmatter.title)+" ",1),d]),i])}const F=a(r,[["render",y]]);export{g as __pageData,F as default}; diff --git a/docs/assets/v1_guide_setup.md.8rJExCV3.js b/docs/assets/v1_guide_setup.md.8rJExCV3.js new file mode 100644 index 0000000000..6ce56411ed --- /dev/null +++ b/docs/assets/v1_guide_setup.md.8rJExCV3.js @@ -0,0 +1,131 @@ +import{_ as a,o as l,c as p,k as s,a as o,t as e,R as t}from"./chunks/framework._u06EGUx.js";const g=JSON.parse('{"title":"Setup","description":"","frontmatter":{"title":"Setup"},"headers":[],"relativePath":"v1/guide/setup.md","filePath":"v1/guide/setup.md"}'),c={name:"v1/guide/setup.md"},r={id:"frontmatter-title",tabindex:"-1"},E=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),y=t(`

Check the Nuxt.js documentation for more information about installing and using modules in Nuxt.js.

Installation

Add nuxt-speedkit dependency to your project:

bash
yarn add nuxt-speedkit
yarn add nuxt-speedkit
bash
npm install nuxt-speedkit
npm install nuxt-speedkit

Then, add nuxt-speedkit to the modules section of nuxt.config.js:

js
{
+  modules: [
+    'nuxt-speedkit'
+  ],
+  speedkit: {
+    // Options
+  }
+}
{
+  modules: [
+    'nuxt-speedkit'
+  ],
+  speedkit: {
+    // Options
+  }
+}

Example Configuration

js
{
+  speedkit: {
+    detection: {
+      performance: true,
+      browserSupport: true
+    },
+    performanceMetrics: {
+      device: {
+        hardwareConcurrency: { min: 2, max: 48 },
+        deviceMemory: { min: 2 }
+      },
+      timing: {
+        fcp: 800,
+        dcl: 1200
+      },
+      lighthouseDetectionByUserAgent: false
+    },
+    fonts: [{
+      family: 'Font A',
+      locals: ['Font A'],
+      fallback: ['Arial', 'sans-serif'],
+      variances: [
+        {
+          style: 'normal',
+          weight: 400,
+          sources: [
+            { src: '@/assets/fonts/font-a-regular.woff', type:'woff' },
+            { src: '@/assets/fonts/font-a-regular.woff2', type:'woff2' }
+          ]
+        }, {
+          style: 'italic',
+          weight: 400,
+          sources: [
+            { src: '@/assets/fonts/font-a-regularItalic.woff', type:'woff' },
+            { src: '@/assets/fonts/font-a-regularItalic.woff2', type:'woff2' }
+          ]
+        }, {
+          style: 'normal',
+          weight: 700,
+          sources: [
+            { src: '@/assets/fonts/font-a-700.woff', type:'woff' },
+            { src: '@/assets/fonts/font-a-700.woff2', type:'woff2' }
+          ]
+        }
+      ]
+    }],
+
+    componentAutoImport: false,
+    componentPrefix: undefined,
+
+    /**
+     * IntersectionObserver rootMargin for Compoennts and Assets
+     */
+    lazyOffset: {
+      component: '0%',
+      asset: '0%'
+    }
+  }
+}
{
+  speedkit: {
+    detection: {
+      performance: true,
+      browserSupport: true
+    },
+    performanceMetrics: {
+      device: {
+        hardwareConcurrency: { min: 2, max: 48 },
+        deviceMemory: { min: 2 }
+      },
+      timing: {
+        fcp: 800,
+        dcl: 1200
+      },
+      lighthouseDetectionByUserAgent: false
+    },
+    fonts: [{
+      family: 'Font A',
+      locals: ['Font A'],
+      fallback: ['Arial', 'sans-serif'],
+      variances: [
+        {
+          style: 'normal',
+          weight: 400,
+          sources: [
+            { src: '@/assets/fonts/font-a-regular.woff', type:'woff' },
+            { src: '@/assets/fonts/font-a-regular.woff2', type:'woff2' }
+          ]
+        }, {
+          style: 'italic',
+          weight: 400,
+          sources: [
+            { src: '@/assets/fonts/font-a-regularItalic.woff', type:'woff' },
+            { src: '@/assets/fonts/font-a-regularItalic.woff2', type:'woff2' }
+          ]
+        }, {
+          style: 'normal',
+          weight: 700,
+          sources: [
+            { src: '@/assets/fonts/font-a-700.woff', type:'woff' },
+            { src: '@/assets/fonts/font-a-700.woff2', type:'woff2' }
+          ]
+        }
+      ]
+    }],
+
+    componentAutoImport: false,
+    componentPrefix: undefined,
+
+    /**
+     * IntersectionObserver rootMargin for Compoennts and Assets
+     */
+    lazyOffset: {
+      component: '0%',
+      asset: '0%'
+    }
+  }
+}

See module options.

`,9);function i(n,d,F,f,u,C){return l(),p("div",null,[s("h1",r,[o(e(n.$frontmatter.title)+" ",1),E]),y])}const m=a(c,[["render",i]]);export{g as __pageData,m as default}; diff --git a/docs/assets/v1_guide_setup.md.8rJExCV3.lean.js b/docs/assets/v1_guide_setup.md.8rJExCV3.lean.js new file mode 100644 index 0000000000..7783b07b51 --- /dev/null +++ b/docs/assets/v1_guide_setup.md.8rJExCV3.lean.js @@ -0,0 +1 @@ +import{_ as a,o as l,c as p,k as s,a as o,t as e,R as t}from"./chunks/framework._u06EGUx.js";const g=JSON.parse('{"title":"Setup","description":"","frontmatter":{"title":"Setup"},"headers":[],"relativePath":"v1/guide/setup.md","filePath":"v1/guide/setup.md"}'),c={name:"v1/guide/setup.md"},r={id:"frontmatter-title",tabindex:"-1"},E=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),y=t("",9);function i(n,d,F,f,u,C){return l(),p("div",null,[s("h1",r,[o(e(n.$frontmatter.title)+" ",1),E]),y])}const m=a(c,[["render",i]]);export{g as __pageData,m as default}; diff --git a/docs/assets/v1_guide_usage.md.tutYb0dJ.js b/docs/assets/v1_guide_usage.md.tutYb0dJ.js new file mode 100644 index 0000000000..e696f873fa --- /dev/null +++ b/docs/assets/v1_guide_usage.md.tutYb0dJ.js @@ -0,0 +1,97 @@ +import{_ as n,o as e,c as l,k as s,a as o,t as p,R as t}from"./chunks/framework._u06EGUx.js";const f=JSON.parse('{"title":"Usage","description":"","frontmatter":{"title":"Usage"},"headers":[],"relativePath":"v1/guide/usage.md","filePath":"v1/guide/usage.md"}'),c={name:"v1/guide/usage.md"},r={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),E=t(`

The following tools are provided to optimize your webpage:

Critical prop for critical components

A critical component is visible in the viewport when the web page is initially loaded. This can be communicated to the automated background process via a critical prop. The flag is passed on to all child components. This means that only the main component (organism) must be provided with it. With the help of this flag, the corresponding static resources (images & fonts) are also declared as preload tags in the page head. All other components and their associated resources, that do not have a positive critical prop, are lazy loaded on demand.

html
<component :critical="true" />
<component :critical="true" />

INFO

In the current version, the critical flag must be set manually on the components. Automation would be conceivable in the future. However, according to current knowledge, this would have a massive impact on deployment times when using Puppeteer or similar tools. We are still collecting ideas here. If you know of a more efficient way, please send us a feature request.

Font declaration

The integration of fonts is component-based directly in the Vue template. All fonts, which have been declared in nuxt.config, can be assigned directly to the corresponding HTML element or component. In addition, subselectors and media queries can be defined, which enable viewport-based declarations or rich-text declarations. The cool thing about this is that it saves the additional declaration in the CSS. You no longer have to keep the template and the CSS with its corresponding selectors for fonts in sync. Yeah! This is extremely helpful, especially when it comes to theming.

html
<component v-font="$fonts.getFont(…)" />
<component v-font="$fonts.getFont(…)" />

Learn more about directive v-font.

WARNING

Fonts are no longer declared via CSS with the help of this module. They may even no longer be explicitly defined via CSS, as otherwise the loading behaviour would be negatively affected in the worst case.

Import components

Until now, the components available in the page were always declared via the attribute components. The import was done statically (import component from '@/component';) or dynamically (import('@/component')). nuxt-speedkit provides a new attribute named speedkitComponents that only allows dynamic imports. This ensures that only the components visible in the viewport are executed on initial load and the remaining components outside the viewport are executed on demand. In the background, the module by Markus Oberlehner is used in a standardised way.

js
{
+  speedkitComponents: {
+    Stage: () => import('@/components/organisms/Stage'),
+  }
+}
{
+  speedkitComponents: {
+    Stage: () => import('@/components/organisms/Stage'),
+  }
+}

Whether a component is in the viewport or not is determined in the background by the intersection observer. If the initialisation is to take place earlier, e.g. when scrolling, this can be adjusted accordingly via the rootMargin option in the nuxt.config.

WARNING

Although the attribute "speedkitComponents" can be filled in every component, we recommend its explicit use only in pages and layout. The use within components can only make sense in explicit special cases. Here we recommend the general use of static imports.

Speedkit Components

In order to be able to load further static resources such as pictures, iFrames or Youtube videos in the iFrame in a performance-optimised way, we provide the following components. The speedkit components can be imported via the namespace #speedkit/components.

html
<template>
+  <speedkit-picture>
+</template>
+
+<script>
+import SpeedkitPicture from '#speedkit/components/SpeedkitPicture'
+export default {
+  components: {
+    SpeedkitPicture
+  }
+}
+</script>
<template>
+  <speedkit-picture>
+</template>
+
+<script>
+import SpeedkitPicture from '#speedkit/components/SpeedkitPicture'
+export default {
+  components: {
+    SpeedkitPicture
+  }
+}
+</script>

INFO

The speedkit components will be expanded in the future. If you have explicit wishes, please send us a feature request or directly a pull request with the corresponding feature 😃

Example

You can check out a sample integration of nuxt-speedkit at Nuxt Speedkit Example.

:sandbox{src="https://codesandbox.io/embed/github/GrabarzUndPartner/nuxt-speedkit-example/tree/main/?hidenavigation=1&theme=dark"}

Browser compatibility

You can use nuxt-speedkit with Internet Explorer 11 browser.

INFO

Note that there is no optimization based on preloads in IE 11.

You need the following polyfills:

The PostCSS Plugin postcss-object-fit-images and following build.transpile entries for @nuxt/image:

  • @nuxt/image
  • image-meta

For the polyfills, it is recommended to integrate them as a plugin, polyfills loading must follow a specific order.

You can see a live example at Nuxt Speedkit Example.

Example

js
async function polyfills (){
+
+  if (!('IntersectionObserver' in global)) {
+    await import('intersection-observer');
+  }
+
+  if (!('objectFit' in document.documentElement.style)) {
+    await import('object-fit-images');
+  }
+
+  if (!('HTMLPictureElement' in global || 'picturefill' in global)) {
+    await import('picturefill');
+    await import('picturefill/dist/plugins/mutation/pf.mutation.js');
+  }
+
+}
+
+polyfills ();
async function polyfills (){
+
+  if (!('IntersectionObserver' in global)) {
+    await import('intersection-observer');
+  }
+
+  if (!('objectFit' in document.documentElement.style)) {
+    await import('object-fit-images');
+  }
+
+  if (!('HTMLPictureElement' in global || 'picturefill' in global)) {
+    await import('picturefill');
+    await import('picturefill/dist/plugins/mutation/pf.mutation.js');
+  }
+
+}
+
+polyfills ();
js
{
+  build: {
+    
+    transpile: ['@nuxt/image', 'image-meta'],
+
+    postcss: {
+      plugins: {
+        'postcss-object-fit-images': {}
+      }
+    }
+    
+  },
+
+  plugins: [
+    { src: "@/plugins/polyfills.js", mode: "client" }
+  ]
+}
{
+  build: {
+    
+    transpile: ['@nuxt/image', 'image-meta'],
+
+    postcss: {
+      plugins: {
+        'postcss-object-fit-images': {}
+      }
+    }
+    
+  },
+
+  plugins: [
+    { src: "@/plugins/polyfills.js", mode: "client" }
+  ]
+}
`,34);function y(a,d,m,h,u,g){return e(),l("div",null,[s("h1",r,[o(p(a.$frontmatter.title)+" ",1),i]),E])}const v=n(c,[["render",y]]);export{f as __pageData,v as default}; diff --git a/docs/assets/v1_guide_usage.md.tutYb0dJ.lean.js b/docs/assets/v1_guide_usage.md.tutYb0dJ.lean.js new file mode 100644 index 0000000000..b3fa667587 --- /dev/null +++ b/docs/assets/v1_guide_usage.md.tutYb0dJ.lean.js @@ -0,0 +1 @@ +import{_ as n,o as e,c as l,k as s,a as o,t as p,R as t}from"./chunks/framework._u06EGUx.js";const f=JSON.parse('{"title":"Usage","description":"","frontmatter":{"title":"Usage"},"headers":[],"relativePath":"v1/guide/usage.md","filePath":"v1/guide/usage.md"}'),c={name:"v1/guide/usage.md"},r={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),E=t("",34);function y(a,d,m,h,u,g){return e(),l("div",null,[s("h1",r,[o(p(a.$frontmatter.title)+" ",1),i]),E])}const v=n(c,[["render",y]]);export{f as __pageData,v as default}; diff --git a/docs/assets/v1_index.md.sX8weVju.js b/docs/assets/v1_index.md.sX8weVju.js new file mode 100644 index 0000000000..3ac99e9a16 --- /dev/null +++ b/docs/assets/v1_index.md.sX8weVju.js @@ -0,0 +1 @@ +import{_ as r,o as a,c as o,k as e,a as i,t as n,R as s,a1 as l}from"./chunks/framework._u06EGUx.js";const x=JSON.parse('{"title":"Introduction","description":"","frontmatter":{"outline":"deep","title":"Introduction"},"headers":[],"relativePath":"v1/index.md","filePath":"v1/index.md"}'),d={name:"v1/index.md"},c={id:"frontmatter-title",tabindex:"-1"},u=e("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),h=s('

Module for NuxtJS.

WARNING

You are reading Nuxt Speedkit v1 docs. For Nuxt 3 go to the v3 docs

Nuxt Speedkit takes over the lighthouse performance optimization of your generated website.

In order to achieve a performance score of 100/100, only the resources that are necessary in the current viewport may be loaded. Concepts already exist for the loading of javascript components and images. However, there is not yet a practicable concept for loading fonts dynamically. This module provides a holistic approach to load all necessary resources on demand, including fonts, based on the current viewport.

This module implements the lazy-hydration concept of Markus Oberlehner and embeds a modified version of nuxt/image.

Requirements

  • NodeJS >= 12.x.x
  • NuxtJS >= 2.15.0

Features

  • dynamic loading of viewport based page resources like fonts (subselectors, media queries), components, pictures
  • optional loading prevention of resources at low bandwidth or weak hardware
  • prevents the loading of unnecessary resources (including components) that are outside the current viewport.
  • optional info layer concept to inform users about a reduced UX when bandwidth or hardware is compromised.

Results

  • delivery of the minimum required resources based on the current viewport
  • if you use the tools as specified you will get a lighthouse performance score of 100/100
',12);function p(t,m,f,g,_,b){return a(),o("div",null,[e("h1",c,[i(n(t.$frontmatter.title)+" ",1),u]),h])}const k=r(d,[["render",p]]);export{x as __pageData,k as default}; diff --git a/docs/assets/v1_index.md.sX8weVju.lean.js b/docs/assets/v1_index.md.sX8weVju.lean.js new file mode 100644 index 0000000000..bf7c0e03c7 --- /dev/null +++ b/docs/assets/v1_index.md.sX8weVju.lean.js @@ -0,0 +1 @@ +import{_ as r,o as a,c as o,k as e,a as i,t as n,R as s,a1 as l}from"./chunks/framework._u06EGUx.js";const x=JSON.parse('{"title":"Introduction","description":"","frontmatter":{"outline":"deep","title":"Introduction"},"headers":[],"relativePath":"v1/index.md","filePath":"v1/index.md"}'),d={name:"v1/index.md"},c={id:"frontmatter-title",tabindex:"-1"},u=e("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),h=s("",12);function p(t,m,f,g,_,b){return a(),o("div",null,[e("h1",c,[i(n(t.$frontmatter.title)+" ",1),u]),h])}const k=r(d,[["render",p]]);export{x as __pageData,k as default}; diff --git a/docs/assets/v2_classes_loading-spinner.md.Kt9B_64I.js b/docs/assets/v2_classes_loading-spinner.md.Kt9B_64I.js new file mode 100644 index 0000000000..b6ecd01764 --- /dev/null +++ b/docs/assets/v2_classes_loading-spinner.md.Kt9B_64I.js @@ -0,0 +1 @@ +import{_ as o,o as s,c as t,k as e,a as n,t as i,R as r}from"./chunks/framework._u06EGUx.js";const k=JSON.parse('{"title":"LoadingSpinner","description":"","frontmatter":{"title":"LoadingSpinner"},"headers":[],"relativePath":"v2/classes/loading-spinner.md","filePath":"v2/classes/loading-spinner.md"}'),c={name:"v2/classes/loading-spinner.md"},l={id:"frontmatter-title",tabindex:"-1"},d=e("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),p=r('

Path: #speedkit/components/SpeedkitImage/classes/LoadingSpinner.js

The LoadingSpinner instance describes the visual appearance of the loading state in the SpeedkitImage. This can be defined globally via the module settings or at the specific components.

js
new LoadingSpinner({dataUri, size, backgroundColor});
new LoadingSpinner({dataUri, size, backgroundColor});

dataUri

  • Type: String

Defines the source of the loader. (e.g. @/assets/spinner/three-circles.svg)

size

  • Type: String

Defines the size of the loader. Use css background-size definition. (e.g. 100px)

backgroundColor

  • Type: String

Defines the background color of the loader. Use css color definition. (e.g. #fff)

',12);function h(a,g,u,f,_,m){return s(),t("div",null,[e("h1",l,[n(i(a.$frontmatter.title)+" ",1),d]),p])}const S=o(c,[["render",h]]);export{k as __pageData,S as default}; diff --git a/docs/assets/v2_classes_loading-spinner.md.Kt9B_64I.lean.js b/docs/assets/v2_classes_loading-spinner.md.Kt9B_64I.lean.js new file mode 100644 index 0000000000..071b72b462 --- /dev/null +++ b/docs/assets/v2_classes_loading-spinner.md.Kt9B_64I.lean.js @@ -0,0 +1 @@ +import{_ as o,o as s,c as t,k as e,a as n,t as i,R as r}from"./chunks/framework._u06EGUx.js";const k=JSON.parse('{"title":"LoadingSpinner","description":"","frontmatter":{"title":"LoadingSpinner"},"headers":[],"relativePath":"v2/classes/loading-spinner.md","filePath":"v2/classes/loading-spinner.md"}'),c={name:"v2/classes/loading-spinner.md"},l={id:"frontmatter-title",tabindex:"-1"},d=e("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),p=r("",12);function h(a,g,u,f,_,m){return s(),t("div",null,[e("h1",l,[n(i(a.$frontmatter.title)+" ",1),d]),p])}const S=o(c,[["render",h]]);export{k as __pageData,S as default}; diff --git a/docs/assets/v2_components_speedkit-iframe.md.mfHg_6sq.js b/docs/assets/v2_components_speedkit-iframe.md.mfHg_6sq.js new file mode 100644 index 0000000000..6382e76e73 --- /dev/null +++ b/docs/assets/v2_components_speedkit-iframe.md.mfHg_6sq.js @@ -0,0 +1,43 @@ +import{_ as a,o as n,c as o,k as s,a as l,t,R as p}from"./chunks/framework._u06EGUx.js";const g=JSON.parse('{"title":"SpeedkitIframe","description":"","frontmatter":{"title":"SpeedkitIframe"},"headers":[],"relativePath":"v2/components/speedkit-iframe.md","filePath":"v2/components/speedkit-iframe.md"}'),r={name:"v2/components/speedkit-iframe.md"},c={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),E=p(`

SpeedkitIframe, Iframe & IntersectionObserver in one.

Exkurs

Iframes have a tendency, in the special case of the initial page load, to disrupt the construction and initialisation of the actual page through the massive loading of resources from another source.

For the user, this is particularly visible by:

  • Freeze (Short freeze of the page)
  • Delayed loading of resources (images, fonts)
  • Unnecessarily generated traffic

Solution

In order to solve these points, care should be taken to ensure that the initialisation of the iframe takes place downstream. This can be realised, for example, via an IntersectionObserver. This sets the source on the iframe only when the visible viewport has been reached.

The following conditions can thus be fulfilled:

  • Iframe load is reactive.
  • No resources are blocked during loading.
  • Traffic is only generated when the iframe is visible.

The strategy mentioned above is provided by the SpeedkitIframe, which can be used in the same way as a normal HTML Iframe. The included IntersectionObserver is configured via the intersectionObserver property.

Usage

The SpeedkitIframe is used like a normal HTML Iframe.

Example

vue
<template>
+  <speedkit-iframe v-bind="iframe" @load="onLoadIframe" />
+</template>
+
+<script>
+  export default {
+    data: {
+      iframe: {
+       src: '…',
+       componentObserver: { trackVisibility: true, delay: 350 }
+      }
+    },
+    methods: {
+      onLoadIframe (){
+        console.log('iframe loaded!');
+      }
+    }
+  };
+</script>
<template>
+  <speedkit-iframe v-bind="iframe" @load="onLoadIframe" />
+</template>
+
+<script>
+  export default {
+    data: {
+      iframe: {
+       src: '…',
+       componentObserver: { trackVisibility: true, delay: 350 }
+      }
+    },
+    methods: {
+      onLoadIframe (){
+        console.log('iframe loaded!');
+      }
+    }
+  };
+</script>

Properties

Use native attributes from HTML Iframe.

componentObserver

Sets the options from the integrated IntersectionObserver.

For advanced usage, learn more about option trackVisibility from IntersectionObserver.

Events

html
<speedkit-iframe 
+  @load="console.log('Iframe Loaded!')" 
+  @enter="console.log('Iframe enter viewport!')" 
+/>
<speedkit-iframe 
+  @load="console.log('Iframe Loaded!')" 
+  @enter="console.log('Iframe enter viewport!')" 
+/>
NameDescription
loadTriggered when Iframe has finished loading.
enterTriggered when component has reached the viewport.
`,23);function d(e,y,h,m,f,u){return n(),o("div",null,[s("h1",c,[l(t(e.$frontmatter.title)+" ",1),i]),E])}const v=a(r,[["render",d]]);export{g as __pageData,v as default}; diff --git a/docs/assets/v2_components_speedkit-iframe.md.mfHg_6sq.lean.js b/docs/assets/v2_components_speedkit-iframe.md.mfHg_6sq.lean.js new file mode 100644 index 0000000000..c10162f4c3 --- /dev/null +++ b/docs/assets/v2_components_speedkit-iframe.md.mfHg_6sq.lean.js @@ -0,0 +1 @@ +import{_ as a,o as n,c as o,k as s,a as l,t,R as p}from"./chunks/framework._u06EGUx.js";const g=JSON.parse('{"title":"SpeedkitIframe","description":"","frontmatter":{"title":"SpeedkitIframe"},"headers":[],"relativePath":"v2/components/speedkit-iframe.md","filePath":"v2/components/speedkit-iframe.md"}'),r={name:"v2/components/speedkit-iframe.md"},c={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),E=p("",23);function d(e,y,h,m,f,u){return n(),o("div",null,[s("h1",c,[l(t(e.$frontmatter.title)+" ",1),i]),E])}const v=a(r,[["render",d]]);export{g as __pageData,v as default}; diff --git a/docs/assets/v2_components_speedkit-image.md.DHxRtycv.js b/docs/assets/v2_components_speedkit-image.md.DHxRtycv.js new file mode 100644 index 0000000000..ac7d5252fb --- /dev/null +++ b/docs/assets/v2_components_speedkit-image.md.DHxRtycv.js @@ -0,0 +1,83 @@ +import{_ as e,o as n,c as l,k as s,a as o,t as p,R as t}from"./chunks/framework._u06EGUx.js";const b=JSON.parse('{"title":"SpeedkitImage","description":"","frontmatter":{"title":"SpeedkitImage"},"headers":[],"relativePath":"v2/components/speedkit-image.md","filePath":"v2/components/speedkit-image.md"}'),r={name:"v2/components/speedkit-image.md"},c={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),d=t(`

The SpeedkitImage is a img implementation based on the module @nuxt/image.
It uses the provided API $img.

Features

With the current implementation of SpeedkitImage we can cover the following functionality:

  • generation of multiple image resolutions (srcset)
  • breakpoint-based differentiation of multiple image resolutions (srcset)
  • optimized preloading of critical image resources
  • lazy load of non-critical image resources
  • base path support
  • lazy hydration support
  • load and optimize remote images from custom domains
  • full SEO support

Usage

The SpeedkitImage is used to automatically generate and display different image sizes for different viewports.

The specified resources can be given by absolute path (static folder) or complete URL. nuxt/image downloads the resources fully automatically and stores the generated and optimized renditions in the destination folder.

WARNING

Important: For using SpeedkitImage do not disable @nuxt/image via disableNuxtImage.

Example

vue
<template>
+  <div>
+    <speedkit-image v-bind="image" @load="onLoadImage"  />
+  </div>
+</template>
+
+<script>
+import SpeedkitImage from '#speedkit/components/SpeedkitImage';
+export default {
+  components: { SpeedkitImage },
+  data () {
+    return {
+      image: {
+        source: { format: 'jpg', src: '/img/image.jpg', sizes: { default: '100vw', xxs: '100vw', xs: '100vw', sm: '100vw', md: '100vw', lg: '100vw', xl: '100vw', xxl: '100vw' } },
+        title: 'Image Title',
+        alt: 'Image Alt'
+      }
+    };
+  },
+  methods: {
+    onLoadImage() {
+      console.log('Image loaded!');
+    }
+  }
+};
+</script>
<template>
+  <div>
+    <speedkit-image v-bind="image" @load="onLoadImage"  />
+  </div>
+</template>
+
+<script>
+import SpeedkitImage from '#speedkit/components/SpeedkitImage';
+export default {
+  components: { SpeedkitImage },
+  data () {
+    return {
+      image: {
+        source: { format: 'jpg', src: '/img/image.jpg', sizes: { default: '100vw', xxs: '100vw', xs: '100vw', sm: '100vw', md: '100vw', lg: '100vw', xl: '100vw', xxl: '100vw' } },
+        title: 'Image Title',
+        alt: 'Image Alt'
+      }
+    };
+  },
+  methods: {
+    onLoadImage() {
+      console.log('Image loaded!');
+    }
+  }
+};
+</script>

Properties

js
{
+  source: { … },
+  title: 'Image Title',
+  alt: 'Image Alt',
+  loadingSpinner: new LoadingSpinner( … ),
+}
{
+  source: { … },
+  title: 'Image Title',
+  alt: 'Image Alt',
+  loadingSpinner: new LoadingSpinner( … ),
+}

hydrate

  • Type: Boolean
    • Default: true

The initialization of the SpeedkitImage in the client can be controlled manually.
Here for the property hydrate must be set externally. If true the SpeedkitImage is initialized.

source

  • Type: Object
js
{
+  format: '<output-format>', 
+  src: '<file-path|url>', 
+  sizes: { … }, 
+  modifiers: { … }, 
+  preset: { … }, 
+  provider: '<provider>'
+}
{
+  format: '<output-format>', 
+  src: '<file-path|url>', 
+  sizes: { … }, 
+  modifiers: { … }, 
+  preset: { … }, 
+  provider: '<provider>'
+}

format

Sets the image output format.

Available formats:

  • avif
  • webp
  • png
  • jpg

WARNING

Important: Note that if you specify src without a file extension, the format must be included.

src

Information on property src can be found at here.

sizes

Describes the image sizes in which the resource should be displayed. The image sizes are passed as an object and describe with the key-value pairs the image width and the width of the viewport depending on it, e.g. ImageWidth:MinWidth.

The image width, is definied by screens option from @nuxt/image

Example

In the following example, one image with two different image sizes by breakpoints and output format is webp.

js
[
+  { format: 'webp', src: '/img/image.jpg', sizes: { default: '100vw', sm: '100vw' } }
+]
[
+  { format: 'webp', src: '/img/image.jpg', sizes: { default: '100vw', sm: '100vw' } }
+]

modifiers

  • Type: Object

You can give separate modifiers to each source. This overwrites the global ones of the preset if available.

Learn more about modifiers:

preset

  • Type: Object

If a preset is set on a source, the globally defined one is ignored.

This means that a separate preset can be specified for each source.

Learn more about preset:

provider

  • Type: String

If a provider is set on a source, the globally defined one is ignored.

This means that a separate provider can be specified for each source.

Learn more about provider:

loadingSpinner

Sets a loading spinner definition of type LoadingSpinner, this describes the visual appearance of the loading state.

alt

  • Type: String

Image alternative Text.

MDN - HTMLImageElement.alt

title

  • Type: String

Image Title.

MDN - HTMLElement.title

crossorigin

  • Type: String, Boolean

If not set, the global crossorigin is used this.$speedkit.crossorigin.

Learn more about crossorigin option

MDN - HTML.Attributes.crossorigin

critical

  • Type: Boolean
    • Default: $parent.isCritical

Set component as critical component.

Learn more about critical components

Events

html
<speedkit-image 
+  @load="console.log('Image Loaded!')" 
+/>
<speedkit-image 
+  @load="console.log('Image Loaded!')" 
+/>
NameDescription
loadTriggered when the image resource has been completely loaded.
`,71);function E(a,y,g,h,m,u){return n(),l("div",null,[s("h1",c,[o(p(a.$frontmatter.title)+" ",1),i]),d])}const F=e(r,[["render",E]]);export{b as __pageData,F as default}; diff --git a/docs/assets/v2_components_speedkit-image.md.DHxRtycv.lean.js b/docs/assets/v2_components_speedkit-image.md.DHxRtycv.lean.js new file mode 100644 index 0000000000..76b0b80ed0 --- /dev/null +++ b/docs/assets/v2_components_speedkit-image.md.DHxRtycv.lean.js @@ -0,0 +1 @@ +import{_ as e,o as n,c as l,k as s,a as o,t as p,R as t}from"./chunks/framework._u06EGUx.js";const b=JSON.parse('{"title":"SpeedkitImage","description":"","frontmatter":{"title":"SpeedkitImage"},"headers":[],"relativePath":"v2/components/speedkit-image.md","filePath":"v2/components/speedkit-image.md"}'),r={name:"v2/components/speedkit-image.md"},c={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),d=t("",71);function E(a,y,g,h,m,u){return n(),l("div",null,[s("h1",c,[o(p(a.$frontmatter.title)+" ",1),i]),d])}const F=e(r,[["render",E]]);export{b as __pageData,F as default}; diff --git a/docs/assets/v2_components_speedkit-layer.md.WkUt2OmF.js b/docs/assets/v2_components_speedkit-layer.md.WkUt2OmF.js new file mode 100644 index 0000000000..8e3cfeed0a --- /dev/null +++ b/docs/assets/v2_components_speedkit-layer.md.WkUt2OmF.js @@ -0,0 +1,105 @@ +import{_ as p,D as c,o as r,c as i,k as s,a as e,t as E,I as a,w as l,R as t}from"./chunks/framework._u06EGUx.js";const O=JSON.parse('{"title":"SpeedkitLayer","description":"","frontmatter":{"title":"SpeedkitLayer"},"headers":[],"relativePath":"v2/components/speedkit-layer.md","filePath":"v2/components/speedkit-layer.md"}'),y={name:"v2/components/speedkit-layer.md"},d={id:"frontmatter-title",tabindex:"-1"},u=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),h=t('

If the SpeedkitLayer is implemented, the javascript initialisation is automatically monitored. If one of the events

INFO

  • reduced bandwidth
  • weak hardware
  • unsupported browser

occurs, the process is paused and only continued or cancelled after a user interaction in the layer.

The layer is placed once in the layout (e.g. layouts/default.vue). The included SpeedkitLayer serves as a wrapper and must be filled according to the template, see example component.

The content contains messages and buttons that are displayed in the respective event. Messages and buttons are defined with an id, these are set to display: none; by default via CSS.

  • e.g. nuxt-speedkit-message-unsupported-browser for message
  • e.g. nuxt-speedkit-button-init-app for button

TIP

For the closing mechanism of the layer, see Hide Layer.

Messages

The messages are elements that are displayed for the relevant events.

Initially, all IDs are set to display: none;, so no message is visible.
When an event is triggered, the relevant message is displayed via the ID using the style attribute display: block;.

',10),b=s("thead",null,[s("tr",null,[s("th",null,"ID"),s("th",null,"Description")])],-1),g=s("code",null,"nuxt-speedkit-message-nojs",-1),_=s("td",null,"Javascript is disabled.",-1),m=s("code",null,"nuxt-speedkit-message-reduced-bandwidth",-1),v=s("td",null,"Connection bandwidth is too low.",-1),k=s("code",null,"nuxt-speedkit-message-weak-hardware",-1),f=s("td",null,"User hardware are not sufficient.",-1),w=s("code",null,"nuxt-speedkit-message-unsupported-browser",-1),F=s("td",null,[e("User Browser is not supported by "),s("a",{href:"/v2/guide/options.html#browsersupport"},[s("code",null,"Browserslist")]),e(".")],-1),D=t(`

Example

html
<!-- initial -->
+<div id="nuxt-speedkit-message-unsupported-browser">
+  Your browser is not supported!
+</div>
+
+<!-- active -->
+<div id="nuxt-speedkit-message-unsupported-browser" style="display: block;">
+  Your browser is not supported!
+</div>
<!-- initial -->
+<div id="nuxt-speedkit-message-unsupported-browser">
+  Your browser is not supported!
+</div>
+
+<!-- active -->
+<div id="nuxt-speedkit-message-unsupported-browser" style="display: block;">
+  Your browser is not supported!
+</div>

Buttons

The buttons are interaction elements for the user with which he can make his choice at the relevant event.

Initially, all IDs except for nuxt-speedkit-button-nojs are set to display: none;. When an event is triggered, the relevant button is displayed via the ID using the style attribute display: block;.

`,5),A=s("thead",null,[s("tr",null,[s("th",null,"ID"),s("th",null,"Description")])],-1),q=s("code",null,"nuxt-speedkit-button-init-nojs",-1),x=s("td",null,[e("Visible when javascript is disabled, needed so that the user can hide the layer. Requires the "),s("a",{href:"/v2/components/speedkit-layer.html#hide-layer"},"Hide Layer"),e(" implementation.")],-1),C=s("code",null,"nuxt-speedkit-button-init-reduced-view",-1),B=s("td",null,"Is used to offer the user the possibility to visit the page only with activated fonts and images. Other initialisations of the Javascript are prevented.",-1),T=s("code",null,"nuxt-speedkit-button-init-app",-1),I=s("td",null,"Activates all features. The initialisation of the JavaScript is started, images are loaded.",-1),j=t(`

INFO

It is recommended to register an Inline Click-Event for the buttons #nuxt-speedkit-button-init-reduced-view and #nuxt-speedkit-button-init-app.

More information under Force App initialization

Hide Layer

html
<label for="nuxt-speedkit-layer-close">
+  Close Layer
+</label>
<label for="nuxt-speedkit-layer-close">
+  Close Layer
+</label>

The layer can be closed via a for attribute with the id nuxt-speedkit-layer-close.

TIP

Closing mechanics does not require javascript.

Template

html
<speedkit-layer>
+  <div>
+    <p>Sorry, but you will have a limited user experience due to a…</p>
+
+    <ul style="padding: 0; list-style: none;">
+      <!-- Displayed when javascript is disabled. -->
+      <li id="nuxt-speedkit-message-nojs">
+        disabled javascript
+      </li>
+      <!-- Displayed when browser does not support. -->
+      <li id="nuxt-speedkit-message-unsupported-browser">
+        outdated browser
+      </li>
+      <!-- Displayed when connection bandwidth is too low. -->
+      <li id="nuxt-speedkit-message-reduced-bandwidth">
+        reduced-bandwidth
+      </li>
+      <!-- Displayed when user hardware are not sufficient.  -->
+      <li id="nuxt-speedkit-message-weak-hardware">
+        weak hardware
+      </li>
+    </ul>
+
+    <!-- Button to hide the layer with no javascript -->
+    <button id="nuxt-speedkit-button-init-nojs">
+      <label for="nuxt-speedkit-layer-close">
+        Apply without js
+      </label>
+    </button>
+
+    <!-- Button for use without javascript and with fonts -->
+    <button id="nuxt-speedkit-button-init-reduced-view">
+      <label for="nuxt-speedkit-layer-close">
+        Apply without scripts
+      </label>
+    </button>
+
+    <!-- Button for activate javascript by bad connection or browser support -->
+    <button id="nuxt-speedkit-button-init-app">
+      Apply with all Features
+    </button>
+  </div>
+</speedkit-layer>
<speedkit-layer>
+  <div>
+    <p>Sorry, but you will have a limited user experience due to a…</p>
+
+    <ul style="padding: 0; list-style: none;">
+      <!-- Displayed when javascript is disabled. -->
+      <li id="nuxt-speedkit-message-nojs">
+        disabled javascript
+      </li>
+      <!-- Displayed when browser does not support. -->
+      <li id="nuxt-speedkit-message-unsupported-browser">
+        outdated browser
+      </li>
+      <!-- Displayed when connection bandwidth is too low. -->
+      <li id="nuxt-speedkit-message-reduced-bandwidth">
+        reduced-bandwidth
+      </li>
+      <!-- Displayed when user hardware are not sufficient.  -->
+      <li id="nuxt-speedkit-message-weak-hardware">
+        weak hardware
+      </li>
+    </ul>
+
+    <!-- Button to hide the layer with no javascript -->
+    <button id="nuxt-speedkit-button-init-nojs">
+      <label for="nuxt-speedkit-layer-close">
+        Apply without js
+      </label>
+    </button>
+
+    <!-- Button for use without javascript and with fonts -->
+    <button id="nuxt-speedkit-button-init-reduced-view">
+      <label for="nuxt-speedkit-layer-close">
+        Apply without scripts
+      </label>
+    </button>
+
+    <!-- Button for activate javascript by bad connection or browser support -->
+    <button id="nuxt-speedkit-button-init-app">
+      Apply with all Features
+    </button>
+  </div>
+</speedkit-layer>

Force App initialization

For Unsupported-Browser and Insufficient Hardware events, an onclick event must also be set with the id.

In the event, the global variable __NUXT_SPEEDKIT_AUTO_INIT__ must be set to true.

These are needed if the user has already reacted before the initial Javascript has been loaded. After the javascript has been successfully loaded, the app is automatically initialised.

VariableTypeDescriptionDefault
__NUXT_SPEEDKIT_AUTO_INIT__BooleanIf set, initialisation continues after the javascript has been fully loaded.false
`,12);function S(o,P,N,L,V,U){const n=c("nobr");return r(),i("div",null,[s("h1",d,[e(E(o.$frontmatter.title)+" ",1),u]),h,s("table",null,[b,s("tbody",null,[s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[g]),_:1})]),_]),s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[m]),_:1})]),v]),s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[k]),_:1})]),f]),s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[w]),_:1})]),F])])]),D,s("table",null,[A,s("tbody",null,[s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[q]),_:1})]),x]),s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[C]),_:1})]),B]),s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[T]),_:1})]),I])])]),j])}const $=p(y,[["render",S]]);export{O as __pageData,$ as default}; diff --git a/docs/assets/v2_components_speedkit-layer.md.WkUt2OmF.lean.js b/docs/assets/v2_components_speedkit-layer.md.WkUt2OmF.lean.js new file mode 100644 index 0000000000..522c6b8582 --- /dev/null +++ b/docs/assets/v2_components_speedkit-layer.md.WkUt2OmF.lean.js @@ -0,0 +1 @@ +import{_ as p,D as c,o as r,c as i,k as s,a as e,t as E,I as a,w as l,R as t}from"./chunks/framework._u06EGUx.js";const O=JSON.parse('{"title":"SpeedkitLayer","description":"","frontmatter":{"title":"SpeedkitLayer"},"headers":[],"relativePath":"v2/components/speedkit-layer.md","filePath":"v2/components/speedkit-layer.md"}'),y={name:"v2/components/speedkit-layer.md"},d={id:"frontmatter-title",tabindex:"-1"},u=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),h=t("",10),b=s("thead",null,[s("tr",null,[s("th",null,"ID"),s("th",null,"Description")])],-1),g=s("code",null,"nuxt-speedkit-message-nojs",-1),_=s("td",null,"Javascript is disabled.",-1),m=s("code",null,"nuxt-speedkit-message-reduced-bandwidth",-1),v=s("td",null,"Connection bandwidth is too low.",-1),k=s("code",null,"nuxt-speedkit-message-weak-hardware",-1),f=s("td",null,"User hardware are not sufficient.",-1),w=s("code",null,"nuxt-speedkit-message-unsupported-browser",-1),F=s("td",null,[e("User Browser is not supported by "),s("a",{href:"/v2/guide/options.html#browsersupport"},[s("code",null,"Browserslist")]),e(".")],-1),D=t("",5),A=s("thead",null,[s("tr",null,[s("th",null,"ID"),s("th",null,"Description")])],-1),q=s("code",null,"nuxt-speedkit-button-init-nojs",-1),x=s("td",null,[e("Visible when javascript is disabled, needed so that the user can hide the layer. Requires the "),s("a",{href:"/v2/components/speedkit-layer.html#hide-layer"},"Hide Layer"),e(" implementation.")],-1),C=s("code",null,"nuxt-speedkit-button-init-reduced-view",-1),B=s("td",null,"Is used to offer the user the possibility to visit the page only with activated fonts and images. Other initialisations of the Javascript are prevented.",-1),T=s("code",null,"nuxt-speedkit-button-init-app",-1),I=s("td",null,"Activates all features. The initialisation of the JavaScript is started, images are loaded.",-1),j=t("",12);function S(o,P,N,L,V,U){const n=c("nobr");return r(),i("div",null,[s("h1",d,[e(E(o.$frontmatter.title)+" ",1),u]),h,s("table",null,[b,s("tbody",null,[s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[g]),_:1})]),_]),s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[m]),_:1})]),v]),s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[k]),_:1})]),f]),s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[w]),_:1})]),F])])]),D,s("table",null,[A,s("tbody",null,[s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[q]),_:1})]),x]),s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[C]),_:1})]),B]),s("tr",null,[s("td",null,[a(n,null,{default:l(()=>[T]),_:1})]),I])])]),j])}const $=p(y,[["render",S]]);export{O as __pageData,$ as default}; diff --git a/docs/assets/v2_components_speedkit-picture.md.O5xJ3ujA.js b/docs/assets/v2_components_speedkit-picture.md.O5xJ3ujA.js new file mode 100644 index 0000000000..fb77370b91 --- /dev/null +++ b/docs/assets/v2_components_speedkit-picture.md.O5xJ3ujA.js @@ -0,0 +1,79 @@ +import{_ as e,o as n,c as o,k as s,a as l,t as p,R as t}from"./chunks/framework._u06EGUx.js";const f=JSON.parse('{"title":"SpeedkitPicture","description":"","frontmatter":{"title":"SpeedkitPicture"},"headers":[],"relativePath":"v2/components/speedkit-picture.md","filePath":"v2/components/speedkit-picture.md"}'),r={name:"v2/components/speedkit-picture.md"},c={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),d=t(`

The SpeedkitPicture is a picture implementation based on the module @nuxt/image.
It uses the provided API $img.

Features

With the current implementation of SpeedkitPicture we can cover the following functionality:

  • generation of multiple sources with multiple image resolutions (srcset)
  • breakpoint-based differentiation of multiple image resolutions and ratios (srcset + media-rule)
  • optimized preloading of critical image resources
  • lazy load of non-critical image resources
  • base path support
  • lazy hydration support
  • load and optimize remote images from custom domains
  • full SEO support

Usage

The SpeedkitPicture is used to automatically generate and display different image sizes and/or image ratios for different viewports.

The specified resources can be given by absolute path (static folder) or complete URL. nuxt/image downloads the resources fully automatically and stores the generated and optimized renditions in the destination folder.

WARNING

Important: For using SpeedkitPicture do not disable @nuxt/image via disableNuxtImage.

Example

vue
<template>
+  <div>
+    <speedkit-picture v-bind="picture" @load="onLoadPicture"  />
+  </div>
+</template>
+
+<script>
+import SpeedkitPicture from '#speedkit/components/SpeedkitPicture';
+export default {
+  components: { SpeedkitPicture },
+  data () {
+    return {
+      picture: {
+        sources: [
+          { src: '/img/landscape.png', sizes: { sm: '100vw', md: '100vw', lg: '100vw', xl: '100vw', xxl: '100vw' }, media: '(orientation: landscape)' },
+          { src: '/img/portrait.png', sizes: { default: '100vw', xxs: '100vw', xs: '100vw' }, media: '(orientation: portrait)' }
+        ],
+        title: 'Image Title',
+        alt: 'Image Alt'
+      }
+    };
+  },
+  methods: {
+    onLoadPicture() {
+      console.log('Picture loaded!');
+    }
+  }
+};
+</script>
<template>
+  <div>
+    <speedkit-picture v-bind="picture" @load="onLoadPicture"  />
+  </div>
+</template>
+
+<script>
+import SpeedkitPicture from '#speedkit/components/SpeedkitPicture';
+export default {
+  components: { SpeedkitPicture },
+  data () {
+    return {
+      picture: {
+        sources: [
+          { src: '/img/landscape.png', sizes: { sm: '100vw', md: '100vw', lg: '100vw', xl: '100vw', xxl: '100vw' }, media: '(orientation: landscape)' },
+          { src: '/img/portrait.png', sizes: { default: '100vw', xxs: '100vw', xs: '100vw' }, media: '(orientation: portrait)' }
+        ],
+        title: 'Image Title',
+        alt: 'Image Alt'
+      }
+    };
+  },
+  methods: {
+    onLoadPicture() {
+      console.log('Picture loaded!');
+    }
+  }
+};
+</script>

Properties

js
{
+  sources: [ … ],
+  formats: ['avif', 'webp', 'jpg|jpeg|png'],
+  loadingSpinner: new LoadingSpinner( … ),
+  alt: 'Image Alt',
+  title: 'Image Title',
+}
{
+  sources: [ … ],
+  formats: ['avif', 'webp', 'jpg|jpeg|png'],
+  loadingSpinner: new LoadingSpinner( … ),
+  alt: 'Image Alt',
+  title: 'Image Title',
+}

hydrate

  • Type: Boolean
    • Default: true

The initialization of the SpeedkitPicture in the client can be controlled manually.
Here for the property hydrate must be set externally. If true the SpeedkitPicture is initialized.

sources

  • Type: Array

List of resources used.

The definitions in the sources are equivalent to the SpeedkitImage (source).

The only differences are:

  • The media property can be used. This allows even more dependencies for the display, e.g. (orientation: portrait).
  • The format property is not used. Instead formats is used for setting the output formats.

Example

In the following example, two different image ratios are used.

  • landscape.jpg is applied at a viewport of 996px with an image size of 996px (100vw) by orientation landscape.
  • portrait.jpg is applied below 768px and has two viewport dependent image sizes, at (min-width: 768px) the width 768px and everything below that the width 320px by orientation portrait
js
[
+  { src: '/img/landscape.png', sizes: { md: '100vw' }, media: '(orientation: landscape)' },
+  { src: '/img/portrait.png', sizes: { default: '100vw', sm: '100vw' }, media: '(orientation: portrait)' }
+]
[
+  { src: '/img/landscape.png', sizes: { md: '100vw' }, media: '(orientation: landscape)' },
+  { src: '/img/portrait.png', sizes: { default: '100vw', sm: '100vw' }, media: '(orientation: portrait)' }
+]

formats

  • Type: Array
    • Default: ['webp', 'avif', 'jpg|jpeg|png|gif']

Overrides the pictureFormats property defined in the module options.

Defines the formats that are to be generated and provided as source in the Picture.
Is used to offer the correct image type for the browser.

WARNING

Formats can also be specified as OR condition (jpg|jpeg|png|gif). This is important when using JPGs and PNGs with the same format configuration.

loadingSpinner

Sets a loading spinner definition of type LoadingSpinner, this describes the visual appearance of the loading state of the SpeedkitImage contained in the SpeedkitPicture.

alt

  • Type: String

Image alternative Text.

MDN - HTMLImageElement.alt

title

  • Type: String

Image Title.

MDN - HTMLElement.title

crossorigin

  • Type: String, Boolean

If not set, the global crossorigin is used this.$speedkit.crossorigin.

Learn more about crossorigin option

MDN - HTML.Attributes.crossorigin

sortSources

  • Type: Boolean
    • Default: true

If set, the sources are sorted by the media properties.

This is made possible by sort-css-media-queries.

critical

  • Type: Boolean
    • Default: $parent.isCritical

Set component as critical component.

Learn more about critical components

Events

html
<speedkit-picture 
+  @load="console.log('Loaded!')" 
+/>
<speedkit-picture 
+  @load="console.log('Loaded!')" 
+/>
NameDescription
loadTriggered when the image resource has been completely loaded.
`,57);function E(a,y,u,h,m,g){return n(),o("div",null,[s("h1",c,[l(p(a.$frontmatter.title)+" ",1),i]),d])}const b=e(r,[["render",E]]);export{f as __pageData,b as default}; diff --git a/docs/assets/v2_components_speedkit-picture.md.O5xJ3ujA.lean.js b/docs/assets/v2_components_speedkit-picture.md.O5xJ3ujA.lean.js new file mode 100644 index 0000000000..7605e4bf48 --- /dev/null +++ b/docs/assets/v2_components_speedkit-picture.md.O5xJ3ujA.lean.js @@ -0,0 +1 @@ +import{_ as e,o as n,c as o,k as s,a as l,t as p,R as t}from"./chunks/framework._u06EGUx.js";const f=JSON.parse('{"title":"SpeedkitPicture","description":"","frontmatter":{"title":"SpeedkitPicture"},"headers":[],"relativePath":"v2/components/speedkit-picture.md","filePath":"v2/components/speedkit-picture.md"}'),r={name:"v2/components/speedkit-picture.md"},c={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),d=t("",57);function E(a,y,u,h,m,g){return n(),o("div",null,[s("h1",c,[l(p(a.$frontmatter.title)+" ",1),i]),d])}const b=e(r,[["render",E]]);export{f as __pageData,b as default}; diff --git a/docs/assets/v2_components_speedkit-vimeo.md.6hA686kW.js b/docs/assets/v2_components_speedkit-vimeo.md.6hA686kW.js new file mode 100644 index 0000000000..39407294f6 --- /dev/null +++ b/docs/assets/v2_components_speedkit-vimeo.md.6hA686kW.js @@ -0,0 +1,105 @@ +import{_ as n,o as e,c as l,k as s,a as o,t as p,R as t}from"./chunks/framework._u06EGUx.js";const b=JSON.parse('{"title":"SpeedkitVimeo","description":"","frontmatter":{"title":"SpeedkitVimeo"},"headers":[],"relativePath":"v2/components/speedkit-vimeo.md","filePath":"v2/components/speedkit-vimeo.md"}'),c={name:"v2/components/speedkit-vimeo.md"},r={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),E=t(`

We have integrated SpeedkitVimeo as an example to show how iFrame content must be integrated in a performance-optimized manner. For this purpose, a separate IntersectionObserver has been implemented, which detects a longer dwell time of the component in the viewport. The iFrame is initialized only after a positive detection. This prevents immense data from having to be loaded when simply scrolling through the page. So that no empty space is visible to the user, we use the functionality of the SpeedkitPicture and preload the corresponding Vimeo poster in different renditions, so the illusion is perfect for the user and he does not notice anything of the optimized lazy load procedure.

Usage

The SpeedkitVimeo is used to initialise Vimeo videos with Vimeo Player-SDK.

TIP

The SDK is not part of nuxt-speedkit and will be loaded by an external script.

The url of the Vimeo video must be specified.

The SpeedkitPicture is used for the poster, so the generation of the poster is automated, you can define the image sizes with sizes (What is sizes?).

Learn more about SpeedkitPicture

WARNING

Important: For using SpeedkitVimeo do not disable @nuxt/image via disableNuxtImage

Example

vue
<template>
+  <div>
+    <speedkit-vimeo v-bind="vimeo" @playing="onPlaying"  />
+  </div>
+</template>
+
+<script>
+import SpeedkitVimeo from '#speedkit/components/SpeedkitVimeo';
+export default {
+  components: { SpeedkitVimeo },
+  data () {
+    return {
+      vimeo: {
+        url: '<vimeo-url>',
+        title: 'Vimeo Title',
+        loadingSpinner: undefined,
+        options: {
+          controls: false
+        },
+        posterSizes: {
+          default: '100vw',
+          md: '50vw'
+        }
+      }
+    };
+  },
+  methods: {
+    onPlaying () {
+      console.log('Vimeo Player playing!');
+    }
+  }
+};
+</script>
<template>
+  <div>
+    <speedkit-vimeo v-bind="vimeo" @playing="onPlaying"  />
+  </div>
+</template>
+
+<script>
+import SpeedkitVimeo from '#speedkit/components/SpeedkitVimeo';
+export default {
+  components: { SpeedkitVimeo },
+  data () {
+    return {
+      vimeo: {
+        url: '<vimeo-url>',
+        title: 'Vimeo Title',
+        loadingSpinner: undefined,
+        options: {
+          controls: false
+        },
+        posterSizes: {
+          default: '100vw',
+          md: '50vw'
+        }
+      }
+    };
+  },
+  methods: {
+    onPlaying () {
+      console.log('Vimeo Player playing!');
+    }
+  }
+};
+</script>

Properties

js
{
+  url: '<vimeo-url>',
+  title: 'Player Title',
+  autoplay: false,
+  mute: false,
+  posterSizes: { … },
+  options: { … }
+}
{
+  url: '<vimeo-url>',
+  title: 'Player Title',
+  autoplay: false,
+  mute: false,
+  posterSizes: { … },
+  options: { … }
+}

url

  • Type: String

Sets a video via the vimeo url.

title

  • Type: String

Sets the title for the player iframe and poster.

autoplay

  • Type: Boolean
    • Default: false

When set starts video in autoplay. It is required that the component is integrated via SpeedkitHydrate or is only activated when entering the visible area.

mute

  • Type: Boolean
    • Default: false

When set the player is muted.

posterLoadingSpinner

Sets a loading spinner definition of type LoadingSpinner, this describes the visual appearance of the loading state of the SpeedkitImage contained in the SpeedkitPicture.

posterSizes

  • Type: String
    • Default: { default: '100vw', xxs: '100vw', xs: '100vw', sm: '100vw', md: '100vw', lg: '100vw', xl: '100vw', xxl: '100vw' }

Sets the image sizes of the poster.

Learn more about sizes

options

  • Type: Object

Overrides the vimeo player options. These will be the same as the vimeo player embed options.

Learn more about Vimeo Player Parameters

WARNING

For autoplay and mute the component properties are used.

Option playsinline is always set, mute is set automatically for touch devices.
This is important for autoplay on mobile devices.

Slots

html
<template #default="{ videoData }">
+  {{videoData.title}}
+</template>
+
+<template #loading-spinner>
+  Loading…
+</template>
+
+<template #play>
+  <span>Click!</span>
+</template>
<template #default="{ videoData }">
+  {{videoData.title}}
+</template>
+
+<template #loading-spinner>
+  Loading…
+</template>
+
+<template #play>
+  <span>Click!</span>
+</template>
NameDescription
defaultUsed to display more information about the video below the player.
The slot has a scoped property videoData.
This contains the result from the Vimeo oembed api.

https://developer.vimeo.com/api/oembed/videos#table-2
loading-spinnerOverwrites the loading spinner.
playOverwrites the play button.

Events

html
<speedkit-vimeo 
+  @ready="console.log('Player Ready!')" 
+  @playing="console.log('Player Playing!')" 
+/>
<speedkit-vimeo 
+  @ready="console.log('Player Ready!')" 
+  @playing="console.log('Player Playing!')" 
+/>
NameDescription
readyTriggered when Vimeo Player-SDK is completely loaded.
playingTriggered when video is finished loading and playing.
`,42);function y(a,d,h,m,u,g){return e(),l("div",null,[s("h1",r,[o(p(a.$frontmatter.title)+" ",1),i]),E])}const f=n(c,[["render",y]]);export{b as __pageData,f as default}; diff --git a/docs/assets/v2_components_speedkit-vimeo.md.6hA686kW.lean.js b/docs/assets/v2_components_speedkit-vimeo.md.6hA686kW.lean.js new file mode 100644 index 0000000000..8b8d8e4b4d --- /dev/null +++ b/docs/assets/v2_components_speedkit-vimeo.md.6hA686kW.lean.js @@ -0,0 +1 @@ +import{_ as n,o as e,c as l,k as s,a as o,t as p,R as t}from"./chunks/framework._u06EGUx.js";const b=JSON.parse('{"title":"SpeedkitVimeo","description":"","frontmatter":{"title":"SpeedkitVimeo"},"headers":[],"relativePath":"v2/components/speedkit-vimeo.md","filePath":"v2/components/speedkit-vimeo.md"}'),c={name:"v2/components/speedkit-vimeo.md"},r={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),E=t("",42);function y(a,d,h,m,u,g){return e(),l("div",null,[s("h1",r,[o(p(a.$frontmatter.title)+" ",1),i]),E])}const f=n(c,[["render",y]]);export{b as __pageData,f as default}; diff --git a/docs/assets/v2_components_speedkit-youtube.md.3WlGGiXM.js b/docs/assets/v2_components_speedkit-youtube.md.3WlGGiXM.js new file mode 100644 index 0000000000..a22c779e24 --- /dev/null +++ b/docs/assets/v2_components_speedkit-youtube.md.3WlGGiXM.js @@ -0,0 +1,99 @@ +import{_ as n,o as e,c as l,k as s,a as o,t as p,R as t}from"./chunks/framework._u06EGUx.js";const f=JSON.parse('{"title":"SpeedkitYoutube","description":"","frontmatter":{"title":"SpeedkitYoutube"},"headers":[],"relativePath":"v2/components/speedkit-youtube.md","filePath":"v2/components/speedkit-youtube.md"}'),c={name:"v2/components/speedkit-youtube.md"},r={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),E=t(`

We have integrated SpeedkitYoutube as an example to show how iFrame content must be integrated in a performance-optimized manner. For this purpose, a separate IntersectionObserver has been implemented, which detects a longer dwell time of the component in the viewport. The iFrame is initialized only after a positive detection. This prevents immense data from having to be loaded when simply scrolling through the page. So that no empty space is visible to the user, we use the functionality of the SpeedkitPicture and preload the corresponding Youtube poster in different renditions, so the illusion is perfect for the user and he does not notice anything of the optimized lazy load procedure.

Usage

The SpeedkitYoutubeis used to initialise Youtube videos with Youtube Iframe-API.

The url of the Youtube video must be specified.

The SpeedkitPicture is used for the poster, so the generation of the poster is automated, you can define the image sizes with sizes (What is sizes?).

Learn more about SpeedkitPicture

WARNING

Important: For using SpeedkitYoutube do not disable @nuxt/image via disableNuxtImage.

Example

vue
<template>
+  <div>
+    <speedkit-youtube v-bind="youtube" @playing="onPlaying"  />
+  </div>
+</template>
+
+<script>
+import SpeedkitPicture from '#speedkit/components/SpeedkitYoutube';
+export default {
+  components: { SpeedkitPicture },
+  data () {
+    return {
+      youtube: {
+        url: '<youtube-url>',
+        title: 'Youtube Title',
+        loadingSpinner: undefined,
+        host: 'https://www.youtube.com',
+        options: {
+          fs: false // use boolean instead of 0 or 1
+        },
+        posterSizes: {
+          default: '100vw',
+          md: '50vw'
+        }
+      }
+    };
+  },
+  methods: {
+    onPlaying () {
+      console.log('Youtube Player playing!');
+    }
+  }
+};
+</script>
<template>
+  <div>
+    <speedkit-youtube v-bind="youtube" @playing="onPlaying"  />
+  </div>
+</template>
+
+<script>
+import SpeedkitPicture from '#speedkit/components/SpeedkitYoutube';
+export default {
+  components: { SpeedkitPicture },
+  data () {
+    return {
+      youtube: {
+        url: '<youtube-url>',
+        title: 'Youtube Title',
+        loadingSpinner: undefined,
+        host: 'https://www.youtube.com',
+        options: {
+          fs: false // use boolean instead of 0 or 1
+        },
+        posterSizes: {
+          default: '100vw',
+          md: '50vw'
+        }
+      }
+    };
+  },
+  methods: {
+    onPlaying () {
+      console.log('Youtube Player playing!');
+    }
+  }
+};
+</script>

Properties

js
{
+  url: '<youtube-url>',
+  title: 'Player Title',
+  autoplay: false,
+  mute: false,
+  posterSizes: { … },
+  options: { … }
+}
{
+  url: '<youtube-url>',
+  title: 'Player Title',
+  autoplay: false,
+  mute: false,
+  posterSizes: { … },
+  options: { … }
+}

url

  • Type: String

Sets a video via the youtube url.

title

  • Type: String

Sets the title for the player iframe and poster.

autoplay

  • Type: Boolean
    • Default: false

When set starts video in autoplay. It is required that the component is integrated via SpeedkitHydrate or is only activated when entering the visible area.

mute

  • Type: Boolean
    • Default: false

When set the player is muted.

posterLoadingSpinner

Sets a loading spinner definition of type LoadingSpinner, this describes the visual appearance of the loading state of the SpeedkitImage contained in the SpeedkitPicture.

posterSizes

  • Type: String
    • Default: { default: '100vw', xxs: '100vw', xs: '100vw', sm: '100vw', md: '100vw', lg: '100vw', xl: '100vw', xxl: '100vw' }

Sets the image sizes of the poster.

Learn more about sizes

options

  • Type: Object

Overrides the youtube player options. These will be the same as the youtube player parameters.

Use boolean values instead of integers (e.g. 0, 1).

Learn more about Youtube Player Parameters

WARNING

For autoplay and mute the component properties are used.

Option playsinline is always set, mute is set automatically for touch devices.
This is important for autoplay on mobile devices.

host

  • Type: String
    • default: 'https://www.youtube-nocookie.com'

Sets the host for the player.

TIP

It is recommended to use the default (https://www.youtube-nocookie.com).

Slots

html
<template #loading-spinner>
+  Loading…
+</template>
+
+<template #play>
+  <span>Click!</span>
+</template>
<template #loading-spinner>
+  Loading…
+</template>
+
+<template #play>
+  <span>Click!</span>
+</template>
NameDescription
loading-spinnerOverwrites the loading spinner.
playOverwrites the play button.

Events

html
<speedkit-youtube 
+  @ready="console.log('Player Ready!')" 
+  @playing="console.log('Player Playing!')" 
+/>
<speedkit-youtube 
+  @ready="console.log('Player Ready!')" 
+  @playing="console.log('Player Playing!')" 
+/>
NameDescription
readyTriggered when Youtube-API is completely loaded.
playingTriggered when video is finished loading and playing.
`,46);function y(a,d,u,h,m,g){return e(),l("div",null,[s("h1",r,[o(p(a.$frontmatter.title)+" ",1),i]),E])}const v=n(c,[["render",y]]);export{f as __pageData,v as default}; diff --git a/docs/assets/v2_components_speedkit-youtube.md.3WlGGiXM.lean.js b/docs/assets/v2_components_speedkit-youtube.md.3WlGGiXM.lean.js new file mode 100644 index 0000000000..d8a8815042 --- /dev/null +++ b/docs/assets/v2_components_speedkit-youtube.md.3WlGGiXM.lean.js @@ -0,0 +1 @@ +import{_ as n,o as e,c as l,k as s,a as o,t as p,R as t}from"./chunks/framework._u06EGUx.js";const f=JSON.parse('{"title":"SpeedkitYoutube","description":"","frontmatter":{"title":"SpeedkitYoutube"},"headers":[],"relativePath":"v2/components/speedkit-youtube.md","filePath":"v2/components/speedkit-youtube.md"}'),c={name:"v2/components/speedkit-youtube.md"},r={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),E=t("",46);function y(a,d,u,h,m,g){return e(),l("div",null,[s("h1",r,[o(p(a.$frontmatter.title)+" ",1),i]),E])}const v=n(c,[["render",y]]);export{f as __pageData,v as default}; diff --git a/docs/assets/v2_concept.md.p1bQg3c8.js b/docs/assets/v2_concept.md.p1bQg3c8.js new file mode 100644 index 0000000000..82788dbe89 --- /dev/null +++ b/docs/assets/v2_concept.md.p1bQg3c8.js @@ -0,0 +1 @@ +import{_ as t,o as i,c as o,k as e,a as n,t as r,R as s}from"./chunks/framework._u06EGUx.js";const v=JSON.parse('{"title":"Concept","description":"","frontmatter":{"title":"Concept"},"headers":[],"relativePath":"v2/concept.md","filePath":"v2/concept.md"}'),l={name:"v2/concept.md"},h={id:"frontmatter-title",tabindex:"-1"},d=e("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),c=s('

Current Situation

The loading behavior of webpages based on NuxtJS is designed in such a way that all necessary Javascript resources are preloaded and directly initialized with the initial load of the page. However, this behavior creates a negative impact on the Lighthouse Performance Score (TTI) for larger pages that have an increased initial load of additional resources, such as fonts, images, plugins, modules (@nuxtjs/i18n, ...).

Excursus

The Lighthouse Test is not a tool to make a general statement about the quality of a website programming. Lighthouse rather tries to map a metric for the usability of a page from the user's point of view. This includes accessibility, best practices, SEO and of course performance.

This last point is often misinterpreted by developers. If you want to implement features that increase usability for the user (interactions/more complex animations, ...), this will always have an impact on performance in the Lighthouse Test for larger website projects, as the corresponding Javascript must be loaded for this. Finally, Lighthouse does also not rate the design, but the accessibility (size of click areas, etc.) of a website. You should therefore not ask yourself the following question: "How can I fully optimize my JavaScript to achieve a Lighthouse score of 100/100?". You have to ask yourself much more the question: "What is especially important to a user with low bandwidth or weak hardware on my site?".

The answer to this is relatively simple: the content must be accessible and you must be able to get to the information you need quickly.

No more and no less.

The user doesn't need any fancy slider animations and parallax effects that can only be implemented with certain libraries. Or a softload mechanism to get to more pages in a more elegant and animated way, but which initially needs an increased amount of javascript logic. All he wants is that information is retrievable reasonably fast and he can click through the presence.

Problem

The good news is that the NuxtJS SSR build provides the right foundation. The content is already in the form of HTML and CSS and can be used without Javascript. But what is missing

  • is a fully automated preload logic that allows component and viewport based handling and prioritization of the individual resources (FCP, LCP, CLS)
  • is a logic that enables a perfomance-oriented initialization of the javascript (TTI, TBT)

These two central points are handled by Nuxt Speedkit and enable a fast and resource-saving loading behavior of the website.

Approach

Over a longer period of time, we analyzed the Google Lighthouse test in more detail and approached the topic with the help of use cases. We did not start with the best case for page content (one image, one font, minimal javascript), but with the worst case (many images, many fonts, large Javascript files, ...). So we avoided to develop only a solution for simple SinglePages. Our claim was much more to create a generalistic, performant solution even with a CMS connection and dynamic component compositions per page. All our thoughts are based on HTTP/2 request prioritization and the lazy hydration approach. Initial resources are prioritized by preload and all further data is reloaded viewport-based.

Insights & Solutions

During the tests, we gained the following insights, which we would like to share with you, but which also allow us to draw conclusions regarding the performance optimization of the initial loading process and which have been incorporated into the Nuxt Speedkit solution.

Critical Render Path

The critical render path is the core of a high-performance and efficient loading and rendering behavior of a website. It is important that components and resources in the viewport are loaded and executed with priority so that the user can be provided with a functioning page as quickly as possible. A browser is not able to recognize this fully automatically to dynamically adapt the loading behavior. Some attempts have been made in the past to systematically identify the critical render path.However, this has the consequence that every generated page in a virtual browser has to be analyzed in given viewport sizes, which slows down the deployment process and makes it more error-prone. For this reason, we (the developers) will be forced to provide the build process with appropriate hints in the form of a Critical Attribute on the affected component, so that an automated optimization by preloads, lazy hydration, etc. can be performed in response.

Font Loading

Fonts are the great mystery on the Internet. For more complex designs it is not uncommon that more than 6 font files have to be loaded. It would be desirable if there were many more variable fonts, but the reality is usually different. Often, developers are forced to register tons of fonts with different font styles. So it can happen that the website needs a total count of 12 font files, which have to be loaded initially to achieve the right visual result on the whole page.

This is a real performance problem. If you look for solutions, you like to hear

  • don't use WebFonts that have to be loaded
  • use another optimized font
  • reduce the number of used fonts
  • embed the fonts via Base64

You will find some articles about font loading. But most of them are more than 3 years old. Summary: not much happened here. A nice and recommendable list of different strategies can be found at web-font-loading-recipes or comprehensive-webfonts. From this it can be deduced that there is still no universal solution to the problem. However, it is possible to approach the issue very efficiently by using a preload strategy and setting classes accordingly. However, this does not make the handling of the fonts any easier. On the one hand, the preloads have to be defined per page and on the other hand, the CSS in the respective component has to be activated with the corresponding font declaration per class on demand. This is manageable for smaller projects in a 1 person team. But if several people are working in parallel, it can quickly become a nightmare. This will inevitably lead to the fact that the approach will not be accepted by the team and the optimization will be optimized out of the project in the long run.

INFO

A few words about Google Fonts: If possible, the FontFaces should always be included directly as Woff/Woff2 files via inline style. The loading mechanism via external CSS file, as it is the case with Google Fonts, creates an additional network roundtrip, which delays the loading of the actual font files.

The strategy mentioned above makes sense, but is hardly implementable with the current tools. For this reason, we are introducing Directive v-font, which takes care of the outlined behavior in a fully automated way and thus represents a truly relevant solution even on larger projects. Combined with the lazy hydration approach, the relevant fonts can be declared and loaded per component. The preloads are controlled via the critical attribute. With the help of this loading strategy, a FOUT (flash of unstyled text) and CLS can be massively reduced or eliminated. If no javascript is activated on the end device, all fonts are automatically activated via CSS.

Image Loading

For image compression and different image formats, the module nuxt-optimized-images was popularly used in the nuxt world in the past. The downside, however, is that this approach is not particularly CMS and deployment friendly. With each image change, a full build process had to be initiated. For this reason, we use the nuxt-image module, as this takes advantage of a change in NuxtJS as of version 2.13.0. In this version update, the build was split into two separate processes (javascript compilation + page generation). With nuxt-optimized-images the full build process had to be run for every image change. This is no longer the case with nuxt-image. Here only the page generation process is necessary. As a result, deployment times for all content changes can be massively reduced.

We use the module in its complete form. However, we have redeveloped the nuxt-image and nuxt-picture components, as the current version does not fully meet our requirements. For example, we lacked an appropriate preloading and lazy hydration strategy. Although there is a native loading attribute on the image element that allows prioritization, the use for websites with a lot of images is still not optimal, because the distance-from-viewport threshold is still too generous and the loading performance can deteriorate unintentionally. For this we have implemented a corresponding SEO-compliant alternative, which loads the images only when the viewport is reached, but also provides the image sources for search engines via no-script tag. This way all relevant images can be displayed even if Javascript is disabled. Furthermore you can also define multiple image sources in the picture, so it is possible to display an image in portait mode with a 9/16 aspect-ratio (multiple renditions) and in landscape mode with a 16/9 aspect-ratio (multiple renditions).

Javascript Loading

NuxtJS follows the approach to load the core files (page, app, payload, vendor, state, etc.) as fast and efficient as possible via (module-)preload from the client. This also makes total sense if you want to deliver an SPA. For the SSR build, however, we modified the delivery a bit. The many parallel downloads (fonts, images, js, ...) have a negative impact from a performance perspective. This effect increases when the javascript files grow in size due to modules and plugins. It would make sense if the initial package is kept small and only the absolutely necessary resources that can trigger the further initialization process are transferred via dynamic import. This leaves enough bandwidth to load the remaining resources (fonts, images).

This loading behavior only makes sense with an SSR build, since the full page-related static content can already be delivered and rendered with the HTML and the included CSS. This means that the user does not notice any time lags and the page is still usable. Another advantage: If the bandwidth is low, a basic functionality of the page (links, ...) can be ensured thanks to the SSR build.

RequestIdleCallback

The TimeRemaining function of the IdleDeadline object continuously returns a value <= 10 in the Lighthouse Test (simulated Motorola G4). This can be seen as an indicator for weak hardware on the end device and allows the following conclusion. If there are not enough hardware resources available to execute the JavaScript quickly, this process is suppressed. Who needs optional functionality that takes a long time to initialize and possibly leads to a temporary freeze in the browser.

We use this effect by executing the intial javascript process and the component initialization in the RequestIdleCallback, if we get a time slot >10ms from the device. Hereby we achieve a massive reduction of the TTI/TBT in the Lighthouse Test and on weak hardware, because the javascript execution is simply paused in the worst case until sufficient resources are available. This also prevents blocking of the MainThread.

Side effect: The timeslots in the Google Lighthouse Test are always <= 10ms, so no javascript will be initialized.

SpeedkitLayer

With the solutions described above, the user gets a functioning webpage displayed very quickly. However, the following situation can also occur on the end device:

  • no Javascript enabled
  • reduced bandwidth
  • weak hardware
  • unsupported browser

The reduced bandwidth or weak hardware should get a focus especially when larger amounts of data have to be transferred and executed, e.g. a ThreeJS component with more complex 3D objects. In this case, we should inform the user that the experience will be negatively affected and that there may be waiting times.

For this purpose, we provide an InfoLayer that is displayed when a minimum FCP time is exceeded, when the number of available CPU cores falls below a minimum level, when javascript is disabled or the users opens the page by an unsupported browser. The user can decide in this dialog box whether he wants to load the remaining resources despite the restrictions. If the user declines this dialog, only the fonts and images for the page will be loaded and no further javascript will be loaded or executed.

Conclusion

The findings and solutions described above have been incorporated and systematized in the Nuxt Speedkit module. Only in combination can they unfold their full functionality and ensure an overall optimization of the loading behavior. Overall we have reduced the following timing metrics ...

  • FCP
  • LCP
  • TTI
  • TBT

With this module we enable every developer in the NuxtJS context to achieve a Lighthouse Performance Score 100/100 and drastically reduce the development time for website performance optimization.

',44);function p(a,u,m,f,g,b){return i(),o("div",null,[e("h1",h,[n(r(a.$frontmatter.title)+" ",1),d]),c])}const y=t(l,[["render",p]]);export{v as __pageData,y as default}; diff --git a/docs/assets/v2_concept.md.p1bQg3c8.lean.js b/docs/assets/v2_concept.md.p1bQg3c8.lean.js new file mode 100644 index 0000000000..4c428ed414 --- /dev/null +++ b/docs/assets/v2_concept.md.p1bQg3c8.lean.js @@ -0,0 +1 @@ +import{_ as t,o as i,c as o,k as e,a as n,t as r,R as s}from"./chunks/framework._u06EGUx.js";const v=JSON.parse('{"title":"Concept","description":"","frontmatter":{"title":"Concept"},"headers":[],"relativePath":"v2/concept.md","filePath":"v2/concept.md"}'),l={name:"v2/concept.md"},h={id:"frontmatter-title",tabindex:"-1"},d=e("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),c=s("",44);function p(a,u,m,f,g,b){return i(),o("div",null,[e("h1",h,[n(r(a.$frontmatter.title)+" ",1),d]),c])}const y=t(l,[["render",p]]);export{v as __pageData,y as default}; diff --git a/docs/assets/v2_directives_v-font.md.JKoSYXEX.js b/docs/assets/v2_directives_v-font.md.JKoSYXEX.js new file mode 100644 index 0000000000..3047747bbf --- /dev/null +++ b/docs/assets/v2_directives_v-font.md.JKoSYXEX.js @@ -0,0 +1,79 @@ +import{_ as a,o,c as l,k as s,a as t,t as e,R as p}from"./chunks/framework._u06EGUx.js";const v=JSON.parse('{"title":"v-font","description":"","frontmatter":{"title":"v-font"},"headers":[],"relativePath":"v2/directives/v-font.md","filePath":"v2/directives/v-font.md"}'),c={name:"v2/directives/v-font.md"},r={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),E=p(`

The directive v-font is used to integrate the fonts defined in the module options into the website.

To do this, the respective font must be retrieved via the $getFont method contained in the component scope (e.g. this).

Fonts are specified by family, weight and style and can be limited to elements and viewports via the options (media, selector).

Normally the directive activates the fonts only when the viewport is reached. It is recommended to use the property critical for components that are already initially contained in the viewport.

With critical component the fonts are preloaded and are initially active.
More information on critical components can be found here.

For multiple fonts, a list (Array) can be passed.

html

+<!-- single definition -->
+<element v-font="$getFont(…)">
+
+<!-- multiple definitions -->
+<element v-font="[
+  $getFont(…),
+  $getFont(…)
+]">

+<!-- single definition -->
+<element v-font="$getFont(…)">
+
+<!-- multiple definitions -->
+<element v-font="[
+  $getFont(…),
+  $getFont(…)
+]">

DANGER

Currently it is not possible to use v-font in combination with v-html/v-text directives. The reason is a bug in Vue SSR, the directive is not applied. As long as this error exists, you can look here for workarounds.
Read more: vue-server-renderer: directive not applied to imported component.

$getFont(family, [weight, style, options])

$getFont is included as a plugin and can be accessed via any component scope.
Use $getFont in the v-font directive and create the relevant font definition.

KeyTypeRequriedDescriptionDefault
familyStringyesFont-Family e.g. Custom Font
weightString, NumberFont-Style e.g. normal, italic400
styleStringFont-Weight e.g. 400, normalnormal
optionsObjectMedia & Selector Options see more

options

Each definition can be modified in its behaviour via the options.
With the property media, the call of the font definition can be made dependent on the viewport. The property selector can be used to limit the font to elements (e.g. span, .class).

js
{
+  media: '(min-width: 768px)',
+  selector: 'element, .elm, .elm:before'
+}
{
+  media: '(min-width: 768px)',
+  selector: 'element, .elm, .elm:before'
+}
KeyTypeRequriedDescriptionDefault
mediaStringCSS Media Query e.g. (min-width: 768px)
selectorStringCSS Selector e.g. element, .elm, .elm:before

Examples

Basic Usage

html
<element v-font="$getFont('Font Family', 700)">Text…</element>
<element v-font="$getFont('Font Family', 700)">Text…</element>

Advanced Usage

js
[
+  
+  // Font wird auf alles angewendet
+  $getFont('Font Family A'),
+
+  // Font wird auf \`b\` und \`strong\` Tags angwendet
+  $getFont('Font Family B', 700, 'normal', { selector: 'b, strong' }),
+
+  // Font erscheint erst ab Viewport \`>768px\`
+  $getFont('Font Family B', 400, 'normal', { media: '(min-width: 768px)' }),
+
+  // Font wird auf \`b\` und \`strong\` Tags angwendet und erscheint erst ab Viewport \`>768px\`
+  $getFont('Font Family B', 700, 'normal', { selector: 'b, strong', media: '(min-width: 768px)' })
+
+]
[
+  
+  // Font wird auf alles angewendet
+  $getFont('Font Family A'),
+
+  // Font wird auf \`b\` und \`strong\` Tags angwendet
+  $getFont('Font Family B', 700, 'normal', { selector: 'b, strong' }),
+
+  // Font erscheint erst ab Viewport \`>768px\`
+  $getFont('Font Family B', 400, 'normal', { media: '(min-width: 768px)' }),
+
+  // Font wird auf \`b\` und \`strong\` Tags angwendet und erscheint erst ab Viewport \`>768px\`
+  $getFont('Font Family B', 700, 'normal', { selector: 'b, strong', media: '(min-width: 768px)' })
+
+]

Workarounds

Workarounds to prevent bugs with default nuxt components and directives, read more in Usage.

Use component

Bad

html
<template>
+  <nuxt-link to="/" v-font="$getFont(…)">Back</nuxt-link>
+</template>
<template>
+  <nuxt-link to="/" v-font="$getFont(…)">Back</nuxt-link>
+</template>

Good

html
<template>
+  <nuxt-link to="/">
+    <span v-font="$getFont(…)">Back</span>
+  </nuxt-link>
+</template>
<template>
+  <nuxt-link to="/">
+    <span v-font="$getFont(…)">Back</span>
+  </nuxt-link>
+</template>

Use v-html/v-text

Bad

html
<template>
+  <div>
+    <div v-font="$getFont(…)" v-html="…" />
+  </div>
+</template>
<template>
+  <div>
+    <div v-font="$getFont(…)" v-html="…" />
+  </div>
+</template>

Good

html
<template>
+  <div v-font="$getFont(…)">
+    <div v-html="…" />
+  </div>
+</template>
<template>
+  <div v-font="$getFont(…)">
+    <div v-html="…" />
+  </div>
+</template>
`,32);function y(n,d,h,g,F,u){return o(),l("div",null,[s("h1",r,[t(e(n.$frontmatter.title)+" ",1),i]),E])}const f=a(c,[["render",y]]);export{v as __pageData,f as default}; diff --git a/docs/assets/v2_directives_v-font.md.JKoSYXEX.lean.js b/docs/assets/v2_directives_v-font.md.JKoSYXEX.lean.js new file mode 100644 index 0000000000..e03e1bc6af --- /dev/null +++ b/docs/assets/v2_directives_v-font.md.JKoSYXEX.lean.js @@ -0,0 +1 @@ +import{_ as a,o,c as l,k as s,a as t,t as e,R as p}from"./chunks/framework._u06EGUx.js";const v=JSON.parse('{"title":"v-font","description":"","frontmatter":{"title":"v-font"},"headers":[],"relativePath":"v2/directives/v-font.md","filePath":"v2/directives/v-font.md"}'),c={name:"v2/directives/v-font.md"},r={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),E=p("",32);function y(n,d,h,g,F,u){return o(),l("div",null,[s("h1",r,[t(e(n.$frontmatter.title)+" ",1),i]),E])}const f=a(c,[["render",y]]);export{v as __pageData,f as default}; diff --git a/docs/assets/v2_guide_caveats.md.QQFtLQVa.js b/docs/assets/v2_guide_caveats.md.QQFtLQVa.js new file mode 100644 index 0000000000..8740a87738 --- /dev/null +++ b/docs/assets/v2_guide_caveats.md.QQFtLQVa.js @@ -0,0 +1,159 @@ +import{_ as a,o as l,c as p,k as s,a as e,t as o,R as t}from"./chunks/framework._u06EGUx.js";const b=JSON.parse('{"title":"Caveats","description":"","frontmatter":{"title":"Caveats"},"headers":[],"relativePath":"v2/guide/caveats.md","filePath":"v2/guide/caveats.md"}'),c={name:"v2/guide/caveats.md"},r={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),E=t(`

v-font and custom head

When a v-font directive is called in a component with a custom head, the directive specific head settings must be applied in the head.

The method this.$speedkit.head(headAddition) is provided, it queries the required head settings and returns them.

By passing the headAddition argument, additional head settings can be applied.

WARNING

$speedkit.head() is only available in vue component scope.

Example

html
<template>
+  <span v-font="$getFont(…)"></span>
+</template>
+
+<script>
+  export default {
+    head () {
+      return this.$speedkit.head({
+        link: […],
+        style: […],
+        noscript: [
+          { hid: 'critical-css', innerHTML: '<style> … </style>' }
+        ],
+        __dangerouslyDisableSanitizers: [
+          'noscript'
+        ]
+      });
+    }
+  }
+</script>
<template>
+  <span v-font="$getFont(…)"></span>
+</template>
+
+<script>
+  export default {
+    head () {
+      return this.$speedkit.head({
+        link: […],
+        style: […],
+        noscript: [
+          { hid: 'critical-css', innerHTML: '<style> … </style>' }
+        ],
+        __dangerouslyDisableSanitizers: [
+          'noscript'
+        ]
+      });
+    }
+  }
+</script>

Issues

Browser compatibility

You can use nuxt-speedkit with Internet Explorer 11 browser.

INFO

Note that there is no optimization based on preloads in IE 11.

You need the following polyfills:

The PostCSS Plugin postcss-object-fit-images and following build.transpile entries for @nuxt/image:

  • @nuxt/image
  • image-meta

For the polyfills, it is recommended to integrate them as a plugin, polyfills loading must follow a specific order.

You can see a example with live demo at Nuxt Speedkit Example.

Example

js
async function polyfills (){
+
+  if (!('IntersectionObserver' in global)) {
+    await import('intersection-observer');
+  }
+
+  if (!('objectFit' in document.documentElement.style)) {
+    await import('object-fit-images');
+  }
+
+  if (!('HTMLPictureElement' in global || 'picturefill' in global)) {
+    await import('picturefill');
+    await import('picturefill/dist/plugins/mutation/pf.mutation.js');
+  }
+
+}
+
+polyfills ();
async function polyfills (){
+
+  if (!('IntersectionObserver' in global)) {
+    await import('intersection-observer');
+  }
+
+  if (!('objectFit' in document.documentElement.style)) {
+    await import('object-fit-images');
+  }
+
+  if (!('HTMLPictureElement' in global || 'picturefill' in global)) {
+    await import('picturefill');
+    await import('picturefill/dist/plugins/mutation/pf.mutation.js');
+  }
+
+}
+
+polyfills ();

js
{
+  build: {
+    
+    transpile: ['@nuxt/image', 'image-meta'],
+
+    postcss: {
+      plugins: {
+        'postcss-object-fit-images': {}
+      }
+    }
+    
+  },
+
+  plugins: [
+    { src: "@/plugins/polyfills.js", mode: "client" }
+  ]
+}
{
+  build: {
+    
+    transpile: ['@nuxt/image', 'image-meta'],
+
+    postcss: {
+      plugins: {
+        'postcss-object-fit-images': {}
+      }
+    }
+    
+  },
+
+  plugins: [
+    { src: "@/plugins/polyfills.js", mode: "client" }
+  ]
+}

Prevent SPEEDINDEX_OF_ZERO and NO_LCP

The window event nuxt-speedkit:run is provided and useable to run code outside the app during initialization.

If the performance is not sufficient on the client side, this can be retrieved with the help of the event object e.detail.sufficient.

Example

A case where the event may be needed would be when the initial viewport on a website is blank and it is not displayed until the initialization is complete.

In this case, measurements with Lighthouse can lead to these errors SPEEDINDEX_OF_ZERO and NO_LCP.

In order to solve this case, it can be provided that the content of the stage can already be displayed outside of the app initialization in the case of a slow initialization.

In this case the global event nuxt-speedkit:run can be used. It will return an event object with e.detail.sufficient as value. With the help of this status you can decide whether the stage should be displayed in advance.

Component Example

vue
<template>
+  <div class="stage">…</div>
+</template>
+
+<script>
+  export default {
+    head () {
+      return {
+        script: [
+          {
+            hid: 'prevent-script',
+            innerHTML: \`
+              window.addEventListener("nuxt-speedkit:run", function (e) {
+                if (!e.detail.sufficient) {
+                  // added style class to display the content
+                  document.querySelector('.stage').classList.add('visible')
+                }
+              });
+            \`
+          }
+        ],
+        __dangerouslyDisableSanitizers: [
+          'script'
+        ]
+      };
+    }
+  };
+</script>
<template>
+  <div class="stage">…</div>
+</template>
+
+<script>
+  export default {
+    head () {
+      return {
+        script: [
+          {
+            hid: 'prevent-script',
+            innerHTML: \`
+              window.addEventListener("nuxt-speedkit:run", function (e) {
+                if (!e.detail.sufficient) {
+                  // added style class to display the content
+                  document.querySelector('.stage').classList.add('visible')
+                }
+              });
+            \`
+          }
+        ],
+        __dangerouslyDisableSanitizers: [
+          'script'
+        ]
+      };
+    }
+  };
+</script>
`,32);function y(n,d,u,h,F,g){return l(),p("div",null,[s("h1",r,[e(o(n.$frontmatter.title)+" ",1),i]),E])}const f=a(c,[["render",y]]);export{b as __pageData,f as default}; diff --git a/docs/assets/v2_guide_caveats.md.QQFtLQVa.lean.js b/docs/assets/v2_guide_caveats.md.QQFtLQVa.lean.js new file mode 100644 index 0000000000..6df4498097 --- /dev/null +++ b/docs/assets/v2_guide_caveats.md.QQFtLQVa.lean.js @@ -0,0 +1 @@ +import{_ as a,o as l,c as p,k as s,a as e,t as o,R as t}from"./chunks/framework._u06EGUx.js";const b=JSON.parse('{"title":"Caveats","description":"","frontmatter":{"title":"Caveats"},"headers":[],"relativePath":"v2/guide/caveats.md","filePath":"v2/guide/caveats.md"}'),c={name:"v2/guide/caveats.md"},r={id:"frontmatter-title",tabindex:"-1"},i=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),E=t("",32);function y(n,d,u,h,F,g){return l(),p("div",null,[s("h1",r,[e(o(n.$frontmatter.title)+" ",1),i]),E])}const f=a(c,[["render",y]]);export{b as __pageData,f as default}; diff --git a/docs/assets/v2_guide_options.md.FbwA7bCa.js b/docs/assets/v2_guide_options.md.FbwA7bCa.js new file mode 100644 index 0000000000..5d0278c277 --- /dev/null +++ b/docs/assets/v2_guide_options.md.FbwA7bCa.js @@ -0,0 +1,115 @@ +import{_ as a,o,c as n,k as s,a as t,t as l,R as p}from"./chunks/framework._u06EGUx.js";const g=JSON.parse('{"title":"Options","description":"","frontmatter":{"title":"Options"},"headers":[],"relativePath":"v2/guide/options.md","filePath":"v2/guide/options.md"}'),c={name:"v2/guide/options.md"},r={id:"frontmatter-title",tabindex:"-1"},d=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),i=p(`

crossorigin

  • Type: String, Boolean
    • Default: 'anonymous'
    • Valid values: anonymous, use-credentials, '', true, false

Sets the global crossorigin value of the Nuxt Speedkit preloads.
The default value is the crossorigin value from the Render Configuration.

Set false to disable the crossorigin.

MDN - HTML.Attributes.crossorigin

optimizePreloads

  • Type: Boolean
    • Default: true

Activating this option optimizes the initial script preloads and removes unnecessary loads.

The following NuxtJS settings are made or overwritten in the nuxt.config:

PropertyValue
nuxt.options.generate.manifestfalse
nuxt.options.render.resourceHintstrue
nuxt.options.render.asyncScriptstrue
nuxt.options.render.http2.pushfalse

detection

  • Type: Object

These options can be used to define the initial checks to display the SpeedkitLayer. The prerequisite are that the SpeedkitLayer has been embedded into the layout.

js
{
+  performance: true,
+  browserSupport: true
+}
{
+  performance: true,
+  browserSupport: true
+}
KeyTypeRequiredDescriptionDefault
performanceBooleanyesChecking whether the minimum characteristic values have been reached. If the test is negative, the SpeedkitLayer will be displayed.true
browserSupportBooleanyesCheck if the current browser on client side is supported. If the test is negative, the SpeedkitLayer will be displayed.true

INFO

For the browser support detection, the default Browserslist of the NuxtJS configuration is used.

performanceMetrics

  • Type: Object

With the help of the metrics, the actual performance check on client side can be configured.

js
{
+  device: {
+    hardwareConcurrency: { min: 2, max: 48 },
+    deviceMemory: { min: 2 }
+  },
+  timing: {
+    fcp: 800,
+    dcl: 1200 // fallback if fcp is not available (safari)
+  }
+}
{
+  device: {
+    hardwareConcurrency: { min: 2, max: 48 },
+    deviceMemory: { min: 2 }
+  },
+  timing: {
+    fcp: 800,
+    dcl: 1200 // fallback if fcp is not available (safari)
+  }
+}

device

  • Type: Object

Definition of the minimum hardware requirements for visiting the website.

js
{
+  hardwareConcurrency: { min: 2, max: 48 },
+  deviceMemory: { min: 2 }
+}
{
+  hardwareConcurrency: { min: 2, max: 48 },
+  deviceMemory: { min: 2 }
+}
KeyTypeRequiredDescriptionDefault
hardwareConcurrencyObjectyesmin/max number of CPUs{ min: 2, max: 48 }
deviceMemoryObjectyesmin size of memory{ min: 2 }

timing

  • Type: Object

Definition of the max. FCP duration (ms). If the specified value is exceeded, the SpeedkitLayer will be displayed. If the browser does not grant access to the FCP, as fallback the DCL will be evaluated.

js
{
+  fcp: 800,
+  dcl: 1200 // fallback if fcp is not available (safari)
+}
{
+  fcp: 800,
+  dcl: 1200 // fallback if fcp is not available (safari)
+}
KeyTypeRequiredDescriptionDefault
fcpNumberyesMax. FCP duration in ms learn More800
dclNumberyesMax. DCL duration in ms1200

fonts

  • Type: Array

List of all font families used in the project. Only the fonts that are listed in the configuration can be retrieved and integrated via $fonts.getFont(...).

js
[
+  {
+    family: 'Font A',
+    locals: ['Font A'],
+    fallback: ['Arial', 'sans-serif'],
+    variances: […]
+  },
+  {
+    family: 'Font B',
+    locals: ['Font B'],
+    fallback: ['Arial', 'sans-serif'],
+    variances: […]
+  }
+]
[
+  {
+    family: 'Font A',
+    locals: ['Font A'],
+    fallback: ['Arial', 'sans-serif'],
+    variances: […]
+  },
+  {
+    family: 'Font B',
+    locals: ['Font B'],
+    fallback: ['Arial', 'sans-serif'],
+    variances: […]
+  }
+]

Font-Family

  • Type: Object

Describes a font family with all its variants.

js
{
+  family: 'Font A',
+  locals: ['Font A'],
+  fallback: ['Arial', 'sans-serif'],
+  variances: […]
+}
{
+  family: 'Font A',
+  locals: ['Font A'],
+  fallback: ['Arial', 'sans-serif'],
+  variances: […]
+}
KeyTypeRequiredDescription
familyStringyesname of the font family
localsArrayyessystem font name of the specified font family
fallbackArrayyesfallback fonts e.g. ['Arial', 'sans-serif']
variancesArrayyeslist of font family variants (e.g. Bold, Italic)

WARNING

Prevent sizing discrepancy between your custom and fallback font for perfect swap and reduction of layout shifts. Learn more

Font-Variance

  • Type: Object

A font variant describes an instance of a font family and is used to generate the FontFace declaration. Font variants differ in style and weight.

js
{
+  style: 'normal',
+  weight: 400,
+  sources: [
+    { src: '@/assets/fonts/font-a-regular.woff', type:'woff' },
+    { src: '@/assets/fonts/font-a-regular.woff2', type:'woff2' }
+  ]
+}
{
+  style: 'normal',
+  weight: 400,
+  sources: [
+    { src: '@/assets/fonts/font-a-regular.woff', type:'woff' },
+    { src: '@/assets/fonts/font-a-regular.woff2', type:'woff2' }
+  ]
+}
KeyTypeRequiredDescription
styleStringyesfont-style of FontFace, e.g. normal, italic
weightString or Numberyesfont-weight of FontFace, e.g. 400, normal
sourcesArrayyeslist of all font files assigned to the variant (sources)

sources

  • Type: Array

List of all available font files of a font family variation.

js
[
+  { src: '@/assets/fonts/font-a-regular.woff', type:'woff' },
+  { src: '@/assets/fonts/font-a-regular.woff2', type:'woff2' }
+]
[
+  { src: '@/assets/fonts/font-a-regular.woff', type:'woff' },
+  { src: '@/assets/fonts/font-a-regular.woff2', type:'woff2' }
+]
KeyTypeRequiredValue
srcStringyespath to a font file, the use of aliases is possible
typeStringyesfile format of the specified file, e.g. woff, woff2, …

targetFormats

  • Type: Array
    • Default: ['webp', 'avif', 'jpg|jpeg|png|gif']

Sets the default formats for the SpeedkitPicture.

Can be overridden in the SpeedkitPicture via the formats property.

For png, jpeg and gif formats we have added the | operator in the declaration.
This adjusts the destination format to the source format.

Example

Bad

The declaration below generates a png, jpeg and gif (destination format) for each jpeg (source format). The same applies to a png and a gif as source format. However, this is not practical for the source specifications in the Picture.

js
{
+  targetFormats: ['jpg', 'jpeg', 'png', 'gif']
+}
{
+  targetFormats: ['jpg', 'jpeg', 'png', 'gif']
+}

Good

Based on the source format, the appropriate target format is created using the declaration described below.

js
{
+  targetFormats: ['jpg|jpeg|png|gif']
+}
{
+  targetFormats: ['jpg|jpeg|png|gif']
+}

INFO

For the avif and webp formats the | operator is not needed, because these two image formats do not depend on the source format, as it is the case for png, jpeg and gif.

componentAutoImport

  • Type: Boolean
    • Default: false

With this attribute all components that can be found under #speedkit/components can be registered globally. Learn more @nuxt/components.

WARNING

This option is not recommended if you want to achieve a lighthouse score of 100/100.

Available components

Global NameImport Path
SpeedkitIframe#speedkit/components/SpeedkitIframeSource
SpeedkitLayer#speedkit/components/SpeedkitLayerSource
SpeedkitPicture#speedkit/components/SpeedkitPictureSource
SpeedkitYoutube#speedkit/components/SpeedkitYoutubeSource
AbstractComponentObserver#speedkit/components/abstracts/ComponentObserverSource
AbstractOnlySsr#speedkit/components/abstracts/OnlySsrSource

componentPrefix

  • Type: String
    • Default: undefined

Defines a prefix for the module components, important for auto import e.g. option componentAutoImport.

Example: SpeedkitPicture => PrefixSpeedkitPicture

lazyOffset

  • Type: Object

Global option for the IntersectionObserver built into the Nuxt Speedkit.

js
{
+  component: '0%',
+  asset: '0%' 
+}
{
+  component: '0%',
+  asset: '0%' 
+}
KeyTypeRequiredDescriptionDefault
componentStringyesrootMargin value for SpeedkitHydrate.0%
assetStringyesrootMargin value for all static ressources (v-font, SpeedkitPicture & SpeedkitImage).0%

loader

  • Type: Object

Defines the global built-in LoadingSpinner.

js
{
+  dataUri: undefined,
+  size: '100px',
+  backgroundColor: 'grey'
+}
{
+  dataUri: undefined,
+  size: '100px',
+  backgroundColor: 'grey'
+}
KeyTypeRequiredDescriptionDefault
dataUriStringnoDefines the source of the loader. (e.g. @/assets/spinner/three-circles.svg)undefined
sizeStringnoDefines the size of the loader. Use css background-size definition.100px
backgroundColorStringnoDefines the background color of the loader. Use css color definition.grey

disableNuxtImage

  • Type: Boolean
    • Default: false

If set, @nuxt/image will not be integrated.

DANGER

Note that the use of SpeedkitImage, SpeedkitPicture, SpeedkitVimeo and SpeedkitYoutube is not supported if @nuxt/image is not integrated.

`,87);function y(e,E,h,f,u,b){return o(),n("div",null,[s("h1",r,[t(l(e.$frontmatter.title)+" ",1),d]),i])}const F=a(c,[["render",y]]);export{g as __pageData,F as default}; diff --git a/docs/assets/v2_guide_options.md.FbwA7bCa.lean.js b/docs/assets/v2_guide_options.md.FbwA7bCa.lean.js new file mode 100644 index 0000000000..c25e3564e9 --- /dev/null +++ b/docs/assets/v2_guide_options.md.FbwA7bCa.lean.js @@ -0,0 +1 @@ +import{_ as a,o,c as n,k as s,a as t,t as l,R as p}from"./chunks/framework._u06EGUx.js";const g=JSON.parse('{"title":"Options","description":"","frontmatter":{"title":"Options"},"headers":[],"relativePath":"v2/guide/options.md","filePath":"v2/guide/options.md"}'),c={name:"v2/guide/options.md"},r={id:"frontmatter-title",tabindex:"-1"},d=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),i=p("",87);function y(e,E,h,f,u,b){return o(),n("div",null,[s("h1",r,[t(l(e.$frontmatter.title)+" ",1),d]),i])}const F=a(c,[["render",y]]);export{g as __pageData,F as default}; diff --git a/docs/assets/v2_guide_setup.md.Q4GqF1oR.js b/docs/assets/v2_guide_setup.md.Q4GqF1oR.js new file mode 100644 index 0000000000..1d497170f0 --- /dev/null +++ b/docs/assets/v2_guide_setup.md.Q4GqF1oR.js @@ -0,0 +1,201 @@ +import{_ as a,o as l,c as p,k as s,a as o,t as e,R as c}from"./chunks/framework._u06EGUx.js";const g=JSON.parse('{"title":"Setup","description":"","frontmatter":{"title":"Setup"},"headers":[],"relativePath":"v2/guide/setup.md","filePath":"v2/guide/setup.md"}'),t={name:"v2/guide/setup.md"},r={id:"frontmatter-title",tabindex:"-1"},E=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),y=c(`

Check the Nuxt.js documentation for more information about installing and using modules in Nuxt.js.

Installation

Install nuxt-speedkit as a dependency to your project:

bash
yarn add nuxt-speedkit
yarn add nuxt-speedkit
bash
npm install nuxt-speedkit
npm install nuxt-speedkit

Add nuxt-speedkit to the modules section of nuxt.config.js:

@nuxt/image

Nuxt Speedkit uses the module @nuxt/image, if this is not already present, it will be integrated automatically.

It is necessary for the use of the components SpeedkitYoutube and SpeedkitVimeo to add aliases and domains to the @nuxt/image options. These are needed to retrieve the images from Youtube and Vimeo.

js
{
+  domains: ['img.youtube.com', 'i.vimeocdn.com'],
+  alias: {
+    youtube: 'https://img.youtube.com',
+    vimeo: 'https://i.vimeocdn.com',
+  }
+}
{
+  domains: ['img.youtube.com', 'i.vimeocdn.com'],
+  alias: {
+    youtube: 'https://img.youtube.com',
+    vimeo: 'https://i.vimeocdn.com',
+  }
+}

More about @nuxt/image module options can be found here.

Example Configuration

js
{
+  modules: [
+    'nuxt-speedkit'
+  ],
+
+  speedkit: {
+
+    detection: {
+      performance: true,
+      browserSupport: true
+    },
+
+    performanceMetrics: {
+      device: {
+        hardwareConcurrency: { min: 2, max: 48 },
+        deviceMemory: { min: 2 }
+      },
+      timing: {
+        fcp: 800,
+        dcl: 1200
+      }
+    },
+
+    fonts: [{
+      family: 'Font A',
+      locals: ['Font A'],
+      fallback: ['Arial', 'sans-serif'],
+      variances: [
+        {
+          style: 'normal',
+          weight: 400,
+          sources: [
+            { src: '@/assets/fonts/font-a-regular.woff', type:'woff' },
+            { src: '@/assets/fonts/font-a-regular.woff2', type:'woff2' }
+          ]
+        }, {
+          style: 'italic',
+          weight: 400,
+          sources: [
+            { src: '@/assets/fonts/font-a-regularItalic.woff', type:'woff' },
+            { src: '@/assets/fonts/font-a-regularItalic.woff2', type:'woff2' }
+          ]
+        }, {
+          style: 'normal',
+          weight: 700,
+          sources: [
+            { src: '@/assets/fonts/font-a-700.woff', type:'woff' },
+            { src: '@/assets/fonts/font-a-700.woff2', type:'woff2' }
+          ]
+        }
+      ]
+    }],
+
+    targetFormats: ['webp', 'avif', 'jpg|jpeg|png|gif'],
+
+    componentAutoImport: false,
+    componentPrefix: undefined,
+
+    /**
+     * IntersectionObserver rootMargin for Compoennts and Assets
+     */
+    lazyOffset: {
+      component: '0%',
+      asset: '0%'
+    },
+
+    loader: {
+      dataUri: null,
+      size: '100px',
+      backgroundColor: 'grey'
+    }
+    
+  },
+
+  image: {
+    screens: {
+      default: 320,
+      xxs: 480,
+      xs: 576,
+      sm: 768,
+      md: 996,
+      lg: 1200,
+      xl: 1367,
+      xxl: 1600,
+      '4k': 1921
+    },
+
+    domains: ['img.youtube.com', 'i.vimeocdn.com'],
+
+    alias: {
+      youtube: 'https://img.youtube.com',
+      vimeo: 'https://i.vimeocdn.com',
+    }
+  }
+}
{
+  modules: [
+    'nuxt-speedkit'
+  ],
+
+  speedkit: {
+
+    detection: {
+      performance: true,
+      browserSupport: true
+    },
+
+    performanceMetrics: {
+      device: {
+        hardwareConcurrency: { min: 2, max: 48 },
+        deviceMemory: { min: 2 }
+      },
+      timing: {
+        fcp: 800,
+        dcl: 1200
+      }
+    },
+
+    fonts: [{
+      family: 'Font A',
+      locals: ['Font A'],
+      fallback: ['Arial', 'sans-serif'],
+      variances: [
+        {
+          style: 'normal',
+          weight: 400,
+          sources: [
+            { src: '@/assets/fonts/font-a-regular.woff', type:'woff' },
+            { src: '@/assets/fonts/font-a-regular.woff2', type:'woff2' }
+          ]
+        }, {
+          style: 'italic',
+          weight: 400,
+          sources: [
+            { src: '@/assets/fonts/font-a-regularItalic.woff', type:'woff' },
+            { src: '@/assets/fonts/font-a-regularItalic.woff2', type:'woff2' }
+          ]
+        }, {
+          style: 'normal',
+          weight: 700,
+          sources: [
+            { src: '@/assets/fonts/font-a-700.woff', type:'woff' },
+            { src: '@/assets/fonts/font-a-700.woff2', type:'woff2' }
+          ]
+        }
+      ]
+    }],
+
+    targetFormats: ['webp', 'avif', 'jpg|jpeg|png|gif'],
+
+    componentAutoImport: false,
+    componentPrefix: undefined,
+
+    /**
+     * IntersectionObserver rootMargin for Compoennts and Assets
+     */
+    lazyOffset: {
+      component: '0%',
+      asset: '0%'
+    },
+
+    loader: {
+      dataUri: null,
+      size: '100px',
+      backgroundColor: 'grey'
+    }
+    
+  },
+
+  image: {
+    screens: {
+      default: 320,
+      xxs: 480,
+      xs: 576,
+      sm: 768,
+      md: 996,
+      lg: 1200,
+      xl: 1367,
+      xxl: 1600,
+      '4k': 1921
+    },
+
+    domains: ['img.youtube.com', 'i.vimeocdn.com'],
+
+    alias: {
+      youtube: 'https://img.youtube.com',
+      vimeo: 'https://i.vimeocdn.com',
+    }
+  }
+}

See module options.

`,13);function i(n,F,d,u,C,f){return l(),p("div",null,[s("h1",r,[o(e(n.$frontmatter.title)+" ",1),E]),y])}const B=a(t,[["render",i]]);export{g as __pageData,B as default}; diff --git a/docs/assets/v2_guide_setup.md.Q4GqF1oR.lean.js b/docs/assets/v2_guide_setup.md.Q4GqF1oR.lean.js new file mode 100644 index 0000000000..ec18be1b60 --- /dev/null +++ b/docs/assets/v2_guide_setup.md.Q4GqF1oR.lean.js @@ -0,0 +1 @@ +import{_ as a,o as l,c as p,k as s,a as o,t as e,R as c}from"./chunks/framework._u06EGUx.js";const g=JSON.parse('{"title":"Setup","description":"","frontmatter":{"title":"Setup"},"headers":[],"relativePath":"v2/guide/setup.md","filePath":"v2/guide/setup.md"}'),t={name:"v2/guide/setup.md"},r={id:"frontmatter-title",tabindex:"-1"},E=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),y=c("",13);function i(n,F,d,u,C,f){return l(),p("div",null,[s("h1",r,[o(e(n.$frontmatter.title)+" ",1),E]),y])}const B=a(t,[["render",i]]);export{g as __pageData,B as default}; diff --git a/docs/assets/v2_guide_usage.md.DrnFp7qu.js b/docs/assets/v2_guide_usage.md.DrnFp7qu.js new file mode 100644 index 0000000000..92d6777190 --- /dev/null +++ b/docs/assets/v2_guide_usage.md.DrnFp7qu.js @@ -0,0 +1,35 @@ +import{_ as o,D as l,o as p,c as i,k as s,a as e,t as c,I as r,w as d,R as a}from"./chunks/framework._u06EGUx.js";const x=JSON.parse('{"title":"Usage","description":"","frontmatter":{"title":"Usage"},"headers":[],"relativePath":"v2/guide/usage.md","filePath":"v2/guide/usage.md"}'),h={name:"v2/guide/usage.md"},m={id:"frontmatter-title",tabindex:"-1"},y=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),E=a(`

The following tools are provided to optimize your webpage:

Critical prop for critical components

A critical component is visible in the viewport when the web page is initially loaded. This can be communicated to the automated background process via a critical prop. The flag is passed on to all child components. This means that only the main component (organism) must be provided with it. With the help of this flag, the corresponding static resources (images & fonts) are also declared as preload tags in the page head. All other components and their associated resources, that do not have a positive critical prop, are lazy loaded on demand.

html
<component :critical="true" />
<component :critical="true" />

INFO

In the current version, the critical flag must be set manually on the components. Automation would be conceivable in the future. However, according to current knowledge, this would have a massive impact on deployment times when using Puppeteer or similar tools. We are still collecting ideas here. If you know of a more efficient way, please send us a feature request.

Font declaration

The integration of fonts is component-based directly in the Vue template. All fonts, which have been declared in nuxt.config, can be assigned directly to the corresponding HTML element or component. In addition, subselectors and media queries can be defined, which enable viewport-based declarations or rich-text declarations. The cool thing about this is that it saves the additional declaration in the CSS. You no longer have to keep the template and the CSS with its corresponding selectors for fonts in sync. Yeah! This is extremely helpful, especially when it comes to theming.

html
<element v-font="$fonts.getFont(…)" />
<element v-font="$fonts.getFont(…)" />

Learn more about directive v-font.

WARNING

Fonts are no longer explicitly defined via CSS, otherwise the loading behavior of the fonts cannot be controlled and an optimized loading behavior of the page can no longer be guaranteed.

Import components

Until now, components were imported either statically (import component from '@/component';) or dynamically (import('@/component')). However, with these two variants, hydration cannot be controlled. As a result, all components are also initialized on initial load. nuxt-speedkit offers a corresponding loader for this feature request. Each async component import should be enclosed with this loader in a page or layout.

  • 'Ensures that components are initialized only when needed in the visible viewport.'
  • 'Optimizes initialization of critical components on initial page load (critical components are initially in the visible viewport).'

In the background, the module by Markus Oberlehner is used in a standardised way.

js
import speedkitHydrate from '#speedkit/hydrate';
+
+export default {
+  components: {
+    Stage: speedkitHydrate(() => import('@/components/organisms/Stage')),
+  }
+};
import speedkitHydrate from '#speedkit/hydrate';
+
+export default {
+  components: {
+    Stage: speedkitHydrate(() => import('@/components/organisms/Stage')),
+  }
+};
`,15),u=s("code",null,"rootMargin",-1),g=a(`

WARNING

Although the #speedkit/hydrate function can be used in any component, we recommend its explicit use only in pages and layout. Its use within components can be useful only in explicit special cases. Here we recommend the general use of static imports.

INFO

With NODE-ENV (development), the components are included directly.
This is relevant for the hot reload of the imported vue files.

Speedkit Components

In order to be able to load further static resources such as pictures, iFrames or Vimeo/Youtube videos in the iFrame in a performance-optimised way, we provide the following components. The speedkit components can be imported via the namespace #speedkit/components.

html
<template>
+  <speedkit-picture>
+</template>
+
+<script>
+import SpeedkitPicture from '#speedkit/components/SpeedkitPicture'
+export default {
+  components: {
+    SpeedkitPicture
+  }
+}
+</script>
<template>
+  <speedkit-picture>
+</template>
+
+<script>
+import SpeedkitPicture from '#speedkit/components/SpeedkitPicture'
+export default {
+  components: {
+    SpeedkitPicture
+  }
+}
+</script>

INFO

The speedkit components will be expanded in the future. If you have explicit wishes, please send us a feature request or directly a pull request with the corresponding feature 😃

Example

You can check out a sample integration of nuxt-speedkit at Nuxt Speedkit Example.

`,10);function f(n,b,v,k,_,w){const t=l("nuxt-link");return p(),i("div",null,[s("h1",m,[e(c(n.$frontmatter.title)+" ",1),y]),E,s("p",null,[e("Whether a component is in the viewport or not is determined in the background by the intersection observer. If the initialisation is to take place earlier, e.g. when scrolling, this can be adjusted accordingly via the "),u,e(" option in the "),r(t,{to:"/options#lazyoffset"},{default:d(()=>[e("nuxt.config")]),_:1}),e(".")]),g])}const S=o(h,[["render",f]]);export{x as __pageData,S as default}; diff --git a/docs/assets/v2_guide_usage.md.DrnFp7qu.lean.js b/docs/assets/v2_guide_usage.md.DrnFp7qu.lean.js new file mode 100644 index 0000000000..f893115480 --- /dev/null +++ b/docs/assets/v2_guide_usage.md.DrnFp7qu.lean.js @@ -0,0 +1 @@ +import{_ as o,D as l,o as p,c as i,k as s,a as e,t as c,I as r,w as d,R as a}from"./chunks/framework._u06EGUx.js";const x=JSON.parse('{"title":"Usage","description":"","frontmatter":{"title":"Usage"},"headers":[],"relativePath":"v2/guide/usage.md","filePath":"v2/guide/usage.md"}'),h={name:"v2/guide/usage.md"},m={id:"frontmatter-title",tabindex:"-1"},y=s("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),E=a("",15),u=s("code",null,"rootMargin",-1),g=a("",10);function f(n,b,v,k,_,w){const t=l("nuxt-link");return p(),i("div",null,[s("h1",m,[e(c(n.$frontmatter.title)+" ",1),y]),E,s("p",null,[e("Whether a component is in the viewport or not is determined in the background by the intersection observer. If the initialisation is to take place earlier, e.g. when scrolling, this can be adjusted accordingly via the "),u,e(" option in the "),r(t,{to:"/options#lazyoffset"},{default:d(()=>[e("nuxt.config")]),_:1}),e(".")]),g])}const S=o(h,[["render",f]]);export{x as __pageData,S as default}; diff --git a/docs/assets/v2_index.md.GB204pUc.js b/docs/assets/v2_index.md.GB204pUc.js new file mode 100644 index 0000000000..5751dc5d9b --- /dev/null +++ b/docs/assets/v2_index.md.GB204pUc.js @@ -0,0 +1 @@ +import{_ as r,o as a,c as o,k as e,a as i,t as n,R as s,a1 as l}from"./chunks/framework._u06EGUx.js";const _=JSON.parse('{"title":"Introduction","description":"","frontmatter":{"outline":"deep","title":"Introduction"},"headers":[],"relativePath":"v2/index.md","filePath":"v2/index.md"}'),d={name:"v2/index.md"},p={id:"frontmatter-title",tabindex:"-1"},u=e("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),h=s('

Module for NuxtJS.

WARNING

You are reading Nuxt Speedkit v2 docs. For Nuxt 3 go to the v3 docs

You are reading the documentation for Nuxt Speedkit (v2)!

Nuxt Speedkit takes over the lighthouse performance optimization of your generated website.

In order to achieve a performance score of 100/100, only the necessary resources located in the current viewport may be initialized when the page is loaded. This includes images, fonts and the js-modules. Until now, there has been no practical and usable concept to help developers maintain an overview and enable accurate targeting in NuxtJS projects.

This module addresses this problem and provides a holistic approach to intelligently load the necessary viewport related resources to reduce FCP, DCL, TTI, TBT and CLS.

We didn't reinvent the whole wheel. We adapt the lazy hydration concept of Markus Oberlehner to load js components in an efficient way, use the nuxt/image module as a base to retrieve optimized image resolutions for our picture and image components and add some new stuff to obtain a holistic solution.

Requirements

  • NodeJS >= 12.x.x
  • NuxtJS >= 2.15.0

Features

We provide the following CMS-friendly features:

  • dynamic loading of viewport based page resources like fonts, components, pictures, images and iframes
  • optional blocking of javascript execution by initial performance measuring
  • optimized initial load of javascript files by eliminating of unnecessary javascript files
  • prevents the loading of unnecessary resources (including components) that are outside the current viewport.
  • optional info layer concept to inform users about a reduced UX when bandwidth or hardware is compromised.
  • completely new approach of font declaration
  • optimized picture component (supports viewport based sources e.g. landscape/portrait)
  • optimized image component
  • supports SEO-friendly lazy hydration mode (picture + image)
  • optimized youtube/vimeo component (auto generated poster image in different resolutions)

Results

  • delivery of the minimum required resources based on the current viewport
  • if you use the tools as specified you will get a lighthouse performance score of 100/100

Demos

',17);function c(t,m,g,f,b,v){return a(),o("div",null,[e("h1",p,[i(n(t.$frontmatter.title)+" ",1),u]),h])}const x=r(d,[["render",c]]);export{_ as __pageData,x as default}; diff --git a/docs/assets/v2_index.md.GB204pUc.lean.js b/docs/assets/v2_index.md.GB204pUc.lean.js new file mode 100644 index 0000000000..6e33f2d964 --- /dev/null +++ b/docs/assets/v2_index.md.GB204pUc.lean.js @@ -0,0 +1 @@ +import{_ as r,o as a,c as o,k as e,a as i,t as n,R as s,a1 as l}from"./chunks/framework._u06EGUx.js";const _=JSON.parse('{"title":"Introduction","description":"","frontmatter":{"outline":"deep","title":"Introduction"},"headers":[],"relativePath":"v2/index.md","filePath":"v2/index.md"}'),d={name:"v2/index.md"},p={id:"frontmatter-title",tabindex:"-1"},u=e("a",{class:"header-anchor",href:"#frontmatter-title","aria-label":'Permalink to "{{$frontmatter.title}}"'},"​",-1),h=s("",17);function c(t,m,g,f,b,v){return a(),o("div",null,[e("h1",p,[i(n(t.$frontmatter.title)+" ",1),u]),h])}const x=r(d,[["render",c]]);export{_ as __pageData,x as default}; diff --git a/docs/components/speedkit-iframe.html b/docs/components/speedkit-iframe.html index eeb477f2e1..af73a4f865 100644 --- a/docs/components/speedkit-iframe.html +++ b/docs/components/speedkit-iframe.html @@ -5,14 +5,14 @@ SpeedkitIframe | Nuxt Speedkit - + - + - - - + + + @@ -56,7 +56,7 @@ @load="console.log('Iframe Loaded!')" @enter="console.log('Iframe enter viewport!')" />
NameDescription
loadTriggered when Iframe has finished loading.
enterTriggered when component has reached the viewport.
- + \ No newline at end of file diff --git a/docs/components/speedkit-image.html b/docs/components/speedkit-image.html index f4130dd3ac..092e72e79f 100644 --- a/docs/components/speedkit-image.html +++ b/docs/components/speedkit-image.html @@ -5,14 +5,14 @@ SpeedkitImage | Nuxt Speedkit - + - + - - - + + + @@ -118,7 +118,7 @@ />
<speedkit-image 
   @load="console.log('Image Loaded!')" 
 />
NameDescription
loadTriggered when the image resource has been completely loaded.
- + \ No newline at end of file diff --git a/docs/components/speedkit-layer.html b/docs/components/speedkit-layer.html index 5c4f4387e1..925be36968 100644 --- a/docs/components/speedkit-layer.html +++ b/docs/components/speedkit-layer.html @@ -5,14 +5,14 @@ SpeedkitLayer | Nuxt Speedkit - + - + - - - + + + @@ -122,7 +122,7 @@ </button> </div> </speedkit-layer>

Force App initialization

For Unsupported-Browser and Insufficient Hardware events, an onclick event must also be set with the id.

In the event, the global variable __NUXT_SPEEDKIT_AUTO_INIT__ must be set to true.

These are needed if the user has already reacted before the initial Javascript has been loaded. After the javascript has been successfully loaded, the app is automatically initialised.

VariableTypeDescriptionDefault
__NUXT_SPEEDKIT_AUTO_INIT__BooleanIf set, initialisation continues after the javascript has been fully loaded.false
- + \ No newline at end of file diff --git a/docs/components/speedkit-picture.html b/docs/components/speedkit-picture.html index 32824f58c9..9c92abcda2 100644 --- a/docs/components/speedkit-picture.html +++ b/docs/components/speedkit-picture.html @@ -5,14 +5,14 @@ SpeedkitPicture | Nuxt Speedkit - + - + - - - + + + @@ -114,7 +114,7 @@ />
<speedkit-picture 
   @load="console.log('Loaded!')" 
 />
NameDescription
loadTriggered when the image resource has been completely loaded.
- + \ No newline at end of file diff --git a/docs/components/speedkit-vimeo.html b/docs/components/speedkit-vimeo.html index 8d01b494bd..d357a863dd 100644 --- a/docs/components/speedkit-vimeo.html +++ b/docs/components/speedkit-vimeo.html @@ -5,14 +5,14 @@ SpeedkitVimeo | Nuxt Speedkit - + - + - - - + + + @@ -136,7 +136,7 @@ @ready="console.log('Player Ready!')" @playing="console.log('Player Playing!')" />
NameDescription
readyTriggered when Vimeo Player-SDK is completely loaded.
playingTriggered when video is finished loading and playing.
beforePlayerUsed to place elements in the player container (before).
afterPlayerUsed to place elements in the player container (after).
- + \ No newline at end of file diff --git a/docs/components/speedkit-youtube.html b/docs/components/speedkit-youtube.html index be49e8b53c..f52a7f0b90 100644 --- a/docs/components/speedkit-youtube.html +++ b/docs/components/speedkit-youtube.html @@ -5,14 +5,14 @@ SpeedkitYoutube | Nuxt Speedkit - + - + - - - + + + @@ -134,7 +134,7 @@ @ready="console.log('Player Ready!')" @playing="console.log('Player Playing!')" />
NameDescription
readyTriggered when Youtube-API is completely loaded.
playingTriggered when video is finished loading and playing.
beforePlayerUsed to place elements in the player container (before).
afterPlayerUsed to place elements in the player container (after).
- + \ No newline at end of file diff --git a/docs/components/weak-hardware-overlay.html b/docs/components/weak-hardware-overlay.html index 7cdcfadb36..05e53bcad0 100644 --- a/docs/components/weak-hardware-overlay.html +++ b/docs/components/weak-hardware-overlay.html @@ -5,19 +5,19 @@ WeakHardwareOverlay | Nuxt Speedkit - + - + - - - + + + -
Skip to content

WeakHardwareOverlay

The WeakHardwareOverlay is used in components that are affected by the SpeedkitLayer event Weak Hardware. (Example: Component requires the execution of mounted for functionality.)

INFO

The performance issue event occurs when initialization determines that the client is overloaded with execution and the user has confirmed the #nuxt-speedkit-button-init-reduced-view button in the SpeedkitLayer.

Basically, the overlay is used to make content visible when the Weak Hardware has occurred, if this does not occur, the overlay is not visible.

It is recommended to include an interaction element in the overlay that allows the user to switch to the normal state. For this the interaction element must get the ID nuxt-speedkit-button-init-app and reacts on click with the initialization of the app.

Example

Example of defining a custom WeakHardwareOverlay component and placing it in a target component that is affected by the Weak Hardware event.

Customize Overlay

vue
<template>
+    
Skip to content

WeakHardwareOverlay

The WeakHardwareOverlay is used in components that are affected by the SpeedkitLayer event Weak Hardware. (Example: Component requires the execution of mounted for functionality.)

INFO

The performance issue event occurs when initialization determines that the client is overloaded with execution and the user has confirmed the #nuxt-speedkit-button-init-reduced-view button in the SpeedkitLayer.

Basically, the overlay is used to make content visible when the Weak Hardware has occurred, if this does not occur, the overlay is not visible.

It is recommended to include an interaction element in the overlay that allows the user to switch to the normal state. For this the interaction element must get the ID nuxt-speedkit-button-init-app and reacts on click with the initialization of the app.

Example

Example of defining a custom WeakHardwareOverlay component and placing it in a target component that is affected by the Weak Hardware event.

Customize Overlay

vue
<template>
   <speedkit-weak-hardware-overlay>
     To improve your experience, extensive features have been disabled.<br>
     <button id="nuxt-speedkit-button-init-app">
@@ -65,7 +65,7 @@
   background: rgb(0 0 0 / 60%);
   backdrop-filter: blur(em(2px));
 }
-</style>

Usage Overlay

vue
<template>
+</style>

Usage Overlay

vue
<template>
   <div>
     <div ref="player" />
     <weak-hardware-overlay />
@@ -116,7 +116,7 @@
   backdrop-filter: blur(2px);
 }
 </style>
- + \ No newline at end of file diff --git a/docs/composables/useComponentObserver.html b/docs/composables/useComponentObserver.html index 19137dee24..f6e737e24d 100644 --- a/docs/composables/useComponentObserver.html +++ b/docs/composables/useComponentObserver.html @@ -5,14 +5,14 @@ useComponentObserver | Nuxt Speedkit - + - + - - - + + + @@ -46,7 +46,7 @@ }); </script>
- + \ No newline at end of file diff --git a/docs/composables/useConfig.html b/docs/composables/useConfig.html index 7f7e933999..a300abbd27 100644 --- a/docs/composables/useConfig.html +++ b/docs/composables/useConfig.html @@ -5,14 +5,14 @@ useConfig | Nuxt Speedkit - + - + - - - + + + @@ -20,7 +20,7 @@
Skip to content

useConfig

Return

Returns nuxt-speedkit public runtime options.

Example

js
import useConfig from '#speedkit/composables/config';
 const $speedkitOptions = useConfig();
import useConfig from '#speedkit/composables/config';
 const $speedkitOptions = useConfig();
- + \ No newline at end of file diff --git a/docs/composables/useCritical.html b/docs/composables/useCritical.html index 612924f4d9..750fd46ca3 100644 --- a/docs/composables/useCritical.html +++ b/docs/composables/useCritical.html @@ -5,14 +5,14 @@ useCritical | Nuxt Speedkit - + - + - - - + + + @@ -32,7 +32,7 @@ import useCritical from '#speedkit/composables/critical'; const { isCritical } = useCritical(); </script> - + \ No newline at end of file diff --git a/docs/composables/useFont.html b/docs/composables/useFont.html index 2537c5a1b1..044fa5e112 100644 --- a/docs/composables/useFont.html +++ b/docs/composables/useFont.html @@ -5,14 +5,14 @@ useFont | Nuxt Speedkit - + - + - - - + + + @@ -32,7 +32,7 @@ import useFonts from '#speedkit/composables/fonts'; const { $getFont } = useFonts(); </script> - + \ No newline at end of file diff --git a/docs/concept.html b/docs/concept.html index 630067f848..73e23e49fd 100644 --- a/docs/concept.html +++ b/docs/concept.html @@ -5,20 +5,20 @@ Concept | Nuxt Speedkit - + - + - - - + + +
Skip to content

Concept

Current Situation

The loading behavior of webpages based on NuxtJS is designed in such a way that all necessary Javascript resources are preloaded and directly initialized with the initial load of the page. However, this behavior creates a negative impact on the Lighthouse Performance Score (TTI) for larger pages that have an increased initial load of additional resources, such as fonts, images, plugins, modules (@nuxtjs/i18n, ...).

Excursus

The Lighthouse Test is not a tool to make a general statement about the quality of a website programming. Lighthouse rather tries to map a metric for the usability of a page from the user's point of view. This includes accessibility, best practices, SEO and of course performance.

This last point is often misinterpreted by developers. If you want to implement features that increase usability for the user (interactions/more complex animations, ...), this will always have an impact on performance in the Lighthouse Test for larger website projects, as the corresponding Javascript must be loaded for this. Finally, Lighthouse does also not rate the design, but the accessibility (size of click areas, etc.) of a website. You should therefore not ask yourself the following question: "How can I fully optimize my JavaScript to achieve a Lighthouse score of 100/100?". You have to ask yourself much more the question: "What is especially important to a user with low bandwidth or weak hardware on my site?".

The answer to this is relatively simple: the content must be accessible and you must be able to get to the information you need quickly.

No more and no less.

The user doesn't need any fancy slider animations and parallax effects that can only be implemented with certain libraries. Or a softload mechanism to get to more pages in a more elegant and animated way, but which initially needs an increased amount of javascript logic. All he wants is that information is retrievable reasonably fast and he can click through the presence.

Problem

The good news is that the NuxtJS SSR build provides the right foundation. The content is already in the form of HTML and CSS and can be used without Javascript. But what is missing

  • is a fully automated preload logic that allows component and viewport based handling and prioritization of the individual resources (FCP, LCP, CLS)
  • is a logic that enables a perfomance-oriented initialization of the javascript (TTI, TBT)

These two central points are handled by Nuxt Speedkit and enable a fast and resource-saving loading behavior of the website.

Approach

Over a longer period of time, we analyzed the Google Lighthouse test in more detail and approached the topic with the help of use cases. We did not start with the best case for page content (one image, one font, minimal javascript), but with the worst case (many images, many fonts, large Javascript files, ...). So we avoided to develop only a solution for simple SinglePages. Our claim was much more to create a generalistic, performant solution even with a CMS connection and dynamic component compositions per page. All our thoughts are based on HTTP/2 request prioritization and the lazy hydration approach. Initial resources are prioritized by preload and all further data is reloaded viewport-based.

Insights & Solutions

During the tests, we gained the following insights, which we would like to share with you, but which also allow us to draw conclusions regarding the performance optimization of the initial loading process and which have been incorporated into the Nuxt Speedkit solution.

Critical Render Path

The critical render path is the core of a high-performance and efficient loading and rendering behavior of a website. It is important that components and resources in the viewport are loaded and executed with priority so that the user can be provided with a functioning page as quickly as possible. A browser is not able to recognize this fully automatically to dynamically adapt the loading behavior. Some attempts have been made in the past to systematically identify the critical render path.However, this has the consequence that every generated page in a virtual browser has to be analyzed in given viewport sizes, which slows down the deployment process and makes it more error-prone. For this reason, we (the developers) will be forced to provide the build process with appropriate hints in the form of a Critical Attribute on the affected component, so that an automated optimization by preloads, lazy hydration, etc. can be performed in response.

Font Loading

Fonts are the great mystery on the Internet. For more complex designs it is not uncommon that more than 6 font files have to be loaded. It would be desirable if there were many more variable fonts, but the reality is usually different. Often, developers are forced to register tons of fonts with different font styles. So it can happen that the website needs a total count of 12 font files, which have to be loaded initially to achieve the right visual result on the whole page.

This is a real performance problem. If you look for solutions, you like to hear

  • don't use WebFonts that have to be loaded
  • use another optimized font
  • reduce the number of used fonts
  • embed the fonts via Base64

You will find some articles about font loading. But most of them are more than 3 years old. Summary: not much happened here. A nice and recommendable list of different strategies can be found at web-font-loading-recipes or comprehensive-webfonts. From this it can be deduced that there is still no universal solution to the problem. However, it is possible to approach the issue very efficiently by using a preload strategy and setting classes accordingly. However, this does not make the handling of the fonts any easier. On the one hand, the preloads have to be defined per page and on the other hand, the CSS in the respective component has to be activated with the corresponding font declaration per class on demand. This is manageable for smaller projects in a 1 person team. But if several people are working in parallel, it can quickly become a nightmare. This will inevitably lead to the fact that the approach will not be accepted by the team and the optimization will be optimized out of the project in the long run.

INFO

A few words about Google Fonts: If possible, the FontFaces should always be included directly as Woff/Woff2 files via inline style. The loading mechanism via external CSS file, as it is the case with Google Fonts, creates an additional network roundtrip, which delays the loading of the actual font files.

The strategy mentioned above makes sense, but is hardly implementable with the current tools. For this reason, we are introducing Directive v-font, which takes care of the outlined behavior in a fully automated way and thus represents a truly relevant solution even on larger projects. Combined with the lazy hydration approach, the relevant fonts can be declared and loaded per component. The preloads are controlled via the critical attribute. With the help of this loading strategy, a FOUT (flash of unstyled text) and CLS can be massively reduced or eliminated. If no javascript is activated on the end device, all fonts are automatically activated via CSS.

Image Loading

For image compression and different image formats, the module nuxt-optimized-images was popularly used in the nuxt world in the past. The downside, however, is that this approach is not particularly CMS and deployment friendly. With each image change, a full build process had to be initiated. For this reason, we use the nuxt-image module, as this takes advantage of a change in NuxtJS as of version 2.13.0. In this version update, the build was split into two separate processes (javascript compilation + page generation). With nuxt-optimized-images the full build process had to be run for every image change. This is no longer the case with nuxt-image. Here only the page generation process is necessary. As a result, deployment times for all content changes can be massively reduced.

We use the module in its complete form. However, we have redeveloped the nuxt-image and nuxt-picture components, as the current version does not fully meet our requirements. For example, we lacked an appropriate preloading and lazy hydration strategy. Although there is a native loading attribute on the image element that allows prioritization, the use for websites with a lot of images is still not optimal, because the distance-from-viewport threshold is still too generous and the loading performance can deteriorate unintentionally. For this we have implemented a corresponding SEO-compliant alternative, which loads the images only when the viewport is reached, but also provides the image sources for search engines via no-script tag. This way all relevant images can be displayed even if Javascript is disabled. Furthermore you can also define multiple image sources in the picture, so it is possible to display an image in portait mode with a 9/16 aspect-ratio (multiple renditions) and in landscape mode with a 16/9 aspect-ratio (multiple renditions).

Javascript Loading

NuxtJS follows the approach to load the core files (page, app, payload, vendor, state, etc.) as fast and efficient as possible via (module-)preload from the client. This also makes total sense if you want to deliver an SPA. For the SSR build, however, we modified the delivery a bit. The many parallel downloads (fonts, images, js, ...) have a negative impact from a performance perspective. This effect increases when the javascript files grow in size due to modules and plugins. It would make sense if the initial package is kept small and only the absolutely necessary resources that can trigger the further initialization process are transferred via dynamic import. This leaves enough bandwidth to load the remaining resources (fonts, images).

This loading behavior only makes sense with an SSR build, since the full page-related static content can already be delivered and rendered with the HTML and the included CSS. This means that the user does not notice any time lags and the page is still usable. Another advantage: If the bandwidth is low, a basic functionality of the page (links, ...) can be ensured thanks to the SSR build.

RequestIdleCallback

The TimeRemaining function of the IdleDeadline object continuously returns a value <= 10 in the Lighthouse Test (simulated Motorola G4). This can be seen as an indicator for weak hardware on the end device and allows the following conclusion. If there are not enough hardware resources available to execute the JavaScript quickly, this process is suppressed. Who needs optional functionality that takes a long time to initialize and possibly leads to a temporary freeze in the browser.

We use this effect by executing the intial javascript process and the component initialization in the RequestIdleCallback, if we get a time slot >10ms from the device. Hereby we achieve a massive reduction of the TTI/TBT in the Lighthouse Test and on weak hardware, because the javascript execution is simply paused in the worst case until sufficient resources are available. This also prevents blocking of the MainThread.

Side effect: The timeslots in the Google Lighthouse Test are always <= 10ms, so no javascript will be initialized.

SpeedkitLayer

With the solutions described above, the user gets a functioning webpage displayed very quickly. However, the following situation can also occur on the end device:

  • no Javascript enabled
  • reduced bandwidth
  • weak hardware
  • unsupported browser

The reduced bandwidth or weak hardware should get a focus especially when larger amounts of data have to be transferred and executed, e.g. a ThreeJS component with more complex 3D objects. In this case, we should inform the user that the experience will be negatively affected and that there may be waiting times.

For this purpose, we provide an InfoLayer that is displayed when a minimum FCP time is exceeded, when the number of available CPU cores falls below a minimum level, when javascript is disabled or the users opens the page by an unsupported browser. The user can decide in this dialog box whether he wants to load the remaining resources despite the restrictions. If the user declines this dialog, only the fonts and images for the page will be loaded and no further javascript will be loaded or executed.

Conclusion

The findings and solutions described above have been incorporated and systematized in the Nuxt Speedkit module. Only in combination can they unfold their full functionality and ensure an overall optimization of the loading behavior. Overall we have reduced the following timing metrics ...

  • FCP
  • LCP
  • TTI
  • TBT

With this module we enable every developer in the NuxtJS context to achieve a Lighthouse Performance Score 100/100 and drastically reduce the development time for website performance optimization.

- + \ No newline at end of file diff --git a/docs/directives/v-font.html b/docs/directives/v-font.html index c7f87072f9..3a436d208f 100644 --- a/docs/directives/v-font.html +++ b/docs/directives/v-font.html @@ -5,14 +5,14 @@ v-font | Nuxt Speedkit - + - + - - - + + + @@ -68,7 +68,7 @@ $getFont('Font Family B', 700, 'normal', { selector: 'b, strong', media: '(min-width: 768px)' }) ] - + \ No newline at end of file diff --git a/docs/examples/api-examples.html b/docs/examples/api-examples.html index 3fd6dbd2db..510da31e82 100644 --- a/docs/examples/api-examples.html +++ b/docs/examples/api-examples.html @@ -5,14 +5,14 @@ Runtime API Examples | Nuxt Speedkit - + - + - - - + + + @@ -373,7 +373,7 @@ }

Page Frontmatter

{
   "outline": "deep"
 }

More

Check out the documentation for the full list of runtime APIs.

- + \ No newline at end of file diff --git a/docs/examples/index.html b/docs/examples/index.html index c44b76a19a..20a730a92f 100644 --- a/docs/examples/index.html +++ b/docs/examples/index.html @@ -5,20 +5,20 @@ Nuxt Speedkit | Nuxt Speedkit - + - + - - - + + +
Skip to content

Nuxt Speedkit

Nuxt Speedkit takes over the Lighthouse performance optimization of your generated website.

My great project tagline

- + \ No newline at end of file diff --git a/docs/examples/markdown-examples.html b/docs/examples/markdown-examples.html index 4d8564df6d..f87d21c6bc 100644 --- a/docs/examples/markdown-examples.html +++ b/docs/examples/markdown-examples.html @@ -5,14 +5,14 @@ Markdown Extension Examples | Nuxt Speedkit - + - + - - - + + + @@ -82,7 +82,7 @@ ::: details This is a details block. :::

Output

INFO

This is an info box.

TIP

This is a tip.

WARNING

This is a warning.

DANGER

This is a dangerous warning.

Details

This is a details block.

More

Check out the documentation for the full list of markdown extensions.

- + \ No newline at end of file diff --git a/docs/guide/caveats.html b/docs/guide/caveats.html index 8d8ead851a..5e114572e7 100644 --- a/docs/guide/caveats.html +++ b/docs/guide/caveats.html @@ -5,14 +5,14 @@ Caveats | Nuxt Speedkit - + - + - - - + + + @@ -62,7 +62,7 @@ }) </script> - + \ No newline at end of file diff --git a/docs/guide/options.html b/docs/guide/options.html index 28f4f88f73..6039cfd768 100644 --- a/docs/guide/options.html +++ b/docs/guide/options.html @@ -5,14 +5,14 @@ Options | Nuxt Speedkit - + - + - - - + + + @@ -124,7 +124,7 @@ component: '0%', asset: '0%' }
KeyTypeRequiredDescriptionDefault
componentStringyesrootMargin value for SpeedkitHydrate.0%
assetStringyesrootMargin value for all static ressources (v-font, SpeedkitPicture & SpeedkitImage).0%

disableNuxtFontaine

  • Type: Boolean
    • Default: false

If set, @nuxtjs/fontaine will not be integrated.

disableNuxtImage

  • Type: Boolean
    • Default: false

If set, @nuxt/image will not be integrated.

DANGER

Note that the use of SpeedkitImage, SpeedkitPicture, SpeedkitVimeo and SpeedkitYoutube is not supported if @nuxt/image is not integrated.

- + \ No newline at end of file diff --git a/docs/guide/setup.html b/docs/guide/setup.html index b1a897ea5c..6b505c1d10 100644 --- a/docs/guide/setup.html +++ b/docs/guide/setup.html @@ -5,19 +5,19 @@ Setup | Nuxt Speedkit - + - + - - - + + + -
Skip to content

Setup

Check the Nuxt.js documentation for more information about installing and using modules in Nuxt.js.

Installation

Install nuxt-speedkit as a dependency to your project:

bash
yarn add nuxt-speedkit@next
yarn add nuxt-speedkit@next
bash
npm install nuxt-speedkit@next
npm install nuxt-speedkit@next

Add nuxt-speedkit to the modules section of nuxt.config.js:

@nuxt/image

Nuxt Speedkit uses the module @nuxt/image, if this is not already present, it will be integrated automatically.

It is necessary for the use of the components SpeedkitYoutube and SpeedkitVimeo to add aliases and domains to the @nuxt/image options. These are needed to retrieve the images from Youtube and Vimeo.

js
{
+    
Skip to content

Setup

Check the Nuxt.js documentation for more information about installing and using modules in Nuxt.js.

Installation

Install nuxt-speedkit as a dependency to your project:

bash
yarn add nuxt-speedkit@next
yarn add nuxt-speedkit@next
bash
npm install nuxt-speedkit@next
npm install nuxt-speedkit@next

Add nuxt-speedkit to the modules section of nuxt.config.js:

@nuxt/image

Nuxt Speedkit uses the module @nuxt/image, if this is not already present, it will be integrated automatically.

It is necessary for the use of the components SpeedkitYoutube and SpeedkitVimeo to add aliases and domains to the @nuxt/image options. These are needed to retrieve the images from Youtube and Vimeo.

js
{
   domains: ['img.youtube.com', 'i.vimeocdn.com'],
   alias: {
     youtube: 'https://img.youtube.com',
@@ -29,7 +29,7 @@
     youtube: 'https://img.youtube.com',
     vimeo: 'https://i.vimeocdn.com',
   }
-}

More about @nuxt/image module options can be found here.

Example Configuration

js
{
+}

More about @nuxt/image module options can be found here.

Example Configuration

js
{
   modules: [
     'nuxt-speedkit'
   ],
@@ -206,7 +206,7 @@
     }
   }
 }

See module options.

- + \ No newline at end of file diff --git a/docs/guide/usage.html b/docs/guide/usage.html index 19d5317dbc..022b10b384 100644 --- a/docs/guide/usage.html +++ b/docs/guide/usage.html @@ -5,14 +5,14 @@ Usage | Nuxt Speedkit - + - + - - - + + + @@ -52,7 +52,7 @@ } } </script>

INFO

The speedkit components will be expanded in the future. If you have explicit wishes, please send us a feature request or directly a pull request with the corresponding feature 😃

Example

You can check out a sample integration of nuxt-speedkit at Nuxt Speedkit Example.

- + \ No newline at end of file diff --git a/docs/hashmap.json b/docs/hashmap.json index 60816cbeef..dfd25237ea 100644 --- a/docs/hashmap.json +++ b/docs/hashmap.json @@ -1 +1 @@ -{"examples_index.md":"sidCw30q","examples_api-examples.md":"ObzwBcPJ","concept.md":"SjaPkrXu","index.md":"v-MVmz3M","migration_v2-2-0.md":"c8k9zRhJ","components_speedkit-image.md":"7fLTEMQe","components_speedkit-iframe.md":"Fie6swUb","composables_usecomponentobserver.md":"39xUlzqH","v1_components_experimental_speedkit-youtube.md":"bpl18kCe","migration_v2-0-13.md":"njAUbwxn","composables_useconfig.md":"SZ87FZNn","composables_usefont.md":"QWnANQbM","guide_setup.md":"MF2cLXBh","guide_usage.md":"2cmRwWxL","v1_components_experimental_speedkit-picture.md":"1laSHmUz","components_speedkit-vimeo.md":"dLLCqW0A","guide_caveats.md":"qq-EopOK","guide_options.md":"YEVKbwX8","components_weak-hardware-overlay.md":"3_oSScfa","v1_components_speedkit-iframe.md":"4nkRUP-d","components_speedkit-layer.md":"08GcO1fv","examples_markdown-examples.md":"IO_jZ2jq","v2_concept.md":"iK2Y_J-7","v2_index.md":"hOCeZiW8","v2_components_speedkit-picture.md":"SS83SaOd","v2_components_speedkit-layer.md":"VxuySd36","v1_guide_options.md":"b933oe2Y","v1_components_speedkit-picture.md":"aI99K7yn","v1_index.md":"CwZ4yKbg","components_speedkit-youtube.md":"w_VheRxq","v2_components_speedkit-iframe.md":"m5QRPFkN","v1_guide_usage.md":"qnsbJDsf","v2_classes_loading-spinner.md":"WHFuPmgf","migration_v3.md":"-07Xv_tv","migration_v2.md":"AZKRmkJd","directives_v-font.md":"emiYm-hF","v1_components_speedkit-layer.md":"yuKlywhL","composables_usecritical.md":"YdzX2xJ7","v2_guide_usage.md":"tEA1DBYA","v2_guide_setup.md":"wp5uIsqC","v2_guide_caveats.md":"hjxmLBqJ","components_speedkit-picture.md":"zGoCjmyy","v2_directives_v-font.md":"bC3A64Se","v2_components_speedkit-youtube.md":"lPXZND6T","v1_concept.md":"syk1TL-T","v1_guide_setup.md":"QPJ6Q593","v1_directives_v-font.md":"1yGb3WOD","v2_components_speedkit-image.md":"e_v9-b8-","v2_components_speedkit-vimeo.md":"VVsgidwN","v2_guide_options.md":"tywpzmB8","v1_components_speedkit-youtube.md":"ml3ferS1"} +{"components_speedkit-youtube.md":"MYGlq3t8","components_speedkit-layer.md":"IBUESyjR","components_speedkit-picture.md":"9XE6ev5S","concept.md":"s_9WUY8f","composables_useconfig.md":"HxJG3vdy","examples_markdown-examples.md":"M6_8vTVM","v2_concept.md":"p1bQg3c8","guide_options.md":"G-dxl61U","guide_setup.md":"5ri77JFP","v1_components_experimental_speedkit-picture.md":"8TZco5vW","components_speedkit-iframe.md":"c5N44rt-","migration_v2.md":"Jnf8W_4F","v2_guide_usage.md":"DrnFp7qu","composables_usefont.md":"zDVqwOCI","index.md":"itw9ycj3","v1_components_speedkit-iframe.md":"FFeQ4ub1","v1_components_speedkit-layer.md":"lVxe18Ze","guide_caveats.md":"rxJAUaDq","v1_components_experimental_speedkit-youtube.md":"m7szeORz","v2_classes_loading-spinner.md":"Kt9B_64I","migration_v2-2-0.md":"NbSpjQqB","composables_usecomponentobserver.md":"86iHNGdS","composables_usecritical.md":"nddRf2fx","examples_index.md":"UAHQB5IW","v2_components_speedkit-picture.md":"O5xJ3ujA","v1_guide_setup.md":"8rJExCV3","components_weak-hardware-overlay.md":"sQu1lie2","components_speedkit-vimeo.md":"VOcroeY4","v2_guide_setup.md":"Q4GqF1oR","migration_v2-0-13.md":"kI3DdF5o","v2_components_speedkit-iframe.md":"mfHg_6sq","v2_index.md":"GB204pUc","v1_components_speedkit-youtube.md":"42HHjCjS","directives_v-font.md":"vdRrO8ZL","components_speedkit-image.md":"ch9gdyuF","migration_v3.md":"pgAE9dPh","v1_directives_v-font.md":"kNiUmR7R","v1_index.md":"sX8weVju","v1_components_speedkit-picture.md":"8iUNQNQI","v2_directives_v-font.md":"JKoSYXEX","v2_components_speedkit-layer.md":"WkUt2OmF","v1_concept.md":"Z6zMo_qc","v2_components_speedkit-vimeo.md":"6hA686kW","v1_guide_options.md":"gNDuU5V1","v2_components_speedkit-youtube.md":"3WlGGiXM","guide_usage.md":"wgLYVO7n","examples_api-examples.md":"mC_SbDeW","v2_components_speedkit-image.md":"DHxRtycv","v2_guide_caveats.md":"QQFtLQVa","v1_guide_usage.md":"tutYb0dJ","v2_guide_options.md":"FbwA7bCa"} diff --git a/docs/index.html b/docs/index.html index a2e8ce4981..3077f4860f 100644 --- a/docs/index.html +++ b/docs/index.html @@ -5,20 +5,20 @@ Introduction | Nuxt Speedkit - + - + - - - + + +
Skip to content

Introduction

Module for NuxtJS.

You are reading the documentation for Nuxt Speedkit (v3)!

Nuxt Speedkit takes over the lighthouse performance optimization of your generated website.

In order to achieve a performance score of 100/100, only the necessary resources located in the current viewport may be initialized when the page is loaded. This includes images, fonts and the js-modules. Until now, there has been no practical and usable concept to help developers maintain an overview and enable accurate targeting in NuxtJS projects.

This module addresses this problem and provides a holistic approach to intelligently load the necessary viewport related resources to reduce FCP, DCL, TTI, TBT and CLS.

We didn't reinvent the whole wheel. We adapt the lazy hydration concept of Markus Oberlehner to load js components in an efficient way, use the nuxt/image module as a base to retrieve optimized image resolutions for our picture and image components and add some new stuff to obtain a holistic solution.

Requirements

  • NodeJS >= 19
  • NuxtJS >= 3.5.0

Features

We provide the following CMS-friendly features:

  • dynamic loading of viewport based page resources like fonts, components, pictures, images and iframes
  • optional blocking of javascript execution by initial performance measuring
  • optimized initial load of javascript files by eliminating of unnecessary javascript files
  • prevents the loading of unnecessary resources (including components) that are outside the current viewport.
  • optional info layer concept to inform users about a reduced UX when bandwidth or hardware is compromised.
  • completely new approach of font declaration
  • optimized picture component (supports viewport based sources e.g. landscape/portrait)
  • optimized image component
  • supports SEO-friendly lazy hydration mode (picture + image)
  • optimized youtube/vimeo component (auto generated poster image in different resolutions)

Results

  • delivery of the minimum required resources based on the current viewport
  • if you use the tools as specified you will get a lighthouse performance score of 100/100

Demos

- + \ No newline at end of file diff --git a/docs/migration/v2-0-13.html b/docs/migration/v2-0-13.html index 441c3aacc6..b18d2a4505 100644 --- a/docs/migration/v2-0-13.html +++ b/docs/migration/v2-0-13.html @@ -5,14 +5,14 @@ Migrate from v2.0.x to v2.0.13 | Nuxt Speedkit - + - + - - - + + + @@ -34,7 +34,7 @@ </li>
<li id="nuxt-speedkit-message-reduced-bandwidth">
   reduced-bandwidth
 </li>

Button Interactions

Button #nuxt-speedkit-button-init-font has been replaced by #nuxt-speedkit-button-init-reduced-view.

#nuxt-speedkit-button-init-reduced-view does the following when clicked:

  1. Sets the CSS class nuxt-speedkit-reduced-view on the html tag.
  2. Activates all fonts by setting the class font-active on all elements with the attribute data-font.
  3. Converts all not activated pictures (:hydrate="false") from noscript to picture.

INFO

The CSS class nuxt-speedkit-reduced-view is removed again at app initialization.

- + \ No newline at end of file diff --git a/docs/migration/v2-2-0.html b/docs/migration/v2-2-0.html index fbcb086d54..6cef96eabd 100644 --- a/docs/migration/v2-2-0.html +++ b/docs/migration/v2-2-0.html @@ -5,20 +5,20 @@ Migrate from v2.0.13 to v2.2.0 | Nuxt Speedkit - + - + - - - + + +
Skip to content

Migrate from v2.0.13 to v2.2.0

With the change to version 2.2.0 there are the following changes:

Package Structure

Package structure was updated.

Everything in the folder runtime is available with the alias #speedkit.

General

Old PathNew Path
nuxt-speedkit/hydrate#speedkit/hyrdate

Components

Old PathNew Path
nuxt-speedkit/components/abstracts/ComponentObserver#speedkit/components/abstracts/ComponentObserver
nuxt-speedkit/components/abstracts/OnlySsr#speedkit/components/abstracts/OnlySsr
nuxt-speedkit/components/GoogleLighthouse#speedkit/components/GoogleLighthouse
nuxt-speedkit/components/SpeedkitImage#speedkit/components/SpeedkitImage
nuxt-speedkit/components/SpeedkitPicture#speedkit/components/SpeedkitPicture
nuxt-speedkit/components/SpeedkitVimeo#speedkit/components/SpeedkitVimeo
nuxt-speedkit/components/SpeedkitYoutube#speedkit/components/SpeedkitYoutube
nuxt-speedkit/components/SpeedkitIframe#speedkit/components/SpeedkitIframe
nuxt-speedkit/components/SpeedkitImage#speedkit/components/SpeedkitImage
nuxt-speedkit/components/SpeedkitLayer#speedkit/components/SpeedkitLayer
nuxt-speedkit/components/SpeedkitPicture#speedkit/components/SpeedkitPicture
nuxt-speedkit/components/SpeedkitVimeo#speedkit/components/SpeedkitVimeo
nuxt-speedkit/components/SpeedkitYoutube#speedkit/components/SpeedkitYoutube

Utils

Old PathNew Path
nuxt-speedkit/utils#speedkit/utils
nuxt-speedkit/utils/base64#speedkit/utils/base64
nuxt-speedkit/utils/browser#speedkit/utils/browser
nuxt-speedkit/utils/description#speedkit/utils/description
nuxt-speedkit/utils/lighthouse#speedkit/utils/lighthouse
nuxt-speedkit/utils/mimeType#speedkit/utils/mimeType
nuxt-speedkit/utils/performance#speedkit/utils/performance
nuxt-speedkit/utils/placeholder#speedkit/utils/placeholder
nuxt-speedkit/utils/string#speedkit/utils/string
nuxt-speedkit/utils/support#speedkit/utils/support
- + \ No newline at end of file diff --git a/docs/migration/v2.html b/docs/migration/v2.html index 64d189f2a6..75a400e7cf 100644 --- a/docs/migration/v2.html +++ b/docs/migration/v2.html @@ -5,14 +5,14 @@ Migrate from v1 to v2 | Nuxt Speedkit - + - + - - - + + + @@ -52,7 +52,7 @@ { src: '/img/landscape.png', sizes: { md: '100vw' }, media: '(orientation: landscape)' }, { src: '/img/portrait.png', sizes: { default: '100vw', sm: '100vw' }, media: '(orientation: portrait)' } ]

More information about the integration of SpeedkitPicture can be found here.

WARNING

Important: In the new version of SpeedkitPicture the placeholder property is no longer included.

SpeedkitYoutube / SpeedkitYoutubeExperimental

With the change of the SpeedkitPicture also SpeedkitYoutube and SpeedkitYoutubeExperimental were reduced to SpeedkitYoutube.

The events loading and enter have been removed.

More information about the integration of SpeedkitYoutube can be found here.

SpeedkitIframe

Property intersectionObserver was renamed to componentObserver.

More information about the integration of SpeedkitIframe can be found here.

- + \ No newline at end of file diff --git a/docs/migration/v3.html b/docs/migration/v3.html index 16aa1580e2..0dc6180ccf 100644 --- a/docs/migration/v3.html +++ b/docs/migration/v3.html @@ -5,14 +5,14 @@ Migrate from v2 to v3 | Nuxt Speedkit - + - + - - - + + + @@ -32,7 +32,7 @@ import useFonts from '#speedkit/composables/fonts'; const { $getFont } = useFonts(); </script> - + \ No newline at end of file diff --git a/docs/v1/components/experimental/speedkit-picture.html b/docs/v1/components/experimental/speedkit-picture.html index 172afdc6b2..5662bb0799 100644 --- a/docs/v1/components/experimental/speedkit-picture.html +++ b/docs/v1/components/experimental/speedkit-picture.html @@ -5,14 +5,14 @@ SpeedkitPicture (Experimental) | Nuxt Speedkit - + - + - - - + + + @@ -88,7 +88,7 @@ @load="console.log('Loaded!')" @enter="console.log('Viewport!')" />
NameDescription
loadTriggered when the image resource has been completely loaded.
enterTriggered when component has reached the viewport.
- + \ No newline at end of file diff --git a/docs/v1/components/experimental/speedkit-youtube.html b/docs/v1/components/experimental/speedkit-youtube.html index 6c2dd07e4a..f75f62d5cf 100644 --- a/docs/v1/components/experimental/speedkit-youtube.html +++ b/docs/v1/components/experimental/speedkit-youtube.html @@ -5,14 +5,14 @@ SpeedkitYoutube (Experimental) | Nuxt Speedkit - + - + - - - + + + @@ -102,7 +102,7 @@ @playing="console.log('Video Playing!')" @enter="console.log('Viewport!')" />
NameDescription
readyTriggered when Youtube-Api is completely loaded.
loadingTriggered when video starts loading.
playingTriggered when video is finished loading and playing.
enterTriggered when component has reached the viewport.
- + \ No newline at end of file diff --git a/docs/v1/components/speedkit-iframe.html b/docs/v1/components/speedkit-iframe.html index 5987ff854b..673ec62e46 100644 --- a/docs/v1/components/speedkit-iframe.html +++ b/docs/v1/components/speedkit-iframe.html @@ -5,14 +5,14 @@ SpeedkitIframe | Nuxt Speedkit - + - + - - - + + + @@ -60,7 +60,7 @@ @load="console.log('Loaded!')" @enter="console.log('Enter Viewport!')" />
NameDescription
loadTriggered when Iframe has finished loading.
enterTriggered when component has reached the viewport.
- + \ No newline at end of file diff --git a/docs/v1/components/speedkit-layer.html b/docs/v1/components/speedkit-layer.html index d9d3a2e479..204b0e0359 100644 --- a/docs/v1/components/speedkit-layer.html +++ b/docs/v1/components/speedkit-layer.html @@ -5,14 +5,14 @@ SpeedkitLayer | Nuxt Speedkit - + - + - - - + + + @@ -142,7 +142,7 @@ </div> </speedkit-layer> - + \ No newline at end of file diff --git a/docs/v1/components/speedkit-picture.html b/docs/v1/components/speedkit-picture.html index 0d812845da..c4ff6c705a 100644 --- a/docs/v1/components/speedkit-picture.html +++ b/docs/v1/components/speedkit-picture.html @@ -5,14 +5,14 @@ SpeedkitPicture | Nuxt Speedkit - + - + - - - + + + @@ -246,7 +246,7 @@ url: 'portrait.jpg' // base64 or url } ]

Events

More on events at SpeedkitPicture (Experimental) - Events.

- + \ No newline at end of file diff --git a/docs/v1/components/speedkit-youtube.html b/docs/v1/components/speedkit-youtube.html index d2fc144a6f..2687037a62 100644 --- a/docs/v1/components/speedkit-youtube.html +++ b/docs/v1/components/speedkit-youtube.html @@ -5,14 +5,14 @@ SpeedkitYoutube | Nuxt Speedkit - + - + - - - + + + @@ -142,7 +142,7 @@ host: 'https://www.youtube-nocookie.com', config: { … } }

All properties except poster are identical to SpeedkitYoutube.

Learn more about SpeedkitYoutube (Experimental) - Properties.

poster

Poster is displayed as long as the player is not playing.

Events

More on events at SpeedkitYoutube (Experimental) - Events.

- + \ No newline at end of file diff --git a/docs/v1/concept.html b/docs/v1/concept.html index 6727a90428..85c5e272aa 100644 --- a/docs/v1/concept.html +++ b/docs/v1/concept.html @@ -5,20 +5,20 @@ Concept | Nuxt Speedkit - + - + - - - + + +
Skip to content

Concept

nuxt-speedkit is used to increase the initial loading performance of the website.
For this purpose, various tools are provided that optimise the loading and initialisation of resources (images, fonts) and components automatically and on demand.

This has the following impact:

1. Reduced initial download of the web page ::list

  • only the critical viewport resources will be loaded
  • all other resources will be loaded on demand, e.g. when scrolling. ::

2. Reduction of timing metrics

  • FCP
  • TTI
  • TBT

The module recognises the critical resources (images, fonts, javascript) for the initial load and preloads them when the page is called up directly. However, if an impairment of the UX is detected during the initialisation phase due to the following factors:

  • no Javascript enabled
  • reduced bandwidth
  • weak hardware
  • unsupported browser

the further initialisation process is paused and the user is given the decision whether to load the website completely (incl. Javascript) or to have only the static content (HTML, CSS, images and fonts) displayed. Through this loading behaviour, a correspondingly high performance score can be achieved even with a low bandwidth, as specified by the lighthouse test, for example. For the user, on the other hand, it becomes transparent why there may be delays in the display of complex components or static resources in the further course of the website visit.

For this reason, this module can only be used with NuxtJS, as this requires static HTML in order to continue to display the full content to the user despite uninitialised Javascript.

- + \ No newline at end of file diff --git a/docs/v1/directives/v-font.html b/docs/v1/directives/v-font.html index 93ad6b9d65..e2b13b85f0 100644 --- a/docs/v1/directives/v-font.html +++ b/docs/v1/directives/v-font.html @@ -5,14 +5,14 @@ v-font | Nuxt Speedkit - + - + - - - + + + @@ -67,7 +67,7 @@ // Font wird auf `b` und `strong` Tags angwendet und erscheint erst ab Viewport `>768px` $getFont('Font Family B', 700, 'normal', { selector: 'b, strong', media: '(min-width: 768px)' }) -]

Workarounds

Workarounds are used to work around a bug in the Vue SSR, read more in Usage.

Use component

html
<template>
+]

Workarounds

Workarounds are used to work around a bug in the Vue SSR, read more in Usage.

Use component

html
<template>
   <nuxt-link to="/" v-font="$getFont(…)">Back</nuxt-link>
 </template>
<template>
   <nuxt-link to="/" v-font="$getFont(…)">Back</nuxt-link>
@@ -79,7 +79,7 @@
   <nuxt-link to="/">
     <span v-font="$getFont(…)">Back</span>
   </nuxt-link>
-</template>

Use v-html/v-text

html
<template>
+</template>

Use v-html/v-text

html
<template>
   <div>
     <div v-font="$getFont(…)" v-html="…" />
   </div>
@@ -96,7 +96,7 @@
     <div v-html="…" />
   </div>
 </template>
- + \ No newline at end of file diff --git a/docs/v1/guide/options.html b/docs/v1/guide/options.html index 2e1a44fb08..2eb3c1ffa3 100644 --- a/docs/v1/guide/options.html +++ b/docs/v1/guide/options.html @@ -5,14 +5,14 @@ Options | Nuxt Speedkit - + - + - - - + + + @@ -122,7 +122,7 @@ // rootMargin for SpeedkitPicture and SpeedkitImage asset: '0%' }
KeyTypeRequiredDescriptionDefault
componentStringyesrootMargin value for speedkitComponents0%
assetStringyesrootMargin value for all static ressources (v-font und SpeedkitPicture)0%

disableNuxtImage

  • Type: Boolean
    • Default: false

If set, @nuxt/image will not be integrated.

⚠️  The following components can no longer be used:

- + \ No newline at end of file diff --git a/docs/v1/guide/setup.html b/docs/v1/guide/setup.html index c1135ad8d3..cdf39279ce 100644 --- a/docs/v1/guide/setup.html +++ b/docs/v1/guide/setup.html @@ -5,19 +5,19 @@ Setup | Nuxt Speedkit - + - + - - - + + + -
Skip to content

Setup

Check the Nuxt.js documentation for more information about installing and using modules in Nuxt.js.

Installation

Add nuxt-speedkit dependency to your project:

bash
yarn add nuxt-speedkit
yarn add nuxt-speedkit
bash
npm install nuxt-speedkit
npm install nuxt-speedkit

Then, add nuxt-speedkit to the modules section of nuxt.config.js:

js
{
+    
Skip to content

Setup

Check the Nuxt.js documentation for more information about installing and using modules in Nuxt.js.

Installation

Add nuxt-speedkit dependency to your project:

bash
yarn add nuxt-speedkit
yarn add nuxt-speedkit
bash
npm install nuxt-speedkit
npm install nuxt-speedkit

Then, add nuxt-speedkit to the modules section of nuxt.config.js:

js
{
   modules: [
     'nuxt-speedkit'
   ],
@@ -148,7 +148,7 @@
     }
   }
 }

See module options.

- + \ No newline at end of file diff --git a/docs/v1/guide/usage.html b/docs/v1/guide/usage.html index 2b45d3fc80..53f40ba6ec 100644 --- a/docs/v1/guide/usage.html +++ b/docs/v1/guide/usage.html @@ -5,14 +5,14 @@ Usage | Nuxt Speedkit - + - + - - - + + + @@ -47,7 +47,7 @@ SpeedkitPicture } } -</script>

INFO

The speedkit components will be expanded in the future. If you have explicit wishes, please send us a feature request or directly a pull request with the corresponding feature 😃

Example

You can check out a sample integration of nuxt-speedkit at Nuxt Speedkit Example.

:sandbox{src="https://codesandbox.io/embed/github/GrabarzUndPartner/nuxt-speedkit-example/tree/main/?hidenavigation=1&theme=dark"}

Browser compatibility

You can use nuxt-speedkit with Internet Explorer 11 browser.

INFO

Note that there is no optimization based on preloads in IE 11.

You need the following polyfills:

The PostCSS Plugin postcss-object-fit-images and following build.transpile entries for @nuxt/image:

  • @nuxt/image
  • image-meta

For the polyfills, it is recommended to integrate them as a plugin, polyfills loading must follow a specific order.

You can see a live example at Nuxt Speedkit Example.

Example

js
async function polyfills (){
+</script>

INFO

The speedkit components will be expanded in the future. If you have explicit wishes, please send us a feature request or directly a pull request with the corresponding feature 😃

Example

You can check out a sample integration of nuxt-speedkit at Nuxt Speedkit Example.

:sandbox{src="https://codesandbox.io/embed/github/GrabarzUndPartner/nuxt-speedkit-example/tree/main/?hidenavigation=1&theme=dark"}

Browser compatibility

You can use nuxt-speedkit with Internet Explorer 11 browser.

INFO

Note that there is no optimization based on preloads in IE 11.

You need the following polyfills:

The PostCSS Plugin postcss-object-fit-images and following build.transpile entries for @nuxt/image:

  • @nuxt/image
  • image-meta

For the polyfills, it is recommended to integrate them as a plugin, polyfills loading must follow a specific order.

You can see a live example at Nuxt Speedkit Example.

Example

js
async function polyfills (){
 
   if (!('IntersectionObserver' in global)) {
     await import('intersection-observer');
@@ -114,7 +114,7 @@
     { src: "@/plugins/polyfills.js", mode: "client" }
   ]
 }
- + \ No newline at end of file diff --git a/docs/v1/index.html b/docs/v1/index.html index 82c4d6f636..169db1736c 100644 --- a/docs/v1/index.html +++ b/docs/v1/index.html @@ -5,20 +5,20 @@ Introduction | Nuxt Speedkit - + - + - - - + + +
Skip to content

Introduction

Module for NuxtJS.

WARNING

You are reading Nuxt Speedkit v1 docs. For Nuxt 3 go to the v3 docs

Nuxt Speedkit takes over the lighthouse performance optimization of your generated website.

In order to achieve a performance score of 100/100, only the resources that are necessary in the current viewport may be loaded. Concepts already exist for the loading of javascript components and images. However, there is not yet a practicable concept for loading fonts dynamically. This module provides a holistic approach to load all necessary resources on demand, including fonts, based on the current viewport.

This module implements the lazy-hydration concept of Markus Oberlehner and embeds a modified version of nuxt/image.

Requirements

  • NodeJS >= 12.x.x
  • NuxtJS >= 2.15.0

Features

  • dynamic loading of viewport based page resources like fonts (subselectors, media queries), components, pictures
  • optional loading prevention of resources at low bandwidth or weak hardware
  • prevents the loading of unnecessary resources (including components) that are outside the current viewport.
  • optional info layer concept to inform users about a reduced UX when bandwidth or hardware is compromised.

Results

  • delivery of the minimum required resources based on the current viewport
  • if you use the tools as specified you will get a lighthouse performance score of 100/100
- + \ No newline at end of file diff --git a/docs/v2/classes/loading-spinner.html b/docs/v2/classes/loading-spinner.html index dfda489ead..8768232b9a 100644 --- a/docs/v2/classes/loading-spinner.html +++ b/docs/v2/classes/loading-spinner.html @@ -5,20 +5,20 @@ LoadingSpinner | Nuxt Speedkit - + - + - - - + + +
Skip to content

LoadingSpinner

Path: #speedkit/components/SpeedkitImage/classes/LoadingSpinner.js

The LoadingSpinner instance describes the visual appearance of the loading state in the SpeedkitImage. This can be defined globally via the module settings or at the specific components.

js
new LoadingSpinner({dataUri, size, backgroundColor});
new LoadingSpinner({dataUri, size, backgroundColor});

dataUri

  • Type: String

Defines the source of the loader. (e.g. @/assets/spinner/three-circles.svg)

size

  • Type: String

Defines the size of the loader. Use css background-size definition. (e.g. 100px)

backgroundColor

  • Type: String

Defines the background color of the loader. Use css color definition. (e.g. #fff)

- + \ No newline at end of file diff --git a/docs/v2/components/speedkit-iframe.html b/docs/v2/components/speedkit-iframe.html index ae3132106a..d67390fd5a 100644 --- a/docs/v2/components/speedkit-iframe.html +++ b/docs/v2/components/speedkit-iframe.html @@ -5,14 +5,14 @@ SpeedkitIframe | Nuxt Speedkit - + - + - - - + + + @@ -60,7 +60,7 @@ @load="console.log('Iframe Loaded!')" @enter="console.log('Iframe enter viewport!')" />
NameDescription
loadTriggered when Iframe has finished loading.
enterTriggered when component has reached the viewport.
- + \ No newline at end of file diff --git a/docs/v2/components/speedkit-image.html b/docs/v2/components/speedkit-image.html index 9ce732805a..20f0bdd508 100644 --- a/docs/v2/components/speedkit-image.html +++ b/docs/v2/components/speedkit-image.html @@ -5,14 +5,14 @@ SpeedkitImage | Nuxt Speedkit - + - + - - - + + + @@ -100,7 +100,7 @@ />
<speedkit-image 
   @load="console.log('Image Loaded!')" 
 />
NameDescription
loadTriggered when the image resource has been completely loaded.
- + \ No newline at end of file diff --git a/docs/v2/components/speedkit-layer.html b/docs/v2/components/speedkit-layer.html index 21a05880f6..104440d5dd 100644 --- a/docs/v2/components/speedkit-layer.html +++ b/docs/v2/components/speedkit-layer.html @@ -5,14 +5,14 @@ SpeedkitLayer | Nuxt Speedkit - + - + - - - + + + @@ -122,7 +122,7 @@ </button> </div> </speedkit-layer>

Force App initialization

For Unsupported-Browser and Insufficient Hardware events, an onclick event must also be set with the id.

In the event, the global variable __NUXT_SPEEDKIT_AUTO_INIT__ must be set to true.

These are needed if the user has already reacted before the initial Javascript has been loaded. After the javascript has been successfully loaded, the app is automatically initialised.

VariableTypeDescriptionDefault
__NUXT_SPEEDKIT_AUTO_INIT__BooleanIf set, initialisation continues after the javascript has been fully loaded.false
- + \ No newline at end of file diff --git a/docs/v2/components/speedkit-picture.html b/docs/v2/components/speedkit-picture.html index db4c2eada7..3e12ce05f4 100644 --- a/docs/v2/components/speedkit-picture.html +++ b/docs/v2/components/speedkit-picture.html @@ -5,14 +5,14 @@ SpeedkitPicture | Nuxt Speedkit - + - + - - - + + + @@ -96,7 +96,7 @@ />
<speedkit-picture 
   @load="console.log('Loaded!')" 
 />
NameDescription
loadTriggered when the image resource has been completely loaded.
- + \ No newline at end of file diff --git a/docs/v2/components/speedkit-vimeo.html b/docs/v2/components/speedkit-vimeo.html index d55f17ce52..772bec9be5 100644 --- a/docs/v2/components/speedkit-vimeo.html +++ b/docs/v2/components/speedkit-vimeo.html @@ -5,14 +5,14 @@ SpeedkitVimeo | Nuxt Speedkit - + - + - - - + + + @@ -122,7 +122,7 @@ @ready="console.log('Player Ready!')" @playing="console.log('Player Playing!')" />
NameDescription
readyTriggered when Vimeo Player-SDK is completely loaded.
playingTriggered when video is finished loading and playing.
- + \ No newline at end of file diff --git a/docs/v2/components/speedkit-youtube.html b/docs/v2/components/speedkit-youtube.html index 1a73a46f1a..7b55bdb588 100644 --- a/docs/v2/components/speedkit-youtube.html +++ b/docs/v2/components/speedkit-youtube.html @@ -5,14 +5,14 @@ SpeedkitYoutube | Nuxt Speedkit - + - + - - - + + + @@ -116,7 +116,7 @@ @ready="console.log('Player Ready!')" @playing="console.log('Player Playing!')" />
NameDescription
readyTriggered when Youtube-API is completely loaded.
playingTriggered when video is finished loading and playing.
- + \ No newline at end of file diff --git a/docs/v2/concept.html b/docs/v2/concept.html index c966aaabfd..eef250ff02 100644 --- a/docs/v2/concept.html +++ b/docs/v2/concept.html @@ -5,20 +5,20 @@ Concept | Nuxt Speedkit - + - + - - - + + +
Skip to content

Concept

Current Situation

The loading behavior of webpages based on NuxtJS is designed in such a way that all necessary Javascript resources are preloaded and directly initialized with the initial load of the page. However, this behavior creates a negative impact on the Lighthouse Performance Score (TTI) for larger pages that have an increased initial load of additional resources, such as fonts, images, plugins, modules (@nuxtjs/i18n, ...).

Excursus

The Lighthouse Test is not a tool to make a general statement about the quality of a website programming. Lighthouse rather tries to map a metric for the usability of a page from the user's point of view. This includes accessibility, best practices, SEO and of course performance.

This last point is often misinterpreted by developers. If you want to implement features that increase usability for the user (interactions/more complex animations, ...), this will always have an impact on performance in the Lighthouse Test for larger website projects, as the corresponding Javascript must be loaded for this. Finally, Lighthouse does also not rate the design, but the accessibility (size of click areas, etc.) of a website. You should therefore not ask yourself the following question: "How can I fully optimize my JavaScript to achieve a Lighthouse score of 100/100?". You have to ask yourself much more the question: "What is especially important to a user with low bandwidth or weak hardware on my site?".

The answer to this is relatively simple: the content must be accessible and you must be able to get to the information you need quickly.

No more and no less.

The user doesn't need any fancy slider animations and parallax effects that can only be implemented with certain libraries. Or a softload mechanism to get to more pages in a more elegant and animated way, but which initially needs an increased amount of javascript logic. All he wants is that information is retrievable reasonably fast and he can click through the presence.

Problem

The good news is that the NuxtJS SSR build provides the right foundation. The content is already in the form of HTML and CSS and can be used without Javascript. But what is missing

  • is a fully automated preload logic that allows component and viewport based handling and prioritization of the individual resources (FCP, LCP, CLS)
  • is a logic that enables a perfomance-oriented initialization of the javascript (TTI, TBT)

These two central points are handled by Nuxt Speedkit and enable a fast and resource-saving loading behavior of the website.

Approach

Over a longer period of time, we analyzed the Google Lighthouse test in more detail and approached the topic with the help of use cases. We did not start with the best case for page content (one image, one font, minimal javascript), but with the worst case (many images, many fonts, large Javascript files, ...). So we avoided to develop only a solution for simple SinglePages. Our claim was much more to create a generalistic, performant solution even with a CMS connection and dynamic component compositions per page. All our thoughts are based on HTTP/2 request prioritization and the lazy hydration approach. Initial resources are prioritized by preload and all further data is reloaded viewport-based.

Insights & Solutions

During the tests, we gained the following insights, which we would like to share with you, but which also allow us to draw conclusions regarding the performance optimization of the initial loading process and which have been incorporated into the Nuxt Speedkit solution.

Critical Render Path

The critical render path is the core of a high-performance and efficient loading and rendering behavior of a website. It is important that components and resources in the viewport are loaded and executed with priority so that the user can be provided with a functioning page as quickly as possible. A browser is not able to recognize this fully automatically to dynamically adapt the loading behavior. Some attempts have been made in the past to systematically identify the critical render path.However, this has the consequence that every generated page in a virtual browser has to be analyzed in given viewport sizes, which slows down the deployment process and makes it more error-prone. For this reason, we (the developers) will be forced to provide the build process with appropriate hints in the form of a Critical Attribute on the affected component, so that an automated optimization by preloads, lazy hydration, etc. can be performed in response.

Font Loading

Fonts are the great mystery on the Internet. For more complex designs it is not uncommon that more than 6 font files have to be loaded. It would be desirable if there were many more variable fonts, but the reality is usually different. Often, developers are forced to register tons of fonts with different font styles. So it can happen that the website needs a total count of 12 font files, which have to be loaded initially to achieve the right visual result on the whole page.

This is a real performance problem. If you look for solutions, you like to hear

  • don't use WebFonts that have to be loaded
  • use another optimized font
  • reduce the number of used fonts
  • embed the fonts via Base64

You will find some articles about font loading. But most of them are more than 3 years old. Summary: not much happened here. A nice and recommendable list of different strategies can be found at web-font-loading-recipes or comprehensive-webfonts. From this it can be deduced that there is still no universal solution to the problem. However, it is possible to approach the issue very efficiently by using a preload strategy and setting classes accordingly. However, this does not make the handling of the fonts any easier. On the one hand, the preloads have to be defined per page and on the other hand, the CSS in the respective component has to be activated with the corresponding font declaration per class on demand. This is manageable for smaller projects in a 1 person team. But if several people are working in parallel, it can quickly become a nightmare. This will inevitably lead to the fact that the approach will not be accepted by the team and the optimization will be optimized out of the project in the long run.

INFO

A few words about Google Fonts: If possible, the FontFaces should always be included directly as Woff/Woff2 files via inline style. The loading mechanism via external CSS file, as it is the case with Google Fonts, creates an additional network roundtrip, which delays the loading of the actual font files.

The strategy mentioned above makes sense, but is hardly implementable with the current tools. For this reason, we are introducing Directive v-font, which takes care of the outlined behavior in a fully automated way and thus represents a truly relevant solution even on larger projects. Combined with the lazy hydration approach, the relevant fonts can be declared and loaded per component. The preloads are controlled via the critical attribute. With the help of this loading strategy, a FOUT (flash of unstyled text) and CLS can be massively reduced or eliminated. If no javascript is activated on the end device, all fonts are automatically activated via CSS.

Image Loading

For image compression and different image formats, the module nuxt-optimized-images was popularly used in the nuxt world in the past. The downside, however, is that this approach is not particularly CMS and deployment friendly. With each image change, a full build process had to be initiated. For this reason, we use the nuxt-image module, as this takes advantage of a change in NuxtJS as of version 2.13.0. In this version update, the build was split into two separate processes (javascript compilation + page generation). With nuxt-optimized-images the full build process had to be run for every image change. This is no longer the case with nuxt-image. Here only the page generation process is necessary. As a result, deployment times for all content changes can be massively reduced.

We use the module in its complete form. However, we have redeveloped the nuxt-image and nuxt-picture components, as the current version does not fully meet our requirements. For example, we lacked an appropriate preloading and lazy hydration strategy. Although there is a native loading attribute on the image element that allows prioritization, the use for websites with a lot of images is still not optimal, because the distance-from-viewport threshold is still too generous and the loading performance can deteriorate unintentionally. For this we have implemented a corresponding SEO-compliant alternative, which loads the images only when the viewport is reached, but also provides the image sources for search engines via no-script tag. This way all relevant images can be displayed even if Javascript is disabled. Furthermore you can also define multiple image sources in the picture, so it is possible to display an image in portait mode with a 9/16 aspect-ratio (multiple renditions) and in landscape mode with a 16/9 aspect-ratio (multiple renditions).

Javascript Loading

NuxtJS follows the approach to load the core files (page, app, payload, vendor, state, etc.) as fast and efficient as possible via (module-)preload from the client. This also makes total sense if you want to deliver an SPA. For the SSR build, however, we modified the delivery a bit. The many parallel downloads (fonts, images, js, ...) have a negative impact from a performance perspective. This effect increases when the javascript files grow in size due to modules and plugins. It would make sense if the initial package is kept small and only the absolutely necessary resources that can trigger the further initialization process are transferred via dynamic import. This leaves enough bandwidth to load the remaining resources (fonts, images).

This loading behavior only makes sense with an SSR build, since the full page-related static content can already be delivered and rendered with the HTML and the included CSS. This means that the user does not notice any time lags and the page is still usable. Another advantage: If the bandwidth is low, a basic functionality of the page (links, ...) can be ensured thanks to the SSR build.

RequestIdleCallback

The TimeRemaining function of the IdleDeadline object continuously returns a value <= 10 in the Lighthouse Test (simulated Motorola G4). This can be seen as an indicator for weak hardware on the end device and allows the following conclusion. If there are not enough hardware resources available to execute the JavaScript quickly, this process is suppressed. Who needs optional functionality that takes a long time to initialize and possibly leads to a temporary freeze in the browser.

We use this effect by executing the intial javascript process and the component initialization in the RequestIdleCallback, if we get a time slot >10ms from the device. Hereby we achieve a massive reduction of the TTI/TBT in the Lighthouse Test and on weak hardware, because the javascript execution is simply paused in the worst case until sufficient resources are available. This also prevents blocking of the MainThread.

Side effect: The timeslots in the Google Lighthouse Test are always <= 10ms, so no javascript will be initialized.

SpeedkitLayer

With the solutions described above, the user gets a functioning webpage displayed very quickly. However, the following situation can also occur on the end device:

  • no Javascript enabled
  • reduced bandwidth
  • weak hardware
  • unsupported browser

The reduced bandwidth or weak hardware should get a focus especially when larger amounts of data have to be transferred and executed, e.g. a ThreeJS component with more complex 3D objects. In this case, we should inform the user that the experience will be negatively affected and that there may be waiting times.

For this purpose, we provide an InfoLayer that is displayed when a minimum FCP time is exceeded, when the number of available CPU cores falls below a minimum level, when javascript is disabled or the users opens the page by an unsupported browser. The user can decide in this dialog box whether he wants to load the remaining resources despite the restrictions. If the user declines this dialog, only the fonts and images for the page will be loaded and no further javascript will be loaded or executed.

Conclusion

The findings and solutions described above have been incorporated and systematized in the Nuxt Speedkit module. Only in combination can they unfold their full functionality and ensure an overall optimization of the loading behavior. Overall we have reduced the following timing metrics ...

  • FCP
  • LCP
  • TTI
  • TBT

With this module we enable every developer in the NuxtJS context to achieve a Lighthouse Performance Score 100/100 and drastically reduce the development time for website performance optimization.

- + \ No newline at end of file diff --git a/docs/v2/directives/v-font.html b/docs/v2/directives/v-font.html index 8cf1de7213..4dc2327264 100644 --- a/docs/v2/directives/v-font.html +++ b/docs/v2/directives/v-font.html @@ -5,14 +5,14 @@ v-font | Nuxt Speedkit - + - + - - - + + + @@ -96,7 +96,7 @@ <div v-html="…" /> </div> </template> - + \ No newline at end of file diff --git a/docs/v2/guide/caveats.html b/docs/v2/guide/caveats.html index f62e92b189..425a62e277 100644 --- a/docs/v2/guide/caveats.html +++ b/docs/v2/guide/caveats.html @@ -5,14 +5,14 @@ Caveats | Nuxt Speedkit - + - + - - - + + + @@ -55,7 +55,7 @@ }); } } -</script>

Issues

Browser compatibility

You can use nuxt-speedkit with Internet Explorer 11 browser.

INFO

Note that there is no optimization based on preloads in IE 11.

You need the following polyfills:

The PostCSS Plugin postcss-object-fit-images and following build.transpile entries for @nuxt/image:

  • @nuxt/image
  • image-meta

For the polyfills, it is recommended to integrate them as a plugin, polyfills loading must follow a specific order.

You can see a example with live demo at Nuxt Speedkit Example.

Example

js
async function polyfills (){
+</script>

Issues

Browser compatibility

You can use nuxt-speedkit with Internet Explorer 11 browser.

INFO

Note that there is no optimization based on preloads in IE 11.

You need the following polyfills:

The PostCSS Plugin postcss-object-fit-images and following build.transpile entries for @nuxt/image:

  • @nuxt/image
  • image-meta

For the polyfills, it is recommended to integrate them as a plugin, polyfills loading must follow a specific order.

You can see a example with live demo at Nuxt Speedkit Example.

Example

js
async function polyfills (){
 
   if (!('IntersectionObserver' in global)) {
     await import('intersection-observer');
@@ -89,7 +89,7 @@
 
 }
 
-polyfills ();

js
{
+polyfills ();

js
{
   build: {
     
     transpile: ['@nuxt/image', 'image-meta'],
@@ -176,7 +176,7 @@
     }
   };
 </script>
- + \ No newline at end of file diff --git a/docs/v2/guide/options.html b/docs/v2/guide/options.html index c06445b5b4..a7617ffe8d 100644 --- a/docs/v2/guide/options.html +++ b/docs/v2/guide/options.html @@ -5,14 +5,14 @@ Options | Nuxt Speedkit - + - + - - - + + + @@ -132,7 +132,7 @@ size: '100px', backgroundColor: 'grey' }
KeyTypeRequiredDescriptionDefault
dataUriStringnoDefines the source of the loader. (e.g. @/assets/spinner/three-circles.svg)undefined
sizeStringnoDefines the size of the loader. Use css background-size definition.100px
backgroundColorStringnoDefines the background color of the loader. Use css color definition.grey

disableNuxtImage

  • Type: Boolean
    • Default: false

If set, @nuxt/image will not be integrated.

DANGER

Note that the use of SpeedkitImage, SpeedkitPicture, SpeedkitVimeo and SpeedkitYoutube is not supported if @nuxt/image is not integrated.

- + \ No newline at end of file diff --git a/docs/v2/guide/setup.html b/docs/v2/guide/setup.html index 727258fa14..f628460893 100644 --- a/docs/v2/guide/setup.html +++ b/docs/v2/guide/setup.html @@ -5,19 +5,19 @@ Setup | Nuxt Speedkit - + - + - - - + + + -
Skip to content

Setup

Check the Nuxt.js documentation for more information about installing and using modules in Nuxt.js.

Installation

Install nuxt-speedkit as a dependency to your project:

bash
yarn add nuxt-speedkit
yarn add nuxt-speedkit
bash
npm install nuxt-speedkit
npm install nuxt-speedkit

Add nuxt-speedkit to the modules section of nuxt.config.js:

@nuxt/image

Nuxt Speedkit uses the module @nuxt/image, if this is not already present, it will be integrated automatically.

It is necessary for the use of the components SpeedkitYoutube and SpeedkitVimeo to add aliases and domains to the @nuxt/image options. These are needed to retrieve the images from Youtube and Vimeo.

js
{
+    
Skip to content

Setup

Check the Nuxt.js documentation for more information about installing and using modules in Nuxt.js.

Installation

Install nuxt-speedkit as a dependency to your project:

bash
yarn add nuxt-speedkit
yarn add nuxt-speedkit
bash
npm install nuxt-speedkit
npm install nuxt-speedkit

Add nuxt-speedkit to the modules section of nuxt.config.js:

@nuxt/image

Nuxt Speedkit uses the module @nuxt/image, if this is not already present, it will be integrated automatically.

It is necessary for the use of the components SpeedkitYoutube and SpeedkitVimeo to add aliases and domains to the @nuxt/image options. These are needed to retrieve the images from Youtube and Vimeo.

js
{
   domains: ['img.youtube.com', 'i.vimeocdn.com'],
   alias: {
     youtube: 'https://img.youtube.com',
@@ -29,7 +29,7 @@
     youtube: 'https://img.youtube.com',
     vimeo: 'https://i.vimeocdn.com',
   }
-}

More about @nuxt/image module options can be found here.

Example Configuration

js
{
+}

More about @nuxt/image module options can be found here.

Example Configuration

js
{
   modules: [
     'nuxt-speedkit'
   ],
@@ -218,7 +218,7 @@
     }
   }
 }

See module options.

- + \ No newline at end of file diff --git a/docs/v2/guide/usage.html b/docs/v2/guide/usage.html index be8292d30a..791b815721 100644 --- a/docs/v2/guide/usage.html +++ b/docs/v2/guide/usage.html @@ -5,14 +5,14 @@ Usage | Nuxt Speedkit - + - + - - - + + + @@ -52,7 +52,7 @@ } } </script>

INFO

The speedkit components will be expanded in the future. If you have explicit wishes, please send us a feature request or directly a pull request with the corresponding feature 😃

Example

You can check out a sample integration of nuxt-speedkit at Nuxt Speedkit Example.

- + \ No newline at end of file diff --git a/docs/v2/index.html b/docs/v2/index.html index 4f64b62af8..24c77b2345 100644 --- a/docs/v2/index.html +++ b/docs/v2/index.html @@ -5,20 +5,20 @@ Introduction | Nuxt Speedkit - + - + - - - + + +
Skip to content

Introduction

Module for NuxtJS.

WARNING

You are reading Nuxt Speedkit v2 docs. For Nuxt 3 go to the v3 docs

You are reading the documentation for Nuxt Speedkit (v2)!

Nuxt Speedkit takes over the lighthouse performance optimization of your generated website.

In order to achieve a performance score of 100/100, only the necessary resources located in the current viewport may be initialized when the page is loaded. This includes images, fonts and the js-modules. Until now, there has been no practical and usable concept to help developers maintain an overview and enable accurate targeting in NuxtJS projects.

This module addresses this problem and provides a holistic approach to intelligently load the necessary viewport related resources to reduce FCP, DCL, TTI, TBT and CLS.

We didn't reinvent the whole wheel. We adapt the lazy hydration concept of Markus Oberlehner to load js components in an efficient way, use the nuxt/image module as a base to retrieve optimized image resolutions for our picture and image components and add some new stuff to obtain a holistic solution.

Requirements

  • NodeJS >= 12.x.x
  • NuxtJS >= 2.15.0

Features

We provide the following CMS-friendly features:

  • dynamic loading of viewport based page resources like fonts, components, pictures, images and iframes
  • optional blocking of javascript execution by initial performance measuring
  • optimized initial load of javascript files by eliminating of unnecessary javascript files
  • prevents the loading of unnecessary resources (including components) that are outside the current viewport.
  • optional info layer concept to inform users about a reduced UX when bandwidth or hardware is compromised.
  • completely new approach of font declaration
  • optimized picture component (supports viewport based sources e.g. landscape/portrait)
  • optimized image component
  • supports SEO-friendly lazy hydration mode (picture + image)
  • optimized youtube/vimeo component (auto generated poster image in different resolutions)

Results

  • delivery of the minimum required resources based on the current viewport
  • if you use the tools as specified you will get a lighthouse performance score of 100/100

Demos

- + \ No newline at end of file