VS Code用WordPress Post拡張機能を修正してみる その2

VS Code用WordPress Post拡張機能を修正してみる その2 Visual Studio Code
スポンサーリンク

前回は、WordPressの記事をVS Codeで作成できる拡張機能であるWordPress Post拡張機能を修正したことを紹介しました。

今回はその続きになります。

スポンサーリンク

WordPress Postの残念な点

WordPress Postは下記で公開されているVS Code用拡張機能です。

WordPress Post - Visual Studio Marketplace
Extension for Visual Studio Code - This extension posts articles to WordPress.

この拡張機能を使うと、VS Code上でMarkdownで作成した記事をそのままWordPressに投稿することができ、大変便利です。

しかし、実際に使って見るとこの拡張機能には次のような残念な点がありました。

  • 画像にサイズ指定がない
    WordPress Post拡張機能によりMarkdownから生成されるHTMLのimgタグにはサイズを示す属性(widthとheight)がつきません。
    このためSEO的にはマイナス要素である「Cumulative Layout Shift (CLS)」を発生させる可能性があります。
  • 画像のサイズが考慮されない
    Markdownに埋め込んだ画像をそのままWordPressに投稿してしまうため、画像が大きいと表示時に縮小されるにもかかわらず大きいが画像データを読み込むことになります。
    これはWebページの表示が遅くなることにつながり、あまり好ましくありません。できればリサイズしたいところです。
  • コードブロックから生成されるHTMLがイマイチ
    Markdownでソースコードを記述するとWordPress Post拡張機能ではHTMLのpreタグとcodeタグを利用したブロックを生成してくれます。
    しかし、私が利用しているWordPressテーマで利用しているSyntax Highlighterでは正しくハイライト表示してくれません。
  • 生成されるHTMLがイマイチ
    WordPress Post拡張機能で生成されるHTMLは整理されていないため、加工するのが少々面倒です。
    WordPRessに記事を投稿してから、WordPress上でHTMLを修正することもあるので、できれば見やすいHTMLを生成して欲しいところです。

前回はこのうち「コードブロックから生成されるHTMLがイマイチ」という点を修正しました。

今回は画像関係の「画像にサイズ指定がない」と「画像のサイズが考慮されない」について修正をしてみます。

なお、修正するに当たり、オリジナルのWordPress Post拡張機能のソースコードは下記にフォークして作業をしています。

GitHub - scratchpadjp/vscode-wordpress-post
Contribute to scratchpadjp/vscode-wordpress-post development by creating an account on GitHub.
スポンサーリンク

画像の取扱の修正

WordPress Post拡張機能を使った時の最大の課題が画像の取扱でした。

特に「画像にサイズ指定がない」「画像のサイズが考慮されない」の二点は、私にこの拡張機能の利用を断念させた主な理由です。

このあたりをなんとか修正してこの拡張機能をより使いやすいものとしたいところです。

修正の方針

今回は画像周りを変えるということで一気に次のような変更・機能追加をすることにしました。

  • 画像の最大サイズ(幅と高さ)を設定できるようにして、Markdownに埋め込んだ画像データがそのサイズを超えている場合は縮小した画像をWordPressに投稿する。
    • JPEG画像を縮小する際には、データサイズを削減できるように、クォリティと高効率のMozJpegエンコーダを使用できるようにする。
    • PNG画像を縮小する際には、データサイズを削減できるように、パレットモード(インデックスカラー)を使用できるようにする。
  • WordPress Post拡張機能に元から有る「useLinkableImage」という設定がtrueの場合は、縮小前の画像データにリンクするようにする。
  • 生成されるHTMLのimgタグにはwidth属性とhight属性をつける。
  • Markdownで画像のタイトルが指定されていない場合は、生成されるHTMLのimgタグにはalt属性の値を流用してtitle属性をつける

なお、新設する設定項目でデフォルト値を選択すると、オリジナルのWordPress Post拡張機能と同じ動作となるようにします。

ソースコードの修正

この修正を行ったブランチは下記になります。

GitHub - scratchpadjp/vscode-wordpress-post at change-image-handling
Contribute to scratchpadjp/vscode-wordpress-post development by creating an account on GitHub.

package.json

package.jsonでは、拡張機能の設定項目の追加と、動作に必要なパッケージ(ライブラリ)の追加のために変更します。

設定項目の追加

設定項目の追加はpackage.jsonを変更することで行います。
今回は「contributes」→「configuration」→「properties」に下記の項目を追加しました。

        "wordpress-post.image.addTitleAttribute" : {
          "type" : "boolean",
          "default": false,
          "description": "Add title attribute to img tag using alt attribute value if title is not specified in Markdown."
        },
        "wordpress-post.image.addSizeAttributes" : {
          "type": "boolean",
          "default": false,
          "description": "Add hight and width attributes to img tag."
        },
        "wordpress-post.image.resize" : {
          "type": "boolean",
          "default" : false,
          "description" : "Resize image size down to max width or max height if exceeds. To use this feature you need to specify either max width or max height at least. In addition, size attributes are automatically added to img tag if this option is true."
        },
        "wordpress-post.image.resizeJpegQuality" : {
          "type": "number",
          "default": 80,
          "description": "JPEG quality when resizing. (default: 80)"
        },
        "wordpress-post.image.resizeJpegUseMozjpeg" : {
          "type": "boolean",
          "default": false,
          "description": "Use mozjpeg to reduce JPEG file size when resizing."
        },
        "wordpress-post.image.resizePngUsePalette" : {
          "type": "boolean",
          "default": false,
          "description": "Use palette (index color) to reduce PNG file size when resizing."
        },
        "wordpress-post.image.maxWidth" : {
          "type": "number",
          "default": 0,
          "description": "Max image width. Use 0 to specify no limit."
        },
        "wordpress-post.image.maxHeight" : {
          "type": "number",
          "default": 0,
          "description": "Max image height. use 0 to specify no limit."
        },

「wordpress-post.image.maxWidth」と「wordpress-post.image.maxHeight」は画像の最大サイズ(幅と高さ)を設定で、デフォルトは「0」になっています。これらの設定が0の場合はサイズに制限がありません(=画像は縮小されません)。

「wordpress-post.image.resize」はMarkdownに埋め込まれた画像が「wordpress-post.image.maxWidth」あるいは「wordpress-post.image.maxHeight」を超過した場合にリサイズ(縮小)するかどうかの設定でデフォルトは 「false(リサイズはしない)」です。

「wordpress-post.image.resizeJpegQuality」はJPEG画像をリサイズするときのJPEG品質で、デフォルトは「80」です。これを小さくすればリサイズしたJPEGファイルのサイズは小さくなりますが、画質は低下します。

「wordpress-post.image.resizeJpegUseMozjpeg」はリサイズ時にMozJpegエンコーダを使うかどうかの設定で、デフォルトは「false(使用しない)」です。MozJpegエンコーダを使うと同一クォリティでもデータサイズが小さくなると言われています。

「wordpress-post.image.resizePngUsePalette」はPNG画像をリサイズするときにパレットモードに変換するかどうかの設定で、デフォルトは「false(変換しない)」です。パレットモードはインデックスカラーとも呼ばれ、色を表すデータ量を8bitに削減するものです。パレットモードを利用するとデータサイズが小さくなりますが、画質は低下します。

「wordpress-post.image.addSizeAttributes」はHTMLのimgタグにwidth属性とheight属性をつけるかどうかの設定で、デフォルトは「false(つけない)」です。
width属性とheight属性がないとHTMLの表示時にレイアウトがずれることがあるので、この設定はオン「(true)」にするのがオススメです。

「wordpress-post.image.addTitleAttribute」はMarkdownで画像にタイトルを設定していないときに、HTMLのimgタグにtitle属性をつけるかどうかの設定で、デフォルトは「false(つけない)」です。この設定を「オン(true)」にすると、Markdownでタイトルが省略された場合に、title属性にalt属性と同じ値を設定します。なお、Markdownで画像のタイトルを設定している場合は、この設定によらず、imgタグにtitle属性が設定されます。

パッケージの追加

今回は画像の画像サイズを取得するのに「probe-image-size」というパッケージを利用することにしました。

probe-image-size
Get image size without full download (JPG, GIF, PNG, WebP, BMP, TIFF, PSD). Latest version: 7.2.3, last published: 2 years ago. Start using probe-image-size in your project by running `npm i probe-image-size`. There are 263 other projects i...

この「probe-image-size」はURLまたはストリーム(ファイル)で指定された画像のサイズを取得することができ、今回の用途にぴったりです。

また、画像のリサイズには「sharp」というパッケージを利用しました。

sharp
High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, GIF, AVIF and TIFF images. Latest version: 0.32.4, last published: 24 days ago. Start using sharp in your project by running `npm i sharp`. There are 3...

このパッケージを選択した理由は、ググってたまたまヒットしたのと、日本語の情報があったためです。特にこだわりはないのですが、試してみると十分な機能を持っていました。

今回の修正で追加したMozJpegの利用やPNGでのパレットモードの利用などは、このsharpの機能を利用しています。

利用するパッケージの情報はpackage.jsonの「dependencies」に次のように追加します。

    "probe-image-size" : "^7.2.3",
    "sharp" : "^0.30.7"

パッケージのバージョンには自分が試したとき以降のバージョンを利用するように指定します。

「devDependencies」にも同様の情報を追記したのですが、どうも「dependencies」への追加だけで良いようです。

なお、package.jsonの「dependencies」を変更した場合には、「npm install」を実行する必要があるようです。

「npm install」を実行すると、package-lock.jsonが更新されるので、このファイルもコミットしておきます。

context.ts

この拡張機能では設定項目関係の情報を取得する関数がcontext.tsに記述されています。

今回はこのファイルにpackage.jsonで追加した設定項目を取得するための関数を追加しました。

  imageAddTitleAttribute(): boolean {
    return this.getConf("image.addTitleAttribute");
  }

  imageAddSizeAttributes(): boolean {
    return this.getConf("image.addSizeAttributes");
  }

  imageResize(): boolean {
    return this.getConf("image.resize");
  }

  getImageResizeJpegQuality(): number {
    return this.getConf("image.resizeJpegQuality");
  }

  useMozjpeg(): boolean {
    return this.getConf("image.resizeJpegUseMozjpeg");
  }

  usePngPalette(): boolean {
    return this.getConf("image.resizePngUsePalette");
  }

  getImageMaxSize(): [number, number] {
    return [this.getConf("image.maxWidth"), this.getConf("image.maxHeight")];
  }

また、画像を縮小した場合に、その縮小画像のファイル名を決めるための関数も追加しました。

  getAttachedImageThumbnailSlug(imageSlug: string, width: number, height: number): string {
    const sep: string = this.getConf("slugSepalator");
    const size: string = width.toString() + "x" + height.toString();
    return imageSlug + sep + size;
  }

この関数は元からあった画像のファイル名を決める関数(getAttachedImageSlug)を参考に作成しました。

post.ts

post.tsはこの拡張機能のメイン動作を記述しているファイルです。

今回の修正ではこのファイルへの変更はかなり多くなります。

関数の追加

まずMarkdownに埋め込まれた画像の縮小が必要かどうかを判定するために、画像サイズを取得する必要があります。
この処理については次のような関数を用意しました。

async function getImageSize(base: string, src: string) {
  const probe = require('probe-image-size');
  
  if (src.match(REG_WWWIMG)) {
    const result = await probe(src);
    return [result.width, result.height];
  }

  let data = fs.readFileSync(base + "/" + src);
  let result = probe.sync(data);
  return [result.width, result.height];
};

第1引数baseはMarkdownが置かれているディレクトリで、第2引数srcはMarkdownに記載された画像のURIです。

画像サイズの取得についてはprobe-image-sizeパッケージを利用します。

引数srcに指定されたURIがhttp・httpsの場合はそのままprobe-image-sizeパッケージに渡し、そうでない場合(ローカルのファイルが指定されている場合)は引数baseに指定されたディレクトリを利用してファイルパスを生成した上でprobe-image-sizeパッケージを利用しします。

また、画像の表示サイズを計算する関数を次のように用意しました。

function calculateImageSize(imgWidth: number, imgHeight: number, maxWidth: number, maxHeight: number) : [number, number] {

  if ( (imgWidth <= maxWidth) || (maxWidth === 0) ) {
    if ( (imgHeight <= maxHeight) || (maxHeight === 0) ) {
      return [imgWidth, imgHeight];
    } else {
      return [Math.trunc(imgWidth * maxHeight / imgHeight), maxHeight];
    }
  }

  // imgWidth is greater than maxWidth
  if ( (imgHeight <= maxHeight) || (maxHeight === 0) ) {
      return [maxWidth, Math.trunc(imgHeight * maxWidth / imgWidth)];
  }

  // both imgHeight and imgWidth are greater than maxWidth and maxHeight
  const widthRatio = imgWidth / maxWidth;
  const heightRatio = imgHeight / maxHeight;
  if ( widthRatio > heightRatio ) {
    return [maxWidth, Math.trunc(imgHeight * maxWidth / imgWidth)];
  } else {
    return [Math.trunc(imgWidth * maxHeight / imgHeight), maxHeight];
  }
};

引数のimgWidthとimgHeightは画像のサイズ、引数のmaxWidth・maxHeightは設定された画像の最大サイズです。

この関数ではimgWidthとimgHeightが、maxWidth・maxHeightに収まるようなサイズを計算します。もちろん画像のアスペクト比は維持するようにします。

post関数の修正

post関数はこの拡張機能が実行されたときに実行される関数です。

この関数でMarkdown内の各画像に対して次のような処理をしていきます。

まず、title属性を追加するように設定されている場合に、Markdownで画像にタイトルが指定されていないと、alt属性の値をtitle属性に利用します。

    // add title attribute
    if ( context.imageAddTitleAttribute() ) {
      if ( !ch(imgs[i]).attr("title") ) {
        ch(imgs[i]).attr("title", ch(imgs[i]).attr("alt"));
      }
    }

続いて、画像サイズに関する情報を取得します。

    const [orgImgWidth, orgImgHeight] = await getImageSize(docParsedPath.dir, srcAttr);
    const [maxImgWidth, maxImgHeight] = context.getImageMaxSize();
    const [displayImgWidth, displayImgHeight] = calculateImageSize(orgImgWidth, orgImgHeight, maxImgWidth, maxImgHeight);

画像のURIがhttp・httpsであった場合は、必要に応じてimgタグにwidth・height属性を追加します。

      if ( context.imageResize() ) {
        ch(imgs[i]).attr("width", displayImgWidth.toString());
        ch(imgs[i]).attr("height", displayImgHeight.toString());        
      } else {
        if ( context.imageAddSizeAttributes() ) {
          ch(imgs[i]).attr("width", orgImgWidth.toString());
          ch(imgs[i]).attr("height", orgImgHeight.toString());
        }
      }

width・height属性の値は、リサイズをする設定であった場合にはcalculateImageSize()で計算したサイズを、そうでない場合は元々の画像サイズを利用します。

画像のURIがhttp・httpsであった場合には画像をダウンロードして縮小した画像を生成することも考えたのですが、勝手に画像をダウンロードするのもよくないかと思い、縮小はimgタグのwidth・height属性を使って行うことにしました。

一方、画像のURIがファイルであった場合には、オリジナルファイルのアップロードに加え、リサイズする設定かつリサイズする必要があったら、CalculateImageSize()で計算したサイズに会わせて縮小画像(サムネイル画像)を生成します。

      // generate thumbnail image if needed.
      if ( context.imageResize() ) {
        if ( (orgImgWidth !== displayImgWidth) || (orgImgHeight !== displayImgHeight) ) {
          const size = displayImgWidth.toString() + "x" + displayImgHeight.toString();
          const thumbnail = 
            path.join(
              path.parse(attachedImgPath).dir,
              path.parse(attachedImgPath).name + "-" + size + path.parse(attachedImgPath).ext
            );
          const thumbnailSlug = context.getAttachedImageThumbnailSlug(imgSlug, displayImgWidth, displayImgHeight);  

          /* generate thumbnail */
          const sharp = require("sharp");
          try {
            let data = sharp(attachedImgPath).resize({
              width: displayImgWidth,
              height: displayImgHeight,
              fit: "fill" 
            });

            // encode JPEG or PNG according to configuration
            const ext = path.parse(attachedImgPath).ext.toLowerCase();
            if ( (ext === ".jpg") || (ext === ".jpeg") ) {
              data = await data.jpeg({
                quality: context.getImageResizeJpegQuality(),
                mozjpeg: context.useMozjpeg()
              });
            }
            if ( ext === ".png" ) {
              data = await data.png({
                palette: context.usePngPalette()
              });
            }
            await data.toFile(thumbnail);
          }
          catch(err) {
            const msg = `Can't generate thumbnail file: ${attachedImgPath}`;
            context.debug(msg);
            throw new Error(msg);
          };

          /* upload thumbnail to wordpress */
          const imgItem = await uploadImage(context, thumbnailSlug, thumbnail);
          srcAttr = context.replaceAttachedImageUrl(imgItem["source_url"]);

          ch(imgs[i]).attr("width", displayImgWidth.toString());
          ch(imgs[i]).attr("height", displayImgHeight.toString());
        }
      } else {
        if ( context.imageAddSizeAttributes() ) {
          ch(imgs[i]).attr("width", orgImgWidth.toString());
          ch(imgs[i]).attr("height", orgImgHeight.toString());
        }
      }

サムネイル画像の生成にはsharpパッケージを利用し、縮小する際のJPEGクオリティ・MozJpegの利用・PNGパレットモードの利用等は拡張機能の設定に応じます。

生成されたサムネイル画像は、context.tsで定義したgetAttachedImageThumbnailSlug()によって決定されるファイル名で保存され、WordPressにアップロードされます。

このWordPressにアップロードする処理には、元からあったuploadImage()を流用しています。そしてHTMLのimgタグのwidth・height属性をサムネイル画像のサイズに変更します。

なお、縮小が不要な場合には、設定に応じてimgタグにwidth・height属性を追加するだけです。

最後に拡張機能の設定で画像のリンクを有効にしていた場合に、リンク先をオリジナル画像(縮小する前の画像)のURLに変更します。

      ch(imgs[i]).replaceWith(`<a href="${linkUri}">${newImgTag}</a>`);

実行

ここまで紹介した修正を行った上で、拡張機能の実行(デバッグ)を行うと、「設定」→「拡張機能」→「WordPress Post Configuration」に「Image」で始まる設定項目が複数現われます。

追加された設定項目

これらの設定項目によって今回の修正の動作を制御することができます。

私は画像のリサイズを行うようにして、最大幅を640としています。また、縮小においては画像データを小さくするための設定すべてオンにしています。

そして

![ローカル画像のテスト](img/2022-05-30-23-24-42.png)

![URL画像のテスト](https://scratchpad.jp/wp-content/uploads/2022/06/locker-gf5529f0bc_1280.jpg)

を言う記事を作成して投稿すると

<p>
  <a href="/wp-content/uploads/2022/07/markdown-test-2022-05-30-23-24-42.png">
    <img src="/wp-content/uploads/2022/07/markdown-test-2022-05-30-23-24-42-640x479-4.png" alt="ローカル画像のテスト" title="ローカル画像のテスト" width="640" height="479"/>
  </a>
</p>
<p>
  <a href="https://scratchpad.jp/wp-content/uploads/2022/06/locker-gf5529f0bc_1280.jpg">
    <img src="https://scratchpad.jp/wp-content/uploads/2022/06/locker-gf5529f0bc_1280.jpg" alt="URL画像のテスト" title="URL画像のテスト" width="640" height="360"/>
  </a>
</p>

というHTMLが生成されます。

見やすいようにHTMLには適宜空行を入れています。

「ローカル画像のテスト」の画像の方は幅が640よりも大きかったため、640×479のサムネイル画像が生成され、その画像がimgタグで埋め込まれています。そしてimgタグのリンク先はオリジナル(縮小前)の画像データになっています。また、imgタグにはwidth・height・titleの属性が指定されています。

「URL画像のテスト」の画像も幅は640よりも大きいのですが、こちらはWeb上のデータであることからサムネイル画像は生成せず、imgタグにwidth・height属性を追加することで幅640に収まるようになっています。、またimgタグのリンク先はオリジナルの画像データとなっています。

これでWordPress Post拡張機能の画像関係の不満は解消されそうです。

プルリクエスト

GitHubでフォークしたプロジェクトは、オリジナルのプロジェクトに対して修正内容をプルリクエストという形で通知することができます。

オリジナルのプロジェクトの管理者が、プルリクエストの内容を確認して、同意してくれるとその修正がオリジナルのプロジェクトに取り込まれます。

今回の修正についてはまだプルリクエストを作成しておりません。
変更点が多いのと、前回の修正のプルリクエストが受け入れられるかどうか不明のためです。

前回の修正のプルリクエストが受け入れられた場合は、今回の修正のプルリクエスト化をしたいと思います。

まとめ

今回もVS Code用の拡張機能であるWordPress Post拡張機能の修正したことを紹介しました。

元々のWordPress Post拡張機能は画像の埋め込みに対応しているものの、サイズの指定や縮小には対応していません。

今回の修正では画像の最大サイズを指定できるようにしたことで、必要に応じて画像を縮小させることができるようになりました。

次回はその他の修正を紹介します。

コメント

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