namespace הוא בלוק עם שם שמקבץ פונקציות, קבועים וטיפוסים תחת שם אחד. איברים שמסומנים ב-export נגישים כ-Name.member; השאר נשארים פרטיים לבלוק. בזמן ריצה namespace הוא אובייקט רגיל.
למה מתקמפל namespace
namespaces הם אחת מהתכונות הבודדות של TypeScript שמייצרות קוד. הבלוק שלמעלה הופך לפונקציה שממלאת אובייקט:
var Geometry;
(function (Geometry) {
const TAU = Math.PI * 2;
function circumference(radius) {
return TAU * radius;
}
Geometry.circumference = circumference;
function area(radius) {
return Math.PI * radius ** 2;
}
Geometry.area = area;
})(Geometry || (Geometry = {}));
זה הדפוס שקוד JavaScript השתמש בו לפני שהיו מודולים, כדי לא לשים כל שם ב-scope הגלובלי. TAU הוא משתנה מקומי של הפונקציה, ולכן קוד אחר לא רואה אותו. ניסיון לקרוא אותו הוא שגיאת קומפילציה:
הקומפיילר מדווח index.ts(10,17): error TS2339: Property 'rate' does not exist on type 'typeof Tax'. הוסיפו export לפני const rate ושתי השורות ירוצו.
קינון, מיזוג וכינויים
אפשר לקנן namespaces, ושני בלוקים עם אותו שם מתמזגים לאחד. ה-Geometry || (Geometry = {}) בפלט הוא מה שגורם לזה לעבוד: הבלוק השני מוסיף לאובייקט הקיים. import X = A.B יוצר כינוי קצר.
namespace A.B.C { } הוא קיצור לשלושה בלוקים מקוננים. הכתיב הישן module Shop { } אומר אותו דבר אבל כיום נדחה עם שגיאה TS1540, A 'namespace' declaration should not be declared using the 'module' keyword. Please use the 'namespace' keyword instead.
מיזוג עם פונקציות ומחלקות
namespace יכול לחלוק את השם שלו עם פונקציה, מחלקה או enum ולהוסיף לה איברים. זו עדיין הדרך הנקייה ביותר לתאר פונקציה שגם נושאת מאפיינים, או מחלקה עם פונקציות עזר מוצמדות.
במחלקה, מתודה static עושה את אותה עבודה והיא JavaScript רגיל. בפונקציה, אפשר גם לוותר על ה-namespace ולהשים format.prefix = "$" מיד אחרי ההצהרה; TypeScript עוקב אחרי מאפיינים שמושמים כך.
Namespaces מול מודולים
לפני ES modules, תוכנית TypeScript גדולה הייתה הרבה קבצי סקריפט שחולקים namespaces גלובליים, מחוברים יחד עם /// <reference path="..." /> ומקומפלים לקובץ אחד עם outFile. מודולים החליפו את זה: כל קובץ הוא scope משלו, תלויות הן imports מפורשים, ו-bundlers יכולים להשמיט exports שלא בשימוש. TypeScript 7 הסיר את outFile (שגיאה TS5102), כך שתצורת namespaces מרובת קבצים כבר אינה אפשרות build.
| Namespace | מודול | |
|---|---|---|
| יחידה | בלוק עם שם בתוך קובץ | הקובץ עצמו |
| Scope | גלובלי אלא אם הוא בתוך מודול | תמיד משלו |
| תלויות | מרומזות, לפי סדר הטעינה | import מפורש |
| פלט | אובייקט שנבנה על ידי פונקציה | import/export או require |
| הסרת קוד שלא בשימוש | bundlers שומרים כל איבר | bundlers יכולים להשמיט exports שלא בשימוש |
| רץ תחת type stripping של Node | רק אם הוא מכיל טיפוסים בלבד | כן |
בתוך מודול, עטיפת הכול ב-namespace מוסיפה רמת שמות שנייה בלי שום תועלת: המייבאים יכתבו Utils.Utils.format. ייצאו את הפונקציות ישירות ותנו למייבא לבחור import * as Utils from "./utils.js" אם הוא רוצה קידומת.
איפה עדיין פוגשים namespaces
קבצי הצהרות משתמשים בהם כדי לתאר ספריות שחושפות אובייקט גלובלי אחד, וכדי לקבץ טיפוסים:
// jquery-like.d.ts: a global function that also has properties
declare function $(selector: string): unknown;
declare namespace $ {
const version: string;
function ajax(url: string): Promise<unknown>;
}
חבילות טיפוסים משתמשות בהם כדי לחשוף טיפוסים שאפשר להרחיב. @types/node מצהיר על namespace NodeJS, והוספה ל-interface בשם ProcessEnv שלו מכל מודול דורשת declare global:
// env.d.ts
export {};
declare global {
namespace NodeJS {
interface ProcessEnv {
API_URL: string; // process.env.API_URL is now string, not string | undefined
}
}
}
גם interfaces בתוך namespaces שמתמזגים מתמזגים, וזה מה שגורם להרחבה הזו לעבוד.
Namespaces ו-type stripping
Node 24 מריץ קבצי .ts על ידי מחיקת תחביר הטיפוסים. namespace עם ערכים אי אפשר למחוק, צריך לקמפל אותו לאובייקט שמוצג למעלה, ולכן node app.ts נעצר עם:
SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript namespace declaration is not supported in strip-only mode
שני סוגים של namespace תקינים כי הם נעלמים לגמרי: declare namespace, ו-namespace שכל האיברים שלו הם טיפוסים או interfaces. אפשרות הקומפיילר erasableSyntaxOnly: true מדווחת על האחרים בזמן קומפילציה כשגיאה TS1294, This syntax is not allowed when 'erasableSyntaxOnly' is enabled., וכך פרויקטים שרצים עם type stripping מרחיקים אותם. node --experimental-transform-types כן מקמפל namespaces, עם אזהרה על תכונה ניסיונית.
שאלות נפוצות
האם להשתמש ב-namespaces או במודולים ב-TypeScript?
השתמשו במודולים (import ו-export) בקוד חדש. כל קובץ הוא כבר scope משלו, bundlers ו-Node מבינים מודולים, ואפשר להסיר exports שלא בשימוש. namespaces נשארים שימושיים בקבצי הצהרות, להרחבה גלובלית, ולהצמדת טיפוסים או פונקציות עזר לפונקציה או למחלקה באותו שם.
למה מתקמפל namespace ב-TypeScript?
לאובייקט שממולא על ידי פונקציה שמופעלת מיד: var Geometry; (function (Geometry) { Geometry.circle = circle; })(Geometry || (Geometry = {}));. איברים עם export הופכים למאפיינים של האובייקט הזה; איברים בלי export נשארים מקומיים לפונקציה.
מה ההבדל בין namespace ל-module ב-TypeScript?
מודול הוא קובץ עם import או export ברמה העליונה. namespace הוא בלוק עם שם בתוך קובץ. גרסאות ישנות של TypeScript קראו ל-namespaces "internal modules" ואפשרו module Foo {}; הכתיב הזה הוא היום שגיאה TS1540, ורק namespace Foo {} מתקבל.
האם Node יכול להריץ קבצי TypeScript שמשתמשים ב-namespaces?
לא עם ה-type stripping שמוגדר כברירת מחדל. namespace שמכיל ערכים מייצר ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX, כי stripping רק מוחק טיפוסים ו-namespace צריך קוד שנוצר. namespaces שמכילים רק טיפוסים, ו-declare namespace, נמחקים ורצים בלי בעיה.