Lambdaカクテル

京都在住Webエンジニアの日記です

サクサク年表君開発日記(フロントエンド環境構築編)

f:id:Windymelt:20200609013345p:plain
TypeScriptが動くようになった

blog.3qe.us

先日からの作業により,必要最低限のバックエンドの機能が出来た。とはいえ年表の作成UIはデバッグ用途でしかなく,到底実用には堪えない。表示だけがいちおうまともにできるようになった,という状態だ。

そういうわけで投稿用UIを作ることになるのだが,HTMLフォームでいったん投稿用UIを作ってからTypeScriptとReactなどを駆使して置換するというのもあまりに無駄であり,そもそも年表投稿UIを静的HTMLのみでやるのはかなり大変だ。そこで一旦フロントエンド環境を構築してHello Worldを表示させるまで行った。

npm

yarnって最近あまり聞かないんですけどnpmでいいんですよね?

% npm init

TypeScript + Webpack + React

基本的にここ見ながらひととおり構築します。次回あたりでtslint入れたり,tsconfig.jsonをオシャレにしようと思います。動けばヨシ!

ics.media

% npm i -D webpack webpack-cli typescript ts-loader
% npm i -S react react-dom @types/react @types/react-dom

僕はts-loaderっていう文字列を見るたびに頭痛がします。babel-loaderとかなんかいろいろが混在して百鬼夜行になっていたのを思い出すからです…………

そのへんはこれ読むとだいたい分かります

speakerdeck.com

tsconfig.json

多分これが一番速いと思います(大嘘)

{
    "compilerOptions": {
        "sourceMap": true,
        "target": "es5",
        "module": "es2015",
        "jsx": "react",
        "moduleResolution": "node",
        "lib": [
            "es2020",
            "dom"
        ]
    }
}

webpack.config.js

webpack何もわからない。

module.exports = {
    mode: "development",

    entry: "./src/entrypoint.ts",
    output: {
        path: `${__dirname}/dist`,
        filename: "entrypoint.js"
    },
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                use: "ts-loader"
            }
        ]
    },
    resolve: {
        extensions: [".ts", ".tsx", ".js", ".json"]
    }
};

package.json

{
  "name": "timeline",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack",
    "watch": "webpack -w",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "windymelt",
  "license": "MIT",
  "devDependencies": {
    "ts-loader": "^7.0.5",
    "typescript": "^3.9.5",
    "webpack": "^4.43.0",
    "webpack-cli": "^3.3.11"
  },
  "dependencies": {
    "@types/react": "^16.9.35",
    "@types/react-dom": "^16.9.8",
    "react": "^16.13.1",
    "react-dom": "^16.13.1"
  },
  "private": true
}

というわけでこれでnpm run buildができるようになった。これを行うとfrontend/dist/entrypoint.jsが吐き出される。

entrypoint.tsには素朴なHello Worldを書いておく。

console.log("hello, typescript!")

nginx

さて,開発するにあたっては,ブラウザにはコンパイルしたての最新のjsを読み込ませたいが,ブラウザは最新のjsがどこにあるかなんて知らない。ブラウザと成果物JSとの橋渡しをnginxにやってもらう。ついでにアプリケーションサーバの前段にもなってもらう。

nginxをホストに直でインストールする時代は終わっているのでDockerを使う。

まずdocker-compose.ymlを書く。

version: '3'
services:
    nginx: # js file distributing: local development only
        image: nginx:1.19.0
        volumes:
            - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
            - ./frontend/dist/:/usr/share/nginx/js/:ro
        ports: 
            - 8000:80

nginxサービスを作成し,TypeScriptをコンパイルして出来たJSファイル とnginx.confとをマウントしてコンテナから視えるようにする。

ここでnginx.confは以下の通り。デフォルトっぽいのをパチってきただけなので特に変哲なし。JSファイルの配信と,アプリケーションサーバへの転送だけが主たる記述である。

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;

    server {
        listen       80 default_server;
        listen       [::]:80 default_server;
        server_name  _;
        root         /usr/share/nginx/html;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location ~ ^/js/(.+) {
            alias /usr/share/nginx/js/$1;
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }

        location / {
            proxy_pass http://192.168.xxx.yyy:8080; # TODO: dockerize backend
        }
    }
}

proxy_passにはlocalhostと書きたかったが,アプリケーションサーバはdocker化されていないのでうまく接続できない。このためLAN上のアドレスを指定している。そのうちアプリケーションサーバもDocker化したいが,JVMアプリケーションをDocker化するの明らかにダルいんだよなー。

動作確認

アプリケーションサーバを起動する。

% sbt
sbt:Timeline> jetty:stop;jetty:start

nginxも起動する。

% docker-compose up

ここでブラウザを使ってlocalhost:8000にアクセスする。

f:id:Windymelt:20200609021017p:plain
nginx経由でアプリケーションサーバに接続している

よしよし。

f:id:Windymelt:20200609013345p:plain
TypeScriptが動くようになった

Hello Worldも動いているので,無事JSが配信されている。

まとめ

フロントエンドまわりの環境構築ができた。成果物をdocker+nginxでブラウザに読み込ませる,一般的(だと思う)なローカル開発環境だと思う。npmやアプリケーションサーバもDockerに載せられると色々助かる。

次回からは年表書くためのUIを書いていく。TypeScript書く環境は整っているので,あとはルータなどを書くところからやる。そろそろ複雑になってきたのでテスト環境も整えたい。