gregberge / loadable-components

The recommended Code Splitting library for React ✂️✨
https://loadable-components.com
MIT License
7.65k stars 380 forks source link

Duplicated prefetch link (with SSR) #247

Closed EliaECoyote closed 5 years ago

EliaECoyote commented 5 years ago

I'm creating a component chunk that needs to be prefetched. I'm rendering the prefetch link on SSR for component chunk through ChunkExtractorManager

// loadableComponents.js
const LoadableLibrary = loadable.lib(() => import(
  /* webpackChunkName: "component" */
  /* webpackPrefetch: true */
  'external-library',
));
export const ExampleComponent = props => (
  <LoadableLibrary>
    {({ default: Component }) => <Component {...props} />}
  </LoadableLibrary>
);

// renderer.js
const extractor = new ChunkExtractor({ statsFile, entrypoints: ['client'] });
const content = (
  <ChunkExtractorManager extractor={extractor}>
    { /* content management */ }
  </ChunkExtractorManager>
);
const linkTags = renderToStaticMarkup(extractor.getLinkElements());
return `
  <!DOCTYPE html>
  <html>
    <head>
      ${linkTags}
    </head>
    <body>
      <div id="root">${content}</div>
    </body>
    <!-- bundle script -->
  </html>
`;

After the client receives the template, and the browser evaluates my bundle.js, webpack runtime finds those dynamic imports and add again the prefetch link for script.js, causing:

Is this the correct behavior? Am I missing something?

gregberge commented 5 years ago

Hello @asulta you got a point. I don't know if it is really a problem, but if it is, we should try to talk to webpack team. Currently it is not possible to tell webpack "do not prefetch these".

luishdz1010 commented 5 years ago

A somewhat related issue: when loadable detects chunks during render they get preloaded on the head, but if the preloaded dependency was marked as "webpackPrefetch: true" it gets added as a prefetch as well (in addition to the preload), which is not ideal.

There should be a way to detect the dependency is already being included in a link preload, and don't prefetch it in that case.

gregberge commented 5 years ago

I think it is more a webpack issue, if the chunk is already prefetched in the page, they should not prefetch it. I think it has no implication by having it twice in the page (if already prefetched it should be ignored by browser).

aseem2625 commented 5 years ago

Facing same issue. Most probably a webpack issue only. But very difficult to get a way around. In all cases I'm having extractor.getLinkTags() inserted in html

With prefetch magic comment

const Home = loadable (() => import(/* webpackChunkName: "home" */ /* webpackPrefetch: true */ 'views/Home'));

const People = loadable (() => import(/* webpackChunkName: "people" */ 'views/People'));

const UI = loadable (() => import(/* webpackChunkName: "ui" */ 'views/UI/UI'));

const NotFound = loadable (() => import(/* webpackChunkName: "404" */ /* webpackPrefetch: true */ 'views/NotFound'));
  1. RED: loadable-components preloading the chunks.
  2. GREEN: loadable-components also takes prefetch magic comment and inserts them as prefetch
  3. BLUE: When main.js is loaded on client side, it again inserts the prefetch links.

1

Having preload magic comment instead of prefetch image

Without any magic comment image

Is there any way to figure this out, or otherwise manually can add prefetch links for desired routes based on their corresponding generated chunks. But that's one ugly approach I wanted to keep for last ?

gregberge commented 5 years ago

We have to open an issue in webpack, actually, it is a webpack problem. It duplicated prefetch links by not checking if they are already inserted.

aw-davidson commented 4 years ago

@gregberge, I don't completely understand the issue, but I am still experiencing this. I also tried to switch out loadable with react.lazy (I'm not working with SSR) and there are no duplicated requests. This alone would lead me to believe that something can be done in loadable-components.

gerhardsletten commented 4 years ago

Its also possible to filter out which files that should be listed for prefetch like this:

const srcFiles = extractor
    .getScriptElements()
    .map((item) => item.props.src)
    .filter(Boolean)
  // Filter out prefetch files allready loaded in scriptfiles
  const prefetchFilter = (item) =>
    !srcFiles.some((file) => file === item.props.href)
return (
<html lang={config.locale}>
      <head>
        {extractor.getLinkElements().filter(prefetchFilter)}
      </head>
</html>
)