webpack handlbars-loader: inlineRequires doesn't work with dynamic path - dynamic

I have a simple partial
<span class="icon-dim_{{name}}">
<svg class="icon">
<use xlink:href="#{{name}}"></use>
</svg>
<noscript>
<img src="../../assets/{{name}}.png" alt="">
</noscript>
</span>
But i get this error message.
ERROR in ./hbs/partials/icon.hbs
Module parse failed: Unterminated string constant (10:110)
You may need an appropriate loader to handle this file type.
If I put a static path inside my partial
<img src="../../assets/myIcon.png" alt="">
it works.
What I have todo, that I can use dynamic paths inside a handlebars file?

I got help at github 😎
https://github.com/pcardune/handlebars-loader/issues/132
use chainged loaders
{
test: /\.hbs$/,
use: [
{
loader: 'handlebars-loader'
},
{
loader: 'extract-loader'
},
{
loader: 'html-loader',
options: {
interpolate: true
}
}
]
}
and then require the asset as a parameter
{{> ./partials/partial asset='${require('./assets/1.jpg')}' }}

Related

how to deal with icons from assets in vue

I try to use images, named from JSON data, which are located in my assets in Vue with:
My JSON:
cards: [
{
title: 'my Title'
image: 'myimagename.png'
style: 'color: green'
},
title: 'another Title'
image: 'myotherimagename.png',
style: 'color: red'
}
]
and then I try in it with:
<q-card :style="card.style" class="dashboard-card text-white q-mt-md" v-for="card in cards"
:key="card.id" style="height: 132px; width: 132px">
<q-card-section dense>
<q-img
:src="require('../assets/icons/' + card.image)"
basic
/>
<div class="dashboard-card-title"> {{ card.title }}</div>
</q-card-section>
</q-card>
but I always get a 404, how can I use the images in my assets folder?
Error: Cannot find module './myimagename.png'
at webpackContextResolve (eval at ./src/assets/icons sync recursive ^\.\/.*$ (0.js:218), <anonymous>:27:11)
at webpackContext (eval at ./src/assets/icons sync recursive ^\.\/.*$ (0.js:218), <anonymous>:22:11)
I had to use the public folder like public/images and access them with
:src="'/images/' + card.image"
all works now like expected.
You cannot require an image. Depending on your bundle solution, you should be able to just do this
<q-img :src="'../assets/icons/' + card.image)" basic />

v-for delay dom patching when data update

I'm noticing that the v-for I'm using to render some images inside a component, when data are updated using the event bus,I will have a little delay in DOM content replacing. Is there a way to solve this little problem?
NB: I can't replicate the data passed because it's a 50 elements list of images urls and are provided by an external api service.
<template>
<div class="row m-0 pl-5 pr-5">
<div class="col-4 col-img hide p-0" v-for="(img, idx) in feed" :key="idx" v-observe-visibility="visibilityChanged">
<img class="img-fluid w-100 h-100 ig-image" :src="img.url">
<div class="card-img-overlay text-center">
<p class="text-white">{{ img.likes }} <i class="fas fa-heart"></i> {{ img.comments }} <i class="fas fa-comment"></i></p>
</div>
<a class="stretched-link" href="#image-modal" data-toggle="modal" v-on:click.prevent="zoomImage(img.url)"></a>
</div>
</div>
</template>
<script>
import { EventBus } from '#/standalone/event-bus'
export default {
name: 'UserMedia',
data() {
return {
feed: null,
imgUrl: null
}
},
mounted() {
EventBus.$on('profile-media', (media) => {
this.$set(this, 'feed', media)
})
},
methods: {
zoomImage(url) {
this.$set(this, 'imgUrl', url)
},
visibilityChanged(isVisible, el) {
if(isVisible){
el.target.classList.remove('hide')
el.target.classList.add('show')
}
}
}
}
</script>
<style scoped>
.col-img {
height: 420px;
}
</style>
Since if you change the data 'feed', it will take time to load the images but inorder to load small size images prior to heavy size for good user experience you can use some very good npm packages:
npm i vue-lazyload (I have used it and recommend this one)
npm i v-lazy-image (I haven't used yet but you can explore this as well)

Nuxt - The template root requires exactly one element

I recently updated a few dependencies in a Nuxt based project I had a developer work on for me (I'm a designer with a very basic JS/vue knowledge-base). Now the build is spitting out the 'template root requires exactly one element' error. From searching other threads I can see the principle of what I need to change (contain everything in one element) but I'm just unsure how to do that with this files particular structure (v-if arrangement). I've included the offending file below and wondered if anyone could point me in the right direction? Much appreciated!
<template>
<nuxt-link
v-if="to"
:class="classes"
:to="to"
v-bind="inheritedProps"
v-on="$listeners"
>
<slot />
</nuxt-link>
<a
v-else-if="href"
:class="classes"
:href="href"
v-bind="inheritedProps"
v-on="$listeners"
>
<slot />
</a>
<button
v-else
:class="classes"
:type="type"
v-bind="inheritedProps"
v-on="$listeners"
>
<slot />
</button>
</template>
<script>
export default {
name: 'BaseButton',
props: {
block: {
type: Boolean,
default: false
},
variant: {
type: String,
default: () => {}
},
href: {
type: String,
default: () => {}
},
to: {
type: String,
default: () => {}
},
type: {
type: String,
default: () => {}
}
},
computed: {
inheritedProps () {
return {
...this.$props,
...this.$attrs
}
},
classes () {
return [
'btn',
{
'btn--block': this.block
}
].concat(this.modifiers)
},
modifiers () {
const modifiersArray = this.variant && this.variant.split(' ')
return this.variant ? modifiersArray.map(modifier => `btn--${modifier}`) : false
}
}
}
</script>
I'm a little surprised that you're seeing this error as Vue doesn't normally complain if you're using v-if/v-else-if/v-else like that. The template is guaranteed to output a single element when it runs, so usually Vue allows it. It may shed more light on what's going on if you include the exact error message in the question.
I suggest checking you aren't running into the problem discussed below, caused by incompatible library versions, which incorrectly reports this error:
https://github.com/vuejs/eslint-plugin-vue/issues/986
I really don't think there's anything wrong with your code, so I suggest investigating library versions before making any code changes.
Further, if it is just the linter that's complaining you could consider suppressing that rule. The Vue template compiler will shout soon enough if there's a real problem with multiple root nodes.
That said, if you really can't make the error message go away...
The simplest solution is just to wrap everything in an extra element at the root.
If you don't want to use a wrapper element (possibly because it interferes with your layout) you can use is to reduce down your template:
<template>
<component
:class="classes"
v-bind="childProps"
v-on="$listeners"
>
<slot />
</component>
</template>
<script>
export default {
// ... other stuff ...
computed: {
childProps () {
const childProps = {...this.inheritedProps}
if (this.to) {
childProps.is = 'nuxt-link'
childProps.to = this.to
} else if (this.href) {
childProps.is = 'a'
childProps.href = this.href
} else {
childProps.is = 'button'
childProps.type = this.type
}
return childProps
}
}
}
</script>
That said, you're almost in render function territory doing it this way.
you should wrap your all content under template into on root tag,just for example i have used div to wrap all html content under template. you can use any other tag based on your requirement.
you can use below solution
<template>
<div>
<nuxt-link
v-if="to"
:class="classes"
:to="to"
v-bind="inheritedProps"
v-on="$listeners"
>
<slot />
</nuxt-link>
<a
v-else-if="href"
:class="classes"
:href="href"
v-bind="inheritedProps"
v-on="$listeners"
>
<slot />
</a>
<button
v-else
:class="classes"
:type="type"
v-bind="inheritedProps"
v-on="$listeners"
>
<slot />
</button>
</div>
</template>

Laravel 6.4.1 + webpack + vue + scss dynamically loaded

I have laravel 6.4.1 with minor changes to the default webpack config.
I'm using vue components with scoped styling as well.
When I run npm run dev, everything works as it should. My Vue component is loaded and has styling.
When I run npm run production, my Vue component is not loaded.
Or well... The JS file is loaded, but the component never fires created or mounted and is not visible on screen and not visible in the DOM.
How do I know it's loaded then?
When I put console.log('test') above (or below) the export default it is displayed in the console.
When I remove the <style scoped lang="scss"> tag completely, my component is visible on screen as well.
I've already tried deleting parts of the styling, but it never works. Even an empty style tag will not render the component. It will only work when I fully remove it.
Ofcourse, I want to keep my styling in the component, so how can I fix this problem?
I've removed some JS from the Vue component to make it more readable and since I strongly suspect the issue in not in the JS I don't think it has any value for this issue.
webpack.mix.js
const mix = require('laravel-mix');
/*
|--------------------------------------------------------------------------
| Mix Asset Management
|--------------------------------------------------------------------------
|
| Mix provides a clean, fluent API for defining some Webpack build steps
| for your Laravel application. By default, we are compiling the Sass
| file for the application as well as bundling up all the JS files.
|
*/
mix
.js('resources/js/app.js', 'public/js')
.sass('resources/sass/app.scss', 'public/css');
if (mix.inProduction()) {
mix.version();
} else {
mix.sourceMaps();
}
// webpack.mix.js
const path = require('path'),
WebpackShellPlugin = require('webpack-shell-plugin'),
BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin,
{CleanWebpackPlugin} = require('clean-webpack-plugin');
mix.webpackConfig({
plugins: [
new WebpackShellPlugin({
onBuildStart: [
'php artisan js-localization:export --quiet',
'php artisan ziggy:generate resources/js/ziggy-routes.js --quiet'
]
}),
new BundleAnalyzerPlugin({
analyzerMode: mix.inProduction() ? 'disabled' : 'server',
openAnalyzer: false
}),
new CleanWebpackPlugin({
dry: false,
cleanStaleWebpackAssets: true,
cleanOnceBeforeBuildPatterns: [],
cleanAfterEveryBuildPatterns: [
'js/chunk/*'
],
dangerouslyAllowCleanPatternsOutsideProject: true,
}),
],
resolve: {
alias: {
ziggy: path.resolve('vendor/tightenco/ziggy/dist/js/route.js'),
},
},
output: {
publicPath: '/',
chunkFilename: 'js/chunk/[name].[chunkhash].js',
},
});
Vue component
<template>
<div class="position-relative my-2" :style="{backgroundColor: properties.settings.backgroundColor}">
<block-template-content-text
v-if="canDisplayBlock(block, 'content', 'text')"
:text-config="tinyMce.text"
:block="block"
#block-change="storeBlockChange">
</block-template-content-text>
<block-template-content-text-image
v-if="canDisplayBlock(block, 'content', 'text-image')"
:text-config="tinyMce.text"
:image-config="tinyMce.image"
:block="block"
#block-change="storeBlockChange">
</block-template-content-text-image>
<div class="d-flex align-items-center justify-content-center flex-column position-absolute controls">
<i class="fas fa-2x fa-fw fa-chevron-up cursor-pointer" #click="$emit('sort-item', 'up', block)"></i>
<div>
<i class="fas fa-1x fa-fw fa-plus-circle cursor-pointer" #click="$emit('add-new-block', block)"></i>
<!-- delete section start -->
<i class="fas fa-1x fa-fw fa-trash cursor-pointer"
:id="'delete-' + block.hash"
#click="$emit('delete-block', $event, block)"></i>
<b-tooltip :target="'delete-' + block.hash"
:id="'tooltip-' + block.hash"
triggers="focus">
<button type="button"
class="btn btn-primary"
#click="$emit('cancel-delete-block', block)">
{{ Lang.get('general.buttons.cancel') }}
</button>
<button type="button"
class="btn btn-danger"
#click="$emit('delete-block', $event, block)">
<i class="fas fa-fw fa-trash"></i>
{{ Lang.get('general.buttons.delete')}}
</button>
</b-tooltip>
<!-- delete section end -->
<!-- popover start -->
<div class="popover-content" :hidden="!showPopover">
<i class="fas fa-fw fa-times close-popover cursor-pointer" #click="closePopover"></i>
<div class="form-group">
<label for="background-color">
{{ Lang.get('project.design.pages.block.settings.background-color') }}
</label>
<input type="color"
name="background-color"
id="background-color"
class="form-control"
v-model="properties.settings.backgroundColor"
value="#ffffff"
#input="setSetting('backgroundColor', $event)">
</div>
</div>
<i class="fas fa-1x fa-fw fa-cog cursor-pointer"
#click="togglePopover"></i>
<!-- popover end -->
</div>
<i class="fas fa-2x fa-fw fa-chevron-down sort-order cursor-pointer"
#click="$emit('sort-item', 'down', block)"></i>
</div>
</div>
</template>
<script>
// Bootstrap Vue
import {TooltipPlugin} from 'bootstrap-vue';
Vue.use(TooltipPlugin);
// TinyMCE editor
import 'tinymce/tinymce.min';
import 'tinymce/themes/silver/theme.min';
import 'tinymce/plugins/paste';
import 'tinymce/plugins/link';
import 'tinymce/plugins/imagetools';
import {EventBus} from "../../vue/EventBus";
export default {
name: "display-block",
components: {
'block-template-content-text': () => import('./blocks/content/Text'),
'block-template-content-text-image': () => import('./blocks/content/TextImage'),
},
props: {
block: {
required: true,
type: Object
}
},
data() {
[...]
},
methods: {
[...]
},
mounted() {
[...]
}
}
</script>
<style scoped lang="scss">
#import "../../../sass/variables";
#import "~bootstrap/scss/mixins";
#import "~bootstrap/scss/bootstrap-grid";
#import "~bootstrap/scss/utilities/position";
#import "~bootstrap/scss/popover";
// Bootstrap Vue
#import '~bootstrap-vue/src/index.scss';
.block {
.block- {
&text {
#import '~tinymce/skins/ui/oxide/skin.min.css';
#import '~tinymce/skins/ui/oxide/content.min.css';
#import '~tinymce/skins/content/default/content.min.css';
}
}
}
.controls {
#extend .position-relative;
top: map_get($sizes, 50);
right: - map_get($spacers, 5);
transform: translateY(-50%);
&:first-child {
#extend .d-none;
}
.popover-content {
#extend .popover;
#extend .p-2;
min-width: 150px;
min-height: 150px;
.close-popover {
#extend .position-absolute;
#extend .mt-2;
#extend .mr-2;
top: 0;
right: 0;
}
input {
&[type=color] {
#extend .p-0;
width: 25px;
height: 25px;
border: none;
}
}
}
}
</style>
page.js
import {EventBus} from "../../vue/EventBus";
import {TooltipPlugin} from 'bootstrap-vue';
// import DisplayBlock from "../../components/project/DisplayBlock";
if (document.getElementById('page-editor')) {
// Bootstrap Vue
Vue.use(TooltipPlugin);
new Vue({
el: '#page-editor',
components: {
BlockModal: () => import('../../components/project/BlockModal'),
DisplayBlock: () => import('../../components/project/DisplayBlock'),
// DisplayBlock
},
data: {
[...]
},
computed: {
[...]
},
watch: {
[...]
},
methods: {
[...]
},
mounted() {
[...]
}
});
}
In your webpack file the resolver is missing for VUE.
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js' // 'vue/dist/vue.common.js'
}
}
Try by adding the above snippet.

vue babel-loader SyntaxError: Unexpected token on ":click" syntax

I use render function like this:
.script.js:
methods: {
handleClick(data){
console.log(data)
},
render(h, { node, data, store }) {
return (
<span>
<span>
<span>{node.label}</span>
</span>
<span style="float: right; margin-right: 20px">
<a href="javascript:;"
:attr="data.id" #click="handleClick">Edit{data.id}
</a>
</span>
</span>
);
}
}
But babel encoutners error saying the :click has Unexpected token.
.vue:
<template src="./template.html"></template>
<script src="./script.js"></script>
package.json:
"vue": "^2.2.6"
"vue-router": "^2.4.0"
"element-ui": "^1.2.8",
"babel-core": "^6.24.0",
"babel-loader": "^6.4.1",
"babel-plugin-transform-vue-jsx": "^3.4.2",
"vue-loader": "^11.3.4",
"webpack": "^2.3.1",
webpack:
{
test: /\.vue$/,
loader: `vue-loader`,
options: {
loaders: {
js: 'babel-loader'
}
}
},
{
test: /\.js$/,
loader: `babel-loader`,
exclude: /(node_modules)/
}
.babelrc:
{
"presets": [
["es2015", { "modules": false }], "stage-1", "stage-2", "stage-3"
],
"plugins": [
["transform-vue-jsx"],
["transform-object-assign"],
["component", [{
"libraryName": "element-ui",
"styleLibraryName": "theme-default"
}]]
]
}
When i run gulp dist, babel throws an error like follows:
Namespaced tags/attributes are not supported. JSX is not XML.\nFor attributes like xlink:href, use xlinkHref instead.
As #Bert Evans suggested,
after re-reading the reademe docs of https://github.com/vuejs/babel-plugin-transform-vue-jsx, I figured out that i just wrote the code without understanding the syntax of vue-specific jsx syntax.
As the docs says:
Note that almost all built-in Vue directives are not supported when using JSX, the sole exception being v-show, which can be used with the v-show={value} syntax. In most cases there are obvious programmatic equivalents, for example v-if is just a ternary expression, and v-for is just an array.map() expression, etc.
The equivalent of the above in Vue 2.0 JSX is:
render (h) {
return (
<div
// normal attributes or component props.
id="foo"
// DOM properties are prefixed with `domProps`
domPropsInnerHTML="bar"
// event listeners are prefixed with `on` or `nativeOn`
onClick={this.clickHandler}
nativeOnClick={this.nativeClickHandler}
// other special top-level properties
class={{ foo: true, bar: false }}
style={{ color: 'red', fontSize: '14px' }}
key="key"
ref="ref"
// assign the `ref` is used on elements/components with v-for
refInFor
slot="slot">
</div>
)
}
So, i changed my code to
render(h, {node,data,store}) {
const link = {
href: `/#/schema-type/${data.id}`
};
return (
<span>
<span>
<span>{node.label}</span>
</span>
<span>
edit
</span>
</span>
);
}
And it works!
Try to write your html as a string:
Vue.component('my-component', {
template: '<div>A custom component!</div>'
});
It looks like you are used to React, but it write a little different.