Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vue.js detach styles from template

I use a template with a <style> block which must be near its div for CMS reasons.

When I run Vue.js, it seems to remove the style block, saying...

- Templates should only be responsible for mapping the state to the UI.     
  Avoid placing tags with side-effects in your templates, such as <style>, 
  as they will not be parsed.

What can I do?

var app = new Vue({
  el: '#app'
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.0/vue.js"></script>

<div id="app">
  <style>
    #div_123 {
      background: http://placehold.it/850x150;
    }

    @media screen and (max-width: 640px) {
      #div_123 {
         background: http://placehold.it/350x150;
      }
    }
  </style>
  <div id="div_123">
    Test
  </div>
</div>
like image 704
Hugo H Avatar asked Mar 12 '17 11:03

Hugo H


2 Answers

The Issue

In Vue 2 the root instance is treated more like a component than it was in Vue 1.

This means when you bind the Vue instance to #app it digests everything in #app as a vue template. This means tags are invalid and they'll be removed from the template. This is just the way things work in Vue 2.

Recreation

I recreated the issue in a codepen here

https://codepen.io/Fusty/pen/gqXavm?editors=1010

The <style> tag nested within the tag Vue is bound to. It should style the background red and the text color green. However, we see only a flash of this (depending on how fast your browser fires up Vue) and eventually vue removes these style tags as it digest #app as a template and then updates the DOM with what it thinks should be there (without <style> tags).

Better Recreation

Thanks to user @joestrouth1#6053 on the Vue-Land discord, we also have this fork of my recreation of the issue.

https://codepen.io/joestrouth1/pen/WPXrbg?editors=1011

Check out the console. It reads . . .

"[Vue warn]: Error compiling template:

Templates should only be responsible for mapping the state to the UI. Avoid placing tags with side-effects in your templates, such as <style>, as they will not be parsed.

1  |  <div>
2  |      <style>
   |      ^^^^^^^
... etc ...

Complaining about the style tags in a template.

This zeroes in on the actual issue. It is good to note this doesn't occur in Vue 1. Probably because it treats the root instance more uniquely than components, but I am not 100% sure on this topic.

Solution (Hack, not best practice or especially recommended)

The <style> tags are still in the DOM during the created lifecycle hook for the Vue instance and they are removed by the time the mountedlifecycle hook fires. Let's just query for all of the style tags within the #app element, save them, and then append them back to the #app element after Vue has digested the template.

Adding the following to your root Vue instance will take any <style> tags within whatever element your Vue instance is bound to (via el: 'someSelector') and append them (possibly relocating them) to the element your Vue instance is bound to.

  created: function() {
    this.styleTagNodeList = document.querySelector(this.$options.el).querySelectorAll('style');
  },
  mounted: function() {
    for(var i = 0; i < this.styleTagNodeList.length; ++i)
      this.$el.appendChild(this.styleTagNodeList[i]);
  }

NOTE: This is definitely a hack which likely has unintended consequences I have not run into yet and cannot specifically disclaim. USE AT YOUR OWN RISK.

like image 170
Fusty Avatar answered Sep 30 '22 18:09

Fusty


This works for my specific situation where I allow the users to store a string of CSS and then I need to render it on specific pages - ei: preview page.

The context here is css is saved as string in database, fetched and rendered within an Vue component.

# html
<html>
  <head>
    <style id="app_style"></style>
  </head>
  <body>

    <div id="app"></div>

  </body>
</html>

# app.vue

data() {
  return {
    dynamic_css: ''
  }
},

created() {
  // fetch css from database, set as `this.dynamic_css`
},

watch {
  dynamic_css: function(newValue) {
    document.getElementById('app_style').innerHTML = newValue
  }
}
like image 38
baconck Avatar answered Sep 30 '22 19:09

baconck