Mobile Factory Tech Blog

技術好きな方へ!モバイルファクトリーのエンジニアたちが楽しい技術話をお届けします!

CommonJS と ESModules が混在している環境で、lodash を lodash-es に置き換え、バンドルサイズを減らす

こんにちは、21卒エンジニアの id:d-kimuson です。

先日、プロダクトで使用している lodash を lodash-es に置き換えることで、バンドルサイズの削減をしました。 lodash を lodash-es に置き換える話はよくありますが、今回のプロダクトは運用歴が長く CommonJS と ESModules が混在している少し特殊な環境での試みだったので、知見を共有したいと思います。

利用されていないコードを消し、バンドルサイズを減らす

lodash はバンドルサイズの大きなライブラリです。minified な状態で 69.9KB のサイズになります。

参考: lodash v4.17.21 ❘ Bundlephobia

webpack にはデッドコードを削除する TreeShaking という機能があり、ライブラリのデッドコードを削除することができるのでバンドルサイズの削減に効果的です。 しかし、lodash は CommonJS で記述されていて、TreeShaking の効果を得にくいので ESModules で書かれている lodash-es を使用することで、効果的にバンドルサイズの削減をすることができます。

長いので以降 CommonJS は CJS, ESModules は ESM と記します。

lodash から lodash-esに TreeShaking が効くように置き換える

このプロダクトでは、CJS で記述されたモジュールと ESM で記述されているモジュールが混在しているので、個別に置き換えていきます。

ESModules で記述されているモジュールを置き換える

ESM の置き換えはシンプルです。

  • lodash-es を使うようにする
  • 全体ではなく、使う関数のみ個別でインポートする

形に変更します。

-import _ from 'lodash';
+import { defaults } from 'lodash-es';

// ...
-const obj = _.defaults(obj1, obj2);
+const obj = defaults(obj1, obj2);

あとは webpack が自動的にデッドコード(defaults 以外) を削除してくれます。

CommonJSで記述されているモジュールは、CommonJS のまま TreeShaking が効くように置き換える

TreeShaking が効く条件は

  • webpack が TreeShaking に対応していること
    • webpack4: ESM のみ
    • webpack5: ESM, CJS 両対応 (効果は ESM のほうが大きい)
  • 使用する値のみ import していること

です。 このプロダクトでは CJS のコードも多く残っていますが、webpack5 を利用しているので「CJS のままでも TreeShaking の効果を得られるが、ESM にしてから置き換えるとより効果が大きい」という状態でした。

ですので

  1. CommonJS のまま置き換える (楽だけど効果は小さい)
  2. ESM に置き換えつつ、lodash-es に置き換える (大変だけど効果が大きい)

の選択肢があります。

詳しくは触れませんが、webpack 環境で ESM に置き換えるときには、default export の場合、同時にそのモジュールを参照している他のモジュールも ESM に置き換える、あるいは require 文を書き換えなくてはいけないという制約があり、2の内容で修正すると変更範囲が大きくなってしまうという懸念点がありました。

したがって、CJS で記述されている場合には

  • 置き換えファイルを参照しているファイルに CJS で記述されているものが存在するか
  • default export に置き換える必要があるか

を確認して、変更範囲がそのファイルのみに閉じている一部のものは ESM に置き換えますが、それ以外の大部分のソースコードでは CJS のまま置き開ける方針を取ることにしました。

変更範囲がファイル内に閉じているものは以下のように ESModules にしつつ置き換えます。

// 参照元がCJSでかつdefault_export
-const _ = require('lodash');
+const { map } = require('lodash-es');

-const obj = _.defaults(obj1, obj2);
+const obj = defaults(obj1, obj2);

変更範囲がファイル外に及ぶものは、ESModules には置き換えず、CJS のまま TreeShaking だけ効くように個別の値をインポートします。(※後述しますが、この方法ではTreeShakingが効かないので、別の方法を取る必要があります)

// それ以外
-const _ = require('lodash');
+import { map } from 'lodash-es';

-const obj = _.defaults(obj1, obj2);
+const obj = defaults(obj1, obj2);

これで CJS でも置き換えが完了しました。

CJS から lodash-es を読むとむしろバンドルサイズが増える問題に遭遇

上記の指針で置き換えを完了したのですが、いざバンドルサイズを計測してみるとむしろバンドルサイズが増えていることがわかりました。

エントリ 元サイズ 置き換え後のサイズ
entry1 493.95KB 534KB
entry2 369.18KB 408.39KB
entry3 362.57KB 401.97KB

バンドルサイズが増えてしまっては置き換えた意味がないので、原因を調査しました

原因①: ESModules を CommonJS で利用するための変換コードが増えてしまった

バンドルサイズを個別に見てみると、lodash のサイズが増えてしまっていることが分かったので、詳細な原因を探すために、以下のパターンでそれぞれ lodash 関係のバンドルサイズを計測してみました。

  • lodash を CJS で全体/個別 に読む
  • lodash を ESM で全体/個別 に読む
  • lodash-es を ESM で全体/個別を読む
  • lodash-es を ESM で全体/個別に読む

以下が調査結果です。

ライブラリ 呼び出し 全体サイズ tree-shaking
lodash CJS 69.2 KiB X
lodash ESM 69.4 KiB X
lodash-es CJS 148 KiB O (24.2 KiB)
lodash-es ESM 81.1 KiB O (7.19 KiB)

この結果から「lodash-es を CJS で依存解決しても TreeShaking でデッドコードを削除することができるが、総バンドルサイズが増えてしまうので一定数読んだ時点で lodash 時よりバンドルサイズが増えてしまう」ということがわかりました。

ただ、lodash-es を CJS で依存解決すると総バンドルサイズが増える原因はよく分からなかったので、実際に ESM, CJS で記述されたモジュールをそれぞれ ESM, CJS で依存解決するときの webpack のバンドルがどういう形になるのかを確認してみました。

webpack では概ね以下のようなコードにバンドルされています。

※そのままでは読みにくいので、読みやすいようにかなり簡素化・改変しています。

;(() => {
  var moduleMap = {
    100: (initilizedModule) => {
      initilizedModule.exports = "export text"
    }
  },
  moduleCache = {}
  function require(id) {
    if (id in moduleCachle) return moduelCache[id];
    const mod = moduelCache[id] = { exports: {} }
    moduleMap[id](mod)
    return mod
  }
  (() => {
    // エントリーポイント
    const resolved = require(100)
  })()
})()

ESM だとエントリーポイント箇所に直接展開されますが、CJS や、CJS のモジュールを ESM から読んだり、ESM のモジュールを CJS から読むときには moduleMap にモジュール定義が書かれ、それをエントリーポイントから require を使って解決するようです。したがって、require や moduleMap 部分が ESM のみの場合と比べて余分なコードになります。

また、ESM で書かれたモジュールを CJS で読むときには、モジュール定義箇所で default export に改変するような処理が追加で必要になるため、以下のようにコード量が増えます。

 var moduleMap = {
-    100: (initilizedModule) => {
+    100: (initilizedModule, _exports, require) => {
+      "use strict"
-      initializedModule.exports = "export text"
+      require.r(_exports), require.merge(_exports, { default: () => t })
+      const t = "export text"
     },
   },

この辺りがサイズ増加の原因になって増えているようです。実際のバンドルでは変数名は1文字なので use strict も比較的コード量が増える部分です。特に lodash-es の場合は、関数ごとにファイルが分かれているので、関数ごとに上記の余分なコードが生成されるため影響が大きくなっていると思われます。

全体のバンドルサイズが増える問題については、CJS から依存解決する以上必要なコードが増えているだけなので、CJS を使うのであれば避けようがないサイズの増加であることが分かります。

原因②: そもそも TreeShaking が効いていなかった

また、原因①の調査をしていて分かったのですが、そもそも今回の書き換えのように

const { findIndex } = require('lodash-es')

の書き方だと、TreeShaking が働かないことも分かりました。 webpack では上記のコードを以下のように展開します。

;(() => {
  var moduleMap = {
    100: (initilizedModule) => {  // lodash-es
      // ここに全 lodash-es のバンドルが書き出される
    }
  },
  // ... 省略
  (() => {
    // エントリーポイント
    const { findIndex } = require(100)
  })()
})()

見てわかるように全てソースコードがバンドルに含まれてしまっています。

TreeShaking が効く書き方に直して、バンドルサイズを減らす

原因①については、将来的に ESM への置き換えが終われば解消しますが、CJS の状態では依存解決のための必要なコードが増えているだけなので許容するしかありません。

一方、原因②については以下のような書き方ならきちんと TreeShaking が働いて使われている箇所だけバンドルしてくれます。

// その1: require からメソッドチェーンで関数を呼ぶ
require('lodash-es').findIndex()

また、TreeShaking ではありませんが個別のモジュールを指定して依存解決することで使用する関数のみバンドルに含めることもできます。

デッドコードさえ削除できれば良いので、こちらの方法でも原因②は解消できます。

// その2: 使用する関数を指定して import する
const { default: findIndex } = require('lodash-es/findIndex');
findIndex()

その1の書き方では、使用箇所に require を書く必要があることに対して、ESModules は基本的にモジュールの先頭でまとめて依存解決を書くので、将来的に ESM を置き換えることを考えてより近いその2の書き方で置き換えることにしました。

バンドルサイズを削減することができた

CJS で個別に import するようにしたことでやや可読性が悪くなりましたが、これでプロジェクトの lodash を全て置き換えることができましたので、再びバンドルサイズを計測しました。

エントリ 元サイズ 置き換え後のサイズ 割合
entry1 493.95KB 459.74KB -6.9%
entry2 369.18KB 327.9KB -11.2%
entry3 362.57KB 325.01KB -10.4%

バンドルサイズを 6-11% 程度削減できていることが分かります。

今回のプロジェクトでは無事削減することができましたが、lodash-es の総バンドルサイズが増える原因①については解消していないので、lodash-es の関数の使用量が一定量を超えるとむしろバンドルサイズが増えてしまうと推測されます。実際にバンドルサイズがどうなったか計測することをおすすめします。

また、このプロジェクトではフロントエンドエコシステムのアップデートに取り組んでいて、その中で CommonJS で記述されたモジュールを ESM に置き換えようとしています。置き換えが完了すれば lodash-es の総バンドルサイズが増える問題も解消されるので、さらにバンドルサイズの削減が期待できます。

おまけ

基本的には、この記事で書いた形で置き換えていけば良かったのですが一部特殊対応が必要だったので紹介します。

wrapper 記法を置き換える

lodash には wrapper 記法という関数をメソッドチェーンで呼ぶことができる書き方があり、一部でこの記法が使われていました。

import _ from 'lodash'

const arr1 = _.map([1, 2, 3, 4, 5])                    // 基本的な書き方
const arr2 = _([1, 2, 3, 4, 5]).map((num) => num * 2)  // wrapper 記法

メソッドチェーンで書かれているので、一意な置き換えができませんでしたが数が多くなかったので手動で書き直すことにしました。

chain を利用しない

lodash に存在する chain 関数が lodash-es には存在しませんでした。

How to use chain with lodash-es while supports tree shaking? · Issue #3298 · lodash/lodash · GitHub

上記の Issue に書かれているように TreeShaking をサポートできないため追加してない様でした。

こちらも1箇所しか使われていなかったのと、無駄な処理が多く書かれていたことから、lodash を使わずに書き換える形になりました。

まとめ

今回は ESM と CJS が共存する環境下で lodash-es を置き換えることでバンドルサイズを削減する知見を共有しました。 バンドルサイズが大きいので lodash-es 使うようにしてサクッとサイズを減らしたいくらいの温度感だったのですが、実際やってみると CJS と ESM 周りで詰まることがあり、webpack で CJS と ESM 周りがどう依存解決するのか、webpack がどういう形でコードをバンドルするのか等学びが多かったです。

CJS 環境下では

  • webpack5 を利用している
  • lodash から使用している関数の量が一定以下

という条件付きではありますが、lodash-es に置き換え、TreeShaking が使えるようにすることでバンドルサイズを削減できることが分かりました。

Perlの最新動向 2021

こんにちは、エンジニアの id:mp0liiu です。

8月28日(土)の Learn Languages 2021 というイベントの Language Update というセッションで@charsbarさんと一緒に2018年以降のPerl5やPerlコミュニティの最新動向について話してきたので、そのとき話した内容に補足などしつつ記事にしていきたいと思います。

配信アーカイブはこちらから見れます。

時系列

  • 2019/5/22 Perl5.30 リリース
  • 2020/6/20 Perl5.32 リリース
  • 2020/6/24 Perl7の発表
  • 2021/5/21 Perl5.34 リリース

Perl5.30 の変更点

正規表現や文字周りの細かい改善などはありますが、正直めぼしい変更点が見られないです。

Perl5.32 の変更点

isa 演算子の実装

値があるクラスのインスタンスもしくはそのサブクラスのインスタンスかを調べる演算子です。

use feature qw( isa );

package A { use Moo }
my $obj = A->new;
  
print '$obj is instance of A.' if $obj isa A;

今まででも isa メソッドで判定できましたが、メソッドの場合クラス名からでも呼び出せてしまうので、インスタンスの場合のみ判定したい場合は長い条件や経験者でないとわからないような workaround を書く必要がありました。
これを使えばその必要がなくなります。

連鎖比較機能

例えば $x < $y && $y <= $z$x < $y <= $z といったふうに、同じ値を比較する条件式を簡潔に書けるようになりました。

間接オブジェクト記法を無効化できるように

間接オブジェクト記法は method $object @args というふうにメソッドを呼び出せる記法です。
Perl は割と英語として読み下せることを重視する文化があった*1ので、昔はこのような書き方をするのが主流でした。

次のようなコードを動かしてみると無効にできるのが確認できます。

no feature 'indirect';
new Foo; # Bareword found where operator expected, near "new Foo"

無効にしていない場合、次のようなわかりにくいエラーメッセージがでます。

new Foo; # Can't locate object method "new" via package "Foo" (perhaps you forgot to load "Foo"?)

間接オブジェクト記法は関数の括弧を省略し呼び出した場合の文法と似ていて、関数がインポートされているかどうかで間接オブジェクト記法呼び出しとして解釈される関数呼び出しとして解釈されるかが変わり、わかりにくいエラーメッセージがでる場合があります。
これがよく開発者を混乱させていたので無効にできるようにしたようです。

Perl7の発表

Perl5.32 のリリースの少し後、Conference in the Cloud にて当時のPerl5のリリースマネージャー(Pumpking)であるSawyer X氏によってPerl7が発表されました。*2

Perl7の内容は、以下のようなものでした。

  • 機能的には5.32と同じ
  • デフォルトで strict, warnings などのよく使われるプラグマや最近追加された新機能をONにし、Perl5.0以前からあるPerlの文法を複雑にしている構文を無効にする
  • 目的
    • 初心者がいろんなプラグマを覚えなくてもすぐにPerlコードを書き始められるようにすること
    • 20世紀に書かれたコードの互換性を残すことを大事にしすぎない
    • Perl6がRakuに改名され名前的にも完全に別の言語となったことで、ちゃんとPerl5の後継バージョンをだしたいというモチベーションもあった

最初に書かなければならないボイラープレートがなくなるとコードが書きやすくなりますし、初心者もとっつきやすくなるのでかなり良さそうに見えますね。
少なくとも発表後、日本のPerl界隈ではかなり好反応だった印象があります。

しかし、Perl7やPerl7の発表には様々な問題がありました。

過去のPerl5で書かれたコードを書き換えないと動かなくなる

やはり一番目立つ問題として過去のPerl5で書かれたコードを書き換えないと動かなくなることがあげられます。

Perlには後方互換性を重視する文化があり、これはPerlコアにまつわるさまざまな方針を文書化した perlpolicy にも記述されています。
発表された内容のとおりにPerl7が実装されてしまうと perlpolicy に反するわけです。

他にも後方互換性がなくなることで、次のような問題が発生することが考えられます。

  • /usr/bin/perl は常にPerl5互換だとおもっていたら、OSディストリビュータの都合で任意のタイミングでPerl7に切り替わり過去の資産が壊れてしまう
  • #!/usr/bin/perl でどのバージョンのPerlで実行しているのかが明確でなくなって、7で書いていたつもりのコードを5で実行してしまいONになっていたつもりの機能が実はOFFになってしまう
  • Linuxディストリビューションのパッケージ管理が大変になってしまう

コミュニティ内で十分に議論せずに突然発表された

コミュニティ内で十分に議論せずに Pumpking が突然発表したことも問題になりました。
従来ならこのようなPerlコアの方針の重要な変更を行う場合は Perl5 Porters(Perl5開発者向けのメーリングリストに参加しているPerlの開発に興味のある人たち)に発表してから公にするものですが、これによってコミュニティの一部からはPerl7が受け入れられにくい雰囲気になってしまいました。

より正確に言うと、コミュニティ内で十分に議論せずに突然発表されたというよりは、Pumpking は perlpolicy によって定義される Perl Porter の一人に過ぎず perlpolicy を超越した立場ではないはずなのに perlpolicy に明確に反する方針を打ち出したことが問題視されました。
Pumpking = 行政機関、Perl Porters = 立法機関 と考えてもらうとわかりやすいかと思います。
要は行政を担当する人が法律そのものを変えるような行動をしてしまったというわけですが、これによってコアチームやコミュニティ内で混乱が発生してしまい、Perl5の正式なガバナンス制定が必要だという流れになりました。

Perlコミュニティのガバナンス制定

そういった流れでガバナンス制定についていろいろ話し合いが行われ、2020年12月頃ガバナンスが制定されました。
この内容は perlgov に文書化されています。

ざっくり内容を紹介すると、コアチームとPSC(Perl Steering Council)と呼ばれる運営評議会について明確に定義されるようになりました。

コアチームはPerlの継続的な開発に携わるグループです。
基本的にはコアのコミッターで、運営評議会のメンバーを選出し、コアチームのメンバーを管理する権限を持ちます。

PSCはPerl5の新しいバージョンのリリーススケジュール、プロセスの管理など最終的な決定をする人たちのことで、コアチームから選ばれた3人のメンバーから構成されます。
従来の pumpking を置き換えるものです。
すでにPSCのメンバーはいろいろ入れ替わっていますが、現在のメンバーは Paul Evans 氏, Ricardo Signes 氏, Neil Bowers 氏の3人のようです。

Perl5と7の今後

今後の方針について混乱していたPerl5と7ですが、2021/4/11 にPSCのミーティング*3が行われ、そこで以下のような方針が決まりました。

  • 以前発表された Perl7 の内容は取り下げて、従来のバージョンによる機能ガードを続けていく(use feature version の形で新機能を有効に)
  • Perl7はそのうちリリースするが、少なくとも来年リリースされることはなくて、当分はこれまでと同じように毎年安定版のリリースを続けていく
  • 十分な機能が揃ったら Perl7 と呼ぶことになるかもしれない

Perl5.34 の変更点

コミュニティのゴタゴタがあったのであまり大きな変更点はないですが、大きな変更点としては次のようなものがあげられます。

try-catch 構文

次のように try-catch で例外処理を記述できるようになりました。

use feature qw( try );

try {
  die 'hogehoge';
}
catch ($e) {
  print $e;
}

これにより今まで eval-if で書いていた例外処理をわかりやすくかけるようになります。
また、Try::Tiny などと違って ; 忘れがなくなったり、例外処理のモジュールも乱立しがちだったのでどれを使えばいいのかあまり悩まなくて良くなります。
更に自分でコードを動かしてみた限りだと Try::Tiny などの外部モジュールとコンフリクトしないので、既存のプロジェクトにも取り入れやすそうです。

8進数数値リテラルの新構文

2進数が 0b1111、 16進数が 0xFF といったように記述できていた感じで、 8進数が 0o777 というふうに記述できるようになりました。
今までは8進数を記述する場合先頭に0をつけて 0777 というふうに書いていたので、以前より8進数の記述が見やすくなったかなと思います。

barewordファイルハンドルを無効化できるように

barewordファイルハンドルとは型グロブに格納されたファイルハンドルのことです。
昔はファイルハンドルは型グロブに格納するのが一般的でしたが、今は変数に格納するのが一般的なのでそれを強制するような機能ですね。
次のようなコードを動かしてみると無効にできるのが確認できます。

no feature 'bareword_filehandles';
open FH, '<', 'tmp.txt';
my @lines = <FH>;
close FH;

擬似的な多次元配列(multidimensional)を無効化できるように

擬似的な多次元配列というのは、ハッシュの要素を

$foo{$a, $b, $c}

というふうにアクセスすると

$foo{ join($;, $a, $b, $c) }

というふうに動作することで擬似的に多次元配列をエミュレートする機能のことです。

昔のPerlでは多次元配列を使えなかったために実装された機能で、今は使うことも目にすることもないと思いますが、ハッシュスライスの構文 @foo{$a, $b, $c} と似ていて混乱を招くので無効化できるようにしたのだと思います。

次のようなコードを動かしてみると無効にできるのが確認できます。

no feature 'multidimensional';
my %foo;
$foo{qw( a b )}; #Multidimensional hash lookup is disabled

Perl5.36以降はどうなるか

Perl5.35 での変更

開発版の Perl5.35 ですでに新機能の追加や言語機能の改善が行われています。
これらはこのまま特に問題がなければ Perl5.36 にも取り込まれると思います。

defer構文の追加

Goなど他の言語などでも見られる構文と同じような感じで、スコープを抜けたときに実行される処理を defer { ... } 内に記述することができるようになります。
この構文は perl5.35.4 で追加されました。*4

真偽値の扱いの改善

条件式などから返される値や、!!0!!1 などの真偽値を変数に代入しても真偽値としての性質を保つようになりました。
これによって他の言語との相互運用やJSONのエンコードなどデータのシリアライズなどが楽になります。
この改善は perl5.35.4 で行われました。*5

RFCとして提案されているもの

Perlの変更の提案はこちらのリポジトリを見ればわかるのですが、これらのうちからいくつか取り上げます。

forループの繰り返しごとに複数の要素を参照する構文の追加

現在のfor文だと繰り返しごとにリストの先頭から1要素ずつしか参照できませんが、for my ($foo, $bar, $baz) (@array) { ... というように複数の要素を順番に参照できるようになります。

この構文が実装されるとハッシュ内の全key, valueのペアを for my ($key, $value) (%hash) { ... } で参照できるようになります。*6

Everything Slices

ハッシュや配列に含まれる値やキー/値の完全なセットを含むスライスを取得する構文を追加しようという提案です。
具体例をあげると

 %hash{*};   # %hash{ keys %hash } と同じ
 %array[*];  # %array[ 0 .. $#array ] と同じ
 
 @hash{*};   # @hash{ keys %hash } と同じ
 @array[*];  # @array[ 0 .. $#array ] と同じ

といった感じになります。
{}, [] の中はコードも書けるようで、例えば %array[ grep {; $_ > 5 } * ] といったコードも書けます。

forループの繰り返しごとに複数の要素を参照する構文の追加と一緒に追加されると便利になりそうで、組み合わせて使うと for my ($i, $v) (%array[ *]) { ... } と書くと配列のindexと要素をイテレーションすることができるようになります。

その他

既にカンファレンスでPSCのメンバーが5.36以降どうしたいかなどといったことを話しているので、いくつか取り上げます。

FOSDOM2021 での Paul Evans氏 のトーク

2021/02/06~07 の FOSDOM2021 でPSCのメンバーである Paul Evans 氏が「Perl's Amazing Time Machine」*7というタイトルでトークしており、その中で2025年には次のような機能を実装したいという話をしていました。

  • match-case構文 の追加、現状の壊れた given-when を置き換える
  • 厳密比較演算子の追加、暗黙的な型変換をせずに値を比較する演算子
  • 静的/動的に型チェックする構文の追加
  • サブルーチンのマルチディスパッチを可能に、同名でシグネチャが違うサブルーチンを書けるようにする

Paul Evans 氏はXS(perl処理系の記述に使われているCのマクロ言語)をバリバリ書ける開発者です。
5.34 の try-catch 構文や 5.32 のisa演算子もこの方が実装しています。
Perl開発の意思決定者にバリバリコード書ける人が入るのは久しぶりのことで、これによりPerlコアの開発が活発化しそうだという期待があります。

Conference in the Cloud! A Perl and Raku Conf での Ricardo Signes 氏 のトーク

2021/06/10 の Conference in the Cloud! A Perl and Raku Conf で PSCのメンバーである Ricardo Signes 氏が「What's new in Perl? 」*8というタイトルでトークしており、その中で5.36では次のようなことをしたいと話していました。

  • use version でベストなコードが書けるようにしたい
    • warnings, utf8 の有効化
    • barewordファイルハンドル, 間接オブジェクト記法などの混乱を招きやすい機能の無効化
  • 現在20近くある実験的機能の整理、experimental を外せるものは外し、Perlに追加すべきでない機能は削除する

Perlコア以外の動向

Perlコア以外にも面白い動きがあるのでいくつかとりあげます。

Corinna

Ovid氏 によるオブジェクト指向構文をPerlコアに入れようという提案です。
github に提案や実験的実装がまとめられています。
Perlコアに現状あるオブジェクト指向プログラミング関連の機能は貧弱だったり、CPANにはそれを補うようなモジュールが乱立しているので、このような動きは学習コストが下がったりコードが書きやすくなって嬉しいです。

Perlコアにオブジェクト指向構文を入れようとする挑戦は過去にも何度かありました。
有名なものでは Stevan Little 氏が Moose をPerlコアに入れようと頑張ってたりしていましたが、Moose が大きすぎるなどでうまく行きませんでした。

仕様的には Moose や Raku の影響を大きく受けています。
Moose、Moo などのOOPフレームワークモジュールとは違って keyword plugin の仕組みを使い新しい構文を追加します。
なので ; 忘れがなくなる、記述量が減る、 $self を介さず変数から直接インスタンス変数にアクセスできてインスタンス変数の宣言がなければコンパイル時エラーになるのでtypoに気づきやすい、などといったメリットがあります。

Paul evans 氏が Object::Pad という実験的実装モジュールを作っており、既にCPANにあがっています。
彼の会社では使い始めたりもしているようです。

UV2.0 のリリース

libuv のPerlインターフェースを提供する UV が Perl foundation の助成を受けて開発が進み、バージョン2.0がリリースされました。

まとめ

ここ最近はPerlコミュニティでいろいろゴタゴタがありましたが、それもようやく落ち着いてきていて今後開発が活発になっていきそうだという感じです。
これからのPerlに期待ですね!

*1:「実用 Perl プログラミング」にはPerlで詩を書く話がでてきますが、そのことを考えると当時の雰囲気が少しわかると思います

*2:https://www.perl.com/article/announcing-perl-7/

*3:https://www.nntp.perl.org/group/perl.perl5.porters/2021/04/msg259789.html

*4:https://metacpan.org/release/WOLFSAGE/perl-5.35.4/view/pod/perldelta.pod#defer-blocks

*5:https://metacpan.org/release/WOLFSAGE/perl-5.35.4/view/pod/perldelta.pod#Stable-boolean-tracking

*6:現状でも while (my ($key, $value) = each %hash) { ... } でできますが、途中でループが止まった場合などでハマりどころがあり for my $key (keys %hash) { my $value = $hash{$key}; ... というふうに書かれることが多いです

*7:https://www.youtube.com/watch?v=Kc_bP73xNyM

*8:https://www.youtube.com/watch?v=FlGpiS39NMY