How to create component library in vuejs - javascript

Motivation
Create vuejs library with components (buttons, headers,...) for re-using it in more projects so they looks the same in different projects.
My not working idea
I would like to reach something like this:
When you have these files
/src/components/TestComponent.vue - vue SFC
/src/main.js
/src/package.js
Where:
TestComponent.vue:
<template>
<div class="test-component">
<h1>{{ msg }}</h1>
</div>
</template>
<script>
export default {
name: 'test-component',
props: {
msg: String
}
}
</script>
<style scoped>
</style>
/src/main.js export that component:
import testComponent from './components/TestComponent.vue'
export { testComponent }
packake.json contains build description which should create library:
{
"name": "#my/test-lib",
"scripts": {
"build": "vue-cli-service build --target lib --name test-lib src/main.js",
...
So I can build it via npm run build and afterwards npm publish to private repo and afterwards use component TestComponent in another project like this:
<template>
<div>
<test-component msg="TEST"></test-component>
</div>
</template>
<script>
import { testComponent } from "#my/test-lib"
export default {
name: "usage-test",
components: {
testComponent
}
};
</script>
<style scoped>
</style>
---> Except it doesnt work. It fails on Cannot find module '#my/test-lib'. My guess is that I am doing export somehow wrong. Can someone provide a hint?
Working idea
TestComponent.vue looks exactly the same as above
main.js use #vue/web-component-wrapper:
import Vue from "vue";
import wrap from "#vue/web-component-wrapper";
import testComponent from "./components/TestComponent.vue";
const wrappedTestComponent = wrap(Vue, testComponent);
window.customElements.define("test-component", wrappedTestComponent);
But this solution has a limitation. When usage - it accepts only String props. So no Objects by nature (I am not counting ser / deser brittle solution)

Related

VueJS nested components fail in an NPM package

The example below are using simplified examples.
Both components work separately, however when one is nested within the other neither render on the page.
index.js (entry point)
// Test components
import TestComponent from '../src/TestComponent.vue'
import Test2Component from '../src/Test2Component.vue'
export {
TestComponent,
Test2Component
}
Both TestComponent and Test2Component will render this way:
<template>
<div class="container">
<TestComponent></TestComponent>
<Test2Component></Test2Component>
</div>
</template>
<script>
import Vue from 'vue'
import { TestComponent, Test2Component } from 'myPackage'
Vue.component('TestComponent', TestComponent);
Vue.component('Test2Component', Test2Component);
However if I move the Test2Component tag into Test1Component.Vue:
<template>
<p>This is the TestComponent</p>
<Test2Component></Test2Component>
</template>
<script>
import Vue from 'vue'
import Test2Component from './Test2Component';
Vue.component('Test2Component', Test2Component);
console.log( Test2Component)
export default {
name: 'TestComponent',
components: {
Test2Component
}
}
</script>
Not even the TestComponent.vue parent component renders.
I found the solution. Do not import Vue from 'vue' and modify it, this is a duplicate of the Vue instance. This syntax seems especially restrictive to get it to work when packaging.
Instead add it as a component to export default:
<script>
import { TestComponent, Test2Component} from 'myPackage'
export default {
components: {
'test-component': TestComponent,
'test2-component': Test2Component
},
...
</script>

How can I use different resources for different Vue layouts?

I've read related sections of the book Full-Stack Vue.js 2 and Laravel 5 and browsed some questions (e.g. vuejs application with different layouts (e.g. login layout, page layout, signup etc.)) to solve this issue. But none of them helped me.
I've created a Laravel + Vue SPA which works great. But when I want to create an administrator dashboard for this SPA with different JavaScript and CSS resources (as the dashboard should have completely different styles), I've confused too much. I don't know and understand what kind of an algorithm I should follow to do this.
You can see the general view of the software below,
// app.js
require('./bootstrap');
import Vue from 'vue'
import App from './views/App'
import router from './router'
import store from './store'
// Layouts
import Default from './views/layouts/Default.vue'
import Dashboard from './views/layouts/Dashboard.vue'
Vue.component('default-layout', Default);
Vue.component('dashboard-layout', Dashboard);
Vue.config.productionTip = false
export default window.vue = new Vue({
el: 'app',
components: {
App
},
router,
store
});
// App.vue
<template>
<div>
<component :is="layout">
<router-view/>
</component>
</div>
</template>
<script>
import $ from 'jquery';
window.$ = window.jQuery = $;
const default_layout = 'default';
export default {
name: 'App',
computed: {
layout() {
return (this.$route.meta.layout || default_layout) + '-layout';
}
},
};
</script>
// Default Layout
<template>
<div>
<default-navigation :is-absolute="isAbsolute" />
<slot/>
<default-footer />
</div>
</template>
<script>
import DefaultNavigation from '../components/DefaultNavigation.vue';
import DefaultFooter from '../components/DefaultFooter.vue';
export default {
name: 'Default',
components: {
DefaultNavigation,
DefaultFooter,
},
computed: {
isAbsolute() {
if (this.$route.name == 'home') {
return true;
}
}
},
};
</script>
// Dashboard Layout
<template>
<div>
<!-- Nothing here yet. -->
</div>
</template>
<script>
import DHeader from '../components/Dashboard/Header.vue';
import DSidebar from '../components/Dashboard/Sidebar.vue';
import DTitle from '../components/Dashboard/Title.vue';
export default {
name: 'Dashboard',
components: {
DHeader,
DSidebar,
DTitle
},
};
</script>
// webpack.mix.js
mix.js('resources/js/app.js', 'public/js')
.sass('resources/sass/app.scss', 'public/css');
mix.js('resources/js/dashboard.js', 'public/js')
.sass('resources/sass/dashboard.scss', 'public/css');
What should I do to use a totally different style file and an extra JavaScript file in the dashboard layout?
If you're looking for separate Vue instances for both, then you can follow this
Create separate blade templates for both the instances. Add separate routes for both templates.
Route::get('/dashboard/{any?}', function () {
return view('dashboard');
})->where('any', '[\/\w\.-]*');
Route::get('/{any?}', function () {
return view('index');
})->where('any', '[\/\w\.-]*');
Create two Vue instances in separate js files, say app.js and dashboard.js. You can create this in another directory like dashboard/dashboard.js
Update your webpack.mix.js
mix.js('resources/js/app.js', 'public/js')
.sass('resources/sass/app.scss', 'public/css');
mix.js('resources/dashboard/dashboard.js', 'public/js')
.sass('resources/sass/dashboard.scss', 'public/css');
You can link the build files in public to the corresponding blade templates.

composition api doesn't work with Nuxt-TS

Created nuxt app using npx create-nuxt-app. Followed documentation at https://typescript.nuxtjs.org, however i have an issue using #vue/composition-api:
example component.vue:
<template>
<div>
{{ msg }}
</div>
</template>
<script lang="ts">
import { createComponent, ref } from '#vue/composition-api'
export default createComponent({
setup() {
const msg = ref('hello')
return {
msg
}
}
})
</script>
Doesn't work, throws an error "Property or method "msg" is not defined on the instance but referenced during render." because it doesn't see my ref. I've added composition API as plugin in "plugins/composition-api.ts":
import Vue from 'vue'
import VueCompositionApi from '#vue/composition-api'
Vue.use(VueCompositionApi)
Then in nuxt.config.ts:
plugins: ['#/plugins/composition-api']
My bad, forgot about TS-runtime in nuxt:
npm i -S #nuxt/typescript-runtime
Then you need to update package.json: https://typescript.nuxtjs.org/guide/runtime.html#installation

How to register component in vue+ts correctly?

I have this code:
App.vue
<template>
<v-container>
<div>
<schedule-table
v-if = "schedule.length > 0"
:exercises="schedule[0].exercises"
/>
</div>
</v-container>
</template>
<script lang="ts">
import Vue from "vue";
import { Component } from "vue-property-decorator";
import ScheduleTable from '#/components/ScheduleTable.vue'
#Component({
components: {
ScheduleTable,
}
})
</script>
ScheduleTable.vue
<template>
<v-container>
<schedule-week
:exercises="exercises"
/>
</v-container>
</template>
<script lang="ts">
import Vue from "vue";
import { Component, Prop } from "vue-property-decorator";
import { IExercise } from "#/interfaces"
import ScheduleWeek from '#/components/ScheduleWeek.vue'
#Component({
name: 'ScheduleWeek',
components: {
ScheduleWeek,
}
})
#Component
export default class ScheduleTable extends Vue {
#Prop( {required: true, type: Array } ) readonly exercises!: IExercise;
}
</script>
ScheduleWeek.vue
<template>
<v-container
:ex="exercises"
>
here is tables (but this tables dosn't show and any other text dosn't show)
</v-container>
</template>
<script lang="ts">
import Vue from "vue";
import { Component, Prop } from "vue-property-decorator";
import { IExercise } from "#/interfaces"
#Component
export default class ScheduleWeek extends Vue {
#Prop( {required: true, type: Array } ) readonly exercises!: IExercise;
}
</script>
And have vue warn:
Unknown custom element: < schedule-week > - did you register the
component correctly? For recursive components, make sure to provide
the "name" option.
How to fix this problem? How to register component correctly?
There are multiple ways to declare a vue-component when you use typescript. The class-based SFC approach (the one you are using) needs to follow a slightly different syntax. You used the typescript decorator twice in your schedule-week component
App.vue
<v-container>
<div>
<schedule-table
v-if = "schedule.length > 0"
:exercises="schedule[0].exercises"
/>
</div>
</v-container>
</template>
<script lang="ts">
import { Component, Vue } from "vue-property-decorator"; // you can import vue here
import ScheduleTable from '#/components/ScheduleTable.vue'
#Component({
components: {
ScheduleTable,
}
})
export default class App extends Vue {} // the #Component() decorator needs to be followed by the exported class
Correspondingly your other components:
ScheduleTable.vue
<template>
<v-container>
<schedule-week
:exercises="exercises"
/>
</v-container>
</template>
<script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator"; // see above
import { IExercise } from "#/interfaces"
import ScheduleWeek from '#/components/ScheduleWeek.vue'
#Component({
name: 'ScheduleTable',
components: {
ScheduleWeek,
}
}) // >>>don't use the decorator twice<<<
export default class ScheduleTable extends Vue {
#Prop( {required: true, type: Array } ) readonly exercises!: IExercise;
}
</script>
ScheduleWeek.vue
<template>
<v-container
:ex="exercises"
>
here is tables (but this tables dosn't show and any other text dosn't show)
</v-container>
</template>
<script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator"; // see above
import { IExercise } from "#/interfaces"
#Component
export default class ScheduleWeek extends Vue {
#Prop( {required: true, type: Array } ) readonly exercises!: IExercise;
}
</script>
##EDIT:
From the officiel TypeScript Documentation:
With the introduction of Classes in TypeScript and ES6, there now exist certain scenarios that require additional features to support annotating or modifying classes and class members. Decorators provide a way to add both annotations and a meta-programming syntax for class declarations and members.
Decorators are basically TypeScript(JavaScript) functions which are used to add additional information to the following class (your component) or class members. This also means a decorator cannot stand alone. The #Component() decorator takes an Object as parameter which is 'as-is' translated into component-options.
TL;DR
To register the components SubOne and SubTwo in MyComponent, pass it to the #Component decorator:
import { Component, Vue } from "vue-property-decorator";
#Component({
components: {
SubOne,
SubTwo,
}
})
export default class MyComponent extends Vue {
…
}
And make sure you also decorate the SubComponents like so;
import { Component, Vue } from "vue-property-decorator";
#Component
export default class SubOne extends Vue {
…
}
Error in #MarcRo's answer
Setup and resulting Error
In the Component ScheduleTable.vue, the decorator #Component gets the parameter name set to the Component it is about to import. This leads to a recursive call of said Component and hence an Error.
Fix
Set name: 'ScheduleTable' - or even drop it. The class name seems to be used (which is what we are doing).
Aside
I could not find a doc on what can actually passed to the decorator, but it's used like this in #MarcRo's answer in App.vue as well as here. (The link in the comments there seems to be old).
I don't have enought reputation to comment and a fixing edit got rejected, so sorry for the extra post.
Thank you #MarcRo for teaching me how to use this and the facepalm moment after solving the recursion error after a way too long time ;)

How to use an external module inside a Vue SFC?

I'm new to Vue, and I'm trying to build an app which uses Typescript and vue-property-decorator. This is my first attempt to use an external module inside an SFC. I want to create a calendar component using v-calendar and render it in a page (schedule.vue).
I've done yarn add, and I'm trying to follow what the docs say about using v-calendar as a component. But as of now, when I try to render the page, there are no compilation errors yet the whole page goes blank. What's going wrong here? Any help is greatly appreciated!
// pages/schedule.vue
<template lang="pug">
VCalendar
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import VCalendar from '../components/calendar.vue';
#Component({
components: {
VCalendar
}
})
export default class Schedule extends Vue {
}
// components/calendar.vue
<template lang="pug">
<v-calendar :attributes="attrs" />
</template>
<script lang="ts">
import 'v-calendar/lib/v-calendar.min.css';
import { Calendar, setupCalendar } from 'v-calendar';
import { Component, Vue } from 'vue-property-decorator';
#Component({
components: {
setupCalendar,
Calendar
}
})
export default class VCalendar extends Vue {
data() {
return {
attrs: [
{
key: 'today',
highlight: {
backgroundColor: '#ff8080',
},
dates: new Date(2018, 0, 1)
}
],
};
}
mounted():any {
setupCalendar({
firstDayOfWeek: 2,
});
}
}
</script>
I've checked these questions but I'm still lost:
How to use external vue npm components
How to include external js inside vue component
You need to register it globally using Vue.component('v-calendar',Calendar ) or in case of class based SFC you are importing Calendar a named import, then it should be used as <calendar></calendar>
#Component({ components: { setupCalendar, 'v-calendar':Calendar } }) can also be used in class based SFCs to provide a custom tag name to some imported component.

Categories