So we now have native es-modules in most of the major browsers
This means you can do things like
import {foo} from './bar.js'
foo()
The above means that you have a file called 'bar.js' at a relative path from where you are doing the import.
so something like:
Which implies that your modules are sitting somewhere on your server, having a nice and cosy time.
But what if you actually need to load 'virtual' (usually user created) modules on the fly ? with the following requirements:
Note: this are our barebone requirements for OpenJscad, as users can drag & drop files from desktop, or edit design code directly in the browser, with no special tooling other than the most basic http server
If you are impatient, the code is here
After spending weeks (actually months, sporadically) and not finding ANY information at all about how to solve this, and then I found this excellent article by accident
Service Workers can intercept Fetch API requests. This comes in handy as ES6 import uses fetch behind the scenes.
More in depth, it looks something like this:
In your root index.js:
const sendMessageToSW = msg => {
return new Promise( (resolve, reject) => {
// Create a Message Channel
const msg_chan = new MessageChannel()
// Handler for recieving message reply from service worker
msg_chan.port1.onmessage = event => {
if (event.data.error) {
reject(event.data.error)
} else {
resolve(event.data)
}
}
// Send message to service worker along with port for reply
navigator.serviceWorker.controller.postMessage(msg, [msg_chan.port2])
})
}
And in the service worker:
Handle the messages that set the content and uris for our 'virtual' modules See here for a good article on service worker messenging
self.addEventListener('message', async function (event) {
if (event.data.name && event.data.name === 'reset') {
modules = { ...builtIns }
} else if (event.data.name) {
modules[event.data.name] = event.data.source
}
})
and handle the fetch requests:
we are using a prefix to distinguish between the 'normal' modules to fetch (actual files that exists on the server) modules and the virtual ones
self.addEventListener('fetch', (event) => {
const url = event.request.url
let path = url.replace('http://localhost:8080/zborg/', '').replace('http://localhost:8080/', '')
const ext = path.split('.').pop()
if (url.includes('--dynamic--') || url.includes('/@jscad')) {
const module = modules[path]
console.log('loading module', path, module)
event.respondWith(
new Promise((resolve, reject) => {
const headers = new Headers({
'Content-Type': 'application/javascript',
'Cache-Control': 'no-store', // 'no-cache', // attempt at forced invalidation
'Expires': 'Wed, 21 Oct 2015 07:28:00 GMT' // same
})
resolve(new Response(module, { headers }))
})
)
}
})
Of course, it would have been too good to be true (by that I mean, hours of trying to figure out what is wrong and having an urge to forget about this for the next few months) it turns out it does not quite work the way I hoped
You currently cannot use es modules FROM Web workers: ie javascript let worker = new Worker("worker.js", { type: "module" });
does not work yet !
see status for chrome for example: https://www.chromestatus.com/feature/5761300827209728 and track the resolution https://bugs.chromium.org/p/chromium/issues/detail?id=680046
This might not be a problem for everyone, but for us for JSCAD it is a show stopper sadly, as we are loading & evalutating module code in the background (in web workers). I would love to be optimistic about this , but it has been dragging on for years now, and I have only been able to find information about future support for this for Chromium so far.