Sử dụng một mô-đun nút chia sẻ cho các lớp phổ biến


15

Mục tiêu

Vì vậy, tôi đang có một dự án với cấu trúc này:

  • ứng dụng ion
  • chức năng hỏa lực
  • chia sẻ

Mục tiêu là để xác định các giao diện và các lớp phổ biến trong sharedmô-đun.

Những hạn chế

Tôi không muốn tải mã của mình lên npm để sử dụng cục bộ và không có ý định tải lên mã. Nó nên hoạt động 100% nhé.

Trong khi quá trình phát triển nên hoạt động ngoại tuyến, các mô-đun ionic-appfirebase-functionssẽ được triển khai đến căn cứ hỏa lực (lưu trữ & chức năng). Do đó, mã từ các sharedmô-đun nên có sẵn ở đó.

Những gì tôi đã cố gắng cho đến nay

  • Tôi đã thử sử dụng Project Reference trong bản thảo, nhưng tôi không thể làm việc được
  • Tôi đã thử nó với việc cài đặt nó như một mô-đun npm như trong câu trả lời thứ hai của câu hỏi này
    • Nó có vẻ hoạt động tốt lúc đầu, nhưng trong quá trình xây dựng, tôi gặp lỗi như thế này khi chạy firebase deploy:
Function failed on loading user code. Error message: Code in file lib/index.js can't be loaded.
Did you list all required modules in the package.json dependencies?
Detailed stack trace: Error: Cannot find module 'shared'
    at Function.Module._resolveFilename (module.js:548:15)
    at Function.Module._load (module.js:475:25)
    at Module.require (module.js:597:17)
    at require (internal/module.js:11:18)
    at Object.<anonymous> (/srv/lib/index.js:5:18)

Câu hỏi

Bạn có giải pháp nào để tạo một mô-đun chia sẻ bằng cách sử dụng cấu hình kiểu chữ hoặc NPM không?

Vui lòng không đánh dấu đây là bản sao → Tôi đã thử bất kỳ giải pháp nào tôi tìm thấy trên StackOverflow.

Thông tin bổ sung

Cấu hình để chia sẻ:

// package.json
{
  "name": "shared",
  "version": "1.0.0",
  "description": "",
  "main": "dist/src/index.js",
  "types": "dist/src/index.d.ts",
  "files": [
    "dist/src/**/*"
  ],
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "publishConfig": {
    "access": "private"
  }
}

// tsconfig.json
{
  "compilerOptions": {
    "module": "commonjs",
    "rootDir": ".",
    "sourceRoot": "src",
    "outDir": "dist",
    "sourceMap": true,
    "declaration": true,
    "target": "es2017"
  }
}

Cấu hình cho các chức năng:

// package.json
{
  "name": "functions",
  "scripts": {
    "lint": "tslint --project tsconfig.json",
    "build": "tsc",
    "serve": "npm run build && firebase serve --only functions",
    "shell": "npm run build && firebase functions:shell",
    "start": "npm run shell",
    "deploy": "firebase deploy --only functions",
    "logs": "firebase functions:log"
  },
  "engines": {
    "node": "8"
  },
  "main": "lib/index.js",
  "dependencies": {
    "firebase-admin": "^8.0.0",
    "firebase-functions": "^3.1.0",
    "shared": "file:../../shared"
  },
  "devDependencies": {
    "@types/braintree": "^2.20.0",
    "tslint": "^5.12.0",
    "typescript": "^3.2.2"
  },
  "private": true
}


// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": "./",
    "module": "commonjs",
    "noImplicitReturns": true,
    "noUnusedLocals": false,
    "rootDir": "src",
    "outDir": "lib",
    "sourceMap": true,
    "strict": true,
    "target": "es2017"
  }
}

Soution hiện tại

Tôi đã thêm một tập lệnh npm vào mô-đun chia sẻ, sao chép tất cả các tệp (không có index.js) sang các mô-đun khác. Điều này có vấn đề, rằng tôi kiểm tra mã trùng lặp vào SCM và tôi cần chạy lệnh đó trên mỗi thay đổi. Ngoài ra, IDE chỉ coi nó là các tệp khác nhau.

Câu trả lời:


4

Lời nói đầu: Tôi không quá quen thuộc với cách thức biên dịch typecript và cách package.jsonxác định trong một mô-đun như vậy. Giải pháp này, mặc dù nó hoạt động, có thể được coi là một cách khó khăn để đạt được nhiệm vụ trong tầm tay.

Giả sử cấu trúc thư mục sau:

project/
  ionic-app/
    package.json
  functions/
    src/
      index.ts
    lib/
      index.js
    package.json
  shared/
    src/
      shared.ts
    lib/
      shared.js
    package.json

Khi triển khai dịch vụ Firebase, bạn có thể đính kèm các lệnh vào các móc nối trước và sau triển khai . Điều này được thực hiện firebase.jsonthông qua các thuộc tính predeploypostdeploytrên dịch vụ mong muốn. Các thuộc tính này chứa một loạt các lệnh tuần tự để chạy trước và sau khi triển khai mã của bạn tương ứng. Hơn nữa, các lệnh này được gọi với các biến môi trường RESOURCE_DIR(đường dẫn thư mục của ./functionshoặc./ionic-app , tùy theo trường hợp nào) và PROJECT_DIR(đường dẫn thư mục chứa firebase.json).

Sử dụng predeploymảng cho functionsbên trong firebase.json, chúng ta có thể sao chép mã của thư viện dùng chung vào thư mục được triển khai vào đối tượng Cloud Function. Bằng cách này, bạn có thể chỉ cần bao gồm mã được chia sẻ như thể đó là một thư viện nằm trong thư mục con hoặc bạn có thể ánh xạ tên của nó bằng cách sử dụng ánh xạ đường dẫn của typecript trongtsconfig.json một mô-đun được đặt tên (để bạn có thể sử dụng import { hiThere } from 'shared';).

Các predeploynét móc (sử dụng toàn cầu cài đặt của shxđể tương thích Windows):

// firebase.json
{
  "functions": {
    "predeploy": [
      "shx rm -rf \"$RESOURCE_DIR/src/shared\"", // delete existing files
      "shx cp -R \"$PROJECT_DIR/shared/.\" \"$RESOURCE_DIR/src/shared\"", // copy latest version
      "npm --prefix \"$RESOURCE_DIR\" run lint", // lint & compile
      "npm --prefix \"$RESOURCE_DIR\" run build"
    ]
  },
  "hosting": {
    "public": "ionic-app",
    ...
  }
}

Liên kết nguồn bản thảo của thư viện đã sao chép với cấu hình trình biên dịch bản thảo:

// functions/tsconfig.json
{
  "compilerOptions": {
    ...,
    "baseUrl": "./src",
    "paths": {
      "shared": ["shared/src"]
    }
  },
  "include": [
    "src"
  ],
  ...
}

Liên kết tên mô-đun, "chia sẻ", với thư mục gói của thư viện đã sao chép.

// functions/package.json
{
  "name": "functions",
  "scripts": {
    ...
  },
  "engines": {
    "node": "8"
  },
  "main": "lib/index.js",
  "dependencies": {
    "firebase-admin": "^8.6.0",
    "firebase-functions": "^3.3.0",
    "shared": "file:./src/shared",
    ...
  },
  "devDependencies": {
    "tslint": "^5.12.0",
    "typescript": "^3.2.2",
    "firebase-functions-test": "^0.1.6"
  },
  "private": true
}

Cách tiếp cận tương tự có thể được sử dụng với thư mục lưu trữ.


Hy vọng rằng điều này truyền cảm hứng cho một người quen thuộc hơn với trình biên dịch Typecript để đưa ra một giải pháp sạch hơn sử dụng các hook này.


3

Bạn có thể muốn thử Lerna , một công cụ để quản lý các dự án JavaScript (và TypeScript) với nhiều gói.

Thiết lập

Giả sử rằng dự án của bạn có cấu trúc thư mục sau:

packages
  ionic-app
    package.json
  firebase-functions
    package.json
  shared
    package.json

Đảm bảo chỉ định cấp truy cập ( privateconfig/accesskhóa) chính xác trong tất cả các mô-đun bạn không muốn xuất bản, cũng như typingsmục trong sharedmô-đun của bạn :

Đã chia sẻ:

{
  "name": "shared",
  "version": "1.0.0",
  "private": true,
  "config": {
    "access": "private"
  },
  "main": "lib/index.js",
  "typings": "lib/index.d.ts",
  "scripts": {
    "compile": "tsc --project tsconfig.json"
  }
}

Ứng dụng ion:

{
  "name": "ionic-app",
  "version": "1.0.0",
  "private": true,
  "config": {
    "access": "private"
  },
  "main": "lib/index.js",
  "scripts": {
    "compile": "tsc --project tsconfig.json"
  },
  "dependencies": {
    "shared": "1.0.0"
  }
}

Với các thay đổi ở trên, bạn có thể tạo một cấp độ gốc package.jsonnơi bạn có thể chỉ định bất kỳ devDependenciesmô-đun nào bạn muốn tất cả các mô-đun dự án của bạn có quyền truy cập, chẳng hạn như khung thử nghiệm đơn vị, tslint, v.v.

packages
  ionic-app
    package.json
  firebase-functions
    package.json
  shared
    package.json
package.json         // root-level, same as the `packages` dir

Bạn cũng có thể sử dụng cấp độ gốc này package.jsonđể xác định các tập lệnh npm sẽ gọi các tập lệnh tương ứng trong các mô-đun dự án của bạn (thông qua lerna):

{
  "name": "my-project",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "compile": "lerna run compile --stream",
    "postinstall": "lerna bootstrap",
  },
  "devDependencies": {
    "lerna": "^3.18.4",
    "tslint": "^5.20.1",
    "typescript": "^3.7.2"
  },
}

Với vị trí đó, thêm tệp cấu hình lerna trong thư mục gốc của bạn:

packages
  ionic-app
    package.json
  firebase-functions
    package.json
  shared
    package.json
package.json
lerna.json

với nội dung sau:

{
  "lerna": "3.18.4",
  "loglevel": "info",
  "packages": [
    "packages/*"
  ],
  "version": "1.0.0"
}

Bây giờ khi bạn chạy npm installtrong thư mục gốc, postinstalltập lệnh được xác định trong cấp gốc của bạn package.jsonsẽ gọi lerna bootstrap.

Điều gì lerna bootstraplàm là nó sẽ liên kết sharedmô-đun của bạn với ionic-app/node_modules/sharedfirebase-functions/node_modules/shared, do đó, từ điểm của hai mô-đun đó, sharedtrông giống như bất kỳ mô-đun npm nào khác.

Biên soạn

Tất nhiên, liên kết các mô-đun là không đủ vì bạn vẫn cần phải biên dịch chúng từ TypeScript sang JavaScript.

Đó là nơi package.json compilekịch bản cấp gốc phát huy tác dụng.

Khi bạn chạy npm run compiletrong thư mục gốc của dự án, npm sẽ gọi lerna run compile --streamlerna run compile --streamgọi tập lệnh được gọi compiletrong mỗi package.jsontệp của mô-đun .

Vì mỗi mô-đun của bạn hiện có compiletập lệnh riêng , bạn có thể có một tsonfig.jsontệp cho mỗi mô-đun. Nếu bạn không thích sự trùng lặp, bạn có thể thoát khỏi một tsconfig cấp gốc hoặc kết hợp các tệp tsconfig cấp độ gốc và các tệp tsconfig cấp mô-đun kế thừa từ gốc.

Nếu bạn muốn xem cách thiết lập này hoạt động trong một dự án trong thế giới thực, hãy xem Serenity / JS nơi tôi đã sử dụng nó khá nhiều.

Triển khai

Điều tuyệt vời khi có sharedmô-đun được liên kết bên node_modulesdưới firebase-functionsionic-app, và devDepedenciesbên node_modulesdưới gốc dự án của bạn là nếu bạn cần triển khai mô-đun tiêu dùng ở bất cứ đâu ( ionic-appví dụ như vậy), bạn có thể chỉ cần nén nó cùng với nó node_modulesvà không phải lo lắng về nó phải loại bỏ các phụ tùng dev trước khi triển khai.

Hi vọng điêu nay co ich!

tháng một


Giới thiệu! Tôi chắc chắn sẽ kiểm tra nó và xem nếu điều này là phù hợp.
MauriceNino

2

Một giải pháp khả thi khác, nếu bạn đang sử dụng git để quản lý mã của mình, đó là sử dụng git submodule. Sử dụng git submodulebạn có thể bao gồm một kho git khác vào dự án của bạn.

Áp dụng cho trường hợp sử dụng của bạn:

  1. Đẩy phiên bản hiện tại của kho lưu trữ chia sẻ git của bạn
  2. Sử dụng git submodule add <shared-git-repository-link>bên trong (các) dự án chính của bạn để liên kết kho lưu trữ được chia sẻ.

Đây là một liên kết đến tài liệu: https://git-scm.com/docs/git-submodule


Nó thực sự không phải là một ý tưởng tồi, nhưng về cơ bản phát triển và thử nghiệm địa phương không còn phù hợp với phương pháp này.
MauriceNino

0

Nếu tôi hiểu chính xác vấn đề của bạn, giải pháp phức tạp hơn một câu trả lời duy nhất và nó một phần phụ thuộc vào sở thích của bạn.

Cách tiếp cận 1: Bản sao địa phương

Bạn có thể sử dụng Gulp để tự động hóa giải pháp làm việc mà bạn đã mô tả, nhưng IMO không dễ để duy trì và làm tăng đáng kể sự phức tạp nếu đến một lúc nào đó, một nhà phát triển khác bước vào.

Cách tiếp cận 2: Monorepo

Bạn có thể tạo một kho lưu trữ duy nhất chứa cả ba thư mục và kết nối chúng để chúng hoạt động như một dự án duy nhất. Như đã trả lời ở trên, bạn có thể sử dụng Lerna . Nó đòi hỏi một chút cấu hình, nhưng sau khi hoàn thành, các thư mục đó sẽ hoạt động như một dự án duy nhất.

Cách tiếp cận 3: Thành phần

Hãy coi mỗi một trong những thư mục này là một thành phần độc lập. Hãy xem Bit . Nó sẽ cho phép bạn thiết lập các thư mục dưới dạng các phần nhỏ hơn của một dự án lớn hơn và bạn có thể tạo một tài khoản riêng chỉ giới hạn các thành phần đó cho bạn. Khi được thiết lập ban đầu, nó sẽ cho phép bạn thậm chí áp dụng các bản cập nhật cho các thư mục riêng biệt và thư mục gốc sử dụng chúng sẽ tự động nhận được các bản cập nhật.

Cách tiếp cận 4: Gói

Bạn đặc biệt nói rằng bạn không muốn sử dụng npm, nhưng tôi muốn chia sẻ nó, bởi vì tôi hiện đang làm việc với một thiết lập như được mô tả dưới đây và đang làm một công việc hoàn hảo cho tôi:

  1. Sử dụng npmhoặcyarn để tạo một gói cho mỗi thư mục (bạn có thể tạo các gói có phạm vi cho cả hai để mã sẽ chỉ có sẵn cho bạn, nếu đây là mối quan tâm của bạn).
  2. Trong thư mục mẹ (sử dụng tất cả các thư mục này), các gói đã tạo được kết nối dưới dạng phụ thuộc.
  3. Tôi sử dụng webpack để bó tất cả các mã, sử dụng các bí danh đường dẫn webpack kết hợp với các đường dẫn bản thảo.

Hoạt động như một bùa mê và khi các gói được liên kết với nhau để phát triển cục bộ, nó hoạt động hoàn toàn ngoại tuyến và theo kinh nghiệm của tôi - mỗi thư mục có thể mở rộng riêng biệt và rất dễ bảo trì.

Ghi chú

Các gói 'con' đã được biên dịch sẵn trong trường hợp của tôi vì chúng khá lớn và tôi đã tạo các tscsigs riêng cho mỗi gói, nhưng điều tuyệt vời là bạn có thể thay đổi dễ dàng. Trước đây, tôi đã từng sử dụng bản thảo trong mô-đun và các tệp được biên dịch, và cả các tệp js thô, vì vậy toàn bộ điều này rất, rất linh hoạt.

Hi vọng điêu nay co ich

***** CẬP NHẬT **** Để tiếp tục ở điểm 4: Tôi xin lỗi, xấu của tôi. Có thể tôi đã hiểu sai vì theo như tôi biết, bạn không thể liên kết một mô-đun nếu nó không được tải lên. Tuy nhiên, đây là:

  1. Bạn có một mô-đun npm riêng, hãy sử dụng firebase-functionscho điều đó. Bạn biên dịch nó, hoặc sử dụng ts thô, tùy thuộc vào sở thích của bạn.
  2. Trong dự án cha mẹ của bạn thêm firebase-functionsnhư là một phụ thuộc.
  3. Trong tsconfig.json, thêm"paths": {"firebase-functions: ['node_modules/firebase-functions']"}
  4. Trong gói web - resolve: {extensions: ['ts', 'js'], alias: 'firebase-functions': }

Bằng cách này, bạn tham chiếu tất cả các chức năng được xuất của mình từ firebase-functionsmô-đun chỉ bằng cách sử dụng import { Something } from 'firebase-functions'. Webpack và TypeScript sẽ liên kết nó với thư mục mô-đun nút. Với cấu hình này, dự án mẹ sẽ không quan tâm nếu firebase-functionsmô-đun được viết bằng TypeScript hoặc vanilla javascript.

Sau khi thiết lập, nó sẽ hoạt động hoàn hảo cho sản xuất. Sau đó, để liên kết và làm việc ngoại tuyến:

  1. Điều hướng đến firebase-functionsdự án và viết npm link. Nó sẽ tạo một liên kết tượng trưng, ​​cục bộ vào máy của bạn và sẽ ánh xạ liên kết tên bạn đặt trong pack.json.
  2. Điều hướng đến dự án mẹ và viết npm link firebase-functions, điều này sẽ tạo liên kết tượng trưng và ánh xạ sự phụ thuộc của các hàm firebase vào thư mục mà bạn đã tạo.

Tôi nghĩ rằng bạn đã hiểu nhầm một cái gì đó. Tôi chưa bao giờ nói tôi không muốn sử dụng npm. Trong thực tế cả ba mô-đun đó là mô-đun nút. Tôi vừa nói rằng tôi không muốn tải các mô-đun của mình lên npm. Bạn có thể giải thích thêm về phần 4 đó một chút nữa không - nghe có vẻ khó hiểu phải không? có thể cung cấp một mẫu mã?
MauriceNino

Tôi sẽ thêm một câu trả lời khác, vì nó sẽ dài và không thể đọc được như một bình luận
Ivan Dzhurov

Cập nhật câu trả lời ban đầu của tôi, hy vọng nó rõ ràng hơn
Ivan Dzhurov

0

Tôi không muốn tải mã của mình lên npm để sử dụng cục bộ và không có ý định tải lên mã. Nó nên hoạt động 100% nhé.

Tất cả các mô-đun npm được cài đặt cục bộ và luôn hoạt động ngoại tuyến, nhưng nếu bạn không muốn xuất bản các gói của mình một cách công khai để mọi người có thể nhìn thấy nó, bạn có thể cài đặt đăng ký npm riêng.

ProGet là máy chủ kho lưu trữ riêng của NuGet / Npm có sẵn cho các cửa sổ mà bạn có thể sử dụng trong môi trường sản xuất / phát triển riêng để lưu trữ, truy cập và xuất bản các gói riêng tư của mình. Mặc dù nó là trên windows nhưng tôi chắc chắn có nhiều lựa chọn thay thế có sẵn trên linux.

  1. Git Submodules là một ý tưởng tồi, nó thực sự là một cách thức cũ để chia sẻ mã không được phiên bản giống như các gói, thay đổi và cam kết các mô hình con là nỗi đau thực sự.
  2. Thư mục nhập nguồn cũng là một ý tưởng tồi, một lần nữa phiên bản là vấn đề, bởi vì nếu ai đó sửa đổi thư mục phụ thuộc trong kho lưu trữ phụ thuộc, một lần nữa theo dõi nó là cơn ác mộng.
  3. Bất kỳ công cụ kịch bản nào của bên thứ ba để mô phỏng tách gói đều lãng phí thời gian vì npm đã cung cấp rất nhiều công cụ để quản lý các gói rất tốt.

Đây là kịch bản xây dựng / triển khai của chúng tôi.

  1. Mỗi gói riêng có .npmrcchứa registry=https://private-npm-repository.
  2. Chúng tôi xuất bản tất cả các gói riêng tư lên kho lưu trữ ProGet được lưu trữ riêng tư của chúng tôi.
  3. Mỗi gói riêng đều chứa các gói riêng phụ thuộc trên ProGet.
  4. Máy chủ xây dựng của chúng tôi truy cập ProGet thông qua xác thực npm do chúng tôi đặt. Không ai ngoài mạng của chúng tôi có quyền truy cập vào kho lưu trữ này.
  5. Máy chủ xây dựng của chúng tôi tạo gói npm bundled dependencieschứa tất cả các gói bên trong node_modulesvà máy chủ sản xuất không bao giờ cần truy cập các gói NPM hoặc NPM riêng vì tất cả các gói cần thiết đã được đóng gói.

Sử dụng kho lưu trữ npm riêng có nhiều lợi thế khác nhau,

  1. Không cần kịch bản tùy chỉnh
  2. Phù hợp với nút buid / xuất bản đường ống
  3. Mỗi gói npm riêng sẽ chứa liên kết trực tiếp đến kiểm soát nguồn git riêng của bạn, dễ dàng gỡ lỗi và điều tra lỗi trong tương lai
  4. Mỗi gói là một ảnh chụp nhanh chỉ đọc, do đó, một khi đã xuất bản không thể sửa đổi và trong khi bạn tạo các tính năng mới, cơ sở mã hiện tại với phiên bản cũ hơn của các gói phụ thuộc sẽ không bị ảnh hưởng.
  5. Bạn có thể dễ dàng đặt một số gói công khai và chuyển sang một số kho lưu trữ khác trong tương lai
  6. Nếu phần mềm nhà cung cấp npm riêng của bạn thay đổi, ví dụ: bạn quyết định chuyển mã của mình vào đám mây đăng ký gói npm riêng của nút, bạn sẽ không cần thực hiện bất kỳ thay đổi nào trong mã của mình.

Đây có thể là một giải pháp, nhưng thật không may cho tôi. Cảm ơn bạn đã dành thời gian của bạn!
MauriceNino

Ngoài ra còn có một kho lưu trữ npm cục bộ được cài đặt như một máy chủ nút nhỏ, verdaccio.org
Akash Kava

-1

Công cụ bạn đang tìm kiếm là npm link. npm linkcung cấp symlink cho gói npm cục bộ. Bằng cách đó, bạn có thể liên kết một gói và sử dụng nó trong dự án chính của mình mà không cần xuất bản nó đến thư viện gói npm.

Áp dụng cho trường hợp sử dụng của bạn:

  1. Sử dụng npm linkbên trong sharedgói của bạn . Điều này sẽ đặt đích symlink cho các lần cài đặt trong tương lai.
  2. Điều hướng đến (các) dự án chính của bạn. Trong functionsgói của bạn và sử dụng npm link sharedđể liên kết gói chia sẻ và thêm nó vào node_modulesthư mục.

Đây là một liên kết đến tài liệu: https://docs.npmjs.com/cli/link.html


Theo tôi biết, liên kết npm chỉ để thử nghiệm và không hoạt động nếu bạn muốn triển khai mã kết quả (ví dụ: các chức năng của tôi).
MauriceNino

Tôi thấy, có lẽ bạn nên thêm yêu cầu này vào câu hỏi của bạn.
Friedow

Nó đã được đề cập trong câu hỏi rồi, nhưng tôi sẽ làm rõ nó.
MauriceNino
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.