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.tsutils/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-i18nindex.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/i18nwp.i18nにあることがわかります。 そのため、スクリプトをキューに入れるときに、その依存関係もキューに入れる必要があります。

2 つの個別のスクリプトを生成するためのカスタム構成

これらすべてを念頭に置いて、 utilsパッケージで同じことを達成したいとしましょう。 つまり、コンテンツをindex.jsに埋め込むのではなく、独自の.jsファイルにコンパイルして、 index.asset.phpへの依存関係として表示する必要があります。 どうやってそれを行うのですか?

まず、実際のパッケージに見えるように、 index.jsimportステートメントの名前を変更する必要があります。 つまり、相対パス ( ./utils ) を使用してスクリプトをインポートする代わりに、 @nelio/utilsのような名前を使用できると便利です。 これを行うには、プロジェクトのpackage.jsonファイルを編集して、 dependenciesに新しい依存関係を追加するだけです。

 { ... "dependencies": { "@nelio/utils": "./src/utils" }, "devDependencies": { "@wordpress/i18n": ..., "@wordpress/scripts": ... }, ... }

npm installを実行してnode_modulesにこの「新しい」パッケージを指すシンボリックリンクを作成し、最後にsrc/utilsnpm 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 つあります。 ブラウザーに移動し、ダッシュボード ページを更新して、コンソールを確認します。 何も表示されませんよね? なんで? さて、 nelionelio-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/utilsexport.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による注目の画像。