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

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'
},

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"]

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

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.

Creating a simple VUE.JS application

I am trying to use a simple polygon cropper from Vue within an application by following the steps in this article.
I created my app using:
vue init webpack myproject
Now, I need to add the sample template to my app (it has a src folder), but I am not sure how to amend or add this piece of code to my application. The template should be as follows per the linked article:
// Global
import Vue from 'vue';
import VuePolygonCropper from 'vue-polygon-cropper';
Vue.component(VuePolygonCropper);
// Local
import VueCropper from 'vue-polygon-cropper';
export default {
components: { VueCropper}
}
<template>
<div id="app">
<polygon-crop :imageSource="'/demo.png'" ref="canvas"></polygon-crop>
<button #click.prevent="crop">Crop</button>
<button #click.prevent="undo">Undo</button>
<button #click.prevent="redo">Redo</button>
<button #click.prevent="reset">Reset</button>
</div>
</template>
<script>
export default {
name: 'App',
methods: {
crop: function () {
this.$refs.canvas.crop();
},
undo: function () {
this.$refs.canvas.undo();
},
redo: function () {
this.$refs.canvas.redo();
},
reset: function () {
this.$refs.canvas.reset();
}
}
};
</script>
I am not sure what the meaning of global or local is there. My src folder structure is as follows:
Directory of C:\ThermoAnalyser\vue_js\myproject\src
27/12/2020 11:37 AM <DIR> .
27/12/2020 11:37 AM <DIR> ..
27/12/2020 11:37 AM 374 App.vue
27/12/2020 11:37 AM <DIR> assets
27/12/2020 11:37 AM <DIR> components
27/12/2020 11:37 AM 360 main.js
27/12/2020 11:37 AM <DIR> router
2 File(s) 734 bytes
5 Dir(s) 301,183,393,792 bytes free
You won't be able to get this component running with just this code snippet, there's a couple of things that you would need to do to fix this up.
Before we go any deeper, I would like you to make sure if you have installed this vue-polygon-cropper component. If you navigated to the package.json that is located in the same level as your "src" folder, you would see a mention of vue-polygon-cropper there, if not please install it by npm install vue-polygon-croper .
Let's take a look at your <template> section first:
1- In the template, you call a component <polygon-crop> but, there is no component registered by that name in your script (What you are attempting to register is 'VuePolygonCropper' so you should try using <VuePolygonCropper> component instead.
2-I see there you copied and pasted the logo image in assets, that's a great way to test it! However, Digging through the creator's example that they put up on github, It seems like this component requires a full path to your image file instead of the relative path. so instead of /src/assets/logo.png try doing :imageSource="require('../assets/logo.png')"
I'm assuming the assets logo is on a folder that is one level above your current component.
So your template should look like this:
<template>
<div id="app">
<VuePolygonCropper :imageSource = "require('../assets/logo.png')"
ref="canvas"> </VuePolygonCropper>
<button #click.prevent="crop"> Crop </button>
<button #click.prevent="undo"> Undo </button>
<button #click.prevent="redo"> Redo </button>
<button #click.prevent="reset"> Reset </button>
</div>
</template>
Now on to your script!
just import the VuePolygonCropper and mention it as a component in the components section.
You don't need to import vue and do Vue.component(VuePolygonCropper). The correct way to register this component would be like this
<script>
import VuePolygonCropper from 'vue-polygon-cropper';
export
default
{
name: 'App',
components:{VuePolygonCropper},
methods: {
crop: function() {
this.$refs.canvas.crop();
},
undo: function()
{
this.$refs.canvas.undo();
},
redo: function()
{
this.$refs.canvas.redo();
},
reset: function()
{
this.$refs.canvas.reset();
}
}
};
</script>
For the heck of it, I have created a codesandbox that you can play around with . You can try to play around with the App.vue file and see how it was created.
Happy coding!
"Global" vs "Local"
The "global" and "local" comments refer to global component registration and local component registration. The article shows both ways of registering the vue-polygon-cropper component probably to make it easier to copy-paste into your own code.
Global component registration
You can register a component globally so that it could be used in another component without the consuming component having to register it locally. This is normally used for commonly used components that are frequently found in several components (e.g., a button).
Below is an example of global component registration that allows MyButton to be used in MyForm. Notice how MyForm's <template> uses MyButton without any component registration for MyButton in its <script>.
// main.js
import Vue from 'vue'
Vue.component('MyButton', { /*...*/ })
// MyForm.vue
<template>
<MyButton #click="onClick" />
</template>
<script>
export default {
methods: {
onClick() { /*...*/ }
}
}
</script>
Local component registration
For seldom used components (ones that are only found in a few components of your app), register the components locally instead to help minimize the bundle size if needed. If your component is never used (e.g., from a refactoring down the road), local registration allows the bundler to crop out your component from the final output.
Here's the previous example with local registration instead:
// MyButton.vue
<template>
<button />
</template>
// MyForm.vue
<template>
<MyButton #click="onClick" />
</template>
<script>
import MyButton from './MyButton.vue'
export default {
components: {
MyButton 👈
},
methods: {
onClick() { /*...*/ }
}
}
</script>
Getting started with the sample code
To quickly get the sample code working in your project:
Copy the <template> and <script> parts of the sample code into your src/App.vue, replacing everything.
In the App.vue's component definition, locally register vue-polygon-cropper as polygon-crop:
<script>
import VuePolygonCropper from 'vue-polygon-cropper'
export default {
components: {
'polygon-crop': VuePolygonCropper
}
}
<script>
The sample code refers to an image at /demo.png, but your sample app only has src/assets/logo.png, so edit src/App.vue's <template> so that polygon-crop uses src/assets/logo.png. We have to require the asset's path so that Webpack properly resolves the path from source:
<polygon-crop :imageSource="require('#/assets/logo.png')">
sample GitHub repo
Update to Vue CLI
Consider using Vue CLI's default generated templates (from vue create) instead of that outdated webpack template. The newly created project would still use Webpack, but most of the config for developer ergonomics are abstracted away, which can be helpful for beginners.
vue create myproject

Import npm package into a Vue.js Single File component

I would like to use Jodit in a SFC, but I am not sure how this is supposed to be done. I realized there is a wrapper (jodit-vue), but for educational purposes, I would like to know how it's done without it. I created a Vue CLI project with default presets, and all I changed is the App.vue:
<template>
<div id="app">
<textarea id="editor" name="editor"></textarea>
</div>
</template>
<script>
import "../node_modules/jodit/build/jodit.min.js"
export default {
name: 'App',
created(){
let editor = new Jodit('#editor');
editor.value = '<p>start</p>';
}
}
</script>
<style>
#import "../node_modules/jodit/build/jodit.min.css" ;
</style>
This produces the error: error 'Jodit' is not defined no-undef, and
if I change the import to:
import Jodit from "../node_modules/jodit/build/jodit.min.js"
Then the compilation is fine, but the browser console says:
vue.runtime.esm.js?2b0e:1888 TypeError: _node_modules_jodit_build_jodit_min_js__WEBPACK_IMPORTED_MODULE_0___default.a is not a constructor
Admittedly, I am new to all of this, but pointing me to the right direction is appreciated.
The jodit module exports the Jodit constructor, so your component would import it like this:
import { Jodit } from 'jodit'
You'd also need the Jodit styles, which could be imported like this:
import 'jodit/build/jodit.min.css'
To create a Jodit instance, we need to provide an element or selector to an existing <textarea>. The Vue component's elements are available in the mounted() lifecycle hook (not in the created() hook), so that's where we would initialize:
export default {
mounted() {
const editor = new Jodit('#editor')
editor.value = '<p>start</p>'
},
}
demo

How do I import Three.js into my Nuxt project

I want to import modules in examples folder in THREE.js such as OBJLoader into my Nuxt Project.
I can import main folder of THREE, but error occurs when trying to import modules in examples folder.
Tried these steps in official docs.
https://threejs.org/docs/index.html#manual/en/introduction/Import-via-modules
I'm getting error below
SyntaxError
Unexpected token {
<template>
</template>
<script>
import * as THREE from 'three'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
export default{
}
</script>
here are my github repository
https://github.com/ksuhara/threejs-test
Finally I could find what was wrong.
Well, it has to do with nuxt building system. When using third parts libs, you should add them into nuxt.config.js bild->transpile array so it can be included as a dependency with Babel.
transpile: [
"three"
]
Ref: https://nuxtjs.org/api/configuration-build#transpile
Threejs must be run on the client side so enclosed the component with <client-only> tag and loaded it dynamically with const MyComponent = () => import('~/path/to/MyComponent.vue'); but now I am getting the error on server side.
Finally I managed to do it like this!
<template>
<div>
<client-only>
<threejs-component />
</client-only>
</div>
</template>
<script>
export default {
components: {
ThreejsComponent: process.browser ? () => import('~/path/to/ThreejsComponent.vue') : null
}
}
</script>
inside ThreejsComponent.vue are all the threejs imports