Skip to main content

On mobile? Send a link to your computer to download HTTP Toolkit there:

No spam, no newsletters - just a quick & easy download link

On mobile? Send a link to your computer to download HTTP Toolkit there:

No spam, no newsletters - just a quick & easy download link

webpack

javascript

Bundling Remote Scripts with Webpack

As a JavaScript developer nowadays, almost everything you use comes from npm. Unfortunately, not absolutely everything: there's still a small subset of scripts that expect to be included from a remote CDN somewhere, and when bundling your application these pose a problem.

You could use these scripts from the CDN, as intended. If you do so you'll lose opportunities for bundling benefits like tree shaking, but more importantly you now have to independently load scripts from one more domain at the same time as your other bundle(s). That means another point of failure, and means you need logic in your main app to wait until the remote script has loaded before using it, and to potentially handle loading failures too.

Instead, you could download the script directly, save it into your codebase ('vendor' it), and treat it like your own source. What if it changes though? Many of these CDN scripts change frequently, so you'll need to repeatedly update this, and every change is extra noise and mess in your codebase & git history.

I hit this recently working on HTTP Toolkitopens in a new tab trying to use the JS SDK for a 3rd party service, which is only available from a CDN, and isn't published on npm. Fortunately, there's another option: webpack can solve this for us.

Val Loader

Webpack's little-known val loaderopens in a new tab allows you to easily define your own loading logic that is run at build time. When you load a file with most webpack loaders they read the file, transform the content somehow, and add some content to your bundle, which will later be returned from the initial import/require statement.

When you load a file with val loader however it:

  • Executes the file contents as a node module
  • Looks for an exported function or promise from the module
  • Waits on the promise/calls the function (which may in turn return a promise)
  • Takes the code property from the final result, and uses this as the content to be bundled and returned by the original import/require

This means you can write a simple node script that dynamically generates content, you can require that script elsewhere, and webpack will pre-generate the content for you at build time, totally automatically. Magic!

Fetching Remote Scripts

You can probably see where this is going. Putting this together: we need to write a module that fetches our remote script at build time, and returns it to val loader.

In practice, this looks something like this:

  • Install val loader: npm install --save-dev val-loader
  • Create a fetch-script.js loader script:

Code example

Code example// I'm using fetch here, but any HTTP library will do.
const fetch = require('node-fetch');

const SCRIPT_URL = 'https://cdn.example.com/your-script.js';

module.exports = function () {
    return fetch(SCRIPT_URL)
    .then((response) => {
        if (!response.ok) {
            throw new Error('Could not download ' + SCRIPT_URL);
        }
        return response.text();
    })
    .then((remoteScript) => ({ code: remoteScript }));
}
  • In the rest of your codebase, require the module like any other, but using val loader:

Code example

Code exampleconst scriptExport = import('val-loader!./fetch-script');

That's it! No extra config, just a tiny node script.

With that in place, any code that needs the remote script can import our module via val loader, and get the remote script as if it were a normal dependency. It gets properly bundled with the rest of your app, and is always immediately available, like any other bundled script. At the same time, it still keeps up to date automatically: every build, we pull down the latest version from the CDN. You don't need to commit the script into your own repo, or manually check for updates.

One thing to watch out for here: the loader script does not get built by webpack before it's run. That means it needs to be natively runnable by node, so no TypeScript/babel/etc. It's a very simple script though, and this is node not browsers, so you can use modern JS regardless.

Accepting change

Depending on the script of course, safely pulling in changes is another article in itself. In general most remote scripts like these have some kind of compatibility guarantees (otherwise using them remotely would be impossible), but you may still want some kind of locking mechanism.

If there's versioning available in the remote URL that's trivial, if not though you'll need to check changes manually.

One reasonable approach would be to include & check a hash of the remote file in your loader script, and to fail the build if it changes, or perhaps just send yourself a notification. Failing the build forces you to manually confirm changes when the remote script changes, and then update the hash, which does at least ensure that you won't see unpredictable changes in your application. You'll need to play around, but there's many options here, depending on how flexibly you want to handle new changes.

Putting it all together

Enjoy! If you'd like to see a working example, take a look at how HTTP Toolkit's UI loads paddle.js. Check out the paddle.js loading scriptopens in a new tab, and the code that imports itopens in a new tab.

Have any thoughts or ideas about this? Just love/hate webpack? Let me know on twitteropens in a new tab, or join the discussion on redditopens in a new tab.

Share this post:

Blog newsletter

Become an HTTP & debugging expert, by subscribing to receive new posts like these emailed straight to your inbox:

Related content

node.js

Automatic npm publishing, with GitHub Actions & npm granular tokens

This week, at long last, GitHub announced granular access tokens for npm. This is a big deal! It's great for security generally, but also particularly useful if you maintain any npm packages, as it removes the main downside of automating npm publishing, by allowing you to give CI jobs only a very limited token instead of full 2FA-free access to your account. In the past, I've wished for this, because I maintain a fair few npm packages including some very widely used ones. The previous solution of "just disable 2FA on your account, create an all-powerful access token with global access to every package, and give that token to your CI job" was not a comfortable one.

decentralized-web

Debugging WebRTC, IPFS & Ethereum with HTTP Toolkit

HTTP is important on the web, but as other alternative protocols grow popular in networked applications, it's often important to be able to capture, debug and mock those too. I've been working on expanding HTTP Toolkit's support for this over the past year (as one part of a project funded by EU Horizon's Next Generation Internet initiative), to extend HTTP Toolkit to cover three additional rising protocols that are often used alongside simple HTTP in decentralized web applications: WebRTC, IPFS & Ethereum.

decentralized-web

Testing libraries for the Decentralized Web

The world of decentralized web applications is an exciting place that has exploded in recent years, with technologies such as IPFS and Ethereum opening up possibilities for a peer-to-peer web - creating applications that live outside the traditional client/server model, where users to interact and control their own data directly. At the same time, it's still immature, and for software developers it lacks a lot of the affordances & ecosystem of the traditional HTTP-based web app world. There's far fewer tools and libraries for developers working in this space.