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

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>

Related

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

Getting started with components, trying to nest them

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
}

Setting up a simple example of page routing using vue-router on vue-cli

I am trying to get the simplest of page routing working using vue-cli.
I have been trying to follow the Vue-router documentation (https://router.vuejs.org/guide/#html) as well as various other guides I have come across on google and have not been successful getting anything working on vue-cli.
I was able to get the example shown here: https://www.tutorialspoint.com/vuejs/vuejs_routing working, which does not use vue-cli. From there I tried to 'copy' that example into vue-cli to see if I can get it to work.
My main.js file looks like this:
import Vue from 'vue';
import VueRouter from 'vue-router';
import App from './App.vue';
Vue.config.productionTip = false
const Route1 = { template: '<div>router 1</div>' }
const Route2 = { template: '<div>router 2</div>' }
const routes = [
{ path: '/route1', component: Route1 },
{ path: '/route2', component: Route2 }
];
const router = new VueRouter({
routes
});
new Vue({
el: '#app',
router,
render: h => h(App)
});
And my App.vue file looks like this:
<template>
<div id="app">
<h1>Routing Example</h1>
<p>
<router-link to = "/route1">Router Link 1</router-link>
<router-link to = "/route2">Router Link 2</router-link>
</p>
<router-view></router-view>
</div>
</template>
<script>
export default {
}
</script>
It does not work. I have tried both direct access to the dist/index.html on the file system and viewing the app on localhost, using npm run serve. I see the page but the <router-link> tags render only as <router-link>, not as anchor (<a>) tags. It is impossible to click on them and thus no routing is happening.
The page in my browser has the following source:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="icon" href="favicon.ico">
<title>ucic</title>
<link href="js/app.a486cc75.js" rel="preload" as="script">
<link href="js/chunk-vendors.a6df83c5.js" rel="preload" as="script">
</head>
<body>
<noscript><strong>We're sorry but ucic doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript>
<div id="app">
<h1>Routing Example</h1>
<p>
<router-link to="/route1">Router Link 1</router-link>
<router-link to="/route2">Router Link 2</router-link>
</p>
<router-view></router-view>
</div>
<script src="js/chunk-vendors.a6df83c5.js"></script>
<script src="js/app.a486cc75.js"></script>
</body>
</html>
There are no console errors and no errors trying to download javascript files.
Can anyone point me in the right direction? What am I doing wrong?
Update:
As Pukwa has mentioned, I did not correctly mount the application. After doing so I no longer receive a white screen, but as mentioned above, the <router-link> tag is not rendering as an anchor but literally as a <router-link> tag. The javascript is obviously doing something, or else even that wouldn't show up on the page.
I've updated my question rather than ask a new one as my original problem has not been solved (vue-router is still not working) and this is only one of many iterations to try and get this to work (I have, on previous iterations, had the application correctly mounted and seen the error described above).
I guess you did not mount your application inside of main.js
new Vue({
el: '#app',
router,
render: h => h(App)
});
I had to apply three fixes to make this code work:
mounting the application as identified by Puwka in their answer
Adding Vue.use(VueRouter); in main.js (I got help from answers to this question: [Vue warn]: Unknown custom element: <router-view> - did you register the component correctly?)
Adding "runtimeCompiler": true to vue.config.js (I got help from answers to this question: Vue replaces HTML with comment when compiling with webpack and this: https://cli.vuejs.org/config/#runtimecompiler)
Additionally, I was not able to see logs in the console because it seems vue or npm turns off logging? (I have to use // eslint-disable-next-line no-console before I can use a console.log statement).
A comment to this question Vue router does not render/mount root path component helped me to resolve that problem. Somehow, after logging out this.$router.currentRoute.path in a mounted function I was able to see the errors in the developer console.

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