Search code examples
es6-modulesrollupjs

Resolving an ES6 module imported from a URL with Rollup


It is perfectly valid to import from a URL inside an ES6 module and as such I've been using this technique to reuse modules between microservices that sit on different hosts/ports:

import { authInstance } from "http://auth-microservice/js/authInstance.js"

I'm approaching a release cycle and have started down my usual path of bundling to IIFEs using rollup. Rollup doesn't appear to support es6 module imports from URLs, I think it should as this is allowed in the spec :(

module-name The module to import from. This is often a relative or absolute path name to the .js file containing the module. Certain bundlers may permit or require the use of the extension; check your environment. Only single quotes and double quotes Strings are allowed. (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import)

I've dug through the interwebs for an hour now and have come up with nothing. Has anybody seen a resolver similar to rollup-plugin-node-resolve for resolving modules from URLs?


Solution

  • I had to move on from this quickly so ended up just writing a skeleton of a rollup plugin. I still feel that resolving absolute paths should be a core feature of rollup.

    Updated snippet

    We have been using this to transpile production code for several of our apps for a considerable amount of time now.

    const fs = require('fs'),
        path = require('path'),
        axios = require("axios")
    
    const createDir = path => !fs.existsSync(path) && fs.mkdirSync(path)
    const mirrorDirectoryPaths = async ({ cacheLocation, url }) => {
        createDir(cacheLocation)
        const dirs = [], scriptPath = url.replace(/:\/\/|:/g, "-")
    
        let currentDir = path.dirname(scriptPath)
        while (currentDir !== '.') {
            dirs.unshift(currentDir)
            currentDir = path.dirname(currentDir)
        }
        dirs.forEach(d => createDir(`${cacheLocation}${d}`))
        return `${cacheLocation}${scriptPath}`
    }
    
    const cacheIndex = {}
    const writeToDiskCache = async ({ cacheLocation, url }) => {
        //Write a file to the local disk cache for rollup to pick up.
        //If the file is already existing use it instead of writing a new one.
        const cached = cacheIndex[url]
        if (cached) return cached
    
        const cacheFile = await mirrorDirectoryPaths({ cacheLocation, url }),
            data = (await axiosInstance.get(url).catch((e) => { console.log(url, e) })).data
        fs.writeFileSync(cacheFile, data)
        cacheIndex[url] = cacheFile
    
        return cacheFile
    }
    
    const urlPlugin = (options = { cacheLocation }) => {
        return {
            async resolveId(importee, importer) {
                //We importing from a URL
                if (/^https?:\/\//.test(importee)) {
                    return await writeToDiskCache({ cacheLocation: options.cacheLocation, url: importee })
                }
                //We are importing from a file within the cacheLocation (originally from a URL) and need to continue the cache import chain.
                if (importer && importer.startsWith(options.cacheLocation) && /^..?\//.test(importee)) {
                    const importerUrl = Object.keys(cacheIndex).find(key => cacheIndex[key] === importer),
                        importerPath = path.dirname(importerUrl),
                        importeeUrl = path.normalize(`${importerPath}/${importee}`).replace(":\\", "://").replace(/\\/g, "/")
                    return await writeToDiskCache({ cacheLocation: options.cacheLocation, url: importeeUrl })
                }
            }
        }
    }