PERF: Stop eagerly-loading core helper modules (#24015)

Now that core has a file structure and default imports, Ember's resolver can load helpers lazily. So we can remove the lazy loading, and helpers in ember templates will continue to work. This should provide a slight performance improvement for initial boot.

However, there is a slight complication: some of our helpers are also registered with our Raw Handlebars system as a side-effect of loading the module. Therefore, this commit adds a `helperMissing` helper to our RawHandlebars system. This looks up the helper by name in the ember resolver, which triggers the relevant module to be evaluated, and the raw helper to be registered as a side effect.

For backwards-compatibility, plugin and theme helpers continue to be eagerly evaluated. Once the `discourse.register-unbound` deprecation is resolved, we can safely remove this eager loading.
This commit is contained in:
David Taylor 2023-10-19 15:52:51 +01:00 committed by GitHub
parent 98cb14dd82
commit 7ed6195f19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 38 additions and 4 deletions

View File

@ -4,10 +4,13 @@ export const RUNTIME_OPTIONS = {
allowProtoPropertiesByDefault: true,
};
export function registerRawHelpers(hbs, handlebarsClass) {
export function registerRawHelpers(hbs, handlebarsClass, owner) {
if (!hbs.helpers) {
hbs.helpers = Object.create(handlebarsClass.helpers);
}
lazyLoadHelpers(hbs, owner);
if (hbs.__helpers_registered) {
return;
}
@ -85,3 +88,24 @@ export function registerRawHelpers(hbs, handlebarsClass) {
stringCompatHelper("if");
stringCompatHelper("with");
}
function lazyLoadHelpers(hbs, owner) {
// Reimplements `helperMissing` so that it triggers a lookup() for
// a helper of that name. Means we don't need to eagerly load all
// helpers/* files during boot.
hbs.registerHelper("helperMissing", function (...args) {
const opts = args[args.length - 1];
if (opts?.name) {
// Lookup and evaluate the relevant module. Raw helpers may be registed as a side effect
owner.lookup(`helper:${opts.name}`);
if (hbs.helpers[opts.name]) {
// Helper now exists, invoke it
return hbs.helpers[opts.name]?.call(this, ...arguments);
} else {
// Not a helper, treat as property
return hbs.helpers["get"].call(this, ...arguments);
}
}
});
}

View File

@ -7,12 +7,22 @@ import {
import RawHandlebars from "discourse-common/lib/raw-handlebars";
import { registerRawHelpers } from "discourse-common/lib/raw-handlebars-helpers";
function isThemeOrPluginHelper(path) {
return (
path.includes("/helpers/") &&
(path.startsWith("discourse/theme-") ||
path.startsWith("discourse/plugins/")) &&
!path.endsWith("-test")
);
}
export function autoLoadModules(owner, registry) {
Object.keys(requirejs.entries).forEach((entry) => {
if (/\/helpers\//.test(entry) && !/-test/.test(entry)) {
if (isThemeOrPluginHelper(entry)) {
// Once the discourse.register-unbound deprecation is resolved, we can remove this eager loading
requirejs(entry, null, null, true);
}
if (/\/widgets\//.test(entry) && !/-test/.test(entry)) {
if (entry.includes("/widgets/") && !entry.endsWith("-test")) {
requirejs(entry, null, null, true);
}
});
@ -31,7 +41,7 @@ export function autoLoadModules(owner, registry) {
createHelperContext(context);
registerHelpers(registry);
registerRawHelpers(RawHandlebars, Handlebars);
registerRawHelpers(RawHandlebars, Handlebars, owner);
}
export default {