Firebase-tools: ๋ฐฐํฌ ์‹œ ๋‹จ์ผ ๋ฆฌํฌ์ง€ํ† ๋ฆฌ ์ง€์›

์— ๋งŒ๋“  2018๋…„ 01์›” 31์ผ  ยท  47์ฝ”๋ฉ˜ํŠธ  ยท  ์ถœ์ฒ˜: firebase/firebase-tools

์ฐธ์กฐ: https://github.com/firebase/firebase-functions/issues/172

๋ฒ„์ „ ์ •๋ณด

3.17.3

์žฌํ˜„ ๋‹จ๊ณ„

์˜ˆ์ƒ๋˜๋Š” ํ–‰๋™

์‹ค์ œ ํ–‰๋™

feature request

๊ฐ€์žฅ ์œ ์šฉํ•œ ๋Œ“๊ธ€

์ด์— ๋Œ€ํ•ด +1, ํด๋ผ์šฐ๋“œ ๊ธฐ๋Šฅ์€ ์ผ๋ฐ˜์ ์œผ๋กœ ๋‹ค๋ฅธ ์•ฑ๊ณผ ์ผ๋ถ€ ๊ณตํ†ต ์ฝ”๋“œ(์˜ˆ: ์ธํ„ฐํŽ˜์ด์Šค)๋ฅผ ๊ณต์œ ํ•ด์•ผ ํ•˜๋ฉฐ ์ด๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ์ข‹์€ ๋ฐฉ๋ฒ•์€ monorepo(์˜ˆ: lerna) ๋˜๋Š” ์ง์ ‘ ์‹ฌ๋ณผ๋ฆญ ๋งํฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋‚˜๋Š” ํ›„์ž๋ฅผ ์ทจํ•˜๊ณ  ์ผ๋ถ€ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ž‘์„ฑํ•˜์—ฌ ํ•ด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ฐœ๋…์€ ๋งค์šฐ ์‰ฝ์Šต๋‹ˆ๋‹ค. functions ๋””๋ ‰ํ† ๋ฆฌ ์•ˆ์— ํ•„์š”ํ•œ ๊ฒƒ์„ ๋ณต์‚ฌํ•˜๊ณ  ๋‚˜์ค‘์— ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ์€ ์ด ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ๋กœ ์ˆ˜ํ–‰ํ•œ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.
```

  • ๋ฟŒ๋ฆฌ/
    | - .firebaserc
    | - firebase.json
    | - ...
  • ๊ธฐ๋Šฅ/
    | - src/
    | - ํŒจํ‚ค์ง€.json
    | - pre-deploy.js
    | - post-deploy.js
    | - ....
  • ๊ณต์œ /
    | - src/
    | - ํŒจํ‚ค์ง€.json
    | - ....
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 ๋„๊ตฌ๊ฐ€ ์‹ค์ œ๋กœ ์‹ฌ๋ณผ๋ฆญ ๋งํฌ๋ฅผ ์ง€์›ํ•ด์•ผ ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

๋ชจ๋“  47 ๋Œ“๊ธ€

์ด๊ฒƒ์€ ์ฆ‰, ๋‚ด ์„ค์ •์—์„œ ์ž‘์—… ๋ณด์ธ๋‹ค 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_๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
  • ๋ชจ๋“  ์ถœ๋ ฅ์„ ํŠน์ • ๋””๋ ‰ํ† ๋ฆฌ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.
  • ์ž‘์—… ๊ณต๊ฐ„ ์ข…์†์„ฑ์— tgz ํŒŒ์ผ์„ ์‚ฌ์šฉํ•˜๋„๋ก _package.json_์„ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ: dependencies { "utilities": "1.0.0" -> dependencies { "utilities": "file:./utilities.tgz"
  • ํ•ด๋‹น ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ Firebase์— ๋ฐฐํฌ

์—…๋กœ๋“œํ•˜๊ธฐ ์ „์— ์ถœ๋ ฅ ๋””๋ ‰ํ† ๋ฆฌ ๋‚ด์šฉ:

- 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์—์„œ ์‹ฌ๋ณผ๋ฆญ ๋งํฌ๋œ ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค๋Š” ์‚ฌ์‹ค์— ๋†€๋ž„ ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ํŠนํžˆ ๋ฐฐํฌํ•  ๋•Œ๊นŒ์ง€ ์ œ๋Œ€๋กœ ์ž‘๋™ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

npm์ด ์ž‘์—… ๊ณต๊ฐ„ ์—

์ด ๋ฌธ์ œ๋Š” 1๋…„์ด ๋„˜์—ˆ์œผ๋ฏ€๋กœ Firebase ์ธก์—์„œ ๊ณ„ํš(๋˜๋Š” ๊ณ„ํš ๋ถ€์กฑ)์— ๋Œ€ํ•œ ์—…๋ฐ์ดํŠธ๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ?

์•„์ฃผ ๋ฉ‹์ง„ ๊ธฐํšŒ๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. Firebase์˜ ์ผ๋ จ์˜ ์„œ๋น„์Šค๋Š” ์ข‹์€ monorepo ์„ค์ •์„ ์š”๊ตฌํ•ฉ๋‹ˆ๋‹ค.

์ด์— ๋Œ€ํ•ด +1, ํด๋ผ์šฐ๋“œ ๊ธฐ๋Šฅ์€ ์ผ๋ฐ˜์ ์œผ๋กœ ๋‹ค๋ฅธ ์•ฑ๊ณผ ์ผ๋ถ€ ๊ณตํ†ต ์ฝ”๋“œ(์˜ˆ: ์ธํ„ฐํŽ˜์ด์Šค)๋ฅผ ๊ณต์œ ํ•ด์•ผ ํ•˜๋ฉฐ ์ด๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ์ข‹์€ ๋ฐฉ๋ฒ•์€ monorepo(์˜ˆ: lerna) ๋˜๋Š” ์ง์ ‘ ์‹ฌ๋ณผ๋ฆญ ๋งํฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋‚˜๋Š” ํ›„์ž๋ฅผ ์ทจํ•˜๊ณ  ์ผ๋ถ€ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ž‘์„ฑํ•˜์—ฌ ํ•ด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ฐœ๋…์€ ๋งค์šฐ ์‰ฝ์Šต๋‹ˆ๋‹ค. functions ๋””๋ ‰ํ† ๋ฆฌ ์•ˆ์— ํ•„์š”ํ•œ ๊ฒƒ์„ ๋ณต์‚ฌํ•˜๊ณ  ๋‚˜์ค‘์— ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ์€ ์ด ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ๋กœ ์ˆ˜ํ–‰ํ•œ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.
```

  • ๋ฟŒ๋ฆฌ/
    | - .firebaserc
    | - firebase.json
    | - ...
  • ๊ธฐ๋Šฅ/
    | - src/
    | - ํŒจํ‚ค์ง€.json
    | - pre-deploy.js
    | - post-deploy.js
    | - ....
  • ๊ณต์œ /
    | - src/
    | - ํŒจํ‚ค์ง€.json
    | - ....
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 ๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
image

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

์›นํŒฉ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ฝ”๋“œ๋ฅผ ๋ฌถ์„ ๋•Œ์˜ ๋˜ ๋‹ค๋ฅธ ์ด์ ์€ ๋งˆ์นจ๋‚ด ๋ชจ๋“ˆ ๋ณ„์นญ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. :)

์ด ํŽ˜์ด์ง€๊ฐ€ ๋„์›€์ด ๋˜์—ˆ๋‚˜์š”?
0 / 5 - 0 ๋“ฑ๊ธ‰