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

2e0e89a34badf79dcff642cb7b5c126f?s=47 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
2e0e89a34badf79dcff642cb7b5c126f?s=128

Kazunari Hara

September 24, 2017
Tweet

Transcript

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

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

  3.  όοΫΤϯυγεςϜͷ࡮৽   *TPNPSQIJD+BWB4DSJQU ".1 IUUQT $PEFTQMJUUJOH /PEFKTW 3FBDU

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

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

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

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

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

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

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

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

  12. IUUQTHPPHM,4-L 3FOEFSJOHPO(PPHMF4FBSDI

  13. *TPNPSQIJD+BWB4DSJQU IUUQTHPPHMZ-[/

  14. *TPNPSQIJD+BWB4DSJQU IUUQTHPPHMZ-[/

  15. 6OJWFSTBM+BWB4DSJQU IUUQTHPPHMYBL9"

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

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

  18. *TPNPSQIJD+BWB4DSJQU ෳࡶ͕͞গ͠૿͢ ಛʹ443ͱ41"੾Ε໨  /PEFKTͷӡ༻࣮੷͕ඞཁ ͋Δͱྑ͍  શΞϓϦʹ޲͔ͳ͍ ࢀরܥʹ޲͘ $POT

  19. *OJUJBM4QFFEXJUI443BOE41" Y'BTUFS

  20. 3VOUJNF4QFFEXJUI443BOE41" Y'BTUFS

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

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

  23. export class SampleComponent extends React.Component { componentWillMount() {} // αʔό༻ɻconstructorΛ࢖͏ͷ͕ਪ঑

    componentDidMount() {} // ΫϥΠΞϯτ༻ɻॳճͷදࣔ࣌ componentDidUpdate() {} // ΫϥΠΞϯτ༻ɻSPAͷભҠ࣌ʹ࢖ΘΕΔ͜ͱ͕ଟ͍ componentWillUnmount() {} // ΫϥΠΞϯτ༻ɻλΠϚʔॲཧΛফ͢ͳͲ render() {} //αʔόαΠυɺΫϥΠΞϯταΠυ྆ํ } 3FBDU 3FOEFSJOH 5IF1BSUTPG*TPNPSQIJD+BWB4DSJQU
  24. 3FEVY 4UBUFDPOUBJOFS 5IF1BSUTPG*TPNPSQIJD+BWB4DSJQU 'MVYϕʔεͷঢ়ଶ؅ཧͷͨΊͷϑϨʔϜϫʔΫ ؔ਺ϕʔεͰ*TPNPSQIJD͠΍͍͢ ෭࡞༻ͳͲᐆດͳͱ͜Ζ͸ެࣜαΠτͷਪ঑ʹ߹ΘͤΔ

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

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

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

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

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

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

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

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

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

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

  35. $BDIFT Y'BTUFS

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

  37. 1FSGPSNBODF.FUSJDT

  38. 1FSGPSNBODF.FUSJDT

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

  40. 8FCQBDL %ZOBNJD*NQPSUT %ZOBNJD*NQPSUTΛར༻ IUUQTHPPHM'XY "NFCBͰ͸"UPNJD%FTJHOͷ 0SHBOJTNT୯ҐͰ෼ׂ ࠓ͸ () => import(‘./organisms/Header.js’);

  41. +BWB4DSJQUධՁ࣌ؒ Y'BTUFS

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

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

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

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

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

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

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

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

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

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

  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
  53. $413FQPSU $POUFOU4FDVSJUZ1PMJDZ

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

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

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

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

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

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

  60. ".1BU"NFCB Y4FTTJPOTGSPN.BSDI

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

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

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

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

    <amp-carousel height=“100" controls> {{#data}} <div> <p>{{entry_title}}</p> </div> {{/data}} </amp-carousel> </template> </amp-list> BNQMJTU -B[Z-PBEXJUIBNQMJTU
  65. IUUQTHPPHM;%K "DUJPOTBOE&WFOUTJO".1 IUUQTHPPHM-KBQ $0343FRVFTUTJO".1

  66. BNQCJOE "VUP4VHHFTUJPO4FBSDI#BSXJUIBNQCJOE BNQTUBUFͰॳظঢ়ଶΛઃఆ JOQVUೖྗʹԠͯ͡".1TFU4UBUF  Ͱঢ়ଶΛߋ৽ BNQMJTUͰਪન݁ՌΛ࠶औಘ

  67. BNQCJOE "VUP4VHHFTUJPO4FBSDI#BSXJUIBNQCJOE <amp-state id="searchState"> <script type="application/json"> { "autosuggest" : ""

    } </script> </amp-state> <input class="search-bar__input" type="search" name="q" placeholder="ϒϩάΛݕࡧ" tabindex="0" on=“input-debounced:AMP.setState( { searchState: { autosuggest: event.value }})” />
  68. BNQCJOE "VUP4VHHFTUJPO4FBSDI#BSXJUIBNQCJOE <amp-list layout="fixed-height" src="https://gamp.ameblo.jp/_api/autosuggest?limit=5&amp;q=" [src]="'https://gamp.ameblo.jp/_api/autosuggest?limit=5&amp;q=' + searchState.autosuggest" items=“." height=“225"

    width="auto" > <template type="amp-mustache"> <a href="https://search.ameba.jp/search.html?q={{.}}">{{.}}</a> </template> </amp-list>
  69. BNQGPSN -JLFBDUJPOTXJUIBNQGPSNBOEBNQCJOE BNQTUBUFͰॳظঢ়ଶΛऔಘ ϘλϯλοϓͰঢ়ଶΛ൓ө ϙοϓΞοϓ಺ͷϘλϯλοϓ͸ BNQGPSNͰ"1*ʹσʔλૹ৴

  70. <amp-state id=“iineState” credentials="include" src=“https://iine.ameba.jp/web/display_iine.json > </amp-state> <button on="tap:iine-lightbox,AMP.setState({iineState: iineState})” >͍͍Ͷʂ</button>

    <amp-lightbox id=“iine-lightbox” layout=“nodisplay”> <!— contents —> <button on=“tap:iine-lightbox.close”>Close</button> <amp-lightbox> BNQGPSN -JLFBDUJPOTXJUIBNQGPSNBOEBNQCJOE
  71. <amp-lightbox id=“iine-lightbox” layout=“nodisplay”> <form method=“post" target=“_top” action-xhr=“https://iine.ameba.jp/web/exec_iine.json” on="submit-success:AMP.setState({iineState: {exist: true}})"

    [class]="!iineState.exist ? 'visible' : 'hidden'" >͍͍Ͷʂ͢Δ</form> <form method=“post" target=“_top” action-xhr=“https://iine.ameba.jp/web/cancel_iine.json” on="submit-success:AMP.setState({iineState: {exist: false}})" [class]="iineState.exist ? 'visible' : 'hidden'" >͍͍ͶʂΛΩϟϯηϧ͢Δ</form> <amp-lightbox> BNQGPSN -JLFBDUJPOTXJUIBNQGPSNBOEBNQCJOE
  72. BNQJOTUBMMTFSWJDFXPSLFS *OTUBMM4FSWJDF8PSLFSTXJUIBNQJOTUBMMTFSWJDFXPSLFS

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

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

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

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

    ॳظද͕ࣔ͸΍͍ ϑϧػೳ͕࢖͑Δ ಈత *TPNPSQIJD
  77. *OTUBMM4FSWJDF8PSLFSTXJUIBNQJOTUBMMTFSWJDFXPSLFS ".1XJUI18" ".1 ( *TPNPSQIJD ) * * * BNQJOTUBMMTFSWJDFXPSLFS

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  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 ΫϩευϝΠϯରԠ
  93. 1SFDBDIF

  94. 1SFDBDIF

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

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

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

  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
  99. /FUXPSL$POTVNQUJPO Y$IFBQFS ,

  100. (P:BLJOJLV

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

  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(‘Πϯλʔωοτʹ઀ଓͰ͖·ͤΜɻ’) } };
  103. 0⒐JOF"WBJMBCJMJUZ ΦϑϥΠϯͰ΋هࣄ͕ಡΊΔ 4FSWJDF8PSLFSͰهࣄʹඞཁͳ ΞηοτΛϓϨΩϟογϡ  ϥϯλΠϜΩϟογϡ

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

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

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

  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
  108. "QQ*OTUBMM#BOOFST ඞਢ৚݅ มߋͷՄೳੑ͋Γ ɿ TIPSU@OBNF OBNF TUBSU@VSM  4FSWJDF8PSLFS )5514

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

  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 }); } });
  111. IUUQTHPPHM)$SP; ΢ΣϒΞϓϦͷΠϯετʔϧόφʔ

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

  113. <!— AndroidManifest.xml —> <meta-data android:name="asset_statements" android:resource="@string/ asset_statements" /> <!— strings.xml

    —> <string name=“asset_statements"> [{ \"relation\": [\”delegate_permission/common.handle_all_urls\"], \"target\": { \"namespace\": \”web\", \"site\": \”https://ameblo.jp" } }] </string> OBWJHBUPSHFU*OTUBMMFE3FMBUFE"QQT "OESPJEͷ࣮૷
  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ͷ࣮૷
  115. IUUQTHPPHM$%IB %FUFDUJGZPVS/BUJWFBQQJTJOTUBMMFE GSPNZPVSXFCTJUF

  116. "NFCB18" #FUB "EEUP)PNFTDSFFO͔Β ͓ࢼ͍͚ͨͩ͠·͢

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

  118. !IFSBCMPH