前回に続き今回もVS Code用のWordPress Post拡張機能の修正についてです。今回はいくつか機能追加などをしてみました。
この拡張機能は、VS CodeでMarkdownを用いて作成した記事を直接WordPressに投稿できるという大変便利なものです。
以前の修正については下記を参照してみてください。
VSIXファイルのダウンロード
最初に今回の修正後のファイルの案内をしておきます。
今回の修正でバージョンを「0.0.6-m」となっています。
これを利用してみたい方は下記のURLからVSIXファイルをダウンロードして、VS Codeにインストールしてみてください。
WebPアップロード機能の追加
WebPはJPEG/PNGよりも圧縮率が高い画像フォーマットです。
WebPは最近のChrome・Edge・Firefoxなどのブラウザであれば表示することができます。そこで、WebPに対応したブラウザから接続してきたらWebPを送信し、WebPに未対応のブラウザに対してはJPEG/PNGを送信する、というようにWebサーバ(WordPRess)を設定することで、サイトの表示を高速化することができます。
今回の修正では「wordpress-post.image.uploadWebp」という設定項目を用意しました。
ソースコードだと下記の部分です。
このオプションを選択しておくと、WordPress Post拡張機能でJPEG/PNGをWordPRessにアップロードする際に、WebPファイルを生成してアップロードします。この際、JPEGファイルであればロッシーモードでWebPファイルを作成し、PNGファイルであればロスレスモードでWebPファイルを作成します。
このときWebPファイルのスラッグ(=ファイル名)は元のJPEG/PNGファイルのスラッグに「.webp」がついたものになります。
なおこのWebPファイルを活用するには、WordPress(というかWebサーバ)側の設定が必要です。具体的には「.htaccess」というファイルに
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTP_ACCEPT} image/webp
RewriteCond %{REQUEST_FILENAME} (.*)\.(jpe?g|png|gif)$
RewriteCond %{REQUEST_FILENAME}\.webp -f
RewriteCond %{QUERY_STRING} !type=original
RewriteRule (.+)\.(jpe?g|png|gif)$ %{REQUEST_URI}.webp [T=image/webp,L]
</IfModule>
<IfModule mod_headers.c>
<FilesMatch "\.(jpe?g|png|gif)$">
Header append Vary Accept
</FilesMatch>
</IfModule>
AddType image/webp .webp
という記述を追加する必要があります。
.htaccessファイルは設定をミスるとWebサイトに重大な障害を引き起こす可能性があります。
意味がわからない場合は、この設定をすることはオススメしません。
ただし、課題としてWordPressのメディアライブラリにWebPファイルが表示されてしまうという問題があります。もちろんWebPファイルがメディアライブラリに表示されても不具合ではないのですが、元になったPNG/JPEGファイルもも表示されるので、メディアライブラリ内に見た目が同じ画像が2つ表示されることになります。これは少々一覧性を悪くする結果となります。
WebPファイルの生成はWordPressのプラグインを使って行った方が良い気がしてきました。
実装したものの、私はこのオプションをオフにしています。
画像がアップロード済みの場合の動作を改善
画像のアップロード処理についてもいくつか改善をしました。
画像の重複アップロードの回避
私がWordPress拡張機能を使っていて気になったのは同じ画像ファイルが何度もアップロードされてしまうと言うことです。
Markdownで記事を作成して1回だけWordPressに投稿・公開する場合は問題ありません。しかし、WordPressに投稿した後に、もう一度同じMarkdownを使って投稿すると、Markdownに埋め込まれた画像が再度アップロードされてしまうケースがありました。
WordPressの記事としては問題ないのですが、WordPressのメディアライブラリに同じ画像が並んでしまいます。
オリジナルのWordPress拡張機能では、アップロードしようとしている画像のスラグ(ファイル名のようなもの)と一致する画像がWordPressのライブラリにあれば、画像データはアップロードせずWordPressのライブラリの画像を使うように実装されています。
ソースコードで言うと、getUpItem()という関数で有効な戻り値があればリターンしている部分です。
const uploadImage = async (context: Context, slug: string, imgPath: string) => {
// path
const imgParsedPath = path.parse(imgPath);
// find image from wordpress, if exists return this item
const item = await getWpItem(context, "media", { slug: slug }, false);
if (item) {
return item;
}
…
この機能がうまく働けば、画像の重複アップロードは生じないはずなのです。しかし、調べてみるとうまくいかないケースがあることがわかりました。
WordPressは、アップロードした画像のスラグを変更する(末尾に「-1」や「-2」のような枝番つける)ことがあります。これが発動してしまうと、「アップロードしようとしている画像のスラグ(ファイル名のようなもの)と 一致 する画像がWordPressのライブラリにある」という判定はNOになり、画像をアップロードしてしまいます。その結果、重複した画像がアップロードされてしまうことになります。
私が試した感じでは、アップロードした画像のスラグが「640×480」の様な画像サイズ的な情報が含まれていると、枝番をつけるというルールがあるようでした。
それ以外にも枝番をつけるケースはあるかもしれません。
そこでアップロードしようとしている画像のスラグでWordPressのライブラリを検索し、検索結果のスラグから末尾の枝番を除いたものと、アップロードしようとしている画像のスラグを比較することにしました。
また、この比較で同一のものがあった場合は、その画像データを取得し、アップロードしようとしている画像とバイナリ比較を行い、一致した場合はアップロード済みの画像を利用、不一致の場合は新たにアップロードするようにしました。
ソースコードで言うと下記の部分です。
const uploadImage = async (context: Context, slug: string, imgPath: string) => {
// if not exists local image, error
if (!fs.existsSync(imgPath)) {
throw new Error(`Not found local image file : ${imgPath}`);
}
// path
const imageExt = path.parse(imgPath).ext.toLowerCase();
// load image
const imageBin = fs.readFileSync(imgPath);
// find image from wordpress, if exists return this item
const items = await searchMediaInWordPress(context, slug);
for (const item of items) {
const itemSlug = item["slug"];
const itemSlugOriginal = itemSlug.replace(/-[0-9]+$/,""); // remove suffix like "-1", "-2" or etc
if (itemSlugOriginal !== slug.toLowerCase()) {
continue; // item's slug doesn't match with given slug, skip this item
}
if (!item["source_url"].endsWith(imageExt.toLowerCase())) {
continue; // item has different extension, skip this item
}
const res = await axios.get(item["source_url"], {responseType : 'arraybuffer'});
if (res.status !== 200) {
continue; // item doesn't exist, skip this item
}
if (Buffer.compare(res.data, imageBin) !== 0) {
// same file is there but data is different
continue; // different binary, skip this item
}
return item; // same image data is already in WordPress, use this item
}
…
この処理の中で利用されているsearchMediaInWordPress()は次のように実装されています。
const searchMediaInWordPress = async(context: Context, keyword: string) => {
const res = await axios({
url: context.getUrl("media"),
method: 'GET',
params: {search : keyword},
auth: context.getAuth(),
});
return res.data;
};
この関数はアップロードしようとしている画像のスラグでWordPressのメディアライブラリを検索し、ヒットしたアイテムを返します。
複数のアイテムがヒットする場合があるので、uploadImage関数ではfor分でループを回して末尾の枝番を除いたスラグとの一致とバイナリでの一致を調べています。
こうすることで画像が本当に更新された場合だけ、画像をアップロードするようにすることができました。これで同じ画像の重複アップロードは回避できます。
メディアライブラリの検索、および、バイナリ一致の確認処理が追加されたため、WordPress Post拡張機能の処理時間が少々長くなります。
しかし処理が遅くなってもメリットの方が多いと考えています。
古い画像の削除
前節の修正で画像の重複アップロードはしなくなったのですが、スラグが一致してバイナリが異なるファイルが既にWordPressにある場合は、新たな画像をアップロードする動作になります。
これがどうなるかというと、「一度記事をWordPressに投稿した後に、画像を修正して再度投稿すると、その修正した画像は新規にアップロードされる」と言うことになります。これはこれで問題ないのですが、この動作では修正する前の画像はWordPressのメディアライブラリに残ったままとなります。
メディアライブラリに残った画像は利用されませんし、たとえば画像の修正理由がセンシティブな情報の隠し忘れだったりすると、リスクのある画像データがインターネットでアクセス可能なままとなります。これはあまり好ましい状況ではないかもしれません。
そこで「wordpress-post.image.removeIfExist」という設定項目を追加しました。
ソースコードだと下記の部分です。
この設定項目がオンになっている場合は、スラグが一致してバイナリが異なるファイルが既にWordPressにある場合は削除してから、新たな画像をアップロードするようになります。
コードで言うと前節で紹介したバイナリ一致を確認しているところで、不一致だったら、wordpress-post.image.removeIfExistの設定に従ってメディアライブラリ内の画像を削除する様にしました。
if (Buffer.compare(res.data, imageBin) !== 0) {
// same file is there but data is different
if (context.imageRemoveIfExist()) {
let postUrl = context.getUrl("media");
postUrl = `${postUrl}/${item["id"]}?force=true`;
await axios({
url: postUrl,
method: `DELETE`,
auth: context.getAuth(),
});
}
continue; // different binary, skip this item
}
これで画像を修正して再度投稿した際には、過去にアップロードした画像は削除されるようになります。
この削除機能には一つ注意点があります。
ブラウザのキャッシュのせいか、新しい画像を正常にアップロードしたとしても、ブラウザ上には古い画像(削除した画像)が表示されてしまいます。
画像を修正して再度投稿した場合は、その画像のURLを使って直接ブラウザで開くと、キャッシュが更新され、あたらにアップロードした画像が表示されるようになります。
細かい修正
主な修正は上記の2点ですが、そのほかにも細かい修正を行いました。
依存パッケージを更新
VSCodeの拡張機能はTypeScriptで記述されていますが、実行に必要となる他のTypeScript/JavaScriptパッケージはpackages.jsonというファイルに記載されています。
"dependencies": {
"axios": "^0.24.0",
"cheerio": "^1.0.0-rc.10",
"gray-matter": "^4.0.3",
"markdown-it": "^12.3.0",
"probe-image-size": "^7.2.3",
"sharp": "^0.30.7",
"js-beautify": "^1.14.4",
"markdown-it-container": "^3.0.0",
"markdown-it-bracketed-spans": "^1.0.1",
"markdown-it-attrs": "^4.1.4",
"markdown-it-imsize": "^2.0.1",
"markdown-it-html" : "^1.0.0"
}
バージョン情報が古いので、最新のバージョンを利用するように修正しました。
"dependencies": {
"axios": "^1.3.4",
"cheerio": "^1.0.0-rc.10",
"gray-matter": "^4.0.3",
"htmlparser2": "^8.0.2",
"js-beautify": "^1.14.7",
"markdown-it": "^13.0.1",
"markdown-it-attrs": "^4.1.6",
"markdown-it-bracketed-spans": "^1.0.1",
"markdown-it-container": "^3.0.0",
"markdown-it-html": "^1.0.0",
"markdown-it-imsize": "^2.0.1",
"probe-image-size": "^7.2.3",
"sharp": "^0.32.0"
}
「cheerio」についてはすでに1.0.0-rc.12がリリースされていますが、この最新バージョンではどうもうまく動きませんでした。そのため1.0.0-rc.10を使うようにしています。
また、htmlparser2については、パッケー時間の依存関係で自動的にインストールされるため本来は記述不要ですが、何らかの原因でうまく動かないバージョンが入ってしまうことがあるようなので、8.0.2を利用するように明示的に追記しました。
アップロードした縮小画像のスラグ(ファイル名)の変更
WordPress Post拡張機能では「wordpress-post.image.resize」という設定をオンにすることにより、大きい画像を貼り付けた場合に縮小した画像をアップロードすることができます。
このとき縮小した画像のスラグは縮小前の画像のスラグとは別にする必要があります。そこで「縮小前の画像のスラグ-縮小後の幅x縮小後の高さ」を縮小した画像のスラグとして利用するようにしていました。
しかし、このスラグには次のような問題があることの気づきました。
- スラグの末尾に「-640×480」のようなものがついていると、WordPressが自動的に枝番(「-1」や「-2」のような数値)を付与してしまう
- 画像のサイズを変えて再投稿した場合に、以前にアップロードした縮小画像のスラグと異なってしまう → 新設した「wordpress-post.image.removeIfExist」を有効にしても、縮小画像のスラグが異なるため、サイズ変更前の画像をメディアライブラリから削除することができない
前者の方はスラグが長くなるだけですが、後者の方は不要な画像がメディアライブラリに残ってしまうためあまり好ましくありません。
そこで縮小画像のスラグを縮小前スラグの末尾に「s」をつけるように変更しました。
getResizedImageSlug(imageSlug: string, width: number, height: number): string {
const sep: string = this.getConf("slugSepalator");
const suffix: string = "s";
return imageSlug + sep + suffix;
}
元々は縮小画像のスラグにサイズの情報入れていたため、この関数の引数には画像のサイズ(widthとheight)がありますが、これは使わずに「s」を末尾につけるようにしています。
VSIXファイルの構成変更
私が修正したWordPress Post拡張機能は非公式修正のため、VS Code拡張機能のマーケットプレースには公開しておりません。その代わり、GitHubにVSIXファイルを公開することで簡単にインストールできるようにしています。
このVSIXファイルはVSCodeの拡張機能を作成する環境で「vsce package」というコマンドを実行すると作成することができます。
しかし、修正したWordPress Post拡張機能では画像のリサイズのためSharpというパッケージを利用しており、このSharpのおかげで一筋縄では行かないことがわかりました。
このSharpは処理の高速化のため、ネイティブのライブラリを使用しており、実行しているPCのアーキテクチャをマッチしたライブラリがVSIXファイルに含まれていないと、実行時にエラーになってしまいます。そしていろいろ調べてみると、32bit Windows用と64bit Windows用ではこのネイティブライブラリの名前が全く同一のため、1つのVSIXファイルで共存できない(1つのVSIXファイルで32bit Windowsと64bit Windowsの両対応ができない)ことがわかりました。
そこで32bit Windows用のVSIXファイルだけ別にすることにしました。
具体的には次のようなスクリプトを造り、「wordpress-post-win32-ia32-バージョン.vsix (32bit Window専用のパッケージ)と「wordpress-post-バージョン.vsix (その他の環境用のパッケージ)」を生成することにしました。
#!/bin/sh
# clean binaries
rm -rf node_modules/sharp/vendor/
rm -rf node_modules/sharp/build/Release/
# for win32-ia32 architecture
npm rebuild --platform=win32 --arch=ia32 sharp
npx vsce package --target win32-ia32
# clean binaries
rm -rf node_modules/sharp/vendor/
rm -rf node_modules/sharp/build/Release/
# for other architectures
npm rebuild --platform=win32 --arch=x64 sharp
npm rebuild --platform=darwin --arch=x64 sharp
npm rebuild --platform=darwin --arch=arm64 sharp
npm rebuild --platform=linux --arch=x64 sharp
npm rebuild --platform=linux --arch=arm64 sharp
npx vsce package
このスクリプトもGitHubに追加してあります。
まとめ
今回はWordPressに記事を投稿することができるVS Code拡張機能であるWordPRess Post拡張機能の気になる点を修正してみました。
画像の多重アップロードは結構困っていたので、解決できて良かったです。自分で機能を増やしたり、動作を修正できるのがオープンソースの良いところと実感できました。
コメント