-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Bundling Front-end Assets #5445
Comments
Related: #2756
|
Two small things:
|
Considering that as of #5042 we're processing all block comments anyways, it probably is not a far stretch to extend it one step further to finding the assets associated with the blocks for whom we are stripping the comments. |
Yes, that seemed like a good hook for something like this to me. |
Hello everybody, I've started working on this task. I have a few questions. Please pardon me if any of my questions sound stupid. I'm just starting to get familiar with the project.
I'm hoping I've not overwhelmed everybody with so many questions. |
I think your project will put you in a position where you can actually inform this decision. :)
These operations on blocks — comment stripping, asset loading optimizations — may be good enough arguments to keep front-end parsing. I also think there's room to improve parsing performance in the medium term. That said, I wouldn't be surprised to see third-party plugins promising performance gains by dropping front-end parsing — if this means that asset-loading optimization is lost, so be it, unless an ahead-of-time solution is devised (which I don't feel strongly about). I think you're generally on the right track, @kanishkdudeja. I'll let others answer the remaining questions. |
Thanks for your answers @mcsf. It makes things much more clear :) |
To support per-block asset loading for core blocks, we will need to refactor existing core blocks to register as independent scripts. See #4841 for prior work here. |
Hello everybody, After going through the related issues (#2751, #5360) and having discussions with @mtias, @aduth and @mcsf, I believe this should be the scope of this task. ScopeWe should enqueue front-end styles for only those blocks which are present in the post/page content. This is possible since we can find out which blocks are present by parsing post_content field on the server.
Disabling this functionality
Extending this functionality
Things to considerTheme-supplied stylesAs of now, themes can supply styles for As per the discussion in #5360, a better approach might be to have the theme specify In my opinion, the scope here should be to come up with a Opinionated Visual StylesAs per the discussion in #5360, we may decide to give themes an option to opt in to be able to use If we do go this route and if the current theme opts in for this feature, we should aim to intelligently enqueue the associated Next steps
|
This looks great @kanishkdudeja . A few points of feedback:
|
Thanks for the feedback @aduth 👍 I will keep these points in mind while brainstorming on the possible approaches for this task. |
Solid report, @kanishkdudeja. The scope looks good, and I think you have all the right resources, esp. with what Andrew shared. |
Hello everybody, I've been thinking about this and have come up with the following approach. Please give me feedback on the same :) ApproachPre-requisitesWe will need to complete work on #4841 before taking this up since the following aspects of #4841 are pre-requisites for this task:
Therefore, I can complete the work started by @aduth in #4841 and then proceed on working on this task :) Getting a list of blocks present in the postThe first step is to get a list of blocks present in the post's content. Some approaches could be:
While both approaches seem reasonable, the first approach seems better to me here since we anyway need to strip block comments from the HTML when a post is requested and also because it doesn't need any auxiliary storage. Enqueueing styles for blocks present in the postOnce we have a list of blocks present in the post's content, we can then think of only enqueueing styles for that list of blocks. Some potential approaches are: Enqueue stylesheet for each block type separately?This seems to be the simplest approach but can potentially lead to a lot of HTTP requests. HTTP/2 Server Push?Using HTTP/2 Server Push, the server can push resources to the client even if the client hasn't requested them yet. This should have a significant improvement over the first approach. However, this has 2 concerns:
Therefore, keeping WordPress's backward compatibility as an ideal, I am not sure if we should choose this approach since I believe there would be a lot of friction involved in getting users to get their server infrastructure upgraded to be able to use HTTP/2 Server Push. Bundle required CSSA reasonable option that we're left now with is to serve a single bundled CSS file. That would contain CSS for each of the blocks present in the post's content. So, there can be two options here:
Caching mechanisms for dynamic bundlesAssuming that we go with dynamic bundles, we should think about caching mechanisms both on:
One way to accomplish this would be to generate an Once the bundle identifier is generated, we can then create a file on disk containing the bundle's CSS. That file can have this naming format:
Once the file has been created on disk, we can enqueue the same using
Generating the bundle identifierOnce we have a list of $bundle_identifier = hash_function( 'string denoting a set of block types' ) It's imperative that the Also, it's important that we choose a hash function which minimizes the probability of collisions for these type of inputs. For denoting different block types in the So, let's say there are a total of 4 block types: Now, if a post contains a paragraph, image and a twitter block, the bundle identifier for the CSS for that post can be generated as follows: $bundle_identifier = hash_function('image-paragraph-twitter') // We should order the blocks in the string supplied to the hash function in an alphabetical order What if CSS for a block needs to be modified?The above approach doesn't consider the possibility that we might need to modify the CSS for a block. If the CSS for a block is modified, the backend will still generate the same To get around this problem, we need to also include some sort of versioning in the Now, the $block_identifier = $block_name . '-' . $css_version // Name of the block concatenated with the version of the block's CSS Some versioning strategies can be:
I like the first (MD5/SHA1 hash) approach here since it can be verified. Therefore, now, let's say there are a total of 4 block types: And these are the first 6 characters of the SHA1 hash of their CSS: Now, if a post contains a $bundle_identifier = hash_function('image-5k8dn4-paragraph-y5b4k6-twitter-9ytb4d') One thing that needs to be considered here is how will a The version (first 6 characters of the MD5/SHA1 hash) can be supplied in the wp_register_style (
'gutenberg-paragraph-block',
plugins_url( 'blocks/paragraph/style.css', __FILE__ ),
array( 'wp-blocks' ),
'y5b4k6' // The first 6 chars of the MD5/SHA1 hash
);
register_block_type( 'core/paragraph', array (
'style' => 'gutenberg-paragraph-block',
) ); The process of computation of the MD5/SHA1 hash for the block's new CSS might take some manual work on part of the developer or some automation. If this feels like too much overhead, we can go with Inter-operability with custom block types registered by plugins/themesSince The only thing that a Inter-operability with theme-supplied stylesAs of now, there is no documented / suggested way for themes to be able to supply styles for One way that themes can do the same (keeping in mind that it should be compatible with the If a theme wants to supply styles for one or more
Therefore, now, let's say there are a total of 4 block types: Now, if a post contains a $bundle_identifier = hash_function('paragraph-y5b4k6-paragraphtheme-3hy6b3-image-5k8dn4') This way, we can dynamically bundle theme-supplied styles as well. This above approach will bundle Alternatively, we can opt to create This approach of letting themes supply styles will also enable plugins to be able to define a Inter-operability with opinionated visual styles
The above algorithm should work the same way. Just that, for each Therefore, now, let's say there are a total of 4 block types: Now, if a post contains a $bundle_identifier = hash_function('paragraph-y5b4k6-paragraphvisual-7d53b4-image-5k8dn4-imagevisual-1ybe5d') Disabling this functionality
Plugins/themes might want to disable this Disabling it on a global levelBasically, if a plugin/theme wishes to disable this, we should enqueue a stylesheet containing styles for The approach for this depends on how this functionality is coded. If all of the remove_action( 'wp_enqueue_scripts', 'generate_and_enqueue_dynamic_bundle` ); Disabling it for some blocksIf a plugin/theme wishes to disable this functionality for only
I'm not really sure what the API to disable this functionality for specific block types should look like. Can someone advise? Extending this functionality
We can probably provide an action ( add_action( 'enqueue_assets_bundle_for_required_blocks', 'my_function' );
function my_function( $bundle_identifier, $bundle_file_path, $bundle_url ) {
// Any custom logic can come here
// This will be executed after the bundle is enqueued
} Alternatively, a theme/plugin might want to execute a function after a dynamic CSS bundle is add_action( 'generate_assets_bundle_for_required_blocks', 'my_function' );
function my_function( $bundle_identifier, $bundle_file_path, $bundle_url ) {
// Any custom logic can come here
// This will be executed after the bundle is generated
} Next steps
|
Very thorough overview @kanishkdudeja !
This could be okay, though it poses a challenge in ensuring that the post's content is kept in sync with the parsed block list.
It seems fine to me as well. I do think there are separate concerns between "strip comments" and "enumerate blocks present in post". Ideally we're not performing the same operation twice, but conversely I worry of a mega-function which both strips comments and aggregates the set of blocks present in a post.
Agree.
To be honest, I think this can be a future optimization. If we can limit to just styles for relevant blocks, even if separate files, it's a good first step. Aside: Since HTTP/2 was mentioned, there is some reason to believe the separate file requirement could be mitigated by multiplexing, though I'm not sure if consensus has been reached here. Related:
You may also be interested in existing script concatenation: https://stackoverflow.com/a/32589922/995445
This seems reasonable. There's some prior art in the form of oEmbed caching that exists in WordPress already:
Style versioning isn't a new requirement, and plugin/theme authors should have the same expectation that cache busting occurs by the
This is a pretty neat idea. One potential concern could be backwards-compatibility for existing themes which happen to have folders named
At least in Gutenberg, the convention we've settled on is
How well would this work with child themes, where both a parent and a child may apply styles to a block? Other options:
I'm curious what the use case is for disabling this behavior per block. |
Thanks for the detailed feedback @aduth :). My comments are inline.
Yes. That's why I think we should go ahead with the other approach of getting
I totally agree. I think we can look more into how this can be elegantly achieved once I start writing code for this.
If we enqueue separate styles for each I feel that the What are your thoughts?
This looks interesting! There seems to be similar functionality for styles in We can think of this approach as well. But this will need me to re-evaluate how everything will work with this approach: Should I evaluate this approach as well?
We can use something similar :)
This is needed for both Basically, we need Let's say these two blocks are being used in a post: If we don't use $bundle_identifier = hash_function( 'paragraph-image' ); On page load, the Now, let's say if the CSS for the Therefore, to fix this, the version of a $bundle_identifier = hash_function( 'paragraph-h5bdj5-image-7dh4b2' ); Therefore, now when the CSS for a block is modified, it's version (first 6 chars of it's MD5sum) will also change. Therefore, the string passed into the hash function will change and now a new bundle identifier will be generated. This will now lead us to generate a new CSS bundle on disk (since there will be no file on disk for the new bundle identifier). Therefore, this will serve as a cache bursting mechanism for Let me know if this is still unclear. I can try to explain with the help of some illustration.
Yes, that should not be a problem.
As you mention, a theme might already have a folder named
We can go by the same convention you mention (
These are nice ideas. This one sounds better to me: "Extending with *_style properties merges into, but doesn't replace existing stylesheets assigned" The word Could there be other concerns with the
I can't think of use cases here too. Maybe, we can skip this for now and take this up later if users demand it. For now, we can give plugins/themes an option to disable it on a |
This looks great, @kanishkdudeja. Re bundling, two things:
|
Thanks for your feedback @mcsf :)
I agree that a big monolithic bundle might not be the ideal solution here. Like you suggest, some ways in which we could group bundles are:
So, the question we need to answer is this:
@mtias @nb I would love to hear your thoughts on this so that we can reach a consensus here. |
If, for the sake of incremental development and separation of concerns, it makes more sense to first split all assets and serve them individually, that seems fine to me, but any v1 of this needs some bundling capability, IMO. Re bundling method, I don't really have an opinion. It would be pretty nice to be able to develop some heuristics and determine which method is best (in general, or for a given site), but I don't mean to divert from the scope of this task ;-), so I'll defer to @nb. |
I also agree here. Since I believe HTTP/2 support will still take quite a bit of time to propagate over the web. Or maybe I'm biased since the approach I proposed above uses a bundling mechanism.
I agree here as well :) |
The way I see it, most blocks won't have lots of styling anyway. I mean most will be less than 1-2kb. In some cases we may see some blocks with more than that, but still their size should be taken into account. Serving 2 files 5kb each would be more costly that a single 10kb file. If the files however are larger, then it would make sense to separate them. Perhaps we could implement a threshold? Count the total size of the styles, and if they are larger than say 100kb then split, otherwise serve as a single asset. It shouldn't be too hard to implement... |
@aduth @kanishkdudeja @mcsf do you have any idea what impact on real users will have either approach? What % of users have access to working HTTP/2 push infrastructure? In a fairly typical case, how much of a hit will load time take if we’re issuing a request for each stylesheet? |
~85% of browsers support HTTP/2 - https://caniuse.com/#feat=http2
~25% of servers support HTTP/2 -
https://w3techs.com/technologies/details/ce-http2/all/all
Significantly Less than 1% of nginx sites are running a version new enough
to support server push -
https://w3techs.com/technologies/details/ws-nginx/1.13.9/all
https://w3techs.com/technologies/details/ws-nginx/1.13.10/all
https://w3techs.com/technologies/details/ws-nginx/1.13.11/all
I think it's way too early to assume server push is going to benefit enough
users that we should be optimizing towards it.
|
Thanks for your comment @nb :)
I'll get back on this. |
Thanks a lot for providing these numbers @aaronjorbin :) Even without Server Push (over HTTP/2), multiplexing support in HTTP/2 should help optimize the page load experience even if we enqueue multiple stylesheets (one for each But as you mention, I'm not sure if we should rely on HTTP/2 support since ~25% of servers support it as of now and also because it's rate of growth doesn't seem to be promising. |
Sorry, I didn't mean to derail the conversation by bringing up HTTP/2 😄 I do think that a decision on it needn't be a blocker for moving forward with some of the more immediate tasks. It seemed to me an optimization which could be bolted on after we've at least implemented the basics of understanding which blocks exist and the styles of each need to be enqueued. I did mean to raise it in the sense of questioning assumptions based on the state of the web as it exists today, in mind of the fact that it may not be worth dedicating the bulk of the effort here toward optimizing for deprecated standards (Is it fair to call it deprecated? I honestly don't know). |
As per @nb's suggestion, I've been conducting some benchmarks on the performance comparison between both approaches and have some results to show. Test Environment
Test variationsThese 2 web pages were tested.
This is what they have in common:
This is how they are different:
Evaluation of test resultsIn my opinion, the two most important factors (taking user experience into account) for evaluating web page performance are
Therefore, for both of these metrics, the smaller they are, the better. We can also look at Test ResultsTest 1This test was performed on a desktop version of Chrome from San Fransisco.
Test 2This test was performed on a mobile version of Chrome on Moto G4 from Dullus, Virginia.
ResultsFor the desktop version, the For each of these tests, a Most browsers can open upto 6 parallel HTTP connections with a specific According to my interpretation of the waterfall view for the Interpretation of the resultsThe results concur with our theory that enqueueing multiple stylesheets will lead to some delays for A delay of 50-200 ms might not seem much here. But taking an average of a page containing Therefore, I think we should do some amount of bundling (something in between All of this will obviously vary depending upon how many assets (apart from the CSS for blocks) the web page requests for itself (like images, other stylesheets, javascript). But this example of a typical web page should give us some idea about the tradeoffs involved. Improving the accuracy of these testsEven though these test results are a median over 3 consecutive attempts, variable factors like network latency / congestion can skew these test results. If needed, I can try to simulate these tests on my local web server so that we can remove that factor as well. Feedback?I would love to hear feedback on the following:
|
What about this solution? |
I'm doing something similar on my theme too... https://aristath.github.io/wordpress/2019/05/11/howto-print-used-blocks-styles.html |
Hi from 2020 🦠 I'm working on opposite solution: bundling all stylesheets together: I would like to share my thoughts. Main ideas:
Updates about HTTP/2~ 96.1% of browsers support HTTP/2 - https://caniuse.com/#feat=http2 ~ 43.9% of servers support HTTP/2 - 2018 data: #5445 (comment) But there are bad news. Most of webservers doesn't have HTTP/2 enabled/compilled by default:
Yes, 44% of websites uses HTTP/2 but my assumption what it is Worst of all, proxies and CDNs have many limitations, for example: https://cloud.google.com/load-balancing/docs/https#HTTP2-limitations So, in the world of HTTP/2 separated styles for each pease of frontend (block, widget etc) is a great idea. But we are still in HTTP 1.1. And this two standards requires a completely different approach to bundle/separate assets. Some questions:
|
I've been examining ways to improve the way styles for blocks get loaded, posting some of my findings and thoughts here: With FSE coming soon, blocks will not only be in the content so we can't "parse the content" of a post to see what blocks we need and then print styles for these blocks in
|
Continuing work here: I currently have 2 POC PRs for this:
Both have their pros and cons, and as they are proof-of-concept PRs they are merely ideas on how we can do things, not final implementations. #25220 leans on what we already have and extends the logic to include core blocks. The bad thing is that it adds styles in the footer so there will be FOUC. Additionally, on an FSE template only stylesheets for blocks added in the content get enqueued. So anything outside that (logo, navigation etc) is unstyled. Ideally, I'd like a combination of the 2, but as a POC they can at least help us continue the discussion and figure out where we want to go. |
Thank you for your updates, Ari! |
One of long standing original issues, thanks for all the effort here! |
Related core ticket: https://core.trac.wordpress.org/ticket/50328.
Explore ways in which front-end styles for blocks could be assembled based on which blocks are used in a given page response.
Note: this is a deviation from how themes and resources have operated so far, since a theme usually adds a single stylesheet including all possible HTML a user might add, as well as handling for widgets, etc, that might never be present on a page.
The granularity of blocks, and our ability to tell on the server which blocks are being used, should open up opportunities for being more intelligent about enqueueing these assets.
Considerations
This would need to have proper hooks for disabling and extending, as there will be issues with async loading of content (like infinite scroll) and caching mechanisms if the bundle is dynamic.
Furthermore, it should at least consider how theme supplied styles for blocks might interop. See #5360
The text was updated successfully, but these errors were encountered: