nisshiee.org

html-webpack-pluginにhookを差し込んでtarget="_blank"を自動でつける

2019-02-04

経緯

以前このブログをnisshiee.org下に移設したときの記事に書いたとおり、本サイトはwebpackを使って生成している。この手法は、良くも悪くもビルドステップを自由にカスタマイズできるという特徴がある。自分で好きなようにできるというメリットでもあるが、そこらのブログサービスならだいたいやってくれそうなことも自分でやらないといけないというデメリットでもある。

で、そういうビルドステップでやりたいけど放置してたことの1つに「外部サイトへのリンクに自動でtarget="_blank"をつける」というものがある。これまでは全ての外部リンクに1個ずつ手で書いていた(ちなみに1個typoしていた)。

いつか自動にしたいなーと思っていたら、ちょうどtwitterとVeinにこんな記事が流れてきた。

リンクを作る時の target="_blank" の危険性 - 隙あらば寝る(Hatena Blog)

これはちょっと前の記事だが、nisshiee.orgもこの対応をしなきゃいけないんだった、と思い出し、自動化と合わせてやることにした。

方針

既存のビルドステップは以下のようにwebpackとhtml-webpack-pluginを使ってHTMLを生成している。

webpack.config.js

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

const htmlPlugins = pageFiles.map(template => {
  const filename = template.replace(/^src\/page\//, '').replace(/\.pug$/, '.html')
  return new HtmlWebpackPlugin({ filename, template, inject: false })
})

const config = {
  plugins: [
    ...
  ].concat(htmlPlugins),
}

html-webpack-pluginには処理の間にhookを挟む機構がある。詳細は本家を見てもらいたいが、html-webpack-pluginがHTML文字列を生成したあとに処理を差し込んで、HTMLの書き換えが可能だ。

というわけで今回は、html-webpack-pluginに差し込むhookを自前のwebpack pluginとして実装する。

詳細

この記事は以下のバージョンを前提にしている。

  • webpack: 4.26.0
  • html-webpack-plugin: 3.2.0

ここから書いたコードなどを載せていくが、pluginの書き方はwebpackやhtml-webpack-pluginのバージョンによってかなり変わっているようなので注意。特にhtml-webpack-pluginのREADME.mdは2019-02-04時点のmasterブランチが3.2.0からだいぶ変わっている模様。

lib/plugins/extlink-plugin.js

function ExtlinkPlugin(options) {}

const extlinkRegex = /(<a(?= )[^>]*) (href="http)/g
const extlinkReplacement = '$1 target="_blank" rel="noopener noreferrer" $2'

ExtlinkPlugin.prototype.apply = function(compiler) {
  compiler.hooks.compilation.tap('ExtlinkPlugin', function (compilation) {
    compilation.hooks.htmlWebpackPluginAfterHtmlProcessing.tapAsync('ExtlinkPlugin', function (data, cb) {
      data.html = data.html.replace(extlinkRegex, extlinkReplacement)
      cb(null, data)
    });
  })
}

module.exports = ExtlinkPlugin

webpack.config.js

const config = {
  plugins: [
    ...
  ].concat(htmlPlugins).concat([
    new ExtlinkPlugin(),
  ]),
}

やっていることは先に説明したとおりで、HTML生成後に、正規表現で外部リンクにtarget="_blank" rel="noopener noreferrer"を付与している。

また注意点として、webpack.config.jsに渡すplugins配列は、自前pluginがhtml-webpack-pluginより後ろにある必要がある。

結果

というわけでこれでうまく動いた。

markdownで書いているブログ記事は以下のように、

[webpack](https://webpack.js.org/)

pugで書いている他のページは、

a(href="https://twitter.com/nisshieeorg") @nisshieeorg

と書くだけで自動で属性がつくようになった。