Just Tried Stimulus

Just Tried Stimulus

4d9515a22091b00688c6fe9db89e0601?s=128

cafedomancer

June 27, 2019
Tweet

Transcript

  1. Stimulus をちょっと 使ってみました Shohei Umemoto (@cafedomancer) Rakuten Tech Sapporo WEBエンジニア

    MeetUp@札幌 #5 (June 27, 2019)
  2. 自己紹介 • Shohei Umemoto (@cafedomancer) • 社会人4 年目です (たぶん…) •

    開発では Ruby / Rails をよく使います • fish shell / fzf/ neovim が好きです
  3. 会社紹介 • Enishi Tech Inc. (@enishitech) • いろいろなことをやっています • お客様といっしょに考えます

    • Ruby / Rails だけの会社というわけでは
 特にありません (でも大切にしています)
  4. よろしくおねがいします

  5. 話すこと • Stimulus の紹介 • Stimulus の実践的な使い方 (?) • その他いろいろ気になっていること

  6. 話さないこと • JavaScript の細かい話 • Stimulus のセットアップやインストール • Webpacker (Webpack)

    の (以下同文) • Stimulus を使う上での Tips みたいなの (in-depth なので)
  7. Stimulus 入門編

  8. Stimulus とは? • Basecamp (元 37signals) が作っている JavaScript フレームワーク •

    すごく雑に言うと他のフレームワーク
 でいうところの Controller しかない • https://stimulusjs.org もぜひ目を通して
 みてください
  9. Stimulus の 3 つの要素 • 出てくるのは 3 つだけ • Controller

    / Action / Target • 実際にコードを読んでみよう!
  10. Stimulus のコード <!--HTML from anywhere--> <div data-controller="hello"> <input data-target="hello.name" type="text">

    <button data-action="click->hello#greet"> Greet </button> <span data-target="hello.output"> </span> </div> // hello_controller.js import { Controller } from "stimulus" export default class extends Controller { static targets = [ "name", "output" ] greet() { this.outputTarget.textContent = `Hello, ${this.nameTarget.value}!` } }
  11. Controller の指定 <!--HTML from anywhere--> <div data-controller="hello"> <input data-target="hello.name" type="text">

    <button data-action="click->hello#greet"> Greet </button> <span data-target="hello.output"> </span> </div> // hello_controller.js import { Controller } from "stimulus" export default class extends Controller { static targets = [ "name", "output" ] greet() { this.outputTarget.textContent = `Hello, ${this.nameTarget.value}!` } } <!--HTML from anywhere--> <div data-controller="hello"> <input data-target="hello.name" type="text"> <button data-action="click->hello#greet"> Greet </button> <span data-target="hello.output"> </span> </div> // hello_controller.js import { Controller } from "stimulus" export default class extends Controller { static targets = [ "name", "output" ] greet() { this.outputTarget.textContent = `Hello, ${this.nameTarget.value}!` } }
  12. Action の指定 <!--HTML from anywhere--> <div data-controller="hello"> <input data-target="hello.name" type="text">

    <button data-action="click->hello#greet"> Greet </button> <span data-target="hello.output"> </span> </div> // hello_controller.js import { Controller } from "stimulus" export default class extends Controller { static targets = [ "name", "output" ] greet() { this.outputTarget.textContent = `Hello, ${this.nameTarget.value}!` } } <!--HTML from anywhere--> <div data-controller="hello"> <input data-target="hello.name" type="text"> <button data-action="click->hello#greet"> Greet </button> <span data-target="hello.output"> </span> </div> // hello_controller.js import { Controller } from "stimulus" export default class extends Controller { static targets = [ "name", "output" ] greet() { this.outputTarget.textContent = `Hello, ${this.nameTarget.value}!` } }
  13. Target の指定 <!--HTML from anywhere--> <div data-controller="hello"> <input data-target="hello.name" type="text">

    <button data-action="click->hello#greet"> Greet </button> <span data-target="hello.output"> </span> </div> // hello_controller.js import { Controller } from "stimulus" export default class extends Controller { static targets = [ "name", "output" ] greet() { this.outputTarget.textContent = `Hello, ${this.nameTarget.value}!` } } // hello_controller.js import { Controller } from "stimulus" export default class extends Controller { static targets = [ "name", "output" ] greet() { this.outputTarget.textContent = `Hello, ${this.nameTarget.value}!` } } <!--HTML from anywhere--> <div data-controller="hello"> <input data-target="hello.name" type="text"> <button data-action="click->hello#greet"> Greet </button> <span data-target="hello.output"> </span> </div> <!--HTML from anywhere--> <div data-controller="hello"> <button data-action="click->hello#greet"> Greet </button> <span data-target="hello.output"> </span> </div> // hello_controller.js import { Controller } from "stimulus" export default class extends Controller { greet() { this.outputTarget.textContent = `Hello, ${this.nameTarget.value}!` } } // hello_controller.js import { Controller } from "stimulus" export default class extends Controller { greet() { `Hello, ${this.nameTarget.value}!` } }
  14. ねっ?カンタンでしょ! • これだけです • ちょっとデモをします • stimulus-starter を使います

  15. Stimulus の 3 つの要素 (再) • Controller • JavaScript で動作させたい単位となるもの

    • Action • HTML のイベントが発生したときに実行されるもの • Target • HTML からの入力と HTML への出力の対象となるもの
  16. サンプルをもう 1 個 <!--HTML from anywhere--> <div data-controller="clipboard"> PIN: <input

    data-target="clipboard.source" type="text" value="1234" readonly> <button data-action=“clipboard#copy"> Copy to Clipboard </button> </div> // clipboard_controller.js import { Controller } from "stimulus" export default class extends Controller { static targets = [ "source" ] copy() { this.sourceTarget.select() document.execCommand("copy") } }
  17. Controller の指定 // clipboard_controller.js import { Controller } from "stimulus"

    export default class extends Controller { static targets = [ "source" ] copy() { this.sourceTarget.select() document.execCommand("copy") } } // clipboard_controller.js import { Controller } from "stimulus" export default class extends Controller { static targets = [ "source" ] copy() { this.sourceTarget.select() document.execCommand("copy") } } <!--HTML from anywhere--> <div data-controller="clipboard"> PIN: <input data-target="clipboard.source" type="text" value="1234" readonly> <button data-action="clipboard#copy"> Copy to Clipboard </button> </div>
  18. Action の指定 // clipboard_controller.js import { Controller } from "stimulus"

    export default class extends Controller { static targets = [ "source" ] copy() { this.sourceTarget.select() document.execCommand("copy") } } <!--HTML from anywhere--> <div data-controller="clipboard"> PIN: <input data-target="clipboard.source" type="text" value="1234" readonly> <button data-action="clipboard#copy"> Copy to Clipboard </button> </div> // clipboard_controller.js import { Controller } from "stimulus" export default class extends Controller { static targets = [ "source" ] copy() { this.sourceTarget.select() document.execCommand("copy") } }
  19. Target の指定 // clipboard_controller.js import { Controller } from "stimulus"

    export default class extends Controller { static targets = [ "source" ] copy() { this.sourceTarget.select() document.execCommand("copy") } } <!--HTML from anywhere--> <div data-controller="clipboard"> PIN: <input data-target="clipboard.source" type="text" value="1234" readonly> <button data-action="clipboard#copy"> Copy to Clipboard </button> </div> // clipboard_controller.js import { Controller } from "stimulus" export default class extends Controller { static targets = [ "source" ] copy() { this.sourceTarget.select() document.execCommand("copy") } } // clipboard_controller.js import { Controller } from "stimulus" export default class extends Controller { copy() { this.sourceTarget.select() document.execCommand("copy") } }
  20. クリップボードのサンプル • こちらもデモをしてみます • 同じく stimulus-starter を使います

  21. Stimulus の pros & cons • とてもシンプルなので分かりやすく覚えることが少ない • Reference を見てみると分かると思います

    • HTML と JavaScript との関係がひと目で分かる • このあと説明します • まわりで使っている人が見当たらない • ノウハウがまだ無さそう • ゴリゴリの SPA みたいなのを作るのには向いてなさそう
  22. jQuery を使ったコードとの比較 $('div.hello button.hello-greet’) .click(function() { const $name = $(this).siblings('input.hello-name')

    const $output = $(this).siblings('span.hello-output') $output.text(`Hello, ${$name.val()}!`) }) <div class="hello"> <input class="hello-name" type="text"> <button class="hello-greet"> Greet </button> <span class="hello-output"> </span> </div> <div data-controller="hello"> <input data-target="hello.name" type="text"> <button data-action="click->hello#greet"> Greet </button> <span data-target="hello.output"> </span> </div> import { Controller } from "stimulus" export default class extends Controller { static targets = [ "name", "output" ] greet() { this.outputTarget.textContent = `Hello, ${this.nameTarget.value}!` } }
  23. Stimulus のまとめ • Controller しかない JavaScript フレームワーク • 要素としては Controller/

    Action / Target の 3 つだけがある • ちょっとした動きモノを作るのに向いてそう
  24. Stimulus 実践編

  25. HTML に対する Stimulus の考え方 • “Stimulus is a JavaScript framework

    with modest ambitions. It doesn’t seek to take over your entire front-end—in fact, it’s not concerned with rendering HTML at all.” • でも HTML を render したいケースも無くはない • でもテンプレートの仕組みは用意されていない
  26. HTML を render したいケース • API から JSON を受け取ってそれを HTML

    で表示したい • ユーザーのアクションに基づいて要素を追加したい
  27. こんな issue がありました • https://github.com/stimulusjs/stimulus/ issues/41 • “HTML 要素の追加に <template>

    タグを
 使うのはどうだろうか?” • というわけで <template> タグを使って要 素の追加をやってみました
  28. <template> タグとは? • https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ template • HTML の断片を <template> タグの要素として記述することで


    いわゆるテンプレートとして使用することができる • https://www.html5rocks.com/en/tutorials/webcomponents/template/ も参考にしました
  29. <template> タグの使い方 <table id="producttable"> <thead> <tr> <td>UPC_Code</td> <td>Product_Name</td> </tr> </thead>

    <tbody> <!-- ͜͜ʹੜ੒ͨ͠ཁૉΛࠩ͠ࠐΉ --> </tbody> </table> <template id="productrow"> <tr> <td class="record"></td> <td></td> </tr> </template> let t = document.querySelector('#productrow') let tb = document.querySelector('tbody') let clone = document.importNode(t.content, true) let td = clone1.querySelectorAll('td') td[0].textContent = '1235646565' td[1].textContent = 'Stuff'
 tb.appendChild(clone)
  30. <template> タグの使い方 <table id="producttable"> <thead> <tr> <td>UPC_Code</td> <td>Product_Name</td> </tr> </thead>

    <tbody> <!-- ͜͜ʹੜ੒ͨ͠ཁૉΛࠩ͠ࠐΉ --> </tbody> </table> <template id="productrow"> <tr> <td class="record"></td> <td></td> </tr> </template> let t = document.querySelector('#productrow') let tb = document.querySelector('tbody') let clone = document.importNode(t.content, true) let td = clone1.querySelectorAll('td') td[0].textContent = '1235646565' td[1].textContent = 'Stuff'
 tb.appendChild(clone)
  31. <template> タグの使い方 <table id="producttable"> <thead> <tr> <td>UPC_Code</td> <td>Product_Name</td> </tr> </thead>

    <tbody> <!-- ͜͜ʹੜ੒ͨ͠ཁૉΛࠩ͠ࠐΉ -—> </tbody> </table> <template id="productrow"> <tr> <td class="record"></td> <td></td> </tr> </template> let t = document.querySelector('#productrow') let tb = document.querySelector('tbody') let clone = document.importNode(t.content, true) let td = clone1.querySelectorAll('td') td[0].textContent = '1235646565' td[1].textContent = 'Stuff'
 tb.appendChild(clone)
  32. <template> タグの使い方 <table id="producttable"> <thead> <tr> <td>UPC_Code</td> <td>Product_Name</td> </tr> </thead>

    <tbody> <!-- ͜͜ʹੜ੒ͨ͠ཁૉΛࠩ͠ࠐΉ --> </tbody> </table> <template id="productrow"> <tr> <td class="record"></td> <td></td> </tr> </template> let t = document.querySelector('#productrow') let tb = document.querySelector('tbody') let clone = document.importNode(t.content, true) let td = clone1.querySelectorAll('td') td[0].textContent = '1235646565' td[1].textContent = 'Stuff'
 tb.appendChild(clone)
  33. <template> タグの使い方 <table id="producttable"> <thead> <tr> <td>UPC_Code</td> <td>Product_Name</td> </tr> </thead>

    <tbody> <!-- ͜͜ʹੜ੒ͨ͠ཁૉΛࠩ͠ࠐΉ --> </tbody> </table> <template id="productrow"> <tr> <td class="record"></td> <td></td> </tr> </template> let t = document.querySelector('#productrow') let tb = document.querySelector('tbody') let clone = document.importNode(t.content, true) let td = clone1.querySelectorAll('td') td[0].textContent = '1235646565' td[1].textContent = 'Stuff'
 tb.appendChild(clone)
  34. <template> タグの使い方 <table id="producttable"> <thead> <tr> <td>UPC_Code</td> <td>Product_Name</td> </tr> </thead>

    <tbody> <!-- ͜͜ʹੜ੒ͨ͠ཁૉΛࠩ͠ࠐΉ --> </tbody> </table> <template id="productrow"> <tr> <td class="record"></td> <td></td> </tr> </template> let t = document.querySelector('#productrow') let tb = document.querySelector('tbody') let clone = document.importNode(t.content, true) let td = clone1.querySelectorAll(‘td') td[0].textContent = '1235646565' td[1].textContent = 'Stuff'
 tb.appendChild(clone)
  35. <template> タグの使い方 <table id="producttable"> <thead> <tr> <td>UPC_Code</td> <td>Product_Name</td> </tr> </thead>

    <tbody> <!-- ͜͜ʹੜ੒ͨ͠ཁૉΛࠩ͠ࠐΉ --> </tbody> </table> <template id="productrow"> <tr> <td class="record"></td> <td></td> </tr> </template> let t = document.querySelector('#productrow') let tb = document.querySelector('tbody') let clone = document.importNode(t.content, true) let td = clone1.querySelectorAll('td') td[0].textContent = '1235646565' td[1].textContent = 'Stuff'
 tb.appendChild(clone)
  36. <template> タグの使い方 <table id="producttable"> <thead> <tr> <td>UPC_Code</td> <td>Product_Name</td> </tr> </thead>

    <tbody> <!-- ͜͜ʹੜ੒ͨ͠ཁૉΛࠩ͠ࠐΉ —> </tbody> </table> <template id="productrow"> <tr> <td class="record"></td> <td></td> </tr> </template> let t = document.querySelector('#productrow') let tb = document.querySelector('tbody') let clone = document.importNode(t.content, true) let td = clone1.querySelectorAll('td') td[0].textContent = '1235646565' td[1].textContent = 'Stuff'
 tb.appendChild(clone)
  37. TODO リストのサンプル • よくあるやつ • ちょっとデモをします

  38. <template> タグの pros & cons • ただの HTML なので読みやすい •

    ノンプログラマーの人がいじる時にもやりやすい • だいたいのブラウザーで使える • https://caniuse.com/#feat=template • Fetch API と組み合わせて良い感じに動かせる • https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API • Internet Explorer でうまく動かないケースがある • このあと説明します
  39. <template> タグのまとめ • HTML の中にテンプレートとして使いたい
 HTML の断片を埋め込むことができる • ただの HTML

    なので色々とベンリです
  40. Stimulus を使ってみた プロジェクトのおはなし

  41. デモをします • 架空のプロジェクトのおはなし (だと思って聞いてください)

  42. Stimulus と Turbolinks • “Stimulus pairs beautifully with Turbolinks to

    provide a complete solution for fast, compelling applications with a minimal amount of effort.” • Turbolinks と組み合わせても問題なく使用することができます • モバイル向けのページでは Turbolinks を使っておくと良さそう
  43. Internet Explorer で動かすには? • Stimulus のPolyfill が用意されています • https://www.npmjs.com/package/ @stimulus/polyfills

    • <template> も Polyfill が用意されています • https://github.com/webcomponents/ polyfills/tree/master/packages/ template • Fetch API も (以下同文) • https://github.com/github/fetch
  44. <template> タグのネスト • <template> タグは
 ネストすることもできる • 要素の外側もテンプレートに
 しておきたいときに使う •

    Internet Explorer でも問題なく
 動作させておきたい <template id="outer"> <ul> <template id="inner"> <li></li> </template> </ul> </template> <div id="todos"> </div> et outer = document.querySelector('#outer') let list = document.importNode(outer.content, true) let ul = list.querySelector('ul') let inner = list.querySelector('#inner') for (let i = 1; i <= 3; i++) { let item = document.importNode(inner.content, true) let li = item.querySelector('li') li.textContent = 'List Item ' + i list.querySelector('ul').appendChild(item) } let todos = document.querySelector('#todos') todos.appendChild(list)
  45. ぼくの <template> どこいった • ちょっとデモをします • ネストされた <template> を使います

  46. こんな issue がありました • https://github.com/webcomponents/ template/pull/ 39#issuecomment-430617066 • “HTML parser

    of IE will drop <template /> directly, you won't find anything of your template…” • まじかよ…
  47. どうしようもないので… • createElement でがんばりました • <template> の意味よ…

  48. 実際に使ってみて • フレームワークが構造を用意してくれているのは
 指針になるのですごく分かりやすくて良い • 人数が少ないチームだとコンポーネントに
 分かれているよりもずっと触りやすい • <template> が複雑になってくると大変なので


    テンプレートで頑張りたいときはもう少し
 カンタンなアプローチがあると良いかも
  49. 雑多なトピック

  50. フロントエンドのパッケージ管理 • Stimulus で使うものは Yarn で入れています • Stimulus で使うもの以外は Rails

    Assets で入れています • Webpack (er) に乗せるとハマりそう… (見たことある) • Sprocket 4 でどうにかできるのかな?
  51. コードスタイルの統一 • Standard JS • No configuration を売りにしている • Standard

    RB • Rubocop の config ファイルが同梱されている • コードスタイルをレビューする余力がないので
 なるべく自動化できるところはそちらに寄せたい • スタイルの指摘をされるのもあんまり嬉しくないので
 (auto correct でわりと解決できる)
  52. まとめ ,

  53. お話したこと • Stimlus について簡単に紹介しました • Stimulus と <template> タグとを
 組み合わせるアプローチについて解説しました

    • ベストプラクティスなどは
 まだ模索しているところです