Nuxtベースで静的・Wordpress共用の開発環境を整えました

#####19/06/13 追記#####
**その後、実環境で使用していくつか改善したので、編集加えました :raising_hand:
そして大したものでもないのですが、せっかくなのでgitでファイル公開しました**
[https://github.com/webiscuit/Boilerplate-Nuxt-Wordpress](https://github.com/webiscuit/Boilerplate-Nuxt-Wordpress)

#####20/01/24 追記#####
**↓REST APIなどを使用しないライトな構造です。
なんだかんだ受託案件などで急にREST API導入は難しい場合にご参考になれば**

こんにちは、はじめまして:relaxed:
フリーランスでフロントエンドデベロッパーをしています。
普段は静的サイトのHTML,CSS,JavaScriptコーディング、WordPressテーマの開発なんかやっていることが多いです。

Qiitaは以前から使ってたのですが見る専でしたが
最近、開発環境を見直して整え直したので、せっかくなので備忘録も兼ねてシェアさせていただきます✨

ゴリゴリクリエイティブなことをしているというよりは
黙々とプロジェクトのコードと遊んでいる地味～な環境ですが、もし少しでも需要があれば幸いです:relaxed:

# まず結論から。

- 静的サイトのコンパイルおよびもろもろ:　[Nuxt.js](https://ja.nuxtjs.org/)
- モジュールバンドラー（WordPress用）：　[Webpack](https://webpack.js.org/)
- WordPressローカル環境： [Local by Flywheel](https://localbyflywheel.com/)

静的サイトの場合もWordpressテーマの場合も、
まずは静的ファイルとしてコーディングすることが多いので
一つのプロジェクト内でNuxtとWordpressを共存させました。

ひとつの環境で両方をカバーしようと思ったのは、単にめんどくさかったからです（）
あとWordpress用にwebpackを併用したのは、やっぱりめんどくさかったからです…（）

### 今まではどうしていたか

以前はgulpを使用していましたが、数年前にwebpackメインに切り替えていました。
こんな感じ。

- タスクランナー:　npm script
- モジュールバンドラー：　Webpack
- CSS:　PostCSS（プラグインなどで必要な場合のみ Sass を併用）
- テンプレートエンジン:　Pug

Wordpressはローカルサーバー内に立ててましたが、いい機会なのでこれも止めていこうと思ってます。

### Nuxtを導入した理由と変わったこと

なんとなく、そろそろ見直したいなぁと思ったのがきっかけ。

NuxtはVueを利用しているので、ページごとにHTMLべたうちしたりPugにしたり
外部ファイルももっと簡単にSassやPostCSSを混在出来たりするので
チームで開発する際にいいかなーと思っています。

自分自身が新しいものにも挑戦したくなった時にやりやすいですしね:relaxed:

## ディレクトリ構造

では、新環境について記載していきます。
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155724/51a827e1-773d-60fb-f711-9b4e8c421b23.png)
Wordpress環境を併用する場合は、Localで作成したディレクトリのapp内に上記を入れます。

## 各設定ファイル

Nuxtや各種ツールの導入方法はたくさんの記事があるし何より説明が下手なので割愛します…

これから使っていくにつれてアップデートをしていこうとは思っていますが
とりあえず現段階の設定ファイルたちです

### package.json

```json:package.json
{
  "name": "boilerplate-nuxt",
  "version": "1.0.0",
  "description": "Boilerplate for static pages",
  "author": "Hiromi Kozai",
  "private": true,
  "scripts": {
    "dev": "nuxt",
    "build": "nuxt build",
    "start": "nuxt start",
    "generate": "nuxt generate",
    "lint": "eslint --ext .js,.vue --ignore-path .gitignore .",
    "precommit": "npm run lint",
    "wp:dev": "webpack -w --env.development",
    "wp:build": "webpack --env.production"
  },
  "babel": {
    "presets": [
      "@babel/preset-env"
    ]
  },
  "dependencies": {
    "@nuxtjs/dotenv": "^1.3.0",
    "bootstrap": "^4.1.3",
    "bootstrap-vue": "^2.0.0-rc.11",
    "cross-env": "^5.2.0",
    "nuxt": "^2.4.0"
  },
  "devDependencies": {
    "@babel/core": "^7.4.4",
    "@babel/preset-env": "^7.4.4",
    "@babel/register": "^7.4.4",
    "@nuxtjs/eslint-config": "^0.0.1",
    "babel-eslint": "^10.0.1",
    "eslint": "^5.15.1",
    "eslint-config-prettier": "^4.1.0",
    "eslint-config-standard": ">=12.0.0",
    "eslint-loader": "^2.1.2",
    "eslint-plugin-import": ">=2.16.0",
    "eslint-plugin-jest": ">=22.3.0",
    "eslint-plugin-node": ">=8.0.1",
    "eslint-plugin-nuxt": ">=0.4.2",
    "eslint-plugin-prettier": "^3.0.1",
    "eslint-plugin-promise": ">=4.0.1",
    "eslint-plugin-standard": ">=4.0.0",
    "eslint-plugin-vue": "^5.2.2",
    "imagemin-mozjpeg": "^8.0.0",
    "imagemin-webpack-plugin": "^2.4.2",
    "jquery": "^3.4.1",
    "mini-css-extract-plugin": "^0.6.0",
    "node-sass": "^4.12.0",
    "nodemon": "^1.18.9",
    "nuxt-imagemin": "^1.0.0",
    "optimize-css-assets-webpack-plugin": "^5.0.1",
    "path": "^0.12.7",
    "postcss-calc": "latest",
    "postcss-cli": "latest",
    "postcss-css-variables": "latest",
    "postcss-cssnext": "^3.1.0",
    "postcss-custom-media": "^7.0.8",
    "postcss-discard-duplicates": "latest",
    "postcss-import": "^12.0.1",
    "postcss-loader": "^3.0.0",
    "postcss-nested": "latest",
    "postcss-preset-env": "latest",
    "postcss-short": "latest",
    "prettier": "^1.16.4",
    "pug": "^2.0.3",
    "pug-plain-loader": "^1.0.0",
    "sass-loader": "^7.1.0",
    "terser-webpack-plugin": "^1.2.4",
    "url-loader": "^1.1.2",
    "vue-scrollto": "^2.15.0",
    "webpack": "^4.31.0",
    "webpack-cli": "^3.3.2"
  }
}
```

### nuxt.config.js

```js:nuxt.config.js
/* -∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-
  Import
-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-*/
import pkg from './package'
import webpack from 'webpack'
import path from 'path';
const imageminMozjpeg = require('imagemin-mozjpeg');



/* -∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-
  Use Global Variables
-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-*/
require('dotenv').config(); // from .env


/* -∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-
  Settings
-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-∵-∴-*/
export default {
  dev: (process.env.NODE_ENV !== 'production'),
  mode: 'universal',

  /*
   ** Headers of the page
   */
  head: {
    htmlAttrs: {
      lang: 'ja',
    },
    bodyAttrs: {
    },
    titleTemplate: '%s｜' + process.env.site_name,
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: process.env.viewport },
      { hid: 'description', name: 'description', content: process.env.def_description },
      { hid: 'og:type', property: 'og:type', content: 'website' },
      { hid: 'og:url', property: 'og:url', content: process.env.def_url },
      { hid: 'og:title', content: process.env.site_name },
      { hid: 'og:description', property: 'og:description', content: process.env.def_description },
      { hid: 'og:image', property: 'og:image', content: process.env.def_ogimage },
    ],
    // link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]
  },

  /*
   ** Customize the progress-bar color
   */
  loading: { color: '#333' },

  /*
   ** Global CSS
   */
  css: [
    '@/assets/css/bootstrap-settings.scss',
    '@/assets/css/style.css'
  ],

  /*
   ** Plugins to load before mounting the App
   */
  plugins: [
    { src: '@/assets/js/script.js', mode: 'client' },  // Old-style Common JS
    // '~/plugins/vue-scrollto.js' // scroll
  ],

  /*
   ** Nuxt.js modules
   */
  modules: [
    // Doc: https://bootstrap-vue.js.org/docs/
    'bootstrap-vue/nuxt',
    '@nuxtjs/dotenv',
    ["nuxt-imagemin", {
      pngquant: { quality: '80' },
      plugins: [
        imageminMozjpeg( {quality: '80'} )
      ]
    }]
  ],
    bootstrapVue: {
      bootstrapCSS: false,
      bootstrapVueCSS: false
    },

  /*
    ** Source Directory
    */
  srcDir: 'src/',
  
  /*
   ** Build configuration
   */
  build: {
    // quiet: true,
    fallback: false,
    publicPath: '/assets/',
    devtools: process.env.NODE_ENV === 'production',
    extractCSS:	process.env.NODE_ENV === 'production',
    // subFolders: false,
    filenames: {
      app: () => 'js/[name].js',
      chunk: () => 'js/[name].js',
      css: () => 'css/[name].css',
      img: () => 'img/[folder]/[name].[ext]',
      font: () => 'font/[name].[ext]'
    },

    /*
     ** You can extend webpack config here
     */
    extend(config) {
      // // Run ESLint on save
      // if (ctx.isDev && ctx.isClient) {
      //   config.module.rules.push({
      //     enforce: 'pre',
      //     test: /\.(js|vue)$/,
      //     loader: 'eslint-loader',
      //     exclude: /(node_modules)/
      //   })
      // }
    }
  }
}
```

### webpack.config.babel.js

```js:webpack.config.babel.js
import webpack from 'webpack';
import path from 'path';

const env = process.env.NODE_ENV;
const TerserJSPlugin = require('terser-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const ImageminPlugin = require('imagemin-webpack-plugin').default;
const imageminMozjpeg = require('imagemin-mozjpeg');

const srcpath = './src/assets/';
const csspath = './style.css';
const outputpath = './public/wp-content/themes/sample/assets/';

module.exports = env => {
  const mode = (env && env.production) ? 'production' : 'development';

  return {
    mode: mode,
    devtool: (mode == 'production') ? false : 'source-map',
    entry:  path.resolve(__dirname, srcpath + 'index.js'),
    output: {
      path: path.resolve(__dirname, outputpath),
      filename: 'app.js'
    },
    devServer: {
      clientLogLevel  : 'none',
    },
    stats: {
      children: false,
      hash: false,
      warnings: false,
      performance: false,
      modules: false,
    },
    module: {
      rules: [
        {
          test: /\.js$/,
          exclude: /node_modules/,
          loader: 'babel-loader',
          query:{
            presets: ['@babel/preset-env']
          }
        },
        {
          test: /\.(png|jpe?g|gif|svg|webp)$/,
          loader: 'url-loader',
          query: {
            limit: 1000, // 1kB
            name: 'img/[name].[ext]'
          }
        },
        {
          test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
          loader: 'url-loader',
          query: {
            limit: 1000, // 1kB
            name: 'fonts/[name].[ext]'
          }
        },
        {
          test: /\.(sa|sc|c)ss$/,
          use: [
            {
            loader: MiniCssExtractPlugin.loader,
            options: {
              hmr: process.env.NODE_ENV === 'development',
            },
          },
            { loader: 'css-loader', options: {
              importLoaders: 1,
              url: true,
              sourceMap: (env && env.production) ? false : true,
            } },
            { loader: 'postcss-loader', options: {
              ident: 'postcss',
              sourceMap:  (env && env.production) ? false : true,
            } },
            { loader: 'sass-loader', options: {
              sourceMap:  (env && env.production) ? false : true,
            } }
          ]
        }
      ]
    },
    resolve: {
      alias: {
        'assets': path.resolve(__dirname, 'src/assets')
      },
      extensions: ['.js', '.jsx']
    },
    optimization: {
      minimizer: [
        new TerserJSPlugin({ extractComments: false }),
        new OptimizeCssAssetsPlugin({
          cssProcessor: require('cssnano'),
          cssProcessorPluginOptions: {
            preset: ['default', { discardComments: { removeAll: true } }],
          }
        }),
      ],
    },
    plugins: [
      new MiniCssExtractPlugin({
        filename: csspath
      }),
      new webpack.ProvidePlugin({
        $: 'jquery',
        jQuery: 'jquery'
      }),
      new ImageminPlugin({
        disable: !env.production,
        test: /\.(jpe?g|png|gif|svg)$/i,
        pngquant: { quality: '80' },
        plugins: [
          imageminMozjpeg( {quality: '80'} )
        ]
      })
    ],
    performance: { hints: false }
  }
};
```

### postcss.config.js

postcssの設定をNuxt内に書かず外に置くのは非推奨で
次バージョンでは対応しないようなのですが、向こうしばらく環境改変の予定はないのでとりあえず。

```js:postcss.config.js
module.exports = {
  plugins: {
    'postcss-import': {},
    'postcss-discard-duplicates': {},
    'postcss-discard-comments': { removeAllButFirst: false },
    'postcss-custom-media': {},
    'postcss-preset-env': {
      stage: 0,
      browsers: "last 2 versions",
      features: {
        'nesting-rules': false
      }
    },
    'postcss-short': {},
    'postcss-calc': {},
    'postcss-nested': {}
  }
}
```

### ページVueファイル

ページはこんな感じ。

```vue:index.vue
<template>
</template>

<script>
export default {
  scrollToTop: true,

  data() {
    return {
      bodyClass: 'home', /* bodyに設定するclass */
    }
  },
  head() {
    return {
      title: process.env.site_name, /* ページタイトル（初期値：サイト名） */
      titleTemplate: null,
      meta: [
        { hid: 'description', name: 'description', content: process.env.def_description } /* descriptionを変更するときは"process.env.def_description"を差し替え */
      ],
      bodyAttrs: {
        class: this.bodyClass
      }
    }
  }
}
</script>

<style scoped>
</style>

```

記載を忘れていました…！
更新版でディレクトリ整理のため、参考にさせていただきました。
[https://qiita.com/amishiro/items/9c31a3a9424de27efe28](https://qiita.com/amishiro/items/9c31a3a9424de27efe28)