Smart Bundling: วิธีให้บริการรหัสดั้งเดิมกับเบราว์เซอร์รุ่นเก่าเท่านั้น
เผยแพร่แล้ว: 2022-03-10เว็บไซต์ในปัจจุบันได้รับปริมาณการใช้งานจำนวนมากจากเบราว์เซอร์ที่ใช้งานได้ยาวนาน ซึ่งส่วนใหญ่รองรับ ES6+, มาตรฐาน JavaScript ใหม่, API แพลตฟอร์มเว็บใหม่ และแอตทริบิวต์ CSS ได้ดี อย่างไรก็ตาม เบราว์เซอร์รุ่นเก่ายังคงต้องได้รับการสนับสนุนในอนาคตอันใกล้ — ส่วนแบ่งการใช้งานมีขนาดใหญ่พอที่จะไม่ถูกละเว้น ขึ้นอยู่กับฐานผู้ใช้ของคุณ
การดูตารางการใช้งานของ caniuse.com อย่างรวดเร็วเผยให้เห็นว่าเบราว์เซอร์ที่เขียวชอุ่มตลอดปีครอบครองส่วนแบ่งตลาดเบราว์เซอร์อย่างมาก — มากกว่า 75% ถึงแม้ว่าสิ่งนี้ จะเป็นบรรทัดฐานคือการเติมคำนำหน้า CSS, transpile ของ JavaScript ทั้งหมดของเราเป็น ES5 และรวม polyfills เพื่อรองรับผู้ใช้ทุกคนที่เราใส่ใจ
แม้ว่าสิ่งนี้สามารถเข้าใจได้จากบริบททางประวัติศาสตร์ — เว็บเป็นเรื่องเกี่ยวกับการปรับปรุงที่ก้าวหน้ามาโดยตลอด — คำถามยังคงอยู่: เรากำลังทำให้เว็บช้าลงสำหรับผู้ใช้ส่วนใหญ่ของเราหรือไม่เพื่อรองรับชุดเบราว์เซอร์รุ่นเก่าที่ลดน้อยลง
ค่าใช้จ่ายในการสนับสนุนเบราว์เซอร์รุ่นเก่า
มาทำความเข้าใจกันว่าขั้นตอนต่างๆ ในไปป์ไลน์การสร้างทั่วไปสามารถเพิ่มน้ำหนักให้กับทรัพยากรส่วนหน้าของเราได้อย่างไร:
กำลังถ่ายโอนไปยัง ES5
ในการประมาณการว่าทรานสพิลน้ำหนักสามารถเพิ่มไปยังบันเดิล JavaScript ฉันได้นำไลบรารี JavaScript ยอดนิยมสองสามตัวที่เขียนด้วย ES6+ มาแต่แรก และเปรียบเทียบขนาดบันเดิลก่อนและหลัง transpilation:
ห้องสมุด | ขนาด (ลดขนาด ES6) | ขนาด (ลดขนาด ES5) | ความแตกต่าง |
---|---|---|---|
TodoMVC | 8.4 KB | 11 KB | 24.5% |
ลากได้ | 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% |
โดยเฉลี่ย บันเดิลที่ไม่มีการขนถ่ายจะเล็กกว่ามัดที่ทรานสไพล์ลงเป็น ES5 ประมาณ 25% ไม่น่าแปลกใจเลยที่ ES6+ ให้วิธีการที่กระชับและชัดเจนมากขึ้นในการแสดงตรรกะที่เทียบเท่า และการถ่ายทอดคุณลักษณะบางอย่างเหล่านี้ไปยัง ES5 อาจต้องใช้โค้ดจำนวนมาก
โพลีฟิล ES6+
แม้ว่า Babel จะใช้การเปลี่ยนรูปแบบวากยสัมพันธ์กับโค้ด ES6+ ของเราได้ดี แต่ฟีเจอร์ในตัวที่นำมาใช้ใน ES6+ — เช่น Promise
, Map
and Set
และวิธีการอาร์เรย์และสตริงแบบใหม่ — ยังคงต้องมีโพลีฟิล การลดลงใน babel-polyfill
ตามที่เป็นอยู่สามารถเพิ่มได้เกือบ 90 KB ไปยังบันเดิลที่ย่อเล็กสุดของคุณ
Polyfills แพลตฟอร์มเว็บ
การพัฒนาเว็บแอปพลิเคชันสมัยใหม่ง่ายขึ้นเนื่องจากมี API เบราว์เซอร์ใหม่มากมายเหลือเฟือ สิ่งที่ใช้กันทั่วไปคือ fetch
สำหรับการขอทรัพยากร IntersectionObserver
สำหรับการสังเกตการมองเห็นองค์ประกอบและข้อกำหนด URL
อย่างมีประสิทธิภาพ ซึ่งทำให้การอ่านและการจัดการ URL บนเว็บง่ายขึ้น
การเพิ่มโพลีฟิลที่เป็นไปตามข้อกำหนดสำหรับแต่ละคุณลักษณะเหล่านี้สามารถมีผลกระทบอย่างเห็นได้ชัดต่อขนาดบันเดิล
CSS Prefixing
สุดท้ายนี้ มาดูผลกระทบของคำนำหน้า CSS แม้ว่าคำนำหน้าจะไม่เพิ่มน้ำหนักให้กับการรวมกลุ่มมากเท่ากับการเปลี่ยนรูปแบบบิลด์อื่น ๆ โดยเฉพาะอย่างยิ่งเนื่องจากบีบอัดได้ดีเมื่อ Gzip'd แต่ก็ยังมีเงินออมอยู่บ้าง
ห้องสมุด | ขนาด (ย่อเล็กสุด นำหน้าสำหรับ 5 เบราว์เซอร์เวอร์ชันล่าสุด) | ขนาด (ย่อเล็กสุด นำหน้าสำหรับเบราว์เซอร์เวอร์ชันล่าสุด) | ความแตกต่าง |
---|---|---|---|
Bootstrap | 159 KB | 132 KB | 17% |
บูลมา | 184 KB | 164 KB | 10.9% |
รากฐาน | 139 KB | 118 KB | 15.1% |
UI ความหมาย | 622 KB | 569 KB | 8.5% |
คู่มือปฏิบัติในการจัดส่งรหัสที่มีประสิทธิภาพ
มันอาจจะชัดเจนว่าฉันจะไปกับเรื่องนี้ หากเราใช้ประโยชน์จากไพพ์ไลน์ของบิลด์ที่มีอยู่เพื่อจัดส่งเลเยอร์ความเข้ากันได้เหล่านี้ไปยังเบราว์เซอร์ที่ต้องการเท่านั้น เราสามารถมอบประสบการณ์ที่เบากว่าให้กับผู้ใช้ที่เหลือของเรา — ผู้ที่เป็นคนส่วนใหญ่ — ในขณะที่ยังคงความเข้ากันได้สำหรับเบราว์เซอร์รุ่นเก่า
ความคิดนี้ไม่ใช่เรื่องใหม่ทั้งหมด บริการต่างๆ เช่น Polyfill.io เป็นการพยายามสร้างสภาพแวดล้อมเบราว์เซอร์แบบ polyfill แบบไดนามิก ณ รันไทม์ แต่วิธีการเช่นนี้ประสบข้อบกพร่องบางประการ:
- การเลือกโพลีฟิลจะจำกัดเฉพาะรายการที่ระบุไว้ในบริการ เว้นแต่คุณจะโฮสต์และดูแลรักษาบริการด้วยตัวเอง
- เนื่องจาก polyfilling เกิดขึ้นที่รันไทม์และเป็นการดำเนินการบล็อก เวลาในการโหลดหน้าเว็บจึงสูงขึ้นอย่างมากสำหรับผู้ใช้ในเบราว์เซอร์รุ่นเก่า
- การให้บริการไฟล์ polyfill แบบกำหนดเองแก่ผู้ใช้ทุกคนจะแนะนำเอนโทรปีให้กับระบบ ซึ่งทำให้การแก้ไขปัญหายากขึ้นเมื่อมีสิ่งผิดปกติเกิดขึ้น
นอกจากนี้ ยังไม่สามารถแก้ปัญหาเรื่องน้ำหนักที่เพิ่มโดยการทรานสพิลของโค้ดแอปพลิเคชัน ซึ่งบางครั้งอาจมีขนาดใหญ่กว่าโพลิฟิลเอง
มาดูกันว่าเราจะแก้ปัญหาที่มาของอาการบวมได้อย่างไรที่เราเคยพบมาจนถึงตอนนี้
เครื่องมือที่เราต้องการ
- เว็บแพ็ค
นี่จะเป็นเครื่องมือสร้างของเรา แม้ว่ากระบวนการจะยังคงคล้ายกับเครื่องมือสร้างอื่นๆ เช่น Parcel และ Rollup - รายการเบราว์เซอร์
ด้วยเหตุนี้ เราจะจัดการและกำหนดเบราว์เซอร์ที่เราต้องการสนับสนุน - และเราจะใช้ ปลั๊กอินสนับสนุน Browserslist บางตัว
1. การกำหนดเบราว์เซอร์ที่ทันสมัยและดั้งเดิม
อันดับแรก เราต้องการทำให้ชัดเจนว่าเบราว์เซอร์ "ทันสมัย" และ "ดั้งเดิม" หมายถึงอะไร เพื่อความสะดวกในการบำรุงรักษาและทดสอบ การแบ่งเบราว์เซอร์ออกเป็นสองกลุ่มแยกกัน: การเพิ่มเบราว์เซอร์ที่ไม่ต้องการ polyfilling หรือ transpilation เพียงเล็กน้อยหรือไม่มีเลยในรายการที่ทันสมัยของเรา และวางส่วนที่เหลือในรายการเดิมของเรา
การกำหนดค่า Browserslist ที่รูทของโปรเจ็กต์ของคุณสามารถจัดเก็บข้อมูลนี้ได้ ส่วนย่อย "สภาพแวดล้อม" สามารถใช้เพื่อจัดทำเอกสารกลุ่มเบราว์เซอร์สองกลุ่มได้ดังนี้:
[modern] Firefox >= 53 Edge >= 15 Chrome >= 58 iOS >= 10.1 [legacy] > 1%
รายการที่ระบุในที่นี้เป็นเพียงตัวอย่างเท่านั้น และสามารถปรับแต่งและอัปเดตได้ตามความต้องการของเว็บไซต์และเวลาที่มี การกำหนดค่านี้จะทำหน้าที่เป็นแหล่งที่มาของความจริงสำหรับชุดข้อมูลส่วนหน้าสองชุดที่เราจะสร้างต่อไป ชุดหนึ่งสำหรับเบราว์เซอร์รุ่นใหม่และชุดหนึ่งสำหรับผู้ใช้อื่นๆ ทั้งหมด
2. ES6+ ขนย้ายและโพลีฟิลลิ่ง
เพื่อแปลงจาวาสคริปต์ของเราในลักษณะที่คำนึงถึงสิ่งแวดล้อม เราจะใช้ babel-preset-env
เริ่มต้นไฟล์ .babelrc
ที่รูทของโปรเจ็กต์ด้วยสิ่งนี้:
{ "presets": [ ["env", { "useBuiltIns": "entry"}] ] }
การเปิดใช้งานแฟ useBuiltIns
ช่วยให้ Babel สามารถเลือกโพลีฟิลแบบบิวท์อินคุณลักษณะที่ได้รับการแนะนำโดยเป็นส่วนหนึ่งของ ES6+ เนื่องจากมันกรองโพลีฟิลให้รวมเฉพาะโพลีฟิลที่สภาพแวดล้อมต้องการ เราจึงลดค่าใช้จ่ายในการขนส่งด้วย babel-polyfill
อย่างครบถ้วน
เพื่อให้แฟล็กนี้ใช้งานได้ เราจะต้องนำเข้า babel-polyfill
ในจุดเริ่มต้นของเราด้วย
// In import "babel-polyfill";
การทำเช่นนี้จะแทนที่การนำเข้า babel-polyfill
ขนาดใหญ่ด้วยการนำเข้าที่ละเอียด ซึ่งกรองโดยสภาพแวดล้อมของเบราว์เซอร์ที่เรากำหนดเป้าหมาย
// 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. คุณสมบัติแพลตฟอร์มเว็บ Polyfilling
ในการจัดส่งคุณสมบัติ polyfills สำหรับแพลตฟอร์มเว็บให้กับผู้ใช้ของเรา เราจะต้องสร้างจุดเริ่มต้นสองจุดสำหรับทั้งสองสภาพแวดล้อม:
require('whatwg-fetch'); require('es6-promise').polyfill(); // … other polyfills
และนี่:
// polyfills for modern browsers (if any) require('intersection-observer');
นี่เป็นขั้นตอนเดียวในโฟลว์ของเราที่ต้องมีการบำรุงรักษาด้วยตนเองในระดับหนึ่ง เราสามารถทำให้กระบวนการนี้เกิดข้อผิดพลาดน้อยลงได้โดยการเพิ่ม eslint-plugin-compat ให้กับโปรเจ็กต์ ปลั๊กอินนี้เตือนเราเมื่อเราใช้คุณลักษณะเบราว์เซอร์ที่ยังไม่ได้ polyfilled
4. CSS Prefixing
สุดท้ายนี้ เรามาดูกันว่าเราจะลด CSS นำหน้าสำหรับเบราว์เซอร์ที่ไม่ต้องการได้อย่างไร เนื่องจาก autoprefixer
เป็นเครื่องมือแรกในระบบนิเวศที่รองรับการอ่านจากไฟล์การกำหนดค่ารายการ browserslist
เราจึงไม่ต้องทำอะไรมาก
การสร้างไฟล์การกำหนดค่า PostCSS อย่างง่ายที่รูทของโปรเจ็กต์ควรเพียงพอ:
module.exports = { plugins: [ require('autoprefixer') ], }
วางมันทั้งหมดเข้าด้วยกัน
ตอนนี้เราได้กำหนดการกำหนดค่าปลั๊กอินที่จำเป็นทั้งหมดแล้ว เราสามารถรวบรวมการกำหนดค่า webpack ที่อ่านข้อมูลเหล่านี้และส่งออกบิลด์สองส่วนแยกกันในโฟลเดอร์ dist/modern
และ 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', }), }, };
ในการดำเนินการให้เสร็จสิ้น เราจะสร้างคำสั่งบิลด์สองสามคำสั่งในไฟล์ 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" }
แค่นั้นแหละ. ตอนนี้งาน yarn build
วิ่งควรให้เราสองงานสร้าง ซึ่งเทียบเท่าในหน้าที่การใช้งาน
การให้บริการ Bundle ที่เหมาะสมแก่ผู้ใช้
การสร้างบิลด์ที่แยกจากกันช่วยให้เราบรรลุเป้าหมายเพียงครึ่งแรกเท่านั้น เรายังจำเป็นต้องระบุและให้บริการชุดข้อมูลที่ถูกต้องแก่ผู้ใช้
จำการกำหนดค่า Browserslist ที่เรากำหนดไว้ก่อนหน้านี้หรือไม่ คงจะดีไม่น้อยถ้าเราสามารถใช้การกำหนดค่าเดียวกันเพื่อกำหนดว่าผู้ใช้อยู่ในหมวดหมู่ใด
ป้อน browserslist-usergent ตามชื่อที่แนะนำ browserslist-useragent
สามารถอ่านการกำหนดค่า browserslist
ของเราแล้วจับคู่ตัวแทนผู้ใช้กับสภาพแวดล้อมที่เกี่ยวข้อง ตัวอย่างต่อไปนี้สาธิตสิ่งนี้กับเซิร์ฟเวอร์ 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); });
ในที่นี้ การตั้งค่าสถานะ allowHigherVersions
ช่วยให้มั่นใจได้ว่าหากมีการเปิดตัวเบราว์เซอร์เวอร์ชันใหม่ ซึ่งยังไม่ได้เป็นส่วนหนึ่งของฐานข้อมูลของ Can I Use เบราว์เซอร์เหล่านั้นจะยังรายงานว่าเป็นจริงสำหรับเบราว์เซอร์สมัยใหม่
หนึ่งในหน้าที่ของ browserslist-useragent
คือการตรวจสอบให้แน่ใจว่าได้คำนึงถึงความไม่ชอบมาพากลของแพลตฟอร์มในขณะที่จับคู่ตัวแทนผู้ใช้ ตัวอย่างเช่น เบราว์เซอร์ทั้งหมดบน iOS (รวมถึง Chrome) ใช้ WebKit เป็นเอ็นจิ้นพื้นฐาน และจะจับคู่กับการค้นหารายการเบราว์เซอร์เฉพาะของ Safari
อาจไม่รอบคอบที่จะพึ่งพาความถูกต้องของการแยกวิเคราะห์ user-agent ในการผลิตเท่านั้น โดยการย้อนกลับไปใช้กลุ่มเดิมสำหรับเบราว์เซอร์ที่ไม่ได้กำหนดไว้ในรายการสมัยใหม่ หรือมีสตริง user-agent ที่ไม่รู้จักหรือแยกวิเคราะห์ไม่ได้ เรารับรองว่าเว็บไซต์ของเรายังคงใช้งานได้
สรุป: มันคุ้มค่าหรือไม่
เราได้จัดการเพื่อให้ครอบคลุมขั้นตอนแบบ end-to-end สำหรับการจัดส่งชุดรวมที่ปราศจากการบวมไปยังลูกค้าของเรา แต่มีเหตุผลเพียงเท่านั้นที่จะสงสัยว่าค่าใช้จ่ายในการบำรุงรักษาที่เพิ่มขึ้นในโครงการนั้นคุ้มค่ากับผลประโยชน์หรือไม่ มาประเมินข้อดีข้อเสียของวิธีนี้กัน:
1. การบำรุงรักษาและการทดสอบ
จำเป็นต้องมีหนึ่งรายการเพื่อรักษาการกำหนดค่า Browserslist เดียวที่ขับเคลื่อนเครื่องมือทั้งหมดในไปป์ไลน์นี้ การอัปเดตคำจำกัดความของเบราว์เซอร์รุ่นเก่าและรุ่นใหม่สามารถทำได้ทุกเมื่อในอนาคต โดยไม่ต้องปรับโครงสร้างใหม่เพื่อรองรับการกำหนดค่าหรือโค้ด ฉันขอยืนยันว่าสิ่งนี้ทำให้ค่าบำรุงรักษาแทบไม่มีความสำคัญ
อย่างไรก็ตาม มีความเสี่ยงทางทฤษฎีเล็กน้อยที่เกี่ยวข้องกับการใช้ Babel เพื่อสร้างชุดโค้ดที่แตกต่างกันสองชุด ซึ่งแต่ละชุดต้องทำงานได้ดีในสภาพแวดล้อมที่เกี่ยวข้อง
แม้ว่าข้อผิดพลาดอันเนื่องมาจากความแตกต่างในกลุ่มอาจพบได้ยาก การตรวจสอบตัวแปรเหล่านี้เพื่อหาข้อผิดพลาดควรช่วยในการระบุและบรรเทาปัญหาอย่างมีประสิทธิภาพ
2. เวลาสร้างเทียบกับรันไทม์
ต่างจากเทคนิคอื่นๆ ที่แพร่หลายในปัจจุบัน การปรับให้เหมาะสมทั้งหมดเหล่านี้เกิดขึ้นในเวลาสร้างและลูกค้าจะมองไม่เห็น
3. ความเร็วที่เพิ่มขึ้นเรื่อย ๆ
ประสบการณ์ของผู้ใช้บนเบราว์เซอร์รุ่นใหม่จะเร็วขึ้นอย่างมาก ในขณะที่ผู้ใช้บนเบราว์เซอร์รุ่นเก่ายังคงได้รับบริการชุดเดียวกันเช่นเดิม โดยไม่มีผลกระทบด้านลบใดๆ
4. การใช้คุณลักษณะเบราว์เซอร์สมัยใหม่อย่างง่ายดาย
เรามักหลีกเลี่ยงการใช้คุณลักษณะใหม่ของเบราว์เซอร์เนื่องจากขนาดของโพลีฟิลที่จำเป็นต่อการใช้งาน ในบางครั้ง เรายังเลือกโพลีฟิลที่ไม่เป็นไปตามข้อกำหนดที่มีขนาดเล็กกว่าเพื่อประหยัดขนาด วิธีการใหม่นี้ช่วยให้เราใช้โพลีฟิลที่เป็นไปตามข้อกำหนดโดยไม่ต้องกังวลเกี่ยวกับผลกระทบกับผู้ใช้ทั้งหมดมากนัก
ชุดส่วนต่างที่ให้บริการในการผลิต
ด้วยข้อได้เปรียบที่สำคัญ เราจึงนำขั้นตอนการสร้างนี้มาใช้ในการสร้างประสบการณ์การชำระเงินผ่านมือถือสำหรับลูกค้าของ Urban Ladder ซึ่งเป็นหนึ่งในผู้ค้าปลีกเฟอร์นิเจอร์และของประดับตกแต่งที่ใหญ่ที่สุดในอินเดีย
ในชุดรวมที่ปรับให้เหมาะสมแล้ว เราสามารถบีบการประหยัดได้ประมาณ 20% สำหรับทรัพยากร CSS และ JavaScript ของ Gzip ที่ส่งไปยังผู้ใช้มือถือยุคใหม่ เนื่องจากมากกว่า 80% ของผู้เข้าชมรายวันของเราใช้เบราว์เซอร์ที่เป็นมิตรกับสิ่งแวดล้อมเหล่านี้ ความพยายามจึงคุ้มค่ากับผลกระทบ
แหล่งข้อมูลเพิ่มเติม
- “การโหลด Polyfills เมื่อจำเป็นเท่านั้น”, Philip Walton
-
@babel/preset-env
ที่ตั้งไว้ล่วงหน้า Babel ที่ชาญฉลาด - รายการเบราว์เซอร์ "เครื่องมือ"
ระบบนิเวศของปลั๊กอินที่สร้างขึ้นสำหรับ Browserslist - ฉันสามารถใช้
ตารางส่วนแบ่งการตลาดของเบราว์เซอร์ปัจจุบัน