question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Screen Plugin in SSR mode is defaulting mobile sizes

See original GitHub issue

Describe the bug When SSR prerendering a view on desktop,a mobile screen is assumed causing a blink that can be very confusing when developing a responsive site (for example, using the grid view for mobile and normal view in desktop for a QTable, as shown in screenshot)

Expected behavior The Screen plugin should detect the platform (it can do that via request headers analyzing) and use a better heuristic to determine which size we have. I’v checked out the code and its doing this:

    if (isSSR === true) {
      $q.screen = this
      return
    }

(Making $q.screen = this defaults to mobile sizes) Screenshots Responsive table rendered as mobile on server and rendering as desktop on client side. Also the left drawer disappears server side and appears back on client Peek 13-09-2019 20-04

Platform (please complete the following information):

Operating System - Linux(4.19.69-1-MANJARO) - linux/x64 NodeJs - 12.9.1

Global packages NPM - 6.11.2 yarn - 1.17.3 @quasar/cli - 1.0.0 cordova - Not installed

Important local packages quasar - 1.1.0 – Build high-performance VueJS user interfaces (SPA, PWA, SSR, Mobile and Desktop) in record time @quasar/app - 1.0.6 – Quasar Framework local CLI @quasar/extras - 1.3.1 – Quasar Framework fonts, icons and animations vue - 2.6.10 – Reactive, component-oriented view layer for modern web interfaces. vue-router - 3.1.2 – Official router for Vue.js 2 vuex - 3.1.1 – state management for Vue.js electron - Not installed electron-packager - Not installed electron-builder - Not installed @babel/core - 7.5.5 – Babel compiler core. webpack - 4.39.3 – Packs CommonJs/AMD modules for the browser. Allows to split your codebase into multiple bundles, which can be loaded on demand. Support loaders to preprocess files, i.e. json, jsx, es7, css, less, … and your custom stuff. webpack-dev-server - 3.8.0 – Serves a webpack app. Updates the browser on changes. workbox-webpack-plugin - 4.3.1 – A plugin for your Webpack build process, helping you generate a manifest of local files that workbox-sw should precache. register-service-worker - 1.6.2 – Script for registering service worker, with hooks

Quasar App Extensions None installed

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:1
  • Comments:10 (5 by maintainers)

github_iconTop GitHub Comments

2reactions
rstoenescucommented, Sep 21, 2019

@jigarzon

Coming back to this. This would be a start, however there’s a major issue which I will detail at the end of the post.

// ui/src/install.js

// from
Screen.install($q, queues)
// to
Screen.install($q, queues, cfg)
// ui/src/plugins/Platform.js
import Platform, { isSSR, fromSSR } from './Platform.js'

export default {
  width: 0,
  height: 0,

  // ...

  install ($q, queues, { screen: { desktop = {} } = {} }) {
    if (desktop.width === void 0) {
      desktop.width = 1920
    }
    if (desktop.height === void 0) {
      desktop.height = 1080
    }

    let update = (force, w = window.innerWidth, h = window.innerHeight) => {
      if (h !== this.height) {
        this.height = h
      }

      if (w !== this.width) {
        this.width = w
      }
      else if (force !== true) {
        return
      }

      // ...
    }

    if (isSSR === true) {
      $q.screen = this
      queues.server.push(q => {
        if (q.platform.is.desktop === true) {
          // assume a default size;
          // on server-side, we have no idea on the client's window width
          update(true, desktop.width, desktop.height)
        }
      })
      return
    }

    // ...

    if (fromSSR === true) {
      if (Platform.is.desktop === true) {
        // avoid hydration errors
        update(true, desktop.width, desktop.height)
      }
      queues.takeover.push(start)
    }

    // ...
  }
}

The issue is that Screen is a singleton. Meaning you can’t just change its properties. Serving multiple SSR clients at the same time will render a non deterministic output, since there’s lots of async “gateways” in the code. One client might start being served while another changes the Screen singleton and it will end with rendered output with some content with the old values from Screen and the rest with the new changed values.

Furthermore, even if we go over this problem, then comes another one. Better Screen assumptions won’t mean that Layout components (header/footer/drawer/page/page-sticky/…) can be rendered on SSR as on a SPA, because they need to communicate with each other (for sizes, offsets, etc). So for SSR, it would matter a lot the order in which template declares those components. If a QHeader is before the QPage, then QPage would have the correct size, but not if QPage is declared before QHeader. On server-side we have: render QLayout --> hey, we have a QPage here, so render QPage --> ok, here is QPage, going back to QLayout render --> hey, we have a QDrawer, so render QDrawer --> …and so on. So rendering a subcomponent is “final” and if a subsequent component communicates with the layout, the first subcomponent is NOT getting these updates.

But let’s assume we get over this problem too. QDrawer does not uses a Vue state to render the left/right position because this would mean opening/closing by touch/mouse dragging requires a Vue rerender. And you need LOTS of re-renders while you swipe – this would make QDrawer act far far away from smooth (it would be ridiculously slow!). So to avoid this, QDrawer acts upon the DOM node directly (only for updating this part of the position). On SSR, again, you cannot do this (access DOM, cause there’s no DOM), so the initial state (even if it was decided that “hey, we’ll have it on layout”) cannot be applied (it will still be hidden until we takeover from client-side).

I’d continue, but it’s already a very long post (sorry) 😃

0reactions
jigarzoncommented, Sep 21, 2019

Thanks Razvan for such a big explanation. This is exactly what I wanted. I don’t understand reason #2, maybe I’m missing something, but what would be the problem if everyone ask size and it’s 1920x1080? Despite of declaration order? I understand that Layout components need to communicate and are dependant, but right now they can perfectly overcome the situation of a changing size viewport (when I manually resize screen size on browser). Sorry but maybe I’m losing the architecture of those. But I don’t understand why assuming an initial size would cause problems. The #3 situation regarding DOM can be the most challenging, but let me ask you: isn’t the biggest work for making it work on ssr already done? I mean: right now it comes correctly rendered on ssr on the correct side, and it disappears on the client side. So , have you tested that code and it’s breaking? Thanks a lot for going so deep in this, I really appreciate it. I think this is limiting a true implementation of the “code once use everywhere”. A ui designer reported that on a normal connection he had to wait almost 7 seconds with the screen broken because of this reason, and I could reproduce. Let me start working further with this next week, sorry last week I had no time.

El sáb., 21 sept. 2019 6:30, Razvan Stoenescu notifications@github.com escribió:

@jigarzon https://github.com/jigarzon

Coming back to this. This would be a start, however there’s a major issue which I will detail at the end of the post.

// ui/src/install.js // fromScreen.install($q, queues)// toScreen.install($q, queues, cfg)

// ui/src/plugins/Platform.jsimport Platform, { isSSR, fromSSR } from ‘./Platform.js’ export default { width: 0, height: 0,

// …

install ($q, queues, { screen: { desktop = {} } = {} }) { if (desktop.width === void 0) { desktop.width = 1920 } if (desktop.height === void 0) { desktop.height = 1080 }

let update = (force, w = window.innerWidth, h = window.innerHeight) => {
  if (h !== this.height) {
    this.height = h
  }

  if (w !== this.width) {
    this.width = w
  }
  else if (force !== true) {
    return
  }

  // ...
}

if (isSSR === true) {
  $q.screen = this
  queues.server.push(q => {
    if (q.platform.is.desktop === true) {
      // assume a default size;
      // on server-side, we have no idea on the client's window width
      update(true, desktop.width, desktop.height)
    }
  })
  return
}

// ...

if (fromSSR === true) {
  if (Platform.is.desktop === true) {
    // avoid hydration errors
    update(true, desktop.width, desktop.height)
  }
  queues.takeover.push(start)
}

// ...

} }

The issue is that Screen is a singleton. Meaning you can’t just change its properties. Serving multiple SSR clients at the same time will render a non deterministic output, since there’s lots of async “gateways” in the code. One client might start being served while another changes the Screen singleton and it will end with rendered output with some content with the old values from Screen and the rest with the new changed values.

Furthermore, even if we go over this problem, then comes another one. Better Screen assumptions won’t mean that Layout components (header/footer/drawer/page/page-sticky/…) can be rendered on SSR as on a SPA, because they need to communicate with each other (for sizes, offsets, etc). So for SSR, it would matter a lot the order in which template declares those components. If a QHeader is before the QPage, then QPage would have the correct size, but not if QPage is declared before QHeader. On server-side we have: render QLayout --> hey, we have a QPage here, so render QPage --> ok, here is QPage, going back to QLayout render --> hey, we have a QDrawer, so render QDrawer --> …and so on. So rendering a subcomponent is “final” and if a subsequent component communicates with the layout, the first subcomponent is NOT getting these updates.

But let’s assume we get over this problem too. QDrawer does not uses a Vue state to render the left/right position because this would mean opening/closing by touch/mouse dragging requires a Vue rerender. And you need LOTS of re-renders while you swipe – this would make QDrawer act far far away from smooth (it would be ridiculously slow!). So to avoid this, QDrawer acts upon the DOM node directly (only for updating this part of the position). On SSR, again, you cannot do this (access DOM, cause there’s no DOM), so the initial state (even if it was decided that “hey, we’ll have it on layout”) cannot be applied (it will still be hidden until we takeover from client-side).

I’d continue, but it’s already a very long post (sorry) 😃

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/quasarframework/quasar/issues/5079?email_source=notifications&email_token=ACIHOSWWSN6VCG73TQT3IC3QKXSSDA5CNFSM4IWVBANKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD7IOGVQ#issuecomment-533783382, or mute the thread https://github.com/notifications/unsubscribe-auth/ACIHOSWLUZGFO437NNKABXTQKXSSDANCNFSM4IWVBANA .

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to do SSR for responsive apps without knowing the ...
Try to guess the user's device screen size, and render something that might not be optimal for their screen size. Because right now,...
Read more >
Stitches — CSS-in-JS with near-zero runtime
With multiple variants, compound variants, and default variants, you can design composable component APIs which are typed automatically.
Read more >
Server-side rendering with Vue and Nuxt.js - LogRocket Blog
Learn about server-side rendering (SSR) and client-side rendering (CSR), how its evolved, and how to use implement it with Nuxt.js and Vue.
Read more >
Multiple resolutions - Godot Docs
The Stretch Mode setting defines how the base size is stretched to fit the resolution of the window or screen. Stretch Mode =...
Read more >
Mobile Rendering | Unreal Engine 4.27 Documentation
By default, only high---end mobile devices enable r.Mobile.PixelProjectedReflectionQuality in [Project]Scalability.ini . Other Topics. Reference.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found