Slide 1

Slide 1 text

PWA ΁ࢸΔಓ Toro_Unit @2018.05.05 / WBOsaka 1

Slide 2

Slide 2 text

$ whoami 2

Slide 3

Slide 3 text

Toro_Unit ઎෦ ߛ (͏Β΂ ͻΖ͠) • Frontend Engineer • WordPress Plugin and Theme Developer • ͍͖͞Μ ͍͢͝Hຊ ͸͡Ί·ͨ͠ɻ Github: @torounit Twitter: @Toro_Unit 3

Slide 4

Slide 4 text

ࡳຈͷձࣾͰϦϞʔτϫʔΫͯ͠·͢ 4

Slide 5

Slide 5 text

5

Slide 6

Slide 6 text

Contribution • WordBench ௕໺ ϞσϨʔλʔ • WordCamp Kyoto 2017 / Osaka 2018 / Ogijima 2018 • WordCamp Tokyo 2017 Speaker • etc... 6

Slide 7

Slide 7 text

Plugins and Themes • Custom Post Type Permalinks • Simple Post Type Permalinks • Powerful Posts Per Page (PPPP) • Responsive Slide • Vanilla • and more... 7

Slide 8

Slide 8 text

WordBench Nagano ຖ݄΍ͬͯ·͢ɻ • ࣍ճ͸ 5/26 8

Slide 9

Slide 9 text

9

Slide 10

Slide 10 text

10

Slide 11

Slide 11 text

ωοτΊͬͪΌ੾ΕΔɻ ͭΒ͍ɻ 11

Slide 12

Slide 12 text

§1. Minimum PWA on WordPress 12

Slide 13

Slide 13 text

13

Slide 14

Slide 14 text

ͩΕ͔͕ݴͬͨ ʮશͯͷαΠτ͸PWAʹͳΔ΂͖ɻʯ 14

Slide 15

Slide 15 text

SEYANA Θ͔Δ 15

Slide 16

Slide 16 text

16

Slide 17

Slide 17 text

17

Slide 18

Slide 18 text

18

Slide 19

Slide 19 text

HTML/CSS/JS + REST API • ҰൠతͳΞϓϩʔνͩͱࢥΘΕΔ • PHPॻ͔ͳͯ͘ྑ͍ • JSͰͷ஌ݟ͕͍Ζ͍Ζ࢖͑ΔɻNetlify ͱ͔ɺGithub Pages ͱ͔࢖ ͑͹Ϋοιָɻ • .com ΛόοΫΤϯυʹ͢Ε͹αʔόʔ୅ɺอकෆཁɻ • ςʔϚͷͭΒΈͱ͔WordPress ͕ jQuery ు͘ͱ͔ߟ͑ͳ͍ͯ͘ ͍ɻ 19

Slide 20

Slide 20 text

͜ΕΒΛ WordPress Ͱ PWA ͱ͍ͬͯྑ͍ͷͰ ͠ΐ͏͔ɻ • ผʹ REST API ͋Ε͹ͳΜͰ΋͑͑΍Μ͚ɻ • طଘͷαΠτͷผͷ഑৴ܗଶͱ͍͏ܗͳΒΘ͔Δɻ • ͍ͬͯ͏͔͜ͷΞϓϩʔνͰ WordPress ͍ͨ΁Μ͡Όͳ͍? • هࣄͷϓϨϏϡʔͱ͔ಈ͔ͳ͍͠ɻ 20

Slide 21

Slide 21 text

͜ͷΞϓϩʔνͷσϝϦοτɻ • WordPress ͷςʔϚ͸࢖͑ͳ͍ɻ • ͓Ε͸ςʔϚ࡞ऀɻ • ϓϥάΠϯ΋͍Ζ͍Ζ࢖͑ͳ͍ɻ 21

Slide 22

Slide 22 text

ʮશͯͷαΠτ͸PWAʹͳΔ΂͖ɻʯ Ͱ͖ͳ͍΍Μ͚ʂʂ 22

Slide 23

Slide 23 text

23

Slide 24

Slide 24 text

ͱ͍͏Θ͚ͰطଘͷWordPressαΠτΛPWAԽ͢ΔϓϥάΠ ϯ࡞Γ·ͨ͠ɻ • Smart PWA — WordPress Plugins • torounit/smart-pwa • GWͱ͸͍͍ͬͨɻ 24

Slide 25

Slide 25 text

΍ͬͨ͜ͱɻ 25

Slide 26

Slide 26 text

'use strict'; const APP_SHELL_CACHE_NAME = 'smart-pwa-'; const RUNTIME_CACHE_NAME = 'smart-pwa-runtime-cache'; const NOT_AVAILABLE_KEY = ''; const PRE_CACHE_ASSETS = JSON.parse( '' ); const urlsToPreCache = [ '/', NOT_AVAILABLE_KEY, ].concat( PRE_CACHE_ASSETS ); const updateMessage = ( message ) => { self.clients.matchAll().then( clients => clients.forEach( client => client.postMessage( message ) ) ); } self.addEventListener( 'install', ( event ) => { console.log( '[ServiceWorker] Install' ); event.waitUntil( caches.open( APP_SHELL_CACHE_NAME ) .then( ( cache ) => { console.log( '[ServiceWorker] Caching app' ); return cache.addAll( urlsToPreCache ); } ) ); } ); self.addEventListener( 'activate', ( event ) => { console.log( '[ServiceWorker] Activate' ); const cacheWhitelist = [ APP_SHELL_CACHE_NAME ]; event.waitUntil( caches.keys().then( ( cacheNames ) => { return Promise.all( cacheNames.map( ( cacheName ) => { if (cacheWhitelist.indexOf( cacheName ) === - 1) { console.log( '[ServiceWorker] Removing old cache', cacheName ); return caches.delete( cacheName ); } } ) ); } ) ); } ); 26

Slide 27

Slide 27 text

self.addEventListener( 'fetch', ( event ) => { if ( event.request.url.indexOf( 'wp-admin' ) === - 1 && event.request.url.indexOf( 'wp-login' ) === - 1 && event.request.url.indexOf( 'customize_changeset_uuid' ) === - 1 && event.request.method === 'GET' ) { event.respondWith( caches.match( event.request ).then( ( responseFromCache ) => { //Return static content in cache. if (responseFromCache) { if ([ 'style', 'script', 'image' ].indexOf( event.request.destination ) > - 1) { return responseFromCache; } } //Fetch and save content to cache. return fetch( event.request ).then( ( response ) => { //Not 200. if (! response || response.status !== 200 || response.type !== 'basic') { return response; } let responseToCache = response.clone(); caches.open( RUNTIME_CACHE_NAME ).then( cache => { cache.put( event.request, responseToCache ); console.log( '[ServiceWorker] Fetched&Cached Data', event.request.url ); updateMessage( { key: 'updateContent', value: event.request.url } ); } ); return response; } ).catch( ( reason ) => { //when fetching failed, return content in cache. if (responseFromCache) { console.log( '[ServiceWorker] Cache Matched!', event.request.url, responseFromCache ); return responseFromCache; } //if not found in cache, return fallback page. if (event.request.mode === 'navigate') { return caches.match( NOT_AVAILABLE_KEY ).then( ( response ) => { return response; } ); } } ); } ) ); } } ); 27

Slide 28

Slide 28 text

• global $wp_style, global $wp_scripts Λ୳ͯ͠ɺenqueue ͞Εͯ Δ CSS, JS ΛऔಘɻͦΕΛ PreCache ʹɻ • Ұఆ࣌ؒຖʹɺPreCache ͷ࠶औಘ & Worker ͷߋ৽ɻ • Transient API ࢖ͬͯΔ͚ͩͰ͕͢ɻ • style, script, image ͸ΩϟογϡΛฦ٫ɻhttps:// fetch.spec.whatwg.org/#requests • ͦΕҎ֎ͷ͸ɺfetch࣌ʹΩϟογϡͭͭ͠ɺݟ͔ͭΒͳ͚Ε͹Ωϟο γϡΛฦ٫ɻ • ͦΕ΋ͩΊͳΒɺݱࡏ͝ར༻௖͚·ͤΜϖʔδΛฦ٫ɻ 28

Slide 29

Slide 29 text

DEMO. https://torounit.com 29

Slide 30

Slide 30 text

ඍົϙΠϯτ • HTMLΛ·Δ͝ͱΩϟογϡ͍ͯ͠Δɻ • ςʔϚม͑ͨͱ͖ͭΒ͍ɻ ίϯςϯπͱݟͨ໨ͷ෼཭͕ ग़དྷແ͍ͷਏ͍ɻ • Ωϟογϡ͕ॏ͘ͳΓ΍͍͢ɻ • Ωϟογϡͷߋ৽Λड͚ͯಈతʹॻ͖׵͑Α͏ͱ͢Δͱ jQueryͰࢮ͵ɻ 30

Slide 31

Slide 31 text

§2. FullSpec PWA on WordPress 31

Slide 32

Slide 32 text

• App Shell ϞσϧΛͪΌΜͱ΍Γ͍ͨɻ 32

Slide 33

Slide 33 text

ඞཁͳϞϊɻ • Service Worker • SPA ʹͳ͍ͬͯΔςʔϚ 33

Slide 34

Slide 34 text

SPA on WordPress ςʔϚͷਏΈ • @see RESTࡇ • REST API ͳ WordPress ςʔϚʹ͍ͭͯߟ͑Δɻ • ϦϥΠτਏ͍ɻ • WordPressͷURLϑϦʔμϜ 34

Slide 35

Slide 35 text

ͭͬͨ͘ɻ DEMO 35

Slide 36

Slide 36 text

torounit/vue-spa-wp-theme • Vue + Vuex + Workbox • REST API ͱͷ௨৴͸ɺWordPressʹಉࠝͷΫϥΠΞϯτɻ (Backbone.js !!!) • ೝূपΓΛউखʹ΍ͬͯ͘ΕΔͷͰָͰ͸͋Δɻ 36

Slide 37

Slide 37 text

URL ͷ ύʔεਏ͍໰୊ɻ 37

Slide 38

Slide 38 text

function get_permastructs() { global $wp_rewrite; if ( get_option( 'page_for_posts' ) ) { $home_structs = [ 'front-page' => '/', 'home' => get_page_uri( get_option( 'page_for_posts' ) ) ]; } else { $home_structs = [ 'home' => '/' ]; } $extra_permastructs = array_map( function ( $permastruct ) { return $permastruct['struct']; }, $wp_rewrite->extra_permastructs ); $permastructs = [ 'search' => $wp_rewrite->get_search_permastruct(), 'author' => $wp_rewrite->get_author_permastruct(), 'date' => $wp_rewrite->get_date_permastruct(), 'month' => $wp_rewrite->get_month_permastruct(), 'year' => $wp_rewrite->get_year_permastruct(), ]; if ( $wp_rewrite->use_verbose_page_rules ) { $permastructs['post'] = $wp_rewrite->permalink_structure; $permastructs['page'] = $wp_rewrite->get_page_permastruct(); } else { $permastructs['page'] = $wp_rewrite->get_page_permastruct(); $permastructs['post'] = $wp_rewrite->permalink_structure; } $permastructs = array_merge( $home_structs, $extra_permastructs, $permastructs ); return array_map( function ( $key, $value ) { $struct = trim( preg_replace( '/%([^\/]+)%/', ':$1', $value ), '/\\' ); $struct = str_replace( [':year',':monthnum',':day',':post_id'], [':year(\\d{4})',':monthnum(\\d{1,2})',':day(\\d{1,2})',':post_id(\\d+)'], $struct ); return [ 'name' => $key, 'path' => untrailingslashit( '/' . $struct ) . '/:endpoint(page)?/:page(\\d*)' ]; }, array_keys( $permastructs ), array_values( $permastructs ) ); } 38

Slide 39

Slide 39 text

ؾ߹͍Ͱύʔε͢Ε͹·͊ͳΜͱ͔ͳΔΒ͍͠ɻ • WP_Rewrite ͔ΒύʔϚϦϯΫߏ଄Λऔಘ͠·ͬͯ͘ɺpath- to-regexp (vue-router Ͱ࢖ΘΕͯΔURLύʔαʔ)͕ղऍͰ ͖Δܗࣜʹؤுͬͯม׵͢Ε͹ͳΜͱ͔ͳΔɻ • ఴ෇ϑΝΠϧͷϖʔδ/ίϝϯτճΓ/ͱ͔͸஌Βͳ͍ • ΧελϜ౤ߘͱ͔͸݁ߏͲ͏ʹ͔ͳΓͦ͏ɻ • Custom Post Type Permalinks ͱ͔஌Βͳ͍Ͱ͢ɻ 39

Slide 40

Slide 40 text

export const fetchPosts = async ( { commit, state } ) => { let postsCollection = new wp.api.collections.Posts(); let posts = []; let queriedObject = {}; let hasMore = false; let page = state.route.params.page || 1; let data = { page: page }; switch (state.route.name) { case 'home': posts = await postsCollection.fetch( { data: data } ); hasMore = postsCollection.hasMore(); break; case 'category': { let categories = await (new wp.api.collections.Categories()).fetch( { data: { slug: state.route.params.category } } ); queriedObject = categories[ 0 ]; data = Object.assign( data, { categories: categories[ 0 ].id } ); posts = await postsCollection.fetch( { data: data } ); hasMore = postsCollection.hasMore(); break; } case 'post_tag': { let tags = await (new wp.api.collections.Tags()).fetch( { data: { slug: state.route.params.post_tag } } ); queriedObject = tags[ 0 ]; data = Object.assign( data, { tags: tags[ 0 ].id } ); posts = await postsCollection.fetch( { data: data } ); hasMore = postsCollection.hasMore(); break; } case 'author': { let users = await (new wp.api.collections.Users()).fetch( { data: { slug: state.route.params.author } } ); queriedObject = users[ 0 ]; data = Object.assign( data, { author: users[ 0 ].id } ); posts = await postsCollection.fetch( { data: data } ); hasMore = postsCollection.hasMore(); break; } case 'post': { if (state.route.params.postname) { posts = await postsCollection.fetch( { data: { slug: state.route.params.postname } } ); } else { let postModel = new wp.api.models.Post( { id: state.route.params.post_id } ); let post = await postModel.fetch(); posts = [ post ] } queriedObject = posts[ 0 ]; break; } case 'page': let pagesCollection = new wp.api.collections.Pages(); posts = await pagesCollection.fetch( { data: { slug: state.route.params.pagename } } ); queriedObject = posts[ 0 ]; break; case 'front-page': let pageModel = new wp.api.models.Page( { id: global.themeSettings.pageOnFront } ); let post = await pageModel.fetch(); posts = [ post ]; break; } commit( types.SET_QUERIED_OBJECT, queriedObject ); commit( types.SET_HASMORE, hasMore ); commit( types.SET_POSTS, posts ); }; 40

Slide 41

Slide 41 text

ؾ߹͍Ͱ WP_Query::parse_query ͷؾ࣋ͪʹͳΕ͹ͳΜͱ ͔ͳΔ͔΋͠Εͳ͍ɻ • ؾ߹͍ͰͳΜͱ͔͸ͳΔɻGWͭͿΕ͚ͨͲɻ • ͜ΕͰ΋શવ଍Γͳ͍ɻɻɻ • wp.api.getCollectionByRoute, wp.api.getModelByRoute ͱ͔࢖͑͹ΧελϜ౤ߘͱ͔ͱ΋ઓ͑ͦ͏ɻ • Backbone.jsϕʔεͩ͠jQuery΋ಡΈࠐΉ͚Ͳɺೝূͱ͔ߟ ͑ͳͯ͘ྑ͍ͷ͸ָɻ 41

Slide 42

Slide 42 text

Other tips • Service Worker ͸ / ༻ͱɺςʔϚ༻Ͱ̎छྨ༻ҙͨ͠ํ͕ଟ ෼ָɻ • Workbox ͕ศརͩͬͨɻ • άϩʔόϧͳঢ়ଶʹґଘ͢ΔϞϊ͕ଟ͍ͷͰɺVuex ͱ͔ Redux ͱ͔࢖ͬͨํָ͕ɻ 42

Slide 43

Slide 43 text

43

Slide 44

Slide 44 text

FullSpec ͷ ͍͍ͱ͜Ζ • Service Worker ͷ install ޙʹ fetch ͢Ε͹ॳճΞΫηε࣌ͷ ίϯςϯπ΋Ωϟογϡग़དྷΔɻ • ϑΥʔϧόοΫ࡞Γ΍͍͢ɻTOPϖʔδΛApp Shellʹಥͬࠐ ΜͲ͚͹ԿͱͰ΋ͳΔɻ • ͔͍͍ͬ͜ɻ 44

Slide 45

Slide 45 text

FullSpec ͷ σϝϦοτ • ΊͬͪΌ͍ͨ΁Μɻ • ςϯϓϨʔτλάతͳϞϊ͸શ࣮ͯ૷͠ͳ͍ͱ͍͚ͳ͍ɻ͖΄Μඇಉظɻ • ͷ࣮૷ͱ͔΍͹͍ɻ • SQL࢖͑ͳ͍ɻ೥ผΞʔΧΠϒͷҰཡͱ͔͸JSͰ͸ਏ͍ɻ • ௨ৗͷREST APIͰऔΕͳ͍஋(Φϓγϣϯɺϝλσʔλ)͸APIΛ֦ு͢Δ͔ɺPHP͔ΒJSʹ஋ Λ౉͢ͳͲͱʹ͔͍͘Ζ͍Ζ΍Βͳ͍ͱ͍͚ͳ͍ɻ • JS ͷboilerplateతͳͷ͸࢖͍ʹ͍͘ɻ • webpack ॻ͖ͨ͘ͳ͍ • ࢖͑ͳ͘ͳΔϓϥάΠϯ΋ଟ͍ͷͰͦͷखͷ࣮૷΋΍Βͳ͍ͱɻɻ 45

Slide 46

Slide 46 text

·ͱΊ • WordPress ͬΆ͍ΞʔΩςΫνϟͰ PWA ΋·͊ग़དྷΔɻ • ςʔϚͰͷ SPA ͸ۀ຿ϨϕϧͳΒɺଟ෼ग़དྷΔɻ • ഑෍͢ΔςʔϚͩͱશͯͷػೳΛαϙʔτͰ͖ΔΘ͚Ͱ͸ ͳ͍ͷͰ͠ΜͲͦ͏ɻ 46

Slide 47

Slide 47 text

͓ΘΓɻ Thanks! Github: @torounit Twitter: @Toro_Unit Facebook: fb.me/torounit WEB Site: https://torounit.com 47