discourse/app/assets/javascripts/discourse-common/addon/lib/discourse-template-map.js
David Taylor c139767055
DEV: Remove Ember.TEMPLATES and centralize template resolution rules (#19220)
In the past, the result of template compilation would be stored directly in `Ember.TEMPLATES`. Following the move to more modern ember-cli-based compilation, templates are now compiled to es6 modules. To handle forward/backwards compatibility during these changes we had logic in `discourse-boot` which would extract templates from the es6 modules and store them into the legacy-style `Ember.TEMPLATES` object.

This commit removes that shim, and updates our resolver to fetch templates directly from es6 modules. This is closer to how 'vanilla' Ember handles template resolution. We still have a lot of discourse-specific logic, but now it is centralised in one location and should be easier to understand and normalize in future.

This commit should not introduce any behaviour change.
2022-11-29 10:24:35 +00:00

103 lines
2.6 KiB
JavaScript

const pluginRegex = /^discourse\/plugins\/([^\/]+)\/(.*)$/;
const themeRegex = /^discourse\/theme-([^\/]+)\/(.*)$/;
function appendToCache(cache, key, value) {
let cachedValue = cache.get(key);
cachedValue ??= [];
cachedValue.push(value);
cache.set(key, cachedValue);
}
const NAMESPACES = ["discourse/", "wizard/", "admin/"];
function isInRecognisedNamespace(moduleName) {
for (const ns of NAMESPACES) {
if (moduleName.startsWith(ns)) {
return true;
}
}
return false;
}
function isTemplate(moduleName) {
return moduleName.includes("/templates/");
}
/**
* This class provides takes set of core/plugin/theme modules, finds the template modules,
* and makes an efficient lookup table for the resolver to use. It takes care of sourcing
* component/route templates from themes/plugins, and also handles template overrides.
*/
class DiscourseTemplateMap {
coreTemplates = new Map();
pluginTemplates = new Map();
themeTemplates = new Map();
prioritizedCaches = [
this.themeTemplates,
this.pluginTemplates,
this.coreTemplates,
];
/**
* Reset the TemplateMap to use the supplied module names. It is expected that the list
* will be generated using `Object.keys(requirejs.entries)`.
*/
setModuleNames(moduleNames) {
this.coreTemplates.clear();
this.pluginTemplates.clear();
this.themeTemplates.clear();
for (const moduleName of moduleNames) {
if (isInRecognisedNamespace(moduleName) && isTemplate(moduleName)) {
this.#add(moduleName);
}
}
}
#add(originalPath) {
let path = originalPath;
let pluginMatch, themeMatch, cache;
if ((pluginMatch = path.match(pluginRegex))) {
path = pluginMatch[2];
cache = this.pluginTemplates;
} else if ((themeMatch = path.match(themeRegex))) {
path = themeMatch[2];
cache = this.themeTemplates;
} else {
cache = this.coreTemplates;
}
path = path.replace(/^discourse\/templates\//, "");
appendToCache(cache, path, originalPath);
}
/**
* Resolve a template name to a module name, taking into account
* theme/plugin namespaces and overrides.
*/
resolve(name) {
for (const cache of this.prioritizedCaches) {
const val = cache.get(name);
if (val) {
return val[val.length - 1];
}
}
}
/**
* List all available template keys, after theme/plugin namespaces have
* been stripped.
*/
keys() {
const uniqueKeys = new Set([
...this.coreTemplates.keys(),
...this.pluginTemplates.keys(),
...this.themeTemplates.keys(),
]);
return [...uniqueKeys];
}
}
export default new DiscourseTemplateMap();