Verwenden eines gemeinsam genutzten Knotenmoduls für allgemeine Klassen

15

Tor

Ich habe also ein Projekt mit dieser Struktur:

  • ionic-app
  • Firebase-Funktionen
  • geteilt

Ziel ist es, gemeinsame Schnittstellen und Klassen im sharedModul zu definieren .

Beschränkungen

Ich möchte meinen Code nicht auf npm hochladen, um ihn lokal zu verwenden, und habe nicht vor, den Code überhaupt hochzuladen. Es sollte zu 100% offline funktionieren.

Während der Entwicklungsprozess offline funktionieren sollte, werden die Module ionic-appund firebase-functionsin der Firebase (Hosting & Funktionen) bereitgestellt. Daher sollte der Code aus dem sharedModul dort verfügbar sein.

Was ich bisher versucht habe

  • Ich habe versucht, Projektreferenzen in Typoskript zu verwenden, aber ich habe es nicht annähernd zum Laufen gebracht
  • Ich habe es mit der Installation als npm-Modul versucht, wie in der zweiten Antwort auf diese Frage
    • Es scheint zunächst gut zu funktionieren, aber während des Builds erhalte ich beim Ausführen firebase deployfolgende Fehlermeldung :
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)

Frage

Haben Sie eine Lösung für die Erstellung eines gemeinsam genutzten Moduls mit Typescripts Config oder NPM?

Bitte markieren Sie dies nicht als Duplikat → Ich habe eine Lösung ausprobiert, die ich in StackOverflow gefunden habe.

Zusätzliche Information

Konfiguration für gemeinsam genutzte:

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

Konfiguration für Funktionen:

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

Aktuelle Lösung

Ich habe dem freigegebenen Modul ein npm-Skript hinzugefügt, das alle Dateien (ohne index.js) in die anderen Module kopiert. Dies hat das Problem, dass ich doppelten Code in SCM einchecke und diesen Befehl bei jeder Änderung ausführen muss. Außerdem behandelt die IDE sie nur als unterschiedliche Dateien.

MauriceNino
quelle

Antworten:

4

Vorwort: Ich bin nicht allzu vertraut damit, wie die Typescript-Kompilierung funktioniert und wie package.jsonin einem solchen Modul definiert werden sollte. Obwohl diese Lösung funktioniert, kann sie als hackiger Weg zur Erreichung der vorliegenden Aufgabe angesehen werden.

Angenommen, die folgende Verzeichnisstruktur:

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

Wenn Sie einen Firebase-Dienst bereitstellen , können Sie Befehle an die Hooks vor und nach der Bereitstellung anhängen . Dies erfolgt firebase.jsonüber die Eigenschaften predeployund postdeployüber den gewünschten Service. Diese Eigenschaften enthalten eine Reihe von sequentiellen Befehlen, die vor bzw. nach der Bereitstellung Ihres Codes ausgeführt werden. Darüber hinaus werden diese Befehle mit den Umgebungsvariablen RESOURCE_DIR(dem Verzeichnispfad von ./functionsoder ./ionic-app, je nachdem, was zutreffend ist) und PROJECT_DIR(dem Verzeichnispfad, der enthält ) aufgerufenfirebase.json ) .

Mit dem predeployArray for functionsinside firebase.jsonkönnen wir den Code der gemeinsam genutzten Bibliothek in den Ordner kopieren, der für die Cloud Functions-Instanz bereitgestellt wird. Auf diese Weise können Sie einfach schließen den gemeinsamen Code , als ob es eine Bibliothek in einem Unterordner waren oder Sie können es Namen mit Karte Typoskript der Pfadzuordnung in tsconfig.jsoneinem benannten Modul (so Sie verwenden können ,import { hiThere } from 'shared'; ).

Die predeployHook-Definition (verwendet die globale Installation von shxfür Windows-Kompatibilität):

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

Verknüpfen der Typoskriptquelle der kopierten Bibliothek mit der Funktion Typskript-Compiler-Konfiguration:

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

Verknüpfen des Modulnamens "freigegeben" mit dem Paketordner der kopierten Bibliothek.

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

Der gleiche Ansatz kann mit dem Hosting-Ordner verwendet werden.


Hoffentlich inspiriert dies jemanden, der mit der Typescript-Kompilierung besser vertraut ist, eine sauberere Lösung zu finden, die diese Hooks verwendet.

samthecodingman
quelle
3

Vielleicht möchten Sie Lerna ausprobieren , ein Tool zum Verwalten von JavaScript- (und TypeScript-) Projekten mit mehreren Paketen.

Installieren

Angenommen, Ihr Projekt hat die folgende Verzeichnisstruktur:

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

Stellen Sie sicher, dass Sie in allen Modulen, die Sie nicht veröffentlichen möchten , die richtige Zugriffsebene ( privateund config/accessSchlüssel) sowie den typingsEintrag in Ihrem sharedModul angeben :

Geteilt:

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

Ionen-App:

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

Mit den oben genannten Änderungen können Sie eine Stammebene erstellen , in der Sie alle package.jsonangeben können devDependencies, auf die alle Ihre Projektmodule Zugriff haben sollen, z. B. Ihr Unit-Test-Framework, tslint usw.

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

Sie können diese Stammebene auch verwenden package.json, um npm-Skripte zu definieren, die die entsprechenden Skripte in den Modulen Ihres Projekts aufrufen (über 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"
  },
}

Fügen Sie anschließend die lerna-Konfigurationsdatei in Ihr Stammverzeichnis ein:

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

mit folgenden Inhalten:

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

Wenn Sie jetzt npm installim Stammverzeichnis ausgeführt werden, wird das postinstallin Ihrer Stammebene definierte Skript package.jsonaufgerufen lerna bootstrap.

Was lerna bootstrapbedeutet, ist, dass es Ihr sharedModul mit einem Symlink verbindet ionic-app/node_modules/sharedund firebase-functions/node_modules/sharedvom Standpunkt dieser beiden Module sharedaus wie jedes andere npm-Modul aussieht.

Zusammenstellung

Das Symlinking der Module reicht natürlich nicht aus, da Sie sie noch von TypeScript nach JavaScript kompilieren müssen.

Hier kommt das Root-Level- package.json compileSkript ins Spiel.

Wenn Sie npm run compilein Ihrem Projektstamm ausführen lerna run compile --stream, lerna run compile --streamruft npm das Skript auf, das compilein jeder package.jsonDatei Ihres Moduls aufgerufen wird .

Da jedes Ihrer Module jetzt ein eigenes compileSkript hat, sollten Sie eine tsonfig.jsonDatei pro Modul haben können. Wenn Ihnen die Duplizierung nicht gefällt, können Sie eine tsconfig auf Stammebene oder eine Kombination aus tsconfig auf Stammebene und tsconfig-Dateien auf Modulebene verwenden, die von der Root-Datei erben.

Wenn Sie sehen möchten, wie dieses Setup in einem realen Projekt funktioniert, schauen Sie sich Serenity / JS an, wo ich es ziemlich häufig verwendet habe.

Einsatz

Die nette Sache über das mit sharedModul Symlink unter node_modulesunter firebase-functionsund ionic-app, und Sie devDepedenciesunter node_modulesunter Projektwurzel ist , dass , wenn Sie die Verbraucher an beliebige Stelle bereitstellen müssen (so die ionic-appzum Beispiel), können Sie zip einfach alles zusammen mit seinem node_modulesund nicht zu kümmern Die Entwickler müssen vor der Bereitstellung entfernt werden.

Hoffe das hilft!

Jan.

Jan Molak
quelle
Interessant! Ich werde es auf jeden Fall überprüfen und prüfen, ob dies die richtige Passform ist.
MauriceNino
2

Eine andere mögliche Lösung, wenn Sie git zum Verwalten Ihres Codes verwenden, ist die Verwendung von git submodule. Mit können git submoduleSie ein weiteres Git-Repository in Ihr Projekt aufnehmen.

Auf Ihren Anwendungsfall angewendet:

  1. Push die aktuelle Version Ihres Shared-Git-Repository
  2. Verwenden git submodule add <shared-git-repository-link>Sie diese Option in Ihren Hauptprojekten, um das freigegebene Repository zu verknüpfen.

Hier ist ein Link zur Dokumentation: https://git-scm.com/docs/git-submodule

Friedow
quelle
Es ist eigentlich keine schlechte Idee, aber die lokale Entwicklung und Erprobung ist mit diesem Ansatz im Grunde genommen weg.
MauriceNino
0

Wenn ich Ihr Problem richtig verstehe, ist die Lösung komplexer als eine einzelne Antwort und hängt teilweise von Ihrer Präferenz ab.

Ansatz 1: Lokale Kopien

Sie können Gulp verwenden , um die bereits beschriebene Arbeitslösung zu automatisieren. IMO ist jedoch nicht sehr einfach zu warten und erhöht die Komplexität drastisch, wenn irgendwann ein anderer Entwickler hinzukommt.

Ansatz 2: Monorepo

Sie können ein einzelnes Repository erstellen, das alle drei Ordner enthält, und sie so verbinden, dass sie sich wie ein einzelnes Projekt verhalten. Wie oben bereits beantwortet, können Sie Lerna verwenden . Es erfordert eine gewisse Konfiguration, aber sobald dies erledigt ist, verhalten sich diese Ordner wie ein einzelnes Projekt.

Ansatz 3: Komponenten

Behandeln Sie jeden dieser Ordner als eigenständige Komponente. Schauen Sie sich Bit an . Sie können die Ordner als kleinere Teile eines größeren Projekts einrichten und ein privates Konto erstellen, das diese Komponenten nur für Sie bereitstellt. Nach der erstmaligen Einrichtung können Sie sogar Aktualisierungen auf die separaten Ordner anwenden, und der übergeordnete Ordner, der sie verwendet, erhält die Aktualisierungen automatisch.

Ansatz 4: Pakete

Sie haben ausdrücklich gesagt, dass Sie npm nicht verwenden möchten, aber ich möchte es teilen, da ich derzeit mit einem Setup wie unten beschrieben arbeite und einen perfekten Job für mich mache:

  1. Verwenden Sie npmoder yarn, um ein Paket für jeden Ordner zu erstellen (Sie können Pakete mit Gültigkeitsbereich für beide erstellen, damit der Code nur dann für Sie verfügbar ist, wenn dies Ihr Anliegen ist).
  2. Im übergeordneten Ordner (der alle diese Ordner verwendet) sind die erstellten Pakete als Abhängigkeiten verbunden.
  3. Ich verwende Webpack, um den gesamten Code zu bündeln, und verwende Webpack-Pfad-Aliase in Kombination mit Typoskript-Pfaden.

Funktioniert wie ein Zauber und wenn die Pakete für die lokale Entwicklung verknüpft sind, funktioniert es vollständig offline und meiner Erfahrung nach - jeder Ordner ist separat skalierbar und sehr einfach zu warten.

Hinweis

Die 'untergeordneten' Pakete sind in meinem Fall bereits vorkompiliert, da sie ziemlich groß sind und ich für jedes Paket separate tsconfigs erstellt habe, aber das Schöne ist, dass Sie es leicht ändern können. In der Vergangenheit habe ich sowohl Typoskript im Modul als auch kompilierte Dateien und auch rohe JS-Dateien verwendet, so dass das Ganze sehr, sehr vielseitig ist.

Hoffe das hilft

***** UPDATE **** Um mit Punkt 4 fortzufahren: Ich entschuldige mich, mein schlechtes. Vielleicht habe ich es falsch verstanden, weil Sie, soweit ich weiß, ein Modul nicht symlinken können, wenn es nicht hochgeladen wird. Trotzdem ist es hier:

  1. Sie haben ein separates npm-Modul, das wir dafür verwenden firebase-functions. Sie kompilieren es oder verwenden rohe ts, je nach Ihren Vorlieben.
  2. In Ihrem übergeordneten Projekt firebase-functionsals Abhängigkeit hinzufügen .
  3. In tsconfig.json, Add"paths": {"firebase-functions: ['node_modules/firebase-functions']"}
  4. Im Webpack - resolve: {extensions: ['ts', 'js'], alias: 'firebase-functions': }

Auf diese Weise referenzieren Sie alle aus dem firebase-functionsModul exportierten Funktionen einfach mit import { Something } from 'firebase-functions'. Webpack und TypeScript verknüpfen es mit dem Ordner der Knotenmodule. Bei dieser Konfiguration ist es dem übergeordneten Projekt egal, ob das firebase-functionsModul in TypeScript oder Vanille-Javascript geschrieben ist.

Einmal eingerichtet, funktioniert es perfekt für die Produktion. Dann, um zu verknüpfen und offline zu arbeiten:

  1. Navigieren Sie zu firebase-functionsProjekt und schreiben Sie npm link. Es wird ein Symlink erstellt, der lokal für Ihren Computer ist, und der Link wird dem Namen zugeordnet, den Sie in package.json festgelegt haben.
  2. Navigieren Sie zum übergeordneten Projekt und schreiben Sie npm link firebase-functions, wodurch der Symlink erstellt und die Abhängigkeit der Firebase-Funktionen dem Ordner zugeordnet wird, in dem Sie ihn erstellt haben.
Ivan Dzhurov
quelle
Ich denke du hast etwas falsch verstanden. Ich habe nie gesagt, dass ich npm nicht verwenden möchte. Tatsächlich sind alle drei dieser Module Knotenmodule. Ich habe gerade gesagt, dass ich meine Module nicht auf npm hochladen möchte. Können Sie diesen 4. Teil etwas näher erläutern - das klingt interessant? Vielleicht ein Codebeispiel bereitstellen?
MauriceNino
Ich werde eine weitere Antwort hinzufügen, da sie als Kommentar lang und unlesbar sein wird
Ivan Dzhurov,
Aktualisiert meine erste Antwort, hoffe es ist klarer
Ivan Dzhurov
0

Ich möchte meinen Code nicht auf npm hochladen, um ihn lokal zu verwenden, und habe nicht vor, den Code überhaupt hochzuladen. Es sollte zu 100% offline funktionieren.

Alle npm-Module werden lokal installiert und funktionieren immer offline. Wenn Sie Ihre Pakete jedoch nicht öffentlich veröffentlichen möchten, damit die Benutzer sie sehen können, können Sie die private npm-Registrierung installieren.

ProGet ist ein für Windows verfügbarer privater NuGet / Npm-Repository-Server, den Sie in Ihrer privaten Entwicklungs- / Produktionsumgebung zum Hosten, Zugreifen auf und Veröffentlichen Ihrer privaten Pakete verwenden können. Es ist zwar unter Windows, aber ich bin sicher, dass es unter Linux verschiedene Alternativen gibt.

  1. Git-Submodule sind eine schlechte Idee, es ist wirklich eine altmodische Art, Code zu teilen, der nicht wie Pakete versioniert ist. Das Ändern und Festschreiben von Submodulen ist ein echtes Problem.
  2. Der Quellimportordner ist ebenfalls eine schlechte Idee. Auch hier ist die Versionierung ein Problem, denn wenn jemand einen abhängigen Ordner im abhängigen Repository ändert, ist die erneute Verfolgung ein Albtraum.
  3. Alle Skript-Tools von Drittanbietern zur Emulation der Pakettrennung sind Zeitverschwendung, da npm bereits eine Reihe von Tools zur Verfügung stellt, mit denen Pakete so gut verwaltet werden können.

Hier ist unser Build / Deployment-Szenario.

  1. Jedes private Paket hat .npmrcwas enthält registry=https://private-npm-repository.
  2. Wir veröffentlichen alle unsere privaten Pakete in unserem privat gehosteten ProGet-Repository.
  3. Jedes private Paket enthält abhängige private Pakete von ProGet.
  4. Unser Build-Server greift über die von uns festgelegte npm-Authentifizierung auf ProGet zu. Niemand außerhalb unseres Netzwerks hat Zugriff auf dieses Repository.
  5. Unser Build-Server erstellt ein npm-Paket, bundled dependenciesdas alle darin enthaltenen Pakete enthält, node_modulesund der Produktionsserver muss niemals auf NPM- oder private NPM-Pakete zugreifen, da alle erforderlichen Pakete bereits gebündelt sind.

Die Verwendung eines privaten npm-Repositorys bietet verschiedene Vorteile:

  1. Kein benutzerdefiniertes Skript erforderlich
  2. Passt in die Buid / Publish-Pipeline des Knotens
  3. Jedes private npm-Paket enthält einen direkten Link zu Ihrer privaten Git-Quellcodeverwaltung, der in Zukunft leicht zu debuggen und Fehler zu untersuchen ist
  4. Jedes Paket ist ein schreibgeschützter Snapshot. Sobald es veröffentlicht wurde, kann es nicht mehr geändert werden. Während Sie neue Funktionen erstellen, ist die vorhandene Codebasis mit älteren Versionen abhängiger Pakete nicht betroffen.
  5. Sie können einige Pakete problemlos veröffentlichen und in Zukunft in ein anderes Repository verschieben
  6. Wenn sich die Software Ihres privaten npm-Anbieters ändert, z. B. wenn Sie Ihren Code in die Registrierungswolke des privaten npm-Pakets des Knotens verschieben möchten, müssen Sie keine Änderungen an Ihrem Code vornehmen.
Akash Kava
quelle
Dies mag eine Lösung sein, ist aber leider nichts für mich. Vielen Dank für Ihre Zeit!
MauriceNino
Es gibt auch ein lokales npm-Repository, das als kleiner Knotenserver
Akash Kava
-1

Das Werkzeug, das Sie suchen, ist npm link. npm linkbietet Symlinks zu einem lokalen npm-Paket. Auf diese Weise können Sie ein Paket verknüpfen und in Ihrem Hauptprojekt verwenden, ohne es in der npm-Paketbibliothek zu veröffentlichen.

Auf Ihren Anwendungsfall angewendet:

  1. Verwenden Sie npm linkin Ihrem sharedPaket. Dadurch wird das Symlink-Ziel für zukünftige Installationen festgelegt.
  2. Navigieren Sie zu Ihren Hauptprojekten. In Ihrem functionsPaket und verwenden Sie npm link shared, um das freigegebene Paket zu verknüpfen und es dem node_modulesVerzeichnis hinzuzufügen .

Hier ist ein Link zur Dokumentation: https://docs.npmjs.com/cli/link.html

Friedow
quelle
Soweit ich weiß, dient der npm-Link nur zum Testen und funktioniert nicht, wenn Sie den resultierenden Code bereitstellen möchten (zum Beispiel meine Funktionen).
MauriceNino
Ich verstehe, Sie sollten diese Anforderung wahrscheinlich zu Ihrer Frage hinzufügen.
Friedow
Es wird bereits in der Frage erwähnt, aber ich werde es klarstellen.
MauriceNino