2022.09.18

webpackのEJS Loaderを作って自作のボイラーテンプレートに入れてみた

経緯

webpackとgulpとEJSを利用したボイラーテンプレートからgulpを取り除いてwebpackだけでビルド・ウォッチを実現させる為。

実装した時点でwebpackだけで利用できるLoaderが無くは無かったのですが、webpackのentryに追加することにより出力される .js ファイルを無くすべく自作することにしました。

最終的に出来たものがこちらです。

ejs-template(ver14.0.0)

この記事でのゴール

webpackでEJSのビルドとウォッチをできるようにする。

「ビルド中にエラーを吐かずに使えれば良い」くらいのノリで作ったものなので、公式リポジトリにコントリビュートするレベルの丁寧な作りではないです。

前提条件

以下をインストール済みであること

  • Node.js(16系)
  • npm または yarn

使うもの

ファイル構成

├── babel.config.js
├── package.json
├── src         
│   └── templates      // ビルド対象のEJSを入れるディレクトリ
├── webpack-extensions
│   └── ejs-loader     // Loaderのファイル
└── webpack.config.js

やること

  1. Loader用のファイルを用意
  2. webpack.config.js で1のファイルを利用する

1. Loader用のファイルを用意

このファイルでやること

  • ローカルにある .ejs のファイルを読み取る
  • EJSファイルからhtmlファイルにビルドする

webpackのLoaderの作り方については公式を参照

ejs-loader.js

const path = require('path')
const ejs = require('ejs')

module.exports = function(source) {
  const fileName = path.basename(this.resourcePath)
  const file = this._module.rawRequest
  let _source = source
  
  const regExp = new RegExp(`./src/templates/pages/`)
  const outputFilePath = file.replace(regExp, '')
  const filePathLength = file.split('/').length
  const outputFileName = file.split('/')[filePathLength-1]
  const excludedExtensionFileName = outputFileName.split('.')[0]
  const fileDirectory = outputFilePath.replace(outputFileName, '')
  
  const sourceFileName = `${fileDirectory}/${fileName}`
  const outputFile = `${fileDirectory}${excludedExtensionFileName}.html`
  const assetInfo = { sourceFilename: sourceFileName }  

  ejs.renderFile(this.resourcePath, (err, str) => {
    _source = str
  })
  
  this.emitFile(outputFile, _source, null, assetInfo)
  
  return `export default ${JSON.stringify(_source)}`
}

webpack.config.js で1のファイルを利用する

webpack.config.js

const plugins = [
  compiler => {
    compiler.hooks.compilation.tap('Compile', compilation => {
      compilation.hooks.processAssets.tap(
        {
          name: 'Compile',
          stage: ''
        },
        (files) => {
          // ここで出力されるjsファイルを強制的に取り除いている
          Object.keys(files).forEach((fileName) => {
            const isMatchRemoveFileName = !fileName.includes('assets') && fileName.includes('.js')
            if (isMatchRemoveFileName) {
              compilation.deleteAsset(fileName)
            }
          })
        }
      )
    })
  }
]

module.exports = {
  // ~ 省略 ~
  module: {
    rules: [
      // ~ 省略 ~
      {
        test: //
        use: [
          {
            loader: path.resolve(__dirname, 'webpack-extensions/ejs-loader/index.js') // ここに作成したLoaderのパスを入れる
          }
        ]
      }
    ]
  }
}