Webpacker: FOUC with Webpacker 4.0.0.pre.pre.2 and webpack-dev-server with HMR: true

Created on 1 Jun 2018  路  11Comments  路  Source: rails/webpacker

I'm getting a flash of unstyled content whenever I reload a page in the most current version of Webpacker 4.x.

I'm mostly using the vanilla config; however, as far as I can tell this takes into account hmr being enabled and switches to style-loader from mini-css-extract-plugin. It's worth mentioning that this is an upgrade from Webpacker v3.5 to 4; I did run rails webpacker:install after the upgrade.

Here's my config:

# Gemfile.lock
webpacker (4.0.0.pre.pre.2)

...

# package.json
"dependencies": {
    "@rails/webpacker": "4.0.0-pre.2",
    "autoprefixer": "^8.5.1",
    "babel-loader": "^7.1.4",
    "bootstrap-sass": "^3.3.7",
    "case-sensitive-paths-webpack-plugin": "^2.1.2",
    "clean-webpack-plugin": "^0.1.19",
    "compression-webpack-plugin": "^1.1.11",
    "css-hot-loader": "^1.3.9",
    "css-loader": "^0.28.11",
    "file-loader": "^1.1.11",
    "font-awesome": "^4.7.0",
    "jquery": "^3.3.1",
    "jquery-ujs": "^1.2.2",
    "mini-css-extract-plugin": "^0.4.0",
    "moment": "^2.22.1",
    "node": "^10.2.0",
    "node-sass": "^4.9.0",
    "popper.js": "^1.14.3",
    "postcss-loader": "^2.1.5",
    "precss": "^3.1.2",
    "resolve-url-loader": "^2.3.0",
    "sass-loader": "^7.0.1",
    "style-loader": "^0.21.0",
    "uglifyjs-webpack-plugin": "^1.2.5",
    "url-loader": "^1.0.1",
    "webpack-manifest-plugin": "^2.0.3",
    "webpack-merge": "^4.1.2"
"devDependencies": {
    "webpack-cli": "^2.1.4",
    "webpack-dev-server": "^3.1.4"

...

# webpacker.yml
default: &default
  source_path: app/javascript
  source_entry_path: packs
  public_output_path: packs
  cache_path: tmp/cache/webpacker

  # Additional paths webpack should lookup modules
  # ['app/assets', 'engine/foo/app/assets']
  resolved_paths: ['vendor/assets/javascripts', 'vendor/assets/stylesheets', 'public/javascripts', 'public/assets/images']

  # Reload manifest.json on all requests so we reload latest compiled packs
  cache_manifest: false

  extensions:
    - .js
    - .jsx
    - .sass
    - .scss
    - .css
    - .module.sass
    - .module.scss
    - .module.css
    - .png
    - .svg
    - .gif
    - .jpeg
    - .jpg

development:
  <<: *default
  compile: true

  # Reference: https://webpack.js.org/configuration/dev-server/
  dev_server:
    https: false
    host: localhost
    port: 3035
    public: localhost:3035
    hmr: true
    # Inline should be set to true if using HMR
    inline: true
    overlay: true
    compress: true
    disable_host_check: true
    use_local_ip: false
    quiet: false
    headers:
      'Access-Control-Allow-Origin': '*'
    watch_options:
      ignored: /node_modules/

test: &test
  <<: *default

  # Compile test packs to a separate directory
  public_output_path: packs-test

production: &production
  <<: *default

  # Production depends on precompilation of packs prior to booting for performance.
  compile: false

  # Cache manifest.json for performance
  cache_manifest: true

staging:
  <<: *production

qa:
  <<: *production

integration:
  <<: *production

...

# environment.js
const { environment } = require('@rails/webpacker')
const { env } = require('process')
const { resolve } = require('path');
const rootPath = resolve(__dirname, '../..');

const CleanWebpackPlugin = require('clean-webpack-plugin');

const fileLoader = environment.loaders.get('file');
fileLoader.use = [
  {
    loader: 'url-loader',
    options: {
      name: '[path][name]-[hash].[ext]',
      limit: 10000,
      context: 'app/assets',
    }
  }
]
fileLoader.test = /\.(jpg|jpeg|png|gif|tiff|ico|svg)$/i

environment.loaders.append('font', {
  test: /\.(eot|otf|ttf|woff|woff2)$/i,
  use: [
    {
      loader: 'url-loader',
      options: {
        name: '[path][name]-[hash].[ext]',
        limit: 10000,
        context: 'node_modules',
      }
    }
  ]
})

const pathsToClean = [
  'packs',
  'packs-test'
]
const cleanOptions = {
  root: resolve(rootPath, 'public'),
  verbose: true,
}
if (env.NODE_ENV !== 'test') environment.plugins.insert('CleanWebpackPlugin', new CleanWebpackPlugin(pathsToClean, cleanOptions));

module.exports = environment

development.js is vanilla. I'm aware I've a lot of dependencies I can remove from package.json (mostly from playing around with an earlier version of Webpacker) but I assume they're not at fault here.

Apologies if this isn't a ton of info to go on, and am happy to provide anything required. Kudos =)


Edit a day or so later:

I'm going to add a bit to this. When I set hmr and inline to false and have webpack-dev-server running, I had expected to begin seeing the CSS without the flash of unstyled content at the beginning. Instead, I'm _never_ seeing my compiled css and the page remains unstyled.

I'd guess this is somehow due to a misconfiguration on my part with Webpacker; however, I'd have nonetheless expected the default configuration of Webpacker to be close enough to optimal to not require much tinkering on my part, so I'm mentioning this here. I am, of course, also curious what I'm doing wrong 馃槃

Most helpful comment

You probably want to set extract_css: true in development that way CSS would be extracted out of JS into a separate bundle and you won't see flashing due to CSS in JS.

All 11 comments

I tried a ton of configurations to get hmr working, and nothing works.

I am on 4.0.0.pre.pre.2, and I tried adding each of mini-css-extract-plugin and extract-css-chunks-webpack-plugin

extract-text-webpack-plugin no longer works with Webpack 4.

I've got the same thing, using typescript (awesome-typescript-loader), and scss file, imported into my js.

I'm also seeing the FOUC with webpacker 4.0.2 and Rails 5.2, when using the webpack-dev-server in the Rails development environment

You probably want to set extract_css: true in development that way CSS would be extracted out of JS into a separate bundle and you won't see flashing due to CSS in JS.

webpack-dev-server uses the style-loader which injects the styles from the JS file. To give you an idea of the process:

*something changes* (If you only changed a js file, start at 5.)

  1. compile scss (馃嵕 biggest bottleneck 馃嵕)
  2. run through postcss
  3. pass to css-loader to convert styles into CommonJS
  4. pass to style-loader to make a script that injects cjs styles into the <head>
  5. reimport your CommonJS styles in the js file and re-bundle that file
  6. convert the file to js if you use typescript-loader like @G-Rath
  7. send the new js chunk (along with cjs styles) over a websocket to the browser and execute
  8. inject the cjs styles into the <head>

This issue has also been discussed a lot by non-webpackER folks, eg:

mini-css-extract-plugin works for me, but you need to get it just right with weird things like this since mini-css-extract-plugin does not work with WPD:

if (window.isHot()) {
  import('./styles.scss');
}

Once you start to understand the steps, you can connect the dots in a way that makes sense for your stack. Hope this helps.

@gauravtiwari setting extract_css = true in development requires that you use <%= stylesheet_pack_tag %> in order to load css.

In general, our team is experiencing this pain point: https://github.com/rails/webpacker/issues/1720#issuecomment-449379412.

Which requires you add the right style sheets with stylesheet_pack_tag whenever a react component uses a stylesheet. Is there a better way to do this?

Screenshot 2019-05-18 17 37 55

@derrickmar To solve this problem, we set the style extractor to only extract styles from files following a naming convention, eg: name.index.scss. This allows your react components to import their local styles via JS, thus, you would not need to include those tags.

Here is the regex for the MiniCssExtractPlugin loader: test: /(.+?)\.index\.scss$/
Here is the regex for the local styles loader: test: /^((?!\.index).)+\.s?css$/

This is a batteries not included solution, you will need to somehow override webpackERs internals in order to achieve the behavior you want: https://github.com/rails/webpacker/blob/41d79c96187154b5485289e8c3c42428dd819bfc/package/utils/get_style_rule.js#L41

@jakeNiemiec that's an interesting idea but that doesn't solve FOUC because local styles are still imported via JS as you said.

@derrickmar doesn't solve FOUC because local styles are still imported via JS as you said

A: If you add the JS in your <head>, there should be no FOUC. There will always be a bit of FOUC with webpack-dev-server since the code is optimized for quick code changes, not render/loading speed. But, this new development may be the behavior you are looking for: https://github.com/rails/webpacker/issues/2062#issuecomment-490558992

B: If you intend for your app to be rendered server-side, you would need to seek help in other SSR-related issues in this repo like https://github.com/rails/webpacker/issues/2085 or https://github.com/rails/webpacker/issues/2074. (I don't use SSR)

I was able to prevent FOUC and enable HMR for stylesheets like this:

In Webpacker development.js file:

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

// Replace default MiniCssExtractPlugin loader config to enable HMR
// You might want to change "sass" to the loader that you actually use
// Default loaders are `css`, `sass`, `moduleCss` or `moduleSass`
environment.loaders.get('sass').use[0] = {
  loader: MiniCssExtractPlugin.loader,
  options: {
    hmr: true,
  },
};

// Remove [hash] from filename in the plugin so HMR is able to match the file to refresh
environment.plugins.get('MiniCssExtract').options.filename = 'css/[name].css';

In webpacker.yml, enable CSS extraction for the development env:

development:
  extract_css: true
  # ...
  dev_server:
    hmr: true
    inline: true
    # ...

Ensure you have <%= stylesheet_pack_tag "your_pack_name" %> somewhere in your <head> in development too.

Can this issue be closed ?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

naps62 picture naps62  路  3Comments

eriknygren picture eriknygren  路  3Comments

christianrojas picture christianrojas  路  3Comments

ijdickinson picture ijdickinson  路  3Comments

suhomozgy-andrey picture suhomozgy-andrey  路  3Comments