I have two separate but dependent dynamic imports requested from my JS application at virtually the same time. How can I avoid make two import calls or batch them into one?
You can combine the power of URL.createObjectURL()
and dynamic imports to import multiple files in one HTTP call.
You'll obviously need some sort of api to fetch multiple files in one API call. For this you'll need to make the server somehow send multiple files in one HTTP response. The syntax can vary, but for this example I'm using the syntax GET /a.js+b.js
, which will return a string.
Example: 16 24;export default 3export default [2, 3, 5]
. This has two files, one with a length of 16
characters, and one with 24
. The numbers before the ;
are like metadata for the contents of the files. Idk you might put the metadata in the headers or something, but this example uses the ;
seperating the metadata and contents.
I created a function called fetchMultiple
, which is like fetch
, but it returns a Promise<Array<Promise< the data exported by the files >>>
.
// I created a syntax where it goes
// {length of file 1} {length of file 2} {...lengths of files};(no \n)
// {contents of file 1} {contents of file 2} {...contents of files}
const mockServerResponses = new Map()
.set('a.js+b.js', '16 24;export default 3export default [2, 3, 5]')
// The real thing would fetch the files from the server
const fetchMultiple = async (...urls) =>
mockServerResponses.get(urls.join('+'))
// You could probably optimize this function to load the first script as soon as it is streamed, so that the first script will be loaded while the second one is still being streamed.
const importMultiple = async (...urls) => {
const result = await fetchMultiple(...urls)
const semi = result.indexOf(';')
const lengths = result.slice(0, semi).split(' ').map(str =>
parseInt(str))
const rawContents = result.slice(semi + 1)
let currentIndex = 0
const contents = []
for (const length of lengths) {
contents.push(rawContents.slice(currentIndex, currentIndex + length))
currentIndex += length
}
return contents.map(content =>
import(URL.createObjectURL(new Blob(
[content],
{ type: 'application/javascript' }
))))
}
importMultiple('a.js', 'b.js')
.then(promises => Promise.all(promises))
.then(console.log)
In case the snippet stops working (like a Content Security Policy change), here is the link to the repl: https://replit.com/@Programmerraj/dynamic-import-url#script.js.
What could make the example above slow is that it waits for the entire two (or more) files to get fetched, and then it loads them. Since the files are streamed file 1, file2, ...files
, faster code would load file 1
as soon as it is available, and load the other files as they get downloaded.
I didn't implement this optimized stream stuff because I didn't setup a server which streams a response, but you could for maximum efficiency.