Firebase-tools: Suporte mono-repos na implantação

Criado em 31 jan. 2018  ·  47Comentários  ·  Fonte: firebase/firebase-tools

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

Informação da versão

3.17.3

Passos para reproduzir

Comportamento esperado

Comportamento real

feature request

Comentários muito úteis

+1 sobre isso, as funções de nuvem geralmente precisam compartilhar algum código comum (por exemplo, interfaces) com outros aplicativos e uma boa maneira de lidar com isso é um monorepo (por exemplo, lerna) ou usando links simbólicos diretamente. Peguei o último e resolvi criando alguns scripts. O conceito é bastante fácil: copio o que é necessário dentro do diretório de funções e removo depois

Veja como fiz isso com esta estrutura de diretório:
`` `

  • raiz/
    | - .firebaserc
    | - firebase.json
    | - ...
  • funções/
    | - src /
    | - package.json
    | - pre-deploy.js
    | - post-deploy.js
    | - ....
  • compartilhado/
    | - src /
    | - package.json
    | - ....
content of `pre-deploy.js`

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

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

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

(assíncrono () => {
aguarde 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)

"funções": {
"fonte": "funções",
"predeploy": [
"npm --prefix" $ RESOURCE_DIR "executar pré-implantação"
],
"postdeploy": [
"npm --prefix" $ RESOURCE_DIR "executar pós-implantação"
]
},
`` `

Se você fizer a construção, dentro do diretório dist ou lib você agora deve ter dois irmãos: functions e shared (isso aconteceu por causa da dependência compartilhada). Certifique-se de atualizar as funções package.json main para apontar para lib/functions/src/index.js para fazer a implantação funcionar.

Por enquanto, está resolvido, mas isso é uma solução alternativa, não uma solução. Eu acho que as ferramentas do firebase devem realmente oferecer suporte a links simbólicos

Todos 47 comentários

Isso parece funcionar na minha configuração, ou seja, deploy coleta pacotes da raiz node_modules , embora package.json para isso esteja localizado na área de trabalho api/ ( Usei um nome de pasta diferente em vez de functions/ ). Há mais alguma coisa que precisa ser consertada aqui?

EDITAR: Além disso, copio package.json em api/dist para ser usado por deploy .

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

Portanto, 2 níveis de aninhamento ainda resolvem a raiz node_modules sucesso.

@dinvlad você poderia compartilhar um

@orouz infelizmente ainda não, é um código fechado por enquanto.

Alguém conseguiu resolver este problema? Compartilhar um projeto de exemplo simples seria muito útil.

@audkar Atualmente, eu apenas uso lerna.js.org e é o comando run para executar um script npm em cada subpasta com esta estrutura de pastas:

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

Garantir que os arquivos firebase.json para cada serviço não colidam uns com os outros é responsabilidade do usuário. Convenções simples de uso de grupos de funções e segmentação por nome de vários sites significam que isso é resolvido para Cloud Functions and Hosting. Ainda não tenho uma solução para as regras do Firestore / GCS, embora dividi-las possa não ser o ideal ...

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

@jthegedus obrigado por sua resposta. Mas acho que a questão desse tíquete é diferente. Estou tentando usar espaços de trabalho de fios. E parece que as ferramentas do firebase não captam dependências de links simbólicos ao enviar funções

É justo, eu mesma evitei aquela toca de coelho

Você poderia explicar qual é o problema? Como mencionado acima, eu apenas uso Yarn puro com api e app espaços de trabalho nele, e os construo usando yarn workspace api build && yarn workspace app build (com build script específico para cada área de trabalho). Os scripts de construção
1) compilar o código TS com outDir em api/dist e app/dist respectivamente
2) copiar os correspondentes package.json arquivos em dist diretórios
3) copie yarn.lock da pasta _root_ para os diretórios dist

Então eu apenas executo yarn firebase deploy da pasta _root_, e ele pega api/dist e app/dist sem soluços. Meu firebase.json parece

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

Infelizmente, ainda não consigo compartilhar o código completo, mas essa configuração é tudo o que importa, afaik.

Além disso, posso estar errado, mas acho que o script firebase deploy não usa realmente seu diretório node_modules . Acho que apenas pega o código package.json e yarn.lock dos diretórios dist e faz o resto.

Isso é verdade. O valor padrão de "functions.ignore" em firebase.json é
["node_modules"] então não é carregado. Eu acredito que você pode substituir isso
embora se você quiser enviar alguns módulos locais.

Na segunda-feira, 17 de junho de 2019, 18:58 Denis Loginov [email protected]
escreveu:

Além disso, posso estar errado, mas acho que o script de implantação do Firebase não
realmente use seu diretório node_modules. Eu acho que apenas pega o
cod, package.json e yarn.lock dos diretórios dist e faz o
descanso.

-
Você está recebendo isto porque está inscrito neste tópico.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/firebase/firebase-tools/issues/653?email_source=notifications&email_token=ACATB2U73VS2KIILUVRFFB3P3A6NPA5CNFSM4EOR24GKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODX46ABQ#issuecomment-502915078 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/ACATB2U3Q2TLVBICRJ3B5OLP3A6NPANCNFSM4EOR24GA
.

@dinvlad Sim, requer package.json e qualquer arquivo de bloqueio que você usar, pois ele instala os deps na pós-implantação da nuvem.

Acredito que o cenário originalmente descrito na outra edição estava usando um pacote compartilhado dentro do espaço de trabalho e alguns problemas com levantamento de escopo. Como eu não estava usando fios dessa forma, posso apenas especular com o que li lá.

@samtstern @jthegedus obrigado, é bom saber!

Parece que todos falamos sobre problemas diferentes. Vou tentar descrever o problema de yarn workspaces .

Projeto problemático

layout do projeto

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

_./package.json_

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

_functions / package.json_

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

Problema

Erro durante a implantação da função:

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

As funções funcionam bem localmente no emulador.

Soluções tentadas

Enviando node_modules (usando functions.ignore em _firebase.json_). O resultado é o mesmo.

Meu palpite é que utilities foi criado como syslink em _node-modules_ node_modules/utilities -> ../../utilities

Será que firebase-tools não inclui conteúdo de módulos com links simbólicos durante o upload (sem desreferenciamento)?

Desculpe, você poderia esclarecer em qual pasta seu firebase.json reside (e mostrar sua seção de configuração para functions )?

_firebase.json_ estava na pasta raiz. A configuração era padrão. Smth assim:

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

tudo foi implantado conforme o esperado (incluindo _node_modules_) exceto node_modules/utilities que é um link simbólico.


Consigo contornar esse problema escrevendo alguns scripts que:

  • crie pacotes para cada espaço de trabalho ( yarn pack ). por exemplo, isso cria _utilities.tgz_.
  • movendo todas as saídas para algum diretório específico.
  • modificar _package.json_ para usar arquivos tgz para dependências de espaço de trabalho. por exemplo, dependencies { "utilities": "1.0.0" -> dependencies { "utilities": "file:./utilities.tgz"
  • implantar esse dir no firebase

envie o conteúdo do dir antes do upload:

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

@audkar Hoje encontrei o mesmo problema que você.

Eu sou novo nos espaços de trabalho do Lerna e do Yarn. Pelo que entendi, você também pode simplesmente usar Lerna. Isso ajudaria de alguma forma?

Sua solução alternativa parece um pouco complicada para mim 🤔

Também está se perguntando: para que serve `--cwd" $ RESOURCE_DIR "?

--cwd significa "diretório de trabalho atual" e $RESOURCE_DIR contém o valor do diretório de origem ( functions neste caso). Adicionar este sinalizador fará com que yarn seja executado em functions dir em vez de root

@audkar Ah entendo. Então você poderia fazer o mesmo com yarn workspace functions lint e yarn workspace functions build

@dinvlad Não está claro para mim por que você está direcionando a pasta dist e copiando as coisas lá. Se você construir para dist, mas deixar o package.json onde está e apontar main para dist/index.js então as coisas devem funcionar da mesma forma, não? Você deve então definir a origem como api em vez de api / dist.

@dinvlad Aprendi o comando yarn workspace com seus comentários, mas não consigo fazê-lo funcionar por algum motivo. Veja aqui . Qualquer ideia?

Desculpe por sair um pouco do assunto aqui. Talvez comentar no SO, para minimizar o ruído.

@ 0x80 Eu copio package.json para api/dist e aponto firebase.json para api/dist para que apenas os arquivos "construídos" sejam empacotados dentro da função de nuvem. Não tenho certeza do que acontecerá se eu apontar firebase.json para api - talvez ainda seja inteligente o suficiente para empacotar apenas o que está dentro de api/dist (com base em main atributo em package.json ). Mas achei mais limpo apontar apenas para api/dist .

Re yarn workspace , respondi em SO ;)

@dinvlad ele

Agora usei uma solução alternativa semelhante para @audkar.

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

Então, o script pre-deploy-cloud-functions é:

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

E packages / cloud-functions tem um arquivo gitignore extra:

yarn.lock
*.tgz

aqui está o que funcionou para mim

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

e no root/firebase.json :
`` `
{
"funções": {
"predeploy": "npm --prefix" $ RESOURCE_DIR "run build",
"fonte": "pacotes / funções"
}
}
`` ``

@kaminskypavel seus pacotes / funções dependem de packages / package1 (ou algum outro pacote irmão)?

@ 0x80 positivo.

Acho que havia algo fundamental que não entendi sobre monorepos. Presumi que você pode compartilhar um pacote e implantar um aplicativo usando esse pacote sem realmente publicar o pacote compartilhado no NPM.

Parece que isso não é possível, porque implantações como Firebase ou Now.sh geralmente carregam o código e, em seguida, na nuvem, fazem uma instalação e compilação. Estou correcto?

@kaminskypavel Tentei sua abordagem e aqui

@audkar Você está publicando seu pacote comum no NPM ou, como eu, tentando implementar com código compartilhado que não foi publicado?

@ 0x80 Estou com você neste entendimento - acho que as implantações da função Firebase estão apenas (erroneamente) assumindo que todos os pacotes nomeados em package.json estarão disponíveis no npm, em nome da aceleração das implantações.

À medida que as configurações do espaço de trabalho do yarn estão se tornando mais populares, imagino que mais pessoas ficarão surpresas por não poderem usar pacotes com links simbólicos no Firebase Functions - especialmente porque eles funcionam bem até você implantar.

Com o npm adicionando suporte para espaços de trabalho , temos um padrão de ecossistema para como os pacotes locais devem funcionar.

Como esse problema já existe há mais de um ano, alguma atualização do lado do Firebase sobre os planos (ou falta de planos) aqui?

Acho que é uma oportunidade muito legal - a gama de serviços do Firebase implora por uma boa configuração de monorepo.

+1 sobre isso, as funções de nuvem geralmente precisam compartilhar algum código comum (por exemplo, interfaces) com outros aplicativos e uma boa maneira de lidar com isso é um monorepo (por exemplo, lerna) ou usando links simbólicos diretamente. Peguei o último e resolvi criando alguns scripts. O conceito é bastante fácil: copio o que é necessário dentro do diretório de funções e removo depois

Veja como fiz isso com esta estrutura de diretório:
`` `

  • raiz/
    | - .firebaserc
    | - firebase.json
    | - ...
  • funções/
    | - src /
    | - package.json
    | - pre-deploy.js
    | - post-deploy.js
    | - ....
  • compartilhado/
    | - src /
    | - package.json
    | - ....
content of `pre-deploy.js`

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

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

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

(assíncrono () => {
aguarde 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)

"funções": {
"fonte": "funções",
"predeploy": [
"npm --prefix" $ RESOURCE_DIR "executar pré-implantação"
],
"postdeploy": [
"npm --prefix" $ RESOURCE_DIR "executar pós-implantação"
]
},
`` `

Se você fizer a construção, dentro do diretório dist ou lib você agora deve ter dois irmãos: functions e shared (isso aconteceu por causa da dependência compartilhada). Certifique-se de atualizar as funções package.json main para apontar para lib/functions/src/index.js para fazer a implantação funcionar.

Por enquanto, está resolvido, mas isso é uma solução alternativa, não uma solução. Eu acho que as ferramentas do firebase devem realmente oferecer suporte a links simbólicos

@michelepatrassi inspirado no que você me lembrou que criei a biblioteca firelink para gerenciar este caso. Ele usa internamente rsync para copiar arquivos recursivos.

https://github.com/rxdi/firelink

npm i -g @rxdi/firelink

Uso básico
Supondo que você tenha uma abordagem monorepo e seus pacotes estejam localizados 2 níveis abaixo do diretório atual onde package.json está localizado.

package.json

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

Executando firelink ele irá Copiar os pacotes relacionados com as pastas e então mapear os pacotes existentes com a instalação do módulo local "@graphql/database": "file:./.packages/database", então irá executar o comando firebase e irá passar o resto dos argumentos de firelink comando
Basicamente firelink é um substituto para firebase CLI, pois gera firebase no final, quando termina seu trabalho, copiando packages e modificando package.json !

Cumprimentos!

Acabamos de ser mordidos por isso, acho que monorepos se tornará o padrão e isso deve ser suportado por padrão.

Pessoal, uma solução simples para este problema é usar o webpack para agrupar suas funções de nuvem e implantar a partir do diretório agrupado. Anexado aqui está um arquivo webpack básico que estou usando. Durante a construção, isso irá empacotar todo o código de funções, incluindo as dependências resolvidas dentro do mono-repo para uma pasta de nível superior (neste caso é webpack/cloud-functions , pode ser qualquer coisa que você configurar)

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

E, finalmente, em seu arquivo firebase.json , consulte esta pasta para implantação.

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

Lembre-se de ter um package.json arquivo no webpack/cloud-functions pasta também.

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

Isso foi testado e está funcionando. Estou usando o Google Cloud Build. Peça mais informações, se necessário.

Obrigado,

@sowdri Obrigado por compartilhar! Você aciona a compilação do webpack a partir da etapa firebase.json functions.predeploy ou a partir do script de compilação em nuvem antes de chamar o firebase deploy?

@ 0x80 Estou construindo na etapa cloud build . Então, de acordo com firebase cli é apenas um conjunto de funções construídas com JS

@sowdri Obrigado pelo exemplo. Acabamos usando a mesma abordagem em um projeto anterior para AWS Lambdas / Serverless: era mais fácil carregar um pacote (Webpack) do que forçar as ferramentas a trabalhar com o monorepo yarn.

Eu também estou preso com isso ... tudo funcionou bem (até o emulador do Firebase) até que eu tentei implantar funções (compilação do aplicativo da web em react implanta OK). :( Esperamos que a equipe do firebase possa adicionar suporte para monorepos em breve.

Eu uso o babel para outros pacotes do meu monorepo, então prefiro ficar com isso. Se nada mais posso tentar o webpack ... que parece estar funcionando para alguns.

EDIT: Eu resolvi isso usando o registro de pacote do GitHub. Então eu publico todos os meus pacotes lá e então para travis e functions server, eu tenho que configurar o registro e fornecer um token para autenticação (feito via .npmrc ). ... parece uma solução elegante. Tive a ideia para essa abordagem aqui: https://medium.com/gdgeurope/how-to-use-firebase-cloud-functions-and-yarn-workspaces-24ca35e941eb

Sim, também fui mordido por isso. Tudo funcionou perfeitamente em firebase serve --only functions mas quando implantado, não foi possível localizar um módulo.

Acabei construindo um pequeno script para construir pacotes para mim. Embora isso reflita particularidades do meu ambiente, como o uso de módulos e nomes de pacotes correspondentes aos nomes de meus diretórios, espero que seja útil para outros.

`` `importe fs de" fs ";
importar child_process de "child_process";

const internalPackagesFull = new Map ();

// Encontre todas as dependências para o pacote
const getDepsForPackage = (packageName) => {
const packageDir = packageName.split ("/") [1]; // ISTO PODE PRECISAR DE ALTERAR PARA VOCÊ
const packageSpecFileName = ../${packageDir}/package.json ;
const packageSpecFile = fs.readFileSync (packageSpecFileName);
const packageSpec = JSON.parse (packageSpecFile);
const packageInternalDeps = Object.keys (
packageSpec.dependencies
) .filter ((key) => key.includes ("turing")); // ISTO PRECISA MUDAR PARA VOCÊ

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")). name;
child_process.execSync ( cp ./package.json ./package.json.org );
getDepsForPackage (packageName);

// escrever pacotes atualizados - usar js comuns e as referências são arquivos tgz locais
[... internalPackagesFull.values ​​()]. ​​forEach ((internalDep) => {
const {packageSpec, packageSpecFileName, packageInternalDeps} = internalDep;

// mude o tipo de pacote
packageSpec.type = "commonjs"; // ISTO PODE PRECISAR DE ALTERAR PARA VOCÊ

// especifique a localização do dep para ser o arquivo zip empacotado
packageInternalDeps.forEach ((internalDepOfPackage) => {
const {packageTgzName} = internalPackagesFull.get (internalDepOfPackage);
packageSpec.dependencies [internalDepOfPackage] = ./${packageTgzName} ;
});

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

// execute a construção e embalagem do fio
[... internalPackagesFull.values ​​()]. ​​forEach ((internalDep) => {
Experimente {
console.log ( Buliding ${internalDep.packageDir} );
child_process.execSync ("yarn build", {
cwd: ../${internalDep.packageDir} ,
});
console.log ( Packaging ${internalDep.packageDir} );
child_process.execSync ("yarn pack", {
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: ".",
    }
  );
}

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

// volta para a estrutura de pacotes padrão
[... 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, "  ")
);

});
`` `

Usei a solução fornecida por @sowdri (obrigado por isso!), Com um pequeno ajuste para que eu possa excluir e regenerar livremente todo o diretório dist/ , incluindo o package.json secundário:

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

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

E então mantenho o seguinte package.dist.json na raiz do pacote:

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

Pode ser possível remover as dependências desse arquivo e contar com o Webpack para empacotá-las também (removendo a necessidade de manter essas dependências sincronizadas com seu package.json ), mas não tentei.

Estou usando um único repositório com um script para copiar .firebaserc & firebase.json para todos os diretórios relevantes ou projetos firebase usando o pacote npm

Eu crio package.json do package.json original para implantação de funções.
image

aqui está o 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 implantação, vá para ./out/[project-name]/firebase deploy --only=functions ou escreva um script para ele também.

Estou recebendo alguns avisos do Webpack como este:

AVISO em /Users/me/Development/myproject/node_modules/firebase-functions/lib/config.js 61: 23-42
Dependência crítica: o pedido de uma dependência é uma expressão

Você encontrou uma maneira de resolver isso ou está os ignorando / suprimindo?

Consegui fazer as coisas funcionarem sem avisos 🥳 Acabei com a configuração abaixo. Especialmente o uso de padrões regex para os externos fez uma diferença porque se você tiver apenas "firebase-functions" em seus externos, qualquer importação que você fizer de um submódulo não será correspondida e a biblioteca ainda estará incluída em seu pacote.

Como resultado de problemas de empacotamento, também encontrei alguns erros crípticos de @grpc durante a implantação. Esqueci de guardá-los para referência.

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

Acontece que não há necessidade de um package.dist.json separado. A coisa chata de ter esse arquivo é, claro, que você precisa atualizá-lo manualmente toda vez que atualizar qualquer uma das dependências listadas lá. Portanto, é muito fácil esquecer isso.

Em vez disso, mova todos os pacotes que você _não_ listou em seu package.dist.json para a lista devDependencies e apenas use esse arquivo no plugin de cópia do webpack. Agora você só tem um único package.json para lidar com 🎉

Além disso, não quero minha versão do nodejs local necessariamente igual à versão do node implantado das funções. Descobri que agora você pode especificar functions.runtime no arquivo firebase.json. Portanto, retire o campo engines do package.json e, em vez disso, defina functions.runtime como "10" ou "12" na configuração do Firebase.

Isso é o que parece para mim:

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

Como você está lidando com mapas de origem? Se eu agrupar minhas funções com o webpack usando devtool: "inline-source-map" ele não será detectado no relatório de erro do stackdriver. @sowdri

Encontrei a mesma coisa no meu projeto e isso foi uma grande chatice, já que não quero publicar todos os pacotes de um projeto paralelo. Era irritante manter-se atualizado com vários arquivos package.json ou ter que construir fora do pacote. Acontece que, se você incluir seus recursos como opcionais, o Lerna ainda os selecionará e o Firebase não reclamará quando estiver fazendo o upload.

A configuração abaixo fornecerá suporte dep com link simbólico com um único 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
    }
  }
}

Estou usando espaços de trabalho do yarn, mas também não preciso mencionar os nomes dos meus pacotes locais em outros arquivos package.json. Estou implantando no Firebase e no Vercel com sucesso sem ele.

Não tenho certeza do que o faz funcionar. Basta ter esta configuração padrão em meu package.json de nível superior:

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

Não me importo de ter um package.json em cada uma das pastas / packages / *, já que a execução de yarn upgrade-interactive irá lidar com todos eles de uma vez. Em alguns pacotes, acho útil poder adicionar script especificamente para esse escopo.

---- editar ----

Esqueci de mencionar que estou usando Typescript com referências de projeto. Isso pode ter algo a ver com isso. Para Vercel, estou usando next-transpile-modules para incluir meu código compartilhado no pacote.

Estou usando uma configuração semelhante a @ 0x80 e @sowdri para fazer isso funcionar, mas em vez de mover minhas dependências locais para devDependencies (que na verdade não funcionou para mim, acho que estava faltando uma etapa) e usando copy-webpack-plugin , Estou usando generate-package-json-webpack-plugin para construir meu package.json na pasta dist. Isso cria minha lista de dependências a partir do que o código resultante está realmente exigindo, portanto, se for empacotado ou uma dependência dev, não está incluída no package.json resultante.

Também configurei meus externos usando webpack-node-externals para tornar tudo externo, exceto meus pacotes monorepo com link simbólico, que estou usando regex para corresponder ao prefixo do nome do projeto. Também adicionei as expressões regex para os pacotes firebase @ 0x80 postados como

Esta é a minha configuração

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

O outro benefício de usar o webpack para agrupar o código é que posso finalmente usar aliases de módulo :)

Esta página foi útil?
0 / 5 - 0 avaliações