Vue.js src searching localhost instead of file system - vue.js

I have an image whose src I'm trying to change when it is clicked.
<b-img :id="favorite" src="~/static/svg/favorite.svg" #click="iconClicked(favorite)" right />
And down under export default, I have
methods: {
iconClicked(name: any) {
(<HTMLImageElement> document.getElementById(name))!.src="~/static/svg/favoriteAlternate.svg";
}
}
When I run my code at localhost:3000, I get a 404 error, and the code appears to be searching localhost:3000/~/static/svg/favoriteAlternate.svg instead of my local file system.
Why might this be? What should I do to fix this?
Thanks!

There's a couple of things wrong here.
First, you shouldn't be manually interacting with the DOM like that – Vue "owns" the DOM and you should leave it to Vue to change.
When Vue compiles the template, it treats the src attribute specially by loading the file on disk it refers to through webpack. But then when you manually change the element's src attribute like that, the new file it references was never bundled by webpack so it won't load.
You need to require/import both images so they get bundled. Then swap between them using a binding on the src attribute.
Something like:
<img :src="src">
// These are not javascript files, however webpack will bundle them
// and export the correct src you should use to refer to them
import FavoriteImage from '~/static/svg/favorite.svg'
import FavoriteAltImage from '~/static/svg/favoriteAlternate.svg'
export default {
data() {
return {
src: FavoriteImage,
}
},
methods: {
iconClicked() {
this.src = FavoriteAltImage
}
}
}

Related

Nuxt avoid import of client-side script for server-side rendering

In my nuxt.js application, I have a script that imports an NPM package which is only compatible with browser contexts (it references document, location, window, etc.)
Is there a way to exclude this from SSR?
import thing from "#vendor/thing"; // causes `document not defined` error
export default showThing(){
if (process.client) {
thing();
}
}
I can use the method with process.client but this file is still imported in my components.
You could import it dynamically rather than in every context.
As explained in my answer here: https://stackoverflow.com/a/67825061/8816585
In your example, that would be something like this
export default showThing(){
if (process.client) {
const thing = await import('#vendor/thing')
thing()
}
}

Dynamically load .mp3s in Vue.js+Nuxt.js on mounted()

I'm tying to follow the official nuxt.js documentation to dynamically load dozens of .mp3 files as the user progresses through a quiz.I've set up my config file as follows:
nuxt.config.js (Nuxt v2.15.8):
build: {
extend ( config, ctx ) {
// Might be used for loading media files
config.module.rules.push({
test: /\.(ogg|mp3|wav|mpe?g)$/i,
loader: 'file-loader',
options: {
name: '[path][name].[ext]'
}
});
}
}
This works:
import audioFile from '~/assets/audio/sfx_Q1_1.mp3';
// Outputs /_nuxt/assets/audio/sfx_Q1_1.mp3, and the file plays as expected
console.log(audioFile);
Using an import statement works, and it gives me the correct asset path. The problem is that I need dozens of mp3s with sfx_Q#_#.mp3 format, so I need a more dynamic approach.
This doesn't work:
mounted() {
let audioURL = `~/assets/audio/sfx_Q${this.qID}_${this.aID}.mp3`;
let audio = require(audioURL);
console.log(audio);
}
With this approach, my app crashes and I get Cannot find module '~/assets/audio/sfx_Q1_1.mp3', even though it's exactly the same path as the version that worked with the import statement.
This also works
<audio v-bind:src="require(`~/assets/audio/sfx_Q${qID}_${aID}.mp3`).default"></audio>
...but this doesn't work
<audio v-bind:src="require(getAudioURL()).default"></audio>
// ... //
getAudioURL() {
return `~/assets/audio/sfx_Q${this.qID}_${this.aID}.mp3`;
}
... I get the same Cannot find module error. Why does the import break when the file path is generated dynamically inside a method? I've tried with # symbol as well, to no avail. This question had the same issue, but I've tried exactly the solutions suggested, and it doesn't help.

Vue static assets are not accessible to a library

I am using a single file Vue component and import a face-api library. I want to use a function from that library, loadSsdMobilenetv1Model(url), which takes URL of folder, where the necessary files are located and loads them. The function however cannot fetch the files if I use #/assets/weights as url (# in Vue represents the src folder). I would like to be able to host the assets for. I'm able to read files from the assets folder folder with require('#/assets/file.json), but the library seems to need a static url.
What is the best solution in my situation? Maybe I'm missing some understanding.
Can I make it so that the assets folder is served and accessible?
Here's my component and the comments show some things I've tried:
<template>
<div>...stuff...</div>
</template>
<script>
import * as faceapi from 'face-api.js';
async function load() {
// example below: If I serve the files on a separate port with CORS allowed, the function loads files fine.
// const MODEL_URL = 'http://127.0.0.1:8081/weights/';
// example below: this does not work, but I would like this to work!
const MODEL_URL = '#/assets/weights';
// example below: also doesn't work, conscious of relative paths
// const MODEL_URL = '../assets/weights';
// example below: a file loads, but I can't just this unfortunately
// return require('#/assets/file.json')
return await faceapi.loadSsdMobilenetv1Model(MODEL_URL);
}
export default {
mounted() {
var promise = load();
promise.then((model) => {
this.model = model
}, (reject) => {
console.log(reject)
// alert(reject);
})
},
name: "Home",
data() {
return {
model: null
}
}
};
</script>
I'm not sure if it's relevant, but I set up the project with
vue create
and run the dev environment with
nmp run serve

Import Vue components from folder programmatically not hard coded (I'm actually using Nuxt but I'll call it Vue)

I have a number of SVG cards as components in Vue; I probably have 50 or more. I could import them one by one just after the script tag:
<script>
import MyVueComponent1 from "~/components/MyVueComponent1.vue";
import MyVueComponent2 from "~/components/MyVueComponent2.vue";
import MyVueComponent3 from "~/components/MyVueComponent3.vue";
...
import MyVueComponent50 from "~/components/MyVueComponent50.vue";
But I've been reading that I can do this programmatically. I just haven't found any one example that makes it crystal clear to me. I able to register the components dynamically but I'm not certain how to import an entire folder of components.
I was able to register the components dynamically using this code in the created hook:
created() {
const requireComponent = require.context(
// Look for files in the current directory
"../components",
// Do not look in subdirectories
false,
// Only include "S" prefixed .vue files
/S[\w-]+\.vue$/
);
// For each matching file name...
requireComponent.keys().forEach((fileName) => {
// Get the component config
const componentConfig = requireComponent(fileName);
// Get the PascalCase version of the component name
fileName = fileName.replace("./", "");
fileName = fileName.replace(".vue", "");
const componentName = fileName;
this.generatedComponentList.push(componentName);
// Globally register the component
Vue.component(componentName, componentConfig.default || componentConfig);
});
},
And I'm using the generatedComponentList of component names to display the cards:
<div
v-for="componentName in generatedComponentList"
:key="componentName"
>
<component :is="componentName" :id="componentName"></component>
</div>
But I'd love to get rid of all the import lines under the script tag and have cleaner and more dynamic code. That way if I add a new component card to the folder, it will simply be "picked up" and displayed without having to add the "import" line, or register the component etc. Hopefully I've clearly articulated what I'm looking to achieve.
Nuxt auto-imports the components you use from the ~/components directory, so you don't need to import or register them explicitly.
This feature is enabled in nuxt.config.js with the components config:
// nuxt.config.js
export default {
components: true
}
Thanks tony19 suggesting I look at the nuxt.config.js file, your answer definitely put me on the right track; also thanks to whoever suggested 1 might be the right answer.
Here's the solution that worked for me:
Based on tony19's suggestion I looked at my nuxt.config.js file; specifically the component section. I already had this line in my code to automatically import any components in my components folder:
components: true,
But the components I wanted to import were nested within another folder within the components folder.
After reading this 2 from the nuxt.org docs, I replaced my previous code with this:
//components: true,
components: [
// Equivalent to { path: '~/components' }
'~/components',
{ path: '~/components/myTargetComponents', extensions: ['vue'] }
],
Then, I was able to remove all of my import lines:
<script>
import MyVueComponent1 from "~/components/MyVueComponent1.vue";
import MyVueComponent2 from "~/components/MyVueComponent2.vue";
import MyVueComponent3 from "~/components/MyVueComponent3.vue";
...
import MyVueComponent50 from "~/components/MyVueComponent50.vue";
In my index.vue file I don't have anything listed in the components section anymore...just this as a reminder to myself:
,
components: {
//see nuxt.config.js file ...component section
},
Just to be clear, in my index.vue file, I don't import any components using this format, "import MyVueComponent1 from "~/components/MyVueComponent1.vue"; AND I don't have anything listed in the components section. Also just to clarify, the components I'm wanting to import ARE in a sub folder of the components folder (~/components/myTargetComponents). I realize now that I didn't clearly articulate that in my original post.
As a related piece of this...
As you can see from my original post, I'm using a block of code in the created hook to populate a list of the component names:
this.generatedComponentList.push(componentName);
And then using this list to iterate through the component cards:
<div
v-for="componentName in generatedComponentList"
:key="componentName"
>
<component :is="componentName" :id="componentName"></component>
</div>
But I'm wondering if there's a list of these components already generated by nuxt.config.js file. Any suggestions? And again, thanks everyone for the help, I greatly appreciate it.

Vue.js: is it possible to have a SFC factory?

I'm using single-file-components in a larger project and I'm new to Vue.JS. I'm looking for a way to dynamically create components, especially their templates on-the-fly at run-time.
My idea is to create a "component factory" also as a SFC and to parameterize it with props, e.g. one for the template, one for the data and so forth. I get how this works for already specified SFC and that I can simply exchange them with <component v-bind:is= ..., however the task here is different. I need to compose a template string, which potentially is composed of other component instance declarations, on-the-fly and inject it in another SFC. The code below doesn't work unfortunately.
<template>
<div>
<produced-component/>
</div>
</template>
<style></style>
<script>
import Vue from 'vue'
export default {
props: {
template: { type: String, default: '<div>no template prop provided</div>' }
},
components: {
'produced-component': Vue.extend(
{
template: '<div>my runtime template, this I want to be able to compose on-the-fly</div>'
}
)
}
}
</script>
It says:
The following answer: https://stackoverflow.com/a/44648296/4432432 answers the question partially but I can't figure out how to use this concept in the context of single-file-components.
Any help is greatly appreciated.
Edit 2020.03.16:
For future reference the final result of the SFC factory achieved as per the accepted answer looks like this:
<template>
<component:is="loader"/>
</template>
<script>
const compiler = require('vue-template-compiler')
import Vue from 'vue'
export default {
props: {
templateSpec: { type: String, default: '<div>no template provided</div>' }
},
computed: {
loader () {
let compSpec = {
...compiler.compileToFunctions(this.templateSpec)
}
return Vue.extend(compSpec)
}
}
}
</script>
You can surely have a Factory component with SFC; though what you are trying to do is a very rare case scenario and seems more like an anti/awkward pattern.
Whatever, you are doing currently is right. All you need to do is - use Full build of Vue which include the Vue Template Compiler, which will enable you to compile template at run-time (on the browser when app is running). But remember that for the components written in SFC, they must be compiled at build time by vue-loader or rollup equivalent plugin.
Instead of using vue.runtime.min.js or vue.runtime.js, use vue.min.js or vue.js.
Read Vue installation docs for more details.
Assuming you are using Webpack, by default main field of Vue's package.json file points to the runtime build. Thus you are getting this error. You must tell webpack to use full bundle. In the configuration resolve the Vue imports to the following file:
webpack.config.js
module.exports = {
// ... other config
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
}
};