Dynamic import of modules, using string variable, with Angular, only with specific exports not the entire file

2.5k Views Asked by At

The use case is simply to be able to use any FontAwesome icon, from a string, without necessarily knowing what those icons are going to be until runtime, without the client having to download icons they won't be using.

I'm using angular-fontawesome, using the Icon Library approach described here: https://github.com/FortAwesome/angular-fontawesome/blob/master/docs/usage/icon-library.md

With this example syntax:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
import { faCoffee } from '@fortawesome/free-solid-svg-icons';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, FontAwesomeModule],
  bootstrap: [AppComponent]
})
export class AppModule {
  constructor(library: FaIconLibrary) {
    // Add an icon to the library for convenient access in other components
    library.addIcons(faCoffee);
  }
}

So, ideally it would be possible to make the faCoffee part dynamic. I know that static analysis can see that faCoffee is being imported, so it knows to bundle it, and that if it were a dynamic variable it can't possibly guess what export would be required - but then I'm just at that point wondering if there is anything in Webpack, or any other utility, which would allow the code to be included in the build but only downloaded to the client if necessary at runtime.

I've tried a few things out.

I was aware of this attempt, but this is Vue, and it may not even work: https://github.com/FortAwesome/vue-fontawesome/issues/170#issuecomment-484544272

I know you can abandon tree-shaking and use deep imports, as described here: https://fontawesome.com/v5/docs/apis/javascript/tree-shaking#alternative-to-tree-shaking-deep-imports

i.e.

import { faCoffee } from '@fortawesome/free-solid-svg-icons/faCoffee'

so I've tried an approach around that, based on https://dmitripavlutin.com/ecmascript-modules-dynamic-import/#1-dynamic-import-of-modules.

const iconModule = from(import('@fortawesome/pro-duotone-svg-icons/faCoffee'));

return iconModule.pipe(map(module => module[fullIconName] as IconDefinition));

This obviously works, you can then just add the IconDefinition to the library - and within a switch statement it means that the faCoffee bundle is only loaded when the code is executed at runtime.

However if you try and do something dynamic:

const getPath = (fullIconName: string) => `@fortawesome/pro-duotone-svg-icons/${fullIconName}`;

const iconModule = from(import(getPath(fullIconName)));

return iconModule.pipe(map(module => module[fullIconName] as IconDefinition));

Then quite understandably it doesn't work as it hasn't been bundled.

Error: Cannot find module '@fortawesome/pro-duotone-svg-icons/faCoffee'

And indeed the import documentation also states that the best that might be achievable is to import the entire file, see: https://webpack.js.org/api/module-methods/#dynamic-expressions-in-import https://javascript.info/modules-dynamic-imports#the-import-expression

This answer also suggests it might not be possible: https://stackoverflow.com/a/55744858/1061602

However, this one suggests it might be possible with the right approach: https://stackoverflow.com/a/65298694/1061602

I can see there is a bunch of information here on dynamic imports and code splitting, and I've tried a few things out, but I can't quite figure out what it is I should be doing. https://webpack.js.org/guides/code-splitting/#dynamic-imports

There might be some other plugin for FontAwesome I could use instead.

Alternatively, someone may have written a massive switch statement along the lines of

switch (iconName) {
    case 'faCoffee':
       /* import faCoffee */
    case 'faSomethingElse':
       /* import faSomethingElse */

which would do the trick too.

0

There are 0 best solutions below