plone / volto

React-based frontend for the Plone Content Management System
https://demo.plone.org/
MIT License
475 stars 644 forks source link

Javascript bundle optimization #5419

Open pnicolli opened 11 months ago

pnicolli commented 11 months ago

PLIP (Plone Improvement Proposal)

Responsible Persons

Piero Nicolli (@pnicolli)

Proposer

Piero Nicolli (@pnicolli)

Seconder

Victor Fernandez de Alba (@sneridagh) Tiberiu Ichim (@tiberiuichim)

Abstract

This PLIP aims at optimizing the main javascript bundle that is downloaded by the client for every page load. It got quite big (~580kb gzipped) and most of it is really not needed, most of it is actually only needed by logged in users (e.g. controlpanels).

Motivation

Web vitals, as measured by Lighthouse for example, are becoming more and more important as a selling point. Some clients reportedly required specific scores. We need to provide a good starting point with Volto core and better examples for client projects.

Assumptions

The first assumption made here is that this will result in a breaking change for Volto, therefore only targeting version 18. We will definitely try to find a way to avoid a breaking change, but we should assume that it is for now, for release planning purposes.

Proposal & Implementation

There is a PR with a proposal of how to load parts of the code lazily #5295

A brief explanation of a the implementation details:

We should try to avoid "just lazy loading", we should group javascript chunks into appropriate ones. For example, all widgets will be lazy-loaded but it would be better if only one javascript chunk is downloaded with all the widgets. Example code:

import loadable from '@loadable/component';

export const Field = loadable(
  () =>
    import(
      /* webpackChunkName: "Form" */ '@plone/volto/components/manage/Form/Field'
    ),
);
export const InlineForm = loadable(
  () =>
    import(
      /* webpackChunkName: "Form" */ '@plone/volto/components/manage/Form/InlineForm'
    ),
);
export const ModalForm = loadable(
  () =>
    import(
      /* webpackChunkName: "Form" */ '@plone/volto/components/manage/Form/ModalForm'
    ),
);
export const UndoToolbar = loadable(
  () =>
    import(
      /* webpackChunkName: "Form" */ '@plone/volto/components/manage/Form/UndoToolbar'
    ),
);

By specifying the magic comment webpackChunkName, webpack knows that we are asking to put those things in the same javascript chunk and it actually does IF this is the only way that these components are imported elsewhere. In other words, every time a component is lazy-loaded, a check should be done to see if it is actually split and if the split chunk is the only way it is used. The command yarn analyze helps with that: after changing the import and adding lazy loading, the web page of the report allows to search. Searching "Form" for example highlights every occurrence of the word Form in the javascript chunks and we are able to see if a single copy of the Form component is there and if it is in its own new chunk or if is left in the main chunk for some mistake that we made or some fix that we need to do.

Deliverables

Risks

If a solution is not found for keeping the main src/components/index.js index, a big breaking change would happen. If it happens, we could discuss an addition to the deliverables, which is a codemod tool to update the affected imports in client projects.

Participants

@plone/volto-team @plone/documentation-team

JeffersonBledsoe commented 11 months ago

If a solution is not found for keeping the main src/components/index.js index, a big breaking change would happen

@pnicolli I'll admit that I've not fully kept up to speed on this, but if all of the Volto code is moved away from directly using src/components/index.js, is it not possible for the barrel file to be left there for integrators to use later (but without the bundle optimizations) and the file tree-shaken away, avoiding the 'breaking change'?

pnicolli commented 11 months ago

if all of the Volto code is moved away from directly using src/components/index.js, is it not possible for the barrel file to be left there for integrators to use later (but without the bundle optimizations) and the file tree-shaken away, avoiding the 'breaking change'?

Yeah that's the point of that, we need to try and find a way to maintain the file and have webpack avoid bundling it.

We tried babel-plugin-transform-imports but it behaves in a weird way (see the PR for details).

One thing I still have to try is to basically remove every reference to the index file in core Volto, changing all imports in the code base, but leaving the file where it is. Webpack should ignore it if nobody requires it, we will see if it is true.

Another thing would be to just change the imports in the index to be lazy imports and see if it is enough.

We have ideas, time will tell :)

pnicolli commented 6 months ago

Hi all, an update on the status of this PLIP. The first PR has been merged: #5295

We avoided the risk of this being a breaking change. We split the biggest chunks away from the main ones for now and set a path for splitting more as soon as possible. There is still much more js to shave off so I would leave this open for now, waiting for at least another round of optimization.

giuliaghisini commented 1 month ago

second part of js bundle optimizations is in progress here https://github.com/plone/volto/pull/6310

tisto commented 1 week ago

@pnicolli @giuliaghisini @sneridagh do we track the progress of the bundle optimization anywhere? I am wondering if we want to mention this in this year's "state of plone" keynote. If we want to mention it we need some kind of number on how much we reduced the bundle size.