เพิ่มประสิทธิภาพเว็บแอปพลิเคชันของคุณด้วยคู่มือฉบับสมบูรณ์เกี่ยวกับการทำ Code Splitting เรียนรู้กลยุทธ์ตาม Route และ Component พร้อมตัวอย่างสำหรับ React, Vue และ Angular
การทำ Code Splitting ฝั่ง Frontend: เจาะลึกกลยุทธ์ตาม Route และ Component
ในภูมิทัศน์ดิจิทัลสมัยใหม่ ความประทับใจแรกของผู้ใช้ที่มีต่อเว็บไซต์ของคุณมักจะถูกตัดสินด้วยตัวชี้วัดเดียว นั่นคือความเร็ว แอปพลิเคชันที่โหลดช้าอาจนำไปสู่ bounce rate ที่สูง ผู้ใช้หงุดหงิด และสูญเสียรายได้ เมื่อแอปพลิเคชันฝั่ง frontend มีความซับซ้อนมากขึ้น การจัดการขนาดของมันจึงกลายเป็นความท้าทายที่สำคัญ พฤติกรรมเริ่มต้นของ bundler ส่วนใหญ่คือการสร้างไฟล์ JavaScript ขนาดใหญ่ไฟล์เดียว (monolithic) ที่บรรจุโค้ดทั้งหมดของแอปพลิเคชันคุณ ซึ่งหมายความว่าผู้ใช้ที่เข้ามายังหน้าแรกของคุณอาจกำลังดาวน์โหลดโค้ดสำหรับหน้าแดชบอร์ดผู้ดูแลระบบ การตั้งค่าโปรไฟล์ผู้ใช้ และขั้นตอนการชำระเงินที่พวกเขาอาจไม่เคยได้ใช้เลย
นี่คือจุดที่ code splitting เข้ามามีบทบาท มันเป็นเทคนิคที่ทรงพลังที่ช่วยให้คุณสามารถแบ่ง JavaScript bundle ขนาดใหญ่ของคุณออกเป็นส่วนเล็กๆ ที่จัดการได้ง่าย (chunks) ซึ่งสามารถโหลดได้ตามความต้องการ (on demand) ด้วยการส่งเฉพาะโค้ดที่ผู้ใช้ต้องการสำหรับมุมมองเริ่มต้น คุณสามารถปรับปรุงเวลาในการโหลดได้อย่างมาก เพิ่มประสบการณ์ผู้ใช้ และส่งผลดีต่อตัวชี้วัดประสิทธิภาพที่สำคัญอย่าง Core Web Vitals ของ Google
คู่มือฉบับสมบูรณ์นี้จะสำรวจสองกลยุทธ์หลักสำหรับการทำ code splitting ฝั่ง frontend: ตาม Route (route-based) และ ตาม Component (component-based) เราจะเจาะลึกถึงเหตุผล วิธีการ และช่วงเวลาที่เหมาะสมของแต่ละแนวทาง พร้อมด้วยตัวอย่างที่ใช้งานได้จริงโดยใช้เฟรมเวิร์กยอดนิยมอย่าง React, Vue และ Angular
ปัญหา: JavaScript Bundle แบบ Monolithic
ลองจินตนาการว่าคุณกำลังจัดกระเป๋าสำหรับการเดินทางหลายที่ ซึ่งรวมถึงวันหยุดที่ชายหาด การเดินป่าบนภูเขา และการประชุมทางธุรกิจที่เป็นทางการ แนวทางแบบ monolithic ก็เหมือนกับการพยายามยัดชุดว่ายน้ำ รองเท้าเดินป่า และชุดสูทธุรกิจลงในกระเป๋าเดินทางใบใหญ่ใบเดียว เมื่อคุณไปถึงชายหาด คุณต้องลากกระเป๋าใบยักษ์นี้ไปรอบๆ ทั้งที่คุณต้องการแค่ชุดว่ายน้ำ มันทั้งหนัก ไม่มีประสิทธิภาพ และยุ่งยาก
JavaScript bundle แบบ monolithic ก็สร้างปัญหาที่คล้ายกันสำหรับเว็บแอปพลิเคชัน:
- เวลาในการโหลดเริ่มต้นที่มากเกินไป: เบราว์เซอร์ต้องดาวน์โหลด แยกวิเคราะห์ และรันโค้ดทั้งหมดของแอปพลิเคชันก่อนที่ผู้ใช้จะสามารถเห็นหรือโต้ตอบกับสิ่งใดๆ ได้ ซึ่งอาจใช้เวลาหลายวินาทีบนเครือข่ายที่ช้าหรืออุปกรณ์ที่มีประสิทธิภาพต่ำ
- สิ้นเปลืองแบนด์วิดท์: ผู้ใช้ดาวน์โหลดโค้ดสำหรับฟีเจอร์ที่พวกเขาอาจไม่เคยเข้าถึง ซึ่งเป็นการใช้ดาต้าของพวกเขาโดยไม่จำเป็น สิ่งนี้เป็นปัญหาโดยเฉพาะสำหรับผู้ใช้มือถือในภูมิภาคที่มีค่าบริการอินเทอร์เน็ตแพงหรือจำกัด
- ประสิทธิภาพการแคชที่ไม่ดี: การเปลี่ยนแปลงโค้ดเพียงบรรทัดเดียวในฟีเจอร์หนึ่งๆ จะทำให้แคชของ bundle ทั้งหมดใช้ไม่ได้ ผู้ใช้จึงถูกบังคับให้ดาวน์โหลดแอปพลิเคชันทั้งหมดใหม่อีกครั้ง แม้ว่า 99% ของมันจะไม่มีการเปลี่ยนแปลงก็ตาม
- ผลกระทบเชิงลบต่อ Core Web Vitals: bundle ขนาดใหญ่ส่งผลเสียโดยตรงต่อตัวชี้วัดอย่าง Largest Contentful Paint (LCP) และ Time to Interactive (TTI) ซึ่งอาจส่งผลต่ออันดับ SEO และความพึงพอใจของผู้ใช้เว็บไซต์ของคุณ
Code splitting คือทางออกของปัญหานี้ มันเหมือนกับการจัดกระเป๋าใบเล็กๆ สามใบแยกกัน: ใบหนึ่งสำหรับชายหาด ใบหนึ่งสำหรับภูเขา และอีกใบหนึ่งสำหรับการประชุม คุณจะพกเฉพาะสิ่งที่คุณต้องการ ในเวลาที่คุณต้องการ
ทางออก: Code Splitting คืออะไร?
Code splitting คือกระบวนการแบ่งโค้ดของแอปพลิเคชันออกเป็น bundle หรือ "chunks" ต่างๆ ซึ่งสามารถโหลดได้ตามความต้องการหรือโหลดแบบขนาน แทนที่จะมีไฟล์ `app.js` ขนาดใหญ่ไฟล์เดียว คุณอาจมี `main.js`, `dashboard.chunk.js`, `profile.chunk.js` และอื่นๆ
เครื่องมือ build สมัยใหม่เช่น Webpack, Vite, และ Rollup ทำให้กระบวนการนี้เข้าถึงได้ง่ายอย่างไม่น่าเชื่อ พวกมันใช้ประโยชน์จาก синтаксис `import()` แบบไดนามิก ซึ่งเป็นฟีเจอร์ของ JavaScript สมัยใหม่ (ECMAScript) ที่อนุญาตให้คุณ import โมดูลแบบอะซิงโครนัสได้ เมื่อ bundler เห็น `import()` มันจะสร้าง chunk แยกต่างหากสำหรับโมดูลนั้นและ dependencies ของมันโดยอัตโนมัติ
เรามาสำรวจสองกลยุทธ์ที่พบบ่อยและมีประสิทธิภาพที่สุดสำหรับการทำ code splitting กัน
กลยุทธ์ที่ 1: การทำ Code Splitting ตาม Route
การแยกโค้ดตาม Route เป็นกลยุทธ์ code splitting ที่เข้าใจง่ายและถูกนำมาใช้กันอย่างแพร่หลายที่สุด ตรรกะของมันง่ายมาก: ถ้าผู้ใช้อยู่ที่หน้า `/home` พวกเขาก็ไม่จำเป็นต้องใช้โค้ดสำหรับหน้า `/dashboard` หรือ `/settings` ด้วยการแบ่งโค้ดของคุณตาม route ของแอปพลิเคชัน คุณจะมั่นใจได้ว่าผู้ใช้จะดาวน์โหลดเฉพาะโค้ดสำหรับหน้าที่พวกเขากำลังดูอยู่เท่านั้น
มันทำงานอย่างไร
คุณกำหนดค่า router ของแอปพลิเคชันให้โหลด component ที่เกี่ยวข้องกับ route นั้นๆ แบบไดนามิก เมื่อผู้ใช้ไปยัง route นั้นเป็นครั้งแรก router จะส่งคำขอเครือข่ายเพื่อดึง JavaScript chunk ที่สอดคล้องกัน เมื่อโหลดเสร็จแล้ว component จะถูกเรนเดอร์ และ chunk นั้นจะถูกแคชโดยเบราว์เซอร์สำหรับการเข้าชมครั้งต่อไป
ข้อดีของการแยกโค้ดตาม Route
- ลดเวลาในการโหลดเริ่มต้นได้อย่างมีนัยสำคัญ: bundle เริ่มต้นจะมีเพียงตรรกะหลักของแอปพลิเคชันและโค้ดสำหรับ route เริ่มต้น (เช่น หน้าแรก) ทำให้มีขนาดเล็กลงและโหลดได้เร็วขึ้นมาก
- ง่ายต่อการนำไปใช้: ไลบรารี routing สมัยใหม่ส่วนใหญ่รองรับ lazy loading ในตัว ทำให้การนำไปใช้งานตรงไปตรงมา
- ขอบเขตทางตรรกะที่ชัดเจน: Route เป็นจุดแบ่งโค้ดที่เป็นธรรมชาติและชัดเจน ทำให้ง่ายต่อการทำความเข้าใจว่าส่วนใดของแอปพลิเคชันของคุณถูกแยกออกไป
ตัวอย่างการนำไปใช้งาน
React กับ React Router
React มีเครื่องมือหลักสองอย่างสำหรับสิ่งนี้: `React.lazy()` และ `
ตัวอย่าง `App.js` ที่ใช้ React Router:
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
// Statically import components that are always needed
import Navbar from './components/Navbar';
import LoadingSpinner from './components/LoadingSpinner';
// Lazily import route components
const HomePage = lazy(() => import('./pages/HomePage'));
const DashboardPage = lazy(() => import('./pages/DashboardPage'));
const SettingsPage = lazy(() => import('./pages/SettingsPage'));
const NotFoundPage = lazy(() => import('./pages/NotFoundPage'));
function App() {
return (
<Router>
<Navbar />
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/dashboard" element={<DashboardPage />} />
<Route path="/settings" element={<SettingsPage />} />
<Route path="*" element={<NotFoundPage />} />
</Routes>
</Suspense>
</Router>
);
}
export default App;
ในตัวอย่างนี้ โค้ดสำหรับ `DashboardPage` และ `SettingsPage` จะไม่ถูกรวมอยู่ใน bundle เริ่มต้น มันจะถูกดึงจากเซิร์ฟเวอร์ก็ต่อเมื่อผู้ใช้ไปยัง `/dashboard` หรือ `/settings` เท่านั้น `Suspense` component จะช่วยให้ผู้ใช้ได้รับประสบการณ์ที่ราบรื่นโดยการแสดง `LoadingSpinner` ในระหว่างการดึงข้อมูลนี้
Vue กับ Vue Router
Vue Router รองรับการทำ lazy loading routes ได้ทันทีโดยใช้ `import()` แบบไดนามิกโดยตรงในการกำหนดค่า route ของคุณ
ตัวอย่าง `router/index.js` ที่ใช้ Vue Router:
import { createRouter, createWebHistory } from 'vue-router';
import HomeView from '../views/HomeView.vue'; // Statically imported for initial load
const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
// Route level code-splitting
// This generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
},
{
path: '/dashboard',
name: 'dashboard',
component: () => import(/* webpackChunkName: "dashboard" */ '../views/DashboardView.vue')
}
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
});
export default router;
ในที่นี้ component สำหรับ route `/about` และ `/dashboard` ถูกกำหนดเป็นฟังก์ชันที่คืนค่า dynamic import ซึ่ง bundler จะเข้าใจสิ่งนี้และสร้าง chunk แยกต่างหาก `/* webpackChunkName: "about" */` เป็น "magic comment" ที่บอกให้ Webpack ตั้งชื่อ chunk ที่ได้เป็น `about.js` แทนที่จะเป็น ID ทั่วไป ซึ่งมีประโยชน์สำหรับการดีบัก
Angular กับ Angular Router
Router ของ Angular ใช้ property `loadChildren` ในการกำหนดค่า route เพื่อเปิดใช้งานการทำ lazy loading ทั้งโมดูล
ตัวอย่าง `app-routing.module.ts`:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component'; // Part of the main bundle
const routes: Routes = [
{
path: '',
component: HomeComponent
},
{
path: 'products',
// Lazy load the ProductsModule
loadChildren: () => import('./products/products.module').then(m => m.ProductsModule)
},
{
path: 'admin',
// Lazy load the AdminModule
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
ในตัวอย่างของ Angular นี้ โค้ดที่เกี่ยวข้องกับฟีเจอร์ `products` และ `admin` จะถูกห่อหุ้มไว้ในโมดูลของตัวเอง (`ProductsModule` และ `AdminModule`) ไวยากรณ์ `loadChildren` จะสั่งให้ Angular router ดึงและโหลดโมดูลเหล่านี้ก็ต่อเมื่อผู้ใช้ไปยัง URL ที่ขึ้นต้นด้วย `/products` หรือ `/admin` เท่านั้น
กลยุทธ์ที่ 2: การทำ Code Splitting ตาม Component
ในขณะที่การแยกโค้ดตาม route เป็นจุดเริ่มต้นที่ยอดเยี่ยม คุณสามารถเพิ่มประสิทธิภาพให้ดียิ่งขึ้นไปอีกขั้นด้วยการแยกโค้ดตาม component กลยุทธ์นี้เกี่ยวข้องกับการโหลด component ก็ต่อเมื่อมีความจำเป็นต้องใช้จริงๆ ภายในมุมมองที่กำหนด ซึ่งมักจะตอบสนองต่อการโต้ตอบของผู้ใช้
ลองนึกถึง component ที่ไม่สามารถมองเห็นได้ทันทีหรือมีการใช้งานไม่บ่อยนัก ทำไมโค้ดของมันจึงควรเป็นส่วนหนึ่งของการโหลดหน้าเริ่มต้นด้วยล่ะ?
กรณีการใช้งานทั่วไปสำหรับการแยกโค้ดตาม Component
- Modals และ Dialogs: โค้ดสำหรับ modal ที่ซับซ้อน (เช่น ตัวแก้ไขโปรไฟล์ผู้ใช้) ควรถูกโหลดก็ต่อเมื่อผู้ใช้คลิกปุ่มเพื่อเปิดมันเท่านั้น
- เนื้อหาที่อยู่ด้านล่างของหน้า (Below-the-Fold): สำหรับหน้าแรกที่ยาว component ที่ซับซ้อนซึ่งอยู่ด้านล่างของหน้าสามารถโหลดได้ก็ต่อเมื่อผู้ใช้เลื่อนหน้าจอเข้ามาใกล้
- องค์ประกอบ UI ที่ซับซ้อน: component ที่มีขนาดใหญ่ เช่น กราฟเชิงโต้ตอบ, date pickers, หรือ rich text editors สามารถทำ lazy-load เพื่อเร่งการเรนเดอร์เริ่มต้นของหน้าที่มันอยู่ได้
- Feature Flags หรือ A/B Tests: โหลด component ก็ต่อเมื่อ feature flag ที่ระบุถูกเปิดใช้งานสำหรับผู้ใช้
- UI ตามบทบาท: component สำหรับผู้ดูแลระบบบนแดชบอร์ดควรโหลดสำหรับผู้ใช้ที่มีบทบาทเป็น 'admin' เท่านั้น
ตัวอย่างการนำไปใช้งาน
React
คุณสามารถใช้รูปแบบ `React.lazy` และ `Suspense` แบบเดียวกันได้ แต่ให้การเรนเดอร์ทำงานตามเงื่อนไขของ state ในแอปพลิเคชัน
ตัวอย่าง modal ที่ทำ lazy-load:
import React, { useState, Suspense, lazy } from 'react';
import LoadingSpinner from './components/LoadingSpinner';
// Lazily import the modal component
const EditProfileModal = lazy(() => import('./components/EditProfileModal'));
function UserProfilePage() {
const [isModalOpen, setIsModalOpen] = useState(false);
const openModal = () => {
setIsModalOpen(true);
};
const closeModal = () => {
setIsModalOpen(false);
};
return (
<div>
<h1>User Profile</h1>
<p>Some user information here...</p>
<button onClick={openModal}>Edit Profile</button>
{/* The modal component and its code will only be loaded when isModalOpen is true */}
{isModalOpen && (
<Suspense fallback={<LoadingSpinner />}>
<EditProfileModal onClose={closeModal} />
</Suspense>
)}
</div>
);
}
export default UserProfilePage;
ในสถานการณ์นี้ JavaScript chunk สำหรับ `EditProfileModal.js` จะถูกร้องขอจากเซิร์ฟเวอร์หลังจากที่ผู้ใช้คลิกปุ่ม "Edit Profile" เป็นครั้งแรกเท่านั้น
Vue
ฟังก์ชัน `defineAsyncComponent` ของ Vue เหมาะสำหรับสิ่งนี้มาก มันช่วยให้คุณสร้าง wrapper รอบ component ที่จะถูกโหลดก็ต่อเมื่อมันถูกเรนเดอร์จริงๆ
ตัวอย่าง chart component ที่ทำ lazy-load:
<template>
<div>
<h1>Sales Dashboard</h1>
<button @click="showChart = true" v-if="!showChart">Show Sales Chart</button>
<!-- The SalesChart component will be loaded and rendered only when showChart is true -->
<SalesChart v-if="showChart" />
</div>
</template>
<script setup>
import { ref, defineAsyncComponent } from 'vue';
const showChart = ref(false);
// Define an async component. The heavy charting library will be in its own chunk.
const SalesChart = defineAsyncComponent(() =>
import('../components/SalesChart.vue')
);
</script>
ในที่นี้ โค้ดสำหรับ `SalesChart` component ที่อาจมีขนาดใหญ่ (และ dependencies ของมัน เช่น ไลบรารีกราฟ) จะถูกแยกออกไป มันจะถูกดาวน์โหลดและติดตั้งก็ต่อเมื่อผู้ใช้ร้องขออย่างชัดเจนโดยการคลิกปุ่ม
เทคนิคและรูปแบบขั้นสูง
เมื่อคุณเชี่ยวชาญพื้นฐานของการแยกโค้ดตาม route และ component แล้ว คุณสามารถใช้เทคนิคขั้นสูงเพิ่มเติมเพื่อปรับปรุงประสบการณ์ของผู้ใช้ให้ดียิ่งขึ้น
การ Preloading และ Prefetching Chunks
การรอให้ผู้ใช้คลิกลิงก์ก่อนที่จะดึงโค้ดของ route ถัดไปอาจทำให้เกิดความล่าช้าเล็กน้อย เราสามารถทำได้ฉลาดกว่านี้โดยการโหลดโค้ดล่วงหน้า
- Prefetching: นี่เป็นการบอกเบราว์เซอร์ให้ดึง resource ในช่วงเวลาที่ไม่ได้ใช้งาน (idle time) เพราะผู้ใช้อาจต้องการมันสำหรับการไปยังหน้า ถัดไป ในอนาคต มันเป็นคำแนะนำที่มีลำดับความสำคัญต่ำ ตัวอย่างเช่น เมื่อผู้ใช้เข้าสู่ระบบแล้ว คุณสามารถ prefetch โค้ดสำหรับหน้าแดชบอร์ดได้ เนื่องจากมีความเป็นไปได้สูงที่พวกเขาจะไปที่นั่นต่อ
- Preloading: นี่เป็นการบอกเบราว์เซอร์ให้ดึง resource ด้วยลำดับความสำคัญสูง เพราะมันจำเป็นสำหรับหน้า ปัจจุบัน แต่การค้นพบมันล่าช้า (เช่น font ที่กำหนดไว้ลึกในไฟล์ CSS) ในบริบทของ code splitting คุณสามารถ preload chunk เมื่อผู้ใช้เลื่อนเมาส์ไปเหนือลิงก์ ทำให้การนำทางรู้สึกเหมือนเกิดขึ้นทันทีเมื่อพวกเขาคลิก
Bundler อย่าง Webpack และ Vite อนุญาตให้คุณทำสิ่งนี้ได้โดยใช้ "magic comments":
// Prefetch: good for likely next pages
import(/* webpackPrefetch: true, webpackChunkName: "dashboard" */ './pages/DashboardPage');
// Preload: good for high-confidence next interactions on the current page
const openModal = () => {
import(/* webpackPreload: true, webpackChunkName: "profile-modal" */ './components/ProfileModal');
// ... then open the modal
}
การจัดการสถานะ Loading และ Error
การโหลดโค้ดผ่านเครือข่ายเป็นการทำงานแบบอะซิงโครนัสที่อาจล้มเหลวได้ การใช้งานที่แข็งแกร่งต้องคำนึงถึงสิ่งนี้ด้วย
- สถานะ Loading: ควรให้ feedback แก่ผู้ใช้เสมอในขณะที่กำลังดึง chunk ซึ่งจะช่วยป้องกันไม่ให้ UI รู้สึกว่าไม่ตอบสนอง Skeletons (UI ที่เป็นโครงร่างซึ่งเลียนแบบ layout สุดท้าย) มักจะให้ประสบการณ์ผู้ใช้ที่ดีกว่า spinner ทั่วไป `
` ของ React ทำให้เรื่องนี้ง่ายขึ้น ใน Vue และ Angular คุณสามารถใช้ `v-if`/`ngIf` ร่วมกับ loading flag - สถานะ Error: จะเกิดอะไรขึ้นถ้าผู้ใช้อยู่บนเครือข่ายที่ไม่เสถียรและ JavaScript chunk โหลดไม่สำเร็จ? แอปพลิเคชันของคุณไม่ควรพัง ควรสรุป component ที่ lazy-load ของคุณไว้ใน Error Boundary (ใน React) หรือใช้ `.catch()` กับ promise ของ dynamic import เพื่อจัดการความล้มเหลวอย่างนุ่มนวล คุณสามารถแสดงข้อความข้อผิดพลาดและปุ่ม "Retry" ได้
ตัวอย่าง Error Boundary ใน React:
import { ErrorBoundary } from 'react-error-boundary';
function MyComponent() {
return (
<ErrorBoundary
FallbackComponent={({ error, resetErrorBoundary }) => (
<div>
<p>Oops! Failed to load component.</p>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
)}
>
<Suspense fallback={<Spinner />}>
<MyLazyLoadedComponent />
</Suspense>
</ErrorBoundary>
);
}
เครื่องมือและการวิเคราะห์
คุณไม่สามารถเพิ่มประสิทธิภาพในสิ่งที่คุณวัดผลไม่ได้ เครื่องมือ frontend สมัยใหม่มีเครื่องมือที่ยอดเยี่ยมสำหรับการแสดงภาพและวิเคราะห์ bundle ของแอปพลิเคชันของคุณ
- Webpack Bundle Analyzer: เครื่องมือนี้จะสร้างภาพ treemap ของ bundle ที่คุณสร้างขึ้น มันมีค่าอย่างยิ่งในการระบุว่ามีอะไรอยู่ข้างในแต่ละ chunk, ค้นหา dependencies ที่มีขนาดใหญ่หรือซ้ำซ้อน และตรวจสอบว่ากลยุทธ์ code splitting ของคุณทำงานตามที่คาดไว้
- Vite (Rollup Plugin Visualizer): ผู้ใช้ Vite สามารถใช้ `rollup-plugin-visualizer` เพื่อให้ได้แผนภูมิเชิงโต้ตอบที่คล้ายกันขององค์ประกอบ bundle ของพวกเขา
โดยการวิเคราะห์ bundle ของคุณเป็นประจำ คุณสามารถระบุโอกาสในการเพิ่มประสิทธิภาพเพิ่มเติมได้ ตัวอย่างเช่น คุณอาจพบว่าไลบรารีขนาดใหญ่อย่าง `moment.js` หรือ `lodash` ถูกรวมอยู่ในหลายๆ chunk นี่อาจเป็นโอกาสที่จะย้ายมันไปยัง chunk `vendors` ที่ใช้ร่วมกัน หรือหาทางเลือกอื่นที่มีขนาดเล็กกว่า
แนวทางปฏิบัติที่ดีที่สุดและข้อผิดพลาดที่พบบ่อย
แม้ว่าจะมีประสิทธิภาพ แต่ code splitting ก็ไม่ใช่ยาวิเศษ การนำไปใช้อย่างไม่ถูกต้องบางครั้งอาจส่งผลเสียต่อประสิทธิภาพได้
- อย่าแยกโค้ดมากเกินไป: การสร้าง chunk เล็กๆ จำนวนมากเกินไปอาจส่งผลเสียได้ แต่ละ chunk ต้องการ HTTP request แยกกัน และ overhead ของ request เหล่านี้อาจมีมากกว่าประโยชน์ของขนาดไฟล์ที่เล็กลง โดยเฉพาะบนเครือข่ายมือถือที่มี latency สูง จงหาสมดุล เริ่มต้นด้วย route แล้วค่อยๆ แยกเฉพาะ component ที่ใหญ่ที่สุดหรือใช้น้อยที่สุดออกไปอย่างมีกลยุทธ์
- วิเคราะห์เส้นทางของผู้ใช้: แยกโค้ดของคุณตามวิธีที่ผู้ใช้ใช้งานแอปพลิเคชันของคุณจริงๆ หาก 95% ของผู้ใช้ไปจากหน้าเข้าสู่ระบบไปยังหน้าแดชบอร์ดโดยตรง ให้พิจารณา prefetching โค้ดของแดชบอร์ดบนหน้าเข้าสู่ระบบ
- จัดกลุ่ม Dependencies ที่ใช้ร่วมกัน: bundler ส่วนใหญ่มีกลยุทธ์ (เช่น `SplitChunksPlugin` ของ Webpack) เพื่อสร้าง chunk `vendors` ที่ใช้ร่วมกันโดยอัตโนมัติสำหรับไลบรารีที่ใช้ในหลายๆ route ซึ่งช่วยป้องกันการซ้ำซ้อนและปรับปรุงการแคช
- ระวัง Cumulative Layout Shift (CLS): เมื่อโหลด component ตรวจสอบให้แน่ใจว่าสถานะ loading ของคุณ (เช่น skeleton) ใช้พื้นที่เท่ากับ component สุดท้าย มิฉะนั้น เนื้อหาของหน้าจะกระโดดไปมาเมื่อ component โหลด ซึ่งจะนำไปสู่คะแนน CLS ที่ไม่ดี
สรุป: เว็บที่เร็วขึ้นสำหรับทุกคน
Code splitting ไม่ใช่เทคนิคขั้นสูงเฉพาะกลุ่มอีกต่อไป แต่มันเป็นข้อกำหนดพื้นฐานสำหรับการสร้างเว็บแอปพลิเคชันสมัยใหม่ที่มีประสิทธิภาพสูง ด้วยการเปลี่ยนจากการใช้ bundle แบบ monolithic ไฟล์เดียวมาเป็นการโหลดตามความต้องการ คุณสามารถมอบประสบการณ์ที่เร็วและตอบสนองได้ดีขึ้นอย่างมีนัยสำคัญแก่ผู้ใช้ของคุณ โดยไม่คำนึงถึงอุปกรณ์หรือสภาพเครือข่ายของพวกเขา
เริ่มต้นด้วย การแยกโค้ดตาม route—มันเป็นสิ่งที่ทำได้ง่ายและให้ผลตอบแทนด้านประสิทธิภาพสูงสุดในตอนแรก เมื่อทำเสร็จแล้ว ให้วิเคราะห์แอปพลิเคชันของคุณด้วย bundle analyzer และระบุส่วนที่เหมาะสำหรับการแยกโค้ดตาม component มุ่งเน้นไปที่ component ที่มีขนาดใหญ่, มีการโต้ตอบสูง, หรือไม่ค่อยได้ใช้ เพื่อปรับปรุงประสิทธิภาพการโหลดของแอปพลิเคชันของคุณให้ดียิ่งขึ้น
ด้วยการใช้กลยุทธ์เหล่านี้อย่างรอบคอบ คุณไม่ได้แค่ทำให้เว็บไซต์ของคุณเร็วขึ้นเท่านั้น แต่คุณกำลังทำให้เว็บเข้าถึงได้ง่ายและน่าใช้งานมากขึ้นสำหรับผู้ชมทั่วโลก ทีละ chunk