gulp+webpack+jQuery+pug+sassで開発環境構築

gulp+webpack+jQuery+pug+sassで開発環境構築

長いこといつか更新しないとと思っていた開発環境を時間があったので作り直しました!
あれもこれもと機能充実させていったら、トータル3日ぐらい使ったかもしれません。。
しかし、良い開発環境があれば大きな工数削減・ストレス軽減につながります。

LP~数ページレベルの案件だと使い勝手が良いかなと思います。
大型案件向きではありません。

だいぶオレオレ開発環境なのですが、GitHubに置いておきますので、使いたい方はcloneして使ってみてください。

開発環境を変えた理由

開発環境を変えた理由はいくつかあります。

  • gulp・node・webpackを最新にしたかった。
  • gulpのwatch機能が微妙で、ファイル追加したときいちいち再起動が必要だった。
  • jsをwebpackでbundleして1ファイルにまとめたかった。
  • jsをmodule化して機能ごとに分離したかった。
  • meta周りを一括で変更できるようにしたかった。
  • UnCSSで不要なCSSを削除したかった
  • sassもnode_modulesから直接importしたかった。
  • eslint + prettierを使ってきれいなコードを保ちたかった。
  • es6の記述でjs書きたかった。

・・・などなど、いろいろと不満があったのですが、これらをすべて解消した環境がようやくできました。
これらの機能を盛り込んで作ってみました。

機能紹介

開発環境

  • gulp
  • pug
  • webpack
  • babel
  • sass
  • eslint
  • prettier

ライブラリ

  • jQuery
  • modaal
  • GSAP(TweenMax/TimelineMax)
  • ScrollMagic
  • Swiper

コードなど紹介

ファイル構成

.babelrc
.eslintrc.json
.gitignore
gulpfile.js
package-lock.json
package.json
README.md
webpack.config.js
_src
 │  index.pug
 │  test.php
 │  
 ├─assets
 │  ├─images
 │  │  │  favicon.png
 │  │  │  
 │  │  └─video
 │  │          video.mp4
 │  │          
 │  ├─js
 │  │  │  main.js
 │  │  │  
 │  │  └─modules
 │  │          animation.js
 │  │          modal.js
 │  │          slider.js
 │  │          
 │  └─sass
 │      │  main.scss
 │      │  utility.scss
 │      │  
 │      ├─foundation
 │      │  ├─base
 │      │  │      _base.scss
 │      │  │      _fonts.scss
 │      │  │      
 │      │  ├─mixin
 │      │  │      _bgi.scss
 │      │  │      _clearfix.scss
 │      │  │      _content.scss
 │      │  │      _mediaquerys.scss
 │      │  │      
 │      │  ├─reset
 │      │  │      _reset.scss
 │      │  │      
 │      │  ├─var
 │      │  │      _variable.scss
 │      │  │      
 │      │  └─vendor
 │      │          _vendor.scss
 │      │          
 │      ├─object
 │      │      _container.scss
 │      │      _test.scss
 │      │      _wrap.scss
 │      │      
 │      └─utility
 │              _display.scss
 │              _indent.scss
 │              _margin.scss
 │              _padding.scss
 │              
 ├─under
 │      index.pug
 │      
 ├─_data
 │      meta.json
 │      
 └─_include
     │  _layout.pug
     │  
     └─common
             _footer.pug
             _gabody.pug
             _gahead.pug
             _head.pug
             _header.pug

package.json

{
  "name": "jws-template",
  "description": "gulp + webpack + jQuery + pug + sass",
  "version": "1.0.0",
  "author": "JWS",
  "browserslist": [
    "last 2 versions"
  ],
  "dependencies": {
    "gsap": "^2.1.3",
    "jquery": "^3.4.1",
    "modaal": "^0.4.4",
    "scrollmagic": "^2.0.7",
    "scrollmagic-plugin-gsap": "^1.0.3"
  },
  "devDependencies": {
    "@babel/core": "^7.6.3",
    "@babel/preset-env": "^7.6.3",
    "@babel/register": "^7.6.2",
    "babel-eslint": "^10.0.3",
    "babel-loader": "^8.0.6",
    "browser-sync": "^2.26.7",
    "connect-ssi": "^1.1.1",
    "del": "^5.1.0",
    "eslint": "^6.5.1",
    "eslint-config-prettier": "^6.4.0",
    "eslint-config-standard": "^14.1.0",
    "eslint-loader": "^3.0.2",
    "eslint-plugin-import": "^2.18.2",
    "eslint-plugin-node": "^10.0.0",
    "eslint-plugin-prettier": "^3.1.1",
    "eslint-plugin-promise": "^4.2.1",
    "eslint-plugin-standard": "^4.0.1",
    "gulp": "^4.0.2",
    "gulp-autoprefixer": "^7.0.1",
    "gulp-babel": "^8.0.0",
    "gulp-changed": "^4.0.2",
    "gulp-clean-css": "^4.2.0",
    "gulp-data": "^1.3.1",
    "gulp-group-css-media-queries": "^1.2.2",
    "gulp-imagemin": "^6.1.1",
    "gulp-notify": "^3.2.0",
    "gulp-plumber": "^1.2.1",
    "gulp-postcss": "^8.0.0",
    "gulp-prettify": "^0.5.0",
    "gulp-pug": "^4.0.1",
    "gulp-sass": "^4.0.2",
    "gulp-sass-glob": "^1.1.0",
    "gulp-uglify-es": "^1.0.4",
    "imagemin-mozjpeg": "^8.0.0",
    "imagemin-pngquant": "^8.0.0",
    "imports-loader": "^0.8.0",
    "postcss-import": "^12.0.1",
    "postcss-uncss": "^0.17.0",
    "prettier": "^1.18.2",
    "swiper": "^5.0.4",
    "terser-webpack-plugin": "^2.1.2",
    "uncss": "^0.17.2",
    "webpack": "^4.41.0",
    "webpack-cli": "^3.3.9",
    "webpack-stream": "^5.2.1"
  },
  "license": "ISC",
  "main": "index.js",
  "scripts": {
    "dev": "npx gulp",
    "build": "npx gulp build",
    "test": "echo \"Error: no test specified\" && exit 1"
  }
}

gulpfile.js

/* =========================================================
  Import
========================================================= */
/* ---------------------------------------------------------
  common
--------------------------------------------------------- */
const gulp = require('gulp');
const browserSync = require('browser-sync');
const ssi = require('connect-ssi');
const del = require('del');
const plumber = require('gulp-plumber');
const notify = require('gulp-notify');

/* ---------------------------------------------------------
  pug
--------------------------------------------------------- */
const pug = require('gulp-pug');
const fs = require('fs');
const data = require('gulp-data');

/* ---------------------------------------------------------
  sass
--------------------------------------------------------- */
const sass = require('gulp-sass');
const autoprefixer = require('gulp-autoprefixer');
const cleanCSS = require('gulp-clean-css');
const gcmq = require('gulp-group-css-media-queries');
const sassGlob = require('gulp-sass-glob');
const postcss = require('gulp-postcss');
const uncss = require('postcss-uncss');
const cssImport = require('postcss-import');

/* ---------------------------------------------------------
  javascript
--------------------------------------------------------- */
const webpackStream = require('webpack-stream');
const webpack = require('webpack');
const babel = require('gulp-babel');
const uglify = require('gulp-uglify-es').default;
const webpackConfig = require('./webpack.config');

/* ---------------------------------------------------------
  image
--------------------------------------------------------- */
const imagemin = require('gulp-imagemin');
const mozjpeg = require('imagemin-mozjpeg');
const pngquant = require('imagemin-pngquant');
const changed = require('gulp-changed');

/* =========================================================
  settings
========================================================= */
const paths = {
  sass: [
    './_src/assets/sass/*.scss',
    './_src/assets/sass/**/*.scss',
    './_src/assets/sass/**/**/*.scss'
  ],
  sassDist: './_dist/assets/css/',
  css: ['./_dist/assets/css/*.css'],
  pug: [
    './_src/*.pug',
    './_src/**/*.pug',
    './_src/**/**/*.pug',
    '!./_src/_*.pug',
    '!./_src/**/_*.pug',
    '!./_src/**/**/_*.pug'
  ],
  pugDist: './_dist/',
  html: ['./_src/*.html', './_src/**/*.html'],
  htmlDist: ['./_dist/*.html', './_dist/**/*.html'],
  php: ['./_src/*.php', './_src/**/*.php'],
  phpDist: './_dist/',
  js: ['./_src/assets/js/*.js', './_src/assets/js/**/*.js'],
  jsDist: './_dist/assets/js/',
  image: ['./_src/assets/images/**/*'],
  imageDist: './_dist/assets/images/',
  dist: './_dist/',
  meta: './_src/_data/meta.json'
};

/* =========================================================
  Task
========================================================= */
/* ---------------------------------------------------------
  pug
--------------------------------------------------------- */
function pugFunc(done) {
  const option = {
    pretty: true
  };
  return gulp
    .src(paths.pug)
    .pipe(
      plumber({
        errorHandler: notify.onError('Error: <%= error.message %>')
      })
    )
    .pipe(
      data(function() {
        return JSON.parse(fs.readFileSync(paths.meta));
      })
    )
    .pipe(pug(option))
    .pipe(gulp.dest(paths.pugDist))
    .pipe(browserSync.reload({ stream: true }));
}

/* ---------------------------------------------------------
  sass
--------------------------------------------------------- */
function sassFunc() {
  const plugins = [
    cssImport({
      path: ['node_modules']
    })
  ];
  return gulp
    .src(paths.sass, {
      sourcemaps: true
    })
    .pipe(plumber())
    .pipe(sassGlob())
    .pipe(
      sass({
        outputStyle: 'expanded'
      })
    )
    .pipe(postcss(plugins))
    .pipe(cleanCSS())
    .pipe(
      autoprefixer({
        cascade: false
      })
    )
    .pipe(
      gulp.dest(paths.sassDist, {
        sourcemaps: './sourcemaps'
      })
    )
    .pipe(
      browserSync.reload({
        stream: true
      })
    );
}

function sassDistFunc() {
  const plugins = [
    cssImport({
      path: ['node_modules']
    })
  ];
  return gulp
    .src(paths.sass, {
      sourcemaps: false
    })
    .pipe(plumber())
    .pipe(sassGlob())
    .pipe(
      sass({
        outputStyle: 'compressed'
      })
    )
    .pipe(postcss(plugins))
    .pipe(cleanCSS())
    .pipe(
      autoprefixer({
        cascade: false
      })
    )
    .pipe(gcmq())
    .pipe(
      sass({
        outputStyle: 'compressed'
      })
    )
    .pipe(gulp.dest(paths.sassDist))
    .pipe(
      browserSync.reload({
        stream: true
      })
    );
}

/* ---------------------------------------------------------
  uncss
--------------------------------------------------------- */
function uncssFunc(done) {
  const plugins = [
    uncss({
      html: paths.htmlDist,
      ignore: []
    })
  ];
  gulp
    .src('_dist/assets/css/utility.css')
    .pipe(
      plumber({
        errorHandler: notify.onError('Error: <%= error.message %>')
      })
    )
    .pipe(postcss(plugins))
    .pipe(gulp.dest('_dist/assets/css/'));
  done();
}

/* ---------------------------------------------------------
  image
--------------------------------------------------------- */
function imageFunc() {
  return gulp
    .src(paths.image)
    .pipe(changed(paths.imageDist))
    .pipe(
      imagemin(
        [
          mozjpeg({
            quality: 80
          }),
          pngquant()
        ],
        {
          verbose: true
        }
      )
    )
    .pipe(gulp.dest(paths.imageDist))
    .pipe(
      browserSync.reload({
        stream: true
      })
    );
}

/* ---------------------------------------------------------
  js
--------------------------------------------------------- */
function jsFunc() {
  return plumber({
    errorHandler: notify.onError('Error: <%= error.message %>')
  })
    .pipe(webpackStream(webpackConfig, webpack))
    .pipe(babel())
    .pipe(uglify())
    .pipe(gulp.dest(paths.jsDist))
    .pipe(
      browserSync.reload({
        stream: true
      })
    );
}

/* ---------------------------------------------------------
  php
--------------------------------------------------------- */
function phpFunc() {
  return gulp
    .src(paths.php)
    .pipe(gulp.dest(paths.phpDist))
    .pipe(
      browserSync.reload({
        stream: true
      })
    );
}

/* ---------------------------------------------------------
  server
--------------------------------------------------------- */
const browserSyncOptions = {
  port: 3000,
  reloadOnRestart: true,
  server: {
    baseDir: paths.dist,
    index: 'index.html',
    middleware: [
      ssi({
        baseDir: __dirname + '/_dist',
        ext: '.html'
      })
    ]
  },
  ghostMode: {
    clicks: false,
    forms: false,
    scroll: false
  }
};

function browserSyncFunc(done) {
  browserSync.init(browserSyncOptions);
  done();
}

/* ---------------------------------------------------------
  clean
--------------------------------------------------------- */

function cleanFunc(done) {
  del.sync(['./_dist']);
  done();
}

/* ---------------------------------------------------------
  watch
--------------------------------------------------------- */
function watchFunc(done) {
  const browserReload = function() {
    browserSync.reload({
      stream: true
    });
    done();
  };
  gulp.watch(paths.pug).on('change', gulp.series(pugFunc, browserReload));
  gulp.watch(paths.sass).on('change', gulp.series(sassFunc, browserReload));
  gulp.watch(paths.js).on('change', gulp.series(jsFunc, browserReload));
  gulp.watch(paths.image).on('change', gulp.series(imageFunc, browserReload));
  gulp.watch(paths.php).on('change', gulp.series(phpFunc, browserReload));
  done();
}

/* =========================================================
  Task main
========================================================= */
/* ---------------------------------------------------------
  gulp
--------------------------------------------------------- */
gulp.task(
  'default',
  gulp.series(
    gulp.parallel(pugFunc, sassFunc, jsFunc, imageFunc, phpFunc),
    gulp.series(browserSyncFunc, watchFunc)
  )
);

gulp.task(
  'build',
  gulp.series(
    gulp.series(cleanFunc),
    gulp.parallel(pugFunc, sassDistFunc, jsFunc, imageFunc, phpFunc),
    gulp.series(uncssFunc)
  )
);

gulp.task('uncss', gulp.series(uncssFunc));

.babelrc

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": ">0.25% in JP, not ie <= 10, not op_mini all"
      }
    ]
  ]
}

.eslintrc.json

{
  "extends": ["standard", "prettier", "plugin:prettier/recommended"],
  "env": {
    "browser": true
  },
  "plugins": ["prettier"],
  "rules": {
    "prettier/prettier": [
      "error",
      {
        "singleQuote": true,
        "semi": true,
        "tabWidth": 2
      }
    ],
    "yoda": 0,
    "no-unused-vars": 0,
    "import/no-webpack-loader-syntax": 0,
    "no-undef": 0,
    "no-path-concat": 0
  },
  "globals": {
    "$": false
  }
}

まとめ

長々とコードの羅列をしました。。。
Gulp v3からv4に変わっていろいろと文法が変わって苦戦しました。
開発環境は、1年ぐらいで見直した方がよさそうです。

説明を書かないといけないんだけど、まぁとにかくやっていることが多すぎて書ききれない感じですね。
前職の先輩コーダーの方の開発コードを参考にしてみましたが、他にもいろいろ便利機能を実装されていて、凄さを実感しました!

大型案件だとこれぐらいじゃ回せないですが、LP~数ページレベルの案件だと使い勝手がいいような環境です。
とりあえずこれをベースに開発して、ほかにも便利なものがあったら更新していくイメージでいこうと思います。
コード一式はこちらに置いておきます。

ポートフォリオ・お問合せはこちら