-
Notifications
You must be signed in to change notification settings - Fork 10.3k
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
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
Manual code-splitting #18689
Comments
This is awesome!
Ok, so this to me is problematic part, because you would need to know what "widgets" are needed to render the page in I think that the data pipeline part is on point, and we need a way for our loader to load additional resources for a page that will work in SSR properly. Just the part that determines what those resources are is something that needs more thought. My idea here is to implement this inside data layer. When you query:
the We also should think about other cases that something like this might be useful other than just this use case. I was thinking about few examples:
Yes, we do want to support use cases like this, but we also need to be smart about how we implement this. So there will be a lot of discussion here. It would also be beneficial to create RFC after initial discussion here, to make it a more formal proposal. |
This reminds me of the F8 2019: Building the New Facebook.com with React, GraphQL and Relay @22:00 talk. Not sure how much of it is relevant to Gatsby. |
@pieh You are right. The createPages hook should be as clean and small as possible. But somewhere you have to calculate your needed widgets. So I ended up to create a custom field extension to calculate my widgets: // gatsby-node.js
exports.createSchemaCustomization = ({actions}) => {
...
// generates dict for used widgets -> { WidgetA: 'path/to/WidggetA' }
createFieldExtension({
name: 'StoryComponentPaths',
extend: () => ({
resolve: async (source, args, context, info) => {
if(!source.story) return null
return source.story.components
.reduce((p,{name}) => {
p[name] = path.resolve(__dirname, `src/storybook/components/${name}.js`)
return p
}, {})
}
})
})
...
createTypes(`
type Page implements Node {
...
storyComponentPaths: JSON @StoryComponentPaths
}
`)
}
exports.createPages = async ({actions, grapqhl}) => {
const gq = await graphql(`{
pages:allPage {
nodes {
urlKey
storyComponentPaths
}
}
}`)
gq.data.pages.nodes.forEach(page => {
actions.createPage({
path: `/page/${page.urlKey}/`,
component: path.resolve(__dirname, 'src/theme/templates/Page.js'),
context: {urlKey: page.urlKey},
widgets: page.storyComponentPaths
})
})
} I think something similar should work with MDX too. You could create a custom field extension that does some static analysis in the mdx content. there you could read all your dependencies and format them in a way you need. That ways your createPages will be clean and small
I already created this feature in my fork of gatsby. The features are:
Basically the exact same process that a page-component has. If I'm missing something feel free to point it out. The only thing i'm not really familiar with is the aggressive code-splitting process. Maybe here someone would help, that deeply knows this process |
@universse Yup, not gonna lie - I was inspired by that and wanted to do something like this ever since I saw that talk :) @manuelJung I will be doing some pseudocode examples do illustrate what I had in mind: In your gatsby-node -> createPages you would continue to only care about minimal data required to be able to query things for a page so: exports.createPages = async ({actions, grapqhl}) => {
const gq = await graphql(`{
pages:allPage {
nodes {
urlKey
- storyComponentPaths
}
}
}`)
gq.data.pages.nodes.forEach(page => {
actions.createPage({
path: `/page/${page.urlKey}/`,
component: path.resolve(__dirname, 'src/theme/templates/Page.js'),
context: {urlKey: page.urlKey},
- widgets: page.storyComponentPaths
})
})
} The "magic" would happen when executing page queries. Consider this content data: [
{
"component": "MyCoolWidget",
"props": {
"title": "Hello World"
}
}
] Let's assume graphql type name would be
So in the page template user could do:
What
With this everything would be lazy, you don't need to know what components will be used for page during This also align nicer with MDX use case better, because we wouldn't need to do extra static analisys to figure out components/widgets during What are your thoughts? |
ok, i understand. That's a really cool api! but how would you solve ssr, like creating preload links and injecting the used scripts into the DOM. All of this is done within the createPage hook. Also the code-splitting could be a tricky thing: Webpack analyses the code and creates shared chunks (1.[hash].js...) different pages can use. This can be done, because you have a single entry (createPage) for your code-splitting. But as soon as you add another totally different entry (createSchemaCustomization) it can be a really hard task (maybe impossible) to create shared chunks. Can we support both? Also you could basically do the same thing in both apis, both have their strength and weaknesses. Your idea would be perfect for plugins but can be quite complex for beginners, whereas my idea is easy to use, but not so plugin-friendly as yours. Let the programmer decide, what fits best for his needs |
Under the hood it would be pretty similar to what you already implemented - so
I don't think there would be any difference in terms of webpack setup for both of our ideas - in the end we would end up with list of additional components that each page depend on and need to figure out what's best strategy for webpack setup for this - should we let webpack do some automatic splitting with
I would argue that implementation part wouldn't take that long (similar to implementation of widgets that you did). But I'm with you about figuring out stable API (and this includes both approaches). Your implementation does add new public APIs and adding those is not something that should be rushed to quickly solve particular use case. This is why I mentioned RFC process, because API design should be scrutinized by a lot of people, especially ones like these that could impact a lot of DX flows (which could impact future API decisions).
In my idea we would essentially need internal APIs that would accomplish similar things as
|
ok. so what are the next steps? Who should create the RFC? |
Awesome to see this discussion! There's a lot of fun things we could do with this. One note is that the API would need to be generic so multiple types of resources could be attached to pages for prefetching (didn't see if that came out clearly in the above discussion). See my RFC on prefetching critical images for another use case this would satisfy https://github.com/gatsbyjs/rfcs/blob/prefetch-images/text/0000-support-prefetching-images.md |
Another use case could be loading language bundles. Right now if we query language bundle via page queries, routes of the same language will have their own and potentially duplicated language data passed as props. It would be great if all routes of the same language can share the same bundle. |
so should I create an RFC or should someone from the gatsby team do this? (due to the complexity) |
Just commenting that I didn't forgot about this. We are looking into other cases where this feature/API could be used:
We need this to figure our requirements and constraints. We need to figure out how those extra resources would be accessed (adding it to page component props won't work ultimately, because at least some of them might end up above page level - so I'm looking into some solution with react context, with Provider wrapping entire application)
Because of the above, I think I'll be person that will write up RFC for it. I do think that querying component directly idea will not be part of the rfc (it could be build using that API probably, but this will be out of scope of the initial one) |
Hiya! This issue has gone quiet. Spooky quiet. 👻 We get a lot of issues, so we currently close issues after 30 days of inactivity. It’s been at least 20 days since the last update here. If we missed this issue or if you want to keep it open, please reply here. You can also add the label "not stale" to keep this issue open! As a friendly reminder: the best way to see this issue, or any other, fixed is to open a Pull Request. Check out gatsby.dev/contribute for more information about opening PRs, triaging issues, and contributing! Thanks for being a part of the Gatsby community! 💪💜 |
Hey again! It’s been 30 days since anything happened on this issue, so our friendly neighborhood robot (that’s me!) is going to close it. Thanks again for being part of the Gatsby community! 💪💜 |
@pieh curious if you have any more insight on "Using this to tie MDX imports to pages instead of main app bundle" Our theme exports a bunch of components for themed sites to use. When they add additional MDX components, the main bundle balloons up pretty quickly. Would be great for those components to be split up per page. |
could this be reopened? integrating with react-loadable and supporting |
@manuelJung are you still working on this idea / using it? Could you share the work you did in a fork to create a POC? I'm very interested in this for my own work. Seems like a killer feature for gatsby if it could be supported in a generic way. |
I think a good solution to this could be made by using React.lazy and Suspense, but React doesn't support those yet for server-side rendering. It might make sense to wait on React to make progress in this area before trying to attack this issue. (+1 to re-opening the issue though.) |
@karlsander yes ic can share with you. give me a week to setup a clean gatsby repo where i can create a POC. I think it would be best if we can have a zoom-call where i can explain my thoughts behind this |
for everyone who is interested: I created a POC of this feature. |
Hi guys, I was inspired by this thread and created a plugin which does the querying of components. The solution is a little bit different than @manuelJung POC without the need to patch anything in gatsby's core. Would be happy if you test it out and leave feedback. https://github.com/pristas-peter/gatsby-plugin-graphql-component |
@pristas-peter WOW!!! The idea is brilliant. I will check it out and test it in a real-world example with dozend of components. |
Hey folks, I just want to let you know that work has started on the code-splittable modules added by queries. We are still in API design and exploration phase, but what I'm aiming for right now is something like that: This register module (so webpack can bundle it) and ties this module to page we execute query for (so it get's loaded in frontend as part of resource loading) Later on on frontend it get's consumed like this: ( there is Now - this is pretty low-level API. It's not expected for all users to make use of. We are still looking to do GraphQL Components ( awesome plugin btw @pristas-peter! I didn't even know it was doable as a plugin - tho granted you do have a fair share of hacks to make it work :P). Plan is that GraphQL Components will make use of the low-level API internally, so ideally users don't have to use those themselves and this can be nicely abstracted from them. For the MDX case - we do plan to migrate MDX plugin to make use of those new APIs, but there's quite a bit work ahead of us. My code "works" today but in very limited way (i.e. only page queries are handled, and you better not use if with |
@pristas-peter Awesome plugin! One missing part that we planned for this api is being able to pass props to components from the GraphQL resolver (so you can eg create The initialization logic that you do in |
Thanks @freiksenet and @pieh. In a bigger scale of things I needed this feature for my @freiksenet the props feature is cool, it should not be hard to add support for it, will have a look at it when I am finished with mentioned above, thanks. |
Yesss!! Page builder is the other "demo" scenario I have for this API we work on (other being MDX) - if you check code links I provided in #18689 (comment) - those actually showcase API in page builder scenario (so I assume Gutenberg case would be very similar) Few more links to my demo site:
Of course it's very simplified and doesn't touch on more complex scenarios (i.e. having image component that would want to run image optimizations and use gatsby-image to display it) - this is case that we are also missing in your plugin right now I think? |
Well, what I am doing is that since every block has its own graphql type (sourced by new wordpress plugin by @TylerBarnes - still working on a support for this (I am also the author of |
I will take a look at using Do you use Also is your project public? Maybe I could use yours and not have to build one from scratch 🤔 Alternatively - do you have smaller example sites that use Gutenberg? I see you have example https://github.com/pristas-peter/gatsby-plugin-graphql-component/blob/master/examples/gatsby-plugin-graphql-component-default-example/ but this seems pretty static (you statically register |
Yes, it's open-source, but it it still a WIP, although I have written some docs here: https://gatsbywpgutenberg.netlify.app/features/page-templates/. However I want to get rid of that page templates part and replace it with this queryable component. Yes, it uses gatsby-source-wordpress (v4), but I have to source some parts myself because of the complicated schema. We are chatting with Tyler to work things out so I can also get rid of that gatsby-source-wordpress-gutenberg, which is also a part of this mini framework, and rely on his extension exclusively. |
fwiw @pristas-peter @pieh I believe I have wp-graphql-gutenberg working in the new WP source plugin now (and as a side effect sped up some parts of the build process by 20x). I should have a release out in the next little while. |
@pieh That's so exciting – thanks for the great work. Can't wait to use it. |
Hey folks - it was quiet here for some time, but it doesn't mean that there was no work done! I just opened first PR (in the series) that implements needed low-level APIs to make "data-driven code-splitting" a reality in Gatsby - if you are interested in experimenting with it - go ahead to #24903 (especially "Trying it out" section which mentions canary version of gatsby that you can try out) |
@pieh Thanks, will try to rewrite my |
@pieh Thanks for such an amazing work! We've tried it out internally and it's working correctly – totally helped with TBT/TTI metrics as it reduces the amount of code that ended up otherwise in |
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
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":
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
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
The text was updated successfully, but these errors were encountered: