Regarding future-supported patterns: controlling component and helper registration at runtime via an instance initializer is definitely not going to make sense anymore with strict mode templates and template imports. Where we’re headed, components, helpers, and modifiers won’t go through the runtime resolver at all.
Also, you’re not going to be able to use embroider’s route splitting if you’re registering components at runtime. It needs to know which ones are used on every route, so they need to all come from their conventional locations. If you really want to swap out implementations under embroider, you can, but you’ll want to do it via @embroider/macros, and that would also mean having visible branches in your code itself, rather than magically replacing whole files.
This is not how it works in the standard build. Co-located templates go down a very different path than traditional standalone templates.
Okay, my templates must be getting compiled to JS because they are not where ember normally expects to find them.
you should be able to adapt your customized build to do that if you want to keep doing what you’ve been doing, just with co-located templates
Very clear explanation on to use setComponentTemplate. I think my only remaining question – which might be irrelevant if we take your advice on going a different path – would be how to pass a template to setComponentTemplate when the template is a separate hbs file and not an inline string.
Being able to override whole files is a great way to move fast and then end up with something very hard to maintain.
Some of the primary reasons we’ve gone down the override-whole-files path so far are:
We need to ultimately support 14 versions. The idea of having any condition with 14 branches really turned us away from using conditional and more towards overriding files.
It does seem simple and clean at the onset. When working on the applications, the rule just becomes ‘navigate to the correct versions directory and work away.’
when you’re overriding whole files, you can’t choose to make a tiny customization, every customization copies all the code and gives you yet another copy to maintain.
As we have pushed the override-whole-files approach further, we have seen exactly what you describe e.g., if there’s one small customization in a file it often requires copying the entire file – even parts that are not version-specific
.
Your suggestions and examples on how to manage features and conditionals are helpful.
controlling component and helper registration at runtime via an instance initializer is definitely not going to make sense anymore
More than anything, the things you’ve clarified about the future of components is going to push us to rework how we’re handling versions. The thing that sparked taking another look at it in the first place was taking steps toward embroider compatibility, and that remains a primary goal.
After discussing your examples and thinking more about them, I think we might end up with a blend of what you’ve suggested, something like the following for components:
// app/service/version-manager.ts
export default class VersionManagerService extends Service {
config = getOwner(this).lookup('main:config');
version: Version = this.config.version;
// app/components/example/index.ts
import Icon1 from './versions/icon-1';
import Icon2 from './versions/icon-2';
import Icon3 from './versions/icon-3';
const IconVersions = {
version1: Icon1,
version2: Icon2,
version3: Icon3,
export default class Example extends Component {
@service versionManager: VersionManager;
get icon() {
return VersionIcons[this.versionManager.version];
{{! app/component/example/index.hbs }}
<this.icon />
Another route we are looking at is using decorators to handle drying up the conditionals, for example:
import macro from 'macro-decorators';
import { getOwner } from '@ember/application';
import { dasherize } from '@ember/string';
export function columnsFor(fileType: string): PropertyDecorator {
return macro({
get() {
const owner = getOwner(this);
const versionManager = owner.lookup(`service:version-manager`);
let columnDefs;
// Try to find version-specific columns
columnDefs = owner.factoryFor(`util:tables/column-definitions/styles/${versionManager.version}/${dasherize(fileType)}`)?.class;
// If no version-specific columns are found, fallback to the defaults
if (!columnDefs) {
columnDefs = owner.factoryFor(`util:tables/column-definitions/${dasherize(fileType)}`)?.class;
return columnDefs.class?.columns;
and usage:
export default class SomeController extends Controller {
@columnsFor('some-table') tableColumns: Array<ColumnDefinition>;
Thank you again @ef4 