Importing JavaScript modules from URL
I am a happily married tech guy from a small town near Znojmo, Czech Republic. I enjoy writing clean code and designing the architecture for complex solutions. I like challenging tasks.
Search for a command to run...
I am a happily married tech guy from a small town near Znojmo, Czech Republic. I enjoy writing clean code and designing the architecture for complex solutions. I like challenging tasks.
No comments yet. Be the first to comment.
This article was originally published on Medium on the 6th of May 2019
In Dixons Carphone, we are rebuilding our platform with microfrontend architecture. It means investigating ways how to achieve the best user and developer experience possible. This is a series about our journey.
I will not repeat what was already written somewhere else. Should you wonder what microfrontend is, take a short break with the excellent article written by Vinci Rufus and then come back.
We want to be able to load our micro apps from the registry by URL. Therefore we need to make NodeJS able to import modules from some URL.
Since Node 11 there is an experimental API which makes you able to change NodeJS module resolution behaviour. As the API is really experimental, it is changing with Node 12 ( API ), which I will use in the examples.
Let’s show some code. We want to be able to do this:
import app from 'http://localhost:8080/mock@1.0.0/index.js'
import _ from 'https://unpkg.com/lodash@4.17.11/lodash.js'
import express from 'express'
const server = express()
server.get('/', (req, res) => res.send(app.render(_.flattenDeep([1, [2, [3, [4]], 5]]))))
server.listen(3000)
It is just a simple Express server which renders tiny application. The file loads lodash from unpkg.com and app from own HTTP server:
const React = require('react')
const ReactDOMServer = require('react-dom/server')
module.exports = {
render: (data) => {
return ReactDOMServer.renderToString(
React.createElement('h1', { children: data })
)
}
}
Note that we import react and react-dom/server. We don't have it installed next to the application and in future, when we will be bundling the application we will mark those “platform” dependencies like externals. It will be provided by a renderer application.
Let's finally show some code which does the magic (check the comments in the code for explanation):
import axios from 'axios';
import fs from 'fs'
import path from 'path';
import process from 'process';
import Module from 'module';
import packageJson from './package.json'
const builtins = Module.builtinModules;
const baseURL = new URL('file://');
baseURL.pathname = `${process.cwd()}/`;
export async function resolve(specifier, parentModuleURL = baseURL, defaultResolve) {
// Basic support for built-in modules
if (builtins.includes(specifier)) {
return {
url: specifier,
format: 'builtin'
};
}
if (specifier.startsWith('http')) {
// We load from unpkg so we want to save the file under some "normal" name. This extracts the package name
const s = specifier.split('@')[0]
const basename = s.substring(
s.lastIndexOf('/') + 1,
s.length
) + '.js'
// basename should be for example 'lodash.js'
const finalPath = path.resolve('.tmp', basename)
if (!fs.existsSync(finalPath)) {
// Download from URL if the file does not exist yet
const source = (await axios.get(specifier)).data
fs.writeFileSync(
finalPath,
source
)
}
return {
// Tell NodeJS to load this module from our new file
url: 'file://' + finalPath,
format: 'commonjs'
}
}
// If required module is specified as dependency in package.json, use defaultResolver
if (!!packageJson.dependencies[specifier]) {
return defaultResolve(specifier, parentModuleURL)
}
// Otherways, resolve it as normal import
const resolved = new URL(specifier, parentModuleURL);
return {
url: resolved.href,
format: 'module'
};
}
This module loading is really just an experiment. We need to make this work with Next.js which we use in our wrapper application that renders page layouts.
We did not have to solve these areas yet:
In the next article, I will show how we solved the integration to Next.js via Webpack plugin. Let me know, what do you think about our idea and about importing modules from some URLs in general.
Would you do micro-frontends in a better way?