Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to load a font with Vue Web Component Wrapper #54

Open
serbemas opened this issue May 29, 2019 · 15 comments
Open

How to load a font with Vue Web Component Wrapper #54

serbemas opened this issue May 29, 2019 · 15 comments

Comments

@serbemas
Copy link

I'm working in a new project using Vue CLI 3 and I'm having an issue when I try to load a font in a web component.

I have prepared this basic example.

<template>
  <div>
    <p>
      For a guide and recipes on how to configure / customize this project
    </p>
  </div>
</template>

<script>
export default {
  name: 'Test',
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style>
p {
  @import url('https://fonts.googleapis.com/css?family=Montserrat&display=swap');

  font-family: 'Montserrat';
  color: #42b983;
  font-size: 28px;
}
</style>

And this is the code generated running npm serve:

image

As you can see in the image above, it generates an <style> tag with this content:

<style type="text/css">@import url(https://fonts.googleapis.com/css?family=Montserrat&display=swap);</style>

How can I solve this?

@karol-f
Copy link

karol-f commented May 29, 2019

If You want to use pure CSS use @font-face, otherwise same as in regular vue components https://cli.vuejs.org/guide/css.html#pre-processors e.g. <style lang="scss">

@serbemas
Copy link
Author

serbemas commented May 29, 2019

I have changed the example and I have added a .less file with font face definitions.

<template>
  <div>
    <p>
      For a guide and recipes on how to configure / customize this project
    </p>
  </div>
</template>

<script>
export default {
  name: 'Test',
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style lang="less">
  @import "~@/assets/less/base/fonts.less";

p {
  font-family: 'Montserrat';
  color: #42b983;
  font-size: 28px;
}
</style>

And this is the file fonts.less


@font-face {
  font-family: 'Montserrat';
  src: url("./../../fonts/Montserrat-ExtraLight.otf") format("opentype");
  font-style: normal;
}

The fonts aren't loaded by browser:

image

I have read that font-face is not working with shadow-root. Thanks!!

@karol-f
Copy link

karol-f commented May 29, 2019

Take a look on what is generated, if path is correct etc.

@serbemas
Copy link
Author

I think that everything is generated correctly and the paths are corrects too, because in the shadow-root is added a <style> with the file content.

image

In the dist folder are the fonts too.

@karol-f
Copy link

karol-f commented May 29, 2019

So it's working correctly, right? Did You try using it? Maybe Chrome won't download it untill used?

@serbemas
Copy link
Author

But why is not being used? I can't understand it. The style is loaded inside shadow-root however it seems that the font-face rule is not being processed.

I think load a font inside a web component should be very common.. so I don't think that I'm the first who has this problem.

Can be this a bug of the wrapper or is related to the web components maybe?

PS: If I load the styles in doing something like it works correctly.

created() {
    const linkNode = document.createElement('link');
    linkNode.type = 'text/css';
    linkNode.rel = 'stylesheet';
    linkNode.href = '//fonts.googleapis.com/css?family=Montserrat&display=swap';
    document.head.appendChild(linkNode);
  }

@karol-f
Copy link

karol-f commented May 30, 2019

I mean - did you style some div/p etc. with font-family you've imported? If not why Chrome should download this font if it's not used?
This is just guess.

@serbemas
Copy link
Author

Yes off course.. the code is in the first comment. The styles are:

<style lang="less">
  @import "~@/assets/less/base/fonts.less";

p {
  font-family: 'Montserrat';
  color: #42b983;
  font-size: 28px;
}

and the template:

<template>
  <div>
    <p>
      For a guide and recipes on how to configure / customize this project
    </p>
  </div>
</template>

@karol-f
Copy link

karol-f commented May 30, 2019

Please prepare GitHub or CodeSandbox repo. Regards!

@serbemas
Copy link
Author

serbemas commented Jun 19, 2019

Finally I have solved it creating a component called for example 'FontsLoader.vue' that loads the fonts style

<style scoped lang="less">
  @import "../../assets/less/base/fonts.less";
</style>

and then when its mounted() loop over the shadowRoot child nodes to find the node with @font-face declarations and append a new child in the head with the node content.

@ananiy
Copy link

ananiy commented Jul 11, 2019

same issue~

@tonimitrevski
Copy link

Any solution for this?

@n4ks
Copy link

n4ks commented Sep 2, 2022

My fonts load fine in the dev version, but they don't work in build.

@seyfer
Copy link

seyfer commented Aug 25, 2023

@serbemas could you please share your solution code?

this is what I did following your spec

<template>
  <!-- This component doesn't need to render anything -->
</template>

<script setup lang="ts">
import { onMounted } from 'vue';

onMounted(() => {
  // Target the my-chatbot custom element
  const myChatbotElem = document.querySelector('my-chatbot');

  if (myChatbotElem && oskChatbotElem.shadowRoot) {
    const fontFaceNodes: NodeListOf<ChildNode> = myChatbotElem.shadowRoot.querySelectorAll('style');

    // because browser cannot read fonts from shadowDom, we need to inject them globally
    fontFaceNodes.forEach((node) => {
      if (node.textContent && node.textContent.includes('@font-face')) {
        const newStyleNode = document.createElement('style');
        newStyleNode.textContent = node.textContent;
        document.head.appendChild(newStyleNode);
      }
    });
  }
});
</script>

<style scoped lang="scss">
@import "@/assets/font/font-europa";
</style>

given that @font-face definitions are in SCSS file.
I also made sure fonts are inlined, not referenced by URL, as web-component build will have just one umd.js file in my case.
in Vue config for webpack 5 (for webpack 4 use url-loader)

chainWebpack: (config) => {
    // Clear the existing rules for fonts
    config.module.rules.delete('fonts');

    config.module
      .rule('fonts')
      .test(/\.(woff2?|eot|ttf|otf)(\?.*)?$/i)
      .type('asset/inline')
      .parser({
        dataUrlCondition: {
          maxSize: 1000000, // Inline fonts smaller than 1MB
        },
      });
  },

and after doing that, I was able to use fonts in web component.

.my-elem-ui {
    font-family: "Europa", sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

Important note! I did include @font-face definitions in the component styles as well.
So they need to be present in the component shadowDom AND in the global page document to work.

@sadiqkhoja
Copy link

sadiqkhoja commented May 6, 2024

I wish @vitejs/vite-plugin-vue could extract @font-face nodes and inject a javascript to add them in the head of the document.

Or if somebody can write a custom compileStyle: (options: SFCStyleCompileOptions): SFCStyleCompileResults function/configuration to achieve that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants