Skip to content
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

Get CSS and JS for requested chunk at server itself #343

Closed
aseem2625 opened this issue May 18, 2019 · 18 comments
Closed

Get CSS and JS for requested chunk at server itself #343

aseem2625 opened this issue May 18, 2019 · 18 comments

Comments

@aseem2625
Copy link

aseem2625 commented May 18, 2019

When a route is requested from server, I need to find all the required style and bundle required for that route.

import { ChunkExtractor, ChunkExtractorManager } from '@loadable/server';
const statsFile = path.resolve('./loadable-stats-web.json');

const extractor = new ChunkExtractor({ statsFile }); // Do I need to pass more params here to get my case working?
const scriptTags = extractor.getScriptTags();
const styleTags = extractor.getStyleTags();

extractor.getRequiredChunksScriptContent() // . This is giving empty array.

This gives me script and style tags for base and vendor bundle. BUT the requested chunk's JS and CSS are not included here. It's this base.js bundle when loaded on browser makes request to corresponding chunk.

How we can achieve that in such a way that loadableReady includes that chunk's JS and CSS as well for that loadableReady( ofcourse on client)?

import { loadableReady } from '@loadable/component';

I can do the same in react-loadable

import { getBundles } from "react-loadable/webpack";
const stats = require("./react-loadable-web.json");

getBundles(stats, modules);

This gives me the required chunk's JS and CSS but the limitation is it doesn't include base and vendor bundle's JS/CSS

and hence, opposite to loadable-components, Loadable.preloadReady won't include main and vendor bundle's CSS and JS to load.

import Loadable from "react-loadable";
Loadable.preloadReady()

So, for react-loadable, I've to implement loading vendor and base bundle before chunk's JS/CSS. But not sure how to know the chunk in case of loadable-components while at server

Note: I'm having only one entry point for JS base. style for base's CSS are extracted using mini-css-extract plugin. And vendor is split using webpack's Splitchunks.

@aseem2625 aseem2625 changed the title Get requested chunkname .CSS and .JS at server itself Get requested CSS and JS for requested chunk at server itself May 18, 2019
@aseem2625 aseem2625 changed the title Get requested CSS and JS for requested chunk at server itself Get CSS and JS for requested chunk at server itself May 18, 2019
@aseem2625
Copy link
Author

I'll simplify my query. I'm able to make SSR work using "loadable-components".

When the app loads in the browser, it's the main bundle which inserts the requested route's chunk in DOM, as I'm not able to get the requested chunk on server.

This works fine but I need to get requested route's chunk's JS+CSS file on server side itself. How can we do that?

const scriptTags = extractor.getScriptTags();
const styleTags = extractor.getStyleTags();

This only gives me vendor.js and main.js and main.css

@aseem2625
Copy link
Author

@Rid
Would have take 4-5min to read my query. I said chunking is working.

Anyways, ignore. I found in some article saying that this lib behaves in same manner as in my post. So, using react-loadable for my purpose.

@gregberge
Copy link
Owner

@aseem2625 you should not use react-loadable, but anyway if it fits your needs, stick with it.

@Rid
Copy link

Rid commented May 23, 2019

@aseem2625 did you tag me in by a mistake? I'm not the maintainer of this project. We're using it in https://github.com/wellyshen/react-cool-starter

@aseem2625
Copy link
Author

aseem2625 commented May 23, 2019

Yeh, right @Rid. Actually, you referenced it in the wrong issue, so I tagged you. So, I just wanted to tell that this is not issue with setting up SSR with loadable-components, just that probably this lib doesn't support what I was expecting (, or probably, I couldn't find a way to do so, or probably neoziro have reasons for not having it) 😊

@neoziro I'm not sure why shouldn't use react-loadable? I also actually noticed that it's having slightly lesser size than loadable-components.

My requirement was to have chunk JS/CSS info available at the server only. But it's being available at browser side. So, wanted to reduce the latency. I was able to make chunking perfectly working with loadable-components but couldn't find way to do that part.

@gregberge
Copy link
Owner

@aseem2625 react-loadable have issue tab closed and only merge PR that modifies docs. The project is literally dead. It is not compatible with new features coming into React and not compatible with last versions of webpack. But yeah you could use it if you want.

@eseQ
Copy link

eseQ commented Oct 15, 2019

@neoziro Hi there! Thanks for this package.
I have the same issue. All work fine on client and load chunks correctly. But on ssr I have only (without chunks):

scriptTags

<script id="__LOADABLE_REQUIRED_CHUNKS__" type="application/json">[]</script>
<script async data-chunk="app" src="/build/runtime.js"></script>
<script async data-chunk="app" src="/build/app-chunk.js"></script>

linkTags

<link data-chunk="app" rel="preload" as="script" href="/build/runtime.js">
<link data-chunk="app" rel="preload" as="style" href="/build/app.css">
<link data-chunk="app" rel="preload" as="script" href="/build/app-chunk.js">

styleTags

<link data-chunk="app" rel="stylesheet" href="/build/app.css">

Also, I have two entry points: app for client and node for ssr. SSR result code is correct but doesn`t content js and css links on chunks.
Use it:

const extractor = new ChunkExtractor({ statsFile, entrypoints: ['app'] });
const nodeExtractor = new ChunkExtractor({ statsFile: nodeStatsFile, entrypoints: ['node'] });
const App = nodeExtractor.requireEntrypoint('node');
const rContext = { ...routerContext, ...RC };
const jsx = extractor.collectChunks(
  React.createElement(App, {
    userAgent    : req && req.headers['user-agent'],
    url,
    data,
    routerContext: rContext,
  }),
);
const scriptTags = extractor.getScriptTags();
...

In webpack:

...
externals: [
      '@loadable/component',
      nodeExternals({
        whitelist      : [/\.(?!(?:jsx?|json)$).{1,5}$/i],
        modulesFromFile: true,
      }),
    ],
...

And @theKashey your used-styles work correct. But I don`t know what I can do with js chunks.

@theKashey
Copy link
Collaborator

You have to use clientStats for the SSR. nodeExtractor(which does not contain css files) is not required at all - try to remove it and repeat.

@eseQ
Copy link

eseQ commented Oct 15, 2019

@theKashey thanks for a quick answer. I use nodeExtractor only to get App component. But without it, I have the same issue.
Where I should search for the problem? On webpack side or in server code?

@theKashey
Copy link
Collaborator

Server code emits "chunk-name" of the used component and ChunkExtractor them maps those "reported chunks" to the assets, listed in "stats.json"
1 - double check that .css assets are listed in your stats.json
2 - next to the js assets for a specific chunk
3 - chunk names are the same for client and server. Ie chunk names which SSR would report are exists in clientside bundle, so one could load them.

@eseQ
Copy link

eseQ commented Oct 15, 2019

All my chunks has webpackChunkName.
For example, at the start, I render the react-eq-posts-list chunk and both stats.json has it.
Both build folders (ssr and client) has react-eq-posts-list-chunk.js and react-eq-posts-list.css files.

...
    "react-eq-posts-list": [
      "react-eq-posts-list.css",
      "react-eq-posts-list-chunk.js"
    ],
...
"assets": [
...

    {
      "name": "react-eq-posts-list-chunk.js",
      "size": 78703,
      "chunks": [
        "react-eq-posts-list"
      ],
      "chunkNames": [
        "react-eq-posts-list"
      ]
    },
    {
      "name": "react-eq-posts-list.css",
      "size": 8635,
      "chunks": [
        "react-eq-posts-list"
      ],
      "chunkNames": [
        "react-eq-posts-list"
      ]
    },
...
]
...

But I have no one chunk.

@theKashey
Copy link
Collaborator

🤷‍♂️ I'm afraid it's not solvable without example.
However - try to add some console.log here to understand which chunks were reported to the extractor.

@eseQ
Copy link

eseQ commented Oct 16, 2019

Thanks for the idea.

Result:
[
  'react-eq-shape-overlay',
  'react-eq-menu',
  'react-eq-gallery',
  'react-eq-notifications-notify',
  'react-eq-notifications',
  'react-eq-theme-paper',
  'react-eq-posts-filter-calendar',
  'react-eq-cart-button',
  'react-eq-theme-paper-header-large',
  'react-eq-weather',
  'react-eq-tags',
  'react-eq-ui-feature',
  'react-eq-theme-paper-footer-content',
  'react-eq-theme-paper-auth-info',
  'react-eq-posts-list',
  'react-eq-social-share',
  'react-eq-card',
  'react-eq-special-block',
  'react-eq-comments'
]
usedStyles can find that:
[
  "app.css"
  "react-eq-card.css"
  "react-eq-comments.css"
  "react-eq-events~react-eq-events-form.css"
  "react-eq-gallery.css"
  "react-eq-notifications.css"
  "react-eq-places~react-eq-places-form.css"
  "react-eq-posts-list.css"
  "react-eq-posts-list~react-eq-posts-preview.css"
  "react-eq-posts-list~react-eq-posts-single.css"
  "react-eq-posts-list~react-eq-posts-single~react-eq-posts-theme-form.css"
  "react-eq-posts-preview.css"
  "react-eq-shape-overlay.css"
  "react-eq-shop-routes.css"
  "react-eq-social-share.css"
  "react-eq-tags.css"
  "react-eq-theme-paper-header-default~react-eq-theme-paper-header-large.css"
  "react-eq-theme-paper-header-large.css"
  "react-eq-theme-paper-news-aside.css"
  "react-eq-theme-paper.css"
  "react-eq-ui-feature.css"
]

I will try to understand how this package work and what happens with this.chunks.

@eseQ
Copy link

eseQ commented Oct 16, 2019

I found it!
Just getMainAssets call before addChunk. Because I used it like:

Wrong way
const getExt = (url, { routerContext: RC, ...data }, routerContext, req) => {
  const extractor = new ChunkExtractor({ statsFile, entrypoints: ['app'] });
  const nodeExtractor = new ChunkExtractor({ statsFile: nodeStatsFile, entrypoints: ['node'] });
  const App = nodeExtractor.requireEntrypoint('node');
  const jsx = extractor.collectChunks(React.createElement(App, params));

  const scriptTags = extractor.getScriptTags();
  const linkTags = extractor.getLinkTags();
  const styleTags = extractor.getStyleTags();
  return { jsx, scriptTags, linkTags, styleTags };
};

const renderWithStream = (params) => {
  const { jsx, ...ext } = getExt(params);
  const htmlStream = ReactDOMServer.renderToNodeStream(jsx);
  ...
  return { htmlStream, ...ext };
};

const render = (params) => {
  const { jsx, ...ext } = getExt(params);
  const html = ReactDOMServer.renderToString(jsx);
  ...
  return { html, ...ext };
}

IMPORTANT use renderToString or renderToNodeStream before get scripts and styles with extractor.

const renderStatic = (params) => {
  const { jsx, extractor } = getExt(params);
  const html = ReactDOMServer.renderToString(jsx); // <--- IMPORTANT before use extractor.get
  const scriptTags = extractor.getScriptTags();
  const linkTags = extractor.getLinkTags();
  const styleTags = extractor.getStyleTags();
  return { html, scriptTags, linkTags, styleTags };
}

Can I ask why it is so important? How extractor works with ReactDOMServer?

@aseem2625 maybe it is will be useful for you.

@theKashey
Copy link
Collaborator

Ok. So renderToNodeStream was the problem? That's understandable - by the time you are asking for getScriptTags - they are not yet rendered. See the docs for the proper setup.

@eseQ
Copy link

eseQ commented Oct 17, 2019

No renderToNodeStream just for example. In my case, I use simple renderToString. Now all right.

@place-tuan
Copy link

Wow i ran into the same issue that is fixed by #343 (comment). Never thought that ReactDOMServer.renderToString must be called before pulling the tags 🤯

@vickygaogao
Copy link

i am still wondering how extractor works with ReactDOMServer?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants