text: (function(e){function t(t){for(var n,r,i=t[0],c=t[1],l=t[2],d=0,u=[];d<i.length;d++)r=i[d],Object.prototype.hasOwnProperty.call(a,r)&&a[r]&&u.push(a[r][0]),a[r]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);p&&p(t);while(u.length)u.shift()();return o.push.apply(o,l||[]),s()}function s(){for(var e,t=0;t<o.length;t++){for(var s=o[t],n=!0,i=1;i<s.length;i++){var c=s[i];0!==a[c]&&(n=!1)}n&&(o.splice(t--,1),e=r(r.s=s[0]))}return e}var n={},a={app:0},o=[];function r(t){if(n[t])return n[t].exports;var s=n[t]={i:t,l:!1,exports:{}};return e[t].call(s.exports,s,s.exports,r),s.l=!0,s.exports}r.m=e,r.c=n,r.d=function(e,t,s){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:s})},r.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var s=Object.create(null);if(r.r(s),Object.defineProperty(s,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)r.d(s,n,function(t){return e[t]}.bind(null,n));return s},r.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="/vuejs-composition-api-course/";var i=window["webpackJsonp"]=window["webpackJsonp"]||[],c=i.push.bind(i);i.push=t,i=i.slice();for(var l=0;l<i.length;l++)t(i[l]);var p=c;o.push([0,"chunk-vendors"]),s()})({0:function(e,t,s){e.exports=s("cd49")},"0847":function(e,t,s){"use strict";s("d4a2")},"0a44":function(e,t,s){},"151f":function(e,t,s){"use strict";s("4608")},4608:function(e,t,s){},4678:function(e,t,s){var n={"./af":"2bfb","./af.js":"2bfb","./ar":"8e73","./ar-ma":"0a84","./ar-ma.js":"0a84","./ar-sa":"8230","./ar-sa.js":"8230","./ar-tn":"6d83","./ar-tn.js":"6d83","./ar.js":"8e73","./az":"485c","./az.js":"485c","./be":"1fc1","./be.js":"1fc1","./bg":"84aa","./bg.js":"84aa","./bn":"9043","./bn.js":"9043","./bo":"d26a","./bo.js":"d26a","./br":"6887","./br.js":"6887","./bs":"2554","./bs.js":"2554","./ca":"d716","./ca.js":"d716","./cs":"3c0d","./cs.js":"3c0d","./cv":"03ec","./cv.js":"03ec","./cy":"9797","./cy.js":"9797","./da":"0f14","./da.js":"0f14","./de":"b469","./de-at":"b3eb","./de-at.js":"b3eb","./de.js":"b469","./dv":"598a","./dv.js":"598a","./el":"8d47","./el.js":"8d47","./en-au":"0e6b","./en-au.js":"0e6b","./en-ca":"3886","./en-ca.js":"3886","./en-gb":"39a6","./en-gb.js":"39a6","./en-ie":"e1d3","./en-ie.js":"e1d3","./en-nz":"6f50","./en-nz.js":"6f50","./eo":"65db","./eo.js":"65db","./es":"898b","./es.js":"898b","./et":"ec18","./et.js":"ec18","./eu":"0ff2","./eu.js":"0ff2","./fa":"8df4","./fa.js":"8df4","./fi":"81e9","./fi.js":"81e9","./fo":"0721","./fo.js":"0721","./fr":"9f26","./fr-ca":"d9f8","./fr-ca.js":"d9f8","./fr-ch":"0e49","./fr-ch.js":"0e49","./fr.js":"9f26","./fy":"7118","./fy.js":"7118","./gd":"f6b4","./gd.js":"f6b4","./gl":"8840","./gl.js":"8840","./he":"c7aa","./he.js":"c7aa","./hi":"dc4d","./hi.js":"dc4d","./hr":"4ba9","./hr.js":"4ba9","./hu":"5b14","./hu.js":"5b14","./hy-am":"d6b6","./hy-am.js":"d6b6","./id":"5038","./id.js":"5038","./is":"0558","./is.js":"0558","./it":"6e98","./it.js":"6e98","./ja":"079e","./ja.js":"079e","./jv":"b540","./jv.js":"b540","./ka":"201b","./ka.js":"201b","./kk":"6d79","./kk.js":"6d79","./km":"e81d","./km.js":"e81d","./ko":"22f8","./ko.js":"22f8","./ky":"9609","./ky.js":"9609","./lb":"440c","./lb.js":"440c","./lo":"b29d","./lo.js":"b29d","./lt":"26f9","./lt.js":"26f9","./lv":"b97c","./lv.js":"b97c","./me":"293c","./me.js":"293c","./mk":"6909","./mk.js":"6909","./ml":"02fb","./ml.js":"02fb","./mr":"39bd","./mr.js":"39bd","./ms":"ebe4","./ms-my":"6403","./ms-my.js":"6403","./ms.js":"ebe4","./my":"8689","./my.js":"8689","./nb":"6ce3","./nb.js":"6ce3","./ne":"3a39","./ne.js":"3a39","./nl":"facd","./nl.js":"facd","./nn":"b84c","./nn.js":"b84c","./pa-in":"f3ff","./pa-in.js":"f3ff","./pl":"8d57","./pl.js":"8d57","./pt":"f260","./pt-br":"d2d4","./pt-br.js":"d2d4","./pt.js":"f260","./ro":"972c","./ro.js":"972c","./ru":"957c","./ru.js":"957c","./se":"ffff","./se.js":"ffff","./si":"eda5","./si.js":"eda5","./sk":"7be6","./sk.js":"7be6","./sl":"8155","./sl.js":"8155","./sq":"c8f3","./sq.js":"c8f3","./sr":"cf1e","./sr-cyrl":"13e9","./sr-cyrl.js":"13e9","./sr.js":"cf1e","./ss":"52bd","./ss.js":"52bd","./sv":"5fbd","./sv.js":"5fbd","./sw":"74dc","./sw.js":"74dc","./ta":"3de5","./ta.js":"3de5","./te":"5cbb","./te.js":"5cbb","./th":"10e8","./th.js":"10e8","./tl-ph":"0f38","./tl-ph.js":"0f38","./tlh":"cf75","./tlh.js":"cf75","./tr":"0e81","./tr.js":"0e81","./tzl":"cf51","./tzl.js":"cf51","./tzm":"c109","./tzm-latn":"b53d","./tzm-latn.js":"b53d","./tzm.js":"c109","./uk":"ada2","./uk.js":"ada2","./uz":"2e8c","./uz.js":"2e8c","./vi":"2921","./vi.js":"2921","./x-pseudo":"fd7e","./x-pseudo.js":"fd7e","./zh-cn":"5c3a","./zh-cn.js":"5c3a","./zh-tw":"90ea","./zh-tw.js":"90ea"};function a(e){var t=o(e);return s(t)}function o(e){if(!s.o(n,e)){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}return n[e]}a.keys=function(){return Object.keys(n)},a.resolve=o,e.exports=a,a.id="4678"},"4ba1":function(e,t,s){"use strict";s("f00e")},9250:function(e,t,s){"use strict";s("0a44")},cd49:function(e,t,s){"use strict";s.r(t);var n=s("7a23");const a=Object(n["i"])("div",{class:"modal-background"},null,-1),o=Object(n["i"])("div",{class:"modal-content"},[Object(n["i"])("div",{id:"modal"})],-1),r={class:"section"},i={class:"container"},c=Object(n["i"])("div",{class:"notification is-info is-size-3"},[Object(n["i"])("p",null," Learn to build this application with Vue.js 3, the Composition API, TypeScript, Vue Router and Vue Test Utils! "),Object(n["i"])("p",null,[Object(n["i"])("a",{href:"https://gum.co/vuejs-composition-api"},"Get it on Gumroad."),Object(n["h"])(" 👈🏼 20% off with "),Object(n["i"])("b",null,"VUE_SHARE_THE_LOVE"),Object(n["h"])(" 🎉. ")])],-1);function l(e,t,s,l,p,d){const u=Object(n["B"])("navbar"),h=Object(n["B"])("router-view");return Object(n["t"])(),Object(n["f"])(n["a"],null,[Object(n["i"])("div",{class:"modal",style:e.style},[a,o,Object(n["i"])("button",{onClick:t[1]||(t[1]=(...t)=>e.hide&&e.hide(...t)),class:"modal-close is-large"})],4),Object(n["i"])("section",r,[Object(n["i"])("div",i,[c,Object(n["i"])(u),Object(n["i"])(h)])])],64)}const p={class:"navbar"},d={class:"navbar-end"},u={key:0,class:"buttons"},h=Object(n["h"])(" New Post "),m={key:1,class:"buttons"};function f(e,t,s,a,o,r){const i=Object(n["B"])("router-link");return Object(n["t"])(),Object(n["f"])(n["a"],null,[Object(n["i"])("div",p,[Object(n["i"])("div",d,[e.auth?(Object(n["t"])(),Object(n["f"])("div",u,[Object(n["i"])(i,{class:"button",to:"/posts/new"},{default:Object(n["I"])(()=>[h]),_:1}),Object(n["i"])("button",{class:"button",onClick:t[1]||(t[1]=(...t)=>e.signOut&&e.signOut(...t))}," Sign Out ")])):(Object(n["t"])(),Object(n["f"])("div",m,[Object(n["i"])("button",{class:"button","data-test":"sign-up",onClick:t[2]||(t[2]=(...t)=>e.signUp&&e.signUp(...t))}," Sign Up "),Object(n["i"])("button",{class:"button",onClick:t[3]||(t[3]=(...t)=>e.signIn&&e.signIn(...t))}," Sign In ")]))])]),(Object(n["t"])(),Object(n["f"])(n["c"],{to:"#modal"},[(Object(n["t"])(),Object(n["f"])(Object(n["C"])(e.component)))]))],64)}var y=s("6c02");const g=Object(n["L"])("data-v-44253169"),b=g((e,t,s,a,o,r)=>{const i=Object(n["B"])("form-input");return Object(n["t"])(),Object(n["f"])("form",{onSubmit:t[3]||(t[3]=Object(n["K"])((...t)=>e.submit&&e.submit(...t),["prevent"]))},[Object(n["i"])(i,{modelValue:e.username,"onUpdate:modelValue":t[1]||(t[1]=t=>e.username=t),name:"Username",type:"text",error:e.usernameStatus.message},null,8,["modelValue","error"]),Object(n["i"])(i,{modelValue:e.password,"onUpdate:modelValue":t[2]||(t[2]=t=>e.password=t),name:"Password",type:"password",error:e.passwordStatus.message},null,8,["modelValue","error"]),Object(n["i"])("button",{class:"button is-primary",disabled:!e.usernameStatus.valid||!e.passwordStatus.valid}," Submit ",8,["disabled"])],32)}),j={class:"field"},w={class:"control"},v={class:"is-danger help"};function S(e,t,s,a,o,r){return Object(n["t"])(),Object(n["f"])("div",j,[Object(n["i"])("label",{class:"label",name:e.name},Object(n["E"])(e.name),9,["name"]),Object(n["i"])("div",w,[Object(n["i"])("input",{class:"input",id:e.name,type:e.type,value:e.modelValue,onInput:t[1]||(t[1]=(...t)=>e.handleInput&&e.handleInput(...t))},null,40,["id","type","value"])]),Object(n["i"])("p",v,Object(n["E"])(e.error),1)])}var k=Object(n["j"])({props:{name:{type:String,required:!0},type:{type:String,default:"text"},modelValue:{type:String,required:!0},error:{type:String}},setup(e,t){const s=e=>{t.emit("update:modelValue",e.target.value)};return{handleInput:s}}});k.render=S;var O=k;function x(){return{type:"required"}}function T(e){return{type:"length",options:e}}function A(e,t){for(const s of t){if("required"===s.type&&(!e||0===e.length))return{valid:!1,message:"This value is required."};if("length"===s.type&&(e.length<s.options.min||e.length>s.options.max))return{valid:!1,message:`The value must be between ${s.options.min} and ${s.options.max}`}}return{valid:!0}}var I=s("bc3a"),W=s.n(I);const C=Symbol("store");class V{constructor(e){this.state=Object(n["x"])(e)}install(e){e.provide(C,this)}getState(){return Object(n["y"])(this.state)}async createPost(e){const t=await W.a.post("/posts",e);this.state.posts.all.set(t.data.id,t.data),this.state.posts.ids.push(t.data.id)}async updatePost(e){const t=await W.a.put("/posts",e);this.state.posts.all.set(t.data.id,t.data)}async createUser(e){const t=await W.a.post("/users",e);this.state.authors.all.set(t.data.id,t.data),this.state.authors.ids.push(t.data.id),this.state.authors.currentUserId=t.data.id,console.log(this.state.authors)}async signIn(e){const t=await W.a.post("/sign_in",e);this.state.authors.all.set("1",t.data),this.state.authors.ids.push("1"),this.state.authors.currentUserId="1"}async fetchPosts(){const e=await W.a.get("/posts"),t={ids:[],all:new Map,loaded:!0};for(const s of e.data)t.ids.push(s.id),t.all.set(s.id,s);this.state.posts=t}signOut(){this.state.authors.currentUserId=void 0}}const _=new Map,P=new V({authors:{all:new Map,ids:[],loaded:!1,currentUserId:"1"},posts:{all:_,ids:[],loaded:!1}});function q(){const e=Object(n["m"])(C);if(!e)throw Error("Did you forgot to call provide?");return e}P.getState().posts.loaded;const M=Object(n["z"])(!1),E=Object(n["z"])();function R(){return{component:E,show:M,showModal:()=>M.value=!0,hideModal:()=>M.value=!1}}var U=Object(n["j"])({components:{FormInput:O},setup(){const e=Object(n["z"])("username"),t=Object(n["d"])(()=>A(e.value,[x(),T({min:5,max:10})])),s=Object(n["z"])("password"),a=Object(n["d"])(()=>A(s.value,[x(),T({min:10,max:40})])),o=q(),r=R(),i=async n=>{if(!t.value.valid||!a.value.valid)return;const i={id:"-1",username:e.value,password:s.value};await o.createUser(i),r.hideModal()};return{usernameStatus:t,username:e,submit:i,password:s,passwordStatus:a}}});s("151f");U.render=b,U.__scopeId="data-v-44253169";var B=U;const L=Object(n["L"])("data-v-03e5fcd1");Object(n["w"])("data-v-03e5fcd1");const N=Object(n["i"])("p",null,'Test account is username: "user123", password: "pass42".',-1);Object(n["u"])();const D=L((e,t,s,a,o,r)=>{const i=Object(n["B"])("form-input");return Object(n["t"])(),Object(n["f"])("form",{onSubmit:t[3]||(t[3]=Object(n["K"])((...t)=>e.submit&&e.submit(...t),["prevent"]))},[N,Object(n["i"])(i,{modelValue:e.username,"onUpdate:modelValue":t[1]||(t[1]=t=>e.username=t),name:"Username",type:"text",error:e.usernameStatus.message},null,8,["modelValue","error"]),Object(n["i"])(i,{modelValue:e.password,"onUpdate:modelValue":t[2]||(t[2]=t=>e.password=t),name:"Password",type:"password",error:e.passwordStatus.message},null,8,["modelValue","error"]),Object(n["i"])("button",{class:"button is-primary",disabled:!e.usernameStatus.valid||!e.passwordStatus.valid}," Submit ",8,["disabled"])],32)});var z=Object(n["j"])({components:{FormInput:O},setup(){const e=Object(n["z"])(""),t=Object(n["d"])(()=>A(e.value,[x()])),s=Object(n["z"])(""),a=Object(n["d"])(()=>A(s.value,[x()])),o=q(),r=R(),i=async n=>{if(!t.value.valid||!a.value.valid)return;const i={id:"-1",username:e.value,password:s.value};await o.signIn(i),r.hideModal()};return{usernameStatus:t,username:e,submit:i,password:s,passwordStatus:a}}});s("4ba1");z.render=D,z.__scopeId="data-v-03e5fcd1";var F=z,H=Object(n["j"])({components:{Signup:B},setup(){const e=R(),t=q(),s=Object(y["d"])(),a=Object(n["d"])(()=>!!t.getState().authors.currentUserId),o=()=>{e.component.value=Object(n["n"])(F),e.showModal()},r=()=>{e.component.value=Object(n["n"])(B),e.showModal()},i=()=>{t.signOut(),s.push("/")};return{component:e.component,signIn:o,signUp:r,signOut:i,auth:a}}});H.render=f;var Y=H,J=Object(n["j"])({name:"App",components:{Navbar:Y,FormInput:O},setup(){const e=R(),t=Object(n["d"])(()=>({display:e.show.value?"block":"none"}));return{style:t,hide:()=>{e.hideModal()}}}});s("0847");J.render=l;var G=J,K=s("c1df"),$=s.n(K),X="Vue 3 has a super neat reactivity system based on the ES6 [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) object. In this article we build a simplified version of the reactivity system. We will stay as close the [Vue 3 source code](https://github.com/vuejs/vue-next/tree/master/packages/reactivity) as possible. The idea is to prepare you better to read and understand it.\n\nThe source code repository is for this article is exclusive to my [GitHub Sponsors](https://github.com/sponsors/lmiller1990).\n\nBy the end of this article, you'll be in a position to read through Vue's `reactivity` package and have a general understand of what's going on under the hood.\n\nAs we work through our implementation, we will compare what we have written to the actual source code and see what's different, and why.\n\nThe initial goal will be the following:\n\n```ts\ntest('ref', () => {\n  const fooref = ref('foo')\n  let foo\n  effect(() => {\n    foo = fooref.value\n  })\n  foo //=> 'foo'\n  expect(foo).toBe('foo')\n  fooref.value = 'bar'\n  expect(foo).toBe('bar') // foo is now 'bar' via reactivity!\n})\n```\n\nThe implementation will scale to something a (little) more interesting, too:\n\n```ts\ntest('multiple refs', () => {\n  const fooref = ref('')\n  const barref = ref('')\n  let foo\n  let foobar\n\n  effect(() => {\n    foo = fooref.value\n    foobar = fooref.value + barref.value\n  })\n\n  expect(foo).toBe('')\n  expect(foobar).toBe('')\n\n  fooref.value = 'foo'\n  expect(foo).toBe('foo')\n  expect(foobar).toBe('foo')\n\n  barref.value = 'bar'\n  expect(foo).toBe('foo')\n  expect(foobar).toBe('foobar')\n})\n```\n\nIf you have used Vue with the Composition API, you will be familiar with `ref`. You may not have seen `effect` - that's because it's not actually exposed to the end user. What you will be familiar with is `watch`, `watchEffect` and `computed` - the idea is more or less the same. All of these, as well as Vue's reactivity APIs, are powered by `effect` under the hood. Since we are doing everything from scratch, we will need to implement `effect` before implementing the rest.\n\nWrapping something in `effect` sets up reactivity. Take this snippet:\n\n```ts\nconst fooref = ref('foo')\nlet foo\neffect(() => {\n  foo = fooref.value\n})\nfooref.value = 'bar'\nconsole.log(foo) // now it's 'bar' via reactivity!\n```\n\nBy wrapping `foo = fooref.value` in `effect`, we are saying that the value of `foo` depends on the value of `fooref.value`. Now, any time that `fooref.value` changes, the `effect` will be executed and `foo` will also be updated.\n",Q='<p>Vue 3 has a super neat reactivity system based on the ES6 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy">Proxy</a> object. In this article we build a simplified version of the reactivity system. We will stay as close the <a href="https://github.com/vuejs/vue-next/tree/master/packages/reactivity">Vue 3 source code</a> as possible. The idea is to prepare you better to read and understand it.</p>\n<p>The source code repository is for this article is exclusive to my <a href="https://github.com/sponsors/lmiller1990">GitHub Sponsors</a>.</p>\n<p>By the end of this article, you&#39;ll be in a position to read through Vue&#39;s <code>reactivity</code> package and have a general understand of what&#39;s going on under the hood.</p>\n<p>As we work through our implementation, we will compare what we have written to the actual source code and see what&#39;s different, and why.</p>\n<p>The initial goal will be the following:</p>\n<pre><code class="language-ts">test(&#x27;ref&#x27;, () =&gt; {\n  const fooref = ref(&#x27;foo&#x27;)\n  let foo\n  effect(() =&gt; {\n    foo = fooref.value\n  })\n  foo //=&gt; &#x27;foo&#x27;\n  expect(<span class="hljs-name">foo</span>).toBe(&#x27;foo&#x27;)\n  fooref.value = &#x27;bar&#x27;\n  expect(<span class="hljs-name">foo</span>).toBe(&#x27;bar&#x27;) // foo is now &#x27;bar&#x27; via reactivity!\n})\n</code></pre>\n<p>The implementation will scale to something a (little) more interesting, too:</p>\n<pre><code class="language-ts">test(&#x27;multiple refs&#x27;, () =&gt; {\n  const fooref = ref(&#x27;&#x27;)\n  const barref = ref(&#x27;&#x27;)\n  let foo\n  let foobar\n\n  effect(() =&gt; {\n    foo = fooref.value\n    foobar = fooref.value + barref.value\n  })\n\n  expect(<span class="hljs-name">foo</span>).toBe(&#x27;&#x27;)\n  expect(<span class="hljs-name">foobar</span>).toBe(&#x27;&#x27;)\n\n  fooref.value = &#x27;foo&#x27;\n  expect(<span class="hljs-name">foo</span>).toBe(&#x27;foo&#x27;)\n  expect(<span class="hljs-name">foobar</span>).toBe(&#x27;foo&#x27;)\n\n  barref.value = &#x27;bar&#x27;\n  expect(<span class="hljs-name">foo</span>).toBe(&#x27;foo&#x27;)\n  expect(<span class="hljs-name">foobar</span>).toBe(&#x27;foobar&#x27;)\n})\n</code></pre>\n<p>If you have used Vue with the Composition API, you will be familiar with <code>ref</code>. You may not have seen <code>effect</code> - that&#39;s because it&#39;s not actually exposed to the end user. What you will be familiar with is <code>watch</code>, <code>watchEffect</code> and <code>computed</code> - the idea is more or less the same. All of these, as well as Vue&#39;s reactivity APIs, are powered by <code>effect</code> under the hood. Since we are doing everything from scratch, we will need to implement <code>effect</code> before implementing the rest.</p>\n<p>Wrapping something in <code>effect</code> sets up reactivity. Take this snippet:</p>\n<pre><code class="language-ts">const fooref = ref(<span class="hljs-string">&#x27;foo&#x27;</span>)\nlet foo\n<span class="hljs-function"><span class="hljs-title">effect</span><span class="hljs-params">(()</span></span> =&gt; {\n  foo = fooref<span class="hljs-selector-class">.value</span>\n})\nfooref<span class="hljs-selector-class">.value</span> = <span class="hljs-string">&#x27;bar&#x27;</span>\nconsole<span class="hljs-selector-class">.log</span>(foo) <span class="hljs-comment">// now it&#x27;s &#x27;bar&#x27; via reactivity!</span>\n</code></pre>\n<p>By wrapping <code>foo = fooref.value</code> in <code>effect</code>, we are saying that the value of <code>foo</code> depends on the value of <code>fooref.value</code>. Now, any time that <code>fooref.value</code> changes, the <code>effect</code> will be executed and <code>foo</code> will also be updated.</p>',Z="Among many other cool features, Vue.js 3 is much more modular than Vue.js 3. The project is consists of [many different packages](https://github.com/vuejs/vue-next), making it even more flexible and customizable.\n\nOne of the more interesting architectural changes is the decoupled renderer and runtime. This makes it much easier to build custom renderers.\n\n## What is a Custom Renderer?\n\nVue consists of several \"systems\". There is the reactivity system, it's custom component system, a virtual DOM, and several others. A renderer is what takes the output of the virtual DOM and *renders* it using some UI layer. [The DOM renderer](https://github.com/vuejs/vue-next/tree/master/packages/runtime-dom) (the only one that ships with Vue) could be considered only official renderer, and as such, the reference renderer. \n\nSo, a custom renderer is renderer that targets anything other than the DOM.\n\nThe official DOM renderer can also be considered the best resource to learn to build a custom renderer - if you want to write one, you will become very well acquainted with it, since there are not many other resources on building a Vue 3 renderer.\n\n## Existing Literature\n\nThe main resources I used when preparing this post were:\n\n- [Vuminal](https://github.com/ycmjason/vuminal). A terminal renderer. It's source code is overly modular and kind of difficult to navigate, and I couldn't get it to do anything much more than the basic counter example in the README.\n- [Vugel](https://github.com/Planning-nl/vugel), a WebGL renderer.\n- [Vue 3 DOM Renderer source](https://github.com/vuejs/vue-next/tree/master/packages/runtime-dom). This was the most useful resource by far.\n- [React PDF](https://react-pdf.org/). This is a custom PDF renderer for React. Not Vue, but the ideas apply, and the inspiration for this project.\n",ee='<p>Among many other cool features, Vue.js 3 is much more modular than Vue.js 3. The project is consists of <a href="https://github.com/vuejs/vue-next">many different packages</a>, making it even more flexible and customizable.</p>\n<p>One of the more interesting architectural changes is the decoupled renderer and runtime. This makes it much easier to build custom renderers.</p>\n<h2 id="what-is-a-custom-renderer">What is a Custom Renderer?</h2>\n<p>Vue consists of several &quot;systems&quot;. There is the reactivity system, it&#39;s custom component system, a virtual DOM, and several others. A renderer is what takes the output of the virtual DOM and <em>renders</em> it using some UI layer. <a href="https://github.com/vuejs/vue-next/tree/master/packages/runtime-dom">The DOM renderer</a> (the only one that ships with Vue) could be considered only official renderer, and as such, the reference renderer. </p>\n<p>So, a custom renderer is renderer that targets anything other than the DOM.</p>\n<p>The official DOM renderer can also be considered the best resource to learn to build a custom renderer - if you want to write one, you will become very well acquainted with it, since there are not many other resources on building a Vue 3 renderer.</p>\n<h2 id="existing-literature">Existing Literature</h2>\n<p>The main resources I used when preparing this post were:</p>\n<ul>\n<li><a href="https://github.com/ycmjason/vuminal">Vuminal</a>. A terminal renderer. It&#39;s source code is overly modular and kind of difficult to navigate, and I couldn&#39;t get it to do anything much more than the basic counter example in the README.</li>\n<li><a href="https://github.com/Planning-nl/vugel">Vugel</a>, a WebGL renderer.</li>\n<li><a href="https://github.com/vuejs/vue-next/tree/master/packages/runtime-dom">Vue 3 DOM Renderer source</a>. This was the most useful resource by far.</li>\n<li><a href="https://react-pdf.org/">React PDF</a>. This is a custom PDF renderer for React. Not Vue, but the ideas apply, and the inspiration for this project.</li>\n</ul>',te='Welcome to the future. Check out this snippet from a popular web programming language that\'s gaining traction - it\'s a functional programming language where all values are immutable:\n\n```js\nconst sanitized = input\n  |> Str.split(?, \',\')\n  |> Arr.map(?, toNumber)\n```\n\nAs you can see it has a number operators like `|>` (pipeline) and `?` (partial application).\n\nWhat year is it, and language are you coding in, you ask? The year is 2021 - and the language is *JavaScript*!\n\nYou can watch a video version of this post on my [Youtube channel](https://youtube.com/c/LachlanMiller).\n\n## What is `|>`? Introducing the Pipeline Operator\n\nMany functional language have a *pipeline* operator. Examples include Elm:\n\n```elm\n"Hello everyone" \n  |> left 5 \n  |> toLower \n  |> append "I say " -- "I say hello"\n```\n\nElixir:\n\n```elm\n"Elixir rocks"\n  |> String.upcase() \n  |> String.split()\n#=> ["ELIXIR", "ROCKS"]\n```\n\nF#\n\n```fsharp\nlet finalSeq = \n    seq { 0..10 }\n    |> Seq.filter (fun c -> (c % 2) = 0)\n    |> Seq.map ((*) 2)\n    |> Seq.map (sprintf "The value is %i.")\n```\n\nYou see the syntax - lots of `|>` which let you modify data in some way. Why is this a useful feature, and why would we want it in JavaScript? Pipelines make it easy to *chain functions together*. \n',se='<p>Welcome to the future. Check out this snippet from a popular web programming language that&#39;s gaining traction - it&#39;s a functional programming language where all values are immutable:</p>\n<pre><code class="language-js">const sanitized = <span class="hljs-built_in">input</span>\n  |&gt; Str.<span class="hljs-keyword">split</span>(?, <span class="hljs-string">&#x27;,&#x27;</span>)\n  |&gt; Arr.<span class="hljs-keyword">map</span>(?, toNumber)\n</code></pre>\n<p>As you can see it has a number operators like <code>|&gt;</code> (pipeline) and <code>?</code> (partial application).</p>\n<p>What year is it, and language are you coding in, you ask? The year is 2021 - and the language is <em>JavaScript</em>!</p>\n<p>You can watch a video version of this post on my <a href="https://youtube.com/c/LachlanMiller">Youtube channel</a>.</p>\n<h2 id="what-is--introducing-the-pipeline-operator">What is <code>|&gt;</code>? Introducing the Pipeline Operator</h2>\n<p>Many functional language have a <em>pipeline</em> operator. Examples include Elm:</p>\n<pre><code class="language-elm"><span class="hljs-string">&quot;Hello everyone&quot;</span> \n  <span class="hljs-string">|&gt; left 5 </span>\n  <span class="hljs-string">|&gt; toLower </span>\n  <span class="hljs-string">|&gt; append &quot;</span>I say <span class="hljs-string">&quot; -- &quot;</span>I say hello<span class="hljs-string">&quot;</span>\n</code></pre>\n<p>Elixir:</p>\n<pre><code class="language-elm"><span class="hljs-string">&quot;Elixir rocks&quot;</span>\n  |<span class="hljs-type">&gt; String</span>.upcase() \n  |<span class="hljs-type">&gt; String</span>.<span class="hljs-built_in">split</span>()\n#=&gt; [<span class="hljs-string">&quot;ELIXIR&quot;</span>, <span class="hljs-string">&quot;ROCKS&quot;</span>]\n</code></pre>\n<p>F#</p>\n<pre><code class="language-fsharp">let finalSeq = \n    se<span class="hljs-string">q { 0..10 }</span>\n    |&gt; Seq.filter (fun c -&gt; (c % <span class="hljs-number">2</span>) = <span class="hljs-number">0</span>)\n    |&gt; Seq.map ((*) <span class="hljs-number">2</span>)\n    |&gt; Seq.map (<span class="hljs-keyword">sprintf</span> <span class="hljs-string">&quot;The value is %i.&quot;</span>)\n</code></pre>\n<p>You see the syntax - lots of <code>|&gt;</code> which let you modify data in some way. Why is this a useful feature, and why would we want it in JavaScript? Pipelines make it easy to <em>chain functions together</em>. </p>',ne="In this article we explore some advanced TypeScript while building a type safe store with a similar API to [Pinia](https://github.com/posva/pinia) or [Vuex 5](https://github.com/vuejs/rfcs/discussions/270) (which is still in the RFC stage). I learned a lot of what was needed to write this article by reading the [Pinia](https://github.com/posva/pinia/) source code.\n\nYou can find the source code [here](https://gist.github.com/lmiller1990/9ef32df8fb401e5f0482692ae974e6e0).\n\nThe goal will be a `defineStore` function that looks like this:\n\n```ts\nexport const useMainStore = defineStore({\n  state: {\n    counter: 0,\n  },\n  actions: {\n    inc(val: number = 1) {\n      this.state.counter += val\n    },\n  },\n});\n```\n\nThe store can then be used by simply calling `useMainStore()`:\n\n```html\n<template>\n  <p>Count is: {{ state.counter }}</p>\n  <button @click=\"click\">Inc</button>\n</template>\n\n<script lang=\"ts\">\nimport { computed } from 'vue'\nimport { useMainStore, useOtherStore } from './index2'\n\nexport default {\n  setup() {\n    const store = useMainStore()\n    return {\n      click: () => store.inc(),\n      state: store.state,\n    }\n  },\n}\n<\/script>\n```\n\nThe primary goal is to explore some advanced TypeScript types.\n\n## `defineStore`\n\nWe will start of with `defineStore`. It needs to be generic to be type safe - in this case, both the state and actions needs to be declared as generic parameters:\n\n```ts\nfunction defineStore<\n  S extends StateTree,\n  A extends Record<string, Method>\n>(options) {\n  // ...\n}\n```\n\nWe will also need a few utility types - `StateTree` and `Method`. `Method` is simple:\n\n```ts\ntype Method = (...args: any[]) => any;\n```\n\n`StateTree` isn't much more complex - it's basically typing a JavaScript object, where the key can be a `string`, `number` or `symbol`:\n\n```ts\nexport type StateTree = Record<string | number | symbol, any>;\n```\n\n## Typing the Store Options\n\n`defineStore` takes an object of options - `state` and `actions`. `state` is very easy to type - it's just `S`:\n\n```ts\nfunction defineStore<\n  S extends StateTree,\n  A extends Record<string, Method>\n>(options: {\n  state: S,\n}) {\n```\n\n`actions` is a bit more tricky. We know it's going to be a type of `A`, the second generic type passed to `defineStore`, but it also needs to have knowledge of `this`. Specifically, it needs to know that `this.state` exists, and `state` is a typed as `S`.\n\nThe way this is typed is using an intersection type (`&`) and [`ThisType`](https://www.typescriptlang.org/docs/handbook/utility-types.html#thistypetype). We need something similar to the example [in the docs](https://www.typescriptlang.org/docs/handbook/utility-types.html#thistypetype):\n\n```ts\nactions: A & ThisType<A & _STORE_WITH_STATE_>\n```\n\nWe just need to declare `_STORE_WITH_STATE_`. We could do it inline:\n\n```ts\nactions: A & ThisType<A & { state: S }>\n```\n\nPutting it all together:\n\n```ts\nexport type StateTree = Record<string | number | symbol, any>;\nfunction defineStore<\n  S extends StateTree,\n  A extends Record<string, Method>\n>(options: {\n  state: S,\n  actions: A & ThisType<A & { state: S }>\n}) {\n  // ...\n}\n```\n\nI'd like to add more features to the store later, so I am actually going to extract a type, `StoreWithState`:\n\n```ts\ntype StoreWithState<S extends StateTree> = {\n  state: S\n}\n\nexport type StateTree = Record<string | number | symbol, any>;\nfunction defineStore<\n  S extends StateTree,\n  A extends Record<string, Method>\n>(options: {\n  state: S,\n  actions: A & ThisType<A & StoreWithState<S>>\n}) {\n  // ...\n}\n```\n\nNow `this` in actions is typed! Let's move on to implementing the actual store.\n\n## Implementing `useStore` and Reactive State\n\nThe first thing we need is a store with a state property. Create that (`defineStore` is shown without the types for simplicity):\n\n```ts\nfunction defineStore(options) {\n  const initialStore = {\n    state: options.state || {}\n  }\n}\n```\n\n`defineStore` should return a function. I will call it `useStore`, and it needs to return what we are going to type as `Store`:\n\n```ts\nfunction defineStore(options) {\n  const initialStore = {\n    state: options.state || {}\n  }\n\n  return function useStore(): Store<S, A> {\n    // ...\n  }\n}\n```\n\nTyping `Store` is a bit tricky. It needs to expose a `state` property, as well as all the methods (which we are calling \"actions\" here). We want the arguments to those methods to be typed, too. We already have a `StoreWithState` type - we also need a `StoreWithActions` type. \n\nThe `Store` type looks like this:\n\n```ts\ntype Store<\n  S extends StateTree,\n  A extends Record<string, Method>\n> = StoreWithState<S> & S & StoreWithActions<A>;\n```\n\n## Typing Actions and Inferring Parameters\n\nBefore we write the type, we should figure out what we are actually typing:\n\n```ts\nconst actions = {\n  inc(val: number = 1) {\n    this.state.counter += val\n  },\n}\n```\n\nWe don't care too much about the body of the function - just the parameters. If we describe `actions` in plain English, it would be \"a key value object. If the value exists, and it's a function, infer the type of the arguments and return type\". Or something like that.\n\nLet's start with \"a key value object\":\n\n```ts\ntype StoreWithActions<A> = {\n  [k in keyof A]: any\n}\n```\n\nThis infers the methods exists. `useMainStore().inc` is typed, but not as a function. Let's fix that:\n\n```ts\ntype StoreWithActions<A> = {\n  [k in keyof A]: (...args: any[]) => any\n}\n```\n\nNow we know it's a function. But the parameters still aren't typed! We need to `infer` them - to infer a function has parameters, though, we first need to validate that it is actually a function:\n\n```ts\ntype StoreWithActions<A> = {\n  [k in keyof A]: A[k] extends (...args: infer P) => infer R\n    ? /* type */\n    : /* the key does not exist, or it's not a method */\n}\n```\n\nWe use `extends` to see if `A[k]` (in this case, actions['inc']` exists, and is a method - that is to say, it has arguments and returns a type. We don't know the type, so we *infer* it. `infer` is kind of like a generic type, except we are creating it based on a type that already exists. If we did not use `infer`, we'd get an error \"cannot find name P\", since TypeScript would be expecting us to provide that parameters.\n\nFinish of the type:\n\n```ts\ntype StoreWithActions<A> = {\n  [k in keyof A]: A[k] extends (...args: infer P) => infer R\n    ? (...args: P) => R\n    : never\n}\n```\n\nThis is definitely an advanced type. It combines conditional types (using `extends`) and `infer`. This is the most complex type in the store.\n\n## Creating the Store Object\n\nNow that we finished the types, we can actually implement the store. Inside of `useStore` create a `store` variable:\n\n```ts\nfunction defineStore(options) {\n  const initialStore = {\n    state: options.state || {}\n  }\n\n  return function useStore(): Store<S, A> {\n    const store: Store<S, A> = reactive({\n      ...initialStore,\n    }) as Store<S, A>\n  }\n}\n```\n\nThis has type errors - we need to provide an object typed as `StoreWithActions`. We need the actions to be called with `store` as the `this` context, so we can do `this.state`. This means we will *wrap* the actions and call them with `apply`, passing `store` as the first argument. For this reason the variable is called `wrappedActions` and typed as `StoreWithActions<A>`:\n\n```ts\nfunction defineStore(options) {\n  const initialStore = {\n    state: options.state || {}\n  }\n\n  return function useStore(): Store<S, A> {\n    const wrappedActions: StoreWithActions<A> = {} as StoreWithActions<A>\n\n    const store: Store<S, A> = reactive({\n      ...initialStore,\n      ...wrappedActions\n    }) as Store<S, A>\n\n    return store\n  }\n}\n```\n\nA bit messy, but it works. Finally, we just need to wrap the actions. First, type it:\n\n```ts\nconst wrappedActions: StoreWithActions<A> = {} as StoreWithActions<A>\nconst actions = (options.actions || {}) as A\nfor (const actionName in actions) {\n  wrappedActions[actionName] = function() {\n\n  } as StoreWithActions<A>[typeof actionName]\n}\n```\n\nAgain, a bit messy. We need the `as StoreWithActions<A>[typeof actionName]` to get the correct typing. This paralells the `[k in keyof A]` typing we did earlier in `StoreWithActions`. \n\nFinally, call the original `actions[actionName]` with `apply`, passing in `store` as the `this` context:\n\n```ts\n\nfunction defineStore(options) {\n  // ...\n  return function useStore(): Store<S, A> {\n    const wrappedActions: StoreWithActions<A> = {} as StoreWithActions<A>\n    const actions = (options.actions || {}) as A\n\n    for (const actionName in actions) {\n      wrappedActions[actionName] = function(...args: any[]) {\n        return actions[actionName].apply(store, args)\n      } as StoreWithActions<A>[typeof actionName]\n    }\n\n    const store: Store<S, A> = reactive({\n      ...initialStore,\n      ...wrappedActions\n    }) as Store<S, A>\n\n    return store\n  }\n}\n```\n\nThat's it! A type safe store. \n\n## Conclusion\n\nWe created a type safe store. The types are a bit complex. We covered:\n\n- `infer` keyword\n- `ThisType`\n- Generics\n- Conditional types with `extends`\n- Intersections (`&`)\n\nAn improvement would be to add `getters` using Vue's `computed` function. \n\nYou can find the source code [here](https://gist.github.com/lmiller1990/9ef32df8fb401e5f0482692ae974e6e0).",ae='<p>In this article we explore some advanced TypeScript while building a type safe store with a similar API to <a href="https://github.com/posva/pinia">Pinia</a> or <a href="https://github.com/vuejs/rfcs/discussions/270">Vuex 5</a> (which is still in the RFC stage). I learned a lot of what was needed to write this article by reading the <a href="https://github.com/posva/pinia/">Pinia</a> source code.</p>\n<p>You can find the source code <a href="https://gist.github.com/lmiller1990/9ef32df8fb401e5f0482692ae974e6e0">here</a>.</p>\n<p>The goal will be a <code>defineStore</code> function that looks like this:</p>\n<pre><code class="language-ts">export <span class="hljs-keyword">const</span> useMainStore = defineStore({\n  <span class="hljs-keyword">state</span>: {\n    counter: <span class="hljs-number">0</span>,\n  },\n  actions: {\n    inc(val: number = <span class="hljs-number">1</span>) {\n      this.<span class="hljs-keyword">state</span>.counter += val\n    },\n  },\n});\n</code></pre>\n<p>The store can then be used by simply calling <code>useMainStore()</code>:</p>\n<pre><code class="language-html"><span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span>\n  <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Count is: </span><span class="hljs-template-variable">{{ <span class="hljs-name">state.counter</span> }}</span><span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>\n  <span class="hljs-tag">&lt;<span class="hljs-name">button</span> @<span class="hljs-attr">click</span>=<span class="hljs-string">&quot;click&quot;</span>&gt;</span>Inc<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>\n<span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>\n\n<span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">&quot;ts&quot;</span>&gt;</span><span class="language-javascript">\n<span class="hljs-keyword">import</span> { computed } <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;vue&#x27;</span>\n<span class="hljs-keyword">import</span> { useMainStore, useOtherStore } <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;./index2&#x27;</span>\n\n<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {\n  <span class="hljs-title function_">setup</span>(<span class="hljs-params"></span>) {\n    <span class="hljs-keyword">const</span> store = <span class="hljs-title function_">useMainStore</span>()\n    <span class="hljs-keyword">return</span> {\n      <span class="hljs-attr">click</span>: <span class="hljs-function">() =&gt;</span> store.<span class="hljs-title function_">inc</span>(),\n      <span class="hljs-attr">state</span>: store.<span class="hljs-property">state</span>,\n    }\n  },\n}\n</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>\n</code></pre>\n<p>The primary goal is to explore some advanced TypeScript types.</p>\n<h2 id="definestore"><code>defineStore</code></h2>\n<p>We will start of with <code>defineStore</code>. It needs to be generic to be type safe - in this case, both the state and actions needs to be declared as generic parameters:</p>\n<pre><code class="language-ts"><span class="hljs-keyword">function</span> defineStore&lt;\n  S <span class="hljs-keyword">extends</span> <span class="hljs-title class_ inherited__">StateTree</span>,\n  A <span class="hljs-keyword">extends</span> <span class="hljs-title class_ inherited__">Record</span>&lt;<span class="hljs-built_in">string</span>, <span class="hljs-title class_">Method</span>&gt;\n&gt;(options) {\n  <span class="hljs-comment">// ...</span>\n}\n</code></pre>\n<p>We will also need a few utility types - <code>StateTree</code> and <code>Method</code>. <code>Method</code> is simple:</p>\n<pre><code class="language-ts"><span class="hljs-keyword">type</span> <span class="hljs-function"><span class="hljs-keyword">Method</span> = <span class="hljs-params">(...args: any[])</span> =&gt; <span class="hljs-title">any</span>;</span>\n</code></pre>\n<p><code>StateTree</code> isn&#39;t much more complex - it&#39;s basically typing a JavaScript object, where the key can be a <code>string</code>, <code>number</code> or <code>symbol</code>:</p>\n<pre><code class="language-ts"><span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> <span class="hljs-title class_">StateTree</span> = <span class="hljs-title class_">Record</span>&lt;<span class="hljs-built_in">string</span> | <span class="hljs-built_in">number</span> | symbol, <span class="hljs-built_in">any</span>&gt;;\n</code></pre>\n<h2 id="typing-the-store-options">Typing the Store Options</h2>\n<p><code>defineStore</code> takes an object of options - <code>state</code> and <code>actions</code>. <code>state</code> is very easy to type - it&#39;s just <code>S</code>:</p>\n<pre><code class="language-ts">function defineStore<span class="hljs-variable">&lt;\n  S extends StateTree,\n  A extends Record&lt;string, Method&gt;</span>\n&gt;(options: {\n  <span class="hljs-keyword">state</span>: S,\n}) {\n</code></pre>\n<p><code>actions</code> is a bit more tricky. We know it&#39;s going to be a type of <code>A</code>, the second generic type passed to <code>defineStore</code>, but it also needs to have knowledge of <code>this</code>. Specifically, it needs to know that <code>this.state</code> exists, and <code>state</code> is a typed as <code>S</code>.</p>\n<p>The way this is typed is using an intersection type (<code>&amp;</code>) and <a href="https://www.typescriptlang.org/docs/handbook/utility-types.html#thistypetype"><code>ThisType</code></a>. We need something similar to the example <a href="https://www.typescriptlang.org/docs/handbook/utility-types.html#thistypetype">in the docs</a>:</p>\n<pre><code class="language-ts">actions: <span class="hljs-keyword">A</span> &amp; ThisType&lt;<span class="hljs-keyword">A</span> &amp; _STORE_WITH_STATE_&gt;\n</code></pre>\n<p>We just need to declare <code>_STORE_WITH_STATE_</code>. We could do it inline:</p>\n<pre><code class="language-ts">actions: <span class="hljs-keyword">A</span> &amp; ThisType&lt;<span class="hljs-keyword">A</span> &amp; { state: S }&gt;\n</code></pre>\n<p>Putting it all together:</p>\n<pre><code class="language-ts"><span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> <span class="hljs-title class_">StateTree</span> = <span class="hljs-title class_">Record</span>&lt;<span class="hljs-built_in">string</span> | <span class="hljs-built_in">number</span> | symbol, <span class="hljs-built_in">any</span>&gt;;\n<span class="hljs-keyword">function</span> defineStore&lt;\n  S <span class="hljs-keyword">extends</span> <span class="hljs-title class_ inherited__">StateTree</span>,\n  A <span class="hljs-keyword">extends</span> <span class="hljs-title class_ inherited__">Record</span>&lt;<span class="hljs-built_in">string</span>, <span class="hljs-title class_">Method</span>&gt;\n&gt;(<span class="hljs-attr">options</span>: {\n  <span class="hljs-attr">state</span>: S,\n  <span class="hljs-attr">actions</span>: A &amp; <span class="hljs-title class_">ThisType</span>&lt;A &amp; { <span class="hljs-attr">state</span>: S }&gt;\n}) {\n  <span class="hljs-comment">// ...</span>\n}\n</code></pre>\n<p>I&#39;d like to add more features to the store later, so I am actually going to extract a type, <code>StoreWithState</code>:</p>\n<pre><code class="language-ts"><span class="hljs-class"><span class="hljs-keyword">type</span> <span class="hljs-title">StoreWithState&lt;S</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StateTree&gt;</span> </span>= {\n  state: <span class="hljs-type">S</span>\n}\n\nexport <span class="hljs-class"><span class="hljs-keyword">type</span> <span class="hljs-title">StateTree</span> </span>= <span class="hljs-type">Record</span>&lt;string | number | symbol, any&gt;;\nfunction defineStore&lt;\n  <span class="hljs-type">S</span> <span class="hljs-keyword">extends</span> <span class="hljs-type">StateTree</span>,\n  <span class="hljs-type">A</span> <span class="hljs-keyword">extends</span> <span class="hljs-type">Record</span>&lt;string, <span class="hljs-type">Method</span>&gt;\n&gt;(options: {\n  state: <span class="hljs-type">S</span>,\n  actions: <span class="hljs-type">A</span> &amp; <span class="hljs-type">ThisType</span>&lt;<span class="hljs-type">A</span> &amp; <span class="hljs-type">StoreWithState</span>&lt;<span class="hljs-type">S</span>&gt;&gt;\n}) {\n  <span class="hljs-comment">// ...</span>\n}\n</code></pre>\n<p>Now <code>this</code> in actions is typed! Let&#39;s move on to implementing the actual store.</p>\n<h2 id="implementing-usestore-and-reactive-state">Implementing <code>useStore</code> and Reactive State</h2>\n<p>The first thing we need is a store with a state property. Create that (<code>defineStore</code> is shown without the types for simplicity):</p>\n<pre><code class="language-ts">function defineStore(options) {\n  <span class="hljs-keyword">const</span> initialStore = {\n    <span class="hljs-keyword">state</span>: options.<span class="hljs-keyword">state</span> || {}\n  }\n}\n</code></pre>\n<p><code>defineStore</code> should return a function. I will call it <code>useStore</code>, and it needs to return what we are going to type as <code>Store</code>:</p>\n<pre><code class="language-ts">function defineStore(options) {\n  <span class="hljs-keyword">const</span> initialStore = {\n    <span class="hljs-keyword">state</span>: options.<span class="hljs-keyword">state</span> || {}\n  }\n\n  return function useStore(): Store<span class="hljs-variable">&lt;S, A&gt;</span> {\n    // ...\n  }\n}\n</code></pre>\n<p>Typing <code>Store</code> is a bit tricky. It needs to expose a <code>state</code> property, as well as all the methods (which we are calling &quot;actions&quot; here). We want the arguments to those methods to be typed, too. We already have a <code>StoreWithState</code> type - we also need a <code>StoreWithActions</code> type. </p>\n<p>The <code>Store</code> type looks like this:</p>\n<pre><code class="language-ts"><span class="hljs-keyword">type</span> Store&lt;\n  S extends StateTree,\n  A extends <span class="hljs-keyword">Record</span>&lt;string, <span class="hljs-function"><span class="hljs-keyword">Method</span>&gt;\n&gt; = <span class="hljs-title">StoreWithState</span>&lt;<span class="hljs-title">S</span>&gt; &amp; <span class="hljs-title">S</span> &amp; <span class="hljs-title">StoreWithActions</span>&lt;<span class="hljs-title">A</span>&gt;;</span>\n</code></pre>\n<h2 id="typing-actions-and-inferring-parameters">Typing Actions and Inferring Parameters</h2>\n<p>Before we write the type, we should figure out what we are actually typing:</p>\n<pre><code class="language-ts"><span class="hljs-keyword">const</span> actions = {\n  inc(val: number = <span class="hljs-number">1</span>) {\n    this.<span class="hljs-keyword">state</span>.counter += val\n  },\n}\n</code></pre>\n<p>We don&#39;t care too much about the body of the function - just the parameters. If we describe <code>actions</code> in plain English, it would be &quot;a key value object. If the value exists, and it&#39;s a function, infer the type of the arguments and return type&quot;. Or something like that.</p>\n<p>Let&#39;s start with &quot;a key value object&quot;:</p>\n<pre><code class="language-ts"><span class="hljs-keyword">type</span> <span class="hljs-type">StoreWithActions</span>&lt;<span class="hljs-type">A</span>&gt; = {\n  [k in keyof <span class="hljs-type">A</span>]: any\n}\n</code></pre>\n<p>This infers the methods exists. <code>useMainStore().inc</code> is typed, but not as a function. Let&#39;s fix that:</p>\n<pre><code class="language-ts"><span class="hljs-keyword">type</span> <span class="hljs-title class_">StoreWithActions</span>&lt;A&gt; = {\n  [k <span class="hljs-keyword">in</span> keyof A]: <span class="hljs-function">(<span class="hljs-params">...args: <span class="hljs-built_in">any</span>[]</span>) =&gt;</span> <span class="hljs-built_in">any</span>\n}\n</code></pre>\n<p>Now we know it&#39;s a function. But the parameters still aren&#39;t typed! We need to <code>infer</code> them - to infer a function has parameters, though, we first need to validate that it is actually a function:</p>\n<pre><code class="language-ts"><span class="hljs-keyword">type</span> <span class="hljs-type">StoreWithActions</span>&lt;<span class="hljs-type">A</span>&gt; = {\n  [k in keyof <span class="hljs-type">A</span>]: <span class="hljs-type">A</span>[k] extends (...args: infer <span class="hljs-type">P</span>) =&gt; infer <span class="hljs-type">R</span>\n    ? /* type */\n    : /* the key does not exist, or it&#x27;s not a method */\n}\n</code></pre>\n<p>We use <code>extends</code> to see if <code>A[k]</code> (in this case, actions[&#39;inc&#39;]<code>exists, and is a method - that is to say, it has arguments and returns a type. We don&#39;t know the type, so we *infer* it.</code>infer<code>is kind of like a generic type, except we are creating it based on a type that already exists. If we did not use</code>infer`, we&#39;d get an error &quot;cannot find name P&quot;, since TypeScript would be expecting us to provide that parameters.</p>\n<p>Finish of the type:</p>\n<pre><code class="language-ts"><span class="hljs-keyword">type</span> <span class="hljs-type">StoreWithActions</span>&lt;<span class="hljs-type">A</span>&gt; = {\n  [k in keyof <span class="hljs-type">A</span>]: <span class="hljs-type">A</span>[k] extends (...args: infer <span class="hljs-type">P</span>) =&gt; infer <span class="hljs-type">R</span>\n    ? (...args: <span class="hljs-type">P</span>) =&gt; <span class="hljs-type">R</span>\n    : never\n}\n</code></pre>\n<p>This is definitely an advanced type. It combines conditional types (using <code>extends</code>) and <code>infer</code>. This is the most complex type in the store.</p>\n<h2 id="creating-the-store-object">Creating the Store Object</h2>\n<p>Now that we finished the types, we can actually implement the store. Inside of <code>useStore</code> create a <code>store</code> variable:</p>\n<pre><code class="language-ts">function defineStore(options) {\n  <span class="hljs-keyword">const</span> initialStore = {\n    <span class="hljs-keyword">state</span>: options.<span class="hljs-keyword">state</span> || {}\n  }\n\n  return function useStore(): Store<span class="hljs-variable">&lt;S, A&gt;</span> {\n    <span class="hljs-keyword">const</span> store: Store<span class="hljs-variable">&lt;S, A&gt;</span> = reactive({\n      ...initialStore,\n    }) as Store<span class="hljs-variable">&lt;S, A&gt;</span>\n  }\n}\n</code></pre>\n<p>This has type errors - we need to provide an object typed as <code>StoreWithActions</code>. We need the actions to be called with <code>store</code> as the <code>this</code> context, so we can do <code>this.state</code>. This means we will <em>wrap</em> the actions and call them with <code>apply</code>, passing <code>store</code> as the first argument. For this reason the variable is called <code>wrappedActions</code> and typed as <code>StoreWithActions&lt;A&gt;</code>:</p>\n<pre><code class="language-ts">function defineStore(options) {\n  <span class="hljs-keyword">const</span> initialStore = {\n    <span class="hljs-keyword">state</span>: options.<span class="hljs-keyword">state</span> || {}\n  }\n\n  return function useStore(): Store<span class="hljs-variable">&lt;S, A&gt;</span> {\n    <span class="hljs-keyword">const</span> wrappedActions: StoreWithActions<span class="hljs-variable">&lt;A&gt;</span> = {} as StoreWithActions<span class="hljs-variable">&lt;A&gt;</span>\n\n    <span class="hljs-keyword">const</span> store: Store<span class="hljs-variable">&lt;S, A&gt;</span> = reactive({\n      ...initialStore,\n      ...wrappedActions\n    }) as Store<span class="hljs-variable">&lt;S, A&gt;</span>\n\n    return store\n  }\n}\n</code></pre>\n<p>A bit messy, but it works. Finally, we just need to wrap the actions. First, type it:</p>\n<pre><code class="language-ts"><span class="hljs-keyword">const</span> wrappedActions: StoreWithActions&lt;A&gt; = {} <span class="hljs-keyword">as</span> StoreWithActions&lt;A&gt;\n<span class="hljs-keyword">const</span> actions = (options.actions || {}) <span class="hljs-keyword">as</span> A\n<span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> actionName <span class="hljs-keyword">in</span> actions) {\n  wrappedActions[actionName] = <span class="hljs-keyword">function</span><span class="hljs-params">()</span> {\n\n  } <span class="hljs-keyword">as</span> StoreWithActions&lt;A&gt;[<span class="hljs-keyword">typeof</span> actionName]\n}\n</code></pre>\n<p>Again, a bit messy. We need the <code>as StoreWithActions&lt;A&gt;[typeof actionName]</code> to get the correct typing. This paralells the <code>[k in keyof A]</code> typing we did earlier in <code>StoreWithActions</code>. </p>\n<p>Finally, call the original <code>actions[actionName]</code> with <code>apply</code>, passing in <code>store</code> as the <code>this</code> context:</p>\n<pre><code class="language-ts">\n<span class="hljs-keyword">function</span> <span class="hljs-title function_">defineStore</span>(<span class="hljs-params">options</span>) {\n  <span class="hljs-comment">// ...</span>\n  <span class="hljs-keyword">return</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">useStore</span>(<span class="hljs-params"></span>): <span class="hljs-title class_">Store</span>&lt;S, A&gt; {\n    <span class="hljs-keyword">const</span> <span class="hljs-attr">wrappedActions</span>: <span class="hljs-title class_">StoreWithActions</span>&lt;A&gt; = {} <span class="hljs-keyword">as</span> <span class="hljs-title class_">StoreWithActions</span>&lt;A&gt;\n    <span class="hljs-keyword">const</span> actions = (options.<span class="hljs-property">actions</span> || {}) <span class="hljs-keyword">as</span> A\n\n    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> actionName <span class="hljs-keyword">in</span> actions) {\n      wrappedActions[actionName] = <span class="hljs-keyword">function</span>(<span class="hljs-params">...args: any[]</span>) {\n        <span class="hljs-keyword">return</span> actions[actionName].<span class="hljs-title function_">apply</span>(store, args)\n      } <span class="hljs-keyword">as</span> <span class="hljs-title class_">StoreWithActions</span>&lt;A&gt;[<span class="hljs-keyword">typeof</span> actionName]\n    }\n\n    <span class="hljs-keyword">const</span> <span class="hljs-attr">store</span>: <span class="hljs-title class_">Store</span>&lt;S, A&gt; = <span class="hljs-title function_">reactive</span>({\n      ...initialStore,\n      ...wrappedActions\n    }) <span class="hljs-keyword">as</span> <span class="hljs-title class_">Store</span>&lt;S, A&gt;\n\n    <span class="hljs-keyword">return</span> store\n  }\n}\n</code></pre>\n<p>That&#39;s it! A type safe store. </p>\n<h2 id="conclusion">Conclusion</h2>\n<p>We created a type safe store. The types are a bit complex. We covered:</p>\n<ul>\n<li><code>infer</code> keyword</li>\n<li><code>ThisType</code></li>\n<li>Generics</li>\n<li>Conditional types with <code>extends</code></li>\n<li>Intersections (<code>&amp;</code>)</li>\n</ul>\n<p>An improvement would be to add <code>getters</code> using Vue&#39;s <code>computed</code> function. </p>\n<p>You can find the source code <a href="https://gist.github.com/lmiller1990/9ef32df8fb401e5f0482692ae974e6e0">here</a>.</p>\n',oe="As of Cypress 7.0, the new Component Testing (CT) runner is now bundled with the Cypress when you install it! It takes inspiration from the original CT implementation, which was hidden behind the `experimentalComponentTesting` flag.\n\nIn this blog post we will see how to set up CT with a Vue CLI project. CT supports both Vue 2 and Vue 3, with TypeScript support out of the box.\n\nYou can get the source code for the blog post [here](https://github.com/lmiller1990/vue-cypress-template).\n\n## Creating a new Vue CLI Project\n\nCreate a new Vue CLI project to get started. For this example I chose Vue 2 and TypeScript, but everything works exactly the same with Vue 3.\n\n## Configuring Cypress Component Testing\n\nOnce you've got a Vue project, you'll also need to install Cypress and the Webpack Dev Server and Vue adapters. Vue CLI projects are Webpack based; that's why we are installing the relevant Webpack adapter:\n\n```sh\nyarn add cypress @cypress/vue @cypress/webpack-dev-server --dev\n# or \nnpm install cypress @cypress/vue @cypress/webpack-dev-server --dev\n```\n\nIf you would like to use Vue 3, you'll need the `next` branch of `@cypress/vue`. Install that with:\n\n```sh\nyarn add @cypress/vue@next --dev\n# or\nnpm install @cypress/vue@next --dev\n```\n\nThe last thing we need to do is add some basic configuration. It is possible to configure things manually, but to save some time we will use `create-cypress-tests`, a little utility to help you scaffold configuration, much like the Vue CLI.\n\n```sh\nyarn create cypress-tests\n\n# or npm\nnpx create-cypress-tests\n```\n\nYou are prompted with \"Do you want to setup component testing?\". Answer yes!\n\nThe next question asks for your template. Select `vue-cli`. There are a number of other adapters to use. At the time of this post, the Webpack adapter is the most mature and stable.\n\nThe final question asks where you'd like to place your spec files. I like to use `src`, so my specs are near the relevant components.\n\nThe wizard created a number of files for you. The most interesting is `cypress/plugins/index.js`:\n\n```js\nconst { startDevServer } = require(\"@cypress/webpack-dev-server\");\n\nconst webpackConfig = require(\"@vue/cli-service/webpack.config.js\");\n\nmodule.exports = (on, config) => {\n  on(\"dev-server:start\", options =>\n    startDevServer({\n      options,\n      webpackConfig\n    })\n  );\n};\n```\n\nThis will configure the Cypress Webpack Dev Server to use the same `webpack.config.js` as your project. \n\n## Writing Some Tests\n\nLet's get started! I created `src/components/HelloWorld.spec.ts` to try things out. Add a minimal test:\n\n```ts\nimport { mount } from '@cypress/vue'\nimport HelloWorld from './HelloWorld.vue'\n\ndescribe('HelloWorld', () => {\n  it('renders a message', () => {\n    const msg = 'Hello Cypress Component Testing!'\n    mount(HelloWorld, {\n      propsData: {\n        msg\n      }\n    })\n\n    cy.get('h1').should('have.text', msg)\n  })\n})\n```\n\nThe `mount` function is very similar to the one from [Vue Test Utils](https://vue-test-utils.vuejs.org/). It's actually built on top of Vue Test Utils! So you can use the mounting options you might already be familiar with.\n\nLearn more about how to write assertions with Cypress [in the official docs](https://docs.cypress.io/guides/references/assertions). Get an overview of the `cy` object and how to use it [here](https://docs.cypress.io/guides/core-concepts/introduction-to-cypress#Cypress-Can-Be-Simple-Sometimes).\n\nFinally, open the Component Testing runner:\n\n```sh\nyarn cypress open-ct # or npx cypress open-ct\n```\n\nSelect the spec and watch the test pass!\n\n![Cypress Component Testing Runner Success](https://github.com/lmiller1990/vue-cypress-template/raw/master/img1.png)\n\nTry updating the test and making it fail. Cypress will re-run your test (almost) instantly. This makes for a great red-green-refactor loop.\n\n![Cypress Component Testing Runner Failure](https://github.com/lmiller1990/vue-cypress-template/raw/master/img2.png)\n\n## Vue 3 Usage\n\nEverything works the same with Vue 3. Just make sure you have a Vue 3 project and the correct adapter:\n\n```sh\nyarn add @cypress/vue@next --dev\n# or\nnpm install @cypress/vue@next --dev\n```\n\nThe mounting options are the same as [Vue Test Utils](https://next.vue-test-utils.vuejs.org/api/#mount), so if you've used Vue Test Utils before, you'll feel right at home. \n\n## Discussion\n\nCypress Component Testing is an alternative to a jsdom based testing environment, such as Jest and Vue Test Utils. Cypress Component Testing offers many benefits:\n\n- Runs in a real browser. This means your tests are closer to what your users will be experiencing.\n- Visual. You can see exactly what is rendered. No more `console.log(wrapper.html())`.\n- Powered by Cypress - the most popular and reliable E2E testing tool out there.\n\nIt also doubles as a *design environment*. You can see the component as you develop it, and hot reload give you a near instance feedback loop. It can potentially take the place of not only your Jest based test infrastructure, but your Storybook based design infrastructure as well. \n\nCypress Component Testing is still in alpha but the product is quickly evolving and promises to change the landscape of Component Testing.\n\n## Conclusion\n\nCypress Component Testing brings everything that is great about Cypress to Component Testing. Since the underlying adapters are built on libraries like Webpack and Vue Test Utils, you don't need to throw away your entire test suite - incrementally migration is more than possible. \n\nThe visual aspect united testing and design in a single tool. My days of grepping a messy console output to figure out what the user will see are over - I can see exactly what the component will look like as my tests run.\n\nYou can get the source code for the blog post [here](https://github.com/lmiller1990/vue-cypress-template).",re='<p>As of Cypress 7.0, the new Component Testing (CT) runner is now bundled with the Cypress when you install it! It takes inspiration from the original CT implementation, which was hidden behind the <code>experimentalComponentTesting</code> flag.</p>\n<p>In this blog post we will see how to set up CT with a Vue CLI project. CT supports both Vue 2 and Vue 3, with TypeScript support out of the box.</p>\n<p>You can get the source code for the blog post <a href="https://github.com/lmiller1990/vue-cypress-template">here</a>.</p>\n<h2 id="creating-a-new-vue-cli-project">Creating a new Vue CLI Project</h2>\n<p>Create a new Vue CLI project to get started. For this example I chose Vue 2 and TypeScript, but everything works exactly the same with Vue 3.</p>\n<h2 id="configuring-cypress-component-testing">Configuring Cypress Component Testing</h2>\n<p>Once you&#39;ve got a Vue project, you&#39;ll also need to install Cypress and the Webpack Dev Server and Vue adapters. Vue CLI projects are Webpack based; that&#39;s why we are installing the relevant Webpack adapter:</p>\n<pre><code class="language-sh">yarn <span class="hljs-keyword">add</span> cypress <span class="hljs-variable">@cypress</span><span class="hljs-operator">/</span>vue <span class="hljs-variable">@cypress</span><span class="hljs-operator">/</span>webpack<span class="hljs-operator">-</span>dev<span class="hljs-operator">-</span>server <span class="hljs-comment">--dev</span>\n# <span class="hljs-keyword">or</span> \nnpm install cypress <span class="hljs-variable">@cypress</span><span class="hljs-operator">/</span>vue <span class="hljs-variable">@cypress</span><span class="hljs-operator">/</span>webpack<span class="hljs-operator">-</span>dev<span class="hljs-operator">-</span>server <span class="hljs-comment">--dev</span>\n</code></pre>\n<p>If you would like to use Vue 3, you&#39;ll need the <code>next</code> branch of <code>@cypress/vue</code>. Install that with:</p>\n<pre><code class="language-sh">yarn <span class="hljs-keyword">add</span> <span class="hljs-variable">@cypress</span><span class="hljs-operator">/</span>vue<span class="hljs-variable">@next</span> <span class="hljs-comment">--dev</span>\n# <span class="hljs-keyword">or</span>\nnpm install <span class="hljs-variable">@cypress</span><span class="hljs-operator">/</span>vue<span class="hljs-variable">@next</span> <span class="hljs-comment">--dev</span>\n</code></pre>\n<p>The last thing we need to do is add some basic configuration. It is possible to configure things manually, but to save some time we will use <code>create-cypress-tests</code>, a little utility to help you scaffold configuration, much like the Vue CLI.</p>\n<pre><code class="language-sh">yarn <span class="hljs-keyword">create</span> cypress-tests\n\n<span class="hljs-meta"># or npm</span>\nnpx <span class="hljs-keyword">create</span>-cypress-tests\n</code></pre>\n<p>You are prompted with &quot;Do you want to setup component testing?&quot;. Answer yes!</p>\n<p>The next question asks for your template. Select <code>vue-cli</code>. There are a number of other adapters to use. At the time of this post, the Webpack adapter is the most mature and stable.</p>\n<p>The final question asks where you&#39;d like to place your spec files. I like to use <code>src</code>, so my specs are near the relevant components.</p>\n<p>The wizard created a number of files for you. The most interesting is <code>cypress/plugins/index.js</code>:</p>\n<pre><code class="language-js"><span class="hljs-keyword">const</span> { startDevServer } = <span class="hljs-built_in">require</span>(<span class="hljs-string">&quot;@cypress/webpack-dev-server&quot;</span>);\n\n<span class="hljs-keyword">const</span> webpackConfig = <span class="hljs-built_in">require</span>(<span class="hljs-string">&quot;@vue/cli-service/webpack.config.js&quot;</span>);\n\nmodule.<span class="hljs-built_in">exports</span> = (<span class="hljs-literal">on</span>, config) =&gt; {\n  <span class="hljs-literal">on</span>(<span class="hljs-string">&quot;dev-server:start&quot;</span>, options =&gt;\n    startDevServer({\n      options,\n      webpackConfig\n    })\n  );\n};\n</code></pre>\n<p>This will configure the Cypress Webpack Dev Server to use the same <code>webpack.config.js</code> as your project. </p>\n<h2 id="writing-some-tests">Writing Some Tests</h2>\n<p>Let&#39;s get started! I created <code>src/components/HelloWorld.spec.ts</code> to try things out. Add a minimal test:</p>\n<pre><code class="language-ts"><span class="hljs-keyword">import</span> { mount } <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;@cypress/vue&#x27;</span>\n<span class="hljs-keyword">import</span> HelloWorld <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;./HelloWorld.vue&#x27;</span>\n\ndescribe(<span class="hljs-string">&#x27;HelloWorld&#x27;</span>, <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {\n  it(<span class="hljs-string">&#x27;renders a message&#x27;</span>, <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {\n    const msg = <span class="hljs-string">&#x27;Hello Cypress Component Testing!&#x27;</span>\n    mount(HelloWorld, {\n      propsData: {\n        msg\n      }\n    })\n\n    cy.get(<span class="hljs-string">&#x27;h1&#x27;</span>).should(<span class="hljs-string">&#x27;have.text&#x27;</span>, msg)\n  })\n})\n</code></pre>\n<p>The <code>mount</code> function is very similar to the one from <a href="https://vue-test-utils.vuejs.org/">Vue Test Utils</a>. It&#39;s actually built on top of Vue Test Utils! So you can use the mounting options you might already be familiar with.</p>\n<p>Learn more about how to write assertions with Cypress <a href="https://docs.cypress.io/guides/references/assertions">in the official docs</a>. Get an overview of the <code>cy</code> object and how to use it <a href="https://docs.cypress.io/guides/core-concepts/introduction-to-cypress#Cypress-Can-Be-Simple-Sometimes">here</a>.</p>\n<p>Finally, open the Component Testing runner:</p>\n<pre><code class="language-sh">yarn cypress <span class="hljs-keyword">open</span>-<span class="hljs-keyword">ct</span> # or npx cypress <span class="hljs-keyword">open</span>-<span class="hljs-keyword">ct</span>\n</code></pre>\n<p>Select the spec and watch the test pass!</p>\n<p><img src="https://github.com/lmiller1990/vue-cypress-template/raw/master/img1.png" alt="Cypress Component Testing Runner Success"></p>\n<p>Try updating the test and making it fail. Cypress will re-run your test (almost) instantly. This makes for a great red-green-refactor loop.</p>\n<p><img src="https://github.com/lmiller1990/vue-cypress-template/raw/master/img2.png" alt="Cypress Component Testing Runner Failure"></p>\n<h2 id="vue-3-usage">Vue 3 Usage</h2>\n<p>Everything works the same with Vue 3. Just make sure you have a Vue 3 project and the correct adapter:</p>\n<pre><code class="language-sh">yarn <span class="hljs-keyword">add</span> <span class="hljs-variable">@cypress</span><span class="hljs-operator">/</span>vue<span class="hljs-variable">@next</span> <span class="hljs-comment">--dev</span>\n# <span class="hljs-keyword">or</span>\nnpm install <span class="hljs-variable">@cypress</span><span class="hljs-operator">/</span>vue<span class="hljs-variable">@next</span> <span class="hljs-comment">--dev</span>\n</code></pre>\n<p>The mounting options are the same as <a href="https://next.vue-test-utils.vuejs.org/api/#mount">Vue Test Utils</a>, so if you&#39;ve used Vue Test Utils before, you&#39;ll feel right at home. </p>\n<h2 id="discussion">Discussion</h2>\n<p>Cypress Component Testing is an alternative to a jsdom based testing environment, such as Jest and Vue Test Utils. Cypress Component Testing offers many benefits:</p>\n<ul>\n<li>Runs in a real browser. This means your tests are closer to what your users will be experiencing.</li>\n<li>Visual. You can see exactly what is rendered. No more <code>console.log(wrapper.html())</code>.</li>\n<li>Powered by Cypress - the most popular and reliable E2E testing tool out there.</li>\n</ul>\n<p>It also doubles as a <em>design environment</em>. You can see the component as you develop it, and hot reload give you a near instance feedback loop. It can potentially take the place of not only your Jest based test infrastructure, but your Storybook based design infrastructure as well. </p>\n<p>Cypress Component Testing is still in alpha but the product is quickly evolving and promises to change the landscape of Component Testing.</p>\n<h2 id="conclusion">Conclusion</h2>\n<p>Cypress Component Testing brings everything that is great about Cypress to Component Testing. Since the underlying adapters are built on libraries like Webpack and Vue Test Utils, you don&#39;t need to throw away your entire test suite - incrementally migration is more than possible. </p>\n<p>The visual aspect united testing and design in a single tool. My days of grepping a messy console output to figure out what the user will see are over - I can see exactly what the component will look like as my tests run.</p>\n<p>You can get the source code for the blog post <a href="https://github.com/lmiller1990/vue-cypress-template">here</a>.</p>';const ie={id:"1",title:"Today",created:$()().subtract(1,"second"),authorId:"1"},ce={id:"2",title:"This Week",created:$()().subtract(2,"days"),authorId:"1"},le={id:"3",title:"This Month",created:$()().subtract(12,"days"),authorId:"1"},pe=[{id:"4",title:"🐙 Understanding Vue 3 Reactivity",created:$()().subtract(1,"second"),authorId:"1",markdown:X,html:Q},{id:"5",title:"😎 Writing a Custom Renderer",created:$()().subtract(1,"second"),authorId:"1",markdown:Z,html:ee},{id:"6",title:"🔮 A Futuristic Functional Language for Web Dev - ESNext Pipelines",created:$()().subtract(1,"second"),authorId:"1",markdown:te,html:se},{id:"7",title:"🏪 Building a Type Safe Store",created:$()().subtract(1,"second"),authorId:"1",markdown:ne,html:ae},{id:"8",title:"🧪 Cypress Component Testing",created:$()().subtract(1,"second"),authorId:"1",markdown:oe,html:re}];function de(e,t,s,a,o,r){const i=Object(n["B"])("timeline"),c=Object(n["B"])("spinner");return Object(n["t"])(),Object(n["f"])(n["b"],null,{default:Object(n["I"])(()=>[Object(n["i"])(i)]),fallback:Object(n["I"])(()=>[Object(n["i"])(c)]),_:1})}const ue=Object(n["L"])("data-v-404a6766");Object(n["w"])("data-v-404a6766");const he={class:"message is-primary is-marginless"},me={class:"message-header"},fe={class:"is-primary panel"},ye={class:"panel-tabs"};Object(n["u"])();const ge=ue((e,t,s,a,o,r)=>{const i=Object(n["B"])("timeline-post");return Object(n["t"])(),Object(n["f"])(n["a"],null,[Object(n["i"])("div",he,[Object(n["i"])("div",me,[Object(n["i"])("div",null,"Posts for "+Object(n["E"])(e.currentPeriod.toLowerCase()),1)])]),Object(n["i"])("nav",fe,[Object(n["i"])("span",ye,[(Object(n["t"])(!0),Object(n["f"])(n["a"],null,Object(n["A"])(e.periods,t=>(Object(n["t"])(),Object(n["f"])("a",{key:t,class:{"is-active":t===e.currentPeriod},"data-test":t,onClick:s=>e.setPeriod(t)},Object(n["E"])(t),11,["data-test","onClick"]))),128))]),(Object(n["t"])(!0),Object(n["f"])(n["a"],null,Object(n["A"])(e.posts,e=>(Object(n["t"])(),Object(n["f"])(i,{key:e.id,post:e,class:"panel-block"},null,8,["post"]))),128))])],64)}),be=Object(n["L"])("data-v-6cd469de");Object(n["w"])("data-v-6cd469de");const je={class:"is-flex is-flex-direction-column is-align-items-flex-start"};Object(n["u"])();const we=be((e,t,s,a,o,r)=>{const i=Object(n["B"])("router-link");return Object(n["t"])(),Object(n["f"])(i,{key:e.post.id,class:"panel-block",to:e.to},{default:be(()=>[Object(n["i"])("div",je,[Object(n["i"])("a",null,Object(n["E"])(e.post.title),1),Object(n["i"])("div",null,Object(n["E"])(e.post.created.format("Do MMM")),1)])]),_:1},8,["to"])});var ve=Object(n["j"])({name:"TimelinePost",props:{post:{type:Object,required:!0}},setup(e){return{to:"/posts/"+e.post.id}}});ve.render=we,ve.__scopeId="data-v-6cd469de";var Se=ve,ke=Object(n["j"])({name:"Timeline",components:{TimelinePost:Se},async setup(){const e=["Today","This Week","This Month"],t=Object(n["z"])("Today"),s=q();s.getState().posts.loaded||await s.fetchPosts();const a=s.getState().posts.ids.reduce((e,t)=>{const n=s.getState().posts.all.get(t);if(!n)throw Error("This post was not found");return e.concat(n)},[]),o=Object(n["d"])(()=>a.filter(e=>"Today"===t.value?e.created.isAfter($()().subtract(1,"day")):"This Week"===t.value?e.created.isAfter($()().subtract(1,"week")):"This Month"===t.value&&e.created.isAfter($()().subtract(1,"month")))),r=e=>{t.value=e};return{posts:o,periods:e,setPeriod:r,currentPeriod:t}}});ke.render=ge,ke.__scopeId="data-v-404a6766";var Oe=ke;const xe={class:"progress is-primary is-small"};function Te(e,t){return Object(n["t"])(),Object(n["f"])("progress",xe)}const Ae={};Ae.render=Te;var Ie=Ae,We=Object(n["j"])({name:"App",components:{Timeline:Oe,Spinner:Ie}});We.render=de;var Ce=We;function Ve(e,t,s,a,o,r){const i=Object(n["B"])("post-writer");return Object(n["t"])(),Object(n["f"])(i,{post:e.newPost,onSave:e.save},null,8,["post","onSave"])}const _e={class:"columns"},Pe={class:"column"},qe={class:"field"},Me=Object(n["i"])("div",{class:"label"},"New Post",-1),Ee={class:"columns"},Re={class:"column"},Ue={class:"column"},Be={class:"columns"},Le={class:"column"};function Ne(e,t,s,a,o,r){return Object(n["t"])(),Object(n["f"])(n["a"],null,[Object(n["i"])("div",_e,[Object(n["i"])("div",Pe,[Object(n["i"])("div",qe,[Me,Object(n["J"])(Object(n["i"])("input",{type:"text",class:"input","onUpdate:modelValue":t[1]||(t[1]=t=>e.title=t),"data-test":"title"},null,512),[[n["G"],e.title]])])])]),Object(n["i"])("div",Ee,[Object(n["i"])("div",Re,[Object(n["i"])("div",{contenteditable:"",id:"markdown",ref:"contentEditable",onInput:t[2]||(t[2]=(...t)=>e.handleInput&&e.handleInput(...t)),"data-test":"content"},null,544)]),Object(n["i"])("div",Ue,[Object(n["i"])("div",{innerHTML:e.html},null,8,["innerHTML"])])]),Object(n["i"])("div",Be,[Object(n["i"])("div",Le,[Object(n["i"])("button",{onClick:t[3]||(t[3]=(...t)=>e.save&&e.save(...t)),class:"button is-primary is-pulled-right","data-test":"submit"}," Submit ")])])],64)}var De=s("0e54"),ze=s("1487"),Fe=s.n(ze),He=s("b047"),Ye=s.n(He),Je=Object(n["j"])({props:{post:{type:Object,required:!0}},emits:{save:e=>!0},setup(e,t){const s=Object(n["z"])(e.post.title),a=Object(n["z"])(e.post.markdown||""),o=Object(n["z"])(""),r=e=>{o.value=Object(De["parse"])(e,{gfm:!0,breaks:!0,highlight:e=>Fe.a.highlightAuto(e).value})};Object(n["H"])(a,Ye()(e=>{r(e)},250),{immediate:!0});const i=Object(n["z"])(null),c=()=>{if(!i.value)throw Error("This should never happen");a.value=i.value.innerText||""};Object(n["r"])(()=>{if(!i.value)throw Error("This should never happen");i.value.innerText=a.value});const l=()=>{const n={...e.post,title:s.value,html:o.value,markdown:a.value};t.emit("save",n)};return{save:l,html:o,title:s,handleInput:c,content:a,contentEditable:i}}});s("9250");Je.render=Ne;var Ge=Je,Ke=Object(n["j"])({components:{PostWriter:Ge},setup(){const e=q(),t=Object(y["d"])(),s=e.getState().authors.currentUserId;if(!s)throw Error("currentUserId was not found.");const n={id:"-1",title:"Enter your title...",created:$()().subtract(1,"second"),authorId:s},a=async s=>{await e.createPost(s),t.push("/")};return{newPost:n,save:a}}});Ke.render=Ve;var $e=Ke;function Xe(e,t,s,a,o,r){const i=Object(n["B"])("post-viewer"),c=Object(n["B"])("spinner");return Object(n["t"])(),Object(n["f"])(n["b"],null,{default:Object(n["I"])(()=>[Object(n["i"])(i)]),fallback:Object(n["I"])(()=>[Object(n["i"])(c)]),_:1})}const Qe={class:"columns"},Ze=Object(n["i"])("div",{class:"column"},null,-1),et={class:"column is-two-thirds"},tt=Object(n["h"])(" Edit "),st=Object(n["i"])("div",{class:"column"},null,-1);function nt(e,t,s,a,o,r){const i=Object(n["B"])("router-link");return Object(n["t"])(),Object(n["f"])("div",Qe,[Ze,Object(n["i"])("div",et,[e.canEdit?(Object(n["t"])(),Object(n["f"])(i,{key:0,to:`/posts/${e.post.id}/edit`,class:"button is-link is-rounded","data-test":"can-edit"},{default:Object(n["I"])(()=>[tt]),_:1},8,["to"])):Object(n["g"])("",!0),Object(n["i"])("h1",null,Object(n["E"])(e.post.title),1),Object(n["i"])("div",{innerHTML:e.post.html},null,8,["innerHTML"])]),st])}var at=Object(n["j"])({async setup(e){const t=q(),s=Object(y["c"])().params.id;t.getState().posts.loaded||await t.fetchPosts();const n=t.getState().posts.all.get(s);if(!n)throw Error("Post was not found.");const a=n.authorId===t.getState().authors.currentUserId;return{post:n,canEdit:a}}});at.render=nt;var ot=at,rt=Object(n["j"])({components:{PostViewer:ot,Spinner:Ie}});rt.render=Xe;var it=rt;function ct(e,t,s,a,o,r){const i=Object(n["B"])("post-editor"),c=Object(n["B"])("spinner");return Object(n["t"])(),Object(n["f"])(n["b"],null,{default:Object(n["I"])(()=>[Object(n["i"])(i)]),fallback:Object(n["I"])(()=>[Object(n["i"])(c)]),_:1})}function lt(e,t,s,a,o,r){const i=Object(n["B"])("post-writer");return Object(n["t"])(),Object(n["f"])(i,{post:e.post,onSave:e.save},null,8,["post","onSave"])}var pt=Object(n["j"])({components:{PostWriter:Ge},async setup(e){const t=q(),s=Object(y["d"])(),n=Object(y["c"])().params.id;t.getState().posts.loaded||await t.fetchPosts();const a=t.getState().posts.all.get(n);if(!a)throw Error("Post was not found.");a.authorId!==t.getState().authors.currentUserId&&s.push("/");const o=async e=>{await t.updatePost(e),s.push("/")};return{post:a,save:o}}});pt.render=lt;var dt=pt,ut=Object(n["j"])({components:{PostEditor:dt,Spinner:Ie}});ut.render=ct;var ht=ut;function mt(e){const t=Object(y["a"])({history:Object(y["b"])("/vuejs-composition-api-course"),routes:[{path:"/",component:Ce},{path:"/posts/:id/edit",component:ht,meta:{requiresAuth:!0}},{path:"/posts/:id",component:it},{path:"/posts/new",component:$e,meta:{requiresAuth:!0}}]});return t.beforeEach((t,s,n)=>{const a=!!e.getState().authors.currentUserId;t.meta.requiresAuth?t.meta.requiresAuth&&a?n():n({path:"/"}):n()}),t}var ft=s("0f40"),yt=s.n(ft);s("9f21");function gt(){return new Promise(e=>{setTimeout(e,500)})}W.a.get=async e=>{if("/posts"===e)return await gt(),Promise.resolve({data:[ie,ce,le,...pe]})},W.a.post=async(e,t)=>{if("/posts"===e){const e=yt()(100,1e4);await gt();const s={...t,id:e.toString(),title:t.title,created:t.created,authorId:t.authorId};return Promise.resolve({data:s})}if("/users"===e){const e=yt()(100,1e4);await gt();const s={id:e.toString(),username:t.username};return Promise.resolve({data:s})}if("/sign_in"===e){if("user123"!==t.username||"pass42"!==t.password)return;await gt();const e={id:"1",username:t.username};return Promise.resolve({data:e})}},W.a.put=async(e,t)=>{if("/posts"===e){await gt();const e={...t,title:t.title,created:t.created,authorId:t.authorId};return Promise.resolve({data:e})}};const bt=Object(n["e"])(G),jt=mt(P);bt.use(jt),bt.use(P),bt.mount("#app")},d4a2:function(e,t,s){},f00e:function(e,t,s){}});
  //# sourceMappingURL=app.4209b07d.js.map