[Webpack]ejsをhtmlに変換時、includeとsassと画像を利用する

やりたいこと

  • webpackでejsをhtmlに変換する
  • フッターやヘッダーなどの共通部分をincludeで読み込む
  • sassをcssに変換する
  • 画像やフォントなどを使用する

想像以上にハマったのでテンプレートを公開する。

TL;DR

githubならココ
https://github.com/kajikentaro/webpack-ejs-html

ソースのzipがほしいならココ
https://github.com/kajikentaro/webpack-ejs-html/archive/refs/tags/1.2.zip

環境

  • node 18.6

古すぎるとうまく行かないかも

仕組み

srcフォルダ内の全てのejsのパスをエントリーポイント(webpackが最初に読み込むファイル)にする。
ファイル名先頭が_で始まるものはスキップ。

template-ejs-loader ejsのパスをHTMLに変換する。includeが使われていたらそれも処理する
html-loader HTMLを読み込む。ファイル内でsassや外部JavaScriptが使われていたらsass, 外部JavaScriptのパスをwebpackに渡す
HtmlWebpackPlugin HTMLをファイルに出力する。改行やコメントが使われていたら消す

webpack-remove-empty-scripts webpackはejsやsassなどの入力ファイルを全てjs(main.jsなど)として出力してしまうので、その余計なファイルを消す

MiniCssExtractPlugin.loader, “css-loader”, “

sass-loader sassのパスをcssに変換する
css-loader cssを読み込む。ほぼ何もしないで右から左に渡す
mini-css-extract-plugin cssをファイルに出力する

copy-webpack-plugin src/publicフォルダーをdist/publicにコピーする

注意

ejs, sass, jsはwebpackでコンパイルされるため相対パスを使う

ejsはwebpack上でtemplate-ejs-loaderhtml-loadercopy-webpack-pluginの順番で処理される。
includeや外部jsを使用する際は相対パスを利用すること

<%- include("../template/_footer.ejs") %>
<script type="text/javascript" charset="utf-8" src="./script/script.js"></script>

静的ファイル(ejs, sass, js以外)は全てpublicフォルダに置く

画像やフォントなどwebpackで変換が不要なものはsrc/publicに配置する。
CopyWebpackPluginがpublicディレクトリをまるごとdistにコピーしているため

こちらは<img src="/public/sample.png"/>のように絶対パスで記述してもOK

テンプレートはファイル名先頭に_をつける

拡張子がejsのファイルは全てwebpackのエントリーポイントになるが、
アンダーバー_ をファイル名の先頭につけることで例外になる。

例えば共通フッターを_footer.ejsではなくfooter.ejsのファイル名にするとfooter.htmlというファイルが作成されてしまう。

webpack.confing.js

const glob = require("glob");
const path = require("path");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const RemoveEmptyScriptsPlugin = require('webpack-remove-empty-scripts');

// srcディレクトリと拡張子を消す
// ex: ./src/index.html -> ./index
const arrangePath = (fullPath) => {
  const removedExtension = fullPath.match(/(.+\/.+?).[a-z]+([?#;].*)?$/)[1];
  const removedSrcDir = removedExtension.replace(/src\//, "");
  return removedSrcDir;
}

// src配下の全てのejsパスを取得する
const getAllEjs = () => {
  const ejsList = glob.sync("./src/**/[!_]*.ejs");
  return ejsList;
}

// src配下の全てのejsを、ejsからhtmlに変換するプラグインを作成する
const getHtmlPlugins = () => {
  const ejsList = getAllEjs();
  const htmlWebpackPlugins = ejsList.map((v) => {
    return new HtmlWebpackPlugin({
      filename: arrangePath(v) + ".html",
      template: v,
      minify: true
    })
  })
  return htmlWebpackPlugins;
}


module.exports = {
  entry: getAllEjs(),
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "[name].js",
  },
  module: {
    rules: [
      {
        test: /\.ejs$/,
        use: [
          {
            loader: "html-loader",
            options: {
              sources: {
                // copy-webpack-pluginでpublicをまるごとコピーするためcss以外の名前解決は行わない
                urlFilter: (attribute, value, resourcePath) => {
                  if (/\.(scss|sass|css)$/.test(value)) {
                    return true;
                  }
                  return false;
                },
              }
            },
          },
          "template-ejs-loader"
        ],
      },
      {
        test: /\.(scss|sass|css)$/,
        use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
      },
    ],
  },
  plugins: [
    // webpackの仕様上, 余計なjsファイルが生まれるので削除
    new RemoveEmptyScriptsPlugin({
      extensions: /\.(css|scss|sass|less|styl|ejs|html)([?].*)?$/,
      remove: /\.(js|mjs)$/
    }),
    // htmlをdistに出力
    ...getHtmlPlugins(),
    // cssをdistに出力
    new MiniCssExtractPlugin({
      filename: "[contenthash].css"
    }),
    // publicフォルダーをdistにコピー
    new CopyWebpackPlugin({
      patterns: [
        { from: path.resolve(__dirname, "src/public"), to: path.resolve(__dirname, "dist/public") },
      ],
    }),
  ],
};

package.json

npm run dev を実行することでdist フォルダに開発用のビルドされたファイルが生成される

npm run build を実行することでdist フォルダに本番用のビルドされたファイルが生成される

npm run watch を実行することでWebサーバーを起動する。ファイルを編集すると自動でリロードされる

{
  "name": "test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "webpack --mode=development",
    "build": "webpack --mode=production",
    "watch": "browser-sync start -s dist -w dist/** & webpack --mode=development --watch"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "browser-sync": "^2.27.10",
    "copy-webpack-plugin": "^11.0.0",
    "css-loader": "^6.7.1",
    "ejs": "^3.1.8",
    "file-loader": "^6.2.0",
    "glob": "^8.0.3",
    "html-loader": "^4.1.0",
    "html-webpack-plugin": "^5.5.0",
    "mini-css-extract-plugin": "^2.6.1",
    "sass": "^1.45.1",
    "sass-loader": "^12.4.0",
    "template-ejs-loader": "^0.9.3",
    "webpack": "^5.73.0",
    "webpack-cli": "^4.10.0",
    "webpack-remove-empty-scripts": "^0.8.1"
  }
}

まとめ

webpackを使用することでejsをhtmlにビルドすることができた。

ejsを用いればヘッダーやフッター、各コンポーネントなどの共通化を行うことができる。
もちろんfor文で同じ処理を繰り返すみたいな事もできるので便利。

変換したファイル群はdist/に出力されるので、それをS3に保存したりやftpでアップロードすれば公開ができる。

webpackで生成したファイルは、コメントや無駄な空白改行が削除されているので、容量の削減やユーザーの信頼性の向上が期待できる。

コメント

タイトルとURLをコピーしました