Getting started with components, trying to nest them - vue.js

I am trying to nest two components in vuejs as I get started in it. I just don't want to jump into cli or webpack. So I wanted to do that without import/export. From the browser's console I get the warn:
[Vue warn]: Error compiling template:
Component template should contain exactly one root element. If you are using v-if on multiple elements, use v-else-if to chain them instead.
1 | This is the Component A
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
found in
--->
Tried a similar problem with answer here.
VueJS nested components
but it seems to be an old version of vuejs. I could not make it work that way.
index.html:
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<script src="vue.js"></script>
<meta charset="utf-8" />
</head>
<body>
<div id="app">
<component-a>
</component-a>
</div>
<script src="app.js"></script>
</body>
</html>
app.js:
var ComponentB = {
template: "<p>This is the Component B</p>",
}
var ComponentA = {
template: '<p>This is the Component A</p><component-b></component-b>',
components: {
'component-b': ComponentB
}
}
new Vue({
el: '#app',
components: {
'component-a': ComponentA,
}
});
Expected that template of component b show up inside the template of complement a.

In your component template you must have only one HTML element. You can wrap your elements in div.
var ComponentA = {
template: '<div><p>This is the Component A</p><component-b></component-b></div>',
components: {
'component-b': ComponentB
}

Related

How to use Compose API in a standalone (one-file) Vue3 SPA?

I write (amateur) Vue3 applications by bootstrapping the content of the project and then building it for deployment(*). It works great.
I need to create a standalone, single HTML page that can be loaded directly in a browser. I used to do that when I was starting with Vue a few years ago (it was during the transition v1 → v2) and at that time I immediately found the proper documentation.
I cannot find a similar one for Vue3 and the Composition API.
What would be a skeleton page that would display the value reactive variable {{hello}} (that I would define in <script setup> in the context of a full, built application)
This is how I used to do it in the past (I hope I got it right)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://unpkg.com/vue#2"></script>
</head>
<body>
<div id="app">
{{hello}}
</div>
<script>
// this is how I used to do it in Vue2 if I remember correctly
new Vue({
el: '#app',
data: {
hello: "bonjour!"
}
// methods, watch, computed, mounted, ...
})
</script>
</body>
</html>
(*) I actually use the Quasar framework but this does not change the core of my question.
You couldn't use script setup using the CDN, according to official docs:
<script setup> is a compile-time syntactic sugar for using Composition API inside Single File Components (SFCs)
but you could use the setup hook inside the page script as follows :
const {
createApp,
ref
} = Vue;
const App = {
setup() {
const hello = ref('Bonjour')
return {
hello
}
}
}
const app = createApp(App)
app.mount('#app')
<script src="https://unpkg.com/vue#3.0.0-rc.11/dist/vue.global.prod.js"></script>
<div id="app">
{{hello}}
</div>

Add Vue 3 to CMS generated HTML

i got a site with a cms here, which generates html the common way. Now i try to add Vue 3. CSS and JS is created by webpack.
The CMS generates a source like this:
<html>
<head>
<script src="/dist/app.js"></script>
<link rel="stylesheet" type="text/css" href="/dist/app.css">
</head>
<body>
<div id="app">
<h1>Hello {{name}}</h1>
<MyComponent />
<div>Awesome Copyright</div>
</div>
</body>
</html>
Is it possible to mount vue 3 to #app, but keep the source as structure/content for the page and use vue 3 inside? Like setting {{name}} to a value from vue and MyComponent from a vue file? And all JS is compiled by webpack?
I did not figure out how to solve this. Something like SSR seems not to be a practicable solution and switching to a headless constellation with the cms as api is not either.
After reading and understanding the documentation, i answer myself.
https://v3.vuejs.org/guide/installation.html#with-a-bundler
See section "In-browser template compilation".
Step 1: Alias vue within webpack
resolve: {
alias: {
vue: "vue/dist/vue.esm-bundler.js"
}
}
Step 2: Run Vue ;-)
createApp({
data() {
return {
name: 'John Doe'
}
},
}).mount('#app')
The definition of template is not necessary. It takes the content from #app.
You can do this
createApp({
data() { return {} },
template: document.querySelector('#app').innerHTML
}).mount('#app')

How can I use Vue2 old component (.vue file) with a new Vue3 project?

Vue3 version is out, but I don't see any example of using old components code with the new version. How come?
Here is my index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Vue 3 Example using Vue 2 component</title>
<script src="https://unpkg.com/vue#next"></script>
</head>
<body>
<div id="app">
<h1>{{ product }}</h1>
<my-old-vue-component :my-prop="'my string in here'"></my-old-vue-component>
</div>
<script src="./main.js"></script>
<script src="./myOldVueComponent.vue"></script>
<!-- Mount App -->
<script>
const mountedApp = app.mount('#app')
</script>
</body>
</html>
Here is my main.js:
const app = Vue.createApp({
data() {
return {
product: 'my product',
}
}
})
Here is my old simple Vue2 component (myOldVueComponent.vue):
<template>
<div>
{{myProp}}
</div>
</template>
<script>
export default {
name: "myOldVueComponent",
props: {
myProp: { type: String }
},
data() {
return {
},
}
</script>
I'm getting error on the import of ".vue" file:
uncaught SyntaxError:
Unexpected token '<'
(meaning the <template> tag inside my old component.
Vue2 components works in Vue3. That is not the issue in your code.
The problem is here:
<script src="./myOldVueComponent.vue"></script>
You can't import .vue files directly in a browser. You could not do it in vue 1,2 and you can't yet in vue 3. The browser is not able to understand that syntax, there needs to be a bundler that converts your code is something that can be used by the browser. The most popular bundlers are webpack, rollup ecc ecc
See: https://v3.vuejs.org/guide/single-file-component.html#for-users-new-to-module-build-systems-in-javascript
I highly recommend using the Vue cli to setup your project, especially if you are a beginner to the npm/bundlers world

Laravel Mix Vue, Lazy loading component returns error as unknown custom element when using Vue Router

I have got a fresh install of Laravel Mix and I am trying to setup lazy loading components in the project. I have got the correct setup with the babel plugin 'syntax-dynamic-import' so the import statement in app.js works as expected. The issue occurs when I attempt to use the lazy loaded component with vue-router.
My app.js file looks like this:
require('./bootstrap');
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
const EC = () => import(/* webpackChunkName: "example-component" */ './components/ExampleComponent.vue');
const router = new VueRouter({
mode: 'history',
routes: [
{ path: '/', component: EC }
]
});
const app = new Vue({
router,
el: '#app'
});
and my welcome.blade.php file looks like this:
<!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" content="{{ csrf_token() }}">
<title>Laravel</title>
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
<base href="/" />
</head>
<body>
<div id="app">
<h1>Base title</h1>
<example-component></example-component>
</div>
<script src="{{ asset('js/app.js') }}"></script>
</body>
</html>
So I just trying to land on the root route and display the Example Component. The example component is included in the welcome.blade.php file.
I am receiving this error in the console:
[Vue warn]: Unknown custom element: <example-component> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
(found in <Root>)
I think I am missing something simple, any advice is appreciated.
First, i think you are mixing routes concepts with core components vue concepts...
Try loading the component directly in your vue app...
const app = new Vue({
router,
el: '#app',
components: {
'example-component': () => import('./components/ExampleComponent.vue')
}
});
Component loading is done with <component>
<component v-bind:is="currentTabComponent"></component>
Check the docs, for more info on dynamic components: https://v2.vuejs.org/v2/guide/components-dynamic-async.html
#Erubiel answer did work but it still quite wasn't the setup I wanted. As I am trying to use vue-router I needed to update the view by removing the explicit call to the component and adding the tag in the welcome.blade.php file. This now means my routes are injected into that space. The updated area is:
...
<body>
<div id="app">
<router-view></router-view>
</div>
<script src="{{ asset('js/app.js') }}"></script>
</body>
...
The problem is in the scss splitting in Vue and using mix.scss() both. Laravel mix when having both creates a css file with manifest js file content in it. which is definitely a bug. which the community mentions a bug from Webpack and will be resolved in webpack 5. But If you use only code splitting in Vue and have the default app.scss file imported to main Vue component like this(not in scope), so each other component will get the styling properly
// resources/js/components/app.vue
<template>
<!-- Main Vue Component -->
</template>
<script>
// Main Script
</script>
<style lang="scss">
#import '~#/sass/app.scss';
</style>
and the webpack.mix.js file will have no mix.scss function to run only a single app.js file. here is my file.
// webpack.mix.js
const mix = require('laravel-mix')
mix.babelConfig({
plugins: ['#babel/plugin-syntax-dynamic-import'] // important to install -D
})
mix.config.webpackConfig.output = {
chunkFilename: 'js/[name].bundle.js',
publicPath: '/'
}
mix
.js('resources/js/app.js', 'public/js')
.extract(['vue'])
.webpackConfig({
resolve: {
alias: {
'#': path.resolve('resources/') // just to use relative path properly
}
}
})
Hope this solves everyone's question

Vuejs single file components mixed with normal components

I am trying to mix vuejs single file components with the normal style of components (not sure what they are called) which I have existing code developed for already.
main.js
import Vue from 'vue'
import test from './test.vue'
import VueMaterial from 'vue-material'
Vue.use(VueMaterial)
new Vue({
el: '#app',
render: h => h(test,
{props: {
testprop: 'ttttt'
}
}),
data:{
// /testprop: 'tytytytyty'
}
})
test.vue
<template>
<div>
<my-component></my-component>
<div>This is the first single page component</div>
</div>
</template>
<script>
import MyComponent from '../src/components/MyComponent.js'
import TestTest from '../src/components/TestTest.vue'
export default {
name: 'MainApp',
props: ['testprop'],
components: {
TestTest,
MyComponent
},
mounted: function(){
},
computed:{
returnProp: function(){
return this.testprop
}
}
}
</script>
<style lang="scss" scoped>
.md-menu {
margin: 24px;
}
</style>
MyComponent.js Normal style component
window.Vue = require('Vue') //would give errors vue undefined if i dont't add this line
Vue.component('my-component', {
name: 'my-component',
template: '<div>Normal component</div>'
})
index.html
<html lang="en">
<head>
<meta charset="utf-8">
<meta content="width=device-width,initial-scale=1,minimal-ui" name="viewport">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700,400italic|Material+Icons">
<link rel="stylesheet" href="https://unpkg.com/vue-material#beta/dist/vue-material.min.css">
<link rel="stylesheet" href="https://unpkg.com/vue-material#beta/dist/theme/default.css">
<title>vuematerial</title>
</head>
<body>
<div id="app">
<main-app :testprop="testprop"></main-app>
</div>
<script src="dist/build.js"></script>
</body>
</html>
The single file component and a child single file component (not showed here) display fine. The normal type will show up as
<!--function (t,n,r,i){return Mt(e,t,n,r,i,!0)}-->
In the generated html.
Iv'e also tried doing the MyComponent import in the main.js file.
Any ideas?
I don't really want to convert all my existing components into single file ones :(
According to the docs, a child component is an object, not something attached to the Vue instance (i.e Vue.component()) so declare your component like this:
MyComponent.js
export default {
name: 'my-component',
template: '<div>Normal component</div>'
}
If you want to keep the format of MyComponent as is then you'll need to register the component before the new Vue call.