0
0
Lập trình
Flame Kris
Flame Krisbacodekiller

Quản lý kiểu TypeScript nội bộ: Giải pháp cho tổ chức lớn

Đăng vào 1 tháng trước

• 8 phút đọc

Quản lý kiểu TypeScript nội bộ: Giải pháp cho tổ chức lớn

TypeScript mang đến khả năng kiểm tra kiểu mạnh mẽ và chúng ta thường dựa vào DefinitelyTyped cho các kiểu thư viện công khai. Tuy nhiên, khi bạn tải các thư viện qua CDN hoặc làm việc trên nhiều dự án nội bộ mà không cung cấp kiểu riêng, mọi thứ bắt đầu gặp vấn đề. Mỗi dự án định nghĩa phiên bản của riêng mình, gây ra sự trùng lặp và TypeScript bắt đầu thông báo lỗi.

Trong các tổ chức lớn, điều này thường xảy ra: bạn muốn tất cả các dự án chia sẻ cùng một kiểu, nhưng thực tế, mỗi nhóm thường tự phát triển kiểu của riêng mình.

Giải pháp? Một kho lưu trữ kiểu TypeScript tập trung nội bộ.

Cách kiểu được truyền bá: NPM vs CDN

Khi bạn cài đặt một gói từ NPM mà đi kèm với kiểu của riêng nó, mọi thứ hoạt động ngay lập tức. Nhưng khi bạn sử dụng một thư viện qua CDN, TypeScript không thể suy diễn bất cứ điều gì. Bạn phải khai báo kiểu một cách thủ công, thường bằng cách mở rộng Window.

Khi đó, các vấn đề xuất hiện. Một nhóm khai báo một tập hợp các phương thức, nhóm khác khai báo các phương thức hơi khác. Khi kết hợp các dự án lại, TypeScript sẽ không biết định nghĩa nào cần tin cậy.

Việc tải một script qua CDN thay vì NPM phụ thuộc vào trường hợp sử dụng. Đôi khi script không tồn tại trên NPM, đôi khi bạn muốn ưu tiên một thứ tự thực thi cụ thể.

Vấn đề: kiểu bị xung đột hoặc thiếu

Lấy ví dụ về script sau được tải qua CDN, trong trường hợp này là Dailymotion Web SDK.

html Copy
<script defer src="https://geo.dailymotion.com/player.js"></script>

Nó xuất hiện một đối tượng toàn cục window.dailymotion. Trong TypeScript, bạn sẽ khai báo như sau theo yêu cầu của bạn:

typescript Copy
declare global {
  interface Window {
    dailymotion: {
      createPlayer: (
        selectorId: string,
        options: {
          player: string;
          video: string;
        }
      ) => Promise<{
        on: (event: string, callback: () => void) => void;
        play: () => void;
      }>;
      events: {
        PLAYER_START: string;
        PLAYER_END: string;
      };
    };
  }
}

Bây giờ hãy tưởng tượng hai dự án sử dụng Dailymotion SDK và không khai báo chính xác cùng một kiểu. Nếu bạn kết hợp hai dự án đó, sẽ xảy ra xung đột. Điều này thường không thể tránh khỏi vì mỗi bên viết phiên bản của riêng mình, có thể khác nhau.

Trong trường hợp có xung đột, lỗi TypeScript sau có thể xuất hiện:

error TS2717: Subsequent property declarations must have the same type.

Xem chi tiết về lỗi TS2717 trên TypeScript.tv.

Tập trung các kiểu: khái niệm

Giải pháp là tập trung tất cả các định nghĩa kiểu nội bộ và liên quan đến CDN trong một kho monorepo duy nhất. Hãy nghĩ về nó như một DefinitelyTyped riêng của bạn.

Các lợi ích là ngay lập tức:

  • Tính nhất quán: tất cả các dự án sử dụng cùng một định nghĩa
  • Tính tái sử dụng: xuất bản một lần, cài đặt ở mọi nơi từ kho lưu trữ riêng của bạn
  • An toàn kiểu: không còn kiểu không khớp giữa các dự án
  • Bảo trì: sửa một kiểu một lần, tất cả người dùng đều nhận được bản cập nhật

Tại Prisma Media, chúng tôi gọi monorepo của mình là Prime-Types 🤖

Triển khai tối thiểu

Cấu trúc monorepo tối thiểu có thể trông như sau:

json Copy
package.json  // định nghĩa các workspaces
types/
  dailymotion/
    package.json
    index.d.ts
    README.md

package.json gốc của monorepo:

json Copy
{
  "name": "types",
  "workspaces": ["./types/*"],
  "scripts": {
    "dev": "tsc --noEmit --watch"
  },
  "devDependencies": {
    "typescript": "5.8.3"
  }
}

Bên trong types/dailymotion/package.json:

json Copy
{
  "name": "@prime-types/dailymotion",
  "version": "1.0.0",
  "description": "TypeScript types for Dailymotion",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.d.ts"
    }
  },
  "main": "./index.d.ts",
  "types": "./index.d.ts",
  "devDependencies": {
    "typescript": "5.8.3"
  }
}

Ví dụ định nghĩa kiểu cho Dailymotion Web SDK trong index.d.ts:

typescript Copy
// /types/dailymotion/index.d.ts
declare global {
  interface Window {
    dailymotion: {
      createPlayer: (
        selectorId: string,
        options: {
          player: string;
          video: string;
        }
      ) => Promise<{
        on: (event: string, callback: () => void) => void;
        play: () => void;
      }>;
      events: {
        PLAYER_START: string;
        PLAYER_END: string;
      };
    };
  }
}

// Xuất khẩu để đảm bảo tệp được xử lý như một module
export {};

💡 Thêm export {} ở cuối tệp nếu nó không xuất khẩu bất kỳ kiểu nào hoặc chỉ có một declare global.

Để tiêu thụ các kiểu này trong một dự án, hãy cài đặt gói này trong peer-dependencies nếu có thể (xem dưới đây tại sao trong phần này).

bash Copy
npm install @prime-types/dailymotion

Mặc định, TypeScript tìm kiếm các khai báo trong đường dẫn node_modules/@types và trong mã nguồn dự án của bạn. Phần còn lại của node_modules sẽ bị bỏ qua. Do đó, bạn cần thêm đường dẫn đến các tệp khai báo trong tsconfig.json để cho phép TypeScript tham chiếu và truyền tải chúng.

json Copy
{
  "include": [
    "./src/**/*",
+   "./node_modules/@prime-types"
  ]
}

👀 Khám phá ví dụ này trong monorepo tối giản Prime Types trên GitHub.

Quản lý nhiều phiên bản: peer dependencies

Một cạm bẫy có thể xảy ra: nhiều phiên bản của cùng một gói kiểu.

Nếu thư viện A và thư viện B phụ thuộc vào các phiên bản khác nhau của @prime-types/dailymotion, có thể xuất hiện lỗi TypeScript.

Giải pháp là luôn khai báo gói kiểu là một peer dependency:

json Copy
{
  "name": "my-website",
  "peerDependencies": {
    "@prime-types/dailymotion": "1.0.0"
  }
}

Như vậy, chỉ một phiên bản có thể được cài đặt ở cấp độ cao nhất, và tất cả các dự án sẽ đồng bộ.

Giải pháp thay thế: một gói duy nhất

Đối với các nhóm nhỏ, bạn có thể xem xét một phiên bản đơn giản hóa: nhóm tất cả các kiểu thành một gói duy nhất.

Ưu điểm:

  • Dễ dàng thiết lập và triển khai
  • Cài đặt một gói và bạn xong

Nhược điểm:

  • Khó khăn trong việc phiên bản kiểu độc lập
  • Kích thước gói tăng nhanh khi bạn thêm kiểu khác

Một monorepo với nhiều gói là khả thi hơn về lâu dài.

Kết luận

DefinitelyTyped là lý tưởng cho mã nguồn mở, nhưng các dự án nội bộ đôi khi cần giải pháp riêng của mình. Tập trung các kiểu TypeScript mang lại nhiều lợi ích:

  • Loại bỏ sự trùng lặp và xung đột
  • Cải thiện tính an toàn kiểu giữa các dự án
  • Cung cấp một nguồn thông tin duy nhất

Ví dụ hoàn chỉnh có sẵn trên GitHub, vì vậy bạn có thể thử nghiệm với nó! 🧑‍💻

Chúng tôi chưa thảo luận về việc triển khai trong bài viết này, nhưng tôi sẽ chi tiết quy trình triển khai của chúng tôi với GitLab trong một bài viết sau.

Thực hành tốt nhất

  • Luôn sử dụng peer dependencies để tránh xung đột kiểu.
  • Tạo một bộ công cụ để tự động kiểm tra và cập nhật kiểu khi có thay đổi.

Cạm bẫy phổ biến

  • Không khai báo đúng kiểu khi dùng CDN có thể dẫn đến xung đột.
  • Quản lý nhiều phiên bản kiểu không đúng cách có thể gây ra lỗi TypeScript.

Mẹo hiệu suất

  • Giảm thiểu kích thước gói kiểu bằng cách chỉ định các kiểu cần thiết.
  • Sử dụng caching để tăng tốc độ tải kiểu từ kho lưu trữ.

Khắc phục sự cố

  • Kiểm tra đường dẫn trong tsconfig.json nếu kiểu không được nhận diện.
  • Đảm bảo rằng các kiểu được khai báo đúng trong tất cả các dự án sử dụng.

Câu hỏi thường gặp

  • Tôi có thể sử dụng kiểu từ một gói khác không?
    Có, nhưng hãy đảm bảo rằng các kiểu đó tương thích với gói bạn đang sử dụng.
  • Làm thế nào để cập nhật kiểu?
    Chỉ cần sửa đổi định nghĩa trong monorepo và phát hành phiên bản mới.

So sánh với các giải pháp khác

Tính năng Monorepo Gói đơn
Tính nhất quán Không
Khả năng mở rộng Cao Thấp
Dễ thiết lập Thấp Cao
Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào