DockerでNode.js + TypeScript + WebPack環境構築
はじめに
今回はこちらの本のハンズオンを試すためにDockerの環境を作成しました。
作成にあたりトラブルが起きてかなりの時間を費やしたので書籍を今後学習する人のため、webpackでホットリロードを効かせたい人のためにまとめます。
また、環境構築で発生したエラーはこちらに載せました。
開発環境
- VSCode
- Ubuntu 20.04 (WSL2)
- Docker 20.10.12
- docker-compose version v2.2.3
- git version 2.25.1
環境構築
作成後は以下のディレクトリになります。
ts_docker_env
┣ docker
  ┣ nginx
    ┣ default.conf
┣ src
  ┣ html
    ┣ js
      ┣ dist
        ┣ main.js
    ┣ ts
      ┣ index.ts
    ┣ index.html
    ┣ node_modules
    ┣ package-lock.json
    ┣ package.json
    ┣ tsconfig.json
    ┣ webpack.config.js
┣ Dockerfile
┣ docker-compose.yml
┣ README.md
ts_docker_envのディレクトリにいるところから始めます。
$ mkdir ts_docker_env
$ cd ts_docker_env
NodeとNginx作成
まずDockerfileとdocker-compose.ymlを作成します。
コンテナはNode.jsのコンテナとNginxのコンテナを利用します。
Node.jsコンテナでコンパイルして、Nginxで表示する構成です。
FROM node
Dockerfileは今後パッケージ追加を考えて作成していますが、イメージだけなのでdocker-compose.ymlにimageで指定してもよいです。
version: '3'
services:
  app:
    image: nginx:latest
    container_name: "app"
    ports:
      - "8080:80"
    volumes:
      - ./src/html:/app
      - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
  node:
    build: .
    container_name: node
    tty: true
    user: node
    working_dir: /usr/src/app
    volumes:
      - ./src:/usr/src/app
./docker/nignxのディレクトリを作成します。
$ mkdir -p ./docker/nginx
Nginxの設定ファイルを作成します。
server {
    listen       80;
    listen       443;
    server_name  localhost;
    location / {
        root   /app;
        index  index.html index.htm;
    }
    error_page  404 /404.html;
    location = /40x.html {
        root   /usr/share/nginx/html;
    }
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}
TypeScriptの追加
./src/html/js/dictと./src/html/js/tsのディレクトリを作成します。
$ mkdir -p ./src/html/js/dict
$ mkdir -p ./src/html/js/ts
node.jsの設定ファイルを作成します。
$ docker-compose build
$ docker-compose up
# 別ターミナルを開く
$ docker exec -it node sh
$ npm init -y
./src/package.jsonが作成されたのでscriptsを修正します。
(省略)
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "clean": "rimraf html/js/dist",
    "tsc": "tsc",
    "build": "npm-run-all clean tsc"
  },
(省略)
必要なライブラリをいれていきます
$ npm install typescript @types/node --save-dev
node_modulesとpackage-lock.jsonが作成されました
次にTypescriptの設定ファイルを作成します
$ npx tsc --init
tsconfig.jsが作成されました
Webpackの追加
Webpackをインストールします
$ npm install webpack ts-loader @webpack-cli/generators
Webpackの設定ファイルを作成します
$ npx webpack-cli init
ターミナルで質問されるので答えていきます。基本的にはエンターですが、一部違うところだけ載せます。
1問目: Typescriptを選択 → Enter
2-4問目: Enter
5問目: noneを選択 → Enter
6問目: Enter
7問目: npmを選択 → Enter
8問目: Enter → y → Enter
9問目: Enter → y → Enter
tsconfig.jsを修正します
{
  "compilerOptions": {
    "target": "ES2019",
    "module": "commonjs",
    "sourceMap": true,
    "outDir": "./html/js/dist",
    "strict": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": [
    "html/js/ts/**/*"
  ]
}
./src/html/js/ts/*のファイルを対象にしました。
またコンテナ上でnpx tsc TSファイル名でトランスコンパルする際に変換したjsのファイルの出力先を./html/js/distに設定しました
./src/html/js/ts.....TSファイルを入れる
./src/html/js/dist.....変換したJSファイルを入れる
という使い分けを行っています。
次にwebpack.config.jsを修正します。
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const WorkboxWebpackPlugin = require("workbox-webpack-plugin");
const isProduction = process.env.NODE_ENV == "production";
const config = {
  entry: "./html/js/ts/index.ts",
  output: {
    path: path.join(__dirname, "/html/js/dist"),
    filename: "main.js"
  },
  devServer: {
    open: true,
    host: "localhost",
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "./html/index.html",
    }),
    // Add your plugins here
    // Learn more about plugins from https://webpack.js.org/configuration/plugins/
  ],
  module: {
    rules: [
      {
        test: /\.(ts|tsx)$/i,
        loader: "ts-loader",
        exclude: ["/node_modules/"],
      },
      {
        test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
        type: "asset",
      },
      // Add your rules for custom modules here
      // Learn more about loaders from https://webpack.js.org/loaders/
    ],
  },
  resolve: {
    extensions: [".tsx", ".ts", ".jsx", ".js", "..."],
  },
};
module.exports = () => {
  if (isProduction) {
    config.mode = "production";
    config.plugins.push(new WorkboxWebpackPlugin.GenerateSW());
  } else {
    config.mode = "development";
  }
  return config;
};
サンプルファイルの作成
実際に画面に表示するHTMLを作成します。
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8" />
  <title>Sample</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
  <script src="./js/dist/main.js"></script>
</head>
<body>
  <h1 class="bg-primary text-white p-2">Sample page</h1>
  <div class="container py-2">
    <p class="h5" id="target">wait...</p>
  </div>
</body>
</html>
tsファイルを作成します
window.addEventListener('load',(event)=> {
  let p = document.querySelector('#target')
  p!.textContent = "This is message by TypeScript."
})
./src/index.htmlと./src/src/index.tsが不要なファイルなので削除します。webpack-cli initで自動作成してしまったものです
起動確認
$ npm run build
$ npm run serve
起動したらlocalhost:8888にアクセスします。
以下の画面が表示されれば成功です。

ホットリロードを使う場合は以下のコマンドで起動します
$ npm run watch
serveはメモリにコンパイルされるため、開発はwatchを使う必要があるそうです。
おわりに
たまたま本をやろうと思って購入したらまさかの1章で辛い思いをするとはと思いました。これでやっと肝心の勉強を進められるようになりました。
今回作成した環境は以下のリポジトリにあります




Discussion