ChrisM
ChrisM

Reputation: 2248

Avoid subsequent API request with dynamic import in webpack

I have an app which transcribes and analyses short musical scores from a larger, multi-page book of scores. Each score is made available to the user individually, from a dropdown select. Only one score is loaded at a time, and users will typically only be interested in a small number of scores in any given session. Recently I have started using Webpack, with each score separated out into its own bundle using dynamic import. Even though I have a large number of scores (>50), the incremental nature of the app means they still need to broken out into their own separate bundles, loadable individually at runtime.

The problem I have is that each score also has its own data, which is pulled down from the backend (Django) with an API request. So now I have two requests being performed when the user loads the module for a given score (at least on the first occasion): the bundle for the module, followed by an additional API request to get the data:

import(/* webpackChunkName: '[request]' */ "./someChunkName").then(module => {
  //...API request to get data...
});

How can I avoid this? Ideally I would like the data bundled with the requested chunk, dispensing with the API request altogether, but is there any way to do this using Webpack? Or possibly wrap both the import statement and the API fetch in a Promise?

Upvotes: 0

Views: 696

Answers (1)

Brendan Gannon
Brendan Gannon

Reputation: 2652

Pretty sure there's no way to do quite what you're asking because you're trying to combine code you have at compile time (your webpack chunk) with data you won't 'have' until you fetch it at runtime (your Django backend data). Webpack chunks are created at compile time, and all that happens at runtime is that webpack loads them so they can execute.

Wrapping the import and the runtime data fetch inside a promise should be doable, but you'll have to use require rather than import -- import, per the spec, can't be nested inside a code block. require can, so you should be able to do something like

const promisifiedRequire = new Promise((resolve, reject) => {
  require.ensure('scores/score-foo', (require) => {
    resolve();    
  });
});

const promisifiedDataFetch = new Promise((resolve, reject) => {
  fetchBackendDataForScoreSomehow().then(() => {
    resolve();
  }).catch((e) => {
    console.log('error fetching data from backend');
    reject(e);
  });
});

Promise.all([ promisifiedRequire, promisifiedDataFetch ]).then(() => {
  // everything is loaded, render stuff etc. now
});

I'm sure that could be cleaner and more elegant, but hopefully it gives you the idea; here you would have two parallel requests. Are you worried that two parallel requests will be slow, and thinking one that has all the info would be faster? I wouldn't take that for granted, and would test it before trying to optimize, but if it is the case and you have a strong reason to only make one request per score, there is probably a way to approach this:

If I really wanted to bundle both score and data into a single asset that will require only a single http request, I would write a custom loader for the score JS files, that bundles or inserts the score data into the score code that it returns. If you haven't written a loader before, it's basically just a function that takes source code and returns something else, with the last loader in your chain normally returning plain JS.

Of course, to bundle the data in at compile time, you need the data from your Django app available at compile time. Your custom loader could conceivably make live requests to an instance of your django backend that you set up locally while doing your webpack build but that would be weird and awkward. Ideally you would extract the data from your db into files first, and have your custom loader read from those files at compile time to get the data and bundle it with your code. How you choose to insert the data retrieved in this way into your code is up to you and largely a design decision. This approach means when you fetch a chunk at runtime, that chunk already includes, in whatever pattern/format you decided on, the data you would otherwise fetch from Django, and there is no longer a need to make that second call to Django.

Upvotes: 1

Related Questions