Skip to content

CSS with [contenthash] not refreshing with HMR, webpack-dev-middleware #1089

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
YellowSaleTag opened this issue Mar 17, 2024 · 16 comments
Open

Comments

@YellowSaleTag
Copy link

YellowSaleTag commented Mar 17, 2024

Bug report

CSS with [contenthash] not refreshing with HMR, webpack-dev-middleware

1. We see our stylesheet loaded in the document with the expected CSS.

Screenshot 2024-03-17 at 2 24 45 AM

2. We make an update to our CSS. We change the padding from 50 to 75 px.

Screenshot 2024-03-17 at 2 27 24 AM

3. We see a request in the network tab for the CSS update. However, the contents is stale. It has the old value of 50 px.

Screenshot 2024-03-17 at 2 31 01 AM

4. We refresh the page using the refresh button and see the updated value of 75 px.

Screenshot 2024-03-17 at 2 34 53 AM

Package Versions

"express": "4.18.2",
"webpack": "5.90.3",
"webpack-dev-middleware": "7.0.0",
"webpack-hot-middleware": "2.26.1",
"webpack-manifest-plugin": "5.0.0"

Webpack Config (relevant parts)

{
  name: '<build-name>',
  mode: 'development',
  devtool,
  context: __dirname,
  entry: [`webpack-hot-middleware/client?name=<build-name>&path=${publicPath}__webpack_hmr`],
  output: {
    chunkFilename: '[name].[contenthash].js',
    filename: '[name].[contenthash].js',
    path: path.resolve(__dirname, 'dist', 'browser'),
    publicPath
  },
  optimization: {
    splitChunks: {
      chunks: 'all'
    }
  },
  module: {
    rules: [
      {
        test: /\.(css)$/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1, // https://webpack.js.org/loaders/css-loader/#importloaders
              modules: {
                localIdentName: '[hash:8]'
              }
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new HtmlWebpackPlugin({
      filename: 'index.html',
      inject: false,
      template: path.resolve(__dirname, 'src', 'targets', 'browser', 'index-browser.html')
    }),
    new MiniCssExtractPlugin({
      chunkFilename: '[name].[contenthash].css',
      filename: '[name].[contenthash].css'
    })
  ]
}

Middleware Config

webpackDevMiddleware(compiler, { serverSideRender: true })
webpackHotMiddleware(compiler, { path: `/dist/__webpack_hmr` })

Actual Behavior

  1. We see our stylesheet loaded in the document with the expected CSS.
  2. We make an update to our CSS. We change the padding from 50 to 75 px.
  3. We see a request in the network tab for the CSS update. However, the contents is stale. It has the old value of 50 px.
  4. We refresh the page using the refresh button and see the updated value of 75 px.

Expected Behavior

  1. The requested CSS via hot reload update should render the new CSS values and the page should update.

How Do We Reproduce?

Minimal configuration provided above for now...

Please paste the results of npx webpack-cli info here, and mention other relevant information

  System:
    OS: macOS 14.3.1
    CPU: (10) arm64 Apple M1 Pro
    Memory: 1.03 GB / 32.00 GB
  Binaries:
    Node: 18.19.0 - ~/.nvm/versions/node/v18.19.0/bin/node
    Yarn: 1.22.18 - /opt/homebrew/bin/yarn
    npm: 10.2.3 - ~/.nvm/versions/node/v18.19.0/bin/npm
  Browsers:
    Edge: 122.0.2365.92
    Safari: 17.3.1
  Packages:
    babel-loader: 9.1.3 => 9.1.3 
    copy-webpack-plugin: 12.0.2 => 12.0.2 
    css-loader: 6.8.1 => 6.8.1 
    html-webpack-plugin: 5.5.4 => 5.5.4 
    style-loader: 3.3.3 => 3.3.3 
    webpack: 5.90.3 => 5.90.3 
    webpack-cli: 5.1.4 => 5.1.4 
    webpack-hot-middleware: 2.26.1 => 2.26.1 
    webpack-manifest-plugin: 5.0.0 => 5.0.0 
  Global Packages:
    webpack: 5.90.3
@alexander-akait
Copy link
Member

Please create a github repo with the reproducible example of the problem, thank you

@YellowSaleTag
Copy link
Author

YellowSaleTag commented Mar 17, 2024

Thank you so much for taking the time to respond. I created a minimal example here.

@YellowSaleTag
Copy link
Author

YellowSaleTag commented Mar 17, 2024

I found the root cause.

It breaks when:

new MiniCssExtractPlugin({
  chunkFilename: '[name].[contenthash].id-[id].css',
  filename: '[name].[contenthash].css'
}),

It works when:

new MiniCssExtractPlugin({
  chunkFilename: '[name].[contenthash].id-[id].css',
  filename: '[name].css'
})

It seems [contenthash] with hot module reloading does not work. I use content hash to avoid caching of the CSS file.

Is there another way I can avoid caching of the css file?

I updated the minimal PoC to reflect this. Here too.

Attached is a screenshot of the PoC with [contenthash] and a stale CSS payload in hot reload network request.
Screenshot 2024-03-17 at 4 30 54 PM

@YellowSaleTag YellowSaleTag reopened this Mar 17, 2024
@YellowSaleTag YellowSaleTag changed the title CSS not automatically loading with HMR, webpack-dev-middleware CSS with [contenthash] not refreshing with HMR, webpack-dev-middleware Mar 17, 2024
@alexander-akait
Copy link
Member

alexander-akait commented Mar 18, 2024

Weird, I can't reproduce, even more, we have test cases for this...

@alexander-akait
Copy link
Member

alexander-akait commented Mar 18, 2024

Can you provide full steps using your repo above - https://github.com/YellowSaleTag/hmr-stale-css-payload/?

@YellowSaleTag
Copy link
Author

YellowSaleTag commented Mar 19, 2024

Hmm interesting, sure see the following.

  1. npm i
  2. npm run build
  3. npm run start
  4. browse localhost:3000
  5. inspect the network logs to see the main.css request and payload. confirm the html rule has padding of 200px
  6. change the padding value in ./app/components/App/app.css to 50 px
  7. confirm the hot update network request - main.css update request should have a payload with an html padding value of 50px
  8. kill the app
  9. modify ./webpack.config.js both lines 89 and 128. At these lines change [name].css to [name].[contenthash].css.
  10. run npm run build
  11. run npm run start
  12. follow steps 4, 5 (confirm 50px), 6 (update to 200px).
  13. follow step 7. except when you confirm the hot update request for main.[contenthash].css you'll find padding: 50px even though you changed it to 200px

@MiumMi
Copy link

MiumMi commented Jul 22, 2024

how did you resolve this problem? I found this problem in my project too

@YellowSaleTag
Copy link
Author

YellowSaleTag commented Jul 22, 2024

@evenstensberg
Copy link
Member

Have you tried generating your own unique hash?

https://webpack.js.org/plugins/mini-css-extract-plugin/#filename-option-as-function

@MiumMi
Copy link

MiumMi commented Jul 23, 2024

@alexander-akait
Copy link
Member

Found very easy fix - webpack/webpack@193b712#diff-9cd4ea60898beb7155d8dc56617fbb77b7688cca9c32adf59b9a656548ffd071R69, we just need to add three lines to CSS module, if someone want to fix it here - PR welcome

@davidk55
Copy link

As this relates to getting hot reloading with CSS files in WordPress working, I would really like to get this fixed. Since I am not really familiar with Webpack codebase, could you elaborate how this can be fixed?

@gziolo
Copy link

gziolo commented Apr 16, 2025

There are two issues with hot reloading CSS files in WordPress. Many CSS files are located inside the iframe, and the plugin operates only on the main document level, examples:

const elements = document.querySelectorAll("link");

const elements = document.querySelectorAll("link");

When I was debugging it, I also noticed that the plugin always calls reloadAll as it isn't able to correctly match the modified style to reload. The problem was in the following logic:

src.some(
/**
* @param {string} url
*/
// eslint-disable-next-line array-callback-return
(url) => {
if (href.indexOf(src) > -1) {
ret = url;
}
}
);

src is an array of strings, so the check should be modified to use the value from the callback passed to src.some call:

- if (href.indexOf(src) > -1) {
+ if (href.indexOf(url) > -1) {

@alexander-akait
Copy link
Member

Many CSS files are located inside the iframe, and the plugin operates only on the main document level, examples:

We don't support hot reloading in iframes officially. But I am fine to improve it. Can you provide reproducible test repo?

@gziolo
Copy link

gziolo commented Apr 18, 2025

A quick note. There was a contribution to the WordPress project that explored adding hot reloading for CSS in WordPress/gutenberg#64444. The relevant code for hot reloading support in iframes was pretty straightforward:

function getAllStylesheets( win ) {
	const links = [
		...win.document.querySelectorAll( "link[rel='stylesheet']" ),
	];

	// Recursively loop through all frames with the same origin.
	for ( let i = 0; i < win.frames.length; i++ ) {
		try {
			links.push( ...getAllStylesheets( win.frames[ i ] ) );
		} catch ( err ) {
			// Ignore same origin policy errors.
		}
	}

	return links;

Sure, I can think of how to set up a reproducible repo to help with that.

In this particular case discussed, the challenge is that WordPress provides tools for development like @wordpress/scripts npm package that depends on mini-css-extract-plugin. To test everything, you also need a WordPress instance. You could replicate everything with a few commands when you have Docker installed:

npx @wordpress/create-block@latest my-block --wp-env
cd my-block
npx wp-env start
npm start -- --hot

Next step is to open http://localhost:8888/wp-admin/ in the browser, log in with username: admin and password password. Go to http://localhost:8888/wp-admin/post.php?post=1&action=edit and insert "My Block" block. Then you can edit a CSS file in the scaffolded project, example: src/my-block/editor.scss. Everything works on the plugin side, but CSS is in the iframe so it doesn't reload:

Image

I also recorded a screencast showing how I test it in the browser. On the second screen I edited CSS in the referenced file. I had to refresh the page to see the result.

wp-scripts-start-hot.mov

By the way, please note that the plugin correctly detected the updated file, but the action to "Reload all css" was executed. It might be related to the iframe issue, but it could be also related to the code path I highlighted in #1089 (comment).

@gziolo
Copy link

gziolo commented Apr 18, 2025

By the way, please note that the plugin correctly detected the updated file, but the action to "Reload all css" was executed. It might be related to the iframe issue, but it could be also related to the code path I highlighted in #1089 (comment).

I included another screencast where I disabled support for iframes to show that the CSS is correctly reloaded with HMR and the same app:

wp-scripts-hot-no-iframe.mov

Still "Reload all css" was executed instread of targeting the single specific file I edit in the editor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants