[PERF] mail, web: accelerate page loading by lazy-loading emoji data

Previously, the emoji data that is used in the emoji grid was part of
the main assets bundle. This file is almost 500kB and the data it needs
is only needed when opening the emoji grid. This commit moves this file
into its own bundle and only loads it when opening the emoji grid
instead.

This PR also batches the inserts in the emoji model, bringing the time
it takes to do those inserts from ~8000ms to ~800ms.

closes odoo/odoo#102562

X-original-commit: e272e4fc6abce3153cce3748d8a5a7df63565916
Related: odoo/enterprise#32468
Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
Samuel Degueldre
2022-10-07 13:37:11 +02:00
committed by Alexandre Kühn
parent 9c8bff35be
commit 89013fa42a
5 changed files with 56 additions and 34 deletions
+3 -1
View File
@@ -121,9 +121,11 @@ For more specific needs, you may also assign custom-defined actions
'mail.assets_messaging': [
('include', 'mail.assets_core_messaging'),
'mail/static/src/models/*.js',
'mail/static/src/models_data/*.js',
'mail/static/lib/selfie_segmentation/selfie_segmentation.js',
],
'mail.assets_model_data': [
'mail/static/src/models_data/*.js',
],
# Custom bundle in case we want to remove things that are later added to web.assets_common
'mail.assets_common_discuss_public': [
('include', 'web.assets_common'),
@@ -6,6 +6,14 @@ import { clear } from '@mail/model/model_field_command';
registerModel({
name: 'EmojiPickerView',
lifecycleHooks: {
_created() {
if (this.messaging.emojiRegistry.isLoaded || this.messaging.emojiRegistry.isLoading) {
return;
}
this.messaging.emojiRegistry.loadEmojiData();
},
},
fields: {
activeCategoryByGridViewScroll: one('EmojiPickerView.Category'),
activeCategory: one('EmojiPickerView.Category', {
+30 -27
View File
@@ -1,23 +1,24 @@
/** @odoo-module **/
import { registerModel } from '@mail/model/model_core';
import { many } from '@mail/model/model_field';
import { attr, many } from '@mail/model/model_field';
import { insert } from '@mail/model/model_field_command';
import { emojiCategoriesData, emojisData } from '@mail/models_data/emoji_data';
import { getBundle, loadBundle } from '@web/core/assets';
registerModel({
name: 'EmojiRegistry',
lifecycleHooks: {
_created() {
recordMethods: {
async loadEmojiData() {
this.update({ isLoading: true });
await getBundle('mail.assets_model_data').then(loadBundle);
const { emojiCategoriesData, emojisData } = await odoo.runtimeImport("@mail/models_data/emoji_data");
if (!this.exists()) {
return;
}
this._populateFromEmojiData(emojiCategoriesData, emojisData);
},
},
recordMethods: {
async _populateFromEmojiData(dataCategories, dataEmojis) {
await this.messaging.executeGracefully(dataCategories.map(category => () => {
if (!this.exists()) {
return;
}
dataCategories.map(category => {
const emojiCount = dataEmojis.reduce((acc, emoji) => emoji.category === category.name ? acc + 1 : acc, 0);
this.update({
dataCategories: insert({
@@ -27,23 +28,19 @@ registerModel({
emojiCount,
}),
});
}));
if (!this.exists()) {
return;
}
await this.messaging.executeGracefully(dataEmojis.map(emojiData => () => {
if (!this.exists()) {
return;
}
this.models['Emoji'].insert({
codepoints: emojiData.codepoints,
shortcodes: emojiData.shortcodes,
emoticons: emojiData.emoticons,
name: emojiData.name,
keywords: emojiData.keywords,
emojiDataCategory: { name: emojiData.category },
});
}));
});
this.models['Emoji'].insert(dataEmojis.map(emojiData => ({
codepoints: emojiData.codepoints,
shortcodes: emojiData.shortcodes,
emoticons: emojiData.emoticons,
name: emojiData.name,
keywords: emojiData.keywords,
emojiDataCategory: { name: emojiData.category },
})));
this.update({
isLoaded: true,
isLoading: false,
});
},
},
fields: {
@@ -59,5 +56,11 @@ registerModel({
sort: [['smaller-first', 'codepoints']],
}),
dataCategories: many('EmojiCategory'),
isLoaded: attr({
default: false,
}),
isLoading: attr({
default: false,
}),
},
});
@@ -5,7 +5,7 @@ import { registerPatch } from '@mail/model/model_core';
registerPatch({
name: 'EmojiRegistry',
recordMethods: {
async _populateFromEmojiData() {
async loadEmojiData() {
const dataEmojiCategories = [
{
"name": "Smileys & Emotion",
@@ -95,7 +95,7 @@ registerPatch({
]
},
];
this._super(dataEmojiCategories, dataEmojis);
this._populateFromEmojiData(dataEmojiCategories, dataEmojis);
},
},
});
+13 -4
View File
@@ -235,7 +235,9 @@
factories[name] = factory;
let promiseResolve;
const promise = new Promise(resolve => {promiseResolve = resolve;});
const promise = new Promise((resolve) => {
promiseResolve = resolve;
});
jobs.push({
name: name,
factory: factory,
@@ -372,13 +374,20 @@
* loaded. The value is equal to the number of services found.
*/
odoo.ready = async function (serviceName) {
function match (name) {
return typeof serviceName === 'string' ? name === serviceName : serviceName.test(name);
function match(name) {
return typeof serviceName === "string" ? name === serviceName : serviceName.test(name);
}
await Promise.all(jobs.filter(job => match(job.name)).map(job => job.promise));
await Promise.all(jobs.filter((job) => match(job.name)).map((job) => job.promise));
return Object.keys(factories).filter(match).length;
};
odoo.runtimeImport = function (moduleName) {
if (!(moduleName in services)) {
throw new Error(`Service "${moduleName} is not defined or isn't finished loading."`);
}
return services[moduleName];
};
// Automatically log errors detected when loading modules
globalThis.addEventListener("load", function logWhenLoaded() {
const len = jobPromises.length;