💉

自分的静的ウェブサイトにおける開発環境まとめ2021

10 min read

総評としてSSGつかわない環境ならなんだかんだGulpが使いやすいです。

表題における開発環境のベストプラクティスがまとまった、
がもっとベターな方法があれば知りたい。

主目的

基本は静的コンテンツがメイン、vueやReactなどのコンポーネントは部分的に採用したい。
APIとの連携も今後発生する可能性があるプロジェクト。

※linter系は好みやプロジェクトの思想に依存するので割愛します。
※production(本番) || dev(開発)での出し分けについても割愛します。
※命名についてはセンスないのでご愛嬌

使用するHTMLとCSSのメタ言語 && JS

その他

  • browserSync
    開発環境のブラウザのオートリロード

使用するタスクランナー && モジュールバンドラー

  • Gulp 4系
    SCSSやPugファイルのトランスパイルおよび、webpack-streamを使用、BrowserSyncの設定
  • webpack 4系
    JSファイル、各jsフレームワークファイルをbabelを通してES5環境にトランスパイル
    chunk周りはキニシナイ
  • npm scripts
    各タスクや.jsで書いたタスクを実行
    ※ICS mediaさまの記事がわかりやすいので引用

実際の作りとディレクトリ構成

jsとVueの処理ははwebpackに任せてしまい、pugやscss、画像周りは何もしません。
ただしGulp側でもjs||Vueの処理は受け取りたいため、webpack-streamを使ってGulp上でwebpackをimport

.
├── _task
│   ├── babel.js
│   ├── css.js
│   ├── html.js
│   ├── server.js
│   └── watch.js
├── gulpfile.babel.js
├── node_modules
├── package.json
├── public // ここにsrc配下のトランスパイルしたものが吐き出される
│   └── assets
│       ├── css
│       ├── img // 画像は処理しないのでこちらに格納
│       └── js
├── src
│   ├── css
│   │   └── style.scss
│   ├── html
│   │   ├── about
│   │   │   └── index.pug
│   │   └── index.pug
│   └── js
│       ├── app.js
│       └── components
│           ├── A.vue
│           └── B.vue
└── webpack.config.js

Gulp gulpfile.babel.js

/**
* 各ファイルを_taskディレクトリからimport
* @description
* 直列でSCSS,js,pugのトランスパイルが実行され、並列でローカルサーバーが立ち上がる
* 直列にしているのは初期トランスパイル状況が見やすくするためなので好み
*/
import {series, parallel} from "gulp"
import scssToCss from "./_task/css"
import esTojs from "./_task/babel"
import pugToHtml from "./_task/html"
import browseServe from "./_task/server"
import fileWatch from "./_task/watch"


exports.default = series(
  scssToCss.cssTasks,
  esTojs.babelTasks,
  pugToHtml.htmlTasks,
  parallel(browseServe.browseTasks, fileWatch.watchTasks)
)

Pug → HTML ./_taks/html.js

SourceCode
/**
* PugをHTMLで出力
* @description
* lastRunを使うことで変更ファイルだけトランスパイルされるため処理が早いが問題もある。。※後述
*/
import {lastRun, src, dest} from "gulp"
import pug from "gulp-pug"
import plumber from "gulp-plumber"
import notify from "gulp-notify"

function _pug(cb){
  return src(["./src/html/**/*.pug", "!./src/html/**/_*.pug"],
    {
      since: lastRun(_pug)
    })
    .pipe(plumber({ errorHandler: notify.onError("<%= error.message %>") }))
    .pipe(pug({
      pretty: true,
      basedir: "./src/html/"
    }))
    .pipe(dest("./public/"))
  cb()
}

const htmlTasks = _pug,
exports.htmlTasks = htmlTasks

SCSS(Postcss) → CSS ./_taks/css.js

SourceCode
/**
* src配下のSCSSファイルをトランスパイルする関数
* @description
* SCSSファイルをpartialでまとめて、cssにトランスパイル後autoprefixを付与
*/

import {lastRun, src, dest, series} from "gulp"
import sass from "gulp-sass"
import postcss from "gulp-postcss"
import autoprefixer from "autoprefixer"
import plumber from "gulp-plumber"
import glob from "gulp-sass-glob"

function _sass(cb) {
  return src("./src/css/**/*.scss",
    {
      sourcemaps: true,
      since: lastRun(_sass)
    })
    .pipe(plumber({}))
    .pipe(glob())
    .pipe(sass({ outputStyle: "expanded" }).on("error", sass.logError))
    .pipe(postcss([
      autoprefixer()
    ]))
    .pipe(dest("./public/assets/css/", { sourcemaps: true }))
  cb()
}

JS||Vue → ES5相当 ./_taks/babel.js

SourceCode
/**
* src配下のjsファイルをES5相当にトランスパイル
* @description
* webpack.config.jsにbabelやloaderで処理を加えてる
*/

import {src, dest, series} from "gulp"
import plumber from "gulp-plumber"
import notify from "gulp-notify"
import webpack from "webpack"
import webpackStream from "webpack-stream"
import webpackConfig from "../webpack.config.js"

function _babel (cb) {
  return plumber({
    errorHandler: notify.onError("<%= error.message %>"),
  })
    .pipe(webpackStream(webpackConfig, webpack))
    .pipe(dest("./public/assets/js/"))
  cb()
}

const
  babelTasks = series(_babel, _customer_babel),

exports.babelTasks = babelTasks

server BrowserSync ./_taks/server.js

SourceCode
import browserSync from "browser-sync"
import {watch} from "gulp"

/**
* browserSyncを用いてブラウザ
* @description
* publicを起点としてローカルサーバーを立ち上げ
* ui等機能を仕様しないためfalseに
*/
function _browserSync(cb) {
  browserSync({
    server: {
      baseDir: "./public/",
      directory: false,
      open: "external",
      online: false,
      ui: false,
    }
  })
  cb()
}

const browseTasks = _browserSync
exports.browseTasks = browseTasks

watch fileWatch ./_watch.js

SourceCode
/**
* 各タスクを監視、ブラウザリロードをする関数
* @description
* src配下のファイルがコンパイルされたタイミングを検知し、各タスクが実行される
* public配下のファイルの変更が検知されるとブラウザのオートリロードが走ります
*/

/* eslint-disable no-undef */
import { watch } from "gulp"
import scssToCss from "../_task/css.js"
import esTojs from "../_task/babel.js"
import pugToHtml from "../_task/html.js"
import browserSync from "browser-sync"

function _watch(cb) {
  watch(["./src/**/*.pug","./src/**/*.json"], pugToHtml.htmlTasks)
  watch("./src/**/*.scss", scssToCss.cssTasks)
  watch(["./src/**/*.js", "./src/**/*.vue"], esTojs.babelTasks)

  watch("public/**/*.html").on("change", browserSync.reload)
  watch("public/**/*.js").on("change", browserSync.reload)
  watch("public/**/*.css").on("change", browserSync.reload)
  cb()
}

const watchTasks = _watch
exports.watchTasks = watchTasks

webpack webpack.config.js

SourceCode
/**
 * webpackはjsとVueをトランスパイルするためだけに使用。
 * @description
 * アウトプット先は_task/babel.jsの方に記載されてる。
 * 今回はproductionはフォーカスから外しているためprod / dev の切り替えについては記述しておりません。
 * @var srcDir === 開発ディレクトリをglobを用いて配列に入れて、回してる
 * HardSourceWebpackPlugin で開発時のファイルをキャッシュしてトランスパイル時間を削ってる
 */

import path from 'path';
import HardSourceWebpackPlugin from "hard-source-webpack-plugin"
import { VueLoaderPlugin } from'vue-loader'
import VuetifyLoaderPlugin from'vuetify-loader/lib/plugin'
import glob from'glob'

const srcDir = "./src/js/"
const entries = {}

glob.sync("**/app.js", {
  ignore: ['**/_*.js'],
  cwd: srcDir
}).map(function (key) {
  entries[key] = path.resolve(srcDir, key);
});
module.exports = {
  mode: 'development',
  entry: entries,

  output: {
    filename: '[name]'
  },
  devtool: 'source-map',

  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
      },
      {
        test: /\.m?js$/,
        exclude: (file) => (
	// swiperをIE11でも使えるように
          (/node_modules/.test(file) &&
          !/dom7/.test(file) &&
          !/swiper/.test(file) &&
          !/\.vue\.js/.test(file)
        ),
        use: {
          loader: 'babel-loader'
        }
      },
      {
        test: /\.scss$/,
        use: [
          {
            loader: 'style-loader',
          },
          {
            loader: 'css-loader',
            options: {
              url: false,
              sourceMap: true,
              importLoaders: 2
            }
          },
          {
            loader: 'postcss-loader',
            options: {
              sourceMap: true,
              plugins: [
                require("autoprefixer")({
                  grid: true
                })
              ]
            }
          },
          {
            loader: 'sass-loader',
            options: {
              sourceMap: true
            },
          },
          {
            loader: "import-glob-loader"
          },
        ]
      },
      {
        test: /\.sass$/,
        use: [
          "vue-style-loader",
          "css-loader",
          {
            loader: 'sass-loader',
            options: {
              implementation: require('sass'),
              sassOptions: {
                fiber: require('fibers'),
                indentedSyntax: true
              },
            },
          }
        ],
      },
      {
        test: /\.css$/,
        use: [
          'vue-style-loader',
          {
            loader: 'css-loader',
          }
        ]
      },
    ]
  },
  plugins: [
    new HardSourceWebpackPlugin(),
    new VueLoaderPlugin(),
    new VuetifyLoaderPlugin(),
  ],
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@json': path.join(__dirname, 'public')

    },
    extensions: ['*', '.js', '.vue', '.json'],
  }
}

npm sciprts

本番環境などなどを削っていったらこれだけになってしまった。。書かなくていいレベル

  "scripts": {
    "start": "gulp ",
  },

lastRun問題

twitterでも延々と嘆いていたけれど、_a.pugをindex.pugで読み込んでいる時に、
_a.pugを変更し保存しても、index.pugの更新には伝播されないため、index.pugにスペースいれるなりの変更を加えて保存しないと反映がされない…。
※lastRunの仕様としては正しいはず

scssでも同様でpartial先のstyle.scssなりを保存しトランスパイルが行われないと、@import "_a"の_a.scssを変更、保存をしてもstyle.scssを保存しないとトランスパイルされない。。

このあたりがとても不便。
どなたか解決策お持ちでしょうか。

総評

モダンフロントエンドが声高なこの時代に開発環境を作るということそもそもが、もう古いのではないかと
SSGできるフレームワークのCLIを見ていて思いました。
React || Vue || Svelte || etcから選んでTS || JSだけの選択肢で住む世界・・・。楽で涙が出る。

ペライチLP等でさくっとnpm scrips書けるくらいにはなっておくと色々らくかもしれません。

結論CLIでいい

Discussion

ログインするとコメントできます