【WordPress】 @wordpress/scriptsとStylusでカスタムブロック自作環境構築 

Gutenbergブロックエディターのカスタムブロックを自作したい。しかもSassではなく**Stylus**で。という、Stylus大好きスタイラサーのための環境構築編です。```@wordpress/scripts``` をベースとなります。

ベーシックな部分はこちらを参考にさせていただきました。加えてSassに関連する部分をStylus版に変更した感じです。

[【WordPress】カスタムブロックの作り方を書いてみた - Qiita -](https://qiita.com/Kodak_tmo/items/bf011e31db4293db8312)

## ディレクトリ構成図

```
my-block-plugin
  ├ node_modules
  ├ public (公開フォルダ)
  │   ├ assets (buildフォルダ)
  │   └ my-block-plugin.php (プラグインのメインファイル）
  ├ src
  │   ├ editor.js (エディター用JS)
  │   ├ editor.styl (エディター用CSS)
  │   ├ script.js (エディター・フロント両用JS)
  │   └ style.styl (エディター・フロント両用CSS)
  ├ package.json
  ├ webpack.config.js
  └ (yarn.lock)
```

## package.json


<details><summary>package.json （クリックで全体を表示)</summary><div>

```package.json
{
  "name": "my-block-plugin",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "wp-scripts build --mode=development --config webpack.config.js --watch",
    "build": "wp-scripts build --mode=production --config webpack.config.js"
  },
  "author": "Watashi",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.10.4",
    "@babel/preset-env": "^7.10.4",
    "@babel/preset-react": "^7.10.4",
    "@lipemat/css-mqpacker": "^8.0.1",
    "@wordpress/block-editor": "^4.3.1",
    "@wordpress/blocks": "^6.20.1",
    "@wordpress/browserslist-config": "^2.7.0",
    "@wordpress/components": "^10.0.1",
    "@wordpress/dependency-extraction-webpack-plugin": "^2.8.0",
    "@wordpress/editor": "^9.20.1",
    "@wordpress/element": "^2.16.0",
    "@wordpress/scripts": "^12.1.1",
    "autoprefixer": "^9.8.5",
    "babel-loader": "^8.1.0",
    "clean-webpack-plugin": "^3.0.0",
    "css-declaration-sorter": "^5.1.2",
    "css-loader": "^3.6.0",
    "mini-css-extract-plugin": "^0.9.0",
    "optimize-css-assets-webpack-plugin": "^5.0.3",
    "path": "^0.12.7",
    "postcss-combine-duplicated-selectors": "^9.0.0",
    "postcss-loader": "^3.0.0",
    "sort-css-media-queries": "^1.5.0",
    "stylus": "^0.54.7",
    "stylus-loader": "^3.0.2",
    "terser-webpack-plugin": "^3.0.6",
    "webpack": "^4.43.0",
    "webpack-cli": "^3.3.12",
    "webpack-fix-style-only-entries": "^0.5.1",
    "webpack-merge": "^5.0.9"
  },
  "browserslist": [
    "extends @wordpress/browserslist-config"
  ]
}

```

</div></details>

モジュールをざっくり分類するとこんな感じです。

<details><summary>モジュールをざっくり分類 (クリックで全体を表示)</summary><div>

```
WordPress公式
@wordpress/block-editor
@wordpress/blocks
@wordpress/browserslist-config 
@wordpress/components 
@wordpress/dependency-extraction-webpack-plugin 
@wordpress/editor 
@wordpress/element 
@wordpress/scripts 

JS/BABEL関連
@babel/core
@babel/preset-env 
@babel/preset-react 
babel-loader 
terser-webpack-plugin 

Stylus/CSS関連
@lipemat/css-mqpacker 
autoprefixer 
css-declaration-sorter
css-loader 
mini-css-extract-plugin 
optimize-css-assets-webpack-plugin
postcss-combine-duplicated-selectors
postcss-loader
sort-css-media-queries 
stylus
stylus-loader 
webpack-fix-style-only-entries 

その他
clean-webpack-plugin 
path 
webpack 
webpack-cli 
```

</div></details>

モジュールごとの役割は ```webpack.config.js```のコメントにも書いてあります。

今回は使用していませんが、 ```img-loader```, ```file-loader```, ```url-loader```, ```resolve-url-loader``` なども、もちろんSass同様に使えます。


## webpack.config.js

<details><summary>webpack.config.js (クリックして全体を表示)</summary><div>

```webpack.config.js
const DependencyExtractionWebpackPlugin = require('@wordpress/dependency-extraction-webpack-plugin')
const FixStyleOnlyEntriesPlugin         = require('webpack-fix-style-only-entries')
const MiniCssExtractPlugin              = require('mini-css-extract-plugin')
const OptimizeCSSAssetsWebpackPlugin    = require('optimize-css-assets-webpack-plugin')
const TerserWebpackPlugin               = require('terser-webpack-plugin')
const autoprefixer                      = require('autoprefixer')
const path                              = require('path')
const {CleanWebpackPlugin}              = require('clean-webpack-plugin')

// For PostCSS
const cssDeclarationSorter              = require('css-declaration-sorter')
const cssMqpacker                       = require('@lipemat/css-mqpacker')
const postCssCombineDuplicatedSelectors = require('postcss-combine-duplicated-selectors')
const sortCssMediaQueries               = require('sort-css-media-queries')

module.exports = (env, argv) => {
  // モードが development なら trueを返す
  function isDevelopment () {
    return argv.mode === 'development'
  }

  var config = {
    entry: {
      editor: path.resolve(__dirname, 'src', 'editor.js'),
      script: path.resolve(__dirname, 'src', 'script.js'),
      style: path.resolve(__dirname, 'src', 'style.styl')
    },

    output: {
      filename: '[name].js',
      path: path.resolve(__dirname, 'public', 'assets')
    },

    optimization: {
      minimizer: [
        // JavaScriptを圧縮する
        new TerserWebpackPlugin({
          // sourceMapをmodeによって削除する
          sourceMap: isDevelopment()
        }),
        // CSSを圧縮する
        new OptimizeCSSAssetsWebpackPlugin(
          {
            cssProcessorOptions: {
              map: {
                inline: false,
                annotation: true
              }
            }
          }
        )
      ]
    },

    plugins: [
      // ビルドの度にビルドフォルダーを削除する
      new CleanWebpackPlugin(),

      // assets.phpファイルを出力する
      new DependencyExtractionWebpackPlugin(),

      // CSSをentryに指定したとき同名の不要なjsが出力されないようにする
      new FixStyleOnlyEntriesPlugin({
        extensions: ['styl', 'css']
      }),

      // CSSファイルを別ファイルで出力する
      new MiniCssExtractPlugin({
        filename: '[name].css'
      })
    ],

    devtool: 'source-map',

    module: {
      rules: [
        {
          test: /\.js$/,
          exclude: /node_modules/,
          use: {
            loader: 'babel-loader',
            options: {
              presets: [
                '@babel/preset-env',
                [
                  '@babel/preset-react',
                  {
                    // 通常は'React.createElement.xxxx'を記述しますが、WordPressのブロックエディターはWordPress用のReactを使用するため、以下のように記述します
                    'pragma': 'wp.element.createElement',
                    'pragmaFrag': 'wp.element.Fragment',
                    'development': isDevelopment()
                  }
                ]
              ]
            }
          }
        },
        {
          test: /\.(styl|css)$/,
          use: [
            {
              loader: MiniCssExtractPlugin.loader
            },
            {
              loader: 'css-loader'
            },
            {
              loader: 'postcss-loader',
              options: {
                ident: 'postcss',
                plugins: () => [
                  // 重複するセレクタをまとめる
                  postCssCombineDuplicatedSelectors({
                    removeDuplicatedProperties: true
                  }),
                  // 重複するメディアクエリをまとめる
                  cssMqpacker({
                    // メディアクエリをソート
                    sort: sortCssMediaQueries
                  }),
                  // CSSの属性をソート
                  cssDeclarationSorter({
                    order: 'smacss'
                  }),
                  // ベンダープレフィックスを付与
                  autoprefixer({
                    // CSSグリッドを使う
                    grid: true
                  })
                ]
              }
            },
            {
              loader: 'stylus-loader'
            }
          ]
        }
      ]
    }
  }

  return config
}


```

</div></details>

## my-block-plugin.php

プラグインのメインファイル。

<details><summary>my-block-plugin.php (クリックして全体を表示) </summary><div>

```my-block-plugin.php
<?php
/**
 * Plugin Name: 僕のStylusブロック
 */

if ( !defined( 'ABSPATH' ) ) {
  exit;
}

add_action( 'init', 'my_block_plugin_register' );

function my_block_plugin_register() {
  $editor_asset = include( plugin_dir_path( __FILE__ ) . 'assets/editor.asset.php' );
  $script_asset = include( plugin_dir_path( __FILE__ ) . 'assets/script.asset.php' );

  // エディター用JSを登録
  wp_register_script(
    'my-block-plugin-editor-script',
    plugins_url( 'assets/editor.js', __FILE__ ),
    $editor_asset['dependencies'],
    $editor_asset['version']
  );

  // エディター・フロント両用JSを登録
  wp_register_script(
    'my-block-plugin-script',
    plugins_url( 'assets/script.js', __FILE__ ),
    $script_asset['dependencies'],
    $script_asset['version']
  );

  // エディター用CSSを登録
  wp_register_style(
    'my-block-plugin-editor-style',
    plugins_url( 'assets/editor.css', __FILE__ ),
    array( 'wp-edit-blocks' ),
    filemtime( plugin_dir_path( __FILE__ ) . 'assets/editor.css' )
  );

  // エディター・フロント両用CSSを登録
  wp_register_style(
    'my-block-plugin-style',
    plugins_url( 'assets/style.css', __FILE__ ),
    array(),
    filemtime( plugin_dir_path( __FILE__ ) . 'assets/style.css' )
  );

  // カスタムブロックの登録
  register_block_type(
    'my-block-plugin/my-block',
    [
      'editor_script' => 'my-block-plugin-editor-script', // エディター用JSを読み込む
      // 'script' => 'my-block-plugin-script', // エディター・フロント両用JSを読み込む
      // 'editor_style'  => 'my-block-plugin-editor-style', //  エディター用CSSを読み込む
      'style'  => 'my-block-plugin-style' //  エディター・フロント両用CSSを読み込む
    ]
  );
}

```

</div></details>


```my_block_plugin``` , ```my-block-plugin``` の文字列はご自分のプラグイン名に合わせて変更してください。

```register_block_type``` の部分では必要ないJS, CSSは必要に応じてコメントアウトしてください。今回のサンプルでは ```editor_script``` と ```style``` のみ使うので他はコメント化しています。

```wp_register_style``` の第4引数 ```filetime``` は、ビルドするごとにブラウザキャッシュを消すための小技です。ただし、Stylus側が空でCSSが出力されていない場合にはエラーが出るのでご注意ください。

## editor.js

<details><summary>editor.js (クリックして全体を表示) </summary><div>

```jsx:editor.js
import { RichText } from '@wordpress/editor'
import { registerBlockType } from '@wordpress/blocks'

registerBlockType( 'my-block-plugin/my-block', {
  title: '僕のサンプル 青いブロック',
  category: 'common',
  icon: 'wordpress-alt',
  attributes: {
    text: {
      type: 'string',
      source: 'html',
      selector: 'p',
    },
  },

  edit: ( props ) => {
    const onChangeText = ( newText ) => {
      props.setAttributes( { text: newText } );
    };
    return (
      <div>
        <RichText
          className={ props.className }
          tagName="p"
          value={ props.attributes.text }
          onChange={ onChangeText }
        />
      </div>
    );
  },

  save: ( props ) => {
    return (
      <div>
        <p>{ props.attributes.text }</p>
      </div>
    );
  },
});
```

</div></detail>

エディター画面のみで読み込まれるJS。実際にブロックを設計するファイルです。ここをReactで書いていきます。

内容は、動作確認用として、テキストを入力して表示するブロックのサンプルとなります。今回は環境構築が目的ということで、具体的な中身の解説は今回は割愛します。

## style.styl

同じく動作確認用です。エディター、フロント両方で読み込まれるCSSです。

<details><summary>style.styl (クリックして全体を表示) </summary><div>

```stylus:style.styl
.wp-block-my-block-plugin-my-block
  background-color: #5cb1e3
  border-radius: 4px
  color:  rgba(255,255,255,0.95)
  margin-top: 24px
  margin-bottom: 24px
  padding: 24px
```

</div></detail>

## 動作確認

ターミナルでプラグインディレクトリへ移動して、ビルドしましょう。

```
npm run build
```

プラグインの ```public``` フォルダをまるごとWordPressの ```plugins``` フォルダ直下に入れます。僕は、テスト環境では ```public```フォルダのシンボリックリンクを作って ```plugins```フォルダ直下に入れています…が、もっといい方法があったら教えてください。

WordPressプラグイン画面で「僕のStylusブロック」を有効化します。

![screenshot 2020-07-15 12.34.26.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32948/1facbbf4-f7db-b5b1-d231-66113f68dac6.jpeg)

投稿編集画面へ行き、自作ブロックが「一般ブロック」の中に登録されているか確認し、あったら使ってみましょう。青い背景のブロックができます。

![screenshot 2020-07-15 12.36.01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32948/1fe5b056-a2f3-d354-7084-2f739eae6989.jpeg)

文字を書いて保存します。

<img width="738" alt="screenshot 2020-07-15 13.02.54.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32948/40e6e8b8-1bda-936e-5c36-25aa04cbeaa3.jpeg">
<img width="735" alt="screenshot 2020-07-15 13.03.04.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32948/8b44d6d9-384f-5e32-8036-acd1d3e19731.jpeg">


フロント側をプレビューして文字が表示されていればOKです。




以上 ```@wordpress/scripts``` と Stylusを使った環境構築の一例でした。


## 参考文献
[【WordPress】カスタムブロックの作り方を書いてみた - Qiita -](https://qiita.com/Kodak_tmo/items/bf011e31db4293db8312)
[registerBlockTypeで指定する内容について - Qiita -](https://qiita.com/harapeko_momiji/items/83cd0953d030c0d8a59f)