Error when compiling assets after adding Vue in Sage 10

Hi guys,

I have added Vue to my Sage 10 theme using “bud-vue”, everything is good in dev mode.

After compiling the assets using “yarn build”, it’s like everything inside <main id="main" class="main"> breaks, showing nothing of its content.

My current devDependencies:

"devDependencies": {
    "@roots/bud": "6.11.0",
    "@roots/bud-babel": "^6.11.0",
    "@roots/bud-tailwindcss": "6.11.0",
    "@roots/bud-vue": "^6.11.0",
    "@roots/sage": "6.11.0",
    "browser-sync": "^2.28.3",
    "browser-sync-webpack-plugin": "^2.3.0"
  },

Here is the app.js:

import domReady from '@roots/sage/client/dom-ready';
import {createApp} from "vue";
import App from "./components/App.vue";

/**
 * Application entrypoint
 */
domReady(async () => {
  // ...
});

/**
 * @see {@link https://webpack.js.org/api/hot-module-replacement/}
 */
import.meta.webpackHot?.accept(console.error);

createApp({
  // Every vue component has to be imported here in order to be available
  // in every template.
  components: {App}
}).mount("#main");

My App.vue component:

<script setup>
  import { ref, onMounted } from 'vue'

  const props = defineProps({
    msg: {
      type: String,
    }
  })

  // reactive state
  const count = ref(0)

  // functions that mutate state and trigger updates
  function increment() {
    count.value++
  }

  // lifecycle hooks
  onMounted(() => {
    console.log(`The initial count is ${count.value}.`)
  })
</script>

<template>
  <h3>Here is the message prop: {{ props.msg }}</h3>
  <button @click="increment" class="bg-blue-400 px-10 py-3">Count is: {{ count }}</button>
</template>

No modifications to the bud.config.js

The weird thing is that I don’t see any error in the console. Any error would help but there are no error messages.

What am I doing wrong?

Cheers

Hi @luis_troya-as,

Thanks for providing code and some good detail. The only thing that looks unusual there to me is that you’re calling createApp() outside of the domReady handler. It’s possible the behaviour you’re seeing is due to createApp firing before the DOM is ready. In dev mode, your code will be loaded by HMR, which I guess could mean a delay (to get the DOM in order). Perhaps it’s only working by chance in dev.

Just a thought - I may be completely wrong! Try:

import domReady from '@roots/sage/client/dom-ready';
import {createApp} from "vue";
import App from "./components/App.vue";

/**
 * Application entrypoint
 */
domReady(async () => {
  createApp({
    // Every vue component has to be imported here in order to be available
    // in every template.
    components: {App}
  }).mount("#main");
});

/**
 * @see {@link https://webpack.js.org/api/hot-module-replacement/}
 */
import.meta.webpackHot?.accept(console.error);

Hi @talss89

It doesn’t work :frowning:.

But I have created a repo wit the minimal configuration to run Vue. The repo is a Bedrock project, I have also added the steps to run the project

After creating the repo, I got the same result but I can see an error this time (not sure if it is related)

EventSource's response has a MIME type ("text/html") that is not "text/event-stream". Aborting the connection.

Hope it helps! Let me know if I can do anything else to help.

Cheers

1 Like

In your code, createApp only gets fed a list of components and no root component is set, so Vue will mount but render nothing.

If you change your call to this (as per the docs), the application will be mounted and the App.vue component will be loaded automatically:

domReady(async () => {
   createApp(App).mount("#main");
});

Beware though that mounting on #main directly will clear any PHP/Blade template output provided by @yield('content') inside the <main> element. It might be better to mount on an empty dedicated element (<div id="app"></div>, for example) inside <main></main> instead. Otherwise you have all the database overhead of WordPress querying posts only to have all that content cleared and replaced by Vue when mounting the app.

Hi @mensch,

That’s not entirely true, this is a strategy I have been using in Laravel for several years. This is how it looks with vue 2:

new Vue({
  el: '#app',
});

My idea is to use components in demand. For example, in any given page that extends the template, call a component like <Carousel data="someObj">, and it will render only on that page.

if I use the following code as the docs suggest, it will render the component all the time.

domReady(async () => {
   createApp(App).mount("#main");
});

Does that make sense?

Ah, I can see what you’re doing now - thanks for the repo.

I think the problem you’re running into is that in production, the Vue bundle doesn’t contain the template compiler. Even though <app></app> is present inside #main, the template isn’t compiled, so the App component never initialises.

I haven’t had to bundle the template compiler in Vue 3 before, but you should be able to use vue.runtime.esm-browser.prod.js instead of vue.esm-bundler.js.

I haven’t checked this, but app.vue.runtimeOnly(false) in your bud.config.js seems like it may have the desired effect, and bundle the compiler too.


The more I think about this, the more I wonder if you actually need the compiler if your objective is to…

I hacked together a helper a few weeks ago which could possibly be adapted to avoid the need to include the compiler. You can mountAll(AppComponent, '.css-selector', [ ... plugins ]). Data attributes set on the selected element(s) will automatically be passed as props.

EDIT: I realise I was really vague here, sorry. The idea is to mount multiple ‘root’ Vue components as Apps using a selector string. You can then share state if required using a store like pinia loaded via the uses argument to mountAll(). Instead of <Component> in your blade view, output a <div class="vue-app-component1">, then mountAll(Component1, '.vue-app-component1'). Do this for each component.

May or may not be useful - do what you want with it (MIT).

import { createApp } from 'vue'
import type { Component, Plugin } from 'vue'

export function mountAll(component: Component, selector: string, uses: Plugin[] = []): Component[] {
  
    var apps: Component[] = []
  
    if (typeof document.querySelectorAll(selector)[0] !== 'undefined') {
      const els = Array.prototype.slice.call(document.querySelectorAll(selector));
      
      for (var i in els) {
  
        const atts = {  ...els[i].dataset }
        const innerHtml = els[i].innerHTML
        delete(atts.vApp)
  
        if(typeof atts.inner === 'undefined') {
          atts.inner = innerHtml
        }
  
        const app = createApp(component, atts)
  
        uses.forEach((plugin: Plugin) => {
          app.use(plugin)
        })
  
        app.mount(els[i])
  
        apps.push(app)
  
      }
    }
  
    return apps
  }

The reason I mentioned it, is because bud-vue is set up to do this by default, as it only bundles the runtime version of Vue 3 out of the box.

As @talss89 already mentioned, adding app.vue.runtimeOnly(false) to your bud config should enable you to use the strategy you’re familiar with.

Hi @mensch and @talss89, sorry for the delay in getting back to you.

I added that configuration to the bud.config.js but got the same result.

I didn’t know that. I thought it was doing a compiling process under the hood. But taking a look at the source code, it makes sense now

The only thing that worries me is that everything under #main breaks because of this error without a console error, failing silently.

No problem @talss89, I appreciate any help, and that snippet you shared it’s really good. Nice to know more ways how to use the strategy I have in mind.

I think I should fix the problem I have right now in order to iterate new ideas of the best way to use Vue. What do you think @talss89?

If by any chance @talss89 @mensch, you have an example I can take a look at using Vue, I will really appreciate it!

Kind regards,
Luis.

2 Likes

I’ve just cloned your repo, and the template compiler is working in both dev and prod…

Eh!? :thinking:

I wonder if Bud is reusing some cached fragments between builds. I’ve managed to break the project by removing the runtimeOnly setting, then doing a clean.

Keep your bud.config.js file as-is, then try running yarn run bud clean before running yarn build again. Hopefully that should now work.

Also, prefer app.vue.set('runtimeOnly', false) over app.vue.runtimeOnly(false) - I wasn’t aware of the deprecation!

1 Like

Hey @talss89, the clean command did trick!

Several hours doing changes without seeing any result, not sure for how many hours I have been using cached :frowning:

But finally, it is working for me.

Thank you so much for the help, ideas and for taking the time to check the repo!

1 Like

Glad to hear you’re up and running @luis_troya-as!

I think others will run into the same issue you highlighted (needing to bud clean).

I’ve opened a feature request to address this. A fix should be included in Bud 6.12.0, due for release tomorrow (23rd March 2023), thanks to kellymears.

1 Like