From 89013fa42a4cfdcf0f548954915e3a51dae73938 Mon Sep 17 00:00:00 2001 From: Samuel Degueldre Date: Thu, 6 Oct 2022 11:45:14 +0000 Subject: [PATCH] [PERF] mail, web: accelerate page loading by lazy-loading emoji data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- addons/mail/__manifest__.py | 4 +- .../static/src/models/emoji_picker_view.js | 8 +++ .../mail/static/src/models/emoji_registry.js | 57 ++++++++++--------- .../models/emoji_registry_qunit_tests.js | 4 +- addons/web/static/src/boot.js | 17 ++++-- 5 files changed, 56 insertions(+), 34 deletions(-) diff --git a/addons/mail/__manifest__.py b/addons/mail/__manifest__.py index ce912d8d49d..dbb66178680 100644 --- a/addons/mail/__manifest__.py +++ b/addons/mail/__manifest__.py @@ -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'), diff --git a/addons/mail/static/src/models/emoji_picker_view.js b/addons/mail/static/src/models/emoji_picker_view.js index 99f3a897781..f569f7321eb 100644 --- a/addons/mail/static/src/models/emoji_picker_view.js +++ b/addons/mail/static/src/models/emoji_picker_view.js @@ -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', { diff --git a/addons/mail/static/src/models/emoji_registry.js b/addons/mail/static/src/models/emoji_registry.js index ce3c42e4031..2b69d7e4de0 100644 --- a/addons/mail/static/src/models/emoji_registry.js +++ b/addons/mail/static/src/models/emoji_registry.js @@ -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, + }), }, }); diff --git a/addons/mail/static/tests/models/emoji_registry_qunit_tests.js b/addons/mail/static/tests/models/emoji_registry_qunit_tests.js index 91f9c7be887..a963aa1cf92 100644 --- a/addons/mail/static/tests/models/emoji_registry_qunit_tests.js +++ b/addons/mail/static/tests/models/emoji_registry_qunit_tests.js @@ -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); }, }, }); diff --git a/addons/web/static/src/boot.js b/addons/web/static/src/boot.js index 0e573ac123a..fc42d8108af 100644 --- a/addons/web/static/src/boot.js +++ b/addons/web/static/src/boot.js @@ -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;