Abstracting routes away


#1

Hi,

I have an Ember.js project that is a room planner. We try to sell it as it is but the customer usually wants customisations made to it. One of the battles I’ve had while changing my plain javascript/html project to Ember.js is attempting to keep the different versions of the planner separated at the same time of reusing components. At the same time I was becoming familiar with Ember.js so wasn’t always making the right choices as I progressed. All the time I’m wondering whether I should have simply created a whole new project for each customer, or attempt to keep the same one but make it more customisable.

I found that creating one project for all customers forced me to write the code better and more modular, but then it means I have a more complicated config file, and means in my router.js I have a few routes that reply on a config setting that specifies which customer it is.

What I would find useful is a bit way to kind of abstract the routes away. If Company A has a few child routes that Company B did not need. I dislike the Company A routes being in the router.js. It would be better if router.js stayed abstract in nature and didn’t assume anything. Can I put these this.route calls into a different file?

There seems nowhere to inject a service, which I would normally do by name, extracted from data, if I wanted to separate out functionality.

Does anyone have any opinion about whether I’d be better off just keeping separate build environments for each customer (it would give the freedom) or continuing to try and keep all customers inside the same project.

I worry that the file watcher will be more burdened, the more customers I have. I’m almost duplicating code now anyway, changing the templates at the top level. But this is still useful to do as it makes a better starting point for a new customer.

The other thing I find awkward is CSS between customers. I use div company-acme as a parent selector in all my styles so that the styles for one customer do not affect the styles for another - because they usually are quite different. It means when there is a new customer I have to go through the project changing these parent selectors to div.acme-2 to change the company name to kind of create a new space. But of course this means I’m injecting all CSS styles into the browser, even if the browser is not using a large subset of them. Again, it feels wrong and gives me cause to think I should keep a separate build environment for each customer who has customised the planner.

There must be a better way? Or is there not?


#2

I think I would use ember-cli-deploy and fix things in th deploy pipeline in this case. See for example:

And:


#3

I appreciate your replying, but I just went to that page, read through it and sorry to say, I’m none the wiser. These add-on pages need to explain the advantages more. I could not see where the positives are or what I will gain from the plug-in.


#4

One off the positives is that you can create one Ember App and control what is build en uploaded depending on what and where to you are deploying.

ember deploy customer1
ember deploy customer2

See also Ember-cli-deploy, different build targets

We use ember-cli-deploy on every project and get value from it. With your App per customer you will secure with ember-cli-deploy in Git how each customer is deployed.

It is how I would solve your case. There are many other ways. @dknutsen can you help with some tips here?


#5

I believe you could change the contents of router.js depending on your environment config, so you could set a flag in your env config based on what customer you’re building for and then only declare certain routes if that flag is set. For example in one of our older apps we have a route hidden behind a feature flag in router.js:

...
Router.map(function() {
  ...
  if(config.APP.ENABLE_OVERVIEW){
    this.route("overview", { path: "/overview"});
  }
  ...
});
...

And of course ember-cli-deploy would allow you to set up a nice build/deploy pipeline for a wide variety of different configurations and deployment targets.

All that said, depending on how many customers you intend to gain/end up with, keeping everything in one app might become unsustainable, or so complex that it’s not worth the convenience of having a single app repo. Obviously if you’re maintaining a ton of separate projects and keeping the updated that could be a huge pain too, there’s tradeoffs in both directions. If it were me, and I thought there were going to end up being a lot of these different apps I’d probably try and abstract as much as possible into a private addon, then come up with some sort of ember app starter kit that installs a fresh ember app, your private shared code addon, and whatever other common addons/libraries you use. Maintaining an addon can be kind of a pain too but it at least allows you to abstract out shared code well, keep separate projects which may be more maintainable, and enables much easer CSS management. Also depending on how you write the addon you could probably manage to compile only what’s used in the final vendor output which would address one of your other concerns.

Anyway, that’s my 2 cents. Really depends on the use case and how much customization is involved and how many customers you’ll have with custom configs. We “whitelabel” one of our apps for multiple customers, and it’s easy to do in one repo because the customizations are pretty minimal and there are only a few different versions. I could see it getting out of hand really quickly with more customers though.


#6

Appreciate your replies. I am currently doing the ENV.config and rouer.js method you mentioned. It’s what I didn’t like.

It did become unsustainable to have just 5 customers, and now I’ve noticed I have simply changed the whole tree of templates at the top level, depending on customer. Which isn’t that much different to having a separate build environment for each customer.

Since you also have talked about ember-cli-deploy, and add-ons I need to do a bit of research in that probably to see if they can help in anyway.


#7

Only moderately relevant, but Square just posted about how they manage multiple repos across projects using yarn workspaces, could be worth a look if you’re thinking about addons: https://medium.com/square-corner-blog/ember-and-yarn-workspaces-fca69dc5d44a


#8

You may find this talk from EmberConf 2018 interesting. IIRC, the speaker advocates keeping a vanilla, unbranded template repository that is forked for each new client, and even mentions being certain that any improvements that could be considered generic or widely useful are propagated back up into the template repository.

I haven’t used them at all, but the only way I’m aware of to define routes outside of the router.js file is using Ember Engines. I spoke to some folks at EmberConf who said they’ve build their entire app composed of engines, and they seemed to like it! :man_shrugging:

Good luck, sounds like you’re figuring it out!


#9

Can I put these this.route calls into a different file?

well, you can:

import myCustomerRouter from './my-customer-router';

...

Router.map(function() {
  myCustomerRouter.apply(this);
});

and then inside my-custom-router.js:

export default function() {
  this.route('foo');
};

Another approach:

I have a quite similar use-case, and what I’m doing is to use different branches. Next I merge master to the customer branches whenever I wish updates. For stlyes and customer extensions this works great. And for configuration I just use the config/environment.js file.


#10

So I suppose in theory could you do:

import myCustomerRouter from ENV.my_customer_router; ?


#11

I like the idea now of the vanilla copy, and then branching off it. It seems to be kind of what I do anyway at the heart of it. I was just going about it in a more roundabout way. Thanks.