Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Just Tried Stimulus

Just Tried Stimulus

Shohei Umemoto

June 27, 2019
Tweet

More Decks by Shohei Umemoto

Other Decks in Technology

Transcript

  1. 自己紹介 • Shohei Umemoto (@cafedomancer) • 社会人4 年目です (たぶん…) •

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

    • Ruby / Rails だけの会社というわけでは
 特にありません (でも大切にしています)
  3. 話さないこと • JavaScript の細かい話 • Stimulus のセットアップやインストール • Webpacker (Webpack)

    の (以下同文) • Stimulus を使う上での Tips みたいなの (in-depth なので)
  4. Stimulus とは? • Basecamp (元 37signals) が作っている JavaScript フレームワーク •

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

    / Action / Target • 実際にコードを読んでみよう!
  6. 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}!` } }
  7. 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}!` } }
  8. 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}!` } }
  9. 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}!` } }
  10. Stimulus の 3 つの要素 (再) • Controller • JavaScript で動作させたい単位となるもの

    • Action • HTML のイベントが発生したときに実行されるもの • Target • HTML からの入力と HTML への出力の対象となるもの
  11. サンプルをもう 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") } }
  12. 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>
  13. 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") } }
  14. 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") } }
  15. Stimulus の pros & cons • とてもシンプルなので分かりやすく覚えることが少ない • Reference を見てみると分かると思います

    • HTML と JavaScript との関係がひと目で分かる • このあと説明します • まわりで使っている人が見当たらない • ノウハウがまだ無さそう • ゴリゴリの SPA みたいなのを作るのには向いてなさそう
  16. 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}!` } }
  17. Stimulus のまとめ • Controller しかない JavaScript フレームワーク • 要素としては Controller/

    Action / Target の 3 つだけがある • ちょっとした動きモノを作るのに向いてそう
  18. 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 したいケースも無くはない • でもテンプレートの仕組みは用意されていない
  19. HTML を render したいケース • API から JSON を受け取ってそれを HTML

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

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


    いわゆるテンプレートとして使用することができる • https://www.html5rocks.com/en/tutorials/webcomponents/template/ も参考にしました
  22. <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)
  23. <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)
  24. <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)
  25. <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)
  26. <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)
  27. <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)
  28. <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)
  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> タグの pros & cons • ただの HTML なので読みやすい •

    ノンプログラマーの人がいじる時にもやりやすい • だいたいのブラウザーで使える • https://caniuse.com/#feat=template • Fetch API と組み合わせて良い感じに動かせる • https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API • Internet Explorer でうまく動かないケースがある • このあと説明します
  31. Stimulus と Turbolinks • “Stimulus pairs beautifully with Turbolinks to

    provide a complete solution for fast, compelling applications with a minimal amount of effort.” • Turbolinks と組み合わせても問題なく使用することができます • モバイル向けのページでは Turbolinks を使っておくと良さそう
  32. 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
  33. <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)
  34. こんな 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…” • まじかよ…
  35. フロントエンドのパッケージ管理 • Stimulus で使うものは Yarn で入れています • Stimulus で使うもの以外は Rails

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

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