web-dev-qa-db-fra.com

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

Objectif

J'ai donc un projet avec cette structure:

  • ionic-app
  • fonctions firebase
  • partagé

Le but est de définir des interfaces et des classes communes dans le module shared.

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-app Et firebase-functions Vont être déployés sur firebase (hébergement et fonctions). Par conséquent, le code du module shared doit y être disponible.

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

  • J'ai essayé d'utiliser Références de projet dans TypeScript, mais je ne l'ai pas réussi à travailler
  • Je l'ai essayé en l'installant en tant que module npm comme dans la deuxième réponse de cette question
    • Cela semble bien fonctionner au début, mais pendant la construction, j'obtiens une erreur comme celle-ci lors de l'exécution de 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.

Informations supplémentaires

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 l'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. En outre, le IDE le traite simplement comme des fichiers différents.

15
MauriceNino

Préface: Je ne suis pas trop familier avec le fonctionnement de la compilation TypeScript et comment package.json dans un tel module doit être défini. 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 attacher des commandes aux hooks de pré-déploiement et post-déploiement . Cela se fait dans firebase.json via les propriétés predeploy et postdeploy sur 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 du répertoire de ./functions ou ./ionic-app, selon le cas) et PROJECT_DIR (le chemin du répertoire contenant firebase.json).

Utilisation du tableau predeploy pour functions dans 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 mappage de chemin de TypeScript in tsconfig.json à un module nommé (vous pouvez donc utiliser import { hiThere } from 'shared';).

La définition de hook predeploy (utilise l'installation globale de shx pour 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 TypeScript de la bibliothèque copiée à la configuration du compilateur de fonctions TypeScript:

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


4
samthecodingman

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 correct (private et config/access clés) dans tous les modules que vous ne souhaitez pas publier, ainsi que l'entrée typings dans votre module shared:

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

Une fois les modifications ci-dessus en place, vous pouvez créer un niveau racine package.json où vous pouvez spécifier tout devDependencies auquel vous souhaitez que tous vos modules de projet aient accès, comme votre framework 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.json pour 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 install dans le répertoire racine, le script postinstall défini dans votre niveau racine package.json invoquera lerna bootstrap.

Quelle lerna bootstrap fait, c'est qu'il va créer un lien symbolique entre votre module shared et ionic-app/node_modules/shared et firebase-functions/node_modules/shared, donc du point de vue de ces deux modules, shared ressemble à 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 niveau racine package.json Le script compile entre en jeu.

Lorsque vous exécutez npm run compile dans la racine de votre projet, npm invoquera lerna run compile --stream, et lerna run compile --stream invoque le script appelé compile dans chacun de vos modules package.json fichier.

Étant donné que chacun de vos modules possède désormais son propre script compile, vous devriez avoir un tsonfig.json fichier 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

Ce qui est bien, c'est que le module shared soit lié sous node_modules sous firebase-functions et ionic-app et votre devDepedencies sous node_modules sous la racine du projet est que si vous devez déployer le module consommateur n'importe où (donc le ionic-app par exemple), vous pouvez tout simplement zipper le tout avec son node_modules et ne vous inquiétez pas de devoir supprimer les dépendances de développement avant le déploiement.

J'espère que cela t'aides!

Jan

3
Jan Molak

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

Appliqué à votre cas d'utilisation:

  1. Poussez la version actuelle de votre référentiel partagé-git
  2. Utilisation git submodule add <shared-git-repository-link> à l'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

2
friedow

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 l'OMI 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 afin 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: packages

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 npm ou yarn pour 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 webpack en combinaison avec des chemins TypeScript.

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 TypeScript dans le module et les fichiers compilés, ainsi que les 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, la voici:

  1. Vous avez un module npm séparé, utilisons firebase-functions pour ça. Vous le compilez ou utilisez des ts bruts, selon vos préférences.
  2. Dans votre projet parent, ajoutez firebase-functions comme dépendance.
  3. Dans tsconfig.json, ajouter "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-functions module 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-functions le module est écrit en TypeScript ou javascript Vanilla.

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

  1. Aller vers firebase-functions projetez 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, qui créera le lien symbolique et mappera la dépendance des fonctions firebase au dossier dans lequel vous l'avez créé.
0
Ivan Dzhurov

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 package privé a .npmrc qui 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 dependencies qui contient tous les packages à l'intérieur de node_modules et 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à regroupés.

L'utilisation du dépôt npm privé 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.
0
Akash Kava