URLPattern מביא ניתוב לפלטפורמת האינטרנט

גישה ליצירת סטנדרטים לתרחישים נפוצים לדוגמה של התאמה לדפוסים.

רקע

ניתוב הוא חלק חשוב בכל אפליקציית אינטרנט. בבסיסו, ניתוב כולל קבלת כתובת URL, החלת התאמה לתבנית או לוגיקה אחרת ספציפית לאפליקציה, ובדרך כלל הצגת תוכן אינטרנט על סמך התוצאה. אפשר להטמיע ניתוב במספר דרכים: לפעמים זה קוד שפועל בשרת וממפה נתיב לקבצים בדיסק, או לוגיקה באפליקציה של דף יחיד שמחכה לשינויים במיקום הנוכחי ויוצרת קטע DOM תואם להצגה.

אין תקן מוגדר אחד, אבל מפתחי אתרים נטו לשימוש בסינטקס נפוץ להבעת תבניות ניתוב של כתובות URL, שיש להן הרבה דמיון ל-regular expressions, אבל עם תוספות ספציפיות לדומיין כמו אסימונים להתאמה לפלחי נתיב. התחביר הזה (או משהו דומה מאוד לו) משמש בפלטפורמות פופולריות בצד השרת כמו Express ו-Ruby on Rails, ומפתחי JavaScript יכולים להשתמש במודולים כמו path-to-regexp או regexpparam כדי להוסיף את הלוגיקה הזו לקוד שלהם.

URLPattern הוא תוסף לפלטפורמת האינטרנט שמבוסס על הבסיס שנוצר על ידי המסגרות האלה. המטרה שלו היא לסטנדרטיזציה של תחביר של תבניות ניתוב, כולל תמיכה בתווים כלליים לחיפוש, בקבוצות אסימונים בעלות שם, בקבוצות של ביטויים רגולריים ובמפעילי קבוצות. מכונות URLPattern שנוצרות באמצעות התחביר הזה יכולות לבצע משימות ניתוב נפוצות, כמו התאמה לכתובות URL מלאות או לכתובת URL‏ pathname, ולהחזיר מידע על ההתאמות של האסימון והקבוצה.

יתרון נוסף של התאמת כתובות URL ישירות בפלטפורמת האינטרנט הוא שאפשר לשתף תחביר משותף עם ממשקי API אחרים שגם צריכים להתאים לכתובות URL.

תמיכה בדפדפנים ו-polyfills

URLPattern מופעל כברירת מחדל ב-Chrome וב-Edge מגרסה 95 ואילך.

הספרייה urlpattern-polyfill מאפשרת להשתמש בממשק URLPattern בדפדפנים או בסביבות כמו Node, שבהן אין תמיכה מובנית. אם אתם משתמשים ב-polyfill, חשוב להשתמש בזיהוי תכונות כדי לוודא שאתם טוענים אותו רק אם אין תמיכה בסביבה הנוכחית. אחרת, תאבדו את אחת מהיתרונות העיקריים של URLPattern: העובדה שבסביבות התמיכה לא צריך להוריד ולנתח קוד נוסף כדי להשתמש בו.

if (!(globalThis && 'URLPattern' in globalThis)) {
  // URLPattern is not available, so the polyfill is needed.
}

תאימות תחביר

אחד העקרונות המנחים של URLPattern הוא להימנע מיצירת משהו חדש לגמרי. אם אתם כבר מכירים את התחביר של הניתוב ב-Express או ב-Ruby on Rails, לא תצטרכו ללמוד שום דבר חדש. עם זאת, בגלל ההבדלים הקלים בין התחבירים בספריות ניתוב פופולריות, היה צריך לבחור משהו בתור התחביר הבסיסי, והמעצבים של URLPattern החליטו להשתמש בתחביר התבניות מ-path-to-regexp (אבל לא בממשק ה-API שלו) כנקודת ההתחלה.

ההחלטה הזו התקבלה לאחר התייעצות מעמיקה עם המנהל הנוכחי של path-to-regexp.

הדרך הטובה ביותר להכיר את הליבה של התחביר הנתמך היא לעיין במסמכי התיעוד של path-to-regexp. אפשר לקרוא את המסמכים שמיועדים לפרסום ב-MDN באתר הנוכחי שלהם ב-GitHub.

תכונות נוספות

התחביר של URLPattern הוא קבוצה רחבה יותר מזו שנתמכת ב-path-to-regexp, כי URLPattern תומך בתכונה נדירה בספריות ניתוב: התאמה של מקורות, כולל תווים כלליים לחיפוש בשמות מארחים. רוב ספריות הניתוב האחרות מטפלות רק בpathname, ולפעמים בחלק של החיפוש או ה-hash בכתובת ה-URL. הם אף פעם לא צריכים לבדוק את החלק של המקור בכתובת ה-URL, כי הם משמשים רק לניתוב מאותו מקור באפליקציית אינטרנט עצמאית.

התייחסות למקורות פותחת את הדלת לתרחישי שימוש נוספים, כמו ניתוב בקשות ממקורות שונים בתוך פונקציית הטיפול באירועים fetch של service worker. אם אתם מנווטים רק כתובות URL מאותו מקור, תוכלו להתעלם מהתכונה הנוספת הזו ולהשתמש ב-URLPattern כמו בספריות אחרות.

דוגמאות

בניית התבנית

כדי ליצור URLPattern, מעבירים למבנה ה-constructor מחרוזות או אובייקט שהמאפיינים שלו מכילים מידע על התבנית שרוצים להתאים אליה.

העברת אובייקט מספקת את השליטה הברורה ביותר לגבי התבנית שבה צריך להשתמש כדי להתאים לכל רכיב של כתובת ה-URL. בצורתה המפורטת ביותר, הקוד יכול להיראות כך:

const p = new URLPattern({
  protocol: 'https',
  username: '',
  password: '',
  hostname: 'example.com',
  port: '',
  pathname: '/foo/:image.jpg',
  search: '*',
  hash: '*',
});

אם מציינים מחרוזת ריקה לנכס, תהיה התאמה רק אם החלק התואם של כתובת ה-URL לא מוגדר. התו הכללי לחיפוש * יתאים לכל ערך בחלק מסוים של כתובת ה-URL.

ב-constructor יש כמה קיצורי דרך לשימוש פשוט יותר. השמטה מוחלטת של search ו-hash, או של מאפיינים אחרים, זהה להגדרתם כתובת ה-wildcard '*'. אפשר לפשט את הדוגמה שלמעלה כך:

const p = new URLPattern({
  protocol: 'https',
  username: '',
  password: '',
  hostname: 'example.com',
  port: '',
  pathname: '/foo/:image.jpg',
});

קיצור דרך נוסף: אפשר לספק את כל המידע על המקור בנכס יחיד, baseURL, שמובילה אל

const p = new URLPattern({
  pathname: '/foo/:image.jpg',
  baseURL: 'https://example.com',
});

כל הדוגמאות האלה מבוססות על ההנחה שהתרחיש לדוגמה שלכם כולל מקורות תואמים. אם אתם רוצים להתאים רק לחלקים האחרים של כתובת ה-URL, לא כולל המקור (כמו שקורה בתרחישים רבים של ניתוב 'מסורתי' למקור יחיד), תוכלו להשמיט את פרטי המקור לגמרי ולספק רק שילוב כלשהו של המאפיינים pathname,‏ search ו-hash. כמו קודם, נכסים שהושמטו יטופלו כאילו הוגדרו לדפוס התו הכללי לחיפוש *.

const p = new URLPattern({pathname: '/foo/:image.jpg'});

במקום להעביר אובייקט ל-constructor, אפשר לספק מחרוזת אחת או שתיים. אם מציינים מחרוזת אחת, היא צריכה לייצג תבנית מלאה של כתובת URL, כולל פרטי התבנית שמשמשים להתאמה למקור. אם מציינים שתי מחרוזות, המחרוזת השנייה משמשת כ-baseURL והמחרוזת הראשונה נחשבת ביחס לבסיס הזה.

בין שמספקים מחרוזת אחת ובין ששתיים, ה-constructor של URLPattern ינתח את תבנית כתובת ה-URL המלאה, יחלק אותה לרכיבי כתובת URL וימפה כל חלק מהתבנית הגדולה יותר לרכיב המתאים. כלומר, מתחת לפני השטח, כל URLPattern שנוצר באמצעות מחרוזות מיוצג בסופו של דבר באותו אופן כמו URLPattern מקביל שנוצר באמצעות אובייקט. ה-constructor של המחרוזות הוא רק קיצור דרך, למי שמעדיף ממשק פחות מפורט.

const p = new URLPattern('https://example.com/foo/:image.jpg?*#*');

כשמשתמשים במחרוזות כדי ליצור URLPattern, יש כמה דברים שחשוב לזכור.

השמטת מאפיין כשמשתמשים באובייקט כדי ליצור את URLPattern שווה ערך למתן תו כללי לחיפוש * למאפיין הזה. כשמבצעים ניתוח של דפוס מחרוזת כתובת ה-URL המלאה, אם חסר ערך באחד מרכיבי כתובת ה-URL, המערכת מתייחסת אליו כאילו המאפיין של הרכיב הוגדר כ-'', שיתאים רק כשהרכיב הזה ריק.

כשמשתמשים במחרוזות, צריך לכלול את תוו המשתנה הלא ידוע באופן מפורש אם רוצים להשתמש בו ב-URLPattern שנוצר.

// p1 and p2 are equivalent.
const p1 = new URLPattern('/foo', location.origin);
const p2 = new URLPattern({
  protocol: location.protocol,
  hostname: location.hostname,
  pathname: '/foo',
  search: '',
  hash: '',
});

// p3 and p4 are equivalent.
const p3 = new URLPattern('/foo?*#*', location.origin);
const p4 = new URLPattern({
  protocol: location.protocol,
  hostname: location.hostname,
  pathname: '/foo',
});

חשוב גם לזכור שניתוח של תבנית מחרוזת לרכיביה עשוי להיות לא ברור. יש תווים, כמו :, שמופיעים בכתובות URL, אבל יש להם גם משמעות מיוחדת בתחביר של התאמת התבניות. כדי להימנע מהמצב הזה, המאגר (constructor) של URLPattern מניח שכל אחד מהתווים המיוחדים האלה הוא חלק מתבנית, ולא חלק מכתובת ה-URL. אם רוצים שתו מסוים שיכול להתפרש כחלק מכתובת ה-URL יפורש כחלק ממנה, צריך להשתמש ב-escape‏ \` character. For example, the literal URLabout:blankshould be escaped as'about\:blank'` כשהוא מסופק כמחרוזת.

שימוש בקו ביטול הנעילה

אחרי שיוצרים URLPattern, יש שתי אפשרויות לשימוש בו. השיטות test() ו-exec() מקבלות את אותו קלט ומשתמשות באותו אלגוריתם כדי לבדוק אם יש התאמה, וההבדל ביניהן הוא רק בערך המוחזר. test() מחזירה את הערך true אם יש התאמה לקלט הנתון, ואת הערך false במקרים אחרים. הפונקציה exec() מחזירה מידע מפורט על ההתאמה, יחד עם קבוצות לחילוץ, או את הערך null אם אין התאמה. בדוגמאות הבאות מוצג שימוש ב-exec(), אבל אפשר להחליף את test() בכל אחת מהן אם רוצים רק ערך חזרה בוליאני פשוט.

אחת מהדרכים להשתמש בשיטות test() ו-exec() היא להעביר מחרוזות. בדומה למה שה-constructor תומך בו, אם מציינים מחרוזת אחת, היא צריכה להיות כתובת URL מלאה, כולל המקור. אם מציינים שתי מחרוזות, המחרוזת השנייה נחשבת כערך של baseURL והמחרוזת הראשונה נבדקת ביחס לבסיס הזה.

const p = new URLPattern({
  pathname: '/foo/:image.jpg',
  baseURL: 'https://example.com',
});

const result = p.exec('https://example.com/foo/cat.jpg');
// result will contain info about the successful match.
// const result = p.exec('/foo/cat.jpg', 'https://example.com')
// is equivalent, using the baseURL syntax.

const noMatchResult = p.exec('https://example.com/bar');
// noMatchResult will be null.

לחלופין, אפשר להעביר את אותו סוג אובייקט שה-constructor תומך בו, עם מאפיינים שמוגדרים רק לחלקים של כתובת ה-URL שחשוב לכם להתאים.

const p = new URLPattern({pathname: '/foo/:image.jpg'});

const result = p.exec({pathname: '/foo/:image.jpg'});
// result will contain info about the successful match.

כשמשתמשים ב-exec() ב-URLPattern שמכיל תווים כלליים לחיפוש או אסימונים, הערך המוחזר יספק מידע על הערכים התואמים בכתובת ה-URL שהוזנה. כך תוכלו לחסוך את הצורך לנתח את הערכים האלה בעצמכם.

const p = new URLPattern({
  hostname: ':subdomain.example.com',
  pathname: '/*/:image.jpg'
});

const result = p.exec('https://imagecdn1.example.com/foo/cat.jpg');
// result.hostname.groups.subdomain will be 'imagecdn1'
// result.pathname.groups[0] will be 'foo', corresponding to *
// result.pathname.groups.image will be 'cat'

קבוצות אנונימיות וקבוצות בעלות שם

כשמעבירים מחרוזת של כתובת URL לפונקציה exec(), מקבלים בחזרה ערך שמציין אילו קטעים התאימו לכל הקבוצות של התבנית.

לערך המוחזר יש מאפיינים שתואמים לרכיבים של URLPattern, כמו pathname. לכן, אם קבוצה הוגדרה כחלק מחלק pathname של URLPattern, ההתאמות נמצאות ב-pathname.groups של ערך ההחזרה. ההתאמות מיוצגות באופן שונה בהתאם לכך שהתבנית התואמת הייתה קבוצה אנונימית או קבוצה בעלת שם.

אפשר להשתמש באינדיקטורים של מערכי נתונים כדי לגשת לערכים של התאמה אנונימית לדפוס. אם יש כמה דפוסים אנונימיים, האינדקס 0 ייצג את הערך התואם לדפוס שמשמאל ביותר, והאינדקסים 1 ואינדקסים נוספים ישמשו לדפוסים הבאים.

כשמשתמשים בקבוצות בעלות שם בתבנית, ההתאמות יוצגו כמאפיינים ששמותיהם תואמים לשמות של כל קבוצה.

תמיכה ב-Unicode ותקינה

URLPattern תומך בתווי Unicode בכמה דרכים שונות.

  • קבוצות בעלות שם, כמו :café, יכולות להכיל תווים של Unicode. הכללים שחלים על מזהי JavaScript תקינים חלים על קבוצות בעלות שם.

  • טקסט בתוך תבנית יקודר באופן אוטומטי לפי אותם כללים שמשמשים לקידוד כתובות URL של הרכיב הספציפי הזה. תווי Unicode ב-pathname יקודרו לפי אחוזים, כך שתבנית pathname כמו /café תהפוך באופן אוטומטי ל-/caf%C3%A9. תווי Unicode ב-hostname מקודדים באופן אוטומטי באמצעות Punycode, ולא באמצעות קידוד אחוזים.

  • קבוצות של ביטויים רגולריים חייבות להכיל רק תווים מסוג ASCII. תחביר הביטוי הרגולרי מקשה על קידוד אוטומטי של תווים ב-Unicode בקבוצות האלה, וגם לא בטוח לעשות זאת. אם רוצים להתאים תו של Unicode בקבוצה של ביטוי רגולרי, צריך לבצע קידוד אחוזים ידני, למשל (caf%C3%A9) כדי להתאים ל-café.

בנוסף לקידוד של תווים ב-Unicode, URLPattern מבצע גם נורמליזציה של כתובות URL. לדוגמה, הערך /foo/./bar ברכיב pathname ימוקם ב-/foo/bar המקביל.

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

סיכום של כל המידע

הדוגמה של Glitch שמוטמעת בהמשך ממחישה תרחיש לדוגמה של שימוש ב-URLPattern בתוך fetch event handler של שירות העבודה, שממפה דפוסים ספציפיים לפונקציות אסינכררוניות שיכולות ליצור תגובה לבקשות מהרשת. אפשר להחיל את העקרונות שבדוגמאות האלה גם על תרחישים אחרים של ניתוב, בצד השרת או בצד הלקוח.

משוב ותוכניות עתידיות

הפונקציונליות הבסיסית של URLPattern הגיעה ל-Chrome ול-Edge, אבל אנחנו מתכננים להוסיף עוד תכונות. יש היבטים מסוימים של URLPattern שעדיין נמצאים בפיתוח, ויש כמה שאלות פתוחות לגבי התנהגויות ספציפיות שעדיין אפשר לשפר. אנחנו ממליצים לנסות את URLPattern ולשלוח משוב באמצעות דיווח על בעיה ב-GitHub.

תמיכה ביצירת תבניות

בספרייה path-to-regexp יש פונקציה compile() function שמשנה את התנהגות הניתוב. הפונקציה compile() מקבלת תבנית וערכים למשתני placeholder של האסימון, ומחזירה מחרוזת של נתיב כתובת URL עם ההחלפה של הערכים האלה.

אנחנו מקווים להוסיף את האפשרות הזו ל-URLPattern בעתיד, אבל היא לא נכללת בהיקף של הגרסה הראשונית.

הפעלת תכונות עתידיות בפלטפורמת האינטרנט

בהנחה ש-URLPattern יהפוך לחלק קבוע בפלטפורמת האינטרנט, תכונות אחרות שיכולות להפיק תועלת מהניתוב או מהתאמת התבניות יוכלו להתבסס עליו כעל רכיב פרימיטיבי.

אנחנו ממשיכים לדון בשימוש ב-URLPattern בתכונות מוצעות כמו התאמת דפוסים ברמת ה-service worker, אפליקציות PWA כמנהלי קבצים ואחזור מראש ספקולטיבי.

תודות

במסמך ההסבר המקורי מופיעה רשימה מלאה של תודות.