Objectif
J'ai donc un projet avec cette structure:
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
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.
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.
Vous voudrez peut-être essayer Lerna , un outil pour gérer des projets JavaScript (et TypeScript) avec plusieurs packages.
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.
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.
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
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:
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
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:
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).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:
firebase-functions
pour ça. Vous le compilez ou utilisez des ts bruts, selon vos préférences.firebase-functions
comme dépendance.tsconfig.json
, ajouter "paths": {"firebase-functions: ['node_modules/firebase-functions']"}
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:
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.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éé.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.
Voici notre scénario de construction/déploiement.
.npmrc
qui contient registry=https://private-npm-repository
.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,