Korzystanie z modułu węzła współdzielonego dla wspólnych klas

15

Cel

Mam więc projekt o tej strukturze:

  • aplikacja jonowa
  • funkcje bazy ogniowej
  • udostępnione

Celem jest zdefiniowanie wspólnych interfejsów i klas w sharedmodule.

Ograniczenia

Nie chcę przesyłać mojego kodu do npm, aby używać go lokalnie i w ogóle nie planuję przesyłać kodu. Powinien w 100% działać w trybie offline.

Podczas gdy proces rozwoju powinien pracować w trybie offline, ionic-appa firebase-functionsmoduły zostaną wdrożone do Firebase (hosting i funkcje). Dlatego kod z sharedmodułu powinien być tam dostępny.

Co próbowałem do tej pory

  • Próbowałem używać referencji projektu w maszynopisie, ale nie zbliżyłem się do niego
  • Próbowałem go, instalując go jako moduł npm, jak w drugiej odpowiedzi na to pytanie
    • Początkowo wydaje się, że działa dobrze, ale podczas kompilacji pojawia się taki błąd podczas uruchamiania 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)

Pytanie

Czy masz rozwiązanie do tworzenia modułu współdzielonego przy użyciu konfiguracji maszynopisu lub NPM?

Nie zaznaczaj tego jako duplikatu → Wypróbowałem każde rozwiązanie, które znalazłem na StackOverflow.

Dodatkowe informacje

Konfiguracja dla udostępnionego:

// 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"
  }
}

Konfiguracja funkcji:

// 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"
  }
}

Aktualne południe

Dodałem skrypt npm do współdzielonego modułu, który kopiuje wszystkie pliki (bez index.js) do innych modułów. Problem polega na tym, że sprawdzam zduplikowany kod w SCM i muszę uruchamiać to polecenie przy każdej zmianie. Ponadto IDE traktuje to jak różne pliki.

MauriceNino
źródło

Odpowiedzi:

4

Przedmowa: Nie znam się zbyt dobrze na tym, jak działa kompilacja maszynopisu i jak package.jsonw takim module powinien zostać zdefiniowany. To rozwiązanie, choć działa, można uznać za hacking sposób na wykonanie zadania.

Zakładając następującą strukturę katalogów:

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

Wdrażając usługę Firebase , możesz dołączać polecenia do haków wstępnego i późniejszego wdrożenia . Odbywa się to za firebase.jsonpośrednictwem właściwości predeployi postdeployżądanej usługi. Te właściwości zawierają tablicę sekwencji poleceń uruchamianych odpowiednio przed i po wdrożeniu kodu. Ponadto te polecenia są wywoływane ze zmiennymi środowiskowymi RESOURCE_DIR(ścieżka katalogu ./functionslub ./ionic-app, w zależności od tego, co ma zastosowanie) i PROJECT_DIR(ścieżka zawierająca katalog firebase.json).

Za pomocą predeploytablicy do functionswewnątrz firebase.jsonmożemy skopiować kod biblioteki współużytkowanej do folderu, który jest wdrożony w wystąpieniu Cloud Functions. W ten sposób można po prostu zawierać udostępniony kod tak, jakby to była biblioteka znajduje się w podkatalogu lub można odwzorować jego nazwę za pomocą mapowania ścieżki maszynopis jest w tsconfig.jsondo określonego modułu (dzięki czemu można używać import { hiThere } from 'shared';).

predeployDefinicja hak (oparta na globalnej instalację shxpod kątem zgodności z systemem 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",
    ...
  }
}

Łączenie źródła maszynopisu skopiowanej biblioteki ze konfiguracją kompilatora maszynopisu funkcji:

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

Skojarzenie nazwy modułu „udostępnionego” z folderem skopiowanej biblioteki.

// 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
}

To samo podejście można zastosować do folderu hostingowego.


Mam nadzieję, że zainspiruje to kogoś, kto jest bardziej zaznajomiony z kompilacją Typescript, do opracowania czystszego rozwiązania, które korzysta z tych haków.

samthecodingman
źródło
3

Możesz wypróbować Lerna , narzędzie do zarządzania projektami JavaScript (i TypeScript) z wieloma pakietami.

Ustawiać

Zakładając, że twój projekt ma następującą strukturę katalogów:

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

Upewnij się, że podałeś prawidłowy poziom dostępu ( privatei config/accessklucze) we wszystkich modułach, których nie chcesz publikować, a także typingspozycję w sharedmodule:

Udostępniono:

{
  "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"
  }
}

Aplikacja jonowa:

{
  "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"
  }
}

Po wprowadzeniu powyższych zmian możesz utworzyć poziom główny, w package.jsonktórym możesz określić dowolne devDependencies, do których wszystkie moduły projektu mają mieć dostęp, takie jak struktura testów jednostkowych, tslint itp.

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

Możesz także użyć tego poziomu głównego package.jsondo zdefiniowania skryptów npm, które będą wywoływały odpowiednie skrypty w modułach twojego projektu (przez 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"
  },
}

Mając to na miejscu, dodaj plik konfiguracyjny lerna do katalogu głównego:

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

o następującej treści:

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

Teraz, gdy uruchomisz npm installw katalogu głównym, postinstallskrypt zdefiniowany na poziomie głównym package.jsonuruchomi się lerna bootstrap.

Co lerna bootstraprobi jest, że będzie to podlinkowujemy swój sharedmoduł ionic-app/node_modules/sharedi firebase-functions/node_modules/sharedtak z punktu widzenia tych dwóch modułów, sharedwygląda jak każdy inny moduł npm.

Kompilacja

Oczywiście symlinkowanie modułów nie wystarczy, ponieważ nadal musisz je skompilować z TypeScript na JavaScript.

W tym momencie package.json compilepojawia się skrypt na poziomie root .

Po uruchomieniu npm run compilew katalogu głównym projektu, npm wywoła lerna run compile --streami wywoła lerna run compile --streamskrypt wywołany compilew package.jsonpliku każdego modułu .

Ponieważ każdy moduł ma teraz własny compileskrypt, powinieneś mieć tsonfig.jsonplik na moduł. Jeśli nie podoba ci się powielanie, możesz uciec z tsconfig na poziomie root lub kombinacją plików tsconfig na poziomie root i tsconfig na poziomie modułu dziedziczących z pliku root.

Jeśli chcesz zobaczyć, jak ta konfiguracja działa w projekcie z prawdziwego świata, spójrz na Serenity / JS, gdzie dość często go używałem.

Rozlokowanie

Dobrą rzeczą jest sharedsymlinkowanie modułu node_modulespod firebase-functionsi pod ionic-app, a twój devDepedenciesunder node_modulesunder root projektu jest taki, że jeśli musisz wdrożyć moduł klienta w dowolnym miejscu ( ionic-appna przykład), możesz po prostu spakować go razem node_modulesi nie martwić się o konieczność usunięcia zależności programistycznych przed wdrożeniem.

Mam nadzieję że to pomoże!

Jan

Jan Molak
źródło
Interesujące! Na pewno to sprawdzę i sprawdzę, czy jest to właściwe dopasowanie.
MauriceNino,
2

Innym możliwym rozwiązaniem jest użycie git do zarządzania kodem git submodule. Korzystając z niego, git submodulemożesz dołączyć do swojego projektu kolejne repozytorium git.

Dotyczy twojego przypadku użycia:

  1. Wciśnij aktualną wersję swojego współdzielonego repozytorium git
  2. Użyj git submodule add <shared-git-repository-link>wewnątrz głównego projektu (projektów), aby połączyć repozytorium wspólne.

Oto link do dokumentacji: https://git-scm.com/docs/git-submodule

Friedow
źródło
W rzeczywistości nie jest to zły pomysł, ale lokalne podejście do programowania i testowania zasadniczo przestało działać.
MauriceNino,
0

Jeśli dobrze rozumiem twój problem, rozwiązanie jest bardziej złożone niż pojedyncza odpowiedź i częściowo zależy od twoich preferencji.

Podejście 1: Lokalne kopie

Możesz użyć Gulp do zautomatyzowania działającego rozwiązania, które już opisałeś, ale IMO nie jest bardzo łatwe w utrzymaniu i drastycznie zwiększa złożoność, jeśli w pewnym momencie pojawi się inny programista.

Podejście 2: Monorepo

Możesz utworzyć pojedyncze repozytorium, które zawiera wszystkie trzy foldery i połączyć je tak, aby zachowywały się jak jeden projekt. Jak już wspomniano powyżej, możesz użyć Lerny . Wymaga to trochę konfiguracji, ale kiedy to zrobisz, te foldery będą zachowywać się jak pojedynczy projekt.

Podejście 3: Komponenty

Traktuj każdy z tych folderów jako samodzielny komponent. Spójrz na Bit . Pozwoli ci to skonfigurować foldery jako mniejsze części większego projektu i możesz utworzyć konto prywatne, które będzie obejmowało tylko te elementy. Po wstępnej konfiguracji pozwoli ci nawet zastosować aktualizacje do oddzielnych folderów, a nadrzędny, który ich używa, automatycznie pobierze aktualizacje.

Podejście 4: Pakiety

W szczególności powiedziałeś, że nie chcesz używać npm, ale chcę się nim podzielić, ponieważ obecnie pracuję z zestawem, jak opisano poniżej i wykonuję dla mnie idealną robotę:

  1. Użyj npmlub, yarnaby utworzyć pakiet dla każdego folderu (możesz utworzyć pakiety o określonym zakresie dla obu z nich, aby kod był dostępny tylko dla ciebie, jeśli jest to twoja sprawa).
  2. W folderze nadrzędnym (który wykorzystuje wszystkie te foldery) utworzone pakiety są połączone jako zależności.
  3. Używam webpack, aby spakować cały kod, używając aliasów ścieżek webpack w połączeniu ze ścieżkami maszynopisu.

Działa jak urok, a kiedy pakiety są dowiązane do lokalnego rozwoju, działa całkowicie offline i z mojego doświadczenia - każdy folder jest skalowalny osobno i bardzo łatwy w utrzymaniu.

Uwaga

Pakiety „potomne” są już w moim przypadku wstępnie skompilowane, ponieważ są dość duże i stworzyłem osobne tsconfig dla każdego pakietu, ale piękną rzeczą jest to, że można to łatwo zmienić. W przeszłości używałem zarówno maszynopisu w module, jak i plików skompilowanych, a także nieprzetworzonych plików js, więc całość jest bardzo, bardzo wszechstronna.

Mam nadzieję że to pomoże

***** AKTUALIZACJA **** Aby kontynuować w punkcie 4: przepraszam, mój zły. Może pomyliłem się, ponieważ o ile mi wiadomo, nie można dowiązać modułu do modułu, jeśli nie został przesłany. Niemniej jednak oto:

  1. Masz osobny moduł npm, użyjmy firebase-functionsdo tego. Kompilujesz go lub używasz surowych ts, w zależności od twoich preferencji.
  2. W projekcie nadrzędnym dodaj firebase-functionsjako zależność.
  3. W tsconfig.jsondodaj"paths": {"firebase-functions: ['node_modules/firebase-functions']"}
  4. W pakiecie internetowym - resolve: {extensions: ['ts', 'js'], alias: 'firebase-functions': }

W ten sposób odwołujesz się do wszystkich eksportowanych funkcji z firebase-functionsmodułu po prostu za pomocą import { Something } from 'firebase-functions'. Webpack i TypeScript połączą go z folderem modułów węzłów. W tej konfiguracji projekt nadrzędny nie będzie dbał o to, czy firebase-functionsmoduł zostanie napisany w języku JavaScript lub javascript.

Po skonfigurowaniu będzie działał idealnie do produkcji. Następnie, aby połączyć i pracować w trybie offline:

  1. Przejdź do firebase-functionsprojektu i napisz npm link. Stworzy dowiązanie symboliczne, lokalne dla twojego komputera i zamapuje łącze na nazwę, którą ustawiłeś w package.json.
  2. Przejdź do projektu nadrzędnego i napisz npm link firebase-functions, który utworzy dowiązanie symboliczne i zamapuje zależność funkcji firebase od folderu, w którym go utworzyłeś.
Ivan Dzhurov
źródło
Myślę, że coś źle zrozumiałeś. Nigdy nie powiedziałem, że nie chcę używać npm. W rzeczywistości wszystkie trzy z tych modułów są modułami węzłów. Powiedziałem właśnie, że nie chcę wgrywać moich modułów na npm. Czy możesz bardziej szczegółowo rozwinąć tę czwartą część - brzmi to interesująco? może podać próbkę kodu?
MauriceNino,
Dodam inną odpowiedź, ponieważ będzie ona długa i nieczytelna jako komentarz
Iwan Dzhurov
Zaktualizowałem moją wstępną odpowiedź, mam nadzieję, że jest jaśniejsza
Ivan Dzhurov
0

Nie chcę przesyłać mojego kodu do npm, aby używać go lokalnie i w ogóle nie planuję przesyłać kodu. Powinien w 100% działać w trybie offline.

Wszystkie moduły npm są instalowane lokalnie i zawsze działają w trybie offline, ale jeśli nie chcesz publikować swoich pakietów publicznie, aby ludzie mogli je zobaczyć, możesz zainstalować prywatny rejestr npm.

ProGet to serwer prywatnego repozytorium NuGet / Npm dostępny dla systemu Windows, którego można używać w prywatnym środowisku programistycznym / produkcyjnym do hostowania, uzyskiwania dostępu i publikowania prywatnych pakietów. Chociaż jest w systemie Windows, ale jestem pewien, że istnieją różne alternatywy dostępne dla systemu Linux.

  1. Git Submodules to zły pomysł, to naprawdę stara moda na dzielenie się kodem, który nie jest wersjonowany jak pakiety, zmienianie i zatwierdzanie submodułów jest prawdziwym bólem.
  2. Źródłowy folder importu jest również złym pomysłem, ponownie problem z wersjonowaniem, ponieważ jeśli ktoś zmodyfikuje zależny folder w zależnym repozytorium, ponowne śledzenie go to koszmar.
  3. Wszelkie narzędzia skryptowe innych firm do emulacji separacji pakietów to strata czasu, ponieważ npm już teraz oferuje szereg narzędzi do tak dobrego zarządzania pakietami.

Oto nasz scenariusz kompilacji / wdrożenia.

  1. Każdy pakiet prywatny zawiera to, .npmrcco zawiera registry=https://private-npm-repository.
  2. Publikujemy wszystkie nasze prywatne pakiety w naszym prywatnym repozytorium ProGet.
  3. Każdy pakiet prywatny zawiera zależne pakiety prywatne w ProGet.
  4. Nasz serwer kompilacji uzyskuje dostęp do ProGet poprzez ustawione przez nas uwierzytelnianie npm. Nikt spoza naszej sieci nie ma dostępu do tego repozytorium.
  5. Nasz serwer kompilacji tworzy pakiet npm, bundled dependenciesktóry zawiera wszystkie pakiety wewnątrz, node_modulesa serwer produkcyjny nigdy nie musi uzyskiwać dostępu do NPM ani prywatnych pakietów NPM, ponieważ wszystkie niezbędne pakiety są już zawarte w pakiecie.

Korzystanie z prywatnego repozytorium npm ma różne zalety,

  1. Nie potrzeba niestandardowego skryptu
  2. Pasuje do potoku buid / publikowania węzła
  3. Każdy prywatny pakiet npm będzie zawierał bezpośredni link do twojej prywatnej kontroli źródła git, łatwy do debugowania i badania błędów w przyszłości
  4. Każdy pakiet jest tylko do odczytu migawką, więc po opublikowaniu nie można go modyfikować, a podczas tworzenia nowych funkcji nie będzie to miało wpływu na istniejącą bazę kodu ze starszą wersją pakietów zależnych.
  5. Możesz łatwo upublicznić niektóre pakiety i przenieść się do innego repozytorium w przyszłości
  6. Jeśli zmieni się oprogramowanie twojego prywatnego dostawcy npm, na przykład zdecydujesz się przenieść swój kod do prywatnej chmury rejestru pakietu npm węzła, nie będziesz musiał dokonywać żadnych zmian w kodzie.
Akash Kava
źródło
To może być rozwiązanie, ale niestety nie jest dla mnie. Dziękuję za poświęcony czas!
MauriceNino,
Istnieje również lokalne repozytorium npm, które jest zainstalowane jako serwer małego węzła, verdaccio.org
Akash Kava,
-1

Narzędzie, którego szukasz, to npm link. npm linkdostarcza dowiązania symboliczne do lokalnego pakietu npm. W ten sposób możesz połączyć pakiet i używać go w głównym projekcie bez publikowania go w bibliotece pakietów npm.

Dotyczy twojego przypadku użycia:

  1. Użyj npm linkw sharedpakiecie. Ustawi to miejsce docelowe dowiązania symbolicznego dla przyszłych instalacji.
  2. Przejdź do głównego projektu (projektów). Wewnątrz functionspakietu i użyj go, npm link sharedaby połączyć udostępniony pakiet i dodać go do node_moduleskatalogu.

Oto link do dokumentacji: https://docs.npmjs.com/cli/link.html

Friedow
źródło
O ile mi wiadomo, link npm służy tylko do testowania i nie działa, jeśli chcesz wdrożyć wynikowy kod (na przykład moje funkcje).
MauriceNino,
Rozumiem, prawdopodobnie powinieneś dodać ten wymóg do swojego pytania.
friedow
Zostało to już wspomniane w pytaniu, ale wyjaśnię to.
MauriceNino,