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

Just Tried Stimulus

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

Just Tried Stimulus

Avatar for Shohei Umemoto

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 でわりと解決できる)