點燈坊

失くすものさえない今が強くなるチャンスよ

使用 Babel 打造 Apollo GraphQL 開發環境

Sam Xiao's Avatar 2019-12-12

Node 雖然支援大部分 ECMAScript 2015 語法,但更新的 ECMAScript 版本則不確定,且預設還是要使用 require() 與 CommonJS,若學 Frontend 使用 Babel,則 Apollo GraphQL 也能享受 ECMAScript 最新語法。

Version

macOS Catalina 10.15.2
WebStorm 2019.3
Node 13.2.0
Apollo GraphQL 2.9.6
Babel 7.6.4

Install Package

$ yarn add apollo-server graphql
$ yarn add @babel/core @babel/cli @babel/preset-env @babel/node --dev
$ yarn add nodemon rimraf cross-env --dev

安裝 Apollo GraphQL 與 Babel 所需 package。

$ yarn add apollo-server graphql

安裝 Apollo GraphQL 與 GraphQL。

$ yarn add @babel/core @babel/cli @babel/preset-env @babel/node --dev

安裝 Babel 相關 package,由於只是轉譯用,安裝成開發用的 devDependency 即可。

其中 @babel/node 負責將 ES6+ 轉譯成 Node 能執行的 js。

$ yarn add nodemon rimraf cross-env --dev

安裝 nodemon 可監控檔案修改重新 Babel 轉譯與重啟 Node。

安裝 rimraf 可令 yarn clean 跨平台刪除 dist 目錄。

安裝 cross-env 可跨平台設定 NODE_ENV 環境變數。

babel004

Babel Configuration

.babelrc

{
  "presets": [
    "@babel/preset-env"
  ]
}

在 project 根目錄下建立 .babelrc,使用剛剛安裝的 @babel/preset-env 的設定將 ES6+ 轉譯。

babel006

Nodemon Configuration

nodemon.json

{
  "exec": "cross-env NODE_ENV=development yarn dev",
  "watch": ["src/*"],
  "ext": "js, json"
}
  • exec:當有變動時,將執行 yarn dev
  • watch:Nodemon 將持續觀察的目錄
  • ext:Nodemon 將持續觀察的 extension

watchext 可視實際需求加以修改

babel007

Apollo GraphQL

src/index.js

import { ApolloServer, gql } from 'apollo-server'

let data = [
  { title: 'FP in JavaScript', category: 'FP'},
  { title: 'RxJS in Action', category: 'FRP'},
  { title: 'Speaking JavaScript', category: 'JS'}
]

let typeDefs = gql`
  type Query {
    books(category: BookCategory!): [Book]
  }

  type Book {
    title: String
    category: BookCategory
  }

  enum BookCategory {
    FP
    FRP
    JS
  }
`

let books = (_, { category }) => data.filter(x => x.category === category)

let resolvers = {
  Query: {
    books
  }
}

let apolloServer = new ApolloServer({ typeDefs, resolvers })

apolloServer.listen()
  .then(({ url }) => `GraphQL Server ready at ${ url }`)
  .then(console.log)

第 1 行

import { ApolloServer, gql } from 'apollo-server';

使用 ES6 的 import 而非 require

Yarn Script

package.json

"scripts": {
  "serve": "nodemon",
  "start": "node ./dist/index.js",
  "dev": "babel-node ./src/index.js",
  "prod": "yarn clean && cross-env NODE_ENV=production yarn build && yarn start",
  "clean": "rimraf dist",
  "build": "babel ./src --out-dir dist",
  "docker:build": "yarn clean && cross-env NODE_ENV=production yarn build && docker build -t apollo-babel:$npm_package_version .",
  "docker:up": "docker-compose up -d",
  "docker:down": "docker-compose down"
},

本文最關鍵的就是 Yarn script 配置。

"serve": "nodemon",

yarn serve 執行 nodemon

"start": "node ./dist/index.js",

yarn start 使用 node 執行 Babel 轉譯後的 js 並啟動 Apollo GraphQL。

"dev": "babel-node ./src/index.js",

yarn dev 使用 babel-node 將 ES6+ 轉譯成 Node 能執行的 js 在記憶體內,並啟動 Apollo GraphQL。

"prod": "yarn clean && cross-env NODE_ENV=production yarn build && yarn start",

yarn prod 依序執行以下步驟:

  • yarn clean 刪除 dist 目錄
  • yarn build 設定 NODE_ENVproduction,並使用 Babel 轉譯成 Node 能執行的 js
  • yarn start 使用 node 執行 Babel 轉譯後的 js 並啟動 Apollo GraphQL
"clean": "rimraf dist",

yarn clean 刪除 dist 目錄。

"build": "babel ./src --out-dir dist"

yarn build 使用 Babel 將 src 目錄下的 ES6+ 轉譯成 Node 能執行的 js 到 dist 目錄下。

將來要包進 Docker 的 js 也是 dist 目錄,而非 src 目錄

"docker:build": "yarn clean && cross-env NODE_ENV=production yarn build && docker build -t $npm_package_name:$npm_package_version .",

yarn docker:buildyarn prod 類似,差異只在最後 docker:build 包成 image 而非啟動 Apollo GraphQL。

使用了 $npm_package_name$npm_package_version 直接讀取 package.jsonnameversion

babel005

Summary

雖然 Yarn script 內分的很細,但其實常用的 script 只有 3 個:

  • yarn serve:development 時使用,存檔後 Nodemon 會自動 Babel 轉譯,以 babel-node 重新啟動 Apollo GraphQL
  • yarn build:development 時使用,以 babelsrc 目錄下所有 js 重新轉譯到 dist 目錄下
  • yarn docker:build:產生 Apollo GraphQL 的 Docker image

Dockerfile

dockerfile

FROM node:lts-alpine
WORKDIR /usr/app
COPY package.json .
RUN yarn install --production
COPY dist/* ./
CMD [ "node", "index.js" ]

server 目錄下建立 dockerfile,由於我們要另外安裝 Apollo GraphQL,且將自己寫的 js 包進 Docker image,因此不可直接使用 docker-compose.yml

第 1 行

FROM node:lts-alpine

使用最新 LTS 版的 node:lts-alpine 為基底建立 image。

建議使用 alpine 為 production image,size 會小很多

第 2 行

WORKDIR /usr/app

將 working directory 切換到 /usr/app,相當於:

mkdir /usr/app
cd /usr/app

稍後 COPYRUNCMD 都會在此目錄下。

第 3 行

COPY package.json .

將根目錄的 package.json 複製到 Docker 內的 /usr/app,因為要使用 yarn install 安裝 Apollo GraphQL。

第 4 行

RUN yarn install --production

執行 yarn install 安裝 dependencies 下的 package。

不需安裝 Babel 所需套件進 Docker,因此加上 --production

第 5 行

COPY dist/* ./

將 Babel 編譯過的 js 複製進 Docker 內的 /usr/app

第 6 行

CMD [ "node", "index.js" ]

執行 Docker 內的 /usr/app/index.js 啟動 Apollo GraphQL。

babel008

Docker Compose

docker-compose.yml

version: "3"
services:
  graphql:
    image: apollo-babel:${GQL_TAG}
    container_name: MyGraphQL
    restart: always
    ports:
      - ${GQL_PORT}:4000

使用 docker-compose.yml 啟動 Apollo Server。

babel009

.env

GQL_TAG=1.0.0
GQL_PORT=4000

設定 docker-compose.yml 的環境變數。

babel010

Start Development Server

$ yarn serve

使用 yarn serve 啟動 Apollo GraphQL,適合 development 使用。

babel000

Babel Transpile

$ yarn build

使用 yarn build 命令 Babel 將 src 目錄下所有 js 轉譯成 Node 能執行的 js 到 dist 目錄下。

babel001

Build Docker Image

$ yarn docker:build

使用 yarn docker:build 命令 Babel 重新轉譯,並切包成 Docker image。

babel002

GraphQL Playground

http://localhost:4000

babel003

Conclusion

  • Apollo GraphQL 也能享受 ES6+ 最新語法與 ES module,只要設定好 Babel 環境即可

Sample Code

完整範例可在我的 GitHub 上找到

Reference

Karim Aya, How to enable ES6 (and beyond) syntax with Node and Express