disfated
disfated

Reputation: 10999

Vue.js inject styles in <body> instead of <head> of index.html (webpack, HtmlWebpackPlugin)

Basically, I want achieve this index.html structure:

<html>
<head>
    <!-- titles, metas and other "static" stuff -->
    <link rel="preload/prefetch" ...> <!-- injected by webpack (ok) -->
    <!-- By default compiled styles are injected here, in head, I want them in body -->

</head>
<body>
<div>
    My static loading animation
    All possible styling is inlined
    It doesn't depend on anything!
    It also could be anything, even just a plain "Loading..." text.
    You still WON'T see it until all style's in head are loaded.
</div>

<div id="app">Vue application goes here</div>

<link rel="stylesheet" href="..."> <!-- Styles injected by webpack (WANTED!) -->
<script src="..."></script> <!-- Scripts injected by webpack (by default, OK) -->

</body>

The reason I want this, is that my html is completely capable of displaying initial loading animation to the user and I want it to render instantly as soon as index.html is loaded and not depend on any other resources. Really, I think this is everybody want, just to say...

But Vue by debault is configured to include it's compiled styles to the <head> tag, which blocks rendering of the page until these styles are loaded. I cannot find any docs of how I could change it.


Update: Pictures!

So, I've managed to manually simulate two variants:

  1. styles are injected in <head> (default)
  2. styles are injected in <body> (wanted)

Here are the pictures of the visual difference:

1) styles are injected in <head> (default):

styles are injected in head

2) styles are injected in <body> (wanted):

styles are injected in body

The label "html rendering starts" on pictures means that a user actually sees loading animation, defined completely inside html (small piece of svg and styling in my case, could be anything in general case) and doesn't depend on any other external resources for it to render.

Upvotes: 3

Views: 1737

Answers (1)

disfated
disfated

Reputation: 10999

Solution

vue.config.js

class InjectStylesInBody {
    apply(compiler) {
        compiler.hooks.compilation.tap('inject-styles-in-body', (compilation) => {
            if (!compilation.hooks.htmlWebpackPluginAlterAssetTags) return;
            compilation.hooks.htmlWebpackPluginAlterAssetTags.tap('inject-styles-in-body', function(pluginArgs) {
                const { head, body } = pluginArgs;
                head
                    .filter(asset => asset.tagName === 'link' && asset.attributes && asset.attributes.rel === 'stylesheet')
                    .forEach(asset => {
                        head.splice(head.indexOf(asset), 1);
                        body.push(asset);
                    });
            });
        });
    }
}

module.exports = {
    // ...
    chainWebpack: config => {
        // ...
        config
            .plugin('inject-styles-in-body')
            .use(InjectStylesInBody)
        ;
        // ...
    }
    // ...
};

Notes

Upvotes: 3

Related Questions