vue compile shared computed functions into separate package - vue.js

How can i share common vue/nuxt specific code between different packages?
I do not want to use a monorepo however I have shared code that I want to separate into its own package. The shared code(new package), is written using #nuxtjs/composition-api and is just shared computed and methods used over and over in different components/templates.
I do not want the package to be setup as a plugin. Instead something to directly import to utilize tree shaking(just like the composition-api).
I am familiar with rollupjs to create the importable modules.
//New package
//index.js
export { default as isTrue } from './src/isTrue'
...
//src/isTrue
import { computed } from '#nuxtjs/composition-api'
export default (p) => {
return computed(() => p === 'true') //Im not 100% is this will break reactivity?!?!
}
I havent had any issues compiling this into .ssr, .esm, .min formats via rollupjs
The issue I come across is when i import the new package into a working file.
import { isTrue } from 'new-package'
export default{
name: 'testComp',
setup(props){
return {
isActive: isTrue(props.active)
}
}
will yield:
[vue-composition-api] must call Vue.use(VueCompositionAPI) before using any function.
i understand the #nuxtjs/composition-api is a wrapper of the VueCompositionAPI.
i dont really want to install the new package as a plugin therefore I have omitted the install on the new package(install ex: https://github.com/wuruoyun/vue-component-lib-starter/blob/master/src/install.js)

Used the options API
//library.js
export default function(){ // access to this -> arrow function doesnt have this
return this.disabled == true // when applied using the options api this will be the vue context aka property disabled
}
library.js is compiled using rollupjs and can be imported
//component.vue
import { isDisabled } from 'library'
export default {
//Composition API:
setup(props){
return {
//stuff
}
},
//Options API:
computed:{
isDisabled,
}
}

Related

How to add included Pug files to Vite module graph

I wrote a Rollup plugin to import Pug as an HTML string:
// Rollup plugin imported to Vite config
import { render } from 'pug';
export default function pug() {
return {
name: 'rollup-plugin-pug-html',
transform(src, id) {
if (id.endsWith('.pug')) {
const html = render(src, { filename: id });
const code = `export default ${JSON.stringify(html)};`;
return { code };
}
},
};
}
I'm using it in Vite to create templates for Vue components, as in this reduced example:
// ProofOfConceptSFC.vue
<script>
import { compile } from 'vue/dist/vue.esm-bundler.js';
import template from './template.pug';
export default {
render: compile(template)
};
</script>
The HMR is working great when I edit template.pug. The new template appears and the latest reactive values persist.
My problem is that template.pug may depend on other Pug files with include:
//- template.pug
include ./header.pug
p Hello {{ name }}
include ./footer.pug
The Vite server doesn't know about those files, and nothing happens if I touch them. Ideally I could invalidate template.pug when any Pug file is changed.
I'm guessing I want my plugin to update the ViteDevServer's server.moduleGraph. Is there a supported way to do that?
Huge thanks to the friendly Vite chat on Discord for setting me in the right direction.
The two keys I was missing:
Use Pug compile to create a render method that has render.dependencies, as done by Parcel
Use virtual import statements to attach the dependencies to the transform hook result, as done by vite-plugin-svelte.
Here is the working plugin:
import { compile } from 'pug';
export default function pluginPug() {
return {
name: 'vite-plugin-pug',
transform(src, id) {
if (id.endsWith('.pug')) {
const render = compile(src, { filename: id });
const html = render();
let code = '';
for (let dep of render.dependencies) {
code += `import ${JSON.stringify(dep)};\n`;
}
code += `export default ${JSON.stringify(html)};`;
return { code };
}
},
};
}

How to use provide/inject with TypeScript but without vue-class-component

When I use provide/inject with class components everything works as expected. But when I use it in a "normal" vue component I get type-errors.
In this example I get errors when referencing this.testService. The code works though.
export default Vue.extend({
name: "HelloWorldBasic" as string,
inject: ["testService"],
computed: {
message(): string | null {
return this.testService ? this.testService.hello() : null;
}
}
});
Where did I make my mistake? How should I write the code?
I set up a small project to be able to reproduce it and work with it:
$ git clone git#github.com:schnetzi/vue-provide-inject.git
$ npm ci
$ npm start
This is a bit tricky to do, but it is possible using the same workaround that is commonly used for mixins, which entails defining an interface for whatever you want to add to the Vue instance.
In your case, here's how it would be done:
import Vue_ from 'vue';
import MyTestServiceType from 'wherever/it/is';
interface HelloWorldBasicInjected {
testService: MyTestServiceType
}
const Vue = Vue_ as VueConstructor<Vue_ & HelloWorldBasicInjected>
export default Vue.extend({
name: "HelloWorldBasic" as string,
inject: ["testService"],
computed: {
message(): string | null {
return this.testService ? this.testService.hello() : null;
}
}
});
An alternative to the inject attribute is to use the inject() method
import { inject } from 'vue';
// (...)
data() {
return {
testService: inject('testService') as TestService,
};
},
After which you can use this.testService as you did: this.testService?.hello()
inject() can even take a default value as second parameter in case the injection isn't resolved.

Nuxtjs using Vuex-module-decorator doesn't wordk

I want to use my vuex modules as classes to make my code more clean and readable. I used the section (Accessing modules with NuxtJS) at the bottom of this document: https://github.com/championswimmer/vuex-module-decorators/blob/master/README.md
I've searched for the solution for almost 3 days and tried out this link:
vuex not loading module decorated with vuex-module-decorators
but, it didn't work.
Also, I used getModule directly in the component like the solution in this issue page: https://github.com/championswimmer/vuex-module-decorators/issues/80
import CounterModule from '../store/modules/test_module';
import { getModule } from 'vuex-module-decorators';
let counterModule: CounterModule;
Then
created() {
counterModule = getModule(CounterModule, this.$store);
}
Then, accessing method elsewhere
computed: {
counter() {
return counterModule.getCount
}
}
it didn't work for me!
This is my Module in store folder in Nuxtjs project:
import { ICurrentUser } from '~/models/ICurrentUser'
import { Module, VuexModule, Mutation, MutationAction } from 'vuex-module-decorators'
#Module({ stateFactory: true, namespaced: true, name: 'CurrentUserStore' })
export default class CurrentUser extends VuexModule {
user: ICurrentUser = {
DisplayName: null,
UserId: null,
};
#Mutation
setUser(userInfo: ICurrentUser) {
this.user = userInfo;
}
get userInfo() {
return this.user;
}
}
In index.ts file in sore folder:
import { Store } from 'vuex'
import { getModule } from 'vuex-module-decorators'
import CurrentUser from './currentUser'
let currentUserStore: CurrentUser
const initializer = (store: Store<any>): void => {
debugger
currentUserStore = getModule(CurrentUser, store)
}
export const plugins = [initializer]
export {
currentUserStore,
}
I think the problem stems from this line:
currentUserStore = getModule(CurrentUser, store)
currentUserStore is created as object but properties and methods are not recognizable.
when I want to use getters or mutation I get error. For instance, "unknown mutation type" for using mutation
Probably several months late but I struggled with a similar issue, and eventually found the solution in https://github.com/championswimmer/vuex-module-decorators/issues/179
It talks about multiple requirements (which are summarised elsewhere)
The one that relates to this issue is that the file name of the module has to match the name you specify in the #Module definition.
In your case, if you rename your file from currentUser to CurrentUserStore' or change the name of the module toCurrentUser`, it should fix the issue.

How to use Toasted inside an export default {}

I'm trying to use the package Toasted but I'm having a hard time understading how to use it.
I have a package called TreatErrors.js and I call this package to handle all errors from my application based on HTTP code returned by API a restfull API.
TreatErrors.js
import toasted from 'vue-toasted';
export default {
treatDefaultError(err){
let statusCode = err.response.status;
let data = err.response.data;
for(let field in data.errors){
if (data.errors.hasOwnProperty(field)) {
data.errors[field].forEach(message => {
toasted.show(message);
})
}
}
if(statusCode === 401){
toastr.error('Your token has expired. Please logout and login again to retrieve a new token');
}
return null;
},
}
and I'm tryin to call Toasted from within this package but I'm getting vue_toasted__WEBPACK_IMPORTED_MODULE_2___default.a.show is not a function. Any idea how I can use this Toasted inside of my own defined package?
The vue-toasted plugin must be registered with Vue first:
import Toasted from 'vue-toasted';
Vue.use(Toasted); // <-- register plugin
Then, your module could use it via Vue.toasted.show(...):
// TreatErrors.js
export default {
treatDefaultError(err) {
Vue.toasted.show(err.message);
}
}
And your Vue components could also use it via this.$toasted.show(...):
// Foo.vue
export default {
methods: {
showError(err) {
this.$toasted.show(err.message);
}
}
}

vue bestpractice api handling

Been reading the docs and googling around for best practice to handle api calls in bigger projects without luck (or ateast not what Im searching for).
I want to create a service / facade for the backend that I can load in every component that needs it. For exampel.
I want to fetch historical data for weather in a service so in every component I need this I can just load the weather-serivce and use a getter to fetch the wanted data. I would like to end up with something like below. But I dosent get it to work. So I wonder, what is best practice for this in vue.js?
import WeatherFacade from './data/WeatherFacade.vue'
export default {
name: 'Chart',
created () {
console.log(WeatherFacade.getWeather())
},
components: {
WeatherFacade
}
}
ps. using vue 2.1.10
It could be easily done by creating some external object that will hold those data and module bundling.What I usually do in my projects is that I create services directory and group them in order I want.
Let's break it down - services/WeatherFascade.js (using VueResource)
import Vue from 'vue'
export default {
getWeather() {
return Vue.http.get('api/weather')
}
}
If you have to pass some dynamic data such as ID, pass it as just parameter
import Vue from 'vue'
export default {
getWeather(id) {
return Vue.http.get(`api/weather/${id}`)
}
}
Then in your component you can import this service, pass parameters (if you have them) and got data back.
import WeatherFascade from '../services/WeatherFascade'
export default {
data() {
return {
weatherItems: []
}
},
created() {
this.getWeatherData()
},
methods: {
getWeatherData() {
WeatherFascade.getWather(// you can pass params here)
.then(response => this.weatherItems = response.data)
.catch(error => console.log(error))
}
}
}
You can use any library for that you like, for instance axios is cool.