DevTips – 如何有效地編譯你的資產
已發表: 2022-08-25我們今年的目標之一是將我們的兩個旗艦插件(Nelio Content 和 Nelio A/B 測試)重構為 TypeScript 和 React Hooks。 好吧,我們剛剛完成了半年,我們已經可以說這個目標已經完全成功了但是,我必須承認:這條路比預期的要復雜一些……尤其是如果我們考慮到,在引入 TypeScript 之後,我們的插件構建時間從幾秒鐘到超過兩分鐘! 出了點問題,我們不知道是什麼。
好吧,在今天的帖子中,我想告訴你一些關於那次經歷以及我們為解決它所做的工作。 畢竟,我們都知道 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 代碼! 我們要做的第一件事是在我們的插件文件夾中初始化 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 測試和 Nelio Content 中的內容,因此在我們的項目中創建一個src
文件夾,其中包含幾個 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
為什麼是兩個文件? 好吧,一個是編譯後的 JavaScript 文件(duh),另一個是一個依賴文件,其中包含一些關於我們腳本的有用信息。 特別是,它告訴我們它依賴於 WordPress 中包含的那些 JavaScript 庫。 例如,我們的index.ts
依賴於@wordpress/i18n
包來國際化字符串,這是一個包含在 WordPress 中的庫,所以……是的, wp-i18n
將出現在index.asset.php
中:
build/index.asset.php <?php return array( 'dependencies' => array( 'wp-i18n' ), 'version' => 'c6131c7f24df4fa803b7', );
不幸的是,如果你問我,默認配置並不完美。 這就是為什麼。
如果我們在您的代碼中引入錯誤(例如,讓我們使用string
arg 而不是number
調用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 配置,然後通過將ts-loader
添加到所有.ts
文件來擴展defaultConfig
。 十分簡單!
現在,看:
> 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
中。 這就是為什麼在將我們的腳本排入隊列時,我們還需要將其依賴項排入隊列。
自定義配置以生成兩個單獨的腳本
考慮到這一切,假設我們想用我們的utils
包實現同樣的目標。 也就是說,我們不希望它的內容嵌入到index.js
中,而是應該將其編譯到自己的.js
文件中,並作為對index.asset.php
的依賴項出現。 我們如何做到這一點?
首先,我們應該重命名index.js
中的import
語句,使它看起來像一個真正的包。 換句話說,與其使用相對路徑( ./utils
)導入腳本,不如使用像@nelio/utils
這樣的名稱。 為此,您所要做的就是編輯項目的package.json
文件,在 dependencies 中添加一個新的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
配置並定義兩個導出:
- 我們已經擁有的(
./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
文件夾——你會看到我們現在都有兩個腳本。 看看./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"]; }) ...
不過,我們還需要解決最後一個問題。 轉到您的瀏覽器,刷新儀表板頁面,然後查看控制台。 什麼都沒有出現,對吧? 為什麼? 好吧, 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 測試這樣的真實項目需要幾分鐘來編譯。
為什麼它“這麼慢”,我們可以做些什麼來加快速度? 據我所知,問題在於我們的 webpack 配置。 您在 wepack 的module.exports
中的導出越多,編譯時間就越慢。 但是,單個導出方式更快。
讓我們稍微重構一下我們的項目以使用單個導出。 首先,在src/utils
中創建一個export.ts
文件,內容如下:
export * as utils from './index';
然後,編輯您的webpack.config.js
,使其具有包含兩個條目的單個導出:
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 配置,編譯您的項目可能會更快(或更慢)。 甜蜜點需要一個單一的出口……這就是我們今天討論的內容。
我希望你喜歡這篇文章。 如果有,請分享。 如果您知道其他優化 webpack 的方法,請在下面的評論部分告訴我。 祝你有美好的一天!
Saffu 在 Unsplash 上的精選圖片。