webdiscus / pug-plugin

Renders Pug template to HTML or template function. Resolves source files of scripts, styles, images in Pug . Uses Pug template as entry point.
https://webdiscus.github.io/pug-plugin/hello-world
ISC License
73 stars 8 forks source link

[FEATURE REQUEST] How to do inline CSS #48

Closed MarcelRobitaille closed 2 years ago

MarcelRobitaille commented 2 years ago

I am trying to inline my critical CSS for a faster initial load.

I tried the following pug code:

style !{require('../css/styles.css?raw')}

And in my webpack config, I put:

{
    resourceQuery: /raw/,
    type: 'asset/source',
    use: 'css-loader',

}

But this throws the following error:

ERROR in [entry] [initial]
[pug-plugin] Can't resolve the file /home/marcel/code/biography-site/src/css/styles.css?raw in /home/marcel/code/biography-site/src/pug/index.pug
The reason: this file not found!
/home/marcel/code/biography-site/node_modules/pug-plugin/src/Exceptions.js:34
  throw new PugPluginException(lastError);
  ^

PugPluginException:
[pug-plugin] Can't resolve the file /home/marcel/code/biography-site/src/css/styles.css?raw in /home/marcel/code/biography-site/src/pug/index.pug
The reason: this file not found!

    at PugPluginError (/home/marcel/code/biography-site/node_modules/pug-plugin/src/Exceptions.js:34:9)
    at resolveException (/home/marcel/code/biography-site/node_modules/pug-plugin/src/Exceptions.js:77:3)
    at Resolver.require (/home/marcel/code/biography-site/node_modules/pug-plugin/src/Resolver.js:305:5)
    at /home/marcel/code/biography-site/src/pug/index.pug:1:955
    at Script.runInContext (node:vm:141:12)
    at PugPlugin.renderModule (/home/marcel/code/biography-site/node_modules/pug-plugin/src/index.js:718:33)
    at PugPlugin.renderManifest (/home/marcel/code/biography-site/node_modules/pug-plugin/src/index.js:652:28)
    at Hook.eval [as call] (eval at create (/home/marcel/code/biography-site/node_modules/tapable/lib/HookCodeFactory.js:19:10), <anonymous>:7:16)
    at Hook.CALL_DELEGATE [as _call] (/home/marcel/code/biography-site/node_modules/tapable/lib/Hook.js:14:14)
    at Compilation.getRenderManifest (/home/marcel/code/biography-site/node_modules/webpack/lib/Compilation.js:4483:36)

It seems like a problem with this plugin, because if I delete the style tag from the pug code and move the contents of require() to a second entry in my webpack config, it compiles without problems.

I also tried with asset/inline, but that is putting the base64 encoding of the styles in the <style> tag.

{
    resourceQuery: /raw/,
    type: 'asset/inline',
    use: 'css-loader',

}

My source for type: 'asset/source': https://webpack.js.org/guides/asset-modules/

Is there a way to achieve this?

webdiscus commented 2 years ago

Hi @MarcelRobitaille,

to inline CSS in Pug use include:

style: include ../css/styles.css

You don't need to configure Webpack for this.

Using type: 'asset/inline' Pug plugin can inline images like SVG, PNG, JPG, etc., but to inline CSS must be used native Pug feature.

MarcelRobitaille commented 2 years ago

Hi @webdiscus. Thanks for the response. I'm aware of pug's include keyword. However, this includes the file totally raw exactly as it is in src. I would like to minify the styles using webpack. That's why I am trying to use require() with asset/inline. Is there really no way to do this? Thanks

webdiscus commented 2 years ago

Yes, it makes sense. I note this feature request in TODO for next release.

webdiscus commented 2 years ago

@MarcelRobitaille,

Pug plugin has full control over all required assets in Pug and the inlining CSS must be implemented in the plugin. It is in development.

MarcelRobitaille commented 2 years ago

@webdiscus That is great! Thank you so much

webdiscus commented 2 years ago

@MarcelRobitaille

please update to v4.4.0.

For inline CSS and load CSS as file use in Webpack config the oneOf:

{
  test: /\.(css|sass|scss)$/,
  oneOf: [
    // inline styles in HTML
    {
      resourceQuery: /^\?raw/u, // match exact first URL query `?raw`
      type: 'asset/source',
      use: ['css-loader', 'sass-loader'],
    },
    // load styles as file
    {
      use: ['css-loader', 'sass-loader'],
    },
  ],
},

Then you can use both:

//- load CSS as file
link(href=require('./main.scss') rel='stylesheet')

//- inline CSS from styles.scss
style=require('./styles.scss?raw')

Note

The IDEA (JetBrains) don't understand the style !{require('./styles.css?raw')} syntax. I use Escaped String Interpolation: style=require('./styles.scss?raw') w/o linter error in my IDE.

Here is the test case for inline CSS with postcss plugin cssnano.

The inlined CSS support the sourceMaps. If in Webpack config is defined devtool: 'inline-source-map' then source map will be inlined too and it works in browser fine.

Question:

P.S. thanks for donate :-)

MarcelRobitaille commented 2 years ago

@webdiscus Thank you so much! :rocket: I will try it now.

what should be with devtool: 'source-map'? To inline map like inline-source-map or ignore the map, because for inlined CSS not exists an original CSS file?

I am not sure the best way to handle this to be honest. Sorry

P.S. thanks for donate :-)

No prob. I figure I bugged you enough that I should support you somehow. I'm not sure how long I can afford to stay a patron though.

MarcelRobitaille commented 2 years ago

I just tried 4.4.0. It seems like it's not handling url() in the CSS anymore.

ERROR in [entry] [initial]
[pug-plugin] Can't resolve the file ../svg/background-tile.svg in /home/marcel/code/biography-site/src/css/styles.css?raw
/home/marcel/code/biography-site/node_modules/pug-plugin/src/Exceptions.js:34
  throw new PugPluginException(lastError);
  ^

PugPluginException:
[pug-plugin] Can't resolve the file ../svg/background-tile.svg in /home/marcel/code/biography-site/src/css/styles.css?raw

    at PugPluginError (/home/marcel/code/biography-site/node_modules/pug-plugin/src/Exceptions.js:34:9)
    at resolveException (/home/marcel/code/biography-site/node_modules/pug-plugin/src/Exceptions.js:77:3)
    at Resolver.require (/home/marcel/code/biography-site/node_modules/pug-plugin/src/Resolver.js:309:5)
    at /home/marcel/code/biography-site/src/css/styles.css:5:37
    at Script.runInContext (node:vm:141:12)
    at PugPlugin.renderModule (/home/marcel/code/biography-site/node_modules/pug-plugin/src/index.js:755:33)
    at PugPlugin.renderManifest (/home/marcel/code/biography-site/node_modules/pug-plugin/src/index.js:683:28)
    at Hook.eval [as call] (eval at create (/home/marcel/code/biography-site/node_modules/tapable/lib/HookCodeFactory.js:19:10), <anonymous>:7:16)
    at Hook.CALL_DELEGATE [as _call] (/home/marcel/code/biography-site/node_modules/tapable/lib/Hook.js:14:14)
    at Compilation.getRenderManifest (/home/marcel/code/biography-site/node_modules/webpack/lib/Compilation.js:4483:36)
.foo {
    background-image: url(../svg/background-tile.svg);
}

If I remove that line, it's working perfectly!

webdiscus commented 2 years ago

@MarcelRobitaille

I will fix it.

P.S. For me, the money does not play any role. I am financially independent. For a private user, a one-time donation is enough. I see it as an acknowledgment of my work that the plugin is really useful. You can cancel a patron. I'm grateful for one donation.

webdiscus commented 2 years ago

@MarcelRobitaille

to implement support of URL in inline CSS using asset/source is very complex, because Webpack for asset/source type don't analyse/parse the source and don't resolve a resource in CSS. The asset/source exports the source code of the asset, e.g. for text files. Webpack simple inject content of file. This type is not suitable for inline CSS. I must implement the resolving of resources in CSS self. It will take a while.

As alternative syntax for inline CSS I can suggest following:

//- inline CSS from styles.scss link(href=require('./styles.scss?inline') rel='stylesheet')

Then the `link` tag will be replaced with `<style>... inline CSS content ...</style>`.

- variant 2
```pug
//- load CSS as file
link(href=require('./styles.scss') rel='stylesheet')

//- inline CSS from styles.scss
style=require('./styles.scss?inline')

For both variants, the Webpack config still be unchanged, also w/o a type:

{
  test: /\.(css|sass|scss)$/,
  use: ['css-loader', 'sass-loader'],
},

Variant 2 can be implemented faster. I will implement this variant.

Note: for both variants the URL query ?inline will be hard & fix coded. It will be as marker to inline the file.

MarcelRobitaille commented 2 years ago

@webdiscus Thanks for the updates. I didn't expect making url() work would be so complicated. So, if I understand correctly, the workaround is to link the stylesheet in the normal way (not inline)? I assume this helps webpack recognize the imports somehow? Is it possible for this to be commented so the full stylesheet doesn't actually load or something like that?

webdiscus commented 2 years ago

For asset/source type Webpack inject a source without processing and urls will be not resolved in Webpack.

To inline CSS with supports url(), the style file must be processed (parsed and resolved all URLs) via Webpack as javascript/auto type. Then Webpack call special hooks for each asset (image, font, etc) resolved in url(). In this hooks I can resolve output asset files. Well, it is complex process, but I know wath to do. I'm in process... ;-)

MarcelRobitaille commented 2 years ago

Can I just put the css file as an entry in webpack without linking it to my HTML?

webdiscus commented 2 years ago

Can I just put the css file as an entry in webpack without linking it to my HTML?

Yes, of cause. The Pug plugin can extract CSS from a style (css, scss, etc) defined in Webpack entry into separate file without linking to a Pug/HTML.

Webpack config:

module.exports = {
  output: {
    path: path.join(__dirname, 'dist/'),
  },

  entry: {
    styles: './src/assets/main.scss', // => dist/asset/css/styles.css
  },

  plugins: [
    new PugPlugin({
      // extract CSS from Pug or from Webpack entry 
      extractCss: {
        filename: 'asset/css/[name].css' , // output CSS filename
        //filename: 'asset/css/[name].[contenthash:8].css' , // output CSS hashed filename
      },
    }),
  ],

  module: {
    rules: [
      {
        test: /\.(css|sass|scss)$/,
        use: [ 'css-loader', 'sass-loader'],
      },
    ],
  },
};

Note

An additional plugin and loader, such as mini-css-extract-plugin, is not required.

MarcelRobitaille commented 2 years ago

To be clear, my question was if adding the CSS file as another entry in webpack would satisfy the first line of variant 1 and variant 2 from here.

I have just tried this, and I am getting the same error. I also tried variant 2 exactly as you have it. Neither of these variants has been implemented yet, right?

webdiscus commented 2 years ago

To be clear, my question was if adding the CSS file as another entry in webpack would satisfy the first line of variant 1 and variant 2 from here.

I have just tried this, and I am getting the same error. I also tried variant 2 exactly as you have it. Neither of these variants has been implemented yet, right?

All my variants were implementation suggestions. It will be implemented. It's not ready yet.

See my comment above:

 I'm in process... ;-)
MarcelRobitaille commented 2 years ago

Yes ok that was what I thought. I just wanted to see what would happen

webdiscus commented 2 years ago

@MarcelRobitaille

update please to v4.5.0. In this version is implemented the variant 2.

To inline CSS use the ?inline query:

//- load CSS as file
link(href=require('./styles.scss') rel='stylesheet')

//- inline CSS from styles.scss
style=require('./styles.scss?inline')

Webpack config rule for styles:

{
  test: /\.(css|sass|scss)$/,
  use: ['css-loader', 'sass-loader'],
},
MarcelRobitaille commented 2 years ago

@webdiscus It's working perfectly! Thank you so much.

//- load CSS as file
link(href=require('./styles.scss') rel='stylesheet')

//- inline CSS from styles.scss
style=require('./styles.scss?inline')

I totally misunderstood this. I thought you were saying I needed to have both tags in order for the inline minification and linking to work. That lead to the confusion in later comments. Sorry about that.

webdiscus commented 2 years ago

@MarcelRobitaille

I wanted to demonstrate both usage cases in one example. It would be correct to demonstrate:

Use case load CSS as file

link(href=require('./styles-one.scss') rel='stylesheet')

Use case inline CSS

style=require('./styles-two.scss?inline')

Both use cases are independent of each other.

webdiscus commented 2 years ago

@MarcelRobitaille

is everything working? Can be closed the issue?

MarcelRobitaille commented 2 years ago

Working beautifully. Thank you :heart: