How to create and style svelte 3 custom elements with nested components? - web-component

I'm trying to create custom-element (web component) in svelte 3. I can't find any way to style nested components from css. Svelte removes styles before injecting them to <style> inside of ShadowDOM.
The problem is that I want to have nested components in my root element.
For example:
RootComponent (svelte custom-element)
(imports) FooComponent
(imports) BarComponent
As stated here: svelte-custom-element
All the components imported to custom-element must have compiler option set to <svelte:options tag="component-name" />.
With this option set nested components works as expected and are injected into root's element ShadowDOM. The problem is that styles defined in nested components are not being injected.
The workaround for this problem would be to inject them into root's element <style> as global styles within ShadowDom.
(Un)fortunately svelte automatically removes all unused styles during compilation when custom elements not yet exist.
My goal is to create web component with svelte and then use it outside of svelte as native web-component.
Here is REPL
Custom elements do not really work on REPL as Conduitry wrote:
The compiler options in the REPL don't actually affect the code that >is run, just the code that is displayed. So enabling customElement >doesn't mean you are building and running a web component
So it's more like a code example than working one.
I would like to know if there is another way to create svelte custom-element with nested component and proper styling.
Is there a way to disable removing of unused css?
https://imgur.com/a/zZia566
from <div class="nested"> starts Nested component imported from Nested.svelte.
<style> element should have .nested class injected but it is removed by svelte compiler.

This is because when customElement option is on, each style in a component is injected into the shadowRoot of the custom element.
class YourComponent extends SvelteElement {
constructor(options) {
super();
this.shadowRoot.innerHTML = `<style>.foo{color:red;}</style>`;
// continues
Thus, in order to make style appear, you must use svelte component as custom element, not as svelte component.
Your App.svelte should be like below.
<script>
import Foo from './Foo.svelte'
import Bar from './Bar.svelte'
</script>
<svelte:options tag="web-component" />
<foo-component/>
<bar-component/>
However, this neither solve the problems related with custom element.
:global selector is not transformed into actual global selector.
Every nested component will produce shadowRoot, whereas mostly you will want only top-level one.
Check out some issues below from svelte repository related to custom elements.
nested component in custom element does not inherit style #2605
:global(...) not working in custom elements #2969
It seems like svelte does not fully support style cascading in custom element yet, should be handled in future.
Checked in svelte v3.12.1.

Thanks to brunoalano for sending me this: svelte-custom-element-template. It solves the styling problem with custom build script.

You just have to prevent the compiler from removing unused CSS
Let's say we have a custom element : App.svelte
App.svelte imports a normal svelte component : ./components/Message.svelte
But when you do this, any styles inside Message.svelte will disappear.
Solution
Move all content in the <style> tag of Message.svelte into the <style> tag of App.svelte
Add this to script of App.svelte
let cssKeep: string = "";
Add this to body of App.svelte
<span style="display: none;" class={cssKeep}><span class={cssKeep} /> </span>
This will prevent the compiler from removing any styles
Example
src/components/Message.svelte
<script lang="ts">
export let content: string;
</script>
<p class="red"> {content} </p>
src/App.svelte
<svelte:options tag="my-element" />
<script lang="ts">
import Message from "./components/Message.svelte";
let cssKeep: string = "";
</script>
<Message content="hello" />
<span style="display: none;" class={cssKeep}><span class={cssKeep} /> </span>
<style>
.red {
color: red;
}
</style>
vite.config.ts
import { defineConfig } from 'vite'
import { svelte } from '#sveltejs/vite-plugin-svelte'
export default defineConfig({
build: {
lib: {
entry: './src/main.ts',
name: 'MyElement'
},
},
plugins: [
svelte(
{
compilerOptions: {
css: true,
},
exclude: "./src/App.svelte",
emitCss: true,
}
),
svelte(
{
compilerOptions: {
customElement: true,
css: true,
},
exclude: "./src/components/**",
emitCss: true,
}
),
],
})
// guide: https://www.thisdot.co/blog/web-components-with-svelte
This may answer:
How to export web component in Svelte with nested Svelte components
Use Svelte components with custom elements
Styles missing when Svelte component imported into custom web component / custom element

Related

Vue 3 : disable shadow dom while using custom element

I would like to add in a non-vue application a custom element.
For that, I've created a classical SFC :
//test.ce.vue
<template>
<div class="text-primary">Test</div>
</template>
<script>
export default {
name: 'test',
};
</script>
<style>
.text-primary {
color: red;
}
</style>
And then a main script :
//app.js
import Test from 'test.ce.vue';
const testElement = defineCustomElement(Test);
customElements.define('test-element', testElement);
document.body.appendChild(document.createElement('test-element'));
Everything is running normally with the creation of a shadow dom element :
<test-component>
#shadow-root (open)
<style>
.text-primary {
color: red;
}
</style>
<div class="text-primary">Test</div>
</test-component>
I would like to avoid to redefine .text-primary class in the component as this class is already defined in the main css file. I also don't need to define specific classes for this component only, so in other terms, I would like to remove the shadow dom like a classical custom element will do.
So basically, render this :
<test-component>
<div class="text-primary">Test</div>
</test-component>
Is there's any option to define in vue that permit that ?
Older question, but in case someone still needs a solution for this...
there is currently no way to tell Vue not to use the shadow-dom. In Vue 2 there was a official package for creating web-components without shadow-root. And there is a community port for Vue 3 of that:
https://www.npmjs.com/package/vue3-webcomponent-wrapper
It was only meant to help people who have just migrated from Vue 2 to keep there application working. It was never intended to replace the official solution and should only be used until the official package can handle Vue 3.
Unfortunately that never happend.
The community port still works, but the package does not contain any source code, so it is a bit scary to use.
I came up with another solution for our project. Using defineCustomElement on a more complex vue component wich is composed by a bunch of nested components reveals another problem. The css of the child components wont be copied to shadow root. So only the css of the root component will work.
You can find the related issue and a workaround with full example here:
https://github.com/vuejs/core/issues/4662#issuecomment-1116001438
It basically grabs the css from the head and appends it to the shadow root.
You just have to extend it to also copy your main.css, like
<template>
<div id="app" ref="injectionElementRef">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App" />
</div>
</template>
<script lang="ts">
import {defineComponent} from 'vue';
import HelloWorld from './components/HelloWorld.vue';
export default defineComponent({
name: 'App',
components: {
HelloWorld
},
mounted() {
const el = this.$refs.injectionElementRef as HTMLElement
const root = el.getRootNode()
const linkTag = document.getElementById('main-css-id')
root.insertBefore(linkTag.cloneNode(), el)
}
});
</script>
The downside of this method is, there is a short flicker because the css is applied after mount. You could show an empty element till css is applied to work around that.
You are using Vue as a Tool to create Web Components, but why use a Tool over Native Technology?
Tools are not better; Tools are only faster in performing a task.
And in your case the Tool does something you do not want it to do.
Using native Web Components Technology, all you need is:
<style>
.text-primary {
color: red;
}
</style>
<test-component></test-component>
<script>
customElements.define("test-component", class extends HTMLElement {
connectedCallback() {
this.innerHTML = `<div class="text-primary">Test</div>`;
}
})
</script>

Material-UI - why different css is shown on prod environment then development env

I use material UI (verison: ^4.12.3) Select, with custom input.
For some reason the prod env Select input has a black background and :before element with white background.
I don't know from where it comes from.
this is image of the prod:
this is image of the dev Select:
when comparing the 2 css & html of the envs Select element, it's is shown that there is a ::before element added in prod that is not presented in dev
also, the background color is different. in prod there is another class added to the InputBase element, which doesn't exist in dev. this class adds a background-color black:
Edit 1
it seems like MUI inject <style>. in the prod html i see the background-color: black and the ::before. ill try adding the index solution, but my problem is not precedence (the style that i do use override the injected style). also, it wont help the ::before element. how to disable the injected styles ? or work around it ?
the injected bad css:
Please refer to this question. As answered by user Mordechai.
It seems like webpack could mess with MUI's rules on JSS precedence... This could be solved by adding an index of one to MUI's methods.
//Hook
const useStyles = makeStyles({
// your styles here
}, {index: 1})
// HOC
MyComponent = withStyles({
// your styles here
}, {index: 1})(MyComponent)
adding <StylesProvider /> wrapper to the app fixed it. we use micro-frontend infrastructure. and one of the frontends app also had makeStyles. this is causing classNames conflicts in MUI.
in the root component <App/>:
import {
StylesProvider,
createGenerateClassName
} from '#material-ui/core/styles';
const generateClassName = createGenerateClassName({
seed: 'app1'
});
const App = () => {
return (
<StylesProvider generateClassName={generateClassName}>
<OtherAppComponents />
</StylesProvider>
)
}
if you have more then 2 add a provider and a generator to each, with different seed

adding dynamic class name in svelte

I am currently writing an app with svelte, sapper and tailwind. So to get tailwind working I have added this to my rollup config
svelte({
compilerOptions: {
dev,
hydratable: true,
},
preprocess: sveltePreprocess({
sourceMap: dev,
postcss: {
plugins: [
require("tailwindcss"),
require("autoprefixer"),
require("postcss-nesting"),
],
},
}),
emitCss: true,
})
All in all this works, but I am getting some issues with dynamic class names.
Writing something like this always seems to work
<div class={true ? 'class-a' : 'class-b'}>
both class-a and class-b will be included in the final emitted CSS and everything works as expected.
But when I try to add a variable class name it won't work. So imagine this:
<div class={`col-span-6`}>
It will work exactly as expected and it will get the proper styling from the css class col-span-6 in tailwind.
But if I change it to this:
<div class={`col-span-${6}`}>
Then the style won't be included.
If I on the other hand already have a DOM element with the class col-span-6 then the styling will be added to both elements.
So my guess here is that the compiler sees that the css is not used and it gets removed.
And I suppose that my question is then if there is any way to force in all the styling from tailwind? so that I can use more dynamic class names
and not sure if it is relevant but the component I have been testing this on, have this style block
<style>
#tailwind base;
#tailwind components;
#tailwind utilities;
</style>
edit: can add that I am getting a bunch of prints in the log saying that there are unused css selectors that seems to match all tailwind classes
I think it was purgeCSS (built-in in tailwind 2.0) that did not recognize the dynamic classes.
It is difficult to solve this problem for every tailwind classes, but if you don't have a lot of these you can manually safe list those classnames:
// tailwind.config.js
module.exports = {
purge: {
content: ['./src/**/*.html'],
// These options are passed through directly to PurgeCSS
options: {
// Generate col-span-1 -> 12
safelist: [...Array.from({ length: 12. }).fill('').map((_, i) => `col-span-${i + 1}`],
},
},
// ...
}
I think that when the class attribute is a variable or depends on a variable it will not used to extract style during compilation (class-${6} is not evaluated during compilation but during runtime), because svelte marks it as unused css selector because the value of that class attribute is not known when the code is compiled.
To force svelte to include your style you must mark it as global, and to do that we have two options:
<script>
// component logic goes here
</script>
div class={`class-${6}`}/>
option 1:
<style>
:global(.class-6){
// style goes here
}
</style>
option 2: this will mark all your style as global
<style global>
.class-6{
// style goes here
}
</style>
I encounter the same problem, <div class="pl-{indent*4}"> do not work in svelte.
My solution is to use inline style,
<div style="padding-left:{indent}rem">,
which is inferred from pl-1=padding-left: 0.25rem; /* 4px */.
I think it's convenient for simple class.

Applying global styles in Next.js

I am using Next.js with Typescript. The margin of the body is default 8px and I want to get it to 0px. When I try to add an external style sheet to my index.tsx file it throws an error that you can only add external stylesheet to _app.tsx. However, even when I try to import in my _app.tsx, it doesn't change the global style of the body. I am using Emotion css for the styling part. Is there a different way to change the style of the body in the index file using global style? Here is my index.tsx code and I have tried adding the global styles using Emotion CSS as well but it doesn't work.
class Index extends React.Component {
render() {
return (
<div className='body'>
<Container>
<style jsx global>{`
.body:global() {
margin: 0px;
}
`}</style>
<NavBar />
</Container>
</div>
);
}
}
You need some global styles (<style jsx global>) to style the body element or provide css resets.
Example:
import Link from "next/link";
export default () => (
<div>
<style jsx global>{`
body {
background-color: red;
}
`}</style>
Hello, One!
<Link href="/two">
<a>Go to two</a>
</Link>
</div>
);
Code Sandbox
You can have global styles using emotion with Next.js
In your _app.tsx file, you must to
import { Global, css } from '#emotion/core'
return (
<>
<Global styles={css` /* styles */ `}/>
<Component {...pageProps} />
</>
You can see how to implement it, here
https://github.com/pabloobandodev/social-media/blob/master/pages/_app.tsx
According to the official docs:
Global CSS cannot be used in files other than your Custom <App> due to its side-effects and ordering problems.
Possible Ways to Fix It
Relocate all Global CSS imports to your pages/_app.js file.
How to do this in your case?
Well, the best way is to use a CSS base, lets take normalize.css for example.
Run yarn add normalize.css or npm i normalize.css, depending on whichever you are using.
Add import 'normalize.css'; in each of the page you want to use the base on. Official Docs.
Well this could seem redundant if you want to use the base in all of your pages. If so, you can, alternatively, create a file page/_app.tsx (any of the extension .js,.jsx,.ts,.tsx will work) and put this in it:
import 'normalize.css';
export { default } from 'next/app';
Note : If your app is running and you just added a custom App, you'll need to restart the development server. Only required if pages/_app.tsx didn't exist before.
No need to worry about other caveats mentioned in the docs as we are simply re-exporting App without any modification.
There are many CSS bases available choose any that seems best for you.
If you want to add custom global styles, then follow this:
Create a file styles/globals.css (.scss,.sass,etc. will also work if you have configured Next.js properly) and put your styles in that file.
Now add an import in pages/_app.tsx.
import '../styles/globals.css'; // change extension from `.css` to
// whatever you created above
export { default } from 'next/app';
If you have already created a module path alias for ../styles, then you might wanna change the styles import statement (probably to something like import '#styles/globals.css').
Also, if you are using less/sass/scss and want to use a base at the same time along with your custom global styles you simply need to use an import statement in your stylesheet (no need to import the base in _app.tsx if imported in the global stylesheet). An example:
// file: styles/globals.scss
#import '../node_modules/normalize.css/normalize.css';
// your styles...
body {
color: red;
}
// file: pages/_app.tsx
import '#styles/globals.scss';
export { default } from 'next/app';
Moreover, in your case it has not worked most probably because you were styling .body instead of body. It is likely that margin was present in the body, not your div.body.
This is how your _app.js, _app.tsx should look like; styles.css may have your CSS to reset the default browser properties, you can try adding other stylesheets here.
import '../styles/styles.css'
export default function App({ Component, pageProps }) {
return <Component {...pageProps} />
}

How to compile component styles dynamically in Angular 6

I'm trying work out a way to use jss in an angular 6 project to allow dynamic styling of components.
The issue I'm running into is that the dynamic styles are always less specific than the predefined styles, because the dynamic styles are missing the attribute selector from the view encapsulation system.
I can easily get the raw CSS output from jss, but I haven't been able to find a way to run this through the angular compiler to have the selectors modified to include the attribute selector.
Ideally I'd like to be able to bind a <style> tag in the template to a cssText property of the component, but this doesn't seem possible.
import {Component, OnInit} from '#angular/core';
import * as color from 'color';
import jss from 'jss';
#Component({
selector: 'app-example',
template: `
<p [ngClass]="cssClasses">TEST TEST</p>
`,
styleUrls: ['./example.component.scss']
})
export class ExampleComponent implements OnInit {
cssClasses: { [name: string]: boolean } = {};
constructor() {
}
ngOnInit() {
const {classes} = jss.createStyleSheet({
dynamicClass: {
color: color('blue').hex(),
}
}).attach();
this.cssClasses[classes.dynamicClass] = true;
}
}
example.component.scss
p {
color: 'red'
}
If there a way of invoking the angular CSS compiler on an arbitrary piece of CSS, with the context of a particular component?
Or another way to achieve what I'm describing above?
Note: I'm aware that I can bind and apply inline styles to elements, but this doesn't meet my requirements - in particular you cannot target pseudo selectors, or do media queries etc using this mechanism.
I could probably work around this by not using the scss file at all and defining all default styles through the jss mechanism however I would prefer to retain the ability to use the normal style system so that the jss is only used where needed. Also I think I would still run into selectivity issues when styling 3rd party components using jss.

Resources