Vue.component alternative in Vue 3 - laravel

I'm using Vue with Laravel Mix. In Vue version 2, I was able to do this:
1. resources/js/app.js:
import Vue from 'vue';
import MyComponent from './MyComponent';
Vue.component('my-component', MyComponent);
2. resources/js/MyComponent.vue:
<template>
<p>{{message}}</p>
</template>
<script>
export default {
name: "MyComponent",
props: ['message']
}
</script>
As this Vue 2 document instructed. I know it was not the best practice, but it was the only approach that I have to conveniently pass data from Laravel's Blade template to the component, such as below:
3. anyview.blade.php:
<my-component :message='message' id='app'></my-component>
<script src='public/js/app.js'><script> //include compiled resources/js/app.js
<script>
let app = new Vue({
el: '#app',
data() {
return {
message: 'Hello World';
}
}
})
</script>
In real case, 'Hello World' would be replaced with something like:
message: {{$myMessaGe}}
But since Vue 3, Vue.component is no longer a thing because Vue object is not a default export.
This work flow (1-2-3) has been seamlessly fine, so returning to Vue2 is the last unhappy choice :(
I have tried to work around, just changing Vue.component with the new createApp:
4. resources/js/app.js:
import { createApp } from 'vue';
import MyComponent from './MyComponent';
createApp({
components: {
MyComponent,
}
}).mount('#app');
But instead of adding MyComponent to current instance, it just creates a new one as depicted below - meaning that the prop message can't be passed through.
My question is: Is there any alternative API or workaround to compensate the loss of Vue.component()?

I have only worked with Vue3 so far but what I understand from the documentation is that the components in Vue3 are not that different from components in Vue2.
Try this solution:
import { createApp } from 'vue';
import MyComponent from './MyComponent';
const app = createApp({});
app
.component('MyComponent', MyComponent)
.mount('#app');
You can find more about this in the Vue3 docs.

Related

Laravel + SPA VueJs not giving error for unknown component

<template>
<div>
<test-component />
</div>
</template>
<script>
//import TestComponent from '../path-to-components/TestComponent';
export default {
name: 'SomeRandomComponent',
components: {
TestComponent,
}
}
</script>
Expected Behaviour:
The application should give some console error if there is a problem with the imports or anything else similar to this.
Current Behaviour:
The page get blank in the browser and there is no error in console even if the import statement is commented out.
Code for reference
app.js
import Vue from 'vue';
import router from '~/router';
import App from '~/components/App';
new Vue({
router,
...App
})
router.js
export default [
{ path: '/some-path', name: 'testing', component: import( `~/pages/path-to-component`).then(m => m.default || m) },
]
App.vue
<template>
<div id="app">
<loading ref="loading" />
<transition name="page" mode="out-in">
<component :is="layout" v-if="layout" />
</transition>
</div>
</template>
<script>
import Loading from './Loading'
// Load layout components dynamically.
const requireContext = require.context('~/layouts', false, /.*\.vue$/)
const layouts = requireContext.keys()
.map(file =>
[file.replace(/(^.\/)|(\.vue$)/g, ''), requireContext(file)]
)
.reduce((components, [name, component]) => {
components[name] = component.default || component
return components
}, {})
export default {
el: '#app',
components: {
Loading
},
data: () => ({
layout: null,
defaultLayout: 'default'
}),
metaInfo () {
const { appName } = window.config
return {
title: appName,
titleTemplate: `%s ยท ${appName}`
}
},
mounted () {
this.$loading = this.$refs.loading
},
methods: {
/**
* Set the application layout.
*
* #param {String} layout
*/
setLayout (layout) {
if (!layout || !layouts[layout]) {
layout = this.defaultLayout
}
this.layout = layouts[layout]
}
}
}
</script>
I am assuming that you are using Vue2 since Vue3 is in beta.
The first problem I see with your code, is that your <div id="app"> is inside a Vue component.
Declarative Rendering
What happening is that everything is compile, but you are trying to render Vue inside a component that does not exist.
Instead, create a div element inside the html or blade.php file that is loaded by the client. For my part, I use a blade layout like this:
//resources/views/layouts/app.blade.php
...
</head>
<body>
<div id="app" v-cloak> //This is plain loaded when client render views, then, when script will initiate, Vue will be able to access everything inside this.
<navbar v-bind:core="core"></navbar> //There is my navbar which is a Vue component.
<div class="boxed">
<div id="content-container">
#yield('content') //Blade component are injected there. since it's inside the <div id="app">, thoses component can include Vue directive/component.
</div>
...
Another thing that seems a problem is that you are initiating Vue inside a Vue component. Things are that Vue is a package and compiled into javascript in the end. But, to make all the magic happen, you need to have somwhere to initiate all this. In that case, your App.js file should look something like this:
/**
* First we will load all of this project's JavaScript dependencies which
* includes Vue and other libraries. It is a great starting point when
* building robust, powerful web applications using Vue and Laravel.
*/
require('./bootstrap');
window.Vue = require('vue');
/**
* The following block of code may be used to automatically register your
* Vue components. It will recursively scan this directory for the Vue
* components and automatically register them with their "basename".
*
* Eg. ./components/ExampleComponent.vue -> <example-component></example-component>
*/
// const files = require.context('./', true, /\.vue$/i)
// files.keys().map(key => Vue.component(key.split('/').pop().split('.')[0], files(key).default))
Vue.component('example-component', require('./components/ExampleComponent.vue').default);
/**
* Next, we will create a fresh Vue application instance and attach it to
* the page. Then, you may begin adding components to this application
* or customize the JavaScript scaffolding to fit your unique needs.
*/
const app = new Vue({
el: '#app',
});
This file is the one created by default by laravel for Vue template on fresh Laravel 8 install.
The difference between import and require is that import create a promise.
You don't want a promise there, because you want that file to be execute at the moment the client will begin to render your page.
Following thoses recommendation, I think you will be able to get your app working quickly and Vue will start logging error into your console.

Vue Component not showing text when called from blade.php file

I have a basic vue 2.6.11 component that lives in a laravel 6 application. It lives at resources/js/components. I create a basic component and then in my app.js file I have vue imported and I define my vue component. Then in my app.blade.php I use the vue component but the text within my <template><div>Text here</div></template> does not appear on the screen. The <h1> text appears but not the vue component text. I looked at other posts but the vue versions other questions on here use are 2-3 years too old and don't apply. Any help is appreciated, thanks.
TableDraggable.vue
<template>
<div>
<h1>Test Text</h1>
</div>
</template>
<script>
export default {
mounted() {
console.log('this component has been mounted');
}
}
</script>
What I am using in my app.blade.php file
<h1>This is a test to see if VUE is working</h1>
<table-draggable></table-draggable>
app.js snippet
//Bring in VUE and vue draggable
window.Vue = require('vue');
import VueDraggable from 'vuedraggable';
Vue.component('table-draggable', require('./components/TableDraggable'));
In app.js try the following:
...
window.Vue = require('vue');
import VueDraggable from 'vuedraggable';
Vue.use(VueDraggable);
import TableDraggable from './components/TableDraggable';
Vue.component('table-draggable', TableDraggable);
const app = new Vue({
el: '#app',
data() {
return {};
},
});
...
Then in some parent element of where you are using your component, make sure it has an id of app. eg:
<div id="app">
<h1>This is a test to see if VUE is working</h1>
<table-draggable></table-draggable>
</div>
This is a basic example of getting vue working on your site, and not necessarily the best way to be structuring your assets depending on your needs. The biggest considerations about how to structure these are how bulky your assets will become if you include everything in app.js.

Importing a JavaScript class into a Vue single file component using Laravel Mix

I'm writing a Vue single page component in my project which is bundled using Laravel Mix. I want to extract some logic out into its own class so that it can be easily re-used across other components (but this isn't logic that should be a Vue component itself).
I made a new class and put it in TimeRangeConverter.js, in the same directory as my Vue component.
export default class TimeRangeConverter {
static getFromTimestamp() {
return 1;
}
}
And in my component, I'm importing it as I think I normally would:
<template>
<div>
Example component
</div>
</template>
<script>
import './TimeRangeConverter';
export default {
mounted() {
console.log(TimeRangeConverter.getToTimestamp());
}
}
</script>
However Vue is throwing an error saying ReferenceError: TimeRangeConverter is not defined.
I'm using the default webpack.mix.js config file that comes with Laravel 5.7:
mix.js('resources/js/app.js', 'public/js');
And in my main app.js file I'm automatically including Vue components with:
const files = require.context('./', true, /\.vue$/i);
files.keys().map(key => Vue.component(key.split('/').pop().split('.')[0], files(key).default));
What's the correct way to import this class into a Vue component for usage?
You should import your class inside your component code using the following syntax :
import TimeRangeConverter from './TimeRangeConverter';

How to import Vue component to script tag?

I have some Vue components (.vue) in my Laravel project, resources\assets\js\components. I want to import the components to a view file .blade.php:
<div id='lol'>
<some-component></some-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<script>
import SomeComponent from '.\resources\assets\js\components\some.vue
new Vue({
el: '#lol'
});
</script>
I got error in console: Uncaught SyntaxError: Unexpected identifier at the import
I don't wanna register the component in app.js
If you don't want to use a compiler, you could use a library such as http-vue-loader, which provides a function to do just that.
Important: The author of the library itself does not recommend to use this library for production environments. Instead, he recommends you to compile your .vue files.
When using the http-vue-loader library, your snippet would look like this:
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<script src="https://unpkg.com/http-vue-loader"></script>
<div id='lol'>
<some-component></some-component>
</div>
<script>
new Vue({
components: {
'some-component': httpVueLoader('./resources/assets/js/components/some.vue')
},
el: '#lol',
});
</script>
Alternatively, you can compile and export your vue component as a library, which you then can add to your project.
To export your component as a library, you first need to create an entry point for your component, for example: 'resources\assets\js\components\index.js'. In this entry point, you register your components globally.
import Vue from "vue";
import Some from "./some.vue"
const Components = {Some};
Object.keys(Components).forEach(name=>{
Vue.component(name,Components[name])
})
export default Components;
Next, you need to define a build step for building your library in your package.json, which links to the entry point of your component:
"scripts": {
...
"build-bundle" : "vue-cli-service build --target lib --name some ./resources/assets/js/components/index.js",
...
},
Next, call this build step, e.g. by invoking 'yarn build-bundle', or the npm equivalent.
Finally, you can add the compiled component to your webpage. Note, that you don't need to explicitly include your component, since you registered it globally before. Also note that you need to add the type="module" attribute to the script tage, because otherwise you cannot use the import statement. Finally, the outpath of the compiled libraries is the one that is used in your vue.config.js.
<div id='lol'>
<some-component></some-component>
</div>
<script type="module">
import "/<path>/<to>/<compiled>/<library>/some.umd.min.js";
const app = new Vue({
el: '#lol',
})
</script>

Laravel and VueJS with blade templates

The new laravel comes with a built-in VueJs. But what I need is one instance of vuejs binded to body tag for the whole app. How can I achieve this ? Is there a way of binding vue directly on body element, even though its not recommended ? The purpose is only using vue to make an API calls for certain parts of my app, but I need vue to be available globally and without binding to specific element, which will destroy my current app
Update Code:
default layout - laravel
...
</head>
<body id="app" class="{{isActiveRoute('home')}}">
...
</body>
resources/assets/app.js
require('./bootstrap');
window.Vue = require('vue');
Vue.component('example', require('./components/Example.vue'));
const app = new Vue({
el: '#app',
components: ['example'],
data: {
name: 'test'
},
methods: {
addToCart() {
console.log('add to cart');
}
},

Resources