Npm package Vue3 UI library don't run in Nuxt - npm

I have a UI library with Vue3 Composition API components. It works fine in other Vue projects but there is a problem in Nuxt 2.6 project.
My library has component Badge
// components/badge.vue
<template>
<div :class="classes">{{ label }}</div>
</template>
<script>
import { computed, reactive } from 'vue';
export default {
name: 'badge',
props: {
label: {
type: String,
required: true,
},
highlighted: {
type: Boolean,
default: false,
}
},
setup(props, { emit }) {
props = reactive(props);
return {
classes: computed(() => ({
'badge': true,
'badge--highlighted': props.highlighted
})),
onClick() {
emit('click');
}
}
},
}
</script>
<style lang="scss">
#import '_variables';
.badge {
font-size: $badge-font-size;
border-radius: $badge-border-radius;
background-color: $badge-bg;
color: $badge-color;
display: inline-block;
line-height: $badge-line-height;
padding: $badge-padding;
&--highlighted {
background-color: $badge-bg-accent;
color: $badge-color-accent;
}
}
</style>
I export component for npm package
// components/index.js
export { default as Badge } from './Badge/Badge.vue';
Build npm package with Umd and Es files and publish as private package
In Nuxt project I installed package and created plugin to use it.
// plugins/CupPatternLibrary.ts
import Vue from 'vue';
import * as CupPatternLibrary from '#cambridgecore/cup-pattern-library';
Vue.use(CupPatternLibrary);
import {
Badge
} from '#cambridgecore/cup-pattern-library'
Vue.component('Badge', Badge)
Registered plugin
// nuxt.config.js
plugins: [
'~/plugins/CupPatternLibrary.ts'
],
and want to use it
// landing-page.vue
<Badge label="xxx" />
And now i get TypeError and browser display error
Cannot read properties of undefined (reading 'classes')
in file
node_modules\#cambridgecore\cup-pattern-library\dist\cup-pattern-library.umd.js:2:416
So my question is how to make npm package to work with Nuxt project

Related

Styles is missing after I bundle my project with Vite

As title, my styles (including style tag in SFC and css imported to app.ts) are missing when I compile my app in IIFE format.
I have no idea whether it's by Vite or RollUp... It works properly with vite serve, but not vite build.
I saw the css emitted in other format, but not IIFE. For that, I can't load Vue form CDN, which I want to.
// vite.config.js
import vue from "#vitejs/plugin-vue";
import { defineConfig } from "vite";
import env from "vite-plugin-env-compatible";
export default defineConfig({
plugins: [
env({
prefix: "",
mountedPath: "process.env",
}),
vue(),
],
build: {
minify: true,
rollupOptions: {
external: ["vue"],
output: {
format: "iife",
globals: {
vue: "Vue",
},
},
},
},
});
// src/app.ts
import { createApp } from "vue";
import App from "./App.vue";
import "./main.css";
createApp(App).mount("#app");
<!-- src/App.vue -->
<template>
<h1>Hello World</h1>
<button #click="increment">Click Me!</button>
<div>Clicked: {{ count }}</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
//#region Counter
const count = ref(0);
const increment = () => (count.value += 1);
//#endregion
</script>
<style scoped>
h1 {
color: green;
}
</style>
I found in documentation, all I need is setting build.cssCodeSplit to false.
https://vitejs.dev/guide/features.html#css-code-splitting
Related issue for follow up

Vue3 + Webpack Encore styles are not applied

I use Vue3 with webpack encore. tag in single file components are not included in build. My configuration is default. I also tried this Configuring Vue framework with webpack - styles are not applied but it didn't help in my case.
webpack.config.js
const Encore = require('#symfony/webpack-encore');
if (!Encore.isRuntimeEnvironmentConfigured()) {
Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev');
}
Encore
[...]
.enableVueLoader(() => {}, { runtimeCompilerBuild: true })
.configureCssLoader((config) => {
config.esModule = false; //it does not matter, didn't help
})
;
module.exports = Encore.getWebpackConfig();
main.vue
<template>
[...]
</template>
<script>
export default {
name: 'Main',
}
</script>
<style>
body{
color: green;
}
</style>

TypeError: this.$store is undefined

I'm following a Vue 2 tutorial but got stuck by this error. I've checked multiple questions before posting this one because I'm still unsure of what did I do wrong. Any input will be appreciated!
App.vue code:
<template>
<div id="app">
<!-- <img alt="Vue logo" src="./assets/logo.png"> -->
<!-- <HelloWorld msg="Welcome to Your Vue.js App"/> -->
<TodoApp />
</div>
</template>
<script>
// import HelloWorld from './components/HelloWorld.vue'
import TodoApp from './components/TodoApp.vue';
export default {
name: 'App',
components: {
// HelloWorld,
TodoApp
}
}
</script>
body {
font-family: "Franklin Gothic Medium", "Arial Narrow", Arial, sans-serif;
line-height: 1.6;
background: #e8f7f0;
}
.container {
max-width: 1100px;
margin: auto;
overflow: auto;
padding: 0 2rem;
}
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
Store (index.js) code:
import Vuex from 'vuex';
import Vue from 'vue';
import TodoModule from './modules/TodoModule.js';
// Load vuex
Vue.use(Vuex);
// Create store
export default new Vuex.Store({
modules: {
TodoModule
}
});
Main.js code:
import Vue from 'vue'
import App from './App.vue'
import store from './store'
Vue.config.productionTip = false
new Vue({
store,
render: h => h(App),
}).$mount('#app')
TodoApp.vue code:
<template>
<div>
<h3>Vuex Todo App</h3>
<div class="todos">
<div class="todo" v-for="todo in allTodos" :key="todo.id">
{{ todo.title }}
</div>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
name: "TodoApp",
computed: mapGetters(['allTodos'])
};
</script>
<style>
</style>
TodoModule.js code:
// import axios from "axios";
const state = {
todos: [
{
id: 1,
title: "first todo"
},
{
id: 2,
title: "second todo"
},
{
id: 3,
title: "third todo"
}
]
};
const getters = {
allTodos: state => state.todos
};
const actions = {};
const mutations = {};
export default {
state,
getters,
actions,
mutations
}
No Error on Compiler:
Error on Browser Console:
=== UPDATE ===
I still haven't fix this issue and I have tried restarting the localhost server, even my own laptop to check if it'll fix it.. no luck there.
From what I've read from answers, is it possible that this error happen because of my vue installation? Or perhaps I forgot to install something else when starting the development?
=== UPDATE 2 ===
Found the culprit, apparently my vue and vuex npm package either got corrupted or has a conflict, changing the version of vue to 2.7.8 and vuex to 3.0.1 then rerun npm i (after deleting the node_modules and package-lock.json) has fixed the issue!
Thanks for all the help!
I got the same problem while using Vue 2.
npm uninstall vuex
then
npm i vuex#3.0.1
npm install vuex by default npm installs version 4 which is not supported by Vue 2
https://github.com/vuejs/vuex/releases
Your code seems ok.
I could reproduce with no erros using TodoModule below.
const TodoModule = {
state: {
todos: [{ title: "1" }, { title: "2" }, { title: "3" }, { title: "4" }]
},
getters: {
allTodos: (state) => state.todos
}
};
export default TodoModule;
If error continues, you could check the naming on your files.
The store index.js as you said should be named as store.js.
Or perhaps restart server.
I think I might have actually done the same youtube tutorial on vuex state management but I didn't use the mapGetter helper function, instead, I used the vanilla functions of vuex for getting and setting global variables.
main.js
store: new vuex.Store({
state: {
user: {}
},
mutations: {
setUser(state, payload){
state.user = payload;
}
},
getters: {
getUser: state => state.user,
}})
any-component.js
this.user = this.$store.getters.getUser; //get
this.$store.commit("setUser", this.user); //set
I think you should change getter inTo
export default {
name: "TodoApp",
computed: mapGetters(['allTodos'])
};
===>
export default {
name: "TodoApp",
computed: {
...mapGetters(['allTodos'])
}
};

VueJS PSPDFKit Integration

I am trying to integrate PSPDFKit (standalone) with a VueJS project, but struggling with some unexpected behaviour.
For background:
I have a component which includes the import PSPDFKit from 'pspdfkit' statement. When I run the Vue application and navigate to the view that utilises this component, I get an Uncaught SyntaxError: Unexpected token '<' - suggesting that I'm encountering HTML instead of the intended library.
However, when running locally, if I comment out the import line, wait for the PSPDFKit is not defined errors, then un-comment the import line, the viewer loads as expected.
Can anyone shed any light on what's happening here? I'm struggling to diagnose the issue and ensure the integration works as expected on first load. Component code is below.
Thanks
<template>
<div class="container"></div>
</template>
<script>
import PSPDFKit from 'pspdfkit'
export default {
name: 'PSPDFKitWrapper',
props: ["documentUrl", "licenseKey", "baseUrl"],
_instance: null,
mounted: function ()
{
this.load();
},
methods: {
load()
{
PSPDFKit.load({
document: this.documentUrl,
container: ".container",
licenseKey: this.licenseKey,
baseUrl: this.baseUrl
})
.then(instance =>
{
this._instance = instance;
this.$emit("update:error", "");
})
.catch(err =>
{
PSPDFKit.unload(".container");
this.$emit("update:error", err.message);
});
},
unload()
{
if (this._instance) {
PSPDFKit.unload(this._instance);
this._instance = null;
} else {
PSPDFKit.unload(".container");
}
}
}
}
</script>
<style scoped>
.container {
width: 100%;
height: 90vh;
}
</style>

How to import a Vue component that is packaged into a JavaScript library?

I am attempting to package a Vue component into a JavaScript library and then use it in another project using vue-sfc-rollup.
I am able to package the component just as the README says to do. Then when I copy the .min.js file into another project and attempt to use the component, I always get the error:
Vue warn handler: Failed to mount component: template or render function not defined.
The way I'm trying to use the component from inside another Vue component is this:
import Vue from 'vue'
import MyComponent from '../lib/my-components.min.js'
Vue.use(MyComponent)
Then in the components section:
components: {
'my-component': MyComponent
}
Then in the template:
<my-component></my-component>
What am I missing here? What is the correct way to use the component in another project?
EDIT: Adding component code in response to comment.
<template>
<div class="my-component">
<p>The counter was {{ changedBy }} to <b>{{ counter }}</b>.</p>
<button #click="increment">
Click +1
</button>
<button #click="decrement">
Click -1
</button>
<button #click="increment(5)">
Click +5
</button>
<button #click="decrement(5)">
Click -5
</button>
<button #click="reset">
Reset
</button>
</div>
</template>
<script>
export default {
name: 'MyComponent', // vue component name
data() {
return {
counter: 5,
initCounter: 5,
message: {
action: null,
amount: null,
},
};
},
computed: {
changedBy() {
const {
message
} = this;
if (!message.action) return 'initialized';
return `${message?.action} ${message.amount ?? ''}`.trim();
},
},
methods: {
increment(arg) {
const amount = (typeof arg !== 'number') ? 1 : arg;
this.counter += amount;
this.message.action = 'incremented by';
this.message.amount = amount;
},
decrement(arg) {
const amount = (typeof arg !== 'number') ? 1 : arg;
this.counter -= amount;
this.message.action = 'decremented by';
this.message.amount = amount;
},
reset() {
this.counter = this.initCounter;
this.message.action = 'reset';
this.message.amount = null;
},
},
};
</script>
<style scoped>
.my-component {
display: block;
width: 400px;
margin: 25px auto;
border: 1px solid #ccc;
background: #eaeaea;
text-align: center;
padding: 25px;
}
.my-component p {
margin: 0 0 1em;
}
</style>
I found one way to do this at this Stack Overflow question: Register local Vue.js component dynamically.
I got it to work by implementing a simpler version of the solution shown there. I removed the component section from the outer component, then added this created() lifecycle hook:
created() {
console.log('pages/PageHome.vue: created(): Fired!')
// From https://stackoverflow.com/questions/40622425/register-local-vue-js-component-dynamically
// "This is how I ended up importing and registering components dynamically to a component locally"
const componentConfig = require('../lib/components/my-component.js')
console.log('pages/PageHome.vue: created(): componentConfig.default = ')
console.log(componentConfig.default)
const componentName = 'my-component'
console.log('pages/PageHome.vue: componentName = ' + componentName)
this.$options.components[componentName] = componentConfig.default
}
The component is imported using a require() call, then registered locally by adding it to the this.$options.components dictionary. The secret sauce is to add .default to the componentConfig expression. This doesn't seem to be formally documented anywhere.
Editorial comment: I'm surprised the Vue documentation pays such little attention to distribution patterns for re-usability. As great as the Vue docs are, this is a glaring omission.