นำโค้ดที่ไม่ได้ใช้งานออก

ใน Codelab นี้ คุณจะได้ปรับปรุงประสิทธิภาพของแอปพลิเคชันต่อไปนี้โดยนำทรัพยากร Dependency ที่ไม่ได้ใช้และไม่จำเป็นออก

ภาพหน้าจอแอป

วัดผล

คุณควรวัดประสิทธิภาพของเว็บไซต์ก่อนที่จะเพิ่มการเพิ่มประสิทธิภาพเสมอ

  • หากต้องการดูตัวอย่างเว็บไซต์ ให้กดดูแอป แล้วกดเต็มหน้าจอ เต็มหน้าจอ

คลิกที่ลูกแมวตัวโปรดได้เลย แอปพลิเคชันนี้ใช้ Realtime Database ของ Firebase ซึ่งเป็นสาเหตุที่คะแนนอัปเดตแบบเรียลไทม์และซิงค์กับทุกคนที่ใช้แอปพลิเคชัน 🐈

  1. กด `Control+Shift+J` (หรือ `Command+Option+J` บน Mac) เพื่อเปิดเครื่องมือสำหรับนักพัฒนาเว็บ
  2. คลิกแท็บเครือข่าย
  3. เลือกช่องทำเครื่องหมายปิดใช้แคช
  4. โหลดแอปซ้ำ

ขนาดแพ็กเกจเดิม 992 KB

ระบบกำลังส่ง JavaScript ขนาดเกือบ 1 MB เพื่อโหลดแอปพลิเคชันง่ายๆ นี้

ดูคำเตือนเกี่ยวกับโปรเจ็กต์ในเครื่องมือสำหรับนักพัฒนาเว็บ

  • คลิกแท็บคอนโซล
  • ตรวจสอบว่าได้เปิดใช้ Warnings ในเมนูแบบเลื่อนลงของระดับข้างอินพุต Filter

ตัวกรองคำเตือน

  • ดูคำเตือนที่แสดง

คำเตือนในคอนโซล

Firebase ซึ่งเป็นหนึ่งในไลบรารีที่ใช้ในแอปพลิเคชันนี้กำลังทำหน้าที่เป็นพลเมืองดีโดยการแสดงคำเตือนเพื่อให้นักพัฒนาแอปทราบว่าไม่ควรนำเข้าทั้งแพ็กเกจ แต่ควรนำเข้าเฉพาะคอมโพเนนต์ที่ใช้เท่านั้น กล่าวคือ มีไลบรารีที่ไม่ได้ใช้ซึ่งสามารถนำออกจากแอปพลิเคชันนี้เพื่อให้โหลดเร็วขึ้นได้

นอกจากนี้ยังมีบางกรณีที่มีการใช้ไลบรารีหนึ่งๆ แต่ก็อาจมีทางเลือกที่ง่ายกว่า เราจะพูดถึงแนวคิดในการนำไลบรารีที่ไม่จำเป็นออกในส่วนถัดไปของบทแนะนำนี้

กำลังวิเคราะห์แพ็กเกจ

แอปพลิเคชันมีการขึ้นต่อกันหลักๆ 2 รายการดังนี้

  • Firebase: แพลตฟอร์มที่ให้บริการที่มีประโยชน์มากมายสำหรับแอปพลิเคชัน iOS, Android หรือเว็บแอปพลิเคชัน ในกรณีนี้ ระบบจะใช้ Realtime Database เพื่อจัดเก็บและซิงค์ข้อมูลของลูกแมวแต่ละตัวแบบเรียลไทม์
  • Moment.js: ไลบรารียูทิลิตีที่ช่วยให้จัดการวันที่ใน JavaScript ได้ง่ายขึ้น ระบบจะจัดเก็บวันเกิดของลูกแมวแต่ละตัวไว้ในฐานข้อมูล Firebase และใช้ moment เพื่อคำนวณอายุเป็นสัปดาห์

ทรัพยากร Dependency เพียง 2 รายการจะทำให้ Bundle มีขนาดเกือบ 1 MB ได้อย่างไร เหตุผลหนึ่งก็คือทรัพยากร Dependency ใดๆ ก็ตามอาจมีทรัพยากร Dependency ของตัวเองได้ ดังนั้นจึงมีทรัพยากร Dependency มากกว่า 2 รายการหากพิจารณา "แผนผัง" ทรัพยากร Dependency ทุกระดับความลึก/สาขา แอปพลิเคชันอาจมีขนาดใหญ่ขึ้นอย่างรวดเร็วหากมีการพึ่งพาจำนวนมาก

วิเคราะห์ Bundler เพื่อให้เข้าใจสิ่งที่เกิดขึ้นได้ดียิ่งขึ้น มีเครื่องมือต่างๆ มากมายที่ชุมชนสร้างขึ้นซึ่งช่วยทำสิ่งนี้ได้ เช่น webpack-bundle-analyzer

แพ็กเกจสำหรับเครื่องมือนี้รวมอยู่ในแอปเป็น devDependency แล้ว

"devDependencies": {
  //...
  "webpack-bundle-analyzer": "^2.13.1"
},

ซึ่งหมายความว่าคุณสามารถใช้ปลั๊กอินนี้ได้โดยตรงในไฟล์การกำหนดค่า webpack นำเข้าที่จุดเริ่มต้นของ webpack.config.js:

const path = require("path");

//...
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
  .BundleAnalyzerPlugin;

ตอนนี้ให้เพิ่มเป็นปลั๊กอินที่ส่วนท้ายสุดของไฟล์ภายในอาร์เรย์ plugins ดังนี้

module.exports = {
  //...
  plugins: [
    //...
    new BundleAnalyzerPlugin()
  ]
};

เมื่อแอปพลิเคชันโหลดซ้ำ คุณควรเห็นภาพของทั้งแพ็กเกจแทนที่จะเป็นตัวแอปเอง

Webpack Bundle Analyzer

แม้จะไม่น่ารักเท่าการดูวิดีโอลูกแมว 🐱 แต่ก็มีประโยชน์อย่างมาก การวางเมาส์เหนือแพ็กเกจใดๆ จะแสดงขนาดของแพ็กเกจใน 3 รูปแบบที่แตกต่างกัน ดังนี้

ขนาดสถิติ ขนาดก่อนการลดขนาดหรือการบีบอัด
ขนาดที่แยกวิเคราะห์ ขนาดของแพ็กเกจจริงภายในแพ็กเกจรวมหลังจากคอมไพล์แล้ว Webpack เวอร์ชัน 4 (ซึ่งใช้ในแอปพลิเคชันนี้) จะลดขนาดไฟล์ที่คอมไพล์โดยอัตโนมัติ ซึ่งเป็นสาเหตุที่ทำให้ไฟล์นี้มีขนาดเล็กกว่าขนาดสถิติ
ขนาดที่บีบอัดด้วย Gzip ขนาดของแพ็กเกจหลังจากบีบอัดด้วยการเข้ารหัส gzip หัวข้อนี้จะกล่าวถึงในคู่มือแยกต่างหาก

เครื่องมือ webpack-bundle-analyzer ช่วยให้ระบุแพ็กเกจที่ไม่ได้ใช้หรือไม่จำเป็นซึ่งคิดเป็นเปอร์เซ็นต์สูงของแพ็กเกจได้ง่ายขึ้น

กำลังนำแพ็กเกจที่ไม่ได้ใช้ออก

การแสดงภาพแสดงให้เห็นว่าแพ็กเกจ firebase ประกอบด้วยสิ่งต่างๆ มากกว่าแค่ฐานข้อมูล ซึ่งรวมถึงแพ็กเกจเพิ่มเติมต่อไปนี้

  • firestore
  • auth
  • storage
  • messaging
  • functions

บริการเหล่านี้เป็นบริการที่ยอดเยี่ยมทั้งหมดที่ Firebase มีให้ (และโปรดดูเอกสารประกอบเพื่อดูข้อมูลเพิ่มเติม) แต่ไม่มีการใช้บริการใดเลยในแอปพลิเคชัน จึงไม่มีเหตุผลที่จะต้องนำเข้าบริการทั้งหมด

โปรดยกเลิกการเปลี่ยนแปลงใน webpack.config.js เพื่อดูแอปพลิเคชันอีกครั้ง โดยทำดังนี้

  • นำ BundleAnalyzerPlugin ออกจากรายการปลั๊กอิน
plugins: [
  //...
  new BundleAnalyzerPlugin()
];
  • จากนั้นให้นำการนำเข้าที่ไม่ได้ใช้ออกจากด้านบนของไฟล์ ดังนี้
const path = require("path");

//...
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

ตอนนี้แอปพลิเคชันควรโหลดได้ตามปกติแล้ว แก้ไข src/index.js เพื่ออัปเดตการนําเข้า Firebase

import firebase from 'firebase';
import firebase from 'firebase/app';
import 'firebase/database';

ตอนนี้เมื่อแอปโหลดซ้ำ คำเตือนเครื่องมือสำหรับนักพัฒนาเว็บจะไม่แสดง การเปิดแผงเครือข่ายของเครื่องมือสำหรับนักพัฒนาเว็บยังแสดงให้เห็นว่าขนาดแพ็กเกจลดลงอย่างมากด้วย

ขนาดแพ็กเกจลดลงเหลือ 480 KB

นำขนาดแพ็กเกจออกมากกว่าครึ่งหนึ่ง Firebase มีบริการที่หลากหลายและเปิดโอกาสให้นักพัฒนาแอปเลือกใช้เฉพาะบริการที่จำเป็นจริงๆ ในแอปพลิเคชันนี้ มีเพียง firebase/database เท่านั้นที่ใช้ในการจัดเก็บและซิงค์ข้อมูลทั้งหมด ต้องมีการนำเข้า firebase/app ซึ่งจะตั้งค่าแพลตฟอร์ม API สำหรับบริการต่างๆ เสมอ

ไลบรารียอดนิยมอื่นๆ อีกหลายรายการ เช่น lodash ยังช่วยให้นักพัฒนาแอปเลือกนำเข้าส่วนต่างๆ ของแพ็กเกจได้ด้วย การอัปเดตการนำเข้าไลบรารีในแอปพลิเคชันให้รวมเฉพาะสิ่งที่ใช้อยู่จะช่วยปรับปรุงประสิทธิภาพได้อย่างมากโดยไม่ต้องทำงานมากนัก

แม้ว่าขนาดแพ็กเกจจะลดลงไปพอสมควร แต่ก็ยังมีงานที่ต้องทำอีกมาก 😈

กำลังนำแพ็กเกจที่ไม่จำเป็นออก

การนำเข้าบางส่วนของไลบรารี moment ไม่สามารถทำได้ง่ายๆ เหมือน Firebase แต่คุณอาจนำออกทั้งหมดได้

วันเกิดของลูกแมวน่ารักแต่ละตัวจะจัดเก็บไว้ในรูปแบบ Unix (มิลลิวินาที) ในฐานข้อมูล Firebase

วันเกิดที่จัดเก็บในรูปแบบ Unix

นี่คือการประทับเวลาของวันที่และเวลาที่เฉพาะเจาะจงซึ่งแสดงด้วยจำนวนมิลลิวินาทีที่ผ่านไปนับตั้งแต่วันที่ 1 มกราคม 1970 เวลา 00:00 น. UTC หากสามารถคำนวณวันที่และเวลาปัจจุบันในรูปแบบเดียวกันได้ ก็อาจสร้างฟังก์ชันเล็กๆ เพื่อหาอายุของลูกแมวแต่ละตัวเป็นสัปดาห์ได้

และเช่นเคย พยายามอย่าคัดลอกและวางขณะทำตามขั้นตอนที่นี่ เริ่มต้นด้วยการนำ moment ออกจากการนำเข้าใน src/index.js

import firebase from 'firebase/app';
import 'firebase/database';
import * as moment from 'moment';

มี Firebase Listener เหตุการณ์ ที่จัดการการเปลี่ยนแปลงค่าในฐานข้อมูลของเรา ดังนี้

favoritesRef.on("value", (snapshot) => { ... })

เหนือโค้ดนี้ ให้เพิ่มฟังก์ชันเล็กๆ เพื่อคำนวณจำนวนสัปดาห์จากวันที่ที่ระบุ ดังนี้

const ageInWeeks = birthDate => {
  const WEEK_IN_MILLISECONDS = 1000 * 60 * 60 * 24 * 7;
  const diff = Math.abs((new Date).getTime() - birthDate);
  return Math.floor(diff / WEEK_IN_MILLISECONDS);
}

ในฟังก์ชันนี้ ระบบจะคำนวณความแตกต่างในหน่วยมิลลิวินาทีระหว่างวันที่และเวลาปัจจุบัน (new Date).getTime() กับวันเกิด (อาร์กิวเมนต์ birthDate ซึ่งอยู่ในหน่วยมิลลิวินาทีอยู่แล้ว) แล้วหารด้วยจำนวนมิลลิวินาทีใน 1 สัปดาห์

สุดท้าย คุณสามารถนำอินสแตนซ์ทั้งหมดของ moment ออกใน Listener เหตุการณ์ได้โดยใช้ฟังก์ชันนี้แทน

favoritesRef.on("value", (snapshot) => {
  const { kitties, favorites, names, birthDates } = snapshot.val();
  favoritesScores = favorites;

  kittiesList.innerHTML = kitties.map((kittiePic, index) => {
    const birthday = moment(birthDates[index]);

    return `
      <li>
        <img src=${kittiePic} onclick="favKittie(${index})">
        <div class="extra">
          <div class="details">
            <p class="name">${names[index]}</p>
            <p class="age">${moment().diff(birthday, 'weeks')} weeks old</p>
            <p class="age">${ageInWeeks(birthDates[index])} weeks old</p>
          </div>
          <p class="score">${favorites[index]} ❤</p>
        </div>
      </li>
    `})
});

ตอนนี้ให้โหลดแอปพลิเคชันซ้ำและดูแผงเครือข่ายอีกครั้ง

ลดขนาดแพ็กเกจลงเหลือ 225 KB

ขนาดของ Bundle ลดลงมากกว่าครึ่งอีกครั้ง

บทสรุป

เมื่อทำ Codelab นี้ คุณควรมีความเข้าใจที่เหมาะสมเกี่ยวกับวิธีวิเคราะห์แพ็กเกจหนึ่งๆ และเหตุผลที่การนำแพ็กเกจที่ไม่ได้ใช้หรือไม่จำเป็นออกจึงมีประโยชน์มาก ก่อนที่จะเริ่มเพิ่มประสิทธิภาพแอปพลิเคชันด้วยเทคนิคนี้ คุณควรทราบว่าวิธีนี้อาจซับซ้อนกว่ามากในแอปพลิเคชันขนาดใหญ่

ในส่วนของการนำไลบรารีที่ไม่ได้ใช้ออก ให้ลองดูว่ามีการใช้ส่วนใดของแพ็กเกจและส่วนใดที่ไม่ได้ใช้ สำหรับแพ็กเกจที่ดูลึกลับซึ่งดูเหมือนว่าจะไม่ได้ใช้ที่ใดเลย ให้ย้อนกลับไปดูว่าทรัพยากร Dependency ระดับบนสุดใดบ้างที่อาจต้องใช้แพ็กเกจดังกล่าว ลองหาวิธีแยกทั้ง 2 ส่วนออกจากกัน

เมื่อพูดถึงการนำไลบรารีที่ไม่จำเป็นออก สิ่งต่างๆ อาจซับซ้อนขึ้นเล็กน้อย สิ่งสำคัญคือต้องทำงานร่วมกับทีมอย่างใกล้ชิดและดูว่ามีโอกาสที่จะลดความซับซ้อนของบางส่วนในฐานของโค้ดหรือไม่ การนำ moment ออกในแอปพลิเคชันนี้อาจดูเหมือนเป็นสิ่งที่ควรทำทุกครั้ง แต่จะเกิดอะไรขึ้นหากมีเขตเวลาและภาษาที่แตกต่างกันซึ่งต้องจัดการ หรือจะเกิดอะไรขึ้นหากมีการจัดการวันที่ที่ซับซ้อนมากขึ้น การจัดการและการแยกวิเคราะห์วันที่/เวลาอาจเป็นเรื่องที่ยุ่งยากมาก และไลบรารีอย่าง moment และ date-fns จะช่วยลดความซับซ้อนนี้ได้อย่างมาก

ทุกอย่างมีข้อดีข้อเสีย และคุณควรประเมินว่าการเปิดตัวโซลูชันที่กำหนดเองแทนที่จะใช้ไลบรารีของบุคคลที่สามนั้นคุ้มค่ากับความซับซ้อนและความพยายามหรือไม่