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

WordPress で PWA へ至る道

WordPress で PWA へ至る道

「WordBench大阪 & 京都 & Ionic Japan 5月 フルスイング」登壇資料です。

More Decks by Toro_Unit (Hiroshi Urabe)

Other Decks in Technology

Transcript

  1. PWA ΁ࢸΔಓ
    Toro_Unit @2018.05.05 / WBOsaka
    1

    View Slide

  2. $ whoami
    2

    View Slide

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

    View Slide

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

    View Slide

  5. 5

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  9. 9

    View Slide

  10. 10

    View Slide

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

    View Slide

  12. §1. Minimum PWA on WordPress
    12

    View Slide

  13. 13

    View Slide

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

    View Slide

  15. SEYANA
    Θ͔Δ
    15

    View Slide

  16. 16

    View Slide

  17. 17

    View Slide

  18. 18

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  23. 23

    View Slide

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

    View Slide

  25. ΍ͬͨ͜ͱɻ
    25

    View Slide

  26. /**
    * Service Worker
    *
    * @package Smart_PWA
    */
    ?>
    '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

    View Slide

  27. 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

    View Slide

  28. • 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

    View Slide

  29. DEMO.
    https://torounit.com
    29

    View Slide

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

    View Slide

  31. §2. FullSpec PWA on WordPress
    31

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  35. ͭͬͨ͘ɻ
    DEMO
    35

    View Slide

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

    View Slide

  37. URL ͷ ύʔεਏ͍໰୊ɻ
    37

    View Slide

  38. 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

    View Slide

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

    View Slide

  40. 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

    View Slide

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

    View Slide

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

    View Slide

  43. 43

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide