์ฐธ์กฐ: https://github.com/firebase/firebase-functions/issues/172
3.17.3
์ด๊ฒ์ ์ฆ, ๋ด ์ค์ ์์ ์์
๋ณด์ธ๋ค deploy
์ข
๋ชฉ ๋ฃจํธ์์ ํจํค์ง๊น์ง node_modules
, ๋น๋ก package.json
๊ทธ ์๋์ ์์น์ ๋ํ api/
์์
๊ณต๊ฐ ( functions/
๋์ ๋ค๋ฅธ ํด๋ ์ด๋ฆ์ ์ฌ์ฉํ์ต๋๋ค. ์ฌ๊ธฐ์ ์์ ํด์ผ ํ ๋ค๋ฅธ ๊ฒ์ด ์์ต๋๊น?
ํธ์ง : ๋ํ, ๋๋ ๋ณต์ฌ package.json
์ api/dist
์ ์ํด ์ฌ์ฉ๋๋ deploy
.
// firebase.json
...
"functions": {
"source": "api/dist"
},
...
๋ฐ๋ผ์ 2๋จ๊ณ ์ค์ฒฉ์ ์ฌ์ ํ โโ๋ฃจํธ node_modules
์ฑ๊ณต์ ์ผ๋ก ํด๊ฒฐํฉ๋๋ค.
@dinvlad ๋ฆฌํฌ์งํ ๋ฆฌ ๋ฅผ ๊ณต์ ํ ์ ์์ต๋๊น?
@orouz๋ ๋ถํํ๋ ์์ง ์๋์ง๋ง ํ์ฌ๋ก์๋ ๋น๊ณต๊ฐ ์์ค์ ๋๋ค.
๋๊ตฌ๋ ์ง์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์์ต๋๊น? ๊ฐ๋จํ ์์ ํ๋ก์ ํธ๋ฅผ ๊ณต์ ํ๋ ๊ฒ์ ๋งค์ฐ ์ ์ฉํ ๊ฒ์ ๋๋ค.
@audkar ํ์ฌ ๋ ๊ทธ๋ฅ ์ฌ์ฉ lerna.js.org ๊ทธ๊ฒ์์ด๋ค run
์ด ํด๋ ๊ตฌ์กฐ์ ๊ฐ ํ์ ํด๋์ NPM ์คํฌ๋ฆฝํธ๋ฅผ ์คํํ๋ ๋ช
๋ น
- service1/
| - .firebaserc
| - firebase.json
- service2/
| - .firebaserc
| - firebase.json
- app1/
| - .firebaserc
| - firebase.json
- app2/
| - .firebaserc
| - firebase.json
- firestore/
| - firestore.rules
| - firestore.indexes.json
- etc...
๊ฐ ์๋น์ค์ ๋ํ firebase.json
ํ์ผ์ด ์๋ก๋ฅผ ์ง๋ฐ์ง ์๋๋ก ํ๋ ๊ฒ์ ์ฌ์ฉ์์๊ฒ ๋ฌ๋ ค ์์ต๋๋ค. ํจ์ ๊ทธ๋ฃน ๋ฐ ๋ค์ค ์ฌ์ดํธ ์ด๋ฆ ํ๊ฒํ
์ ์ฌ์ฉํ๋ ๊ฐ๋จํ ๊ท์น์ ์ด๊ฒ์ด Cloud Functions ๋ฐ Hosting์์ ํด๊ฒฐ๋์์์ ์๋ฏธํฉ๋๋ค. Firestore/GCS ๊ท์น์ ๋ํ ์๋ฃจ์
์ ์์ง ์์ง๋ง ๋ถํ ํ๋ ๊ฒ์ด ์ด์์ ์ด์ง ์์ ์ ์์ต๋๋ค...
์ด์ ์ ์ฌ๊ธฐ์์ ๋ ผ์๋จ - https://github.com/firebase/firebase-tools/issues/1116
@jgedus ๋ต๋ณ ๊ฐ์ฌํฉ๋๋ค. ํ์ง๋ง ์ด ํฐ์ผ์ ๋ฌธ์ ๋ ๋ค๋ฅด๋ค๊ณ ์๊ฐํฉ๋๋ค. ์์ฌ ์์ ๊ณต๊ฐ์ ์ฌ์ฉํ๋ ค๊ณ ํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ Firebase ๋๊ตฌ๋ ๊ธฐ๋ฅ์ ์ ๋ก๋ํ ๋ ์ฌ๋ณผ๋ฆญ ๋งํฌ ์ข ์์ฑ์ ์ ํํ์ง ์๋ ๊ฒ ๊ฐ์ต๋๋ค.
์ ๋ง๋ค ๊ทธ ํ ๋ผ๊ตด์ ๋ด๊ฐ ํผํ์ด
๋ฌธ์ ๊ฐ ๋ฌด์์ธ์ง ์์ธํ ์ค๋ช
ํด ์ฃผ์๊ฒ ์ต๋๊น? ์์์ ์ธ๊ธํ๋ฏ์ด api
๋ฐ app
์์
๊ณต๊ฐ์ด ์๋ ๋ฒ ์ด Yarn์ ์ฌ์ฉํ๊ณ yarn workspace api build && yarn workspace app build
(๊ฐ๊ฐ์ ํน์ ํ build
์คํฌ๋ฆฝํธ ํฌํจ)๋ฅผ ์ฌ์ฉํ์ฌ ๋น๋ํฉ๋๋ค. ์์
๊ณต๊ฐ). ๋น๋ ์คํฌ๋ฆฝํธ
1) outDir
๋ฅผ ์ฌ์ฉํ์ฌ TS ์ฝ๋๋ฅผ ๊ฐ๊ฐ api/dist
๋ฐ app/dist
์ปดํ์ผํฉ๋๋ค.
2) ํด๋น package.json
ํ์ผ์ dist
๋๋ ํ ๋ฆฌ์ ๋ณต์ฌํฉ๋๋ค.
3) _root_ ํด๋์์ yarn.lock
๋ฅผ dist
๋๋ ํ ๋ฆฌ๋ก ๋ณต์ฌํฉ๋๋ค.
๊ทธ๋ผ ๋ ๊ทธ๋ฅ ์คํ yarn firebase deploy
_root_ ํด๋๋ฅผ, ๊ทธ๋ฆฌ๊ณ ๊ทธ๊ฒ์ ์ง์ด ๋ค๊ณ ๋ชจ๋ api/dist
๋ฐ app/dist
์ด๋ค ๋ธ๊พน์ง์์ด. ๋ด firebase.json
๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
"functions": {
"source": "api/dist"
},
"hosting": {
"public": "app/dist",
๋ถํํ๋ ์์ง ์ ์ฒด ์ฝ๋๋ฅผ ๊ณต์ ํ ์๋ ์์ง๋ง ์ด ์ค์ ์ด ๊ฐ์ฅ ์ค์ํฉ๋๋ค.
๋ํ ๋ด๊ฐ ํ๋ฆด ์๋ ์์ง๋ง firebase deploy
์คํฌ๋ฆฝํธ๊ฐ ์ค์ ๋ก node_modules
๋๋ ํ ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ง ์๋๋ค๊ณ ์๊ฐํฉ๋๋ค. dist
๋๋ ํ ๋ฆฌ์์ package.json
๋ฐ yarn.lock
๋ฅผ ์ ํํ๊ณ ๋๋จธ์ง๋ ์ํํ๋ค๊ณ ์๊ฐํฉ๋๋ค.
๊ทธ๊ฑด ์ฌ์ค์ด์ผ. firebase.json์์ "functions.ignore"์ ๊ธฐ๋ณธ๊ฐ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
["node_modules"] ์
๋ก๋๋์ง ์์ต๋๋ค. ๋๋ ๋น์ ์ด ๊ทธ๊ฒ์ ๋ฌด์ํ ์ ์๋ค๊ณ ๋ฏฟ์ต๋๋ค
๊ทธ๋ฌ๋ ์ผ๋ถ ๋ก์ปฌ ๋ชจ๋์ ๋ฐฐ์กํ๋ ค๋ ๊ฒฝ์ฐ.
2019๋
6์ 17์ผ ์์์ผ ์คํ 6:58 Denis Loginov [email protected]
์ผ๋ค:
๋ํ ๋ด๊ฐ ํ๋ฆด ์๋ ์์ง๋ง Firebase ๋ฐฐํฌ ์คํฌ๋ฆฝํธ๊ฐ
์ค์ ๋ก node_modules ๋๋ ํ ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ญ์์ค. ๋๋ ๊ทธ๊ฒ์ด ๋จ์ง ์ง์ด ๋ ๋ค๊ณ ์๊ฐํ๋ค.
dist ๋๋ ํ ๋ฆฌ์์ cod, package.json ๋ฐ yarn.lock์ ์ํํ๊ณ
์ฌ๋ค.โ
์ด ์ค๋ ๋์ ๊ฐ์ ํ๊ธฐ ๋๋ฌธ์ ์ด ๋ฉ์์ง๋ฅผ ๋ฐ๊ณ ์์ต๋๋ค.
์ด ์ด๋ฉ์ผ์ ์ง์ ๋ต์ฅํ๊ณ GitHub์์ ํ์ธํ์ธ์.
https://github.com/firebase/firebase-tools/issues/653?email_source=notifications&email_token=ACATB2U73VS2KIILUVRFFB3P3A6NPA5CNFSM4EOR24GKYY3PNVWWissue3TUL52HS4DFVREWSG43VMVBW63
๋๋ ์ค๋ ๋ ์์๊ฑฐ
https://github.com/notifications/unsubscribe-auth/ACTB2U3Q2TLVBICRJ3B5OLP3A6NPANCNFSM4EOR24GA
.
@dinvlad ์, package.json
๋ฐ ํด๋ผ์ฐ๋ ์ฌํ ๋ฐฐํฌ์ deps๋ฅผ ์ค์นํ ๋ ์ฌ์ฉํ๋ ์ ๊ธ ํ์ผ์ด ํ์ํฉ๋๋ค.
๋ค๋ฅธ ๋ฌธ์ ์์ ์๋ ์ค๋ช ๋ ์๋๋ฆฌ์ค๋ ์์ ๊ณต๊ฐ ๋ด์์ ๊ณต์ ํจํค์ง๋ฅผ ์ฌ์ฉํ๊ณ ๋ฒ์ ํธ์ด์คํ ๊ณผ ๊ด๋ จ๋ ๋ช ๊ฐ์ง ๋ฌธ์ ๋ผ๊ณ ์๊ฐํฉ๋๋ค. ๋๋ ์ด๋ฐ ์์ผ๋ก ์์ฌ๋ฅผ ์ฌ์ฉํ์ง ์์๊ธฐ ๋๋ฌธ์ ๋ด๊ฐ ๊ทธ๊ณณ์์ ์ฝ์ ๊ฒ์ผ๋ก๋ง ์ถ์ธกํ ์ ์์ต๋๋ค.
@samtstern @jthegedus ๊ฐ์ฌํฉ๋๋ค.
์ฐ๋ฆฌ๋ ๋ชจ๋ ๋ค๋ฅธ ๋ฌธ์ ์ ๋ํด ์ด์ผ๊ธฐํ๋ ๊ฒ ๊ฐ์ต๋๋ค. yarn workspaces
๋ฌธ์ ๋ฅผ ์ค๋ช
ํ๋ ค๊ณ ํฉ๋๋ค.
ํ๋ก์ ํธ ๋ ์ด์์
- utilities/
| - package.json
- functions/
| - package.json
- package.json
__./ํจํค์ง.json_
{
"private": true,
"workspaces": ["functions", "utilities"]
}
_ํจ์/ํจํค์ง.json_
{
<...>
"dependencies": {
"utilities": "1.0.0",
<...>
}
}
ํจ์ ๋ฐฐํฌ ์ค ์ค๋ฅ:
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"}}
ํจ์๋ ์๋ฎฌ๋ ์ดํฐ์์ ๋ก์ปฌ๋ก ์ ์๋ํฉ๋๋ค.
node_modules
์
๋ก๋ ์ค(_firebase.json_์์ functions.ignore
). ๊ฒฐ๊ณผ๋ ๋์ผํฉ๋๋ค.
utilities
๊ฐ _node-modules_ node_modules/utilities -> ../../utilities
์์ syslink๋ก ์์ฑ๋์๊ธฐ ๋๋ฌธ์ด๋ผ๊ณ ์๊ฐํฉ๋๋ค.
firebase-tools๊ฐ ์ ๋ก๋ํ ๋(์ญ์ฐธ์กฐ ์์) ์ฌ๋ณผ๋ฆญ ๋งํฌ๋ ๋ชจ๋์ ๋ด์ฉ์ ํฌํจํ์ง ์์ ์ ์์ต๋๊น?
์ฃ์กํฉ๋๋ค. firebase.json
์๋ ํด๋๋ฅผ ๋ช
ํํ ํ๊ณ functions
๋ํ ๊ตฌ์ฑ ์น์
์ ํ์ํ ์ ์์ต๋๊น?
_firebase.json_์ ๋ฃจํธ ํด๋์ ์์ต๋๋ค. ๊ตฌ์ฑ์ ํ์ค์ด์์ต๋๋ค. ๋ค์๊ณผ ๊ฐ์ด smth:
"functions": {
"predeploy": [
"yarn --cwd \"$RESOURCE_DIR\" run lint",
"yarn --cwd \"$RESOURCE_DIR\" run build"
],
"source": "functions",
"ignore": []
},
<...>
symlink์ธ node_modules/utilities
๋ฅผ ์ ์ธํ๊ณ ๋ชจ๋ ๊ฒ์ด ์์๋๋ก ๋ฐฐํฌ๋์์ต๋๋ค(_node_modules_ ํฌํจ).
๋ค์๊ณผ ๊ฐ์ ๋ช ๊ฐ์ง ์คํฌ๋ฆฝํธ๋ฅผ ์์ฑํ์ฌ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์์ต๋๋ค.
yarn pack
). ์๋ฅผ ๋ค์ด ์ด๊ฒ์ _utilities.tgz_๋ฅผ ์์ฑํฉ๋๋ค.dependencies { "utilities": "1.0.0"
-> dependencies { "utilities": "file:./utilities.tgz"
์ ๋ก๋ํ๊ธฐ ์ ์ ์ถ๋ ฅ ๋๋ ํ ๋ฆฌ ๋ด์ฉ:
- dist
| - lib
| | -index.js
| - utilities.tgz
| - package.json <---------- This is modified to use *.tgz for workspaces
@audkar ์ค๋ ๋๋ ๋น์ ๊ณผ ๊ฐ์ ๋ฌธ์ ์ ๋ถ๋ช์ณค์ต๋๋ค.
์ ๋ Lerna์ Yarn ์์ ๊ณต๊ฐ ๋ชจ๋๋ฅผ ์ฒ์ ์ฌ์ฉํฉ๋๋ค. ๋ด๊ฐ ์ดํดํ๋ ํ Lerna๋ฅผ ์ฌ์ฉํ ์๋ ์์ต๋๋ค. ์ด๋ค ์์ผ๋ก๋ ๋์์ด ๋ ๊น์?
ํด๊ฒฐ ๋ฐฉ๋ฒ์ด ์กฐ๊ธ ๋ณต์กํด ๋ณด์ ๋๋ค ๐ค
๋ํ `--cwd "$RESOURCE_DIR"์ด ๋ฌด์์ธ์ง ๊ถ๊ธํฉ๋๋ค.
--cwd
๋ "ํ์ฌ ์์
๋๋ ํ ๋ฆฌ"๋ฅผ ๋ํ๋ด๊ณ $RESOURCE_DIR
๋ ์์ค ๋๋ ํ ๋ฆฌ(์ด ๊ฒฝ์ฐ functions
์ ๋ํ ๊ฐ์ ๋ณด์ ํฉ๋๋ค. ์ด ํ๋๊ทธ๋ฅผ ์ถ๊ฐํ๋ฉด yarn
๊ฐ root ๋์ functions
dir์์ ์คํ๋ฉ๋๋ค.
@audkar ์ yarn workspace functions lint
๋ฐ yarn workspace functions build
๋์ผํ ์์
์ ์ํํ ์ ์์ต๋๋ค.
@dinvlad ์ dist ํด๋๋ฅผ ๋์์ผ๋ก ํ๊ณ ๊ฑฐ๊ธฐ์ ๋ฌผ๊ฑด์ ๋ณต์ฌํ๋์ง ๋ช
ํํ์ง ์์ต๋๋ค. dist์ ๋น๋ํ์ง๋ง package.json์ ๊ทธ๋๋ก ๋๊ณ main์ dist/index.js
๋ก ๊ฐ๋ฆฌํค๋ฉด ๋ชจ๋ ๊ฒ์ด ๋์ผํ๊ฒ ์๋ํด์ผ ํฉ๋๋ค. ์๋์? ๊ทธ๋ฐ ๋ค์ ์์ค๋ฅผ api/dist ๋์ api๋ก ์ค์ ํด์ผ ํฉ๋๋ค.
@dinvlad ๊ทํ์ ์๊ฒฌ์์ yarn workspace
๋ช
๋ น์ ๋ฐฐ์ ์ง๋ง ์ด๋ค ์ด์ ๋ก ์๋ํ์ง ์๋ ๊ฒ ๊ฐ์ต๋๋ค. ์ฌ๊ธฐ๋ฅผ ์ฐธ์กฐ
์ฌ๊ธฐ์ ์ฃผ์ ๋ฅผ ์กฐ๊ธ ๋ฒ์ด๋์ ์ฃ์กํฉ๋๋ค. ์์์ ์ต์ํํ๊ธฐ ์ํด SO์ ์ฃผ์์ ๋ฌ ์ ์์ต๋๋ค.
@0x80 package.json
๋ฅผ api/dist
๋ณต์ฌํ๊ณ firebase.json
๋ฅผ api/dist
์ง์ ํ์ฌ "๋น๋" ํ์ผ๋ง ํด๋ผ์ฐ๋ ํจ์ ๋ด์ ํจํค์งํฉ๋๋ค. firebase.json
๋ฅผ api
๊ฐ๋ฆฌํค๋ฉด ์ด๋ป๊ฒ ๋ ์ง ์ ๋ชจ๋ฅด๊ฒ ์ต๋๋ค. api/dist
( main
package.json
main
์์ฑ ). ํ์ง๋ง api/dist
๊ฐ๋ฆฌํค๋ ๊ฒ์ด ๋ ๊น๋ํ๋ค๊ณ ์๊ฐํ์ต๋๋ค.
yarn workspace
, ๋๋ SO ์ ์๋ตํ์ต๋๋ค ;)
@dinvlad ๊ทธ๊ฒ์ ๋น์ ์ด ๊ฐ๋ฆฌํค๋ ๋ฃจํธ๋ฅผ ๋ฌถ์
์ด์ @audkar์ ์ ์ฌํ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ์ต๋๋ค.
{
"functions": {
"source": "packages/cloud-functions",
"predeploy": ["./scripts/pre-deploy-cloud-functions"],
"ignore": [
"src",
"node_modules"
]
}
}
๊ทธ๋ฐ ๋ค์ 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
๊ทธ๋ฆฌ๊ณ packages/cloud-functions์๋ ์ถ๊ฐ gitignore ํ์ผ์ด ์์ต๋๋ค.
yarn.lock
*.tgz
์ฌ๊ธฐ ๋๋ฅผ ์ํด ์ผํ ๊ฒ์ด ์์ต๋๋ค
- root/
| - .firebaserc
| - firebase.json
- packages/
| - package1/
| - functions/
| - dist/
| - src/
| packages.json
root/firebase.json
:
```
{
"๊ธฐ๋ฅ": {
"์ฌ์ ๋ฐฐํฌ": "npm --prefix "$RESOURCE_DIR" ๋น๋ ์คํ",
"์์ค": "ํจํค์ง/ํจ์"
}
}
````
@kaminskypavel ์ packages/package1(๋๋ ๋ค๋ฅธ ํ์ ํจํค์ง)์ ๋ฐ๋ผ ๋ฌ๋ผ์ง๋ ํจํค์ง/๊ธฐ๋ฅ์ ๋๊น?
@0x80 ๊ธ์ ์ ์ ๋๋ค.
๋ชจ๋ ธ๋ ํฌ์ ๋ํด ์ ๊ฐ ๊ทผ๋ณธ์ ์ผ๋ก ์๋ชป ์๊ณ ์๋ ๋ถ๋ถ์ด ์์๋ ๊ฒ ๊ฐ์์. ์ค์ ๋ก NPM์ ๊ณต์ ํจํค์ง๋ฅผ ๊ฒ์ํ์ง ์๊ณ ๋ ํจํค์ง๋ฅผ ๊ณต์ ํ๊ณ ํด๋น ํจํค์ง๋ฅผ ์ฌ์ฉํ์ฌ ์ฑ์ ๋ฐฐํฌํ ์ ์๋ค๊ณ ๊ฐ์ ํ์ต๋๋ค.
Firebase ๋๋ Now.sh์ ๊ฐ์ ๋ฐฐํฌ๋ ์ผ๋ฐ์ ์ผ๋ก ์ฝ๋๋ฅผ ์ ๋ก๋ํ ๋ค์ ํด๋ผ์ฐ๋์์ ์ค์น ๋ฐ ๋น๋๋ฅผ ์ํํ๊ธฐ ๋๋ฌธ์ ์ด๊ฒ์ด ๋ถ๊ฐ๋ฅํ ๊ฒ ๊ฐ์ต๋๋ค. ์ ๊ฐ ๋ง์ต๋๊น?
@kaminskypavel ๊ทํ์ ์ ๊ทผ ๋ฐฉ์์ ์๋ํ์ง๋ง ์๋ํ์ง๋ง ๋จผ์ ๋ด ํจํค์ง๋ฅผ NPM์ ๊ฒ์ํ ํ์์ผ ์๋ํฉ๋๋ค. ์ ๊ฒฝ์ฐ์๋ ํจํค์ง๊ฐ ๋น๊ณต๊ฐ์ด๊ธฐ ๋๋ฌธ์ ์ฒ์์ "์ฐพ์ ์ ์์" ์ค๋ฅ๊ฐ ๋ฐ์ํ๊ธฐ ๋๋ฌธ์ ์ฌ๊ธฐ์ ์ค๋ช ๋ ๋๋ก ํด๋ผ์ฐ๋ ๊ธฐ๋ฅ ํจํค์ง์ ๋ฃจํธ์ .npmrc ํ์ผ๋ ์ถ๊ฐํด์ผ ํ์ต๋๋ค
@audkar ๊ณตํต ํจํค์ง๋ฅผ NPM์ ๊ฒ์ํ๊ณ ์์ต๋๊น, ์๋๋ฉด ์ ์ ๊ฐ์ด ๊ฒ์๋์ง ์์ ๊ณต์ ์ฝ๋๋ก ๋ฐฐํฌํ๋ ค๊ณ ํฉ๋๊น?
@0x80 ๋๋ ์ด ์ดํด์ ๋ํด ๋น์ ๊ณผ ํจ๊ปํฉ๋๋ค. Firebase ํจ์ ๋ฐฐํฌ๋ package.json์ ๋ช ๋ช ๋ ๋ชจ๋ ํจํค์ง๊ฐ ๋ฐฐํฌ ์๋๋ฅผ ๋์ด๋ ์ด๋ฆ์ผ๋ก npm์์ ์ฌ์ฉํ ์ ์๋ค๊ณ ๊ฐ์ ํ๋ ๊ฒ๋ฟ์ด๋ผ๊ณ (์๋ชป) ์๊ฐํฉ๋๋ค.
yarn ์์ ๊ณต๊ฐ ์ค์ ์ด ์ ์ ๋ ๋์คํ๋๋ฉด์ ๋ ๋ง์ ์ฌ๋๋ค์ด Firebase Functions์์ ์ฌ๋ณผ๋ฆญ ๋งํฌ๋ ํจํค์ง๋ฅผ ์ฌ์ฉํ ์ ์๋ค๋ ์ฌ์ค์ ๋๋ ๊ฒ์ด๋ผ๊ณ ์๊ฐํฉ๋๋ค. ํนํ ๋ฐฐํฌํ ๋๊น์ง ์ ๋๋ก ์๋ํ๊ธฐ ๋๋ฌธ์ ๋๋ค.
์ด ๋ฌธ์ ๋ 1๋ ์ด ๋์์ผ๋ฏ๋ก Firebase ์ธก์์ ๊ณํ(๋๋ ๊ณํ ๋ถ์กฑ)์ ๋ํ ์ ๋ฐ์ดํธ๊ฐ ์์ต๋๊น?
์์ฃผ ๋ฉ์ง ๊ธฐํ๋ผ๊ณ ์๊ฐํฉ๋๋ค. Firebase์ ์ผ๋ จ์ ์๋น์ค๋ ์ข์ monorepo ์ค์ ์ ์๊ตฌํฉ๋๋ค.
์ด์ ๋ํด +1, ํด๋ผ์ฐ๋ ๊ธฐ๋ฅ์ ์ผ๋ฐ์ ์ผ๋ก ๋ค๋ฅธ ์ฑ๊ณผ ์ผ๋ถ ๊ณตํต ์ฝ๋(์: ์ธํฐํ์ด์ค)๋ฅผ ๊ณต์ ํด์ผ ํ๋ฉฐ ์ด๋ฅผ ์ฒ๋ฆฌํ๋ ์ข์ ๋ฐฉ๋ฒ์ monorepo(์: lerna) ๋๋ ์ง์ ์ฌ๋ณผ๋ฆญ ๋งํฌ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ๋๋ค. ๋๋ ํ์๋ฅผ ์ทจํ๊ณ ์ผ๋ถ ์คํฌ๋ฆฝํธ๋ฅผ ์์ฑํ์ฌ ํด๊ฒฐํ์ต๋๋ค. ๊ฐ๋ ์ ๋งค์ฐ ์ฝ์ต๋๋ค. functions ๋๋ ํ ๋ฆฌ ์์ ํ์ํ ๊ฒ์ ๋ณต์ฌํ๊ณ ๋์ค์ ์ ๊ฑฐํฉ๋๋ค.
๋ค์์ ์ด ๋๋ ํ ๋ฆฌ ๊ตฌ์กฐ๋ก ์ํํ ๋ฐฉ๋ฒ์
๋๋ค.
```
content of `pre-deploy.js`
const fs = ์๊ตฌ("fs-extra");
const packageJsonPath = "./ํจํค์ง.json";
const ํจํค์งJson = ์๊ตฌ(ํจํค์งJsonPath);
(๋น๋๊ธฐ() => {
๋๊ธฐ fs.remove( ./shared
);
๋๊ธฐ 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 = "./ํจํค์ง.json";
const ํจํค์งJson = ์๊ตฌ(ํจํค์งJsonPath);
const fs = ์๊ตฌ("fs-extra");
(๋น๋๊ธฐ() => {
๋๊ธฐ 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)
"๊ธฐ๋ฅ": {
"์์ค": "๊ธฐ๋ฅ",
"์ฌ์ ๋ฐฐํฌ": [
"npm --prefix "$RESOURCE_DIR" ์ฌ์ ๋ฐฐํฌ ์คํ"
],
"๋ฐฐํฌ ํ": [
"npm --prefix "$RESOURCE_DIR" ๋ฐฐํฌ ํ ์คํ"
]
},
```
๋น๋๋ฅผ ์ํํ๋ฉด dist
๋๋ lib
๋๋ ํ ๋ฆฌ ๋ด๋ถ์ ์ด์ ํจ์์ ๊ณต์ ๋ผ๋ ๋ ํ์ ๊ฐ ์์ด์ผ ํฉ๋๋ค(์ด๋ ๊ณต์ ์ข
์์ฑ ๋๋ฌธ์ ๋ฐ์ํ์ต๋๋ค). ๊ธฐ๋ฅ ์
๋ฐ์ดํธํด์ผํฉ๋๋ค package.json
main
๋ฅผ ์ฐจ๋ก๋ก lib/functions/src/index.js
๋ฐฐํฌ ์์
์ ํ ์ ์์ต๋๋ค.
์ง๊ธ์ ํด๊ฒฐ๋์์ง๋ง ํด๊ฒฐ ๋ฐฉ๋ฒ์ด ์๋๋ผ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ๋๋ค. Firebase ๋๊ตฌ๊ฐ ์ค์ ๋ก ์ฌ๋ณผ๋ฆญ ๋งํฌ๋ฅผ ์ง์ํด์ผ ํ๋ค๊ณ ์๊ฐํฉ๋๋ค.
@michelepatrassi ๋ ์ด ์ฌ๋ก๋ฅผ ๊ด๋ฆฌํ๊ธฐ ์ํด firelink
๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ง๋ค์์ต๋๋ค. ๋ด๋ถ์ ์ผ๋ก rsync
๋ฅผ ์ฌ์ฉํ์ฌ ์ฌ๊ท ํ์ผ์ ๋ณต์ฌํฉ๋๋ค.
https://github.com/rxdi/firelink
npm i -g @rxdi/firelink
๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ
monorepo ์ ๊ทผ ๋ฐฉ์์ด ์๊ณ ํจํค์ง๊ฐ package.json
๊ฐ ์๋ ํ์ฌ ๋๋ ํ ๋ฆฌ์์ 2๋จ๊ณ ์๋์ ์๋ค๊ณ ๊ฐ์ ํฉ๋๋ค.
package.json
"fireDependencies": {
"@graphql/database": "../../packages/database",
"@graphql/shared": "../../packages/shared",
"@graphql/introspection": "../../packages/introspection"
},
firelink
์คํํ๋ฉด ํด๋์ ๊ด๋ จ๋ ํจํค์ง๋ฅผ ๋ณต์ฌํ ๋ค์ ๊ธฐ์กด ํจํค์ง๋ฅผ ๋ก์ปฌ ๋ชจ๋ ์ค์น "@graphql/database": "file:./.packages/database",
๋ก ๋งคํํ ๋ค์ firebase
๋ช
๋ น์ ์คํํ๊ณ firelink
๋๋จธ์ง ์ธ์๋ฅผ ์ ๋ฌํฉ๋๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก firelink
์ packages
๋ณต์ฌ ๋ฐ package.json
์์ ์์
์ ๋ง์น๋ฉด ๋์ firebase
๋ฅผ ์์ฑํ๊ธฐ ๋๋ฌธ์ firebase
CLI๋ฅผ ๋์ฒดํฉ๋๋ค!
๋ฌธ์ ์ธ์ฌ!
์ฐ๋ฆฌ๋ ์ด๊ฒ์ ๋ฌผ๋ ธ๊ณ monorepos๊ฐ ํ์ค์ด ๋ ๊ฒ์ด๋ฉฐ ์ด๊ฒ์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ง์๋์ด์ผ ํ๋ค๊ณ ์๊ฐํฉ๋๋ค.
์ฌ๋ฌ๋ถ, ์ด ๋ฌธ์ ์ ๋ํ ๊ฐ๋จํ ํด๊ฒฐ์ฑ
์ webpack์ ์ฌ์ฉํ์ฌ ํด๋ผ์ฐ๋ ๊ธฐ๋ฅ์ ๋ฒ๋ค๋ก ๋ฌถ๊ณ ๋ฒ๋ค ๋๋ ํ ๋ฆฌ์์ ๋ฐฐํฌํ๋ ๊ฒ์
๋๋ค. ์ฌ๊ธฐ์ ์ ๊ฐ ์ฌ์ฉํ๊ณ ์๋ ๊ธฐ๋ณธ webpack ํ์ผ์ด ์ฒจ๋ถ๋์ด ์์ต๋๋ค. ๋น๋ํ๋ ๋์ ๋ชจ๋
ธ ๋ฆฌํฌ์งํ ๋ฆฌ ๋ด์์ ํด๊ฒฐ๋ ์ข
์์ฑ์ ํฌํจํ์ฌ ๋ชจ๋ ๊ธฐ๋ฅ ์ฝ๋๋ฅผ ์ต์์ ํด๋๋ก ์์ถํฉ๋๋ค(์ด ๊ฒฝ์ฐ webpack/cloud-functions
, ๊ตฌ์ฑํ ๋ชจ๋ ํญ๋ชฉ์ผ ์ ์์).
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'
}
};
๊ทธ๋ฆฌ๊ณ ๋ง์ง๋ง์ผ๋ก firebase.json
ํ์ผ์์ ๋ฐฐํฌ๋ฅผ ์ํด ์ด ํด๋๋ฅผ ์ฐธ์กฐํ์ญ์์ค.
{
"functions": {
"source": "webpack/cloud-functions"
}
}
webpack/cloud-functions
ํด๋์๋ package.json
ํ์ผ์ด ์์ด์ผ ํฉ๋๋ค.
{
"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
}
์ด๊ฒ์ ํ ์คํธ๋๊ณ ์๋ํฉ๋๋ค. ๊ตฌ๊ธ ํด๋ผ์ฐ๋ ๋น๋๋ฅผ ์ฌ์ฉํ๊ณ ์์ต๋๋ค. ํ์ํ ๊ฒฝ์ฐ ์ถ๊ฐ ์ ๋ณด๋ฅผ ์์ฒญํ์ญ์์ค.
๊ฐ์ฌ ํด์,
@sowdri ๊ณต์ functions.predeploy
๋จ๊ณ์์ webpack ๋น๋๋ฅผ ํธ๋ฆฌ๊ฑฐํฉ๋๊น, ์๋๋ฉด firebase deploy๋ฅผ ํธ์ถํ๊ธฐ ์ ์ ํด๋ผ์ฐ๋ ๋น๋ ์คํฌ๋ฆฝํธ์์ ํธ๋ฆฌ๊ฑฐํฉ๋๊น?
@0x80 cloud build
๋จ๊ณ์์ ๋น๋ ์ค์
๋๋ค. ๋ฐ๋ผ์ firebase cli
์ ๋ฐ๋ฅด๋ฉด JS๋ก ๋น๋๋ ํจ์ ์งํฉ์ผ ๋ฟ์
๋๋ค.
@sowdri ์๋ฅผ ๋ค์ด ์ฃผ์ ์ ๊ฐ์ฌํฉ๋๋ค. ์ฐ๋ฆฌ๋ AWS Lambdas / Serverless์ ๋ํ ๊ณผ๊ฑฐ ํ๋ก์ ํธ์์ ๋์ผํ ์ ๊ทผ ๋ฐฉ์์ ์ฌ์ฉํ๊ฒ ๋์์ต๋๋ค. ๋๊ตฌ๊ฐ ์์ฌ monorepo์ ํจ๊ป ์๋ํ๋๋ก ํ๋ ๊ฒ๋ณด๋ค (Webpack) ๋ฒ๋ค์ ์ ๋ก๋ํ๋ ๊ฒ์ด ๋ ์ฌ์ ์ต๋๋ค.
๋๋ ๋ํ ์ด๊ฒ์ ๋ถ์ด ์์ต๋๋ค .... ํจ์๋ฅผ ๋ฐฐํฌํ๋ ค๊ณ ํ ๋๊น์ง ๋ชจ๋ ๊ฒ์ด ์ ์๋ํ์ต๋๋ค (์ฌ์ง์ด firebase ์๋ฎฌ๋ ์ดํฐ). :( firebase ํ์ด ๊ณง monorepos์ ๋ํ ์ง์์ ์ถ๊ฐํ ์ ์๊ธฐ๋ฅผ ๋ฐ๋๋๋ค.
๋ด monorepo์ ๋ค๋ฅธ ํจํค์ง์ babel์ ์ฌ์ฉํ๋ฏ๋ก ๊ณ์ ์ฌ์ฉํ๊ณ ์ถ์ต๋๋ค. ๋ค๋ฅธ ๊ฒ์ด ์๋ค๋ฉด ๋๋ webpack์ ์๋ ํ ์ ์์ต๋๋ค ... ์ผ๋ถ๋ ์๋ํ๋ ๊ฒ ๊ฐ์ต๋๋ค.
ํธ์ง: GitHub ํจํค์ง ๋ ์ง์คํธ๋ฆฌ ๋ฅผ ์ฌ์ฉํ์ฌ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ์ต๋๋ค. ๊ทธ๋์ ๊ฑฐ๊ธฐ์ ๋ด ๋ชจ๋ ํจํค์ง๋ฅผ ๊ฒ์ํ ๋ค์ travis
๋ฐ functions
์๋ฒ์ ๋ํด ๋ ์ง์คํธ๋ฆฌ๋ฅผ ์ค์ ํ๊ณ ์ธ์ฆ์ ์ํ ํ ํฐ์ ์ ๊ณตํด์ผ ํฉ๋๋ค( .npmrc
๋ฅผ ํตํด ์ํ). ...์ฐ์ํ ์๋ฃจ์
์ธ ๊ฒ ๊ฐ์ต๋๋ค. ์ฌ๊ธฐ์์ ์ด ์ ๊ทผ ๋ฐฉ์์ ๋ํ ์์ด๋์ด๋ฅผ ์ป์์ต๋๋ค. https://medium.com/gdgeurope/how-to-use-firebase-cloud-functions-and-yarn-workspaces-24ca35e941eb
๋ค, ์ด๊ฒ๋ ๋ฌผ๋ ธ์ต๋๋ค. ๋ชจ๋ ๊ฒ์ด firebase serve --only functions
์์ ์๋ฒฝํ๊ฒ ์๋ํ์ง๋ง ๋ฐฐํฌํ ๋ ๋ชจ๋์ ์ฐพ์ ์ ์์ต๋๋ค.
๋๋ ๋๋ฅผ ์ํด ํจํค์ง๋ฅผ ๋น๋ํ๊ธฐ ์ํด ์์ ์คํฌ๋ฆฝํธ๋ฅผ ์์ฑํ๊ฒ ๋์์ต๋๋ค. ์ด๊ฒ์ ๋ด ๋๋ ํ ๋ฆฌ ์ด๋ฆ๊ณผ ์ผ์นํ๋ ๋ชจ๋ ๋ฐ ํจํค์ง ์ด๋ฆ ์ฌ์ฉ๊ณผ ๊ฐ์ ๋ด ํ๊ฒฝ์ ์ธ๋ถ ์ฌํญ์ ๋ฐ์ํ์ง๋ง ๋ค๋ฅธ ์ฌ๋๋ค์๊ฒ ์ ์ฉํ๊ธฐ๋ฅผ ๋ฐ๋๋๋ค.
``` "fs"์์ fs ๊ฐ์ ธ์ค๊ธฐ;
"child_process"์์ child_process ๊ฐ์ ธ์ค๊ธฐ;
const internalPackagesFull = new Map();
// ํจํค์ง์ ๋ชจ๋ deps ์ฐพ๊ธฐ
const getDepsForPackage = (ํจํค์ง ์ด๋ฆ) => {
const packageDir = packageName.split("/")[1]; // ์ด๊ฒ์ ๋น์ ์ ์ํด ๋ณ๊ฒฝํด์ผ ํ ์๋ ์์ต๋๋ค
const packageSpecFileName = ../${packageDir}/package.json
;
const ํจํค์ง ์ฌ์ ํ์ผ = fs.readFileSync(ํจํค์ง ์ฌ์ ํ์ผ ์ด๋ฆ);
const packageSpec = JSON.parse(packageSpecFile);
const packageInternalDeps = Object.keys(
packageSpec.dependencies
).filter((key) => key.includes("ํ๋ง")); // ์ด๊ฒ์ ๋น์ ์ ์ํด ๋ณ๊ฒฝํด์ผ ํ ๊ฒ์
๋๋ค
const ํจํค์งTgzName = ${packageName.replace("@", "").replace("/", "-")}-v${
packageSpec.version
}.tgz
;
internalPackagesFull.set(ํจํค์ง ์ด๋ฆ, {
ํจํค์ง ์ฌ์ ํ์ผ ์ด๋ฆ,
ํจํค์ง ์ฌ์,
ํจํค์ง ๋๋ ํ ๋ฆฌ,
ํจํค์ง ๋ด๋ถ ๋์ค,
ํจํค์งTgz์ด๋ฆ,
});
const packagesToProcess = packageInternalDeps.filter(
(internalDepName) => !internalPackagesFull.has(internalDepName)
);
packagesToProcess.forEach((internalPackageName) =>
getDepsForPackage(๋ด๋ถ ํจํค์ง ์ด๋ฆ)
);
};
const ํจํค์ง ์ด๋ฆ = JSON.parse(fs.readFileSync("./package.json")).์ด๋ฆ;
child_process.execSync( cp ./package.json ./package.json.org
);
getDepsForPackage(ํจํค์ง ์ด๋ฆ);
// ์
๋ฐ์ดํธ๋ ํจํค์ง ์์ฑ - ๊ณตํต js๋ฅผ ์ฌ์ฉํ๊ณ ์ฐธ์กฐ๋ ๋ก์ปฌ tgz ํ์ผ์
๋๋ค.
[...internalPackagesFull.values()].forEach((internalDep) => {
const { packageSpec, packageSpecFileName, packageInternalDeps } = internalDep;
// ํจํค์ง ์ ํ ๋ณ๊ฒฝ
packageSpec.type = "commonjs"; // ์ด๊ฒ์ ๋น์ ์ ์ํด ๋ณ๊ฒฝํด์ผ ํ ์๋ ์์ต๋๋ค
// ํจํค์ง๋ zip ํ์ผ์ด ๋ dep์ ์์น๋ฅผ โโ์ง์ ํฉ๋๋ค.
packageInternalDeps.forEach((internalDepOfPackage) => {
const { ํจํค์งTgzName } = ๋ด๋ถํจํค์ง ์ ์ฒด.get(internalDepOfPackage);
packageSpec.dependencies[internalDepOfPackage] = ./${packageTgzName}
;
});
fs.writeFileSync(
ํจํค์ง ์ฌ์ ํ์ผ ์ด๋ฆ,
JSON.stringify(packageSpec, null, " ")
);
});
// ์ ๋น๋ ๋ฐ ํฉ ์คํ
[...internalPackagesFull.values()].forEach((internalDep) => {
๋
ธ๋ ฅํ๋ค {
console.log( Buliding ${internalDep.packageDir}
);
child_process.execSync("์์ฌ ๋น๋", {
cwd: ../${internalDep.packageDir}
,
});
console.log( Packaging ${internalDep.packageDir}
);
child_process.execSync("์์ฌ ํฉ", {
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: ".",
}
);
}
} ์ก๊ธฐ (e) {
console.log(e);
}
});
// ํ์ค ํจํค์ง ๊ตฌ์กฐ๋ก ๋ค์ ์ด๋
[...internalPackagesFull.values()]
.filter((internalDep) => ํจํค์ง ์ด๋ฆ !== internalDep.packageSpec.name)
.forEach((internalDep) => {
์์ {
ํจํค์ง ์ฌ์,
ํจํค์ง ์ฌ์ ํ์ผ ์ด๋ฆ,
ํจํค์ง๋ด๋ถ๋์ค,
} = ๋ด๋ถDep;
// 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, " ")
);
});
```
@sowdri์์ ์ ๊ณตํ๋ ์๋ฃจ์
์ ์ฌ์ฉํ์ต๋๋ค(๊ฐ์ฌํฉ๋๋ค!). ๋ณด์กฐ package.json
ํฌํจํ์ฌ ์ ์ฒด dist/
๋๋ ํ ๋ฆฌ๋ฅผ ์์ ๋กญ๊ฒ ์ญ์ ํ๊ณ ๋ค์ ์์ฑํ ์ ์๋๋ก ์ฝ๊ฐ์ ์กฐ์ ์ ํ์ต๋๋ค.
const path = require('path');
const CopyPlugin = require('copy-webpack-plugin');
module.exports = {
...,
plugins: [
new CopyPlugin({
patterns: [{ from: 'package.dist.json', to: 'package.json' }],
}),
],
};
๊ทธ๋ฐ ๋ค์ ํจํค์ง ๋ฃจํธ์ ๋ค์ package.dist.json
์ ์ ์งํฉ๋๋ค.
{
"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
}
์ด ํ์ผ์์ ์ข
์์ฑ์ ์ ๊ฑฐํ๊ณ Webpack์ ์์กดํ์ฌ ์ด๋ฅผ ๋ฒ๋ค๋ก ๋ฌถ๋ ๊ฒ์ด ๊ฐ๋ฅํ ์๋ ์์ง๋ง(์ด ์ข
์์ฑ์ ๊ธฐ๋ณธ package.json
์ ๋๊ธฐํ ์ํ๋ก ์ ์งํ ํ์๊ฐ ์ ๊ฑฐ๋จ) ์๋ํ์ง ์์์ต๋๋ค.
๋ด ์ปดํ์ผ๋ ์ฝ๋๊ฐ ์๋ ์ถ๋ ฅ ๋๋ ํฐ๋ฆฌ์ ๋๋ ํฐ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ๋ณด์กดํ๋ npm cpy ํจํค์ง๋ฅผ ์ฌ์ฉํ์ฌ ๋ชจ๋ ๊ด๋ จ ๋๋ ํฐ๋ฆฌ ๋๋ Firebase ํ๋ก์ ํธ์ ๋ํด .firebaserc
๋ฐ firebase.json
๋ฅผ ๋ณต์ฌํ๋ ์คํฌ๋ฆฝํธ๊ฐ ์๋ ๋จ์ผ ์ ์ฅ์๋ฅผ ์ฌ์ฉํฉ๋๋ค.
ํจ์ ๋ฐฐํฌ๋ฅผ ์ํด ์๋ package.json์์ package.json
๋ฅผ ๋ง๋ญ๋๋ค.
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);
})();
๋ฐฐํฌ๋ฅผ ์ํด ./out/[project-name]/firebase deploy --only=functions
ํ๊ฑฐ๋ ์ด์ ๋ํ ์คํฌ๋ฆฝํธ๋ ์์ฑํ์ญ์์ค.
๋ค์๊ณผ ๊ฐ์ Webpack ๊ฒฝ๊ณ ๊ฐ ๋ฐ์ํฉ๋๋ค.
/Users/me/Development/myproject/node_modules/firebase-functions/lib/config.js 61:23-42์ ๊ฒฝ๊ณ
์ค์ ์ข ์์ฑ: ์ข ์์ฑ ์์ฒญ์ ํํ์์ ๋๋ค.
์ด๋ฅผ ํด๊ฒฐํ ๋ฐฉ๋ฒ์ ์ฐพ์์ต๋๊น, ์๋๋ฉด ๋ฌด์/์ต์ ํ๊ณ ์์ต๋๊น?
๋๋ ๊ฒฝ๊ณ ์์ด ์ผ์ ํ ์ ์์์ต๋๋ค ๐ฅณ ์๋ ๊ตฌ์ฑ์ผ๋ก ๋ง๋ฌด๋ฆฌํ์ต๋๋ค. ํนํ ์ธ๋ถ์ ์ ๊ท์ ํจํด์ ์ฌ์ฉํ๋ฉด ์ฐจ์ด๊ฐ ์๊ฒผ์ต๋๋ค. ์ธ๋ถ์ "firebase-functions"๋ง ์์ผ๋ฉด ํ์ ๋ชจ๋์์ ๊ฐ์ ธ์จ ๋ชจ๋ ํญ๋ชฉ์ด ์ผ์นํ์ง ์๊ณ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ฌ์ ํ ๋ฒ๋ค์ ํฌํจ๋๊ธฐ ๋๋ฌธ์ ๋๋ค.
๋ฒ๋ค ๋ฌธ์ ์ ๊ฒฐ๊ณผ๋ก ๋ฐฐํฌํ ๋ ์ผ๋ถ ์ ๋งคํ @grpc ์ค๋ฅ๋ ๋ฐ์ํ์ต๋๋ค. ์ฐธ๊ณ ์ฉ์ผ๋ก ๋ณด๊ดํ๋ ๊ฒ์ ์์์ต๋๋ค.
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" }],
}),
],
};
๋ณ๋์ package.dist.json์ด ํ์ํ์ง ์์ ๊ฒ์ผ๋ก ๋ํ๋ฌ์ต๋๋ค. ์ด ํ์ผ์ ๊ฐ์ง๊ณ ์๋ ์ฑ๊ฐ์ ์ ์ ๋ฌผ๋ก ๊ฑฐ๊ธฐ์ ๋์ด๋ ์ข ์์ฑ์ ์ ๋ฐ์ดํธํ ๋๋ง๋ค ์๋์ผ๋ก ์ ๋ฐ์ดํธํด์ผ ํ๋ค๋ ๊ฒ์ ๋๋ค. ๊ทธ๋์ ๊ทธ๊ฒ์ ์๋ ๊ฒ์ ๋งค์ฐ ์ฝ์ต๋๋ค.
๋์ package.dist.json์ ๋์ดํ์ง _์์_ ๋ชจ๋ ํจํค์ง๋ฅผ devDependencies ๋ชฉ๋ก์ผ๋ก ์ด๋ํ๊ณ webpack ๋ณต์ฌ ํ๋ฌ๊ทธ์ธ์์ ํด๋น ํ์ผ์ ์ฌ์ฉํ์ธ์. ์ด์ ํ๋์ package.json๋ง ์ฒ๋ฆฌํ ์ ์์ต๋๋ค. ๐
๋ํ ๋ด ๋ก์ปฌ nodejs ๋ฒ์ ์ด ๋ฐฐํฌ๋ ๋
ธ๋ ๋ฒ์ ๊ณผ ๋ฐ๋์ โโ๊ฐ์ ํ์๋ ์์ต๋๋ค. ์ด์ firebase.json ํ์ผ์์ functions.runtime
๋ฅผ ์ง์ ํ ์ ์์ต๋๋ค. ๋ฐ๋ผ์ package.json์์ engines
ํ๋๋ฅผ ์ ๊ฑฐํ๊ณ ๋์ Firebase ๊ตฌ์ฑ์์ functions.runtime
๋ฅผ "10" ๋๋ "12"๋ก ์ค์ ํฉ๋๋ค.
์ด๊ฒ์ ๋๋ฅผ ์ํด ๋ณด์ด๋ ๊ฒ์ ๋๋ค :
{
"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
}
}
}
์์ค ๋งต์ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ๊ณ ์์ต๋๊น? devtool: "inline-source-map"
์ฌ์ฉํ์ฌ ๋ด ๊ธฐ๋ฅ์ webpack๊ณผ ํจ๊ป ๋ฌถ์ผ๋ฉด ์คํ ๋๋ผ์ด๋ฒ ์ค๋ฅ ๋ณด๊ณ ์์ ์ ํ๋์ง ์์ต๋๋ค. @sowdri
๋๋ ๋ด ํ๋ก์ ํธ์์ ๊ฐ์ ์ผ์ ๊ฒช์๊ณ ์ด๊ฒ์ ์ฌ์ด๋ ํ๋ก์ ํธ๋ฅผ ์ํด ๋ชจ๋ ํจํค์ง๋ฅผ ๊ฒ์ํ๊ณ ์ถ์ง ์๊ธฐ ๋๋ฌธ์ ๊ฝค ํฐ ๋ฌธ์ ์์ต๋๋ค. ์ฌ๋ฌ package.json
ํ์ผ์ ์ ์งํ๊ฑฐ๋ ํจํค์ง ์ธ๋ถ์์ ๋น๋ํด์ผ ํ๋ ๊ฒ์ ์ฑ๊ฐ์ ์ผ์ด์์ต๋๋ค. dep๋ฅผ ์ ํ ์ฌํญ์ผ๋ก ํฌํจํ๋ฉด Lerna๊ฐ ๊ณ์ ์ ํํ๊ณ ์
๋ก๋ํ ๋ Firebase๊ฐ ๋ถํํ์ง ์์ต๋๋ค.
์๋ ๊ตฌ์ฑ์ ๋จ์ผ package.json์ผ๋ก ์ฌ๋ณผ๋ฆญ ๋งํฌ๋ dep ์ง์์ ์ ๊ณตํฉ๋๋ค!
ํจํค์ง.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"
}
}
์นํฉ.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
}
}
}
์ ๋ yarn ์์ ๊ณต๊ฐ์ ์ฌ์ฉํ๊ณ ์์ง๋ง ๋ค๋ฅธ package.json ํ์ผ์์ ๋ก์ปฌ ํจํค์ง ์ด๋ฆ์ ์ธ๊ธํ ํ์๋ ์์ต๋๋ค. Firebase์ Vercel ๋ชจ๋์ ์ฑ๊ณต์ ์ผ๋ก ๋ฐฐํฌํ๊ณ ์์ต๋๋ค.
๋ฌด์์ด ์๋ํ๋์ง ์ ๋ชจ๋ฅด๊ฒ ์ต๋๋ค. ๋ด ์ต์์ package.json์ ๋ค์ ํ์ค ๊ตฌ์ฑ์ด ์์ต๋๋ค.
"workspaces": {
"packages": [
"packages/*"
]
},
yarn upgrade-interactive
ํ๋ฉด ํ ๋ฒ์ ๋ชจ๋ ์ฒ๋ฆฌํ ์ ์๊ธฐ ๋๋ฌธ์ ๊ฐ /packages/* ํด๋์ package.json์ด ์์ด๋ ์๊ด ์์ต๋๋ค. ์ผ๋ถ ํจํค์ง์์๋ ํด๋น ๋ฒ์์ ๋ํด ํน๋ณํ ์คํฌ๋ฆฝํธ๋ฅผ ์ถ๊ฐํ ์ ์๋ ๊ฒ์ด ์ ์ฉํ๋ค๋ ๊ฒ์ ์์์ต๋๋ค.
---- ํธ์งํ๋ค ----
ํ๋ก์ ํธ ์ฐธ์กฐ์ ํจ๊ป Typescript๋ฅผ ์ฌ์ฉํ๊ณ ์๋ค๊ณ ์ธ๊ธํ๋ ๊ฒ์ ์์์ต๋๋ค. ๊ทธ๊ฒ๊ณผ ๊ด๋ จ์ด ์์ ์ ์์ต๋๋ค. Vercel์ ๊ฒฝ์ฐ ๋ด ๊ณต์ ์ฝ๋๋ฅผ ๋ฒ๋ค์ ํฌํจํ๊ธฐ ์ํด next-transpile-modules๋ฅผ ์ฌ์ฉํ๊ณ ์์ต๋๋ค.
์ด ์์
์ ์ํํ๊ธฐ ์ํด @0x80 ๋ฐ @sowdri ์ ์ ์ฌํ ์ค์ ์ ์ฌ์ฉํ๊ณ ์์ง๋ง ๋ก์ปฌ ์ข
์์ฑ์ devDependencies๋ก ์ด๋ํ๋ ๋์ (์ค์ ๋ก ์๋ํ์ง ์์ ๋จ๊ณ๊ฐ ๋๋ฝ๋์๋ค๊ณ ์๊ฐํจ) copy-webpack-plugin
, generate-package-json-webpack-plugin
๋ฅผ ์ฌ์ฉํ์ฌ dist ํด๋์ package.json์ ๋น๋ํฉ๋๋ค. ์ด๊ฒ์ ๊ฒฐ๊ณผ ์ฝ๋์ ์ค์ ๋ก ํ์ํ ๊ฒ์์ ๋ด ์ข
์์ฑ ๋ชฉ๋ก์ ์์ฑํ๋ฏ๋ก ๋ฒ๋ค๋ก ์ ๊ณต๋๊ฑฐ๋ dev ์ข
์์ฑ์ธ ๊ฒฝ์ฐ ๊ฒฐ๊ณผ package.json์ ํฌํจ๋์ง ์์ต๋๋ค.
๋ํ webpack-node-externals
๋ฅผ ์ฌ์ฉํ์ฌ ์ธ๋ถ๋ฅผ ์ค์ ํ์ฌ ํ๋ก์ ํธ ์ด๋ฆ ์ ๋์ฌ์ ์ผ์น์ํค๊ธฐ ์ํด ์ ๊ท์์ ์ฌ์ฉํ๋ ์ฌ๋ณผ๋ฆญ ๋งํฌ๋ ๋ชจ๋
ธ๋ ํฌ ํจํค์ง๋ฅผ ์ ์ธํ ๋ชจ๋ ๊ฒ์ ์ธ๋ถ๋ก ๋ง๋ค์์ต๋๋ค. ๋ํ ์ถ๊ฐ ์ธ๋ถ๋ก ๊ฒ์๋ @0x80 Firebase ํจํค์ง์ ๋ํ ์ ๊ท์ ํํ์์ ์ถ๊ฐํ์ต๋๋ค.
์ด๊ฒ์ ๋ด ๊ตฌ์ฑ์ ๋๋ค
/* 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)]
};
์นํฉ์ ์ฌ์ฉํ์ฌ ์ฝ๋๋ฅผ ๋ฌถ์ ๋์ ๋ ๋ค๋ฅธ ์ด์ ์ ๋ง์นจ๋ด ๋ชจ๋ ๋ณ์นญ์ ์ฌ์ฉํ ์ ์๋ค๋ ๊ฒ์ ๋๋ค. :)
๊ฐ์ฅ ์ ์ฉํ ๋๊ธ
์ด์ ๋ํด +1, ํด๋ผ์ฐ๋ ๊ธฐ๋ฅ์ ์ผ๋ฐ์ ์ผ๋ก ๋ค๋ฅธ ์ฑ๊ณผ ์ผ๋ถ ๊ณตํต ์ฝ๋(์: ์ธํฐํ์ด์ค)๋ฅผ ๊ณต์ ํด์ผ ํ๋ฉฐ ์ด๋ฅผ ์ฒ๋ฆฌํ๋ ์ข์ ๋ฐฉ๋ฒ์ monorepo(์: lerna) ๋๋ ์ง์ ์ฌ๋ณผ๋ฆญ ๋งํฌ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ๋๋ค. ๋๋ ํ์๋ฅผ ์ทจํ๊ณ ์ผ๋ถ ์คํฌ๋ฆฝํธ๋ฅผ ์์ฑํ์ฌ ํด๊ฒฐํ์ต๋๋ค. ๊ฐ๋ ์ ๋งค์ฐ ์ฝ์ต๋๋ค. functions ๋๋ ํ ๋ฆฌ ์์ ํ์ํ ๊ฒ์ ๋ณต์ฌํ๊ณ ๋์ค์ ์ ๊ฑฐํฉ๋๋ค.
๋ค์์ ์ด ๋๋ ํ ๋ฆฌ ๊ตฌ์กฐ๋ก ์ํํ ๋ฐฉ๋ฒ์ ๋๋ค.
```
| - .firebaserc
| - firebase.json
| - ...
| - src/
| - ํจํค์ง.json
| - pre-deploy.js
| - post-deploy.js
| - ....
| - src/
| - ํจํค์ง.json
| - ....
const fs = ์๊ตฌ("fs-extra");
const packageJsonPath = "./ํจํค์ง.json";
const ํจํค์งJson = ์๊ตฌ(ํจํค์งJsonPath);
(๋น๋๊ธฐ() => {
๋๊ธฐ fs.remove(
./shared
);๋๊ธฐ fs.copy(
../shared
,./shared
);})();
const packageJsonPath = "./ํจํค์ง.json";
const ํจํค์งJson = ์๊ตฌ(ํจํค์งJsonPath);
const fs = ์๊ตฌ("fs-extra");
(๋น๋๊ธฐ() => {
๋๊ธฐ fs.remove(
./shared
);})();
"๊ธฐ๋ฅ": {
"์์ค": "๊ธฐ๋ฅ",
"์ฌ์ ๋ฐฐํฌ": [
"npm --prefix "$RESOURCE_DIR" ์ฌ์ ๋ฐฐํฌ ์คํ"
],
"๋ฐฐํฌ ํ": [
"npm --prefix "$RESOURCE_DIR" ๋ฐฐํฌ ํ ์คํ"
]
},
```
๋น๋๋ฅผ ์ํํ๋ฉด
dist
๋๋lib
๋๋ ํ ๋ฆฌ ๋ด๋ถ์ ์ด์ ํจ์์ ๊ณต์ ๋ผ๋ ๋ ํ์ ๊ฐ ์์ด์ผ ํฉ๋๋ค(์ด๋ ๊ณต์ ์ข ์์ฑ ๋๋ฌธ์ ๋ฐ์ํ์ต๋๋ค). ๊ธฐ๋ฅ ์ ๋ฐ์ดํธํด์ผํฉ๋๋คpackage.json
main
๋ฅผ ์ฐจ๋ก๋กlib/functions/src/index.js
๋ฐฐํฌ ์์ ์ ํ ์ ์์ต๋๋ค.์ง๊ธ์ ํด๊ฒฐ๋์์ง๋ง ํด๊ฒฐ ๋ฐฉ๋ฒ์ด ์๋๋ผ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ๋๋ค. Firebase ๋๊ตฌ๊ฐ ์ค์ ๋ก ์ฌ๋ณผ๋ฆญ ๋งํฌ๋ฅผ ์ง์ํด์ผ ํ๋ค๊ณ ์๊ฐํฉ๋๋ค.