Bundling inteligent: Cum să distribuiți codul vechi numai browserelor vechi
Publicat: 2022-03-10Un 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?
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.
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.
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ă.
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