jackmiwamiwa devblog

フロントエンドをメインに行ったことをまとめていくサイトになります。

Gulpで動いている既存のプロジェクトをWebpackのみに変更する

f:id:jackswim3411:20200524073937p:plain

個人用のプロジェクトでのビルドを「gulp + webpack」からwebpackのみに変更したので、その時に対応したことのメモです。

環境

gulpファイルを消そうと思った理由

1. 自分の環境ならwebpackのみで変換できる

gulp上で行っていることが

  • pug → htmlへ変換
  • stylus → cssへ変換
  • img, fonts, static → ファイルのコピー

のみだったので、そのくらいだったらwebpackでもできるんじゃないかって思った。

2. gulpのパッケージ周りがあまり更新されていない?(個人的な主観)

  • gulp本体のLast publisha year agoなので(執筆時)、webpackよりも積極的な開発はされていない?(あくまでもnpmjsを見たイメージ)
  • 「gulp-*」のパッケージについてもあまり更新されているイメージがなかったので、webpackのみに乗り換えるいい機会だと思った。

変更前のコード

gulpfile.js

const { src, dest, watch, parallel } = require('gulp')
const pug = require('gulp-pug')
const data = require('gulp-data')
const stylus = require('gulp-stylus')
const postcss = require('gulp-postcss')
const postcssPresetEnv = require('postcss-preset-env')
const autoprefixer = require('autoprefixer')
const plumber = require('gulp-plumber')
const notify = require('gulp-notify')
const sourcemaps = require('gulp-sourcemaps')
const cleanCSS = require('gulp-clean-css')
const browserSync = require('browser-sync')
const webpackStream = require('webpack-stream')
const webpack = require('webpack')
const htmlmin = require('gulp-htmlmin')
const mode = require('gulp-mode')({
  modes: ['production', 'development'],
  default: 'development',
  verbose: false,
})
const isProduction = mode.production()

const webpackConfigDev = require('./webpack.dev')
const webpackConfigProd = require('./webpack.prod')
const webpackConfig = isProduction ? webpackConfigProd : webpackConfigDev

const srcPath = {
  html: ['src/pug/**/*.pug', '!' + 'src/pug/**/_*.pug'],
  stylus: 'src/**/*.styl',
  js: 'src/**/*.ts',
  image: 'src/assets/img/**/*',
  fonts: 'src/assets/fonts/**/*',
  static: 'src/static/**/*',
}

const destPath = {
  root: 'dist/',
  assets: 'dist/assets/',
}

const jsFunc = () => {
  return webpackStream(webpackConfig, webpack)
    .on('error', function(e) {
      this.emit('end')
    })
    .pipe(dest(`${destPath.assets}js/`))
    .pipe(browserSync.reload({ stream: true }))
}

const htmlFunc = () => {
  return src(srcPath.html)
    .pipe(plumber({ errorHandler: notify.onError('Error: <%= error.message %>') }))
    .pipe(
      data(file => {
        return {
          relativePath: file.history[0].replace(file.base, ''), // ページ情報仮置き
        }
      })
    )
    .pipe(
      pug({
        basedir: 'src/pug',
        pretty: true,
      })
    )
    .pipe(
      mode.production(
        htmlmin({
          collapseWhitespace: true,
          minifyJS: true,
          removeComments: true,
        })
      )
    )
    .pipe(dest(destPath.root))
    .pipe(browserSync.reload({ stream: true }))
}

const stylusFunc = () => {
  return src('src/assets/stylus/*.styl')
    .pipe(mode.development(sourcemaps.init()))
    .pipe(plumber({ errorHandler: notify.onError('Error: <%= error.message %>') }))
    .pipe(stylus())
    .pipe(postcss([postcssPresetEnv(autoprefixer)]))
    .pipe(cleanCSS())
    .pipe(mode.development(sourcemaps.write()))
    .pipe(dest(`${destPath.assets}css/`))
    .pipe(browserSync.reload({ stream: true }))
}

const imageFunc = () => {
  return src(srcPath.image).pipe(dest(`${destPath.assets}img/`))
}

const fontsFunc = () => {
  return src(srcPath.fonts).pipe(dest(`${destPath.assets}fonts/`))
}

const staticFunc = () => {
  return src(srcPath.static).pipe(dest(destPath.root))
}

const browserSyncFunc = () => {
  browserSync({
    server: {
      baseDir: 'dist/',
      index: 'index.html',
    },
  })
}

const watchFiles = () => {
  watch(srcPath.html[0], htmlFunc)
  watch(srcPath.stylus, stylusFunc)
  watch(srcPath.js, jsFunc)
  watch(srcPath.image, imageFunc)
  watch(srcPath.static, staticFunc)
  watch(srcPath.fonts, fontsFunc)
}

exports.default = parallel(
  watchFiles,
  [htmlFunc, stylusFunc, jsFunc, imageFunc, staticFunc, fontsFunc],
  browserSyncFunc
)

exports.build = parallel(htmlFunc, stylusFunc, jsFunc, imageFunc, staticFunc, fontsFunc)

webpack.dev.js

const merge = require('webpack-merge')
const common = require('./webpack.common.js')

module.exports = merge(common, {
  mode: 'development',
  devtool: 'inline-source-map',
})

webpack.prod.js

const merge = require('webpack-merge')
const common = require('./webpack.common.js')

module.exports = merge(common, {
  mode: 'production',
  devtool: 'eval',
})

webpack.common.js

const { resolve } = require('path')
const appDir = resolve(__dirname, 'src')
module.exports = {
  context: appDir,
  entry: {
    bundle: './assets/js/common.ts',
    head: './assets/js/head.ts'
  },
  output: {
    filename: '[name].js'
  },
  module: {
    rules: [{
      enforce: 'pre',
      test: /\.ts$/,
      exclude: /node_modules/,
      use: ['ts-loader', {
        loader: 'eslint-loader',
        options: {
          typeCheck: true,
        },
      }],
    }]
  },
  resolve: {
    extensions: [".ts"]
  }
}

変更を行った場所

htmlの変更

パッケージのインストール

$ npm i -D html-webpack-plugin

ページ情報の記載

const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'pug/index.pug',
      inject: false
    }),
  ]
}

プロパティについて

f:id:jackswim3411:20200524041912p:plain
html-webpack-pluginプロパティ一覧

参考: https://www.npmjs.com/package/html-webpack-plugin#hello-world-5

個人的に詰まったところ

htmlの出力を行うとjavascriptファイルの読子mが自動的に出力がされてしまうため、inject: falseの設定を行う必要がある。 ※あくまでも今回は「gulpからの移行 + htmlにjs記載済」の対応のため、inject: falseの設定を行う。

extri.co

cssの変更

パッケージのインストール

$ npm i -D css-loader mini-css-extract-plugin postcss-loader style-loader stylus-loader

cssの設定読み込み

const { resolve, join } = require('path')
const autoprefixer = require('autoprefixer')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const assetsPath = {
  jsPath: 'assets/js',
  cssPath: 'assets/css',
}
module.exports = {
  entry: {
    bundle: './assets/js/common.ts',
  },
  output: {
    path: buildDir,
    publicPath: './',
    filename: join(assetsPath.jsPath, '[name].js'),
  },
  module: {
    rules: [{
      test: /\.styl$/,
      exclude: /node_modules/,
      use: [{
          loader: 'style-loader'
        },
        MiniCssExtractPlugin.loader, {
          loader: 'css-loader',
          options: {
            url: false,
          }
        }, {
          loader: 'postcss-loader',
          options: {
            plugins: [
              autoprefixer({
                grid: true,
                flexbox: true
              })
            ]
          }
        }, 'stylus-loader',
      ]
    }, ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: join(assetsPath.cssPath, '[name].css'),
      ignoreOrder: true
    }),
  ],
}

main.jsにcssファイルの記載

import '../stylus/style.styl' // 指定のcssファイルの場所

img, fonts, staticをコピー

パッケージのインストール

$ npm i -D copy-webpack-plugin

copy-webpack-pluginの設定読み込み

const CopyPlugin = require('copy-webpack-plugin')
const assetsPath = {
  imgPath: 'assets/img',
  fontPath: 'assets/fonts',
  staticPath: 'static',
}
module.exports = {
  plugins: [
    new CopyPlugin({
      patterns: [{
        from: assetsPath.imgPath,
        to: assetsPath.imgPath
      }, ],
    }),
    new CopyPlugin({
      patterns: [{
        from: assetsPath.staticPath,
        to: ''
      }, ],
    }),
    new CopyPlugin({
      patterns: [{
        from: assetsPath.fontPath,
        to: assetsPath.fontPath
      }, ],
    }),
  ]
}

ローカルサーバーの起動

パッケージのインストール

$ npm i -D webpack-dev-server

dev-serverの起動

const { resolve } = require('path')
const merge = require('webpack-merge')
const common = require('./webpack.common.js')
module.exports = {
  devServer: {
    hot: true,
    compress: true,
    writeToDisk: true,
    contentBase: resolve(__dirname, 'dist'),
    watchContentBase: true,
    open: true,
    port: 3000
  },
}

webpack.js.org

個人的に詰まったところ

TypeScriptを使っている場合、

module.exports = {
  resolve: {
    extensions: ['.ts']
  }
}

のような感じで、extensions: ['.ts']の部分が.tsのみだとエラーになる。 このエラーの対応方法として、

module.exports = {
  resolve: {
    extensions: ['.ts', '.js']
  }
}

のように記載を行う。

参考

github.com

ビルドのコマンド変更

変更前

start: ローカルサーバーの起動 build: html, css, jsファイルのビルド

{
  "script": {
    "start": "gulp",
    "build": "gulp build --production",
  }
}

変更後

{
  "script": {
    "start": "webpack-dev-server --config webpack.dev.js",
    "build": "webpack --config webpack.prod.js",
  }
}

追加したパッケージ・削除したパッケージ

今回追加したパッケージ

ファイルのコピーを行う

www.npmjs.com

css + webpackで必要

www.npmjs.com

webpackでhtmlページの出力を行う

www.npmjs.com

webpackでcssファイルを別で書き出しを行う

www.npmjs.com

postcss + webpackで必要(autoprefixerとの連携のため)

www.npmjs.com

pug + webpackで必要

www.npmjs.com

動的にstyleタグを作成し、head内に差し込まれることでcssが適用される

www.npmjs.com

stylus + webpackで必要

www.npmjs.com

webpckでローカルサーバーを開く

www.npmjs.com

今回削除したパッケージ

gulpでローカルサーバーを開くため

www.npmjs.com

gulp本体

www.npmjs.com

gulpでのcssの圧縮

www.npmjs.com

gulpでのページの情報を送るためのもの

www.npmjs.com

gulpでのhtmlの圧縮

www.npmjs.com

gulpで本番環境・開発環境で実行するものを変更ため

www.npmjs.com

gulpでの通知を出す

www.npmjs.com

gulpでのエラー時、処理が停止するのを止める

www.npmjs.com

gulp + postcssで必要

www.npmjs.com

gulp + pugで必要

www.npmjs.com

gulpでstylus, jsのsourcemapの表示

www.npmjs.com

gulp + stylusで必要

www.npmjs.com

gulp + webpackとの連携に必要

www.npmjs.com

変更後のコード

htmlの圧縮などimgファイルの圧縮については未対応。また、ちゃんと整理できていないので、一部不要なものもあり。

webpack.dev.js

const { resolve } = require('path')
const merge = require('webpack-merge')
const common = require('./webpack.common.js')

module.exports = merge(common, {
  mode: 'development',
  devtool: 'inline-source-map',
  devServer: {
    hot: true,
    compress: true,
    writeToDisk: true,
    contentBase: resolve(__dirname, 'dist'),
    watchContentBase: true,
    open: true,
    port: 3000
  },
})

webpack.prod.js

const merge = require('webpack-merge')
const common = require('./webpack.common.js')

module.exports = merge(common, {
  mode: 'production',
  devtool: 'eval',
})

webpcak.common.js

const { resolve, join } = require('path')
const autoprefixer = require('autoprefixer')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CopyPlugin = require('copy-webpack-plugin')
const merge = require('webpack-merge')
const pages = require('./webpack.pages.js')
const appDir = resolve(__dirname, 'src')
const buildDir = resolve(__dirname, 'dist')
const assetsPath = {
  basePath: 'assets',
  jsPath: 'assets/js',
  cssPath: 'assets/css',
  imgPath: 'assets/img',
  fontPath: 'assets/fonts',
  staticPath: 'static',
}
module.exports = merge(pages, {
  context: appDir,
  entry: {
    bundle: './assets/js/common.ts',
    head: './assets/js/head.ts'
  },
  output: {
    path: buildDir,
    publicPath: './',
    filename: join(assetsPath.jsPath, '[name].js'),
    chunkFilename: join(assetsPath.jsPath, '[name]-[hash].bundle.js')
  },
  module: {
    rules: [{
      enforce: 'pre',
      test: /\.ts$/,
      exclude: /node_modules/,
      use: ['ts-loader', {
        loader: 'eslint-loader',
        options: {
          typeCheck: true,
        },
      }],
    }, {
      test: /\.styl$/,
      exclude: /node_modules/,
      use: [{
          loader: 'style-loader'
        },
        MiniCssExtractPlugin.loader, {
          loader: 'css-loader',
          options: {
            url: false,
          }
        }, {
          loader: 'postcss-loader',
          options: {
            plugins: [
              autoprefixer({
                grid: true,
                flexbox: true
              })
            ]
          }
        }, 'stylus-loader',
      ]
    }, {
      test: /\.pug$/,
      use: [{
        loader: 'pug-loader',
        options: {
          pretty: true,
          root: resolve(appDir, 'pug'),
        }
      }]
    }]
  },
  plugins: [
    new CopyPlugin({
      patterns: [{
        from: assetsPath.imgPath,
        to: assetsPath.imgPath
      }, ],
    }),
    new CopyPlugin({
      patterns: [{
        from: assetsPath.staticPath,
        to: ''
      }, ],
    }),
    new CopyPlugin({
      patterns: [{
        from: assetsPath.fontPath,
        to: assetsPath.fontPath
      }, ],
    }),
    new MiniCssExtractPlugin({
      filename: join(assetsPath.cssPath, '[name].css'),
      chunkFilename: join(assetsPath.cssPath, '[name]-[hash].css'),
      ignoreOrder: true
    }),
  ],
  resolve: {
    extensions: ['.ts', '.js']
  }
})

webpack.pages.js

const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'pug/index.pug',
      inject: false
    })
  ]
}