Vue Vite + CosmicJS - vue.js

I'm building a blog SPA website as a hobby, and trying to figure out what would be the best way to get the smallest latency to acquire posts from a database.
So far I've tried Wordpress, but its API at least at the very first initial request every time, with API cache enabled, takes about a second (800-1100ms) to load only a handful posts - 6 to be precise each with a picture and about 2-300 words, and this is only for testing.
So I'm looking around for other possible solutions to make the request faster but stay free of charge and came across Cosmic JS.
I installed the cosmicjs module, but getting all sorts of errors as I try to initiate the requests, based on their documentation which looks like the following:
<script>
const Cosmic = require('cosmicjs')
const api = Cosmic()
// Set these values, found in Bucket > Settings after logging in at https://app.cosmicjs.com/login
const bucket = api.bucket({
slug: "YOUR_BUCKET_SLUG",
read_key: "YOUR_BUCKET_READ_KEY"
})
</script>
First, you can't use require in Vite, so I've changed
this
const Cosmic = require('cosmicjs')
to this
import Cosmic from "cosmicjs"
But I'm still getting error:
ReferenceError: process is not defined
at node_modules/cosmicjs/dist/helpers/constants.js (cosmicjs.js?v=2a84de6d:1367:19)
at __require2 (chunk-NKHIPFFU.js?v=2a84de6d:15:50)
at node_modules/cosmicjs/dist/main.js (cosmicjs.js?v=2a84de6d:1387:21)
at __require2 (chunk-NKHIPFFU.js?v=2a84de6d:15:50)
at node_modules/cosmicjs/dist/index.js (cosmicjs.js?v=2a84de6d:3359:23)
at __require2 (chunk-NKHIPFFU.js?v=2a84de6d:15:50)
at cosmicjs.js?v=2a84de6d:3371:16ű
Can't figure out what to do next to even make this work, currently my code looks like this for the cosmic request part:
import Cosmic from "cosmicjs"
const api = Cosmic();
const bucket = api.bucket({
slug: "NOT-GOING-TO-SHOW-SORRY-AND-THX",
read_key: "NOT-GOING-TO-SHOW-SORRY-AND-THX",
});
const data = await bucket.objects
.find({
type: "posts", // Object Type slug
})
.props("title,slug,metadata") // response properties
.limit(10); // number of Objects to be returned
console.log(data)
Any idea might be a good help, thanks in advance

Figured it out:
So anyone trying to use in Vite ANY node module that has process as a function in any part in that module, should do the following
In your vite.config.ts or vite.config.js add the following
export default defineConfig({
// ...
define: {
'process.env': process.env
}
})
And instead of require
const Cosmic = require('cosmicjs')
always use import
import Cosmic from "cosmicjs"
Besides that, everything works the same as in other API requests, so eg. in my case, I'm API requesting posts from my cosmic js Bucket
<script setup lang="ts">
import { ref } from "vue";
import { onMounted } from "vue";
import moment from "moment";
import Cosmic from "cosmicjs"
const api = Cosmic();
const posts = ref([] as any);
const isLoading = ref(false);
const bucket = api.bucket({
slug: "c27229f0-9018-11ed-b853-65fa50acc7e7",
read_key: "G71yMNlvizQCtrvVyp9Z1AecQp8a4RTr5dl9uEGi6nst9FNQIW",
});
async function fetchData() {
isLoading.value = true
const data = await bucket.objects.find({
type: 'posts'
}).props('slug,title,content,metadata') // Limit the API response data by props
posts.value = data.objects
isLoading.value = false
console.log(posts)
}
onMounted(async () => {
fetchData();
});
</script>
and the iterate through them in my template
<template>
<ul v-if="!isLoading" class="blog-posts-ul" v-for="post in posts" :key="post.slug">
<div class="posts-card">
<a
><router-link
:to="/blog/ + post.slug"
key="post.id"
class="posts-permalink"
>
</router-link
></a>
<img
v-if="post.metadata.image != null"
class="post.metadata.hero"
:src="post.metadata.image.imgix_url"
:alt="post.title"
/>
<img v-else src="#/assets/logos/favicon-big.png" />
<div class="posts-date">
<p>
{{ moment(post.date).fromNow() + " " + "ago" }}
</p>
</div>
<div class="posts-text">
<h1 class="posts-title">{{ post.title }}</h1>
<p v-html="post.excerpt" class="posts-excerpt"></p>
</div>
</div>
</ul>
</template>
As a last sidenote, this works flawlessly compared to Wordpress API requests, I was using Wordpress for my backend CMS and even with API cache plugin enabled, it took around 800-1100ms to load the posts, now that time shrank to about 30ms for the API request of the text based data, and extra 30-40ms for each image (thumbnails for the posts).
As a request from a commenter, I'm including some description as to why Vite needs the change of vite.config.js
Basically, other vue instances do include by default the process.env in their configurations, Vite doesn't cause its generally a slower method for acquiring data - based on what I read, haven't tested this performance difference myself.
Vite is used instead of simply using Vue CLI, because its supposed to be even a bit faster then Vue CLI.
But, this is a small change.
"Why it is needed at all? I don't even see in your code above
calling any function named process"
Because it is this cosmicjs that is using it. I'm using a service that has a free tier, named Cosmic, and in their documentation it's advised to use their own node_module named comsicjs to fetch your data (posts, thumbnails etc etc).
This node module is what is using process, so when you are
importing it as I am as you can see above in my code:
import Cosmic from "cosmicjs"
And this is why you need to change your Vite's configuration file, so it can use process.
TLDR:
If you get an error in your Vite project with "process is not defined", that could be because a module you installed is using process, so simply add the following code to your vite.config.ts or vite.config.js
export default defineConfig({
....,
define: {
'process.env': process.env
}
})

Related

Cannot find module './undefined' when updating a template ref in Vue

Objective
Render components based on a dynamically changing reference (ref). User can perform a "search" feature that returns data and updates the reference. The updated reference should then in turn update the components which are rendered using v-for.
My Setup
I have an onMounted() lifecycle hook that makes an axios request and returns all the data into a reference.
onMounted(async () => {
const response = await axios.get('/api/subject/')
allSubjects.value = await response.data.data;
})
The reference:
const allSubjects = ref(null)
The template:
<OneSubject
v-for="subject in allSubjects"
:key="subject.id"
:subject="subject"
/>
Everything works fine so far...
Problem
When I make another request for my "search" feature, the axios request works fine and I am able to get a response with data (a new array of objects).
The problem occurs when trying to update my reference with this data like so:
async function search(searchInput) {
const response = await axios.get(`/api/subject/search/${searchInput}`)
console.log(response) // <-- RESPONSE HAS DATA
allSubjects.value = await response.data.data; // <-- CAUSES ERROR
}
The error that is thrown comes from the component that is rendered in the v-for:
I can verify that the reference was successfully updated with new data, but the problem seems to arise when rendering the component in the v-for?
Update
Here is the component that it is trying to render in the v-for.
<template>
<div class="subject-wrapper" v-bind:style="{ background: `rgba(0, 0, 0, .3) url('${imgSrc}') center / cover` }">
<div class="darken-bg"></div>
<div class="subject-name">{{ subject.name }}</div>
</div>
</template>
<script setup>
import { onMounted, computed } from 'vue'
const props = defineProps({ subject: Object })
const imgSrc = computed(() => require(`#/assets/images/subject/${props.subject.image}`))
</script>
I updated my component as follows:
<template>
<div class="subject-wrapper" v-bind:style="{ background: `rgba(0, 0, 0, .3) url('${imgSrc}') center / cover` }">
<div class="darken-bg"></div>
<div class="subject-name">{{ subject.name }}</div>
</div>
</template>
<script setup>
import { reactive, computed, onUpdated } from 'vue'
const props = defineProps({ subject: Object })
const subject = reactive(props.subject)
const imgSrc = computed(() => require(`#/assets/images/subject/${subject.image}`))
onUpdated(() => {
console.log('component subject prop:', props.subject)
console.log('subject reactive:', subject)
})
</script>
Afterwards, the search executes without the error, but it renders the wrong component. I've console logged the values:
Solution
I found out that the error was actually coming from my server response. As you can see in the image above, the prop only has 2 values. When I am rendering the component, I require an image property that is non existent, thus throwing the error.
I updated my endpoint to return all the properties in the document and it now works.
Thanks yoduh for the suggestions that helped me get to the bottom if this!
I can't explain the console.logs at the bottom of your question, but based on the child component code I believe there are two changes needed to fix the overall issue.
require shouldn't ever be used in code when using Vite since require is ESM only. The Vite documentation describes a better way to import dynamic assets using new URL with import.meta.url. One caveat is that you can't use # alias when constructing the URL due to Rollup limitations (Vite's bundler). Your imgSrc then should look like this:
const imgSrc = computed(
() => new URL(`../assets/images/subject/${subject.value.image}`, import.meta.url).href
);
(actual relative path might be different on your local machine)
Change from using reactive to toRef when creating subject. Since props is reactive and you want to pull out the individual (non-reactive) property, you would use toRef for the job specifically because it keeps subject and props.subject synced together.
const subject = toRef(props, 'subject');
I don't have your exact same setup but when I tested it locally with some mock data/local image files instead of an API it worked for me. If you still have problems let me know.

How to use Vue in an existing typescript application without modules?

I'm trying to use Vue in "progressive enhancement" mode in an existing site, which already uses Typescript extensively.
The vue tutorial shows code like this:
<script type="module">
import { createApp, reactive, ref } from 'vue'
createApp({
setup() {
const counter = reactive({ count: 0 })
const message = ref('Hello World!')
return {
counter,
message
}
}
}).mount('#app')
</script>
<div id="app">
<h1>{{ message }}</h1>
<p>Count is: {{ counter.count }}</p>
</div>
But I don't want to have javascript in the html file, I want to relocate that code to a proper typescript file. But when I do this, I get compilation errors no matter how I try to arrange this.
If I don't qualify anything, I get errors like Error TS2304 (TS) Cannot find name 'createApp'. This makes sense because the whole point of Typescript is to type check and not let you reference unknown things.
So I try to import Vue like this:
import { Vue } from '../../node_modules/vue/dist/vue.global.js';
and change the call to Vue.createApp(...). This causes the browser error Cannot use import statement outside a module
If I try instead:
let { Vue } = require('../../node_modules/vue/dist/vue.global.js')
I get require is not defined.
I tried getting the vue.d.ts typings from https://github.com/vuejs/vue/blob/dev/types/vue.d.ts and referencing the file with /// <reference path="../../Scripts/vue.d.ts" />" />, but this just doesn't work, giving Error TS2304 (TS) Cannot find name 'Vue'.
I have tried many, many permutations of this but all of them run into one error or another.
So the question is: how can I retrofit Vue into an existing Typescript app without modules?

VueJs Async Api Data best practice

I am building a headless SPA SSR in NuxtJs, and I am wondering what best practices is for making sure that the application only loads if connection has been established to the remote API.
This is currently my index.vue component:
<template>
<div class="wrapper">
<div class="content">
<div class="title">
<h1>{{site.meta.title}}</h1>
</div>
</div>
</div>
</template>
<script>
import Meta from '../classes/api/General/Meta'
export default {
data () {
return {
meta: null
}
},
created () {
Meta.getMeta().then((response) => {
this.meta = response.data
})
}
}
</script>
This sometimes resolves in that site.meta.title is undefined because the site is loading before the api data has been initialised. And yes, site.meta.title is defined under the hood in the api. So. Next step I was thinking was using async like following script:
<script>
import Meta from '../classes/api/General/Meta'
export default {
data () {
return {
meta: null
}
},
async created () {
await Meta.getMeta().then((response) => {
this.meta = response.data
console.log(response.data.site.logo)
})
}
}
</script>
Though this doesn't help anyway.
But with v-if="meta" it does help. Though: now it seems that Axios is not rendering the content in the code (ssr) anymore.
console.log is not something that you can really trust 100% of the time for async debugging tbh.
console.log(JSON.parse(JSON.stringify())) can help a bit more but it's still have some drawbacks sometimes.
As for best practices, both beforeCreate and created do run on both sides (server + client) so it's fine to use those. You can also use asyncData and the new fetch (it's a Nuxt hook, not the actual fetch API).
Beware of using the async/await syntax properly tho (no need for then here):
async created() {
const response = await Meta.getMeta()
this.meta = response.data
console.log(this.meta)
}
Also, with proper async/await, this one will never happen actually
because the site is loading before the api data has been initialised
You can read a bit more about the Nuxt lifecycle here: https://nuxtjs.org/docs/2.x/concepts/nuxt-lifecycle
I'd recommend usually going the async fetch() way (non blocking, nice helpers), or async asyncData() if you need a blocking method. Middlewares can also be useful to make the whole process more flexible around your application.
You can get the whole explanation between the differences of fetch vs asyncData here: https://nuxtjs.org/blog/understanding-how-fetch-works-in-nuxt-2-12/
And if you want to have an example on how to use fetch in a real world scenario, you can read this one: https://nuxtjs.org/blog/build-dev-to-clone-with-nuxt-new-fetch/
So, It turns out that I got this the completely wrong way around.
In the newest nuxt versions, async fetch method is now included (build in).
With this, all rendering etc works fine and as expected.
My ended working code looks like this now:
<script>
export default {
async fetch () {
this.meta = await fetch(
'https://testurl.com/meta'
).then(res => res.json())
},
data () {
return {
meta: null
}
}
}
</script>
And the beauty with fetch, is that you can add listeners" like so:
<p v-if="$fetchState.pending">Fetching site</p>
<p v-else-if="$fetchState.error">Error happened</p>
<p>This content will be rendered server side even though waiting time</p>
I'm just posting this because my original question was a bit miss explained, and hope to help somebody else.
Edit:
I have marked kissu as answer (did see the post after i created this one), because it was explained so nice and well done!
Thanks :-)

VueJS get AJAX data before routing / mounting componenents

I want to initialize some base data once before having VueJS does any routing / mounting.
Now I found out about the Global Navigation Guard router.beforeEach. But It triggers not only on the initial load (page load), but every route that is triggered. Now I could put in some sort of if-statement to have the code run only once, but that's not my preferred way of solving this:
// pseudo:
router.beforeEach((to, from, next) => {
if (state.initialized === false) {
await store.dispatch(init)
}
// the rest of my routing guard logic....
})
I'd prefer not having the if-statement run everytime (knowing it's only going to be true once, and false forever after).
Is there an official way to have (ajax) code run only once, AFTER vue is initialized (so I have access to vuex state, etc.), BEFORE any routing has started.
You can easily perform an asynchronous task before mounting your root Vue instance.
For example
// main.js
import Vue from 'vue'
import store from 'path/to/your/store'
import router from 'path/to/your/router'
import App from './App.vue'
store.dispatch('init').then(() => {
new Vue({
store,
router,
render: h => h(App)
}).$mount('#app')
})
It would be a good idea to show something in index.html while that's loading, eg
<div id="app">
<!-- just an example, this will be replaced when Vue mounts -->
<p>Loading...</p>
</div>

How to define a subscription based on route params?

I'm using RxJS using vue-rx and I have a route that needs to fetch a slightly different AJAX API request depending on the params.
I've been playing with all kinds of different approaches, but this is the one that +should have+ worked.
import Vue from 'vue'
import numeral from 'numeral'
import { Observable } from 'rxjs/Observable'
import 'rxjs/add/observable/interval'
import 'rxjs/add/operator/switchMap'
import 'rxjs/add/operator/map'
import axiosObservable from '../lib/axiosObservable'
export default {
name: 'Exchange',
props: ['exchange_name'],
methods: {
exchangeFetch (exchangeName) {
return Observable
.interval(3000)
.switchMap(axiosObservable.get(Vue.config.infoCoinUrl + '/exchanges/' + exchangeName + '/market-pairs'))
.map((response) => response.data)
}
},
mounted () {
alert(exchangeName)
this.$subscribeTo(
this.exchangeFetch(this.exchange_name),
(data) => {
this.market_pairs = data
})
},
data () {
return {
market_pairs: []
}
},
But what happens is that the alert gets executed only once during browsing (and the wrong AJAX call gets ran every time).
I'm a bit noobish in all of this (Vue & JS), I'm suspecting this might be a bug in the vue-rx framework - or at least a surprising behavior (for noobie me).
The thing that I love about vue-rx is how it integrates into vue.js lifecycles and removes the danger of leaking observables (which coupled with an AJAX call accounts to a ddos attack).
I'm looking for a solution that uses vue-rx API , or at least doesn't require me to stop the observables "manually".
UPDATE 1 seems like the issue has nothing to do with vue-rx, it's the mounted block that doesn't get executed on props change....
The exchanges are loaded like this and it generally works, there should be nothing wrong with this...
<router-link v-for="exchange in exchanges" v-bind:key="exchange.name" class="navbar-item" :to="{ name: 'exchange-show', params: { exchange_name: exchange.name }}">{{ exchange.name }}</router-link>