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

MIXI TECH NOTE #11

MIXI TECH NOTE #11

#技術書典16 に出典された、MIXI GROUP エンジニア有志による技術書です。

MIXI ENGINEERS

May 08, 2024
Tweet

More Decks by MIXI ENGINEERS

Other Decks in Technology

Transcript

  1. ·͕͖͑ ຊॻʮMIXI TECH NOTE #11ʯ͸ɺMIXI GROUP ʹॴଐ͢Δ༗ࢤୡʹΑͬͯࣥචɾ੍࡞͞Εٕͨ ज़ॻͰ͢ɻ࣮ࡍͷݱ৔Ͱ࢖ΘΕٕͨज़΍ߟ͑ํɺ·ͨɺݸਓతʹڵຯɾؔ৺ͷ͋Δ෼໺͔Βɺࢥ͍ࢥ ͍ʹࣥච͍ͨ͠·ͨ͠ɻͦͷͨΊɺ֤ষͦΕͧΕͰ׬͍݁ͯ͠Δ಺༰ʹͳ͍ͬͯ·͢ͷͰɺ޷͖ͳষ ͔Β޷͖ͳॱ൪Ͱָ͓͠Έ͍ͩ͘͞ɻ

    ·ͨɺຊॻ͸ɺMIXI GROUP ʹ͋Δٕज़త஌ݟ΍ΞΠσΞΛੵۃతʹڞ༗ɾެ։͍ͯ͘͜͠ͱ ͰɺੈͷதʹΑΓྑ͍αʔϏε͕ҲΕग़͢͜ͱΛئͬͯץߦ͞Ε͍ͯ·͢ɻܝࡌ͞Ε͍ͯΔ৘ใ͸ɺ ࣥචऀࣗ਎ͷ؀ڥͰݕূࣥ͠ච͞Εͨ΋ͷͰ͢ͷͰɺ͝ࢀߟʹ͞ΕΔࡍ͸ɺࣗ͝਎ͷ੹೚Ͱ൑அ͠ ͝׆༻͍ͩ͘͞ɻͳ͓ɺจষදݱʹ͖ͭ·ͯ͠΋ɺࣥචऀࣗ਎ͷݴ༿Ͱ఻͑ͨ͘ɺϑϥϯΫͳදݱͱ ͳ͓ͬͯΓ·͢͜ͱ͝ཧղ͍͚ͨͩΕ͹ͱࢥ͍·͢ɻ σΟϕϩούʔϦϨʔγϣϯζνʔϜҰಉ ˗ຊॻʹؔ͢Δ͓໰͍߹Θͤઌ ɹ https://twitter.com/mixi_engineers ˗ MIXI GROUP ʹ͍ͭͯ ɹ https://mixi.co.jp/ ˞ MIXI ͷ໊শɺ͜Εʹؔ࿈͢Δ঎ඪٴͼϩΰ͸ɺגࣜձࣾ MIXI ͷ঎ඪٴͼొ࿥঎ඪͰ͢ɻ·ͨɺ ֤ࣾͷձ໊ࣾɺαʔϏεٴͼ੡඼ͷ໊শ͸ɺͦΕͧΕͷॴ༗͢Δ঎ඪ·ͨ͸ొ࿥঎ඪͰ͢ɻ iii
  2. ໨࣍ ·͕͖͑ iii ୈ 1 ষ Ϛελσʔλͱ GitFlow 1 1.1

    ·͕͖͑ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.2 ϚελσʔλͱϫʔΫϑϩʔ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 1.3 1 ͭͷϦϙδτϦɺ2 ͭͷϒϥϯνઓུ . . . . . . . . . . . . . . . . . . . . . . . . 3 1.4 ϒϥϯνઓུΛ౷߹͢Δ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 1.5 ͋ͱ͕͖ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 1.6 ෇࿥ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 ୈ 2 ষ Haskell ͱςετ 13 2.1 αϯϓϧϓϩάϥϜ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 2.2 HUnit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 2.3 QuickCheck . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 2.4 Hspec . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 2.5 doctest . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 2.6 tasty . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 2.7 ͓͠·͍ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 ୈ 3 ষ Unity Ͱ Discord ಺Ϛϧν௨৴ήʔϜΛ࡞Ζ͏ 23 3.1 αϯϓϧήʔϜ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 3.2 Discord ΞΫςΟϏςΟ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 3.3 ωοτϫʔΫτϙϩδʔͷબ୒ . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 3.4 Colyseus ͰϚϧνϓϨΠ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 3.5 Discord ͱήʔϜΛ࿈ܞ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 3.6 ΫϥΠΞϯτ։ൃͷϙΠϯτ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 3.7 ऴΘΓʹ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 ୈ 4 ষ Scala.js Ͱ Figma ϓϥάΠϯ։ൃೖ໳ 39 4.1 ·͕͖͑ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 v
  3. ໨࣍ 4.2 Figma ͱϓϥάΠϯʹ͍ͭͯ . . . . . .

    . . . . . . . . . . . . . . . . . . . . . . . 39 4.3 Figma ϓϥάΠϯͷΞʔΩςΫνϟ . . . . . . . . . . . . . . . . . . . . . . . . . . 41 4.4 TypeScript ͰϓϥάΠϯ։ൃ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 4.5 Figma ϓϥάΠϯΛमਖ਼͢Δ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 4.6 Scala.js Ͱ Figma ϓϥάΠϯΛ࡞ΔͨΊͷ؀ڥߏங . . . . . . . . . . . . . . . . . 52 4.7 Scala.js Ͱ Figma ϓϥάΠϯΛ࡞Δ . . . . . . . . . . . . . . . . . . . . . . . . . . 59 4.8 ऴΘΓʹ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 ୈ 5 ষ TIPSTAR ΞʔΩςΫνϟͷ୳ࡧ 69 5.1 TIPSTAR ͱ͸ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 5.2 TIPSTAR ͷΞʔΩςΫνϟ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 5.3 Ϛελσʔλʹ͍ͭͯ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 5.4 ඇಉظॲཧʹ͍ͭͯ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 5.5 ऴΘΓʹ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76 ஶऀ঺հ 79 vi
  4. ୈ 1 ষ Ϛελσʔλͱ GitFlow 1.1 ·͕͖͑ ͜Μʹͪ͸ɺιʔγϟϧϕοςΟϯάࣄۀຊ෦։ൃࣨͷߐാͰ͢ɻ ࣗ෼͸ιʔγϟϧϕοςΟϯάࣄۀຊ෦ͷѻ͏ TIPSTAR

    *1 Λ͸͡Ίͱ͢Δ֤छࣄۀͷӡӦɾ։ ൃʹܞΘ͍ͬͯ·͢ɻ ͦͷதͰ৽نࣄۀΛ্ཱͪ͛Δػձ͕͋Γɺܝࡌόφʔ΍ϛογϣϯͳͲͷαʔϏεͷجૅ৘ใ (Ϛελσʔλ) Λ؅ཧ͢ΔઃܭΛߦ͍·ͨ͠ɻ ຊࣄۀ෦ͷ֤αʔϏεʹ͓͍ͯɺϚελσʔλ͸ GitHub *2 ্ͰϏδωε৬ɾΤϯδχΞ৬͕ڠ ྗͯ͠ӡӦ͓ͯ͠Γɺదٓ Google Cloud *3 ্ͷσʔλϕʔε΁औΓࠐ·Ε·͢ɻ৽نࣄۀͰ΋ͦ ͷྫʹ࿙ΕͣϦϙδτϦͱσʔλϕʔεΛ࡞੒͠·͕ͨ͠ɺػೳ։ൃʹϦιʔεΛे෼ʹׂͨ͘Ίʹ ΋ɺϏδωε৬ɾΤϯδχΞ৬ͦΕͧΕ͕ࠞཚͤͣӡ༻Ͱ͖ΔϫʔΫϑϩʔͷ༻ҙ͕ෆՄܽͰͨ͠ɻ ຊষͰ͸ɺϏδωεଆͷ࡞ۀ΍ GitFlow *4 ΁ண໨ͨ͠Ϛελσʔλͷ؅ཧख๏Λ঺հ͠·͢ɻ *1 ڝྠɾPIST6ɾΦʔτϨʔεͷωοτ౤ථ͕Ͱ͖Δڞ༡ܕεϙʔπϕοςΟϯάαʔϏε https://about.tipstar.com/ *2 https://github.co.jp/ *3 https://cloud.google.com/ *4 https://www.atlassian.com/ja/git/tutorials/comparing-workflows/gitflow-workflow 1
  5. ୈ 1 ষ Ϛελσʔλͱ GitFlow 1.2 ϚελσʔλͱϫʔΫϑϩʔ 1.2 ϚελσʔλͱϫʔΫϑϩʔ ࠓճܞΘͬͨαʔϏεͰ͸Ϣʔβʔͷ໨ʹ৮ΕΔຊ൪؀ڥɺϦϦʔεલͷػೳ΍ϚελσʔλΛ֬

    ೝ͢Δݕূ؀ڥɺ։ൃதͷػೳ΍ϚελσʔλΛࢼ͢։ൃ؀ڥͷ 3 ͕ͭجຊͷ؀ڥͱͯ͋͠Γ·͠ ͨɻ֤ͦͯ͠؀ڥ΁޲͚ͯϏδωεଆɾ։ൃଆͷઃఆͨ͠Ϛελσʔλ͕ೖߘ͞Ε·͢ɻ αʔϏεͷϩʔϯν΁޲͚ɺࣗ෼͸ҎԼͷཁ݅Λ౿·্͑ͨͰϚελσʔλΛ൓өɾ؅ཧ͢Δํ๏ Λઃܭ͠·ͨ͠ɻ Excel Λར༻ͯ͠σʔλೖྗΛߦ͍ GitHub Λར༻ͯ͠σʔλ؅ཧ͢Δ ϚελσʔλೖߘπʔϧΛ։ൃɾӡ༻͢Δ͚ͩͷ޻਺͕औΕͳ͍͜ͱ͔ΒɺϏδωεଆ͕ී ஈ࢖͍׳Ε͍ͯΔ Excel Λ࢖ͬͯσʔλೖྗΛߦ͍ GitHub Λ࢖ͬͯมߋཤྺΛ؅ཧ͠·͢ TIPSTAR ଆͰ΋ར༻͞Ε͍ͯΔख๏Ͱ͋Γɺ։ൃɾӡ༻ͷͨΊͷҰఆͷϊ΢ϋ΢͕෦ॺʹଘ ࡏ͍ͯͨ͜͠ͱ΋ཁ݅ͱͯ͠਺͑Δཧ༝ͱͳΓ·ͨ͠ Excel ͔ΒͷதؒϑΝΠϧͱͯ͠ CSV Λར༻͢Δ ϚελσʔλΛ SQL ͷσʔλϕʔε (Cloud Spanner *5) ΁Πϯϙʔτ͢ΔͨΊɺCSV Λར ༻͠·͢ GitFlow ͳ͍͠ GitHub Flow Λར༻ͯ͠ϦϙδτϦΛӡ༻͢Δ ϓϩάϥϜίʔυΛ؅ཧ͍ͯ͠ΔଞϦϙδτϦͱͷڠௐΛऔΔͨΊɺGitFlow ͳ͍͠ GitHub Flow Λར༻ͯ͠ϦϙδτϦΛӡ༻͠·͢ ·ͨࣄલ৘ใͱͯ͠ɺϚελσʔλΛ໾ऀͱͨ͠ϫʔΫϑϩʔʹҎԼͷΑ͏ͳέʔε͕༩͑ΒΕͯ ͍·ͨ͠ɻ Ϗδωεଆ: ৽͍͠ࢪࡦΛࢼ͢ ৽͍͠ࢪࡦʹ͍ͭͯ։ൃ؀ڥ΁޲͚ͯϚελσʔλΛೖߘ͠ɺ։ൃ؀ڥͷΞϓϦ্ͰϢʔβʔ ମݧΛ͔֬Ί·͢ Ϗδωεଆ: ࣍ͷϦϦʔεͷϚελσʔλΛ൓ө͢Δ Ϣʔβʔ΁ࢪࡦΛఏڙ͢ΔͨΊɺຊ൪؀ڥ΁ϚελσʔλΛ൓ө͠·͢ɻ൓өલʹ͸ݕূ؀ڥ ΁޲͚ͯϚελσʔλΛ൓ө͠ෆࣗવͳڍಈΛى͜͞ͳ͍͔ͳͲΛݕূ͠·͢ ΤϯδχΞଆ: ৽͍͠ϚελσʔλΛ։ൃ͢Δ ৽ػೳʹରԠ͢Δ৽͍͠Ϛελσʔλͷ௥Ճ΍ɺطଘͷϚελσʔλͷ֦ுɺExcel γʔτ͔ Β CSV ΁ม׵͢ΔπʔϧͷվमΛ։ൃ؀ڥͰߦ͍·͢ ΤϯδχΞଆ: ৽͍͠ϚελσʔλΛϦϦʔε͢Δ ։ൃͨ͠৽͍͠ϚελσʔλΛɺݕূ؀ڥΛܦ༝ͯ͠ຊ൪؀ڥ΁൓ө͠·͢ ΤϯδχΞଆ: ϦϙδτϦͷपลπʔϧΛߋ৽͢Δ GitHub Actions ͷιʔείʔυ΍υΩϡϝϯτͳͲɺϚελσʔλʹ௚઀ؔΘΓͷͳ͍มߋ Λ։ൃ؀ڥɺݕূ؀ڥɺຊ൪؀ڥʹ൓ө͠·͢ *5 https://cloud.google.com/spanner 2
  6. ୈ 1 ষ Ϛελσʔλͱ GitFlow 1.3 1 ͭͷϦϙδτϦɺ2 ͭͷϒϥϯνઓུ 1.3

    1 ͭͷϦϙδτϦɺ2 ͭͷϒϥϯνઓུ ઌͷઅΛ੔ཧ͠ղܾΛ໨ࢦ͢தͰࣗ෼͸ɺ2 ͭͷϒϥϯνઓུΛݟग़͠·ͨ͠ɻ 1. main branch ͔Β੾Γग़ͯ͠։ൃͨ͠಺༰Λ main branch ΁Ϛʔδ͢Δઓུ 2. develop branch ͔Β੾Γग़ͯ͠։ൃͨ͠಺༰Λ main branch ΁Ϛʔδ͢Δઓུ 1. main branch ͔Β੾Γग़ͯ͠։ൃͨ͠಺༰Λ main branch ΁Ϛʔδ͢Δઓུ ʮmain branch ͔Β੾Γग़ͯ͠։ൃͨ͠಺༰Λ main branch ΁Ϛʔδ͢Δઓུʯ͸ɺओʹϦϦʔ ε༧ఆͷϚελσʔλΛ൓ө͢ΔͨΊʹར༻͠·͢ɻຊ൪؀ڥ޲͚ͷϚελσʔλ͸࣮ࢪ༧ఆͷࢪࡦ ʹରͯ͠΄΅ҰҙʹఆΊΒΕ·͢ɻͦͷͨΊݕূ؀ڥͰͷ֬ೝ͑͞ࡁΊ͹ main branch ΁ͨͩͪʹ Ϛʔδɺຊ൪൓өͰ͖·͢ɻ 2. develop branch ͔Β੾Γग़ͯ͠։ൃͨ͠಺༰Λ main branch ΁Ϛʔδ͢Δ ઓུ ʮdevelop branch ͔Β੾Γग़ͯ͠։ൃͨ͠಺༰Λ main branch ΁Ϛʔδ͢Δઓུʯ͸ɺ৽͍͠Ϛ ελσʔλ΍ GitHub Actions ʹ୅ද͞ΕΔपลπʔϧΛϦϦʔε͢ΔͨΊʹར༻͠·͢ɻ͜ΕΒ ͷࠩ෼͸جຊతʹ develop branch Ͱ֬ೝɾௐ੔Λߦ͍ͳ͕Β࣮૷ΛਐΊ͍͖ͯ·͢ɻͦͯ͠ඞཁʹ Ԡͯ͡ release branch Λ੾Γݕূ؀ڥ΁σϓϩΠɺಈ࡞֬ೝͷޙʹ main branch ΁Ϛʔδ͢Δ͜ͱ Ͱຊ൪؀ڥ΁ͷ൓өΛߦ͍·͢ɻ ·ͨ஫ҙ͢Δ఺ͱͯ͠ develop branch ʹੵ·Εͨ։ൃ༻ͷϚελσʔλ͸։ൃ؀ڥ΁޲͚ͯͷΈ ൓ө͞Εͯ΄͘͠ɺؒҧͬͯ΋ຊ൪؀ڥ΁൓ө͞Εͯ΄͘͠͸ͳ͍ͨΊɺϦϦʔεલޙͰϚελσʔ λͷϦηοτ͕ඞཁʹͳΓ·͢ɻ 3
  7. ୈ 1 ষ Ϛελσʔλͱ GitFlow 1.3 1 ͭͷϦϙδτϦɺ2 ͭͷϒϥϯνઓུ ਤ

    1.1: ຊ൪؀ڥ޲͚ͷϚελσʔλΛ൓ө͢Δϒϥϯνઓུ ਤ 1.2: ։ൃ༻ϚελσʔλɾपลπʔϧΛ൓ө͢Δϒϥϯνઓུ 4
  8. ୈ 1 ষ Ϛελσʔλͱ GitFlow 1.4 ϒϥϯνઓུΛ౷߹͢Δ 1.4 ϒϥϯνઓུΛ౷߹͢Δ ݕ౼ͷྲྀΕͷதͰɺ

    develop branch ͱ main branch ͕͋Δํ͕ΑΓ҆ఆͯ͠ӡ༻Ͱ͖ͦ͏ͳ͜ ͱ͔Βɺࣗ෼͸ GitFlow Λར༻ͯ͠ϒϥϯνઓུΛ૊ΈཱͯΔ͜ͱͱ͠·ͨ͠ɻ1. ͱ 2. ͷઓུΛ ·ͱΊɺҎԼͷΑ͏ͳ GitFlow Λ΍΍Ԡ༻ͨ͠ϒϥϯνઓུΛऔΔ͜ͱͱͳΓ·ͨ͠ɻ ਤ 1.3: ϒϥϯνઓུΛ·ͱΊͨ΋ͷ ͜ͷઓུͰ͸ɺϏδωεଆͱ։ൃଆ྆ํʹҎԼͷΑ͏ͳϝϦοτ͕ಘΒΕ·ͨ͠ɻ • Ϗδωεଆͷૢ࡞ͱͯ͠ʮmain branch ͔Β branch Λ੾ͬͯຊ൪൓ө༧ఆͷࠩ෼Λ commit ͯ͠ main branch ΁Ϛʔδ͢Δʯͱ͍͏ۃΊͯγϯϓϧͳϑϩʔʹͳ͍ͬͯΔ • ΤϯδχΞଆ͔ΒΈͯ develop ˠ release ˠ main ͱ͍͏ଞϦϙδτϦͷ։ൃɾϦϦʔεϑ ϩʔʹ͍ۙܗͰϦϙδτϦΛӡ༻Ͱ͖Δ ҰํͰɺdevelop branch ͱ main branch Ͱ·ͬͨ͘ผͷϚελσʔλ͕ೖ͍ͬͯΔ͜ͱ͔Βɺ σΟϨΫτϦߏ੒΍ಛఆͷ GitFlow ͷϑϩʔʹޙड़͢ΔΑ͏ͳ޻෉͕ඞཁʹͳΓ·ͨ͠ɻ 5
  9. ୈ 1 ষ Ϛελσʔλͱ GitFlow 1.4 ϒϥϯνઓུΛ౷߹͢Δ σΟϨΫτϦΛߏ੒͢Δ લઅͰϒϥϯνઓུΛɺࢼߦࡨޡͷ຤ GitFlow

    ͱԠ༻͢ΔϒϥϯνઓུͰཁ݅Λຬͨ͢Ξϓϩʔ νΛܭըͨ͠ͱ͜Ζ·Ͱ঺հ͠·ͨ͠ɻຊઅҎ߱Ͱ͸ɺ࣮ࡍʹܭըͨ͠ΞϓϩʔνΛӡ༻͢Δʹ͋ ͨͬͯඞཁʹͳͬͨ޻෉Λ঺հ͠·͢ɻ ·ͣ͸σΟϨΫτϦߏ଄Ͱ͢ɻϏδωεଆ͕ૢ࡞͢ΔϚελσʔλͱ։ൃଆ͕ૢ࡞͢ΔεΫϦϓτ ΍υΩϡϝϯτ͸෼཭͞Ε͍ͯΔ͜ͱ͕๬·͍͠Ͱ͢ɻ ͦͷͨΊϚελσʔλ୯ҐͰ͸ͳ͘ɺ࡞ۀϨΠϠ͝ͱʹσΟϨΫτϦΛ੾Δߏ੒Λ࠾༻͠·ͨ͠ɻ Ϧετ 1.1: Ϛελσʔλ؅ཧϦϙδτϦͷσΟϨΫτϦߏ੒ . |-- README.md |-- docs ... ӡ༻ɾ։ൃʹ·ͭΘΔυΩϡϝϯτ | |-- planner.md | ‘-- developer.md |-- data ... Ϗδωεଆ͕ૢ࡞͢Δ Excel ͷγʔτ | |-- ϩάΠϯϘʔφε.xlsx | |-- ... | ‘-- όφʔ.xlsx |-- csv ... Excel ͔Βੜ੒͢Δ CSV | |-- master_login_bonuses.csv | |-- ... | ‘-- master_banners.csv |-- bin ... Excel ͔Β CSV Λग़ྗ͢ΔϓϩάϥϜͳͲ | |-- excel_to_csv.sh | ‘-- excel_to_csv.exe |-- ... ‘-- .github |-- actions ‘-- workflows 6
  10. ୈ 1 ষ Ϛελσʔλͱ GitFlow 1.4 ϒϥϯνઓུΛ౷߹͢Δ release / release

    to develop ΛࣗಈԽ͢Δ ࣍ʹ release / release to develop ͷྲྀΕʹ͍ͭͯͰ͢ɻ ࠓճ release ͸ 2 छྨ͋ΓɺҰͭ͸Ϗδωεଆ͕ߦ͏ main branch ͔Β੾Γग़ͯ͠ main branch ΁Ϛʔδ͢ΔͨΊͷϦϦʔε *6ɺ΋͏Ұͭ͸ develop branch ͔Β੾Γग़ͯ͠ main branch ΁Ϛʔ δ͢ΔͨΊͷϦϦʔεʹͳΓ·͢ɻ develop branch ͔Β੾Γग़ͯ͠ release branch Λ࡞Δࡍʹ͸ɺϚελσʔλΛ develop branch ͷ΋ͷ͔Β main branch ͷ΋ͷʹௐ੔͢Δ ඞཁ͕͋Γ·͢ɻ Ϧετ 1.2: develop branch ͔Β੾Γग़͢ϦϦʔε࣌ͷ git ૢ࡞ git checkout origin/main csv data git add csv data git commit --allow-empty -m "[release] reset to production data" git merge -Xours origin/main --no-edit ·ͨ release branch ʹมߋΛՃ͑ͨࡍʹ͸ main branch ʹϚʔδͨ͠ release branch Λ develop branch ΁Ϛʔδ͢Δඞཁ͕͋Γ·͢ɻ͞ΒʹࠓճͷέʔεͰ͸ɺϚελσʔλΛ main branch ͷ ΋ͷ͔Β develop branch ͷ΋ͷʹௐ੔͢Δ ඞཁ͕͋Γ·͢ɻ Ϧετ 1.3: release to develop ࣌ͷ git ૢ࡞ git checkout origin/develop csv data git add csv data git commit -m "[release to develop] reset to develop data" git merge -Xours origin/develop --no-edit ্ड़ͷ 2 ͭ͸ϧʔνϯϫʔΫͰ͋ΓࣗಈԽ͕๬Ί·͢ɻຊख๏Ͱ͸͜ͷϧʔνϯϫʔΫΛ GitHub Actions Λ༻͍ͯࣗಈԽ͠ӡ༻ίετΛ཈͑·ͨ͠ *7ɻ *6 ຊՈͷ GitFlow Ͱ͸ hotfix ͱ͍͏໊শͰ஌ΒΕ͍ͯ·͕͢ɺ͜ͷϒϥϯνઓུͰ͸ҰൠͷϦϦʔεͱಉ౳ʹར༻͞ Ε·͢ɻ *7 ຊষͷ෇࿥Λࢀর͍ͩ͘͞ɻ 7
  11. ୈ 1 ষ Ϛελσʔλͱ GitFlow 1.5 ͋ͱ͕͖ 1.5 ͋ͱ͕͖ ຊষͰ͸৽نࣄۀʹ͓͍ͯ࠾༻ͨ͠Ϛελσʔλͷ؅ཧํ๏ʹ͍ͭͯ঺հ͠·ͨ͠ɻຊख๏͸ࠓճ

    ͷ༩͑ΒΕͨཁ݅ʹ͍ͭͯ͸Ұఆຬͨ͢͜ͱ͕Ͱ͖αʔϏεͱͯ͠΋҆ఆͨ͠Քಇɾӡ༻Λୡ੒ͯ͠ ͍·͢ɻࠓޙͱͯ͠͸ΑΓϏδωεଆͷσʔλೖߘΛγϯϓϧʹ͢Δ͘͠Έͷಋೖ΍ Excel ࣗମΛ ϓϩάϥϜ؅ཧ͢Δख๏ͳͲΛݕ౼͍ͯͭ͘͠΋ΓͰ͢ɻ *8 ࠷ޙʹ͸ͳΓ·͕͢ɺຊষΛΈͳ͞ΜͷϓϩμΫτ։ൃ΁ͷΞΠσΞͷҰͭͱͯ͠໾ཱ͍͚ͯͨͩ Ε͹޾͍Ͱ͢ɻ *8 ຊষͰ͸৮Ε·ͤΜͰ͕ͨ͠ɺϏδωεଆ͕ೖྗ͠΍͍͢Α͏ʹ Excel ΁ೖྗنଇ΍֎෦ࢀরΛ׆༻͍ͯ͠·͢ɻ͠ ͔͜͠ΕΒͷิॿ͸ Excel ୯ମͰ͸؅ཧ͕೉͍ͨ͠Ίίʔυ؅ཧͰ͖ΔΑ͏ݚڀΛߦ͍ͬͯ·͢ɻ 8
  12. ୈ 1 ষ Ϛελσʔλͱ GitFlow 1.6 ෇࿥ 1.6 ෇࿥ Ϧετ

    1.4: develop branch ͔Β੾Γग़͢ release ΛࣗಈԽ͢Δ GitHub Actions name: "Create Feature Release PR" on: workflow_dispatch: inputs: title: description: "PR Title" required: true permissions: contents: write pull-requests: write jobs: create_feature_release_pr: name: Create feature release branch and PR runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: ref: "develop" fetch-depth: 0 - name: Set commit user run: | git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" git config user.name "[bot] ${GITHUB_ACTOR}" - name: Create feature release branch id: create-feature-release-branch run: | DATETIME_SUFFIX=$(date "+%Y%m%d%H%M") releaseBranch="release/${GITHUB_ACTOR}-${DATETIME_SUFFIX}" git checkout -b "$releaseBranch" echo "releaseBranch=${releaseBranch}" >> $GITHUB_OUTPUT env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Reset production data to development one run: | git checkout origin/main csv data git add csv data git commit --allow-empty -m "[release] reset to production data" git merge -Xours origin/main --no-edit env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 9
  13. ୈ 1 ষ Ϛελσʔλͱ GitFlow 1.6 ෇࿥ - name: Push

    feature release branch run: | git push origin \ "${{ steps.create-feature-release-branch.outputs.releaseBranch }}" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Create release PR run: | gh pr create --base main \ --title "${{ github.event.inputs.title }} (to main)" \ --body "Feature Release: develop to main ͷϦϦʔεPRͰ͢" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} Ϧετ 1.5: release to develop ΛࣗಈԽ͢Δ GitHub Actions name: Create Release to Develop PR on: pull_request: branches: - main types: [closed] permissions: contents: write pull-requests: write jobs: create_release_to_develop_pr: if: github.event.pull_request.merged == true && \ startsWith(github.event.pull_request.head.ref, ’release/’) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set commit user run: | git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" git config user.name "[bot] ${GITHUB_ACTOR}" - name: Create release to develop branch id: create-release-to-develop-branch run: | releaseBranch="${{ github.event.pull_request.head.ref }}" releaseToDevelopBranch="${releaseBranch/release\//release-to-develop/}" PRE_MERGE_COMMIT_SHA= \ $(git rev-parse ${{ github.event.pull_request.merge_commit_sha }}^2) 10
  14. ୈ 1 ষ Ϛελσʔλͱ GitFlow 1.6 ෇࿥ git checkout -b

    $releaseToDevelopBranch $PRE_MERGE_COMMIT_SHA echo "releaseBranch=${releaseBranch}" >> $GITHUB_OUTPUT echo "releaseToDevelopBranch=${releaseToDevelopBranch}" >> $GITHUB_OUTPUT env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Reset develop data to production one run: | git checkout origin/develop csv data git add csv data git commit -m "[release to develop] reset to develop data" git merge -Xours origin/develop --no-edit env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Push release to develop branch run: | git push origin \ "${{ steps.create-release-to-develop-branch.outputs.releaseToDevelopBranˠ ch }}" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Create PR to Develop using GitHub CLI run: | PR_TITLE="${{ github.event.pull_request.title }} (release to develop)" PR_BODY=$’Release to Develop\n- #${{ github.event.pull_request.number }}’ gh pr create --base develop --title "$PR_TITLE" --body "$PR_BODY" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 11
  15. ୈ 2 ষ Haskell ͱςετ ຊߘͰ͸ϓϩάϥϛϯάݴޠ Haskell Ͱͷςετͷॻ͖ํʹ͍ͭͯ঺հ͠·͢ɻ Haskell ͷςετϥΠϒϥϦ͸ɺͦͦ͜͜छྨ͕͋Γ·͢*1ɻͦͷதͰ΋ɺগ͠ओ؍తͰ͕͢ɺ༗

    ໊ͳҎԼͷ̐ͭʹ͍ͭͯ঺հ͠·͢ɿ 1. HUnit : Ϣχοτςετ 2. QuickCheck : ϓϩύςΟϕʔεςετ 3. Hspec : RSpec ͷΑ͏ͳ DSL Λఏڙ 4. doctest : υΩϡϝϯτ಺ʹຒΊࠐΊΔςετ ·ͨɺ΄͔ͷςετϥΠϒϥϦΛ·ͱΊͯ࢖͍΍͘͢Ͱ͖ΔϥΠϒϥϦ tasty ΋Α͘࢖ΘΕ͍ͯΔ ҹ৅Ͱ͢ͷͰɺtasty ʹ͍ͭͯ΋঺հ͠·͢ɻͪͳΈʹɺςετͷҰൠతͳॻ͖ํ΍ϓϥΫςΟεʹ ͍ͭͯ͸ݴٴ͠·ͤΜɻ 2.1 αϯϓϧϓϩάϥϜ ޙड़ͷςετίʔυ΋ؚΊͯɺ͢΂ͯͷαϯϓϧίʔυ͸ GitHub ͷ matsubara0507/haskell- test-sample ϦϙδτϦʹ͋Γ·͢*2ɻ·ͨɺHaskell ॲཧܥʹ͸ GHC 9.6.4 Λ࢖͍·͢ɻ αϯϓϧϓϩάϥϜͱͯ͠͸࢛ଇԋࢉΛߦ͏؆୯ͳύʔαΛ࢖͍·͢*3ɻ • ༗ཧ਺Λѻ͏ • ࢛ଇԋࢉ͕Ͱ͖ɺՃࢉɾݮࢉΑΓ৐ࢉɾআࢉͷํ͕༏ઌ౓͕ߴ͍ • ׅހΛ࢖͑Δ ύʔαͷ࣮૷ࣗମ͸ຊ࣭తͰ͸ͳ͍ͷͰɺαϯϓϧϦϙδτϦʹ͸͋Γ·͕͢ɺ͜͜Ͱ͸ׂѪ͠· ͢ɻޙड़ͷςετίʔυʹؔΘͬͯ͘Δ෦෼͚ͩҎԼʹൈਮ͓͖ͯ͠·͢ɻ *1 Haskell ͷϥΠϒϥϦϨδετϦͰ͋Δ Hackage ʹͯɺςετʹؔ͢ΔλάͰݕࡧ͢Δͱ෼͔Γ·͢ɻhttps: //hackage.haskell.org/packages/tag/testing *2 https://github.com/matsubara0507/haskell-test-sample *3 Haskell ͱ͍͑͹ύʔαͩΑͶ 13
  16. ୈ 2 ষ Haskell ͱςετ 2.2 HUnit module Lib (...)

    where import Text.Megaparsec -- megaparsec ͱ͍͏ύʔαϥΠϒϥϦΛ࢖͍ͬͯ·͢ type Parser = Parsec CustomError String type ParserError = ParseErrorBundle String CustomError data CustomError = ... calculate :: String -> Either ParserError Rational calculate = ... rationalParser :: Parser Rational rationalParser = ... natParser :: Parser Natural natParser = ... calculate ؔ਺͕ɺ਺ࣜͷจࣈྻΛߏจղੳͯ͠ܭࢉ݁ՌΛฦͯ͘͠Ε·͢ɻEither a b ܕ͸ ௚࿨ܕΛѻ͑Δ૊ࠐΈܕͰɺܕม਺ a ʹΤϥʔΛද͢ܕΛೖΕͯ࢖ΘΕΔ͜ͱ͕Α͋͘Γ·͢ɻࠓ ճ΋ɺύʔεΤϥʔΛදݱ͢Δܕ͕ฦ͖ͬͯ·͢ɻ rationalParser ؔ਺ͱ natParser ؔ਺͸ɺͲͪΒ΋ calculate ؔ਺ͷ಺෦Ͱར༻͍ͯ͠Δ ύʔαͰ͢ɻଞʹ΋ఆ͍ٛͯ͠·͕͢ɺϢχοτςετ΍ϓϩύςΟϕʔεςετͰ࢖͍΍͍͢ 2 ͭ Λൈਮ͠·ͨ͠ɻ2 ͭ͸ͦΕͧΕɺ༗ཧ਺ͱࣗવ਺Λύʔε͠·͢ɻRational ܕ͕૊ࠐΈͷ༗ཧ਺ ܕͰɺNatural ܕ͸૊ࠐΈͷࣗવ਺ܕͰ͢ɻ 2.2 HUnit HUnit ϥΠϒϥϦ͸ɺHaskell ͰϢχοτςετΛߦ͏ͨΊͷςετϑϨʔϜϫʔΫͰ͢*4ɻ HUnit ͸γϯϓϧͳΞαʔγϣϯ͕͍͔ͭ͘ఆٛ͞Ε͍ͯΔ͚ͩͰɺ͋·Γڽͬͨػೳ͸ఏڙ͞Ε ͍ͯ·ͤΜɻ αϯϓϧίʔυͰ͋Δ natParser ؔ਺ͷϢχοτςετΛ HUnit Ͱॻ͘ͱ࣍ͷΑ͏ʹͳΓ·͢ɻ import Lib import Data.Bifunctor (first) import Test.HUnit import Text.Megaparsec (parse) import Text.Megaparsec.Error (errorBundlePretty) main :: IO () main = runTestTTAndExit $ TestList *4 https://hackage.haskell.org/package/HUnit 14
  17. ୈ 2 ষ Haskell ͱςετ 2.2 HUnit [ TestLabel "natParser"

    $ TestList [ TestCase $ parse’ natParser "zero number" "0" @?= Right 0 , TestCase $ parse’ natParser "positive number" "1234567890" @?= Right 1234567890 , TestCase $ parse’ natParser "negative number" "-1" @?= Left "negative number:1:1:...unexpected ’-’\nexpecting numeric characterˠ \n" , TestCase $ parse’ natParser "rational number" "0.0123" @?= Right 0 ] ] where parse’ p s i = first errorBundlePretty $ parse p s i megaparsec ͷύʔα͸ɺparse ؔ਺Ͱ࣮ߦͰ͖·͢ɻ1 Ҿ਺໨͕࣮ߦ͍ͨ͠ύʔαͰ͢ɻ2 Ҿ਺ ໨͸ೖྗͷϥϕϧͷΑ͏ͳ΋ͷͰɺຊདྷͰ͋Ε͹ϑΝΠϧ໊ͳͲΛ౉͠·͢ɻ3 Ҿ਺໨͸ύʔεͨ͠ ͍ೖྗͰɺࠓճ͸਺ࣜͷจࣈྻͰ͢ɻύʔεΤϥʔͷߏ଄͕ෳࡶͰςετΛॻ͘ͷ͕͍ͨ΁Μͩͬͨ ͨΊɺύʔεΤϥʔΛ Pretty Print ͢ΔͨΊͷ errorBundlePretty ؔ਺Λద༻ͯ͠Ξαʔγϣϯ Λॻ͍͍ͯ·͢ɻ@?= ԋࢉࢠ͕ΞαʔγϣϯΛ࡞ΔͨΊͷؔ਺ͷҰͭͰɺࠨล͕ acutual valueɺӈ ล͕ expected value ʹͳΓ·͢ɻEq a ܕΫϥεΛ΋ͱʹͯ͠౳ՁੑΛνΣοΫͯ͘͠Ε·͢ɻ ͦΕͧΕɺೖྗ͕θϩͷ৔߹ɾਖ਼ͷ਺ͷ৔߹ɾෛͷ਺ͷ৔߹ɾখ਺ͷ৔߹Λॻ͍ͯ͋Γ·͢ɻnatP arser ͸ࣗવ਺͚ͩΛύʔε͢ΔͷͰɺલ൒ 2 ͚͕ͭͩ౳Ձͳ஋Λฦ͍ͯ͠·͢ɻෛͷ਺ͷ৔߹͸ɺ ΤϥʔϝοηʔδΛฦ͍ͯ͠·͕͢ɺখ਺ͷ৔߹͸ύʔεͰ͖Δ 0 ·ͰΛύʔε͍ͯ͠·͢ɻ ͜ͷίʔυͷ࣮ߦ݁Ռ͸࣍ͷΑ͏ʹͳΓ·͢ɻ Cases: 4 Tried: 4 Errors: 0 Failures: 0 ͱͯ΋γϯϓϧͰ͢ɻͪͳΈʹɺࣦഊͨ͠৔߹͸͜͏ͳΓ·͢ɻ ### Failure in: 0:natParser:2 test/hunit/Spec.hs:17 expected: Right 0 but got: Left "negative number:1:1:...unexpected ’-’\nexpecting numeric character\nˠ " ### Failure in: 0:natParser:3 test/hunit/Spec.hs:19 expected: Right 1 but got: Right 0 Cases: 4 Tried: 4 Errors: 0 Failures: 2 15
  18. ୈ 2 ষ Haskell ͱςετ 2.3 QuickCheck 2.3 QuickCheck QuickCheck

    ϥΠϒϥϦ͸ɺHaskell ͰϓϩύςΟϕʔεςετΛߦ͏ͨΊͷςετϑϨʔϜϫʔ ΫͰ͢*5ɻϓϩύςΟϕʔεςετ͸ɺؔ਺͕͋Δੑ࣭Λຬ͔ͨ͢Ͳ͏͔ΛϥϯμϜͳೖྗΛͨ͘͞ Μ༩͑ͯςετ͢Δ͜ͱΛࢦ͠·͢ɻͨͱ͑͹ɺՃࢉͷҾ਺ΛೖΕସ͑ͯ΋݁Ռ͕มΘΒͳ͍͜ͱ ΍ɺϦετ΍഑ྻͷॱ൪Λ 2 ճͻͬ͘Γฦ͢ͱ΋ͱʹ໭Δ͜ͱͳͲΛ؆୯ʹςετͰ͖·͢ɻࠓճͷ ৔߹͸ɺrationalParser ؔ਺ͷੑ࣭ͱͯ͠ɺ਺஋Λจࣈྻʹม׵ͯ͠ύʔεͨ͠৔߹ʹݩͷ਺஋Λ ಘΒΕΔ͔Ͳ͏͔ΛνΣοΫ͠·͢ɻ import Lib import Numeric (showFFloat) import Test.QuickCheck import Text.Megaparsec (parseMaybe) main :: IO () main = do quickCheck propParseRationalNumber propParseRationalNumber :: Double -> Bool propParseRationalNumber n = fmap fromRational (parseMaybe rationalParser (showFFloat Nothing n "")) == Just n Double ܕ͸ഒਫ਼౓ුಈখ਺఺਺ܕͰ͢ɻ͜ΕΛɺ૊ࠐΈͷํ๏Ͱૉ௚ʹจࣈྻม׵͢Δͱ 1e-2 ͷΑ͏ͳه๏ʹ΋ͳΓಘΔͨΊɺshowFFloat ؔ਺Λ࢖ͬͯඞͣ 0.1234 ͷΑ͏ͳه๏ʹ͍ͯ͠· ͢ɻparseMaybe ؔ਺͸ɺparse ͱҟͳΓΦϓγϣφϧܕͰ͋Δ Maybe a ܕͰ݁ՌΛฦ͠·͢ɻͦ ͯ͠ɺfromRational ؔ਺Ͱ Rational ܕ͔Β Double ܕ΁ม׵͠ɺݩͷ Double ܕͷ஋ͱ౳͘͠ ͳΔ͜ͱΛνΣοΫ͍ͯ͠·͢ɻ͜ͷςετͷ࣮ߦ݁Ռ͸࣍ͷΑ͏ʹͳΓ·͢ɻ +++ OK, passed 100 tests. ͜Ε·ͨγϯϓϧͰ͢ͶɻquickCheck ؔ਺͸ɺσϑΥϧτͰ 100 छྨͷςετύλʔϯΛνΣο Ϋͯ͘͠Ε·͢ɻ ͜͜Ͱ Double ܕͷ஋Ͱ͸ͳ͘ Rational ܕͷ஋Λ༩͑ͨ৔߹͸Ͳ͏ͳΔͰ͠ΐ͏͔ɻ *5 https://hackage.haskell.org/package/QuickCheck 16
  19. ୈ 2 ষ Haskell ͱςετ 2.4 Hspec propParseRationalNumber :: Rational

    -> Bool propParseRationalNumber n = parseMaybe rationalParser (showFFloat Nothing n’ "") == Just n where n’ = fromRational n :: Double Rational ܕͷ஋Λ૊ࠐΈͷํ๏Ͱૉ௚ʹจࣈྻม׵͢Δͱ 2 % 3 ͷΑ͏ͳ෼਺ه๏ʹͳΓ·͢ɻ ͜Ε͸ύʔεͰ͖ͳ͍ͷͰɺҰ౓ Double ܕͷ஋ʹม׵ͯ͠ showFFloat ؔ਺Λ࢖͍ͬͯ·͢ɻ͜ ΕΛ࣮ߦ͢Δͱ࣍ͷΑ͏ʹͳΓ·͢ɻ *** Failed! Falsified (after 5 tests and 4 shrinks): 1 % 3 1 % 3 ͸ 3 ෼ͷ 1 Ͱɺ͜Ε͸॥؀খ਺ʹͳΓ·͢ɻ॥؀খ਺͸ɺDouble ܕͷ஋Λܦ༝͢Δͱ΋ͱ ʹ໭ͤͳ͍ͷͰɺ͠ΐ͏͕ͳ͍Ͱ͢Ͷɻ 2.4 Hspec Hspec ϥΠϒϥϦ͸ Ruby ͷ RSpec ͷΑ͏ͳ DSL Λఏڙ͢ΔςετϑϨʔϜϫʔΫͰ͢*6ɻς ετͷΞαʔγϣϯͷ෦෼͸ɺHUnit Λϕʔεʹ͍ͯ͠·͢ɻ·ͨɺQuickCheck ͷΑ͏ͳ΄͔ͷς ετϑϨʔϜϫʔΫΛݺͼग़͢͜ͱ΋ՄೳͰ͢ɻଞʹ΋ɺฒྻ࣮ߦ΍ෳ਺ͷςετϑΝΠϧΛࣗಈͰ ूΊͯ͘Δ͘͠ΈͳͲ΋ఏڙ͍ͯ͠·͢ɻ rationalParser ؔ਺ͷϢχοτςετ΍ϓϩύςΟϕʔεςετΛɺHspec ͷه๏Ͱॻ͖௚͢ ͱ࣍ͷΑ͏ʹͳΓ·͢ɻ import Test.Hspec -- ଞ͸ׂѪ main = hspec $ do describe "rationalParser" $ do let subject n = parse’ rationalParser "" n context "when input is 0" $ it "should be same number" $ subject "0" ‘shouldBe‘ Right 0 context "when input is positive number" $ it "should be same number" $ *6 https://hackage.haskell.org/package/hspec 17
  20. ୈ2ষ Haskellͱςετ 2.4 Hspec subject "1234567890" ‘shouldBe‘ Right 1234567890 context

    "when input is negative number" $ it "should be same number" $ subject "-1" ‘shouldBe‘ Right (-1) context "when input is rational number" $ it "should be same number" $ subject "0.0123" ‘shouldBe‘ Right (123 % 10000) it "return same float number" $ property propParseRationalNumber RSpec Ͱ͓ೃછΈͷ describe ΍ context Ͱจ຺͝ͱʹॻ͖෼͚Δ͜ͱ͕Ͱ͖ɺshouldBe ؔ ਺Λ࢖ͬͯΞαʔγϣϯΛ͍ͯ͠·͢ɻshouldBe ؔ਺͕தஔԋࢉࢠͷΑ͏ʹ഑ஔͰ͖͍ͯΔͷ͸ɺ Haskell ͕೚ҙͷؔ਺ΛόοΫΫΦʔτ ‘ ͰғΉ͜ͱͰɺதஔԋࢉࢠͱͯ͠ར༻Ͱ͖Δ͔ΒͰ͢ɻ͋ ࣦ͑ͯഊ͢ΔΑ͏ʹগ͠ॻ͖׵͑ͯɺ࣮ߦ͢Δͱ࣍ͷΑ͏ʹͳΓ·͢*7ɻ Lib.rationalParser when input is 0 should be same number [o] when input is positive number should be same number [o] when input is negative number should be same number [o] when input is rational number should be same number [x] return same float number [x] Failures: test/hspec/Spec.hs:30:26: 1) Lib.rationalParser, when input is rational number, should be same number expected: Right (123 % 1000) but got: Right (123 % 10000) To rerun use: --match "/Lib.rationalParser/when input is rational number/should beˠ same number/" --seed 629150678 test/hspec/Spec.hs:32:5: 2) Lib.rationalParser return same float number Falsified (after 8 tests and 2 shrinks): 1 % 3 To rerun use: --match "/Lib.rationalParser/return same float number/" --seed 62915ˠ 0678 *7 νΣοΫϚʔΫΛඳࣸͰ͖ͳ͔ͬͨͷͰ o ͱ x ʹஔ͖׵͍͑ͯ·͢ɻ 18
  21. ୈ 2 ষ Haskell ͱςετ 2.5 doctest Randomized with seed

    629150678 Finished in 0.0017 seconds 5 examples, 2 failures ͍ͩͿݟ΍͍͢Ͱ͢Ͷɻ 2.5 doctest Haskell ͸ιʔείʔυ͔Β haddock ͱݺ͹ΕΔυΩϡϝϯτ͕ࣗಈͰੜ੒͞ΕɺͦΕ͕ϥΠϒ ϥϦϨδετϦͰ͋Δ Hackage ΁ࣗಈతʹ഑ஔ͞Ε·͢ɻυΩϡϝϯτʹ͸ɺؔ਺ͷར༻ྫΛॻ͖ ͍ͨͰ͢ΑͶɻͦͯ͠ɺͦͷར༻ྫ͕ Haskell ίʔυͱͯ͠ਖ਼͍͔͠Λςετ͍ͨ͠Ͱ͢ΑͶɻͦΕ Λ΍ͬͯ͘ΕΔͷ͕ doctest Ͱ͢*8ɻ ͨͱ͑͹ɺ࣍ͷΑ͏ʹ calculate ؔ਺ͷར༻ྫΛίϝϯτͰهड़͠·͢ɻ -- >>> calculate "1.23" -- Right (123 % 100) -- >>> calculate "1 + 2 * (3 - 1.0)" -- Right (5 % 1) calculate :: String -> Either ParserError Rational calculate = ... ͦͯ͠ɺ࣍ͷΑ͏ͳςετίʔυΛॻ͘͜ͱͰςετͱ࣮ͯ͠ߦͰ͖·͢ɻ import Test.DocTest main :: IO () main = doctest ["-isrc", "src/Lib.hs"] ͪͳΈʹɺίϝϯτΛؤுΕ͹ɺ֎෦ύοέʔδΛΠϯϙʔτͨ͠Γ QuickCheck Λ࣮ߦͨ͠Γ΋ Ͱ͖ΔΑ͏Ͱ͢ɻ࣮ߦ͢Δͱ͜Μͳײ͡Ͱ͢ɻ Examples: 2 Tried: 2 Errors: 0 Failures: 0 ΄΅΄΅ HUnit ͱಉ͡Ͱ͢Ͷɻ *8 https://hackage.haskell.org/package/doctest 19
  22. ୈ 2 ষ Haskell ͱςετ 2.6 tasty 2.6 tasty ࠷ޙ͸

    tasty Ͱ͢ɻtasty ͸ɺHspec ʹ͍ۙϥΠϒϥϦͰɺHUnit ΍ QuickCheck Λݺͼग़ͯ͠ɺ ΑΓϦονͳ UI/UX Λఏڙͯ͘͠Ε·͢*9ɻਖ਼௚ͳͱ͜Ζɺओ؍తʹ͸྆ऀʹେ͖ͳҧ͍͸ͳ͍ͱ ࢥ͍·͢ɻڧ͍ͯݴ͏ͱɺtasty ͔Β Hspec Λ࢖͏͜ͱ΋Ͱ͖ΔͷͰɺ໎͏ͷͰ͋Ε͹ tasty Λ࢖͏ ͷ͕ΞϦ͔ͳͱࢥ͍·͢ɻ͋Δ͍͸ɺͰ͖Δ͚ͩґଘΛখ͍ͨ͘͞͠ͷͰ͋Ε͹ Hspec Ͱ΋Α͍ͱ ࢥ͍·͢ɻͪͳΈʹɺHaskell ͷ༗໊ͳ OSS ͩͱ Pandoc*10ɾAgda*11ɾhaskell-language-server*12 ͕ tasty Λɺhadolint*13ɾPureScript*14ɾStack*15 ͕ Hspec Λ࢖͍ͬͯΔΑ͏Ͱͨ͠ɻͰ͢ͷͰɺ ͲͪΒ΋࢖ΘΕ͍ͯΔҹ৅Ͱ͢ɻ tasty ͷαϯϓϧίʔυʹ͸ calculate ؔ਺Λ࢖ͬͯΈ·͢ɻcalculate ؔ਺ͷϢχοτςετ ͱϓϩύςΟϕʔεςετΛ࣍ͷΑ͏ʹॻ͍ͯΈ·ͨ͠ɻ import Lib import Data.Ratio (numerator, denominator) import Test.Tasty import Test.Tasty.QuickCheck import Test.Tasty.HUnit main :: IO () main = defaultMain $ testGroup "Tests" [unitTests, properties] unitTests :: TestTree unitTests = testGroup "Unit tests" [ testCase "zero" $ calculate "0" @?= Right 0 , testCase "positive number" $ calculate "0123456789" @?= Right 123456789 , testCase "negative number" $ calculate "-123" @?= Right (-123) , testCase "fraction number" $ calculate "123.0456" @?= Right 123.0456 , testCase "precedence of operators" $ calculate "1 + 2 * (3 - 4)" @?= Right (-1) ] properties :: TestTree *9 https://hackage.haskell.org/package/tasty *10 https://github.com/jgm/pandoc *11 https://github.com/agda/agda *12 https://github.com/haskell/haskell-language-server *13 https://github.com/hadolint/hadolint *14 https://github.com/purescript/purescript *15 https://github.com/commercialhaskell/stack 20
  23. ୈ2ষ Haskellͱςετ 2.6 tasty properties = testGroup "Properties (checked by

    QuickCheck)" [ testProperty "calculate (show n) == Right n" $ \n -> (calculate $ show’ n) == Right n , testProperty "calculate (show n ++ ’+’ ++ show m) == Right (n + m)" $ \(n, m) -> calculate (show’ n ++ "+" ++ show’ m) == Right (n + m) , testProperty "calculate (show n ++ ’-’ ++ show m) == Right (n - m)" $ \(n, m) -> calculate (show’ n ++ "-" ++ show’ m) == Right (n - m) , testProperty "calculate (show n ++ ’*’ ++ show m) == Right (n * m)" $ \(n, m) -> calculate (show’ n ++ "*" ++ show’ m) == Right (n * m) , testProperty "calculate (show n ++ ’/’ ++ show m) == Right (n / m)" $ \(n, m) -> if m /= 0 then calculate (show’ n ++ "/" ++ show’ m) == Right (n / m) else -- θϩআࢉ͸ΤϥʔʹͳΔͨΊεΩοϓ͢Δ True ] where show’ :: Rational -> String show’ n = "(" ++ show (numerator n) ++ "/" ++ show (denominator n) ++ ")" جຊతʹ͸ࠓ·ͰͷςετͱมΘΓ·ͤΜɻϢχοτςετʹԋࢉࢠͷ༏ઌॱҐʹؔ͢Δςετ Λ௥Ճ͠ɺϓϩύςΟϕʔεςετʹ࢛ଇԋࢉʹؔ͢ΔςετΛ௥Ճ͠·ͨ͠ɻ·ͨɺআࢉ͕࢖͑Δ ʢ࣮࣭ɺ෼਺͕ॻ͚ΔʣΑ͏ʹͳͬͨͷͰɺDouble ܕͷ஋ͷ୅ΘΓʹ Rational ܕͷ஋Ͱ௚઀ϓϩ ύςΟϕʔεςετΛߦ͍ͬͯ·͢ɻࣦ͋͑ͯഊ͢ΔΑ͏ʹগ͠ॻ͖׵͑ͯɺ࣮ߦ͢Δͱ࣍ͷΑ͏ʹ ͳΓ·͢ɻ Tests Unit tests zero: OK positive number: OK negative number: OK fraction number: OK precedence of operators: FAIL test/tasty/Spec.hs:23: expected: Right ((-2) % 1) but got: Right ((-1) % 1) Use -p ’/precedence of operators/’ to rerun this test only. Properties (checked by QuickCheck) calculate (show n) == Right n: OK +++ OK, passed 100 tests. calculate (show n ++ ’+’ ++ show m) == Right (n + m): OK +++ OK, passed 100 tests. calculate (show n ++ ’-’ ++ show m) == Right (n - m): OK +++ OK, passed 100 tests. calculate (show n ++ ’*’ ++ show m) == Right (n * m): OK +++ OK, passed 100 tests. calculate (show n ++ ’/’ ++ show m) == Right (n / m): FAIL *** Failed! Exception: ’Ratio has zero denominator’ (after 1 test): 21
  24. ୈ 2 ষ Haskell ͱςετ 2.7 ͓͠·͍ (0 % 1,0

    % 1) Use --quickcheck-replay=230587 to reproduce. Use -p ’/calculate (show n ++ ’\’’\/’\’’ ++ show m) == Right (n \/ m)/’ to rerˠ un this test only. 2 out of 10 tests failed (0.01s) 2.7 ͓͠·͍ Haskell ͷςετϥΠϒϥϦ͸΄͔ʹ΋ SmallCheck ΍ Hedgehog ͳͲɺओʹϓϩύςΟϕʔε ςετͷϥΠϒϥϦ͕ͨ͘͞Μ͋Γ·͢ɻtasty ͷ README ʹϓϩόΠμͱͯ͠ࡌ͍ͬͯΔͷͰɺ ڵຯ͕͋Δਓ͸ݟʹߦͬͯΈ͍ͯͩ͘͞ɻ͋ͱͪͳΈʹɺࣗ෼͸ tasty Λ࢖͍ͬͯ·͢ɻ 22
  25. ୈ 3 ষ Unity Ͱ Discord ಺Ϛϧν௨৴ήʔϜ Λ࡞Ζ͏ ։ൃຊ෦ΫϥΠΞϯτάϧʔϓͷ౻ҪͰ͢ɻຊߘͰ͸ɺ2024 ೥

    3 ݄ެ։ͷ Discord Embedded App SDK(ҎԼ Discord SDK) Λ࢖ͬͨΞΫςΟϏςΟͷ࡞ΓํΛ঺հ͠·͢ɻΫϥΠΞϯταΠ υ͸ Unity Λ࢖͍ɺαʔόαΠυ͸ Colyseus ͱ͍͏ Node.js ͷϑϨʔϜϫʔΫͰ։ൃ͠·͢ɻ 3.1 αϯϓϧήʔϜ ࠓճ࡞੒ͨ͠ Discord ΞΫςΟϏςΟʮϑΝΠτΫϥϒʯʹ͍ͭͯઆ໌͠·͢ɻ ਤ 3.1: ήʔϜͷεΫϦʔϯγϣοτ ͢΂ͯͷαϯϓϧίʔυ͸ GitHubʢhttps://github.com/akira-fujii/unity-discordappʣ 23
  26. ୈ 3 ষ Unity Ͱ Discord ಺Ϛϧν௨৴ήʔϜΛ࡞Ζ͏ 3.1 αϯϓϧήʔϜ ʹެ։͍ͯ͠·͢ͷͰɺৄࡉͳ࣮૷͸ͦͪΒΛࢀর͍ͩ͘͞ɻͳ͓ίʔυ಺ʹ͸ΤϥʔϋϯυϦϯά

    ౳Λলུ͍ͯ͠Δ෦෼΋͋Γ·͢ͷͰɺ͋͘·Ͱػೳݕূ༻ͷαϯϓϧͱͯ͠͝ར༻͍ͩ͘͞ɻ· ͨɺϦϙδτϦʹ͸ϓϨΠಈը΁ͷϦϯΫ΋͋Γ·͢ͷͰɺήʔϜͷ֓ཁΛ஌Γ͍ͨํ͸ͦͪΒ΋͝ ཡ͍ͩ͘͞ɻ ؀ڥͱߏ੒ • Unity 2022.3.9f1 • Node.js 20.11.1 • Discord Embedded App SDK 1.2.0 ʮϑΝΠτΫϥϒʯήʔϜϧʔϧ ҎԼͷΑ͏ͳλʔϯੑͷΧʔυήʔϜͰ͢ɻ • ϓϨΠਓ਺ 2 ਓʙͷରઓήʔϜ • λʔϯͷ࢝ΊʹɺͦΕͧΕઓ͍͍ͨϓϨΠϠʔͱΧʔυΛબ୒͢Δɻ • ઓಆͰ͸ΧʔυͲ͏͠ͷڧ͞Λൺֱ͠ɺڧ͍ํ͕উརͯ͠μϝʔδΛ༩͑ΒΕΔɻ • ্هΛ܁Γฦ͠ɺ࠷ޙ·Ͱੜ͖࢒ͬͨϓϨΠϠʔ͕উར ϘʔυήʔϜΛࢀߟʹɺਓ਺͕ϑϨΩγϒϧͰ஥ؒΛ༠͍΍͍͢ϧʔϧ͕ΞΫςΟϏςΟʹ߹͏ͱ ߟ͑ͯ͜ͷήʔϜΛબͼ·ͨ͠ɻ ·ͨɺΧʔυͷ໊শ΍ը૾σʔλ͸ Google εϓϨουγʔτ͔Β௚઀ csv ܗࣜͰऔಘ͍ͯ͠· ͢ɻ࣮ࡍͷ੡඼ϦϦʔεͰ͸ɺηΩϡϦςΟ΍ΞΫηεස౓ͷ؍఺͔Β ετϨʔδ౳Λܦ༝͢΂͖ ͩͱࢥ͍·͕͢ɺݕূஈ֊Ͱ͸ෳ਺ਓͰڞಉฤूͰ͖ͯศརͰͨ͠ɻ ਤ 3.2: ΧʔυͷϚελσʔλ 24
  27. ୈ 3 ষ Unity Ͱ Discord ಺Ϛϧν௨৴ήʔϜΛ࡞Ζ͏ 3.2 Discord ΞΫςΟϏςΟ

    ਤ 3.3: ήʔϜ಺Χʔυදࣔ ͦͷ΄͔ৄࡉͳϧʔϧʹ͍ͭͯ͸ɺϓϨΠಈը΍ GitHub ͷ Readme Λ͝ཡ͍ͩ͘͞ɻ 3.2 Discord ΞΫςΟϏςΟ ΞΫςΟϏςΟ͸ɺDiscord ಺ͷΠϯϥΠϯϑϨʔϜͰಈ͘Πϯετʔϧෆཁͷ Web ΞϓϦͰ͢ɻ SDK ެ։લ͸Ұ෦ͷσϕϩούʔͷΈ͕։ൃͰ͖ɺΰϧϑήʔϜ΍ಈըࢹௌΞϓϦͳͲ͕ެ։͞Ε ͍ͯ·͕ͨ͠ɺ2024 ೥ 3 ݄ʹ SDK ͷϓϨϏϡʔ൛͕Ұൠެ։͞Εͯ୭Ͱ΋ΞΫςΟϏςΟΛ࡞੒ Ͱ͖ΔΑ͏ʹͳΓ·ͨ͠ɻ ࣥච౰࣌ͷϓϨϏϡʔ൛Ͱ͸ɺ࡞੒ͨ͠ΞΫςΟϏςΟ͸ 25 ਓҎԼͷαʔόͰ͔͠ݕূͰ͖ͳ͍ɺ ΞϓϦ಺՝ۚ౳ͷػೳ͕࢖͑ͳ͍ɺͱ੍͍ͬͨ໿͕͋Γ·͢ɻΑͬͯαϯϓϧήʔϜ΋ඇެ։Ͱͷݕ ূʹͱͲ·͍ͬͯ·͢ɻ Discord Developer Portal ΞΫςΟϏςΟͷ؅ཧ͸ Discord Developer Portal ͱ͍͏μογϡϘʔυ্Ͱߦ͍·͢ɻ λΠτϧ΍ΞΠίϯͷઃఆͷ΄͔ɺΞϓϦ͝ͱʹൃߦ͞ΕΔʮClient IDʯͱʮClient SecretʯΛ ֬ೝͨ͠Γɺ֎෦ URL ΁ͷΞΫηεΛڐՄ͢ΔʮURL Mappingʯͷઃఆ΋ߦ͍·͢ɻ ෳ਺ਓͰ։ൃ΍ݕূΛߦ͏৔߹ɺνʔϜϝϯόʔͷొ࿥ɾݖݶઃఆ΋μογϡϘʔυ্Ͱߦ͍·͢ɻ 25
  28. ୈ 3 ষ Unity Ͱ Discord ಺Ϛϧν௨৴ήʔϜΛ࡞Ζ͏ 3.2 Discord ΞΫςΟϏςΟ

    ਤ 3.4: Discord Developer Portal ͷΞϓϦ؅ཧը໘ Discord Embedded App SDK Discord Embedded App SDK ͸ JavaScript Ͱఏڙ͞Ε͍ͯΔɺΞΫςΟϏςΟ։ൃ༻ͷ SDK Ͱ͢ɻ೔ຊޠͰ͸ʮຒΊࠐΈΞϓϦ SDKʯͱදه͞Ε·͢ɻ ओཁͳ API ΛυΩϡϝϯτ *1 ͔Βൈਮͯ͠঺հ͠·͢ɻ • ΫϥΠΞϯτͷೝূ • Discord νϟϯωϧʹؔ͢Δ৘ใΛऔಘ • ΞΫςΟϏςΟʹࢀՃ͍ͯ͠ΔϢʔβʔͷ৘ใΛऔಘ • Discord ্ͷϑΝΠϧڞ༗ɾট଴μΠΞϩάͷදࣔ ΄͔ʹ΋ɺϓϨϏϡʔ൛Ͱ͸ར༻Ͱ͖·ͤΜ͕ɺΞϓϦ಺ߪೖʹؔ͢Δ API ͳͲ͕༧ఆ͞Ε͍ͯ ·͢ɻ Unity Ͱ Discord ΞΫςΟϏςΟΛ࡞Δʹ͸ Unity Ͱ Discord ΞΫςΟϏςΟΛ࡞Δʹ͸ɺWebGL ϏϧυͰಈ࡞ͤ͞Δඞཁ͕͋Γ·͢ɻ Discord ΞΫςΟϏςΟ͸ Web ΞϓϦέʔγϣϯΛຒΊࠐΉܗͰಈ࡞͢ΔͨΊͰ͢ɻ ୯७ʹ Discord ಺Ͱಈ͔͚ͩ͢ͳΒɺDiscord SDK ͷެࣜαϯϓϧ *2Λϕʔεʹ WebGL Ϗϧυ ΁ΫϥΠΞϯτೝূͷίʔυΛ૊ΈࠐΉ͜ͱͰ࣮ݱͰ͖·͢ɻ ΫϥΠΞϯτೝূ ΫϥΠΞϯτೝূͷखॱΛઆ໌͠·͢ɻ *1 https://discord.com/developers/docs/developer-tools/embedded-app-sdk *2 https://discord.com/developers/docs/activities/building-an-activity#step-1-creating-a-new-a pp 26
  29. ୈ 3 ষ Unity Ͱ Discord ಺Ϛϧν௨৴ήʔϜΛ࡞Ζ͏ 3.2 Discord ΞΫςΟϏςΟ

    ·ͣαʔό্ʹτʔΫϯऔಘ༻ͷΤϯυϙΠϯτΛཱͯ·͢ɻ ͦͯ͠ΫϥΠΞϯτ͸ΤϯυϙΠϯτ͔ΒτʔΫϯΛऔಘ͠·͢ɻ ࠷ޙʹɺτʔΫϯΛ Dicord SDK ͷ commands.authenticate ؔ਺ʹ౉͢͜ͱͰೝূ͕׬ྃ͠ ·͢ɻ αʔόଆ͸ Colyseus ͷ։ൃऀʹΑΔςϯϓϨʔτ*3Λࢀߟʹ࣮૷͠·ͨ͠ɻαϯϓϧήʔϜͷ pj -poi/Server/app.config.ts Ͱ͸ɺήʔϜαʔό্ʹೝূ༻ͷΤϯυϙΠϯτ/api/discord_to ken Λઃఆ͍ͯ͠·͢ɻ ΫϥΠΞϯτଆͰ͸ɺDiscord SDK ͷαϯϓϧ௨Γɺhttp ͰτʔΫϯΛऔಘͯ͠ೝূΛߦ͏ίʔ υΛهड़͠·͢ɻ pj-poi/client/main.js // ݺͼग़͠Օॴ͸লུ... async function setupDiscordSdk() { await discordSdk.ready(); // Authorize with Discord Client const { code } = await discordSdk.commands.authorize({ client_id: import.meta.env.VITE_DISCORD_CLIENT_ID, // ΞΫςΟϏςΟຖͷClienˠ t ID(ޙड़)Λ౉͢ // ...தུ... }); // αʔό͔ΒτʔΫϯΛऔಘ const response = await fetch("/api/discord_token", { // ...தུ... body: JSON.stringify({ code, }), }); const { access_token, profile } = await response.json(); // Discord SDKʹτʔΫϯΛ౉͢ auth = await discordSdk.commands.authenticate({ access_token, }); } ޙ͸্هεΫϦϓτΛ WebGL Ϗϧυʹ૊ΈࠐΊ͹࣮૷׬ྃͰ͢ɻindex.html ͷ body λά಺ ͰɺUnity Ϗϧυ݁ՌͰ͋Δ loader.js ΑΓ΋લʹݺͼग़͠·͢ɻ <script type="module" src="/main.js"></script> ·ͱΊΔͱɺೝূ༻αʔόΛཱͯɺ͞Βʹ WebGL Ϗϧυʹ্هͷೝূίʔυΛ૊ΈࠐΉ͜ͱͰ Unity Ͱ࡞ͬͨήʔϜΛ Discord ΞΫςΟϏςΟͱͯ͠ಈ࡞ͤ͞Δ͜ͱ͕Ͱ͖·͢ɻ ͦͷ΄͔ެࣜαϯϓϧͰ͸ɺΞΫςΟϏςΟΛϩʔΧϧͰݕূ͢ΔͨΊͷτϯωϦϯάํ๏ͳͲ͕ ঺հ͞Ε͍ͯ·͢ɻڵຯ͕͋Δํ͸ࢀߟʹͯ͠͸͍͔͕Ͱ͠ΐ͏͔ɻ *3 https://github.com/colyseus/webgame-template/blob/c1362e5f52d53b7785936651ec7889e23b526073/ packages/backend/src/app.config.ts#L47-L88 27
  30. ୈ 3 ষ Unity Ͱ Discord ಺Ϛϧν௨৴ήʔϜΛ࡞Ζ͏ 3.3 ωοτϫʔΫτϙϩδʔͷબ୒ ϚϧνϓϨΠͷ࣮૷ʹ͍ͭͯ

    Discord SDK ʹ͸ϚϧνϓϨΠͷػೳ͸ͳ͍ͨΊɺࣗ෼ͰήʔϜαʔόΛཱͯΔ͔ɺPhoton ౳ͷ αʔυύʔςΟ੡ͷαʔϏεΛར༻͢Δඞཁ͕͋Γ·͢ɻUnity ͷϚϧνϓϨΠϑϨʔϜϫʔΫͱ͍ ͑͹ Photon ΍ Netcode ͕༗໊Ͱ͕͢ɺࠓճ͸ ήʔϜαʔόܕͰ։ൃͰ͖ΔϥΠϒϥϦʮColyseusʯ Λબ୒͠·ͨ͠ɻ 3.3 ωοτϫʔΫτϙϩδʔͷબ୒ ௨৴ίετ΍ෆਖ਼ϦεΫͳͲͷ؍఺͔ΒɺήʔϜͷاըʹ߹Θͤͯద੾ͳωοτϫʔΫτϙϩδʔ Λબ୒͢Δඞཁ͕͋Γ·͢ɻࠓճ͸ήʔϜαʔόܕΛ࠾༻͠·͕ͨ͠ɺൺֱͷͨΊ ओཁͳωοτϫʔ Ϋτϙϩδʔͷ 1 ͭͰ͋ΔΫϥΠΞϯτϗετܕʹ͍ͭͯ΋આ໌͠·͢ɻ ΫϥΠΞϯτϗετܕ ΫϥΠΞϯτϗετܕͰ͸ɺ1 ୆ͷΫϥΠΞϯτ͕ϗετͱͳΓɺதܧαʔόΛհͯ͠΄͔ͷΫϥ ΠΞϯτͱ௨৴͠·͢ɻ ར఺ͱͯ͠ɺ෺ཧܭࢉ΍ήʔϜϩδοΫ͸جຊతʹΫϥΠΞϯτ͕୲౰͢ΔͨΊɺαʔόͷϥϯχ ϯάίετ͕ܰݮ͞Ε·͢ɻಛʹɺήʔϜ಺ͷॲཧෛՙ΍௨৴ྔ͕ଟ͍ରઓܕͷΞΫγϣϯήʔϜͰ ༗ޮͰ͠ΐ͏ɻ ͨͩ͠ɺϗετ͕੾அ͞ΕΔͱήʔϜ͕ఀࢭ͢ΔͨΊɺϗετϚΠάϨʔγϣϯͳͲͷ͘͠Έ͕ඞ ཁͰ͢ɻ·ͨɺΫϥΠΞϯτ͕ήʔϜͷঢ়ଶΛ؅ཧ͢ΔͨΊɺνʔτ΍ෆਖ਼ૢ࡞ͷϦεΫ͕͋Γ· ͢ɻ͜Εʹରॲ͢ΔͨΊʹ͸ɺॏཁͳσʔλΛαʔόͰ؅ཧɾݕূ͢Δରࡦ͕ߟ͑ΒΕ·͢ɻ ήʔϜαʔόܕ ήʔϜαʔόܕͰ͸ɺήʔϜͷঢ়ଶΛ͢΂ͯαʔόଆͰ؅ཧ͠·͢ɻήʔϜͷϩδοΫ΍݁Ռ൑ఆ ͳͲ͸͢΂ͯαʔόଆͰॲཧ͞Ε·͢ɻ͜ͷํࣜͰ͸αʔόͷίετ͸ߴ͘ͳΓ·͕͢ɺެฏੑͷߴ ͍ϚϧνϓϨΠ௨৴Λ࣮ݱͰ͖Δ΄͔ɺେਓ਺ͷϓϨΠϠʔΛαϙʔτ͢Δ͜ͱ΋ՄೳͰ͢ɻ ΫϥΠΞϯτ͸ʮϏϡʔʯͱͯ͠ৼΔ෣͏ΠϝʔδͰ͢ɻαʔό͔ΒૹΒΕͨ݁ՌΛϨϯμϦϯά ͠ɺϢʔβʔͷೖྗΛαʔόʹૹ৴͢Δ໾ׂΛ୲͍·͢ɻ͜ͷͨΊɺσʔλͷॻ͖׵͑ͳͲͷෆਖ਼ૢ ࡞͸͢΂ͯαʔόଆͰݕ஌͞ΕΔͨΊɺηΩϡϦςΟ͕ߴ͍ͱ͞Ε͍ͯ·͢ɻ ࠓճ͸ɺΑΓଟ͘ͷϓϨΠϠʔָ͕͠ΊΔΑ͏ͳήʔϜΛ໨ࢦͨ͜͠ͱɺ·ͨήʔϜੑ͔Β ϨΠ ςϯγ΍αʔόίετ͕໰୊ʹͳΒͳ͍ͱ൑அ͠ɺήʔϜαʔόܕΛ࠾༻͠·ͨ͠ɻ 28
  31. ୈ 3 ষ Unity Ͱ Discord ಺Ϛϧν௨৴ήʔϜΛ࡞Ζ͏ 3.4 Colyseus ͰϚϧνϓϨΠ

    3.4 Colyseus ͰϚϧνϓϨΠ Colyseus *4 ͸ɺJavaScript/TypeScript ͓Αͼ Node.js Λ࢖༻ͯ͠ήʔϜαʔόΛ࡞੒͠ɺήʔ ϜΤϯδϯͱ؆୯ʹ౷߹͢ΔͨΊͷ OSS ϑϨʔϜϫʔΫͰ͢ɻUnity SDK ΋ఏڙ͞Ε͓ͯΓɺ WebGL Ͱ࢖͑Δ WebSocket ௨৴༻ͷϥΠϒϥϦ΋ؚ·Ε͍ͯ·͢ɻ·ͨɺDiscord SDK ͷαϯϓ ϧʹ΋࢖ΘΕ͍ͯ·͢ɻ Colyseus ͸ϧʔϜ؅ཧ΍ঢ়ଶͷಉظʹՃ͑ͯɺ ʮSchemaʯΛ࢖༻ͨ͠ύϥϝʔλͷಉظ΍Πϕϯ τۦಈͷϝοηʔδૹड৴ͳͲͷػೳΛఏڙ͍ͯ͠·͢ɻҰํͰɺPhoton ͳͲͱ͸ҟͳΓɺ࠲ඪิ ؒ΍ઌಡΈͳͲͷػೳ͸උ͍͑ͯ·ͤΜɻͦͷͨΊɺϦΞϧλΠϜੑ͕ߴ͍ΞΫγϣϯήʔϜΛ։ൃ ͢Δࡍʹ͸޻෉͕ඞཁͰ͕͢ɺλʔϯੑͷήʔϜʹ͸ద͍ͯ͠·͢ɻ ࢖༻ͯ͠Έͨײ૝ͱͯ͠ɺColyseus ͸γϯϓϧͰ֮͑΍͘͢ɺಛʹ Schema ͷػೳ͕ศརͰͨ͠ɻ ͔͜͜ΒϑϨʔϜϫʔΫݻ༗ͷػೳʹ৮Ε·͕͢ɺجຊతͳߟ͑ํ͸΄͔ͷ։ൃͰ΋Ԡ༻Ͱ͖Δͱײ ͨ͡ͷͰ঺հ͠·͢ɻ Schema ఆٛ Colyseus ʹ͸ɺ ΫϥΠΞϯτͱαʔόͰڞ௨ͷσʔλߏ଄Λఆٛ͢Δ IDL ͷΑ͏ͳػೳ ʮSchemaʯ ͕͋Γɺ͜ͷػೳ୯ମͰ΋ެ։͞Ε͍ͯ·͢ɻ ΫϥΠΞϯτʗαʔόͰڞ௨ͷσʔλߏ଄͸ɺTypeScript ʹ Schema ͱͯ͠ఆٛ͢Δ͜ͱͰɺର Ԡ͢Δ C#ίʔυΛࣗಈग़ྗͰ͖·͢ɻ ͨͱ͑͹ϓϨΠϠʔ৘ใʹ͍ͭͯɺಉظ͍ͨ͠σʔλΛ·ͣ TypeScript Ͱهड़͠·͢ɻ pj-poi/server/src/rooms/schema/MyRoomState.ts export class Player extends Schema { @type("boolean") isReady: boolean = false; @type(["string"]) hand = new ArraySchema<string>(); @type("number") hp: number = 0; } Ϧετ΍ key-value ܕʢࣙॻʣ΋ఆٛՄೳͰɺͨͱ͑͹ hand Ͱ͸ ͦͷϓϨΠϠʔ͕࣋ͭΧʔυͷ id ΛϦετͰ֨ೲ͍ͯ͠·͢ɻ ͔͜͜Β C#ίʔυΛੜ੒͢Δʹ͸ɺ npx schema-codegen ίϚϯυΛ࢖͍·͢ɻαϯϓϧήʔ ϜͰ͸ɺϓϩδΣΫτͷϧʔτ͔ΒҎԼͷίϚϯυΛ࣮ߦ͍ͯ͠·͢ɻ cd pj-poi/server *4 https://github.com/colyseus/colyseus 29
  32. ୈ 3 ষ Unity Ͱ Discord ಺Ϛϧν௨৴ήʔϜΛ࡞Ζ͏ 3.5 Discord ͱήʔϜΛ࿈ܞ

    npx schema-codegen src/rooms/schema/MyRoomState.ts --csharp --output ../../discord-test-unity/Assets/Scripts/Schema ੜ੒͞Εͨ C#ίʔυ͸ҎԼͷΑ͏ʹͳΓ·͢ɻ discord-test-unity/Assets/Scripts/Schema/Player.cs public partial class Player : Schema { [Type(1, "boolean")] public bool isReady = default(bool); [Type(2, "array", typeof(ArraySchema<string>), "string")] public ArraySchema<string> hand = new ArraySchema<string>(); [Type(3, "number")] public float hp = default(float); [Type(4, "number")] public float point = default(float); // ҎԼɺϓϩύςΟͷมߋΛ௨஌͢ΔΠϕϯτϋϯυϥ͕ଓ͘... } ͜͏ͯ͠ɺಉظ͍ͯ͠ΔϓϩύςΟͷมԽ΍ɺϦετ΁ͷ௥Ճͱ͍ͬͨΠϕϯτΛ Unity C#ଆͰ ड͚औΔ͜ͱ͕Ͱ͖·͢ɻ 3.5 Discord ͱήʔϜΛ࿈ܞ ࠓճ঺հ͢ΔαϯϓϧήʔϜͰ͸ɺDiscord ͱ࿈ܞͨ͠ػೳΛ 2 ࣮ͭ૷ͨ͠ͷͰ঺հ͠·͢ɻ Discord Ϣʔβʔ໊΍ΞΠίϯΛදࣔ͢Δ Discord SDK Λ࢖ͬͯϢʔβʔ໊΍ΞΠίϯΛऔಘ͠ɺUnity ্Ͱදࣔ͢Δํ๏Λ঺հ͠·͢ɻ 30
  33. ୈ 3 ষ Unity Ͱ Discord ಺Ϛϧν௨৴ήʔϜΛ࡞Ζ͏ 3.5 Discord ͱήʔϜΛ࿈ܞ

    ਤ 3.5: Ϣʔβʔ໊ʮAKIʯͱΞΠίϯΛήʔϜ಺Ͱදࣔ JavaScript ͱ Unity ͷϒϦοδ ·ͣલఏͱͯ͠ɺDiscord SDK ͷ Unity ϓϥάΠϯ͸ఏڙ͞Ε͍ͯ·ͤΜɻΑͬͯ SDK ͷػೳ Λݺͼग़͢ʹ͸ɺJavaScript ͔Β Unity ͷ C#ʹσʔλΛ౉ͨ͢ΊͷϒϦοδΛ࡞੒͢Δඞཁ͕͋ Γ·͢ɻৄࡉͳखॱ͸ׂѪ͠·͕͢ɺࠓճ͸.jslib ্ʹϋϒͱͳΔؔ਺Λ 1 ͭ࡞੒͠ɺͦΕΛհ͠ ͯϝοηʔδΛૹड৴͍ͯ͠·͢ɻ Ϣʔβʔ৘ใͷऔಘ Discord SDK Ͱ͸ɺcommands.authenticate() Ͱͷೝূ࣌ʹ͞·͟·ͳϢʔβʔ৘ใΛऔಘͰ ͖·͢ɻ auth = await discordSdk.commands.authenticate({access_token,}); ͜ͷ auth ʹ͸ҎԼͷΑ͏ͳϢʔβʔ৘ใؚ͕·Ε·͢ɻ • username: ҰҙͳϢʔβʔ໊ • global_name: άϩʔόϧϢʔβʔ໊ • avatar: ΞΠίϯը૾ URL ʹ࢖ΘΕΔ ID • discriminator: Ϣʔβʔࣝผࢠ • id: Ϣʔβʔ ID ͜ͷ͏ͪɺࠓճ͸ avatar ͷΈΛ࢖͍·͢ɻ΄͔ʹϢʔβʔ໊ʹ֘౰͢Δ߲໨͕͋Γ·͕͢ɺ࣮͸ Discord ্Ͱ͸ 3 छྨͷ໊લΛ۠ผ͢Δඞཁ͕͋Γ·͢ɻશϢʔβʔͰϢχʔΫͳ global_name*5ɺ શαʔόڞ௨ͷද໊ࣔ usernameɺͦͯ͠αʔόຖʹઃఆͰ͖Δ nickname Ͱ͢ɻ Discord Ͱ͸͓ޓ͍Λαʔόද໊ࣔͰݺͼ߹͏͜ͱ͕ଟ͍ͨΊɺࠓճͷΞΫςΟϏςΟͰ΋ nickn ame Λද͍ࣔͨ͠ͱࢥ͍·͢ɻ αʔόຖͷද໊ࣔ͸ɺνϟϯωϧ৘ใͷ voice_states ͱ͍͏Ϧετ͔Βऔಘ͠·͢ɻ͜ͷϦε *5 ͔ͭͯ͸ #0001 ͱ͍ͬͨ 4 ܻ਺ࣈͷʮࣝผλάʯʴʮϢʔβʔ໊ʯͷ૊Έ߹Θ͕ͤ࢖ΘΕ͍ͯ·ͨ͠ɻ 31
  34. ୈ 3 ষ Unity Ͱ Discord ಺Ϛϧν௨৴ήʔϜΛ࡞Ζ͏ 3.5 Discord ͱήʔϜΛ࿈ܞ

    τ͸ҎԼͷΑ͏ʹఆٛ͞Ε͍ͯ·͢ɻ voice_states: { user: { // தུ }; nick: string; // ͜Ε͕αʔόຖͷද໊ࣔ mute: boolean; voice_state: { deaf: boolean; mute: boolean; self_mute: boolean; self_deaf: boolean; suppress: boolean; }; volume: number; voice_states ͸ϦετͰ͢ͷͰɺࣗ෼ͷ nick Λऔಘ͢Δʹ͸Ϣʔβʔ ID Ͱൺֱ͠·͢ɻ pj-poi/client/main.js const channel = await discordSdk.commands.getChannel({channel_id: discordSdk.channelˠ Id}); channel.voice_states.forEach((voiceState) => { if (voiceState.user.id === auth.user.id) { console.log("voiceState\n" + voiceState.nick); nickName = voiceState.nick; } }); ͜͏ͯ͠औಘͨ͠Ϣʔβʔ৘ใΛɺઌड़ͷϒϦοδʹΑͬͯ JavaScript ͔Β Unity ଆʹ౉͠·͢ɻ ͳ͓ΞΠίϯը૾͸ɺavatar ͱ͍͏ ID Λ࢖͍ɺҎԼͷ URL Ͱ Discord ͷ CDN αʔό͔Βऔಘ ͠·͢ɻ https://cdn.discordapp.com/avatars/{ϢʔβʔID}/{avatar}.png ͜ͷ URL ʹର͠ɺUnity ্Ͱ͸ UnityWebRequestTexture.GetTexture(url) Λ࢖ͬͯΞΠί ϯͷςΫενϟΛऔಘͰ͖·͢ɻ ϘΠενϟϯωϧ಺ͰࣗಈϚονϯά͢Δ Colyseus Λ࢖͍ɹϘΠενϟϯωϧʢҎԼ VCʣ಺ͰࣗಈϚονϯάΛߦ͏ํ๏Λ঺հ͠·͢ɻҰ ൠతͳϚϧνϓϨΠήʔϜͰ͸ɺϢʔβʔ͕ϧʔϜ໊΍ύεϫʔυΛೖྗʢ͋Δ͍͸બ୒ʣͯ͠Ϛο νϯά͢Δ͜ͱ͕ҰൠతͰ͕͢ɺDiscord ΞΫςΟϏςΟͱͯ͠ެ։͢Δ৔߹͸ɺಉ͡ VC ಺ͷϢʔ βʔͲ͏͠Ͱ͙͢ʹϚϧνϓϨΠͰ͖ΔͱศརͰ͠ΐ͏ɻ ํ਑͸୯७ͰɺColyseus ͷϧʔϜ ID ͱͯ͠ VC ຖʹϢχʔΫͳ ID Λઃఆ͠·͢ɻ͜͜Ͱ͸ɺ 32
  35. ୈ 3 ষ Unity Ͱ Discord ಺Ϛϧν௨৴ήʔϜΛ࡞Ζ͏ 3.5 Discord ͱήʔϜΛ࿈ܞ

    Discord SDK ͷೝূ࣌ʹ΋औಘͰ͖Δ channel_id ΛϧʔϜ ID ͱͯ͠ઃఆ͠·͢ɻ ·ͨϧʔϜΛϓϥΠϕʔτʹઃఆ͢Δ͜ͱͰɺϧʔϜ ID Λ஌͍ͬͯΔΫϥΠΞϯτ͚͕ͩೖࣨͰ ͖ΔΑ͏ʹ͠·͢ɻ΄͔ʹ΋ Colyseus ʹ͸ oAuth() ͱ͍ͬͨೝূͷ͘͠Έ͕͋Γ·͕͢ɺࠓճ͸ѻ ͍·ͤΜɻ ·ͣ Unity ΫϥΠΞϯτͰ͸ɺroomId ͰϧʔϜΛ࡞੒·ͨ͸ೖࣨ͢Δؔ਺Λ࡞੒͠·͢ɻ discord-test-unity/Assets/Scripts/NetworkManager.cs public async Task JoinOrCreateAsync(string roomId) { Dictionary<string, object> options = new() { { "id", roomId } }; try { _room = await Client.JoinById<MyRoomState>(roomId, options); } catch (MatchMakeException e) { Debug.Log("Failed to join room. Creating a new room..."); _room = await Client.Create<MyRoomState>("card", options); } } JoinOrCreateAsync ؔ਺ʹ͸ϘΠενϟϯωϧͷ ID Λ౉͠·͢ɻطଘͷϧʔϜ͕͋Ε͹ɺID Λ ࢖ͬͯ Client.JoinById Ͱೖࣨ͠ɺͳ͚Ε͹ Client.Create Ͱ৽ن࡞੒͍ͯ͠·͢ɻϧʔϜ ID Λ౉ͨ͢ΊʹɺϧʔϜ࡞੒ɾೖࣨ࣌ʹࣗ༝ʹઃఆͰ͖Δ options Ҿ਺Λ࢖͍ͬͯ·͢ɻ ࣍ʹɺαʔόଆͰ͸ ID Λड͚औͬͯϓϥΠϕʔτϧʔϜΛ࡞੒͢Δؔ਺Λ࡞੒͠·͢ɻColyseus ϧʔϜͷॳظԽ͸ onCreate Πϕϯτ࣌ʹߦ͍·͢ɻ͜Ε͸ϧʔϜ࡞੒࣌ʹҰ౓ݺ͹Ε·͢ɻ pj-poi/server/src/rooms/MyRoom.ts export class MyRoom extends Room<MyRoomState> { onCreate (options: {id : string}) { this.roomId = options.id; this.setPrivate(true); this.setState(new MyRoomState()); // ... ུ ... } ্هͷίʔυͰ͸ɺϧʔϜ ID ͷઃఆͱɺsetPrivate(true) ͰϓϥΠϕʔτϧʔϜ΁ͷ੾Γସ͑ Λߦ͍ͬͯ·͢ɻ ͜ΕͰɺVC ಺ͰࣗಈϚονϯά͕Ͱ͖ΔΑ͏ʹͳΓ·ͨ͠ɻࠓճ͸ Colyseus Λ࢖࣮ͬͨ૷ྫΛ ঺հ͠·͕ͨ͠ɺ΄͔ͷϥΠϒϥϦ΍ϚονϯάػೳΛ࢖༻͢Δ৔߹΋ɺಉ͡ߟ͑ํͰ࣮૷Ͱ͖Δͱ ࢥ͍·͢ɻ 33
  36. ୈ 3 ষ Unity Ͱ Discord ಺Ϛϧν௨৴ήʔϜΛ࡞Ζ͏ 3.6 ΫϥΠΞϯτ։ൃͷϙΠϯτ 3.6

    ΫϥΠΞϯτ։ൃͷϙΠϯτ ͜͜Ͱ͸ɺDiscord SDK Λ࢖ͬͨΫϥΠΞϯτ։ൃͰಘΒΕͨ஌ݟΛ঺հ͠·͢ɻ Discord ্Ͱͷϩάͷ֬ೝํ๏ ௨ৗͷ Discord ΫϥΠΞϯτͰ͸ɺىಈதͷϩάΛ֬ೝͰ͖·ͤΜɻͦ͜Ͱ PTB ΞϓϦ*6Λެࣜ αΠτ͔ΒΠϯετʔϧ͠·͢ɻ PTB ΞϓϦͰΞΫςΟϏςΟΛىಈͨ͠ޙɺ • Windows Ͱ͸ Ctrl + Shift + i • macOS Ͱ͸ Cmd + Option + i ͷγϣʔτΧοτͰ։ൃ༻ίϯιʔϧΛ։͖·͢ɻϒϥ΢βͱಉ༷ɺUnity Ͱ Debug.Log ͨ͠಺ ༰΋දࣔ͞Ε·͢ɻ ෳ਺ਓͰͷݕূํ๏ ࣗ෼Ҏ֎ͷϢʔβʔʹΞΫςΟϏςΟΛىಈͯ͠΋Β͏ʹ͸ɺDeveloper Portal ্ͰʮνʔϜʯΛ ࡞੒ͯ͠ট଴͠·͢ɻ࣍ʹɺΞΫςΟϏςΟͷݖݶΛݸਓ͔ΒνʔϜʹҠ͠·͢ɻ׬ྃ͢Δͱɺμο γϡϘʔυ্ʹνʔϜ໊ʢTeamBayayʣͱΞΫςΟϏςΟʢpj-poiʣ͕දࣔ͞Ε·͢ɻ ਤ 3.6: ΞΫςΟϏςΟͱνʔϜ ΄͔ͷਓ͕࡞੒ͨ͠ΞΫςΟϏςΟΛىಈ͢Δʹ͸ɺDiscord ΫϥΠΞϯτͷʮઃఆʯʼʮৄࡉઃ *6 ެ։ςετ൛ϏϧυɻެࣜαΠτ͔Βμ΢ϯϩʔυՄೳͰ͢ɻhttps://discord.com/download 34
  37. ୈ 3 ষ Unity Ͱ Discord ಺Ϛϧν௨৴ήʔϜΛ࡞Ζ͏ 3.6 ΫϥΠΞϯτ։ൃͷϙΠϯτ ఆʯʼʮ։ൃऀϞʔυʯͱʮΞϓϦςετϞʔυʯΛ༗ޮʹ͠·͢ɻμΠΞϩάͰΞΫςΟϏςΟͷ

    ID Λೖྗ͠ɺURL ͷΦϦδϯλΠϓ͸ʮDiscord ϓϩΩγʯΛબ୒͠·͢ɻ ਤ 3.7: ΞΫςΟϏςΟىಈͷઃఆ Ҏ্ͷઃఆΛߦ͏͜ͱͰɺෳ਺ਓͰͷݕূ͕ՄೳͱͳΓ·͢ɻ ֎෦Ϧιʔε΁ͷΞΫηεํ๏ ΫϥΠΞϯτ͔Β Discord ֎ͷυϝΠϯ΁ΞΫηε͢Δʹ͸ɺΞϓϦͷϧʔτ https://[Clie nt ID].discordsays.com Λܦ༝͢Δඞཁ͕͋Γ·͢ɻ͜ͷઃఆ͸ʮURL Mappingʯͱݺ͹Εɺ Discord Developer Portal ্Ͱઃఆ͠·͢ɻαϯϓϧήʔϜͰ͸ɺҎԼͷΑ͏ʹઃఆ͍ͯ͠·͢ɻ 35
  38. ୈ 3 ষ Unity Ͱ Discord ಺Ϛϧν௨৴ήʔϜΛ࡞Ζ͏ 3.6 ΫϥΠΞϯτ։ൃͷϙΠϯτ ਤ

    3.8: URL Mapping ͷઃఆ಺༰ • / : ΫϥΠΞϯτΛ Cloudflared ͰτϯωϦϯάͨ͠υϝΠϯ • /colyseus : ήʔϜαʔόͷτϯωϦϯάઌ • /googledocs : docs.google.comɻGoogle εϓϨουγʔτʹΞΫηε͢ΔͨΊʹ࢖͍ͬͯ ·͢ɻ URL Mapping ʹ͸υϝΠϯ෦෼͚ͩهड़͢Ε͹Α͘ɺͦΕҎԼͷύε͸ඞཁʹԠͯ͡ΫϥΠΞϯ τଆͰࢦఆ͠·͢ɻ ઌ΄ͲͷઃఆΛݩʹɺ઀ଓઌΞυϨεͷྫΛ্͍͔ͭ͛͘·͢ɻͨͱ͑͹ΫϥΠΞϯτ͔Β WebSocket ήʔϜαʔό΁ΞΫηε͢Δʹ͸ɺҎԼͷΞυϨεΛ࢖͍·͢ɻ wss://[Client ID].discordsays.com/colyseus ·ͨࠓճͷήʔϜͰ͸ɺGoogle εϓϨουγʔτ͔ΒΧʔυͷ৘ใΛऔಘ͍ͯ͠·͕͢ɺUnity ΫϥΠΞϯτ͔Β͸ҎԼͷ URL ͰΞΫηε͍ͯ͠·͢ɻ https://[Client ID].discordsays.com/googledocs/spreadsheet/[εϓϨουγʔτͷI D] ͜ͷΑ͏ʹ URL Mapping Λઃఆ͢Δ͜ͱͰɺΞΫςΟϏςΟ಺Ͱ֎෦αʔϏεʹΞΫηεͰ͖ ·͢ɻαϯϓϧͰ͸৮Ε͍ͯ·ͤΜ͕ɺຊ൪؀ڥʹσϓϩΠͨ͠ࡍʹ΋ඞཁͳઃఆ͔ͱࢥ͍·͢ɻ UI ͕ະ׬੒Ͱ΋σόοάՄೳͳʮίϯιʔϧʯͷ༻ҙ Discord SDK ͱ͸௚઀ؔ܎͋Γ·ͤΜ͕ɺࠓճ࡞ͬͨσόοάػೳΛ঺հ͠·͢ɻ αϯϓϧήʔϜͷ։ൃͰ͸ɺϑϩϯτ෦෼ΑΓઌʹαʔόϩδοΫ͕׬੒͠·ͨ͠ɻͦ͜ͰΠϯλ 36
  39. ୈ 3 ষ Unity Ͱ Discord ಺Ϛϧν௨৴ήʔϜΛ࡞Ζ͏ 3.6 ΫϥΠΞϯτ։ൃͷϙΠϯτ ϑΣʔε͕Ͱ͖͍ͯͳ͍ػೳ΋ݕূͰ͖ΔΑ͏ʹɺήʔϜαʔόͷػೳΛจࣈྻͰݺͼग़͘͢͠ΈΛ

    ࣮૷͠·ͨ͠ɻҎԼͷը૾Ͱ͸ɺ ʮΧʔυΛ 1 ຕҾ͘ʯͱ͍͏ϝοηʔδΛɺήʔϜը໘ӈ্ͷ Inpu t Field ʹೖྗ͍ͯ͠·͢ɻ ਤ 3.9: ίϯιʔϧ ্هίϚϯυΛ࣮ߦ͢ΔͱɺϓϨΠϠʔͷखࡳʹϥϯμϜͳΧʔυ͕ 1 ຕ௥Ճ͞Ε·͢ɻ ίϯιʔϧػೳΛ࣮ݱ͢ΔͨΊʹɺ·ͣ Colyseus ͷϝοηʔδʹ͍ͭͯઆ໌͠·͢ɻΫϥΠΞϯ τͰ͸ GameRoom.Send() Λ࢖ͬͯαʔό΁ϝοηʔδΛૹΕ·͢ɻઌ΄ͲͷʮΧʔυΛ 1 ຕҾ͘ʯ ϝοηʔδ͸ҎԼͷΑ͏ʹॻ͚·͢ɻ await GameRoom.Send("draw", new { num = 1 }); ·ͨɺҾ਺͸ࣙॻͰ౉͢͜ͱ΋ՄೳͰ͢ɻ await GameRoom.Send(cmd, new Dictionary<string, object>() { {"num", 1} }); ͭ·ΓʮΧʔυΛ 1 ຕҾ͘ʯίϚϯυΛૹΔʹ͸ɺίϚϯυΛϝοηʔδ໊ʮdrawʯ ɺҾ਺͸{"nu m": 1}ͷࣙॻ΁ύʔε͢Ε͹Α͍ͷͰ͢ɻ αʔόଆͰ͸ɺ͜ͷϝοηʔδΛ onMessage ΠϕϯτͰड͚औΓ·͢ɻ this.onMessage("draw", (client, message: { num : number}) => {/*ॲཧ*/} ͜͏ͨ͠ػೳΛ࢖͍ɺ΄͔ʹ΋ήʔϜʹؔ͢Δ໋ྩΛ͍͔ͭ͘ఆٛ͠·ͨ͠ͷͰҰ෦঺հ͠·͢ɻ • ४උ׬ྃ ready --isReady true • ΧʔυΛ 1 ຕҾ͘ draw --num 1 • ೚ҙͷΧʔυΛ 1 ຕҾ͘ drawId --cardId c00001 • ഊ๺͢Δ surrender • উར͢Δ win • ࣗ਎ʹ hp ճ෮ heal --value 10 37
  40. ୈ 3 ষ Unity Ͱ Discord ಺Ϛϧν௨৴ήʔϜΛ࡞Ζ͏ 3.7 ऴΘΓʹ •

    ࣗ਎ʹμϝʔδ damage --value 10 ͜͏ͨ͠ػೳ͸ɺಈ࡞֬ೝ͚ͩͰͳ͘ήʔϜϧʔϧͷݕূʹ͓͍ͯ΋ඇৗʹ༗ޮͩͱײ͡·ͨ͠ɻ 3.7 ऴΘΓʹ ։ൃͷաఔͰ͸ɺެࣜͷ Discord Developers αʔόͰͨ͘͞ΜͷॿݴΛ͍͖ͨͩ·ͨ͠ɻڵຯ͕ ͋Δํ͸ͥͻͦͪΒ΋೷͍ͯΈͯ͸͍͔͕Ͱ͠ΐ͏͔ɻ 38
  41. ୈ 4 ষ Scala.js Ͱ Figma ϓϥάΠϯ։ൃೖ໳ 4.1 ·͕͖͑ ຊষͰ͸

    Figma ϓϥάΠϯ։ൃΛ Scala.js Ͱߦ͏खॱʹ͍ͭͯ঺հ͠ɺ࣮ࡍʹखΛಈ͔͠ͳ͕Β Scala.js Ͱ Figma ϓϥάΠϯ͕։ൃͰ͖ΔΑ͏ʹͳΔ·ͰͷྲྀΕΛ঺հ͠·͢ɻ શମͷྲྀΕͱͯ͠͸࣍ͷͱ͓ΓͰ͢ɻ 1. TypeScript Ͱ Figma ϓϥάΠϯΛ࡞Δ 2. Figma ϓϥάΠϯΛमਖ਼͢Δ 3. Scala.js Ͱ Figma ϓϥάΠϯΛ࡞ΔͨΊͷ؀ڥߏங 4. Scala.js Ͱ Figma ϓϥάΠϯΛ࡞Δ ஫ҙࣄ߲ ຊষͰͷྫࣔ͸ΩʔϘʔυγϣʔτΧοτ΍؀ڥߏங෦෼ͳͲ͢΂ͯ macOS Λલఏͱ͍ͯ͠ ·͢ɻ ·ͨࠓճ࡞Δ΋ͷ͸චऀͷ GitHub ্Ͱެ։͍ͯ͠·͢ɻ https://github.com/soudai-s/figma-floatviewer ຊষͷதͰίϛοτͱ͍͏ίϝϯτͱͱ΋ʹهࡌ͞ΕΔ URL ͸౰֘ϦϙδτϦͷࠩ෼ͱͳ͍ͬͯ ·͢ͷͰɺͦͷ࣌఺ͰͲͷΑ͏ͳঢ়ଶͱͳ͍ͬͯΔ͔͸ͦͪΒ͔Β֬͝ೝ͍͚ͨͩ·͢ɻ ͦΕͰ͸ͬͦ͘͞ Figma ͱϓϥάΠϯʹ͍ͭͯ঺հ͍͖ͯ͠·͢ɻ 4.2 Figma ͱϓϥάΠϯʹ͍ͭͯ Figma ͸γϯϓϧ͔ͭڧྗͳπʔϧͰ͋Γͳ͕Β֊૚తͳݖݶߏ଄Λ࣋ͪɺϩʔϧʹԠͨ͡ػೳ ੍ݶ͕ՄೳͰෳ਺ਓ͕ಉ࣌ฤूՄೳͳσβΠϯπʔϧͱͯ͠ɺࣥච࣌఺ͷ 2024 ೥ݱࡏʹ͓͍ͯ࠷΋ ਓؾͷ͋ΔΦϯϥΠϯσβΠϯπʔϧͷҰͭͰ͢ɻ·ͨɺϢʔβʔ͸ Figma ίϛϡχςΟʹࢀՃ͢ Δ͜ͱͰ΢ΟδΣοτɺϓϥάΠϯɺσβΠϯϑΝΠϧͱ͍ͬͨܗͰ Figma ͷϓϥοτϑΥʔϜ্ ʹࣗ਎͕ߏஙͨ͠ಠࣗͷ֦ுػೳ΍σβΠϯΛެ։Ͱ͖·͢ɻ͞Βʹ͜ΕΒ͸͢΂ͯ Figma ϓϥο 39
  42. ୈ 4 ষ Scala.js Ͱ Figma ϓϥάΠϯ։ൃೖ໳ 4.2 Figma ͱϓϥάΠϯʹ͍ͭͯ

    τϑΥʔϜΛ௨ͯ͡Ϣʔβʔ΁՝ۚ͢Δ͜ͱ΋ՄೳͰ͢ʢ՝ۚମܥ͸ങ͍੾ΓͱαϒεΫϦϓγϣϯ Λબ୒ՄೳͰ͢ʣ ɻ https://help.figma.com/hc/en-us/articles/11786473291159-Purchase-Community-r esources ϓϥάΠϯͱ΢ΟδΣοτͷҧ͍͸ҎԼͷυΩϡϝϯτʹৄ͘͠هࡌ͕͋Γ·͕͢ɺ຤ඌʹ͋Δͱ ͓Γʮ୯ಠͷϢʔβʔ͕࢖͏΋ͷͳΒϓϥάΠϯɺෳ਺Ϣʔβʔ͕ίϥϘϨʔγϣϯ໨తͰ࢖͏΋ͷ ͳΒ΢ΟδΣοτʯͱ͍͏ͷ͸෼͔Γ΍͍͢ࢦ਑ʹͳΔ͔ͱࢥ͍·͢ɻ https://www.figma.com/widget-docs/widgets-vs-plugins/ ࠓճ͸͜ͷதͰ΋ϓϥάΠϯΛ։ൃͯ͠Έ͍ͨͱࢥ͍·͢ɻ ຊষͰ࡞ΔϓϥάΠϯ ͜ͷهࣄΛࣥච͢Δʹ͋ͨΓ༑ਓͱ࿩ͨ͠ͱ͜ΖʮFigma ্Ͱಉ͡ϑΝΠϧʹ͋ΔผͷΦϒδΣΫ τΛൺֱͰ͖ͨΒศརͳͷͰ͸ͳ͍͔ʯͱ͍͏࿩͕͋Γ·ͨ͠ɻࠓճ͸͜ΕΛ࣮ݱ͢ΔͨΊ Figma ϓϥάΠϯͷ΢Οϯυ΢্Ͱࠓ։͍͍ͯΔ Figma ϑΝΠϧΛ։͚ΔπʔϧΛ࡞ͬͯΈ͍ͨͱࢥ͍ ·͢ɻ ׬੒Πϝʔδ͸࣍ͷͱ͓ΓͰ͢ɻ ਤ 4.1: ϓϥάΠϯͷ׬੒Πϝʔδ ͯ͞ɺ͍͖ͳΓ Scala.js Ͱ Figma ϓϥάΠϯΛ࡞Δͱ͍ͬͯ΋։ൃ͢Δʹ͸ Figma ϓϥάΠϯ ։ൃʹ͓͚Δ࠷௿ݶͷ஌͕ࣝඞཁͱͳΓ·͢ɻͦ͜ͰຊষͰ͸·ͣ Figma ͕ެࣜͰఏڙ͍ͯ͠ΔϘ ΠϥʔϓϨʔτΛखΛಈ͔͠ͳ͕Β࡞੒͠ɺ࣍ʹͦΕΛमਖ਼͠໨తͷϓϥάΠϯʹमਖ਼͢Δ͜ͱͰ 40
  43. ୈ 4 ষ Scala.js Ͱ Figma ϓϥάΠϯ։ൃೖ໳ 4.3 Figma ϓϥάΠϯͷΞʔΩςΫνϟ

    ʮFigma ͰϓϥάΠϯΛ࡞ΔͨΊʹ͸ͲͷΑ͏ʹਐΊΕ͹Α͍͔ʯΛ֬ೝ͠·͢ɻͦͷޙʹ Scala.js ͰͦΕΛॻ͖׵͑Δ͜ͱͰ Scala.js ੡ͷ Figma ϓϥάΠϯΛ࡞੒͠·͢ɻ ͦΕͰ͸·ͣ Figma ͕ఏڙ͍ͯ͠ΔϘΠϥʔϓϨʔτϓϥάΠϯͱಉ͡΋ͷΛ։ൃ͍͖ͯ͠·͢ɻ 4.3 Figma ϓϥάΠϯͷΞʔΩςΫνϟ Figma ͷϓϥάΠϯʹ͸ manifest.json ͱ͍͏ఆٛϑΝΠϧ͕ඞཁͰ͢ɻͦͯ͠ manifest.json Ͱ ͸ϓϥάΠϯͷ UI ͱͳΔ HTML ϑΝΠϧɺϓϥάΠϯͷΤϯτϦϙΠϯτͱͳΔ JavaScript ϑΝ ΠϧΛͦΕͧΕࢦఆ͢Δඞཁ͕͋Γ·͢ɻͭ·ΓɺFigma ϓϥάΠϯ͸ඞͣ͜ͷΑ͏ͳߏ੒ͱͳͬ ͍ͯ·͢ɻ plugin ᵓᴷᴷ index.html ᵓᴷᴷ main.js ᵋᴷᴷ manifest.json ϓϥάΠϯ͕ Figma ্ͰಡΈࠐ·ΕΔͱɺ·ͣϓϥάΠϯͷϧʔτσΟϨΫτϦʹ͋Δ mani- fest.json ͕ಡΈࠐ·Εɺ࣍ʹ manifest.json Ͱࢦఆͨ͠ΤϯτϦϙΠϯτͱͳΔ JavaScriptʢ্هͷ πϦʔͰ͸ main.jsʣ͕ݺͼग़͞Ε·͢ɻΤϯτϦϙΠϯτͱͳΔ JavaScript ಺Ͱ͸ϓϥάΠϯಠࣗ ͷ API Λݺͼग़͢͜ͱ͕Ͱ͖ɺAPI ͷҰͭͰ͋ΔϓϥάΠϯͷ UI Λඳը͢Δ໋ྩΛग़͢͜ͱͰ UI ͕දࣔ͞Ε·͢ɻUI ͸ HTML Ͱ͋Δඞཁ͕͋ΓɺΤϯτϦϙΠϯτͱಉ༷ʹ manifest.json Ͱύε Λࢦఆ͓ͯ͘͠ඞཁ͕͋Γ·͢ʢ্هͷπϦʔͰ͸ index.htmlʣ ɻϓϥάΠϯͷ UI Ͱ͋Δ HTML ͕ඳը͞Εͨ͋ͱ͸ HTML ʹؚ·ΕΔ script λά಺ͷ JavaScript Λ௨ͯ͡ΤϯτϦϙΠϯτͱͳ Δ JavaScript ͱ૒ํ޲ʹϝοηʔδΛ΍ΓͱΓͰ͖ɺΤϯτϦϙΠϯτͱͳΔ JavaScript ଆͰ͸ϓ ϥάΠϯ API Λ௨ͯ͡ Figma ্ͷίϯςϯπΛૢ࡞Ͱ͖·͢ɻ ͦΕͧΕ͕ͲͷΑ͏ͳ໾ׂΛ͍࣋ͬͯΔ͔΍۩ମతʹͲͷΑ͏ͳΞʔΩςΫνϟͰಈ͍͍ͯΔͷ͔ ΛΑΓৄ͘͠஌Δʹ͸ Figma ͕ެ։͍ͯ͠Δ࣍ͷϑΝΠϧ͕෼͔Γ΍͍͢Ͱ͢ɻ https://www.figma.com/file/iJNjjYzeM0DukAHXO92ZWm/Figma-plugin--architectur e-%26-security?type=whiteboard&node-id=0-1&t=MLlTiYdqSO0JxG73-0 4.4 TypeScript ͰϓϥάΠϯ։ൃ ·ͣ͸σΟϨΫτϦΛ༻ҙ͠·͠ΐ͏ɻࠓճ࡞Γ͍ͨ΋ͷ͸ʮϓϥάΠϯͷ΢Οϯυ΢্Ͱࠓݟͯ ͍ΔϑΝΠϧͱಉ͡ϑΝΠϧΛӾཡͰ͖ΔϓϥάΠϯʯͱͳΓ·͢ͷͰ floatviewer ͱ͠·͢ɻ ͦΕͰ͸σΟϨΫτϦΛ੾ͬͯίϛοτ͠·͢ɻ 41
  44. ୈ 4 ষ Scala.js Ͱ Figma ϓϥάΠϯ։ൃೖ໳ 4.4 TypeScript ͰϓϥάΠϯ։ൃ

    mkdir floatviewer cd floatviewer ·ͣ͸.gitignore ϑΝΠϧͰ௥੻ର৅֎ͷϑΝΠϧΛࢦఆ͜͜͠Ͱίϛοτ͠·͢ɻ .gitignore # Node *.log *.log.* node_modules out/ dist/ main.js https://github.com/soudai-s/figma-floatviewer/commit/01590b1d170a3f334e498fd8 8f7bd24416ebbe6e ࣍ʹ manifest.json Λ༻ҙ͠ίϛοτ͠·͢ɻ manifest.json { "name": "floatviewer", "id": "", "api": "1.0.0", "main": "main.js", "capabilities": [], "enableProposedApi": false, "editorType": ["figma"], "ui": "index.html", "networkAccess": { "allowedDomains": ["none"] } } https://github.com/soudai-s/figma-floatviewer/commit/36ca169d2fd85493e6449758 e3db514501bd2bc1 editorType ͸ figma ͱ figjam ΛࢦఆͰ͖ͲͪΒ͔Ұํ͸ඞਢͰ͢ɻfigjam Λࢦఆ͢Δͱ FigJam ͱ͍͏ίϥϘϨʔγϣϯπʔϧʹ΋ϓϥάΠϯΛఏڙͰ͖·͕͢͜͜Ͱ͸ figma ͷΈͱ͠·͢ɻ Figma ͸ϓϥάΠϯ༻ API ͷ TypeScript ͷܕఆٛϑΝΠϧΛ npm ύοέʔδͱͯ͠ެ։ͯ͠ ͍·͢ɻͦ͜Ͱ TypeScript ͱ Figma ϓϥάΠϯͷܕఆٛύοέʔδΛΠϯετʔϧ͠·͠ΐ͏ɻ 42
  45. ୈ 4 ষ Scala.js Ͱ Figma ϓϥάΠϯ։ൃೖ໳ 4.4 TypeScript ͰϓϥάΠϯ։ൃ

    npm i -D typescript @figma/plugin-typings ґଘΛ௥Ճͨ͠Βίϛοτ͠·͢ɻ https://github.com/soudai-s/figma-floatviewer/commit/8068d6c8faf29981c2981661 0e7503ffdadfd90e ࣍ʹ npm εΫϦϓτͱ tsconfig.json Λ௥Ճ͠·͢ɻ package.json { + "scripts": { + "build": "tsc -p tsconfig.json", + "watch": "npm run build -- --watch" + }, "devDependencies": { "@figma/plugin-typings": "^1.92.0", "typescript": "^5.4.5" } } tsconfig.json { "compilerOptions": { "target": "es6", "lib": ["es6"], "strict": true, "typeRoots": ["./node_modules/@types", "./node_modules/@figma"] } } Figma ͷϓϥάΠϯ API ͸ Figma ΞϓϦͷϥϯλΠϜ্ͰͷΈݺͼग़͢͜ͱ͕ՄೳͰɺύοέʔ δͱͯ͠͸ެ։͞Ε͍ͯͳ͍ͨΊɺܕఆٛ͸@types ʹ͸͋Γ·ͤΜɻͦ͜Ͱ Figma ϓϥάΠϯ༻ͷ ܕఆٛύοέʔδΛಡΈࠐΉͨΊ typeRoots Ͱ໌ࣔతʹܕఆٛϑΝΠϧ͕͋ΔσΟϨΫτϦΛ௥Ճ ͍ͯ͠·͢ɻ ͜͜·Ͱ४උ͕Ͱ͖ͨΒίϛοτ͠ɺindex.html ͱ main.ts ͰϓϥάΠϯ༻ͷίʔυΛॻ͍͍ͯ ͖·͢ɻ https://github.com/soudai-s/figma-floatviewer/commit/caeee0df9b79499fa7696cbb 6bc3941c040ec958 ·ͣ͸ index.html Ͱ͢ɻ͜Ε͸্ड़ͷͱ͓Γ Figma Ͱඳը͞ΕΔϓϥάΠϯͷ UI ෦෼ͱͳͬͯ 43
  46. ୈ 4 ষ Scala.js Ͱ Figma ϓϥάΠϯ։ൃೖ໳ 4.4 TypeScript ͰϓϥάΠϯ։ൃ

    ͓ΓɺUI ͷඳըޙ͸͜ͷ HTML ʹ͋ΔεΫϦϓτλά಺ͷ JavaScript Λ௨ͯ͡ΤϯτϦϙΠϯτ Ͱఆٛͨ͠ JavaScript ͱϝοηʔδΛ΍ΓͱΓ͠ɺϓϥάΠϯ༻ͷ API Λݺͼग़͠·͢ɻ͜ͷಈ࡞ Λ֬ೝ͢ΔͨΊख࢝Ίʹʮࣗ࡞ͨ͠ϓϥάΠϯΛදࣔ͠ API Λݺͼग़ͯ͠ϓϥάΠϯΛด͡Δʯͱ ͍͏͜ͱΛ΍ͬͯΈ·͠ΐ͏ɻHTML ͸࣍ͷΑ͏ʹͳΓ·͢ɻ index.html <h2>Rectangle Creator</h2> <button id="cancel">Cancel</button> <script> document.getElementById("cancel").onclick = () => { parent.postMessage({ pluginMessage: { type: "cancel" } }, "*"); }; </script> script λά಺ͷ onclick ΁౉͍ͯ͠ΔίʔϧόοΫؔ਺಺Ͱ࣍ͷΑ͏ͳهड़͕͋Γ·͢ɻ parent.postMessage({ pluginMessage: { type: "cancel" } }, "*") ͜Ε͸ UI ্ʹ͋Δ script λά಺ͰͷΈݺͼग़ͤΔ Figma ϥϯλΠϜͷϒϥ΢β API Ͱ͢ɻ͜Ε ʹΑΓΤϯτϦϙΠϯτͱͳΔ JavaScript ΁ϝοηʔδΛૹΔ͜ͱ͕Ͱ͖·͢ɻ ͯ͞ɺ͜͜Ͱ Figma ϓϥάΠϯΛ࡞Δ্Ͱ஫ҙ͢΂͖఺͕͋Γ·͢ɻ 1 ఺໨͸ʮҰൠతͳ HTML Ͱ͸ͳ͍ʯ͜ͱͰ͢ɻ্ड़ͷ HTML ΛݟΔͱ html λά͔Β࢝·ͬͯ ͍ͳ͍͜ͱ͕෼͔Γ·͢ɻ͜Ε͸ϚʔΫΞοϓ্͸ਖ਼͘͠ͳ͍Α͏ʹݟ͑·͕͢ɺ࣮ࡍʹ͸ Figma ͕ఏڙ͢ΔϓϥάΠϯ༻ͷ΢Οϯυ΢಺ʹ͋Δ html தͷ body λά಺΁ཁૉ͕ࠩ͠ࠐ·Ε·͢ɻҰ ݟ໰୊ͷ͋Δ HTML ʹݟ͑·͕࣮͢͸͜Ε͸໰୊͸ͳ͘ਖ਼ৗͩͱ͍͏͜ͱͰ͢ɻ 2 ఺໨͸ʮϦϯΫΛࢀরͰ͖ͳ͍ʯͱ͍͏͜ͱͰ͢ɻ͜Ε͸Ͳ͏͍͏͜ͱ͔ͱ͍͏ͱ HTML ্Ͱ CSS ΍ JavaScript Λ֎෦ϑΝΠϧͱͯ͠ಡΈࠐΉ͜ͱ͕Ͱ͖ͳ͍ͱ͍͏͜ͱͰ͢ɻͭ·ΓϏϧυ͢ ΔλΠϛϯάͰඞཁͳ΋ͷ͸͢΂ͯ͜ͷ HTML ಺ʹΠϯϥΠϯͰؚΊͳͯ͘͸ͳΓ·ͤΜɻ։ൃ͢ Δ্Ͱϒϥ΢β্Ͱಈ࡞͢Δ JavaScript Λෳ਺ϑΝΠϧͰ؅ཧ͔ͨͬͨ͠Γ໾ׂʹԠͯ͡ϑΝΠϧ Λ෼ׂ͢ΔͷͰ͋Ε͹όϯυϥ౳Λ༻͍ͯϏϧυ࣌ʹ CSS ΍ JavaScript ͕͢΂ؚͯ·Εͨ HTML ͱͯ͠ॻ͖ग़͢ඞཁ͕͋Γ·͢ɻ https://www.figma.com/plugin-docs/libraries-and-bundling/ Ͱ͸࣍ʹΤϯτϦϙΠϯτͱͳΔ JavaScript ϑΝΠϧΛ TypeScript Ͱ༻ҙ͠·͢ɻ main.ts figma.showUI(__html__); 44
  47. ୈ 4 ষ Scala.js Ͱ Figma ϓϥάΠϯ։ൃೖ໳ 4.4 TypeScript ͰϓϥάΠϯ։ൃ

    figma.ui.onmessage = (msg: { type: string }) => { if (msg.type === "cancel") { figma.closePlugin(); } }; ΍͍ͬͯΔ͜ͱ͸γϯϓϧͰɺmanifest.json ͔Βݺͼग़͞Εͨࡍʹ 1 ߦ໨Ͱ UI ͷඳըΛ͍ͯ͠ ·͢ɻ__html__ͱ͍͏ϓϩύςΟʹ͸ manifest.json Ͱࢦఆ͞Εͨύεʹ͋Δ HTML ͕จࣈྻͱ ͯ֨͠ೲ͞Ε͓ͯΓɺFigma ͸ showUI ʹ౉͞ΕͨҾ਺ͷจࣈྻΛ HTML ͱͯ͠ϓϥάΠϯ༻΢Ο ϯυ΢Λ্ཱͪ͛ͯඳը͠·͢ɻ࣍ʹ UI ্ͷ JavaScript ͔ΒϝοηʔδΛड͚औͬͨͱ͖ͷৼΔ ෣͍Λఆٛ͠ϓϥάΠϯΛด͡ΔͨΊͷ API Λݺͼग़͍ͯ͠·͢ɻ ͦΕͰ͸ಈ͔ͯ͠Έ·͠ΐ͏ɻ·ͣ͸Ϗϧυ͠·͢ɻ npm run build ͜ΕͰ main.ts ͕τϥϯεύΠϧ͞Ε main.js ͕Ͱ͖ manifest.json Ͱఆٛͨ͠ΤϯτϦϙΠϯτ ͱ UI ͕༻ҙͰ͖·ͨ͠ɻ ࠓ౓͸͜ΕΛ Figma ͔ΒಡΈࠐΈ·͢ɻ·ͣɺFigma ͷσεΫτοϓΞϓϦ্Ͱ೚ҙͷσβΠϯ ϑΝΠϧΛ։͖·͢ɻ ਤ 4.2: Figma ͷτοϓϖʔδ 45
  48. ୈ 4 ষ Scala.js Ͱ Figma ϓϥάΠϯ։ൃೖ໳ 4.4 TypeScript ͰϓϥάΠϯ։ൃ

    ˞ ը૾Ͱ͸ Editor ݖݶΛ࣋ͬͨϢʔβʔͰૢ࡞͍ͯ͠ΔͨΊσβΠϯϑΝΠϧཝ͔ΒϑΝΠϧΛ ։͍͍ͯ·͕͢ɺEditor ݖݶͷͳ͍ϢʔβʔͰϩάΠϯ͍ͯ͠Δ৔߹͸Լॻ͖͔ΒσβΠϯϑΝΠ ϧΛ։͘͜ͱͰϑΝΠϧΛฤूͰ͖·͢ɻ ࣍ʹɺΞϓϦ಺ϔομͷφϏήʔγϣϯϝχϡʔ͔Β Figma ͷΞΠίϯΛΫϦοΫ͠։͔Εͨϝ χϡʔ͔ΒʮϓϥάΠϯ -> ։ൃ -> ϚχϑΣετ͔ΒϓϥάΠϯΛΠϯϙʔτʯΛબ୒͠·͢ɻ ਤ 4.3: ϓϥάΠϯΛ։͘ ͢Δͱ Finder ͕։͘ͷͰ࡞੒ͨ͠σΟϨΫτϦ಺ʹ͋Δ manifest.json Λࢦఆ͠·͢ɻ͏·͍͘ ͚͹࣍ͷը૾ͷΑ͏ʹϓϥάΠϯͷ΢Οϯυ΢͕ Figma ΞϓϦ্Ͱల։͞ΕΔ͔ͱࢥ͍·͢ɻ 46
  49. ୈ 4 ষ Scala.js Ͱ Figma ϓϥάΠϯ։ൃೖ໳ 4.4 TypeScript ͰϓϥάΠϯ։ൃ

    ਤ 4.4: ϓϥάΠϯͷ΢Οϯυ΢ ͜ΕͰಈ͘΋ͷ͕Ͱ͖·ͨ͠ɻFigma ϓϥάΠϯ͕ͲͷΑ͏ͳྲྀΕͰಈ͍͍ͯΔ͔ͳΜͱͳ͘೺ ѲͰ͖·ͨ͠Ͱ͠ΐ͏͔ɻ͜͜·ͰΛ·ͨίϛοτ͠·͢ɻ https://github.com/soudai-s/figma-floatviewer/commit/eb345d18fc27620ffc016341 ed027a98557400b6 ಈ͘΋ͷ͕Ͱ͖ͨͱ͍ͬͯ΋͜ΕͰ͸ͭ·Βͳ͍Ͱ͢Ͷɻ·ͣ͸ Figma ͕ఏڙ͍ͯ͠ΔϘΠϥʔ ϓϨʔτͱಉ͡Α͏ʹಈ͔ͯ͠Έ·͠ΐ͏ɻindex.html ͱ main.ts Λ࣍ͷΑ͏ʹमਖ਼͠·͢ɻ index.html <h2>Rectangle Creator</h2> +<p>Count: <input id="count" value="5"></p> +<button id="create">Create</button> <button id="cancel">Cancel</button> <script> + document.getElementById("create").onclick = () => { + const textbox = document.getElementById("count"); + const count = parseInt(textbox.value, 10); + parent.postMessage( + { pluginMessage: { type: "create-rectangles", count } }, + "*" + ); + }; 47
  50. ୈ 4 ষ Scala.js Ͱ Figma ϓϥάΠϯ։ൃೖ໳ 4.4 TypeScript ͰϓϥάΠϯ։ൃ

    + document.getElementById("cancel").onclick = () => { parent.postMessage({ pluginMessage: { type: "cancel" } }, "*"); }; </script> main.ts figma.showUI(__html__); -figma.ui.onmessage = (msg: { type: string }) => { +figma.ui.onmessage = (msg: { type: string, count: number }) => { + if (msg.type === "create-rectangles") { + const nodes: SceneNode[] = []; + for (let i = 0; i < msg.count; i++) { + const rect = figma.createRectangle(); + rect.x = i * 150; + rect.fills = [{ type: "SOLID", color: { r: 1, g: 0.5, b: 0 } }]; + figma.currentPage.appendChild(rect); + nodes.push(rect); + } + figma.currentPage.selection = nodes; + figma.viewport.scrollAndZoomIntoView(nodes); + } if (msg.type === "cancel") { figma.closePlugin(); } }; मਖ਼͕Ͱ͖ͨΒ࠶౓Ϗϧυ͠ Figma ্ͰϓϥάΠϯΛϦϩʔυ͠·͢ɻ npm run build ˞ Figma ͸௚લʹಡΈࠐΜͩϓϥάΠϯΛϦϩʔυ͢ΔͨΊͷγϣʔτΧοτ (Cmd+Opt+P) ͕͋ΔͷͰ։ൃத͸͜ΕΛ࢖͏ͱศརͰ͢ɻ ϓϥάΠϯΛϩʔυ͠ Count ͱ͍͏ೖྗϑΟʔϧυͱ Create ͱ͍͏Ϙλϯ͕৽ͨʹ௥Ճ͞ΕϘλ ϯΛԡԼ͢Δͱ Count Ͱࢦఆͨ͠਺͚࢛ͩ֯ΦϒδΣΫτ͕࡞ΒΕ͔ͨͱࢥ͍·͢ɻFigma ϓϥά ΠϯͰ͸͜ͷΑ͏ʹݱࡏ։͍͍ͯΔ Figma ϑΝΠϧʹରͯ͠͞·͟·ͳૢ࡞Λߦ͏͜ͱ͕Ͱ͖·͢ɻ 48
  51. ୈ 4 ষ Scala.js Ͱ Figma ϓϥάΠϯ։ൃೖ໳ 4.5 Figma ϓϥάΠϯΛमਖ਼͢Δ

    ਤ 4.5: ௥Ճ͞Ε࢛ͨ֯ΦϒδΣΫτ ͜͜·ͰΛίϛοτ͠ʢhttps://github.com/soudai-s/figma-floatviewer/commit/a75f c92a428d9aeda944165b47daeeec5f3124e4ʣࠓ౓͸ΦϦδφϧػೳΛ࣋ͭϓϥάΠϯʹमਖ਼͠ ·͢ɻ 4.5 Figma ϓϥάΠϯΛमਖ਼͢Δ ͯ͞ɺࠓճ࡞Γ͍ͨ΋ͷ͸ʮFigma ϓϥάΠϯ্Ͱ Figma ϑΝΠϧΛ։͘ʯϓϥάΠϯͰͨ͠ɻ Figma ϓϥάΠϯ্Ͱ Figma ϑΝΠϧΛ։͘ʹ͸ iframe Ͱ Figma ϑΝΠϧͷ׬શͳ URL Λ౉ͤ ͹࣮ݱͰ͖·͢ɻͦͷͨΊɺ·ͣ Figma ϓϥάΠϯ্Ͱ௨৴ΛڐՄ͢Δ URL Λ manifest.json ΁௥ Ճ͢Δඞཁ͕͋Γ·͢ɻ manifest.json "enableProposedApi": false, "editorType": ["figma"], "ui": "index.html", "networkAccess": { - "allowedDomains": ["none"] + "allowedDomains": ["https://www.figma.com/"] } } ·ͨɺϓϥάΠϯͷ ID ͕ඞཁͱͳΔͨΊద౰ͳ஋Λઃఆ͓͖ͯ͠·͢ɻ manifest.json { "name": "floatviewer", 49
  52. ୈ 4 ষ Scala.js Ͱ Figma ϓϥάΠϯ։ൃೖ໳ 4.5 Figma ϓϥάΠϯΛमਖ਼͢Δ

    + "id": "1", "api": "1.0.0", "main": "main.js", ͯ͞ɺ͜ΕͰ Figma ্Ͱ Figma ϑΝΠϧΛ։͘͜ͱ͸Ͱ͖ΔΑ͏ʹͳΓ·ͨ͠ɻͰ͕͢͜Ε͚ͩ Ͱ͸ϓϥάΠϯ͔Β Figma ϑΝΠϧͷ׬શͳ URL ͸औಘͰ͖·ͤΜɻࣥච࣌఺ʹ͓͍ͯ࣍ͷΑ͏ ͳߏ੒ͱͳ͍ͬͯ·͢ɻ‘https://www.figma.com/file/ϑΝΠϧΩʔ/ϑΝΠϧ໊ ‘ ͜ͷ͏ͪͷϑΝ ΠϧΩʔ͸ެ։͞Ε͍ͯͳ͍ͨΊ manifest.json ͰύʔϛογϣϯΛ௥Ճ͠ϓϥάΠϯ͔ΒΞΫηε Ͱ͖ΔΑ͏ʹ͠·͢ɻ manifest.json "enableProposedApi": false, + "enablePrivatePluginApi": true, "editorType": ["figma"], "ui": "index.html", "networkAccess": { "allowedDomains": ["https://www.figma.com/"] } } ˞ enablePrivatePluginApi Λઃఆͨ͠ϓϥάΠϯ͸ Figma ϓϥοτϑΥʔϜ্Ͱެ։Ͱ͖·ͤ ΜɻϢʔβʔ͕ར༻͢Δʹ͸͜Ε·Ͱ঺հͨ͠खॱͱಉ༷ʹϩʔΧϧϑΝΠϧ্͔ΒΠϯϙʔτ͢Δ ඞཁ͕͋Γ·͢ɻ https://www.figma.com/plugin-docs/manifest/#enableprivatepluginapi ͜ΕͰ Figma ϓϥάΠϯ্Ͱݱࡏ։͍͍ͯΔ Figma ϑΝΠϧΛ։ͨ͘Ίͷ४උ͕Ͱ͖·ͨ͠ɻ࣍ ʹ UI ͔ΒϓϥάΠϯ API Λ࣮ߦ͢ΔͨΊͷ JavaScript ΁౉͢ϝοηʔδΛมߋ͠·͢ɻ index.html <h2>Rectangle Creator</h2> <p>Count: <input id="count" value="5"></p> +<button id="load">Load</button> <button id="create">Create</button> <button id="cancel">Cancel</button> <script> + document.getElementById("load").onclick = () => { + const pluginId = "1"; + parent.postMessage( + { + pluginMessage: { type: "load" }, + pluginId, + }, + "https://www.figma.com", 50
  53. ୈ 4 ষ Scala.js Ͱ Figma ϓϥάΠϯ։ൃೖ໳ 4.5 Figma ϓϥάΠϯΛमਖ਼͢Δ

    + ); + }; + document.getElementById("create").onclick = () => { const textbox = document.getElementById("count"); const count = parseInt(textbox.value, 10); ࠓճͷࠩ෼͸͜Ε·Ͱͷ postMessage ͷҾ਺ͱ͸ 2 ͭҧ͍·͢ɻ͜Ε͸ iframe ΛڐՄ͢Δϓϥά Πϯͷ ID ͱ iframe Ͱඳը͢ΔαΠτͷ URL ͱͳΓ·͢ɻϝοηʔδͷૹ৴࣌ʹ͜ΕΒΛ໌ࣔ͢Δ ͜ͱͰϓϥάΠϯͷ iframe ্Ͱ೚ҙͷαΠτΛඳըͰ͖·͢ɻ ˞ ͳͥ͜ͷΑ͏ʹ͠ͳ͚Ε͹ͳΒͳ͍͔ʹ͍ͭͯ͸࣍ͷυΩϡϝϯτʹهࡌ͕͋Γ·͢ɻ https://www.figma.com/plugin-docs/creating-ui/#non-null-origin-iframes ࠷ޙʹϓϥάΠϯ API ͷݺͼग़͠෦෼Ͱ͢ɻ main.ts + if (msg.type === "load") { + const { width: viewportWidth, height: viewportheight } = + figma.viewport.bounds; + const width = Math.round(viewportWidth / 2); + const height = Math.round(viewportheight / 2); + const url = ‘https://www.figma.com/file/${figma.fileKey}/${figma.root.name}?typˠ e=design&mode=design‘; + figma.ui.postMessage({ url, width, height }); + figma.showUI(‘<script>window.location.href = "${url}";</script>‘, { + width, + height, + }); + } if (msg.type === "cancel") { figma.closePlugin(); } }; ϓϥάΠϯͷ UI Λඳը͢ΔͨΊͷ showUI ؔ਺͸จࣈྻΛड͚औΓͦΕΛ HTML ͱͯ͠ඳը͠ ·͢ɻ͜͜Ͱ script λάΛ౉͠ location.href ʹݱࡏͷ Figma ϑΝΠϧͷ URL Λ౉͢͜ͱͰϓϥά Πϯ্Ͱ Figma ϑΝΠϧΛ։͘͜ͱΛ࣮ݱ͍ͯ͠·͢ɻ ͜ΕͰࠓճ࡞Γ͍ͨ΋ͷ͕Ͱ͖·ͨ͠ɻ࠶౓Ϗϧυ͠৽ͨʹ௥Ճͨ͠ Load ϘλϯΛԡ͢ͱϓϥ άΠϯͷ UI ͕Ϧϩʔυ͞Εɺݱࡏ։͍͍ͯΔϑΝΠϧ͕ϓϥάΠϯͷ UI ্Ͱ΋։͔Ε·͢ʢϓϥ άΠϯͷ UI ΢Οϯυ΢্Ͱ΋φϏήʔγϣϯϝχϡʔ΍αΠυύωϧ͕දࣔ͞Ε·͕͢͜ΕΒ͸ Cmd+\ͷγϣʔτΧοτͰඇදࣔʹͰ͖·͢ʣ ɻ 51
  54. ୈ 4 ষ Scala.js Ͱ Figma ϓϥάΠϯ։ൃೖ໳ 4.6 Scala.js Ͱ

    Figma ϓϥάΠϯΛ࡞ΔͨΊͷ؀ڥߏங ਤ 4.6: Figma in Figma 4.6 Scala.js Ͱ Figma ϓϥάΠϯΛ࡞ΔͨΊͷ؀ڥߏங ͜ΕͰ Figma ϓϥάΠϯΛ։ൃ͢ΔͨΊͷ࠷௿ݶͷ஌ࣝΛಘΒΕͨঢ়ଶɺඞཁʹԠͯ͡υΩϡϝ ϯτΛಡΊ͹։ൃ͕Մೳͳঢ়ଶʹͳ͔ͬͨͱࢥ͍·͢ɻͦ͜Ͱ͔͜͜Β͸ Scala.js Ͱಉ͡Α͏ʹಈ͘ ΋ͷΛ࡞ΔͨΊͷ؀ڥΛ੔͑·͢ɻ ·ͣ͸͜͜·Ͱͷࠩ෼Λίϛοτ͠·͢ɻ https://github.com/soudai-s/figma-floatviewer/commit/7599e33051620e319037f149 dbc0bf4e67fb0df2 ͔ͦͯ͜͜͠Β͸ࠓ࡞ͬͨ΋ͷΛ Scala.js Ͱ࡞Γ௚͠·͢ɻ͜͜·Ͱͷ࡞ۀͰ࡞ͬͨ΋ͷ͸͜Ε͔ Βͷ࡞ۀʹ͓͍ͯෆཁͳͷͰ.gitignore ͱ package.json ͱ package-lock.jsonɺmanifest.json Λ࢒͠ ͢΂ͯ࡟আ͠ɺґଘ΋͢΂ͯΞϯΠϯετʔϧ͠·͢ɻ npm uninstall -D typescript @figma/plugin-typings rm main.js main.ts index.html tsconfig.json τϥϯεύΠϧͷͨΊʹొ࿥ͨ͠εΫϦϓτ΋͢΂ͯ࡟আ͠·͢ɻ 52
  55. ୈ 4 ষ Scala.js Ͱ Figma ϓϥάΠϯ։ൃೖ໳ 4.6 Scala.js Ͱ

    Figma ϓϥάΠϯΛ࡞ΔͨΊͷ؀ڥߏங package.json -{ - "scripts": { - "build": "tsc -p tsconfig.json", - "watch": "npm run build -- --watch" - } -} +{} ͦͯ͠ίϛοτ͠·͢ɻ https://github.com/soudai-s/figma-floatviewer/commit/7cd77d1ec7f06815aeb46467 2b7662ed8503692d ͔͜͜Β Scala.js Ͱ։ൃ͢ΔͨΊͷ؀ڥΛ੔͑·͢ɻ·ͣ͸.gitignore ʹ௥੻ର৅֎ͱ͢ΔϑΝΠ ϧΛ௥Ճ͠ίϛοτ͠·͢ɻ .gitignore out/ dist/ main.js +metals.sbt +project/.bloop/ +project/project/ +project/target/ +target +.cache/ +.history/ +.lib/ +lib_managed/ +src_managed/ +project/boot/ +project/plugins/project/ +.scala_dependencies + +.idea +.idea_modules +.worksheet +*.class +.bloop/ +.bsp/ +.metals/ +.scala-build/ +.vscode/ https://github.com/soudai-s/figma-floatviewer/commit/82891e95f588e73abf305f04 e26d8610e609f7d0 53
  56. ୈ 4 ষ Scala.js Ͱ Figma ϓϥάΠϯ։ൃೖ໳ 4.6 Scala.js Ͱ

    Figma ϓϥάΠϯΛ࡞ΔͨΊͷ؀ڥߏங ࣍ʹ Scala ͷ։ൃʹඞཁͱͳΔ sbt Ͱ͢ɻެࣜαΠτΛࢀߟʹΠϯετʔϧ͠·͠ΐ͏ɻ https://www.scala-sbt.org/1.x/docs/Setup.html ͯ͞ɺ͍Α͍Αຊ൪Ͱ͢ɻFigma ϓϥάΠϯͰ͸͜Ε·Ͱݟ͖ͯͨͱ͓ΓɺUI ෦෼ͱόοΫάϥ ΢ϯυͰ API ݺͼग़͠Λߦ͏෦෼ͷ 2 ͭͷϑΝΠϧ͕ඞཁͰͨ͠ɻ͜ΕΛ Scala.js Ͱ࣮ݱ͢ΔͨΊ ʹ͜͜Ͱ͸ͦΕͧΕผͷ Scala.js ϓϩδΣΫτΛఆٛ͠·͢ɻͦͷͨΊ build.sbt Λ࣍ͷΑ͏ʹهड़ ͠·͠ΐ͏ɻ build.sbt import org.scalajs.linker.interface.ModuleSplitStyle lazy val commonSettings = Seq( scalaVersion := "3.2.2", scalaJSUseMainModuleInitializer := true, ) lazy val ui = project .in(file("ui")) .enablePlugins(ScalaJSPlugin) .settings(commonSettings) .settings( name := "ui", scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.ESModule) .withModuleSplitStyle( ModuleSplitStyle.SmallModulesFor(List("ui"))) }, libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "2.4.0", ) lazy val api = project .in(file("api")) .enablePlugins(ScalaJSPlugin) .settings(commonSettings) .settings( name := "api", scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.ESModule) .withModuleSplitStyle( ModuleSplitStyle.SmallModulesFor(List("api"))) }, ) ͜Ε͸ api ͱ ui ͱ͍͏σΟϨΫτϦΛ༻ҙͦ͠ΕͧΕʹ Scala.js ༻ͷϓϩδΣΫτΛߏங͢Δͨ Ίͷઃఆͱͳ͍ͬͯ·͢ɻ͜ͷ 2 ͭ͸΄ͱΜͲҰॹͰ͕͢ ui ϓϩδΣΫτͰ͸ DOM ΁ͷΞΫηε ͕ඞཁͱͳΔͨΊ scalajs-dom ͱ͍͏ Scala ੡ͷϥΠϒϥϦΛ௥Ճ͍ͯ͠·͢ɻ ࣍ʹ project ͱ͍͏σΟϨΫτϦΛ࡞ΓͦͷԼʹϏϧυ͢ΔͨΊͷ sbt ͷόʔδϣϯͱ࢖༻͢Δϓ ϥάΠϯΛએݴ͠·͢ɻ 54
  57. ୈ 4 ষ Scala.js Ͱ Figma ϓϥάΠϯ։ൃೖ໳ 4.6 Scala.js Ͱ

    Figma ϓϥάΠϯΛ࡞ΔͨΊͷ؀ڥߏங project/build.properties sbt.version=1.9.9 ˞ ͜ͷϑΝΠϧ͸ࣗ਎Ͱ࡞੒͠ͳͯ͘΋ build.sbt Λ࡞੒ͨ͠ޙʹ sbt ͰϏϧυΛ͢ΔͱࣗಈͰ࡞ ੒͞Ε·͢ɻόʔδϣϯ͸ࣗ͝਎ͰΠϯετʔϧͨ͠ sbt ͷόʔδϣϯͱ߹Θ͍ͤͯͩ͘͞ɻ Scala ͷϓϥάΠϯ͸ project/plugins.sbt ͱ͍͏ϑΝΠϧʹهड़͠·͢ɻ project/plugins.sbt addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.16.0") ͦͷޙɺapi ͱ ui ͷσΟϨΫτϦʹͦΕͧΕ Scala ϓϩδΣΫτͷΤϯτϦϙΠϯτͱͳΔϑΝΠ ϧΛ࡞੒͠·͢ɻ api/src/main/scala/floatviewer/api/Main.scala package floatviewer.api object API { def main(args: Array[String]): Unit = { println("Hello World! in API") } } ui/src/main/scala/floatviewer/ui/Main.scala package floatviewer.ui object UI { def main(args: Array[String]): Unit = { println("Hello World! in UI") } } ͜ΕͰ·ͣಈ͘ Scala.js ϓϩδΣΫτ͕Ͱ͖·ͨ͠ɻͬͦ͘͞ಈ͔ͯ͠Έ·͠ΐ͏ɻλʔϛφϧͰ ‘sbt‘Λ࣮ߦͨ͠ޙʹ fastLinkJS Λ࣮ߦ͠·͢ɻ 55
  58. ୈ 4 ষ Scala.js Ͱ Figma ϓϥάΠϯ։ൃೖ໳ 4.6 Scala.js Ͱ

    Figma ϓϥάΠϯΛ࡞ΔͨΊͷ؀ڥߏங sbt:fv> fastLinkJS ... [info] compiling 1 Scala source to /Users/sou/products/fv/api/target/scala-3.2.2/claˠ sses ... [info] compiling 1 Scala source to /Users/sou/products/fv/ui/target/scala-3.2.2/clasˠ ses ... [info] Fast optimizing /Users/sou/products/fv/api/target/scala-3.2.2/api-fastopt [info] Fast optimizing /Users/sou/products/fv/ui/target/scala-3.2.2/ui-fastopt [success] Total time: 10 s, completed 2024/04/29 14:29:24 ແ ࣄ Scala.js Ͱ JavaScript Λ Ϗ ϧ υ Ͱ ͖ · ͠ ͨ ɻfastLinkJS Ͱ ੜ ੒ ͠ ͨ ੒ Ռ ෺ ͸ {PROJECT_ROOT}/target/scala-{SCALA_VERSION}/{PROJECT_NAME}-fastopt/main.js ʹग़ྗ͞Ε·͢ʢͨͱ͑͹ api Ͱ Scala 3.2.2 ͷ৔߹͸ api/target/scala-3.2.2/api-fastopt/main.js Ͱ͢ʣ ɻ͜ΕͰͻͱ·ͣίϛοτ͠·͢ɻ https://github.com/soudai-s/figma-floatviewer/commit/3ce327f9895a61e16e0de52d b9e05d6046e6c16d ࣍ʹ Scala.js Ͱੜ੒͞ΕΔ JavaScript ΛϏϧυ͢ΔͨΊʹ Vite ΛΠϯετʔϧ͠ઃఆΛ͍͖ͯ͠ ·͢ɻ npm i -D vite vite-plugin-singlefile @scala-js/vite-plugin-scalajs ґଘΛΠϯετʔϧͨ͠ΒϏϧυ͢ΔͨΊͷ npm εΫϦϓτΛ௥Ճ͠ίϛοτ͠·͢ɻ package.json + "scripts": { + "dev:ui": "vite --mode ui", + "dev:api": "vite --mode api", + "build": "npm run build:ui && npm run build:api", + "build:ui": "vite build --mode ui", + "build:api": "vite build --mode api", + "preview:ui": "vite preview --mode ui", + "preview:api": "vite preview --mode api" + }, https://github.com/soudai-s/figma-floatviewer/commit/f44bcecacb2b35cc56849ac3 0c80ea72c0e9a8eb UI ෦෼͸ JavaScript Ͱ͸ͳ͘ HTML ͕ඞཁͱͳΔͨΊϑΝΠϧΛ௥Ճ͠·͢ɻScala.js ͸σ ϑΥϧτͩͱϧʔτσΟϨΫτϦ্ͷ index.html ͕ಡΈࠐ·Ε·͢ɻͦ͜Ͱ࣍ͷΑ͏ʹઃఆ͠·͢ɻ 56
  59. ୈ 4 ষ Scala.js Ͱ Figma ϓϥάΠϯ։ൃೖ໳ 4.6 Scala.js Ͱ

    Figma ϓϥάΠϯΛ࡞ΔͨΊͷ؀ڥߏங index.html <div id="app"></div> <script type="module" src="/ui/src/main/javascript/index.js"></script> script λάͰࢦఆͨ͠ύεʹ͋ΔϑΝΠϧ͸࣍ͷΑ͏ʹ͠·͢ɻ ui/src/main/javascript/index.js import "scalajs:main.js"; ͦͯ͠ Vite ͷίϯϑΟάϑΝΠϧΛ࣍ͷΑ͏ʹࢦఆ͠·͢ɻ vite.config.mjs import { defineConfig } from "vite"; import { viteSingleFile } from "vite-plugin-singlefile"; import scalaJSPlugin from "@scala-js/vite-plugin-scalajs"; export default defineConfig(({ mode }) => { if (mode == "ui") { return { plugins: [ viteSingleFile(), scalaJSPlugin({ projectID: "ui", }), ], build: { cssCodeSplit: false, assetsInlineLimit: 100000000, outDir: "./dist/ui", }, }; } if (mode == "api") { return { plugins: [ scalaJSPlugin({ projectID: "api", }), ], publicDir: "./api/target/scala-3.2.2/api-opt", build: { outDir: "./dist/api", }, }; 57
  60. ୈ 4 ষ Scala.js Ͱ Figma ϓϥάΠϯ։ൃೖ໳ 4.6 Scala.js Ͱ

    Figma ϓϥάΠϯΛ࡞ΔͨΊͷ؀ڥߏங } return {}; }); Vite ͕ެ։͍ͯ͠Δ defineConfig ͱ͍͏ؔ਺͸ mode ͱ͍͏Ҿ਺ΛऔΔ͜ͱ͕Ͱ͖ɺ͜Ε͸Ϗϧ υ࣮ߦ࣌ͷ mode Φϓγϣϯͷ஋ͱͳ͍ͬͯ·͢ɻͦͷͨΊ͜ͷΑ͏ʹ mode Ͱ৚݅෼ذΛߦ͏͜ ͱͰҟͳΔઃఆΛద༻Ͱ͖·͢ɻui ͷํͰ͸͢΂ͯͷ੒Ռ෺͕Ұͭͷ html ϑΝΠϧʹؚ·ΕΔΑ͏ vite-plugin-singlefile ͕ެ։͍ͯ͠Δ viteSingleFile ͱ͍͏ؔ਺Λ Vite ϓϥάΠϯͱͯ͠ݺͼग़͠ ͍ͯ·͢ɻ·ͨɺVite Ͱ Scala.js ΛϏϧυ͢ΔͨΊͷϓϥάΠϯͰ͋Δ vite-plugin-scalajs ͕ެ։ ͍ͯ͠Δ scalaJSPlugin ؔ਺Ͱ͸ projectID Λ౉͢͜ͱͰಛఆͷϓϩδΣΫτͷΈΛϏϧυʹؚΊΔ Α͏ʹ͍ͯ͠·͢ɻ͜ΕʹΑΓ ui ϓϩδΣΫτ಺ͷ੒Ռ෺ͷΈΛόϯυϧͨ͠Ұͭͷ HTML ϑΝ ΠϧΛग़ྗ͢Δઃఆͱͳ͍ͬͯ·͢ɻapi ͷํͰ͸ scalaJSPlugin Ͱ api ϓϩδΣΫτͷΈΛϏϧυ ͢ΔΑ͏ʹ͍ͯ͠Δ΄͔ɺpublicDir ͱͯ͠ Scala.js ͕ੜ੒ͨ͠ JavaScript ͷग़ྗઌσΟϨΫτϦΛ ࢦఆ͍ͯ͠·͢ɻ͜ΕʹΑΓ౰֘σΟϨΫτϦ಺ͷϑΝΠϧ͸ͦͷ·· build.outDir Ͱࢦఆͨ͠ઌ ʹ഑ஔ͞Ε·͢ɻ Ͱ͸͜ΕͰ build ͯ͠Έ·͠ΐ͏ɻ npm run build ίϚϯυΛͨͨ͘ͱ ui ͱ api ͷͦΕͧΕ͕ Vite ܦ༝Ͱಈ͍ͨ sbt ͰϏϧυ͞Ε·͢ɻϏϧυࡁΈ ϑΝΠϧͷग़ྗઌ͕มΘ͍ͬͯΔͨΊ͜Εʹ߹Θͤͯ manifest.json ΋ॻ͖׵͑·͢ɻ manifest.json { "name": "floatviewer", "id": "1", "api": "1.0.0", - "main": "main.js", + "main": "dist/api/main.js", "capabilities": [], "enableProposedApi": false, "enablePrivatePluginApi": true, "editorType": ["figma"], - "ui": "index.html", + "ui": "dist/ui/index.html", "networkAccess": { "allowedDomains": ["https://www.figma.com/"] } } 58
  61. ୈ 4 ষ Scala.js Ͱ Figma ϓϥάΠϯ։ൃೖ໳ 4.7 Scala.js Ͱ

    Figma ϓϥάΠϯΛ࡞Δ ͜ΕͰ Figma ϓϥάΠϯΛ Scala.js Ͱ։ൃͰ͖Δ؀ڥ͕੔͍·ͨ͠ɻ͜͜·ͰͷมߋΛίϛοτ ͠·͢ɻ https://github.com/soudai-s/figma-floatviewer/commit/091df5f5ec3b0e1c28a77240 6ae94b8037282c1b 4.7 Scala.js Ͱ Figma ϓϥάΠϯΛ࡞Δ ͦΕͰ͸ Figma ϓϥάΠϯͱͯ͠ಈ͘΋ͷΛ Scala.js Ͱ࡞͍͖ͬͯ·͢ɻ·ͣ TypeScript ͱಉ ༷ʹʮϓϥάΠϯΛด͡Δʯ෦෼Λ࡞Γ·͢ɻUI ෦෼͸࣍ͷΑ͏ʹͳΓ·͢ɻ ui/src/main/scala/floatviewer/ui/Main.scala package floatviewer.ui +import scala.scalajs.js +import org.scalajs.dom +import floatviewer.ui.figmaplugin.FigmaWindow + object UI { def main(args: Array[String]): Unit = { println("Hello World! in UI") + dom.document.querySelector("#app").innerHTML = s""" + <div> + <h2>Float Viewer</h2> + <button id="close">Close</button> + </div> + """ + val closeDom = dom.document.getElementById("close") + closeDom.addEventListener("click", { (e: dom.Event) => + val onCloseMessageValue = js.Dynamic.literal( + "pluginMessage" -> js.Dynamic.literal("type" -> "close") + ) + FigmaWindow.postMessage(onCloseMessageValue, "*") + }) } } ௥Ճͨࠩ͠෼Λ෼ղͯ͠Έ͍͖ͯ·͢ɻ dom.document.querySelector("#app").innerHTML = s""" <div> <h2>Float Viewer</h2> <button id="close">Close</button> 59
  62. ୈ 4 ষ Scala.js Ͱ Figma ϓϥάΠϯ։ൃೖ໳ 4.7 Scala.js Ͱ

    Figma ϓϥάΠϯΛ࡞Δ </div> """ ͜ͷ෦෼͸ൺֱత෼͔Γ΍͍͔͢ͱࢥ͍·͢ɻScala.js ͱ Scala.js ༻ͷϥΠϒϥϦͰ͋Δ scala- js-dom ʹΑΓ dom ͷ innerHTML ϓϩύςΟ΁จࣈྻΛ౉ͯ͠ HTML Λੜ੒͍ͯ͠·͢ɻ࣍ʹ innerHTML Ͱੜ੒ͨ͠Ϙλϯ͕ΫϦοΫ͞Εͨ࣌ͷॲཧΛݟ͍͖ͯ·͢ɻ val closeDom = dom.document.getElementById("close") closeDom.addEventListener("click", { (e: dom.Event) => val onCloseMessageValue = js.Dynamic.literal( "pluginMessage" -> js.Dynamic.literal("type" -> "close") ) FigmaWindow.postMessage(onCloseMessageValue, "*") }) close Ϙλϯʹର͠ addEventListener ϝιουͰΫϦοΫΠϕϯτͷΠϕϯτϋϯυϥΛొ࿥͠ ·͢ɻରԠ͢Δ js ͸࣍ͷΑ͏ʹͳ͍ͬͯ·ͨ͠ɻ document.getElementById("close").onclick = () => { parent.postMessage({ pluginMessage: { type: "close" } }, "*"); }; parent ͸ Figma ϓϥάΠϯͷ UI ༻ API ʹఆٛ͞Ε͍ͯΔΦϒδΣΫτͰ͢ɻparent ΦϒδΣ Ϋτ͕͍࣋ͬͯΔ postMessage ؔ਺ʹΦϒδΣΫτΛ౉͍ͯ͠·ͨ͠ɻ຋ͬͯ Scala.js ͷίʔυΛ ݟΔͱ onCloseMessageValue Λ js.Dynamic.literal ͱ͍͏ϝιουΛݺͼग़ͯ͠ JavaScript Φϒ δΣΫτΛੜ੒͍ͯ͠·͢ɻͦͯ͠ੜ੒ͨ͠ΦϒδΣΫτΛ౉͍ͯ͠Δ postMessage ؔ਺ͷϨγʔ όͱͳ͍ͬͯΔ parent ͷΑ͏ʹϥϯλΠϜͷάϩʔόϧʹ͢Ͱʹఆٛ͞Ε͍ͯΔΦϒδΣΫτ΁ Scala.js ͔ΒΞΫηε͢ΔͨΊʹ͜͜Ͱ͸ผϑΝΠϧ͔Β FigmaWindow ΛΠϯϙʔτ͍ͯ͠·͢ɻ ͦΕͰ͸ FigmaWindow Λఆٛ͠·͠ΐ͏ɻFigma ϓϥάΠϯ༻ͷύοέʔδͱͯ͠σΟϨΫτϦ Λ੾ͬͯϑΝΠϧΛ௥Ճ͠·͢ɻ ui/src/main/scala/floatviewer/ui/figmaplugin/Globals.scala package floatviewer.ui.figmaplugin import scala.scalajs.js import js.annotation._ 60
  63. ୈ 4 ষ Scala.js Ͱ Figma ϓϥάΠϯ։ൃೖ໳ 4.7 Scala.js Ͱ

    Figma ϓϥάΠϯΛ࡞Δ @js.native @JSGlobal("parent") object FigmaWindow extends js.Object { def postMessage(message: js.Any, targetOrigin: String): Unit = js.native } ͜ΕͰ Scala.js ͰϥϯλΠϜʹ͋ΔάϩʔόϧͳΦϒδΣΫτ͸@JSGlobal ΞϊςʔγϣϯͰΞ ΫηεͰ͖·͢ɻ͜͜Ͱ͸ ‘parent‘ΦϒδΣΫτΛ Scala ্Ͱ FigmaWindow ͱ͍͏ΦϒδΣΫτ ͱͯ͠ఆ͍ٛͯ͠·͢ɻ·ͨɺ‘parent‘ΦϒδΣΫτ͕࣋ͭؔ਺ postMessage ΋ JavaScript ଆͰఆ ٛ͞Ε͍ͯΔ΋ͷͱࣔͨ͢Ί postMessage ϝιου͕ݺͼग़͢ॲཧΛ js.native ͱ͠·͢ɻ https://www.scala-js.org/api/scalajs-library/1.0.0-M1/scala/scalajs/js/annot ation/JSGlobal.html ࣍ʹ API ෦෼Ͱ͢ɻUI ಉ༷ʹ·ͣ Main.scala Λॻ͖׵͑·͢ɻ api/src/main/scala/floatviewer/api/Main.scala package floatviewer.api +import floatviewer.api.figmaplugin.Globals + object API { def main(args: Array[String]): Unit = { println("Hello World! in API") + Globals.figma.showUI(Globals.__html__) + Globals.figma.ui.onmessage = { msg => + close() + } + } + + def close(): Unit = { + Globals.figma.closePlugin() } } main ϝιου಺ͷॲཧ͸ JavaScript ͷ࣍ͷ෦෼ʹ֘౰͠·͢ɻ figma.showUI(__html__); figma.ui.onmessage = (msg: { type: string }) => { if (msg.type === "cancel") { figma.closePlugin(); } }; 61
  64. ୈ 4 ষ Scala.js Ͱ Figma ϓϥάΠϯ։ൃೖ໳ 4.7 Scala.js Ͱ

    Figma ϓϥάΠϯΛ࡞Δ API ΦϒδΣΫτ͔ΒಡΈࠐΜͰ͍Δύοέʔδ͸࣍ͷΑ͏ʹͳΓ·͢ɻ api/src/main/scala/floatviewer/api/figmaplugin/Globals.scala api/src/main/scala/floatviewer/api/figmaplugin/Globals.scala package floatviewer.api.figmaplugin import scala.scalajs.js import js.annotation._ @js.native @JSGlobalScope object Globals extends js.Object { val figma: PluginAPI = js.native val ‘__html__‘: String = js.native } ͜͜Ͱಛච͢΂͖͸@JSGlobalScope ͱ͍͏ΞϊςʔγϣϯͰ͢ɻ͜Ε͸ϥϯλΠϜ্Ͱάϩʔ όϧΦϒδΣΫτʹొ࿥͞Ε͍ͯΔϝιου΍ϓϩύςΟΛఆٛͰ͖·͢ɻ͜͜Ͱ͸ figma ͱ͍͏ϓ ϩύςΟʹ PluginAPI ͕ɺ‘__html__‘ͱ͍͏ϓϩύςΟʹจࣈྻ͕֨ೲ͞Ε͍ͯΔ͜ͱΛදͯ͠ ͍·͢ɻ https://www.scala-js.org/api/scalajs-library/1.0.0-M1/scala/scalajs/js/annot ation/JSGlobalScope.html PluginAPI ෦෼͸͞ΒʹผϑΝΠϧʹ෼͚͍ͯ·͢ɻPluginAPI ͷ࣮૷͸࣍ͷΑ͏ʹͳΓ·͢ɻ api/src/main/scala/floatviewer/api/figmaplugin/API.scala package floatviewer.api.figmaplugin import scala.scalajs.js import js.annotation._ @js.native trait PluginAPI extends js.Object { def fileKey: String | Unit = js.native def closePlugin(message: String = ???): Unit = js.native def showUI(html: String, options: ShowUIOptions = ???): Unit = js.native def ui: UIAPI = js.native def root: DocumentNode = js.native } @js.native trait ShowUIOptions extends js.Object { var title: String = js.native var width: Double = js.native var height: Double = js.native 62
  65. ୈ 4 ষ Scala.js Ͱ Figma ϓϥάΠϯ։ൃೖ໳ 4.7 Scala.js Ͱ

    Figma ϓϥάΠϯΛ࡞Δ } @js.native trait UIAPI extends js.Object { var onmessage: MessageEventHandler | Unit = js.native } type MessageEventHandler = js.Function1[MessageEvent, Unit] @js.native trait MessageEvent extends js.Object { var ‘type‘: String = js.native } @js.native trait DocumentNode extends BaseNodeMixin { def ‘type‘: String = js.native } @js.native trait BaseNodeMixin extends js.Object { def id: String = js.native var name: String = js.native } Figma ϓϥάΠϯͷϥϯλΠϜʹ͸΋ͬͱଟ͘ͷ͞·͟·ͳϓϩύςΟ΍ΦϒδΣΫτɺϝιο υ͕༻ҙ͞Ε͍ͯ·͕͢͜͜Ͱ͸ඞཁ࠷খݶʹׂѪ͍ͯ͠·͢ɻ ͯ͞ɺ͜ΕͰʮด͡Δʯ͚ͩͷϓϥάΠϯΛ Scala.js Ͱ࡞Γ௚͢͜ͱ͕Ͱ͖·ͨ͠ɻϏϧυͨ͋͠ ͱʹ Figma ্ͰϦϩʔυ࣮ͯ͠ࡍʹಈ͔֬͘ೝͯ͠Έ·͠ΐ͏ɻ npm run build 63
  66. ୈ 4 ষ Scala.js Ͱ Figma ϓϥάΠϯ։ൃೖ໳ 4.7 Scala.js Ͱ

    Figma ϓϥάΠϯΛ࡞Δ ਤ 4.7: Scala.js ੡ͷ Figma ϓϥάΠϯ ແࣄಈ͍ͨ͜ͱ͕֬ೝͰ͖·ͨ͠ɻ Ͱ͸͜͜·ͰΛίϛοτ͠·͢ɻ https://github.com/soudai-s/figma-floatviewer/commit/4c00486df355ec01361a8d79 feac930012d695bf ࠷ޙʹ Scala.js Ͱ Figma ϑΝΠϧΛϓϥάΠϯ্ʹඳը͢ΔػೳΛ࣮૷͠·͢ɻͦ͜Ͱ·ͣ͸ UI ʹϩʔυϘλϯΛ௥Ճ͠ΠϕϯτϦεφΛొ࿥͠·͢ɻ ui/src/main/scala/floatviewer/ui/Main.scala ui/src/main/scala/floatviewer/ui/Main.scala dom.document.querySelector("#app").innerHTML = s""" <div> <h2>Float Viewer</h2> + <button id="load">Load</button> <button id="close">Close</button> </div> """ ... FigmaWindow.postMessage(onCloseMessageValue, "*") }) + val loadDom = dom.document.getElementById("load") 64
  67. ୈ 4 ষ Scala.js Ͱ Figma ϓϥάΠϯ։ൃೖ໳ 4.7 Scala.js Ͱ

    Figma ϓϥάΠϯΛ࡞Δ + loadDom.addEventListener("click", { (e: dom.Event) => + val onLoadMessageValue = js.Dynamic.literal( + "pluginMessage" -> js.Dynamic.literal("type" -> "load"), + "pluginId" -> "1" + ) + FigmaWindow.postMessage(onLoadMessageValue, "https://www.figma.com") + }) } } API ଆͰ͸ΠϕϯτϦεφܦ༝ͰૹΒΕΔϝοηʔδʹରԠͨ͠ॲཧΛ௥Ճ͠·͢ɻ api/src/main/scala/floatviewer/api/Main.scala println("Hello World! in API") Globals.figma.showUI(Globals.__html__) Globals.figma.ui.onmessage = { msg => - close() + handleMessage(msg.‘type‘) } } + def handleMessage(messageType: String): Unit = { + messageType match { + case "load" => load() + case "close" => close() + } + } + + def load(): Unit = { + val url = s"https://www.figma.com/file/${Globals.figma.fileKey}/${Globals.figmaˠ .root.name}?type=design&mode=design" + Globals.figma.showUI(s"<script>window.location.href = \"${url}\";</script>") + } + def close(): Unit = { Globals.figma.closePlugin() } ࠷ޙʹϏϧυ͠ಈ࡞ݕূΛ͠·͠ΐ͏ɻ npm run build Figma ϓϥάΠϯ΢Οϯυ΢ͷϦαΠζ෦෼ΛׂѪ͍ͯ͠ΔͨΊ΢Οϯυ΢͸খ͍͞··Ͱ͕͢ɺ ແࣄʹ Figma ϓϥάΠϯ্Ͱ Figma ϑΝΠϧΛඳը͢Δ͜ͱ͕֬ೝͰ͖·ͨ͠ɻ 65
  68. ୈ 4 ষ Scala.js Ͱ Figma ϓϥάΠϯ։ൃೖ໳ 4.8 ऴΘΓʹ ਤ

    4.8: ׬੒ͨ͠ Scala.js ੡ͷ Figma ϓϥάΠϯ ࠷ޙͷίϛοτ͸ͪ͜ΒͰ͢ɻ https://github.com/soudai-s/figma-floatviewer/commit/4baeccadc91a6da48283b7ab eb92bc0a1342bfd7 4.8 ऴΘΓʹ ຊষͰ͸ Figma ͱϓϥάΠϯͷ঺հʹ࢝·Γ࣮ࡍʹ TypeScript ͱ Scala.js ͷͧΕͧΕͰ࣮ࡍʹ ಈ͘΋ͷΛ࡞͍͖ͬͯ·ͨ͠ɻචऀ͸ۀ຿Ͱ͸ωΠςΟϒΞϓϦͱαʔόαΠυͷߏஙΛओ຿ͱͯ͠ ͓Γ࢖༻͢Δݴޠ͸ SwiftɺKotlinɺRuby ͕ϝΠϯͰͳ͔ͳ͔ Scala ΍ TypeScript Λ࢖͏৔໘͕͋ Γ·ͤΜɻ͔ͩΒͦ͜ϓϥΠϕʔτͳ։ൃͰ͸ීஈ࢖͍ͬͯͳ͍ٕज़Λ࢖ͬͯΈ͍ͨͱৗ೔ࠒ͔Β ࢥ͍ͬͯ·͢ɻFigma ϓϥάΠϯͱ͍͏ςʔϚΛબΜͩͷ΋͜Ε͕ཧ༝ͷҰͭͰ͢ɻͱ͍͏ͷ΋ϓ ϥάΠϯ͸ͦͷϓϥοτϑΥʔϜͷػೳΛ׆͔͢͜ͱͰθϩ͔Β Web αʔϏε΍ωΠςΟϒΞϓϦ Λ࡞Δ৔߹ͱൺ΂খͯ͘͞΋Ձ஋ͷ͋Δ΋ͷΛ࡞Γ΍͍͢ͱࢥ͍ͬͯ·͢ɻখ͍͞΋ͷͰ͋Ε͹ීஈ ͸৮Βͳ͍͚Ͳ޷͖ͳݴޠʹ௅ઓ͢Δ͜ͱ΋༰қͰ͢ɻͦͯ͠͞·͟·ͳϓϥοτϑΥʔϜ͕͋Δத Ͱࢲ͕޷͖ͳαʔϏεͰ͋Δ Figma ͱɺ޷͖ͳݴޠͰ͋Δ Scala ͰԿ͔࡞ΕͨΒ͓΋͠Ζ͍ͳͱࢥ ͍௅ઓͯ͠Έ·ͨ͠ɻScala Λීஈ৮͍ͬͯΔΘ͚Ͱ͸ͳ͍ͨΊ͋·Γྑ͍ίʔυͰ͸ͳ͍͔΋͠Ε ·ͤΜ͕ɺ͜͜·ͰಡΜͰ͍͚ͨͩΕ͹ Scala ʹ׳ΕͨํͰ͋Ε͹͜ͷίʔυΛվળ͠ΑΓྑ͍ϓϥ άΠϯ։ൃ͕Ͱ͖ΔͷͰ͸ͳ͍Ͱ͠ΐ͏͔ʢͨͱ͑͹ UI ෦෼Λ Laminar*1) ʹม͑ͯಈతͳ UI Λ *1 https://laminar.dev 66
  69. ୈ 4 ষ Scala.js Ͱ Figma ϓϥάΠϯ։ൃೖ໳ 4.8 ऴΘΓʹ ఏڙ͢Δͷ΋͓΋͠Ζ͍ͱࢥ͍·͢ʣ

    ɻ ຊষΛಡΜͰ 1 ਓͰ΋ Figma ϓϥάΠϯ։ൃΛ΍ͬͯΈΑ͏ɺ·ͨ͸ Scala.js ͰԿ͔։ൃΛͯ͠ ΈΑ͏ͱࢥ͍͚ͬͯͨͩͨͳΒ޾͍Ͱ͢ɻ 67
  70. ୈ 5 ষ TIPSTAR ΞʔΩςΫνϟͷ୳ࡧ ॳΊ·ͯ͠ɺ23 ೥౓৽ଔͱͯ͠ೖࣾͨ͠एদͰ͢ɻX ͷΞΧ΢ϯτ͸ɺ@Jaksho500 ͱ͍͏Ϣʔ βʔ໊Ͱ͢ɻຊষͰ͸ɺ

    ࢲ͕೔ʑ։ൃʹܞΘ͍ͬͯΔ ʮTIPSTARʯ ͱ͍͏αʔϏεͷΞʔΩςΫνϟ ਤ͔Βશମ૾Λ೺Ѳ͠ɺԿ఺͔ϐϯϙΠϯτͰয఺Λ౰ͯ͘͠ΈΛಡΈղ͍͍ͯ͜͏ͱࢥ͍·͢ɻ 5.1 TIPSTAR ͱ͸ ·ͣ؆୯ʹαʔϏεͷ঺հΛ͍ͤͯͩ͘͞͞ɻ ʮTIPSTAR*1ʯ͸ɺ365 ೔഑৴͞ΕΔϨʔεө૾ͱ ڝྠɾΦʔτϨʔεɾPIST6 ͷωοτ౤ථΛɺجຊແྉͰָ͠Ή͜ͱ͕Ͱ͖ΔαʔϏεͰ͢ɻ ࣮ࡍͷ͓ۚΛౌ͚ͣͱ΋ɺαʔϏε಺ͷʮϝμϧʯͱ͍͏௨՟Λ༻͍ͯجຊແྉͰར༻͢Δମݧ͕ Ͱ͖·͢ɻॳ৺ऀͰ΋ଞϢʔβʔͷౌ͚ํΛϚωͯ͠౤ථͰ͖Δʮͷ͔ͬΓϕοτʯͱ͍͏ػೳ΍ɺ ଞͷϢʔβʔͷ౤ථ͕తதͨ͠ࡍʹتͼΛ෼͔ͪ߹͑ΔλΠϜϥΠϯػೳʮχϡʔεϑΟʔυʯ ɺ஥ ͕ਂ·ͬͨϢʔβʔؒͰίϛϡχέʔγϣϯΛऔΔͨΊͷʮϑϨϯυίϛϡχςΟʯͳͲެӦڝٕ΁ ͷ౤ථʷίϛϡχέʔγϣϯΛ࣠ͱͨ͠αʔϏεʹͳ͍ͬͯ·͢ɻ 5.2 TIPSTAR ͷΞʔΩςΫνϟ ͬͦ͘͞ TIPSTAR ͷΞʔΩςΫνϟΛݟ͍͖ͯ·͠ΐ͏ɻ *1 TIPSTARʢςΟοϓελʔʣެࣜαϙʔταΠτ: https://about.tipstar.com 69
  71. ୈ 5 ষ TIPSTAR ΞʔΩςΫνϟͷ୳ࡧ 5.2 TIPSTAR ͷΞʔΩςΫνϟ ਤ 5.1:

    TIPSTAR ͷΞʔΩςΫνϟ Ұ෦ຊষͷ಺༰ʹ߹Θͤͯ؆ૉԽ͍ͯ͠·͕͢ɺ্ਤ͕େ·͔ͳʮTIPSTARʯͷΞʔΩςΫνϟ ʹͳΓ·͢ɻ·ͣɺ؆୯ʹਤதͷ֤ཁૉʹ͍ͭͯઆ໌Λ͠·͢ɻ • TIPSTAR APIɿαʔϏεΛࢧ͑ΔϝΠϯ API • ਤӈ্෦෼ɿ౤ථՄೳͳެӦڝٕͷϨʔε৘ใ΍Ϩʔε݁Ռ৘ใΛऔಘ͢Δϓϩηε܈ • ਤࠨԼ෦෼ɿDB ܈ • ਤӈԼ෦෼ɿඇಉظతͳॲཧΛߦ͏ϓϩηε܈ ΫϥΠΞϯτ͔ΒͷϦΫΤετ͸ɺϩʔυόϥϯαΛհͯ͠ TIPSTAR API ʹಧ͖·͢ɻAPI ͸ ଟ͘ͷ֎෦γεςϜͱ΍ΓͱΓΛߦ͍ɺඞཁʹԠ֤ͯ͡छڝٕαʔό΍ܾࡁγεςϜɺͦͷ΄͔ͷ αʔόͱ௨৴͠·͢ɻAPI Ҏ֎ʹ໿ 50 ͷҟͳΔػೳΛ࣋ͭඇಉظϓϩηε͕ɺGoogle Kubernetes Engine(ҎԼɺGKE ͱུ͢)*2্Ͱ࣮ߦ͞Ε͍ͯ·͢ɻ͞ΒʹɺCloud Functions*3΍ Cloud Run*4 ͱ͍ͬͨ΄͔ͷΫϥ΢υαʔϏε্Ͱ΋ϓϩηε͕Քಇ͍ͯ͠·͢ɻTIPSTAR Λࢧ͑ΔγεςϜͷ શମ૾͕গ͠ݟ͖͑ͯͨͰ͠ΐ͏͔ʁ ຊষͰ͸ɺϚελσʔλͱඇಉظॲཧͷ 2 ͭʹয఺Λ౰ͯɺͦΕͧΕͷৄࡉʹ͍ͭͯղઆ͠·͢ɻ • Ϛελσʔλʹ͍ͭͯ • ඇಉظॲཧʹ͍ͭͯ *2 Google Kubernetes Engine: https://cloud.google.com/kubernetes-engine *3 Cloud Functions: https://cloud.google.com/functions *4 Cloud Run: https://cloud.google.com/run 70
  72. ୈ 5 ষ TIPSTAR ΞʔΩςΫνϟͷ୳ࡧ 5.3 Ϛελσʔλʹ͍ͭͯ 5.3 Ϛελσʔλʹ͍ͭͯ ຊઅͰয఺Λ౰ͯΔϚελσʔλʹؔΘΔͷ͸ɺઌ΄Ͳ঺հͨ͠ΞʔΩςΫνϟͷ͏ͪҎԼͷ෦෼

    ʹͳΓ·͢ɻ ਤ 5.2: ϚελσʔλʹؔΘΔ෦෼ Ϛελσʔλͱ͸ Ϛελσʔλͱ͸αʔϏε্ͷϢʔβʔͷߦಈʹΑͬͯมԽ͠ͳ͍σʔλɺαʔϏεΛӡ༻ͯ͠ ্͍͘Ͱඞཁͳσʔληοτͷ͜ͱΛࢦ͠·͢ɻTIPSTAR Ͱ͸ɺ ʮڝྠͰ 3 ࿈୯Λ 3 ճతதͰใु ήοτʯ΍ʮ30 ೔ϩάΠϯͰใुήοτʯͳͲଟछଟ༷ͳϛογϣϯ͕ଘࡏ͓ͯ͠Γɺ͜ΕΒϛο γϣϯʹؔ͢Δ৘ใ͕Ϛελσʔλͱͯ͠ѻΘΕ͍ͯ·͢ɻ΄͔ʹ΋ϛογϣϯҎ֎ͷػೳʹؔ͢Δ ઃఆ΍ɺ௨஌ͷςϯϓϨʔτɺநબܥࢪࡦͷ֬཰ͳͲ͕Ϛελσʔλʹؚ·Ε͍ͯ·͢ɻ 71
  73. ୈ 5 ষ TIPSTAR ΞʔΩςΫνϟͷ୳ࡧ 5.3 Ϛελσʔλʹ͍ͭͯ Ϛελσʔλͷӡ༻ํ๏ʹ͍ͭͯ TIPSTAR Ͱ͸ɺΞϓϦέʔγϣϯͷίʔυΛ؅ཧ͢ΔϦϙδτϦ*5ͱ͸ผʹϚελσʔλ༻ͷϦ

    ϙδτϦ͕ଘࡏ͍ͯ͠·͢ʢҎԼɺσʔλϦϙδτϦͱུ͢ʣ ɻσʔλϦϙδτϦͰӡ༻͢Δ͜ͱͰɺ ਅͬઌʹڍ͛ΒΕΔϝϦοτͱͯ͠όʔδϣχϯά͕ՄೳʹͳΔ͜ͱͰ͢ɻ·ͨɺͦͷ΄͔ͷϝϦο τͱͯ͠ҎԼͷ఺͕ڍ͛ΒΕ·͢ɻ • σʔλͷઃఆϛεʹؾ෇͘͘͠ΈΛࣗಈԽͰ͖Δ • ϒϥϯνΛར༻͢Δ͜ͱͰɺӡ༻ͷϑϩʔΛߏஙͰ͖Δ ͦΕͧΕͷ఺ʹ͍ͭͯ঺հ͍͖ͯ͠·͢ɻ σʔλͷઃఆϛεʹؾ෇͘͘͠ΈΛࣗಈԽͰ͖Δ σʔλϦϙδτϦ಺ʹɺϚελσʔλΛಡΈग़͠ɺઃఆϛε͕ͳ͍͔νΣοΫ͢ΔπʔϧΛ࡞੒͠ ·͢ɻͦΕΛ GitHub Actions*6ͳͲͷ CI ্Ͱ࣮ߦ͢Δ Workflow Λ࡞੒͢Δ͜ͱʹΑΓɺఆৗత ʹσʔλͷઃఆ࿙ΕΛ๷͙͜ͱ͕ՄೳͰ͢ɻ ʮσʔλͷܻ͕ 1 ͭҧͬͨʯ ɺ ʮσΠϦʔͰ੾ΓସΘΔػೳͷϚελσʔλ͕ 1 ೔෼ͳ͔ͬͨʯͳͲ ͪΐͬͱͨ͠ਓҝతϛε͕େن໛ͳଛࣦΛ΋ͨΒͨ͠ΓɺϢʔβʔͷମݧΛ્֐ͯ͠͠·͍·͢ɻͨ ͱ͑͹ɺϚελσʔλʹ͸θϩ͔Β 100 ·Ͱͷ஋ͷΈઃఆՄೳͰ͋ͬͨΓɺ೔෇ࢦఆ෦෼ʹ͸࿈ଓੑ ͕ٻΊΒΕΔ΋ͷ͕ଘࡏ͠·͢ɻ͜ΕΒͷཁ݅ΛϏδωενʔϜͱͱ΋ʹ֬ೝ͠ɺࣗಈνΣοΫπʔ ϧΛ։ൃ͢Δ͜ͱ͕ɺػೳɾαʔϏεͷ҆ఆՔಇʹෆՄܽͰ͢ɻػೳ࣮૷͚ͩͰͳ͘ɺӡ༻·Ͱߟྀ ͠πʔϧ࡞੒·ͰΛػೳ։ൃͷ׬ྃཁ݅ͱͯ͠ఆٛ͢Δ͜ͱͰ૊৫จԽͱͯ͠औΓ૊ΜͰ͍͚Δͱײ ͍ͯ͡·͢ɻ·ͨɺ৽͘͠࡞੒ͨ͠πʔϧ͸ɺ༰қʹطଘͷࣗಈνΣοΫͷ Workflow ʹ૊ΈࠐΉ͜ ͱ͕Ͱ͖·͢ɻ͜ͷ఺ʹ͓͍ͯ΋ɺσʔλϦϙδτϦͷར༻͸༏Ε͍ͯΔͱײ͍ͯ͡·͢ɻ ϒϥϯνΛར༻͢Δ͜ͱͰɺӡ༻ͷϑϩʔΛߏஙͰ͖Δ σʔλϦϙδτϦ಺Ͱɺෳ਺ͷϒϥϯνΛ༻͍Δ͜ͱͰҎԼͷΑ͏ͳϑϩʔΛ૊Ή͜ͱ͕Ͱ͖ ·͢ɻ 1. ৽͘͠௥Ճ͢ΔϚελσʔλ͸ A ϒϥϯνʹ޲͚ͯ PR Λ࡞੒͢ΔʢϚʔδʹ͸ଞϝϯόʔ ͔ΒͷϨϏϡʔ͕ඞཁʣ 2. A ϒϥϯνʹϚʔδ͞ΕΔͱ։ൃ؀ڥσʔλΛ൓ө͢Δ Workflow ͕࣮ߦ͞Εɺ൓ө͞ΕΔ 3. ։ൃ؀ڥͰಈ࡞֬ೝ͕Ͱ͖ͨΒ A ϒϥϯν͔Β B ϒϥϯνʹಉ༷ͷࠩ෼ͷ PR Λ࡞੒͢Δ 4. B ϒϥϯνʹϚʔδ͞ΕΔͱຊ൪؀ڥ΁σʔλΛ൓ө͢Δ Workflow ͕࣮ߦ͞ΕΔ σʔλϦϙδτϦͷϒϥϯνӡ༻ͱ GitHub Actions ͱ͍ͬͨ CI Λ૊Έ߹ΘͤΔ͜ͱͰɺ ʮϚε λσʔλͷϨϏϡʔˠ։ൃ؀ڥ΁ͷ൓өˠ࣮ػͰͷݕূˠຊ൪؀ڥ΁ͷ൓өʯͱ͍ͬͨϑϩʔΛ૊Ή *5 https://docs.github.com/ja/repositories *6 https://github.co.jp/features/actions 72
  74. ୈ 5 ষ TIPSTAR ΞʔΩςΫνϟͷ୳ࡧ 5.3 Ϛελσʔλʹ͍ͭͯ ͜ͱ͕Ͱ͖·͢ɻ ʢຊࢽୈ 1

    ষͰ΋ɺGitHub*7Λ׆༻ͨ͠Ϛελσʔλͷӡ༻ʹ͍ͭͯهࡌ͞Ε͍ͯ ·͢ɻ ʣ ΞϓϦέʔγϣϯ্ͰϚελσʔλΛѻ͏ࡍͷ޻෉ ΞϓϦέʔγϣϯ্ͰϚελσʔλΛѻ͏ࡍͷ޻෉ʹ͍ͭͯͰ͢ɻϚελσʔλͷಛ௃ͱͯ͠ҎԼ ͕ڍ͛ΒΕΔͱࢥ͍ͬͯ·͢ɻ • ࡟আ͞ΕΔ͜ͱ͸΄ͱΜͲͳ͘ɺ૿͑ଓ͚Δ܏޲͕͋Δ • ϚελσʔλΛར༻͢Δ API ͕Α͹ΕΔճ਺͕ଟ͍ ࡟আ͞ΕΔ͜ͱ͸΄ͱΜͲͳ͘ɺ૿͑ଓ͚Δ܏޲͕͋Δ ϛογϣϯͷϚελσʔλΛྫʹͱΔͱɺ։࠵ظؒΛա͍͗ͯͯ΋ୡ੒ࡁΈͷϛογϣϯΛදࣔ͢ ΔҰཡը໘Ͱͷදࣔ༻ʹඞཁͩͬͨΓͱݹ͍σʔλΛ࡟আ͢Δ͜ͱ͸͋Γ·ͤΜɻϛογϣϯΛྫʹ ڍ͛·͕ͨ͠ɺͦΕҎ֎ͷσʔλ΋ DB ͔Β෺ཧ࡟আ͢Δέʔε͸ػೳͷഇࢭҎ֎Ͱ͸΄ͱΜͲͳ͍ ͷͰ͸ͳ͍͔ͱࢲ͸ײ͍ͯ͡·͢ɻ ϚελσʔλΛར༻͢Δ API ͕Α͹ΕΔճ਺͕ଟ͍ Ϛελσʔλ͕ؔ༩͢Δػೳ͸αʔϏεͱͯ͠ॏཁ౓ͷߴ͍ػೳɾར༻͢ΔϢʔβʔ͕ଟ͍ػೳͰ ͋Δ܏޲͕͋Δͱײ͍ͯ͡·͢ɻͦͷͨΊϚελσʔλΛॲཧͷதͰऔಘ͢Δ API ͕ΫϥΠΞϯτ ͔ΒΑ͹ΕΔճ਺͸΄͔ API ʹൺ΂ͯ΋ଟ͘ͳΔͷͰ͸ͳ͍͔ͱࢥ͍·͢ɻ ෛՙΛܰݮ͢ΔͨΊʹ ্هͰ͋͛ͨϚελσʔλͷಛ௃Λ౿·͑ΔͱɺϚελσʔλ͸σʔλ͕૿͍͑ͯ͘ҰํͰςʔ ϒϧʹΑͬͯ͸Ϩίʔυ਺͕਺ઍߦଘࡏ͢Δ΋ͷ΋͋ΓɺॲཧͷதͰඞཁʹͳͬͯ͘Δػձ΋ଟ͍ Ͱ͢ɻ ෛՙΛܰݮ͢ΔͨΊϚελσʔλΛ௚઀ DB ͔ΒಡΈग़͢ػձ͕࠷௿ݶʹͳΔΑ͏ͳ࣮૷Λͯ͠ ͍·͢ɻίʔυ্ͰҰ౓औಘͨ͠ϚελσʔλΛΩϟογϡͱͯ͠ΠϯϝϞϦʹอ͍࣋ͯ͠·͢ɻอ ࣋͢Δ࣌ؒ͸ࢦఆ͓ͯ͠Βͣɺϓϩηε͕Քಇ͍ͯ͠Δؒ͸อ࣋͞Εଓ͚ΔΑ͏ʹͳ͍ͬͯ·͢ɻΠ ϯϝϞϦͰอ͍࣋ͯ͠ΔΩϟογϡΛߋ৽͢ΔλΠϛϯάͷ൑அ͸ɺDB ʹϚελσʔλͷߋ৽ཤྺ Λ؅ཧ͢Δςʔϒϧ͕ଘࡏ͓ͯ͠Γɺͦͷςʔϒϧͷ࠷৽ͷϨίʔυʢ࠷ޙʹϚελσʔληοτ͕ ߋ৽͞Εͨ࣌ࠁʣΛ਺ඵ͓͖ʹνΣοΫ͠ɺΩϟογϡ͕ݹ͍͔֬ೝ͍ͯ͠·͢ɻ ͜ͷΑ͏ʹͯ͠ TIPSTAR Ͱ͸ϚελσʔλΛ DB ͔Β௚઀औಘ͢Δճ਺Λ࠷௿ݶʹ཈͍͑ͯ ·͢ɻ *7 https://github.co.jp 73
  75. ୈ 5 ষ TIPSTAR ΞʔΩςΫνϟͷ୳ࡧ 5.4 ඇಉظॲཧʹ͍ͭͯ 5.4 ඇಉظॲཧʹ͍ͭͯ ຊষͷୈ

    2 અʹͯΞʔΩςΫνϟͷશମ૾Λ঺հͨ͠ࡍʹɺ ʮ50 લޙͷ໾ׂΛ࣋ͭඇಉظతͳॲཧ ͕ GKE ্ͰՔಇ͍ͯ͠Δʯͱ঺հͨ͠ͱࢥ͍·͢ɻTIPSTAR Ͱ͸ɺGKE ΍ Cloud Functions ্ ʹΫϥΠΞϯτ͔ΒͷϦΫΤετॲཧͱ͸ผͰඇಉظతʹ࣮ߦ͞ΕΔϓϩηε͕Քಇ͍ͯ͠·͢ɻ ඇಉظͰ࣮ߦ͞ΕΔॲཧʹ͸ҎԼͷΑ͏ͳಛ௃͕͋Δͱߟ͍͑ͯ·͢ʢࢲ͕ඇಉظॲཧͱ࣮ͯ͠૷ ͢Δ৔߹͸ҎԼͷΑ͏ͳ؍఺Ͱ൑அ͍ͯ͠·͢ʣ ɻ • ࣌ؒΛཁ͢ΔͨΊϦΞϧλΠϜͷฦ౴͕೉͍͠ɾϦΞϧλΠϜੑΛٻΊΒΕ͍ͯͳ͍ • Τϥʔ͕ൃੜͨ͠৔߹ʹ࠶࣮ߦͰ͖ΔΑ͏ʹ͍ͨ͠ • ఆظతʹ࣮ߦ͍ͨ͠ ͜ΕΒͷಛ௃Λ࣋ͭॲཧ͸ɺΫϥΠΞϯτͱ௚઀΍ΓͱΓ͢ΔʢԠ౴଎౓͕ٻΊΒΕΔʣAPI ͔Β ੾Γ཭͠ɺඇಉظॲཧͱ࣮ͯ͠૷͠·͢ɻ۩ମྫΛڍ͛Δͱ TIPSTAR Ͱ͸ɺ ʮϨʔεऴྃޙͷं݊ ͷ݁Ռ൓өॲཧʯ ɾ ʮϥϯΩϯάूܭʯ ɾ ʮ͓͕ۚབྷΉॲཧͷࣦഊ࣌ʹσʔλΛϩʔϧόοΫ͢Δॲཧʯ ͳͲ͕ඇಉظॲཧͱ࣮ͯ͠૷͞Ε͍ͯ·͢ɻ தͰ΋ɺຊઅͰ͸ GKE ্ͰՔಇ͢ΔඇಉظॲཧʢҎԼɺWorker ͱུ͢ʣʹয఺Λ౰͍͖ͯͯ· ͢ɻWorker ʹؔ͢Δ෦෼͸ɺઌ΄Ͳ঺հͨ͠ΞʔΩςΫνϟͷ͏ͪҎԼͷ෦෼ʹͳΓ·͢ɻ ਤ 5.3: ඇಉظॲཧ Worker ͷॲཧ͕࣮ߦ͞ΕΔ·ͰͷྲྀΕ Worker ͸ɺओʹҎԼͷ̎ͭͷྲྀΕͰॲཧ͕࢝·Γ·͢ɻ 74
  76. ୈ 5 ষ TIPSTAR ΞʔΩςΫνϟͷ୳ࡧ 5.4 ඇಉظॲཧʹ͍ͭͯ • TIPSTAR API

    ˠ Worker ͷ৔߹ • Worker ˠ Worker ͷ৔߹ TIPSTAR API ˠ Worker ͷ৔߹ ϢʔβʔͷϦΫΤετ͕τϦΨʹͳΔ৔߹ɺ ʮTIPSTAR APIʯ͔ΒϝοηʔδϯάαʔϏε ʢPub/Sub*8ʣʹϝοηʔδ͕ύϒϦογϡ͞ΕɺWorker ͕ϝοηʔδΛαϒεΫϥΠϒ͢Δ͜ͱ Ͱॲཧ͕։࢝͠·͢ɻTIPSTAR Ͱ͸ɺϢʔβʔ͕ୀձͨ͠ࡍͷσʔλ࡟আɺχϡʔεϑΟʔυʹܝ ࡌ͢Δ౤ߘͷొ࿥ͳͲͷॲཧ͕֘౰͠·͢ɻ Worker ˠ Worker ͷ৔߹ Worker ͷதʹ͸ɺ΄͔ͷ Worker Λܦ༝࣮ͯ͠ߦ͢Δ΋ͷ͕͋Γ·͢ɻࠓճ͸ʮ௥ՃϘʔφεΛ ෇༩͢Δ Worker(ҎԼɺ௥ՃϘʔφε Worker ͱུ͢)ʯΛऔΓ্͛·͢ɻ TIPSTAR ʹ͸ɺຊষ๯಄Ͱ঺հͨ͠ʮͷ͔ͬΓϕοτʯͱʮϚϧνϓϨΠʯͱ͍͏ػೳ͕ଘࡏ͠ ·͢ɻ͜ΕΒͷػೳͰ͸ɺं͕݊తதͨ͠৔߹ɺ௨ৗͷ෷͍໭͠ʹՃ͑ͯ௥ՃϘʔφε͕෇༩͞Ε ·͢ɻ ௥ՃϘʔφεΛ෇༩͢Δʹ͸ɺҎԼͷ৚͕݅ඞཁͰ͢ɻ·ͣɺϨʔε͕ऴ͍ྃͯ͠Δ͜ͱɻ࣍ʹɺ Ϣʔβʔͷं݊σʔλʹ݁Ռ͕൓ө͞Ε͍ͯΔ͜ͱͰ͢ɻͦ͜Ͱ௥ՃϘʔφεΛ෇༩͢Δॲཧ͸ɺ Ϩʔε݁ՌΛं݊ʹ൓ө͢Δ Worker(ҎԼɺ݁Ռ൓ө Worker ͱུ͢) ͱ͸ผͷ Worker ͱ࣮ͯ͠૷ ͞Ε͓ͯΓɺ݁Ռ൓ө Worker ͔Β Pub/Sub Λհͯ͠ॲཧ͕։࢝͞Ε·͢ɻ ݁Ռ൓ө Workerɾ௥ՃϘʔφε Worker ͷҰ࿈ͷॲཧͷྲྀΕΛҎԼʹࣔ͠ɺͦΕͧΕʹ͍ͭͯઆ ໌͠·͢ɻ ਤ 5.4: ݁Ռ൓ө Workerɾ௥ՃϘʔφε Worker ͷҰ࿈ͷॲཧͷྲྀΕ Ϩʔε৘ใऔΓࠐΈ Worker Ϩʔε৘ใऔΓࠐΈ Worker ͸ɺ֎෦ͷαʔό͔Β 30 ඵִؒͰϨʔε৘ใΛऔಘ͠·͢ɻϨʔε ৘ใʹ͸ɺϨʔεΛ։࠵͢Δ৔΍ग़৔બखɺϨʔεͷग़૸ঢ়گɺϨʔε݁ՌͳͲؚ͕·Ε·͢ɻલճ *8 https://cloud.google.com/pubsub 75
  77. ୈ 5 ষ TIPSTAR ΞʔΩςΫνϟͷ୳ࡧ 5.5 ऴΘΓʹ औಘͨ͠৘ใͱൺֱͯ͠ɺมԽ͕͋ͬͨϨʔεͷ ID Λϝοηʔδͱͯ͠

    Pub/Sub ʹύϒϦογϡ ͠·͢ɻ ʢࠓճ͸ʮϨʔε৘ใʹมԽ͕ൃੜ͢Δ=Ϩʔε͕ऴྃͯ݁͠Ռ͕ग़ͨʯͱߟ͍͍͑ͯͨͩ ͯେৎ෉Ͱ͢ɻ ʣ ର৅Ϣʔβʔબఆॲཧ Ϩʔε৘ใऔΓࠐΈ Worker ͕ Pub/Sub ΁ύϒϦογϡͨ͜͠ͱΛड͚ɺCloud Functions ্ͷ ର৅Ϣʔβʔબఆॲཧ͕Քಇ࢝͠Ί·͢ɻ͜ͷॲཧͰ͸ɺର৅ͷϨʔεʹϕοτ͍ͯ͠ΔϢʔβʔ (Ϩʔε݁ՌΛ൓ө͢Δର৅ͱͳΔϢʔβʔ) ͷ ID Λऔಘ͠ɺPub/Sub ʹϨʔε ID ͱϢʔβʔ ID ΛηοτͰύϒϦογϡ͠·͢ɻ ݁Ռ൓ө Worker ݁Ռ൓ө Worker Ͱ͸ɺϝοηʔδΛ 1 ݅ͣͭॲཧ͍͖ͯ͠·͢ɻϨʔε ID ͱϢʔβʔ ID ͔Β Ϣʔβʔͷं݊σʔλͱϨʔε݁ՌΛऔಘ͠ɺ෷͍໭ֹۚ͠ͷࢉग़͠ DB ʹ൓ө͍͖ͯ͠·͢ɻ݁Ռ ൓ө Worker Ͱ͸ɺϨʔε݁Ռͷ൓өҎ֎ʹϛογϣϯͷ൓өॲཧ΋ߦ͍ͬͯ·͢ɻ݁Ռͷ൓өॲཧ ͕׬ྃͨ͠ޙɺϢʔβʔ͕ͷ͔ͬΓϕοτ΍ϚϧνϓϨΠΛར༻͍ͯ͠Δ৔߹ɺ௥ՃϘʔφεࢉग़ॲ ཧΛߦ͏ͨΊ Pub/Sub ʹύϒϦογϡ͠·͢ɻ ௥ՃϘʔφε Worker ௥ՃϘʔφε Worker ͸ɺલड़ͨ͠௨Γʮͷ͔ͬΓϕοτʯ ʮϚϧνϓϨΠʯΛར༻͍ͯͯ͠తத ͨ͠Ϣʔβʔʹ௥ՃϘʔφεΛ෇༩͢ΔॲཧΛߦ͍·͢ɻ ඇಉظॲཧʹؔ͢Δ·ͱΊ ্ड़ͨ͠Α͏ʹɺෳ਺ͷ WorkerɺTIPSTAR APIɺCloud Functions Λ૊Έ߹ΘͤͯҰ࿈ͷॲཧ Λ࣮ݱ͍ͯ͠·͢ɻϝοηʔδϯάαʔϏεΛհ͢Δ͜ͱͷϝϦοτͷҰͭ͸ɺॲཧ͕ࣦഊͨ͠৔߹ ʹࣗಈతʹϦτϥΠ͞ΕΔ఺Ͱ͢ɻ΋ࣦ͠ഊ͕܁Γฦ͞Εͨ৔߹ɺର৅ͷϝοηʔδ͸σουϨλʔ τϐοΫ*9ʹҠ͞ΕɺWorker ͸΄͔ͷϝοηʔδͷॲཧΛଓ͚·͢ɻ͜ΕʹΑΓɺಛఆͷϝοηʔ δͷࣦഊ͕γεςϜશମʹେ͖ͳӨڹΛٴ΅͢͜ͱ͸͋Γ·ͤΜɻσουϨλʔτϐοΫʹͨ·ͬͨ ϝοηʔδ͸ɺ։ൃऀ͕ݪҼௐࠪͱ෮چରԠΛߦ͍·͢ɻ ͜ͷߏ੒ʹΑͬͯɺ੹຿Λ໌֬ʹ෼཭Ͱ͖·͢ɻ·ͨɺϝοηʔδϯάαʔϏεΛ௨֤ͯ͡ॲཧΛ ඇಉظతʹߦ͏͜ͱͰɺγεςϜ͸ΑΓεέʔϥϒϧ͔ͭޮ཰తʹಈ࡞͠·͢ɻ͜ͷΞϓϩʔν͸ɺ γεςϜશମͷ৴པੑͱ֦ுੑΛߴΊΔॿ͚ͱͳ͍ͬͯΔͱײ͍ͯ͡·͢ɻ 5.5 ऴΘΓʹ ຊষͰ͸ TIPSTAR ͷΞʔΩςΫνϟ͔Βશମ૾Λ೺Ѳ͠ɺ ʮϚελσʔλʯͱʮඇಉظॲཧʯʹ য఺Λ౰ͯͯ঺հ͍͖ͤͯͨͩ͞·ͨ͠ɻ঺հͨ͠಺༰͸ɺαʔϏεͷυϝΠϯɾಛੑ໰Θͣݕ౼͕ *9 σουϨλʔτϐοΫ: https://cloud.google.com/pubsub/docs/handling-failures?hl=ja#dead_letter_topic 76
  78. ஶऀ঺հ ߐാ ୓࠸ (ୈ 1 ষ୲౰, GitHub: MokkeMeguru, X: @MeguruMokke)

    ιʔγϟϧϕοςΟϯάࣄۀຊ෦։ൃࣨͱ͍͏ͱ͜ΖͰ TIPSTAR Λ͸͡Ίͱ͢Διʔγϟ ϧϕοςΟϯάʹ·ͭΘΔαʔϏεશൠͷӡ༻ɾ։ൃΛࢧԉ͍ͯ͠·͢ɻNext.js ͷপʹਁ͔ Γͳ͕Β Swift ͱٔΕΑ͏ͱ͍ͯ͠·͢ɻ๭ॳԻϛΫͷԻήʔָ͍͠Ͱ͢Ͷɺ͍͘ΒͰ΋՝ۚ Ͱ͖·͢ɻ দݪ ৴஧ (ୈ 2 ষ୲౰) ॴଐ͸ϞϯεταʔόνʔϜͰ Ruby ΍ Go Λॻ͍ͯΔɻϓϩάϥϛϯά͕޷͖Ͱɺͨ·ʹ ਪ͠ݴޠͷ Haskell Ͱ༡ΜͩΓ͍ͯ͠Δɻ ౻Ҫ ྎ (ୈ 3 ষ୲౰ɺDiscord: aki_chang) 22 ೥౓৽ଔೖࣾͷΫϥΠΞϯτΤϯδχΞͰ͢ɻ։ൃຊ෦ͰϓϩάϥϛϯάֶशΧϦΩϡϥ Ϝͱڭࡐιϑτ΢ΣΞͷ։ൃʹऔΓ૊ΜͰ͍·͢ɻաڈʹ͸ʮֶߍڭҭࢧԉͷٕज़ղ๤ͱ 21 ଔΤϯδχΞ͕ڭࡐ։ൃͰशಘͨ͠ΤϯδχΞϦϯάεΩϧʯhttps://www.youtube.com/ watch?v=_8pki1sahZw ͱ͍͏୊Ͱొஃ͠ɺڭҭࢧԉͷ஌ݟΛήʔϜ։ൃ΁స༻͢ΔࢼΈʹͭ ͍͓ͯ࿩͠·ͨ͠ɻ࠷ۙ͸εέϘʔʹເதͰ͢ɻ ࠤʑా ૖େ (ୈ 4 ষ୲౰, GitHub: soudai-s, X: @soudai_s) ΈͯͶϓϩμΫτ։ൃ෦ͰࣸਅϓϦϯτྖҬΛத৺ʹωΠςΟϒΞϓϦ (iOSɺAndroid) ͱ αʔό (Ruby on Rails) ͷ։ൃΛ͍ͯ͠·͢ɻεϊϘʔ͕޷͖ɻ एদ ৎਓ (ୈ 5 ষ୲౰, GitHub: take-2405 X: @Jaksho500) 23 ೥౓৽ଔͱͯ͠ೖࣾ͠ɺTIPSTAR ͱ͍͏αʔϏεͷ։ൃɾӡ༻Λߦ͍ͬͯ·͢ɻ࠷ۙӳ ޠͷษڧʹ೤ΛೖΕ͍ͯ·͢ɻ Ճ౻ ರ࢙ (σβΠφʔ) 2015 ೥ೖࣾɻ৽نࣄۀ΍σβΠϯνʔϜϦʔμʔͳͲͷܦݧΛܦͯɺݱࡏ͸։ൃຊ෦ʹͯί ϛϡχέʔγϣϯσβΠϯྖҬΛϝΠϯʹ༷ʑͳ͜ͱʹܞΘΒ͍͍͍ͤͯͨͩͯ·͢ɻ3D ϓ Ϧϯλʔͱ২෺ʹғ·Εͨੜ׆Λ͍ͯ͠·͢ɻ ӹࢁ ઍय़ (੍࡞ਐߦ, X: @charlielog_ggg) ։ൃຊ෦ ͨΜΆΆࣨ DevRel άϧʔϓͰɺओʹ࠾༻ϚʔέςΟϯάྖҬͷۀ຿Λ୲౰͍ͯ͠ ·͢ɻ࠷ۙ͸௨৴੍େֶͰΏΔΏΔͱษڧ͍ͯ͠·͢ɻ2022 ೥͔Βژ౎ʹॅΜͰ·͢ɻ 79
  79. MIXI TECH NOTE #11 2024 ೥ 5 ݄ 25 ೔ɹॳ൛ୈ

    1 ࡮ɹൃߦ ஶɹऀ גࣜձࣾ MIXI ༗ࢤ ൃߦॴ גࣜձࣾ MIXI ҹ࡮ॴ ೔ޫاը ɹ ˜ MIXI