VUE Web component inside legacy app results in error "_Ctor, object is not extensible" - vue.js

I'm having trouble importing my custom component in my legacy app.
I pre-compiled my SFC as a webcomponent with vue-cli builder, and I import inside my main.js file this way :
import * as HelloWorld from '../dist/hello-world.js'
Vue.component('hello-world',HelloWorld); //if I add my component globally (same _Ctor error)
Then I load my app on a container div :
var app = new Vue({
el:"#container",
data: {
test: 'Vue is init !' //just a test to validate init
},
components:{
HelloWorld //my web component locally (same _Ctor error)
}
});
When my app load, I get this JS error...
I noticed that when the page is loaded without the following component tag :
<hello-world></hello-world>
no error is thrown and if I add the component tag through JS after the page is loaded, component is properly working.
If I add this component outside of my Vue.el (#container) scope, it's working too.
However, I would like to add this component to #container.
I import Vue 2 through CDN.
Seems like a loading or building error but I can't get it.
Thanks for your help.
hello-world.js is a precompiled .VUE file with vue-cli, just a test file catching some events :
<template>
<h1 v-on:click="clickon"
v-on:mouseenter="addone"
>{{msg}}</h1>
</template>
<script>
export default {
name: "HelloWorld",
data() {
return {
msg: 'Hello world!'
}
},
methods : {
clickon: function () {
// `this` inside methods points to the Vue instance
alert('Clicked')
},
addone: function(){
this.msg = 'Mouse entered'
},
}
}
</script>
<style>
h1{
color:red;
}
</style>
Precompiled with :
vue build --target wc HelloWorld.vue

Got it working by building it with --target lib and importing my JS file with:
import '../dist/HelloWorld.umd.js';
Then it can be added to my Vue instance with:
components:{
"hello-world":HelloWorld
}
Seems to be the right way to do it; web components can't be imported this way.

Related

How to create a library exposing a single Vue component that can be consumed by the distributed .mjs file?

I want to create a single Vue component that gets bundled into a single .mjs file. Another Vue project can fetch this .mjs file via HTTP and consume the component. Installing the pluggable component via npm is not possible, because the other application tries to fetch it based on a configuration during runtime.
Things to consider for the pluggable component
Might be using sub components from another UI framework / library
Might be using custom CSS
Might rely on other files e.g. images
Reproducing the library
I created a new Vuetify project via npm create vuetify
I deleted everything from the src folder except vite-env.d.ts , created a component Renderer.vue
<script setup lang="ts">
import { VContainer } from "vuetify/components"
defineProps<{ value: unknown }>()
</script>
<template>
<v-container>
<span class="red-text">Value is: {{ JSON.stringify(value, null, 2) }}</span>
</v-container>
</template>
<style>
.red-text { color: red; }
</style>
and an index.ts file
import Renderer from "./Renderer.vue";
export { Renderer };
I added the library mode to the vite.config.ts
build: {
lib: {
entry: resolve(__dirname, "./src/index.ts"),
name: "Renderer",
fileName: "renderer",
},
rollupOptions: {
external: ["vue"],
output: {
globals: {
vue: "Vue",
},
},
},
},
and extended the package.json file with
"files": ["dist"],
"main": "./dist/renderer.umd.cjs",
"module": "./dist/renderer.js",
"exports": {
".": {
"import": "./dist/renderer.js",
"require": "./dist/renderer.umd.cjs"
}
},
Since I'm using custom CSS Vite would generate a styles.css file but I have to inject the styles into the .mjs file. Based on this issue I'm using the plugin vite-plugin-css-injected-by-js.
When building I'm getting the desired .mjs file containing my custom CSS
Consuming the component
I created a new Vue project via npm create vue
and for testing purposes I copied the generated .mjs file right into the src directory of the new project and changed the App.vue file to
<script setup lang="ts">
import { onMounted, type Ref, ref } from "vue";
const ComponentToConsume: Ref = ref(null);
onMounted(async () => {
try {
const { Renderer } = await import("./renderer.mjs"); // fetch the component during runtime
ComponentToConsume.value = Renderer;
} catch (e) {
console.log(e);
} finally {
console.log("done...");
}
});
</script>
<template>
<div>Imported component below:</div>
<div v-if="ComponentToConsume === null">"still loading..."</div>
<component-to-consume v-else :value="123" />
</template>
Unfortunately I'm getting the following warnings and errors
[Vue warn]: Vue received a Component which was made a reactive object. This can lead to unnecessary performance overhead, and should be avoided by marking the component with markRaw or using shallowRef instead of ref.
[Vue warn]: injection "Symbol(vuetify:defaults)" not found.
[Vue warn]: Unhandled error during execution of setup function
[Vue warn]: Unhandled error during execution of scheduler flush.
Uncaught (in promise) Error: [Vuetify] Could not find defaults instance
Does someone know what I'm missing or how to fix it?
Vuetify doesn't provide isolated components and requires the plugin to be initialized, you need to do this in main app:
app.use(Vuetify)
Make sure vuetify isn't duplicated in project deps, so the lib and main app use the same copy.
The lib should use vuetify as dev dependency and specify it in Rollup external, in order to prevent the things that are global to the project from being bundled with the lib:
external: ["vue", "vuetify"]

How to compile vue3 SFC component .ce.vue into .js

I am looking for a way to compile a "single-file component" (.vue or .ce.vue extension) with console tools into a js. the project is made with yii2. here is what i tried:
Following this guide i managed to define a custom element (without SFC .vue):
import { defineCustomElement } from 'vue'
const MyVueElement = defineCustomElement({
// normal Vue component options here
props: {},
emits: {},
template: `...`,
// defineCustomElement only: CSS to be injected into shadow root
styles: [`/* inlined css */`]
})
// Register the custom element.
// After registration, all `<my-vue-element>` tags
// on the page will be upgraded.
customElements.define('my-vue-element', MyVueElement)
this guide mentions "Using Vue SFC as Custom Elements", but i have no clue how to run it, as well as this #vue/compiler-sfc.
using the library built with vitejs in library mode makes my (php) app throw
Uncaught TypeError: Failed to resolve module specifier "vue". Relative references must start with either "/", "./", or "../".
so what is the best way to build a SFC
MyTest.ce.vue
<template>
<div>
hello world
</div>
</template>
<script>
export default {
tag: 'my-test',
name: 'MyTest',
data() {
return { count: 0 }
},
};
</script>
<style scoped>
div {
font-size: 200%;
}
</style>
into a .js that i can load in my app, preferably with a single command line tool, without webpack or similar?

Unable to import and use vuejs single-file-component after build

I'm trying to reuse some of my components from one repository in another one. My component is a single file component. I setup webpack to create a .js bundle for that component and then I copy that file and drop it in the other repository's shared directory.
when I import that component and register it in the repository it was created in, there is no issue. Problem is when I import that component and register it on a parent component after I bundle it (separately) and move it to the other repository. I'm getting this error message.
Failed to mount component: template or render function not defined.
Export file
<template>
// template code
</template>
<script>
export default {
data() {
return {
data: 'data'
}
},
</script>
<style>
</style>
Import file:
import sharedComponent from '../../components/shared/sharedComponent.vue';
Vue.component('parent-component', {
components:{
'shared-component': sharedComponent,
},
})
Solution that worked for me:
The issue seemed to be happening because of the way webpack was bundling the file. Fixed by explicitly requesting umd build in the webpack configuration as such:
output: {
libraryTarget: 'umd'
},

Issues using vue-js-modal component

I followed directions on how to use the component from their documentation however I get TypeError: show is not a function
In my main JS file (app.js) I added the following and adding to my project using npm
import VModal from 'vue-js-modal'
Vue.use(VModal)
The documentation states that I can now call a modal from anywhere in the app, so I created a page specific JS file and included the following to hide/show a modal with name="hello-world" on the page that included vue, app.js and the page specific profile.js file.
export default {
methods: {
show() {
this.$modal.show('hello-world');
},
hide() {
this.$modal.hide('hello-world');
}
}
}
When I load the page, I don't see the modal content, however when I click the link Modal I get an error about the show method TypeError: show is not a function
I am using laravel mix and verified that everything is being compiled as expected. Below is a how I am including JS files on the profile page:
<script type='text/javascript' src='/assets/js/manifest.js?ver=5.2.3'></script>
<script type='text/javascript' src='/assets/js/vendor.js?ver=5.2.3'></script>
<script type='text/javascript' src='/assets/js/app.js?ver=1569678574'></script>
<script type='text/javascript' src='/assets/js/profile.js?ver=1569678574'></script>
I am trying to "level up" my Vue and JavaScript experience, previously I just stuck to writing ES5 and my Vue was written without components and bound to a page specific Vue instance, so any help would be greatly appreciated!
EDIT
app.js
window.Vue = require('vue');
require('./global/header.js');
Vue.component('tabs', require('./components/Tabs.vue'));
Vue.component('tab', require('./components/Tab.vue'));
import VModal from 'vue-js-modal'
Vue.use(VModal)
new Vue({
el: '#app'
});
profile.js
export default {
methods: {
show() {
this.$modal.show('hello-world');
},
hide() {
this.$modal.hide('hello-world');
}
}
}
webpack.mix.js that compiles profile.js
mix
.js("resources/js/pages/home.js", "assets/js/home.js")
.js("resources/js/pages/teams.js", "assets/js/teams.js")
.js('resources/js/pages/profile.js', 'assets/js/profile.js')
The error doesn't specify if its the $modal.show() function or your profile.js show() function that is undefined. I suspect that it's your profile.js show() function because it looks like everything is in order with regards to vue-js-modal.
You need to add profile.js as a vue mixin (https://v2.vuejs.org/v2/guide/mixins.html) in order for its functions to be added to the vue instance. So in your app.js add:
import profile from '/assets/js/profile'
Vue.mixin(profile);

How to Access Vue-Loader Components in an HTML File

I would like to use the modular style and file format of Vue Loader (i.e., where I have a template section, script section and style section in each .vue file).
What I can't figure out how to do (or if it is even possible to do) is use my custom templates in an html file.
For instance, in the App.vue file I can use the following code:
<template>
<div id="app">
<message>Hello there</message>
</div>
</template>
This will work to display a custom message component on the home page.
What I would like to do instead is use my custom components in html files. For instance, in the index.html file to use the following code:
<div id="app">
<message>Hello there</message>
</div>
Any idea how I can do this? Thanks.
NOTE: I am new to Vue Loader and semi-new to Vue (so I apologize in advance if the answer to this question is obvious).
There are many ways you can compile a single file component and then use that component in a web page.
Use vue-cli
Vue released a command line interface tool called vue-cli that can initialize projects and build components with zero configuration. One option to build a component that you can use in your page is to use vue build.
vue build MyComponent.vue --prod --lib MyComponent
This will compile a script that exposes MyComponent. If you include that script in your page and then add it globally,
Vue.component(MyComponent)
That component will be available to you in any of your Vues.
Make a plugin
Here is a sample of a very basic framework for making a plugin.
myPluginDefinition.js
window.MyPlugin= {};
MyPlugin.install = function (Vue) {
Vue.component('my-component', require('./my-component.vue'));
}
webpack.config.js
module.exports = {
entry: "./myPluginDefinition.js",
output: {
path: __dirname+'/dist',
filename: "MyPlugin.js"
},
module: {
loaders: [
{
test: /\.vue$/,
loader: 'vue-loader',
}
]
}
};
This will build a file called MyPlugin.js that will contain each of the single file components that you include in the install function. Include the script on your page and then call
Vue.use(MyPlugin)
and you will have all of your components.
Use a custom webpack configuration
There are many ways you could configure webpack to build your single file components. You could build them all into a single file or build them separately. I suggest if you want to use one of these options you ask a separate question.
Actually you can do this easily by:
register your component :
Vue.component('message', {
template: '<div>A custom component!</div>'
});
then comment the render function in your Vue instance like so:
new Vue({
el: '#app',
// render: h => h(App)
})
after that you will be able to render your message Tag like this:
<div id="app">
<message></message>
</div>
Edit :
if you don't want to use this way you can define it in your view instance:
new Vue({
el: '#app',
// render: h => h(App)
components: {
message: {
template: `
<h1>Hello World</h1>
`
}
}
})
Import desired component definition object and pass it to options.components
<template>
<some-component></some-component>
</template>
<style>...</style>
<script>
import SomeComponent from 'path/to/some-component.vue';
export default {
components: {
// ES2015 shorthand for SomeComponent: SomeComponent
SomeComponent
}
}
</script>
That leverages local component registration
Both the default export and SomeComponent are component definition objects.