You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Will not give a reproducable example, however I will provide a very detailed description of what I'm observing here and I think it's a bug. I was using react-static, which uses react-universal-component. One of the things react-static does is it prefetches components that are linked by links visible on the screen. All you need to know is that it ends up calling static preload function from here:
React-static does so in a rather dumb way by just spinning setInterval scanner on <a> elements and runs it from time to time. But that's not that important, because different links may point to the same component anyway.
What happens is this, it starts preloading the component, let's call it Foo and calls Foo.preload() two times (because there are two links which use Foo for page rendering).
The preload function linked above tries to load component in a sync fashion and then makes a promise, where if sync call failed, it starts an async one.
Now I need to talk a bit about webpack and how it works.
Webpack "modules" are essentially an array of functions, to load a module you call that function.
Async webpack modules are just <script> tags which it inserts, when those <script> tags execute, they insert modules within those .js files into the global array of functions aka modules.
If you look at what babel-plugin-universal-import ends up generating, it's something like this in a compiled JS file:
We're interested in inner Promise.all, because this is what webpack substituted for its dynamic import. This is important. It appears that webpack loads all scripts in parallel. I won't go into too many details here, but file 12 here is the one "preload" will preload, but files 4, 3, 6, 5 are its dependencies.
What happens is we basically ask webpack to insert 5 <script> tags, wait for all of them to load and then module 252, which is in file 12, will be executed. The problem however happens, due to the fact that as soon as any of these <script> tags is loaded, it adds itself to the table of modules.
Now let's get back to preload function. It tries sync path first, which ends up looking into webpack's module table:
// warn if there was an error while requiring the chunk during development
// this can sometimes lead the server to render the loading component.
if(process.env.NODE_ENV==='development'){
console.warn(
`chunk not available for synchronous require yet: ${id}: ${
err.message
}`,
err.stack
)
}
}
returnnull
}
Btw note that you swallow exception here in production mode, it only prints an error in development mode.
Okay, so let's get back to my Foo component and the fact that via react-static it's preloaded twice.
First preload sees no component in sync mode, starts async request.
Async request fetches some of the <script> tags, in particular file with our component arrives before its dependencies.
Foo.preload() is called again. This time however, our subject module is in the table and react-universal-component attempts to perform a sync import via tryRequire. But because dependencies of our main module are not loaded yet (remember, webpack loads them all in parallel). The component fails to initialize itself. Even worse, the exception is swallowed.
Anyways, it leads to a very hard to diagnose bug.
Suggested fix: while requireAsync is in progress, there should be a flag that prevents requireSync attempts. In fact the situation seems a bit critical here. If module is in webpack's table it webpack doesn't guarantee that its dependencies are also there, which means I would suggest even making a separate module cache and never trying to load a module statically from the webpack. But whatever you choose is up to you.
Hopefully it's clear what is going on there.
The text was updated successfully, but these errors were encountered:
Will not give a reproducable example, however I will provide a very detailed description of what I'm observing here and I think it's a bug. I was using react-static, which uses react-universal-component. One of the things react-static does is it prefetches components that are linked by links visible on the screen. All you need to know is that it ends up calling static preload function from here:
react-universal-component/src/index.js
Line 70 in cbb7898
React-static does so in a rather dumb way by just spinning setInterval scanner on <a> elements and runs it from time to time. But that's not that important, because different links may point to the same component anyway.
What happens is this, it starts preloading the component, let's call it Foo and calls Foo.preload() two times (because there are two links which use Foo for page rendering).
The preload function linked above tries to load component in a sync fashion and then makes a promise, where if sync call failed, it starts an async one.
Now I need to talk a bit about webpack and how it works.
If you look at what babel-plugin-universal-import ends up generating, it's something like this in a compiled JS file:
We're interested in inner Promise.all, because this is what webpack substituted for its dynamic import. This is important. It appears that webpack loads all scripts in parallel. I won't go into too many details here, but file 12 here is the one "preload" will preload, but files 4, 3, 6, 5 are its dependencies.
What happens is we basically ask webpack to insert 5 <script> tags, wait for all of them to load and then module 252, which is in file 12, will be executed. The problem however happens, due to the fact that as soon as any of these <script> tags is loaded, it adds itself to the table of modules.
Now let's get back to preload function. It tries sync path first, which ends up looking into webpack's module table:
react-universal-component/src/requireUniversalModule.js
Lines 64 to 66 in cbb7898
And if the module is there it will try to load it via
react-universal-component/src/utils.js
Lines 32 to 50 in cbb7898
Btw note that you swallow exception here in production mode, it only prints an error in development mode.
Okay, so let's get back to my Foo component and the fact that via react-static it's preloaded twice.
Anyways, it leads to a very hard to diagnose bug.
Suggested fix: while requireAsync is in progress, there should be a flag that prevents requireSync attempts. In fact the situation seems a bit critical here. If module is in webpack's table it webpack doesn't guarantee that its dependencies are also there, which means I would suggest even making a separate module cache and never trying to load a module statically from the webpack. But whatever you choose is up to you.
Hopefully it's clear what is going on there.
The text was updated successfully, but these errors were encountered: