DevTips – アセットを効率的にコンパイルする方法
公開: 2022-08-25今年の目標の 1 つは、2 つのフラグシップ プラグイン (Nelio Content と Nelio A/B Testing) を TypeScript と React Hooks にリファクタリングすることでした。 まだ半年しか経っていませんが、この目標は完全に成功したと言えます。プラグインのビルド時間が数秒から 2 分以上になりました! 何かが間違っていて、何が原因かわかりませんでした。
さて、今日の投稿では、その経験と、それを修正するために私たちが行ったことについて少しお話ししたいと思います. 結局のところ、TypeScript が常にビルド プロセスを少し遅くすることは誰もが知っています (型チェックには代償が伴います)。 ネタバレ注意: 問題は TypeScript ではなく、私の設定でした。 TypeScript はそれを「明白」にしただけです。 それでは始めましょうか。
おもちゃプロジェクト
数週間前に発生した問題とその修正方法を理解していただくために、私たちができる最善の方法は、非常に簡単な例を作成して、あなたがたどることができるようにすることです. TypeScript を使用する単純な WordPress プラグインを作成し、設定ミスによってコンパイル時間が非常に遅くなる方法を見てみましょう。 始めるにあたって助けが必要な場合は、WordPress 開発環境に関するこの記事をチェックしてください。
プラグインの作成
最初に行うべきことは、WordPress の/wp-content/plugins
ディレクトリに、プラグインの名前 ( nelio
) で新しいフォルダーを作成することです。 次に、次の内容のメイン ファイル ( nelio.php
) を追加します。
<?php /** * Plugin Name: Nelio * Description: This is a test plugin. * Version: 0.0.1 * * Author: Nelio Software * Author URI: https://neliosoftware.com * License: GPL-2.0+ * License URI: http://www.gnu.org/licenses/gpl-2.0.txt * * Text Domain: nelio */ if ( ! defined( 'ABSPATH' ) ) { exit; }
正しく行った場合は、WordPress でプラグインを有効化できることがわかります。

確かに、プラグインはまだ何もしません…しかし、少なくとも表示されます
TypeScript
TypeScript コードを追加してみましょう。 最初に行うことは、プラグイン フォルダーで npm を初期化することです。 これを実行します:
npm init
画面の指示に従います。 次に、依存関係をインストールします。
npm add -D @wordpress/scripts @wordpress/i18n
package.json
ファイルを編集して、 @wordpress/scripts
に必要なビルド スクリプトを追加します。
{ ... "scripts": { "build": "wp-scripts build", "start": "wp-scripts start", }, ... }
npm の準備ができたら、 tsconfig.json
ファイルを追加して TypeScript をカスタマイズしましょう。
{ "compilerOptions": { "target": "es5", "module": "esnext", "moduleResolution": "node", "outDir": "build", "lib": [ "es7", "dom" ] }, "exclude": [ "node_modules" ] }
最後に、TS コードを書きましょう。 これは非常にシンプルでありながら、Nelio A/B Testing と Nelio Content で行ったものに「十分近い」ものにしたいので、プロジェクトにsrc
フォルダーを作成し、その中に 2 つの TypeScript ファイル ( index.ts
とutils/index.ts
。
一方では、 utils/index.ts
がユーティリティ パッケージであると仮定しましょう。 つまり、プロジェクト内の他のファイルが必要とする可能性のあるいくつかの関数が含まれています。 たとえば、従来のmin
およびmax
関数を提供するとします。
export const min = ( a: number, b: number ): number => a < b ? a : b; export const max = ( a: number, b: number ): number => a > b ? a : b;
一方、アプリのメイン ファイルであるindex.ts
を見てみましょう。 テスト目的で必要なのは、ユーティリティ パッケージと WordPress の依存関係を使用する単純なスクリプトだけです。 このようなもの:
import { __, sprintf } from '@wordpress/i18n'; import { min } from './utils'; const a = 2; const b = 3; const m = min( a, b ); console.log( sprintf( /* translators: 1 -> num, 2 -> num, 3 -> num */ __( 'Min between %1$s and %2$s is %3$s', 'nelio' ), a, b, m ) );
@wordpress/scripts デフォルト設定
npm run build
を使用して今すぐプロジェクトをビルドすると、すべてがすぐに機能します。 これは単純に、 @wordpress/scripts
(つまり、プロジェクトの構築に使用している基本ツール) が、この例のようなコードベースで動作するように設計されているためです。 つまり、 src
フォルダーにindex.ts
ファイルがある場合、 build
フォルダーにindex.js
ファイルとindex.asset.php
依存関係ファイルが生成されます。
> ls build index.asset.php index.js
なぜ2つのファイル? 1 つはコンパイル済みの JavaScript ファイル (当たり前) で、もう 1 つはスクリプトに関する有用な情報を含む依存関係ファイルです。 具体的には、WordPress に含まれている JavaScript ライブラリのうち、依存している JavaScript ライブラリを教えてくれます。 たとえば、私たちのindex.ts
は文字列を国際化するために@wordpress/i18n
パッケージに依存しています。これは WordPress に含まれているライブラリなので…そう、 wp-i18n
はindex.asset.php
に表示されます:
build/index.asset.php <?php return array( 'dependencies' => array( 'wp-i18n' ), 'version' => 'c6131c7f24df4fa803b7', );
残念ながら、デフォルトの構成は完璧ではありません。 理由は次のとおりです。
コードにバグを導入した場合 (たとえば、 number
の代わりにstring
arg を使用してmin
関数を呼び出しましょう):
const m = min( `${ a }`, b );
これにより、エラーが発生するはずです。 しかし、そうではありません。 問題なくコンパイルされます。
TypeScript でのコンパイル中の型チェック
前述の「制限」を解決するには、独自の webpack 構成ファイルを作成し、TS コードに遭遇するたびにtsc
(TypeScript コンパイラ) を使用するように指示するだけです。 つまり、次のwebpack.config.json
ファイルが必要です。
const defaultConfig = require( '@wordpress/scripts/config/webpack.config' ); const config = { ...defaultConfig, module: { ...defaultConfig.module, rules: [ ...defaultConfig.module.rules, { test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/, }, ], }, }; module.exports = { ...config, entry: './src/index', output: { path: __dirname + '/build', filename: 'index.js', }, };
ご覧のとおり、 @wordpress/scripts
パッケージに含まれるデフォルトの webpack 構成をロードすることから開始し、すべてのdefaultConfig
ファイルにts-loader
を追加して.ts
を拡張します。 簡単!
そして今、見よ:
> npm run build ... ERROR in ...src/index.ts TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. webpack 5.74.0 compiled with 1 error in 4470 ms
プロジェクトをコンパイルするとエラーが発生します。 万歳! 確かに少し遅くなりますが、少なくとも本番環境にアップロードする前にいくつかのセキュリティ チェックが行われます。
スクリプトのエンキュー
コードに問題があることがわかったので、修正してプラグインを再度コンパイルします。 それはすべてうまくいきましたか? 涼しい! これは、スクリプトとその依存関係を PHP にエンキューして、ブラウザーで試してみるためです。
nelio.php
を開き、次のスニペットを追加します。
add_action( 'admin_enqueue_scripts', function() { $path = untrailingslashit( plugin_dir_path( __FILE__ ) ); $url = untrailingslashit( plugin_dir_url( __FILE__ ) ); $asset = require( $path . '/build/index.asset.php' ); wp_enqueue_script( 'nelio', $url . '/build/index.js', $asset['dependencies'], $asset['version'] ); } );
次に、WordPress ダッシュボード (どのページでも問題ありません) に移動し、ブラウザーの JavaScript コンソールを確認します。 次のテキストが表示されます。
Min between 2 and 3 is 2
良い!
私の依存関係はどうですか?
JavaScript/webpack/WordPress での依存関係の管理について少し話しましょう。 @wordpress/scripts
は、デフォルトで、プロジェクトが WordPress コアにパッケージ化された依存関係を使用する場合、 .asset.php
ファイルにそのようにリストされるように設定されています。 たとえば、これは、スクリプトの依存関係ファイルに@wordpress/i18n
がリストされている理由を説明しています。
しかし、「他の」パッケージへの依存関係はどうでしょうか? utils
パッケージはどうなりましたか? 簡単に言うと、デフォルトでは、webpack はすべての依存関係をコンパイルして出力スクリプトにマージします。生成された JS ファイルを見てください (縮小を無効にするためにnpm run start
でコンパイルします)。
... var __webpack_modules__ = ({ "./src/utils/index.ts": ((...) => ... var min = function (a, b) { return a < b ? a : b; }; var max = function (a, b) { return a > b ? a : b; }; }), ...
見る? utils
コードはすぐそこにあり、出力スクリプトに埋め込まれています。

@wordpress/i18n
はどうですか? まあ、それはグローバル変数への単純な参照です:
... var __webpack_modules__ = ({ "./src/utils/index.ts": ..., "@wordpress/i18n": ((module)) => { module.exports = window["wp"]["i18n"]; }) ...
先ほど言ったように、 @wordpress/scripts
にはプラグインDependency Extraction Webpack Pluginが付属しています。これは、特定の依存関係をコンパイル プロセスから「除外」し、それらがグローバル スコープで利用可能であると想定してコードを生成します。 たとえば、この例では、 @wordpress/i18n
がwp.i18n
にあることがわかります。 そのため、スクリプトをキューに入れるときに、その依存関係もキューに入れる必要があります。
2 つの個別のスクリプトを生成するためのカスタム構成
これらすべてを念頭に置いて、 utils
パッケージで同じことを達成したいとしましょう。 つまり、コンテンツをindex.js
に埋め込むのではなく、独自の.js
ファイルにコンパイルして、 index.asset.php
への依存関係として表示する必要があります。 どうやってそれを行うのですか?
まず、実際のパッケージに見えるように、 index.js
のimport
ステートメントの名前を変更する必要があります。 つまり、相対パス ( ./utils
) を使用してスクリプトをインポートする代わりに、 @nelio/utils
のような名前を使用できると便利です。 これを行うには、プロジェクトのpackage.json
ファイルを編集して、 dependencies
に新しい依存関係を追加するだけです。
{ ... "dependencies": { "@nelio/utils": "./src/utils" }, "devDependencies": { "@wordpress/i18n": ..., "@wordpress/scripts": ... }, ... }
npm install
を実行してnode_modules
にこの「新しい」パッケージを指すシンボリックリンクを作成し、最後にsrc/utils
でnpm init
を実行して、npm の観点から@nelio/utils
が有効なパッケージになるようにします。
次に、 @nelio/utils
を独自のスクリプトにコンパイルするには、 webpack.config.js
構成を編集し、2 つのエクスポートを定義する必要があります。
- すでに持っていたもの (
./src/index.ts
) -
./src/utils
を別のファイルにコンパイルし、そのエクスポートをnelio.utils
などの名前のグローバル変数に公開する別のエクスポート。
つまり、これが必要です。
module.exports = [ { ...config, entry: './src/index', output: { path: __dirname + '/build', filename: 'index.js', }, }, { ...config, entry: './src/utils', output: { path: __dirname + '/build', filename: 'utils.js', library: [ 'nelio', 'utils' ], libraryTarget: 'window', }, }, ];
コードを再度コンパイルし、. ./build
フォルダーを確認します。これで、2 つのスクリプトが作成されていることがわかります。 ./build/utils.js
を見ると、予想どおりnelio.utils
がどのように定義されているかがわかります。
... var min = function (a, b) { return a < b ? a : b; }; var max = function (a, b) { return a > b ? a : b; }; (window.nelio = window.nelio || {}).utils = __webpack_exports__; ...
... var min = function (a, b) { return a < b ? a : b; }; var max = function (a, b) { return a > b ? a : b; }; (window.nelio = window.nelio || {}).utils = __webpack_exports__; ...
残念ながら、まだ道半ばです。 ./build/index.js
も見てみると、 src/utils
がまだ埋め込まれていることがわかります…これは「外部依存関係」であり、定義したばかりのグローバル変数を使用するべきではありませんか?
外部依存関係を作成するためのカスタム構成
@nelio/utils
を実際の外部依存関係に変換するには、webpack をさらにカスタマイズし、前述の依存関係抽出プラグインを利用する必要があります。 webpack.config.js
ファイルを再度開き、 config
変数を次のように変更します。
const DependencyExtractionWebpackPlugin = require( '@wordpress/dependency-extraction-webpack-plugin' ); const config = { ...defaultConfig, module: { ...defaultConfig.module, rules: [ ...defaultConfig.module.rules, { test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/, }, ], }, plugins: [ ...defaultConfig.plugins.filter( ( p ) => p.constructor.name !== 'DependencyExtractionWebpackPlugin' ), new DependencyExtractionWebpackPlugin( { requestToExternal: ( request ) => '@nelio/utils' === request ? [ 'nelio', 'utils' ] : undefined, requestToHandle: ( request ) => '@nelio/utils' === request ? 'nelio-utils' : undefined, outputFormat: 'php', } ), ], };
@nelio/utils
へのすべての参照がコード内でnelio.utils
として変換され、スクリプト ハンドラnelio-utils
への依存関係があるようにします。 両方のスクリプトの依存関係を見ると、次のことがわかります。
build/index.asset.php <?php return array( 'dependencies' => array('nelio-utils', 'wp-i18n')... ?> build/utils.asset.php <?php return array( 'dependencies' => array()... ?>
./build/index.js
を見ると、実際に@nelio/utils
の依存関係が外部になっていることが確認できます。
... var __webpack_modules__ = ({ "@nelio/utils": ((module)) => { module.exports = window["nelio"]["utils"]; }), "@wordpress/i18n": ((module)) => { module.exports = window["wp"]["i18n"]; }) ...
ただし、対処する必要がある最後の問題が 1 つあります。 ブラウザーに移動し、ダッシュボード ページを更新して、コンソールを確認します。 何も表示されませんよね? なんで? さて、 nelio
はnelio-utils
に依存するようになりましたが、このスクリプトは WordPress に登録されていないため、現時点では依存関係を満たすことができません。 これを修正するには、 nelio.php
を編集して新しいスクリプトを登録します。
add_action( 'admin_enqueue_scripts', function() { $path = untrailingslashit( plugin_dir_path( __FILE__ ) ); $url = untrailingslashit( plugin_dir_url( __FILE__ ) ); $asset = require( $path . '/build/utils.asset.php' ); wp_register_script( 'nelio-utils', $url . '/build/utils.js', $asset['dependencies'], $asset['version'] ); } );
ビルド プロセスを高速化する方法
ビルド プロセスを数回実行し、完了するまでにかかる時間を平均すると、ビルドが約 10 秒で実行されることがわかります。
> yarn run build ... ./src/index.ts + 2 modules ... webpack 5.74.0 compiled successfully in 5703 ms ... ./src/utils/index.ts ... webpack 5.74.0 compiled successfully in 5726 m Done in 10.22s.
大したことではないように思えるかもしれませんが、これは単純なおもちゃのプロジェクトであり、前述したように、Nelio Content や Nelio A/B Testing などの実際のプロジェクトでは、コンパイルに数分かかります。
なぜ「とても遅い」のですか?また、それを速くするにはどうすればよいでしょうか? 私が知る限り、問題は webpack の構成にありました。 wepack のmodule.exports
にあるエクスポートが多いほど、コンパイル時間が遅くなります。 ただし、単一のエクスポートははるかに高速です。
プロジェクトを少しリファクタリングして、単一のエクスポートを使用してみましょう。 まず、次の内容でsrc/utils
にexport.ts
ファイルを作成します。
export * as utils from './index';
次に、 webpack.config.js
を編集して、2 つのエントリを持つ単一のエクスポートを作成します。
module.exports = { ...config, entry: { index: './src/index', utils: './src/utils/export', }, output: { path: __dirname + '/dist', filename: 'js/[name].js', library: { name: 'nelio', type: 'assign-properties', }, }, };
最後に、プロジェクトを再度ビルドします。
> yarn run build ... built modules 522 bytes [built] ./src/index.ts + 2 modules ... ./src/utils/export.ts + 1 modules ... webpack 5.74.0 compiled successfully in 4339 ms Done in 6.02s.
わずか6秒で、以前のほぼ半分の時間です! かなりきれいですね。
概要
TypeScript を使用すると、コンパイル時に型が正しく、矛盾がないことを確認できるため、コードの品質を向上させることができます。 しかし、人生のすべてと同様に、TypeScript を使用する利点には代償が伴います。コードのコンパイルが少し遅くなります。
今日の投稿では、webpack の構成によっては、プロジェクトのコンパイルがはるかに高速 (または低速) になることがわかりました。 スイート スポットには 1 つのエクスポートが必要です。それが今日お話ししたことです。
投稿が気に入っていただければ幸いです。 もしそうなら、それを共有してください。 webpack を最適化する他の方法を知っている場合は、下のコメント セクションで教えてください。 良い一日を過ごしてください!
UnsplashのSaffuによる注目の画像。