numical / style-ext-html-webpack-plugin

Enhances html-webpack-plugin functionality by enabling internal ('in-line') styles.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Inlined styles link element is not removed from the HTML template

nathanosdev opened this issue · comments

I'm using a very basic config copied from the readme and everything works fine, but the link tag for my CSS file is still present in the HTML template causing a 404 error in the browser.

Can you give me the config and any html template you are using?

I'm getting the same thing. The inlined CSS is not removed as a external CSS file chunk.

const criticalCSS = new ExtractTextPlugin('[name].critical.css');
const entrypointCSS = new ExtractTextPlugin('[name].css');

const appCriticalCssLoader = {
	test: /\.critical\.scss$/,
	use: criticalCSS.extract(...),
	include: path.resolve(ROOT_DIR, 'src')
};

const appCssLoader = {
	test: /^(?!.*\.critical).*\.scss$/,
	use: entrypointCSS.extract(...),
	include: path.resolve(ROOT_DIR, 'src')
};

var config = {
	module: {
		rules: [
			...
			appCriticalCssLoader,
			appCssLoader,
		]
	},
	entry: {
		'client': './client.js',
		
	},
        ...
	plugins: [
		// Make App vendor chunk
		new webpack.optimize.CommonsChunkPlugin({
			name: 'vendor',
			chunks: ['client'],
			minChunks: function(module) {
				return isVendor(module);
			}
		}),
		// Create app index template
		new HtmlWebpackPlugin({
			template: 'templates/app.ejs',
			filename: '../app.html', // place at root public folder
			inject: false,
			chunks: [
				'vendor',
				'client'
			],
			minify: htmlMinifySettings
		}),
		// External CSS Files
		criticalCSS,		
		entrypointCSS,
		// Inline Critical CSS
		new StyleExtHtmlWebpackPlugin({
			chunks: ['vendor', 'client'],
			cssRegExp: appCriticalCssLoader.test,
			minify: true,
			position: 'head-top',
		}),
	]
}

Same problem here. I use pretty simple config:

{
        entry: './src/index.js',
        output: {
            filename: 'bundle.js',
            path: path.resolve(__dirname, 'dist')
        },
        plugins: [
            new webpack.DefinePlugin({ 
                'process.env': {
                    'NODE_ENV': JSON.stringify('production')
                },
            }),
            new HtmlWebpackPlugin({
                hash: true,
                template: 'src/index.html'
            }),
            new ExtractTextPlugin("styles.css"),
            new StyleExtHtmlWebpackPlugin(),
            new webpack.optimize.UglifyJsPlugin(), 
            new webpack.optimize.AggressiveMergingPlugin(),
        ],
        module: {
            rules: [
                {
                    test: /\.html$/,
                    exclude: /node_modules/,
                    loader: 'html-loader'
                },
                {
                    test: /\.css$/,
                    use: ExtractTextPlugin.extract({
                        fallback: "style-loader",
                        use: [
                            {
                                loader: "css-loader",
                                options: { importLoaders: 1 }
                            },
                            "postcss-loader"
                        ]
                    })
                },
                {
                    test: /\.(png|svg|jpg|gif)$/,
                    use: [
                        'file-loader'
                    ]
                },
                {
                    test: /\.js$/,
                    include: [
                        path.resolve(__dirname, "src"),
                    ],
                    // exclude: /(node_modules|bower_components)/,
                    use: {
                        loader: 'babel-loader',

                        options: {
                            presets: ['es2015', 'env'],
                            plugins: ['transform-runtime']
                        }
                    },

                }

            ]
        }
    };

With webpack 1.12.13 everything was fine, but now we are using webpack 3.8.1 and we have the same problem. 😢

Hi - @medi71, @orenklein, @dictions, @AnimaMundi - can i confirm you are all using the latest version v3.4.3?
Also can I check that none of your html templates have explicit elements for stylesheets?

Yes, we are using the latest version 3.4.3.

Sorry I haven't been much help here, I actually ended up abandoning this approach. I was using the latest version of Webpack and this plugin at the time of writing and did not have any explicit elements for stylesheets.

My index.html looks like this:

<!DOCTYPE html>
<html>

<head>
  <base href="/">

  <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">
  <meta name="format-detection" content="telephone=no">
  <meta name="msapplication-tap-highlight" content="no">
  <meta charset="UTF-8">

  <link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
  <title>My Project</title>
</head>

<body>
  <my-app>Loading...</my-app>
</body>

</html>

My Webpack config was extremely basic:

  new HtmlWebpackPlugin({
    template: 'app/index.html'
  }),
  new ExtractTextWebpackPlugin('styles.css'),
  new StyleExtHtmlWebpackPlugin()

That's not my whole Webpack config, just the plugins section

Thanks for the feedback. I'm struggling to reproduce the problem in my unit tests - once I can I'll hopefully be be able to come up with a fix.

@numical I debugged it.
isCssLinkTag function at replaceTag.js is returning false when HtmlWebpackPlugin hash option is set to true because stylePath is not equal to stylePath?hash, therefore the link tag is not removed. (https://github.com/numical/style-ext-html-webpack-plugin/blob/master/lib/replaceTag.js#L46)

Thanks @orenklein - I have incorporated a fix in version 3.4.4 for the bug you spotted.
Not closing this issue yet as I still get some failing tests for edge cases (with css regex's) and this cannot be the reason for @dictions issue as he's not using the hash.

@dictions : my failing tests are t0 do with the use of the cssRegExp option so hopefully I'm onto your problem now...

@numical aha! good find.

@dictions : version 3.4.5 has a fix for my failing tests - to do with having a '?' in the css filename. Don't think it is your issue but could you give it a try?

FWIW, I'm having this exact same issue. Using 3.4.5.

I'm running into the same issue using 3.4.5 as well, but the problem specific to my setup resides in html-webpack-plugin. Here's the relevant part of my webpack config.

// In external file, included for simplicity
paths.css = name => path.join('css', `${name}.css`);

const critCssPath = paths.css('critical');

const criticalCss = new ExtractTextPlugin(critCssPath);
const externalCss = new ExtractTextPlugin(paths.css('bundle'));

const cssLoaderOpts = {
  use: [{
    loader: 'css-loader?minimize'
  },
  {
    loader: 'postcss-loader',
    options: {
      plugins: [autoprefixer()]
    }
  },
  {
    loader: 'sass-loader'
  }]
};

export default {
  output: {
    publicPath: 'build/'
  },
  module: {
    rules: [{
      test: /critical\.scss/,
      exclude: /node_modules/,
      use: criticalCss.extract(cssLoaderOpts)
    },
    {
      test: /styles\.scss/,
      exclude: /node_modules/,
      use: externalCss.extract(cssLoaderOpts)
    }]
  },
  plugins: [
    new webpack.optimize.UglifyJsPlugin(),
    criticalCss,
    externalCss,
    new StyleExtHtmlWebpackPlugin(critCssPath)
  ]
};

You can view the whole configuration here.

I ran into this issue at work where my computer runs Windows 7 😒. This plugin wasn't removing the <link> for criticalCss.

The first problem was my own fault. I use the path module for pretty much everything in my webpack config, yet I was setting output.publicPath = 'build/' which caused this line to return false. tagDefinition.attributes.href was 'build/css\critical.css' while stylePath was 'build\css\critical.css'.

So I switched to output.publicPath = path.join('build', '/'). Except the <link> still wasn't being removed because of the same condition in isCssLinkTag. However, this time tagDefinition.attributes.href was 'build\/css\critical.css'.

Turns out this is happening because of this conditional. convertToPath inside replaceTag.js correctly constructs the path on Windows, while html-webpack-plugin appends an extra /.

When I changed this:

if (publicPath.length && publicPath.substr(-1, 1) !== '/') {
  publicPath += '/';
}

to this:

var separator = path.join('/');
if (publicPath.length && publicPath.substr(-1, 1) !== separator) {
  publicPath += separator;
}

The <link> for criticalCss was correctly removed without any changes in this plugin.

This is my first time dabbling with webpack plugin source code and I'm not exactly sure where to go from here. Should I be submitting an issue to html-webpack-plugin or is using the path module in webpack config overkill?

Hi, I met same issue. And after debug the plugin I find the output publicPath is undefined, which will lead the isCssLinkTag function return false. When I set the public path , problem solved.
image

@kytrol I tested your solution on my configuration (same issue) and it didn't solve the problem for me.

I'm using Windows 10 64x

In Windows, the problem, I think, is comparing the href link with filesystem path which differ in slashes (slashes vs back-slashes).
Removing the publicPath (making it an empty string) and changing the name of the css to be in the root (i.e. "styles.css") solved the problem for me.
Though now the js is always relative to the folder i'm in.

I'm still experiencing this with "style-ext-html-webpack-plugin": "^4.1.2", here's my config

const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');
const ScriptExtHTMLWebpackPlugin = require('script-ext-html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const MiniCSSExtractPlugin = require('mini-css-extract-plugin');
const TerserWebpackPlugin = require('terser-webpack-plugin');
const StyleExtHTMLWebpackPlugin = require('style-ext-html-webpack-plugin');

const root = path.join(__dirname, '../');

module.exports = {
  performance: {
    hints: false
  },
  entry: path.join(root, 'src/ui/entry.js'),
  output: {
    path: path.join(root, 'build/public'),
    filename: '[hash].js'
  },
  module: {
    strictExportPresence: true,
    rules: [
      {
        test: /\.js$/,
        use: ['babel-loader'],
        exclude: /node_modules/
      },
      {
        test: /\.scss$/,
        exclude: /node_modules/,
        use: [
          MiniCSSExtractPlugin.loader,
          'css-loader',
          'postcss-loader',
          'sass-loader'
        ]
      }
    ]
  },
  plugins: [
    new MiniCSSExtractPlugin({
      filename: '[hash].css'
    }),
    new HTMLWebpackPlugin({
      minify: true,
      cache: true,
      filename: path.join(root, 'build/index.html'),
      template: path.join(root, 'src/index.html')
    }),
    new StyleExtHTMLWebpackPlugin({
      position: 'head-bottom',
      minify: true
    }),
    new ScriptExtHTMLWebpackPlugin({
      defaultAttribute: 'defer',
      preload: /\.js$/
    }),
    new CopyWebpackPlugin([
      {
        from: path.join(root, 'src/server.js'),
        to: path.join(root, 'build')
      },
      {
        from: path.join(root, 'src/.env'),
        to: path.join(root, 'build')
      }
    ])
  ]
};

The CSS is being inlined as style tag in the HTML's head, however the link tag pointing to the now missing CSS file (because it's been inlined so the file wouldn't exist) was not removed.

I also have this problem with 4.1.2. I am on windows but I don't think its a path problem (not sure exactly how to debug as others above have done)

const HtmlWebpackPlugin = require('html-webpack-plugin');
const PurgecssPlugin = require('purgecss-webpack-plugin')
const StyleExtHtmlWebpackPlugin = require("style-ext-html-webpack-plugin");
const ScriptExtHtmlWebpackPlugin = require("script-ext-html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const purgecssFromHtml = require('purgecss-from-html');

const path = require("path");
const glob = require('glob');

const PATHS = {
  src: path.join(__dirname, 'src')
}

module.exports = {
  module: {
    rules: [
      {
        test: /\.scss$/i,
        use: [
            MiniCssExtractPlugin.loader,
            {
                loader: 'css-loader',
                options: {
                  url: false
                }
            },
            'sass-loader',
        ],
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: ["babel-loader"]
      }
    ]
  },
  externals: {
      moment: 'moment'
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "src", "index.html")
    }),
    new MiniCssExtractPlugin({
      filename: "[name].css"
    }),
    new PurgecssPlugin({
      paths: glob.sync(`${PATHS.src}/*.*`),
      styleExtensions: ['.css'],
      whitelist: ['whitelisted'],
      extractors: [
        {
          extractor: purgecssFromHtml,
          extensions: ['html']
        }
      ]
    }),
    new StyleExtHtmlWebpackPlugin({
        position: 'head-bottom'
    }),
    new ScriptExtHtmlWebpackPlugin({
        position: 'head-bottom',
        defaultAttribute: 'defer',
        inline: 'main'
    })
  ]
};

the css is inlined but the <link tag remains.

the scripts are inlined properly and a <script src=" is not present, so that works at least

OK so you need to have position: 'plugin' specified in order for it to replace the link with a style.

Is this documented anywhere? Either I'm blind/did a crappy job of reading/this isnt easy to come across. Maybe update the docs?