Skip to content

Kõik WordPressist, veebiarendusest — ja mitte ainult

⚡ Kuidas laadida välist JavaScripti ilma lehte blokeerimata

⚡ Kuidas laadida välist JavaScripti ilma lehte blokeerimata

Kui brauser kohtab <script> silti ilma atribuutideta, jätab see kõik pooleli. Lehe renderdamine peatub täielikult, kuni skript laaditakse ja käivitatakse. Aeglase 4G ühenduse korral tähendab see 2-3 sekundit tühja ekraani.

Kasutaja on selleks ajaks juba konkurendi juurde lahkunud. Core Web Vitals registreerib läbikukkunud LCP, Google surub lehe otsingutulemustes allapoole ja sina kaotad liiklust ning konversioone. Samas saab probleemi lahendada kolme reaga, kui tead, kust otsida.

Allpool on töötav meetod välise JavaScripti laadimiseks ilma blokeerimiseta. Alates klassikalisest kahefaililisest lähenemisest kuni kaasaegsete async/defer ja dünaamilise import()-ini. Koos tõestatud koodiga, mida saad kopeerida ja kleepida.

💡 Kiirülevaade:

  • Mõista probleemi: kuidas tavaline <script> blokeerib HTML-i parsimise ja tapab laadimiskiiruse
  • Õpi selgeks klassikaline lähenemine: tilluke laadur (≤300 baiti) tõmbab dünaamiliselt sisse peamise JS-faili
  • Õpi tundma async ja defer atribuute: millal ja kumba kasutada
  • Tutvu dünaamilise import()-iga moodulite laadimiseks nõudmisel
  • Vali oma projektile strateegia võrdlustabeli abil

Miks JavaScript renderdamist blokeerib

Kui HTML-i parser jõuab reani <script src="app.js">, teeb see täpselt kolm asja: peatab dokumendi parsimise, laadib faili alla, käivitab selle. Alles pärast seda jätkab DOM-i ehitamist.

Põhjus on arhitektuuriline. Skript võib sisaldada document.write(), mis muudab HTML-i lennult. Brauser ei tea ette, kas selline kutse on olemas, seega mängib ta kindla peale ja ootab täielikku laadimist ja käivitamist. Tulemus: isegi kerge 5 KB skript lisab ainuüksi ühest võrgupöördusest sadu millisekundeid First Contentful Paint ajale.

Probleem pole uus. Juba 2009. aastal kirjeldas Nicholas Zakas dünaamilise mitteblokeeriva JavaScripti laadimise tehnikat ja see töötab tänaseni, kuigi kohandustega kaasaegsetele API-dele. async, defer ja ES-moodulite tulekuga on arendajatel nüüd terve tööriistakast. Vaatame igaühte neist.

Klassikaline lähenemine: kaks faili ja dünaamiline laadimine

Idee on lihtne. Selle asemel, et panna kogu JS ühte faili ja kinnitada see lehele <script src="..."> kaudu, jagad koodi kaheks osaks:

  • Tilluke laadur (200-300 baiti pärast tihendamist)
  • Põhifail rakenduse loogikaga

Laadur paigutatakse inline'is lehe lõppu, vahetult enne </body>. See loob <script> programselt ja lisab selle DOM-i, selline silt ei blokeeri enam parsimist, sest see ilmub väljaspool peamist dokumendivoogu. Niipea kui põhifail laaditakse, käivitub initsialiseerimine.

Funktsiooni kaasaegne versioon puhtas JS-is ilma IE tagasiühilduvuseta:

1function loadScript(url) {
2 return new Promise((resolve, reject) => {
3 const script = document.createElement('script');
4 script.src = url;
5 script.onload = resolve;
6 script.onerror = reject;
7 document.head.appendChild(script);
8 });
9}

Üheksa rida. Ei mingeid readyState kontrolle, harusid vana IE jaoks ega callbackide püramiidi. Lihtsalt funktsioon, mis tagastab Promise'i, mugav kombineerimiseks async/await-ga.

Kasutamine lehel näeb välja selline (kood all, enne sulgevat </body>):

1<script>
2 function loadScript(url) {
3 return new Promise((resolve, reject) => {
4 const script = document.createElement('script');
5 script.src = url;
6 script.onload = resolve;
7 script.onerror = reject;
8 document.head.appendChild(script);
9 });
10 }
11
12 loadScript('/js/app.js').then(() => {
13 // Initialize after main file loads
14 App.init();
15 });
16</script>

Esimene skript (inline) on laadur. See parsitakse ja käivitatakse koheselt, sest see on alla 300 baidi. Teine skript (app.js) laaditakse asünkroonselt ega sega renderdamist.

Mis siis, kui sul on rohkem kui kaks faili? Kombineeri need ehituse käigus. Kaasaegsed komplekteerijad nagu Vite ja Webpack teevad seda automaatselt: tree-shaking, koodi poolitamine, minifitseerimine ühe hooga. Kümmekonna faili laadimisjärjekorra käsitsi haldamine on tee võidujooksutingimuste ja vigadeni.

Async ja defer: natiivne blokeeringu eemaldamine

HTML5 andis meile kaks atribuuti, mis lahendavad probleemi ilma ühegi JavaScripti reata:

1<script async src="analytics.js"></script>
2<script defer src="app.js"></script>

Mõlemad laadivad faili paralleelselt HTML-i parsimisega. Erinevus on käivitamise ajastuses:

Atribuut

Laadimine

Käivitamine

Järjekord

async

Paralleelselt parsimisega

Kohe pärast laadimist

Pole garanteeritud

defer

Paralleelselt parsimisega

Pärast täielikku HTML-i parsimist

Garanteeritud (nagu dokumendis)

Rusikareegel:

  • async sõltumatute skriptide jaoks: analüütika, reklaamid, loendurid. Need ei vaja DOM-i, neid ei huvita järjekord.
  • defer põhirakenduse jaoks: DOM-iga manipuleerimine, liidese initsialiseerimine. Skript ootab lehe valmisolekut ja käivitub õiges järjestuses.

Praktikas on kombinatsioon lihtne: pane defer kõigile <head>-is olevatele skriptidele ja need käituvad nii, nagu oleksid lehe lõpus, kuid laadivad varem. Ei mingit maagiat, lihtsalt brauseri ajastaja.

Ja jah, saad seda kombineerida dünaamilise laadimisega. Näiteks laadi rakenduse tuum <script defer> kaudu ja kinnita rasked vidinad dünaamiliselt loadScript() abil ainult siis, kui neid tegelikult vaja on.

Dünaamiline import(): moodulid nõudmisel

ES2020 tõi dünaamilise import(), natiivse viisi mooduli asünkroonseks laadimiseks, ilma komplekteerijata ja ilma lisafunktsioonideta:

1// Loads only when user clicked
2button.addEventListener('click', async () => {
3 const { heavyChart } = await import('./chart-component.js');
4 heavyChart.render();
5});

import() kutse tagastab Promise'i. Moodul laaditakse taustal, parsimine pole blokeeritud, leht jääb reageerivaks. Mooduli sees olev kood käivitub rangal režiimil ja oma skoobis, nimede konfliktid on välistatud.

See on ideaalne tööriist koodi poolitamiseks ilma komplekteerijata. Rasked komponendid (diagrammid, redaktorid, kaardid) viiakse eraldi failidesse ja laaditakse esimesel interaktsioonil. Kasutaja, kes kunagi diagrammi ei ava, ei maksa selle eest liikluse ja laadimisajaga.

Lähenemiste võrdlus

Igal meetodil on oma nišš. Et vältida oletamist, koondasime omadused tabelisse:

Lähenemine

Blokeerib renderdamist

Nõuab JS-i

Käivitamise järjekord

Milliste skriptide jaoks

<script src>

Jah

Ei

Garanteeritud

Ei kasutata, kui pole hädavajalik

Dünaamiline loadScript

Ei

Jah

.then() ahela kaudu

Tingimuslik laadimine, rasked sõltuvused

<script async>

Ei

Ei

Pole garanteeritud

Analüütika, reklaamid, loendurid

<script defer>

Ei

Ei

Garanteeritud

Põhirakendus, DOM-iga manipuleerimine

import()

Ei

Jah (ES moodul)

await kaudu

Koodi poolitamine, nõudmisel komponendid

Peamine järeldus: ära fikseeru ühel meetodil. Tüüpiline toodangu seadistus kasutab korraga kahte või kolme: defer tuuma jaoks, async mõõdikute jaoks, dünaamiline import() raskete komponentide jaoks.

Lühike demovideo teemal, async ja defer lahtiseletus koos laadimise ajajoone visualiseerimisega:

⁉️🤔 Korduma kippuvad küsimused

Kuidas async erineb defer-ist praktikas?

Mõlemad ei blokeeri laadimise ajal parsimist. Kuid async käivitab skripti kohe pärast faili laadimist, isegi kui HTML pole veel täielikult parsitud, ja ilma järjekorra garantiita. defer ootab alati DOM-i täielikku valmisolekut ja säilitab skriptide järjestuse nagu HTML-is. Põhirakenduse koodi jaoks kasuta defer, isoleeritud loendurite jaoks async.

Kas dünaamilist laadimist saab kombineerida defer-iga?

Jah, see on tavaline stsenaarium. Rakenduse tuum laaditakse defer-iga <head>-is, see initsialiseerib liidese. Rasked või harva kasutatavad moodulid tõmmatakse dünaamilise loadScript() või import() abil kasutaja interaktsioonil. Nii saad nii kiire käivitumise kui ka sekundaarse koodi edasilükatud laadimise.

Mida peaksin kasutama WordPressi saidi puhul?

WordPress lisab automaatselt defer või async wp_enqueue_script() kaudu, kui edastad vastava argumendi viiendas parameetris: wp_enqueue_script('my-script', $url, [], null, ['strategy' => 'defer']). Kolmandate osapoolte skriptide (Google Analytics, reklaamid) puhul on lihtsaim lähenemine async atribuut. Keerulised interaktiivsed plokid (kalkulaatorid, filtrid) tuleks viia dünaamilisse import()-i kohandatud mooduli sees.

Kas see töötab kolmandate osapoolte skriptidega nagu Google Analytics?

Jah. GA4 silt gtag.js laadib vaikimisi async-ga, seega ei blokeeri lehte. Teiste kolmandate osapoolte teenuste puhul kontrolli dokumentatsiooni: kui skript ei vaja valmis DOM-i ega sõltu laadimisjärjekorrast, kasuta julgelt async. Kui see vajab DOM-i, kasuta defer või dünaamilist laadimist koos callbackiga.

Kuidas kontrollida, et skript tõesti ei blokeeri lehte?

Ava Chrome DevTools → Performance → Record → laadi leht uuesti. Ajajoonel otsi kollaseid "Scripting" plokke enne rohelist "First Contentful Paint". Kui skript on laaditud defer või async-ga, on selle käivitamine pärast FCP-d. Lighthouse "Performance" režiimis näitab soovitust "Remove render-blocking resources", selles nimekirjas ei tohiks ühtegi skripti olla.

Kas tasub oma laadimisviisi kohe muuta

Kui sinu skriptid ripuvad endiselt <head>-is ilma atribuutideta, kaotad sa otsingureitinguid ja ärritad kasutajaid. See pole hüpotees, Lighthouse ja PageSpeed Insights näitavad probleemi punaselt raporti esimestel ridadel.

Kiire algus: mine läbi oma malli <script> sildid, lisa defer põhikoodile ja async mõõdikutele. See võtab viis minutit ja LCP võib paraneda 300-500 ms võrra. Edasi, dünaamiline import() rasketele komponentidele, kui jõuad refaktoorimiseni.

Jäta üks laadimismeetod, <script defer> <head>-is, ja leht laadib ilma nähtavate viivitusteta. Testi seda oma projektis juba täna.