איך לשתף קוד בין פרויקטים של TypeScript?


נניח שיש לי שני פרויקטים עם מבנה קבצים הבא

/my-projects/

  /project-a/
    lib.ts
    app.ts
    tsconfig.json

  /project-b/
    app.ts         // import '../project-a/lib.ts'
    tsconfig.json

אני רוצה לצרוך את lib.ts שנמצא ב-project-a גם מתוך project-b. איך עושים את זה?

  • שחרר את זה כ-מודול NPM - ממש לא רוצים את זה, זה יותר מדי עבור מקרה שימוש כל כך פשוט. אני רק רוצה לשתף קובץ אחד בין שני פרויקטים.

  • השתמש ב-import '../project-a/lib.ts' - זה לא עובד, TypeScript מתלונן

'lib.ts' is not under 'rootDir'. 'rootDir' is expected to contain all source files.
  • שים את tsconfig.json רמה אחת למעלה כך שהוא יכלול גם את project-a וגם את project-b - אי אפשר לעשות את זה, קונפיגורציית TypeScript קצת שונה עבור הפרויקטים האלה. גם זה לא מאוד נוח, לא רוצים לעשות את זה.

יש דרכים אחרות?

תשובה #1

מאז TypeScript 3.0 אפשר לעשות זאת עם הפניות לפרויקטים.

מסמכי TypeScript: https://www.typescriptlang.org/docs/handbook/project-references.html

אני מאמין שתצטרך להעביר את lib.ts לפרויקט TypeScript קטן שנקרא משהו כמו 'lib'

לפרויקט הספרייה צריך להיות קובץ tsconfig הכולל:

// lib/tsconfig.json
    {
          "compilerOptions": {
            /* Truncated compiler options to list only relevant options */
            "declaration": true, 
            "declarationMap": true,
            "rootDir": ".",   
            "composite": true,     
          },
          "references": []  // * Any project that is referenced must itself have a `references` array (which may be empty).
        }

אז בשני הפרויקטים, project-a ו-project-b, הוסף את ההפניה לפרויקט הספריה הזו בתוך tsconfig שלך

// project-a/ts-config.json
// project-b/ts-config.json
{
        "compilerOptions": {
            "target": "es5", 
            "module": "es2015",
            "moduleResolution": "node"
            // ...
        },
        "references": [
            { 
                "path": "../lib",
                // add 'prepend' if you want to include the referenced project in your output file
                "prepend": true, 
            }
        ]
    }

בפרויקט הספרייה. צור קובץ index.ts שייצא את כל הקוד שלך שאתה רוצה לשתף עם פרויקטים אחרים.

// lib/index.ts    
export * from 'lib.ts';

עכשיו, נניח ש-lib/lib.ts נראה כך:

// lib/lib.ts
export const log = (message: string) => console.log(message);

אתה יכול עכשיו לייבא את הפונקציה log מ-lib/lib.ts גם ב-project-a וגם ב-project-b

// project-a/app.ts 
// project-b/app.ts
import { log } from '../lib';
log("This is a message");

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

tsc -b 

שזה ייבנה תחילה את ההפניות של הפרויקט שלך (lib במקרה זה) ואז יבנה את הפרויקט הנוכחי (project-a או project-b).

מהמחשב הקומפיילר של TypeScript לא יסתכל על קבצי ה-TypeScript בפועל מהספרייה. במקום זאת הוא ישתמש רק בקבצי ההצהרה של TypeScript (*.d.ts) שנוצרו בעת בניית פרויקט הספרייה.

זו הסיבה שקובץ lib/tsconfig.json שלך חייב להכיל:

"declaration": true,

עם זאת, אם תנווט להגדרת הפונקציה log בקובץ project-a/app.ts באמצעות מקש F12 בקוד Visual Studio, יופיע בפניך קובץ TypeScript הנכון. לפחות, אם הגדרת נכון את lib/tsconfig.json שלך עם:

"declarationMap": true,

יצרתי מאגר GitHub קטן המציג את הדוגמה הזו של הפניות פרויקט עם TypeScript:

https://github.com/thdk/TS3-projects-references-example

תשובה #2

ניתן להשיג זאת באמצעות השימוש בתכונת 'paths' של 'CompilerOptions' בקובץ tsconfig.json

{
  "compilerOptions": {
    "paths": {
      "@otherProject/*": [
        "../otherProject/src/*"
      ]
    }
  },
}

להלן צילום מסך של מבנה התיקיות.

להלן תוכן הקובץ tsconfig.json שמפנה לפרויקט TypeScript אחר

{
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./tsc-out",
    "sourceMap": false,
    "declaration": false,
    "moduleResolution": "node",
    "module": "es6",
    "target": "es5",
    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "es2017",
      "dom"
    ],
    "paths": {
      "@src/*": [ "src/*" ],
      "@qc/*": [
        "../Pti.Web/ClientApp/src/app/*"
      ]
    }
  },
  "exclude": [
    "node_modules",
    "dist",
    "tsc-out"
  ]
}

להלן הצהרת ייבוא כדי להפנות ליצוא ממיזם אחר.

import { IntegrationMessageState } from '@qc/shared/states/integration-message.state';

תשובה #3

אני חושב ש-@qqilihq בדרך הנכונה עם התגובה שלהם - למרות שיש את הבעיות הפוטנציאליות שצוינו בנוגע לניהול ידני של תוכן ספריית node_modules.

היה לי קצת מזל בניהול זה על ידי שימוש ב-lerna (למרות שיש מספר כלים דומים אחרים שם, לדוגמה yarn workspaces נראים קצת דומים, למרות שמעולם לא השתמשתי בהם בעצמי).

אני רק אומר מראש שזה עשוי להיות קצת כבד מדי למה שאתה מדבר עליו, אבל זה נותן לפרויקט שלך הרבה גמישות לגדול בעתיד.

עם התבנית הזו, הקוד שלך ייראה בסופו של דבר משהו כזה:

/my-projects/
    /common-code/
        lib.ts
        tsconfig.json
        package.json
    /project-a/
        app.ts (Can import common-code)
        tsconfig.json
        package.json (with a dependency on common-code)
    /project-b/
        app.ts (Can import common-code)
        tsconfig.json
        package.json (with a dependency on common-code)

התיאוריה הכללית כאן היא שהכלי יוצר קישורים סימבוליים בין הספריות הפנימיות שלך לבין התיקיות node_modules של החבילות התלויות בהם.

המכשולים העיקריים שנתקלתי בהם בביצוע זאת הם

  • common-code חייב לכלול גם את המאפיין main וגם את המאפיין types מוגדר בקובץ package.json שלו
  • common-code צריך להיות מקומפל לפני שכל אחת מהתלויות שלו תוכל להסתמך עליו
  • common-code צריך שיהיה לו declaration מוגדר ל-true בקובץ tsconfig.json שלו

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