webpack

HtmlWebpackPluginの`template:`には`*.js`ファイルを指定してもいい

More than 1 year has passed since last update.

こんな人へ

  • PugとかHandlebarsとか使い慣れてないし、普通にNodeJSで書きたい
  • HtmlWebpackPluginに関する処理をwebpack.config.jsに入れたくない

https://github.com/nju33-qiita/example-templatejs-of-html-webpack-template

HtmlWebpackPlugin

そのWebpackで作られるファイルを、<body/>の最後で読み込むHTMLを作るプラグイン。

{
  plugins: [new HtmlWebpackPlugin()]
}

これで、特に何もしなくてもバンドルファイルを埋め込んだ.htmlが作れる。
webpack.output.filename'bundle.js?[hash]'な感じにしてるとCacheBustingな感じで埋め込んでくれていいです。

Templateを設定

pug-loaderをインストールすれば、./template.pugファイルをテンプレートに設定できる。(最後にバンドルファイルが挿入されるのは一緒)

{
  plugins: [
    new HtmlWebpackPlugin({template: './template.pug'})
  ]
}

ただオプション多いし引数で何渡ってるのか全然分からない😇
あとググるのに疲れてる。

(なのであんまり詳しくないので、オプションでいいじゃんという話かも)

Templateに.jsを指定

Templateのオプションだと分かんなかったんですが.jsが指定できます。

{
  plugins: [
    new HtmlWebpackPlugin({template: './template.js'})
  ]
}

template.jsはこんな感じにHTMLの文字列を返すようにします。

// (params: {[string]: any}) => stirng | Promise<string>
module.exports = () => '<html>...</html>';

ちなみにTypescriptなルールがあるなら./template.tsも渡せます。

HTMLは別ファイルに

別に.jsに書いちゃっていいんですが、分けたいと思ったのでhtmlは同じディレクトリのtemplate.htmlというファイルに記述したいとします。

const fs = require('fs');
const path = require('path');
const {promisify} = require('util');

module.exports = async params => {
  // __dirname === '/'
  // process.cwd() === '/'

  // webpack.context
  const {context} = params.compilation.options; 

  const htmlFilename = path.resolve(context, 'path/to/template.html');
  const html = await promisify(fs.readFile)(htmlFilename, 'utf-8');
  return html;
}

同じディレクトリなので./template.htmlでいいような感じがしますが、それは/template.htmlを見てしまうのでエラーになります。かといって絶対パスを指定すると、他の人も関係するプロジェクトの場合問題になるので、webpack.contextの基準パスを使います。
webpack.contextは、そのWebpackの基準パスが入ってます。

Templateに値を入れて展開

このTemplateでしか使ってないとかなら、ここでPugとか使えばいいんです。
例えば.cssをCacheBustingな感じにして埋め込むみたいなのとか。何でもいいんですが、ちっさいのでいいのでpupaを使ってやってみます。

HTMLにこんな感じで追記して、

<link rel="stylesheet" href="{href}">

globで探して、Hash取って、pupaで展開したHTMLを返します。

// ...
const crypto = requrie('crypto');
const glob = require('glob');

module.exports = async params => {
  // ...

  const [filename] = await promisify(glob)(path.resolve(context, 'base/foo.css'));
  const css = await promisify(fs.readFile)(filename, 'utf-8');
  const basename = path.basename(filename);
  const md5hash = crypto.createHash('md5')
  const md5hash.update(css);
  return pupa(html, {
    href: '/' + basename + '?' + md5hash.digest('hex').slice(0, 20)
  });
};

これでこんな感じ。

<link rel="stylesheet" href="/foo.css?11985b07e3121564a73d">

これで埋め込めました。
バンドルファイルに含ませたくないんだ的なファイルに使うといいと思います。(本番のdllとかcssとか)

注意

babel-loaderとかを.jsが経由する感じだと、template:で読み込む時にシンタックスで怒られたりするので注意です。(Runtimeとかあのへん)