vuejs / vue-style-loader

💅 vue style loader module for webpack

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Injecting CSS imported from JavaScript in a .vue file as critical CSS

Justineo opened this issue · comments

Do you want to request a feature or report a bug?

Feature request (maybe?)

What is the current behavior?

CSS directly imported from JS won't be injected as critical CSS on server side.

What is the expected behavior?

All CSS depended by current component should be injected as critical CSS.

If this is a feature request, what is motivation or use case for changing the behavior?

We have a component library which decoupled with its themes. We put style files into separate NPM packages and made a Babel plugin to rewrite component import statements to use user specified theme. eg.

<script>
import { Button } from 'veui'
// ...
</script>

The script above will be transpiled into the following statements when we configured to use veui-theme-x:

import { Button } from 'veui'
import 'veui-theme-x/components/button.less'

As a result, it seemed that styles directly imported from JS won't be treated as critical CSS and injected into server-rendered HTML. I tried to modify the following code but it doesn't seem to be working.

// on the server: attach to Vue SSR context
if (isVue) {
// inside *.vue file: expose a function so it can be called in
// component's lifecycle hooks
return shared.concat([
'// add CSS to SSR context',
'var add = require(' + addStylesServerPath + ')',
'module.exports.__inject__ = function (context) {',
' add(' + id + ', content, ' + isProduction + ', context)',
'};'
]).join('\n')
} else {
// normal import
return shared.concat([
'require(' + addStylesServerPath + ')(' + id + ', content, ' + isProduction + ')'
]).join('\n')
}

So can vue-style-loader automatically inject this kind of styles into HTML as it does for embedded styles?

Importing CSS from scripts is a common solution for component libraries with multiple themes. Element also suffer from this issue. Actually I'm not sure if this is a problem caused by vue-style-loader or other tools.

F.Y.I. When the loader's pitch function runs, the remainingRequest looks like this.

For styles embedded in <style> blocks of SFCs:

node_modules/css-loader/index.js?{"minimize":true,"importLoaders":1,"sourceMap":true,"alias":{"/static":"/Users/justice/dev/baidu/one-design/web/static","/assets":"/Users/justice/dev/baidu/one-design/web/assets"}}
  node_modules/vue-loader/lib/style-compiler/index.js?{"vue":true,"id":"data-v-fed3543c","scoped":false,"hasInlineConfig":true}
    node_modules/stylus-loader/index.js?{"sourceMap":true,"include css":true}
      node_modules/vue-loader/lib/selector.js?type=styles&index=0&bustCache
        layouts/default.vue

For styles imported from <script> blocks of SFCs:

node_modules/css-loader/index.js??ref--3-1
  node_modules/postcss-loader/lib/index.js??ref--3-2
    node_modules/less-loader/dist/cjs.js??ref--3-3
      node_modules/veui-theme-x/components/button.less

This only happens if the component is split into an async chunk, because the chunk is not evaluated when the bundle is initially run, direct imports are not registered on the initial render context.

But even if the imports are evaluated, they would be collected as global CSS instead of critical CSS. In order to make them critical CSS, they need to be injected inside the lifecycle hooks of the component they are imported in. This is not what the Babel transform was designed to do.

Usually we recommend importing styles using <style src="...">, which makes the style to be compiled by vue-loader and thus properly injected in the component's lifecycle.

For this specific use case, I'd suggest adding a simple loader to pre-process a *.vue file that does the following:

  • Parse for veui imports inside the <script> block
  • Add accompanying <style src="..."> links to the file

I'll try the .vue preprocessor solution to see if it would work as expected. Thanks!

It turns out we don't have to parse imports inside <script> blocks. We can just inject the corresponding <style> block when we are loading a component's .vue file and simple regex matching should be adequate for this scenario.

Hi,

I'am not sure if it's the good place to write this, but I think the information can be maybe usefull to you.

What you want is : "All CSS depended by current component should be injected as critical CSS."

Maybe what you need is : "All CSS depended by current ROUTE should be injected as critical CSS."

You should read this post :
https://vuejsdevelopers.com/2017/07/24/critical-css-webpack/

Context :
Google Lighthouse want us to speed the First meaningful paint.

I try to figure how to fix the "Reduce render-blocking stylesheets" problems in my vue-ssr setup (based on https://github.com/vuejs/vue-hackernews-2.0).

Possible solution :

They use webpack html-critical-webpack-plugin (above link) to detect the critical css (with phantomJs).

In theory, we could detect the critical css for each route, and inject it into a style tag. After we could preload non-critical CSS from a file, instead of be render-blocking.

It's maybe a little bit overkill, and it's not component oriented. It's not perfect, but I think it's maybe the holy grail for the "Reduce render-blocking stylesheets" problem.

If I found how to do it I will reply here

Yes, I agree that prerendering static CSS file for each route may be more performant but it has a prerequisite that the sets of components for different routes should be enumerable. It will absolutely work in some cases though.