טיפ נקסט: שימו לב ל import-ים

05/11/2024

ב next.js בגירסה ה Page Router יש לנו שלוש אפשרויות לטעון ספריות חיצוניות, ואני חושש שההבדל ביניהן לא תמיד מספיק ברור.

1. שימוש בספריה חיצונית בקובץ נפרד

דרך ראשונה היא לקרוא ל import מתוך קובץ של Page, לדוגמה בפרויקט next חדש אני כותב בקובץ index.tsx את הקוד הבא:

import _ from 'lodash';
import moment from 'moment';

// export const getServerSideProps = (async () => {
//   const _ = (await import('lodash')).default;
//   return { props: { name: foo(), number: _.random(100) } }
// });


export default function Home(props: any) {
  return (
    <>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <div
        className={`${styles.page} ${geistSans.variable} ${geistMono.variable}`}
      >        
        <main className={styles.main}>
          <p>Hello {_.head([1, 2, 3])}</p>
          <p>{moment("20111031", "YYYYMMDD").fromNow()}</p>
        </main>
      </div>
    </>
  );
}

בבנייה של הקוד עם npm run build אני מקבל בתיקיית chunks את:

$ ls -1 .next/static/chunks
29107295-e679c2f4ae05922d.js
75fc9c18-25d3a9414ec027b7.js
framework-88d0cc4abe8a5763.js
main-22f485a16e76a198.js
pages
polyfills-42372ed130431b0a.js
webpack-69db33922baa0ba7.js

שימו לב לשני הקבצים ששמותיהם הם מספרים, אלה הספריות lodash ו moment. הם נכנסו לפרויקט בתור קבצים נפרדים, וזה נחמד כי דפדפן יכול לשמור כל אחד מהם בנפרד ב cache, ולטעון אותם רק מהדפים שבאמת צריכים אותם.

2. שימוש בספריה חיצונית מתוך app

בפרויקט next יש גם קובץ כללי בשם _app.tsx. ספריות שאנחנו טוענים ממנו יאוחדו לקובץ אחד שייטען בכל אחד מהדפים ביישום. אני מחזיר את index.tsx למצבו ההתחלתי ומעדכן הפעם את _app.tsx לתוכן הבא:

import "@/styles/globals.css";
import type { AppProps } from "next/app";
import _ from 'lodash';
import moment from 'moment';

export default function App({ Component, pageProps }: AppProps) {
  return <div>
    <p>Hello {_.head([1, 2, 3])}</p>
    <p>{moment("20111031", "YYYYMMDD").fromNow()}</p>
    <Component {...pageProps} />;
  </div>

}

מבחינת העמוד דברים נראים אותו דבר יש לי את אותם טקסטים בדיוק (כן במקום אחר אבל זה לא חשוב כרגע), אבל מה שכן חשוב זה ה JavaScript שנוצר:

$ ls -l .next/static/chunks
total 816
-rw-r--r--  1 ynonp  staff  181258 נוב  4 19:42 framework-88d0cc4abe8a5763.js
-rw-r--r--  1 ynonp  staff  113610 נוב  4 19:42 main-22f485a16e76a198.js
drwxr-xr-x  5 ynonp  staff     160 נוב  4 19:42 pages
-rw-r--r--  1 ynonp  staff  112594 נוב  4 19:42 polyfills-42372ed130431b0a.js
-rw-r--r--  1 ynonp  staff    1553 נוב  4 19:42 webpack-69db33922baa0ba7.js

הפעם אין צ'אנקים עבור הספריות החיצוניות ובמקומים בתוך תיקיית pages אני מקבל:

$ ls -l .next/static/chunks/pages
total 272
-rw-r--r--  1 ynonp  staff  129509 נוב  4 19:42 _app-852f41acee199ae3.js
-rw-r--r--  1 ynonp  staff     232 נוב  4 19:42 _error-8c2b6ff87cd513a2.js
-rw-r--r--  1 ynonp  staff    1385 נוב  4 19:42 index-de2181fd4b2adca1.js

כלומר קובץ אחד בשם _app-852f41acee199ae3.js שמכיל את שתי הספריות. הקובץ הזה ייטען בכל אחד מהדפים ביישום ודפדפן לא יוכל לשמור אותו ב cache, כי כל שינוי באחד המרכיבים שלו יגרום ליצירה מחדש של כל הקובץ.

3. שימוש בספריה חיצונית רק מתוך קוד צד שרת

אופציה שלישית היא לטעון את הספריה החיצונית ולהשתמש בה רק בפונקציה getServerSideProps מתוך אחד הדפים. אני מחזיר את _app.tsx לצורתו המקורית וחוזר ל index.tsx ועכשיו זה יהיה תוכנו:

import _ from 'lodash';
import moment from 'moment';

export const getServerSideProps = (async () => {
  return {
    props: {
      one: _.head([1, 2, 3]),
      two: moment("20111031", "YYYYMMDD").fromNow(),
    }
  }
});


export default function Home(props: any) {
  return (
    <>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <div
        className={`${styles.page} ${geistSans.variable} ${geistMono.variable}`}
      >        
        <main className={styles.main}>
          <p>{props.one}</p>
          <p>{props.two}</p>
        </main>
      </div>
    </>
  );
}

הפעם אלה הצ'אנקים שנוצרו בבנייה:

ynonp@Ynons-MacBook-Air ~/tmp/perf (main*?) $ ls -l .next/static/chunks
total 816
-rw-r--r--  1 ynonp  staff  181258 נוב  4 19:46 framework-88d0cc4abe8a5763.js
-rw-r--r--  1 ynonp  staff  113610 נוב  4 19:46 main-22f485a16e76a198.js
drwxr-xr-x  5 ynonp  staff     160 נוב  4 19:46 pages
-rw-r--r--  1 ynonp  staff  112594 נוב  4 19:46 polyfills-42372ed130431b0a.js
-rw-r--r--  1 ynonp  staff    1459 נוב  4 19:46 webpack-59041051e025bed2.js
ynonp@Ynons-MacBook-Air ~/tmp/perf (main*?) $ ls -l .next/static/chunks/pages
total 24
-rw-r--r--  1 ynonp  staff   400 נוב  4 19:46 _app-f45d290ef4eb0435.js
-rw-r--r--  1 ynonp  staff   232 נוב  4 19:46 _error-8c2b6ff87cd513a2.js
-rw-r--r--  1 ynonp  staff  1480 נוב  4 19:46 index-f051a589b07bc05e.js

הפעם אף קובץ בתיקיית chunks לא כולל את ספריית moment או את ספריית lodash. ספריות אלה נטענות ונמצאות בשימוש רק בצד השרת, והגודל הכולל של כל ה JavaScript שנשלח לדפדפן הוא כ 20% פחות.

שורה תחתונה בעבודה עם next גם בגירסת ה Pages Router אנחנו יכולים להוריד משמעותית את כמות ה JavaScript שנשלחת לדפדפן באמצעות העברת הקוד לצד שרת, או להשתמש נכון יותר ב Cache באמצעות העברת הספריות לקבצים של הדפים. הקובץ app צריך להיות ממש המוצא האחרון כיוון שקוד שאני כותב בו יישלח לכל הדפים במרוכז.