レガシーアプリケーションのリニューアルにNuxt.jsで戦う #vuefes_reject

35b58828e4e24c579c35529061711dfd?s=47 Kenta Suzuki
November 10, 2018

レガシーアプリケーションのリニューアルにNuxt.jsで戦う #vuefes_reject

Vue Fes Japan 2018 Reject Conference で話しました #vuefes_reject
https://vuejs-meetup.connpass.com/event/97557/

35b58828e4e24c579c35529061711dfd?s=128

Kenta Suzuki

November 10, 2018
Tweet

Transcript

  1. エムスリー株式会社 鈴木 健太 @suusan2go 2018/11/10 Vue Fes Japan 2018 Reject

    Conference レガシーアプリケーション リニューアルにNuxt.jsで戦う #vuefes_reject
  2. 自己紹介: 鈴木 健太 • GitHub / Twitter @suusan2go • Software

    Engineer @ M3, Inc • リニューアル プロジェクトで 、Kotlin APIサーバ / フロント Nuxt.jsを担当 • 現在 AI・機械学習チームというところで機械学習 部分以外 こと全部やってます • 自慢 娘(2歳)が同年代で 可愛いことです(当親 比)。今月第二子が生まれます! 2
  3. エムスリーご紹介 3

  4. エムスリーご紹介 4 医療に関するWebサービスを多数展開 全世界で約400万人 医師会員 日本で約27万人 医師会員

  5. VueFesで Silver SponsorとBeer Sponsorでした! 5

  6. 1. リニューアルプロジェクトについて 2. なぜNuxt.jsを選択した か 3. Nuxt.js 開発におけるTips 4. まとめ

    今日お話すること
  7. 1. リニューアルプロジェクトについて 2. なぜNuxt.jsを選択した か 3. Nuxt.js 開発におけるTips 4. まとめ

    今日お話すること
  8. • 10年前 レガシーシステムをサーバサイドKotlinで、フロントエンドを Vue.js(Nuxt.js)でリニューアルしました。 • リニューアルしたレガシーシステム 2つ ◦ 1. 医師

    キャリア支援システム 企業向け画面 ◦ 2. 薬剤師 キャリア支援システム 企業向け画面 ◦ 事業規模感: 年間売上合わせて100億円超 ◦ システム 規模感: それぞれテーブル数150〜200程度 ◦ いずれ システムも10年前 技術スタック (独自Java FW、View XSLT) ◦ 新規 機能開発がとても難しい状態になっていた リニューアルについて 8
  9. 9 リニューアル 話をエンジニアHubで取材していただきました

  10. 10 サーバーサイドKotlin 話 Kotlin Festでもお話しました

  11. 医師キャリア支援システム リニューアル後 E APIサーバー 11

  12. 薬剤師キャリア支援システム リニューアル後 E APIサーバー 12

  13. 薬剤師キャリア支援システム リニューアル後 E APIサーバー 13 今日話す こちら!

  14. どれくらいレガシーかというと 14

  15. _人人人人人人人人人人人人人人_ > Internet Explorer 6.0以上 <  ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y ̄ ※今回 リニューアルで当然見直し 15

  16. _人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人_ > InternetExplorer10.0で 正しく動作しない可能性があります <  ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y ̄ 16

  17. 17

  18. • 通常 機能開発チームと 別に、リニューアルチームを立ち上げ • 薬剤師キャリア リニューアルチーム 2名 ◦ suusan2go(メイン

    エンジニア) ◦ maeharin (リーダー、他プロジェクト兼任、0.2稼働) • リニューアル 開発完了後に、徐々に通常 機能開発チームに引き継 ぎ • 現状 通常 機能開発チームで開発・運用されています リニューアル 体制 18
  19. • サーバーサイド DB そ ままに、アプリケーション部分をKotlinでAPI 化 ◦ 独自FW、既存仕様 ドキュメント 当然まったくないところからスタート

    ◦ まずコードや、出力されるSQL ログを読み解いて現状 仕様調査 ◦ ほぼフルスクラッチで書き直して、 Kotlin + SpringBoot化 • フロントエンド UIからスクラッチで刷新 ◦ XSLTというテンプレートを使っておりそ まま移行する が困難だった ▪ サーバーサイドでやるべきロジックがかなり漏れ出している・・・ ◦ ただし見た目 変わっても、画面構成 できるだけ変えないようにした ▪ 何年も変わってないUIをいきなりかえると混乱が起きる ▪ 画面を大きく変えると仕様 考慮漏れ、ユースケース考慮漏れが起きがち リニューアル 方針 19
  20. ところでVueFesで 素晴らしい発表がありました 23

  21. 24

  22. かぶってる部分も多いですが 皆さん 知見になるような お話できるように頑張ります! 25

  23. 1. リニューアルプロジェクトについて 2. なぜNuxt.jsを選択した か 3. Nuxt.js 開発におけるTips 4. まとめ

    今日お話すること
  24. リニューアル計画時 検討事項 • チームで 既にRails + Vue.jsで開発を経験しており、フロントエンド 技術にVue.jsを使うこと ほぼ確定していた •

    先行する医師キャリアチーム SPAで なく、バックエンド API Rails が叩いて、フロント 一部でVue.jsを使うMPAになっていた 27
  25. 当時先行していた医師キャリア支援システム リニューアル構成 E APIサーバー 28

  26. Rails ほぼバックエンドAPI プロキシ ような存在になる(コード サンプル) 29 class Ajax::FilmsController < ApplicationController

    def show film = film_api.staff_get_film(params[:id]) # APIへのリクエスト render json: film end def create film_api.staff_create_film(_film_create_params) # APIへのリクエスト end private def _film_create_params params.require(:film).permit! end end
  27. こ アーキテクチャ メリット・デメリット 30 • メリット ◦ セッション管理 Railsに任せられる ◦

    Vueが必要 ない画面で Rails 知識だけで構築できる ◦ サーバーサイド 経験が中心 メンバーでも比較的キャッチアップ しやすい • デメリット ◦ Vue.js エコシステムに乗り切れない ▪ 例え Vue Routerが使えない / 使いにくい ◦ VueとRails 責務が曖昧になる ▪ 例え Vue.js フォームでポストしたあと 画面遷移・バリデーションエラー どっちで出す? ◦ せっかくバックエンド APIがSwaggerで型情報付き API定義をくれている に、 Railsを通すことで型情報が失われてしまう・・・ ◦ Vue UIフレームワークをRails側 テンプレートで使いにくい
  28. Rails ほぼバックエンドAPI プロキシになっている 31 • メリット ◦ セッション管理 Railsに任せられる ◦

    Vueが必要 ない画面で Rails 知識だけで構築できる ◦ サーバーサイドが中心 メンバーでもキャッチアップが容易 • デメリット ◦ Vue.js エコシステムに乗り切れない ▪ 例え Vue Routerが使えない / 使いにくい ◦ VueとRails 責務が曖昧になる ▪ 例え 、Vue.js フォームでポストしたあと 画面遷移 どっち 責任? ◦ せっかくバックエンド APIがSwaggerで型情報付き API定義をくれている に、 Railsを通すことで型情報が失われてしまう・・・ Vueで構成されたページが多くなってくると、 VueによるSPAにUIをよせたほうが確実に設計 綺麗になりそう…… でもサーバーサイド中心 メンバーがキャッチアップできるだろうか?
  29. そこでNuxt.js 32 • 当時(2017年11月) まだベータ • リニューアル リリースまでに 1.0が出るだろうという見込みで選択 ◦

    実際、リニューアル開発中に1.0がリリース ◦ 破壊的な変更 少なく、スクロール挙動 調整くらいでなんとかなった
  30. 強力な規約と実用的な機能 33 • pages/ 配下がそ ままルーティングになる • layoutプロパティでレイアウトを切り替え • 組み込み

    Vuexストア • 共通処理をmiddlewareで管理できる • データ 取得処理をどこで書け いいか明確(fetch / asyncData) • 組み込み エラーハンドリング(context.error) • プラグイン機構(Sentry / Google Analyticsなど) 実用的なアプリケーションがすぐに開発できる 設計 ベストプラクティスに乗ることができる
  31. 豊富なドキュメント 34 • 「XXXってどうしたらいいんですか ?」に「公式ドキュメント読んで見ま しょう」で大体答えられる • 自前で1から仕組みを作っていたら、そ ドキュメンテーションとメンテナ ンス

    コスト それなりに重かった ず • 日本語ドキュメントが豊富(メンテナ 方ありがとうございます :bow: )
  32. Nuxt.jsを採用してよかったところ 35 • フロントエンド 開発にレールができた • 以下 ような問いに公式ドキュメントが答えてくれる ◦ 初期データどこでとってくる?

    ◦ ルーティングどうする? ◦ こ ページ レイアウト変えたいんだけど ◦ Webpackでこれどうやって設定する ? • セットアップに時間が取られがちなところ、最初からアプリケーション 開発に集中できた! • SPAを作るコストをめちゃくちゃ下げられた
  33. 1. リニューアルプロジェクトについて 2. なぜNuxt.jsを選択した か 3. Nuxt.js 開発におけるTips 4. まとめ

    今日お話すること
  34. Nuxt.js開発におけるTips 37 • Nuxt.js + TypeScript • components/配下 管理 •

    SSR • Nuxt.jsで テスト
  35. Nuxt.js開発におけるTips 38 • Nuxt.js + TypeScript • components/配下 管理 •

    SSR • Nuxt.jsで テスト
  36. • リニューアル対象 システム 、いわ 企業向け 採用管理業務システム • 歴史的な経緯により巨大なフォームを作らざるをえない • サーバーサイド

    ロジックをできるだけ変えないようにするに 、数十プロパティ でネストした構造を持つJSONを送らざるをえない • サーバーサイド レスポンス / 要求するリクエストが変わったときに、きちんと追 随していく が辛い • サーバーサイドと 連携に型がほしい なぜTypeScriptが使いたかった か 39
  37. サーバーサイド 実装からTypeScriptクライアントを自動生成する 41 Kotlin + Spring Boot API サーバ Spring

    Foxで実装から Swagger Definition作成 Swagger Codegenで TS APIクライアントを自動生成
  38. レスポンス プロパティが補完される! 42

  39. 存在しないプロパティを参照しようとするとコンパイルで落ちる! 43

  40. Nuxt.jsをTypeScript対応させる 44 • 実 Nuxt.js examples配下にTypeScript サンプルがあります! ◦ https://github.com/nuxt/nuxt.js/tree/dev/examples/typescript •

    modules/typescript.jsを用意して、nuxt.config.jsで読み込め OK • Vue2.5からTypeScirptサポートが強化された で、通常 オブジェクトリテラル 構文でも殆ど問題なかった(クラススタイル 使わなかった) • プロジェクトがでかくなると、初回 ビルドに1分以上かかるようになる で fork-ts-checker-webpack-plugin で、TypeCheckを別プロセスに
  41. Nuxt.jsをTypeScript対応させる 45 const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); export default function() {

    this.nuxt.options.extensions.push('ts'); this.extendBuild(config => { // ~~省略 ~~ config.plugins.push( new ForkTsCheckerWebpackPlugin({ vue: true, }), ); }); };
  42. Vue 型定義をNuxt.js用に拡張する 46 • Nuxt.jsが拡張しているコンポーネント オプション(layout, middleware, fetch, asyncData, watchQuery….)

    ために、型定義 自前で拡張する必要がある • もしかしたら公式で近々サポートがあるかも? ( #4050 #4164)
  43. pages配下で拡張されるオプション 型定義 47 declare module 'vue/types/options' { interface ComponentOptions<V extends

    Vue> { layout?: string; middleware?: string | string[]; fetch?: (context: NuxtContext) => void; asyncData?: (context: NuxtContext) => void; watchQuery?: string | string[]; head?: () => { title?: string; meta?: Array<{ hid?: string; name?: string; content?: string }>; }; } }
  44. interface NuxtContext { isClient: boolean; isServer: boolean; isStatic: boolean; idDev:

    boolean; isHMR: boolean; route: Route; store: Store<State>; env: object; query: object; nuxtState: object; req: Request; res: Response; redirect: (path: string) => void; error: (params: { statusCode?: string; message?: string }) => void; beforeNuxtRender: ({ Components, nuxtState }) => void; } Nuxt.jsが提供するContextオブジェクト 型定義 48
  45. declare namespace NodeJS { export interface Process { client: boolean;

    server: boolean; } } process.client / process.server 型定義 49
  46. TypeScriptで Linterについて 52 • TypeScriptな で普通だったらtslint……な だけれどeslint-plugin-vueが使い たい • typescript-eslint-parser

    でTypeScriptにeslintをかけられる • こ 辺り joe_reさん ブログにかなり詳しく書かれてて参考にさせていただき ました。
  47. Nuxt.jsにTypeScriptいれてよかった? 54 • 多少最初 設定 時間とられるけど間違いなくYES • ハマりどころがなかったわけで ありませんが、型で守られるメリット 方が大き

    かったです。 • 開発中 テンプレート部分に型がなくても充分恩恵受けられるなーと思ってたけ ど、QAでテンプレート部分も型で守られてたら・・・的なバグが沢山見つかった でやっ りテンプレートにも型ほしいです
  48. Nuxt.js開発におけるTips 56 • Nuxt.js + TypeScript • components/配下 管理 •

    SSR • Nuxt.jsで テスト
  49. components/配下 管理 57 • Nuxt.js components配下 管理 仕方について ノータッチな で、自分た

    ちでど ように管理していくか考える必要がある • 自分たち プロジェクトで ElementUIというUIフレームワークを使っていて、自 作 コンポーネント 数 それほど多くなかったが、コンポーネントが増えてくる と、コンポーネント 粒度に応じて階層をつけたくなる • Atomic Design 考え方でコンポーネントを管理していくことにしました
  50. ところでVueFesで 素晴らしい発表がありました 58

  51. 59

  52. Vuex 参照ルールもpiece of cakeさんとほぼ同じ 60 •

  53. ElementUI 話をします 61 • ElementUI Vue.js デスクトップUIフレームワーク • TypeScript 型定義も提供してくれる

    • 特にフォームでリッチな表現が簡単にできる、バリデーションルール(中身 yiminghe/async-validator)が柔軟に組める で重宝してました • 詳しく @maeharin 資料をご確認ください ◦ 10年前 レガシーシステムをVue.js TypeScript Elementでフルリニューアルしてい る話 • こういうコンポーネント集、業務要件に合わせようとするとカスタマイズが面倒だったり不可 能だったりしますが、ElementUI 拡張ポイントというかカスタマイズ性が高く、ほぼ困りま せんでした
  54. ElementUI コンポーネント 例 62

  55. • 正直Nuxt.js関係ないんですけど、ElementUIでパフォーマンス問題、特定ブラ ウザで 対応が必要でした • Edgeで インプットコンポーネント パフォーマンス問題 • Windows7

    / Chrome 組み合わせで み起こるインプットコンポーネント 不 具合 ElementUI で ハマりどころ 63
  56. • EdgeだけInputで一文字打つごどに、500msくらいかかる現象が発生 • Chrome、そ 他ブラウザだと問題なし • ElementUI 実装を覗いてみることに Edgeだけ激重になるInput 64

  57. ElementUI Input実装 (packages/input/src/input.vue) 65 // Inputイベントのたびに高さ調節のためにこのメソッドが呼ばれる resizeTextarea() { if (this.$isServer)

    return; var { autosize, type } = this; if (type !== 'textarea') return; if (!autosize) { this.textareaCalcStyle = { minHeight: calcTextareaHeight(this.$refs.textarea).minHeight }; return; } const minRows = autosize.minRows; const maxRows = autosize.maxRows; this.textareaCalcStyle = calcTextareaHeight(this.$refs.textarea, minRows, maxRows); },
  58. if (!hiddenTextarea) { hiddenTextarea = document.createElement('textarea'); document.body.appendChild(hiddenTextarea); } let {

    paddingSize, borderSize, boxSizing, contextStyle } = calculateNodeStyling(targetElement); hiddenTextarea.setAttribute('style', `${contextStyle};${HIDDEN_STYLE}`); hiddenTextarea.value = targetElement.value || targetElement.placeholder || ''; let height = hiddenTextarea.scrollHeight; ElementUI Input実装 ( packages/input/src/calcTextareaHeight.js ) 66
  59. scrollHeight リフローが発生する負荷 高い処理 67 scrollHeightに 529.51ミリ秒

  60. Elementにプルリク 出しているも マージされず 68

  61. • export default ElInput.extend({ methods: { // override 自動での高さ調節が必要なとき以外は、minHeightは初回の計算結果をキャッシュしておく resizeTextarea()

    { if (this.$isServer) return; const { autosize, type } = this; if (type !== 'textarea') return; if (!autosize) { this.textareaCalcStyle = { minHeight: this.textareaCalcStyle.minHeight || calcTextareaHeight(this.$refs.textarea).minHeight, }; return; } const minRows = autosize.minRows; const maxRows = autosize.maxRows; this.textareaCalcStyle = calcTextareaHeight(this.$refs.textarea, minRows, maxRows); }, }, }); カスタムコンポーネントを作ってワークアラウンド 69
  62. • 文字を入力しているとカーソルがテキストフィールド 末尾に移動してしまう • Chrome使ってくださいって言おうとおもったら、Windows7 + Chromeで発生し ていた・・・ ◦ エンジニアになって初めて、不具合

    案内で「 IE使ってください」って言いました Windows7 + Chrome 組み合わせでだけ起こる不具合 70
  63. 原因っぽいIssue 71

  64. ElementUI botが英訳してくれる 72

  65. 中国語でよくわからないが、発生環境 同じっぽい 73

  66. 中国語でよくわからないが、発生環境 同じっぽい 74 ElementUI自体 バージョンアップで解決

  67. ElementUIまとめ 75 • 管理画面・業務アプリ色 強いアプリケーションで使えるコンポーネントが沢山 で便利 • 辛かったポイントを上げましたが逆にいうとInput回り以外で ほぼ問題ありま せんでした

    • おそらくElementUIに限らずInputまわり ブラウザ間で 差異・問題がでやす い で、要動作確認 • Issueが中国語でもめげない心
  68. Nuxt.js開発におけるTips 76 • Nuxt.js + TypeScript • components/配下 管理 •

    SSR • Nuxt.jsで テスト
  69. Nuxt.jsで SSR • Nuxt.jsでSSR 確かに簡単に実現できます! • サーバーサイドと、クライアントサイド 環境 違いを意識していない と、見つけにくいバグを生んだり、ハマったりします

    77
  70. • 例え fetch APIを考えてみる • 認証をCookieで行っている場合を考える • サーバーサイドとクライアントサイドでCookie リクエスト付与 仕方が

    異なる SSRで 認証含むAPIリクエスト 78
  71. • 例え fetch APIを考えてみる • 認証絡む場合に Cookieを含めてリクエストを送る必要がある クライアントサイドでfetch APIでCookieを送る 79

    // クライアントサイドはこれでOK fetch("https://example.com", {credentials: "include"}) .then(e => e.text()) .then(text => { console.log(text) })
  72. • 例え fetch APIを考えてみる • 認証絡む場合に Cookieを含めてリクエストを送る必要がある サーバーサイドでfetch APIでCookieを送る 80

    // サーバーサイドでは、Cookieはリクエストオブジェクトに含まれる const parsedCookie = cookie.parse(context.req.headers.cookie); fetch("https://example.com", { headers: { Cookie: `session_id=${cookie["session_id"]}` } }) .then(e => e.text()) .then(text => { console.log(text) })
  73. • こ 辺り 差分を吸収できる設計にしておかないとあとで大変になる • プロジェクト固有 問題ですが、APIクライアントを自動生成してた で、 そ 辺吸収するようなコード書く

    が辛かった・・・ SSRで 認証含むAPIリクエスト 81
  74. window / document へ アクセス 82 • 不用意に window、 documentにアクセスするようなコードがあると、

    SSRするときだけエラーが起きてしまう • ただしこれ 書いていれ すぐ気がつける、Vueを使っていると window / document を書く必要がある機会 少ない で、それほど問 題にならないかも
  75. SSR非対応なプラグインへ 対応 83 • document / window を参照するようなプラグイン 読み込むだけでエ ラーになってしまう

    // plugins/wysiwyg.js // window / documentをめちゃくちゃ参照しそうなプラグイン import Vue from 'vue'; import HogeWysiwygEditor from 'hoge-wysiwyg-editor'; Vue.use(HogeWysiwygEditor);
  76. SSR非対応なプラグインへ 対応 84 • SSR時に 読み込まないようにnuxt.config.jsで ssr: false オプションを つける

    // nuxt.config.js plugins: [{ src: '~plugins/wysiwyg', ssr: false }],
  77. Nuxt.jsで SSRについて 85 • 結論として SSRする 途中でやめることにしました ◦ SSRしたときだけ起こる見つけにくいバグを生んでしまいそう ◦

    QAが大変になりそう • ほぼログイン前提かつ、SEOが必要ないアプリケーションで、SSR 必 須要件で なかったため、途中からmode: spaに切り替え • デプロイ mode: spaでビルドしたも を配信 • SSR簡単にできるけど、認証含んだAPIが必要だったり、外部ライブラリ 読み込んだりしていくと、辛くなってくる で、最初 設計でサーバーサ イドとクライアントサイド 違いをよく考慮しましょう〜
  78. Nuxt.js開発におけるTips 86 • Nuxt.js + TypeScript • components/配下 管理 •

    SSR • Nuxt.jsで テスト
  79. Nuxt.js アプリケーションをテストしやすくするために • components/とpages/ 棲み分けを意識する • components/ にマウントしただけでAPIアクセスしたりするコンポーネントがある と、扱いにくいしテストしにくい •

    初期データ 取得など 基本的に pages/ 配下で行うようにする • pages/コンポーネント Nuxt.jsに依存する で、できるだけ薄くしておくと、コン ポーネント テストがしやすくなる 87
  80. Nuxt.jsで テスト 考え方 90 • pages/ テスト E2Eテストで担保する • components/配下

    テストをjest + vue-test-utilsで頑張る • コンポーネント テスト どうする?
  81. ところでVueFesで 素晴らしい発表がありました 91

  82. 92

  83. とても勉強になる資料だった で みなさんチェックしましょう 93

  84. まとめ • レガシーなアプリケーション UIをNuxt.jsでリニューアルしました ◦ Nuxt.jsを使うとフロントエンド 開発にレールができる ◦ ドキュメントが豊富でキャッチアップが容易 •

    Nuxt.js開発 Tips ◦ TypeScriptも使えます!おすすめです! ◦ components/配下 管理 ◦ SSR クライアント / サーバー環境 違いを意識して、設計初期段階から 抽象化しよう ◦ pages/配下 薄く保って、テストしやすい状態を作ろう • VueFes 資料 知見に溢れている で皆さん読みま しょう!!!(来年 採択されるぞ!) 94