<turbo-stream action="append" target="posts_list"><template>    <div class="postbit" id="342097" data-post-id="342097">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="Jskalc" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/Jskalc/120/35876_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  Jskalc
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Me again! Just finished a big internal cleanup &amp; migration to TS, fixed some bugs along the way and prepared for further development.</p>
<p>Excerpt from the changelog:</p>
<h2><a name="p-342097-h-051-2024-10-08-1" class="anchor" href="#p-342097-h-051-2024-10-08-1" aria-label="Heading link" rel="nofollow"></a>0.5.1 - 2024-10-08</h2>
<h2><a name="p-342097-fixed-2" class="anchor" href="#p-342097-fixed-2" aria-label="Heading link" rel="nofollow"></a>Fixed</h2>
<ul>
<li>Fixed a bug where the server was not preloading the correct assets for the Vue components. It happened because CursorAI “skipped” important part of the code when migrating to the TypeScript <img src="https://forum.elixirforum.com/images/emoji/apple/sweat_smile.png?v=15" title=":sweat_smile:" class="emoji" alt=":sweat_smile:" loading="lazy" width="20" height="20"></li>
</ul>
<h2><a name="p-342097-h-050-2024-10-08-3" class="anchor" href="#p-342097-h-050-2024-10-08-3" aria-label="Heading link" rel="nofollow"></a>0.5.0 - 2024-10-08</h2>
<h2><a name="p-342097-changed-4" class="anchor" href="#p-342097-changed-4" aria-label="Heading link" rel="nofollow"></a>Changed</h2>
<ul>
<li>Migrated the project to TypeScript <img src="https://forum.elixirforum.com/images/emoji/apple/purple_heart.png?v=15" title=":purple_heart:" class="emoji" alt=":purple_heart:" loading="lazy" width="20" height="20"> <a href="https://github.com/Valian/live_vue/pull/32" rel="noopener nofollow ugc">#32</a></li>
<li>Added <code>createLiveVue</code> entrypoint to make it easier to customize Vue app initialization</li>
</ul>
<h2><a name="p-342097-deprecations-5" class="anchor" href="#p-342097-deprecations-5" aria-label="Heading link" rel="nofollow"></a>Deprecations</h2>
<ul>
<li><code>assets/vue/index.js</code> should export app created by <code>createLiveVue()</code>, not just available components. See migration below.</li>
</ul>
<h2><a name="p-342097-migration-6" class="anchor" href="#p-342097-migration-6" aria-label="Heading link" rel="nofollow"></a>Migration</h2>
<p>In <code>assets/js/app.js</code>, instead of:</p>
<pre data-code-wrap="js"><code class="lang-js">export default {
  ...import.meta.glob("./**/*.vue", { eager: true }),
  ...import.meta.glob("../../lib/**/*.vue", { eager: true }),
}
</code></pre>
<p>use:</p>
<pre data-code-wrap="js"><code class="lang-js">// polyfill recommended by Vite https://vitejs.dev/config/build-options#build-modulepreload
import "vite/modulepreload-polyfill"
import { h } from "vue"
import { createLiveVue, findComponent } from "live_vue"

export default createLiveVue({
  resolve: name =&gt; {
    const components = {
      ...import.meta.glob("./**/*.vue", { eager: true }),
      ...import.meta.glob("../../lib/**/*.vue", { eager: true }),
    }

    // finds component by name or path suffix and gives a nice error message.
    // `path/to/component/index.vue` can be found as `path/to/component` or simply `component`
    // `path/to/Component.vue` can be found as `path/to/Component` or simply `Component`
    return findComponent(components, name)
  },
  setup: ({ createApp, component, props, slots, plugin, el }) =&gt; {
    const app = createApp({ render: () =&gt; h(component, props, slots) })
    app.use(plugin)
    app.mount(el)
    return app
  },
})
</code></pre>
<p>then, in <code>assets/js/app.js</code>, instead of:</p>
<pre data-code-wrap="js"><code class="lang-js">import components from "./vue"
</code></pre>
<p>simply do</p>
<pre data-code-wrap="js"><code class="lang-js">import { getHooks } from "live_vue"
import liveVueApp from "./vue"
// ...

const hooks = { ...getHooks(liveVueApp) }
</code></pre>
<p>If you had any custom initialization code, you have to move it to <code>createLiveVue().setup()</code> function.</p>
<h2><a name="p-342097-fixed-7" class="anchor" href="#p-342097-fixed-7" aria-label="Heading link" rel="nofollow"></a>Fixed</h2>
<ul>
<li>Nicely formatted JS error stracktraces during SSR <a href="https://github.com/Valian/live_vue/commit/10f672bce4104a38523905c52c4879083e4bc6db" rel="noopener nofollow ugc">commit</a></li>
<li>Previously <code>initializeVueApp</code> was not working in SSR mode, since it was declared in app.js which couldn’t be imported by server bundle. Now it’s in a separate file as <code>createLiveVue().setup()</code> and can be imported by both client and server bundles.</li>
</ul> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="342097" data-batch-url="/posts/batch_likers">
                        8
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/livevue-seamless-integration-of-vue-with-phoenix-liveview/63580/42">Post #41</a>
	                </div>
	            </div>
              <div id="likers-container-342097" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="342097"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #41"></div>
  </section>
</div>
    <div class="postbit" id="349524" data-post-id="349524">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="Jskalc" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/Jskalc/120/35876_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  Jskalc
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Hi <img src="https://forum.elixirforum.com/images/emoji/apple/wave.png?v=15" title=":wave:" class="emoji" alt=":wave:" loading="lazy" width="20" height="20">  Things are shaping up nicely!</p>
<p>Important news: I’ve just created a Elixir slack channel where all the contributors and users could freely talk about live_vue. It’s named simply <a href="https://elixir-lang.slack.com/archives/C084WRKG7SQ" rel="noopener nofollow ugc">#live_vue</a> <img src="https://forum.elixirforum.com/images/emoji/apple/slight_smile.png?v=15" title=":slight_smile:" class="emoji" alt=":slight_smile:" loading="lazy" width="20" height="20"></p>
<p>In the meantime, we had a few small patch releases, mostly improving TS support. Current version is 0.5.7, you can check changelog <a href="https://github.com/Valian/live_vue/blob/main/CHANGELOG.md" rel="noopener nofollow ugc">here</a></p>
<p>We also have an ongoing work towards 0.6.0. The plan for now:</p>
<ul>
<li>docs rewrite to make a clear place for everything (Readme got too big)</li>
<li>testing utilities</li>
<li>shared props</li>
<li>multiple JS utilities</li>
</ul>
<p>I’ll let you know once it’s ready <img src="https://forum.elixirforum.com/images/emoji/apple/wink.png?v=15" title=":wink:" class="emoji" alt=":wink:" loading="lazy" width="20" height="20"></p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="349524" data-batch-url="/posts/batch_likers">
                        3
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/livevue-seamless-integration-of-vue-with-phoenix-liveview/63580/43">Post #42</a>
	                </div>
	            </div>
              <div id="likers-container-349524" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="349524"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #42"></div>
  </section>
</div>
    <div class="postbit" id="370198" data-post-id="370198">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="Jskalc" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/Jskalc/120/35876_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  Jskalc
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<h2><a name="p-370198-livevuehttpsforumelixirforumcomtlivevue-seamless-integration-of-vue-with-phoenix-liveview63580-v060-was-released-1" class="anchor" href="#p-370198-livevuehttpsforumelixirforumcomtlivevue-seamless-integration-of-vue-with-phoenix-liveview63580-v060-was-released-1" aria-label="Heading link" rel="nofollow"></a><a href="https://forum.elixirforum.com/t/livevue-seamless-integration-of-vue-with-phoenix-liveview/63580" rel="nofollow">LiveVue</a> v0.6.0 was released!</h2>
<p><img src="https://raw.githubusercontent.com/Valian/live_vue/main/live_vue_logo_rounded.png" alt="live_vue_logo_rounded" width="200" height="200"></p>
<p>Hey everyone! <img src="https://forum.elixirforum.com/images/emoji/apple/waving_hand.png?v=15" title=":waving_hand:" class="emoji" alt=":waving_hand:" loading="lazy" width="20" height="20"></p>
<p>I’m excited to announce the release of <strong>LiveVue v0.6.0</strong> - this is by far the biggest update since the initial release, with many months of development packed into it. If you’ve been waiting for LiveVue to mature, this is the release that takes it to the next level! <img src="https://forum.elixirforum.com/images/emoji/apple/rocket.png?v=15" title=":rocket:" class="emoji" alt=":rocket:" loading="lazy" width="20" height="20"></p>
<h2><a name="p-370198-whats-new-2" class="anchor" href="#p-370198-whats-new-2" aria-label="Heading link" rel="nofollow"></a>What’s New?</h2>
<h3><a name="p-370198-massive-performance-boost-with-json-patch-diffs-3" class="anchor" href="#p-370198-massive-performance-boost-with-json-patch-diffs-3" aria-label="Heading link" rel="nofollow"></a><img src="https://forum.elixirforum.com/images/emoji/apple/high_speed_train.png?v=15" title=":high_speed_train:" class="emoji" alt=":high_speed_train:" loading="lazy" width="20" height="20"> <strong>Massive Performance Boost with JSON Patch Diffs</strong></h3>
<p>LiveVue now uses JSON Patch operations to send only the minimal differences when props change. Instead of sending entire prop objects over the WebSocket, only the specific fields that changed are transmitted. This dramatically reduces payload sizes (in many cases over 90%), especially for complex nested structures and lists. Performance of diffing is carefully optimized to be a non-issue.</p>
<p>It works seamlessly with custom structs through the <code>LiveVue.Encoder</code> protocol, designed to be compatible with <code>Jason.Encoder</code>. You can easily implement custom encoders for your own types - for example, here’s how we hide sensitive fields from User struct:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defimpl LiveVue.Encoder, for: MyApp.Accounts.User do
  def encode(%MyApp.Accounts.User{} = user, _opts) do
    user 
    |&gt; Map.take([:first_name, :last_name]) 
    |&gt; LiveVue.Encoder.encode(opts)
  end
end
</code></pre>
<p>Pulling it of required changes to <a href="https://github.com/phoenixframework/phoenix_live_view/pull/3392" rel="noopener nofollow ugc">LiveView</a> and multiple PRs to <a href="https://github.com/corka149/jsonpatch/pulls?q=is%3Apr+author%3AValian" rel="noopener nofollow ugc">Jsonpatch library</a>.</p>
<h3><a name="p-370198-complete-documentation-overhaul-4" class="anchor" href="#p-370198-complete-documentation-overhaul-4" aria-label="Heading link" rel="nofollow"></a><img src="https://forum.elixirforum.com/images/emoji/apple/books.png?v=15" title=":books:" class="emoji" alt=":books:" loading="lazy" width="20" height="20"> <strong>Complete Documentation Overhaul</strong></h3>
<p>The <a href="https://hexdocs.pm/live_vue/readme.html" rel="noopener nofollow ugc">docs</a> have been completely rewritten with comprehensive guides, API reference, and examples. Much easier to get started and go deeper!</p>
<h3><a name="p-370198-client-side-utilities-5" class="anchor" href="#p-370198-client-side-utilities-5" aria-label="Heading link" rel="nofollow"></a><img src="https://forum.elixirforum.com/images/emoji/apple/toolbox.png?v=15" title=":toolbox:" class="emoji" alt=":toolbox:" loading="lazy" width="20" height="20"> <strong>Client-side utilities</strong></h3>
<ul>
<li><strong><code>useLiveNavigation</code></strong> - programmatic navigation with <code>live_patch</code> and <code>live_redirect</code></li>
<li><strong><code>useLiveEvent</code></strong> - simplified server-pushed event handling with automatic lifecycle management</li>
<li><strong><code>&lt;Link&gt;</code></strong> component - Vue component for LiveView navigation</li>
<li><strong><code>$live</code></strong> available globally in Vue templates - you can now write <code>@click="$live.pushEvent('click')"</code></li>
</ul>
<h3><a name="p-370198-typescript-by-default-6" class="anchor" href="#p-370198-typescript-by-default-6" aria-label="Heading link" rel="nofollow"></a><img src="https://forum.elixirforum.com/images/emoji/apple/blue_heart.png?v=15" title=":blue_heart:" class="emoji" alt=":blue_heart:" loading="lazy" width="20" height="20"> <strong>TypeScript by Default</strong></h3>
<p>The client-side setup is now TypeScript out of the box, with full type safety and autocompletion. The <code>$live</code> property is now automatically available in all Vue templates and fully typed.</p>
<h3><a name="p-370198-testing-utilities-7" class="anchor" href="#p-370198-testing-utilities-7" aria-label="Heading link" rel="nofollow"></a><img src="https://forum.elixirforum.com/images/emoji/apple/test_tube.png?v=15" title=":test_tube:" class="emoji" alt=":test_tube:" loading="lazy" width="20" height="20"> <strong>Testing Utilities</strong></h3>
<p>New <code>LiveVue.Test</code> module provides helpers to inspect Vue component configuration within your LiveView tests.</p>
<h2><a name="p-370198-migration-8" class="anchor" href="#p-370198-migration-8" aria-label="Heading link" rel="nofollow"></a>Migration</h2>
<p>If you have an existing setup, there’s a small migration to move from <code>assets/vue/index.js</code> to <code>assets/vue/index.ts</code> and use the new TypeScript setup. The migration guide in the changelog has all the details - it’s straightforward!</p>
<h2><a name="p-370198-whats-next-9" class="anchor" href="#p-370198-whats-next-9" aria-label="Heading link" rel="nofollow"></a>What’s Next?</h2>
<p>This release really solidifies LiveVue as a mature solution for Vue + LiveView integration. The performance improvements and developer experience enhancements should make it even more enjoyable to work with.</p>
<p>As always, I’d love to hear your feedback and experiences with the new release! And if you find LiveVue useful, a <img src="https://forum.elixirforum.com/images/emoji/apple/star.png?v=15" title=":star:" class="emoji" alt=":star:" loading="lazy" width="20" height="20"> on the <a href="https://github.com/Valian/live_vue" rel="noopener nofollow ugc">GitHub repo</a> would be much appreciated <img src="https://forum.elixirforum.com/images/emoji/apple/blush.png?v=15" title=":blush:" class="emoji" alt=":blush:" loading="lazy" width="20" height="20"></p>
<hr>
<h2><a name="p-370198-full-release-notes-10" class="anchor" href="#p-370198-full-release-notes-10" aria-label="Heading link" rel="nofollow"></a>Full Release Notes</h2>
<details>
<summary>
Click to expand</summary>
<h3><a name="p-370198-features-and-improvements-11" class="anchor" href="#p-370198-features-and-improvements-11" aria-label="Heading link" rel="nofollow"></a><img src="https://forum.elixirforum.com/images/emoji/apple/sparkles.png?v=15" title=":sparkles:" class="emoji" alt=":sparkles:" loading="lazy" width="20" height="20"> Features and Improvements</h3>
<ul>
<li><strong>JSON Patch Diffs for Props</strong>: LiveVue now uses JSON Patch operations to send only the minimal differences when props change, dramatically reducing WebSocket payload sizes. Instead of sending entire prop objects, only the specific fields that changed are transmitted using RFC 6902 JSON Patch format. This optimization works seamlessly with complex nested structures, lists, and custom structs through the <code>LiveVue.Encoder</code> protocol. It’s possible to skip diffs by setting <code>v-diff</code> to <code>false</code> on the component or by setting <code>config :live_vue, enable_props_diff: false</code> in your config. (<a href="https://github.com/Valian/live_vue/pull/60" rel="noopener nofollow ugc">#60</a>)</li>
<li><strong>New <code>useLiveNavigation</code> Composable</strong>: A new <code>useLiveNavigation</code> composable has been added for programmatic navigation, mirroring the functionality of <code>live_patch</code> and <code>live_redirect</code>. (<a href="https://github.com/Valian/live_vue/pull/59" rel="noopener nofollow ugc">#59</a>).</li>
<li><strong>New <code>useLiveEvent</code> Composable</strong>: A new <code>useLiveEvent</code> composable has been added to simplify listening to server-pushed events. It automatically manages event listener lifecycle, reducing boilerplate and preventing memory leaks (<a href="https://github.com/Valian/live_vue/pull/58" rel="noopener nofollow ugc">#58</a>).</li>
<li><strong>New <code>Link</code> Component</strong>: A new <code>&lt;Link&gt;</code> Vue component has been added to simplify <code>live_patch</code> and <code>live_redirect</code> navigation within your Vue components. (<a href="https://github.com/Valian/live_vue/pull/47" rel="noopener nofollow ugc">#47</a>).</li>
<li><strong>TypeScript by Default</strong>: The client-side entrypoint at <code>assets/vue/index.ts</code> is now a TypeScript file by default, improving type safety and the development experience out of the box.</li>
<li><strong><code>$live</code> Template Property</strong>: The LiveView hook instance is now automatically available in all Vue templates as the <code>$live</code> property, providing a convenient alternative to <code>useLiveVue()</code>. The property is now also fully typed, providing autocompletion and type checking in your editor. (<a href="https://github.com/Valian/live_vue/pull/56" rel="noopener nofollow ugc">#56</a>).</li>
<li><strong>Documentation Overhaul</strong>: The documentation has been completely rewritten and expanded. It now includes comprehensive guides on architecture, basic and advanced usage, a full client-side API reference, a component reference, and much more. (<a href="https://github.com/Valian/live_vue/pull/49" rel="noopener nofollow ugc">#49</a>)</li>
<li><strong>Testing Utilities</strong>: The new <code>LiveVue.Test</code> module provides helpers to inspect Vue component configuration (props, slots, event handlers) within your LiveView tests, making it easier to write assertions. (<a href="https://github.com/Valian/live_vue/pull/46" rel="noopener nofollow ugc">#46</a>)</li>
<li><strong>GitHub CI</strong>: A new GitHub Actions workflow has been added to run tests automatically.</li>
</ul>
<h3><a name="p-370198-migration-guide-12" class="anchor" href="#p-370198-migration-guide-12" aria-label="Heading link" rel="nofollow"></a><img src="https://forum.elixirforum.com/images/emoji/apple/up_arrow.png?v=15" title=":up_arrow:" class="emoji" alt=":up_arrow:" loading="lazy" width="20" height="20"> Migration Guide</h3>
<p>This version transitions the default client-side setup to TypeScript and renames ~V sigil to ~VUE. If you have an existing <code>assets/vue/index.js</code>, follow these steps to upgrade:</p>
<ol>
<li>
<p><strong>Rename and replace <code>index.js</code></strong>:</p>
<ul>
<li>Delete your existing <code>assets/vue/index.js</code>.</li>
<li>Create a new file at <code>assets/vue/index.ts</code> with the following content:</li>
</ul>
<pre data-code-wrap="javascript"><code class="lang-javascript">// polyfill recommended by Vite https://vitejs.dev/config/build-options#build-modulepreload
import "vite/modulepreload-polyfill"
import { Component, h } from "vue"
import { createLiveVue, findComponent, type LiveHook, type ComponentMap } from "live_vue"

// needed to make $live available in the Vue component
declare module "vue" {
  interface ComponentCustomProperties {
    $live: LiveHook
  }
}

export default createLiveVue({
  // name will be passed as-is in v-component of the .vue HEEX component
  resolve: name =&gt; {
    // we're importing from ../../lib to allow collocating Vue files with LiveView files
    // eager: true disables lazy loading - all these components will be part of the app.js bundle
    // more: https://vite.dev/guide/features.html#glob-import
    const components = {
      ...import.meta.glob("./**/*.vue", { eager: true }),
      ...import.meta.glob("../../lib/**/*.vue", { eager: true }),
    } as ComponentMap

    // finds component by name or path suffix and gives a nice error message.
    // `path/to/component/index.vue` can be found as `path/to/component` or simply `component`
    // `path/to/Component.vue` can be found as `path/to/Component` or simply `Component`
    return findComponent(components as ComponentMap, name)
  },
  // it's a default implementation of creating and mounting vue app, you can easily extend it to add your own plugins, directives etc.
  setup: ({ createApp, component, props, slots, plugin, el }) =&gt; {
    const app = createApp({ render: () =&gt; h(component as Component, props, slots) })
    app.use(plugin)
    // add your own plugins here
    // app.use(pinia)
    app.mount(el)
    return app
  },
})
</code></pre>
</li>
<li>
<p><strong>Update <code>tsconfig.json</code></strong>:<br>
Add <code>vue/index.ts</code> to the <code>include</code> array in your <code>assets/tsconfig.json</code> file.</p>
</li>
</ol>
<h3><a name="p-370198-bug-fixes-13" class="anchor" href="#p-370198-bug-fixes-13" aria-label="Heading link" rel="nofollow"></a><img src="https://forum.elixirforum.com/images/emoji/apple/bug.png?v=15" title=":bug:" class="emoji" alt=":bug:" loading="lazy" width="20" height="20"> Bug Fixes</h3>
<ul>
<li><strong>SSR Attribute Rendering</strong>: Fixed a bug where the <code>data-ssr</code> attribute was not being rendered correctly as a boolean <code>true</code> or <code>false</code> in the final HTML.</li>
</ul>
<h3><a name="p-370198-housekeeping-14" class="anchor" href="#p-370198-housekeeping-14" aria-label="Heading link" rel="nofollow"></a>Housekeeping</h3>
<ul>
<li><strong>Optional Floki Dependency</strong>: <code>floki</code> is now an optional dependency, only required if you use the new testing utilities.</li>
<li><strong>Dependency Updates</strong>: NPM dependencies have been updated to address security vulnerabilities.</li>
<li><strong>Dropped Elixir 1.12 Support</strong>: Support for Elixir 1.12 has been removed to align with Phoenix LiveView’s supported versions.</li>
</ul>
</details> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="370198" data-batch-url="/posts/batch_likers">
                        23
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/livevue-seamless-integration-of-vue-with-phoenix-liveview/63580/44">Post #43</a>
	                </div>
	            </div>
              <div id="likers-container-370198" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="370198"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #43"></div>
  </section>
</div>
    <div class="postbit" id="370844" data-post-id="370844">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="Jskalc" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/Jskalc/120/35876_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  Jskalc
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<h1><a name="p-370844-livevue-070httpsforumelixirforumcomtlivevue-seamless-integration-of-vue-with-phoenix-liveview63580-file-upload-composable-comprehensive-testing-suite-1" class="anchor" href="#p-370844-livevue-070httpsforumelixirforumcomtlivevue-seamless-integration-of-vue-with-phoenix-liveview63580-file-upload-composable-comprehensive-testing-suite-1" aria-label="Heading link" rel="nofollow"></a><a href="https://forum.elixirforum.com/t/livevue-seamless-integration-of-vue-with-phoenix-liveview/63580" rel="nofollow">LiveVue 0.7.0</a> - File Upload Composable &amp; Comprehensive Testing Suite!</h1>
<p>Hey everyone! <img src="https://forum.elixirforum.com/images/emoji/apple/waving_hand.png?v=15" title=":waving_hand:" class="emoji" alt=":waving_hand:" loading="lazy" width="20" height="20"></p>
<p>I’m excited to announce the release of <strong>LiveVue v0.7.0</strong>! This release focuses on making file uploads seamless and establishing a robust testing foundation for the library. If you’ve been waiting for better file upload support or wondering about LiveVue’s reliability, this release addresses both! <img src="https://forum.elixirforum.com/images/emoji/apple/rocket.png?v=15" title=":rocket:" class="emoji" alt=":rocket:" loading="lazy" width="20" height="20"></p>
<h2><a name="p-370844-whats-new-2" class="anchor" href="#p-370844-whats-new-2" aria-label="Heading link" rel="nofollow"></a>What’s New?</h2>
<h3><a name="p-370844-file_folder-new-useliveupload-composable-3" class="anchor" href="#p-370844-file_folder-new-useliveupload-composable-3" aria-label="Heading link" rel="nofollow"></a><img src="https://forum.elixirforum.com/images/emoji/apple/file_folder.png?v=15" title=":file_folder:" class="emoji" alt=":file_folder:" loading="lazy" width="20" height="20"> <strong>New <code>useLiveUpload</code> Composable</strong></h3>
<p>The biggest new feature in this release is the new <code>useLiveUpload()</code> composable that makes Phoenix LiveView file uploads feel native in Vue! Let’s take a look at how it works:</p>
<p><strong>Backend (LiveView):</strong></p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule MyAppWeb.UploadLive do
  use MyAppWeb, :live_view

  def render(assigns) do
    ~H"""
    &lt;.vue
      upload={@uploads.documents}
      uploaded_files={@uploaded_files}
      v-component="FileUploader"
      v-socket={@socket}
    /&gt;
    """
  end

  def mount(_params, _session, socket) do
    {:ok,
     socket
     |&gt; assign(:uploaded_files, [])
     |&gt; allow_upload(:documents,
       accept: ~w(.pdf .txt .jpg .png),
       max_entries: 3,
       auto_upload: true
     )}
  end

  def handle_event("validate", _params, socket), do: {:noreply, socket}

  def handle_event("save", _params, socket) do
    uploaded_files =
      consume_uploaded_entries(socket, :documents, fn %{path: path}, entry -&gt;
        dest = Path.join("uploads", entry.client_name)
        File.cp!(path, dest)
        {:ok, %{name: entry.client_name, size: entry.client_size}}
      end)

    {:noreply, update(socket, :uploaded_files, &amp;(&amp;1 ++ uploaded_files))}
  end
end
</code></pre>
<p><strong>Frontend (Vue):</strong></p>
<pre data-code-wrap="html"><code class="lang-html">&lt;template&gt;
  &lt;div&gt;
    &lt;button @click="showFilePicker"&gt;Select Files&lt;/button&gt;
    &lt;button v-if="entriesCount &gt; 0" @click="cancel()"&gt;Cancel All&lt;/button&gt;

    &lt;div v-for="entry in entries" :key="entry.ref"&gt;
      &lt;span&gt;{{ entry.client_name }} ({{ entry.client_size }} bytes)&lt;/span&gt;
      &lt;span&gt;{{ entry.progress || 0 }}%&lt;/span&gt;
      &lt;span&gt;{{ entry.done ? 'done' : 'pending' }}&lt;/span&gt;
      &lt;button @click="cancel(entry.ref)"&gt;×&lt;/button&gt;
    &lt;/div&gt;

    &lt;div v-if="uploadedFiles.length"&gt;
      &lt;h3&gt;Uploaded Files&lt;/h3&gt;
      &lt;div v-for="file in uploadedFiles" :key="file.name"&gt;
        {{ file.name }} ({{ file.size }} bytes)
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/template&gt;

&lt;script setup lang="ts"&gt;
import { computed } from "vue"
import { useLiveUpload, UploadConfig } from "live_vue"

interface Props {
  upload: UploadConfig
  uploadedFiles: { name: string; size: number }[]
}

const props = defineProps&lt;Props&gt;()

const { entries, showFilePicker, submit, cancel } = useLiveUpload(() =&gt; props.upload, {
  changeEvent: "validate",
  submitEvent: "save"
})

const entriesCount = computed(() =&gt; entries.value?.length || 0)
&lt;/script&gt;
</code></pre>
<p><strong>Key features:</strong></p>
<ul>
<li><strong>Reactive upload state</strong> - Progress tracking, metadata, and validation status</li>
<li><strong>Automatic DOM management</strong> - File inputs are handled seamlessly</li>
<li><strong>Drag-and-drop support</strong> - Built-in drag-and-drop file handling</li>
<li><strong>LiveView integration</strong> - Works perfectly with Phoenix’s upload validation and submission</li>
<li><strong>Full TypeScript support</strong> - Complete type definitions for all upload configurations</li>
<li><strong>Declarative API</strong> - Vue-friendly approach to complex upload workflows</li>
</ul>
<h3><a name="p-370844-test_tube-comprehensive-testing-foundation-4" class="anchor" href="#p-370844-test_tube-comprehensive-testing-foundation-4" aria-label="Heading link" rel="nofollow"></a><img src="https://forum.elixirforum.com/images/emoji/apple/test_tube.png?v=15" title=":test_tube:" class="emoji" alt=":test_tube:" loading="lazy" width="20" height="20"> <strong>Comprehensive Testing Foundation</strong></h3>
<p>This release establishes LiveVue as a thoroughly tested library with multiple testing layers:</p>
<p><strong>Frontend Test Suite with Vitest:</strong></p>
<ul>
<li>36 test cases covering JSON Patch functionality with edge cases</li>
<li>Tests for escaped characters, deep cloning, and complex patch sequences</li>
<li>jsdom environment for DOM testing</li>
<li>Full coverage of the TypeScript codebase</li>
</ul>
<p><strong>End-to-End Testing with Playwright:</strong></p>
<ul>
<li>5 comprehensive test suites covering all major functionality</li>
<li>Tests for Vue component rendering, props passing, event emission</li>
<li>LiveView/Vue synchronization testing</li>
<li>File upload workflow testing</li>
<li>Navigation and prop diffing validation</li>
</ul>
<p><strong>Improved CI/CD:</strong></p>
<ul>
<li><strong>E2E CI</strong>: Automatically runs E2E tests on every PR</li>
<li><strong>Frontend CI</strong>: Tests across Node.js 20, 22, and 24 (LTS and current)</li>
<li><strong>Elixir CI</strong>: Focused Elixir/OTP matrix testing</li>
</ul>
<p><strong>New Mix Commands:</strong></p>
<pre data-code-wrap="bash"><code class="lang-bash">mix assets.test          # Run frontend tests
npm test                 # Run tests once
npm run test:watch       # Run tests in watch mode
npm run test:ui          # Interactive test UI
npm run e2e:test         # E2E tests
npm run e2e:test:headed  # E2E with browser UI
npm run e2e:test:debug   # E2E debug mode
</code></pre>
<h2><a name="p-370844-why-this-matters-5" class="anchor" href="#p-370844-why-this-matters-5" aria-label="Heading link" rel="nofollow"></a>Why This Matters</h2>
<p>File uploads have always been one of the trickier aspects of web development, especially when bridging server-side frameworks like Phoenix with client-side reactive frameworks. The <code>useLiveUpload</code> composable eliminates this complexity entirely, giving you a Vue-native way to handle uploads while leveraging all of Phoenix LiveView’s powerful upload features.</p>
<p>The comprehensive testing suite ensures LiveVue’s reliability and gives confidence for production use. With both unit tests for the TypeScript code and E2E tests covering real-world scenarios, you can trust that LiveVue will work as expected.</p>
<h2><a name="p-370844-whats-next-6" class="anchor" href="#p-370844-whats-next-6" aria-label="Heading link" rel="nofollow"></a>What’s Next?</h2>
<p>With file uploads now seamlessly integrated and a solid testing foundation in place, LiveVue continues to mature as the go-to solution for Vue + Phoenix LiveView applications. The testing infrastructure also sets us up for more confident development of future features.</p>
<p>Next step: useLiveForm() composable for seamless form handling <img src="https://forum.elixirforum.com/images/emoji/apple/purple_heart.png?v=15" title=":purple_heart:" class="emoji" alt=":purple_heart:" loading="lazy" width="20" height="20"></p>
<p>As always, I’d love to hear your feedback and experiences with the new release! And if you find LiveVue useful, a <img src="https://forum.elixirforum.com/images/emoji/apple/star.png?v=15" title=":star:" class="emoji" alt=":star:" loading="lazy" width="20" height="20"> on the <a href="https://github.com/Valian/live_vue" rel="noopener nofollow ugc">GitHub repo</a> would be much appreciated <img src="https://forum.elixirforum.com/images/emoji/apple/blush.png?v=15" title=":blush:" class="emoji" alt=":blush:" loading="lazy" width="20" height="20"></p>
<hr>
<details>
<summary>
Full Release Notes</summary>
<h3><a name="p-370844-new-composable-useliveupload-7" class="anchor" href="#p-370844-new-composable-useliveupload-7" aria-label="Heading link" rel="nofollow"></a><img src="https://forum.elixirforum.com/images/emoji/apple/rocket.png?v=15" title=":rocket:" class="emoji" alt=":rocket:" loading="lazy" width="20" height="20"> New Composable: useLiveUpload</h3>
<ul>
<li><strong>File Upload Composable</strong>: Added <code>useLiveUpload()</code> composable for seamless Vue integration with Phoenix LiveView file uploads
<ul>
<li>Provides reactive upload state management with progress tracking and metadata</li>
<li>Automatic DOM element management for file inputs</li>
<li>Built-in support for drag-and-drop file handling</li>
<li>Integration with LiveView’s upload validation and submission system</li>
<li>TypeScript support with full type definitions for upload configurations</li>
<li>Simplifies complex upload workflows with a declarative Vue-friendly API</li>
</ul>
</li>
</ul>
<h3><a name="p-370844-testing-improvements-8" class="anchor" href="#p-370844-testing-improvements-8" aria-label="Heading link" rel="nofollow"></a><img src="https://forum.elixirforum.com/images/emoji/apple/test_tube.png?v=15" title=":test_tube:" class="emoji" alt=":test_tube:" loading="lazy" width="20" height="20"> Testing Improvements</h3>
<ul>
<li><strong>Frontend Test Suite</strong>: Added comprehensive test suite for frontend TypeScript code using Vitest framework
<ul>
<li>Tests for JSON Patch functionality with 36 test cases covering all operations</li>
<li>Test configuration with jsdom environment for DOM testing</li>
<li>Coverage includes edge cases like escaped characters, deep cloning, and complex patch sequences</li>
</ul>
</li>
<li><strong>End-to-End Test Suite</strong>: Added comprehensive E2E test suite using Playwright framework
<ul>
<li>Tests for Vue component rendering, props passing, event emission, and LiveView/Vue synchronization</li>
<li>5 test suites covering basic functionality, navigation, events, prop diffs, and file uploads</li>
<li>Test server setup with dedicated Phoenix endpoint for isolated testing</li>
<li>Utilities for LiveView/Vue synchronization and server-side code execution</li>
</ul>
</li>
<li><strong>Separate CI Workflows</strong>: Split testing into dedicated workflows for better separation of concerns
<ul>
<li><strong>Frontend CI</strong>: Tests TypeScript code across Node.js versions 20, 22, and 24 (LTS and current stable)</li>
<li><strong>Elixir CI</strong>: Focused purely on Elixir/OTP matrix testing without redundant frontend test runs</li>
</ul>
</li>
<li><strong>New Mix Commands</strong>: Added <code>mix assets.test</code> alias for running frontend tests alongside existing npm scripts
<ul>
<li><code>npm test</code> - Run tests once</li>
<li><code>npm run test:watch</code> - Run tests in watch mode</li>
<li><code>npm run test:ui</code> - Run tests with interactive UI</li>
<li><code>npm run e2e:test</code> - Run E2E tests</li>
<li><code>npm run e2e:test:headed</code> - Run E2E tests with browser UI</li>
<li><code>npm run e2e:test:debug</code> - Run E2E tests in debug mode</li>
</ul>
</li>
</ul>
</details>
<p>Happy coding! <img src="https://forum.elixirforum.com/images/emoji/apple/rocket.png?v=15" title=":rocket:" class="emoji" alt=":rocket:" loading="lazy" width="20" height="20"></p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="370844" data-batch-url="/posts/batch_likers">
                        8
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/livevue-seamless-integration-of-vue-with-phoenix-liveview/63580/45">Post #44</a>
	                </div>
	            </div>
              <div id="likers-container-370844" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="370844"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-last-post cat-last-post" title="Last post!"></div>
  </section>
</div>
</template></turbo-stream><turbo-stream action="replace" target="load-more-container"><template><div id="load-more-container" class="load-more-container">
    <span class="all-loaded">— All posts loaded —</span>
</div></template></turbo-stream>