Need help with build logic for Ember Addon

Hey everyone. I was wondering if someone could give me advice on adding things to my addon tree (from other addons and apps).

This is what I want to do (need help for step 2: BUILD PROCESS):

1. DEFINE MOCKERS

// my-app/mockers/rest-mocker.js
import MswMocker from 'ember-mocking/mockers/msw';
  
export default class SomeRestMocker extends MswMocker {
  handlers = [
    this.rest.get('/some-api-endpoint', (req, res, ctx) => {
      return res(ctx.status(200), ctx.text("Hello World!"))
    });
  ];
}

2. BUILD PROCESS (index.js, broccoli plugin…?)

Add all mockers from apps and addons to the addon tree (treeForAddon)

Example

INPUT OUTPUT
my-app/mockers/rest-mocker.js ember-mocking/mockers/rest-mocker.js
some-addon/mockers/socket-mocker.js ember-mocking/mockers/some-addon/socket-mocker.js
some-addon/mockers/rest-mocker.js ember-mocking/mockers/some-addon/rest-mocker.js
another-addon/mockers/storage-mocker.js ember-mocking/mockers/another-addon/storage-mocker.js

QUESTIONS:

  • How can we build the mockers directory in the addon tree?
  • Is it possible to use TypeScript in these mocker files?

3. LIST ALL MODULES (initializer)

Get an array of all modules the mockers folder of the addon tree (aka ember-mocking/mockers)

Example

const mockerModules = [
  'ember-mocking/mockers/rest-mocker.js',
  'ember-mocking/mockers/some-addon/socket-mocker.js',
  'ember-mocking/mockers/some-addon/rest-mocker.js',
  'ember-mocking/mockers/another-addon/storage-mocker.js',
]

4. CREATE INSTANCES (initializer)

Create an instance of each mocker and register it in the container

for (const module of mockerModules) {
  const mockerClass = require(module);
  ...
}

Taking a module out of another package and emitting it from your own treeForAddon is a problematic thing to do. There are definitely addons that do it (you should look at ember-cli-mirage as an example that does some of what you’re asking about), but those addons are causing problems as people try to adopt more modern build tooling that supports things like code splitting and instant dev rebuilds.

The main problem is that when you move modules around in the module graph (which is what happens when you emit some other package’s code in your own treeForAddon hook), you’re changing the meaning of import statements in those packages. In a classic ember build you’re also changing how those files will get preprocessed, since that is controlled per-package. And under some conditions, you’re changing whether that file will get watched for rebuilds, since it looks like it’s now owned by your addon and not the app (or original addon).

Having an explicit API for registering your mockers is going to require less code for you to maintain and won’t have any of these downsides. I would suggest making a main mockers config module in your app and importing all the mockers into it and registering them. This is easier on human readers too, because they can see where all the mockers are coming from instead of needing to search through all their dependencies.

1 Like

Do you have any examples of explicit APIs like this in other Ember Addons?

I mean something like this:

import SocketMocker from "some-addon/socket-mocker";
import RestMocker from "some-addon/rest-mocker";
import StorageMocker from "another-addon/storage-mocker";

import { register } from 'ember-mocking';

register(SocketMocker);
register(RestMocker);
register(StorageMocker);

register() would do the work that you would have already been doing in the “…” in your Step 4 above:

-for (const module of mockerModules) {
-  const mockerClass = require(module);
-  ...
-}
+function register(mockerClass) {
+ ...
+}