すきま風

勉強したことのメモとか

フロントエンド開発勉強日誌 2

フロントエンド開発勉強日誌の続きです。前回は Thymeleaf 導入までやっています。

今回はTypeScript開発環境を作ります。

TypeScript開発環境を作る

やることは以下

  • 事前勉強
  • Node Install
  • ProjectにScript実装用のDirectoryを用意する
  • package.json用意
  • Library Install
  • TypeScript実装
    • view
    • container
    • action
    • entry
  • 設定ファイル
  • build
  • ThymeleafのHeadにJavaScriptを設定
  • express (proxy)


やること多い...多くない?

最終的なPackage構成はこんな感じになります。

demo
├── adapters
│    └── web # controllerを記述する
├── application
├── configuration
├── domain
└── script  # TypeScriptとかを書く場所
      ├── bin
      │    └── proxy.js # expressでバックエンドをProxyする
      ├── src
      │    ├── core
      │    └── sample
      │          ├── actions
      │          │    └── sample_action.ts      # actionを記述する
      │          ├── entry
      │          │    └── entry.ts              # entry point
      │          └── views
      │                ├── samle_body.ts         # htmlを記述する
      │                └── sample_container.ts   # 振る舞いを記述する
      ├── package.json
      ├── package.lock.json
      ├── tsconfig.arrow-js.json
      ├── tsconfig.base.json
      ├── tsconfig.json
      └── webpack.config.js

事前勉強

TypeScript Deep Diveを2回通しで読みました。

Node Install

既に入っていたので省略します。過去の自分がbrew install nodebrew して npm i -g npm した後で勉強を挫折したのだと思います。

script directoryを用意する

TypeScriptを実装するDirectoryをSpringのSubProjectと並列に置きます。別Projectを作ってもOK。

package.json

最低限用意しておきます。

{
  "name": "script",
  "version": "1.0.0",
  "description": "",
  "main": "index.js"
}

Library Install

webpack, TypeScript

webpackとTypeScriptを導入します。webpackはcssとかjsをバンドルしてくれる凄い奴です。

$ cd script
$ npm i -D webpack webpack-cli typescript ts-loader

node, jQuery

nodeの標準moduleを使いたい && jQueryも使うのでこのあたりも入れます。

$ npm i -D @types/jquery
$ npm i -D @types/node

express

node製の軽量Webアプリケーション。詳細はここを参照。今回はproxyに利用します。

$ npm i -D express
$ npm i -D http-proxy-middleware

lit-html

Tagged Templatesでviewを管理するライブラリ。HTMLコーディング用に入れます。

$ npm i lit-html

TypeScript実装

ここまできて、ようやくTypeScriptの実装に入ります。

SampleBody.ts

ページレンダリング時に表示するHTMLをlit-htmlで記述します

// sample/views/sample_body.ts
import { html } from 'lit-html';

export const SampleHtml = () => html`
<div>
  render html!!
</div>
`;

SampleContainer.ts

Presentational/Container Components Patternで実装します。

SampleContainer.tsに今後振る舞いを定義していく予定です。まずは初期表示時のRenderingを実装します。

// sample/views/sample_container.ts
import { render } from 'lit-html';
import { SampleHtml } from './sample_body';

export class SampleContainer {
  constructor() {
    // #js-sample-jsonにSampleHtmlをrender
    render(SampleHtml(), document.getElementById('js-sample-json'))
  }
}

SampleAction.ts

Eventを定義するAction Classを用意します。まずはContainerの初期化を実装します。

// sample/actions/sample_action.ts
export class SampleAction {
  static init(containerClass: any) {
    // sample-containerの初期化
    new containerClass();
  }
}

entry.ts

webpack.config.tsに設定するentry pointを用意します。 ここでEventListenerを設定します。SampleActionのinitを呼び、その中で引数のContainerをnewします。

// sample/entry/entry.ts
import { SampleContainer } from '../views/sample_container';
import { SampleAction } from '../actions/sample_action';

document.addEventListener('DOMContentLoaded', () => {
  SampleAction.init(SampleContainer);
});

設定ファイル

tsconfig.json, webpack.config.js を準備します。

tsconfig.base.json

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "incremental": true,
    "tsBuildInfoFile": "./dist/.tsbuildinfo",
    "outDir": "dist",
    "lib": [
      "es5",
      "es2015.promise",
      "es2015.collection",
      "dom"
    ],
    "sourceMap": true,
    "moduleResolution": "node",
    "importHelpers": true
  }
}

tsconfig.json

{
  "extends": "./tsconfig.base",
  "compilerOptions": {
    "allowJs": true
  },
  "include": [
    "src/**/*",
    "test/**/*",
    "node_modules/lit-html"
  ],
  "exclude": [
    "node_modules"
  ]
}

tsconfig.arrow-js.json

{
  "extends": "./tsconfig.base",
  "compilerOptions": {
    "allowJs": true
  },
  "include": [
    "node_modules/lit-html"
  ]
}

webpack.config.js

/* eslint-env es6, node */
const path = require('path');
const { SourceMapDevToolPlugin } = require('webpack');

module.exports = (env, argv) => {
  return {
    mode: argv.mode || 'development',
    entry: {
      sample: './src/sample/entry/entry.ts'
    },
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'target')
    },
    module: {
      rules: [
        {
          test: /\.ts$/,
          use: 'ts-loader'
        },
        // https://github.com/Polymer/lit-html/issues/516#issuecomment-475261804
        // lit-htmlがES5にtranspileされるように設定する
        {
          test: /\.js$/,
          include: [/node_modules\/lit-html/],
          use: {
            loader: 'ts-loader',
            options: {
              configFile: path.resolve(__dirname, 'tsconfig.allow-js.json')
            }
          }
        }
      ]
    },
    plugins: [
      new SourceMapDevToolPlugin({
        filename: 'sourcemaps/[file].[hash].map',
        publicPath: 'http://localhost:3150/target/',
        append: `\n//# sourceMappingURL=[url]`
      })
    ],
    resolve: {
      extensions: ['.ts', '.js']
    },
    // https://github.com/Microsoft/typed-rest-client/issues/139
    // https://github.com/jsoma/tabletop/issues/158#issuecomment-398733851
    externals: ['tls', 'net', 'fs']
  };
};

build

TypeScriptをwebpackでbuildします。

$ ./node_modules/.bin/webpack

Built at: 2099-12-31 23:59:59
                                               Asset      Size  Chunks                   Chunk Names
                                    sample.bundle.js  82.8 KiB  sample  [emitted]        sample
sourcemaps/sample.bundle.js.8606896b6aa71955ad07.map  3.54 KiB  sample  [emitted] [dev]  sample
Entrypoint sample = sample.bundle.js sourcemaps/sample.bundle.js.8606896b6aa71955ad07.map
[./src/sample/actions/sample_action.ts] 358 bytes {sample} [built]
[./src/sample/entry/entry.ts] 338 bytes {sample} [built]
[./src/sample/views/samle_body.ts] 466 bytes {sample} [built]
[./src/sample/views/sample_container.ts] 469 bytes {sample} [built]
    + 12 hidden modules

Thymeleaf

ここでSpringに戻り、ThymeleafのHeadにbuildしたJavascriptを設定します。またターゲットのDOMを用意します。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="ja">
<head>
  <title>top page</title>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <script src="/static/sample.bundle.js" crossorigin="anonymous" defer=""></script>
</head>
<body>
<div>
  <p>Presenterで出力を加工する</p>
  <p th:text="${view.fooLabel()}" />
</div>
<div>
  <p>sample-json</p>
  <!--  TypeScriptで何かをRenderingする場所 -->
  <div id="js-sample-json"></div>
</div>
</body>
</html>

express

expressを使って、Spring (サーバサイド) へのリクエストをproxyします。

// bin/proxy.js

'use strict';

const path = require('path');
const express = require('express');
const httpProxyMiddleware = require('http-proxy-middleware');

const app = express();

// arrow function
// 引数1つなら()省略可能 one linerなら {} 省略可能
const proxy = host =>
  httpProxyMiddleware({
    target: host,
    changeOrigin: true
  });

const FRONT_HOST = 'http://localhost:8080';
const SCRIPT_TARGET_DIR = path.join(__dirname, '..', 'target');

// static files
// https://expressjs.com/ja/starter/static-files.html
app.use('/static', express.static(SCRIPT_TARGET_DIR));

// サーバサイドへのリクエストをproxyする
app.use('/', proxy(FRONT_HOST));

// listen 最高のサイトなのでPORTは3150
app.listen(process.env.PORT || 3150);

expressを起動します。

$ node bin/proxy.js
[HPM] Proxy created: /  -> http://localhost:8080

IntelliJでSpringも起動してアクセスします。

http://localhost:3150/front/practice

HTMLが表示されます。TypeScriptで挿入した render html!! が表示されたのでとりあえず動いています!

まとめ

TypeScriptの実装環境まで用意しました。難しい...難しくない?導入までの障壁高すぎではないだろうか。。フロントエンドエンジニアって大変ですね 😑