Firebase-tools: Soporta repositorios mono en implementación

Creado en 31 ene. 2018  ·  47Comentarios  ·  Fuente: firebase/firebase-tools

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

Información de la versión

3.17.3

pasos para reproducir

Comportamiento esperado

Comportamiento real

feature request

Comentario más útil

+1 en esto, las funciones en la nube generalmente necesitarán compartir algún código común (por ejemplo, interfaces) con otras aplicaciones y una buena manera de lidiar con esto es un monorepo (por ejemplo, lerna) o usando enlaces simbólicos directamente. Tomé este último y lo resolví creando algunos scripts. El concepto es bastante sencillo: copio lo que se necesita dentro del directorio de funciones y lo elimino después

Así es como lo hice con esta estructura de directorio:
''

  • raíz/
    | - .firebaserc
    | - firebase.json
    | - ...
  • funciones /
    | - src /
    | - paquete.json
    | - pre-deploy.js
    | - post-deploy.js
    | - ....
  • compartido/
    | - src /
    | - paquete.json
    | - ....
content of `pre-deploy.js`

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

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

(asíncrono () => {
aguardar fs.remove ( ./shared );
aguardar 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");

(asíncrono () => {
aguardar 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)

"funciones": {
"fuente": "funciones",
"antes del despliegue": [
"npm --prefix" $ RESOURCE_DIR "ejecutar antes de la implementación"
],
"posterior a la implementación": [
"npm --prefix" $ RESOURCE_DIR "ejecutar después de la implementación"
]
},
''

Si realiza la compilación, dentro del directorio dist o lib ahora debería tener dos hermanos: funciones y compartidos (esto sucedió debido a la dependencia compartida). Asegúrese de actualizar las funciones package.json main para que apunten a lib/functions/src/index.js para que la implementación funcione.

Por ahora está resuelto, pero es una solución alternativa, no una solución. Creo que las herramientas de base de fuego realmente deberían admitir enlaces simbólicos

Todos 47 comentarios

Esto parece funcionar en mi configuración, es decir, deploy recoge paquetes de la raíz node_modules , aunque package.json para eso se encuentra debajo del espacio api/ trabajo functions/ ). ¿Hay algo más que deba arreglarse aquí?

EDITAR: Además, copio package.json en api/dist para ser utilizado por deploy .

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

Por lo tanto, 2 niveles de anidamiento aún resuelven la raíz node_modules éxito.

@dinvlad, ¿ podrías compartir un repositorio?

@orouz lamentablemente todavía no, es de código cerrado por ahora.

¿Alguien logró abordar este problema? Compartir un proyecto de ejemplo simple sería muy útil.

@audkar Actualmente solo uso lerna.js.org y es el comando run para ejecutar un script npm en cada subcarpeta con esta estructura de carpeta:

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

Asegurar que los archivos firebase.json para cada servicio no se pisoteen entre sí queda en manos del usuario. Las convenciones simples de usar grupos de funciones y la segmentación por nombres de sitios múltiples significan que esto se resuelve para Cloud Functions y Hosting. Todavía no tengo una solución para las reglas de Firestore / GCS, aunque dividirlas puede no ser lo ideal ...

discutido aquí anteriormente - https://github.com/firebase/firebase-tools/issues/1116

@jthegedus gracias por su respuesta. Pero creo que el tema de este boleto es diferente. Estoy tratando de usar espacios de trabajo de hilo. Y parece que las herramientas de base de fuego no recogen las dependencias de enlaces simbólicos al cargar funciones

Ah, bastante bien, yo mismo he evitado esa madriguera

¿Podría explicarnos cuál es el problema? Como se mencionó anteriormente, solo uso hilo desnudo con api y app espacios de trabajo en él, y los construyo usando yarn workspace api build && yarn workspace app build (con build script específico para cada uno espacio de trabajo). Los scripts de construcción
1) compile el código TS con outDir en api/dist y app/dist respectivamente
2) copiar los correspondientes package.json archivos en dist directorios
3) copie yarn.lock de la carpeta _root_, en los directorios dist

Luego ejecuto yarn firebase deploy desde la carpeta _root_, y recoge tanto api/dist como app/dist sin ningún problema. Mi firebase.json parece

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

Desafortunadamente, todavía no puedo compartir el código completo, pero esta configuración es todo lo que importa, afaik.

Además, podría estar equivocado, pero creo que el script firebase deploy realidad no usa su directorio node_modules . Creo que solo toma el código, package.json y yarn.lock de los directorios dist , y hace el resto.

Eso es cierto. El valor predeterminado de "functions.ignore" en firebase.json es
["node_modules"] por lo que no se carga. Creo que puedes anular eso
aunque si desea enviar algunos módulos locales.

El lunes, 17 de junio de 2019, 6:58 p.m. Denis Loginov [email protected]
escribió:

Además, podría estar equivocado, pero creo que el script de implementación de la base de fuego no lo hace.
realmente use su directorio node_modules. Creo que simplemente recoge el
cod, package.json y yarn.lock de los directorios dist, y hace el
descansar.

-
Estás recibiendo esto porque estás suscrito a este hilo.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/firebase/firebase-tools/issues/653?email_source=notifications&email_token=ACATB2U73VS2KIILUVRFFB3P3A6NPA5CNFSM4EOR24GKYY3PNVWWK3TUL52HS4DFVREXP63JSMVBW5 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/ACATB2U3Q2TLVBICRJ3B5OLP3A6NPANCNFSM4EOR24GA
.

@dinvlad Sí, requiere el package.json y el archivo de bloqueo que use para instalar los departamentos en la implementación de la publicación en la nube.

Creo que el escenario que se describió originalmente en el otro número fue el uso de un paquete compartido dentro del espacio de trabajo y algunos problemas con la elevación del alcance. Como no estaba usando hilo de esta manera, solo puedo especular a partir de lo que he leído allí.

@samtstern @jthegedus gracias, ¡bueno saberlo!

Parece que todos hablamos de diferentes problemas. Intentaré describir el problema yarn workspaces .

Proyecto problemático

diseño del proyecto

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

_./package.json_

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

_functions / package.json_

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

Problema

Error durante la implementación de la función:

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

Las funciones funcionan bien localmente en el emulador.

Soluciones probadas

Subiendo node_modules (usando functions.ignore en _firebase.json_). El resultado es el mismo.

Supongo que es porque utilities se crea como syslink en _node-modules_ node_modules/utilities -> ../../utilities

¿Podría ser que firebase-tools no incluye el contenido de los módulos con enlaces simbólicos al cargar (sin desreferenciar)?

Lo siento, ¿podría aclarar en qué carpeta vive su firebase.json (y mostrar su sección de configuración para functions )?

_firebase.json_ estaba en la carpeta raíz. La configuración era estándar. Algo así:

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

todo se implementó como se esperaba (incluido _node_modules_) excepto node_modules/utilities que es un enlace simbólico.


Me las arreglo para solucionar este problema escribiendo algunos scripts que:

  • cree paquetes para cada espacio de trabajo ( yarn pack ). por ejemplo, esto crea _utilities.tgz_.
  • moviendo toda la salida a algún directorio específico.
  • modificar _package.json_ para usar archivos tgz para las dependencias del espacio de trabajo. por ejemplo, dependencies { "utilities": "1.0.0" -> dependencies { "utilities": "file:./utilities.tgz"
  • desplegando ese directorio en firebase

salida del contenido del directorio antes de la carga:

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

@audkar Hoy me encontré con el mismo problema que tú.

Soy nuevo en los espacios de trabajo de Lerna y Yarn. Según tengo entendido, también puedes usar Lerna. ¿Eso ayudaría de alguna manera?

Tu solución me parece un poco complicada 🤔

También se pregunta, ¿para qué sirve `--cwd" $ RESOURCE_DIR "?

--cwd significa "directorio de trabajo actual" y $RESOURCE_DIR contiene el valor del directorio fuente ( functions en este caso). Agregar esta bandera hará que yarn se ejecute en functions dir en lugar de root

@audkar Ah, ya veo. Entonces podrías hacer lo mismo con yarn workspace functions lint y yarn workspace functions build

@dinvlad No me queda claro por qué está apuntando a la carpeta dist y copiando cosas allí. Si construye para dist, pero deja el package.json donde está y apunta main a dist/index.js entonces las cosas deberían funcionar igual, ¿no? Luego, debe establecer source en api en lugar de api / dist.

@dinvlad Aprendí el yarn workspace de tus comentarios, pero parece que no puedo hacerlo funcionar por alguna razón. Vea aquí . ¿Alguna idea?

Perdón por haberme salido un poco del tema. Tal vez comente en SO, para minimizar el ruido.

@ 0x80 Copio package.json a api/dist y apunto firebase.json a api/dist para que solo los archivos "construidos" estén empaquetados dentro de la función de nube. No estoy seguro de qué pasará si apunto firebase.json a api ; tal vez aún sea lo suficientemente inteligente como para empaquetar solo lo que hay dentro api/dist (basado en main atributo en package.json ). Pero pensé que era más limpio señalar solo api/dist .

Re yarn workspace , respondí SO ;)

@dinvlad agrupará la raíz de aquello a lo que lo

Ahora he usado una solución similar a @audkar.

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

Entonces el script pre-deploy-cloud-functions es:

#!/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

Y packages / cloud-functions tiene un archivo gitignore adicional:

yarn.lock
*.tgz

esto es lo que funcionó para mí

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

y en el root/firebase.json :
''
{
"funciones": {
"predeploy": "npm --prefix" $ RESOURCE_DIR "ejecutar compilación",
"fuente": "paquetes / funciones"
}
}
`` ``

@kaminskypavel, ¿sus paquetes / funciones dependen de los paquetes / paquete1 (o de algún otro paquete hermano)?

@ 0x80 positivo.

Creo que hubo algo fundamental que entendí mal sobre monorepos. Supuse que puede compartir un paquete e implementar una aplicación usando ese paquete sin publicar el paquete compartido en NPM.

Parece que esto no es posible, porque implementaciones como Firebase o Now.sh generalmente cargan el código y luego en la nube hacen una instalación y compilación. ¿Estoy en lo correcto?

@kaminskypavel Probé su enfoque y funciona, pero solo después de publicar mi paquete en NPM primero. Debido a que, en mi caso, el paquete es privado, inicialmente recibí un error de "no encontrado", por lo que también tuve que agregar mi archivo .npmrc a la raíz del paquete de funciones en la nube como se describe aquí.

@audkar ¿Está publicando su paquete común en NPM o es como yo tratando de implementar con código compartido que no está publicado?

@ 0x80 Estoy de acuerdo con usted en este entendimiento: creo que las implementaciones de la función Firebase están asumiendo (erróneamente) que todos los paquetes nombrados en package.json estarán disponibles en npm, en nombre de acelerar las implementaciones.

A medida que las configuraciones del espacio de trabajo de yarn se vuelven más populares, imagino que más personas se sorprenderán de que no puedan usar paquetes con enlaces simbólicos en Firebase Functions, especialmente porque funcionan bien hasta que se implementa.

Con npm agregando soporte para espacios de trabajo , tenemos un estándar de ecosistema sobre cómo deberían funcionar los paquetes locales.

Dado que este problema tiene más de un año, ¿alguna actualización del lado de Firebase sobre los planes (o falta de planes) aquí?

Creo que es una gran oportunidad: la variedad de servicios de Firebase pide una buena configuración de monorepo.

+1 en esto, las funciones en la nube generalmente necesitarán compartir algún código común (por ejemplo, interfaces) con otras aplicaciones y una buena manera de lidiar con esto es un monorepo (por ejemplo, lerna) o usando enlaces simbólicos directamente. Tomé este último y lo resolví creando algunos scripts. El concepto es bastante sencillo: copio lo que se necesita dentro del directorio de funciones y lo elimino después

Así es como lo hice con esta estructura de directorio:
''

  • raíz/
    | - .firebaserc
    | - firebase.json
    | - ...
  • funciones /
    | - src /
    | - paquete.json
    | - pre-deploy.js
    | - post-deploy.js
    | - ....
  • compartido/
    | - src /
    | - paquete.json
    | - ....
content of `pre-deploy.js`

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

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

(asíncrono () => {
aguardar fs.remove ( ./shared );
aguardar 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");

(asíncrono () => {
aguardar 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)

"funciones": {
"fuente": "funciones",
"antes del despliegue": [
"npm --prefix" $ RESOURCE_DIR "ejecutar antes de la implementación"
],
"posterior a la implementación": [
"npm --prefix" $ RESOURCE_DIR "ejecutar después de la implementación"
]
},
''

Si realiza la compilación, dentro del directorio dist o lib ahora debería tener dos hermanos: funciones y compartidos (esto sucedió debido a la dependencia compartida). Asegúrese de actualizar las funciones package.json main para que apunten a lib/functions/src/index.js para que la implementación funcione.

Por ahora está resuelto, pero es una solución alternativa, no una solución. Creo que las herramientas de base de fuego realmente deberían admitir enlaces simbólicos

@michelepatrassi inspirado en lo que me has firelink para administrar este caso. Utiliza internamente rsync para copiar archivos recursivos.

https://github.com/rxdi/firelink

npm i -g @rxdi/firelink

Uso básico
Suponiendo que tiene un enfoque de monorepo y sus paquetes están ubicados 2 niveles por debajo del directorio actual donde se encuentra package.json .

package.json

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

Al ejecutar firelink , copiará los paquetes relacionados con las carpetas, luego mapeará los paquetes existentes con la instalación del módulo local "@graphql/database": "file:./.packages/database", luego ejecutará el comando firebase y pasará el resto de los argumentos de firelink comando.
Básicamente, firelink es un reemplazo de firebase CLI ya que genera firebase al final cuando termina su trabajo copiando packages y modificando package.json !

¡Saludos!

Nos acaba de morder esto, creo que monorepos se convertirá en el estándar y esto debería ser compatible de forma predeterminada.

Chicos, una solución simple para este problema es usar el paquete web para agrupar sus funciones en la nube e implementarlas desde el directorio incluido. Adjunto aquí hay un archivo de paquete web básico que estoy usando. Durante la compilación, esto empaquetará todo el código de funciones, incluidas las dependencias resueltas dentro del repositorio mono en una carpeta de nivel superior (en este caso, es webpack/cloud-functions , podría ser cualquier cosa que configure)

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'
  }
};

Y finalmente, en su archivo firebase.json , consulte esta carpeta para la implementación.

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

Recuerde que debe tener un package.json archivo en el webpack/cloud-functions carpeta también.

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

Esto está probado y funcionando. Estoy usando Google Cloud Build. Solicite más información si es necesario.

Gracias,

@sowdri ¡ Gracias por compartir! ¿Activa la compilación del paquete web desde el paso firebase.json functions.predeploy , o la activa desde el script de compilación en la nube antes de llamar a la implementación de la base de fuego?

@ 0x80 Lo estoy construyendo en el paso cloud build . Entonces, de acuerdo con firebase cli , es solo un conjunto de funciones creadas con JS

@sowdri Gracias por el ejemplo. Terminamos usando el mismo enfoque en un proyecto anterior para AWS Lambdas / Serverless: era más fácil cargar un paquete (Webpack) que forzar a las herramientas a trabajar con el hilo monorepo.

También estoy atascado con esto ... todo funcionó bien (incluso el emulador de base de fuego) hasta que intenté implementar funciones (la compilación de la aplicación web en reaccionar se implementa bien). :( Esperemos que el equipo de Firebase pueda agregar soporte para monorepos pronto.

Utilizo babel para otros paquetes en mi monorepo, así que preferiría quedarme con eso. Si nada más, puedo probar el paquete web ... que parece que funciona para algunos.

EDITAR: Resolví esto usando el registro de paquetes de GitHub. Entonces publico todos mis paquetes allí y luego por travis y functions servidor. Tengo que configurar el registro y proporcionar un token para la autenticación (hecho a través de .npmrc ). ... parece una solución elegante. Tengo la idea de este enfoque aquí: https://medium.com/gdgeurope/how-to-use-firebase-cloud-functions-and-yarn-workspaces-24ca35e941eb

Sí, esto también me mordió. Todo funcionó perfectamente en firebase serve --only functions pero cuando se implementó, no pudo ubicar un módulo.

Terminé construyendo un pequeño script para crear paquetes para mí. Si bien esto refleja detalles de mi entorno, como usar módulos y los nombres de mis paquetes que coincidan con los nombres de mi directorio, espero que sea útil para otros.

`` `importar fs desde" fs ";
importar child_process de "child_process";

const internalPackagesFull = new Map ();

// Encuentra todos los departamentos del paquete
const getDepsForPackage = (packageName) => {
const packageDir = packageName.split ("/") [1]; // ESTO PUEDE NECESITAR CAMBIAR PARA USTED
const packageSpecFileName = ../${packageDir}/package.json ;
const packageSpecFile = fs.readFileSync (packageSpecFileName);
const packageSpec = JSON.parse (packageSpecFile);
const packageInternalDeps = Object.keys (
packageSpec.dependencies
) .filter ((clave) => clave.includes ("turing")); // ESTO TENDRÁ QUE CAMBIAR PARA USTED

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

internalPackagesFull.set (packageName, {
packageSpecFileName,
packageSpec,
packageDir,
packageInternalDeps,
packageTgzName,
});

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

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

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

// escribe paquetes actualizados: usa js comunes y las referencias son archivos tgz locales
[... internalPackagesFull.values ​​()]. ​​forEach ((internalDep) => {
const {packageSpec, packageSpecFileName, packageInternalDeps} = internalDep;

// cambia el tipo de paquete
packageSpec.type = "commonjs"; // ESTO PUEDE NECESITAR CAMBIAR PARA USTED

// especifica la ubicación del dep para ser el archivo zip empaquetado
packageInternalDeps.forEach ((internalDepOfPackage) => {
const {packageTgzName} = internalPackagesFull.get (internalDepOfPackage);
packageSpec.dependencies [internalDepOfPackage] = ./${packageTgzName} ;
});

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

// ejecutar la construcción y el embalaje de hilo
[... internalPackagesFull.values ​​()]. ​​forEach ((internalDep) => {
tratar {
console.log ( Buliding ${internalDep.packageDir} );
child_process.execSync ("construcción de hilo", {
cwd: ../${internalDep.packageDir} ,
});
console.log ( Packaging ${internalDep.packageDir} );
child_process.execSync ("paquete de lanas", {
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: ".",
    }
  );
}

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

// volver a la estructura de paquetes estándar
[... internalPackagesFull.values ​​()]
.filter ((internalDep) => packageName! == internalDep.packageSpec.name)
.forEach ((internalDep) => {
const {
packageSpec,
packageSpecFileName,
packageInternalDeps,
} = internalDep;

// 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, "  ")
);

});
''

Usé la solución proporcionada por @sowdri (¡gracias por eso!), Con un pequeño ajuste para poder eliminar y regenerar libremente todo el directorio dist/ , incluido el package.json secundario:

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

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

Y luego guardo los siguientes package.dist.json en la raíz del paquete:

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

Podría ser posible eliminar las dependencias de este archivo y confiar en Webpack para agruparlas también (eliminando la necesidad de mantener estas dependencias sincronizadas con su package.json ), pero no lo he intentado.

Estoy usando un solo repositorio con un script para copiar .firebaserc & firebase.json para todos los directorios relevantes o proyectos de base de fuego usando el paquete npm

Creo package.json del package.json original para la implementación de funciones.
image

aquí está el 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);
})();

para la implementación, vaya a ./out/[project-name]/firebase deploy --only=functions o escriba un script para él también.

Me encuentro con algunas advertencias de Webpack como esta:

ADVERTENCIA en /Users/me/Development/myproject/node_modules/firebase-functions/lib/config.js 61: 23-42
Dependencia crítica: la solicitud de una dependencia es una expresión

¿Encontraste una manera de resolverlos o los estás ignorando / suprimiendo?

Logré que todo funcionara sin advertencias 🥳 Terminé con la siguiente configuración. Especialmente el uso de patrones de expresiones regulares para los externos marcó la diferencia porque si solo tiene "firebase-functions" en sus externos, cualquier importación que realice desde un submódulo no coincidirá y la biblioteca seguirá incluida en su paquete.

Como resultado de los problemas de agrupación, también encontré algunos errores crípticos de @grpc durante la implementación. Olvidé guardarlos como referencia.

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" }],
    }),
  ],
};

Resulta que no es necesario un package.dist.json por separado. Lo molesto de tener este archivo es, por supuesto, que debe actualizarlo manualmente cada vez que actualice cualquiera de las dependencias enumeradas allí. Entonces es muy fácil olvidar eso.

En su lugar, mueva todos los paquetes que _no_ debería_ enumerar en su package.dist.json a la lista devDependencies, y simplemente use ese archivo en el complemento de copia del paquete web. Ahora solo tienes un paquete.json con el que lidiar 🎉

Además, no quiero que mi versión local de nodejs sea necesariamente la misma que las funciones implementadas en la versión de nodo. Descubrí que ahora puede especificar functions.runtime en el archivo firebase.json. Así que saque el campo engines del package.json y en su lugar configure functions.runtime en "10" o "12" en la configuración de su base de fuego.

Esto es lo que parece para mí:

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

¿Cómo maneja los mapas de origen? Si agrupo mis funciones con el paquete web usando devtool: "inline-source-map" , no se recogen en el informe de errores de stackdriver. @sowdri

Me encontré con lo mismo en mi proyecto y esto fue un fastidio bastante grande ya que no quiero publicar todos los paquetes para un proyecto paralelo. Era molesto mantenerse al día con varios archivos package.json o tener que compilar fuera del paquete. Resulta que si incluye sus departamentos como opcionales, Lerna aún los recogerá y Firebase no se quejará cuando se cargue.

¡La siguiente configuración le brindará soporte para dep con enlaces simbólicos con un solo 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
    }
  }
}

Estoy usando espacios de trabajo de hilo, pero tampoco necesito mencionar los nombres de mis paquetes locales en otros archivos package.json. Estoy implementando con éxito tanto en Firebase como en Vercel sin él.

No estoy seguro de qué lo hace funcionar. Solo tenga esta configuración estándar en mi package.json de nivel superior:

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

No me importa tener un package.json en cada una de las carpetas / packages / *, ya que ejecutar yarn upgrade-interactive solo los manejará todos de una vez. En algunos paquetes, encuentro útil poder agregar un script específicamente para ese alcance.

---- editar ----

Olvidé mencionar que estoy usando TypeScript con referencias de proyectos. Eso podría tener algo que ver con eso. Para Vercel, estoy usando next-transpile-modules para incluir mi código compartido en el paquete.

Estoy usando una configuración similar a @ 0x80 y @sowdri para que esto funcione, pero en lugar de mover mis dependencias locales a devDependencies (que en realidad no funcionó para mí, creo que me faltaba un paso) y uso copy-webpack-plugin , Estoy usando generate-package-json-webpack-plugin para construir mi package.json en la carpeta dist. Esto crea mi lista de dependencias a partir de lo que realmente requiere el código resultante, por lo que si está incluido o es una dependencia de desarrollo, no se incluye en el package.json resultante.

También configuré mis externos usando webpack-node-externals para hacer que todo sea externo, excepto mis paquetes monorepo enlazados simbólicamente, que estoy usando regex para que coincida con el prefijo del nombre del proyecto. También agregué las expresiones regex para los paquetes firebase @ 0x80 publicados como externos adicionales.

Esta es mi configuración

/* 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)]
};

El otro beneficio de usar webpack para empaquetar el código es que finalmente puedo usar alias de módulo :)

¿Fue útil esta página
0 / 5 - 0 calificaciones