Bundling inteligent: Cum să distribuiți codul vechi numai browserelor vechi

Publicat: 2022-03-10
Rezumat rapid ↬ În timp ce gruparea eficientă a resurselor de pe web a primit o mare atenție în ultima vreme, modul în care livrăm resursele front-end utilizatorilor noștri a rămas aproape același. Greutatea medie a JavaScript și a resurselor de stil cu care se livrează un site web este în creștere – chiar dacă instrumentele de construire pentru optimizarea site-ului nu au fost niciodată mai bune. Având în vedere cota de piață a browserelor veșnic verzi în creștere rapidă și browserele care lansează suport pentru noi funcții în lockstep, este timpul să regândim livrarea de active pentru web-ul modern?

Un site web de astăzi primește o mare parte din traficul său de la browsere veșnic verzi - dintre care majoritatea au suport bun pentru ES6+, noi standarde JavaScript, noi API-uri pentru platforme web și atribute CSS. Cu toate acestea, browserele vechi trebuie să fie acceptate în viitorul apropiat - cota lor de utilizare este suficient de mare pentru a nu fi ignorată, în funcție de baza dvs. de utilizatori.

O privire rapidă la tabelul de utilizare al caniuse.com dezvăluie că browserele veșnic verzi ocupă o parte a leului din piața browserelor – mai mult de 75%. În ciuda acestui fapt, norma este să prefixăm CSS, să transpilăm tot JavaScript în ES5 și să includem polyfills pentru a sprijini fiecare utilizator la care ne pasă.

Deși acest lucru este de înțeles dintr-un context istoric - web-ul a fost întotdeauna despre îmbunătățirea progresivă - întrebarea rămâne: încetinim web-ul pentru majoritatea utilizatorilor noștri pentru a sprijini un set de browsere vechi în scădere?

Transpilare la ES5, polifilluri ale platformei web, polyfills ES6+, prefixare CSS
Diferitele straturi de compatibilitate ale unei aplicații web. (Vezi versiunea mare)

Costul suportării browserelor vechi

Să încercăm să înțelegem cum diferiți pași dintr-o conductă de construcție tipică pot adăuga greutate resurselor noastre front-end:

Transpilarea la ES5

Pentru a estima cât de mult poate adăuga transpilarea la un pachet JavaScript, am luat câteva biblioteci JavaScript populare scrise inițial în ES6+ și am comparat dimensiunile pachetului lor înainte și după transpilare:

Bibliotecă mărimea
(ES6 minimizat)
mărimea
(ES5 minimizat)
Diferență
TodoMVC 8,4 KB 11 KB 24,5%
Draggable 53,5 KB 77,9 KB 31,3%
Luxon 75,4 KB 100,3 KB 24,8%
Video.js 237,2 KB 335,8 KB 29,4%
PixiJS 370,8 KB 452 KB 18%

În medie, pachetele netranspilate sunt cu aproximativ 25% mai mici decât cele care au fost transpilate în ES5. Acest lucru nu este surprinzător, având în vedere că ES6+ oferă o modalitate mai compactă și mai expresivă de a reprezenta logica echivalentă și că transpilarea unora dintre aceste caracteristici în ES5 poate necesita mult cod.

ES6+ Polyfills

În timp ce Babel face o treabă bună în aplicarea transformărilor sintactice codului nostru ES6+, funcțiile încorporate introduse în ES6+ — cum ar fi Promise , Map and Set , precum și noile metode de matrice și șir de caractere — trebuie totuși să fie policompletate. babel-polyfill ca atare poate adăuga aproape 90 KB la pachetul dvs. redus.

Mai multe după săritură! Continuați să citiți mai jos ↓

Platforma web Polyfills

Dezvoltarea modernă a aplicațiilor web a fost simplificată datorită disponibilității unei multitudini de noi API-uri de browser. Cele utilizate în mod obișnuit sunt fetch , pentru solicitarea de resurse, IntersectionObserver , pentru observarea eficientă a vizibilității elementelor și specificarea URL , care face citirea și manipularea URL-urilor pe web mai ușoară.

Adăugarea unui polyfill compatibil cu specificațiile pentru fiecare dintre aceste caracteristici poate avea un impact vizibil asupra dimensiunii pachetului.

Prefixare CSS

În cele din urmă, să ne uităm la impactul prefixării CSS. Deși prefixele nu vor adăuga la fel de multă greutate moartă la pachete ca și alte transformări de construcție - mai ales pentru că se comprimă bine atunci când Gzipd - mai sunt câteva economii de realizat aici.

Bibliotecă mărimea
(minimificat, prefixat pentru ultimele 5 versiuni de browser)
mărimea
(minimificat, prefixat pentru ultima versiune de browser)
Diferență
Bootstrap 159 KB 132 KB 17%
Bulma 184 KB 164 KB 10,9%
fundație 139 KB 118 KB 15,1%
Interfața de utilizare semantică 622 KB 569 KB 8,5%

Un ghid practic pentru un cod eficient de expediere

Probabil că este evident unde merg cu asta. Dacă folosim conductele de construcție existente pentru a livra aceste straturi de compatibilitate numai browserelor care o necesită, putem oferi o experiență mai ușoară pentru restul utilizatorilor noștri - celor care formează o majoritate în creștere - menținând în același timp compatibilitatea pentru browserele mai vechi.

Pachetul modern este mai mic decât pachetul vechi, deoarece renunță la unele straturi de compatibilitate.
Ne furcăm pachetele. (Vezi versiunea mare)

Această idee nu este complet nouă. Servicii precum Polyfill.io sunt încercări de a polifill dinamic mediile de browser în timpul rulării. Dar abordări ca aceasta suferă de câteva deficiențe:

  • Selecția de polyfills este limitată la cele enumerate de serviciu – cu excepția cazului în care găzduiți și mențineți singur serviciul.
  • Deoarece policompletarea are loc în timpul execuției și este o operațiune de blocare, timpul de încărcare a paginii poate fi semnificativ mai mare pentru utilizatorii de browsere vechi.
  • Servirea unui fișier polyfill personalizat fiecărui utilizator introduce entropie în sistem, ceea ce face depanarea mai dificilă atunci când lucrurile merg prost.

De asemenea, acest lucru nu rezolvă problema greutății adăugate prin transpilarea codului aplicației, care uneori poate fi mai mare decât poliumpluturile în sine.

Să vedem cum putem rezolva toate sursele de balonare pe care le-am identificat până acum.

Instrumente de care vom avea nevoie

  • Webpack
    Acesta va fi instrumentul nostru de compilare, deși procesul va rămâne similar cu cel al altor instrumente de construcție, cum ar fi Parcel și Rollup.
  • Lista de browsere
    Cu aceasta, vom gestiona și defini browserele pe care dorim să le sprijinim.
  • Și vom folosi câteva pluginuri de asistență pentru Browserslist .

1. Definirea browserelor moderne și vechi

În primul rând, vom dori să clarificăm ce înțelegem prin browsere „moderne” și „moștenite”. Pentru a ușura întreținerea și testarea, ajută la împărțirea browserelor în două grupuri distincte: adăugarea de browsere care necesită puțin sau deloc policompletare sau transpilare la lista noastră modernă și introducerea celorlalte pe lista noastră moștenită.

Firefox >= 53; Muchia >= 15; Chrome >= 58; iOS >= 10.1
Browsere care acceptă ES6+, noile atribute CSS și API-uri de browser precum Promises și Fetch. (Vezi versiunea mare)

O configurație Browserslist la rădăcina proiectului dvs. poate stoca aceste informații. Subsecțiunile „Mediu” pot fi folosite pentru a documenta cele două grupuri de browser, astfel:

 [modern] Firefox >= 53 Edge >= 15 Chrome >= 58 iOS >= 10.1 [legacy] > 1%

Lista oferită aici este doar un exemplu și poate fi personalizată și actualizată în funcție de cerințele site-ului dvs. și de timpul disponibil. Această configurație va acționa ca sursă de adevăr pentru cele două seturi de pachete front-end pe care le vom crea în continuare: unul pentru browserele moderne și unul pentru toți ceilalți utilizatori.

2. Transpilare și poliumplere ES6+

Pentru a transpila JavaScript într-o manieră conștientă de mediu, vom folosi babel-preset-env .

Să inițializam un fișier .babelrc la rădăcina proiectului nostru cu asta:

 { "presets": [ ["env", { "useBuiltIns": "entry"}] ] }

Activarea useBuiltIns îi permite lui Babel să completeze selectiv funcțiile încorporate care au fost introduse ca parte a ES6+. Deoarece filtrează polifillurile pentru a le include doar pe cele cerute de mediu, atenuăm costul de expediere cu babel-polyfill în întregime.

Pentru ca acest steag să funcționeze, va trebui, de asemenea, să importăm babel-polyfill în punctul nostru de intrare.

 // In import "babel-polyfill";

Procedând astfel, importul mare babel-polyfill va fi înlocuit cu importuri granulare, filtrate de mediul browser pe care îl vizam.

 // Transformed output import "core-js/modules/es7.string.pad-start"; import "core-js/modules/es7.string.pad-end"; import "core-js/modules/web.timers"; …

3. Caracteristicile platformei Web Polyfilling

Pentru a livra utilizatorilor noștri polifilluri pentru caracteristicile platformei web, va trebui să creăm două puncte de intrare pentru ambele medii:

 require('whatwg-fetch'); require('es6-promise').polyfill(); // … other polyfills

Și asta:

 // polyfills for modern browsers (if any) require('intersection-observer');

Acesta este singurul pas din fluxul nostru care necesită un anumit grad de întreținere manuală. Putem face acest proces mai puțin predispus la erori adăugând eslint-plugin-compat la proiect. Acest plugin ne avertizează atunci când folosim o funcție de browser care nu a fost încă completată.

4. Prefixare CSS

În cele din urmă, să vedem cum putem reduce prefixele CSS pentru browserele care nu le necesită. Deoarece autoprefixer a fost unul dintre primele instrumente din ecosistem care a susținut citirea dintr-un fișier de configurare a listei de browserslist , nu avem prea multe de făcut aici.

Crearea unui fișier de configurare PostCSS simplu la rădăcina proiectului ar trebui să fie suficientă:

 module.exports = { plugins: [ require('autoprefixer') ], }

Punând totul laolaltă

Acum că am definit toate configurațiile de plugin necesare, putem pune împreună o configurație webpack care le citește și scoate două versiuni separate în folderele dist/modern și dist/legacy .

 const MiniCssExtractPlugin = require('mini-css-extract-plugin') const isModern = process.env.BROWSERSLIST_ENV === 'modern' const buildRoot = path.resolve(__dirname, "dist") module.exports = { entry: [ isModern ? './polyfills.modern.js' : './polyfills.legacy.js', "./main.js" ], output: { path: path.join(buildRoot, isModern ? 'modern' : 'legacy'), filename: 'bundle.[hash].js', }, module: { rules: [ { test: /\.jsx?$/, use: "babel-loader" }, { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'] } ]}, plugins: { new MiniCssExtractPlugin(), new HtmlWebpackPlugin({ template: 'index.hbs', filename: 'index.html', }), }, };

Pentru a finaliza, vom crea câteva comenzi de compilare în fișierul nostru package.json :

 "scripts": { "build": "yarn build:legacy && yarn build:modern", "build:legacy": "BROWSERSLIST_ENV=legacy webpack -p --config webpack.config.js", "build:modern": "BROWSERSLIST_ENV=modern webpack -p --config webpack.config.js" }

Asta e. yarn build de rulare ar trebui să ne ofere acum două versiuni, care sunt echivalente ca funcționalitate.

Servirea pachetului potrivit pentru utilizatori

Crearea de versiuni separate ne ajută să atingem doar prima jumătate a obiectivului nostru. Încă trebuie să identificăm și să le oferim utilizatorilor pachetul potrivit.

Vă amintiți configurația Browserslist pe care am definit-o mai devreme? Nu ar fi frumos dacă am putea folosi aceeași configurație pentru a determina în ce categorie se încadrează utilizatorul?

Introduceți browserslist-useragent. După cum sugerează și numele, browserslist-useragent poate citi configurația noastră browserslist și apoi potrivește un user agent cu mediul relevant. Următorul exemplu demonstrează acest lucru cu un server Koa:

 const Koa = require('koa') const app = new Koa() const send = require('koa-send') const { matchesUA } = require('browserslist-useragent') var router = new Router() app.use(router.routes()) router.get('/', async (ctx, next) => { const useragent = ctx.get('User-Agent') const isModernUser = matchesUA(useragent, { env: 'modern', allowHigherVersions: true, }) const index = isModernUser ? 'dist/modern/index.html', 'dist/legacy/index.html' await send(ctx, index); });

Aici, setarea steagului allowHigherVersions asigură că, dacă sunt lansate versiuni mai noi ale unui browser - cele care nu fac încă parte din baza de date a Can I Use - acestea vor raporta în continuare drept adevărate pentru browserele moderne.

Una dintre funcțiile browserslist-useragent este de a se asigura că particularitățile platformei sunt luate în considerare atunci când potriviți agenții utilizatori. De exemplu, toate browserele de pe iOS (inclusiv Chrome) folosesc WebKit ca motor de bază și vor fi corelate cu interogarea respectivă a listei de browsere specifice Safari.

S-ar putea să nu fie prudent să se bazeze numai pe corectitudinea analizei user-agent în producție. Revenind la pachetul moștenit pentru browsere care nu sunt definite în lista modernă sau care au șiruri user-agent necunoscute sau imposibil de analizat, ne asigurăm că site-ul nostru funcționează în continuare.

Concluzie: Merită?

Am reușit să acoperim un flux de la capăt la capăt pentru livrarea pachetelor fără umflături către clienții noștri. Dar este doar rezonabil să ne întrebăm dacă costul general de întreținere pe care acest lucru îl adaugă unui proiect își merită beneficiile. Să evaluăm avantajele și dezavantajele acestei abordări:

1. Întreținere și testare

Unul este necesar pentru a menține o singură configurație Browserslist care alimentează toate instrumentele din această conductă. Actualizarea definițiilor browserelor moderne și vechi se poate face oricând în viitor, fără a fi nevoie să refactorizeze configurațiile compatibile sau codul. Aș susține că acest lucru face ca cheltuielile generale de întreținere să fie aproape neglijabile.

Există, totuși, un mic risc teoretic asociat cu baza pe Babel pentru a produce două pachete de coduri diferite, fiecare dintre ele trebuie să funcționeze bine în mediul său respectiv.

În timp ce erorile datorate diferențelor dintre pachete pot fi rare, monitorizarea acestor variante pentru erori ar trebui să ajute la identificarea și atenuarea eficientă a oricăror probleme.

2. Build Time vs Runtime

Spre deosebire de alte tehnici răspândite astăzi, toate aceste optimizări apar în timpul construirii și sunt invizibile pentru client.

3. Viteză îmbunătățită progresiv

Experiența utilizatorilor din browserele moderne devine semnificativ mai rapidă, în timp ce utilizatorii din browserele vechi continuă să primească același pachet ca înainte, fără consecințe negative.

4. Utilizarea cu ușurință a caracteristicilor moderne ale browserului

Adesea evităm să folosim funcții noi de browser din cauza dimensiunii polifillurilor necesare pentru a le folosi. Uneori, alegem chiar și umpluturi mai mici, care nu respectă specificațiile, pentru a economisi dimensiunea. Această nouă abordare ne permite să folosim poliumpluturi conforme cu specificațiile fără a ne îngrijora prea mult cu privire la afectarea tuturor utilizatorilor.

Pachet diferențial care servește în producție

Având în vedere avantajele semnificative, am adoptat această construcție atunci când am creat o nouă experiență de plată mobilă pentru clienții Urban Ladder, unul dintre cei mai mari comercianți de mobilier și decor din India.

În pachetul nostru deja optimizat, am reușit să facem economii de aproximativ 20% la resursele CSS și JavaScript Gzip trimise utilizatorilor moderni de telefonie mobilă. Deoarece peste 80% dintre vizitatorii noștri zilnici foloseau aceste browsere veșnic verzi, efortul depus a meritat impactul.

Resurse suplimentare

  • „Încărcarea polyfills numai când este nevoie”, Philip Walton
  • @babel/preset-env
    O presetare inteligentă Babel
  • Lista de browsere „Instrumente”
    Ecosistem de pluginuri construit pentru Browserslist
  • Pot folosi
    Tabelul actual al cotei de piață a browserului