Firebase-tools: Prise en charge des mono-repos dans le déploiement

Créé le 31 janv. 2018  ·  47Commentaires  ·  Source: firebase/firebase-tools

Voir : https://github.com/firebase/firebase-functions/issues/172

Informations sur les versions

3.17.3

Étapes à reproduire

Comportement prévisible

Comportement réel

feature request

Commentaire le plus utile

+1 à ce sujet, les fonctions cloud auront généralement besoin de partager du code commun (par exemple des interfaces) avec d'autres applications et un bon moyen de gérer cela est un monorepo (par exemple lerna) ou l'utilisation directe de liens symboliques. J'ai pris ce dernier et résolu en créant des scripts. Le concept est assez simple : je copie ce qu'il faut dans le répertoire des fonctions et je le supprime après

Voici comment j'ai procédé avec cette structure de répertoires :
```

  • racine/
    | - .firebaserc
    | - firebase.json
    | - ...
  • les fonctions/
    | - src/
    | - package.json
    | - pre-deploy.js
    | - post-deploy.js
    | - ....
  • partagé/
    | - src/
    | - package.json
    | - ....
content of `pre-deploy.js`

const fs = require("fs-extra");

const packageJsonPath = "./package.json";
const packageJson = require(packageJsonPath);

(async () => {
attendre fs.remove( ./shared );
wait fs.copy( ../shared , ./shared );

packageJson.dependencies["@project/shared"] = "file:./shared";

await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));

})();

content of `post-deploy.js`

const packageJsonPath = "./package.json";
const packageJson = require(packageJsonPath);
const fs = require("fs-extra");

(async () => {
attendre fs.remove( ./shared );

packageJson.dependencies["@project/shared"] = "file:../shared";

await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));

})();

Then update `firebase.json` like this (add the build script if you need, I build before in my pipeline)

"les fonctions": {
"source": "fonctions",
"prédéployer": [
"npm --prefix "$RESOURCE_DIR" exécute le pré-déploiement"
],
"postdéploiement": [
"npm --prefix "$RESOURCE_DIR" s'exécute après le déploiement"
]
},
```

Si vous faites la construction, à l'intérieur du répertoire dist ou lib , vous devriez maintenant avoir deux frères : les fonctions et partagé (cela est arrivé à cause de la dépendance partagée). Assurez-vous de mettre à jour les fonctions package.json main pour pointer vers lib/functions/src/index.js pour que le déploiement fonctionne.

Pour l'instant, c'est résolu, mais c'est une solution de contournement, pas une solution. Je pense que les outils firebase devraient vraiment prendre en charge les liens symboliques

Tous les 47 commentaires

Cela semble fonctionner dans ma configuration, c'est- deploy dire que node_modules , même si package.json pour cela se trouve sous l'espace api/ travail functions/ ). Y a-t-il autre chose qui doit être réparé ici?

EDIT : De plus, je copie package.json dans api/dist pour être utilisé par deploy .

// firebase.json
  ...
  "functions": {
    "source": "api/dist"
  },
  ...

Ainsi, 2 niveaux d'imbrication résolvent toujours la racine node_modules avec succès.

@dinvlad pourriez-vous partager un dépôt ?

@orouz malheureusement pas encore, c'est en source fermée pour l'instant.

Quelqu'un a-t-il réussi à résoudre ce problème ? Le partage d'un projet d'exemple simple serait très utile.

@audkar Actuellement, j'utilise simplement lerna.js.org et c'est la commande run pour exécuter un script npm dans chaque sous-dossier avec cette structure de dossiers :

- service1/
|  - .firebaserc
|  - firebase.json
- service2/
|  - .firebaserc
|  - firebase.json
- app1/
|  - .firebaserc
|  - firebase.json
- app2/
|  - .firebaserc
|  - firebase.json
- firestore/
|  - firestore.rules
|  - firestore.indexes.json
- etc...

Veiller à ce que les fichiers firebase.json de chaque service ne se heurtent pas les uns les autres est laissé à l'appréciation de l'utilisateur. Des conventions simples d'utilisation de groupes de fonctions et de ciblage de noms multi-sites signifient que cela est résolu pour les fonctions Cloud et l'hébergement. Nous n'avons toujours pas de solution pour les règles Firestore/GCS, bien que les diviser ne soit peut-être pas idéal...

discuté ici précédemment - https://github.com/firebase/firebase-tools/issues/1116

@jthegedus merci pour votre réponse. Mais je pense que l'émission de ce billet est différente. J'essaie d'utiliser des espaces de travail en laine. Et il semble que les outils Firebase ne récupèrent pas les dépendances de liens symboliques lors du téléchargement de fonctions

Ah d'accord, j'ai moi-même évité ce terrier de lapin

Pourriez-vous préciser quel est le problème ? Comme mentionné ci-dessus, j'utilise simplement du fil nu avec des espaces app travail api et app , et je les construis en utilisant yarn workspace api build && yarn workspace app build (avec build script
1) compiler le code TS avec outDir en api/dist et app/dist respectivement
2) copier les fichiers package.json dans les répertoires dist
3) copiez yarn.lock du dossier _root_, dans les répertoires dist

Ensuite, je lance simplement yarn firebase deploy partir du dossier _root_, et il récupère à la fois api/dist et app/dist sans aucun problème. Mon firebase.json ressemble

  "functions": {
    "source": "api/dist"
  },
  "hosting": {
    "public": "app/dist",

Malheureusement, je ne peux toujours pas partager le code complet, mais cette configuration est tout ce qui compte, autant que je sache.

De plus, je peux me tromper, mais je pense que le script firebase deploy n'utilise pas réellement votre répertoire node_modules . Je pense qu'il récupère simplement le code, package.json , et yarn.lock des répertoires dist , et fait le reste.

C'est vrai. La valeur par défaut de "functions.ignore" dans firebase.json est
["node_modules"] donc il n'est pas téléchargé. Je crois que tu peux passer outre
mais si vous voulez expédier des modules locaux.

Le lun. 17 juin 2019, 18:58 Denis Loginov [email protected]
a écrit:

De plus, je me trompe peut-être, mais je pense que le script de déploiement de firebase ne fonctionne pas
utilisez réellement votre répertoire node_modules. Je pense qu'il reprend juste le
cod, package.json et fil.lock à partir des répertoires dist, et fait le
du repos.

-
Vous recevez ceci parce que vous êtes abonné à ce fil.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/firebase/firebase-tools/issues/653?email_source=notifications&email_token=ACATB2U73VS2KIILUVRFFB3P3A6NPA5CNFSM4EOR24GKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVQ29Z1502DXDNGO
ou couper le fil
https://github.com/notifications/unsubscribe-auth/ACATB2U3Q2TLVBICRJ3B5OLP3A6NPANCNFSM4EOR24GA
.

@dinvlad Oui, cela nécessite le package.json et le fichier de verrouillage que vous utilisez pour l'installation de deps dans le cloud post-déploiement.

Je crois que le scénario initialement décrit dans l'autre problème utilisait un package partagé dans l'espace de travail et certains problèmes avec le levage de portée. Comme je n'utilisais pas le fil de cette façon, je ne peux que spéculer à partir de ce que j'ai lu là-bas.

@samtstern @jthegedus merci, bon à savoir !

On dirait que nous parlons tous de problèmes différents. Je vais essayer de décrire le problème de yarn workspaces .

Projet problématique

mise en page du projet

- utilities/
|  - package.json
- functions/
|  - package.json
- package.json

_./paquet.json_

{
  "private": true,
  "workspaces": ["functions", "utilities"]
}

_functions/package.json_

{
  <...>
  "dependencies": {
    "utilities": "1.0.0",
    <...>
  }
}

Problème

Erreur lors du déploiement de la fonction :

Deployment error.
Build failed: {"error": {"canonicalCode": "INVALID_ARGUMENT", "errorMessage": "`gen_package_lock` had stderr output:\nnpm WARN deprecated [email protected]: use String.prototype.padStart()\nnpm ERR! code E404\nnpm ERR! 404 Not Found: [email protected]\n\nnpm ERR! A complete log of this run can be found in:\nnpm ERR!     /builder/home/.npm/_logs/2019-06-18T07_10_42_472Z-debug.log\n\nerror: `gen_package_lock` returned code: 1", "errorType": "InternalError", "errorId": "1971BEF9"}}

Les fonctions fonctionnent bien localement sur l'émulateur.

Solutions essayées

Téléchargement de node_modules (en utilisant functions.ignore dans _firebase.json_). Le résultat est le même.

Je suppose que c'est parce que utilities est créé en tant que syslink dans _node-modules_ node_modules/utilities -> ../../utilities

Se pourrait-il que firebase-tools n'inclue pas le contenu des modules liés symboliquement lors du téléchargement (pas de déréférencement) ?

Désolé, pourriez-vous préciser dans quel dossier réside votre firebase.json (et afficher sa section de configuration pour functions ) ?

_firebase.json_ était dans le dossier racine. La configuration était standard. qch comme ça :

  "functions": {
    "predeploy": [
      "yarn --cwd \"$RESOURCE_DIR\" run lint",
      "yarn --cwd \"$RESOURCE_DIR\" run build"
    ],
    "source": "functions",
    "ignore": []
  },
  <...>

tout a été déployé comme prévu (y compris _node_modules_) sauf node_modules/utilities qui est un lien symbolique.


J'arrive à contourner ce problème en écrivant quelques scripts qui :

  • créer des packages pour chaque espace de travail ( yarn pack ). par exemple, cela crée _utilities.tgz_.
  • déplacer toutes les sorties vers un répertoire spécifique.
  • modifier _package.json_ pour utiliser les fichiers tgz pour les dépendances de l'espace de travail. par exemple dependencies { "utilities": "1.0.0" -> dependencies { "utilities": "file:./utilities.tgz"
  • déployer ce répertoire sur firebase

contenu du répertoire de sortie avant le téléchargement :

- dist
|  - lib
|  | -index.js
|  - utilities.tgz
|  - package.json <---------- This is modified to use *.tgz for workspaces

@audkar Aujourd'hui, j'ai rencontré le même problème que vous.

Je suis nouveau dans les espaces de travail Lerna et Yarn. Si j'ai bien compris, vous pouvez aussi simplement utiliser Lerna. Cela aiderait-il de quelque manière que ce soit?

Votre solution de contournement me semble un peu compliquée 🤔

Vous vous demandez également à quoi sert `--cwd "$RESOURCE_DIR" ?

--cwd signifie "répertoire de travail actuel" et $RESOURCE_DIR contient la valeur pour le répertoire source ( functions dans ce cas). L'ajout de ce drapeau fera que yarn sera exécuté dans functions dir au lieu de root

@audkar Ah je vois. Vous pouvez donc faire la même chose avec yarn workspace functions lint et yarn workspace functions build

@dinvlad Je ne comprends pas pourquoi vous ciblez le dossier dist et copiez des éléments là-bas. Si vous construisez sur dist, mais laissez le package.json où il se trouve et pointez main sur dist/index.js alors les choses devraient fonctionner de la même manière non? Vous devez ensuite définir la source sur api au lieu de api/dist.

@dinvlad J'ai appris la commande yarn workspace partir de vos commentaires, mais je n'arrive pas à la faire fonctionner pour une raison quelconque. Voir ici . Une idée?

Désolé d'être un peu hors sujet ici. Peut-être commenter en SO, pour minimiser le bruit.

@0x80 Je copie package.json vers api/dist et pointe firebase.json vers api/dist afin que seuls les fichiers "construits" soient empaquetés dans la fonction cloud. Je ne sais pas ce qui se passera si je pointe firebase.json vers api - peut-être sera-t-il encore assez intelligent pour n'emballer que ce qu'il y a à l'intérieur de api/dist (basé sur main attribut dans package.json ). Mais je pensais qu'il était plus propre de pointer simplement vers api/dist .

Re yarn workspace , j'ai répondu sur SO ;)

@dinvlad, il regroupera la racine de ce sur quoi vous le pointez, mais vous pouvez mettre tout ce que vous ne voulez pas inclure dans la liste des ignorés firebase.json.

J'ai maintenant utilisé une solution de contournement similaire à @audkar.

{
  "functions": {
    "source": "packages/cloud-functions",
    "predeploy": ["./scripts/pre-deploy-cloud-functions"],
    "ignore": [
      "src",
      "node_modules"
    ]
  }
}

Ensuite, le script pre-deploy-cloud-functions est :

#!/usr/bin/env bash

set -e

yarn workspace @gemini/common lint
yarn workspace @gemini/common build

cd packages/common
yarn pack --filename gemini-common.tgz
mv gemini-common.tgz ../cloud-functions/
cd -

cp yarn.lock packages/cloud-functions/

yarn workspace @gemini/cloud-functions lint
yarn workspace @gemini/cloud-functions build

Et packages/cloud-functions a un fichier gitignore supplémentaire :

yarn.lock
*.tgz

voici ce qui a fonctionné pour moi

- root/
|  - .firebaserc
|  - firebase.json
- packages/
  | - package1/
  | - functions/
    | - dist/
    | - src/
    | packages.json

et dans le root/firebase.json :
```
{
"les fonctions": {
"predeploy": "npm --prefix "$RESOURCE_DIR" run build",
"source": "paquets/fonctions"
}
}
````

@kaminskypavel est-ce que vos packages/fonctions dépendent des packages/package1 (ou d'un autre package frère) ?

@0x80 positif.

Je pense qu'il y avait quelque chose de fondamental que j'ai mal compris à propos de monorepos. Je suppose que vous pouvez partager un package et déployer une application à l'aide de ce package sans réellement publier le package partagé sur NPM.

Il semble que cela ne soit pas possible, car les déploiements comme Firebase ou Now.sh téléchargent généralement le code, puis dans le cloud, effectuent une installation et une génération. Ai-je raison?

@kaminskypavel J'ai essayé votre approche et cela fonctionne, mais seulement après avoir d'abord publié mon package sur NPM. Parce que dans mon cas, le package est privé, j'ai d'abord eu une erreur "not found", donc j'ai également dû ajouter mon fichier .npmrc à la racine du package de fonctions cloud comme décrit ici

@audkar Publiez -vous votre package commun sur NPM, ou essayez-vous comme moi de le déployer avec du code partagé qui n'est pas publié ?

@ 0x80 Je suis avec vous sur cette compréhension - je pense que les déploiements de la fonction Firebase supposent (à tort) que tous les packages nommés dans package.json seront disponibles sur npm, au nom de l'accélération des déploiements.

À mesure que les configurations d'espace de travail de fil deviennent de plus en plus populaires, j'imagine que de plus en plus de gens seront surpris de ne pas pouvoir utiliser de packages liés symboliquement dans Firebase Functions, d'autant plus qu'ils fonctionnent correctement jusqu'à ce que vous déployiez.

Avec npm ajoutant la prise en charge des espaces de travail , nous avons une norme d'écosystème sur le fonctionnement des packages locaux.

Étant donné que ce problème date de plus d'un an, une mise à jour du côté de Firebase sur les plans (ou l'absence de plans) ici ?

Je pense que c'est une opportunité assez intéressante - la gamme de services de Firebase demande une bonne configuration monorepo.

+1 à ce sujet, les fonctions cloud auront généralement besoin de partager du code commun (par exemple des interfaces) avec d'autres applications et un bon moyen de gérer cela est un monorepo (par exemple lerna) ou l'utilisation directe de liens symboliques. J'ai pris ce dernier et résolu en créant des scripts. Le concept est assez simple : je copie ce qu'il faut dans le répertoire des fonctions et je le supprime après

Voici comment j'ai procédé avec cette structure de répertoires :
```

  • racine/
    | - .firebaserc
    | - firebase.json
    | - ...
  • les fonctions/
    | - src/
    | - package.json
    | - pre-deploy.js
    | - post-deploy.js
    | - ....
  • partagé/
    | - src/
    | - package.json
    | - ....
content of `pre-deploy.js`

const fs = require("fs-extra");

const packageJsonPath = "./package.json";
const packageJson = require(packageJsonPath);

(async () => {
attendre fs.remove( ./shared );
wait fs.copy( ../shared , ./shared );

packageJson.dependencies["@project/shared"] = "file:./shared";

await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));

})();

content of `post-deploy.js`

const packageJsonPath = "./package.json";
const packageJson = require(packageJsonPath);
const fs = require("fs-extra");

(async () => {
attendre fs.remove( ./shared );

packageJson.dependencies["@project/shared"] = "file:../shared";

await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));

})();

Then update `firebase.json` like this (add the build script if you need, I build before in my pipeline)

"les fonctions": {
"source": "fonctions",
"prédéployer": [
"npm --prefix "$RESOURCE_DIR" exécute le pré-déploiement"
],
"postdéploiement": [
"npm --prefix "$RESOURCE_DIR" s'exécute après le déploiement"
]
},
```

Si vous faites la construction, à l'intérieur du répertoire dist ou lib , vous devriez maintenant avoir deux frères : les fonctions et partagé (cela est arrivé à cause de la dépendance partagée). Assurez-vous de mettre à jour les fonctions package.json main pour pointer vers lib/functions/src/index.js pour que le déploiement fonctionne.

Pour l'instant, c'est résolu, mais c'est une solution de contournement, pas une solution. Je pense que les outils firebase devraient vraiment prendre en charge les liens symboliques

@michelepatrassi inspiré par ce que vous avez, me rappelle que j'ai créé firelink bibliothèque rsync pour copier des fichiers récursifs.

https://github.com/rxdi/firelink

npm i -g @rxdi/firelink

Utilisation de base
En supposant que vous ayez une approche monorepo et que vos packages soient situés à 2 niveaux du répertoire actuel où se trouve package.json .

package.json

  "fireDependencies": {
    "@graphql/database": "../../packages/database",
    "@graphql/shared": "../../packages/shared",
    "@graphql/introspection": "../../packages/introspection"
  },

En exécutant firelink il copiera les packages liés aux dossiers puis mappera les packages existants avec l'installation du module local "@graphql/database": "file:./.packages/database", puis exécutera la commande firebase et transmettra le reste des arguments de firelink commande
Fondamentalement, firelink est un remplacement pour firebase CLI car il génère firebase à la fin quand il termine son travail en copiant packages et en modifiant package.json !

Salutations!

Nous venons de nous faire piquer par cela, je pense que monorepos deviendra la norme et cela devrait être pris en charge par défaut.

Les gars, une solution simple à ce problème consiste à utiliser webpack pour regrouper vos fonctions cloud et à déployer à partir du répertoire fourni. Vous trouverez ci-joint un fichier webpack de base que j'utilise. Pendant la construction, cela emballera tout le code des fonctions, y compris les dépendances résolues dans le mono-repo dans un dossier de niveau supérieur (dans ce cas, c'est webpack/cloud-functions , cela peut être tout ce que vous configurez)

const path = require('path');

module.exports = {
  target: 'node',
  mode: 'production',
  entry: './src/index.ts',
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/
      }
    ]
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.js', '.json']
  },
  output: {
    filename: 'index.js',
    path: path.resolve(__dirname, '../../webpack/cloud-functions/dist'),
    libraryTarget: 'commonjs'
  },
  externals: {
    'firebase-admin': 'firebase-admin',
    'firebase-functions': 'firebase-functions'
  }
};

Et enfin dans votre fichier firebase.json , référez-vous à ce dossier pour le déploiement.

{
  "functions": {
    "source": "webpack/cloud-functions"
  }
}

N'oubliez pas d'avoir également un fichier package.json dans le dossier webpack/cloud-functions .

{
  "name": "cloud-functions",
  "version": "1.0.0",
  "scripts": {
    "deploy": "firebase deploy --only functions"
  },
  "engines": {
    "node": "10"
  },
  "main": "dist/index.js",
  "dependencies": {
    "firebase-admin": "8.9.1",
    "firebase-functions": "3.3.0"
  },
  "devDependencies": {},
  "private": true
}

Ceci est testé et fonctionne. J'utilise google cloud build. Demandez plus d'informations si nécessaire.

Merci,

@sowdri Merci pour le partage ! Déclenchez-vous la compilation webpack à partir de l'étape firebase.json functions.predeploy , ou la déclenchez-vous à partir du script de compilation cloud avant d'appeler firebase deploy ?

@0x80 Je le construis à l'étape cloud build . Donc selon firebase cli c'est juste un ensemble de fonctions construites avec JS

@sowdri Merci pour l'exemple. Nous avons fini par utiliser la même approche dans un précédent projet pour AWS Lambdas / Serverless : il était plus facile de télécharger un bundle (Webpack) que de forcer les outils à fonctionner avec le fil monorepo.

Je suis également coincé avec ça... tout fonctionnait bien (même l'émulateur Firebase) jusqu'à ce que j'essaie de déployer des fonctions (la compilation de l'application Web dans React se déploie correctement). :( Espérons que l'équipe de firebase pourra bientôt ajouter un support pour les monorepos.

J'utilise babel pour d'autres packages dans mon monorepo, donc je préférerais rester avec ça. Si rien d'autre, je peux essayer le webpack... qui semble fonctionner pour certains.

EDIT : j'ai résolu ce problème en utilisant le registre de packages GitHub . Je publie donc tous mes packages là-bas, puis pour le serveur travis et functions je dois configurer le registre et fournir un jeton d'authentification (fait via .npmrc ). ... semble être une solution élégante. J'ai eu l'idée de cette approche ici : https://medium.com/gdgeurope/how-to-use-firebase-cloud-functions-and-yarn-workspaces-24ca35e941eb

Ouais, j'ai été mordu par ça aussi. Tout fonctionnait parfaitement dans firebase serve --only functions mais une fois déployé, il n'a pas pu localiser un module.

J'ai fini par créer un petit script pour créer des packages pour moi. Bien que cela reflète les particularités de mon environnement, comme l'utilisation de modules et de noms de packages correspondant à mes noms de répertoire, j'espère que cela sera utile pour d'autres.

```importer fs de "fs" ;
importer child_process depuis "child_process" ;

const internalPackagesFull = new Map();

// Trouver tous les deps pour le paquet
const getDepsForPackage = (nom_paquet) => {
const packageDir = packageName.split("/")[1]; // CELA POURRAIT CHANGER POUR VOUS
const packageSpecFileName = ../${packageDir}/package.json ;
const packageSpecFile = fs.readFileSync(packageSpecFileName);
const packageSpec = JSON.parse(packageSpecFile);
const packageInternalDeps = Object.keys(
packageSpec.dépendances
).filter((clé) => clé.includes("turing")); // CELA DOIT CHANGER POUR VOUS

const packageTgzName = ${packageName.replace("@", "").replace("/", "-")}-v${ packageSpec.version }.tgz ;

internalPackagesFull.set(nom_paquet, {
packageSpecFileName,
packageSpec,
paquetDir,
packageInternalDeps,
packageTgzName,
});

const packagesToProcess = packageInternalDeps.filter(
(internalDepName) => !internalPackagesFull.has(internalDepName)
);

packagesToProcess.forEach((internalPackageName) =>
getDepsForPackage(internalPackageName)
);
} ;

const packageName = JSON.parse(fs.readFileSync("./package.json")).name;
child_process.execSync( cp ./package.json ./package.json.org );
getDepsForPackage(nom_paquet);

// écrire des packages mis à jour - utilisez des js communs et les références sont des fichiers tgz locaux
[...internalPackagesFull.values()].forEach((internalDep) => {
const { packageSpec, packageSpecFileName, packageInternalDeps } = internalDep;

// changer le type de paquet
packageSpec.type = "commonjs"; // CELA POURRAIT CHANGER POUR VOUS

// spécifiez l'emplacement du dep pour être le fichier zip emballé
packageInternalDeps.forEach((internalDepOfPackage) => {
const { packageTgzName } = internalPackagesFull.get(internalDepOfPackage);
packageSpec.dependencies[internalDepOfPackage] = ./${packageTgzName} ;
});

fs.writeFileSync(
packageSpecFileName,
JSON.stringify(packageSpec, null, " ")
);
});

// exécuter le build et le pack de fil
[...internalPackagesFull.values()].forEach((internalDep) => {
essayer {
console.log( Buliding ${internalDep.packageDir} );
child_process.execSync("construction de fils", {
cwd : ../${internalDep.packageDir} ,
});
console.log( Packaging ${internalDep.packageDir} );
child_process.execSync("paquet de fils", {
cwd : ../${internalDep.packageDir} ,
});

if (packageName !== internalDep.packageSpec.name) {
  console.log(`Move to current directory ${internalDep.packageDir}`);
  child_process.execSync(
    `cp ../${internalDep.packageDir}/${internalDep.packageTgzName} .`,
    {
      cwd: ".",
    }
  );
}

} capture (e) {
console.log(e);
}
});

// retour à la structure des packages standard
[...internalPackagesFull.values()]
.filter((internalDep) => packageName !== internalDep.packageSpec.name)
.forEach((interneDep) => {
const {
packageSpec,
packageSpecFileName,
packageInternalDeps,
} = interneDep;

// change the package type
packageSpec.type = "module"; // THIS MAY NEED TO CHANGE FOR YOU

// specify the location of the dep to be the packaged zip file
packageInternalDeps.forEach((internalDepOfPackage) => {
  packageSpec.dependencies[internalDepOfPackage] = "*";
});

fs.writeFileSync(
  packageSpecFileName,
  JSON.stringify(packageSpec, null, "  ")
);

});
```

J'ai utilisé la solution fournie par @sowdri (merci pour ça !), avec un petit ajustement pour que je puisse librement supprimer et régénérer l'intégralité du répertoire dist/ , y compris le secondaire package.json :

const path = require('path');
const CopyPlugin = require('copy-webpack-plugin');

module.exports = {
  ...,
  plugins: [
    new CopyPlugin({
      patterns: [{ from: 'package.dist.json', to: 'package.json' }],
    }),
  ],
};

Et puis je garde les package.dist.json à la racine du package :

{
  "name": "@package/name",
  "version": "0.0.1",
  "engines": {
    "node": "10"
  },
  "main": "index.js",
  "dependencies": {
    "firebase-admin": "8.9.1",
    "firebase-functions": "3.3.0"
  },
  "private": true
}

Il est peut-être possible de supprimer les dépendances de ce fichier et de s'appuyer sur Webpack pour les regrouper également (en supprimant le besoin de synchroniser ces dépendances avec votre package.json ), mais je n'ai pas essayé.

J'utilise un seul référentiel avec un script pour copier .firebaserc & firebase.json pour tous les répertoires ou projets firebase pertinents utilisant le package npm

Je crée package.json partir du package.json d'origine pour le déploiement des fonctions.
image

voici le script copyFiles.js :

const cpy = require('cpy');
const fs = require('fs');
const package = require('./package.json');
(async () => {
    await cpy(['./../package.json', './**/*.json', './**/.firebaserc'], '../out/', {
        parents: true,
        cwd: 'src'
    });
    const dirs = fs.readdirSync('./out/');
    const newPkg = {
        main: package.main,
        dependencies: package.dependencies,
        engines: package.engines,
    }
    dirs.forEach(dir => {
        fs.writeFileSync(`./out/${dir}/package.json`, JSON.stringify({ name: dir, ...newPkg }));
    })
    console.log('Files copied!', dirs);
})();

pour le déploiement, allez à ./out/[project-name]/firebase deploy --only=functions ou écrivez un script pour cela aussi.

Je rencontre des avertissements Webpack comme celui-ci :

AVERTISSEMENT dans /Users/me/Development/myproject/node_modules/firebase-functions/lib/config.js 61:23-42
Dépendance critique : la demande d'une dépendance est une expression

Avez-vous trouvé un moyen de les résoudre ou les ignorez-vous/supprimez-vous ?

J'ai réussi à faire fonctionner les choses sans avertissements 🥳 Je me suis retrouvé avec la configuration ci-dessous. En particulier, l'utilisation des modèles regex pour les externes a fait la différence, car si vous n'avez que des "fonctions firebase" dans vos externes, toute importation que vous effectuez à partir d'un sous-module ne sera pas prise en compte et la bibliothèque est toujours incluse dans votre bundle.

En raison de problèmes de regroupement, j'ai également rencontré des erreurs cryptiques @grpc lors du déploiement. J'ai oublié de les garder pour référence.

const path = require("path");
const CopyPlugin = require("copy-webpack-plugin");

module.exports = {
  target: "node",
  mode: "production",
  entry: "./src/index.ts",
  devtool: "inline-source-map",
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: "ts-loader",
        exclude: /node_modules/,
      },
    ],
  },
  resolve: {
    extensions: [".tsx", ".ts", ".js", ".json"],
    alias: {
      "~": path.resolve(__dirname, "src"),
    },
  },
  output: {
    filename: "index.js",
    path: path.resolve(__dirname, "dist/bundled"),
    libraryTarget: "commonjs",
  },
  externals: ["express", /^firebase.+$/, /^@google.+$/],
  plugins: [
    new CopyPlugin({
      patterns: [{ from: "package.dist.json", to: "package.json" }],
    }),
  ],
};

Il s'avère qu'il n'y a pas besoin d'un package.dist.json séparé. La chose ennuyeuse d'avoir ce fichier est bien sûr que vous devez le mettre à jour manuellement chaque fois que vous mettez à jour l'une des dépendances qui y sont répertoriées. Il est donc très facile de l'oublier.

Au lieu de cela, déplacez tous les packages que vous _n'auriez pas_ listés dans votre package.dist.json vers la liste devDependencies et utilisez simplement ce fichier dans le plug-in de copie webpack. Maintenant, vous n'avez qu'un seul package.json à gérer 🎉

De plus, je ne veux pas que ma version locale de nodejs soit nécessairement la même que la version de nœud des fonctions déployées. J'ai trouvé que vous pouvez maintenant spécifier functions.runtime dans le fichier firebase.json. Retirez donc le champ engines du package.json et définissez plutôt functions.runtime sur "10" ou "12" dans votre configuration firebase.

Voilà à quoi ça ressemble pour moi :

{
  "functions": {
    "source": "packages/cloud-functions/dist/bundled",
    "runtime": "12"
  },
  "firestore": {
    "rules": "firestore.rules",
    "indexes": "firestore.indexes.json"
  },
  "emulators": {
    "functions": {
      "port": 5001
    },
    "firestore": {
      "port": 8080
    },
    "pubsub": {
      "port": 8085
    }
  }
}

Comment gérez-vous les cartes sources ? Si je regroupe mes fonctions avec webpack à l'aide de devtool: "inline-source-map" elles ne sont pas détectées dans le rapport d'erreurs du pilote de pile. @sowdri

J'ai rencontré la même chose sur mon projet et c'était une grosse déception car je ne veux pas publier tous les packages pour un projet parallèle. C'était ennuyeux de suivre plusieurs fichiers package.json ou d'avoir à construire en dehors du package. Il s'avère que si vous incluez vos deps en tant qu'optionnel, Lerna les récupérera toujours et Firebase ne se plaindra pas lors du téléchargement.

La configuration ci-dessous vous procurera un support dep lié symboliquement avec un seul package.json !

package.json

{
  "name": "@your-package-name/functions",
  "version": "0.1.0",
  "scripts": {
    "build": "webpack"
  },
  "engines": {
    "node": "10"
  },
  "main": "dist/index.js",
  "dependencies": {
    "firebase-admin": "^8.10.0",
    "firebase-functions": "^3.6.1"
  },
  "optionalDependencies": {
    "@your-package-name/shared": "^0.1.0",
    "@your-package-name/utils": "^0.1.0"
  }
}

webpack.config.js

const path = require('path')

// The cost of being fancy I suppose
// https://github.com/firebase/firebase-tools/issues/653

module.exports = {
  target: 'node',
  mode: 'production',
  entry: './src/index.ts',
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: 'ts-loader',
        exclude: /node_modules/,
        options: {
          configFile: 'tsconfig.build.json',
        },
      },
    ],
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.js', '.json'],
  },
  output: {
    filename: 'index.js',
    path: path.resolve(__dirname, 'dist'),
    libraryTarget: 'commonjs',
  },
  externals: {
    'firebase-admin': 'firebase-admin',
    'firebase-functions': 'firebase-functions',
  },
}

firebase.json

{
  "firestore": {
    "rules": "firestore.rules",
    "indexes": "firestore.indexes.json"
  },
  "functions": {
    "source": "packages/functions"
  },
  "emulators": {
    "functions": {
      "port": 5476
    },
    "firestore": {
      "port": 4565
    },
    "ui": {
      "enabled": true
    }
  }
}

J'utilise des espaces de travail de fil, mais je n'ai pas non plus besoin de mentionner les noms de mes packages locaux dans d'autres fichiers package.json. Je déploie avec succès sur Firebase et Vercel sans cela.

Je ne sais pas ce qui le fait fonctionner. Il suffit d'avoir cette configuration standard dans mon package.json de niveau supérieur :

"workspaces": {
    "packages": [
      "packages/*"
    ]
  },

Cela ne me dérange pas d'avoir un package.json dans chacun des dossiers /packages/*, car exécuter yarn upgrade-interactive traitera tous en une seule fois. Dans certains packages, je trouve utile de pouvoir ajouter un script spécifiquement pour cette portée.

---- Éditer ----

J'ai oublié de mentionner que j'utilise Typescript avec des références de projet. Cela a peut-être quelque chose à voir avec ça. Pour Vercel, j'utilise les modules next-transpile pour inclure mon code partagé dans le bundle.

J'utilise une configuration similaire à @0x80 et @sowdri pour que cela fonctionne, mais plutôt que de déplacer mes dépendances locales vers devDependencies (ce qui n'a pas fonctionné pour moi, je pense qu'il me manquait une étape) et en utilisant copy-webpack-plugin , j'utilise generate-package-json-webpack-plugin pour construire mon package.json dans le dossier dist. Cela construit ma liste de dépendances à partir de ce que le code résultant requiert réellement, donc s'il est groupé ou une dépendance de développement, il n'est pas inclus dans le package.json résultant.

J'ai également configuré mes externes à l'aide de webpack-node-externals pour que tout soit externe, à l'exception de mes packages monorepo liés symboliquement que j'utilise regex pour correspondre au préfixe du nom du projet. J'ai également ajouté les expressions regex pour les packages firebase @ 0x80 publiés en tant qu'externes supplémentaires.

C'est ma config

/* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path");
const nodeExternals = require("webpack-node-externals");
const GeneratePackageJsonPlugin = require("generate-package-json-webpack-plugin");

const basePackage = {
  name: "@project/functions",
  version: "1.0.0",
  main: "./index.js",
  scripts: {
    start: "yarn run shell"
  },
  engines: {
    node: "12"
  }
};

module.exports = {
  target: "node",
  mode: "production",
  entry: "./src/index.ts",
  devtool: "inline-source-map",
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: "ts-loader",
        exclude: /node_modules/
      }
    ]
  },
  resolve: {
    extensions: [".tsx", ".ts", ".js", ".json"],
    alias: {
      "@": path.resolve(__dirname, "src"),
      "@root": path.resolve(__dirname, "./"),
      "@types": path.resolve(__dirname, "src/@types"),
      "@utils": path.resolve(__dirname, "src/utils")
    }
  },
  output: {
    filename: "index.js",
    path: path.resolve(__dirname, "dist"),
    libraryTarget: "commonjs"
  },
  externals: [
    /^firebase.+$/,
    /^@google.+$/,
    nodeExternals({
      allowlist: [/^@project/]
    })
  ],
  plugins: [new GeneratePackageJsonPlugin(basePackage)]
};

L'autre avantage de l'utilisation de webpack pour regrouper le code est que je peux enfin utiliser des alias de module :)

Cette page vous a été utile?
0 / 5 - 0 notes