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

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:
styles are injected in <head> (default)
styles are injected in <body> (wanted)
Here are the pictures of the visual difference:
1) styles are injected in <head> (default):
2) styles are injected in <body> (wanted):
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.

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
Eventually, this has nothing to do with Vue.js. One could easily use this with other frameworks or with naked webpack.
It could be much easier if HtmlWebpackPlugin had some inject-css option for styles as it has for scripts.
See: https://github.com/jantimon/html-webpack-plugin/blob/e2c6990e94b298ff66bcd885c9a03a78221479f6/index.js#L548

Related

Vue.js (v3): How to have a unique data-v-* hash for each component instance

I have the following code:
blah-foo.vue:
<template>
<div>Hello {{ name }}</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
props: {
name: {
type: String,
}
},
});
</script>
<style scoped>
div {
color: white;
}
</style>
and App.vue:
<template>
<blah-foo
name="Alice"
></blah-foo>
<blah-foo
name="Bob"
></blah-foo>
</template>
The result in my browser is the following:
<div data-v-73bdd40c>Hello Alice</div>
<div data-v-73bdd40c>Hello Bob</div>
Is there any way I could tell the vue loader to generate an unique data-v-* attribute for each of them ?
What is happening in production is that since the component blah-foo is called on many different lazy-loaded pages, I end up having many times
div[data-v-73bdd40c] {
color: white;
}
all overriding each other.
That isn't a problem in itself, it just does seem very disgraceful (code wise) after having loaded a few pages when inspecting elements.
That is not possible with vue-loader. And it shouldn't be done anyway.
The whole point of the data-v-xxx attribute is to identify the components in the DOM so vue can apply to them the correct scoped css.
If it ever applied uniq data-v attributes, it would not be able to apply the correct css.
I understand your problem is that, on production, you see in the css inspector several times the css code, right?
My guess is that it's related with sourcemaps, which may mess with the css inspector. But I can't help more without additional details on your project configurations.
Even if your component is used on several pages, its module will be fetched and loaded only once. You don't have the scoped css loaded several times, it's just an inspector problem.

Vue 3 dynamically loaded component hooks not called

I have this (shorten for the question) single file component (vue 3.2.31):
<template lang="pug">
.test Hello world!
</template>
<style lang="sass" scoped>
.test
font-weight: bold
</style>
<script setup lang="ts">
onMounted(() => {
console.log('Mounted');
});
</script>
It is bundled via vitejs, exported as (let's say) NamedExport and served on demand as a base64 encoded string to be imported client-side.
const component = await defineAsyncComponent(async () => {
// A module that exports multiple components.
const module = await import(base64StringSentFromTheServer);
// Choose one.
return module['NamedExport']);
})
then, the result is bound to:
<component :is="component" />
It works well, except two things, one of these is that hooks are not called (onMounted in this case), the other being that styles importer is not called either.
Is it an expected behavior, or do I miss something? Is it the <script setup> way of writing the component that is responsible?
It appears that I had two instances of Vue running (one bundled with my package, with rollup, and one imported in the script itself), and for an unknown reason, none of the two was calling hooks.
By removing one of the instances (actually, passing vue as external in rollup build configuration) it now works well.

How do I include Vuetify css on Vue js print?

I have tried this code
// Get HTML to print from element
const prtHtml = document.getElementById('print').innerHTML;
// Get all stylesheets HTML
let stylesHtml = '';
for (const node of [...document.querySelectorAll('link[rel="stylesheet"], style')]) {
stylesHtml += node.outerHTML;
}
console.log(stylesHtml);
// Open the print window
const WinPrint = window.open();
WinPrint.document.write(`<!DOCTYPE html>
<html>
<head>
${stylesHtml}
</head>
<body>
${prtHtml}
</body>
</html>`);
WinPrint.document.close();
WinPrint.focus();
WinPrint.print();
WinPrint.close();
but it only add portions of vuetify css like v-btn. My problem is I am printing with a lot of helpers like d-flex text-right on a div but not working with this code.

Can I make this workflow faster? Rollup watch / npm live-server

I'm trying to achieve a 'live' workflow for a website i'm about to build.
It's based on this boilerplate.
I currently have this workflow working:
Write code -> Save -> 'Rollup watch' rebuilds build/main.js from src/main.js -> 'live-server' refreshes browser.
I'm new to a lot of this, so i'll be honest and say that the 8s it takes per build is a hell of a lot faster than my old workflow, which involved manual fileZilla and a noobier me developing on a password protected subdomain.
Is there a way I should be doing this so that I'm not waiting for the builds to happen - it seems unnecessary? E.g. use a dummy index.html that temporarily links to the src/main.js until i'm ready to build and deploy the bundled version on to my domain?
This is the current index.html of the boilerplate:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Example for Three JS</title>
<link rel="stylesheet" type="text/css" href="css/main.css">
</head>
<body>
<div id="homepage"></div>
<script src='build/main.js'></script>
<script>
const app = new LIB.App;
app.init();
</script>
</body>
</html>
And here is what my rollup config file looks like:
import resolve from '#rollup/plugin-node-resolve'; // locate and bundle dependencies in node_modules (mandatory)
import { terser } from "rollup-plugin-terser"; // code minification (optional)
export default {
input: 'src/main.js',
output: [
{
format: 'umd',
name: 'LIB',
file: 'build/main.js'
}
],
plugins: [ resolve(), terser() ]
};
I tried just switching out the script source from
<script src='build/main.js'></script>
to
<script src='src/main.js'></script>
and removing:
<script>
const app = new LIB.App;
app.init();
</script>
But this didn't work - so i'm here looking for input.
The 'answer' is that I should have been using a dev and prod version of my rollup builds. The dev version should have minification removed.
From Documentation:
You can, if you like, specify a different config file from the default rollup.config.js:
rollup --config rollup.config.dev.js
rollup --config rollup.config.prod.js

Vue.js, vue-cli 3.0 and Google Webfonts

I'm COMPLETLY LOST about including Google Fonts into my project.
I've installed google-fonts-webpack-plugin and tried to configure it properly, but the html is not being injected. Or maybe I'm not thinking about it right. Regardless, how to I include Google Webfonts?
Code in my vue.config.js:
const GoogleFontsPlugin = require("google-fonts-webpack-plugin")
module.exports = {
configureWebpack: {
plugins: [
new google-fonts-webpack-plugin({
fonts: [
{ family: "IBM Plex Sans" }
]
})
]
}
}
Go to the end of app.vue and add below code:
<style lang="scss">
#import url("https://fonts.googleapis.com/icon?family=Material+Icons");
</style>
That plugin is not currently compatible with the version of Webpack used by vue-cli.
What I've usually done in the past is just include the fonts via <link> tags in the index.html file, eg
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>vue</title>
<link href="https://fonts.googleapis.com/css?family=IBM+Plex+Sans" rel="stylesheet">
You could also edit the <style> block in your App.vue component to include
#import url("https://fonts.googleapis.com/css?family=IBM+Plex+Sans");