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

“モダン”ウェブアプリケーション 〜アメブロ5ヶ年計画〜

Kazunari Hara
September 24, 2017

“モダン”ウェブアプリケーション 〜アメブロ5ヶ年計画〜

HTML5 Conference 2017.9.24

1. “モダン”ウェブアプリケーション ∼アメブロ5ヶ年計画∼ HTML5 Conference 2017.9.24 原 一成 @herablog
2. アメーバブログ 2004年開始のブログサービス 芸能人の利用が多い 芸能人以外とそれ以外のPVは半々
3. 2015 バックエンドシステムの刷新 2017 2016 Isomorphic JavaScript AMP https Code-splitting Node.js v8 React 16 PWA
4. 5年かけて“モダン”な状態にする
5. “モダン”であること エコシステムとつながっていること
6. “モダン”であること エコシステムからの恩恵 エンジニアの流動性 ! "
7. エコシステムからの恩恵 最新技術を取り込みやすい 世界中のエンジニアと協力できる !
8. エンジニアの流動性 若者の参加を促しやすい 属人化を防ぎやすい "
9. SSRとSPA ServerClient Database Services Presentation Logic Data Services DAO Rendering
10. SSRとSPA ServerClient Database Services Presentation Logic Data Services DAO
11. SSRとSPA 初期表示がはやい ページ遷移は遅め ページ遷移がはやい 初期表示は遅め SSR SPA
12. https://goo.gl/KS3L5k Rendering on Google Search
13. Isomorphic JavaScript https://goo.gl/yLz93N
14. Isomorphic JavaScript https://goo.gl/yLz93N
15. Universal JavaScript https://goo.gl/xakXA1
16. Isomorphic JavaScript 2016年、アメブロのシステム刷新 ServerClient Database Services Presentation Logic Data Services DAO Browser
17. Isomorphic JavaScript Pros DataとUIの責務を分離できる UIは同じエンジニアがコントロールできる 初期・ランタイム両方の表示速度を改善できる
18. Isomorphic JavaScript 複雑さが少し増す (特にSSRとSPA切れ目) Node.jsの運用実績が必要 (あると良い) 全アプリに向かない (参照系に向く) Cons
19. Initial Speed with SSR and SPA 1.4x Faster
20. Runtime Speed with SSR and SPA 1.2x Faster
21. Isomorphic JavaScript 2016年、アメブロのシステム刷新 ServerClient Database Services Presentation Logic Data Services DAO Browser
22. React (Rendering) The Parts of Isomorphic JavaScript コンポーネント指向 サーバー、クライアント両方のレンダリング ライフサイクルメソッドでIsomorphicにしやすい
23. export class SampleComponent extends React.Component { componentWillMount() {} // サーバ用。constructorを使うのが推奨 componentDidMount() {} // クライアント用。初回の表示時 componentDidUpdate() {} // クライアント用。SPAの遷移時に使われることが多い componentWillUnmount() {} // クライアント用。タイマー処理を消すなど render() {} //サーバサイド、クライアントサイド両方 } React (Rendering) The Parts of Isomorphic JavaScript
24. Redux (State container) The Parts of Isomorphic JavaScript Fluxベースの状態管理のためのフレームワーク 関数ベースでIsomorphicしやすい 副作用など曖昧なところは公式サイトの推奨に合わせる
25. https://goo.gl/esQRsK How can I represent “side effects” such as AJAX calls?
26. Fetch API, Fetchr (Fetching) The Parts of Isomorphic JavaScript データの取得は Fetch API (bitinn/node-fetch, github/fetch) Fetchr (yahoo/fetchr) でIsomoprhic化
27. React Router (Routing) The Parts of Isomorphic JavaScript 各pathごとに対応するコンポーネントを呼び出し サーバーでは、エラー・リダイレクト処理 クライアントでは、ヒストリー・スクロール位置管理 などが必要
28. Node.js (Web server) The Parts of Isomorphic JavaScript IsomorphicはNode.jsの進化・安定の賜物 Docker利用しているため、バージョンアップは簡単 基本的に最新版を利用するようにしている
29. Renewal Policy できるだけ「スタンダード」なツールを使う 作っては壊すを繰り返す 共通化・最適化は最終段階でおこなう その代わり、パーツを簡単に取り除けるようにしておく
30. ❝ 早すぎる最適化は諸悪の根源である ❞ Donald Knuth
31. https://goo.gl/Q29qBX アメブロ 2016: React/Reduxでつくる Isomorphic web app https://goo.gl/GyoZh4 アメブロでReactやIsomorphic Web App を採用した理由――その成果と構成技術 https://goo.gl/xHdn83 アメブロの大規模システム刷新と それを支えるSpring
32. Caches 高負荷なサービスやハイパフォーマンスを求める場合、 サーバーサイドのキャッシュは必須 アメブロはブログサービスなのでキャッシュと相性が良い
33. Template Caches # $ %+ ' renderToString() Client Info HTML Cache
34. API Caches 最初はボトルネックになるHTMLのキャッシュのみ APIにも適切にキャッシュを設定
35. Caches 1.5x Faster
36. Code-Splitting JavaScriptの評価時間を短縮するため main.jsの誇大化を防ぐため いつかはフルSPAにするかもしれないため
37. Performance Metrics
38. Performance Metrics
39. https://goo.gl/WaULzs Leveraging the Performance Metrics that Most Affect User Experience
40. Webpack Dynamic Imports Dynamic Importsを利用 https://goo.gl/5Fw35x AmebaではAtomic Designの Organisms単位で分割 (今は) () => import(‘./organisms/Header.js’);
41. JavaScript評価時間 9x Faster
42. React 16 React Fiber 1.4x Faster React 16 (Beta 3) vs 15
43. https://goo.gl/4Cpff6 アメブロ: Isomprhicアプリケーションの パフォーマンス・チューニング https://goo.gl/W1fyTp アメブロ 2017: Isomorphic Web Appの進化編
44. Monitoring 継続的にモニタリングできる 必要に応じてフィードバック(アラート)を設定できる Amebaでは全てSlackに通知が来るようにしている
45. サーバーアプリケーションの レスポンスタイムをモニタリング ボトルネックを発見する
46. アプリケーションの状態を モニタリングする カスタマイズして様々な Metricsを作れる
47. SpeedCurve 継続的にパフォーマンス計測 - 毎日計測・Slack通知 - デプロイごとに計測・Slack通知
48. 備え付けのページスピードの他、 パフォーマンス値を送信して分析 - SpeedIndex (WPO-Foundation/RUM-SpeedIndex) - window.performance (first-paint, first-contentful- paint)
49. https://goo.gl/h4f8cz WebパフォーマンスとプロダクトKPIの相関を 可視化する話
50. HTTPS Hyper Text Transfer Protocol Secure 2017年夏、ついにアメブロ公開面のHTTPS配信が完了🎉 セキュリティ, SEO, Service Workerなどの新機能利用 観点からHTTPSでの配信は必須
51. CSP Report Content Security Policy ポリシーを定義し、違反リソースを 自動的にレポート レポート先はhttps://report-uri.io/ を利用
52. CSP Report Content Security Policy Content-Security-Policy-Report-Only: default-src 'self' https:; script-src 'self' https: 'unsafe-inline' ‘unsafe-eval'; img-src 'self' https: data:; style-src 'self' https: ‘unsafe-inline'; font-src 'self' https: data:; report-uri https://send-to-report-url
53. CSP Report Content Security Policy
54. HSTS HTTP Strict Transport Security HTTPの代わりにHTTPSを使うよう Webページからブラウザに伝達 徐々に期間を広げていった Strict-Transport-Security: max-age=expireTimeSeconds
55. 301 Redirect HSTSで問題がないか確認後、 httpへのアクセスをhttpsへ強制リダイレクト 301(恒久的)でリダイレクトする
56. Headers Mixed Contentsを防ぐためには、以下のヘッダーが有効 Upgrade-Insecure-Requests ブラウザはhttpのリソースを発見するとhttpsに変換してリクエスト。https 未対応の場合は、404になる。 Block-All-Mixed-Content ブラウザは、httpのリソースを常に読み込まないようにする。
57. Referrer Policy URLには機密情報が含まれている場合があるため、 セキュアでないページへのリクエスト時にリファラは無効 Amebaでは「origin」を選択中
58. https://goo.gl/NgSdg1 アメブロ 2017: 大規模サービスhttps化 ∼All Greenを目指して∼
59. AMP Accelerated Mobile Pages Project Web表示速度改善のプロジェクト ホワイトリスト化されたHTML, JS CSSは内に記述 検索結果とAMPカルーセルに載る
60. AMP at Ameba 7x Sessions from March 2017
61. AMP Spike AMPカルーセルからの突発的なアクセス増 普段の検索とは違った流入を得られる あるブログのAMPページのページビュー推移
62. AMP Performance 最新技術の利用により、安定したパフォーマンス 一部ページ、一部機能など段階的に導入するのもあり オリジナルサイトでAMPと同様のパフォーマンスを 担保できる場合は、利用しなくていいかも
63. LazyLoad with XHRしてデータを取得 mustacheでテンプレート記述 LazyLoadを簡単に実装できる 厳密なCORSルール
64. データが取得できませんでした。 {{#data}} {{entry_title}} {{/data}} LazyLoad with
65. https://goo.gl/1ZDj55 Actions and Events in AMP https://goo.gl/Lja76p CORS Requests in AMP
66. Auto Suggestion Search Bar with で初期状態を設定 input入力に応じてAMP.setState() で状態を更新 で推薦結果を再取得
67. Auto Suggestion Search Bar with { "autosuggest" : "" }
68. Auto Suggestion Search Bar with {{.}}
69. Like actions with and で初期状態を取得 ボタンタップで状態を反映 ポップアップ内のボタンタップは でAPIにデータ送信
70. いいね!する いいね!をキャンセルする Like actions with and
72. Install Service Workers with
73. Install Service Workers with
74. Install Service Workers with AMP to PWA 初期表示がはやい 静的 制限された機能 フル機能が使える 動的 初期表示は遅め AMP PWA
75. Install Service Workers with AMP to PWA AMP ( PWA )* * * Navigate to PWA
76. Install Service Workers with AMP with PWA 初期表示がはやい 静的 制限された機能 フル機能が使える 動的 初期表示は遅め AMP PWA 初期表示がはやい フル機能が使える 動的 Isomorphic
77. Install Service Workers with AMP with PWA AMP ( Isomorphic )* * * Link to Original Site
78. PWA Progressive Web App Webのユーザーエクスペリエンス NativeとWebのいいとこ取りを目指す Reliable, Fast, Engaging
79. Image Lightbox Native Like Image Lightbox ネイティブのような画像lightbox スムーズに画像を閲覧できる dimsemenov/PhotoSwipeを利用
80. Article Selector Native Like Article Selector 記事ソート機能 モーダル表示で様々なソートを実現
81. Service Worker Webページとは別にバックグラウンドで動作するスクリプト Fetch, Cache, Push, Background sync… ライフサイクルの理解が重要
82. Service Worker Lifecycle スコープ内のページが同じService Workerで制御 できるようにする Chromeのアップデートに似ている
83. Service Worker Lifecycle Install + * * ActivateWaiting
84. Service Worker Lifecycle Install * * Activate +Waiting
85. Service Worker Lifecycle Install * * Activate + * Fetch & Cache Waiting
86. Service Worker Lifecycle Install + * * Activate +Waiting
87. Service Worker Lifecycle Install * + * Activate +Waiting
88. Service Worker Lifecycle Install * Waiting * Activate + * Fetch & Cache
89. https://goo.gl/YKZmPK Service Workerのライフサイクル
90. Precache Service Workerでアセットをプレキャッシュ GoogleChrome/Workboxを利用 Webpackプロセスに組み込み 取得後は、再度ネットワークリクエストしない
91. self.AMEBLO_CDN = 'https://c.stat100.ameba.jp'; importScripts('https://c.stat100.ameba.jp/ameblo/assets/sp/ 20170914-3b91bd8/service-worker.js'); Precache クロスドメイン対応
92. const = fileManifest = [ { url: self.AMEBLO_CDN + '/ameblo/assets/sp/20170914-3b91bd8/0.js' }, { url: self.AMEBLO_CDN + '/ameblo/assets/sp/20170914-3b91bd8/main.css' }, { url: ‘/shell/20170914-3b91bd8‘ } ]; workboxSW.precache(fileManifest); Precache クロスドメイン対応
93. Precache
94. Precache
95. Runtime Cache 記事に関連するリソースをキャッシュ e.g. 画像, 記事データ GoogleChrome/Workboxを利用
96. Runtime Cache Strategy Cache First 一度キャッシュしたら、再度ネットワークリクエストしない。 バージョン付きのリソースに有効。 Network First 常にネットワークを優先する。取得できなかった場合のみキャッシュを利用。 APIデータなど、更新頻度が高いものに有効。
97. Runtime Cache Strategy StaleWhileRevalidate (Fastest) キャッシュがある場合はキャッシュを利用し、ネットワーク取得後、裏側で キャッシュを更新する。 更新度の低いリソース、アグレッシブなキャッシュに有効。
98. { runtimeCaching: [ { urlPattern: //_api/.*/, // Data from API handler: 'staleWhileRevalidate', }, { // Images in articles urlPattern: /^https://.*stat.ameba.jp/user_images/.*/, handler: ‘cacheFirst', options: { cache: { maxEntries: 100 }, }, } ] } Runtime Cache
99. Network Consumption ???x Cheaper ,
100. Go! Yakiniku🍖
101. Offline Notification オンライン/オフラインの変化時 ノティフィケーションを表示 online/offlineイベント navigator.onLineプロパティ
102. Offline Notification window.addEventListener('online', handleOnlineStatus, false); window.addEventListener('offline', handleOnlineStatus, false); const handleOnlineStatus = () => { const online = window.navigator.onLine; if (online) { hideFloatingNotification(); } else { showFloatingNotification(‘インターネットに接続できません。’) } };
103. Offline Availability オフラインでも記事が読める Service Workerで記事に必要な アセットをプレキャッシュ, ランタイムキャッシュ
104. Offline Availability 記事に必要なアセット: App Shell 記事データ (API) 記事内画像
105. App Shell ページに必要な最低限のアセット 現時点のAmebaでは、SPAモード と同等 Navigation Fallbackに設定
106. Web App Manifest Webアプリのメタデータを記述 ホーム画面からの起動時に利用される
107. { "short_name": "ca-seo", "name": "Ameba", "icons": [{ “src": “https://c.stat100.ameba.jp/img/sp/web-app-icon.png", "type": "image/png", "sizes": "192x192" }], "start_url": "/ca-seo?precache=1&utm_medium=direct&utm_source=homescreen", "background_color": "#efefef", "display": "standalone", "orientation": "portrait", "theme_color": "#efefef" } Web App Manifest
108. App Install Banners 必須条件 (変更の可能性あり): short_name, name, start_url, Service Worker, HTTPS, 144x144以上のpngアイコン
109. App Install Banners beforeinstallpromptイベントでインストールバナーを コントロールできる バナー表示直前/キャンセル/受け入れ 現在、Amebaではバナー非表示にしている (試験中のため)
110. App Install Banners var deferredPrompt; window.addEventListener(‘beforeinstallprompt', (e) => { e.preventDefault(); // プロンプトの表示を先送りする deferredPrompt = e; return false; }); btnSave.addEventListener('click', () => { if (deferredPrompt !== undefined) { deferredPrompt.prompt(); // プロンプトを表示 deferredPrompt.userChoice.then((choiceResult) => { console.log(choiceResult.outcome); // dismissed or accepted }); } });
111. https://goo.gl/H1CroZ ウェブアプリのインストール バナー
112. navigator.getInstalledRelatedApps() ブラウザからアプリのインストールを確認する 「アプリがインストールされていたら、App Install Banner 出さない」実装ができる アプリの実装も少し必要 ChromeでOrigin Trial中
113. [{ "relation": [”delegate_permission/common.handle_all_urls"], "target": { "namespace": ”web", "site": ”https://ameblo.jp" } }] navigator.getInstalledRelatedApps() Androidの実装
114. // manifest.json { ... "related_applications": [{ "platform": "play", "id": “jp.ameba” }], ... } // Your code if (navigator.getInstalledRelatedApps) { navigator.getInstalledRelatedApps().then(relatedApps => { console.log(relatedApps.length); // 0: not installed }); } navigator.getInstalledRelatedApps() Webの実装
115. https://goo.gl/C86Dha Detect if your Native app is installed from your web site
116. Ameba PWA (Beta) Add to Home screenから お試しいただけます
117. Next Milestones CDN Strategy Updates Image Optimization HTTP/2 More PWA iPhone X 😬 etc…
118. @herablog

Kazunari Hara

September 24, 2017
Tweet

More Decks by Kazunari Hara

Other Decks in Technology

Transcript

  1. ʠϞμϯʡ΢ΣϒΞϓϦέʔγϣϯ
    ʙΞϝϒϩϲ೥ܭըʙ
    )5.-$POGFSFODF
    ݪҰ੒!IFSBCMPH

    View Slide

  2. Ξϝʔόϒϩά
    ೥։࢝ͷϒϩάαʔϏε
    ܳೳਓͷར༻͕ଟ͍
    ܳೳਓҎ֎ͱͦΕҎ֎ͷ17͸൒ʑ

    View Slide


  3. όοΫΤϯυγεςϜͷ࡮৽


    *TPNPSQIJD+BWB4DSJQU
    ".1
    IUUQT
    $PEFTQMJUUJOH
    /PEFKTW
    3FBDU
    18"

    View Slide

  4. ೥͔͚ͯʠϞμϯʡͳঢ়ଶʹ͢Δ

    View Slide

  5. ʠϞμϯʡͰ͋Δ͜ͱ
    ΤίγεςϜͱͭͳ͕͍ͬͯΔ͜ͱ

    View Slide

  6. ʠϞμϯʡͰ͋Δ͜ͱ
    ΤίγεςϜ͔ΒͷԸܙ ΤϯδχΞͷྲྀಈੑ
    ! "

    View Slide

  7. ΤίγεςϜ͔ΒͷԸܙ
    ࠷৽ٕज़ΛऔΓࠐΈ΍͍͢
    ੈքதͷΤϯδχΞͱڠྗͰ͖Δ
    !

    View Slide

  8. ΤϯδχΞͷྲྀಈੑ
    एऀͷࢀՃΛଅ͠΍͍͢
    ଐਓԽΛ๷͗΍͍͢
    "

    View Slide

  9. 443ͱ41"
    4FSWFS
    $MJFOU
    Database
    Services
    Presentation
    Logic
    Data Services
    DAO
    Rendering

    View Slide

  10. 443ͱ41"
    4FSWFS
    $MJFOU
    Database
    Services
    Presentation
    Logic
    Data Services
    DAO

    View Slide

  11. 443ͱ41"
    ॳظද͕ࣔ͸΍͍
    ϖʔδભҠ͸஗Ί
    ϖʔδભҠ͕͸΍͍
    ॳظදࣔ͸஗Ί
    443 41"

    View Slide

  12. IUUQTHPPHM,4-L
    3FOEFSJOHPO(PPHMF4FBSDI

    View Slide

  13. *TPNPSQIJD+BWB4DSJQU
    IUUQTHPPHMZ-[/

    View Slide

  14. *TPNPSQIJD+BWB4DSJQU
    IUUQTHPPHMZ-[/

    View Slide

  15. 6OJWFSTBM+BWB4DSJQU
    IUUQTHPPHMYBL9"

    View Slide

  16. *TPNPSQIJD+BWB4DSJQU
    ೥ɺΞϝϒϩͷγεςϜ࡮৽
    4FSWFS
    $MJFOU
    Database
    Services
    Presentation
    Logic
    Data Services
    DAO
    Browser

    View Slide

  17. *TPNPSQIJD+BWB4DSJQU
    1SPT
    %BUBͱ6*ͷ੹຿Λ෼཭Ͱ͖Δ
    6*͸ಉ͡ΤϯδχΞ͕ίϯτϩʔϧͰ͖Δ
    ॳظɾϥϯλΠϜ྆ํͷදࣔ଎౓ΛվળͰ͖Δ

    View Slide

  18. *TPNPSQIJD+BWB4DSJQU
    ෳࡶ͕͞গ͠૿͢ ಛʹ443ͱ41"੾Ε໨

    /PEFKTͷӡ༻࣮੷͕ඞཁ ͋Δͱྑ͍

    શΞϓϦʹ޲͔ͳ͍ ࢀরܥʹ޲͘

    $POT

    View Slide

  19. *OJUJBM4QFFEXJUI443BOE41"
    Y'BTUFS

    View Slide

  20. 3VOUJNF4QFFEXJUI443BOE41"
    Y'BTUFS

    View Slide

  21. *TPNPSQIJD+BWB4DSJQU
    ೥ɺΞϝϒϩͷγεςϜ࡮৽
    4FSWFS
    $MJFOU
    Database
    Services
    Presentation
    Logic
    Data Services
    DAO
    Browser

    View Slide

  22. 3FBDU 3FOEFSJOH

    5IF1BSUTPG*TPNPSQIJD+BWB4DSJQU
    ίϯϙʔωϯτࢦ޲
    αʔόʔɺΫϥΠΞϯτ྆ํͷϨϯμϦϯά
    ϥΠϑαΠΫϧϝιουͰ*TPNPSQIJDʹ͠΍͍͢

    View Slide

  23. export class SampleComponent extends React.Component {
    componentWillMount() {} // αʔό༻ɻconstructorΛ࢖͏ͷ͕ਪ঑
    componentDidMount() {} // ΫϥΠΞϯτ༻ɻॳճͷදࣔ࣌
    componentDidUpdate() {} // ΫϥΠΞϯτ༻ɻSPAͷભҠ࣌ʹ࢖ΘΕΔ͜ͱ͕ଟ͍
    componentWillUnmount() {} // ΫϥΠΞϯτ༻ɻλΠϚʔॲཧΛফ͢ͳͲ
    render() {} //αʔόαΠυɺΫϥΠΞϯταΠυ྆ํ
    }
    3FBDU 3FOEFSJOH

    5IF1BSUTPG*TPNPSQIJD+BWB4DSJQU

    View Slide

  24. 3FEVY 4UBUFDPOUBJOFS

    5IF1BSUTPG*TPNPSQIJD+BWB4DSJQU
    'MVYϕʔεͷঢ়ଶ؅ཧͷͨΊͷϑϨʔϜϫʔΫ
    ؔ਺ϕʔεͰ*TPNPSQIJD͠΍͍͢
    ෭࡞༻ͳͲᐆດͳͱ͜Ζ͸ެࣜαΠτͷਪ঑ʹ߹ΘͤΔ

    View Slide


  25. IUUQTHPPHMFT23T,
    )PXDBO*SFQSFTFOUʠTJEFF⒎FDUTʡ
    TVDIBT"+"9DBMMT

    View Slide

  26. 'FUDI"1* 'FUDIS 'FUDIJOH

    5IF1BSUTPG*TPNPSQIJD+BWB4DSJQU
    σʔλͷऔಘ͸
    'FUDI"1* CJUJOOOPEFGFUDI HJUIVCGFUDI

    'FUDIS ZBIPPGFUDIS
    Ͱ*TPNPQSIJDԽ

    View Slide

  27. 3FBDU3PVUFS 3PVUJOH

    5IF1BSUTPG*TPNPSQIJD+BWB4DSJQU
    ֤QBUI͝ͱʹରԠ͢ΔίϯϙʔωϯτΛݺͼग़͠
    αʔόʔͰ͸ɺΤϥʔɾϦμΠϨΫτॲཧ
    ΫϥΠΞϯτͰ͸ɺώετϦʔɾεΫϩʔϧҐஔ؅ཧ
    ͳͲ͕ඞཁ

    View Slide

  28. /PEFKT 8FCTFSWFS

    5IF1BSUTPG*TPNPSQIJD+BWB4DSJQU
    *TPNPSQIJD͸/PEFKTͷਐԽɾ҆ఆͷࣀ෺
    %PDLFSར༻͍ͯ͠ΔͨΊɺόʔδϣϯΞοϓ͸؆୯
    جຊతʹ࠷৽൛Λར༻͢ΔΑ͏ʹ͍ͯ͠Δ

    View Slide

  29. 3FOFXBM1PMJDZ
    Ͱ͖Δ͚ͩʮελϯμʔυʯͳπʔϧΛ࢖͏
    ࡞ͬͯ͸յ͢Λ܁Γฦ͢
    ڞ௨Խɾ࠷దԽ͸࠷ऴஈ֊Ͱ͓͜ͳ͏
    ͦͷ୅ΘΓɺύʔπΛ؆୯ʹऔΓআ͚ΔΑ͏ʹ͓ͯ͘͠

    View Slide

  30. ❝ૣ͗͢Δ࠷దԽ͸ॾѱͷࠜݯͰ͋Δ❞
    %POBME,OVUI

    View Slide

  31. IUUQTHPPHM2R#9
    Ξϝϒϩ3FBDU3FEVYͰͭ͘Δ
    *TPNPSQIJDXFCBQQ

    IUUQTHPPHM(ZP;I
    ΞϝϒϩͰ3FBDU΍*TPNPSQIJD8FC"QQ
    Λ࠾༻ͨ͠ཧ༝ʕʕͦͷ੒Ռͱߏ੒ٕज़
    IUUQTHPPHMY)EO
    Ξϝϒϩͷେن໛γεςϜ࡮৽ͱ
    ͦΕΛࢧ͑Δ4QSJOH

    View Slide

  32. $BDIFT
    ߴෛՙͳαʔϏε΍ϋΠύϑΥʔϚϯεΛٻΊΔ৔߹ɺ
    αʔόʔαΠυͷΩϟογϡ͸ඞਢ
    Ξϝϒϩ͸ϒϩάαʔϏεͳͷͰΩϟογϡͱ૬ੑ͕ྑ͍

    View Slide

  33. 5FNQMBUF$BDIFT
    # $ %
    +
    '
    SFOEFS5P4USJOH
    $MJFOU*OGP )5.-
    $BDIF

    View Slide

  34. "1*$BDIFT
    ࠷ॳ͸ϘτϧωοΫʹͳΔ)5.-ͷΩϟογϡͷΈ
    "1*ʹ΋ద੾ʹΩϟογϡΛઃఆ

    View Slide

  35. $BDIFT
    Y'BTUFS

    View Slide

  36. $PEF4QMJUUJOH
    +BWB4DSJQUͷධՁ࣌ؒΛ୹ॖ͢ΔͨΊ
    NBJOKTͷތେԽΛ๷͙ͨΊ
    ͍͔ͭ͸ϑϧ41"ʹ͢Δ͔΋͠Εͳ͍ͨΊ

    View Slide

  37. 1FSGPSNBODF.FUSJDT

    View Slide

  38. 1FSGPSNBODF.FUSJDT

    View Slide

  39. IUUQTHPPHM8B6-[T
    -FWFSBHJOHUIF1FSGPSNBODF.FUSJDT
    UIBU.PTU"⒎FDU6TFS&YQFSJFODF

    View Slide

  40. 8FCQBDL
    %ZOBNJD*NQPSUT
    %ZOBNJD*NQPSUTΛར༻
    IUUQTHPPHM'XY
    "NFCBͰ͸"UPNJD%FTJHOͷ
    0SHBOJTNT୯ҐͰ෼ׂ ࠓ͸

    () => import(‘./organisms/Header.js’);

    View Slide

  41. +BWB4DSJQUධՁ࣌ؒ
    Y'BTUFS

    View Slide

  42. 3FBDU
    3FBDU'JCFS
    Y'BTUFS3FBDU #FUB
    WT

    View Slide


  43. IUUQTHPPHM$Q⒎
    Ξϝϒϩ*TPNQSIJDΞϓϦέʔγϣϯͷ
    ύϑΥʔϚϯεɾνϡʔχϯά
    IUUQTHPPHM8GZ5Q
    Ξϝϒϩ
    *TPNPSQIJD8FC"QQͷਐԽฤ

    View Slide

  44. .POJUPSJOH
    ܧଓతʹϞχλϦϯάͰ͖Δ
    ඞཁʹԠͯ͡ϑΟʔυόοΫ Ξϥʔτ
    ΛઃఆͰ͖Δ
    "NFCBͰ͸શͯ4MBDLʹ௨஌͕དྷΔΑ͏ʹ͍ͯ͠Δ

    View Slide

  45. αʔόʔΞϓϦέʔγϣϯͷ
    ϨεϙϯελΠϜΛϞχλϦϯά
    ϘτϧωοΫΛൃݟ͢Δ

    View Slide

  46. ΞϓϦέʔγϣϯͷঢ়ଶΛ
    ϞχλϦϯά͢Δ
    ΧελϚΠζ༷ͯ͠ʑͳ
    .FUSJDTΛ࡞ΕΔ

    View Slide

  47. 4QFFE$VSWF
    ܧଓతʹύϑΥʔϚϯεܭଌ
    ຖ೔ܭଌɾ4MBDL௨஌
    σϓϩΠ͝ͱʹܭଌɾ4MBDL௨஌

    View Slide

  48. උ͑෇͚ͷϖʔδεϐʔυͷଞɺ
    ύϑΥʔϚϯε஋Λૹ৴ͯ͠෼ੳ
    4QFFE*OEFY 810'PVOEBUJPO36.4QFFE*OEFY

    XJOEPXQFSGPSNBODF pSTUQBJOU pSTUDPOUFOUGVM
    QBJOU

    View Slide


  49. IUUQTHPPHMIGD[
    8FCύϑΥʔϚϯεͱϓϩμΫτ,1*ͷ૬ؔΛ
    ՄࢹԽ͢Δ࿩

    View Slide

  50. )5514
    )ZQFS5FYU5SBOTGFS1SPUPDPM4FDVSF
    ೥Նɺ͍ͭʹΞϝϒϩެ։໘ͷ)5514഑৴͕׬ྃ
    ηΩϡϦςΟ 4&0
    4FSWJDF8PSLFSͳͲͷ৽ػೳར༻
    ؍఺͔Β)5514Ͱͷ഑৴͸ඞਢ

    View Slide

  51. $413FQPSU
    $POUFOU4FDVSJUZ1PMJDZ
    ϙϦγʔΛఆٛ͠ɺҧ൓ϦιʔεΛ
    ࣗಈతʹϨϙʔτ
    Ϩϙʔτઌ͸IUUQTSFQPSUVSJJP
    Λར༻

    View Slide

  52. $413FQPSU
    $POUFOU4FDVSJUZ1PMJDZ
    Content-Security-Policy-Report-Only:
    default-src 'self' https:;
    script-src 'self' https: 'unsafe-inline' ‘unsafe-eval';
    img-src 'self' https: data:;
    style-src 'self' https: ‘unsafe-inline';
    font-src 'self' https: data:;
    report-uri https://send-to-report-url

    View Slide

  53. $413FQPSU
    $POUFOU4FDVSJUZ1PMJDZ

    View Slide

  54. )454
    )5514USJDU5SBOTQPSU4FDVSJUZ
    )551ͷ୅ΘΓʹ)5514Λ࢖͏Α͏
    8FCϖʔδ͔Βϒϥ΢βʹ఻ୡ
    ঃʑʹظؒΛ޿͍͛ͯͬͨ
    Strict-Transport-Security: max-age=expireTimeSeconds

    View Slide

  55. 3FEJSFDU
    )454Ͱ໰୊͕ͳ͍͔֬ೝޙɺ
    IUUQ΁ͷΞΫηεΛIUUQT΁ڧ੍ϦμΠϨΫτ
    ߃ٱత
    ͰϦμΠϨΫτ͢Δ

    View Slide

  56. )FBEFST
    .JYFE$POUFOUTΛ๷͙ͨΊʹ͸ɺҎԼͷϔομʔ͕༗ޮ
    6QHSBEF*OTFDVSF3FRVFTUT
    ϒϥ΢β͸IUUQͷϦιʔεΛൃݟ͢ΔͱIUUQTʹม׵ͯ͠ϦΫΤετɻIUUQT
    ະରԠͷ৔߹͸ɺʹͳΔɻ
    #MPDL"MM.JYFE$POUFOU
    ϒϥ΢β͸ɺIUUQͷϦιʔεΛৗʹಡΈࠐ·ͳ͍Α͏ʹ͢Δɻ

    View Slide

  57. 3FGFSSFS1PMJDZ
    63-ʹ͸ػີ৘ใؚ͕·Ε͍ͯΔ৔߹͕͋ΔͨΊɺ
    ηΩϡΞͰͳ͍ϖʔδ΁ͷϦΫΤετ࣌ʹϦϑΝϥ͸ແޮ
    "NFCBͰ͸ʮPSJHJOʯΛબ୒த

    View Slide


  58. IUUQTHPPHM/H4EH
    Ξϝϒϩେن໛αʔϏεIUUQTԽ
    ʙ"MM(SFFOΛ໨ࢦͯ͠ʙ

    View Slide

  59. ".1
    "DDFMFSBUFE.PCJMF1BHFT1SPKFDU
    8FCදࣔ଎౓վળͷϓϩδΣΫτ
    ϗϫΠτϦετԽ͞Εͨ)5.- +4
    $44͸IFBE಺ʹهड़
    ݕࡧ݁Ռͱ".1ΧϧʔηϧʹࡌΔ

    View Slide

  60. ".1BU"NFCB
    Y4FTTJPOTGSPN.BSDI

    View Slide

  61. ".14QJLF
    ".1Χϧʔηϧ͔ΒͷಥൃతͳΞΫηε૿
    ීஈͷݕࡧͱ͸ҧͬͨྲྀೖΛಘΒΕΔ
    ͋Δϒϩάͷ".1ϖʔδͷϖʔδϏϡʔਪҠ

    View Slide

  62. ".11FSGPSNBODF
    ࠷৽ٕज़ͷར༻ʹΑΓɺ҆ఆͨ͠ύϑΥʔϚϯε
    Ұ෦ϖʔδɺҰ෦ػೳͳͲஈ֊తʹಋೖ͢Δͷ΋͋Γ
    ΦϦδφϧαΠτͰ".1ͱಉ༷ͷύϑΥʔϚϯεΛ
    ୲อͰ͖Δ৔߹͸ɺར༻͠ͳ͍͍͔ͯ͘΋

    View Slide

  63. BNQMJTU
    -B[Z-PBEXJUIBNQMJTU
    9)3ͯ͠σʔλΛऔಘ
    NVTUBDIFͰςϯϓϨʔτهड़
    -B[Z-PBEΛ؆୯ʹ࣮૷Ͱ͖Δ
    ݫີͳ$034ϧʔϧ

    View Slide

  64. layout=“fixed-height" height=“105" width="auto"
    src=“https://gamp.ameblo.jp/_api/recent-images”
    >
    σʔλ͕औಘͰ͖·ͤΜͰͨ͠ɻ


    {{#data}}

    {{entry_title}}

    {{/data}}



    BNQMJTU
    -B[Z-PBEXJUIBNQMJTU

    View Slide

  65. IUUQTHPPHM;%K
    "DUJPOTBOE&WFOUTJO".1
    IUUQTHPPHM-KBQ
    $0343FRVFTUTJO".1

    View Slide

  66. BNQCJOE
    "VUP4VHHFTUJPO4FBSDI#BSXJUIBNQCJOE
    BNQTUBUFͰॳظঢ়ଶΛઃఆ
    JOQVUೖྗʹԠͯ͡".1TFU4UBUF

    Ͱঢ়ଶΛߋ৽
    BNQMJTUͰਪન݁ՌΛ࠶औಘ

    View Slide

  67. BNQCJOE
    "VUP4VHHFTUJPO4FBSDI#BSXJUIBNQCJOE

    <br/>{ "autosuggest" : "" }<br/>

    class="search-bar__input"
    type="search"
    name="q"
    placeholder="ϒϩάΛݕࡧ"
    tabindex="0"
    on=“input-debounced:AMP.setState(
    { searchState: { autosuggest: event.value }})”
    />

    View Slide

  68. BNQCJOE
    "VUP4VHHFTUJPO4FBSDI#BSXJUIBNQCJOE
    layout="fixed-height"
    src="https://gamp.ameblo.jp/_api/autosuggest?limit=5&q="
    [src]="'https://gamp.ameblo.jp/_api/autosuggest?limit=5&q=' +
    searchState.autosuggest"
    items=“."
    height=“225"
    width="auto"
    >

    {{.}}


    View Slide

  69. BNQGPSN
    -JLFBDUJPOTXJUIBNQGPSNBOEBNQCJOE
    BNQTUBUFͰॳظঢ়ଶΛऔಘ
    ϘλϯλοϓͰঢ়ଶΛ൓ө
    ϙοϓΞοϓ಺ͷϘλϯλοϓ͸
    BNQGPSNͰ"1*ʹσʔλૹ৴

    View Slide

  70. src=“https://iine.ameba.jp/web/display_iine.json
    >

    on="tap:iine-lightbox,AMP.setState({iineState: iineState})”
    >͍͍Ͷʂ


    Close

    BNQGPSN
    -JLFBDUJPOTXJUIBNQGPSNBOEBNQCJOE

    View Slide


  71. action-xhr=“https://iine.ameba.jp/web/exec_iine.json”
    on="submit-success:AMP.setState({iineState: {exist: true}})"
    [class]="!iineState.exist ? 'visible' : 'hidden'"
    >͍͍Ͷʂ͢Δ
    action-xhr=“https://iine.ameba.jp/web/cancel_iine.json”
    on="submit-success:AMP.setState({iineState: {exist: false}})"
    [class]="iineState.exist ? 'visible' : 'hidden'"
    >͍͍ͶʂΛΩϟϯηϧ͢Δ

    BNQGPSN
    -JLFBDUJPOTXJUIBNQGPSNBOEBNQCJOE

    View Slide

  72. BNQJOTUBMMTFSWJDFXPSLFS
    *OTUBMM4FSWJDF8PSLFSTXJUIBNQJOTUBMMTFSWJDFXPSLFS

    View Slide

  73. src=“https://gamp.ameblo.jp/service-worker.js"
    data-iframe-src=“https://ameblo.jp/_static/install-service-worker.html”
    layout=“nodisplay"
    >
    BNQJOTUBMMTFSWJDFXPSLFS
    *OTUBMM4FSWJDF8PSLFSTXJUIBNQJOTUBMMTFSWJDFXPSLFS

    View Slide

  74. *OTUBMM4FSWJDF8PSLFSTXJUIBNQJOTUBMMTFSWJDFXPSLFS
    ".1UP18"
    ॳظද͕ࣔ͸΍͍
    ੩త
    ੍ݶ͞Εͨػೳ
    ϑϧػೳ͕࢖͑Δ
    ಈత
    ॳظදࣔ͸஗Ί
    ".1 18"

    View Slide

  75. *OTUBMM4FSWJDF8PSLFSTXJUIBNQJOTUBMMTFSWJDFXPSLFS
    ".1UP18"
    ".1
    (
    18"
    )
    *
    *
    *
    BNQJOTUBMMTFSWJDFXPSLFS
    /BWJHBUFUP18"

    View Slide

  76. *OTUBMM4FSWJDF8PSLFSTXJUIBNQJOTUBMMTFSWJDFXPSLFS
    ".1XJUI18"
    ॳظද͕ࣔ͸΍͍
    ੩త
    ੍ݶ͞Εͨػೳ
    ϑϧػೳ͕࢖͑Δ
    ಈత
    ॳظදࣔ͸஗Ί
    ".1 18"
    ॳظද͕ࣔ͸΍͍
    ϑϧػೳ͕࢖͑Δ
    ಈత
    *TPNPSQIJD

    View Slide

  77. *OTUBMM4FSWJDF8PSLFSTXJUIBNQJOTUBMMTFSWJDFXPSLFS
    ".1XJUI18"
    ".1
    (
    *TPNPSQIJD
    )
    *
    *
    *
    BNQJOTUBMMTFSWJDFXPSLFS
    -JOLUP0SJHJOBM4JUF

    View Slide

  78. 18"
    1SPHSFTTJWF8FC"QQ
    8FCͷϢʔβʔΤΫεϖϦΤϯε
    /BUJWFͱ8FCͷ͍͍ͱ͜औΓΛ໨ࢦ͢
    3FMJBCMF 'BTU &OHBHJOH

    View Slide

  79. *NBHF-JHIUCPY
    /BUJWF-JLF*NBHF-JHIUCPY
    ωΠςΟϒͷΑ͏ͳը૾MJHIUCPY
    εϜʔζʹը૾ΛӾཡͰ͖Δ
    EJNTFNFOPW1IPUP4XJQFΛར༻

    View Slide

  80. "SUJDMF4FMFDUPS
    /BUJWF-JLF"SUJDMF4FMFDUPS
    هࣄιʔτػೳ
    ϞʔμϧදࣔͰ༷ʑͳιʔτΛ࣮ݱ

    View Slide

  81. 4FSWJDF8PSLFS
    8FCϖʔδͱ͸ผʹόοΫάϥ΢ϯυͰಈ࡞͢ΔεΫϦϓτ
    'FUDI $BDIF 1VTI #BDLHSPVOETZODʜ
    ϥΠϑαΠΫϧͷཧղ͕ॏཁ

    View Slide

  82. 4FSWJDF8PSLFS-JGFDZDMF
    είʔϓ಺ͷϖʔδ͕ಉ͡4FSWJDF8PSLFSͰ੍ޚ
    Ͱ͖ΔΑ͏ʹ͢Δ
    $ISPNFͷΞοϓσʔτʹࣅ͍ͯΔ

    View Slide

  83. 4FSWJDF8PSLFS-JGFDZDMF
    *OTUBMM
    + * *
    "DUJWBUF
    8BJUJOH

    View Slide

  84. 4FSWJDF8PSLFS-JGFDZDMF
    *OTUBMM
    * *
    "DUJWBUF
    +
    8BJUJOH

    View Slide

  85. 4FSWJDF8PSLFS-JGFDZDMF
    *OTUBMM
    * *
    "DUJWBUF
    +
    *
    'FUDI$BDIF
    8BJUJOH

    View Slide

  86. 4FSWJDF8PSLFS-JGFDZDMF
    *OTUBMM
    + * *
    "DUJWBUF
    +
    8BJUJOH

    View Slide

  87. 4FSWJDF8PSLFS-JGFDZDMF
    *OTUBMM
    *
    + *
    "DUJWBUF
    +
    8BJUJOH

    View Slide

  88. 4FSWJDF8PSLFS-JGFDZDMF
    *OTUBMM
    *
    8BJUJOH
    *
    "DUJWBUF
    +
    *
    'FUDI$BDIF

    View Slide

  89. IUUQTHPPHM:,;N1,
    4FSWJDF8PSLFSͷϥΠϑαΠΫϧ

    View Slide

  90. 1SFDBDIF
    4FSWJDF8PSLFSͰΞηοτΛϓϨΩϟογϡ
    (PPHMF$ISPNF8PSLCPYΛར༻
    8FCQBDLϓϩηεʹ૊ΈࠐΈ
    औಘޙ͸ɺ࠶౓ωοτϫʔΫϦΫΤετ͠ͳ͍

    View Slide

  91. self.AMEBLO_CDN = 'https://c.stat100.ameba.jp';
    importScripts('https://c.stat100.ameba.jp/ameblo/assets/sp/
    20170914-3b91bd8/service-worker.js');
    1SFDBDIF
    ΫϩευϝΠϯରԠ

    View Slide

  92. const = fileManifest = [
    {
    url: self.AMEBLO_CDN + '/ameblo/assets/sp/20170914-3b91bd8/0.js'
    },
    {
    url: self.AMEBLO_CDN + '/ameblo/assets/sp/20170914-3b91bd8/main.css'
    },
    {
    url: ‘/shell/20170914-3b91bd8‘
    }
    ];
    workboxSW.precache(fileManifest);
    1SFDBDIF
    ΫϩευϝΠϯରԠ

    View Slide

  93. 1SFDBDIF

    View Slide

  94. 1SFDBDIF

    View Slide

  95. 3VOUJNF$BDIF
    هࣄʹؔ࿈͢ΔϦιʔεΛΩϟογϡ
    FHը૾ هࣄσʔλ
    (PPHMF$ISPNF8PSLCPYΛར༻

    View Slide

  96. 3VOUJNF$BDIF4USBUFHZ
    $BDIF'JSTU
    Ұ౓Ωϟογϡͨ͠Βɺ࠶౓ωοτϫʔΫϦΫΤετ͠ͳ͍ɻ
    όʔδϣϯ෇͖ͷϦιʔεʹ༗ޮɻ
    /FUXPSL'JSTU
    ৗʹωοτϫʔΫΛ༏ઌ͢ΔɻऔಘͰ͖ͳ͔ͬͨ৔߹ͷΈΩϟογϡΛར༻ɻ
    "1*σʔλͳͲɺߋ৽ස౓͕ߴ͍΋ͷʹ༗ޮɻ

    View Slide

  97. 3VOUJNF$BDIF4USBUFHZ
    4UBMF8IJMF3FWBMJEBUF 'BTUFTU

    Ωϟογϡ͕͋Δ৔߹͸ΩϟογϡΛར༻͠ɺωοτϫʔΫऔಘޙɺཪଆͰ
    ΩϟογϡΛߋ৽͢Δɻ
    ߋ৽౓ͷ௿͍ϦιʔεɺΞάϨογϒͳΩϟογϡʹ༗ޮɻ

    View Slide

  98. {
    runtimeCaching: [
    {
    urlPattern: /\/_api\/.*/, // Data from API
    handler: 'staleWhileRevalidate',
    },
    {
    // Images in articles
    urlPattern: /^https:\/\/.*stat\.ameba\.jp\/user_images\/.*/,
    handler: ‘cacheFirst',
    options: {
    cache: { maxEntries: 100 },
    },
    }
    ]
    }
    3VOUJNF$BDIF

    View Slide

  99. /FUXPSL$POTVNQUJPO
    Y$IFBQFS
    ,

    View Slide

  100. (P:BLJOJLV

    View Slide

  101. 0⒐JOF/PUJpDBUJPO
    ΦϯϥΠϯΦϑϥΠϯͷมԽ࣌
    ϊςΟϑΟέʔγϣϯΛදࣔ
    POMJOFP⒐JOFΠϕϯτ
    OBWJHBUPSPO-JOFϓϩύςΟ

    View Slide

  102. 0⒐JOF/PUJpDBUJPO
    window.addEventListener('online', handleOnlineStatus, false);
    window.addEventListener('offline', handleOnlineStatus, false);
    const handleOnlineStatus = () => {
    const online = window.navigator.onLine;
    if (online) {
    hideFloatingNotification();
    } else {
    showFloatingNotification(‘Πϯλʔωοτʹ઀ଓͰ͖·ͤΜɻ’)
    }
    };

    View Slide

  103. 0⒐JOF"WBJMBCJMJUZ
    ΦϑϥΠϯͰ΋هࣄ͕ಡΊΔ
    4FSWJDF8PSLFSͰهࣄʹඞཁͳ
    ΞηοτΛϓϨΩϟογϡ
    ϥϯλΠϜΩϟογϡ

    View Slide

  104. 0⒐JOF"WBJMBCJMJUZ
    هࣄʹඞཁͳΞηοτɿ
    "QQ4IFMM
    هࣄσʔλ "1*

    هࣄ಺ը૾

    View Slide

  105. "QQ4IFMM
    ϖʔδʹඞཁͳ࠷௿ݶͷΞηοτ
    ݱ࣌఺ͷ"NFCBͰ͸ɺ41"Ϟʔυ
    ͱಉ౳
    /BWJHBUJPO'BMMCBDLʹઃఆ

    View Slide

  106. 8FC"QQ.BOJGFTU
    8FCΞϓϦͷϝλσʔλΛهड़
    ϗʔϜը໘͔Βͷىಈ࣌ʹར༻͞ΕΔ

    View Slide

  107. {
    "short_name": "ca-seo",
    "name": "Ameba",
    "icons": [{
    “src": “https://c.stat100.ameba.jp/img/sp/web-app-icon.png",
    "type": "image/png",
    "sizes": "192x192"
    }],
    "start_url": "/ca-seo?precache=1&utm_medium=direct&utm_source=homescreen",
    "background_color": "#efefef",
    "display": "standalone",
    "orientation": "portrait",
    "theme_color": "#efefef"
    }
    8FC"QQ.BOJGFTU

    View Slide

  108. "QQ*OTUBMM#BOOFST
    ඞਢ৚݅ มߋͷՄೳੑ͋Γ
    ɿ
    [email protected] OBNF [email protected]
    4FSWJDF8PSLFS )5514
    YҎ্ͷQOHΞΠίϯ

    View Slide

  109. "QQ*OTUBMM#BOOFST
    CFGPSFJOTUBMMQSPNQUΠϕϯτͰΠϯετʔϧόφʔΛ
    ίϯτϩʔϧͰ͖Δ
    όφʔදࣔ௚લΩϟϯηϧड͚ೖΕ
    ݱࡏɺ"NFCBͰ͸όφʔඇදࣔʹ͍ͯ͠Δ ࢼݧதͷͨΊ

    View Slide

  110. "QQ*OTUBMM#BOOFST
    var deferredPrompt;
    window.addEventListener(‘beforeinstallprompt', (e) => {
    e.preventDefault(); // ϓϩϯϓτͷදࣔΛઌૹΓ͢Δ
    deferredPrompt = e;
    return false;
    });
    btnSave.addEventListener('click', () => {
    if (deferredPrompt !== undefined) {
    deferredPrompt.prompt(); // ϓϩϯϓτΛදࣔ
    deferredPrompt.userChoice.then((choiceResult) => {
    console.log(choiceResult.outcome); // dismissed or accepted
    });
    }
    });

    View Slide

  111. IUUQTHPPHM)$SP;
    ΢ΣϒΞϓϦͷΠϯετʔϧόφʔ

    View Slide

  112. OBWJHBUPSHFU*OTUBMMFE3FMBUFE"QQT

    ϒϥ΢β͔ΒΞϓϦͷΠϯετʔϧΛ֬ೝ͢Δ
    ʮΞϓϦ͕Πϯετʔϧ͞Ε͍ͯͨΒɺ"QQ*OTUBMM#BOOFS
    ग़͞ͳ͍ʯ࣮૷͕Ͱ͖Δ
    ΞϓϦͷ࣮૷΋গ͠ඞཁ
    $ISPNFͰ0SJHJO5SJBMத

    View Slide





  113. [{
    \"relation\": [\”delegate_permission/common.handle_all_urls\"],
    \"target\": {
    \"namespace\": \”web\",
    \"site\": \”https://ameblo.jp"
    }
    }]

    OBWJHBUPSHFU*OTUBMMFE3FMBUFE"QQT

    "OESPJEͷ࣮૷

    View Slide

  114. // manifest.json
    {
    ...
    "related_applications": [{
    "platform": "play",
    "id": “jp.ameba”
    }],
    ...
    }
    // Your code
    if (navigator.getInstalledRelatedApps) {
    navigator.getInstalledRelatedApps().then(relatedApps => {
    console.log(relatedApps.length); // 0: not installed
    });
    }
    OBWJHBUPSHFU*OTUBMMFE3FMBUFE"QQT

    8FCͷ࣮૷

    View Slide

  115. IUUQTHPPHM$%IB
    %FUFDUJGZPVS/BUJWFBQQJTJOTUBMMFE
    GSPNZPVSXFCTJUF

    View Slide

  116. "NFCB18" #FUB

    "EEUP)PNFTDSFFO͔Β
    ͓ࢼ͍͚ͨͩ͠·͢

    View Slide

  117. /FYU.JMFTUPOFT
    $%/4USBUFHZ6QEBUFT
    *NBHF0QUJNJ[BUJPO
    )551
    .PSF18"
    J1IPOF9FUDʜ

    View Slide

  118. !IFSBCMPH

    View Slide