<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Tech Minutes]]></title><description><![CDATA[Blog of an Architect from SolarWinds company and Unity hobbyist. You can expect articles, interesting solutions for problems we encountered, and some exciting a]]></description><link>https://vysinsky.cz</link><generator>RSS for Node</generator><lastBuildDate>Wed, 20 May 2026 00:44:02 GMT</lastBuildDate><atom:link href="https://vysinsky.cz/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Importing JavaScript modules from URL]]></title><description><![CDATA[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....]]></description><link>https://vysinsky.cz/importing-javascript-modules-from-url</link><guid isPermaLink="true">https://vysinsky.cz/importing-javascript-modules-from-url</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[architecture]]></category><dc:creator><![CDATA[Michal Vyšinský]]></dc:creator><pubDate>Sun, 30 Jun 2019 19:42:07 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p>This article was originally published on  <a target='_blank' rel='noopener noreferrer'  href="https://medium.com/@vysinsky/importing-javascript-modules-from-url-fc71b20f2cd0">Medium on the 6th of May 2019</a></p>
</blockquote>
<hr>
<blockquote>
<p>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. </p>
</blockquote>
<h2 id="what-is-microfrontend">What is microfrontend</h2>
<p>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.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" data-card-width="600px" data-card-key="2e4d628b39a64b99917c73956a16b477" href="https://medium.com/@areai51/microfrontends-an-approach-to-building-scalable-web-apps-e8678e2acdd6" data-card-controls="0" data-card-theme="light">https://medium.com/@areai51/microfrontends-an-approach-to-building-scalable-web-apps-e8678e2acdd6</a></div>
<h2 id="our-vision">Our vision</h2>
<p>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.</p>
<h2 id="loader-hooks-to-the-rescue">Loader hooks to the rescue</h2>
<p>Since Node 11 there is an  <a target='_blank' rel='noopener noreferrer'  href="https://nodejs.org/docs/latest-v11.x/api/esm.html#esm_loader_hooks">experimental API</a>  which makes you able to change NodeJS module resolution behaviour. As the API is really experimental, it is changing with Node 12 ( <a target='_blank' rel='noopener noreferrer'  href="https://nodejs.org/docs/latest-v12.x/api/esm.html#esm_experimental_loader_hooks">API</a> ), which I will use in the examples.</p>
<p>Let’s show some code. We want to be able to do this:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> app <span class="hljs-keyword">from</span> <span class="hljs-string">'http://localhost:8080/mock@1.0.0/index.js'</span>
<span class="hljs-keyword">import</span> _ <span class="hljs-keyword">from</span> <span class="hljs-string">'https://unpkg.com/lodash@4.17.11/lodash.js'</span>
<span class="hljs-keyword">import</span> express <span class="hljs-keyword">from</span> <span class="hljs-string">'express'</span>

<span class="hljs-keyword">const</span> server = express()

server.get(<span class="hljs-string">'/'</span>, (req, res) =&gt; res.send(app.render(_.flattenDeep([<span class="hljs-number">1</span>, [<span class="hljs-number">2</span>, [<span class="hljs-number">3</span>, [<span class="hljs-number">4</span>]], <span class="hljs-number">5</span>]]))))

server.listen(<span class="hljs-number">3000</span>)
</code></pre>
<p>It is just a simple Express server which renders tiny application. The file loads lodash from  <a target='_blank' rel='noopener noreferrer'  href="https://unpkg.com/">unpkg.com</a>  and app from own HTTP server:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> React = <span class="hljs-built_in">require</span>(<span class="hljs-string">'react'</span>)
<span class="hljs-keyword">const</span> ReactDOMServer = <span class="hljs-built_in">require</span>(<span class="hljs-string">'react-dom/server'</span>)

<span class="hljs-built_in">module</span>.exports = {
  render: (data) =&gt; {
    <span class="hljs-keyword">return</span> ReactDOMServer.renderToString(
      React.createElement(<span class="hljs-string">'h1'</span>, { children: data })
    )
  }
}
</code></pre>
<p>Note that we import <code>react</code> and <code>react-dom/server</code>. We don&#39;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.</p>
<h2 id="loader-magic">Loader magic</h2>
<p>Let&#39;s finally show some code which does the magic (check the comments in the code for explanation):</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span>;
<span class="hljs-keyword">import</span> fs <span class="hljs-keyword">from</span> <span class="hljs-string">'fs'</span>
<span class="hljs-keyword">import</span> path <span class="hljs-keyword">from</span> <span class="hljs-string">'path'</span>;
<span class="hljs-keyword">import</span> process <span class="hljs-keyword">from</span> <span class="hljs-string">'process'</span>;
<span class="hljs-keyword">import</span> Module <span class="hljs-keyword">from</span> <span class="hljs-string">'module'</span>;
<span class="hljs-keyword">import</span> packageJson <span class="hljs-keyword">from</span> <span class="hljs-string">'./package.json'</span>

<span class="hljs-keyword">const</span> builtins = Module.builtinModules;

<span class="hljs-keyword">const</span> baseURL = <span class="hljs-keyword">new</span> URL(<span class="hljs-string">'file://'</span>);
baseURL.pathname = <span class="hljs-string">`<span class="hljs-subst">${process.cwd()}</span>/`</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">resolve</span>(<span class="hljs-params">specifier, parentModuleURL = baseURL, defaultResolve</span>) </span>{
  <span class="hljs-comment">// Basic support for built-in modules</span>
  <span class="hljs-keyword">if</span> (builtins.includes(specifier)) {
    <span class="hljs-keyword">return</span> {
      url: specifier,
      format: <span class="hljs-string">'builtin'</span>
    };
  }

  <span class="hljs-keyword">if</span> (specifier.startsWith(<span class="hljs-string">'http'</span>)) {
    <span class="hljs-comment">// We load from unpkg so we want to save the file under some "normal" name. This extracts the package name</span>
    <span class="hljs-keyword">const</span> s = specifier.split(<span class="hljs-string">'@'</span>)[<span class="hljs-number">0</span>]
    <span class="hljs-keyword">const</span> basename = s.substring(
      s.lastIndexOf(<span class="hljs-string">'/'</span>) + <span class="hljs-number">1</span>,
      s.length
    ) + <span class="hljs-string">'.js'</span>
    <span class="hljs-comment">// basename should be for example 'lodash.js'</span>
    <span class="hljs-keyword">const</span> finalPath = path.resolve(<span class="hljs-string">'.tmp'</span>, basename)

    <span class="hljs-keyword">if</span> (!fs.existsSync(finalPath)) {
      <span class="hljs-comment">// Download from URL if the file does not exist yet</span>
      <span class="hljs-keyword">const</span> source = (<span class="hljs-keyword">await</span> axios.get(specifier)).data
      fs.writeFileSync(
        finalPath,
        source
      )
    }

    <span class="hljs-keyword">return</span> {
      <span class="hljs-comment">// Tell NodeJS to load this module from our new file</span>
      url: <span class="hljs-string">'file://'</span> + finalPath,
      format: <span class="hljs-string">'commonjs'</span>
    }
  }

  <span class="hljs-comment">// If required module is specified as dependency in package.json, use defaultResolver</span>
  <span class="hljs-keyword">if</span> (!!packageJson.dependencies[specifier]) {
    <span class="hljs-keyword">return</span> defaultResolve(specifier, parentModuleURL)
  }

  <span class="hljs-comment">// Otherways, resolve it as normal import</span>
  <span class="hljs-keyword">const</span> resolved = <span class="hljs-keyword">new</span> URL(specifier, parentModuleURL);
  <span class="hljs-keyword">return</span> {
    url: resolved.href,
    format: <span class="hljs-string">'module'</span>
  };
}
</code></pre>
<h2 id="what-is-not-solved-yet">What is not solved yet</h2>
<p>This module loading is really just an experiment. We need to make this work with  <a target='_blank' rel='noopener noreferrer'  href="https://nextjs.org/">Next.js</a>  which we use in our wrapper application that renders page layouts.</p>
<p>We did not have to solve these areas yet:</p>
<ul>
<li>whitelisting domains to download from</li>
<li>redownload script on change — if there is file already downloaded, it does not download it again</li>
<li>Make it work with Next.js (via Webpack plugin)</li>
</ul>
<h2 id="next-steps">Next steps</h2>
<p>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.</p>
<p>Would you do micro-frontends in a better way?</p>
]]></content:encoded></item></channel></rss>