Skip to content

Manual code-splitting #18689

@manuelJung

Description

@manuelJung

Summary

Currently Gatsby only supports dynamic code-splitting (by pages). But there are situations, where this process cannot cleverly decide what is the optimal way to split code. That happens when you have to decide at gatsby's bootstrap phase what components you need. One example is, when you use a CMS that defines a Component as a "renderer":

// exampe of a cms-entry
const content = [
  {
    component: 'MyCoolWidget',
    props: {
      title: "Hello World"
    }
  }
]

// later
import * as React from 'react'
import * as components from './components'

export default function CMS ({content}) {
  return (
    <>
      {content.map(row => {
        const Component = components[row.component]
        return <Component {...row.props} />
      })}
    </>
  )
}

The problem here is, that all possible components gets included into the bundle even if they are not used. The Gatsby code-splitting process cannot decide which components are used and which not (Yes, thats a very specific example but there are many more that basically have to deal with the same problem; I used this example because it's easy to understand). That's not a problem, when you have a static file-structure. But as soon as you have to decide at runtime (or at bootstrap-phase in a gatsby context) which components you want to use you have a problem. Code-Splitting does not work any longer

There are solutions to this: react-loadable and loadable-components are designed exactly for this problem. But: they do not work properly with gatsby. So I think it's time for a more gatsby-ish solution for this problem. We could use the exact same mechanism that we use for page-based code-splitting:

Basic example

// gatsby-node.js

exports.createPages = async ({actions}) => {
  actions.createPage({
    path: '/my-cms-component',
    component: path.resolve(__dirname, 'src/theme/templates/cms.js'),
    context: {cmsIdentifier: 'abc'},
    widgets: {
      MyCoolWidget: path.resolve(__dirname, 'src/theme/widgets/MyCoolWidget.js'),
    }
  })
}

// src/theme/templates/cms.js

import * as React from 'react'
import { graphql } from "gatsby"

export default function CMS ({data, widgets}) {
  return (
    <>
      {data.cms.content.map(row => {
        const Component = widgets[row.component]
        return <Component {...row.props} />
      })}
    </>
  )
}

export const query = graphql`
  query($cmsIdentifier: String!) {
    cms (cmsIdentifier: {eq: $cmsIdentifier}){
      content
    }
  }
`

In the above example I add a property "widgets" to the createPage action. Then gatsby resolves these widgets the same way as the components gets resolved and injects these Widgets as props to the page. That way no unnecessary code can be loaded and you get all the cool stuff that gatsby gives you be default, like prefetching and code-splitting (at a widget-level). Some coole side-effect of this approach is, that page-queries could technically also be used inside widgets.

I already created this feature in a fork of mine and it works really smoothly. It took only 2 hours to implement since the whole data-pipeline already exists. I just had to process the widgets the same way the page.component gets processed.

So my question is: Is this something gatsby wants to support? I do not think this feature is something a little blog needs, but for big projects this can be a really badass feature (we pushed our performance from 75 lighthouse points to 93). If so, what do you think about the above implementation? Should I change something before I make a pull-request?

Motivation

My company has really big webshop with over 1000 cms pages and over 400 000 products. I created a similar setup like gatsby (static rendering...) but now we want to move to gatsby because of the better community. We are really happy with gatsby but the above problem is really annoying. By implementing this feature we were able to precisely split our components for our needs and pushed our lighthouse performance by nearly 20 points.

This problem was originally discussed in #5995 and event @KyleAMathews said that this is something gatsby plans to support. By then nothing happens so here I come with a suggestion for an implementation

Metadata

Metadata

Assignees

Labels

type: feature or enhancementIssue that is not a bug and requests the addition of a new feature or enhancement.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions