How to use multiple components on a Vue route - laravel

I am just starting out with Vue and am trying to use two components in a route - a navbar and some sales data. Laravel mix is bundling the assets with Webpack, and npm keeps failing.
index.php
<body>
<div id="app">
<router-view name="nav"></router-view>
<router-view></router-view>
</div>
<script src="{{ asset('js/app.js') }}"></script>
</body>
app.js:
import './bootstrap';
import router from './routes';
const app = new Vue({
el: '#app',
router
});
routes.js:
import VueRouter from 'vue-router';
import Sales from './views/employer/sales/Sales.vue';
import MainNav from './components/MainNav.vue';
let routes = [
{
path: '/sales',
components: {
default: Sales,
nav: MainNav
}
}
];
export default new VueRouter({
routes,
linkActiveClass: 'is-active'
});
Sales.vue
<template>
<p>This is the Sales view</p>
</template>
MainNav.vue
<template>
<p>This is the MAIN NAVIGATION</p>
</template>
The error message from npm is not particularly enlightening:
Failed at the # dev script 'node node_modules/cross-env/dist/bin/cross-env.js NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js
It goes on to suggest that I should check I am running the latest node and npm (which I am).
What am I doing wrong? Curiously, before attempting to include the navigation component this way, I was registering it as a global component. It seemed to register fine, but whenever I included its element tag in a template, npm would fail in the same manner as above.

Looks like node somehow became confused. I removed the node_modules directory and reinstalled it and its all good.

Related

Vue 3 not rendering components (in Laravel 8)

I have a simple blade with a component inside:
<section>
<h1>Carousel</h1>
<carousel :slides="#json($data->slides)">
</carousel>
The component looks like this:
<template>
<fragment>
<h1>My component</h1>
{{ slides }}
</fragment>
</template>
export default {
props: [
'slides'
],
mounted() {
console.log("mounted")
}
}
And of course I have it in app.js (the path is correct):
require('./bootstrap');
window.Vue = require('vue').default;
Vue.component('carousel', require('./components/web/partials/Carousel.vue').default);
const app = new Vue({`
el: '#app',`
});
My webpack.mix.js also contains:
mix.js('resources/js/app.js', 'public/js') .vue() .sass('resources/sass/app.scss', 'public/css');
But when I access the page, I can't see the component. I see just the <h1>Carousel</h1> that is in the blade. Also there are no errors in the console, which I find weird. The "ExampleComponent" that is created after vue install was not rendering either (I removed it from app.js).
I'm using:
"laravel/ui": "^3.3"
"vue": "^3.2.26",
"vue-template-compiler": "^2.6.11"
"sass": "^1.32.11",
"sass-loader": "^11.0.1",
"vue-loader": "^16.2.0"
I have been trying to switch between vue versions, also vue-loader versions, I have been scrolling through SO with similar problems but none of the solutions worked for me.
First, in your app.js, it's how Vue2 init but not Vue3
// vue2 way
new Vue({`
el: '#app',`
});
// vue3 way
Vue.createApp(...).mount('#app')
See how Vue3 init via the hello-word of Vue3.
Second, there is no fragment in Vue3 built-in-components.
Read the document for more details about built-in-components.
In Vue3, you can write a multiple roots component without <fragment> directly
<template>
<h1>My component</h1>
{{ slides }}
</template>
You need to reference app.js in your blade file and add id app to html tag:
<section id="app">
<h1>Carousel</h1>
<carousel :slides="#json($data->slides)"></carousel>
<section>
<script src="{{ mix('css/app.js') }}"></script>

'Unexpected token <' when wrapping .vue file in <template>

new to vue here and I am creating an area in the code for all my vue components to be rendered through a blade.php file (sfc).
I installed the cli using vue create projectName and linked the file but it seems the tag is causing an issue.
Doesnt Vue need to be wrapped within the template?
let me know if theres any more information needed.
Blade.php
#extends('layouts.admin')
#section('content')
<script src="/projectName/src/App.vue"></script>
#endsection
App.vue
<template id="myStuff">
<div>omg it works?</div>
</template>
<script>
import Vue from 'vue'
new Vue({
el: "#myStuff"
});
</script>

Laravel 5.7 cant use assets with Vue in blade template

Hello I have Laravel version 5.7.24. I have problem with import app.js to blade template.
I have app.js in resources/js/app.js, this same file is other location: public/js/app.js
In welcome.blade.php I add:
<body>
<div id="app">
Hello
<example-component></example-component>
<articles></articles>
</div>
<script src="{{ asset('js/app.js') }}"></script>
</body>
I created articles component in resources/js/components/articles.vue:
<template>
<div>
Hello
</div>
</template>
<script>
export default {
name: "Articles"
}
</script>
Now Laravel return me error:
Unknown custom element: - did you register the component
correctly? For recursive components, make sure to provide the "name"
option.
Because asset refers to the public/js/app.js
I read in this article, taht Laravel removed assets foler. So I added assets folder and my file structure looks like this:
but still Laravel references the file public/js/app.js.
How I can import srcipt (resources/js/app.js) to my welcome.blade.php file ?
Edit:
my resources/js/app.js file:
require('./bootstrap');
window.Vue = require('vue');
Vue.component('articles', require('./components/Articles.vue').default);
const app = new Vue({
el: '#app'
});
When I change script from (in welcome.blade.php):
<script src="{{ asset('js/app.js') }}"></script>
to
<script src="{{ asset('assets/js/app.js') }}"></script>
I have error: GET http://127.0.0.1:8000/assets/js/app.js
net::ERR_ABORTED 404 (Not Found)
It looks like you have a mistake in this line of your app.js:
Vue.component('articles', require('./components/Articles.vue').default);
Try removing .default from here, and see if the component is registered correctly when you build again (npm run dev).
Side note: <articles> should contain a hyphen like my-articles, v-articles, or something else.
When using a component directly in the DOM (as opposed to in a string
template or single-file component), we strongly recommend following
the W3C rules for custom tag names (all-lowercase, must contain a
hyphen). This helps you avoid conflicts with current and future HTML
elements.
https://v2.vuejs.org/v2/guide/components-registration.html#Component-Names

Routing on Vue with SSR and Laravel

I am learning Vue and got stuck trying setup it as full front-end with Laravel, on my scenario I already have made an personal blog for test using Laravel with Blade engine and some components of Vue and seems work fine.
I am trying get on next level, removing Blade and letting Laravel as API backend and setup Vue as full front end with SSR, the basic setup works, I mean I can call Vue, render it using SSR with node or PHPv8, the problem I am having is on route systems, thinking as blade way I can't archive same result, on blade I use an default layout as master and import it for every post, page, blog, etc...
Example:
resources/views/layouts/master.blade
<!DOCTYPE html>
<html dir="ltr" lang="{{ app()->getLocale() }}">
<head>
#include('partials._head')
</head>
#if(View::hasSection('body')) {{-- Load Custom Body --}}
<body #section('body')>
#else
<body>
#endif
#yield('content')
#include ('partials._javascripts')
#section('scripts')
</body>
</html>
So I call a dynamic head per page / post, a dynamic content, an basic javascripts (bootstrap, vue, fontawesome, etc...) and custom 'scripts' per page / posts.
Using the librarie:
https://github.com/spatie/laravel-server-side-rendering
I can get SSR working with node or PHPv8, but the vue-route never call the desired page, my setup is:
resources/assets/js/app.js
import Vue from 'vue';
import App from './layouts/App';
import axios from 'axios';
import store from './store';
import router from './router';
import navbar from './components/navbar';
import posts from './components/posts';
import sidebar from './components/sidebar';
import footer from './components/footer';
import BlogIndex from './views/blog/BlogIndex';
export default new Vue({
store,
router,
navbar,
posts,
sidebar,
footer,
BlogIndex,
render: h => h(App),
});
resources/assets/js/entry-client.js
import app from './app';
app.$mount('#app');
resources/assets/js/entry-server.js
import app from './app';
import renderVueComponentToString from 'vue-server-renderer/basic';
app.$router.push(context.url);
renderVueComponentToString(app, (err, html) => {
if (err) {
throw new Error(err);
}
dispatch(html);
});
resources/assets/js/router.js
// router.js
import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from './components/Home';
import BlogIndex from './views/blog/BlogIndex';
Vue.use(VueRouter);
const routes = [
{ path: '/', name: 'home', component: Home },
{ path: '/blog', name: 'blog', component: BlogIndex },
];
export default new VueRouter({
mode: 'history',
routes,
});
resources/assets/js/store.js
import Vue from 'vue';
import uniq from 'lodash/uniq';
import Vuex, { Store } from 'vuex';
Vue.use(Vuex);
export default new Store({
state: {
},
getters: {
},
mutations: {
},
});
resources/assets/js/views/blog/BlogIndex.vue
<template>
<div class="container">
<navbar></navbar>
<posts></posts>
<sidebar></sidebar>
<footer></footer>
</div>
</template>
<script>
export default {
name: "BlogIndex",
components: {
}
}
</script>
<style scoped>
</style>
app/Http/Controllers/VueSSRController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\File;
use Illuminate\Routing\Route;
class VueSSRController extends Controller
{
public function __invoke()
{
return view('layouts.vue');
}
}
resources/views/layouts/vue.blade.php
<!DOCTYPE html>
<html dir="ltr" lang="{{ app()->getLocale() }}">
<head>
#section('extrajavascripts'){{ asset('js/scripts.min.js') }}#endsection
#include('partials._head')
</head>
#if(View::hasSection('body')) {{-- Load Custom Body --}}
<body #section('body')>
#else
<body>
#endif
{{-- Begin of Vue SSR --}}
{!! ssr('resources/assets/js/server_bundle.min.js')
// Share the packages with the server script through context
//->context('packages', $packages)
// If ssr fails, we need a container to render the app client-side
->fallback('<div id="app"></div>')
->render() !!}
{{-- End of Vue SSR --}}
#include ('partials._javascripts')
#section('scripts')
#show
</body>
</html>
/resources/assets/js/layouts/App.vue
<template>
<div id ="app">
{{ message }}
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
message: 'SSR working.'
}
}
}
</script>
<style scoped>
</style>
So SSR works fine, the problem is the resources/assets/js/router.js is not loading the resources/assets/js/views/blog/BlogIndex.vue, the url/blog works, but the component rendered is always the /resources/assets/js/layouts/App.vue.
Someone would point what I am missing setup please?
Thanks in advice!!!
You should place <router-view></router-view> where you want the router to load. I think in your case its below {{message}} in App.vue

Failed to mount component on new laravel 5.5 project

me and a colleague have been having problems in new and old laravel projects when it comes to using vue js because we get the following error every time in the browser console
>`[Vue warn]: Failed to mount component: template or render function not defined.
found in
---> <Example>
<Root>
warn # app.js:32173
mountComponent # app.js:34241
Vue$3.$mount # app.js:39678
Vue$3.$mount # app.js:41868
init # app.js:35260
createComponent # app.js:36909
createElm # app.js:36852
createChildren # app.js:36980
createElm # app.js:36885
patch # app.js:37394
Vue._update # app.js:34147
updateComponent # app.js:34271
get # app.js:34614
Watcher # app.js:34603
mountComponent # app.js:34275
Vue$3.$mount # app.js:39678
Vue$3.$mount # app.js:41868
Vue._init # app.js:36000
Vue$3 # app.js:36085
(anonymous) # app.js:802
__webpack_require__ # app.js:20
(anonymous) # app.js:775
__webpack_require__ # app.js:20
(anonymous) # app.js:63
(anonymous) # app.js:66`
This happens even in new laravel projects using the default Example.vue
My current code is the following
Example.vue
<template>
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Example Component</div>
<div class="panel-body">
I'm an example component!
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
mounted() {
console.log('Component mounted.')
}
}
</script>
assets/js/app.js
/**
* 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');
/**
* 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.
*/
Vue.component('example', require('./components/Example.vue'));
const app = new Vue({
el: '#app'
});
assets/js/bootstrap.js
window._ = require('lodash');
/**
* We'll load jQuery and the Bootstrap jQuery plugin which provides support
* for JavaScript based Bootstrap features such as modals and tabs. This
* code may be modified to fit the specific needs of your application.
*/
try {
window.$ = window.jQuery = require('jquery');
require('bootstrap-sass');
} catch (e) {}
/**
* We'll load the axios HTTP library which allows us to easily issue requests
* to our Laravel back-end. This library automatically handles sending the
* CSRF token as a header based on the value of the "XSRF" token cookie.
*/
window.axios = require('axios');
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
/**
* Next we will register the CSRF Token as a common header with Axios so that
* all outgoing HTTP requests automatically have it attached. This is just
* a simple convenience so we don't have to attach every token manually.
*/
let token = document.head.querySelector('meta[name="csrf-token"]');
if (token) {
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
} else {
console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
}
/**
* Echo exposes an expressive API for subscribing to channels and listening
* for events that are broadcast by Laravel. Echo and event broadcasting
* allows your team to easily build robust real-time web applications.
*/
// import Echo from 'laravel-echo'
// window.Pusher = require('pusher-js');
// window.Echo = new Echo({
// broadcaster: 'pusher',
// key: 'your-pusher-key'
// });
welcome.blade.php
<!doctype html>
<html lang="{{ app()->getLocale() }}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" value="{{csrf_token()}}">
<title>Laravel</title>
<!-- Fonts -->
<link href="https://fonts.googleapis.com/css?family=Raleway:100,600" rel="stylesheet" type="text/css">
<link rel="stylesheet" type="text/css" href="/css/app.css">
<!-- Styles -->
</head>
<body>
<div id="app">
<example></example>
</div>
<script type="text/javascript" src="/js/app.js"></script>
</body>
</html>
webpack.mix.js
let mix = require('laravel-mix');
/*
|--------------------------------------------------------------------------
| Mix Asset Management
|--------------------------------------------------------------------------
|
| Mix provides a clean, fluent API for defining some Webpack build steps
| for your Laravel application. By default, we are compiling the Sass
| file for the application as well as bundling up all the JS files.
|
*/
mix.js('resources/assets/js/app.js', 'public/js')
.sass('resources/assets/sass/app.scss', 'public/css');
package.json
{
"private": true,
"scripts": {
"dev": "npm run development",
"development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
"watch": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
"watch-poll": "npm run watch -- --watch-poll",
"hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
"prod": "npm run production",
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
},
"devDependencies": {
"axios": "^0.16.2",
"bootstrap-sass": "^3.3.7",
"cross-env": "^5.0.1",
"jquery": "^3.1.1",
"laravel-mix": "^1.0",
"lodash": "^4.17.4",
"vue": "^2.1.10"
},
"dependencies": {
"cross-env": "^5.0.5",
"vee-validate": "^2.0.0-rc.17"
}
}
If you haven't changed anything and and have correctly bundled and installed JavaScript then the issue may lie with the default Laravel implementation.
The error message you are receiving means that you are likely importing the runtime only build (vue without the template compiler) in to an app that needs the template compiler.
To better understand this, Vue compiles everything into render functions (essentially a pure JavaScript representation of your webpage). When using single file components you end up with one base component that you mount to your Vue instance, which serves up your all your views, so, we would get something like this:
components/App.vue
<template>
<div>
I'm a base component
<!-- vue-router will mount components here -->
<router-view></router-view>
</div>
</template>
app.js
import App from './components/App.vue'
// "h" is just a standard taken from JSX
new Vue({
render: h => h(App)
}).$mount("#app");
app.blade.php
<html>
<head>
<!-- head stuff -->
</head>
<body>
<div id="app"></div>
<script src="app.js"></script>
</body>
</html>
The important thing here is that app.blade.php only acts as a mounting point for your entire app and App.vue serves as the base component, which is turns serves every other view (this would usually be done via vue-router). To get that to work we need to compile our assets into app.js via webpack, which creates all our render functions for us, so we don't need the compiler because everything has already been compiled. All that's left to do is create a route in routes/web.php to serve up the index blade file. That's essentially setting up an SPA.
What Laravel encourages you do, is add Vue components directly in your markup and register components globally, so you would do:
app.js
Vue.component('my-component', require('./components/My-component.vue'));
const app = new Vue({
el: '#app'
});
index.blade.php
<html>
<head>
<!-- head stuff -->
</head>
<body>
<div id="app">
<my-component></my-component>
</div>
<script src="app.js"></script>
</body>
</html>
Because we've added our component to the markup we need the template compiler to compile the bit between our app div tags into a render function at runtime. So, we need to import vue + compiler, which is what Laravel Mix should do for you by aliasing the runtime + compiler version of Vue (you can find details of how to do that in the docs).
To be honest, I'm not a fan of Laravel Mix because it abstracts away crucial implementation details that as a developer you need to know, such as, "is Laravel Mix correctly aliasing the Vue + compiler build"?, in your case it looks like it isn't.
In the end it's usually easier to just setup your own webpack config directly so you have total control over your config, you can use Vue's webpack simple config as a base.
Once you've correctly setup webpack, you just then need to add the alias to webpack config and you have your runtime + compiler build in:
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
}
I realise that I haven't really given a soultion to your specific problem. I find it hard to believe that Webpack Mix isn't correctly aliasing the vue + compiler build, but that is what that message implies. Hopefully though, this gives you enough information to find where the problem lies.

Resources