DEV Community

Cover image for Better Performance using Dynamic Code Splitting in Gatsby with loadable-components

Better Performance using Dynamic Code Splitting in Gatsby with loadable-components

Tommy May III on June 06, 2019

Preface I use Gatsby at work and in my personal projects because I believe it is the best tool out there right now in terms of efficienc...
Collapse
 
lilianchisca profile image
Lilian Chisca

Hey Tommy,

Just wanted to thank you for sharing this solution with us, this is the only article I was able to find on gatsby addressing this issue. Was just wondering if you have a working example of that pseudo code you outlined at the end of your examples. Would you be willing to share it with us? :-)

Thank you again for writing this article and outlining your solution!

Collapse
 
itmayziii profile image
Tommy May III

Hey Lilian,

I'm happy to share the real code now as we actually made the decision to remove React altogether for our simple site so this blog post quickly went out of date for us. I actually posted on Reddit a few days back with some open source plugins we created to make removing React easy reddit.com/r/gatsbyjs/comments/c6z...

The real code is below

  const modules = useMemo(() => {
    return page.modules.map((module, index) => {
      const { moduleFileName, shouldLoadJavascript } = getFromMap(appModules, module.id)
      if (isServer()) {
        // The server should always render the module so we get the static HTML.
        return renderModule(moduleFileName, module, index, isBusinessOpen)
      }

      const wasUserPreviouslyOnSite = window.history.state
      const htmlEl = document.querySelector(`[data-module-index="${index.toString()}"]`)
      if (htmlEl && !shouldLoadJavascript && !wasUserPreviouslyOnSite) {
        return staticRenderModule(index, htmlEl)
      }

      const fallback = htmlEl ? staticRenderModule(index, htmlEl) : null
      return renderModule(moduleFileName, module, index, fallback, isBusinessOpen)
    })

    // Render the module with the HTML currently rendered from the static HTML file without importing the javascript.
    function staticRenderModule (index, htmlEl) {
      return (<section key={index.toString()} dangerouslySetInnerHTML={{ __html: htmlEl.innerHTML }}/>)
    }

    // Render the module with all the javascript.
    function renderModule (modulePath, module, index, fallback = null, isBusinessOpen) {
      const ModuleComponent = loadable(() => import(`../modules/${modulePath}`))
      return (
        <ModuleContext.Provider key={index.toString()} value={{ lazyLoad: index > 1 }}>
          <ModuleComponent
            moduleIndex={index}
            fallback={fallback}
            module={module.data || {}} webPage={page}
            isBusinessOpen={isBusinessOpen}/>
        </ModuleContext.Provider>
      )
    }
  }, [page])
Enter fullscreen mode Exit fullscreen mode

As you can see for each module we check whether or not the module should load javascript by checking the shouldLoadJavascript flag, if it does not load javascript then we just render the module statically. Here is our module map file that the getFromMap function is working on

export const appModules = {
  'scms-image-list': { moduleFileName: 'image-list', shouldLoadJavascript: false },
  'scms-footer-cta-bar': { moduleFileName: 'footer-cta-bar', shouldLoadJavascript: false },
  'scms-hero-section': { moduleFileName: 'hero/hero', shouldLoadJavascript: true },
  'scms-copy-left-image-right': { moduleFileName: 'copy-image', shouldLoadJavascript: false },
  'scms-full-width-table': { moduleFileName: 'full-width-table', shouldLoadJavascript: false },
  'scms-quick-links-menu': { moduleFileName: 'quick-links-menu', shouldLoadJavascript: false },
  'scms-call-to-action-bar': { moduleFileName: 'cta-bar', shouldLoadJavascript: false },
  'scms-dumpsters-difference': { moduleFileName: 'dumpsters-difference', shouldLoadJavascript: false },
  'scms-icon-list-short': { moduleFileName: 'icon-list-short-desc', shouldLoadJavascript: false },
  'scms-copy-icon-cards': { moduleFileName: 'copy-icon-cards', shouldLoadJavascript: false },
  'scms-icon-list-long': { moduleFileName: 'icon-list-long-desc', shouldLoadJavascript: false },
  'scms-locations': { moduleFileName: 'locations/locations', shouldLoadJavascript: false },
  'scms-full-width-copy': { moduleFileName: 'full-width-copy', shouldLoadJavascript: false },
  'scms-product-cards': { moduleFileName: 'product-cards-stacked/product-cards-stacked', shouldLoadJavascript: false },
  'scms-product-cards-main': { moduleFileName: 'product-cards-main/product-cards-main', shouldLoadJavascript: false },
  'scms-single-product-feature': { moduleFileName: 'single-product-feature/single-product-feature', shouldLoadJavascript: false },
  'scms-project-grid': { moduleFileName: 'project-grid/project-grid', shouldLoadJavascript: false },
  'scms-homepage-hero-section': { moduleFileName: 'homepage-hero/homepage-hero', shouldLoadJavascript: true },
  'scms-logo-banner': { moduleFileName: 'logo-banner', shouldLoadJavascript: true },
  'scms-testimonials': { moduleFileName: 'testimonials/testimonials', shouldLoadJavascript: true },
  'scms-two-column-copy-copy': { moduleFileName: 'copy-copy/copy-copy', shouldLoadJavascript: false },
  'scms-calculator': { moduleFileName: 'calculator', shouldLoadJavascript: true },
  'scms-price-search-cta': { moduleFileName: 'price-search-cta', shouldLoadJavascript: true },
  'scms-image-segment-list': { moduleFileName: 'image-segment-list', shouldLoadJavascript: false },
  'scms-two-column-copy-table': { moduleFileName: 'copy-table', shouldLoadJavascript: false },
  'scms-image-links-list': { moduleFileName: 'image-links-list/image-links-list', shouldLoadJavascript: false },
  'scms-two-column-copy-video': { moduleFileName: 'copy-video', shouldLoadJavascript: true },
  'scms-local-hero': { moduleFileName: 'local-hero/local-hero', shouldLoadJavascript: false },
  'scms-product-cards-local': { moduleFileName: 'local-product-card/local-product-card', shouldLoadJavascript: true },
  'scms-local-quick-links': { moduleFileName: 'local-quick-links/local-quick-links', shouldLoadJavascript: false },
  'scms-two-column-map-copy': { moduleFileName: 'map-copy', shouldLoadJavascript: false },
  'scms-local-accepted-materials': { moduleFileName: 'local-accepted-materials/local-accepted-materials', shouldLoadJavascript: false },
  'scms-tabbed-content': { moduleFileName: 'tabbed-content/tabbed-content', shouldLoadJavascript: true },
  'scms-lightbox-form': { moduleFileName: 'lightbox-form', shouldLoadJavascript: true }
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
lilianchisca profile image
Lilian Chisca

Mate, you've made my day! :-)

Thank you so much for being so forthcoming and for your quick reply. There are so many issues or edge cases where I can get multiple sources addressing them, but for this one, your article is the only one that addressed exactly what I was looking for and in such a straightforward way.

I hope you won't stop sharing your knowledge and expertise. You've already saved me hours of headaches :-)

Collapse
 
suther profile image
Samuel Suther • Edited

Can you please explain a bit more about he isServer() and "shouldLoadJS" Stuff?
I don't really got this in the Gatsby-Context. If the Page was build and served with gatsby, the Page become statically, right? So why is there an "isServer"-Request needed (and how do you test for "isServe"?)
Or is it to force to reload Component, if you are in gatsby develop mode?

And for shouldLoadJS ... what exactly the functionality of it? How do I decide if "javascript" is needed or not by this module. Does it means if you import additional JS inside this module?

Collapse
 
gslamaakqa profile image
gslama-akqa

I am really curious about how you're handling the lazyLoad: index > 1 part.
Could you enlighten me on this?

Thread Thread
 
itmayziii profile image
Tommy May III

Sorry for the late reply, this lazyLoad: index > 1 is there mostly for an <Image/> component we have. We only wanted images to lazy load if they were later down the page, so the first module we had would never lazy load images.

Thread Thread
 
suther profile image
Samuel Suther

Can you give a hint about this isServer() function I've asked above this comment?
Thanks a lot in advance

Thread Thread
 
abohannon profile image
Adam Bohannon

I'm curious about this, as well. But I suppose you could just check for the existence of window.

Collapse
 
unclecheese profile image
Aaron Carlino

This is exactly, almost word for word, the issue I’ve been trying to solve. Thanks so much for sharing in such great detail.

Collapse
 
wiljohnston profile image
wiljohnston

Hey Tommy,

Thanks for the great article.

Where was your .babelrc file located? My gatsby projects only have this file in the .cache/tests/ directory, which is reset on each build

Collapse
 
anthonybrown profile image
Tony Brown

Just tried viewing your site on mobile. Not sure if it’s working

Collapse
 
itmayziii profile image
Tommy May III

I don't really have a personal site at the moment, do you remember what site you were visiting?