Utilisation d'un module de noeud partagé pour les classes communes

15

Objectif

J'ai donc un projet avec cette structure:

  • ionic-app
  • fonctions de base de feu
  • partagé

L'objectif est de définir des interfaces et des classes communes dans le sharedmodule.

Restrictions

Je ne veux pas télécharger mon code sur npm pour l'utiliser localement et je n'envisage pas du tout de télécharger le code. Il devrait fonctionner à 100% hors ligne.

Alors que le processus de développement devrait fonctionner hors ligne, les modules ionic-appet firebase-functionsvont être déployés sur firebase (hébergement et fonctions). Par conséquent, le code du sharedmodule devrait y être disponible.

Ce que j'ai essayé jusqu'à présent

  • J'ai essayé d'utiliser les références de projet en dactylographie, mais je ne l'ai pas approché du travail
  • Je l'ai essayé en l'installant comme module npm comme dans la deuxième réponse de cette question
    • Cela semble bien fonctionner au début, mais pendant la génération, j'obtiens une erreur comme celle-ci lors de l'exécution 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)

Question

Avez-vous une solution pour créer un module partagé à l'aide de la configuration de scripts de saisie ou de NPM?

Veuillez ne pas marquer ceci comme un doublon → J'ai essayé n'importe quelle solution que j'ai trouvée sur StackOverflow.

Information additionnelle

Configuration pour partagé:

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

Configuration pour les fonctions:

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

J'ai ajouté un script npm au module partagé, qui copie tous les fichiers (sans index.js) dans les autres modules. Cela a le problème, que j'archive le code en double dans SCM, et que j'ai besoin d'exécuter cette commande à chaque changement. De plus, l'EDI le traite simplement comme des fichiers différents.

MauriceNino
la source

Réponses:

4

Préface: Je ne suis pas trop familier avec le fonctionnement de la compilation Typescript et comment package.jsondéfinir un tel module. Cette solution, bien qu'elle fonctionne, pourrait être considérée comme un moyen hacky d'accomplir la tâche à accomplir.

En supposant la structure de répertoires suivante:

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

Lors du déploiement d'un service Firebase, vous pouvez associer des commandes aux hooks de pré-déploiement et de post-déploiement . Cela se fait firebase.jsonvia les propriétés predeployet postdeploysur le service souhaité. Ces propriétés contiennent un tableau de commandes séquentielles à exécuter respectivement avant et après le déploiement de votre code. De plus, ces commandes sont appelées avec les variables d'environnement RESOURCE_DIR(le chemin de répertoire de ./functionsou ./ionic-app, selon le cas) et PROJECT_DIR(le chemin de répertoire contenantfirebase.json ).

En utilisant le predeploytableau pour l' functionsintérieur firebase.json, nous pouvons copier le code de la bibliothèque partagée dans le dossier qui est déployé sur l'instance Cloud Functions. En faisant cela, vous pouvez simplement inclure le code partagé comme s'il s'agissait d'une bibliothèque située dans un sous-dossier ou vous pouvez mapper son nom en utilisant le mappage de chemin de Typescript dans tsconfig.jsonun module nommé (afin que vous puissiez utiliserimport { hiThere } from 'shared'; ).

La predeploydéfinition du hook (utilise l'installation globale de shxpour la compatibilité 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",
    ...
  }
}

Liaison de la source de typographie de la bibliothèque copiée à la configuration du compilateur de typographie de fonctions:

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

Associer le nom du module, "partagé", au dossier de package de la bibliothèque copiée.

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

La même approche peut être utilisée avec le dossier d'hébergement.


J'espère que cela incitera quelqu'un qui est plus familier avec la compilation Typescript à trouver une solution plus propre qui utilise ces crochets.

samthecodingman
la source
3

Vous voudrez peut-être essayer Lerna , un outil pour gérer des projets JavaScript (et TypeScript) avec plusieurs packages.

Installer

En supposant que votre projet a la structure de répertoires suivante:

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

Assurez-vous de spécifier le niveau d'accès ( privateet les config/accessclés) corrects dans tous les modules que vous ne souhaitez pas publier, ainsi que l' typingsentrée dans votre sharedmodule:

Partagé:

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

Application ionique:

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

Avec les modifications ci-dessus en place, vous pouvez créer un niveau racine package.jsonoù vous pouvez spécifier devDependenciescelui auquel vous souhaitez que tous vos modules de projet aient accès, tels que votre cadre de test unitaire, tslint, etc.

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

Vous pouvez également utiliser ce niveau racine package.jsonpour définir des scripts npm qui invoqueront les scripts correspondants dans les modules de votre projet (via 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"
  },
}

Avec cela en place, ajoutez le fichier de configuration lerna dans votre répertoire racine:

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

avec le contenu suivant:

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

Maintenant, lorsque vous exécutez npm installdans le répertoire racine, le postinstallscript défini au niveau racine package.jsonsera invoqué lerna bootstrap.

Ce lerna bootstrapqui fait, c'est qu'il va créer un lien symbolique entre votre sharedmodule ionic-app/node_modules/sharedet firebase-functions/node_modules/shared, donc du point de vue de ces deux modules, il sharedressemble à n'importe quel autre module npm.

Compilation

Bien sûr, la liaison symbolique des modules n'est pas suffisante car vous devez toujours les compiler de TypeScript vers JavaScript.

C'est là que le package.json compilescript de niveau racine entre en jeu.

Lorsque vous exécutez npm run compiledans la racine de votre projet, npm invoquera lerna run compile --streamet lerna run compile --streaminvoquera le script appelé compiledans chacun des package.jsonfichiers de vos modules .

Étant donné que chacun de vos modules a maintenant son propre compilescript, vous devriez avoir un tsonfig.jsonfichier par module. Si vous n'aimez pas la duplication, vous pouvez vous en tirer avec un tsconfig au niveau racine, ou une combinaison d'un fichier tsconfig au niveau racine et de fichiers tsconfig au niveau du module héritant du fichier racine.

Si vous souhaitez voir comment cette configuration fonctionne sur un projet réel, jetez un œil à Serenity / JS où je l'ai utilisé assez largement.

Déploiement

La bonne chose à propos du sharedlien symbolique du module sous node_modulesunder firebase-functionset ionic-app, et votre racine de projet devDepedenciesunder node_modulesunder est que si vous avez besoin de déployer le module consommateur n'importe où (donc le ionic-apppar exemple), vous pouvez simplement le compresser avec son node_moduleset ne vous inquiétez pas avoir à supprimer les dépendances dev avant le déploiement.

J'espère que cela t'aides!

Jan

Jan Molak
la source
Intéressant! Je vais certainement le vérifier et regarder si c'est le bon ajustement.
MauriceNino
2

Une autre solution possible, si vous utilisez git pour gérer votre code, est d'utiliser git submodule. En utilisant, git submodulevous pouvez inclure un autre référentiel git dans votre projet.

Appliqué à votre cas d'utilisation:

  1. Poussez la version actuelle de votre référentiel partagé-git
  2. Utilisez à l' git submodule add <shared-git-repository-link>intérieur de vos projets principaux pour lier le référentiel partagé.

Voici un lien vers la documentation: https://git-scm.com/docs/git-submodule

friedow
la source
Ce n'est en fait pas une mauvaise idée, mais le développement local et les tests ont fondamentalement disparu avec cette approche.
MauriceNino
0

Si je comprends bien votre problème, la solution est plus complexe qu'une seule réponse et cela dépend en partie de vos préférences.

Approche 1: copies locales

Vous pouvez utiliser Gulp pour automatiser la solution de travail que vous avez déjà décrite, mais IMO n'est pas très facile à maintenir et augmente considérablement la complexité si à un moment donné un autre développeur entre en jeu.

Approche 2: Monorepo

Vous pouvez créer un référentiel unique contenant les trois dossiers et les connecter pour qu'ils se comportent comme un seul projet. Comme déjà répondu ci-dessus, vous pouvez utiliser Lerna . Cela nécessite un peu de configuration, mais une fois terminé, ces dossiers se comporteront comme un seul projet.

Approche 3: Composants

Traitez chacun de ces dossiers comme un composant autonome. Jetez un oeil à Bit . Il vous permettra de configurer les dossiers en tant que parties plus petites d'un projet plus important et vous pouvez créer un compte privé qui ne couvrira ces composants que pour vous. Une fois initialement configuré, il vous permettra même d'appliquer des mises à jour aux dossiers séparés et le parent qui les utilise recevra automatiquement les mises à jour.

Approche 4: Forfaits

Vous avez spécifiquement dit que vous ne vouliez pas utiliser npm, mais je veux le partager, car je travaille actuellement avec une configuration telle que décrite ci-dessous et fait un travail parfait pour moi:

  1. Utilisez npmou yarnpour créer un package pour chaque dossier (vous pouvez créer des packages de portée pour les deux afin que le code ne soit disponible que si cela vous concerne).
  2. Dans le dossier parent (qui utilise tous ces dossiers), les packages créés sont connectés en tant que dépendances.
  3. J'utilise webpack pour regrouper tout le code, en utilisant des alias de chemin de webpack en combinaison avec des chemins typographiques.

Fonctionne comme un charme et lorsque les packages sont liés par des liens symboliques pour le développement local, cela fonctionne entièrement hors ligne et d'après mon expérience - chaque dossier est évolutif séparément et très facile à entretenir.

Remarque

Les packages «enfants» sont déjà précompilés dans mon cas car ils sont assez gros et j'ai créé des tsconfigs séparés pour chaque package, mais la belle chose est que vous pouvez le changer facilement. Dans le passé, j'ai utilisé à la fois du tapuscrit dans le module et des fichiers compilés, ainsi que des fichiers js bruts, donc le tout est très, très polyvalent.

J'espère que cela t'aides

***** MISE À JOUR **** Pour continuer sur le point 4: je m'excuse, ma mauvaise. Peut-être que je me suis trompé parce que, pour autant que je sache, vous ne pouvez pas créer de lien symbolique pour un module s'il n'est pas téléchargé. Néanmoins, le voici:

  1. Vous avez un module npm séparé, utilisons-le firebase-functionspour cela. Vous le compilez ou utilisez des ts bruts, selon vos préférences.
  2. Dans votre projet parent, ajoutez en firebase-functionstant que dépendance.
  3. Dans tsconfig.json, ajoutez"paths": {"firebase-functions: ['node_modules/firebase-functions']"}
  4. Dans le webpack - resolve: {extensions: ['ts', 'js'], alias: 'firebase-functions': }

De cette façon, vous référencez toutes vos fonctions exportées à partir du firebase-functionsmodule simplement en utilisant import { Something } from 'firebase-functions'. Webpack et TypeScript le lieront au dossier des modules de noeud. Avec cette configuration, le projet parent ne se souciera pas si le firebase-functionsmodule est écrit en TypeScript ou javascript vanilla.

Une fois installé, il fonctionnera parfaitement pour la production. Ensuite, pour lier et travailler hors ligne:

  1. Accédez au firebase-functionsprojet et écrivez npm link. Il créera un lien symbolique local sur votre machine et mappera le lien que vous avez défini dans package.json.
  2. Accédez au projet parent et écrivez npm link firebase-functions, ce qui créera le lien symbolique et mappera la dépendance des fonctions firebase au dossier dans lequel vous l'avez créé.
Ivan Dzhurov
la source
Je pense que vous avez mal compris quelque chose. Je n'ai jamais dit que je ne voulais pas utiliser npm. En fait, ces trois modules sont tous des modules de nœuds. Je viens de dire que je ne veux pas télécharger mes modules sur npm. Pouvez-vous nous en dire un peu plus sur cette 4e partie - cela semble intéressant? peut-être fournir un exemple de code?
MauriceNino
J'ajouterai une autre réponse, car elle sera longue et illisible en tant que commentaire
Ivan Dzhurov
Mis à jour ma réponse initiale, j'espère que c'est plus clair
Ivan Dzhurov
0

Je ne veux pas télécharger mon code sur npm pour l'utiliser localement et je n'envisage pas du tout de télécharger le code. Il devrait fonctionner à 100% hors ligne.

Tous les modules npm sont installés localement et fonctionnent toujours hors ligne, mais si vous ne souhaitez pas publier vos packages publiquement pour que les gens puissent le voir, vous pouvez installer le registre npm privé.

ProGet est un serveur de référentiel privé NuGet / Npm disponible pour Windows que vous pouvez utiliser dans votre environnement de développement / production privé pour héberger, accéder et publier vos packages privés. Bien que ce soit sur Windows, mais je suis sûr qu'il existe différentes alternatives disponibles sur Linux.

  1. Git Sous-modules est une mauvaise idée, c'est vraiment une façon à l'ancienne de partager du code qui n'est pas versionné comme les packages, changer et valider des sous-modules est une vraie douleur.
  2. Le dossier d'importation source est également une mauvaise idée, encore une fois le contrôle de version est un problème, car si quelqu'un modifie le dossier dépendant dans le référentiel dépendant, le suivi à nouveau est un cauchemar.
  3. Tout outil scripté tiers pour émuler la séparation des packages est une perte de temps car npm fournit déjà une gamme d'outils pour gérer si bien les packages.

Voici notre scénario de construction / déploiement.

  1. Chaque paquet privé a .npmrcqui contient registry=https://private-npm-repository.
  2. Nous publions tous nos packages privés sur notre référentiel ProGet hébergé en privé.
  3. Chaque package privé contient des packages privés dépendants sur ProGet.
  4. Notre serveur de build accède à ProGet via l'authentification npm définie par nous. Personne en dehors de notre réseau n'a accès à ce référentiel.
  5. Notre serveur de génération crée un package npm avec bundled dependencieslequel contient tous les packages à l'intérieur node_moduleset le serveur de production n'a jamais besoin d'accéder aux packages NPM ou NPM privés car tous les packages nécessaires sont déjà fournis.

L'utilisation du référentiel privé npm présente divers avantages,

  1. Pas besoin de script personnalisé
  2. Convient au pipeline de création / publication de nœuds
  3. Chaque paquet privé npm contiendra un lien direct vers votre contrôle de source git privé, facile à déboguer et à rechercher les erreurs à l'avenir
  4. Chaque package est un instantané en lecture seule, donc une fois publié ne peut pas être modifié, et pendant que vous créez de nouvelles fonctionnalités, la base de code existante avec l'ancienne version des packages dépendants ne sera pas affectée.
  5. Vous pouvez facilement rendre certains packages publics et les déplacer vers un autre référentiel à l'avenir
  6. Si votre logiciel de fournisseur npm privé change, par exemple si vous décidez de déplacer votre code dans le nuage de registre du package npm privé du nœud, vous n'aurez pas besoin d'apporter de modifications à votre code.
Akash Kava
la source
C'est peut-être une solution, mais ce n'est malheureusement pas pour moi. Merci pour votre temps!
MauriceNino
Il existe également un référentiel npm local qui est installé en tant que petit serveur de nœuds, verdaccio.org
Akash Kava
-1

L'outil que vous recherchez est npm link. npm linkfournit des liens symboliques vers un package npm local. De cette façon, vous pouvez lier un package et l'utiliser dans votre projet principal sans le publier dans la bibliothèque de packages npm.

Appliqué à votre cas d'utilisation:

  1. Utilisez à l' npm linkintérieur de votre sharedcolis. Cela définira la destination du lien symbolique pour les futures installations.
  2. Accédez à vos projets principaux. À l'intérieur de votre functionspackage et utilisez npm link sharedpour lier le package partagé et l'ajouter au node_modulesrépertoire.

Voici un lien vers la documentation: https://docs.npmjs.com/cli/link.html

friedow
la source
Pour autant que je sache, le lien npm est uniquement destiné aux tests et ne fonctionne pas si vous souhaitez déployer le code résultant (par exemple mes fonctions).
MauriceNino
Je vois, vous devriez probablement ajouter cette exigence à votre question.
friedow
Il est déjà mentionné dans la question, mais je vais le clarifier.
MauriceNino