Import dynamic list/array of files in an addon with ember-auto-import

So I maintain an addon that can have a different list of files to import based upon configuration set in ember-cli-build.js (so it’s build-time dynamic, not run-time dynamic). Currently I just app.import() the appropriate files, but I’m not sure what the appropriate ember-auto-import solution is to actually import those files. As a starting point, lets just say I build an array of string filepaths to import in index.js. What next? End goal, provide “embroider optimized” compatibility, which EAI seems to be a prerequisite.?. Thanks for any help/guidance!

One thing I tried (and failed) at was using @embroider/macros to put the array of filepaths in setOwnConfig and then access it in the component file via getOwnConfig(). But neither importSync() nor dynamic import() worked, both required “string literals” so no variables… :frowning:

getOwnConfig().arrayOfFilepaths.foreach(filepath => importSync(filepath));
getOwnConfig().arrayOfFilepaths.foreach(filepath => import(filepath));

Another requirement I forgot to mention, there is a “standard”/“accessible” import that happens in the component file, and these additional files need to be imported after that. They don’t need to be accessible (import foobar from...), just in the build output. Ex:

import Component from '@glimmer/component';
import 3rdPartyLib from '3rd-party-lib';

// These must be imported after the '3rd-party-lib' above
getOwnConfig().arrayOfFilepaths.foreach(filepath => import(filepath));

export default class MyComponent extends Component {}

My Google Fu is failing me, just not finding anything on this topic. My options at this point seem to be;

  1. Do not import additional assets, make consumers of the addon extend the component and explicitly import the “plugin files” they want to use.

  2. Explicitly list each possible plugin that can be enabled (as @embroider/macro config) and make 83 if statements in the component file that use “string literals” for the import statement.

I am torn on which way to go. Anyone know what other addons are doing in these cases??

I’m talking out of my butt here so take this with a grain of salt but I feel like the answer is probably “what would you do if you were just using webpack” and you could look at an equivalent react/vue package for reference.

I am a bit late on an answer here, but I went through a somewhat similar problem for ember-gettext, where users can define locales, which will end up having files somewhere (e.g. app/locales/de.js or app/locales/it.js) which should be imported by an service from the addon. So basically, when a user calls l10nService.setLocale('de'), I want it to load the file app/locales/de.js and use the locale content from that file accordingly.

What I ended up doing is to generate a static file in the host app, like this:

// app/locales/index.js

const map = {
  de: () => import('./de.js'),
  en: () => import('./en.js'),
  ko: () => import('./ko.js')
};  

export default map;

And then load the data via that file, in my service, e.g. like this:

import localeLoadingMap from 'my-app/locales';

async function loadLocale(locale) {
   let loadFn = localeLoadingMap[locale];

   let localeContent = await loadFn();
  // do something with the content
}

In combination with setting staticPaths for embroider in the host app, like this:

// ember-cli-build.js

return require('@embroider/compat').compatBuild(app, Webpack, {
  // Ensure `locales` are considered static & lazy loaded
  staticAppPaths: ['locales'],
});

This leads to the locales being fetched lazily, when needed only.

Maybe this gives you some ideas at least on how to achieve something similar!

Thanks for the info, although I think you import based on run-time info whereas I’m doing it at build-time. I was able to get the importSync() approach working by moving part of the path into a string template (package-name/${filepath}) to get around the “string literal” error.

Also, I had some discussion over on the ember-auto-import and embroider repo’s about this as well and discovered an improved solution. Instead of lazy loading / including all of the possible files in the build output, you can use the each() macro to unroll the loop and turn dynamic imports into static imports. The RFC example for this (while not working as indented just yet):

// This example:
import { getOwnConfig, each, importSync } from "@ember/macros";
let plugins = [];
for (let plugin of each(getOwnConfig().registeredPlugins)) {
  plugins.push(importSync(plugin).default);
}

// could compile to this, given OwnConfig
// containing { registeredPlugins: ['@bigco/bar-chart', '@bigco/line-chart']}

let plugins = [];
plugins.push(importSync("@bigco/bar-chart").default);
plugins.push(importSync("@bigco/line-chart").default);