📰

WordPress + Vue.js 3.2 + TypeScript の開発環境を Docker Compose を使って用意する

2021/11/04に公開

Vue.js を使っていると Firebase のような BaaS だったり Serverless な環境ばかりになっていて、バックエンドの環境構築をする機会がしばらくありませんでした。
久しぶりに WordPress を扱うことになり、せっかくなので利用機会の少ない Docker Compose を使って環境構築をしようかなと。
WordPress で構築したサイト内で JavaScript で実装したい機能がいくつかあるので Vue.js 3.2 を動かすところまでやってみましたので一連の流れを記録しておきます。

環境構築の全体的な流れ

WordPress は Docker Compose で

WordPress は PHP, MySQL で動作しているため、普段使いの Mac に 少なくとも MySQL を入れる必要があります。また PHP が入っていることは知っていますが、バージョン等がどうなっているか分かりません。
以前 PHP で開発していたころから随分と時間が経ち、いまやほとんどは Docker のような閉じた開発環境を効率的に用意するというのが一般的だろうということで、利用頻度の著しく低かった Docker について軽く調べたところ Docker Compose というので良さそうです。
docker-compose.yml という GitHub Actions と同じようなものがあればすぐに立ち上げられそうだということが分かり、これを利用することにしました。

Vue.js は、まずは CDN 版で

自分で Vue.js の環境を用意するときは Nuxt を利用するため npx nuxi init のような始め方しかしていません。WordPress に載せるというときに、はたと「どうやって動かすのだろう🤔」と疑問に思いました。
なんとかなるとは思っていますが、まずは直接 index.php などに <script> タグで読み込んでしまい、最低限の動作を確認するところを一旦のゴールにします。

結局使いませんでしたが petite-vue という軽量 Vue.js もありますしね。

Vue.js + TypeScript の環境をいつもどおりに用意

結論から言うと CDN 版があっさり動きました。となると今度は TypeScript で書きたいという思いが湧いてきます。
そうなると当然にトランスパイルすることになるので、いつもどおりに node.js を使って動かすことになるでしょう。というわけで結果的にいつもどおりに利用できるようにしました。

WordPress を Docker Compose で

どうやら Docker Compose というのは複数の Docker 環境で用意されたアプリケーションを扱うためのツールのようです。
なるほど docker-compose.yml に記述した内容を docker-compose up してあげればすべて整うと。便利すぎる。

公式の docker-compose.yml

Docker WordPress で検索すると Docker 公式サイトの クィックスタート: Compose と WordPress が見つかりました。

これをターミナルで docker-compose up -d とコマンドを実行すれば OK なのでしょう。
(すでに Docker for Mac 等がインストールされている前提で話を進めます。まだの方は、公式サイトよりダウンロードしインストールしておいてください)
-d オプションはデタッチモードのことで「コンテナはバックグラウンドで起動し、そのまま実行し続けます」とのこと。毎回つけて良さそうです。

cd ~/.gitrepos/samples   # いつもお試しするときに利用しているフォルダへ移動
mkdir wordpress          # WordPress 用のフォルダを作成
cd wordpress

プロジェクト用のフォルダ内に docker-compose.yml を作成する

今回は wp-docker-sample というフォルダ名にしたいと思います。

mkdir wp-docker-sample
code wp-docker-sample    # VS Code を起動

関係ないですが code コマンドで VS Code が起動するのは本当に便利ですね。
以降の操作は VS Code 内でターミナルで行えば「どのタブだっけ?」ということがなくなります。

フォルダ内に docker-compose.yml というファイルを作成し、上述の クィックスタート: Compose と WordPress 内のコードをそのまま貼り付けて保存します。

はじめての docker-compose up -d

VS Code 内に新しいターミナルを開き、次のコマンドを実行します。

docker-compose up -d

とくにエラー表示がなく Started のようになれば起動したようです。あっさり。
ブラウザで http://localhost:8000 を開くと WordPress の言語選択画面が表示されましたね! 🎉

いやあ、驚きの簡単さです。

さて、つづいては…

まずはコードを見てみましょう。
Docker アプリが起動していると思いますので Containers / Apps タブを開き、プロジェクト名を確認します(ここでは wp-docker-sample です)

▼ wp-docker-sample
      wp-docker-sample-db-1         # mysql:5.7
    wp-docker-sample-wordpress-1  # wordpress:latest

のようになっていると思います。
wp-docker-sample-wordpress-1 にカーソルを合わせると CLI のアイコンが表示されますのでクリックしターミナルを立ち上げます。
すると Docker 内の /var/www/html フォルダが表示され ls して WordPress の各ファイルがあることが確認できます。

このあとの工程としては VS Code から編集できるよう、フォルダをマウントするのでしょう。
しかし、想像するに、誰かがもっと開発しやすいように準備してくれていると思います。
慣れてない僕が試行錯誤するより、公開してくれていると思われるそれを活用したいですね。

今回ありがたく使わせていただいた docker-compose.yml

WordPress 開発環境 Docker ssh で検索したところ次の記事が見つかりました。
https://qiita.com/ryo2132/items/d75e1846aa181676406e

TypeScript や Vue.js の記事でもよく拝見する @KawamataRyo さんの記事です。

.env に記述した内容に従い環境構築してくれるため管理しやすそうです。
さらにデプロイするための Wordmove というツールもあり、そもそも存在を知らなかったのでとても助かります。

というわけで同じように docker-compose up -d します。
リポジトリはQuick-start-wordpress-dockerです。

cd ~/.gitrepos/samples/wordpress
git clone git@github.com:kawamataryo/quick-start-wordpress-docker.git quick-start-wordpress-docker-sample
code quick-start-wordpress-docker-sample

VS Code が開いたら .env の内容を編集しますが、その前に git のリポジトリを混同しないように origin を変更しておきます。

git remote rename origin template  # origin → template に変更
gh repo create                     # 自分の GitHub リポジトリを作成
git branch -M main                 # 主ブランチを main に変更
git push -u origin main            # main ブランチをプッシュ

これで template:master がclone元のリモートブランチで origin:main が自分の GitHub アカウント上のリモートリポジトリのブランチになりました。

ghGitHub CLI のコマンドです

.env の編集ですが、まだ本番環境については分からないので先頭のみ編集します( PRODUCTION_NAME 以外はデフォルトで良いでしょう)

# -------------------------------------------
# wordpress・mysqlコンテナの設定
# -------------------------------------------
# プロダクトの名前 作成されるcontainer名の接頭語として使用
PRODUCTION_NAME=coedo
# local wordpressを紐付けるPort名(ex: 8080)
LOCAL_SERVER_PORT=8080
# localのmysqlを紐付けるPort名(ex: 3306)
LOCAL_DB_PORT=3306

保存したら docker-compose up -d し、ブラウザで http://localhost:8080 を開くと、動作していることが確認できますね! 🎉

初期設定後、管理画面を開けば見慣れた管理画面が表れます。
このあたりで一度 git commit しておきましょう。

子テーマの作成

WordPress での開発をする際は、テーマを直接編集するのではなく、一般的に子テーマを使いますね。
といっても、子テーマの作成をするのも1億年ぶりくらいなので、手順を確認しつつ進めます。
(自分自身の備忘録を兼ねているので趣旨とずれるので遠慮なく読み飛ばしてくださいませ)

Vue.js を入れるのも、簡易的に CDN 版を読み込むことにするので、まずはデフォルトのテーマ Twenty Twenty-One の子テーマを作成します。

https://developer.wordpress.org/themes/advanced-topics/child-themes/

概ね次のステップでした。

  • 子テーマフォルダを wp-content/themes 内に作成
  • style.css のコメントで子テーマの情報を記述
  • functions.php で子テーマの読み込み
  • 管理画面で作成した子テーマを選択

子テーマフォルダを wp-content/themes 内に作成

子テーマのフォルダ名は、親テーマのフォルダ名に -child をつけるのがベストプラクティスとあります。(どのテーマの子テーマかというのがフォルダ名だけで分かるのと、一覧したときに親テーマと子テーマでフォルダが並ぶからですかね🤔)
というわけで今回は twentytwentyone-child としました。

style.css のコメントで子テーマの情報を記述

style.css の先頭にコメントで子テーマの情報を記述します。
今回の子テーマの名称は Co-Edo としました。

wp-content/themes/twentytwentyone-child/style.css
/*
Theme Name: Co-Edo
Version: 1.6
Template: twentytwentyone
*/

Template は親テーマのフォルダ名です。ここ試験に出ます。(僕は最初ここを間違えてうまく表示されずに困りました)

functions.php で子テーマの読み込み

WordPress といえば functions.php ですね。
古い記憶とは作業内容が変わっていました。どうやら @import で読み込む方式より変更になったようです。

wp-content/themes/twentytwentyone-child/functions.php
<?php

function theme_enqueue_styles() {
  wp_enqueue_style( 'parent-style', get_template_directory_uri() . '/style.css' );
  wp_enqueue_style( 'child-style', get_stylesheet_directory_uri() . '/style.css', array('parent-style'));
}
add_action( 'wp_enqueue_scripts', 'theme_enqueue_styles' );

ここまでの作業に問題がなければ、管理画面で作成した子テーマを選択できるようになっているはずです。
(表示されない場合は、上記の何らかの箇所に問題があります)

といっても、今のままだと親テーマとまったく同一ですので index.php を作成し、子テーマが読み込まれていることを確認しましょう。

cp wp-content/themes/twentytwentyone/index.php wp-content/themes/twentytwentyone-child/
wp-content/themes/twentytwentyone-child/index.php
// 17行目付近の get_header() した直後に挿入
get_header(); ?>

<div id="app" style="text-align: center;">
  <p>
    テスト
  </p>
</div>

ヘッダーの下に テスト とセンタリングして表示されれば子テーマの読み込み確認は無事終了です。
id="app" は Vue.js で使うので、今のうちに記述してます。

CDN の Vue.js を利用する

一般的に Vue.js のような宣言的UIでリアクティブな値を扱うライブラリは npm 等のパッケージ管理ソフトを使ってインストールし利用するはずです。

Vue.jsReact も CDN にホストされている js ファイルを直接 <script> タグに入れてそのまま利用することもできます。

基本的には次のように html ファイルのなかに <script> タグを記述し利用できます。

<script src="https://unpkg.com/vue@next"></script>

利用する箇所では Vue. をつける

注意点としてはおなじみのメソッド等の前には Vue. をつけるということです。
Vue 3 であれば Composition API や setup() も利用可能です。

<script>
Vue.createApp({
  setup() {
    const message = Vue.ref('WordPress に Vue.js')
    return { message }
  },
}).mount("#app")
</script>

もちろん利用する場所では通常通りマスタッシュ記法で {{ message }} のように記述します。

WordPress で <script> を記述する

子テーマ作成時に style.cssfunctions.php で設定したように <script> も同様に行います。
functions.php の末尾で読み込みましょう。

wp-content/themes/twentytwentyone-child/functions.php
// Vue.js 3
function add_vuejs() {
  wp_enqueue_script('vue', 'https://unpkg.com/vue@next');
}
add_action( 'wp_enqueue_scripts', 'add_vuejs' );

さきほどと同様に index.php を編集します。

wp-content/themes/twentytwentyone-child/index.php
get_header(); ?>

<div id="app" style="text-align: center;">
  <p>
    テスト
  </p>
  <p style="min-height: 2em; line-height: 2em;">
    {{ message }}
  </p>
  <div>
    <input type="text" v-model="message" />
  </div>
</div>
<script>
const app = Vue.createApp({
  setup() {
    const message = Vue.ref('WordPress に Vue.js 3 を入れました。')
    return { message }
  },
}).mount("#app")
</script>

<input>v-model にもリアクティブな変数 message を入れましたので、入力欄を書き換えると、そのすぐ上の {{ message }} の箇所も同じ値に書き換わるでしょう。

無事 CDN 版の Vue.js 3.2 を入れることができました! 🎉

WordPress で Vue.js + TypeScript

ここまで予想以上にすんなりいったので、欲張って TypeScript で書けないかと思い調べました。

まずはJavaScript ビルド環境のセットアップのページが見つかりました。

node.js や npm / yarn は利用済みとして説明をします
以下 npm のコマンドで記述

https://ja.wordpress.org/team/handbook/block-editor/how-to-guides/javascript/js-build-setup/

まず npm init をするわけですが、はたと困ったことが、これはどこでやれば良いのでしょうね。
node_modules.gitignore に追記するとしても、開発経験が浅すぎてどこで実行すればよいかが分かりません。

いろいろ試行錯誤しようと、思いきって子テーマのフォルダのなかで実行することにしました。

cd wp-content/themes/twentytwentyone-child/
npm init

package.json を編集後、開発環境で @wordpress/scripts を利用します。

npm install --save-dev --save-exact @wordpress/scripts

Vue.js と TypeScript のトランスパイラをインストールします。
(このふたつとも -D で良かったかも…)

npm install vue@next ts-loader

package.json, tsconfig.json, webpack.config.js はおそらくこんな感じ。

wp-content/themes/twentytwentyone-child/package.json
{
  "main": "build/index.js",
  "scripts": {
    "start": "wp-scripts start",
    "build": "wp-scripts build",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "devDependencies": {
    "@wordpress/scripts": "19.0.0"
  },
  "dependencies": {
    "ts-loader": "^9.2.6",
    "vue": "^3.2.20"
  }
}

wp-content/themes/twentytwentyone-child/tsconfig.json
{
  "compilerOptions": {
    "noImplicitAny": false,
    "noEmitOnError": true,
    "removeComments": false,
    "sourceMap": true,
    "target": "es5",
    "outDir": "./build/"
  },
  "include": [
    "src/**/*"
  ]
}
wp-content/themes/twentytwentyone-child/webpack.config.js
const path = require('path')

module.exports = {
  mode: 'development', // "production" | "development" | "none"

  // ローカル開発用環境を立ち上げる
  // 実行時にブラウザが自動的に localhost を開く
  // devServer: {
  //   contentBase: "build",
  //   open: true
  // },

  // メインとなるJavaScriptファイル(エントリーポイント)
  entry: './src/index.ts',

  output: {
    path: path.resolve(__dirname, "build"),
    filename: "index.js",
  },

  module: {
    rules: [{
      // 拡張子 .ts の場合
      test: /\.ts$/,
      // TypeScriptをコンパイルする
      use: 'ts-loader',
    }]
  },

  // import文で .tsファイルを解決するため
  resolve: {
    modules: [
      "node_modules", // node_modules内も対象とする
    ],
    extensions: [
      '.ts',
      '.js', // node_modulesのライブラリの読み込みに必要
    ],
  },
}

webpack.config.jsTypeScriptを用いたWEBサイトをWebpackを用いて構築するを参考にさせていただきました m(_ _)m

まずは JavaScript で確認します。
webpack.config.jsentry を一旦 './src/index.js' にしてください)

mkdir src

functions.php を書き換え

wp-content/themes/twentytwentyone-child/functions.php
// add_action( 'wp_enqueue_scripts', 'add_vuejs' );
// npm
function add_npm() {
    wp_enqueue_script( 'npm', get_theme_file_uri('/build/index.js') );
}
add_action( 'wp_enqueue_scripts', 'add_npm');

index.php に書いていたものを src/index.js に移動します。

wp-content/themes/twentytwentyone-child/src/index.js
import { createApp, ref } from 'vue'
// window.onload が必要?
window.onload = () => {
  const app = createApp({
    setup() {
      console.log('テスト npm Vue.js 3.2! at setup')
      const message = ref('WordPress に npm で Vue.js 3.2 を入れました。')
      return {
        message,
      }
    },
  }).mount('#app')
}

ビルドし、

npm run build

動作することを確認します。

npm run start

うまく動作することを確認できたら、拡張子を .ts に変更し src/index.tsbuild/index.js に書き出されることを確認しましょう。

console.log() がうまく動いていたら無事 Vue.js 3.2 の動作確認が完了です! 🎉

TypeScript での開発を楽しみましょう🙂

SFC(単一ファイルコンポーネント)を利用する設定を追加する

だんだんと欲は出てくるもので、なるべくいつもと変わらない開発ができると良いなあと思いますね。

というわけで .vue ファイルを使用する記述を追加することにします。

webpack がビルド時に .vue ファイルを適切にコンパイルしてくれればよいので、基本的には webpack.config.js の設定ということになります。
まずは必要なものをインストールします。

npm i -D @vue/compiler-sfc vue-loader@next

つづいて webpack の設定を追記します。

wp-content/themes/twentytwentyone-child/webpack.config.js
const path = require('path')
const { VueLoaderPlugin } = require('vue-loader') // 追加

module.exports = {
  mode: 'development', // "production" | "development" | "none"

  // メインとなるJavaScriptファイル(エントリーポイント)
  entry: './src/index.ts',

  output: {
    path: path.resolve(__dirname, "build"),
    filename: "index.js",
  },

  module: {
    rules: [{
      // 拡張子 .ts の場合
      test: /\.ts$/,
      // TypeScriptをコンパイルする(変更)
      loader: 'ts-loader',
      options: {
        appendTsSuffixTo: [/\.vue$/],
      },
    // 追加
    }, {
      test: /\.vue$/,
      loader: 'vue-loader',
    }]
  },

  // 追加
  plugins: [
    new VueLoaderPlugin(),
  ],

  // import文で .tsファイルを解決するため
  resolve: {
    modules: [
      "node_modules", // node_modules内も対象とする
    ],
    extensions: [
      '.ts',
      '.js', // node_modulesのライブラリの読み込みに必要
    ],
  },
}

型定義ファイルを作成する

wp-content/themes/twentytwentyone-child/src/types/shims-vue.d.ts
declare module "*.vue" {
  import { defineComponent } from "vue"
  const component: ReturnType<typeof defineComponent>
  export default component
}

通常通り Component を利用する

wp-content/themes/twentytwentyone-child/src/components/TheTest.vue
<script setup lang="ts">
const msg = '子コンポーネント at setup'
console.log(msg)
</script>

<template>
  <div>{{ msg }}</div>
</template>

<script setup> も使えます。

wp-content/themes/twentytwentyone-child/src/index.ts
import { createApp } from 'vue'
import TheTest from './components/TheTest.vue'

window.addEventListener('load', () => {
  const app = createApp({
    components: {
      TheTest,
    },
    setup() {
      console.log('親コンポーネント at setup')
    },
  }).mount('#app')
})

<the-test /> のように子コンポーネントを読み込んで、コンソールや画面の表示で確認できたら完了です🎉

その他

SFC 内に style タグを使う場合は vue-style-loader も追加します。

npm install -D vue-style-loader

webpack.config.jsmodule.rules に次の記述も追記します。

    }, {
      test: /\.css$/,
      use: ['vue-style-loader', 'css-loader'],

上述の設定のいずれかがうまくできていない場合は、おそらく次のようなエラーメッセージのどれかが表示されるはずです。

Vue packages version mismatch: vue-template-compiler

You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file

PHP のデータを JavaScript に引き渡す

WordPress が提供しているデータを JavaScript で使用したい場合があります。

wp_localize_script() を使用することでとても簡単に利用することが可能です。

functions.php にて、利用したい変数を渡す

wp-content/themes/twentytwentyone-child/functions.php
function add_npm() {
    wp_enqueue_script( 'npm', get_theme_file_uri('/build/index.js') );
    global $post;
    wp_localize_script('npm', 'serverProps', [
        'currentUser' => wp_get_current_user(),
        'postData' => get_post($post->ID),
        'postOptions' => get_post_custom($post->ID),
    ]);
}
add_action( 'wp_enqueue_scripts', 'add_npm');

関数内で $post を使用するため global で呼び出します。
第一引数にはコードを差し込むスクリプトを指定します(通常は wp_enqueue_script() するときと同じものとなります)
第二引数には JavaScript 側のオブジェクト名を指定します(上記の場合は console.log(serverProps.currentUser) のように使用できます)
第三引数には、JavaScript 側でオブジェクトとして利用したい連想配列を渡します。

型定義ファイルを作成する

利用時に型の補完がされるようにします。

wp-content/themes/twentytwentyone-child/src/types/index.d.ts
interface ServerProps {
  currentUser?: {
    ID: number
    caps: {
      administrator: boolean
    }
    data: {
      ID: number
      display_name: string
      user_email: string
      user_login: string
      user_nickname: string
      user_registered: string // "2021-12-23 04:54:48"
      user_status: string
      user_url: string
    }
    roles: Array<string> // "administrator"
    // 以下略
  }
  postData?: {
    ID: number // 29404
    post_author: string // "1"
    post_date: string // "2020-09-27 09:52:16"
    post_date_gmt: string // "2020-09-27 00:52:16"
    post_content: string
    post_title: string
    // 以下略
  }
  postOptions?: {
    original_custom_field: Array<string> // [ "1" ],
  }
}

declare const serverProps: ServerProps

利用する

wp-content/themes/twentytwentyone-child/src/index.ts
const { currentUser } = serverProps
console.log(currentUser)

オブジェクトはグローバルに読み込まれるようです。
ブラウザの Dev Tools でも確認可能です。

誰でも簡単に確認できるということですので、ユーザーがアクセスしてはいけないデータを渡すことのないようにしましょう。
wp_get_current_user() では user_pass も見れますが、ハッシュ化されているためパスワードそのものを取得することはできません)
セキュリティ上、引き渡すべきではないデータがありましたら、コメント等で教えてくださいませ。

Discussion