Slide 1

Slide 1 text

PWA ΁ࢸΔಓ (Compressed Version) Toro_Unit 2018.07.07 / [email protected] 1

Slide 2

Slide 2 text

2

Slide 3

Slide 3 text

• ͜ͷ࿩͸ɺWordBenchେࡕ & ژ౎ & Ionic Japan 5݄ ϑϧε Πϯά Ͱ50෼ऑ࿩ͨ͠ωλΛ5෼ʹѹॖͨ͠ϞϊͰ͢ɻ • Uncompressed version: • https://speakerdeck.com/torounit/wordpress-de-pwa- hezhi-rudao • ͍Ζ͍Ζͬ͢ඈ͹͠·͢ɻ • ෆ໌఺ͳͲ͸ੋඇฉ͍ͯԼ͍͞ɻ • ๻͕ਲͬ෷͏લʹ͓ئ͍͠·͢ɻ 3

Slide 4

Slide 4 text

$ whoami 4

Slide 5

Slide 5 text

Toro_Unit ઎෦ ߛ (͏Β΂ ͻΖ͠) • Frontend Engineer • WordPress Plugin and Theme Developer • Docker ͱ Haskell ͸͡Ί·ͨ͠ɻ Github: @torounit Twitter: @Toro_Unit 5

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

7

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

৴भ WordPress Meetup • WordBench ௕໺ΛҾ͖ܧ͙ܗͰɻ • WordPressΛࡘʹϐβ৯ͬͯΘʔ Θʔ஻Δձͩͱࢥͬͯ·͢ɻ • ࣮͸Ҋ֎ओ්཰ߴ͍ɻ • vol.1: 7/21 Sat. • vol.2: 8/18 Sat. • ʢদຊͰϏʔϧࡇ΍ͬͯ·͢ʣ 9

Slide 10

Slide 10 text

10

Slide 11

Slide 11 text

11

Slide 12

Slide 12 text

• ͠ͳͷ1Ͱདྷͨɻ • ͠ͳͷӡٳͷͨΊɺࡢ൩ɺಛٸ͋ͣ͞Ͱ౦ژɻࠓே৽װઢ Ͱ໊ݹ԰΁ɻ • ;ͩΜ͸͠ͳͷͰ͘Δɻ 1 Ksaaaaaaaaaaaaaa / CC-BY-SA-3.0 12

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

§1. Progressive Web Apps on WordPress 14

Slide 15

Slide 15 text

15

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

SEYANA Θ͔Δ 17

Slide 18

Slide 18 text

Progressive Web Apps ͷࡶͳઆ໌ • WEBαΠτͱΞϓϦͷ͍͍ͱ͜औΓΛͨ͠Α͏ͳϠπɻ • WEBαΠτΛͦͷ··ΞϓϦέʔγϣϯʹɻ • ΦϑϥΠϯͰ΋ಈ࡞ɻ • Android Ͱ΋ iOS Ͱ΋ Windows Ͱ΋αϙʔτ͞ΕͯΔɻ • Chrome ΞϓϦ΋͜Εɻ 18

Slide 19

Slide 19 text

WordPress ͰͲ͏΍Δʁ • HTML/CSS/JS Ͱͭͬͯ͘ɺREST API ͚ͩ࢖͏ɻ • ϑϩϯτΤϯυ͸ɺS3 ͱ͔ Netlify ͱ͔ Github Pages ͱ͔ʹ ஔ͍ͯɺWordPress ͸ REST API ͚ͩ࢖͏ɻ • ϞμϯͳΞϓϩʔνɻ 19

Slide 20

Slide 20 text

• ͦΕ WordPress ͡Όͳ͍΄͏ָ͕ͳ৔߹΋ɻ • Contentful ͱ͔ Strapi Έ͍ͨͳ Headless CMS ࢖͑͹ָ͔΋ɻ • طଘͷ WordPress αΠτͰ͸࢖͑ͳ͍͠ɺӡ༻ϑϩʔ΋ߟ͑௚͞ͳ͍ ͱɻ • ྫ͑͹ϓϨϏϡʔ͕ग़དྷͳ͍ͱ͔ɻ • طଘαΠτͷผͷ૭ޱͱͯ͠͸༗༻ɻ • WEBαΠτͱ͸ผʹΞϓϦΛ࡞ΔΑ͏ͳέʔεͱ͔ɻ • 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

লུ 29

Slide 30

Slide 30 text

• torounit/smart-pwa ಡΜͰԼ͍͞ɻ • ৮ͬͯΈͯԼ͍͞ɻ • ๻͕ਲͬ෷͏લʹฉ͍ͯԼ͍͞ɻ 30

Slide 31

Slide 31 text

DEMO. https://torounit.com 31

Slide 32

Slide 32 text

ඍົϙΠϯτ • HTMLΛ·Δ͝ͱ֤ϖʔδ͝ͱʹΩϟογϡ͍ͯ͠Δɻ • ςʔϚม͑ͨͱ͖ͭΒ͍ɻ ίϯςϯπͱݟͨ໨ͷ෼཭͕ग़དྷແ͍ͷਏ͍ɻ • αΠυόʔͳͲڞ௨෦෼ͷมߋ΋ɺΩϟογϡ͞Ε͍ͯΔ΋ͷͱͦ͏Ͱͳ ͍΋ͷͰมΘΔɻ • ΩϟογϡͷංେԽɻ ͜ΕΒͷ໰୊Λղܾ͢Δʹ͸ɺSingle-page Application(SPA)ͳ WordPressςʔ Ϛ Λߟ͑ͳ͍ͱ͍͚ͳ͍ɻ 32

Slide 33

Slide 33 text

§2. PWA on WordPress Theme 33

Slide 34

Slide 34 text

ͱ͍͏Θ͚Ͱͭͬͨ͘ɻ 34

Slide 35

Slide 35 text

torounit/Aetherium • DEMO: https://aetherium-demo.torounit.com/ • Vue + Vuex + Workbox • SPA + PWA ͳWordPress ςʔϚɻ • REST API ͱͷ௨৴͸ɺWordPressʹಉࠝͷΫϥΠΞϯτɻ (Backbone.js !!!) 35

Slide 36

Slide 36 text

URL ͷ ύʔεਏ͍໰୊ɻ • WordPress ͷURL͸ࣗ༝ʹϢʔβʔ͕ઃఆՄೳɻ • ͦΕʹԠͯ͡ɺΞʔΧΠϒͷURL͕มΘͬͨΓɻ • %postname% ͳ৔߹ɺݻఆϖʔδ(Page)Λ୳ͯ͠ɺଘࡏ͠ͳ ͔ͬͨΒɺ౤ߘ(Post)Λ୳͠ʹߦ͘ɻ • ΧελϜ౤ߘͳͲΛઃఆ͠ͳ͍ঢ়ଶͰɺ͍͍ͩͨ80ݸ͘Β͍ ͷϦϥΠτͷϧʔϧ͕ੜ੒͞ΕΔɻ 36

Slide 37

Slide 37 text

͜ΕΛؤுͬͯJSଆͰղऍ͢Δඞཁ͕͋Δɻ 37

Slide 38

Slide 38 text

function aetherium_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['page'] = $wp_rewrite->get_page_permastruct(); $permastructs['post'] = $wp_rewrite->permalink_structure; } else { $permastructs['post'] = $wp_rewrite->permalink_structure; $permastructs['page'] = $wp_rewrite->get_page_permastruct(); } $permastructs = array_merge( $extra_permastructs, $home_structs, $permastructs ); $structs = []; foreach ( $permastructs as $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 ); // for singular if ( in_array( $key, get_post_types( [ 'public' => true ] ) ) ) { $post_type = get_post_type_object( $key ); // for pages. if ( $post_type->hierarchical ) { $structs[] = [ 'name' => $key, 'path' => untrailingslashit( '/' . $struct ) . '(.+?)' . '/(\\d*)?' ]; continue; } //for posts. $structs[] = [ 'name' => $key, 'path' => untrailingslashit( '/' . $struct ) . '/(\\d*)?' ]; continue; } // for hierarchical taxonomies. if ( in_array( $key, get_taxonomies( [ 'public' => true ] ) ) ) { $taxonomy = get_taxonomy( $key ); if ( $taxonomy->hierarchical ) { $structs[] = [ 'name' => $key, 'path' => untrailingslashit( '/' . $struct ) . '(.+?)' . '/page/:page(\\d*)?' ]; $structs[] = [ 'name' => $key, 'path' => untrailingslashit( '/' . $struct ) . '(.+?)' ]; continue; } } //for archives. $structs[] = [ 'name' => $key, 'path' => untrailingslashit( '/' . $struct ) . '/page/:page(\\d*)?' ]; $structs[] = [ 'name' => $key, 'path' => untrailingslashit( '/' . $struct ) ]; } 38

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

౤ߘͷऔಘ • URLΛύʔεͨ͠Β౰વɺͦΕΛݩʹ౤ߘΛऔಘ͠ͳ͚Ε͹ ͳΒͳ͍ɻ 40

Slide 41

Slide 41 text

import moment from 'moment/moment'; export const date = async({ state }) => { let year = state.route.params.year; let monthnum = state.route.params.monthnum || '01'; let day = state.route.params.day || '01'; let first = `${year}-${monthnum}-${day}T00:00:00`; let last = moment( `${year}-${monthnum}-${day}` ).endOf( 'year' ).format( 'YYYY-MM-DDTHH:mm:ss' ); let posts = await getPosts( createPostsArguments( state, { after: first, before: last }) ); let queriedObject = {}; return { queriedObject, posts, hasMore: getCollectionInstance( 'Posts' ).hasMore() }; }; export const term = async({ state }) => { let posts = []; let queriedObject = {}; let taxonomy = state.taxonomies[state.route.name]; if ( taxonomy ) { let restBase = taxonomy.rest_base; let Collection = wp.api.getCollectionByRoute( `/wp/v2/${restBase}` ); let key = taxonomy.slug; let slugs = state.route.params[key].split( '/' ); let slug = slugs.pop(); let terms = await( new Collection() ).fetch({ data: { slug: slug } }); queriedObject = terms[0]; posts = await getPosts( createPostsArguments( state, { [restBase]: queriedObject.id }) ); return { queriedObject, posts,hasMore: getCollectionInstance( 'Posts' ).hasMore() }; } return { queriedObject: {}, posts: {}, hasMore: false }; }; export const home = async({ state }) => { let queriedObject = {}; if ( global.themeSettings.pageOnFront ) { queriedObject = await getPost( global.themeSettings.pageForPosts, 'Page' ); } let posts = await getPosts( createPostsArguments( state ) ); return { queriedObject, posts, hasMore: getCollectionInstance( 'Posts' ).hasMore() }; }; export const author = async({ state }) => { let queriedObject = await getUserbySlug( state.route.params.author ); let posts = await getPosts( createPostsArguments( state, { author: queriedObject.id }) ); return { queriedObject, posts, hasMore: getCollectionInstance( 'Posts' ).hasMore() }; }; export const singular = async({ state }) => { let posts = []; let queriedObject = {}; // for singular if ( state.route.query.preview ) { if ( state.route.query.preview_id ) { queriedObject = await getPost( state.route.query.preview_id ); posts = [ queriedObject ]; } else if ( state.route.query.p ) { queriedObject = await getPost( state.route.query.p ); posts = [ queriedObject ]; } else if ( state.route.query.page_id ) { queriedObject = await getPost( state.route.query.page_id, 'Page' ); posts = [ queriedObject ]; } } else { switch ( state.route.name ) { case 'front-page': { queriedObject = await getPost( global.themeSettings.pageOnFront, 'Page' ); posts = [ queriedObject ]; break; } case 'page': if ( state.route.params.pagename ) { let pagenames = state.route.params.pagename.split( '/' ); let pagename = pagenames.pop(); posts = await getPosts({ slug: pagename }, 'Pages' ); if ( 0 < posts.length ) { queriedObject = posts[0]; break; } if ( ! global.themeSettings.useVerbosePageRules ) { break; } } // if not found page, search post. case 'post': { if ( state.route.params.postname || state.route.params.pagename ) { posts = await getPosts({ slug: state.route.params.postname || state.route.params.pagename }); if ( 0 < posts.length ) { queriedObject = posts[0]; } } else { queriedObject = await getPost( state.route.params.post_id, 'Post' ); posts = [ queriedObject ]; } break; } } } return { queriedObject, posts, hasMore: false }; }; 41

Slide 42

Slide 42 text

const getUserbySlug = async( slug ) => { let users = await( new wp.api.collections.Users() ).fetch({ data: { slug: slug } }); if ( users[0]) { return users[0]; } return {}; }; const getPost = async( id, type = 'Post' ) => { let Model = wp.api.models[type]; let model = new Model({ id: id }); return await model.fetch(); }; let instances = []; const getCollectionInstance = ( type ) => { let create = () => { if ( instances[type]) { return instances[type]; } let Collection = wp.api.collections[type]; instances[type] = new Collection(); return instances[type]; }; return create(); }; const getPosts = async( data, type = 'Posts' ) => { return await getCollectionInstance( type ).fetch({ data: data }); }; const createPostsArguments = ( state, param = {}) => { let page = state.route.params.page || 1; let perPage = global.themeSettings.postsPerPage let data = { page: page, per_page: perPage }; return Object.assign( data, param ); }; 42

Slide 43

Slide 43 text

import types from '../mutation-types'; import * as query from './query.js'; export const initialize = async({ commit, state }) => { await fetchSiteOption({ commit, state }); await fetchTypes({ commit, state }); await fetchTaxonomies({ commit, state }); await fetchPosts({ commit, state }); }; export const fetchSiteOption = async({ commit }) => { let response = await fetch( global.wpApiSettings.root ); let data = await response.json(); commit( types.SET_SITE_OPTION, data ); }; export const fetchTypes = async({ commit }) => { let postTypes = await( new wp.api.collections.Types() ).fetch(); commit( types.SET_POST_TYPES, postTypes ); }; export const fetchTaxonomies = async({ commit }) => { let taxnomies = await( new wp.api.collections.Taxonomies() ).fetch(); commit( types.SET_TAXONOMIES, taxnomies ); }; export const fetchPosts = async({ commit, state }) => { let result = { queriedObject: {}, hasMore: false, posts: [] }; let routeName = state.route.name; if ([ 'day', 'month', 'year' ].includes( routeName ) ) { result = await query.date({ state }); } else if ([ 'home' ].includes( routeName ) ) { result = await query.home({ state }); } else if ( Object.keys( state.taxonomies ).includes( routeName ) ) { result = await query.term({ state, taxonomyName: routeName }); } else if ([ 'author' ].includes( routeName ) ) { result = await query.author({ commit, state }); } else if ( state.route.query.preview || [ 'front-page', 'page', 'post' ].includes( routeName ) ) { result = await query.singular({ state }); } commit( types.SET_QUERIED_OBJECT, result.queriedObject ); commit( types.SET_HASMORE, result.hasMore ); commit( types.SET_POSTS, result.posts ); commit( types.SET_TEMPLATE_TYPE ); }; 43

Slide 44

Slide 44 text

ؾ߹͍Ͱ WP_Query::parse_query ͷؾ࣋ͪʹͳΕ͹ͳΜͱ ͔ͳΔ͔΋͠Εͳ͍ɻ • ؾ߹͍ͰͳΜͱ͔͸ͳΔɻ͜ΕͰ΋શવ଍Γͳ͍͚Ͳɻɻɻ • get_post ͱ͔ɺget_posts Έ͍ͨͳͷ΋౰વ JS ଆͰ࣮૷͢Δඞཁɻ • wp.api.getCollectionByRoute, wp.api.getModelByRoute ͱ͔࢖͑͹ ΧελϜ౤ߘͱ͔ͱ΋ઓ͑Δ͸ͣɻ • λΫιϊϛʔ͸࣮૷ग़དྷͨؾ͕͢Δɻ • Backbone.jsϕʔεͩ͠jQuery΋ಡΈࠐΉ͚Ͳɺೝূͱ͔ߟ͑ͳͯ͘ྑ͍ ͷ͸ָɻ 44

Slide 45

Slide 45 text

Other tips • άϩʔόϧͳঢ়ଶʹґଘ͢ΔϞϊ͕ଟ͍ͷͰɺVuex ͱ͔ Redux ͱ͔࢖ͬͨํָ͕ɻ • query_vars ΍ɺqueried_object Έ͍ͨͳͷ͸࡞Βͳ͍ ͱ೉͍͠ͱࢥ͏ɻ 45

Slide 46

Slide 46 text

46

Slide 47

Slide 47 text

• Workbox ͕ศརͩͬͨɻ • Service Worker Ͱ ΦϑϥΠϯαϙʔτ͢ΔͳΒ͜Εศརɻ 47

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

FullSpec ͷ σϝϦοτ • ΊͬͪΌ͍ͨ΁Μɻؾ߹͍ ͕ඞཁɻ • ςϯϓϨʔτλάతͳϞϊ͸શ࣮ͯ૷͠ͳ͍ͱ͍͚ͳ͍ɻ͖΄Μඇಉظɻ • SQL࢖͑ͳ͍ɻ೥ผΞʔΧΠϒͷҰཡͱ͔͸JSͰ͸ਏ͍ɻ • ௨ৗͷREST APIͰऔΕͳ͍஋(Φϓγϣϯɺϝλσʔλ)͸APIΛ֦ு͢Δ ͔ɺPHP͔ΒJSʹ஋Λ౉͢ͳͲɺͱʹ͔͍͘Ζ͍Ζ΍Βͳ͍ͱ͍͚ͳ͍ɻ • JS ͷboilerplateతͳͷ͸࢖͍ʹ͍͘ɻWebpackྗඞཁ • jQuery Λ࢖ͬͯγϣʔτίʔυͰŋŋŋΈ͍ͨͳϓϥάΠϯ͸શ෦ࢮ͵ɻ 49

Slide 50

Slide 50 text

·ͱΊ • ϒϩάʹશ෦ॻ͍ͨɿ Vue.js ͱ REST API Ͱ WordPress ͷςʔϚΛ࡞ͬͯ·͢ɻ – Toro_Unit • WordPress ͬΆ͍ΞʔΩςΫνϟͰ PWA ΋·͊ग़དྷΔɻ • ςʔϚͰͷ SPA ͸ۀ຿ϨϕϧͳΒɺଟ෼ग़དྷΔɻʢػೳΛݶఆग़དྷΔͷͰ͋Ε͹ʣ • ഑෍͢ΔςʔϚͩͱ͠ΜͲͦ͏ɻ • WP API Ͱऔͬͯ͘ΔͷͱɺWP_Query Ͱऔͬͯ͘Δͷ͸ඍົʹҧ͏ͷͰɺશͯ αϙʔτͬͯͷ͸ΊͬͪΌ͠ΜͲͦ͏ɻ • ύʔϚϦϯΫͷਂ෵͸ਂ͍ɻ͍ͬͯ͏͔ WordPress ͷਂ෵͸ਂ͍ɻ 50

Slide 51

Slide 51 text

• torounit/smart-pwa • ৮ͬͯΈͯԼ͍͞ɻελʔԼ͍͞ɻ • ๻͕ਲͬ෷͏લʹฉ͍ͯԼ͍͞ɻ 51

Slide 52

Slide 52 text

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