Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[I18n] Google page speed - reduce execution time? #815

Closed
koteezy opened this issue Jul 31, 2020 · 16 comments
Closed

[I18n] Google page speed - reduce execution time? #815

koteezy opened this issue Jul 31, 2020 · 16 comments

Comments

@koteezy
Copy link

koteezy commented Jul 31, 2020

Hi. Google says that the script for loading languages on mobile devices is very slow. 1.5 seconds, while the main application is 400ms, how i can fix that?

In General, why not add an option that will add JSON with translations inside the HTML page, similar to ExtractCss.

Google page speed

@koteezy
Copy link
Author

koteezy commented Jul 31, 2020

Also, I must say that when we specify one language - everything works well, but, unfortunately , we do not have one language, we have a lot of languages.

Each translation has a size from 13 to 16 KB.

List of languages (95)
[{
"code":"ru",
"iso":"ru",
"name":"Русский",
"file":"ru.json"
},
{
"code":"de",
"iso":"de",
"name":"Deutsch",
"file":"de.json"
},
{
"code":"be",
"iso":"be",
"name":"Беларуская",
"file":"be.json"
},
{
"code":"en",
"iso":"en",
"name":"English",
"file":"en.json"
},
{
"code":"hi",
"iso":"hi",
"name":"हिन्दी, हिंदी",
"file":"hi.json"
},
{
"code":"gd",
"iso":"gd",
"name":"Gàidhlig",
"file":"gd.json"
},
{
"code":"ja",
"iso":"ja",
"name":"日本語 (にほんご/にっぽんご)",
"file":"ja.json"
},
{
"code":"hy",
"iso":"hy",
"name":"Հայերեն",
"file":"hy.json"
},
{
"code":"xh",
"iso":"xh",
"name":"IsiXhosa",
"file":"xh.json"
},
{
"code":"ur",
"iso":"ur",
"name":"اردو",
"file":"ur.json"
},
{
"code":"th",
"iso":"th",
"name":"ไทย",
"file":"th.json"
},
{
"code":"cy",
"iso":"cy",
"name":"Cymraeg",
"file":"cy.json"
},
{
"code":"tr",
"iso":"tr",
"name":"Türkçe",
"file":"tr.json"
},
{
"code":"tl",
"iso":"tl",
"name":"Wikang Tagalog, ᜏᜒᜃᜅ᜔ ᜆᜄᜎᜓᜄ᜔",
"file":"tl.json"
},
{
"code":"tt",
"iso":"tt",
"name":"татарча, tatarça, تاتارچا‎",
"file":"tt.json"
},
{
"code":"udm",
"iso":"udm",
"name":"Удмуртский",
"file":"udm.json"
},
{
"code":"uk",
"iso":"uk",
"name":"українська",
"file":"uk.json"
},
{
"code":"fa",
"iso":"fa",
"name":"فارسی",
"file":"fa.json"
},
{
"code":"tg",
"iso":"tg",
"name":"тоҷикӣ, toğikī, تاجیکی‎",
"file":"tg.json"
},
{
"code":"eo",
"iso":"eo",
"name":"Esperanto",
"file":"eo.json"
},
{
"code":"te",
"iso":"te",
"name":"తెలుగు",
"file":"te.json"
},
{
"code":"ca",
"iso":"ca",
"name":"Català",
"file":"ca.json"
},
{
"code":"ta",
"iso":"ta",
"name":"தமிழ்",
"file":"ta.json"
},
{
"code":"am",
"iso":"am",
"name":"አማርኛ",
"file":"am.json"
},
{
"code":"sw",
"iso":"sw",
"name":"Kiswahili",
"file":"sw.json"
},
{
"code":"ka",
"iso":"ka",
"name":"ქართული",
"file":"ka.json"
},
{
"code":"sv",
"iso":"sv",
"name":"Svenska",
"file":"sv.json"
},
{
"code":"is",
"iso":"is",
"name":"Íslenska",
"file":"is.json"
},
{
"code":"su",
"iso":"su",
"name":"Basa Sunda",
"file":"su.json"
},
{
"code":"ht",
"iso":"ht",
"name":"Kreyòl ayisyen",
"file":"ht.json"
},
{
"code":"sr",
"iso":"sr",
"name":"српски језик",
"file":"sr.json"
},
{
"code":"gu",
"iso":"gu",
"name":"ગુજરાતી",
"file":"gu.json"
},
{
"code":"sq",
"iso":"sq",
"name":"Shqip",
"file":"sq.json"
},
{
"code":"fr",
"iso":"fr",
"name":"Français, langue française",
"file":"fr.json"
},
{
"code":"sl",
"iso":"sl",
"name":"Slovenščina",
"file":"sl.json"
},
{
"code":"et",
"iso":"et",
"name":"Eesti, eesti keel",
"file":"et.json"
},
{
"code":"sk",
"iso":"sk",
"name":"Slovenčina",
"file":"sk.json"
},
{
"code":"vi",
"iso":"vi",
"name":"Tiếng Việt",
"file":"vi.json"
},
{
"code":"si",
"iso":"si",
"name":"සිංහල",
"file":"si.json"
},
{
"code":"cs",
"iso":"cs",
"name":"česky, čeština",
"file":"cs.json"
},
{
"code":"yi",
"iso":"yi",
"name":"ייִדיש",
"file":"yi.json"
},
{
"code":"bn",
"iso":"bn",
"name":"বাংলা",
"file":"bn.json"
},
{
"code":"ko",
"iso":"ko",
"name":"한국어 (韓國語), 조선말 (朝鮮語)",
"file":"ko.json"
},
{
"code":"az",
"iso":"az",
"name":"Azərbaycan dili",
"file":"az.json"
},
{
"code":"km",
"iso":"km",
"name":"ភាសាខ្មែរ",
"file":"km.json"
},
{
"code":"sah",
"iso":"sah",
"name":"Якутский",
"file":"sah.json"
},
{
"code":"uz",
"iso":"uz",
"name":"Zbek, Ўзбек, أۇزبېك‎",
"file":"uz.json"
},
{
"code":"ky",
"iso":"ky",
"name":"кыргыз тили",
"file":"ky.json"
},
{
"code":"ro",
"iso":"ro",
"name":"Română",
"file":"ro.json"
},
{
"code":"kn",
"iso":"kn",
"name":"ಕನ್ನಡ",
"file":"kn.json"
},
{
"code":"pt",
"iso":"pt",
"name":"Português",
"file":"pt.json"
},
{
"code":"kk",
"iso":"kk",
"name":"Қазақ тілі",
"file":"kk.json"
},
{
"code":"pl",
"iso":"pl",
"name":"Polski",
"file":"pl.json"
},
{
"code":"jv",
"iso":"jv",
"name":"Basa Jawa",
"file":"jv.json"
},
{
"code":"pap",
"iso":"pap",
"name":"Папьяменто",
"file":"pap.json"
},
{
"code":"it",
"iso":"it",
"name":"Italiano",
"file":"it.json"
},
{
"code":"pa",
"iso":"pa",
"name":"ਪੰਜਾਬੀ, پنجابی‎",
"file":"pa.json"
},
{
"code":"id",
"iso":"id",
"name":"Bahasa Indonesia",
"file":"id.json"
},
{
"code":"no",
"iso":"no",
"name":"Norsk",
"file":"no.json"
},
{
"code":"hu",
"iso":"hu",
"name":"Magyar",
"file":"hu.json"
},
{
"code":"nl",
"iso":"nl",
"name":"Nederlands, Vlaams",
"file":"nl.json"
},
{
"code":"hr",
"iso":"hr",
"name":"Hrvatski",
"file":"hr.json"
},
{
"code":"ne",
"iso":"ne",
"name":"नेपाली",
"file":"ne.json"
},
{
"code":"he",
"iso":"he",
"name":"עברית",
"file":"he.json"
},
{
"code":"my",
"iso":"my",
"name":"ဗမာစာ",
"file":"my.json"
},
{
"code":"gl",
"iso":"gl",
"name":"Galego",
"file":"gl.json"
},
{
"code":"mt",
"iso":"mt",
"name":"Malti",
"file":"mt.json"
},
{
"code":"ga",
"iso":"ga",
"name":"Gaeilge",
"file":"ga.json"
},
{
"code":"ms",
"iso":"ms",
"name":"Bahasa Melayu, بهاس ملايو‎",
"file":"ms.json"
},
{
"code":"fi",
"iso":"fi",
"name":"Suomi, suomen kieli",
"file":"fi.json"
},
{
"code":"mrj",
"iso":"mrj",
"name":"Горномарийский",
"file":"mrj.json"
},
{
"code":"eu",
"iso":"eu",
"name":"Euskara, euskera",
"file":"eu.json"
},
{
"code":"mr",
"iso":"mr",
"name":"मराठी",
"file":"mr.json"
},
{
"code":"es",
"iso":"es",
"name":"Español, castellano",
"file":"es.json"
},
{
"code":"mn",
"iso":"mn",
"name":"монгол",
"file":"mn.json"
},
{
"code":"el",
"iso":"el",
"name":"Ελληνικά",
"file":"el.json"
},
{
"code":"ml",
"iso":"ml",
"name":"മലയാളം",
"file":"ml.json"
},
{
"code":"da",
"iso":"da",
"name":"Dansk",
"file":"da.json"
},
{
"code":"mk",
"iso":"mk",
"name":"македонски јазик",
"file":"mk.json"
},
{
"code":"cv",
"iso":"cv",
"name":"чӑваш чӗлхи",
"file":"cv.json"
},
{
"code":"mi",
"iso":"mi",
"name":"Te reo Māori",
"file":"mi.json"
},
{
"code":"ceb",
"iso":"ceb",
"name":"Себуанский",
"file":"ceb.json"
},
{
"code":"mhr",
"iso":"mhr",
"name":"Марийский",
"file":"mhr.json"
},
{
"code":"bs",
"iso":"bs",
"name":"Bosanski jezik",
"file":"bs.json"
},
{
"code":"mg",
"iso":"mg",
"name":"Malagasy fiteny",
"file":"mg.json"
},
{
"code":"bg",
"iso":"bg",
"name":"български език",
"file":"bg.json"
},
{
"code":"lv",
"iso":"lv",
"name":"Latviešu valoda",
"file":"lv.json"
},
{
"code":"ba",
"iso":"ba",
"name":"башҡорт теле",
"file":"ba.json"
},
{
"code":"lt",
"iso":"lt",
"name":"Lietuvių kalba",
"file":"lt.json"
},
{
"code":"ar",
"iso":"ar",
"name":"العربية",
"file":"ar.json"
},
{
"code":"lo",
"iso":"lo",
"name":"ພາສາລາວ",
"file":"lo.json"
},
{
"code":"af",
"iso":"af",
"name":"Afrikaans",
"file":"af.json"
},
{
"code":"lb",
"iso":"lb",
"name":"Lëtzebuergesch",
"file":"lb.json"
},
{
"code":"la",
"iso":"la",
"name":"Latine, lingua latina",
"file":"la.json"
},
{
"code":"zh",
"iso":"zh",
"name":"中文 (Zhōngwén), 汉语, 漢語",
"file":"zh.json"
}]

Nuxt.config.js

i18n: {
    locales, // 95
    defaultLocale: 'en',
    lazy: true,
    langDir: 'lang/',
},

Update: Damn, I specified one language with lazy: true - and the picture is about the same. 1000MS.

@rchl
Copy link
Collaborator

rchl commented Jul 31, 2020

This is interesting but I'm not sure why that measurement by Lighthouse is so slow and if it makes sense.

I'm also working with ~12KB JSON translation files and I'm seeing some ridiculously bad times in lighthouse for it:
Screenshot 2020-07-31 at 22 58 02

That javascript file basically has a one JSON.parse call and when I extract that code and run it in the console it seems to take 0 ms...
While, it surely would be slower during page loading (and also my testing is with a simulated mobile device) I'm not sure if it should be that slow...

In General, why not add an option that will add JSON with translations inside the HTML page, similar to ExtractCss.

At least when it comes to the "script evaluation" (which is the bottleneck here), it shouldn't really matter much if it's in the main HTML (in a script element) or in an external JS file. External JS file adds an overhead of an extra network request but shouldn't have any effect on how long it takes to evaluate the code.

BTW. According to the Internet (or v8 engine), parsing JSON object (like happens here) should be more efficient than parsing a JS object - https://v8.dev/blog/cost-of-javascript-2019#json
So it should be as efficient as possible already when it comes to the code.

@koteezy
Copy link
Author

koteezy commented Aug 1, 2020

Alright, i found something, looks like the main problem is count of routes:

helpers/routes.js

if i replace

locales = getLocaleCodes(locales)

to

locales = ['en']

the speed will increase hundreds of times.

With 95 languages, nuxt generating 1995 routes.

Do nuxt support thing like delayed loading routes, or something like that, for avoiding that?

@rchl
Copy link
Collaborator

rchl commented Aug 1, 2020

Large number of routes does affect performance, see #690 .

But I'm not sure that's directly related to the lighthouse issue because in my project I don't have that many translations nor routes (18 translations and 11 routes in total since I'm using no-prefix strategy).

@koteezy
Copy link
Author

koteezy commented Aug 1, 2020

I left only 2 locales, and run lighthouse several times, and got 60/80% on mobile, but when i make some changes in plugin.main.js

Namely, adding JSON right to DOM, for avoid unnecessary ajax request

if ( process.server ) {
    await beforeNuxtRender( async ( {nuxtState} ) => {
      await loadLanguageAsync( context, finalLocale );

      nuxtState.i18n = app.i18n.getLocaleMessage( finalLocale );
    } );
  }

And get it in loadAndSetLocale method

if ( process.client && initialSetup ) {
      app.i18n.setLocaleMessage( newLocale, nuxtState.i18n );
}

The performance has increased, to 90/95 for mobile, for web sometimes i get 100.

@rchl
Copy link
Collaborator

rchl commented Aug 1, 2020

I've tried that.

It has improved performance by a few points only (from 48 to 52 or so).

And as expected, the "Script Evaluation" time that was attributed to the lang file has now moved to the *.app bundle. So Lighthouse attributing all that slowness to the lang file is not really accurate.

To conclude:

  • The majority of the script evaluation budget is just Vue hydrating the page to make it reactive. It's not easy to tell how much of that budget is taken by any individual plugin (but probably not that much).
  • It makes sense to have the default locale in the main bundle already. Should improve the case when the client's locale matches the default one.

BTW. I don't want the solution that is based on the store to be the main one. This core functionality of this module should be functional even without the store.

@koteezy
Copy link
Author

koteezy commented Aug 1, 2020

It makes sense to have the default locale in the main bundle already. Should improve the case when the client's locale matches the default one.

Exactly! But, in which repository should we create an issue for that idea? Nuxt, Vue-router?

@Spunkie
Copy link

Spunkie commented Aug 1, 2020

And as expected, the "Script Evaluation" time that was attributed to the lang file has now moved to the *.app bundle. So Lighthouse attributing all that slowness to the lang file is not really accurate.

Maybe a good use case for user timings? That way it's easy to find how long i18n really took to evaluate in perf tools like lighthouse.

https://web.dev/user-timings/
https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API/Using_the_User_Timing_API

@rchl
Copy link
Collaborator

rchl commented Aug 1, 2020

Exactly! But, in which repository should we create an issue for that idea? Nuxt, Vue-router?

It's this module that is responsible for loading lang files. And I can use this issue to fix it.

@koteezy
Copy link
Author

koteezy commented Aug 1, 2020

Sounds good.

@rchl
Copy link
Collaborator

rchl commented Aug 2, 2020

Maybe a good use case for user timings? That way it's easy to find how long i18n really took to evaluate in perf tools like lighthouse.

Nuxt is in the best position to do that itself.
It would be important to then know metrics for all internal components and all used modules. If I would just implement it for this module, you would know that for example, this module takes 100ms to run but you still wouldn't know where the remaining 2900ms is spent.

So it would make sense to create a feature request for Nuxt, if there isn't one already.

@rchl
Copy link
Collaborator

rchl commented Aug 3, 2020

@koteezy Thanks for your suggestion from #815 (comment) . I have initially mistaken it as using store but nuxtState is not store so it's perfectly fine to use it. I've implemented that in #823

@rchl
Copy link
Collaborator

rchl commented Aug 4, 2020

With v6.13.3 there should be no extra network requests to fetch the languages on initial load.

I don't think it will make a big improvement but it's a good step.

Since those were the issues mentioned in here, I think this can be closed now.

@koteezy
Copy link
Author

koteezy commented Aug 4, 2020

@rchl Thanks you! How about large number of routes? (comment) Did you think is possible to load on client only user's language, or something like this?

@rchl
Copy link
Collaborator

rchl commented Aug 4, 2020

@rchl Thanks you! How about large number of routes? (comment)

There is already an issue #690 for that so we don't need to keep a separate one.

Did you think is possible to load on client only user's language, or something like this?

No, the issue is with initializing VueRouter with a large number of routes. It's an issue that affects both server and client performance. It's a fundamental issue in VueRouter and I'm not planning on doing any hacky workarounds for it.

@koteezy
Copy link
Author

koteezy commented Aug 4, 2020

Got it. Thanks anyway!

@koteezy koteezy closed this as completed Aug 4, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants