I got acquainted with code splitting a long time ago, in the year since 2008, when Yandex hung up a bit, and Yandex.Direct scripts synchronously connected on the site, just this site was killed. In general, in those days it was the norm, if your “scripts” are 10 files that you connect in the only correct order, which still works (with defer) just to cheer.
Then I began to work actively with maps, and they still connect as external scripts, of course, lazy-load. Later, as a member of the Yandex.Maps team, I actively used ymodules on tree-shaking on the client, which provided just the perfect code splitting.
And then I went over to the webpack
and React
, to the country of unafraid idiots who looked at require.ensure
as a ram at a new gate, and they still do so.
Code splitting is not a "wow feature", it is a "mast hev". Still SSR
did not interfere ...
Nowadays, when bandles are getting stout every day, code splitting becomes more important than ever. At the very beginning, people came out of this situation simply by creating separate entry points for each page of their application, which is generally good, but it will not work for the SPA.
Then there was the require.ensure
function, today known as dynamic import
(simply import), through which you can simply request a module that you will later receive and receive.
The first library "about this case" for React was react-loadable , the hyip around which I still do not really understand, and which has already died (just ceased to like the author).
Now, the less “official” choice will be React.lazy
and loadable-components (just @loadable
), and the choice between them is obvious:
Including loadable, it supports beautiful wrappers over loading libraries (loadable.lib, you can take the moment.js to React renderProp), and helps webpack on the server side to collect a list of used scripts, styles and resources for a prefetch (which webpack is not very good at). In general, read the official documentation .
In general - all the problem in the SSR. For CSR (Client Side Render), either React.lazy or a small script of 10 lines will fit - this will definitely be enough, and it does not make sense to include a large external library. But on the server this will not be enough. And if you don’t need an SSR, you can stop reading. You have no problems that need to be solved long and hard.
SSR is a pain. I am (in some way) one of the loadable-components mintainers and it’s just terrible how many bugs come out of different places. And with each update webpack arrives even more.
An even bigger source of problems with SSR is CSS.
If your Styled-components is not so painful, they come with transform-stream
which itself will add to the final code what you need. The main thing is that there should be one version of SC everywhere, otherwise the focus will not work - one version of SC will not be able to tell anything about itself the other, and SC likes to be fruitful (check your bundle). I will be honest - it is because of this restriction that the focus usually does not work.
C emotion is simpler - their styled
adapter just spits out the <style>
in front of the component itself, and the problem is solved. Simple, cheap and cheerful. In principle, very mobile-friendly, and greatly optimizes the very first-view. But a little spoils the second. And for me personally, conscience does not allow inline styles.
With regular CSS (including various magic obtained from CSS-in-JS libraries) is still easier - information about them is in the webpack column, and which CSS is known to be included.
This is where the dog dug. When should I connect?
The point of SSR-friendly code splitting is that before calling ReactDOM.hydrate
you need to download all the "components" that are already in the server response, but not the strength of the scripts downloaded to the client at the moment.
Therefore, all libraries offer a callback that will be called when all-all-all that needs to be loaded, and you can start the brains . This is the meaning of the work of the SSR codesplitting libraries.
JS can be downloaded at any time, and usually the list is added to the end of HTML, but the CSS, so that there is no FOUC, must be added to the beginning.
All libraries can do this for the old renderToString
, and all libraries can not do this for renderToNodeStream
.
It does not matter if you only have JS (this does not happen), or SC / Emotion (which will add themselves). But - if you have "just CSS" - everything. Either they will be at the end, or you will have to use renderToString
, or other buffering, which will provide a time delay for the TTFB (Time To First Byte) and decrease the meaning of this SSR altogether.
And of course - all this is tied to the webpack and nothing else. Therefore, with all due respect to Greg, the author of loadable-components - I propose to consider other options.
Next comes the three-part agenda, the main idea of ​​which is to do something that is not killed and not dependent on the bandler.
React-Imported-Component is not a bad "loader", with a more or less standardized interface, very similar to loadable-components, which can SSR for anything that moves.
The idea is very simple.
import
s are found and copied into a separate filebabel plugin
each call to import
turns into some sugar const AsyncComponent1 = imported(() => import('./MyComponent')); ///// const AsyncComponent1 = imported(() => importedWrapper("imported_18g2v0c_component", import('./MyComponent')));
No need to stats.json
, adapt to optimize webpack (concatenation, or common code) - you just need to match the "label" of one import in the key in the array and perform the import again. How it will be executed within the framework of a specific bandler, how many files will actually be uploaded and from where it is not his problem.
Minus - the start of loading "used" chunks occurs after loading the main bundle that stores the mapping, which is a little "later" than in the case of loadable-components, which will add this information directly to HTML.
Yes, with CCS it does not work from the word in any way.
But used-styles only works with CSS, but just like react-imported-components.
renderToString
)There are no TTBT delays, there is no tethered to the bundler - a fairy tale. Works as a watch, if the styles are normally written.
Example of the work react-import-component + used-styles + parcel .
Not the most obvious bonus - on the server both libraries will do "everything they need" during a startup, until the express server can accept the first client, and will be completely synchronized and just on the server, and during the tests.
And the library closes the top three, which makes "partial rehydration" , and does it in such a grandfather way that I amaze myself. She really adds "divas."
hydrate
const AsyncLoadedComponent = loadable(() => import('./deferredComponent')); const AsyncLoadedComponent = imported(() => import('./deferredComponent')); <PrerenderedComponent live={AsyncLoadedComponent.preload()} // when Promise got resolve - component will go "live" > <AsyncLoadedComponent /> // meanwhile you will see "preexisting" content </PrerenderedComponent>
With loadable-components, this focus does not work, since it does not return from the preload promise . This is especially important for react-snap (and other "prerenders") libraries that have "content", but have not gone through the "real" SSR.
From the point of view of the code, it is 10 lines, plus a little more to get a stable SSR-CSR UID , taking into account the random load order and code execution.
Bonuses:
In principle, serialization and deserialization were the main idea of ​​creating a library to solve the problem of state duplication (picture from an article about SSR). Caching arrived later.
Total - three approaches that can change your view on SSR and code splitting. The first works with JS codesplitting, and does not break. The second works with CSS codesplitting, and does not break. The third works at the HTML level, simplifying and speeding up some processes, and again, does not break.
Links to libraries:
Articles (in English)
Source: https://habr.com/ru/post/442046/
All Articles