Fonto Why & How: How do I add an external dependency?!

In this weekly series, Martin describes a question that was raised by a Fonto developer, how it was resolved and why Fonto behaved like that in the first place. A developer reached out. They are working on a big project where Fonto is just a cog in the machine. They are working a lot with dates, for which they’d like to use the well-known dayjs library. They are looking to integrate it into Fonto, so they do not have to reinvent the wheel!

Let’s take a look at the library. It is available on npm. The important aspects for including it in Fonto:

  • It has zero external dependencies
  • It is available as a JavaScript ES6 module
  • All the imports in the ES6 module are local imports (looking like ./constants)
  • It is fit for the browser: there are no dependencies on NodeJS-only APIs

These aspects are not strict requirements for including a library in Fonto, but they do make it easier.

Before reaching out, the developer already got the dependency to work by installing it as a npm dependency, and importing it like this:

import * as dayjs from '../../../node_modules/dayjs';

This already worked at first sight! The module is imported, typing is available and after building the editor, the dependency is rolled into the full build.

However, it is not perfect. The auto-import feature in VSCode attempts to generate an import statement like this:

import dayjs from 'dayjs';

This import cannot be resolved by the Fonto Development Tools (fdt), causing the editor to fail at building.

A better solution is taking the integration one step further. The Fonto platform also contains a number of external dependencies. They are copied over from a node_modules directory to a Fonto package. We should do the same for this dependency, so they look like normal Fonto package and stay working even if our FDT tooling changes its behavior.

The simplest way to do this is by writing a postinstall hook in the package.json of the project that calls this script:

const fs = require('fs');

const filesToCopy = {
    'node_modules/dayjs/dayjs.min.js': 'packages/external/src/dayjs.js',
    'node_modules/dayjs/index.d.ts': 'packages/external/src/dayjs.d.ts'
};

Object.keys(filesToCopy).forEach(src => {
    const dest = filesToCopy[src];
    fs.copyFile(src, dest, (err) => {
        if (err) {
            throw err;
        }

        console.log(`Copied ${src} to ${dest}`);
    });
});

After creating the packages/external/src/ folder (with a .gitignore to prevent these dependencies to end up in GIT), the code works. Visual studio code now generates the following import statement:

import dayjs from 'external/src/dayjs';

More complex dependencies

This dependency was easy. There are no sub-dependencies. But what if there are? A dependency like ClippyJS is an example of a dependency with its own sub-dependencies. In this case, JQuery. If we’d just copy over the files related to ClippyJS, we would get an error about resolving the import of jquery. This is of course not a serious dependency we expect our partners to integrate. But it is a good example of a more complex case.

There are plenty of approaches to ‘bundle’ external dependencies into the editor. Personally, I have used esinstall a number of times because of its simplicity. It copies over a package and its dependencies and rewrites any imports to relative ones. Exactly what we need for Fonto. Note that this tool is in no way supported by Fonto, and we do not endorse its usage. Do your own research before selecting a build system.

After running npm install --save-dev esinstall, the following configuration will copy over both the ClippyJS and the dayjs dependencies:

const {install, printStats} = require('esinstall');


install(['dayjs', 'clippyjs'], {
    dest: 'packages/external/src/'
}).then(({success, stats}) => {
    printStats(stats);
});

Both dependencies can be imported like they are normal dependencies, with automatically generated imports. The README of the ClippyJS library describes how to integrate it. With the integration done, it loads as expected:

But why?

But why is it so hard to include dependencies? And why are we not opening up our own WebPack configuration to allow you to plug your own things in it? This has a long history. Our build system has grown in the years and we need to have control on the order of loading files. For example, install.js files are evaluated before configureSxModule.js files. operation.json files are automatically loaded at another point in time. All files that import the configurationManager are deferred from loading until all configuration is actually available, which can be happen later, after reaching out to the CMS for instance.

All these files are bundled into one file using a complex Webpack configuration while retaining the load order.

Furthermore, we do not want to pin ourselves down on Webpack, or on any other build tool. If we would expose too much of the Webpack APIs, we might not be able to have an easy upgrade to other versions, or to a different build system as a whole.

Maybe in the future we will be able to open up our configuration, but we have no concrete plans right now. Do you have input for us? Do you have dependencies that you want to include? Do you have a good idea on how to open up our configuration and allow external dependencies? Please reach out!

I hope this explained how Fonto works and why it works like that. During the years we built quite the product, and we are aware some parts work in unexpected ways for those who have not been with it from the start. If you have any points of Fonto you would like some focus on, we are always ready and willing to share! Reach out on Twitter to Martin Middel or file a support issue!

Stay up-to-dateFonto Why & How posts direct in your inbox

Receive updates on new Fonto Why & How blog posts by email

Leave a Comment

Your email address will not be published.

Fonto 8.0 WebinarApril 13, 6.30 pm CET / 11.30 am CST

Providing you with insights from our team and a QA session.

Scroll to Top