Storybook : How to inject Vue Custom Element styles - vue.js

I have a Vue 3.2 Custom Elements project, perfectly running via -ce extension and webpack config
`chainWebpack: config => {
config.module
.rule('vue')
.use('vue-loader')
.tap(options => ({
...options,
compilerOptions: {
// treat any tag that starts with ion- as custom elements
isCustomElement: tag => tag.startsWith('ce-')
}
}))
}`
But, once a component is added to Storybook, the style part on my Single File Components does not get injected in Shadow DOM. No style at all (unless javascript imported from separate .css file).
Thanks for your help.

Related

Vue 3 replacing the HTML tags where v-html is called with the provided HTML

This is about a Vue 3 app with Vite, not webpack.
For now, as you can see from this issue on vite's issue page, vite doesn't have a convenient way of inlining SVGs without using external plugins. Vite does however, support importing files as raw text strings. As such, I had an idea to use this feature and to inline SVG's by passing the raw SVG strings into an element's v-html.
It actually works great, the SVG shows up on the page as expected and I can do the usual CSS transforms (the whole purpose of inlining them like this), but it's not perfect. As it currently stands, the element that receives the v-html directive simply places the provided HTML nested as a child. For example, if I do <span v-html="svgRaw" />, the final HTML comes out something like this
<span>
<svg>
<!-- SVG attributes go here -->
</svg>
</span>
Is there any way for me to essentially replace the parent element on which v-html is declared with the top-level element being passed to it? In the above example, it would mean the <span> just becomes an <svg>
EDIT:
Thanks to tony19 for mentioning custom directives.
My final result looks like this:
// main.ts
import { createApp } from "vue";
import App from "./App.vue";
const app = createApp(App);
app.directive("inline", (element) => {
element.replaceWith(...element.children);
});
app.mount("#app");
Then, in the component I simply use the directive, <svg v-html="svgRaw" v-inline /> and it works great!
You could create a custom directive that replaces the wrapper element with its contents:
Use app.directive() to create a global directive, named v-inline-svg:
// main.js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App)
.directive('inline-svg', el => {
if (!el) {
return
}
// copy attributes to first child
const content = el.tagName === 'TEMPLATE' ? el.content : el
if (content.children.length === 1) {
;[...el.attributes].forEach((attr) => content.firstChild.setAttribute(attr.name, attr.value))
}
// replace element with content
if (el.tagName === 'TEMPLATE') {
el.replaceWith(el.content)
} else {
el.replaceWith(...el.children)
}
})
.mount('#app')
In your component, include v-inline-svg on the v-html wrapper element (also works on <template> in Vue 3):
<svg v-html="svgRaw" v-inline-svg />
<!-- OR -->
<template v-html="svgRaw" v-inline-svg />
demo
I found that using the method above works but is only good for a single rendering of the svg... The element starts throwing errors if I try to change the svg contents dynamically, not sure why but assuming that the dom replacement has something to do with it.
I modified the code slightly for my use case.
app.directive('inline-svg', {
updated: (element) => {
if (element.children.length === 0) {
return
}
const svg = element.children[0]
if(svg.tagName.toLowerCase() !== 'svg') {
return
}
for (let i = 0; i < svg.attributes.length; i++) {
const attr = svg.attributes.item(i)
element.setAttribute(attr.nodeName, attr.nodeValue)
}
svg.replaceWith(...svg.children)
}
})
In my component I have.
<svg v-if="linkType !== null" v-html="linkType" v-inline-svg></svg>
The directive now copies the svg attributes across from the child to the parent and then replaces the child with it's children.
Coming from Vue2. I think this still works:
Instead of span you can use the special Vue tag template:
<template v-html="svgRaw" />
This will not render <template /> as a tag itself, but render the elements given in v-html without a parent element.

Load MaterialDesignIcons in Vuetify project without css cdn reference

I have a project which uses MaterialDesignIcons in Nuxt with Vuetify.
Today I switch completely with all tags to the JS loading type:
https://vuetifyjs.com/en/features/icon-fonts/#material-design-icons-js-svg
<v-icon>{{ mdiCheck }}</v-icon>
(...)
import { mdiCheck, mdiCart } from '#mdi/js'
(...)
data() {
return {
mdiCheck,
mdiCart,
my problem is, that after yarn build / start the loading of the css file from cdn don't stop.
How can I see why this is still loaded and how to fix this?
In Head HTML:
It's a problem with the nuxt plugin for vuetify.
As you can see in their docs they by default load the Roboto font and Material Design Icons. Difficult to spot but easy to fix:
In nuxt.config.js set defaultAssets: false in the vuetify configuration:
vuetify: {
defaultAssets: false,
icons: {
iconfont: 'mdiSvg',
}
}

How do I use `localIdentName` with vue-cli

I'm trying to randomize/minify generated class names.
Currently I'm using a fairly vanilla vue-cli project, and I'm also using Tailwind css which I hope doesn't complicate anything.
Currently this is how far I got in my vue.config.js by reading vue-loader docs, this, and this, however this only works half way... my CSS minifies perfectly, however my vue component classes dont' change, so its like it's not parsing *.vue files.
I thought this could be because I notice it mentioning using <style scoped> but with Tailwind you don't need style tags in your components. Quite literally 100% of the css is contained in my src/assets/styles/main.pcss file, which consists of a few #import statements and that's it.
module.exports = {
css: {
requireModuleExtension: false,
loaderOptions: {
css: {
modules: {
localIdentName: '[hash:6]',
},
},
},
},
chainWebpack: config => {
// disable eslint temporarily
config.module.rules.delete('eslint');
},
};
And my tailwind config:
module.exports = {
future: {
purgeLayersByDefault: true,
removeDeprecatedGapUtilities: true,
},
plugins: [],
purge: [
'./src/**/*.html',
'./src/**/*.vue',
],
theme: {},
};

Vue.js : ID's are not displayed during the load of SVG file

I have a svg file that I am loading to my Component. Each path has an ID associated with a particular path. However, when I render my svg the IDs are not displayed. I attached some pictures to show first one is the svg code and the second one is the dom loaded in browser
Svg file
I've encountered the exactly the same problem. Assuming you're using vue-svg-loader to import the svg files, the issue is that the loader drops some attributes, including the IDs.
My solution was not to import the svg files, but rather paste the content directly into a vue component.
// myIcon.vue
<template>
<svg version="1.1"...>
<path id="81A" .../>
</svg>
</template>
Now if you import myIcon.vue, all the attributes will be preserved.
Ok. So the problem was coming from vue-svg-loader and I was able to find a quick fix by adding the following to the vue.config. If anyone encounters the same issue. Basically add {cleanupIDs: false}, to the svgRule. Seems like by default vue-svg-loader removes the id's no idea why.
module.exports = {
chainWebpack: (config) => {
const svgRule = config.module.rule('svg');
svgRule.uses.clear();
svgRule
.use('vue-svg-loader')
.loader('vue-svg-loader')
.options({
svgo: {
plugins: [
{removeDoctype: true},
{removeComments: true},
{cleanupIDs: false},
{collapseGroups: false},
{removeEmptyContainers: false}
],
},
});
},
};

How to import CSS-Modules correctly with Nuxt?

I'm using CSS Modules with Nuxt and have run into some issues when trying to import a stylesheet in my js. If I import my stylesheet directly into the...
<style module>
#import './index.css';
</style>
...everything works as expected. In my particular case I need to run a computed property to choose between two different stylesheets so instead of importing through the <style module> I need to import into <script> and implement the styles like so:
<script>
import foo from './index.css'
export default {
computed: {
styles() {
return foo
}
}
}
</script>
When implementing this on vue everything works great and I get a style object returned. Nuxt however is returning an empty object and none of my styles render correctly.
I'm activating CSS-Modules in my nuxt.config.js file like this:
export default {
...
loaders: {
css: {
modules: true
}
}
...
}
Is this an issue with Nuxt SSR? I've been looking for the root cause/solution but haven't had much luck in my search.
Update
After taking ivandata's advice and adding to my build script this code:
export default {
....
build: {
extend (config, ctx) {
const cssLoader = config.module.rules.find(rule => {
return rule.test.toString() === '/\\.css$/i';
});
delete cssLoader.oneOf[0].resourceQuery;
...
}
}
}
CSS modules appear to be working but a new problem popped up which is that now the project doesn't understand any vue-component styles that are not css-modules. After doing a bit of research I found out that the resourceQuery is telling the loader what type of file to apply the loader options to.
I've tried digging through the style loader on vue.cli 3 and comparing the differences to Nuxt. I removed ivandata's snippit and I tried matching the loaders of vue and nuxt but the problem still persisted.
Here is what is happening visually when between enabling and disabling ivandata's code:
Disabled
Enabled
And here is a code snippet of what is going on in my project:
<template>
<section :class="style.container">
<h1>hey</h1>
<h2 class="test">hey</h2>
</section>
</template>
<script>
import style from './index.css'
export default {
computed: {
style() {
return style
}
}
}
</script>
<style>
h1 {
font-size: 100px;
}
.test {
font-size: 100px;
}
</style>
So as you can see if I have the resourceQuery in the css-loader my javascript import's of css do not work but all vue-component styles worked as normal. Once I remove the resourceQuery the js imported stylesheet works but the vue-template style classes no longer work. I don't think the solution lies in removing resourceQuery and I'm curious if this has something to do with another loader entirely. I've dug quite a bit through the vue.cli 3 loaders and can't see anything that distinctly sticks out to me.
Ok this another way. Leave your old code. Remove my code and add ?module to import file path:
<script>
import foo from './index.css?module'
export default {
computed: {
styles() {
return foo
}
}
}
</script>
I was wrong. resourceQuery option is used to test against the query section of a request string.
You don't need activate css-modules in nuxt, they active by default.
Nuxt.js use vue-style-loader for load styles.
https://vue-loader.vuejs.org/guide/css-modules.html#opt-in-usage
By default, all styles loading from style tag with module attribute, because style loader use resourceQuery /module/ in oneOf rule. So if remove this property nuxt will load styles as you want.
export default {
....
build: {
extend (config, ctx) {
const cssLoader = config.module.rules.find(rule => {
return rule.test.toString() === '/\\.css$/i';
});
delete cssLoader.oneOf[0].resourceQuery;
...
}
}
}
nuxt version: 2.0.0.
You need to do nothing but exchange scoped to module,such as:
<template>
<div :class="$style.red">TEST</div>
</template>
<style module>
.red{color:red;}
</style>