Skip to content
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

Weird loading order after upgrading to Webpack 4 #2016

Closed
doutatsu opened this issue Mar 17, 2019 · 6 comments
Closed

Weird loading order after upgrading to Webpack 4 #2016

doutatsu opened this issue Mar 17, 2019 · 6 comments

Comments

@doutatsu
Copy link

doutatsu commented Mar 17, 2019

I've upgraded to webpack 4 for Rails. I use it with Vue.js 2. I also use chunks in my configuration. But since upgrading, I've noticed that the page load order is weird. The page loads HTML before styles and JS has been loaded, which is not what happened before. I've attached links to the videos for before and after to understand the issue better.

I've been looking in here and everywhere to find anyone with the same issue, but I couldn't...

With Webpack 3(before)

With Webpack 4(after)

As you can see, it starts of with just HTML, no css applied at all and then loads it bit by bit. ElementUI styles are applied at the very end, as can be seen from the Sign in button becoming styles...

Here are my configuration files:

Dev Config

const environment = require('./environment')
const BundleAnalyzerPlugin =
  require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

environment.plugins.append(
  'BundleAnalyzerPlugin',
  new BundleAnalyzerPlugin()
)


module.exports = environment.toWebpackConfig()

Env (shared) Config

const { environment } = require('@rails/webpacker')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const vue = require('./loaders/vue')

const additionalConfig = {
  plugins: [
    new VueLoaderPlugin(),
  ],
  optimization: {
    splitChunks: {
      cacheGroups: {
        default: false,
        vendor: {
          name: 'vendor',
          chunks: 'all',
          test: /[\\/]node_modules[\\/]/,
          minChunks: 3,
        },
      }
    }
  },
  module: {
    rules: [{
      test: /\.pug$/,
      loader: 'pug-plain-loader'
    }, {
      test: /\.sass$/,
      use: ['vue-style-loader', 'css-loader', 'sass-loader']
    }]
  },
  output: {
  },
  devtool: 'source-map',
}

environment.config.merge(additionalConfig);

environment.loaders.prepend('vue', vue)

module.exports = environment

Pack related to the page in the video

import 'element-ui/lib/theme-chalk/index.css';
import 'element-ui/lib/theme-chalk/display.css';
import 'flexboxgrid/css/flexboxgrid.css';

import Vue from 'vue/dist/vue.esm';
import VueCookies from 'vue-cookies';
import { DateTime } from 'luxon';

// ElementUI Components
import ElementUI from 'element-ui';
import locale from 'element-ui/lib/locale/lang/en';

// Custom Components
import TextSection from '../components/TextSection.vue';
import TopNavigation from '../components/navigation/TheTopNavigation.vue';

import { store } from '../store';

Vue.use(ElementUI, { locale });
Vue.use(VueCookies);

const app = new Vue({
  el: '#app',
  store,
  mounted() {
    var selector = document.querySelector("#app");
    var errors   = selector.dataset.errors;

    if (selector) {
      store.commit('base_states/authenticate',
        JSON.parse(selector.dataset.signedIn)
      );
    }

    if (errors) {
      this.$message({
        dangerouslyUseHTMLString: true,
        message: JSON.parse(errors).join("\n"),
        type: 'error'
      });
    }
  },
  components: { TextSection, TopNavigation },
});

if (!app.$cookies.get('timezone')) {
  app.$cookies.set("timezone", DateTime.local().zoneName);
}

Rails view for that page

#app{ data: { signed_in: "#{user_signed_in?}", errors: flash[:errors] } }
  .landing-top
    .row.banner
      %top-navigation{ ":user" => user, "logo" => logo }
      .row.start-sm.around-sm.middle-sm.center-xs.landing-hero
        .col-lg-4.col-md-4.col-sm-4.col-xs-12
          %h1= t 'static.banner.headline'
          %p= t 'static.banner.subtitle'
          .actions
            %a.no-decoration{ class: "el-button el-button--success", href: "/events" }
              See upcoming events
        .col-lg-6.col-md-6.col-sm-6.col-xs-12
          = video_tag("https://s3.eu-west-2.amazonaws.com/vras-assets/product_preview_new.webm",
                      poster: preview_poster,
                      class: "preview-video", autoplay: "true",
                      muted: "true",          loop: "true" )
  .landing-body.site-padding
    .row.around-lg.middle-lg.middle-md.features
      .col-md-4.col-xs-12.feature-column
        = inline_svg 'icons/potion.svg', class: 'svg-icon'
        %text-section{ "title" => t('static.first_section.title_one'),
                       "text"  => t('static.first_section.text_one') }
      .col-md-4.col-xs-12.feature-column
        = inline_svg 'icons/map.svg', class: 'svg-icon'
        %text-section{ "title" => t('static.first_section.title_two'),
                       "text"  => t('static.first_section.text_two') }
      .col-md-4.col-xs-12.feature-column
        = inline_svg 'icons/unicorn.svg', class: 'svg-icon'
        %text-section{ "title" => t('static.first_section.title_third'),
                       "text"  => t('static.first_section.text_third') }
  .row.center-lg.center-xs.video-showcase
    .col-lg-10.col-md-10.col-xs-12
      = video_tag('https://s3.eu-west-2.amazonaws.com/vras-assets/preview.mp4',
                  poster: 'meta_cover.jpg',
                  class: 'preview-video',
                  autoplay: 'true',
                  muted: 'true',
                  loop: 'true')
    .col-lg-8.col-md-8.col-xs-10{ style: "padding-top: 20px" }
      %h3
        = image_tag("bigscreen_logo.png", width: "250px")
        %br
        = t('static.third_section.title')
      %text-section{ "text"  => t('static.third_section.text') }
  .landing-body.site-padding
    .row.around-lg.middle-lg.middle-md{ style: "margin-bottom: 100px" }
      .col-lg-6.col-md-6.col-xs-12
        %text-section{ "title" => t('static.second_section.title'),
                       "text"  => t('static.second_section.text') }
      .col-lg-6.col-md-6.col-xs-12.first-xs.last-lg.last-md{ style: "text-align: center" }
        %iframe{:title => "Discord Widget", :allowtransparency => "true", :frameborder => "0", :height => "519", :src => "https://discordapp.com/widget?id=402246704252059648&theme=dark", :width => "320"}
  = render "footer"

= javascript_packs_with_chunks_tag 'landing_page'
= stylesheet_packs_with_chunks_tag 'landing_page'
@doutatsu
Copy link
Author

doutatsu commented Jun 5, 2019

My research led me to believe its this issue:

This is happening because you're bundling with style-loader, which puts your CSS as a string inside your Javascript bundle.

So the HTML will render (very fast) while the browser is parsing your JS bundle (very slow). Towards the end of that bundle, the browser will find the module containing your CSS string. Then it'll parse that and apply the styles with Javascript.

Following this, I came up with two solutions:

  1. I've extracted the CSS I need into Rails app/assets folder, to load outside of webpack and Vue. That's the critical CSS I wanted, like footer and header, and margin rules

  2. While I didn't quite get to the bottom of this, but I have found an interim simple solution to get back to the original webpack 3 behaviours, of JS/CSS blocking the loading of the page:

window.addEventListener('load', () => {
  ### Initialise Vue app
});

It forces to wait till window loads all the JS/CSS assets, before loading my Vue. This worked on all, but one page, as everything except landing page is built from Vue components only.

For landing page, I've adopted another suggestion, to delegate JS/CSS pack load to the head, with a yield. So now we are also blocking execution on the landing page as well. Combined, I am seeing exactly the same loading behaviour as before.

Hope this helps some of you here. The final solution I want to eventually do is to adopt a skeleton pattern, where I have only outline of my components to show and then they would fill out as the page loads.

@jakeNiemiec
Copy link
Member

From the v4 upgrade guide, did you have extract_css set?:

Due to the change in #1625, you'll want to make sure that extract_css is set to true for the default environment in webpacker.yml if you want to have Webpacker supply your CSS.

Putting the extracted styles in the <head> is the ideal solution. If your JS is too fast, you can defer it with https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script.

You can also break things up with dynamic import so that things load faster due to http/2.

@infantopratik
Copy link

I was stuck with the same problem in my react app and used the following plugin to extract the CSS (as webpack 4 requires it). Apparently, this is a required step to add in your webpack config, which I had missed earlier.
https://github.com/webpack-contrib/mini-css-extract-plugin

@jakeNiemiec
Copy link
Member

@infantopratik, Webpacker should be automatically adding this plugin for you:

result.append(
'MiniCssExtract',
new MiniCssExtractPlugin({
filename: 'css/[name]-[contenthash:8].css',
chunkFilename: 'css/[name]-[contenthash:8].chunk.css'
})
)

@doutatsu
Copy link
Author

From the v4 upgrade guide, did you have extract_css set?:

@jakeNiemiec I was aware of this and kept it as false in default, hence the development environment, just as the unedited webpacker.yml is. But of course it's set to true in production environment.

That's the reason why production never looked as bad as on development, but it still had a bit of ordering issue, which I fixed by adding that extra load and critical CSS.

I also do use chunks for more efficient caching.

@infantopratik as jake said, Webpack provides this by default and I didn't need to it manually myself. I also at some point thought I needed it, but looking into webpacker source, I saw that it gets added

@guillaumebriday
Copy link
Member

Is this still an issue?

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

4 participants